#include "Engine.h" #include "ui/ImguiUI.h" #include "StringHelper.h" #include "extractor/GameExtractor.h" #include "libultraship/src/Context.h" #include "libultraship/src/controller/controldevice/controller/mapping/ControllerDefaultMappings.h" #include "resource/type/ResourceType.h" #include "resource/importers/AnimFactory.h" #include "resource/importers/ColPolyFactory.h" #include "resource/importers/EnvSettingsFactory.h" #include "resource/importers/GenericArrayFactory.h" #include "resource/importers/HitboxFactory.h" #include "resource/importers/LimbFactory.h" #include "resource/importers/MessageFactory.h" #include "resource/importers/MessageLookupFactory.h" #include "resource/importers/ObjectInitFactory.h" #include "resource/importers/ScriptCommandFactory.h" #include "resource/importers/ScriptFactory.h" #include "resource/importers/SkeletonFactory.h" #include "resource/importers/Vec3fFactory.h" #include "resource/importers/Vec3sFactory.h" #include "resource/importers/audio/AudioTableFactory.h" #include "resource/importers/audio/BookFactory.h" #include "resource/importers/audio/DrumFactory.h" #include "resource/importers/audio/EnvelopeFactory.h" #include "resource/importers/audio/InstrumentFactory.h" #include "resource/importers/audio/LoopFactory.h" #include "resource/importers/audio/SampleFactory.h" #include "resource/importers/audio/SoundFontFactory.h" #include "port/interpolation/FrameInterpolation.h" #include #include #include #include #include #include #include "audio/GameAudio.h" #include "port/patches/DisplayListPatch.h" #include "port/mods/PortEnhancements.h" #include #include #ifdef __SWITCH__ #include #endif namespace fs = std::filesystem; extern "C" { bool prevAltAssets = false; bool gEnableGammaBoost = true; #include #include #include "sf64audio_provisional.h" void AudioThread_CreateNextAudioBuffer(int16_t* samples, uint32_t num_samples); } std::vector MemoryPool; GameEngine* GameEngine::Instance; GameEngine::GameEngine() { #ifdef __SWITCH__ Ship::Switch::Init(Ship::PreInitPhase); Ship::Switch::Init(Ship::PostInitPhase); #endif std::vector archiveFiles; const std::string main_path = Ship::Context::GetPathRelativeToAppDirectory("sf64.o2r"); #ifdef __linux__ const std::string assets_path = Ship::Context::GetPathRelativeToAppBundle("starship.o2r"); #else const std::string assets_path = Ship::Context::GetPathRelativeToAppDirectory("starship.o2r"); #endif #ifdef _WIN32 AllocConsole(); #endif if (!fs::exists("mods")) { fs::create_directories("mods"); } if (std::filesystem::exists(main_path)) { archiveFiles.push_back(main_path); } else { if (ShowYesNoBox("No O2R Files", "No O2R files found. Generate one now?") == IDYES) { if(!GenAssetFile()){ ShowMessage("Error", "An error occured, no O2R file was generated.\n\nExiting..."); exit(1); } else { archiveFiles.push_back(main_path); } if (ShowYesNoBox("Extraction Complete", "ROM Extracted. Extract another?") == IDYES) { if(!GenAssetFile()){ ShowMessage("Error", "An error occured, no O2R file was generated."); } } } else { exit(1); } } if (std::filesystem::exists(assets_path)) { archiveFiles.push_back(assets_path); } if (const std::string patches_path = Ship::Context::GetPathRelativeToAppDirectory("mods"); !patches_path.empty() && std::filesystem::exists(patches_path)) { if (std::filesystem::is_directory(patches_path)) { for (const auto& p : std::filesystem::recursive_directory_iterator(patches_path)) { const auto ext = p.path().extension().string(); if (StringHelper::IEquals(ext, ".otr") || StringHelper::IEquals(ext, ".o2r")) { archiveFiles.push_back(p.path().generic_string()); } if (StringHelper::IEquals(ext, ".zip")) { SPDLOG_WARN("Zip files should be only used for development purposes, not for distribution"); archiveFiles.push_back(p.path().generic_string()); } } } } this->context = Ship::Context::CreateUninitializedInstance("Starship", "ship", "starship.cfg.json"); this->context->InitConfiguration(); // without this line InitConsoleVariables fails at Config::Reload() this->context->InitConsoleVariables(); // without this line the controldeck constructor failes in // ShipDeviceIndexMappingManager::UpdateControllerNamesFromConfig() auto defaultMappings = std::make_shared( // KeyboardKeyToButtonMappings - use built-in LUS defaults std::unordered_map>(), // KeyboardKeyToAxisDirectionMappings - use built-in LUS defaults std::unordered_map>>(), // SDLButtonToButtonMappings std::unordered_map>{ { BTN_A, { SDL_CONTROLLER_BUTTON_A } }, { BTN_B, { SDL_CONTROLLER_BUTTON_X } }, { BTN_START, { SDL_CONTROLLER_BUTTON_START } }, { BTN_CLEFT, { SDL_CONTROLLER_BUTTON_Y } }, { BTN_CDOWN, { SDL_CONTROLLER_BUTTON_B } }, { BTN_DUP, { SDL_CONTROLLER_BUTTON_DPAD_UP } }, { BTN_DDOWN, { SDL_CONTROLLER_BUTTON_DPAD_DOWN } }, { BTN_DLEFT, { SDL_CONTROLLER_BUTTON_DPAD_LEFT } }, { BTN_DRIGHT, { SDL_CONTROLLER_BUTTON_DPAD_RIGHT } }, { BTN_R, { SDL_CONTROLLER_BUTTON_RIGHTSHOULDER } }, { BTN_Z, { SDL_CONTROLLER_BUTTON_LEFTSHOULDER } } }, // SDLButtonToAxisDirectionMappings - use built-in LUS defaults std::unordered_map>>(), // SDLAxisDirectionToButtonMappings std::unordered_map>>{ { BTN_CLEFT, { { SDL_CONTROLLER_AXIS_TRIGGERRIGHT, 1 } } }, { BTN_CDOWN, { { SDL_CONTROLLER_AXIS_TRIGGERLEFT, 1 } } }, { BTN_CUP, { { SDL_CONTROLLER_AXIS_RIGHTY, -1 } } }, { BTN_CRIGHT, { { SDL_CONTROLLER_AXIS_RIGHTX, 1 } } } }, // SDLAxisDirectionToAxisDirectionMappings - use built-in LUS defaults std::unordered_map>>>() ); auto controlDeck = std::make_shared(std::vector(), defaultMappings); this->context->InitResourceManager(archiveFiles, {}, 3); // without this line InitWindow fails in Gui::Init() this->context->InitConsole(); // without this line the GuiWindow constructor fails in ConsoleWindow::InitElement() auto window = std::make_shared(std::vector>({})); auto audioChannelsSetting = Ship::Context::GetInstance()->GetConfig()->GetCurrentAudioChannelsSetting(); this->context->Init(archiveFiles, {}, 3, { 32000, 1024, 1680, audioChannelsSetting }, window, controlDeck); #ifndef __SWITCH__ Ship::Context::GetInstance()->GetLogger()->set_level( (spdlog::level::level_enum) CVarGetInteger("gDeveloperTools.LogLevel", 1)); Ship::Context::GetInstance()->GetLogger()->set_pattern("[%H:%M:%S.%e] [%s:%#] [%l] %v"); #endif auto loader = context->GetResourceManager()->GetResourceLoader(); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Animation", static_cast(SF64::ResourceType::AnimData), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Skeleton", static_cast(SF64::ResourceType::Skeleton), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Limb", static_cast(SF64::ResourceType::Limb), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Message", static_cast(SF64::ResourceType::Message), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "MessageTable", static_cast(SF64::ResourceType::MessageTable), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "EnvSettings", static_cast(SF64::ResourceType::Environment), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "ObjectInit", static_cast(SF64::ResourceType::ObjectInit), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Hitbox", static_cast(SF64::ResourceType::Hitbox), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Script", static_cast(SF64::ResourceType::Script), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "ScriptCMD", static_cast(SF64::ResourceType::ScriptCmd), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "ColPoly", static_cast(SF64::ResourceType::ColPoly), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Vec3f", static_cast(SF64::ResourceType::Vec3f), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Vec3s", static_cast(SF64::ResourceType::Vec3s), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "GenericArray", static_cast(SF64::ResourceType::GenericArray), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Texture", static_cast(Fast::ResourceType::Texture), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Texture", static_cast(Fast::ResourceType::Texture), 1); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Vertex", static_cast(Fast::ResourceType::Vertex), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, "Vertex", static_cast(Fast::ResourceType::Vertex), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "DisplayList", static_cast(Fast::ResourceType::DisplayList), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, "DisplayList", static_cast(Fast::ResourceType::DisplayList), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Matrix", static_cast(Fast::ResourceType::Matrix), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Blob", static_cast(Ship::ResourceType::Blob), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AudioTable", static_cast(SF64::ResourceType::AudioTable), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AdpcmBook", static_cast(SF64::ResourceType::AdpcmBook), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Drum", static_cast(SF64::ResourceType::Drum), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Envelope", static_cast(SF64::ResourceType::Envelope), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Instrument", static_cast(SF64::ResourceType::Instrument), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "AdpcmLoop", static_cast(SF64::ResourceType::AdpcmLoop), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "Sample", static_cast(SF64::ResourceType::Sample), 1); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, "Sample", static_cast(SF64::ResourceType::Sample), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_BINARY, "SoundFont", static_cast(SF64::ResourceType::SoundFont), 0); loader->RegisterResourceFactory(std::make_shared(), RESOURCE_FORMAT_XML, "SoundFont", static_cast(SF64::ResourceType::SoundFont), 0); prevAltAssets = CVarGetInteger("gEnhancements.Mods.AlternateAssets", 0); gEnableGammaBoost = CVarGetInteger("gGraphics.GammaMode", 0) == 0; context->GetResourceManager()->SetAltAssetsEnabled(prevAltAssets); } bool GameEngine::GenAssetFile(bool exitOnFail) { auto extractor = new GameExtractor(); if (!extractor->SelectGameFromUI()) { ShowMessage("Error", "No ROM selected.\n\nExiting..."); if (exitOnFail) { exit(1); } else { return false; } } auto game = extractor->ValidateChecksum(); if (!game.has_value()) { ShowMessage("Unsupported ROM", "The provided ROM is not supported.\n\nCheck the readme for a list of supported versions."); if (exitOnFail) { exit(1); } else { return false; } } ShowMessage(("Found " + game.value()).c_str(), "The extraction process will now begin.\n\nThis may take a few minutes.", SDL_MESSAGEBOX_INFORMATION); return extractor->GenerateOTR(); } void GameEngine::Create() { const auto instance = Instance = new GameEngine(); instance->AudioInit(); DisplayListPatch::Run(); GameUI::SetupGuiElements(); #if defined(__SWITCH__) || defined(__WIIU__) CVarRegisterInteger("gControlNav", 1); // always enable controller nav on switch/wii u osSetTime(0); #endif PortEnhancements_Init(); } void GameEngine::Destroy() { PortEnhancements_Exit(); AudioExit(); for (auto ptr : MemoryPool) { free(ptr); } MemoryPool.clear(); #ifdef __SWITCH__ Ship::Switch::Exit(); #endif } void GameEngine::StartFrame() const { using Ship::KbScancode; const int32_t dwScancode = this->context->GetWindow()->GetLastScancode(); this->context->GetWindow()->SetLastScancode(-1); switch (dwScancode) { case KbScancode::LUS_KB_TAB: { // Toggle HD Assets CVarSetInteger("gEnhancements.Mods.AlternateAssets", !CVarGetInteger("gEnhancements.Mods.AlternateAssets", 0)); break; } case KbScancode::LUS_KB_F4: { gNextGameState = GSTATE_BOOT; break; } default: break; } } #if 0 // Values for 44100 hz #define SAMPLES_HIGH 752 #define SAMPLES_LOW 720 #else // Values for 32000 hz #define SAMPLES_HIGH 560 #define SAMPLES_LOW 528 #endif #define MAX_NUM_AUDIO_CHANNELS 6 extern "C" u16 audBuffer = 0; #include extern "C" volatile s32 gAudioTaskCountQ; int frames = 0; extern "C" int countermin = 0; extern "C" unsigned short samples_high = SAMPLES_HIGH; extern "C" unsigned short samples_low = SAMPLES_LOW; void GameEngine::HandleAudioThread() { #ifdef PIPE_DEBUG std::ofstream outfile("audio.bin", std::ios::binary | std::ios::app); #endif while (audio.running) { { std::unique_lock Lock(audio.mutex); while (!audio.processing && audio.running) { audio.cv_to_thread.wait(Lock); } if (!audio.running) { break; } } // gVIsPerFrame = 2; #define AUDIO_FRAMES_PER_UPDATE (gVIsPerFrame > 0 ? gVIsPerFrame : 1) #define MAX_AUDIO_FRAMES_PER_UPDATE 5 // Compile-time constant with max value of gVIsPerFrame std::unique_lock Lock(audio.mutex); int samples_left = AudioPlayerBuffered(); u32 num_audio_samples = samples_left < AudioPlayerGetDesiredBuffered() ? (((samples_high))) : (((samples_low))); frames++; if (frames > 60) { countermin++; } const int32_t num_audio_channels = GetNumAudioChannels(); s16 audio_buffer[SAMPLES_HIGH * MAX_NUM_AUDIO_CHANNELS * MAX_AUDIO_FRAMES_PER_UPDATE] = { 0 }; for (int i = 0; i < AUDIO_FRAMES_PER_UPDATE; i++) { AudioThread_CreateNextAudioBuffer(audio_buffer + i * (num_audio_samples * num_audio_channels), num_audio_samples); } #ifdef PIPE_DEBUG if (outfile.is_open()) { outfile.write(reinterpret_cast(audio_buffer), num_audio_samples * (sizeof(int16_t) * num_audio_channels * AUDIO_FRAMES_PER_UPDATE)); } #endif AudioPlayerPlayFrame((u8*) audio_buffer, num_audio_samples * (sizeof(int16_t) * num_audio_channels * AUDIO_FRAMES_PER_UPDATE)); audio.processing = false; audio.cv_from_thread.notify_one(); } #ifdef PIPE_DEBUG outfile.close(); #endif } void GameEngine::StartAudioFrame() { { std::unique_lock Lock(audio.mutex); audio.processing = true; } audio.cv_to_thread.notify_one(); } void GameEngine::EndAudioFrame() { { std::unique_lock Lock(audio.mutex); while (audio.processing) { audio.cv_from_thread.wait(Lock); } } } void GameEngine::AudioInit() { if (!audio.running) { audio.running = true; audio.thread = std::thread(HandleAudioThread); } } void GameEngine::AudioExit() { { std::unique_lock lock(audio.mutex); audio.running = false; } audio.cv_to_thread.notify_all(); // Wait until the audio thread quit audio.thread.join(); } void GameEngine::RunCommands(Gfx* Commands, const std::vector>& mtx_replacements) { auto wnd = std::dynamic_pointer_cast(Ship::Context::GetInstance()->GetWindow()); if (wnd == nullptr) { return; } auto interpreter = wnd->GetInterpreterWeak().lock().get(); // Process window events for resize, mouse, keyboard events wnd->HandleEvents(); interpreter->mInterpolationIndex = 0; for (const auto& m : mtx_replacements) { wnd->DrawAndRunGraphicsCommands(Commands, m); interpreter->mInterpolationIndex++; } bool curAltAssets = CVarGetInteger("gEnhancements.Mods.AlternateAssets", 0); if (prevAltAssets != curAltAssets) { prevAltAssets = curAltAssets; Ship::Context::GetInstance()->GetResourceManager()->SetAltAssetsEnabled(curAltAssets); gfx_texture_cache_clear(); } } void GameEngine::ProcessGfxCommands(Gfx* commands) { auto wnd = std::dynamic_pointer_cast(Ship::Context::GetInstance()->GetWindow()); if (wnd == nullptr) { return; } if(gEnableGammaBoost) { wnd->EnableSRGBMode(); } wnd->SetRendererUCode(UcodeHandlers::ucode_f3dex); std::vector> mtx_replacements; int target_fps = GameEngine::Instance->GetInterpolationFPS(); static int last_fps; static int last_update_rate; static int time; int fps = target_fps; int original_fps = 60 / gVIsPerFrame; if (target_fps == 20 || original_fps > target_fps) { fps = original_fps; } if (last_fps != fps || last_update_rate != gVIsPerFrame) { time = 0; } // time_base = fps * original_fps (one second) int next_original_frame = fps; while (time + original_fps <= next_original_frame) { time += original_fps; if (time != next_original_frame) { mtx_replacements.push_back(FrameInterpolation_Interpolate((float) time / next_original_frame)); } else { mtx_replacements.emplace_back(); } } time -= fps; if (wnd != nullptr) { wnd->SetTargetFps(fps); wnd->SetMaximumFrameLatency(CVarGetInteger("gRenderParallelization", 1) ? 2 : 1); } // When the gfx debugger is active, only run with the final mtx if (GfxDebuggerIsDebugging()) { mtx_replacements.clear(); mtx_replacements.emplace_back(); } RunCommands(commands, mtx_replacements); last_fps = fps; last_update_rate = gVIsPerFrame; } uint32_t GameEngine::GetInterpolationFPS() { if (CVarGetInteger("gMatchRefreshRate", 0)) { return Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(); } else if (CVarGetInteger("gVsyncEnabled", 1) || !Ship::Context::GetInstance()->GetWindow()->CanDisableVerticalSync()) { return std::min(Ship::Context::GetInstance()->GetWindow()->GetCurrentRefreshRate(), CVarGetInteger("gInterpolationFPS", 60)); } return CVarGetInteger("gInterpolationFPS", 60); } uint32_t GameEngine::GetInterpolationFrameCount() { return ceil((float)GetInterpolationFPS() / (60.0f / gVIsPerFrame)); } extern "C" uint32_t GameEngine_GetInterpolationFrameCount() { return GameEngine::GetInterpolationFrameCount(); } void GameEngine::ShowMessage(const char* title, const char* message, SDL_MessageBoxFlags type) { #if defined(__SWITCH__) SPDLOG_ERROR(message); #else SDL_ShowSimpleMessageBox(type, title, message, nullptr); SPDLOG_ERROR(message); #endif } int GameEngine::ShowYesNoBox(const char* title, const char* box) { int ret; #ifdef _WIN32 ret = MessageBoxA(nullptr, box, title, MB_YESNO | MB_ICONQUESTION); #elif defined(__SWITCH__) SPDLOG_ERROR(box); return IDYES; #else SDL_MessageBoxData boxData = { 0 }; SDL_MessageBoxButtonData buttons[2] = { { 0 } }; buttons[0].buttonid = IDYES; buttons[0].text = "Yes"; buttons[0].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT; buttons[1].buttonid = IDNO; buttons[1].text = "No"; buttons[1].flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT; boxData.numbuttons = 2; boxData.flags = SDL_MESSAGEBOX_INFORMATION; boxData.message = box; boxData.title = title; boxData.buttons = buttons; SDL_ShowMessageBox(&boxData, &ret); #endif return ret; } bool GameEngine::HasVersion(SF64Version ver){ auto versions = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->GetGameVersions(); return std::find(versions.begin(), versions.end(), ver) != versions.end(); } extern "C" bool GameEngine_HasVersion(SF64Version ver) { return GameEngine::HasVersion(ver); } extern "C" uint32_t GameEngine_GetSampleRate() { auto player = Ship::Context::GetInstance()->GetAudio()->GetAudioPlayer(); if (player == nullptr) { return 0; } if (!player->IsInitialized()) { return 0; } return player->GetSampleRate(); } extern "C" uint32_t GameEngine_GetSamplesPerFrame() { return SAMPLES_PER_FRAME; } // End Fast::Interpreter* GameEngine_GetInterpreter() { return static_pointer_cast(Ship::Context::GetInstance()->GetWindow()) ->GetInterpreterWeak() .lock() .get(); } extern "C" float GameEngine_GetAspectRatio() { auto interpreter = GameEngine_GetInterpreter(); return interpreter->mCurDimensions.aspect_ratio; } extern "C" uint32_t GameEngine_GetGameVersion() { return 0x00000001; } static const char* sOtrSignature = "__OTR__"; extern "C" uint8_t GameEngine_OTRSigCheck(const char* data) { if (data == nullptr) { return 0; } return strncmp(data, sOtrSignature, strlen(sOtrSignature)) == 0; } extern "C" void GameEngine_GetTextureInfo(const char* path, int32_t* width, int32_t* height, float* scale, bool* custom) { if(GameEngine_OTRSigCheck(path) != 1){ *custom = false; return; } std::shared_ptr tex = std::static_pointer_cast( Ship::Context::GetInstance()->GetResourceManager()->LoadResourceProcess(path)); *width = tex->Width; *height = tex->Height; *scale = tex->VPixelScale; *custom = tex->Flags & (1 << 0); } extern "C" float __cosf(float angle) throw() { return cosf(angle); } extern "C" float __sinf(float angle) throw() { return sinf(angle); } extern "C" float SIN_DEG(float angle) { return __sinf(M_DTOR * angle); } extern "C" float COS_DEG(float angle) { return __cosf(M_DTOR * angle); } struct TimedEntry { uint64_t duration; TimerAction action; int32_t* address; int32_t value; bool active; }; std::vector gTimerTasks; uint64_t Timer_GetCurrentMillis() { return SDL_GetTicks(); } extern "C" s32 Timer_CreateTask(u64 time, TimerAction action, s32* address, s32 value) { const auto millis = Timer_GetCurrentMillis(); TimedEntry entry = { .duration = millis + CYCLES_TO_MSEC_PC(time), .action = action, .address = address, .value = value, .active = true, }; gTimerTasks.push_back(entry); return gTimerTasks.size() - 1; } extern "C" void Timer_Increment(int32_t* address, int32_t value) { *address += value; } extern "C" void Timer_SetValue(int32_t* address, int32_t value) { *address = value; } void Timer_CompleteTask(TimedEntry& task) { if (task.action != nullptr) { task.action(task.address, task.value); } task.active = false; } extern "C" void Timer_Update() { if (gTimerTasks.empty()) { return; } const auto millis = Timer_GetCurrentMillis(); for (auto& task : gTimerTasks) { if (task.active && millis >= task.duration) { Timer_CompleteTask(task); } } } // Gets the width of the main ImGui window extern "C" uint32_t OTRGetCurrentWidth() { return GameEngine::Instance->context->GetWindow()->GetWidth(); } // Gets the height of the main ImGui window extern "C" uint32_t OTRGetCurrentHeight() { return GameEngine::Instance->context->GetWindow()->GetHeight(); } extern "C" float OTRGetHUDAspectRatio() { if (CVarGetInteger("gHUDAspectRatio.Enabled", 0) == 0 || CVarGetInteger("gHUDAspectRatio.X", 0) == 0 || CVarGetInteger("gHUDAspectRatio.Y", 0) == 0) { return GameEngine_GetAspectRatio(); } return ((float)CVarGetInteger("gHUDAspectRatio.X", 1) / (float)CVarGetInteger("gHUDAspectRatio.Y", 1)); } extern "C" float OTRGetDimensionFromLeftEdge(float v) { auto interpreter = GameEngine_GetInterpreter(); return (interpreter->mNativeDimensions.width / 2 - interpreter->mNativeDimensions.height / 2 * interpreter->mCurDimensions.aspect_ratio + (v)); } extern "C" float OTRGetDimensionFromRightEdge(float v) { auto interpreter = GameEngine_GetInterpreter(); return (interpreter->mNativeDimensions.width / 2 + interpreter->mNativeDimensions.height / 2 * interpreter->mCurDimensions.aspect_ratio - (interpreter->mNativeDimensions.width - v)); } extern "C" float OTRGetDimensionFromLeftEdgeForcedAspect(float v, float aspectRatio) { auto interpreter = GameEngine_GetInterpreter(); return (interpreter->mNativeDimensions.width / 2 - interpreter->mNativeDimensions.height / 2 * (aspectRatio > 0 ? aspectRatio : interpreter->mCurDimensions.aspect_ratio) + (v)); } extern "C" float OTRGetDimensionFromRightEdgeForcedAspect(float v, float aspectRatio) { auto interpreter = GameEngine_GetInterpreter(); return (interpreter->mNativeDimensions.width / 2 + interpreter->mNativeDimensions.height / 2 * (aspectRatio > 0 ? aspectRatio : interpreter->mCurDimensions.aspect_ratio) - (interpreter->mNativeDimensions.width - v)); } extern "C" float OTRGetDimensionFromLeftEdgeOverride(float v) { return OTRGetDimensionFromLeftEdgeForcedAspect(v, OTRGetHUDAspectRatio()); } extern "C" float OTRGetDimensionFromRightEdgeOverride(float v) { return OTRGetDimensionFromRightEdgeForcedAspect(v, OTRGetHUDAspectRatio()); } // Gets the width of the current render target area extern "C" uint32_t OTRGetGameRenderWidth() { auto interpreter = GameEngine_GetInterpreter(); return interpreter->mCurDimensions.width; } // Gets the height of the current render target area extern "C" uint32_t OTRGetGameRenderHeight() { auto interpreter = GameEngine_GetInterpreter(); return interpreter->mCurDimensions.height; } extern "C" int16_t OTRGetRectDimensionFromLeftEdge(float v) { return ((int) floorf(OTRGetDimensionFromLeftEdge(v))); } extern "C" int16_t OTRGetRectDimensionFromRightEdge(float v) { return ((int) ceilf(OTRGetDimensionFromRightEdge(v))); } extern "C" int16_t OTRGetRectDimensionFromLeftEdgeForcedAspect(float v, float aspectRatio) { return ((int) floorf(OTRGetDimensionFromLeftEdgeForcedAspect(v, aspectRatio))); } extern "C" int16_t OTRGetRectDimensionFromRightEdgeForcedAspect(float v, float aspectRatio) { return ((int) ceilf(OTRGetDimensionFromRightEdgeForcedAspect(v, aspectRatio))); } extern "C" int16_t OTRGetRectDimensionFromLeftEdgeOverride(float v) { return OTRGetRectDimensionFromLeftEdgeForcedAspect(v, OTRGetHUDAspectRatio()); } extern "C" int16_t OTRGetRectDimensionFromRightEdgeOverride(float v) { return OTRGetRectDimensionFromRightEdgeForcedAspect(v, OTRGetHUDAspectRatio()); } extern "C" int32_t OTRConvertHUDXToScreenX(int32_t v) { auto interpreter = GameEngine_GetInterpreter(); float gameAspectRatio = interpreter->mCurDimensions.aspect_ratio; int32_t gameHeight = interpreter->mCurDimensions.height; int32_t gameWidth = interpreter->mCurDimensions.width; float hudAspectRatio = 4.0f / 3.0f; int32_t hudHeight = gameHeight; int32_t hudWidth = hudHeight * hudAspectRatio; float hudScreenRatio = (hudWidth / 320.0f); float hudCoord = v * hudScreenRatio; float gameOffset = (gameWidth - hudWidth) / 2; float gameCoord = hudCoord + gameOffset; float gameScreenRatio = (320.0f / gameWidth); float screenScaledCoord = gameCoord * gameScreenRatio; int32_t screenScaledCoordInt = screenScaledCoord; return screenScaledCoordInt; } extern "C" void* GameEngine_Malloc(size_t size) { MemoryPool.push_back(new uint8_t[size]); return (void*) MemoryPool.back(); } extern "C" void GameEngine_Free(void* ptr) { for (auto it = MemoryPool.begin(); it != MemoryPool.end(); ++it) { if (*it == ptr) { free(ptr); MemoryPool.erase(it); break; } } }