mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-05-29 16:14:54 -04:00
860 lines
28 KiB
C++
860 lines
28 KiB
C++
/**
|
|
* m_Do_main.cpp
|
|
* Main Initialization
|
|
* PC Port Version - based on Aurora integration from Vorversion
|
|
*/
|
|
|
|
#include "m_Do/m_Do_main.h"
|
|
#include <dolphin/vi.h>
|
|
#include <cstring>
|
|
#include "DynamicLink.h"
|
|
#include "JSystem/JAudio2/JASAudioThread.h"
|
|
#include "JSystem/JAudio2/JAUSectionHeap.h"
|
|
#include "JSystem/JAudio2/JAUSoundTable.h"
|
|
#include "JSystem/JFramework/JFWSystem.h"
|
|
#include "JSystem/JHostIO/JORServer.h"
|
|
#include "JSystem/JKernel/JKRAram.h"
|
|
#include "JSystem/JKernel/JKRSolidHeap.h"
|
|
#include "JSystem/JUtility/JUTConsole.h"
|
|
#include "JSystem/JUtility/JUTException.h"
|
|
#include "JSystem/JUtility/JUTProcBar.h"
|
|
#include "JSystem/JUtility/JUTReport.h"
|
|
#include "SSystem/SComponent/c_counter.h"
|
|
#include "SSystem/SComponent/c_API_graphic.h"
|
|
#include "Z2AudioLib/Z2WolfHowlMgr.h"
|
|
#include "c/c_dylink.h"
|
|
#include "d/d_com_inf_game.h"
|
|
#include "d/d_debug_pad.h"
|
|
#include "d/d_s_logo.h"
|
|
#include "d/d_s_menu.h"
|
|
#include "d/d_s_play.h"
|
|
#include "f_ap/f_ap_game.h"
|
|
#include "f_op/f_op_msg.h"
|
|
#include "m_Do/m_Do_MemCard.h"
|
|
#include "m_Do/m_Do_Reset.h"
|
|
#include "m_Do/m_Do_controller_pad.h"
|
|
#include "m_Do/m_Do_dvd_thread.h"
|
|
#include "m_Do/m_Do_ext2.h"
|
|
#include "m_Do/m_Do_graphic.h"
|
|
#include "m_Do/m_Do_machine.h"
|
|
#include "m_Do/m_Do_printf.h"
|
|
#include "m_Do/m_Do_ext2.h"
|
|
#include "SSystem/SComponent/c_counter.h"
|
|
#include <cstring>
|
|
|
|
#include <filesystem>
|
|
#include <system_error>
|
|
#include <thread>
|
|
#include "SSystem/SComponent/c_API.h"
|
|
#include "dusk/app_info.hpp"
|
|
#include "dusk/crash_reporting.h"
|
|
#include "dusk/data.hpp"
|
|
#include "dusk/dusk.h"
|
|
#include "dusk/frame_interpolation.h"
|
|
#include "dusk/game_clock.h"
|
|
#include "dusk/gyro.h"
|
|
#include "dusk/imgui/ImGuiConsole.hpp"
|
|
#include "dusk/imgui/ImGuiEngine.hpp"
|
|
#include "dusk/iso_validate.hpp"
|
|
#include "dusk/logging.h"
|
|
#include "dusk/main.h"
|
|
#include "dusk/ui/menu_bar.hpp"
|
|
#include "dusk/ui/overlay.hpp"
|
|
#include "dusk/ui/prelaunch.hpp"
|
|
#include "dusk/ui/preset.hpp"
|
|
#include "dusk/ui/ui.hpp"
|
|
#include "version.h"
|
|
|
|
#include <aurora/aurora.h>
|
|
#include <aurora/event.h>
|
|
#include <aurora/main.h>
|
|
#include <aurora/dvd.h>
|
|
#include <dolphin/dvd.h>
|
|
|
|
#include "SDL3/SDL_init.h"
|
|
#include "SDL3/SDL_filesystem.h"
|
|
#include "SDL3/SDL_iostream.h"
|
|
#include "SDL3/SDL_misc.h"
|
|
#include "cxxopts.hpp"
|
|
#include "d/actor/d_a_movie_player.h"
|
|
#include "dusk/audio/DuskAudioSystem.h"
|
|
#include "dusk/audio/DuskDsp.hpp"
|
|
#include "dusk/config.hpp"
|
|
#include "dusk/speedrun.h"
|
|
#include "dusk/settings.h"
|
|
#include "dusk/io.hpp"
|
|
#include "dusk/version.hpp"
|
|
#include "dusk/discord_presence.hpp"
|
|
#include "tracy/Tracy.hpp"
|
|
#include "f_pc/f_pc_draw.h"
|
|
#include "tracy/Tracy.hpp"
|
|
#include <RmlUi/Core.h>
|
|
#ifdef __APPLE__
|
|
#include <TargetConditionals.h>
|
|
#endif
|
|
|
|
#if DUSK_ENABLE_SENTRY_NATIVE
|
|
#include "dusk/ui/reporting.hpp"
|
|
#endif
|
|
|
|
#if RANDOMIZER_ONLY
|
|
#include "dusk/randomizer/generator/randomizer.hpp"
|
|
#include "dusk/randomizer/generator/test/test.hpp"
|
|
#endif
|
|
|
|
// --- GLOBALS ---
|
|
s8 mDoMain::developmentMode = -1;
|
|
OSTime mDoMain::sPowerOnTime;
|
|
OSTime mDoMain::sHungUpTime;
|
|
u32 mDoMain::memMargin = 0xFFFFFFFF;
|
|
char mDoMain::COPYDATE_STRING[18] = "??/??/?? ??:??:??";
|
|
#if TARGET_PC
|
|
const int audioHeapSize = 0x14D800 * 2;
|
|
#else
|
|
const int audioHeapSize = 0x14D800;
|
|
#endif
|
|
|
|
// =========================================================================
|
|
// LOAD_COPYDATE - PC Version
|
|
// =========================================================================
|
|
#define COPYDATE_PATH "/str/Final/Release/COPYDATE"
|
|
|
|
#if TARGET_PC
|
|
bool dusk::IsRunning = true;
|
|
bool dusk::IsShuttingDown = false;
|
|
bool dusk::IsGameLaunched = false;
|
|
bool dusk::RestartRequested = false;
|
|
std::filesystem::path dusk::ConfigPath;
|
|
std::filesystem::path dusk::CachePath;
|
|
#endif
|
|
|
|
void dusk::RequestRestart() noexcept {
|
|
RestartRequested = SupportsProcessRestart;
|
|
IsRunning = false;
|
|
}
|
|
|
|
s32 LOAD_COPYDATE(void*) {
|
|
char buffer[32];
|
|
memset(buffer, 0, sizeof(buffer));
|
|
|
|
DVDFileInfo fi;
|
|
if (DVDOpen(COPYDATE_PATH, &fi)) {
|
|
u32 readLen = (fi.length < sizeof(buffer) - 1) ? fi.length : sizeof(buffer) - 1;
|
|
// DVDReadPrio requires 32-byte aligned buffer and length rounded up to 32
|
|
u32 alignedLen = (readLen + 31) & ~31;
|
|
alignas(32) char readBuf[64];
|
|
DVDReadPrio(&fi, readBuf, alignedLen, 0, 2);
|
|
DVDClose(&fi);
|
|
|
|
memcpy(buffer, readBuf, readLen);
|
|
buffer[readLen] = '\0';
|
|
} else {
|
|
strcpy(buffer, "PC PORT BUILD");
|
|
DuskLog.warn("COPYDATE file not found at {}", COPYDATE_PATH);
|
|
}
|
|
|
|
memcpy(mDoMain::COPYDATE_STRING, buffer, sizeof(mDoMain::COPYDATE_STRING) - 1);
|
|
mDoMain::COPYDATE_STRING[sizeof(mDoMain::COPYDATE_STRING) - 1] = '\0';
|
|
|
|
DuskLog.info("COPYDATE=[{}]", mDoMain::COPYDATE_STRING);
|
|
return 1;
|
|
}
|
|
|
|
AuroraInfo auroraInfo;
|
|
AuroraStats dusk::lastFrameAuroraStats;
|
|
float dusk::frameUsagePct = 0.0f;
|
|
|
|
bool launchUILoop() {
|
|
while (dusk::IsRunning && !dusk::IsGameLaunched) {
|
|
const AuroraEvent* event = aurora_update();
|
|
while (event != nullptr && event->type != AURORA_NONE) {
|
|
switch (event->type) {
|
|
case AURORA_SDL_EVENT:
|
|
dusk::ui::handle_event(event->sdl);
|
|
dusk::g_imguiConsole.HandleSDLEvent(event->sdl);
|
|
break;
|
|
case AURORA_DISPLAY_SCALE_CHANGED:
|
|
dusk::ImGuiEngine_Initialize(event->windowSize.scale);
|
|
break;
|
|
case AURORA_EXIT:
|
|
return false;
|
|
}
|
|
|
|
event++;
|
|
}
|
|
|
|
if (!aurora_begin_frame()) {
|
|
DuskLog.debug("aurora_begin_frame returned false, skipping draw this frame");
|
|
continue;
|
|
}
|
|
|
|
dusk::ui::update();
|
|
|
|
dusk::g_imguiConsole.PreDraw();
|
|
dusk::g_imguiConsole.PostDraw();
|
|
|
|
aurora_end_frame();
|
|
}
|
|
|
|
return dusk::IsRunning;
|
|
}
|
|
|
|
void main01(void) {
|
|
OS_REPORT("\x1b[m");
|
|
|
|
// 1. Setup
|
|
mDoMch_Create();
|
|
mDoGph_Create();
|
|
mDoCPd_c::create();
|
|
|
|
// Console Setup
|
|
JUTConsole* console = JFWSystem::getSystemConsole();
|
|
if (console) {
|
|
console->setOutput(mDoMain::developmentMode ? JUTConsole::OUTPUT_OSR_AND_CONSOLE :
|
|
JUTConsole::OUTPUT_NONE);
|
|
console->setPosition(32, 42);
|
|
}
|
|
|
|
// Loader Init
|
|
mDoDvdThd_callback_c::create((mDoDvdThd_callback_func)LOAD_COPYDATE, NULL);
|
|
|
|
OSReport("Calling fapGm_Create()...\n");
|
|
fapGm_Create();
|
|
|
|
OSReport("Calling fopAcM_initManager()...\n");
|
|
fopAcM_initManager();
|
|
|
|
OSReport("Calling cDyl_InitAsync()...\n");
|
|
cDyl_InitAsync();
|
|
|
|
g_mDoAud_audioHeap = JKRCreateSolidHeap(audioHeapSize, JKRGetCurrentHeap(), false);
|
|
JKRHEAP_NAME(g_mDoAud_audioHeap, "g_mDoAud_audioHeap");
|
|
|
|
if (DUSK_AUDIO_DISABLED) {
|
|
// Pretend the audio engine initialized already. This is a lie, but needed to boot.
|
|
mDoAud_zelAudio_c::onInitFlag();
|
|
}
|
|
|
|
OSReport("Entering Main Loop (main01)...\n");
|
|
|
|
dusk::game_clock::ensure_initialized();
|
|
|
|
do {
|
|
// 1. Update Window Events
|
|
const AuroraEvent* event = aurora_update();
|
|
while (true) {
|
|
switch (event->type) {
|
|
case AURORA_NONE:
|
|
goto eventsDone;
|
|
case AURORA_PAUSED:
|
|
dusk::audio::SetPaused(true);
|
|
break;
|
|
case AURORA_UNPAUSED:
|
|
dusk::audio::SetPaused(false);
|
|
dusk::game_clock::reset_frame_timer();
|
|
break;
|
|
case AURORA_SDL_EVENT:
|
|
dusk::ui::handle_event(event->sdl);
|
|
dusk::g_imguiConsole.HandleSDLEvent(event->sdl);
|
|
break;
|
|
case AURORA_DISPLAY_SCALE_CHANGED:
|
|
dusk::ImGuiEngine_Initialize(event->windowSize.scale);
|
|
break;
|
|
case AURORA_EXIT:
|
|
goto exit;
|
|
}
|
|
|
|
event++;
|
|
}
|
|
|
|
eventsDone:;
|
|
|
|
if (!aurora_begin_frame()) {
|
|
DuskLog.debug("aurora_begin_frame returned false, skipping draw this frame");
|
|
continue;
|
|
}
|
|
|
|
VIWaitForRetrace();
|
|
|
|
dusk::lastFrameAuroraStats = *aurora_get_stats();
|
|
mDoGph_gInf_c::updateRenderSize();
|
|
|
|
dusk::ui::update();
|
|
|
|
const auto pacing = dusk::game_clock::advance_main_loop();
|
|
if (pacing.is_interpolating) {
|
|
if (pacing.sim_ticks_to_run > 0) {
|
|
dusk::frame_interp::begin_frame(true, true, 0.0f);
|
|
dusk::frame_interp::set_ui_tick_pending(true);
|
|
for (int sim_tick = 0; sim_tick < pacing.sim_ticks_to_run; ++sim_tick) {
|
|
dusk::frame_interp::begin_sim_tick();
|
|
mDoCPd_c::read();
|
|
dusk::gyro::read(pacing.sim_pace);
|
|
fapGm_Execute();
|
|
mDoAud_Execute();
|
|
dusk::game_clock::commit_sim_tick();
|
|
}
|
|
}
|
|
|
|
dusk::frame_interp::begin_frame(true, false,
|
|
dusk::game_clock::sample_interpolation_step());
|
|
dusk::frame_interp::interpolate();
|
|
dusk::frame_interp::begin_presentation_camera();
|
|
// run draw functions for anything specially marked to handle interp
|
|
fpcM_DrawIterater((fpcM_DrawIteraterFunc)fpcM_Draw);
|
|
cAPIGph_Painter();
|
|
dusk::frame_interp::end_presentation_camera();
|
|
dusk::frame_interp::set_ui_tick_pending(false);
|
|
} else {
|
|
dusk::frame_interp::begin_frame(false, true, 0.0f);
|
|
dusk::frame_interp::set_ui_tick_pending(true);
|
|
|
|
// Game Inputs
|
|
mDoCPd_c::read();
|
|
dusk::gyro::read(pacing.presentation_dt_seconds);
|
|
|
|
// EXECUTE GAME LOGIC & RENDER
|
|
// This calls mDoGph_Painter -> JFWDisplay -> GX Functions
|
|
fapGm_Execute();
|
|
|
|
mDoAud_Execute();
|
|
}
|
|
|
|
aurora_end_frame();
|
|
|
|
FrameMark;
|
|
|
|
#ifdef DUSK_DISCORD
|
|
dusk::discord::run_callbacks();
|
|
dusk::discord::update_presence();
|
|
#endif
|
|
} while (dusk::IsRunning);
|
|
|
|
exit:;
|
|
dusk::ui::shutdown();
|
|
}
|
|
|
|
static bool IsBackendAvailable(AuroraBackend backend) {
|
|
if (backend == BACKEND_AUTO) {
|
|
return true;
|
|
}
|
|
|
|
size_t availableBackendCount = 0;
|
|
const AuroraBackend* availableBackends = aurora_get_available_backends(&availableBackendCount);
|
|
for (size_t i = 0; i < availableBackendCount; ++i) {
|
|
if (availableBackends[i] == backend) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static AuroraBackend ResolveDesiredBackend(const cxxopts::ParseResult& parsedArgOptions) {
|
|
AuroraBackend desiredBackend = BACKEND_AUTO;
|
|
|
|
if (parsedArgOptions.count("backend") != 0) {
|
|
const std::string backendArg = parsedArgOptions["backend"].as<std::string>();
|
|
if (!dusk::try_parse_backend(backendArg, desiredBackend)) {
|
|
fmt::print(stderr, "Unknown backend: {}\n", backendArg);
|
|
exit(1);
|
|
}
|
|
} else if (!dusk::try_parse_backend(
|
|
static_cast<const std::string&>(dusk::getSettings().backend.graphicsBackend),
|
|
desiredBackend))
|
|
{
|
|
DuskLog.warn("Unknown configured backend '{}', falling back to Auto",
|
|
static_cast<const std::string&>(dusk::getSettings().backend.graphicsBackend));
|
|
desiredBackend = BACKEND_AUTO;
|
|
}
|
|
|
|
if (!IsBackendAvailable(desiredBackend)) {
|
|
DuskLog.warn("Requested backend '{}' is unavailable, falling back to Auto",
|
|
dusk::backend_name(desiredBackend));
|
|
desiredBackend = BACKEND_AUTO;
|
|
}
|
|
|
|
return desiredBackend;
|
|
}
|
|
|
|
static void aurora_imgui_init_callback(const AuroraWindowSize* size) {
|
|
dusk::ImGuiEngine_Initialize(size->scale);
|
|
dusk::ImGuiEngine_AddTextures();
|
|
}
|
|
|
|
static void ApplyCVarOverrides(const cxxopts::OptionValue& option) {
|
|
if (option.count() == 0) {
|
|
return;
|
|
}
|
|
|
|
const auto& cVars = option.as<std::vector<std::string>>();
|
|
for (const auto& cvarArg : cVars) {
|
|
const auto sep = cvarArg.find('=');
|
|
if (sep == std::string::npos) {
|
|
DuskLog.fatal("--cvar argument has no '=': '{}'", cvarArg);
|
|
continue;
|
|
}
|
|
|
|
const auto name = std::string_view(cvarArg).substr(0, sep);
|
|
const auto value = std::string_view(cvarArg).substr(sep + 1);
|
|
|
|
const auto cVar = dusk::config::GetConfigVar(name);
|
|
if (!cVar) {
|
|
DuskLog.fatal("Unknown --cvar name: '{}'", name);
|
|
}
|
|
|
|
try {
|
|
cVar->getImpl()->loadFromArg(*cVar, value);
|
|
} catch (const std::exception& e) {
|
|
DuskLog.fatal("Unable to parse: '{}': {}", value, e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
static constexpr PADDefaultMapping defaultPadMapping = {
|
|
.buttons = {
|
|
{SDL_GAMEPAD_BUTTON_SOUTH, PAD_BUTTON_A},
|
|
{SDL_GAMEPAD_BUTTON_EAST, PAD_BUTTON_B},
|
|
{SDL_GAMEPAD_BUTTON_WEST, PAD_BUTTON_X},
|
|
{SDL_GAMEPAD_BUTTON_NORTH, PAD_BUTTON_Y},
|
|
{SDL_GAMEPAD_BUTTON_START, PAD_BUTTON_START},
|
|
{SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, PAD_TRIGGER_Z},
|
|
{PAD_NATIVE_BUTTON_INVALID, PAD_TRIGGER_L},
|
|
{PAD_NATIVE_BUTTON_INVALID, PAD_TRIGGER_R},
|
|
{SDL_GAMEPAD_BUTTON_DPAD_UP, PAD_BUTTON_UP},
|
|
{SDL_GAMEPAD_BUTTON_DPAD_DOWN, PAD_BUTTON_DOWN},
|
|
{SDL_GAMEPAD_BUTTON_DPAD_LEFT, PAD_BUTTON_LEFT},
|
|
{SDL_GAMEPAD_BUTTON_DPAD_RIGHT, PAD_BUTTON_RIGHT},
|
|
},
|
|
.axes = {
|
|
{{SDL_GAMEPAD_AXIS_LEFTX, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_LEFT_X_POS},
|
|
{{SDL_GAMEPAD_AXIS_LEFTX, AXIS_SIGN_NEGATIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_LEFT_X_NEG},
|
|
// SDL's gamepad y-axis is inverted from GC's
|
|
{{SDL_GAMEPAD_AXIS_LEFTY, AXIS_SIGN_NEGATIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_LEFT_Y_POS},
|
|
{{SDL_GAMEPAD_AXIS_LEFTY, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_LEFT_Y_NEG},
|
|
{{SDL_GAMEPAD_AXIS_RIGHTX, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_RIGHT_X_POS},
|
|
{{SDL_GAMEPAD_AXIS_RIGHTX, AXIS_SIGN_NEGATIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_RIGHT_X_NEG},
|
|
// see above
|
|
{{SDL_GAMEPAD_AXIS_RIGHTY, AXIS_SIGN_NEGATIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_RIGHT_Y_POS},
|
|
{{SDL_GAMEPAD_AXIS_RIGHTY, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_RIGHT_Y_NEG},
|
|
{{SDL_GAMEPAD_AXIS_LEFT_TRIGGER, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_TRIGGER_L},
|
|
{{SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, AXIS_SIGN_POSITIVE}, SDL_GAMEPAD_BUTTON_INVALID, PAD_AXIS_TRIGGER_R},
|
|
},
|
|
};
|
|
|
|
static bool mainCalled = false;
|
|
|
|
static u8 selectedLanguage;
|
|
|
|
u8 OSGetLanguage() {
|
|
return selectedLanguage;
|
|
}
|
|
|
|
static void LanguageInit() {
|
|
// Keep language at 0 (English) if not on a PAL disc.
|
|
// Doubt this matters, but avoid funky shit.
|
|
if (!dusk::version::isRegionPal()) {
|
|
return;
|
|
}
|
|
|
|
// Cache this to avoid funky shenanigans.
|
|
selectedLanguage = static_cast<u8>(dusk::getSettings().game.language.getValue());
|
|
}
|
|
|
|
static std::string asset_path(const char* assetName) {
|
|
const char* basePath = SDL_GetBasePath();
|
|
if (basePath != nullptr && basePath[0] != '\0') {
|
|
return std::string(basePath) + "res/" + assetName;
|
|
}
|
|
return std::string("res/") + assetName;
|
|
}
|
|
|
|
static void log_build_info() {
|
|
DuskLog.info("Build: {} (rev {}, built {}, type {})", DUSK_WC_DESCRIBE, DUSK_WC_REVISION, DUSK_WC_DATE, DUSK_BUILD_TYPE);
|
|
DuskLog.info("Platform: {}", DUSK_PLATFORM_NAME);
|
|
}
|
|
|
|
// =========================================================================
|
|
// PC ENTRY POINT
|
|
// =========================================================================
|
|
int game_main(int argc, char* argv[]) {
|
|
|
|
#if RANDOMIZER_ONLY
|
|
#ifdef LOGIC_TESTS
|
|
randomizer::test::test::RunTests();
|
|
#else
|
|
randomizer::Randomizer rando{};
|
|
rando.Generate();
|
|
#endif
|
|
exit(0);
|
|
#endif
|
|
|
|
// On iOS, when connected to an external monitor, SDLUIKitSceneDelegate scene:willConnectToSession:
|
|
// can call our main function again. Explicitly guard against this reinitialization.
|
|
if (mainCalled) {
|
|
return 0;
|
|
}
|
|
mainCalled = true;
|
|
|
|
dusk::registerSettings();
|
|
dusk::config::FinishRegistration();
|
|
|
|
cxxopts::ParseResult parsed_arg_options;
|
|
|
|
try {
|
|
cxxopts::Options arg_options("Dusklight", "PC Port of a classic adventure game");
|
|
|
|
arg_options.add_options()
|
|
("l,log-level", "Log level from " + std::to_string(AuroraLogLevel::LOG_DEBUG) + " to " + std::to_string(AuroraLogLevel::LOG_FATAL), cxxopts::value<uint8_t>()->default_value("0"))
|
|
("h,help", "Print usage")
|
|
("console", "Show the Windows console window for logs", cxxopts::value<bool>()->default_value("false")->implicit_value("true"))
|
|
("dvd", "Path to DVD image file", cxxopts::value<std::string>())
|
|
("backend", "Graphics API backend to use (auto, d3d12, metal, vulkan, null)", cxxopts::value<std::string>())
|
|
("cvar", "Override configuration variables without modifying config", cxxopts::value<std::vector<std::string>>());
|
|
|
|
arg_options.parse_positional({"dvd"});
|
|
arg_options.positional_help("<dvd-image>");
|
|
arg_options.allow_unrecognised_options();
|
|
|
|
parsed_arg_options = arg_options.parse(argc, argv);
|
|
|
|
if (parsed_arg_options.count("help"))
|
|
{
|
|
printf("%s", (arg_options.help() + "\n").c_str());
|
|
exit(0);
|
|
}
|
|
}
|
|
catch (const cxxopts::exceptions::exception& e) {
|
|
fprintf(stderr, "Argument Error: %s\n", e.what());
|
|
exit(1);
|
|
}
|
|
|
|
const auto startupLogLevel =
|
|
static_cast<AuroraLogLevel>(parsed_arg_options["log-level"].as<uint8_t>());
|
|
const auto dataPaths = dusk::data::initialize_data();
|
|
dusk::ConfigPath = dataPaths.userPath;
|
|
dusk::CachePath = dataPaths.cachePath;
|
|
dusk::InitializeFileLogging(dusk::CachePath, startupLogLevel);
|
|
|
|
log_build_info();
|
|
|
|
dusk::config::LoadFromUserPreferences();
|
|
if (dusk::getSettings().game.speedrunMode) {
|
|
dusk::resetForSpeedrunMode();
|
|
}
|
|
ApplyCVarOverrides(parsed_arg_options["cvar"]);
|
|
dusk::crash_reporting::initialize();
|
|
// TODO: How to handle this?
|
|
// PADSetDefaultMapping(&defaultPadMapping, PAD_TYPE_STANDARD);
|
|
|
|
{
|
|
// Load mappings from https://github.com/mdqinc/SDL_GameControllerDB
|
|
const auto mappingsPath = asset_path("gamecontrollerdb.txt");
|
|
if (SDL_AddGamepadMappingsFromFile(mappingsPath.c_str()) < 0) {
|
|
DuskLog.warn("Failed to load gamecontrollerdb.txt: {}", SDL_GetError());
|
|
}
|
|
}
|
|
|
|
// Set SDL metadata for audio mixers and macOS "About" menu
|
|
SDL_SetAppMetadata("Dusklight", DUSK_VERSION_STRING, "dev.twilitrealm.dusk");
|
|
|
|
{
|
|
const auto userPathString = dusk::ConfigPath.u8string();
|
|
const auto cachePathString = dusk::CachePath.u8string();
|
|
AuroraConfig config{};
|
|
config.appName = dusk::AppName;
|
|
config.userPath = reinterpret_cast<const char*>(userPathString.c_str());
|
|
config.cachePath = reinterpret_cast<const char*>(cachePathString.c_str());
|
|
config.vsync = dusk::getSettings().video.enableVsync;
|
|
config.startFullscreen = dusk::getSettings().video.enableFullscreen;
|
|
config.windowPosX = -1;
|
|
config.windowPosY = -1;
|
|
config.windowWidth = defaultWindowWidth * 2;
|
|
config.windowHeight = defaultWindowHeight * 2;
|
|
config.desiredBackend = ResolveDesiredBackend(parsed_arg_options);
|
|
config.logCallback = &aurora_log_callback;
|
|
config.logLevel = startupLogLevel;
|
|
config.mem1Size = 256 * 1024 * 1024;
|
|
config.mem2Size = 24 * 1024 * 1024;
|
|
config.allowJoystickBackgroundEvents = dusk::getSettings().game.allowBackgroundInput;
|
|
config.pauseOnFocusLost = dusk::getSettings().game.pauseOnFocusLost;
|
|
config.imGuiInitCallback = &aurora_imgui_init_callback;
|
|
config.allowTextureReplacements = true;
|
|
config.allowTextureDumps = false;
|
|
auroraInfo = aurora_initialize(argc, argv, &config);
|
|
}
|
|
|
|
#ifdef DUSK_DISCORD
|
|
if (dusk::getSettings().game.enableDiscordPresence) {
|
|
dusk::discord::initialize();
|
|
}
|
|
#endif
|
|
|
|
VISetWindowTitle(
|
|
fmt::format("Dusklight {} [{}]", DUSK_WC_DESCRIBE, dusk::backend_name(auroraInfo.backend))
|
|
.c_str());
|
|
|
|
if (dusk::getSettings().video.lockAspectRatio) {
|
|
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
|
|
} else {
|
|
AuroraSetViewportPolicy(AURORA_VIEWPORT_STRETCH);
|
|
}
|
|
VISetFrameBufferScale(dusk::getSettings().game.internalResolutionScale.getValue());
|
|
|
|
dusk::audio::SetMasterVolume(dusk::audio::MasterVolumeToLinear(dusk::getSettings().audio.masterVolume / 100.0f));
|
|
dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb);
|
|
dusk::audio::EnableHrtf = dusk::getSettings().audio.enableHrtf;
|
|
|
|
// load rando generator configuration data
|
|
LoadRandomizerConfig();
|
|
|
|
// Run ImGui UI loop if Aurora couldn't initialize a backend
|
|
if (auroraInfo.backend == BACKEND_NULL) {
|
|
launchUILoop();
|
|
dusk::crash_reporting::shutdown();
|
|
dusk::ShutdownFileLogging();
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
#ifdef DUSK_DISCORD
|
|
dusk::discord::shutdown();
|
|
#endif
|
|
dusk::ui::shutdown();
|
|
aurora_shutdown();
|
|
return 0;
|
|
}
|
|
|
|
dusk::ui::initialize();
|
|
dusk::ui::push_document(std::make_unique<dusk::ui::Overlay>(), true, true);
|
|
dusk::ui::push_document(std::make_unique<dusk::ui::MenuBar>(), false);
|
|
|
|
// Invalidate a bad saved isoPath so that Dusklight can't get blocked from starting up.
|
|
// This is only a metadata check; full hash verification is handled by the prelaunch UI.
|
|
bool forcePreLaunchUI = false;
|
|
bool saveConfigBeforePrelaunch = false;
|
|
|
|
const std::string p = dusk::getSettings().backend.isoPath;
|
|
dusk::iso::DiscInfo discInfo{};
|
|
if (!p.empty() &&
|
|
dusk::iso::inspect(p.c_str(), discInfo) != dusk::iso::ValidationError::Success)
|
|
{
|
|
DuskLog.warn("Saved DVD image path failed validation, clearing configured path: {}", p);
|
|
dusk::getSettings().backend.isoPath.setValue("");
|
|
dusk::getSettings().backend.isoVerification.setValue(dusk::DiscVerificationState::Unknown);
|
|
forcePreLaunchUI = true;
|
|
saveConfigBeforePrelaunch = true;
|
|
}
|
|
|
|
std::string dvd_path;
|
|
bool dvd_opened = false;
|
|
if (parsed_arg_options.count("dvd")) {
|
|
dvd_path = parsed_arg_options["dvd"].as<std::string>();
|
|
if (dusk::iso::inspect(dvd_path.c_str(), discInfo) == dusk::iso::ValidationError::Success) {
|
|
DuskLog.info("Loading DVD image from command line: {}", dvd_path);
|
|
dvd_opened = aurora_dvd_open(dvd_path.c_str());
|
|
if (!dvd_opened) {
|
|
DuskLog.warn("Failed to open DVD image from command line: {}, opening prelaunch UI", dvd_path);
|
|
forcePreLaunchUI = true;
|
|
} else {
|
|
dusk::getSettings().backend.isoPath.setValue(dvd_path);
|
|
dusk::getSettings().backend.isoVerification.setValue(
|
|
dusk::DiscVerificationState::Unknown);
|
|
dusk::config::Save();
|
|
dusk::IsGameLaunched = true;
|
|
}
|
|
} else {
|
|
DuskLog.warn("DVD image from command line failed validation: {}, opening prelaunch UI", dvd_path);
|
|
forcePreLaunchUI = true;
|
|
}
|
|
}
|
|
|
|
dusk::iso::log_verification_state(
|
|
dusk::getSettings().backend.isoPath.getValue(),
|
|
dusk::getSettings().backend.isoVerification.getValue());
|
|
|
|
if (!dvd_opened) {
|
|
if (dusk::getSettings().backend.isoPath.getValue().empty()) {
|
|
forcePreLaunchUI = true;
|
|
}
|
|
if (forcePreLaunchUI && dusk::getSettings().backend.skipPreLaunchUI.getValue()) {
|
|
DuskLog.warn("Prelaunch UI was disabled with no usable DVD image, enabling prelaunch UI");
|
|
dusk::getSettings().backend.skipPreLaunchUI.setValue(false);
|
|
saveConfigBeforePrelaunch = true;
|
|
}
|
|
if (saveConfigBeforePrelaunch) {
|
|
dusk::config::Save();
|
|
}
|
|
|
|
if (!dusk::getSettings().backend.skipPreLaunchUI) {
|
|
dusk::ui::push_document(std::make_unique<dusk::ui::Prelaunch>(), true);
|
|
|
|
// pre game launch ui main loop
|
|
if (!launchUILoop()) {
|
|
dusk::crash_reporting::shutdown();
|
|
dusk::ShutdownFileLogging();
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
#ifdef DUSK_DISCORD
|
|
dusk::discord::shutdown();
|
|
#endif
|
|
dusk::ui::shutdown();
|
|
aurora_shutdown();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
dvd_path = dusk::getSettings().backend.isoPath;
|
|
|
|
if (dvd_path.empty()) {
|
|
DuskLog.fatal("No DVD image specified, unable to boot!");
|
|
}
|
|
if (!dusk::IsGameLaunched &&
|
|
dusk::iso::inspect(dvd_path.c_str(), discInfo) != dusk::iso::ValidationError::Success)
|
|
{
|
|
DuskLog.fatal("DVD image failed validation: {}", dvd_path);
|
|
}
|
|
DuskLog.info("Loading DVD image: {}", dvd_path);
|
|
if (!aurora_dvd_open(dvd_path.c_str())) {
|
|
DuskLog.fatal("Failed to open DVD image: {}", dvd_path);
|
|
}
|
|
|
|
dusk::IsGameLaunched = true;
|
|
}
|
|
|
|
#if DUSK_ENABLE_SENTRY_NATIVE
|
|
if (dusk::crash_reporting::get_consent() == dusk::crash_reporting::Consent::Unknown) {
|
|
dusk::ui::push_document(std::make_unique<dusk::ui::CrashReportWindow>());
|
|
}
|
|
#endif
|
|
|
|
if (!dusk::getSettings().backend.wasPresetChosen) {
|
|
dusk::ui::push_document(std::make_unique<dusk::ui::PresetWindow>());
|
|
}
|
|
|
|
dusk::version::init();
|
|
LanguageInit();
|
|
|
|
OSInit();
|
|
|
|
mDoMain::sPowerOnTime = OSGetTime();
|
|
|
|
// Reset Data
|
|
static mDoRstData sResetData = {0};
|
|
mDoRst::setResetData(&sResetData);
|
|
mDoRst::offReset();
|
|
mDoRst::setLogoScnFlag(0);
|
|
|
|
// Global Context Init
|
|
dComIfG_ct();
|
|
|
|
// Development Mode
|
|
// mDoMain::developmentMode = 1; // Force Dev Mode for Debugging
|
|
mDoDvdThd::SyncWidthSound = false;
|
|
|
|
OSReport("Starting main01 (Game Loop)...\n");
|
|
|
|
|
|
main01();
|
|
|
|
dusk::MoviePlayerShutdown();
|
|
|
|
dusk::crash_reporting::shutdown();
|
|
dusk::ShutdownFileLogging();
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
|
|
mDoMch_Destroy();
|
|
|
|
// Notifies all CVs and causes threads to exit
|
|
OSResetSystem(OS_RESET_SHUTDOWN, 0, 0);
|
|
|
|
#ifdef DUSK_DISCORD
|
|
dusk::discord::shutdown();
|
|
#endif
|
|
dusk::ui::shutdown();
|
|
aurora_shutdown();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool JKRHeap::dump_sort() {
|
|
return true;
|
|
}
|
|
|
|
#ifdef __MWERKS__
|
|
template <typename T>
|
|
JHIComPortManager<T>* JHIComPortManager<T>::instance = nullptr;
|
|
|
|
template <>
|
|
JHIComPortManager<JHICmnMem>* JHIComPortManager<JHICmnMem>::instance = nullptr;
|
|
|
|
template<>
|
|
Z2WolfHowlMgr* JASGlobalInstance<Z2WolfHowlMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2EnvSeMgr* JASGlobalInstance<Z2EnvSeMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2FxLineMgr* JASGlobalInstance<Z2FxLineMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2Audience* JASGlobalInstance<Z2Audience>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2SoundObjMgr* JASGlobalInstance<Z2SoundObjMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2SoundInfo* JASGlobalInstance<Z2SoundInfo>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
JAUSoundInfo* JASGlobalInstance<JAUSoundInfo>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
JAUSoundNameTable* JASGlobalInstance<JAUSoundNameTable>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
JAUSoundTable* JASGlobalInstance<JAUSoundTable>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
JAISoundInfo* JASGlobalInstance<JAISoundInfo>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2SoundMgr* JASGlobalInstance<Z2SoundMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
JAIStreamMgr* JASGlobalInstance<JAIStreamMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
JAISeqMgr* JASGlobalInstance<JAISeqMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
JAISeMgr* JASGlobalInstance<JAISeMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2SpeechMgr2* JASGlobalInstance<Z2SpeechMgr2>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2SoundStarter* JASGlobalInstance<Z2SoundStarter>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
JAISoundStarter* JASGlobalInstance<JAISoundStarter>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2StatusMgr* JASGlobalInstance<Z2StatusMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2SceneMgr* JASGlobalInstance<Z2SceneMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2SeqMgr* JASGlobalInstance<Z2SeqMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
Z2SeMgr* JASGlobalInstance<Z2SeMgr>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
JASAudioThread* JASGlobalInstance<JASAudioThread>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
|
|
template<>
|
|
JASDefaultBankTable* JASGlobalInstance<JASDefaultBankTable>::sInstance JAS_GLOBAL_INSTANCE_INIT;
|
|
#endif // __MWERKS__
|