From ddc9e6d1c679fe86ea4fdf12f64a29e56f86a67b Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Mon, 12 Oct 2026 14:21:00 -0400 Subject: [PATCH 01/15] API --- MarathonRecomp/api/Marathon.h | 7 ++++ MarathonRecomp/api/Sonicteam/DevTitleMode.h | 34 +++++++++++++++++++ MarathonRecomp/api/Sonicteam/DocState.h | 12 +++++++ MarathonRecomp/api/Sonicteam/PicBase.h | 15 ++++++++ MarathonRecomp/api/Sonicteam/PictureTitle.h | 17 ++++++++++ .../api/Sonicteam/SoX/Engine/DocMode.h | 2 +- .../api/Sonicteam/SoX/Input/Listener.h | 13 +++++++ MarathonRecomp/api/Sonicteam/StageMap.h | 17 ++++++++++ .../api/Sonicteam/StageSelectMode.h | 23 +++++++++++++ MarathonRecomp/ui/dev_title_menu.cpp | 5 +++ MarathonRecomp/ui/dev_title_menu.h | 8 +++++ 11 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 MarathonRecomp/api/Sonicteam/DevTitleMode.h create mode 100644 MarathonRecomp/api/Sonicteam/DocState.h create mode 100644 MarathonRecomp/api/Sonicteam/PicBase.h create mode 100644 MarathonRecomp/api/Sonicteam/PictureTitle.h create mode 100644 MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h create mode 100644 MarathonRecomp/api/Sonicteam/StageMap.h create mode 100644 MarathonRecomp/api/Sonicteam/StageSelectMode.h create mode 100644 MarathonRecomp/ui/dev_title_menu.cpp create mode 100644 MarathonRecomp/ui/dev_title_menu.h diff --git a/MarathonRecomp/api/Marathon.h b/MarathonRecomp/api/Marathon.h index 5f7a50205..e5ac199f5 100644 --- a/MarathonRecomp/api/Marathon.h +++ b/MarathonRecomp/api/Marathon.h @@ -33,9 +33,11 @@ #include "Sonicteam/CsdManager.h" #include "Sonicteam/CsdObject.h" #include "Sonicteam/CsdResource.h" +#include "Sonicteam/DevTitleMode.h" #include "Sonicteam/DocMarathon.h" #include "Sonicteam/DocMarathonImp.h" #include "Sonicteam/DocMarathonState.h" +#include "Sonicteam/DocState.h" #include "Sonicteam/Enemy/EnemyShot.h" #include "Sonicteam/Enemy/EnemyShotNormal.h" #include "Sonicteam/Enemy/EnemyShotPoint.h" @@ -104,6 +106,8 @@ #include "Sonicteam/ObjectVehicleBike.h" #include "Sonicteam/PauseAdapter.h" #include "Sonicteam/PauseTask.h" +#include "Sonicteam/PicBase.h" +#include "Sonicteam/PictureTitle.h" #include "Sonicteam/Player/CommonContextIFFlags.h" #include "Sonicteam/Player/GroundRayListener.h" #include "Sonicteam/Player/ICollisionListener.h" @@ -248,6 +252,7 @@ #include "Sonicteam/SoX/IResource.h" #include "Sonicteam/SoX/IResource2.h" #include "Sonicteam/SoX/IResourceMgr.h" +#include "Sonicteam/SoX/Input/Listener.h" #include "Sonicteam/SoX/Input/Manager.h" #include "Sonicteam/SoX/LinkNode.h" #include "Sonicteam/SoX/LinkedList.h" @@ -285,6 +290,8 @@ #include "Sonicteam/SoX/Scenery/CameraImp.h" #include "Sonicteam/SoX/Scenery/Drawable.h" #include "Sonicteam/SoX/Thread.h" +#include "Sonicteam/StageMap.h" +#include "Sonicteam/StageSelectMode.h" #include "Sonicteam/StageTitleTask.h" #include "Sonicteam/StdImageFilters/BurnoutBlurFilter.h" #include "Sonicteam/StdImageFilters/SingleTechniqueFilter.h" diff --git a/MarathonRecomp/api/Sonicteam/DevTitleMode.h b/MarathonRecomp/api/Sonicteam/DevTitleMode.h new file mode 100644 index 000000000..03fe5ca37 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/DevTitleMode.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include + +// Anonymous namespace +namespace +{ + class StateDevTitle : public Sonicteam::DocState + { + public: + xpointer m_PictureTitle; + }; +} + +namespace Sonicteam +{ + class DevTitleMode : public SoX::Engine::DocMode + { + struct Mode + { + xpointer m_Name; + MARATHON_INSERT_PADDING(4); + }; + + public: + be m_Selected; + MARATHON_INSERT_PADDING(4); + stdx::vector m_Modes; + }; +} diff --git a/MarathonRecomp/api/Sonicteam/DocState.h b/MarathonRecomp/api/Sonicteam/DocState.h new file mode 100644 index 000000000..ce286cf0a --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/DocState.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace Sonicteam +{ + class DocState + { + public: + xpointer m_pVftable; + }; +} diff --git a/MarathonRecomp/api/Sonicteam/PicBase.h b/MarathonRecomp/api/Sonicteam/PicBase.h new file mode 100644 index 000000000..7c7bcc1a3 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/PicBase.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace Sonicteam +{ + class PicBase + { + public: + xpointer m_pVftable; + be m_State1; + be m_State2; + be m_State3; + }; +} diff --git a/MarathonRecomp/api/Sonicteam/PictureTitle.h b/MarathonRecomp/api/Sonicteam/PictureTitle.h new file mode 100644 index 000000000..eb90506e0 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/PictureTitle.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include +#include + +namespace Sonicteam +{ + class PictureTitle:public PicBase, public SoX::Input::Listener + { + public: + MARATHON_INSERT_PADDING(4); + xpointer m_Doc; + MARATHON_INSERT_PADDING(0x18); + }; +} diff --git a/MarathonRecomp/api/Sonicteam/SoX/Engine/DocMode.h b/MarathonRecomp/api/Sonicteam/SoX/Engine/DocMode.h index 1c2284d03..42e8f6f60 100644 --- a/MarathonRecomp/api/Sonicteam/SoX/Engine/DocMode.h +++ b/MarathonRecomp/api/Sonicteam/SoX/Engine/DocMode.h @@ -8,7 +8,7 @@ namespace Sonicteam::SoX::Engine class DocMode : public Task { public: - MARATHON_INSERT_PADDING(4); + be m_type; }; MARATHON_ASSERT_SIZEOF(DocMode, 0x50); diff --git a/MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h b/MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h new file mode 100644 index 000000000..14b6a0162 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +namespace Sonicteam::SoX::Input +{ + class Listener : Object + { + public: + MARATHON_INSERT_PADDING(0x14); // LinkSoxNode LinkInput, flags + }; +} diff --git a/MarathonRecomp/api/Sonicteam/StageMap.h b/MarathonRecomp/api/Sonicteam/StageMap.h new file mode 100644 index 000000000..75dfe884a --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/StageMap.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include +#include + +namespace Sonicteam +{ + class StageMap : public SoX::Component + { + public: + stdx::string m_Name; + stdx::string m_Text; + stdx::vector> m_vpStageMap; + }; +} diff --git a/MarathonRecomp/api/Sonicteam/StageSelectMode.h b/MarathonRecomp/api/Sonicteam/StageSelectMode.h new file mode 100644 index 000000000..13b4bd271 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/StageSelectMode.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +namespace Sonicteam +{ + class StageSelectMode : public SoX::Engine::DocMode + { + public: + xpointer m_StageMap; + xpointer m_CurrentStageMap; + MARATHON_INSERT_PADDING(4); + be m_CurrentStageMapIndex; + be m_Deep; + MARATHON_INSERT_PADDING(0x1C); + xpointer> m_StageMapInfo; + xpointer m_StageMapType; + xpointer m_StageMapName; + MARATHON_INSERT_PADDING(0x14); + }; +} diff --git a/MarathonRecomp/ui/dev_title_menu.cpp b/MarathonRecomp/ui/dev_title_menu.cpp new file mode 100644 index 000000000..847d961d3 --- /dev/null +++ b/MarathonRecomp/ui/dev_title_menu.cpp @@ -0,0 +1,5 @@ +// +// Created by Isaac Marovitz on 12/10/2026. +// + +#include "dev_title_menu.h" diff --git a/MarathonRecomp/ui/dev_title_menu.h b/MarathonRecomp/ui/dev_title_menu.h new file mode 100644 index 000000000..044482021 --- /dev/null +++ b/MarathonRecomp/ui/dev_title_menu.h @@ -0,0 +1,8 @@ +// +// Created by Isaac Marovitz on 12/10/2026. +// + +#ifndef MARATHONRECOMP_ALL_DEV_TITLE_MENU_H +#define MARATHONRECOMP_ALL_DEV_TITLE_MENU_H + +#endif //MARATHONRECOMP_ALL_DEV_TITLE_MENU_H From 9b428b6682694cf79bedb16e43838dae51748505 Mon Sep 17 00:00:00 2001 From: Isaac Marovitz Date: Mon, 12 Oct 2026 15:15:55 -0400 Subject: [PATCH 02/15] DevTitle menu --- MarathonRecomp/CMakeLists.txt | 1 + MarathonRecomp/gpu/video.cpp | 3 + MarathonRecomp/patches/misc_patches.cpp | 92 ++++++- MarathonRecomp/ui/dev_title_menu.cpp | 307 +++++++++++++++++++++++- MarathonRecomp/ui/dev_title_menu.h | 49 +++- MarathonRecomp/user/config_def.h | 1 + 6 files changed, 442 insertions(+), 11 deletions(-) diff --git a/MarathonRecomp/CMakeLists.txt b/MarathonRecomp/CMakeLists.txt index bbcc3dbd1..66859daf7 100644 --- a/MarathonRecomp/CMakeLists.txt +++ b/MarathonRecomp/CMakeLists.txt @@ -171,6 +171,7 @@ set(MARATHON_RECOMP_UI_CXX_SOURCES "ui/installer_wizard.cpp" "ui/message_window.cpp" "ui/options_menu.cpp" + "ui/dev_title_menu.cpp" ) set(MARATHON_RECOMP_INSTALL_CXX_SOURCES diff --git a/MarathonRecomp/gpu/video.cpp b/MarathonRecomp/gpu/video.cpp index 59bf26380..5950aa922 100644 --- a/MarathonRecomp/gpu/video.cpp +++ b/MarathonRecomp/gpu/video.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -1662,6 +1663,7 @@ static void CreateImGuiBackend() InitImGuiUtils(); OptionsMenu::Init(); InstallerWizard::Init(); + DevTitleMenu::Init(); ImGui_ImplSDL3_InitForOther(GameWindow::s_pWindow); @@ -3035,6 +3037,7 @@ static void DrawImGui() ResetImGuiCallbacks(); UpdateImGuiUtils(); + DevTitleMenu::Draw(); AchievementMenu::Draw(); OptionsMenu::Draw(); InstallerWizard::Draw(); diff --git a/MarathonRecomp/patches/misc_patches.cpp b/MarathonRecomp/patches/misc_patches.cpp index 985beb477..b31302a5e 100644 --- a/MarathonRecomp/patches/misc_patches.cpp +++ b/MarathonRecomp/patches/misc_patches.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include @@ -222,4 +222,94 @@ PPC_FUNC(sub_82188460) ctx.r3.u64 = Config::Subtitles; } +// Add audio to MessageReceiver(StageSelectMode) +PPC_FUNC_IMPL(__imp__sub_824A8FF0); +PPC_FUNC(sub_824A8FF0) +{ + auto pMessage = (DevMessage*)(base + ctx.r4.u32); + DevTitleMenu::AudioCommon(pMessage); + __imp__sub_824A8FF0(ctx, base); +} + +// Add audio to MessageReceiver(DevTitleMode) +PPC_FUNC_IMPL(__imp__sub_824A0E38); +PPC_FUNC(sub_824A0E38) +{ + auto pMessage = (DevMessage*)(base + ctx.r4.u32); + auto pTitle = (StateDevTitle*)(base + 0x82D35E6C); + if (pTitle->m_PictureTitle->m_State3 >= 4) + return; + + if (pMessage->id == 0x10002) + { + if (pMessage->id2 == 0x5A || pMessage->id3) + { + pTitle->m_PictureTitle->m_State3 = 4; + DevTitleDelay = DevTitleDelayDefault; + } + else + { + return; + } + } + + DevTitleMenu::AudioCommon(pMessage); + __imp__sub_824A0E38(ctx, base); +} + +PPC_FUNC_IMPL(__imp__sub_82161AB8); +PPC_FUNC(sub_82161AB8) +{ + if (!Config::DevTitle) + { + __imp__sub_82161AB8(ctx, base); + return; + } + + GuestToHostFunction(sub_825E9E28, ctx.r3.u32, ctx.r4.u32); + + // Initialize MainMode + if (!*(uint32_t*)(base + 0x82D35E48)) + *(be*)(base + 0x82D35E48) = GuestToHostFunction(sub_82161150); + + // Initialize DevMode + if (!*(uint32_t*)(base + 0x82D35E78)) + *(be*)(base + 0x82D35E78) = GuestToHostFunction(sub_82161860); + + auto pDevMode = (*(be*)(base + 0x82D35E78)).get(); + + // DocSetMode + GuestToHostFunction(sub_825E8D78, ctx.r3.u32, pDevMode); +} + +// tateDevTitle, vft + 8 +PPC_FUNC_IMPL(__imp__sub_82162298); +PPC_FUNC(sub_82162298) +{ + auto pTitle = (StateDevTitle*)(base + ctx.r3.u32); + + if (ctx.r3.u32 && pTitle->m_PictureTitle && pTitle->m_PictureTitle->m_State3 > 4) + { + DevTitleDelay = std::clamp(DevTitleDelay - 0.016666668f, 0.0f, DevTitleDelay); + } + else + { + DevTitleDelay = DevTitleDelayDefault; + } + + if (!GuestToHostFunction(sub_82581C68, ctx.r4.u32)) + { + // Wait for transistion + GuestToHostFunction(sub_824A0870, pTitle->m_PictureTitle.ptr.get(), 0.016666668); + + // Force to wait transistion + if (pTitle->m_PictureTitle->m_State3 != 6) + { + return; + } + } + + __imp__sub_82162298(ctx, base); +} + void NOP() {} diff --git a/MarathonRecomp/ui/dev_title_menu.cpp b/MarathonRecomp/ui/dev_title_menu.cpp index 847d961d3..890edeb8f 100644 --- a/MarathonRecomp/ui/dev_title_menu.cpp +++ b/MarathonRecomp/ui/dev_title_menu.cpp @@ -1,5 +1,304 @@ -// -// Created by Isaac Marovitz on 12/10/2026. -// - #include "dev_title_menu.h" +#include "imgui_utils.h" +#include +#include +#include +#include + +static ImFont* g_mrodinFont; +static ImFont* g_mnewRodinFont; +const float ScrollAmount = 0.25f; +bool DevTitleMenu::IsVisible = true; + +enum CenterFlag +{ + X = 1, + Y = 2 +}; + +struct DevTitleMenuSTable +{ + struct + { + xpointer m_name; + xpointer m_mode; + } m_state[5]; +}; + +static ImVec2 GetMousePos() +{ + return ImGui::GetIO().MousePos; +} + +static void DrawBackgroundDev() +{ + auto& res = ImGui::GetIO().DisplaySize; + auto drawList = ImGui::GetBackgroundDrawList(); + + auto color = IM_COL32(0, 0, 160, 255); + drawList->AddRectFilled({ 0.0, 0.0 }, res, color); +} + +static void DrawTextDev(const char* text, float posX, float posY, float vfontSize= 24, uint32_t alpha = 255, uint32_t center = X) +{ + auto fontSize = Scale(vfontSize); + auto textSize = g_mnewRodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, text); + + float X = Video::s_viewportWidth * posX; + float Y = Video::s_viewportHeight * posY; + + if (center & CenterFlag::X) + X -= textSize.x * 0.5f; + + if (center & CenterFlag::Y) + Y -= textSize.y * 0.5f; + + DrawTextWithOutline + ( + g_mnewRodinFont, + fontSize, + { X,Y }, + IM_COL32(255, 255, 255, alpha), + text, + 4, + IM_COL32(0, 0, 0, alpha) + ); + + ResetTextSkew(); +} + +static bool Sonic06Button(ImVec2 pos, const char* label, bool& hovered, const ImVec2& sizeArg = {}, + bool selected = false, uint32_t centerFlags = 0, bool group = false) +{ + auto* drawList = ImGui::GetBackgroundDrawList(); + const float fontSize = sizeArg.y / 2.5f; + const float gutter = fontSize * 1.5f; + + const auto labelSize = g_mnewRodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, label); + const auto size = ImVec2(sizeArg.x ? sizeArg.x : gutter + labelSize.x, sizeArg.y ? sizeArg.y : labelSize.y); + + const auto mouse_pos = GetMousePos(); + const ImRect bb(pos, { pos.x + size.x, pos.y + size.y }); + const ImRect clip(drawList->GetClipRectMin(), drawList->GetClipRectMax()); + hovered = bb.Contains(mouse_pos) && clip.Contains(mouse_pos); + const bool pressed = hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left); + + // Background + const ImU32 bg_col = selected ? IM_COL32(255, 255, 255, 60) : hovered ? IM_COL32(255, 255, 255, 25) : 0; + drawList->AddRectFilled(bb.Min, bb.Max, bg_col); + + // Text, indented past the gutter so labels align with and without a chevron + ImVec2 textPos = { pos.x + gutter, pos.y }; + + if (centerFlags & X) + textPos.x = bb.GetCenter().x - labelSize.x * 0.5f; + if (centerFlags & Y) + textPos.y = bb.GetCenter().y - labelSize.y * 0.5f; + + drawList->AddText(g_mnewRodinFont, fontSize, { textPos.x + 1,textPos.y + 1 }, IM_COL32(0, 0, 0, 120), label); + drawList->AddText(g_mnewRodinFont, fontSize, textPos, IM_COL32(255, 255, 255, 255), label); + + if (group) + { + // Chevron centred in the gutter to mark items that open a sub-list + const float h = fontSize * 0.25f; + const float x = pos.x + gutter * 0.5f; + const float y = textPos.y + labelSize.y * 0.5f; + drawList->AddTriangleFilled({ x - h, y - h }, { x + h, y }, { x - h, y + h }, IM_COL32(255, 255, 255, 255)); + } + + return pressed; +} + +static void HandleStageMapSelectorInput(Sonicteam::StageSelectMode* pMode, uintptr_t pMsgRec) +{ + auto& io = ImGui::GetIO(); + + // Mouse wheel scrolling + if (abs(io.MouseWheel) > ScrollAmount) + { + guest_stack_var vMsgRec(0x10001, io.MouseWheel > 0 ? 0 : 0xB4, 130); + GuestToHostFunction(sub_824A8FF0, pMsgRec, vMsgRec.get()); + } + + // Right click handling + if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) + { + guest_stack_var vMsgRec(0x10002, 0, 0); + GuestToHostFunction(sub_824A8FF0, pMsgRec, vMsgRec.get()); + } +} + +static void DrawStageMapSelector() +{ + auto& io = ImGui::GetIO(); + auto& res = io.DisplaySize; + auto& config = s_StageMapSelectorConfig; + + auto* pMode = App::s_pApp->m_pDoc->GetDocMode(); + if (!pMode) return; + + DrawBackgroundDev(); + + // Calculate layout + const int visibleItems = config.visibleItems; + const float buttonHeight = config.buttonHeightRatio * res.y; + const float rowHeight = buttonHeight * config.leading; + const ImVec2 basePos = { + config.paddingRatio.x * res.x, + config.paddingRatio.y * res.y + }; + + auto& items = pMode->m_CurrentStageMap->m_vpStageMap; + const int itemCount = static_cast(items.size()); + int currentIdx = pMode->m_CurrentStageMapIndex.get(); + + // Handle input + auto pMsgRec = reinterpret_cast(static_cast(pMode)); + HandleStageMapSelectorInput(pMode, pMsgRec); + + // Ease the scroll offset towards the selection, snapping when the list changes + const int maxStart = std::max(0, itemCount - visibleItems); + const float target = static_cast(std::clamp(currentIdx - visibleItems / 2, 0, maxStart)); + const void* pStageMap = pMode->m_CurrentStageMap.get(); + + if (pStageMap != s_pLastStageMap) + { + s_StageMapScroll = target; + s_pLastStageMap = pStageMap; + } + else + { + s_StageMapScroll += (target - s_StageMapScroll) * (1.0f - expf(-config.scrollSpeed * io.DeltaTime)); + } + + // Calculate visible range, including partially visible items + const int startIdx = static_cast(s_StageMapScroll); + const int endIdx = std::min(itemCount, static_cast(ceilf(s_StageMapScroll)) + visibleItems); + + // Clip items scrolling in and out of the list + auto* drawList = ImGui::GetBackgroundDrawList(); + drawList->PushClipRect(basePos, { res.x, basePos.y + visibleItems * rowHeight }); + + // Draw visible items + for (int i = startIdx; i < endIdx; ++i) + { + const auto& item = items[i]; + if (!item) continue; + + const bool isSelected = i == currentIdx; + bool hovered = false; + + // Calculate button position + const ImVec2 buttonPos = { + basePos.x, + basePos.y + (i - s_StageMapScroll) * rowHeight + }; + + // Draw button + if (Sonic06Button(buttonPos, item->m_Name.c_str(), hovered, + ImVec2(config.windowWidthRatio * res.x - 20, buttonHeight), + isSelected, Y, item->m_vpStageMap.size() > 1)) + { + currentIdx = i; + pMode->m_CurrentStageMapIndex = i; + guest_stack_var vMsgRec(0x10002, 0x5A, 1); + GuestToHostFunction(sub_824A8FF0, pMsgRec, vMsgRec.get()); + } + } + + drawList->PopClipRect(); +} + +static void DrawDevTitle() +{ + auto& io = ImGui::GetIO(); + auto* pMode = App::s_pApp->m_pDoc->GetDocMode(); + if (!pMode) + return; + + auto pDevTitle = App::s_pApp->m_pDoc->GetDocMode(); + auto pIndex = pDevTitle->m_Selected.get(); + auto pDevTitleMenuSTable = static_cast(g_memory.Translate(0x82B10A40)); + auto pName = pDevTitleMenuSTable->m_state[pIndex].m_name.get(); + + DrawTextDev(pName, 0.5, 0.95, 24, DevTitleDelay * 255, X | Y); + auto pMsgRec = (uintptr_t)static_cast(pMode); + + if (abs(io.MouseWheel) > ScrollAmount) + { + guest_stack_var vMsgRec(0x10001, io.MouseWheel >= 0 ? 0 : 0xB4, 130); + GuestToHostFunction(sub_824A0E38, static_cast(pMsgRec), vMsgRec.get()); + } + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) + { + guest_stack_var vMsgRec(0x10002, 0, 1); + GuestToHostFunction(sub_824A0E38, static_cast(pMsgRec), vMsgRec.get()); + } +} + +void DevTitleMenu::Init() +{ + if (!Config::DevTitle) + return; + + g_mrodinFont = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); + g_mnewRodinFont = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); +} + +void DevTitleMenu::Draw() +{ + if (!Config::DevTitle) + return; + + if (!App::s_pApp) + return; + + if (!App::s_pApp->m_pDoc) + return; + + if (!App::s_pApp->m_pDoc->m_pDocMode) + return; + + if (!EmbeddedPlayer::s_isActive) + EmbeddedPlayer::Init(); + + auto vft = App::s_pApp->m_pDoc->GetDocMode()->m_pVftable.ptr.get(); + switch (vft) + { + // DevTitleMode + case 0x8202B1E0: + IsVisible = true; + DrawDevTitle(); + break; + // StageSelect/StoreSelect + case 0x820336A4: + IsVisible = true; + DrawStageMapSelector(); + break; + default: + IsVisible = false; + break; + } +} + +void DevTitleMenu::AudioCommon(DevMessage* message) +{ + switch (message->id) + { + case 0x10001: + Game_PlaySound("move"); + break; + case 0x10002: + if (message->id2 == 0x5A || message->id3) + Game_PlaySound("main_deside"); + else + Game_PlaySound("window_close"); + break; + case 0: + if (!message->id3) + Game_PlaySound("window_close"); + break; + } +} diff --git a/MarathonRecomp/ui/dev_title_menu.h b/MarathonRecomp/ui/dev_title_menu.h index 044482021..56f5988de 100644 --- a/MarathonRecomp/ui/dev_title_menu.h +++ b/MarathonRecomp/ui/dev_title_menu.h @@ -1,8 +1,45 @@ -// -// Created by Isaac Marovitz on 12/10/2026. -// +#pragma once -#ifndef MARATHONRECOMP_ALL_DEV_TITLE_MENU_H -#define MARATHONRECOMP_ALL_DEV_TITLE_MENU_H +#include -#endif //MARATHONRECOMP_ALL_DEV_TITLE_MENU_H +constexpr float DevTitleDelayDefault = 1.0; +static float DevTitleDelay = DevTitleDelayDefault; + +struct StageMapSelectorConfig { + // Layout + float windowWidthRatio = 0.8f; + float buttonHeightRatio = 0.04f; + float leading = 1.25f; + int visibleItems = 16; + + // Positioning + ImVec2 paddingRatio = { 0.05f, 0.1f }; + + // Animation + float scrollSpeed = 16.0f; +}; + +static StageMapSelectorConfig s_StageMapSelectorConfig; +static float s_StageMapScroll = 0.0f; +static const void* s_pLastStageMap = nullptr; + +struct DevMessage +{ + be id; + be id2; + uint8_t id3; +}; + +class DevTitleMenu +{ +public: + static bool IsVisible; + + static void Init(); + static void Draw(); + + static void AudioCommon(DevMessage* message); + + static void Close(); + static bool CanClose(); +}; diff --git a/MarathonRecomp/user/config_def.h b/MarathonRecomp/user/config_def.h index a83185975..acb6494d9 100644 --- a/MarathonRecomp/user/config_def.h +++ b/MarathonRecomp/user/config_def.h @@ -108,6 +108,7 @@ CONFIG_DEFINE_HIDDEN("Codes", bool, AntigravityRetainsMomentum, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableBoundAttack, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableSpinkick, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableTeleportDash, false, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, DevTitle, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, DisableDWMRoundedCorners, false, true); CONFIG_DEFINE_HIDDEN("Codes", bool, DisableEdgeGrabLeftover, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, DisableKingdomValleyMist, false, false); From ef49d11a9ff36efd6d0456f52c82313208b5d3ce Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Wed, 28 Oct 2026 00:57:44 +0000 Subject: [PATCH 03/15] Implemented CSD scaling for DevTitle --- .../patches/aspect_ratio_patches.cpp | 63 ++++++++++++++++++- MarathonRecomp/patches/aspect_ratio_patches.h | 5 +- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/MarathonRecomp/patches/aspect_ratio_patches.cpp b/MarathonRecomp/patches/aspect_ratio_patches.cpp index 8cdb242ed..6899b0fa9 100644 --- a/MarathonRecomp/patches/aspect_ratio_patches.cpp +++ b/MarathonRecomp/patches/aspect_ratio_patches.cpp @@ -521,7 +521,33 @@ void Draw(PPCContext& ctx, uint8_t* base, PPCFunc* original, uint32_t stride) if ((modifier.Flags & CSD_CHEVRON) != 0 && g_aspectRatio <= WIDE_ASPECT_RATIO) offsetX = 0.0f; - if ((modifier.Flags & CSD_SCALE) != 0) + if ((modifier.Flags & CSD_CROP) != 0) + { + scaleX = std::max(vpWidth / 1280.0f, vpHeight / 720.0f); + + if ((modifier.Flags & CSD_ALIGN_RIGHT) != 0) + offsetX = vpWidth - (1280.0f * scaleX); + else if ((modifier.Flags & CSD_ALIGN_LEFT) != 0) + offsetX = 0.0f; + else + offsetX = (vpWidth - (1280.0f * scaleX)) * 0.5f; + + offsetX += pivotX * scaleX; + } + else if ((modifier.Flags & CSD_FIT) != 0) + { + const auto fitScale = std::min((vpWidth / 1280.0f) / g_aspectRatioScale, 1.0f); + + scaleX *= fitScale; + + if ((modifier.Flags & CSD_ALIGN_RIGHT) != 0) + offsetX += 1280.0f * (1.0f - fitScale) * g_aspectRatioScale; + else if ((modifier.Flags & CSD_ALIGN_LEFT) == 0) + offsetX += g_aspectRatioOffsetX + 640.0f * (1.0f - fitScale) * g_aspectRatioScale - g_aspectRatioOffsetX; + + offsetX += pivotX * g_aspectRatioScale; + } + else if ((modifier.Flags & CSD_SCALE) != 0) { scaleX *= g_aspectRatioGameplayScale; @@ -548,7 +574,33 @@ void Draw(PPCContext& ctx, uint8_t* base, PPCFunc* original, uint32_t stride) else if ((modifier.Flags & CSD_ALIGN_TOP) == 0) offsetY = g_aspectRatioOffsetY; - if ((modifier.Flags & CSD_SCALE) != 0) + if ((modifier.Flags & CSD_CROP) != 0) + { + scaleY = std::max(vpWidth / 1280.0f, vpHeight / 720.0f); + + if ((modifier.Flags & CSD_ALIGN_BOTTOM) != 0) + offsetY = vpHeight - (720.0f * scaleY); + else if ((modifier.Flags & CSD_ALIGN_TOP) != 0) + offsetY = 0.0f; + else + offsetY = (vpHeight - (720.0f * scaleY)) * 0.5f; + + offsetY += pivotY * scaleY; + } + else if ((modifier.Flags & CSD_FIT) != 0) + { + const auto fitScale = std::min((vpWidth / 1280.0f) / g_aspectRatioScale, 1.0f); + + scaleY *= fitScale; + + if ((modifier.Flags & CSD_ALIGN_BOTTOM) != 0) + offsetY += 720.0f * (1.0f - fitScale) * g_aspectRatioScale; + else if ((modifier.Flags & CSD_ALIGN_TOP) == 0) + offsetY += g_aspectRatioOffsetY + 360.0f * (1.0f - fitScale) * g_aspectRatioScale - g_aspectRatioOffsetY; + + offsetY += pivotY * g_aspectRatioScale; + } + else if ((modifier.Flags & CSD_SCALE) != 0) { scaleY *= g_aspectRatioGameplayScale; @@ -2265,6 +2317,13 @@ const xxHashMap g_csdModifiers = { HashStr("sprite/title/title/Scene_Title/Logo"), { CSD_SCALE } }, { HashStr("sprite/title/title/Scene_Title/copyright"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_MODIFIER_NARROW_ONLY } }, + // title (2005) + { HashStr("/back_aoita/Null_0064"), { CSD_CROP | CSD_ALIGN_LEFT } }, + { HashStr("/title_menu/Null_0030kage"), { CSD_FIT } }, + { HashStr("/title_menu/Cast_0157"), { CSD_SCALE | CSD_ALIGN_BOTTOM_RIGHT } }, + { HashStr("/title_menu/Null_0030"), { CSD_FIT } }, + { HashStr("/title_menu/Null_0030boke"), { CSD_FIT } }, + // titleloop_sth { HashStr("sprite/logo/titleloop_sth/Scene_0000"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE | CSD_MOVIE | CSD_MODIFIER_NARROW_ONLY } }, diff --git a/MarathonRecomp/patches/aspect_ratio_patches.h b/MarathonRecomp/patches/aspect_ratio_patches.h index 15303cd09..61d51b59d 100644 --- a/MarathonRecomp/patches/aspect_ratio_patches.h +++ b/MarathonRecomp/patches/aspect_ratio_patches.h @@ -105,7 +105,10 @@ enum CsdFlags : uint64_t CSD_MAIN_MENU_PARTS_CAST_0226 = MAKE_BITFLAG64(39), CSD_MAIN_MENU_PARTS_CAST_0227 = MAKE_BITFLAG64(40), - CSD_BUTTON_WINDOW = MAKE_BITFLAG64(41) + CSD_BUTTON_WINDOW = MAKE_BITFLAG64(41), + + CSD_CROP = MAKE_BITFLAG64(42), + CSD_FIT = MAKE_BITFLAG64(43) }; struct CsdUVs From a0d02e2faf5143f2b1b7efc3d7182814a43af65c Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Wed, 28 Oct 2026 03:29:19 +0000 Subject: [PATCH 04/15] API: clean-up --- MarathonRecomp/api/Marathon.h | 2 ++ MarathonRecomp/api/Sonicteam/DevTitleMode.h | 31 ++++++++----------- MarathonRecomp/api/Sonicteam/PicBase.h | 6 ++++ MarathonRecomp/api/Sonicteam/PictureTitle.h | 13 +++++--- MarathonRecomp/api/Sonicteam/SoX/Engine/Doc.h | 4 +-- .../api/Sonicteam/SoX/Engine/DocMode.h | 3 +- .../api/Sonicteam/SoX/Input/Listener.h | 4 +-- MarathonRecomp/api/Sonicteam/StageMap.h | 7 ++++- .../api/Sonicteam/StageSelectMode.h | 19 +++++++++--- MarathonRecomp/api/Sonicteam/StateDevTitle.h | 16 ++++++++++ MarathonRecomp/api/Sonicteam/StateMainMode.h | 10 ++++++ .../api/Sonicteam/System/Singleton.h | 3 +- MarathonRecomp/ui/dev_title_menu.cpp | 10 +++--- 13 files changed, 88 insertions(+), 40 deletions(-) create mode 100644 MarathonRecomp/api/Sonicteam/StateDevTitle.h create mode 100644 MarathonRecomp/api/Sonicteam/StateMainMode.h diff --git a/MarathonRecomp/api/Marathon.h b/MarathonRecomp/api/Marathon.h index e5ac199f5..cf37b2272 100644 --- a/MarathonRecomp/api/Marathon.h +++ b/MarathonRecomp/api/Marathon.h @@ -293,6 +293,8 @@ #include "Sonicteam/StageMap.h" #include "Sonicteam/StageSelectMode.h" #include "Sonicteam/StageTitleTask.h" +#include "Sonicteam/StateDevTitle.h" +#include "Sonicteam/StateMainMode.h" #include "Sonicteam/StdImageFilters/BurnoutBlurFilter.h" #include "Sonicteam/StdImageFilters/SingleTechniqueFilter.h" #include "Sonicteam/System/CreateStatic.h" diff --git a/MarathonRecomp/api/Sonicteam/DevTitleMode.h b/MarathonRecomp/api/Sonicteam/DevTitleMode.h index 03fe5ca37..960b70bf5 100644 --- a/MarathonRecomp/api/Sonicteam/DevTitleMode.h +++ b/MarathonRecomp/api/Sonicteam/DevTitleMode.h @@ -1,34 +1,29 @@ #pragma once #include -#include -#include -#include #include - -// Anonymous namespace -namespace -{ - class StateDevTitle : public Sonicteam::DocState - { - public: - xpointer m_PictureTitle; - }; -} +#include namespace Sonicteam { class DevTitleMode : public SoX::Engine::DocMode { + public: struct Mode { - xpointer m_Name; - MARATHON_INSERT_PADDING(4); + xpointer Name; + MARATHON_INSERT_PADDING(0x04); }; - public: - be m_Selected; - MARATHON_INSERT_PADDING(4); + be m_SelectedIndex; + MARATHON_INSERT_PADDING(0x04); stdx::vector m_Modes; + MARATHON_INSERT_PADDING(0x2C); }; + + MARATHON_ASSERT_OFFSETOF(DevTitleMode::Mode, Name, 0x00); + + MARATHON_ASSERT_OFFSETOF(DevTitleMode, m_SelectedIndex, 0x50); + MARATHON_ASSERT_OFFSETOF(DevTitleMode, m_Modes, 0x58); + MARATHON_ASSERT_SIZEOF(DevTitleMode, 0x94); } diff --git a/MarathonRecomp/api/Sonicteam/PicBase.h b/MarathonRecomp/api/Sonicteam/PicBase.h index 7c7bcc1a3..c9b44e153 100644 --- a/MarathonRecomp/api/Sonicteam/PicBase.h +++ b/MarathonRecomp/api/Sonicteam/PicBase.h @@ -12,4 +12,10 @@ namespace Sonicteam be m_State2; be m_State3; }; + + MARATHON_ASSERT_OFFSETOF(PicBase, m_pVftable, 0x00); + MARATHON_ASSERT_OFFSETOF(PicBase, m_State1, 0x04); + MARATHON_ASSERT_OFFSETOF(PicBase, m_State2, 0x08); + MARATHON_ASSERT_OFFSETOF(PicBase, m_State3, 0x0C); + MARATHON_ASSERT_SIZEOF(PicBase, 0x10); } diff --git a/MarathonRecomp/api/Sonicteam/PictureTitle.h b/MarathonRecomp/api/Sonicteam/PictureTitle.h index eb90506e0..edadd8229 100644 --- a/MarathonRecomp/api/Sonicteam/PictureTitle.h +++ b/MarathonRecomp/api/Sonicteam/PictureTitle.h @@ -1,17 +1,20 @@ #pragma once #include -#include #include #include +#include namespace Sonicteam { - class PictureTitle:public PicBase, public SoX::Input::Listener + class PictureTitle : public PicBase, public SoX::Input::Listener { public: - MARATHON_INSERT_PADDING(4); - xpointer m_Doc; - MARATHON_INSERT_PADDING(0x18); + MARATHON_INSERT_PADDING(0x04); + xpointer m_pDoc; + MARATHON_INSERT_PADDING(0x20); }; + + MARATHON_ASSERT_OFFSETOF(PictureTitle, m_pDoc, 0x2C); + MARATHON_ASSERT_SIZEOF(PictureTitle, 0x50); } diff --git a/MarathonRecomp/api/Sonicteam/SoX/Engine/Doc.h b/MarathonRecomp/api/Sonicteam/SoX/Engine/Doc.h index 823d9205d..1985465bf 100644 --- a/MarathonRecomp/api/Sonicteam/SoX/Engine/Doc.h +++ b/MarathonRecomp/api/Sonicteam/SoX/Engine/Doc.h @@ -19,11 +19,11 @@ namespace Sonicteam::SoX::Engine }; xpointer m_pVftable; - MARATHON_INSERT_PADDING(4); + MARATHON_INSERT_PADDING(0x04); xpointer m_pDocMode; xpointer m_pRootTask; xpointer m_pRootGTask; - MARATHON_INSERT_PADDING(8); + MARATHON_INSERT_PADDING(0x08); xpointer m_pDocModeExecutor; xpointer m_pRenderScheduler; XRTL_CRITICAL_SECTION m_CriticalSection1; diff --git a/MarathonRecomp/api/Sonicteam/SoX/Engine/DocMode.h b/MarathonRecomp/api/Sonicteam/SoX/Engine/DocMode.h index 42e8f6f60..cbe31d56e 100644 --- a/MarathonRecomp/api/Sonicteam/SoX/Engine/DocMode.h +++ b/MarathonRecomp/api/Sonicteam/SoX/Engine/DocMode.h @@ -8,8 +8,9 @@ namespace Sonicteam::SoX::Engine class DocMode : public Task { public: - be m_type; + be m_Type; }; + MARATHON_ASSERT_OFFSETOF(DocMode, m_Type, 0x4C); MARATHON_ASSERT_SIZEOF(DocMode, 0x50); } diff --git a/MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h b/MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h index 14b6a0162..9ce2216b4 100644 --- a/MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h +++ b/MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h @@ -5,9 +5,9 @@ namespace Sonicteam::SoX::Input { - class Listener : Object + class Listener : public Object { public: - MARATHON_INSERT_PADDING(0x14); // LinkSoxNode LinkInput, flags + MARATHON_INSERT_PADDING(0x14); // LinkNode LinkInput, Flags }; } diff --git a/MarathonRecomp/api/Sonicteam/StageMap.h b/MarathonRecomp/api/Sonicteam/StageMap.h index 75dfe884a..45e5bf9f4 100644 --- a/MarathonRecomp/api/Sonicteam/StageMap.h +++ b/MarathonRecomp/api/Sonicteam/StageMap.h @@ -12,6 +12,11 @@ namespace Sonicteam public: stdx::string m_Name; stdx::string m_Text; - stdx::vector> m_vpStageMap; + stdx::vector> m_Children; }; + + MARATHON_ASSERT_OFFSETOF(StageMap, m_Name, 0x20); + MARATHON_ASSERT_OFFSETOF(StageMap, m_Text, 0x3C); + MARATHON_ASSERT_OFFSETOF(StageMap, m_Children, 0x58); + MARATHON_ASSERT_SIZEOF(StageMap, 0x68); } diff --git a/MarathonRecomp/api/Sonicteam/StageSelectMode.h b/MarathonRecomp/api/Sonicteam/StageSelectMode.h index 13b4bd271..79c0a2a3e 100644 --- a/MarathonRecomp/api/Sonicteam/StageSelectMode.h +++ b/MarathonRecomp/api/Sonicteam/StageSelectMode.h @@ -9,15 +9,24 @@ namespace Sonicteam class StageSelectMode : public SoX::Engine::DocMode { public: - xpointer m_StageMap; + xpointer m_RootStageMap; xpointer m_CurrentStageMap; - MARATHON_INSERT_PADDING(4); - be m_CurrentStageMapIndex; - be m_Deep; + MARATHON_INSERT_PADDING(0x04); + be m_StageMapIndex; + be m_StageMapDepth; MARATHON_INSERT_PADDING(0x1C); xpointer> m_StageMapInfo; xpointer m_StageMapType; xpointer m_StageMapName; - MARATHON_INSERT_PADDING(0x14); + MARATHON_INSERT_PADDING(0x04); }; + + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_RootStageMap, 0x50); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_CurrentStageMap, 0x54); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapIndex, 0x5C); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapDepth, 0x60); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapInfo, 0x80); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapType, 0x84); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapName, 0x88); + MARATHON_ASSERT_SIZEOF(StageSelectMode, 0x90); } diff --git a/MarathonRecomp/api/Sonicteam/StateDevTitle.h b/MarathonRecomp/api/Sonicteam/StateDevTitle.h new file mode 100644 index 000000000..a60470282 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/StateDevTitle.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include +#include +#include + +class StateDevTitle : public Sonicteam::DocState, public Sonicteam::System::Singleton> +{ +public: + xpointer m_pPictureTitle; +}; + +MARATHON_ASSERT_OFFSETOF(StateDevTitle, m_pPictureTitle, 0x04); +MARATHON_ASSERT_SIZEOF(StateDevTitle, 0x08); diff --git a/MarathonRecomp/api/Sonicteam/StateMainMode.h b/MarathonRecomp/api/Sonicteam/StateMainMode.h new file mode 100644 index 000000000..a45e159d4 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/StateMainMode.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include +#include + +class StateMainMode : public Sonicteam::DocState, public Sonicteam::System::Singleton> {}; + +MARATHON_ASSERT_SIZEOF(StateMainMode, 0x04); diff --git a/MarathonRecomp/api/Sonicteam/System/Singleton.h b/MarathonRecomp/api/Sonicteam/System/Singleton.h index 11ac491f4..163d50a73 100644 --- a/MarathonRecomp/api/Sonicteam/System/Singleton.h +++ b/MarathonRecomp/api/Sonicteam/System/Singleton.h @@ -5,12 +5,13 @@ namespace Sonicteam::System template class Singleton { + private: inline static TCreator ms_Creator{}; public: static T* GetInstance() { - auto pInstance = (xpointer*)g_memory.Translate(pSingleton); + const auto pInstance = reinterpret_cast*>(g_memory.Translate(pSingleton)); if (!pInstance->ptr.get()) *pInstance = ms_Creator.Create(); diff --git a/MarathonRecomp/ui/dev_title_menu.cpp b/MarathonRecomp/ui/dev_title_menu.cpp index 890edeb8f..3ae26bd7d 100644 --- a/MarathonRecomp/ui/dev_title_menu.cpp +++ b/MarathonRecomp/ui/dev_title_menu.cpp @@ -149,9 +149,9 @@ static void DrawStageMapSelector() config.paddingRatio.y * res.y }; - auto& items = pMode->m_CurrentStageMap->m_vpStageMap; + auto& items = pMode->m_CurrentStageMap->m_Children; const int itemCount = static_cast(items.size()); - int currentIdx = pMode->m_CurrentStageMapIndex.get(); + int currentIdx = pMode->m_StageMapIndex.get(); // Handle input auto pMsgRec = reinterpret_cast(static_cast(pMode)); @@ -198,10 +198,10 @@ static void DrawStageMapSelector() // Draw button if (Sonic06Button(buttonPos, item->m_Name.c_str(), hovered, ImVec2(config.windowWidthRatio * res.x - 20, buttonHeight), - isSelected, Y, item->m_vpStageMap.size() > 1)) + isSelected, Y, item->m_Children.size() > 1)) { currentIdx = i; - pMode->m_CurrentStageMapIndex = i; + pMode->m_StageMapIndex = i; guest_stack_var vMsgRec(0x10002, 0x5A, 1); GuestToHostFunction(sub_824A8FF0, pMsgRec, vMsgRec.get()); } @@ -218,7 +218,7 @@ static void DrawDevTitle() return; auto pDevTitle = App::s_pApp->m_pDoc->GetDocMode(); - auto pIndex = pDevTitle->m_Selected.get(); + auto pIndex = pDevTitle->m_SelectedIndex.get(); auto pDevTitleMenuSTable = static_cast(g_memory.Translate(0x82B10A40)); auto pName = pDevTitleMenuSTable->m_state[pIndex].m_name.get(); From c974d22b913dbf6ac24c22a856e8c6482ef2ae6d Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Wed, 28 Oct 2026 03:29:54 +0000 Subject: [PATCH 05/15] SkipIntroLogos -> BootDirectlyToTitle, DevTitle -> BootDirectlyToDevTitle --- MarathonRecomp/patches/misc_patches.cpp | 2 +- MarathonRecomp/ui/dev_title_menu.cpp | 6 ------ MarathonRecomp/user/config_def.h | 4 ++-- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/MarathonRecomp/patches/misc_patches.cpp b/MarathonRecomp/patches/misc_patches.cpp index b31302a5e..2e7886f15 100644 --- a/MarathonRecomp/patches/misc_patches.cpp +++ b/MarathonRecomp/patches/misc_patches.cpp @@ -211,7 +211,7 @@ bool Super3_DisableChangeRequestHint() PPC_FUNC_IMPL(__imp__sub_824A6EA8); PPC_FUNC(sub_824A6EA8) { - if ((App::s_isSkipLogos || Config::SkipIntroLogos) && ctx.r4.u32 == 1) + if ((App::s_isSkipLogos || Config::BootDirectlyToTitle) && ctx.r4.u32 == 1) ctx.r4.u32 = 4; __imp__sub_824A6EA8(ctx, base); diff --git a/MarathonRecomp/ui/dev_title_menu.cpp b/MarathonRecomp/ui/dev_title_menu.cpp index 3ae26bd7d..7fcefe667 100644 --- a/MarathonRecomp/ui/dev_title_menu.cpp +++ b/MarathonRecomp/ui/dev_title_menu.cpp @@ -240,18 +240,12 @@ static void DrawDevTitle() void DevTitleMenu::Init() { - if (!Config::DevTitle) - return; - g_mrodinFont = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); g_mnewRodinFont = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); } void DevTitleMenu::Draw() { - if (!Config::DevTitle) - return; - if (!App::s_pApp) return; diff --git a/MarathonRecomp/user/config_def.h b/MarathonRecomp/user/config_def.h index acb6494d9..882b481f2 100644 --- a/MarathonRecomp/user/config_def.h +++ b/MarathonRecomp/user/config_def.h @@ -105,10 +105,11 @@ CONFIG_DEFINE_ENUM_LOCALISED("Video", ECutsceneAspectRatio, CutsceneAspectRatio, CONFIG_DEFINE_ENUM_LOCALISED("Video", EUIAlignmentMode, UIAlignmentMode, EUIAlignmentMode::Edge, false); CONFIG_DEFINE_HIDDEN("Codes", bool, AntigravityRetainsMomentum, false, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, BootDirectlyToTitle, false, false); +CONFIG_DEFINE_HIDDEN("Codes", bool, BootDirectlyToDevTitle, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableBoundAttack, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableSpinkick, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, ControllableTeleportDash, false, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, DevTitle, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, DisableDWMRoundedCorners, false, true); CONFIG_DEFINE_HIDDEN("Codes", bool, DisableEdgeGrabLeftover, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, DisableKingdomValleyMist, false, false); @@ -130,7 +131,6 @@ CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreDemoCameraMode, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreLoadingTransition, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, RestorePauseMissionText, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, RestoreSonicActionGauge, false, false); -CONFIG_DEFINE_HIDDEN("Codes", bool, SkipIntroLogos, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, TailsGauge, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, UnlimitedAntigravity, false, false); CONFIG_DEFINE_HIDDEN("Codes", bool, UseOfficialAchievementText, false, false); From 2328130dc861abdb6ab895a6b62b2ca32ad4d195 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Wed, 28 Oct 2026 03:30:48 +0000 Subject: [PATCH 06/15] Implemented entering DevTitle via the title screen --- MarathonRecomp/CMakeLists.txt | 1 + MarathonRecomp/api/Sonicteam/DocMarathon.h | 21 ++++- MarathonRecomp/api/Sonicteam/DocMarathonImp.h | 4 +- .../patches/DevTitleMode_patches.cpp | 87 ++++++++++++++++++ MarathonRecomp/patches/TitleTask_patches.cpp | 64 ++++++++++--- MarathonRecomp/patches/misc_patches.cpp | 91 ------------------- 6 files changed, 160 insertions(+), 108 deletions(-) create mode 100644 MarathonRecomp/patches/DevTitleMode_patches.cpp diff --git a/MarathonRecomp/CMakeLists.txt b/MarathonRecomp/CMakeLists.txt index 66859daf7..b67cd90cf 100644 --- a/MarathonRecomp/CMakeLists.txt +++ b/MarathonRecomp/CMakeLists.txt @@ -145,6 +145,7 @@ set(MARATHON_RECOMP_PATCHES_CXX_SOURCES "patches/aspect_ratio_patches.cpp" "patches/audio_patches.cpp" "patches/camera_patches.cpp" + "patches/DevTitleMode_patches.cpp" "patches/fps_patches.cpp" "patches/frontend_listener.cpp" "patches/input_patches.cpp" diff --git a/MarathonRecomp/api/Sonicteam/DocMarathon.h b/MarathonRecomp/api/Sonicteam/DocMarathon.h index e62ea31fb..a60e5aa6b 100644 --- a/MarathonRecomp/api/Sonicteam/DocMarathon.h +++ b/MarathonRecomp/api/Sonicteam/DocMarathon.h @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include namespace Sonicteam @@ -11,14 +14,23 @@ namespace Sonicteam public: struct Vftable : public Doc::Vftable { - MARATHON_INSERT_PADDING(0x58); + MARATHON_INSERT_PADDING(0x48); + be fpSetDocState; + MARATHON_INSERT_PADDING(0x0C); be fpGetTextBookContext; MARATHON_INSERT_PADDING(0x60); be fpSetLoading; MARATHON_INSERT_PADDING(0x44); be fpGetLanguage; + MARATHON_INSERT_PADDING(0x4C); + be fpStartMainMode; }; + DocMarathon* SetDocState(DocState* in_pDocState) + { + return GuestToHostFunction(static_cast(m_pVftable.get())->fpSetDocState, this, in_pDocState); + } + TextBook::TextBookContext* GetTextBookContext() { return GuestToHostFunction(static_cast(m_pVftable.get())->fpGetTextBookContext, this); @@ -33,11 +45,18 @@ namespace Sonicteam { return GuestToHostFunction(static_cast(m_pVftable.get())->fpGetLanguage, this); } + + DocMarathon* StartMainMode() + { + return GuestToHostFunction(static_cast(m_pVftable.get())->fpStartMainMode, this); + } }; + MARATHON_ASSERT_OFFSETOF(DocMarathon::Vftable, fpSetDocState, 0x54); MARATHON_ASSERT_OFFSETOF(DocMarathon::Vftable, fpGetTextBookContext, 0x64); MARATHON_ASSERT_OFFSETOF(DocMarathon::Vftable, fpSetLoading, 0xC8); MARATHON_ASSERT_OFFSETOF(DocMarathon::Vftable, fpGetLanguage, 0x110); + MARATHON_ASSERT_OFFSETOF(DocMarathon::Vftable, fpStartMainMode, 0x160); MARATHON_ASSERT_SIZEOF(DocMarathon, 0x5C); } diff --git a/MarathonRecomp/api/Sonicteam/DocMarathonImp.h b/MarathonRecomp/api/Sonicteam/DocMarathonImp.h index 8c44ff72b..4b477a33a 100644 --- a/MarathonRecomp/api/Sonicteam/DocMarathonImp.h +++ b/MarathonRecomp/api/Sonicteam/DocMarathonImp.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -21,7 +22,7 @@ namespace Sonicteam class DocMarathonImp : public DocMarathon { public: - MARATHON_INSERT_PADDING(0x04); + xpointer m_pDocState; xpointer m_pMyGraphicsDevice; xpointer m_pSceneParamManager; MARATHON_INSERT_PADDING(0x34); @@ -48,6 +49,7 @@ namespace Sonicteam MARATHON_INSERT_PADDING(0x2C); }; + MARATHON_ASSERT_OFFSETOF(DocMarathonImp, m_pDocState, 0x5C); MARATHON_ASSERT_OFFSETOF(DocMarathonImp, m_pMyGraphicsDevice, 0x60); MARATHON_ASSERT_OFFSETOF(DocMarathonImp, m_pSceneParamManager, 0x64); MARATHON_ASSERT_OFFSETOF(DocMarathonImp, m_InputManagers, 0x9C); diff --git a/MarathonRecomp/patches/DevTitleMode_patches.cpp b/MarathonRecomp/patches/DevTitleMode_patches.cpp new file mode 100644 index 000000000..dceee093b --- /dev/null +++ b/MarathonRecomp/patches/DevTitleMode_patches.cpp @@ -0,0 +1,87 @@ +#include +#include +#include + +// Sonicteam::StageSelectMode::ProcessMessage +PPC_FUNC_IMPL(__imp__sub_824A8FF0); +PPC_FUNC(sub_824A8FF0) +{ + const auto pMessage = reinterpret_cast(base + ctx.r4.u32); + + DevTitleMenu::AudioCommon(pMessage); + + __imp__sub_824A8FF0(ctx, base); +} + +// Sonicteam::DevTitleMode::ProcessMessage +PPC_FUNC_IMPL(__imp__sub_824A0E38); +PPC_FUNC(sub_824A0E38) +{ + const auto pStateDevTitle = StateDevTitle::GetInstance(); + const auto pMessage = reinterpret_cast(base + ctx.r4.u32); + + if (pStateDevTitle->m_pPictureTitle->m_State3 >= 4) + return; + + if (pMessage->id == 0x10002) + { + if (pMessage->id2 == 0x5A || pMessage->id3) + { + pStateDevTitle->m_pPictureTitle->m_State3 = 4; + DevTitleDelay = DevTitleDelayDefault; + } + else + { + return; + } + } + + DevTitleMenu::AudioCommon(pMessage); + + __imp__sub_824A0E38(ctx, base); +} + +// Sonicteam::DocMarathonState::StartMainMode +PPC_FUNC_IMPL(__imp__sub_82161AB8); +PPC_FUNC(sub_82161AB8) +{ + if (!Config::BootDirectlyToDevTitle) + { + __imp__sub_82161AB8(ctx, base); + return; + } + + const auto pDoc = reinterpret_cast(base + ctx.r3.u32); + + GuestToHostFunction(sub_825E9E28, ctx.r3.u32, ctx.r4.u32); + + ctx.r3.u32 = g_memory.MapVirtual(pDoc->SetDocState(StateDevTitle::GetInstance())); +} + +// StateDevTitle::Update +PPC_FUNC_IMPL(__imp__sub_82162298); +PPC_FUNC(sub_82162298) +{ + const auto pStateDevTitle = reinterpret_cast(base + ctx.r3.u32); + + if (ctx.r3.u32 && pStateDevTitle->m_pPictureTitle && pStateDevTitle->m_pPictureTitle->m_State3 > 4) + { + DevTitleDelay = std::clamp(DevTitleDelay - 0.016666668f, 0.0f, DevTitleDelay); + } + else + { + DevTitleDelay = DevTitleDelayDefault; + } + + if (!GuestToHostFunction(sub_82581C68, ctx.r4.u32)) + { + // Wait for transition. + GuestToHostFunction(sub_824A0870, pStateDevTitle->m_pPictureTitle.ptr.get(), 0.016666668); + + // Force to wait transition. + if (pStateDevTitle->m_pPictureTitle->m_State3 != 6) + return; + } + + __imp__sub_82162298(ctx, base); +} diff --git a/MarathonRecomp/patches/TitleTask_patches.cpp b/MarathonRecomp/patches/TitleTask_patches.cpp index e60c87837..c32b0060b 100644 --- a/MarathonRecomp/patches/TitleTask_patches.cpp +++ b/MarathonRecomp/patches/TitleTask_patches.cpp @@ -19,7 +19,14 @@ static double g_titleExitOutroTime{}; static bool g_quitMessageOpen{}; static bool g_saveDataExists{}; -static bool g_isSecretDone{}; +enum class DevTitleState +{ + Idle, + Open, + Start +}; + +static DevTitleState g_devTitleState{}; static uint32_t g_secretFlags{}; enum @@ -91,27 +98,26 @@ bool TitleTask_RedirectStateTransitionToOutroAnim(PPCRegister& r31) PPC_FUNC_IMPL(__imp__sub_825126A0); PPC_FUNC(sub_825126A0) { - auto pTitleTask = (Sonicteam::TitleTask*)(base + ctx.r3.u32); + auto pTitleTask = reinterpret_cast(base + ctx.r3.u32); auto deltaTime = ctx.f1.f64; switch (pTitleTask->m_State) { case Sonicteam::TitleTask::TitleState_Open: { - if (!Config::DisableTitleInputDelay) - break; - // Skip open animation. - GuestToHostFunction(sub_82511CA0, pTitleTask, (int)Sonicteam::TitleTask::TitleState_Wait); + if (Config::DisableTitleInputDelay) + GuestToHostFunction(sub_82511CA0, pTitleTask, int(Sonicteam::TitleTask::TitleState_Wait)); break; } case Sonicteam::TitleTask::TitleState_Wait: { - if (g_isSecretDone) + if (OptionsMenu::s_isDebugUnlocked) break; + // TODO: fix this being delayed for too long without "Disable Title Input Delay" code. if (auto& spInputManager = App::s_pApp->m_pDoc->m_InputManagers[0]) { auto& rPadState = spInputManager->PadState; @@ -131,8 +137,9 @@ PPC_FUNC(sub_825126A0) if (g_secretFlags == SECRET_ALL) { - Game_PlaySound("totalring_count"); - g_isSecretDone = true; + LOGFN_WARNING("Secret unlocked!"); + Game_PlaySound("totalring_count"); // TODO: why no worky... + OptionsMenu::s_isDebugUnlocked = true; } break; @@ -148,7 +155,7 @@ PPC_FUNC(sub_825126A0) rPadState.IsDown(Sonicteam::SoX::Input::PadButton_South) && rPadState.IsDown(Sonicteam::SoX::Input::PadButton_Start)) { - OptionsMenu::s_isDebugUnlocked = true; + g_devTitleState = DevTitleState::Open; } } @@ -165,13 +172,22 @@ PPC_FUNC(sub_825126A0) AchievementManager::Reset(); } - if (!Config::DisableTitleInputDelay) - { - g_titleProceedOutroTime += deltaTime; + g_titleProceedOutroTime += deltaTime; - // Wait for outro animation to complete before entering menu. - if (g_titleProceedOutroTime > TITLE_OPTION_OUTRO_TOTAL_FRAMES) + if (Config::DisableTitleInputDelay) + g_titleProceedOutroTime = TITLE_OPTION_OUTRO_TOTAL_FRAMES + deltaTime; + + // Wait for outro animation to complete before entering menu. + if (g_titleProceedOutroTime > TITLE_OPTION_OUTRO_TOTAL_FRAMES) + { + if (g_devTitleState == DevTitleState::Open) + { + g_devTitleState = DevTitleState::Start; + } + else + { GuestToHostFunction(sub_82511CA0, pTitleTask, 9); + } } break; @@ -246,3 +262,21 @@ PPC_FUNC(sub_82511540) __imp__sub_82511540(ctx, base); } + +// Sonicteam::MainMode::Update +PPC_FUNC_IMPL(__imp__sub_82161A48); +PPC_FUNC(sub_82161A48) +{ + const auto pDoc = reinterpret_cast(base + ctx.r4.u32); + + if (g_devTitleState == DevTitleState::Start) + { + g_devTitleState = DevTitleState::Idle; + + ctx.r3.u32 = g_memory.MapVirtual(pDoc->SetDocState(StateDevTitle::GetInstance())); + + return; + } + + __imp__sub_82161A48(ctx, base); +} diff --git a/MarathonRecomp/patches/misc_patches.cpp b/MarathonRecomp/patches/misc_patches.cpp index 2e7886f15..27c5d9a76 100644 --- a/MarathonRecomp/patches/misc_patches.cpp +++ b/MarathonRecomp/patches/misc_patches.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -222,94 +221,4 @@ PPC_FUNC(sub_82188460) ctx.r3.u64 = Config::Subtitles; } -// Add audio to MessageReceiver(StageSelectMode) -PPC_FUNC_IMPL(__imp__sub_824A8FF0); -PPC_FUNC(sub_824A8FF0) -{ - auto pMessage = (DevMessage*)(base + ctx.r4.u32); - DevTitleMenu::AudioCommon(pMessage); - __imp__sub_824A8FF0(ctx, base); -} - -// Add audio to MessageReceiver(DevTitleMode) -PPC_FUNC_IMPL(__imp__sub_824A0E38); -PPC_FUNC(sub_824A0E38) -{ - auto pMessage = (DevMessage*)(base + ctx.r4.u32); - auto pTitle = (StateDevTitle*)(base + 0x82D35E6C); - if (pTitle->m_PictureTitle->m_State3 >= 4) - return; - - if (pMessage->id == 0x10002) - { - if (pMessage->id2 == 0x5A || pMessage->id3) - { - pTitle->m_PictureTitle->m_State3 = 4; - DevTitleDelay = DevTitleDelayDefault; - } - else - { - return; - } - } - - DevTitleMenu::AudioCommon(pMessage); - __imp__sub_824A0E38(ctx, base); -} - -PPC_FUNC_IMPL(__imp__sub_82161AB8); -PPC_FUNC(sub_82161AB8) -{ - if (!Config::DevTitle) - { - __imp__sub_82161AB8(ctx, base); - return; - } - - GuestToHostFunction(sub_825E9E28, ctx.r3.u32, ctx.r4.u32); - - // Initialize MainMode - if (!*(uint32_t*)(base + 0x82D35E48)) - *(be*)(base + 0x82D35E48) = GuestToHostFunction(sub_82161150); - - // Initialize DevMode - if (!*(uint32_t*)(base + 0x82D35E78)) - *(be*)(base + 0x82D35E78) = GuestToHostFunction(sub_82161860); - - auto pDevMode = (*(be*)(base + 0x82D35E78)).get(); - - // DocSetMode - GuestToHostFunction(sub_825E8D78, ctx.r3.u32, pDevMode); -} - -// tateDevTitle, vft + 8 -PPC_FUNC_IMPL(__imp__sub_82162298); -PPC_FUNC(sub_82162298) -{ - auto pTitle = (StateDevTitle*)(base + ctx.r3.u32); - - if (ctx.r3.u32 && pTitle->m_PictureTitle && pTitle->m_PictureTitle->m_State3 > 4) - { - DevTitleDelay = std::clamp(DevTitleDelay - 0.016666668f, 0.0f, DevTitleDelay); - } - else - { - DevTitleDelay = DevTitleDelayDefault; - } - - if (!GuestToHostFunction(sub_82581C68, ctx.r4.u32)) - { - // Wait for transistion - GuestToHostFunction(sub_824A0870, pTitle->m_PictureTitle.ptr.get(), 0.016666668); - - // Force to wait transistion - if (pTitle->m_PictureTitle->m_State3 != 6) - { - return; - } - } - - __imp__sub_82162298(ctx, base); -} - void NOP() {} From b30dfc7137a70c6defbc478b00c8c0f8b3664ebc Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Wed, 28 Oct 2026 23:43:05 +0000 Subject: [PATCH 07/15] Redesigned debug menu Went for a look more akin to Sonic Unleashed's debug menu, feels more authentic. --- MarathonRecomp/CMakeLists.txt | 1 - MarathonRecomp/api/Marathon.h | 2 + MarathonRecomp/api/Sonicteam/CsdLink.h | 8 +- MarathonRecomp/api/Sonicteam/DevTitleMode.h | 10 +- MarathonRecomp/api/Sonicteam/MainMode.h | 4 +- .../Message/DevTitleMode/MsgMoveCursor.h | 23 ++ .../Message/DevTitleMode/MsgSelectCursor.h | 17 + MarathonRecomp/api/Sonicteam/PicBase.h | 28 +- MarathonRecomp/api/Sonicteam/PictureTitle.h | 29 +- .../api/Sonicteam/SoX/Input/Listener.h | 11 +- .../api/Sonicteam/SoX/MessageReceiver.h | 7 + MarathonRecomp/api/Sonicteam/StageMap.h | 36 ++ .../api/Sonicteam/StageSelectMode.h | 9 +- MarathonRecomp/gpu/video.cpp | 1 - .../patches/DevTitleMode_patches.cpp | 87 ----- MarathonRecomp/patches/misc_patches.cpp | 17 + MarathonRecomp/ui/dev_title_menu.cpp | 342 +++++------------- MarathonRecomp/ui/dev_title_menu.h | 38 +- 18 files changed, 278 insertions(+), 392 deletions(-) create mode 100644 MarathonRecomp/api/Sonicteam/Message/DevTitleMode/MsgMoveCursor.h create mode 100644 MarathonRecomp/api/Sonicteam/Message/DevTitleMode/MsgSelectCursor.h delete mode 100644 MarathonRecomp/patches/DevTitleMode_patches.cpp diff --git a/MarathonRecomp/CMakeLists.txt b/MarathonRecomp/CMakeLists.txt index b67cd90cf..66859daf7 100644 --- a/MarathonRecomp/CMakeLists.txt +++ b/MarathonRecomp/CMakeLists.txt @@ -145,7 +145,6 @@ set(MARATHON_RECOMP_PATCHES_CXX_SOURCES "patches/aspect_ratio_patches.cpp" "patches/audio_patches.cpp" "patches/camera_patches.cpp" - "patches/DevTitleMode_patches.cpp" "patches/fps_patches.cpp" "patches/frontend_listener.cpp" "patches/input_patches.cpp" diff --git a/MarathonRecomp/api/Marathon.h b/MarathonRecomp/api/Marathon.h index cf37b2272..3b19eb67c 100644 --- a/MarathonRecomp/api/Marathon.h +++ b/MarathonRecomp/api/Marathon.h @@ -71,6 +71,8 @@ #include "Sonicteam/MainMenuTask.h" #include "Sonicteam/MainMode.h" #include "Sonicteam/Message/Camera/Cameraman/MsgChangeMode.h" +#include "Sonicteam/Message/DevTitleMode/MsgMoveCursor.h" +#include "Sonicteam/Message/DevTitleMode/MsgSelectCursor.h" #include "Sonicteam/Message/Game/MsgRegisterPlayer.h" #include "Sonicteam/Message/Game/MsgTextCardAction.h" #include "Sonicteam/Message/HUDButtonWindow/MsgChangeButtons.h" diff --git a/MarathonRecomp/api/Sonicteam/CsdLink.h b/MarathonRecomp/api/Sonicteam/CsdLink.h index d4195e8a5..bcfa03bd7 100644 --- a/MarathonRecomp/api/Sonicteam/CsdLink.h +++ b/MarathonRecomp/api/Sonicteam/CsdLink.h @@ -10,15 +10,15 @@ namespace Sonicteam struct Vftable { be fpDestroy; - MARATHON_INSERT_PADDING(4); + MARATHON_INSERT_PADDING(0x04); be fpUpdate; }; xpointer m_pVftable; xpointer m_pNext; - xpointer m_pPrevious; + xpointer m_pPrev; be m_Priority; - MARATHON_INSERT_PADDING(8); + MARATHON_INSERT_PADDING(0x08); void* Destroy(uint32_t flags = 1) { @@ -33,7 +33,7 @@ namespace Sonicteam MARATHON_ASSERT_OFFSETOF(CsdLink, m_pVftable, 0x00); MARATHON_ASSERT_OFFSETOF(CsdLink, m_pNext, 0x04); - MARATHON_ASSERT_OFFSETOF(CsdLink, m_pPrevious, 0x08); + MARATHON_ASSERT_OFFSETOF(CsdLink, m_pPrev, 0x08); MARATHON_ASSERT_OFFSETOF(CsdLink, m_Priority, 0x0C); MARATHON_ASSERT_SIZEOF(CsdLink, 0x18); } diff --git a/MarathonRecomp/api/Sonicteam/DevTitleMode.h b/MarathonRecomp/api/Sonicteam/DevTitleMode.h index 960b70bf5..16a45936f 100644 --- a/MarathonRecomp/api/Sonicteam/DevTitleMode.h +++ b/MarathonRecomp/api/Sonicteam/DevTitleMode.h @@ -11,17 +11,19 @@ namespace Sonicteam public: struct Mode { - xpointer Name; - MARATHON_INSERT_PADDING(0x04); + xpointer pName; + xpointer pState; }; - be m_SelectedIndex; + be m_SelectedIndex; MARATHON_INSERT_PADDING(0x04); stdx::vector m_Modes; MARATHON_INSERT_PADDING(0x2C); }; - MARATHON_ASSERT_OFFSETOF(DevTitleMode::Mode, Name, 0x00); + MARATHON_ASSERT_OFFSETOF(DevTitleMode::Mode, pName, 0x00); + MARATHON_ASSERT_OFFSETOF(DevTitleMode::Mode, pState, 0x04); + MARATHON_ASSERT_SIZEOF(DevTitleMode::Mode, 0x08); MARATHON_ASSERT_OFFSETOF(DevTitleMode, m_SelectedIndex, 0x50); MARATHON_ASSERT_OFFSETOF(DevTitleMode, m_Modes, 0x58); diff --git a/MarathonRecomp/api/Sonicteam/MainMode.h b/MarathonRecomp/api/Sonicteam/MainMode.h index 45e8e41ff..587a54eac 100644 --- a/MarathonRecomp/api/Sonicteam/MainMode.h +++ b/MarathonRecomp/api/Sonicteam/MainMode.h @@ -26,7 +26,8 @@ namespace Sonicteam }; be m_State; - MARATHON_INSERT_PADDING(0x20); + be m_NextState; + MARATHON_INSERT_PADDING(0x1C); boost::shared_ptr m_spSelectCamera; xpointer m_pFrameGP; MARATHON_INSERT_PADDING(4); @@ -35,6 +36,7 @@ namespace Sonicteam }; MARATHON_ASSERT_OFFSETOF(MainMode, m_State, 0x50); + MARATHON_ASSERT_OFFSETOF(MainMode, m_NextState, 0x54); MARATHON_ASSERT_OFFSETOF(MainMode, m_spSelectCamera, 0x74); MARATHON_ASSERT_OFFSETOF(MainMode, m_pFrameGP, 0x7C); MARATHON_ASSERT_OFFSETOF(MainMode, m_pMainTask, 0x84); diff --git a/MarathonRecomp/api/Sonicteam/Message/DevTitleMode/MsgMoveCursor.h b/MarathonRecomp/api/Sonicteam/Message/DevTitleMode/MsgMoveCursor.h new file mode 100644 index 000000000..33e3a810e --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/DevTitleMode/MsgMoveCursor.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace Sonicteam::Message::DevTitleMode +{ + struct MsgMoveCursor : SoX::Message<0x10001> + { + enum CursorDirection + { + CursorDirection_Up, + CursorDirection_Down = 180 + }; + + be Direction{}; + + MsgMoveCursor(CursorDirection in_direction = CursorDirection_Down) + : Direction(in_direction) {} + }; + + MARATHON_ASSERT_OFFSETOF(MsgMoveCursor, Direction, 0x04); +} diff --git a/MarathonRecomp/api/Sonicteam/Message/DevTitleMode/MsgSelectCursor.h b/MarathonRecomp/api/Sonicteam/Message/DevTitleMode/MsgSelectCursor.h new file mode 100644 index 000000000..a2a7528d2 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/Message/DevTitleMode/MsgSelectCursor.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace Sonicteam::Message::DevTitleMode +{ + struct MsgSelectCursor : SoX::Message<0x10002> + { + MARATHON_INSERT_PADDING(0x04); + bool IsSelected{}; + + MsgSelectCursor(bool in_isSelected = true) : IsSelected(in_isSelected) {} + }; + + MARATHON_ASSERT_OFFSETOF(MsgSelectCursor, IsSelected, 0x08); +} diff --git a/MarathonRecomp/api/Sonicteam/PicBase.h b/MarathonRecomp/api/Sonicteam/PicBase.h index c9b44e153..33cca993c 100644 --- a/MarathonRecomp/api/Sonicteam/PicBase.h +++ b/MarathonRecomp/api/Sonicteam/PicBase.h @@ -1,21 +1,35 @@ #pragma once #include +#include namespace Sonicteam { class PicBase { public: + enum PicState : uint32_t + { + PicState_Load, + PicState_Init, + PicState_Start, + PicState_TitleWait, + PicState_TitleOutro, + PicState_TitleProceed, + PicState_Next + }; + xpointer m_pVftable; - be m_State1; - be m_State2; - be m_State3; + be m_PrevState; + be m_NextState; + be m_State; + xpointer m_pCsdObject; }; MARATHON_ASSERT_OFFSETOF(PicBase, m_pVftable, 0x00); - MARATHON_ASSERT_OFFSETOF(PicBase, m_State1, 0x04); - MARATHON_ASSERT_OFFSETOF(PicBase, m_State2, 0x08); - MARATHON_ASSERT_OFFSETOF(PicBase, m_State3, 0x0C); - MARATHON_ASSERT_SIZEOF(PicBase, 0x10); + MARATHON_ASSERT_OFFSETOF(PicBase, m_PrevState, 0x04); + MARATHON_ASSERT_OFFSETOF(PicBase, m_NextState, 0x08); + MARATHON_ASSERT_OFFSETOF(PicBase, m_State, 0x0C); + MARATHON_ASSERT_OFFSETOF(PicBase, m_pCsdObject, 0x10); + MARATHON_ASSERT_SIZEOF(PicBase, 0x14); } diff --git a/MarathonRecomp/api/Sonicteam/PictureTitle.h b/MarathonRecomp/api/Sonicteam/PictureTitle.h index edadd8229..89a7b9006 100644 --- a/MarathonRecomp/api/Sonicteam/PictureTitle.h +++ b/MarathonRecomp/api/Sonicteam/PictureTitle.h @@ -1,8 +1,10 @@ #pragma once #include +#include +#include +#include #include -#include #include namespace Sonicteam @@ -10,11 +12,30 @@ namespace Sonicteam class PictureTitle : public PicBase, public SoX::Input::Listener { public: - MARATHON_INSERT_PADDING(0x04); - xpointer m_pDoc; - MARATHON_INSERT_PADDING(0x20); + enum PictureTitleFlags + { + PictureTitleFlags_Next = (1 << 0) + }; + + xpointer m_pDoc; + bool m_IsOutroFinished; + be m_Flags; + xpointer> m_pBackAoitaScene; + be m_BackAoitaOutroSpeed; + be m_BackAoitaOutroEndFrame; + xpointer> m_pTitleMenuScene; + be m_TitleMenuOutroSpeed; + be m_TitleMenuOutroEndFrame; }; MARATHON_ASSERT_OFFSETOF(PictureTitle, m_pDoc, 0x2C); + MARATHON_ASSERT_OFFSETOF(PictureTitle, m_IsOutroFinished, 0x30); + MARATHON_ASSERT_OFFSETOF(PictureTitle, m_Flags, 0x34); + MARATHON_ASSERT_OFFSETOF(PictureTitle, m_pBackAoitaScene, 0x38); + MARATHON_ASSERT_OFFSETOF(PictureTitle, m_BackAoitaOutroSpeed, 0x3C); + MARATHON_ASSERT_OFFSETOF(PictureTitle, m_BackAoitaOutroEndFrame, 0x40); + MARATHON_ASSERT_OFFSETOF(PictureTitle, m_pTitleMenuScene, 0x44); + MARATHON_ASSERT_OFFSETOF(PictureTitle, m_TitleMenuOutroSpeed, 0x48); + MARATHON_ASSERT_OFFSETOF(PictureTitle, m_TitleMenuOutroEndFrame, 0x4C); MARATHON_ASSERT_SIZEOF(PictureTitle, 0x50); } diff --git a/MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h b/MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h index 9ce2216b4..72cc021a1 100644 --- a/MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h +++ b/MarathonRecomp/api/Sonicteam/SoX/Input/Listener.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace Sonicteam::SoX::Input @@ -8,6 +9,14 @@ namespace Sonicteam::SoX::Input class Listener : public Object { public: - MARATHON_INSERT_PADDING(0x14); // LinkNode LinkInput, Flags + LinkNode m_Sibling; + uint8_t m_Field10; + uint8_t m_Field11; + MARATHON_INSERT_PADDING(0x06); }; + + MARATHON_ASSERT_OFFSETOF(Listener, m_Sibling, 0x04); + MARATHON_ASSERT_OFFSETOF(Listener, m_Field10, 0x10); + MARATHON_ASSERT_OFFSETOF(Listener, m_Field11, 0x11); + MARATHON_ASSERT_SIZEOF(Listener, 0x18); } diff --git a/MarathonRecomp/api/Sonicteam/SoX/MessageReceiver.h b/MarathonRecomp/api/Sonicteam/SoX/MessageReceiver.h index 09c45232b..c3cd33811 100644 --- a/MarathonRecomp/api/Sonicteam/SoX/MessageReceiver.h +++ b/MarathonRecomp/api/Sonicteam/SoX/MessageReceiver.h @@ -25,6 +25,13 @@ namespace Sonicteam::SoX { return GuestToHostFunction(m_pVftable->fpProcessMessage, this, pMessage); } + + template + bool ProcessMessage(TArgs&&... in_args) + { + guest_stack_var msg(std::forward(in_args)...); + return ProcessMessage(msg); + } }; MARATHON_ASSERT_OFFSETOF(MessageReceiver, m_pVftable, 0x00); diff --git a/MarathonRecomp/api/Sonicteam/StageMap.h b/MarathonRecomp/api/Sonicteam/StageMap.h index 45e5bf9f4..c835440c9 100644 --- a/MarathonRecomp/api/Sonicteam/StageMap.h +++ b/MarathonRecomp/api/Sonicteam/StageMap.h @@ -1,5 +1,8 @@ #pragma once +#include +#include +#include #include #include #include @@ -13,6 +16,39 @@ namespace Sonicteam stdx::string m_Name; stdx::string m_Text; stdx::vector> m_Children; + + std::string GetHierarchy() + { + std::stringstream result{}; + std::vector hierarchy{}; + + auto pStageMap = this; + + while (pStageMap) + { + if (pStageMap->m_Name.empty()) + break; + + hierarchy.push_back(pStageMap->m_Name.c_str()); + + pStageMap = pStageMap->GetParent(); + } + + for (int i = hierarchy.size() - 1; i >= 0; i--) + result << "/" << hierarchy[i]; + + return result.str(); + } + + int GetTotalChildCount() + { + int result = m_Children.size(); + + for (const auto& rChild : m_Children) + result += rChild->m_Children.size(); + + return result; + } }; MARATHON_ASSERT_OFFSETOF(StageMap, m_Name, 0x20); diff --git a/MarathonRecomp/api/Sonicteam/StageSelectMode.h b/MarathonRecomp/api/Sonicteam/StageSelectMode.h index 79c0a2a3e..cf9f8ff87 100644 --- a/MarathonRecomp/api/Sonicteam/StageSelectMode.h +++ b/MarathonRecomp/api/Sonicteam/StageSelectMode.h @@ -2,6 +2,7 @@ #include #include +#include #include namespace Sonicteam @@ -14,8 +15,9 @@ namespace Sonicteam MARATHON_INSERT_PADDING(0x04); be m_StageMapIndex; be m_StageMapDepth; - MARATHON_INSERT_PADDING(0x1C); - xpointer> m_StageMapInfo; + MARATHON_INSERT_PADDING(0x18); + xpointer m_pMenuListener; + xpointer> m_StageMapFilter; xpointer m_StageMapType; xpointer m_StageMapName; MARATHON_INSERT_PADDING(0x04); @@ -25,7 +27,8 @@ namespace Sonicteam MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_CurrentStageMap, 0x54); MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapIndex, 0x5C); MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapDepth, 0x60); - MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapInfo, 0x80); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_pMenuListener, 0x7C); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapFilter, 0x80); MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapType, 0x84); MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapName, 0x88); MARATHON_ASSERT_SIZEOF(StageSelectMode, 0x90); diff --git a/MarathonRecomp/gpu/video.cpp b/MarathonRecomp/gpu/video.cpp index 5950aa922..5ee7245e8 100644 --- a/MarathonRecomp/gpu/video.cpp +++ b/MarathonRecomp/gpu/video.cpp @@ -1663,7 +1663,6 @@ static void CreateImGuiBackend() InitImGuiUtils(); OptionsMenu::Init(); InstallerWizard::Init(); - DevTitleMenu::Init(); ImGui_ImplSDL3_InitForOther(GameWindow::s_pWindow); diff --git a/MarathonRecomp/patches/DevTitleMode_patches.cpp b/MarathonRecomp/patches/DevTitleMode_patches.cpp deleted file mode 100644 index dceee093b..000000000 --- a/MarathonRecomp/patches/DevTitleMode_patches.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include -#include - -// Sonicteam::StageSelectMode::ProcessMessage -PPC_FUNC_IMPL(__imp__sub_824A8FF0); -PPC_FUNC(sub_824A8FF0) -{ - const auto pMessage = reinterpret_cast(base + ctx.r4.u32); - - DevTitleMenu::AudioCommon(pMessage); - - __imp__sub_824A8FF0(ctx, base); -} - -// Sonicteam::DevTitleMode::ProcessMessage -PPC_FUNC_IMPL(__imp__sub_824A0E38); -PPC_FUNC(sub_824A0E38) -{ - const auto pStateDevTitle = StateDevTitle::GetInstance(); - const auto pMessage = reinterpret_cast(base + ctx.r4.u32); - - if (pStateDevTitle->m_pPictureTitle->m_State3 >= 4) - return; - - if (pMessage->id == 0x10002) - { - if (pMessage->id2 == 0x5A || pMessage->id3) - { - pStateDevTitle->m_pPictureTitle->m_State3 = 4; - DevTitleDelay = DevTitleDelayDefault; - } - else - { - return; - } - } - - DevTitleMenu::AudioCommon(pMessage); - - __imp__sub_824A0E38(ctx, base); -} - -// Sonicteam::DocMarathonState::StartMainMode -PPC_FUNC_IMPL(__imp__sub_82161AB8); -PPC_FUNC(sub_82161AB8) -{ - if (!Config::BootDirectlyToDevTitle) - { - __imp__sub_82161AB8(ctx, base); - return; - } - - const auto pDoc = reinterpret_cast(base + ctx.r3.u32); - - GuestToHostFunction(sub_825E9E28, ctx.r3.u32, ctx.r4.u32); - - ctx.r3.u32 = g_memory.MapVirtual(pDoc->SetDocState(StateDevTitle::GetInstance())); -} - -// StateDevTitle::Update -PPC_FUNC_IMPL(__imp__sub_82162298); -PPC_FUNC(sub_82162298) -{ - const auto pStateDevTitle = reinterpret_cast(base + ctx.r3.u32); - - if (ctx.r3.u32 && pStateDevTitle->m_pPictureTitle && pStateDevTitle->m_pPictureTitle->m_State3 > 4) - { - DevTitleDelay = std::clamp(DevTitleDelay - 0.016666668f, 0.0f, DevTitleDelay); - } - else - { - DevTitleDelay = DevTitleDelayDefault; - } - - if (!GuestToHostFunction(sub_82581C68, ctx.r4.u32)) - { - // Wait for transition. - GuestToHostFunction(sub_824A0870, pStateDevTitle->m_pPictureTitle.ptr.get(), 0.016666668); - - // Force to wait transition. - if (pStateDevTitle->m_pPictureTitle->m_State3 != 6) - return; - } - - __imp__sub_82162298(ctx, base); -} diff --git a/MarathonRecomp/patches/misc_patches.cpp b/MarathonRecomp/patches/misc_patches.cpp index 27c5d9a76..4c0e3655f 100644 --- a/MarathonRecomp/patches/misc_patches.cpp +++ b/MarathonRecomp/patches/misc_patches.cpp @@ -216,6 +216,23 @@ PPC_FUNC(sub_824A6EA8) __imp__sub_824A6EA8(ctx, base); } +// Sonicteam::DocMarathonState::StartMainMode +PPC_FUNC_IMPL(__imp__sub_82161AB8); +PPC_FUNC(sub_82161AB8) +{ + if (!Config::BootDirectlyToDevTitle) + { + __imp__sub_82161AB8(ctx, base); + return; + } + + const auto pDoc = reinterpret_cast(base + ctx.r3.u32); + + GuestToHostFunction(sub_825E9E28, ctx.r3.u32, ctx.r4.u32); + + ctx.r3.u32 = g_memory.MapVirtual(pDoc->SetDocState(StateDevTitle::GetInstance())); +} + PPC_FUNC(sub_82188460) { ctx.r3.u64 = Config::Subtitles; diff --git a/MarathonRecomp/ui/dev_title_menu.cpp b/MarathonRecomp/ui/dev_title_menu.cpp index 7fcefe667..5bf38de42 100644 --- a/MarathonRecomp/ui/dev_title_menu.cpp +++ b/MarathonRecomp/ui/dev_title_menu.cpp @@ -1,298 +1,156 @@ #include "dev_title_menu.h" -#include "imgui_utils.h" -#include +#include #include -#include -#include -static ImFont* g_mrodinFont; -static ImFont* g_mnewRodinFont; -const float ScrollAmount = 0.25f; -bool DevTitleMenu::IsVisible = true; - -enum CenterFlag -{ - X = 1, - Y = 2 -}; - -struct DevTitleMenuSTable +static void DrawInfo() { - struct - { - xpointer m_name; - xpointer m_mode; - } m_state[5]; -}; + const auto drawList = ImGui::GetBackgroundDrawList(); + const auto fontSize = Scale(22); -static ImVec2 GetMousePos() -{ - return ImGui::GetIO().MousePos; + drawList->AddText(g_pFntRodin, fontSize, { 0, Scale(4) }, IM_COL32_WHITE, "ソニック・ザ・ヘッジホッグ"); + drawList->AddText(g_pFntRodin, Scale(11), { Scale(350), Scale(34) }, IM_COL32_WHITE, "Mon Oct 23 05:01:13 2006"); } -static void DrawBackgroundDev() +static void DrawItem(const ImVec2& in_rPosition, const char* in_pText, bool in_isSelected = false) { - auto& res = ImGui::GetIO().DisplaySize; - auto drawList = ImGui::GetBackgroundDrawList(); + const auto drawList = ImGui::GetBackgroundDrawList(); + const auto fontSize = Scale(22); - auto color = IM_COL32(0, 0, 160, 255); - drawList->AddRectFilled({ 0.0, 0.0 }, res, color); -} + if (in_isSelected) + drawList->AddText(g_pFntRodin, fontSize, { in_rPosition.x - Scale(35), in_rPosition.y }, IM_COL32_WHITE, ">>"); -static void DrawTextDev(const char* text, float posX, float posY, float vfontSize= 24, uint32_t alpha = 255, uint32_t center = X) -{ - auto fontSize = Scale(vfontSize); - auto textSize = g_mnewRodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, text); - - float X = Video::s_viewportWidth * posX; - float Y = Video::s_viewportHeight * posY; - - if (center & CenterFlag::X) - X -= textSize.x * 0.5f; - - if (center & CenterFlag::Y) - Y -= textSize.y * 0.5f; - - DrawTextWithOutline - ( - g_mnewRodinFont, - fontSize, - { X,Y }, - IM_COL32(255, 255, 255, alpha), - text, - 4, - IM_COL32(0, 0, 0, alpha) - ); - - ResetTextSkew(); + drawList->AddText(g_pFntRodin, fontSize, in_rPosition, IM_COL32_WHITE, in_pText); } -static bool Sonic06Button(ImVec2 pos, const char* label, bool& hovered, const ImVec2& sizeArg = {}, - bool selected = false, uint32_t centerFlags = 0, bool group = false) +static void DrawDevTitle() { - auto* drawList = ImGui::GetBackgroundDrawList(); - const float fontSize = sizeArg.y / 2.5f; - const float gutter = fontSize * 1.5f; + const auto pDevTitleMode = App::s_pApp->m_pDoc->GetDocMode(); + + if (!pDevTitleMode) + return; - const auto labelSize = g_mnewRodinFont->CalcTextSizeA(fontSize, FLT_MAX, 0, label); - const auto size = ImVec2(sizeArg.x ? sizeArg.x : gutter + labelSize.x, sizeArg.y ? sizeArg.y : labelSize.y); + if (const auto pPictureTitle = StateDevTitle::GetInstance()->m_pPictureTitle.get()) + { + if (pPictureTitle->m_State != Sonicteam::PictureTitle::PicState_Next) + return; + } - const auto mouse_pos = GetMousePos(); - const ImRect bb(pos, { pos.x + size.x, pos.y + size.y }); - const ImRect clip(drawList->GetClipRectMin(), drawList->GetClipRectMax()); - hovered = bb.Contains(mouse_pos) && clip.Contains(mouse_pos); - const bool pressed = hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left); + const auto drawList = ImGui::GetBackgroundDrawList(); - // Background - const ImU32 bg_col = selected ? IM_COL32(255, 255, 255, 60) : hovered ? IM_COL32(255, 255, 255, 25) : 0; - drawList->AddRectFilled(bb.Min, bb.Max, bg_col); + const auto& items = pDevTitleMode->m_Modes; + const auto selectedIndex = pDevTitleMode->m_SelectedIndex.get(); - // Text, indented past the gutter so labels align with and without a chevron - ImVec2 textPos = { pos.x + gutter, pos.y }; + const auto itemMargin = Scale(80); + const auto itemHeight = Scale(25); - if (centerFlags & X) - textPos.x = bb.GetCenter().x - labelSize.x * 0.5f; - if (centerFlags & Y) - textPos.y = bb.GetCenter().y - labelSize.y * 0.5f; + drawList->AddRectFilled({ 0, 0 }, { float(Video::s_viewportWidth), float(Video::s_viewportHeight) }, IM_COL32_BLACK); - drawList->AddText(g_mnewRodinFont, fontSize, { textPos.x + 1,textPos.y + 1 }, IM_COL32(0, 0, 0, 120), label); - drawList->AddText(g_mnewRodinFont, fontSize, textPos, IM_COL32(255, 255, 255, 255), label); + DrawInfo(); - if (group) + for (int i = 0; i < pDevTitleMode->m_Modes.size(); i++) { - // Chevron centred in the gutter to mark items that open a sub-list - const float h = fontSize * 0.25f; - const float x = pos.x + gutter * 0.5f; - const float y = textPos.y + labelSize.y * 0.5f; - drawList->AddTriangleFilled({ x - h, y - h }, { x + h, y }, { x - h, y + h }, IM_COL32(255, 255, 255, 255)); - } + const ImVec2 position = { itemMargin, itemMargin + i * itemHeight }; - return pressed; + DrawItem(position, items[i].pName.get(), i == selectedIndex); + } } -static void HandleStageMapSelectorInput(Sonicteam::StageSelectMode* pMode, uintptr_t pMsgRec) +static void DrawStageSelect() { - auto& io = ImGui::GetIO(); + const auto pStageSelectMode = App::s_pApp->m_pDoc->GetDocMode(); - // Mouse wheel scrolling - if (abs(io.MouseWheel) > ScrollAmount) - { - guest_stack_var vMsgRec(0x10001, io.MouseWheel > 0 ? 0 : 0xB4, 130); - GuestToHostFunction(sub_824A8FF0, pMsgRec, vMsgRec.get()); - } + if (!pStageSelectMode) + return; - // Right click handling - if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) - { - guest_stack_var vMsgRec(0x10002, 0, 0); - GuestToHostFunction(sub_824A8FF0, pMsgRec, vMsgRec.get()); - } -} + const auto drawList = ImGui::GetBackgroundDrawList(); -static void DrawStageMapSelector() -{ - auto& io = ImGui::GetIO(); - auto& res = io.DisplaySize; - auto& config = s_StageMapSelectorConfig; - - auto* pMode = App::s_pApp->m_pDoc->GetDocMode(); - if (!pMode) return; - - DrawBackgroundDev(); - - // Calculate layout - const int visibleItems = config.visibleItems; - const float buttonHeight = config.buttonHeightRatio * res.y; - const float rowHeight = buttonHeight * config.leading; - const ImVec2 basePos = { - config.paddingRatio.x * res.x, - config.paddingRatio.y * res.y - }; - - auto& items = pMode->m_CurrentStageMap->m_Children; - const int itemCount = static_cast(items.size()); - int currentIdx = pMode->m_StageMapIndex.get(); - - // Handle input - auto pMsgRec = reinterpret_cast(static_cast(pMode)); - HandleStageMapSelectorInput(pMode, pMsgRec); - - // Ease the scroll offset towards the selection, snapping when the list changes - const int maxStart = std::max(0, itemCount - visibleItems); - const float target = static_cast(std::clamp(currentIdx - visibleItems / 2, 0, maxStart)); - const void* pStageMap = pMode->m_CurrentStageMap.get(); - - if (pStageMap != s_pLastStageMap) + const auto& items = pStageSelectMode->m_CurrentStageMap->m_Children; + const auto itemCount = int(items.size()); + const auto selectedIndex = int(pStageSelectMode->m_StageMapIndex.get()); + + const auto fontSize = Scale(22); + const auto itemMargin = Scale(80); + const auto itemHeight = Scale(25); + + // Ease the scroll offset towards the selection, snapping when the list changes. + const auto visibleItems = int((Video::s_viewportHeight - (itemMargin * 2.0f)) / itemHeight); + const auto startOffset = std::max(0, itemCount - visibleItems); + const auto targetOffset = std::clamp(selectedIndex - visibleItems / 2, 0, startOffset); + + const auto pCurrentStageMap = pStageSelectMode->m_CurrentStageMap.get(); + + static Sonicteam::StageMap* pPrevStageMap{}; + static float scrollOffset{}; + + if (pCurrentStageMap != pPrevStageMap) { - s_StageMapScroll = target; - s_pLastStageMap = pStageMap; + pPrevStageMap = pCurrentStageMap; + scrollOffset = targetOffset; } else { - s_StageMapScroll += (target - s_StageMapScroll) * (1.0f - expf(-config.scrollSpeed * io.DeltaTime)); + scrollOffset += (targetOffset - scrollOffset) * (1.0f - expf(-16.0f * App::s_deltaTime)); } - // Calculate visible range, including partially visible items - const int startIdx = static_cast(s_StageMapScroll); - const int endIdx = std::min(itemCount, static_cast(ceilf(s_StageMapScroll)) + visibleItems); + DrawInfo(); - // Clip items scrolling in and out of the list - auto* drawList = ImGui::GetBackgroundDrawList(); - drawList->PushClipRect(basePos, { res.x, basePos.y + visibleItems * rowHeight }); + drawList->AddText(g_pFntRodin, fontSize, { itemMargin, Scale(56) }, IM_COL32_WHITE, pCurrentStageMap->GetHierarchy().c_str()); + drawList->PushClipRect({ 0, itemMargin }, { float(Video::s_viewportWidth), float(Video::s_viewportHeight) }); - // Draw visible items - for (int i = startIdx; i < endIdx; ++i) + for (int i = 0; i < itemCount; i++) { - const auto& item = items[i]; - if (!item) continue; - - const bool isSelected = i == currentIdx; - bool hovered = false; - - // Calculate button position - const ImVec2 buttonPos = { - basePos.x, - basePos.y + (i - s_StageMapScroll) * rowHeight - }; - - // Draw button - if (Sonic06Button(buttonPos, item->m_Name.c_str(), hovered, - ImVec2(config.windowWidthRatio * res.x - 20, buttonHeight), - isSelected, Y, item->m_Children.size() > 1)) - { - currentIdx = i; - pMode->m_StageMapIndex = i; - guest_stack_var vMsgRec(0x10002, 0x5A, 1); - GuestToHostFunction(sub_824A8FF0, pMsgRec, vMsgRec.get()); - } - } + const auto& pStageMap = items[i]; - drawList->PopClipRect(); -} + if (!pStageMap) + continue; -static void DrawDevTitle() -{ - auto& io = ImGui::GetIO(); - auto* pMode = App::s_pApp->m_pDoc->GetDocMode(); - if (!pMode) - return; + // The game automatically enters the first child node if only one exists. + const auto hasChildren = pStageMap->GetTotalChildCount() > 1; - auto pDevTitle = App::s_pApp->m_pDoc->GetDocMode(); - auto pIndex = pDevTitle->m_SelectedIndex.get(); - auto pDevTitleMenuSTable = static_cast(g_memory.Translate(0x82B10A40)); - auto pName = pDevTitleMenuSTable->m_state[pIndex].m_name.get(); + std::stringstream label{}; - DrawTextDev(pName, 0.5, 0.95, 24, DevTitleDelay * 255, X | Y); - auto pMsgRec = (uintptr_t)static_cast(pMode); + if (hasChildren) + label << "["; - if (abs(io.MouseWheel) > ScrollAmount) - { - guest_stack_var vMsgRec(0x10001, io.MouseWheel >= 0 ? 0 : 0xB4, 130); - GuestToHostFunction(sub_824A0E38, static_cast(pMsgRec), vMsgRec.get()); - } + label << pStageMap->m_Name.c_str(); - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) - { - guest_stack_var vMsgRec(0x10002, 0, 1); - GuestToHostFunction(sub_824A0E38, static_cast(pMsgRec), vMsgRec.get()); + if (hasChildren) + label << "]"; + + const ImVec2 position = { itemMargin, itemMargin + (i - scrollOffset) * itemHeight }; + + DrawItem(position, label.str().c_str(), i == selectedIndex); } -} -void DevTitleMenu::Init() -{ - g_mrodinFont = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); - g_mnewRodinFont = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); + drawList->PopClipRect(); } void DevTitleMenu::Draw() { - if (!App::s_pApp) - return; - - if (!App::s_pApp->m_pDoc) - return; + const auto pApp = App::s_pApp; - if (!App::s_pApp->m_pDoc->m_pDocMode) + if (!pApp || !pApp->m_pDoc || !pApp->m_pDoc->m_pDocMode) return; - if (!EmbeddedPlayer::s_isActive) - EmbeddedPlayer::Init(); - - auto vft = App::s_pApp->m_pDoc->GetDocMode()->m_pVftable.ptr.get(); - switch (vft) - { - // DevTitleMode - case 0x8202B1E0: - IsVisible = true; - DrawDevTitle(); - break; - // StageSelect/StoreSelect - case 0x820336A4: - IsVisible = true; - DrawStageMapSelector(); - break; - default: - IsVisible = false; - break; - } -} + const auto pVftable = pApp->m_pDoc->GetDocMode()->m_pVftable.ptr.get(); -void DevTitleMenu::AudioCommon(DevMessage* message) -{ - switch (message->id) + switch (pVftable) { - case 0x10001: - Game_PlaySound("move"); - break; - case 0x10002: - if (message->id2 == 0x5A || message->id3) - Game_PlaySound("main_deside"); - else - Game_PlaySound("window_close"); - break; - case 0: - if (!message->id3) - Game_PlaySound("window_close"); - break; + // DevTitleMode + case 0x8202B1E0: + IsVisible = true; + DrawDevTitle(); + break; + + // StageSelectMode + case 0x820336A4: + IsVisible = true; + DrawStageSelect(); + break; + + // TODO: handle recovering from TestTextMode and TestCellSpriteMode. + default: + IsVisible = false; + break; } } diff --git a/MarathonRecomp/ui/dev_title_menu.h b/MarathonRecomp/ui/dev_title_menu.h index 56f5988de..1cc7623a2 100644 --- a/MarathonRecomp/ui/dev_title_menu.h +++ b/MarathonRecomp/ui/dev_title_menu.h @@ -1,45 +1,9 @@ #pragma once -#include - -constexpr float DevTitleDelayDefault = 1.0; -static float DevTitleDelay = DevTitleDelayDefault; - -struct StageMapSelectorConfig { - // Layout - float windowWidthRatio = 0.8f; - float buttonHeightRatio = 0.04f; - float leading = 1.25f; - int visibleItems = 16; - - // Positioning - ImVec2 paddingRatio = { 0.05f, 0.1f }; - - // Animation - float scrollSpeed = 16.0f; -}; - -static StageMapSelectorConfig s_StageMapSelectorConfig; -static float s_StageMapScroll = 0.0f; -static const void* s_pLastStageMap = nullptr; - -struct DevMessage -{ - be id; - be id2; - uint8_t id3; -}; - class DevTitleMenu { public: - static bool IsVisible; + static inline bool IsVisible = false; - static void Init(); static void Draw(); - - static void AudioCommon(DevMessage* message); - - static void Close(); - static bool CanClose(); }; From cbfc0be9bc3e9e1993fad1a42136504721f9923c Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Thu, 29 Oct 2026 00:38:10 +0000 Subject: [PATCH 08/15] Align debug menu to user preference --- MarathonRecomp/patches/aspect_ratio_patches.cpp | 16 ++++++++++++++++ MarathonRecomp/patches/aspect_ratio_patches.h | 4 ++++ MarathonRecomp/ui/dev_title_menu.cpp | 11 ++++++----- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/MarathonRecomp/patches/aspect_ratio_patches.cpp b/MarathonRecomp/patches/aspect_ratio_patches.cpp index 6899b0fa9..5d26c9d48 100644 --- a/MarathonRecomp/patches/aspect_ratio_patches.cpp +++ b/MarathonRecomp/patches/aspect_ratio_patches.cpp @@ -174,6 +174,22 @@ void AspectRatioPatches::ComputeOffsets() g_radarMapScale = 256 * g_aspectRatioScale * g_aspectRatioGameplayScale; } +float AspectRatioPatches::AlignX(float in_offset) +{ + if (Config::UIAlignmentMode == EUIAlignmentMode::Centre && g_aspectRatioOffsetX > 0) + return g_aspectRatioOffsetX + in_offset; + + return in_offset; +} + +float AspectRatioPatches::AlignY(float in_offset) +{ + if (Config::UIAlignmentMode == EUIAlignmentMode::Centre && g_aspectRatioOffsetY > 0) + return g_aspectRatioOffsetY + in_offset; + + return in_offset; +} + void EmplacePath(const void* key, const std::string_view& value) { std::lock_guard lock(g_pathMutex); diff --git a/MarathonRecomp/patches/aspect_ratio_patches.h b/MarathonRecomp/patches/aspect_ratio_patches.h index 61d51b59d..7766ee508 100644 --- a/MarathonRecomp/patches/aspect_ratio_patches.h +++ b/MarathonRecomp/patches/aspect_ratio_patches.h @@ -27,7 +27,11 @@ class AspectRatioPatches { public: static void Init(); + static void ComputeOffsets(); + + static float AlignX(float in_offset = 0.0f); + static float AlignY(float in_offset = 0.0f); }; // -------------- CSD MODIFIERS --------------- // diff --git a/MarathonRecomp/ui/dev_title_menu.cpp b/MarathonRecomp/ui/dev_title_menu.cpp index 5bf38de42..366a904fd 100644 --- a/MarathonRecomp/ui/dev_title_menu.cpp +++ b/MarathonRecomp/ui/dev_title_menu.cpp @@ -1,4 +1,5 @@ #include "dev_title_menu.h" +#include #include #include @@ -7,8 +8,8 @@ static void DrawInfo() const auto drawList = ImGui::GetBackgroundDrawList(); const auto fontSize = Scale(22); - drawList->AddText(g_pFntRodin, fontSize, { 0, Scale(4) }, IM_COL32_WHITE, "ソニック・ザ・ヘッジホッグ"); - drawList->AddText(g_pFntRodin, Scale(11), { Scale(350), Scale(34) }, IM_COL32_WHITE, "Mon Oct 23 05:01:13 2006"); + drawList->AddText(g_pFntRodin, fontSize, { AspectRatioPatches::AlignX(), Scale(4)}, IM_COL32_WHITE, "ソニック・ザ・ヘッジホッグ"); + drawList->AddText(g_pFntRodin, Scale(11), { AspectRatioPatches::AlignX(Scale(350)), Scale(34) }, IM_COL32_WHITE, "Mon Oct 23 05:01:13 2006"); } static void DrawItem(const ImVec2& in_rPosition, const char* in_pText, bool in_isSelected = false) @@ -49,7 +50,7 @@ static void DrawDevTitle() for (int i = 0; i < pDevTitleMode->m_Modes.size(); i++) { - const ImVec2 position = { itemMargin, itemMargin + i * itemHeight }; + const ImVec2 position = { AspectRatioPatches::AlignX(itemMargin), itemMargin + i * itemHeight }; DrawItem(position, items[i].pName.get(), i == selectedIndex); } @@ -94,7 +95,7 @@ static void DrawStageSelect() DrawInfo(); - drawList->AddText(g_pFntRodin, fontSize, { itemMargin, Scale(56) }, IM_COL32_WHITE, pCurrentStageMap->GetHierarchy().c_str()); + drawList->AddText(g_pFntRodin, fontSize, { AspectRatioPatches::AlignX(itemMargin), Scale(56) }, IM_COL32_WHITE, pCurrentStageMap->GetHierarchy().c_str()); drawList->PushClipRect({ 0, itemMargin }, { float(Video::s_viewportWidth), float(Video::s_viewportHeight) }); for (int i = 0; i < itemCount; i++) @@ -117,7 +118,7 @@ static void DrawStageSelect() if (hasChildren) label << "]"; - const ImVec2 position = { itemMargin, itemMargin + (i - scrollOffset) * itemHeight }; + const ImVec2 position = { AspectRatioPatches::AlignX(itemMargin), itemMargin + (i - scrollOffset) * itemHeight }; DrawItem(position, label.str().c_str(), i == selectedIndex); } From 1b3032645adc3263ed877e5e449b423f83b67fd7 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Thu, 29 Oct 2026 17:17:25 +0000 Subject: [PATCH 09/15] Restored original debug font renderer Requires ArialUni.ttf (until ImGui snapshot can generate an atlas for it), needs Shift-JIS -> UTF-8 transcoding implementation for Linux and macOS. --- MarathonRecomp/CMakeLists.txt | 5 +- .../api/Chao/CSD/Manager/csdmScene.h | 4 +- MarathonRecomp/api/Sonicteam/StageMap.h | 26 ++- .../api/Sonicteam/StageSelectMode.h | 20 +-- MarathonRecomp/gpu/video.cpp | 14 +- MarathonRecomp/gpu/video.h | 1 + MarathonRecomp/os/linux/locale_linux.cpp | 17 ++ MarathonRecomp/os/locale.h | 13 ++ MarathonRecomp/os/macos/locale_macos.cpp | 17 ++ MarathonRecomp/os/win32/locale_win32.cpp | 32 ++++ MarathonRecomp/patches/TitleTask_patches.cpp | 4 +- MarathonRecomp/patches/debug_patches.cpp | 51 ++++++ MarathonRecomp/patches/misc_patches.cpp | 17 -- MarathonRecomp/ui/dev_title_menu.cpp | 157 ------------------ MarathonRecomp/ui/dev_title_menu.h | 9 - MarathonRecomp/ui/imgui_utils.cpp | 2 + MarathonRecomp/ui/imgui_utils.h | 1 + 17 files changed, 183 insertions(+), 207 deletions(-) create mode 100644 MarathonRecomp/os/linux/locale_linux.cpp create mode 100644 MarathonRecomp/os/locale.h create mode 100644 MarathonRecomp/os/macos/locale_macos.cpp create mode 100644 MarathonRecomp/os/win32/locale_win32.cpp create mode 100644 MarathonRecomp/patches/debug_patches.cpp delete mode 100644 MarathonRecomp/ui/dev_title_menu.cpp delete mode 100644 MarathonRecomp/ui/dev_title_menu.h diff --git a/MarathonRecomp/CMakeLists.txt b/MarathonRecomp/CMakeLists.txt index 66859daf7..f9d865ddb 100644 --- a/MarathonRecomp/CMakeLists.txt +++ b/MarathonRecomp/CMakeLists.txt @@ -88,6 +88,7 @@ set(MARATHON_RECOMP_LOCALE_CXX_SOURCES if (WIN32) set(MARATHON_RECOMP_OS_CXX_SOURCES + "os/win32/locale_win32.cpp" "os/win32/logger_win32.cpp" "os/win32/media_win32.cpp" "os/win32/process_win32.cpp" @@ -97,6 +98,7 @@ if (WIN32) ) elseif (CMAKE_SYSTEM_NAME MATCHES "Linux") set(MARATHON_RECOMP_OS_CXX_SOURCES + "os/linux/locale_linux.cpp" "os/linux/logger_linux.cpp" "os/linux/media_linux.cpp" "os/linux/process_linux.cpp" @@ -106,6 +108,7 @@ elseif (CMAKE_SYSTEM_NAME MATCHES "Linux") ) elseif (APPLE) set(MARATHON_RECOMP_OS_CXX_SOURCES + "os/macos/locale_macos.cpp" "os/macos/logger_macos.cpp" "os/macos/media_macos.cpp" "os/macos/process_macos.cpp" @@ -145,6 +148,7 @@ set(MARATHON_RECOMP_PATCHES_CXX_SOURCES "patches/aspect_ratio_patches.cpp" "patches/audio_patches.cpp" "patches/camera_patches.cpp" + "patches/debug_patches.cpp" "patches/fps_patches.cpp" "patches/frontend_listener.cpp" "patches/input_patches.cpp" @@ -171,7 +175,6 @@ set(MARATHON_RECOMP_UI_CXX_SOURCES "ui/installer_wizard.cpp" "ui/message_window.cpp" "ui/options_menu.cpp" - "ui/dev_title_menu.cpp" ) set(MARATHON_RECOMP_INSTALL_CXX_SOURCES diff --git a/MarathonRecomp/api/Chao/CSD/Manager/csdmScene.h b/MarathonRecomp/api/Chao/CSD/Manager/csdmScene.h index 8f3ba42b2..d0dd95ea5 100644 --- a/MarathonRecomp/api/Chao/CSD/Manager/csdmScene.h +++ b/MarathonRecomp/api/Chao/CSD/Manager/csdmScene.h @@ -127,7 +127,8 @@ namespace Chao::CSD be m_MotionSpeed; be m_MotionStartFrame; be m_MotionEndFrame; - MARATHON_INSERT_PADDING(0x0C); + MARATHON_INSERT_PADDING(0x08); + be m_Field90; CSceneMotionRepeat m_MotionRepeat; be m_MotionIndex; be m_SortOrder; @@ -184,6 +185,7 @@ namespace Chao::CSD MARATHON_ASSERT_OFFSETOF(CScene, m_MotionSpeed, 0x7C); MARATHON_ASSERT_OFFSETOF(CScene, m_MotionStartFrame, 0x80); MARATHON_ASSERT_OFFSETOF(CScene, m_MotionEndFrame, 0x84); + MARATHON_ASSERT_OFFSETOF(CScene, m_Field90, 0x90); MARATHON_ASSERT_OFFSETOF(CScene, m_MotionRepeat, 0x94); MARATHON_ASSERT_OFFSETOF(CScene, m_MotionIndex, 0xB0); MARATHON_ASSERT_OFFSETOF(CScene, m_SortOrder, 0xB4); diff --git a/MarathonRecomp/api/Sonicteam/StageMap.h b/MarathonRecomp/api/Sonicteam/StageMap.h index c835440c9..7e2fab832 100644 --- a/MarathonRecomp/api/Sonicteam/StageMap.h +++ b/MarathonRecomp/api/Sonicteam/StageMap.h @@ -14,13 +14,12 @@ namespace Sonicteam { public: stdx::string m_Name; - stdx::string m_Text; + stdx::string m_Comment; stdx::vector> m_Children; - std::string GetHierarchy() + std::vector GetHierarchy() { - std::stringstream result{}; - std::vector hierarchy{}; + std::vector result{}; auto pStageMap = this; @@ -29,13 +28,24 @@ namespace Sonicteam if (pStageMap->m_Name.empty()) break; - hierarchy.push_back(pStageMap->m_Name.c_str()); + result.push_back(pStageMap->m_Name.c_str()); pStageMap = pStageMap->GetParent(); } - for (int i = hierarchy.size() - 1; i >= 0; i--) - result << "/" << hierarchy[i]; + std::reverse(result.begin(), result.end()); + + return result; + } + + std::string GetPath() + { + std::stringstream result{}; + + const auto hierarchy = GetHierarchy(); + + for (const auto& rName : hierarchy) + result << "/" << rName; return result.str(); } @@ -52,7 +62,7 @@ namespace Sonicteam }; MARATHON_ASSERT_OFFSETOF(StageMap, m_Name, 0x20); - MARATHON_ASSERT_OFFSETOF(StageMap, m_Text, 0x3C); + MARATHON_ASSERT_OFFSETOF(StageMap, m_Comment, 0x3C); MARATHON_ASSERT_OFFSETOF(StageMap, m_Children, 0x58); MARATHON_ASSERT_SIZEOF(StageMap, 0x68); } diff --git a/MarathonRecomp/api/Sonicteam/StageSelectMode.h b/MarathonRecomp/api/Sonicteam/StageSelectMode.h index cf9f8ff87..a3c1f5b8f 100644 --- a/MarathonRecomp/api/Sonicteam/StageSelectMode.h +++ b/MarathonRecomp/api/Sonicteam/StageSelectMode.h @@ -10,26 +10,26 @@ namespace Sonicteam class StageSelectMode : public SoX::Engine::DocMode { public: - xpointer m_RootStageMap; - xpointer m_CurrentStageMap; + xpointer m_pRootStageMap; + xpointer m_pCurrentStageMap; MARATHON_INSERT_PADDING(0x04); be m_StageMapIndex; be m_StageMapDepth; MARATHON_INSERT_PADDING(0x18); xpointer m_pMenuListener; - xpointer> m_StageMapFilter; - xpointer m_StageMapType; - xpointer m_StageMapName; + xpointer> m_pStageMapFilter; + xpointer m_pStageMapType; + xpointer m_pStageMapName; MARATHON_INSERT_PADDING(0x04); }; - MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_RootStageMap, 0x50); - MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_CurrentStageMap, 0x54); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_pRootStageMap, 0x50); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_pCurrentStageMap, 0x54); MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapIndex, 0x5C); MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapDepth, 0x60); MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_pMenuListener, 0x7C); - MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapFilter, 0x80); - MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapType, 0x84); - MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_StageMapName, 0x88); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_pStageMapFilter, 0x80); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_pStageMapType, 0x84); + MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_pStageMapName, 0x88); MARATHON_ASSERT_SIZEOF(StageSelectMode, 0x90); } diff --git a/MarathonRecomp/gpu/video.cpp b/MarathonRecomp/gpu/video.cpp index 5ee7245e8..6c8199516 100644 --- a/MarathonRecomp/gpu/video.cpp +++ b/MarathonRecomp/gpu/video.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -2996,6 +2995,13 @@ static void DrawFPS() drawList->AddText(font, fontSize, textPos, IM_COL32_WHITE, fmt.c_str()); } +static std::vector> g_imguiDrawQueue{}; + +void Video::EnqueueImGuiDraw(const std::function& in_rEvent) +{ + g_imguiDrawQueue.push_back(in_rEvent); +} + static void DrawImGui() { ImGui_ImplSDL3_NewFrame(); @@ -3036,7 +3042,6 @@ static void DrawImGui() ResetImGuiCallbacks(); UpdateImGuiUtils(); - DevTitleMenu::Draw(); AchievementMenu::Draw(); OptionsMenu::Draw(); InstallerWizard::Draw(); @@ -3046,6 +3051,11 @@ static void DrawImGui() Fader::Draw(); BlackBar::Draw(); + for (const auto& rEvent : g_imguiDrawQueue) + rEvent(); + + g_imguiDrawQueue.clear(); + assert(ImGui::GetBackgroundDrawList()->_ClipRectStack.Size == 1 && "Some clip rects were not removed from the stack!"); DrawFPS(); diff --git a/MarathonRecomp/gpu/video.h b/MarathonRecomp/gpu/video.h index 08cff1cd2..6d18d0108 100644 --- a/MarathonRecomp/gpu/video.h +++ b/MarathonRecomp/gpu/video.h @@ -30,6 +30,7 @@ struct Video static inline bool s_needsResize; + static void EnqueueImGuiDraw(const std::function& in_rEvent); static bool CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry); static void WaitOnSwapChain(); static void Present(); diff --git a/MarathonRecomp/os/linux/locale_linux.cpp b/MarathonRecomp/os/linux/locale_linux.cpp new file mode 100644 index 000000000..d0ca396ec --- /dev/null +++ b/MarathonRecomp/os/linux/locale_linux.cpp @@ -0,0 +1,17 @@ +#include + +std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& in_rStr) +{ + std::string result{}; + + switch (in_encoding) + { + case Encoding::ShiftJIS: + { + // TODO + break; + } + } + + return result; +} diff --git a/MarathonRecomp/os/locale.h b/MarathonRecomp/os/locale.h new file mode 100644 index 000000000..bafcc6782 --- /dev/null +++ b/MarathonRecomp/os/locale.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace os::locale +{ + enum class Encoding + { + ShiftJIS + }; + + std::string ConvertToUTF8(Encoding in_encoding, const std::string& in_rStr); +} diff --git a/MarathonRecomp/os/macos/locale_macos.cpp b/MarathonRecomp/os/macos/locale_macos.cpp new file mode 100644 index 000000000..d0ca396ec --- /dev/null +++ b/MarathonRecomp/os/macos/locale_macos.cpp @@ -0,0 +1,17 @@ +#include + +std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& in_rStr) +{ + std::string result{}; + + switch (in_encoding) + { + case Encoding::ShiftJIS: + { + // TODO + break; + } + } + + return result; +} diff --git a/MarathonRecomp/os/win32/locale_win32.cpp b/MarathonRecomp/os/win32/locale_win32.cpp new file mode 100644 index 000000000..36855a525 --- /dev/null +++ b/MarathonRecomp/os/win32/locale_win32.cpp @@ -0,0 +1,32 @@ +#include + +std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& in_rStr) +{ + std::string result{}; + + switch (in_encoding) + { + case Encoding::ShiftJIS: + { + const auto lengthU16 = MultiByteToWideChar(932, 0, in_rStr.c_str(), in_rStr.size(), NULL, 0); + + if (lengthU16 <= 0) + break; + + auto wide = std::wstring(lengthU16, 0); + MultiByteToWideChar(932, 0, in_rStr.c_str(), in_rStr.size(), &wide[0], lengthU16); + + const auto lengthU8 = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), lengthU16, NULL, 0, NULL, NULL); + + if (lengthU8 <= 0) + break; + + result = std::string(lengthU8, 0); + WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), lengthU16, &result[0], lengthU8, NULL, NULL); + + break; + } + } + + return result; +} diff --git a/MarathonRecomp/patches/TitleTask_patches.cpp b/MarathonRecomp/patches/TitleTask_patches.cpp index c32b0060b..5604f9e0d 100644 --- a/MarathonRecomp/patches/TitleTask_patches.cpp +++ b/MarathonRecomp/patches/TitleTask_patches.cpp @@ -137,8 +137,8 @@ PPC_FUNC(sub_825126A0) if (g_secretFlags == SECRET_ALL) { - LOGFN_WARNING("Secret unlocked!"); - Game_PlaySound("totalring_count"); // TODO: why no worky... + LOGN_WARNING("Secret unlocked!"); + Game_PlaySound("totalring_count"); OptionsMenu::s_isDebugUnlocked = true; } diff --git a/MarathonRecomp/patches/debug_patches.cpp b/MarathonRecomp/patches/debug_patches.cpp new file mode 100644 index 000000000..351389d2a --- /dev/null +++ b/MarathonRecomp/patches/debug_patches.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include + +// Sonicteam::SoX::Graphics::Xenon::DeviceXenon::DrawText +PPC_FUNC(sub_8289CB70) +{ + const auto pText = reinterpret_cast(base + ctx.r4.u32); + + auto pTextSeek = pText; + + while (*pTextSeek++); + + uint32_t textLengthInChars = pTextSeek - pText; + uint32_t textLengthInCharsNoTerminator = textLengthInChars - 1; + + if (textLengthInChars > 0x7FFFFFFF || !textLengthInCharsNoTerminator) + return; + + const auto text = os::locale::ConvertToUTF8(os::locale::Encoding::ShiftJIS, std::string(pText, textLengthInCharsNoTerminator)); + const auto x = float(ctx.r5.u32); + const auto y = float(ctx.r6.u32); + const auto colour = reinterpret_cast(base + ctx.r7.u32); + + Video::EnqueueImGuiDraw([=]() + { + const auto drawList = ImGui::GetBackgroundDrawList(); + + drawList->AddText(g_pFntArialUni, Scale(12), { AspectRatioPatches::AlignX(Scale(x)), Scale(y) }, IM_COL32(colour->R(), colour->G(), colour->B(), colour->A()), text.c_str()); + }); +} + +// Sonicteam::DocMarathonState::StartMainMode +PPC_FUNC_IMPL(__imp__sub_82161AB8); +PPC_FUNC(sub_82161AB8) +{ + if (!Config::BootDirectlyToDevTitle) + { + __imp__sub_82161AB8(ctx, base); + return; + } + + const auto pDoc = reinterpret_cast(base + ctx.r3.u32); + + GuestToHostFunction(sub_825E9E28, ctx.r3.u32, ctx.r4.u32); + + ctx.r3.u32 = g_memory.MapVirtual(pDoc->SetDocState(StateDevTitle::GetInstance())); +} diff --git a/MarathonRecomp/patches/misc_patches.cpp b/MarathonRecomp/patches/misc_patches.cpp index 4c0e3655f..27c5d9a76 100644 --- a/MarathonRecomp/patches/misc_patches.cpp +++ b/MarathonRecomp/patches/misc_patches.cpp @@ -216,23 +216,6 @@ PPC_FUNC(sub_824A6EA8) __imp__sub_824A6EA8(ctx, base); } -// Sonicteam::DocMarathonState::StartMainMode -PPC_FUNC_IMPL(__imp__sub_82161AB8); -PPC_FUNC(sub_82161AB8) -{ - if (!Config::BootDirectlyToDevTitle) - { - __imp__sub_82161AB8(ctx, base); - return; - } - - const auto pDoc = reinterpret_cast(base + ctx.r3.u32); - - GuestToHostFunction(sub_825E9E28, ctx.r3.u32, ctx.r4.u32); - - ctx.r3.u32 = g_memory.MapVirtual(pDoc->SetDocState(StateDevTitle::GetInstance())); -} - PPC_FUNC(sub_82188460) { ctx.r3.u64 = Config::Subtitles; diff --git a/MarathonRecomp/ui/dev_title_menu.cpp b/MarathonRecomp/ui/dev_title_menu.cpp deleted file mode 100644 index 366a904fd..000000000 --- a/MarathonRecomp/ui/dev_title_menu.cpp +++ /dev/null @@ -1,157 +0,0 @@ -#include "dev_title_menu.h" -#include -#include -#include - -static void DrawInfo() -{ - const auto drawList = ImGui::GetBackgroundDrawList(); - const auto fontSize = Scale(22); - - drawList->AddText(g_pFntRodin, fontSize, { AspectRatioPatches::AlignX(), Scale(4)}, IM_COL32_WHITE, "ソニック・ザ・ヘッジホッグ"); - drawList->AddText(g_pFntRodin, Scale(11), { AspectRatioPatches::AlignX(Scale(350)), Scale(34) }, IM_COL32_WHITE, "Mon Oct 23 05:01:13 2006"); -} - -static void DrawItem(const ImVec2& in_rPosition, const char* in_pText, bool in_isSelected = false) -{ - const auto drawList = ImGui::GetBackgroundDrawList(); - const auto fontSize = Scale(22); - - if (in_isSelected) - drawList->AddText(g_pFntRodin, fontSize, { in_rPosition.x - Scale(35), in_rPosition.y }, IM_COL32_WHITE, ">>"); - - drawList->AddText(g_pFntRodin, fontSize, in_rPosition, IM_COL32_WHITE, in_pText); -} - -static void DrawDevTitle() -{ - const auto pDevTitleMode = App::s_pApp->m_pDoc->GetDocMode(); - - if (!pDevTitleMode) - return; - - if (const auto pPictureTitle = StateDevTitle::GetInstance()->m_pPictureTitle.get()) - { - if (pPictureTitle->m_State != Sonicteam::PictureTitle::PicState_Next) - return; - } - - const auto drawList = ImGui::GetBackgroundDrawList(); - - const auto& items = pDevTitleMode->m_Modes; - const auto selectedIndex = pDevTitleMode->m_SelectedIndex.get(); - - const auto itemMargin = Scale(80); - const auto itemHeight = Scale(25); - - drawList->AddRectFilled({ 0, 0 }, { float(Video::s_viewportWidth), float(Video::s_viewportHeight) }, IM_COL32_BLACK); - - DrawInfo(); - - for (int i = 0; i < pDevTitleMode->m_Modes.size(); i++) - { - const ImVec2 position = { AspectRatioPatches::AlignX(itemMargin), itemMargin + i * itemHeight }; - - DrawItem(position, items[i].pName.get(), i == selectedIndex); - } -} - -static void DrawStageSelect() -{ - const auto pStageSelectMode = App::s_pApp->m_pDoc->GetDocMode(); - - if (!pStageSelectMode) - return; - - const auto drawList = ImGui::GetBackgroundDrawList(); - - const auto& items = pStageSelectMode->m_CurrentStageMap->m_Children; - const auto itemCount = int(items.size()); - const auto selectedIndex = int(pStageSelectMode->m_StageMapIndex.get()); - - const auto fontSize = Scale(22); - const auto itemMargin = Scale(80); - const auto itemHeight = Scale(25); - - // Ease the scroll offset towards the selection, snapping when the list changes. - const auto visibleItems = int((Video::s_viewportHeight - (itemMargin * 2.0f)) / itemHeight); - const auto startOffset = std::max(0, itemCount - visibleItems); - const auto targetOffset = std::clamp(selectedIndex - visibleItems / 2, 0, startOffset); - - const auto pCurrentStageMap = pStageSelectMode->m_CurrentStageMap.get(); - - static Sonicteam::StageMap* pPrevStageMap{}; - static float scrollOffset{}; - - if (pCurrentStageMap != pPrevStageMap) - { - pPrevStageMap = pCurrentStageMap; - scrollOffset = targetOffset; - } - else - { - scrollOffset += (targetOffset - scrollOffset) * (1.0f - expf(-16.0f * App::s_deltaTime)); - } - - DrawInfo(); - - drawList->AddText(g_pFntRodin, fontSize, { AspectRatioPatches::AlignX(itemMargin), Scale(56) }, IM_COL32_WHITE, pCurrentStageMap->GetHierarchy().c_str()); - drawList->PushClipRect({ 0, itemMargin }, { float(Video::s_viewportWidth), float(Video::s_viewportHeight) }); - - for (int i = 0; i < itemCount; i++) - { - const auto& pStageMap = items[i]; - - if (!pStageMap) - continue; - - // The game automatically enters the first child node if only one exists. - const auto hasChildren = pStageMap->GetTotalChildCount() > 1; - - std::stringstream label{}; - - if (hasChildren) - label << "["; - - label << pStageMap->m_Name.c_str(); - - if (hasChildren) - label << "]"; - - const ImVec2 position = { AspectRatioPatches::AlignX(itemMargin), itemMargin + (i - scrollOffset) * itemHeight }; - - DrawItem(position, label.str().c_str(), i == selectedIndex); - } - - drawList->PopClipRect(); -} - -void DevTitleMenu::Draw() -{ - const auto pApp = App::s_pApp; - - if (!pApp || !pApp->m_pDoc || !pApp->m_pDoc->m_pDocMode) - return; - - const auto pVftable = pApp->m_pDoc->GetDocMode()->m_pVftable.ptr.get(); - - switch (pVftable) - { - // DevTitleMode - case 0x8202B1E0: - IsVisible = true; - DrawDevTitle(); - break; - - // StageSelectMode - case 0x820336A4: - IsVisible = true; - DrawStageSelect(); - break; - - // TODO: handle recovering from TestTextMode and TestCellSpriteMode. - default: - IsVisible = false; - break; - } -} diff --git a/MarathonRecomp/ui/dev_title_menu.h b/MarathonRecomp/ui/dev_title_menu.h deleted file mode 100644 index 1cc7623a2..000000000 --- a/MarathonRecomp/ui/dev_title_menu.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -class DevTitleMenu -{ -public: - static inline bool IsVisible = false; - - static void Draw(); -}; diff --git a/MarathonRecomp/ui/imgui_utils.cpp b/MarathonRecomp/ui/imgui_utils.cpp index 5cbded176..4bd0b8f61 100644 --- a/MarathonRecomp/ui/imgui_utils.cpp +++ b/MarathonRecomp/ui/imgui_utils.cpp @@ -21,6 +21,7 @@ ImFont* g_pFntRodin; ImFont* g_pFntNewRodin; +ImFont* g_pFntArialUni; float g_fntRodinSize{}; @@ -38,6 +39,7 @@ void InitImGuiUtils() { g_pFntRodin = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); g_pFntNewRodin = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); + g_pFntArialUni = ImFontAtlasSnapshot::GetFont("ArialUni.ttf"); g_upTexButtonWindow = LOAD_ZSTD_TEXTURE(g_button_window); g_upTexController = LOAD_ZSTD_TEXTURE(g_controller); diff --git a/MarathonRecomp/ui/imgui_utils.h b/MarathonRecomp/ui/imgui_utils.h index 952044e34..262728551 100644 --- a/MarathonRecomp/ui/imgui_utils.h +++ b/MarathonRecomp/ui/imgui_utils.h @@ -20,6 +20,7 @@ extern ImFont* g_pFntRodin; extern ImFont* g_pFntNewRodin; +extern ImFont* g_pFntArialUni; extern float g_fntRodinSize; From 94a1de03f34f60fecd9507c7b6e7af00ab89580a Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 29 Oct 2026 11:15:04 -0700 Subject: [PATCH 10/15] Add Shift-JIS conversion for Linux and macOS --- MarathonRecomp/CMakeLists.txt | 2 + MarathonRecomp/os/linux/locale_linux.cpp | 63 +++++++++++++++++++++--- MarathonRecomp/os/macos/locale_macos.cpp | 26 ++++++++-- MarathonRecomp/os/win32/locale_win32.cpp | 12 ++--- 4 files changed, 84 insertions(+), 19 deletions(-) diff --git a/MarathonRecomp/CMakeLists.txt b/MarathonRecomp/CMakeLists.txt index f9d865ddb..18714a304 100644 --- a/MarathonRecomp/CMakeLists.txt +++ b/MarathonRecomp/CMakeLists.txt @@ -384,6 +384,8 @@ if (WIN32) winmm windowsapp ) +elseif(NOT APPLE) + target_link_libraries(MarathonRecomp PRIVATE iconv) endif() target_link_libraries(MarathonRecomp PRIVATE diff --git a/MarathonRecomp/os/linux/locale_linux.cpp b/MarathonRecomp/os/linux/locale_linux.cpp index d0ca396ec..b771f3ef7 100644 --- a/MarathonRecomp/os/linux/locale_linux.cpp +++ b/MarathonRecomp/os/linux/locale_linux.cpp @@ -1,17 +1,66 @@ +#include #include -std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& in_rStr) +#define ICONV_INVALID (reinterpret_cast(-1)) + +class IconvContext { - std::string result{}; +public: + IconvContext(const char* to, const char* from) + { + cd = iconv_open(to, from); + assert(cd != ICONV_INVALID && "iconv_open failed"); + } - switch (in_encoding) + ~IconvContext() { - case Encoding::ShiftJIS: + if (cd != ICONV_INVALID) + iconv_close(cd); + } + + std::string Convert(const std::string& str) + { + std::string result{}; + if (cd == ICONV_INVALID) + return result; + + auto* in = const_cast(str.data()); + size_t in_remain = str.size(); + + char buf[128]; + char* out = buf; + size_t out_remain = sizeof(buf); + + while (in_remain > 0) { - // TODO - break; + if (iconv(cd, &in, &in_remain, &out, &out_remain) == -1) + { + break; + } + result.append(buf, out); + + out = buf; + out_remain = sizeof(buf); } + + return result; } - return result; +private: + iconv_t cd = ICONV_INVALID; +}; + +std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& in_rStr) +{ + switch (in_encoding) + { + case Encoding::ShiftJIS: + { + thread_local IconvContext context("UTF-8", "CP932"); + return context.Convert(in_rStr); + } + default: + assert(false && "Unimplemented."); + return ""; + } } diff --git a/MarathonRecomp/os/macos/locale_macos.cpp b/MarathonRecomp/os/macos/locale_macos.cpp index d0ca396ec..46460826d 100644 --- a/MarathonRecomp/os/macos/locale_macos.cpp +++ b/MarathonRecomp/os/macos/locale_macos.cpp @@ -1,17 +1,33 @@ +#include #include std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& in_rStr) { - std::string result{}; - + CFStringEncoding cfEncoding; switch (in_encoding) { case Encoding::ShiftJIS: - { - // TODO + cfEncoding = kCFStringEncodingDOSJapanese; break; - } + default: + assert(false && "Unimplemented."); + return ""; } + CFDataRef cfData = CFDataCreateWithBytesNoCopy(nullptr, reinterpret_cast(in_rStr.data()), + static_cast(in_rStr.size()), kCFAllocatorNull); + assert(data && "CFDataCreateWithBytesNoCopy failed"); + + CFStringRef cfStr = CFStringCreateFromExternalRepresentation(nullptr, cfData, cfEncoding); + CFRelease(cfData); + assert(cfStr && "CFStringCreateFromExternalRepresentation failed"); + + CFDataRef cfConvertedData = CFStringCreateExternalRepresentation(nullptr, cfStr, kCFStringEncodingUTF8, 0); + CFRelease(cfStr); + assert(cfConvertedData && "CFStringCreateExternalRepresentation failed"); + + std::string result(reinterpret_cast(CFDataGetBytePtr(cfConvertedData)), CFDataGetLength(cfConvertedData)); + CFRelease(cfConvertedData); + return result; } diff --git a/MarathonRecomp/os/win32/locale_win32.cpp b/MarathonRecomp/os/win32/locale_win32.cpp index 36855a525..0ea1bb3ce 100644 --- a/MarathonRecomp/os/win32/locale_win32.cpp +++ b/MarathonRecomp/os/win32/locale_win32.cpp @@ -2,8 +2,6 @@ std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& in_rStr) { - std::string result{}; - switch (in_encoding) { case Encoding::ShiftJIS: @@ -21,12 +19,12 @@ std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& i if (lengthU8 <= 0) break; - result = std::string(lengthU8, 0); + std::string result = std::string(lengthU8, 0); WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), lengthU16, &result[0], lengthU8, NULL, NULL); - - break; + return result; } + default: + assert(false && "Unimplemented."); + return ""; } - - return result; } From 7575c4aef15d8a71888b5f168760df8158365d3c Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:39:43 +0000 Subject: [PATCH 11/15] Implemented font atlas generation for debug menu font --- MarathonRecomp/api/Marathon.h | 6 + MarathonRecomp/api/Sonicteam/Globals.h | 4 +- MarathonRecomp/api/Sonicteam/LuaNode.h | 39 +++ MarathonRecomp/api/Sonicteam/LuaNodeImp.h | 17 ++ MarathonRecomp/api/Sonicteam/LuaSystem.h | 13 + .../api/Sonicteam/LuaSystemManager.h | 13 + MarathonRecomp/api/Sonicteam/LuaTableAccess.h | 46 ++++ .../api/Sonicteam/LuaTableAccessImp.h | 20 ++ .../api/Sonicteam/SoX/ResourceManager.h | 14 + .../api/Sonicteam/StageSelectMode.h | 25 ++ .../api/Sonicteam/System/Singleton.h | 12 +- MarathonRecomp/api/Sonicteam/TextBookMgr.h | 1 + MarathonRecomp/api/lua50/lua.h | 12 + MarathonRecomp/app.cpp | 22 +- MarathonRecomp/gpu/imgui/imgui_snapshot.cpp | 241 ++++++++++++++++-- MarathonRecomp/gpu/imgui/imgui_snapshot.h | 16 +- MarathonRecomp/gpu/video.cpp | 225 +++------------- MarathonRecomp/gpu/video.h | 51 ++++ MarathonRecomp/main.cpp | 3 + MarathonRecomp/os/linux/locale_linux.cpp | 17 +- MarathonRecomp/os/macos/locale_macos.cpp | 5 +- MarathonRecomp/os/win32/locale_win32.cpp | 8 +- MarathonRecomp/patches/debug_patches.cpp | 11 +- MarathonRecomp/patches/misc_patches.cpp | 12 +- MarathonRecomp/ui/imgui_utils.cpp | 13 +- 25 files changed, 588 insertions(+), 258 deletions(-) create mode 100644 MarathonRecomp/api/Sonicteam/LuaNode.h create mode 100644 MarathonRecomp/api/Sonicteam/LuaNodeImp.h create mode 100644 MarathonRecomp/api/Sonicteam/LuaSystem.h create mode 100644 MarathonRecomp/api/Sonicteam/LuaSystemManager.h create mode 100644 MarathonRecomp/api/Sonicteam/LuaTableAccess.h create mode 100644 MarathonRecomp/api/Sonicteam/LuaTableAccessImp.h diff --git a/MarathonRecomp/api/Marathon.h b/MarathonRecomp/api/Marathon.h index 3b19eb67c..fb3cb028a 100644 --- a/MarathonRecomp/api/Marathon.h +++ b/MarathonRecomp/api/Marathon.h @@ -66,6 +66,12 @@ #include "Sonicteam/HudTextParts.h" #include "Sonicteam/ImageFilter.h" #include "Sonicteam/LoadingTask.h" +#include "Sonicteam/LuaNode.h" +#include "Sonicteam/LuaNodeImp.h" +#include "Sonicteam/LuaSystem.h" +#include "Sonicteam/LuaSystemManager.h" +#include "Sonicteam/LuaTableAccess.h" +#include "Sonicteam/LuaTableAccessImp.h" #include "Sonicteam/MainDisplayTask.h" #include "Sonicteam/MainMenuExpositionTask.h" #include "Sonicteam/MainMenuTask.h" diff --git a/MarathonRecomp/api/Sonicteam/Globals.h b/MarathonRecomp/api/Sonicteam/Globals.h index aec3f3407..0879b1dbb 100644 --- a/MarathonRecomp/api/Sonicteam/Globals.h +++ b/MarathonRecomp/api/Sonicteam/Globals.h @@ -25,14 +25,14 @@ namespace Sonicteam { static inline stdx::string* ms_pCurrentRenderScript{}; - static inline be* ms_apMainDisplayColours[9]{}; + static inline be* ms_MainDisplayColours[9]{}; static void Init() { ms_pCurrentRenderScript = reinterpret_cast(MmGetHostAddress(0x82B814F8)); for (int i = 0; i < 9; i++) - ms_apMainDisplayColours[i] = reinterpret_cast*>(MmGetHostAddress(0x82036BE4 + (i * 4))); + ms_MainDisplayColours[i] = reinterpret_cast*>(MmGetHostAddress(0x82036BE4 + (i * 4))); } }; } diff --git a/MarathonRecomp/api/Sonicteam/LuaNode.h b/MarathonRecomp/api/Sonicteam/LuaNode.h new file mode 100644 index 000000000..0268a5f88 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/LuaNode.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include + +namespace Sonicteam +{ + class LuaNode + { + public: + struct Vftable + { + MARATHON_INSERT_PADDING(0x60); + be fpGetTable; + }; + + xpointer m_pVftable; + xpointer m_pLuaState; + + boost::shared_ptr GetTable(const char* in_pName) + { + const auto result = guest_stack_var>(); + const auto name = guest_stack_var(in_pName); + + GuestToHostFunction(m_pVftable->fpGetTable.get(), result.get(), this, name->c_str()); + + return *result; + } + }; + + MARATHON_ASSERT_OFFSETOF(LuaNode::Vftable, fpGetTable, 0x60); + MARATHON_ASSERT_SIZEOF(LuaNode::Vftable, 0x64); + + MARATHON_ASSERT_OFFSETOF(LuaNode, m_pVftable, 0x00); + MARATHON_ASSERT_OFFSETOF(LuaNode, m_pLuaState, 0x04); + MARATHON_ASSERT_SIZEOF(LuaNode, 0x08); +} diff --git a/MarathonRecomp/api/Sonicteam/LuaNodeImp.h b/MarathonRecomp/api/Sonicteam/LuaNodeImp.h new file mode 100644 index 000000000..588ae7356 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/LuaNodeImp.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +namespace Sonicteam +{ + class LuaNodeImp : public LuaNode + { + public: + stdx::string m_Name; + }; + + MARATHON_ASSERT_OFFSETOF(LuaNodeImp, m_Name, 0x08); + MARATHON_ASSERT_SIZEOF(LuaNodeImp, 0x24); +} diff --git a/MarathonRecomp/api/Sonicteam/LuaSystem.h b/MarathonRecomp/api/Sonicteam/LuaSystem.h new file mode 100644 index 000000000..594c9f6cb --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/LuaSystem.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include +#include + +namespace Sonicteam +{ + class LuaSystem : public LuaNodeImp, public SoX::IResource2 {}; + + MARATHON_ASSERT_SIZEOF(LuaSystem, 0x88); +} diff --git a/MarathonRecomp/api/Sonicteam/LuaSystemManager.h b/MarathonRecomp/api/Sonicteam/LuaSystemManager.h new file mode 100644 index 000000000..41d2bb384 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/LuaSystemManager.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include +#include + +namespace Sonicteam +{ + class LuaSystemManager : public SoX::IResourceMgr, public System::Singleton> {}; + + MARATHON_ASSERT_SIZEOF(LuaSystemManager, 0x10); +} diff --git a/MarathonRecomp/api/Sonicteam/LuaTableAccess.h b/MarathonRecomp/api/Sonicteam/LuaTableAccess.h new file mode 100644 index 000000000..45ccdb6fe --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/LuaTableAccess.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace Sonicteam +{ + class LuaTableAccess + { + public: + struct Vftable + { + be fpDtor; + be fpIterate; + be fpGetElement; + MARATHON_INSERT_PADDING(0x1C); + be fpIsTable; + }; + + xpointer m_pVftable; + + bool Iterate() + { + return GuestToHostFunction(m_pVftable->fpIterate, this); + } + + const char* GetElement(const char* in_pName) + { + const auto name = guest_stack_var(in_pName); + + return GuestToHostFunction(m_pVftable->fpGetElement, this, name->c_str()); + } + + bool IsTable() + { + return GuestToHostFunction(m_pVftable->fpIsTable, this); + } + }; + + MARATHON_ASSERT_OFFSETOF(LuaTableAccess::Vftable, fpDtor, 0x00); + MARATHON_ASSERT_OFFSETOF(LuaTableAccess::Vftable, fpIterate, 0x04); + MARATHON_ASSERT_OFFSETOF(LuaTableAccess::Vftable, fpIsTable, 0x28); + MARATHON_ASSERT_SIZEOF(LuaTableAccess::Vftable, 0x2C); + + MARATHON_ASSERT_OFFSETOF(LuaTableAccess, m_pVftable, 0x00); + MARATHON_ASSERT_SIZEOF(LuaTableAccess, 0x04); +} diff --git a/MarathonRecomp/api/Sonicteam/LuaTableAccessImp.h b/MarathonRecomp/api/Sonicteam/LuaTableAccessImp.h new file mode 100644 index 000000000..f66b78fb9 --- /dev/null +++ b/MarathonRecomp/api/Sonicteam/LuaTableAccessImp.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include + +namespace Sonicteam +{ + class LuaTableAccessImp : public LuaTableAccess + { + public: + xpointer m_pLuaState; + bool m_IsRoot; + MARATHON_INSERT_PADDING(0x04); + }; + + MARATHON_ASSERT_OFFSETOF(LuaTableAccessImp, m_pLuaState, 0x04); + MARATHON_ASSERT_OFFSETOF(LuaTableAccessImp, m_IsRoot, 0x08); + MARATHON_ASSERT_SIZEOF(LuaTableAccessImp, 0x10); +} diff --git a/MarathonRecomp/api/Sonicteam/SoX/ResourceManager.h b/MarathonRecomp/api/Sonicteam/SoX/ResourceManager.h index c5c9b2b43..087dec8e9 100644 --- a/MarathonRecomp/api/Sonicteam/SoX/ResourceManager.h +++ b/MarathonRecomp/api/Sonicteam/SoX/ResourceManager.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include #include @@ -9,6 +11,7 @@ #include #include #include +#include namespace Sonicteam::SoX { @@ -39,6 +42,17 @@ namespace Sonicteam::SoX stdx::map, ResourceMgrInfo> m_Managers; MARATHON_INSERT_PADDING(0x04); stdx::map, be, stdx::string_compare> m_ManagerIDs; + + template + TRes* LoadResource(const char* in_pFilePath) + { + const auto result = boost::make_unique>(); + const auto scriptName = boost::make_unique(in_pFilePath); + + GuestToHostFunction(sub_82582648, result.get(), TMgr::GetInstance(), scriptName.get(), scriptName.get(), 0, 1); + + return static_cast(result->get()); + } }; MARATHON_ASSERT_OFFSETOF(ResourceManager::ResourceMgrInfo, fpFunc00, 0x00); diff --git a/MarathonRecomp/api/Sonicteam/StageSelectMode.h b/MarathonRecomp/api/Sonicteam/StageSelectMode.h index a3c1f5b8f..556fba992 100644 --- a/MarathonRecomp/api/Sonicteam/StageSelectMode.h +++ b/MarathonRecomp/api/Sonicteam/StageSelectMode.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -21,6 +22,30 @@ namespace Sonicteam xpointer m_pStageMapType; xpointer m_pStageMapName; MARATHON_INSERT_PADDING(0x04); + + private: + bool Walk(StageMap* in_pStageMap, const std::function& in_rVisitor) + { + if (!in_pStageMap) + return true; + + if (!in_rVisitor(in_pStageMap)) + return false; + + for (const auto& rChild : in_pStageMap->m_Children) + { + if (!Walk(rChild, in_rVisitor)) + return false; + } + + return true; + } + + public: + void Walk(const std::function& in_rVisitor) + { + Walk(m_pRootStageMap.get(), in_rVisitor); + } }; MARATHON_ASSERT_OFFSETOF(StageSelectMode, m_pRootStageMap, 0x50); diff --git a/MarathonRecomp/api/Sonicteam/System/Singleton.h b/MarathonRecomp/api/Sonicteam/System/Singleton.h index 163d50a73..e56406cd5 100644 --- a/MarathonRecomp/api/Sonicteam/System/Singleton.h +++ b/MarathonRecomp/api/Sonicteam/System/Singleton.h @@ -1,9 +1,13 @@ #pragma once +#include + namespace Sonicteam::System { + class ISingleton {}; + template - class Singleton + class Singleton : public ISingleton { private: inline static TCreator ms_Creator{}; @@ -19,4 +23,10 @@ namespace Sonicteam::System return *pInstance; } }; + + template + concept DerivedSingleton = std::derived_from && requires + { + { T::GetInstance() } -> std::same_as; + }; } diff --git a/MarathonRecomp/api/Sonicteam/TextBookMgr.h b/MarathonRecomp/api/Sonicteam/TextBookMgr.h index 93c5e8afc..6809ac695 100644 --- a/MarathonRecomp/api/Sonicteam/TextBookMgr.h +++ b/MarathonRecomp/api/Sonicteam/TextBookMgr.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/MarathonRecomp/api/lua50/lua.h b/MarathonRecomp/api/lua50/lua.h index c14e7b3d1..f8b2f4bc3 100644 --- a/MarathonRecomp/api/lua50/lua.h +++ b/MarathonRecomp/api/lua50/lua.h @@ -4,6 +4,18 @@ namespace lua50 { + static inline constexpr int LUA_TNONE = -1; + static inline constexpr int LUA_TNIL = 0; + static inline constexpr int LUA_TBOOLEAN = 1; + static inline constexpr int LUA_TLIGHTUSERDATA = 2; + static inline constexpr int LUA_TNUMBER = 3; + static inline constexpr int LUA_TSTRING = 4; + static inline constexpr int LUA_TTABLE = 5; + static inline constexpr int LUA_TFUNCTION = 6; + static inline constexpr int LUA_TUSERDATA = 7; + static inline constexpr int LUA_TTHREAD = 8; + static inline constexpr int LUA_NUMTYPES = 9; + using lua_State = xpointer; using lua_CFunction = be; diff --git a/MarathonRecomp/app.cpp b/MarathonRecomp/app.cpp index 0c937bc95..d557cf5d0 100644 --- a/MarathonRecomp/app.cpp +++ b/MarathonRecomp/app.cpp @@ -1,4 +1,5 @@ #include "app.h" +#include #include #include #include @@ -51,7 +52,7 @@ PPC_FUNC(sub_8262A568) be Height; }; - auto pRenderConfig = reinterpret_cast(g_memory.Translate(ctx.r4.u32)); + auto pRenderConfig = reinterpret_cast(base + ctx.r4.u32); pRenderConfig->Width = Video::s_viewportWidth; pRenderConfig->Height = Video::s_viewportHeight; @@ -63,8 +64,9 @@ PPC_FUNC(sub_8262A568) __imp__sub_8262A568(ctx, base); - App::s_pApp = (Sonicteam::AppMarathon*)g_memory.Translate(ctx.r3.u32); + App::s_pApp = reinterpret_cast(base + ctx.r3.u32); + ImFontAtlasSnapshot::Init(); InitPatches(); } @@ -132,20 +134,10 @@ PPC_FUNC(sub_825EA610) PPC_FUNC_IMPL(__imp__sub_82582648); PPC_FUNC(sub_82582648) { - struct File - { - public: - MARATHON_INSERT_PADDING(4); - xpointer pFilePath; - MARATHON_INSERT_PADDING(0x0C); - be Length; - be Capacity; - }; - - auto pFile = reinterpret_cast(base + ctx.r5.u32); + const auto pPath = reinterpret_cast(base + ctx.r5.u32); - if (pFile->pFilePath && pFile->Length > 0) - LOGFN_UTILITY("Loading file: {}", pFile->pFilePath.get()); + if (pPath && !pPath->empty()) + LOGFN_UTILITY("Loading file: {}", pPath->c_str()); __imp__sub_82582648(ctx, base); } diff --git a/MarathonRecomp/gpu/imgui/imgui_snapshot.cpp b/MarathonRecomp/gpu/imgui/imgui_snapshot.cpp index b6454cd04..db968bdcd 100644 --- a/MarathonRecomp/gpu/imgui/imgui_snapshot.cpp +++ b/MarathonRecomp/gpu/imgui/imgui_snapshot.cpp @@ -1,11 +1,28 @@ #include "imgui_snapshot.h" - +#include #include +#include #include +#include #include -#include -#include #include +#include + +extern ImFontBuilderIO g_fontBuilderIO; + + +struct FontInfo +{ + const char* pFileName{}; + std::vector GlyphRanges{}; +}; + +static std::map g_fonts = +{ + { ImFontType::Rodin, { "FOT-RodinPro-DB.otf" } }, + { ImFontType::NewRodin, { "FOT-NewRodinPro-UB.otf" } }, + { ImFontType::Arial, { "ArialUni.ttf" } } +}; template void ImFontAtlasSnapshot::SnapPointer(size_t offset, const T1& value, const T2& ptr, size_t count) @@ -125,7 +142,6 @@ ImFontAtlas* ImFontAtlasSnapshot::Load() return reinterpret_cast(g_imFontAtlas.get() + header->dataOffset); } - static void GetGlyphs(std::set& glyphs, const std::string_view& value) { const char* cur = value.data(); @@ -137,11 +153,9 @@ static void GetGlyphs(std::set& glyphs, const std::string_view& value) } } -static std::vector g_glyphRanges; - -void ImFontAtlasSnapshot::GenerateGlyphRanges() +static void GenerateGlyphRanges(ImFontType in_fontType, bool in_isInstaller = false) { - std::vector localeStrings; + std::vector localeStrings{}; for (auto& config : g_configDefinitions) config->GetLocaleStrings(localeStrings); @@ -160,48 +174,221 @@ void ImFontAtlasSnapshot::GenerateGlyphRanges() GetGlyphs(glyphs, value); } - for (size_t i = XDBF_LANGUAGE_ENGLISH; i <= XDBF_LANGUAGE_ITALIAN; i++) + auto& rFontInfo = g_fonts[in_fontType]; + + if (in_isInstaller) { - auto achievements = g_xdbfWrapper.GetAchievements(static_cast(i)); - for (auto& achievement : achievements) + LOGFN_WARNING("Generating font atlas for \"{}\" in the installer wizard. Glyphs may be missing!", rFontInfo.pFileName); + } + else + { + if (in_fontType == ImFontType::Rodin) + { + for (size_t i = XDBF_LANGUAGE_ENGLISH; i <= XDBF_LANGUAGE_ITALIAN; i++) + { + auto achievements = g_xdbfWrapper.GetAchievements(static_cast(i)); + + for (auto& achievement : achievements) + { + GetGlyphs(glyphs, achievement.Name); + GetGlyphs(glyphs, achievement.UnlockedDesc); + GetGlyphs(glyphs, achievement.LockedDesc); + } + } + } + else if (in_fontType == ImFontType::Arial) { - GetGlyphs(glyphs, achievement.Name); - GetGlyphs(glyphs, achievement.UnlockedDesc); - GetGlyphs(glyphs, achievement.LockedDesc); + const std::array stageSelectStrings = + { + g_memory.Translate(0x820336C4), + g_memory.Translate(0x820336C8), + g_memory.Translate(0x820336D0) + }; + + for (const auto& rStr : stageSelectStrings) + GetGlyphs(glyphs, os::locale::ConvertToUTF8(os::locale::Encoding::ShiftJIS, reinterpret_cast(rStr))); + + if (const auto pResourceManager = Sonicteam::SoX::ResourceManager::GetInstance()) + { + if (const auto pStageSelectLua = pResourceManager->LoadResource("scripts/stage/stageselect.lua")) + { + if (const auto pStageListTable = pStageSelectLua->GetTable("StageList")) + { + if (pStageListTable->Iterate() && pStageListTable->IsTable()) + { + do + { + GetGlyphs(glyphs, os::locale::ConvertToUTF8(os::locale::Encoding::ShiftJIS, pStageListTable->GetElement("Comment"))); + } + while (pStageListTable->Iterate()); + } + } + } + } } } + auto& rGlyphRanges = rFontInfo.GlyphRanges; + for (auto glyph : glyphs) { - if (g_glyphRanges.empty() || (g_glyphRanges.back() + 1) != glyph) + if (rGlyphRanges.empty() || (rGlyphRanges.back() + 1) != glyph) { - g_glyphRanges.push_back(glyph); - g_glyphRanges.push_back(glyph); + rGlyphRanges.push_back(glyph); + rGlyphRanges.push_back(glyph); } else { - g_glyphRanges.back() = glyph; + rGlyphRanges.back() = glyph; } } - g_glyphRanges.push_back(0); + rGlyphRanges.push_back(0); + + ImGui::GetIO().Fonts->AddFontFromFileTTF(rFontInfo.pFileName, 24.0f, nullptr, rGlyphRanges.data()); } -ImFont* ImFontAtlasSnapshot::GetFont(const char* name) +void ImFontAtlasSnapshot::Init(bool in_isInstaller) { - auto fontAtlas = ImGui::GetIO().Fonts; - for (auto& configData : fontAtlas->ConfigData) + auto& io = ImGui::GetIO(); + +#ifdef ENABLE_IM_FONT_ATLAS_SNAPSHOT + IM_DELETE(io.Fonts); + io.Fonts = ImFontAtlasSnapshot::Load(); + + FontTexture = LoadTexture + ( + decompressZstd + ( + g_im_font_atlas_texture, + g_im_font_atlas_texture_uncompressed_size + ) + .get(), + + g_im_font_atlas_texture_uncompressed_size + ); +#else + io.Fonts->AddFontDefault(); + + GenerateGlyphRanges(ImFontType::Rodin, in_isInstaller); + GenerateGlyphRanges(ImFontType::NewRodin, in_isInstaller); + GenerateGlyphRanges(ImFontType::Arial, in_isInstaller); + + io.Fonts->FontBuilderIO = &g_fontBuilderIO; + io.Fonts->Build(); + + FontTexture = std::make_unique(ResourceType::Texture); + + uint8_t* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + RenderTextureDesc textureDesc; + textureDesc.dimension = RenderTextureDimension::TEXTURE_2D; + textureDesc.width = width; + textureDesc.height = height; + textureDesc.depth = 1; + textureDesc.mipLevels = 1; + textureDesc.arraySize = 1; + textureDesc.format = RenderFormat::R8G8B8A8_UNORM; + + const auto pRenderDevice = Video::GetRenderDevice(); + + FontTexture->textureHolder = pRenderDevice->createTexture(textureDesc); + FontTexture->texture = FontTexture->textureHolder.get(); + + uint32_t rowPitch = (width * 4 + PITCH_ALIGNMENT - 1) & ~(PITCH_ALIGNMENT - 1); + uint32_t slicePitch = (rowPitch * height + PLACEMENT_ALIGNMENT - 1) & ~(PLACEMENT_ALIGNMENT - 1); + auto uploadBuffer = pRenderDevice->createBuffer(RenderBufferDesc::UploadBuffer(slicePitch)); + uint8_t* mappedMemory = reinterpret_cast(uploadBuffer->map()); + + if (rowPitch == (width * 4)) { - if (strstr(configData.Name, name) != nullptr) + memcpy(mappedMemory, pixels, slicePitch); + } + else + { + for (size_t i = 0; i < height; i++) { - assert(configData.DstFont != nullptr); - return configData.DstFont; + memcpy(mappedMemory, pixels, width * 4); + pixels += width * 4; + mappedMemory += rowPitch; } } + uploadBuffer->unmap(); + + Video::ExecuteCopyCommandList([&](RenderCommandList* in_pCommandList) + { + in_pCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(FontTexture->texture, RenderTextureLayout::COPY_DEST)); + + in_pCommandList->copyTextureRegion( + RenderTextureCopyLocation::Subresource(FontTexture->texture, 0), + RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), RenderFormat::R8G8B8A8_UNORM, width, height, 1, rowPitch / 4, 0)); + }); + + FontTexture->layout = RenderTextureLayout::COPY_DEST; + + RenderTextureViewDesc textureViewDesc; + textureViewDesc.format = textureDesc.format; + textureViewDesc.dimension = RenderTextureViewDimension::TEXTURE_2D; + textureViewDesc.mipLevels = 1; + FontTexture->textureView = FontTexture->texture->createTextureView(textureViewDesc); + + FontTexture->descriptorIndex = g_textureDescriptorAllocator.allocate(); + g_textureDescriptorSet->setTexture(FontTexture->descriptorIndex, FontTexture->texture, RenderTextureLayout::SHADER_READ, FontTexture->textureView.get()); +#endif + + io.Fonts->SetTexID(FontTexture.get()); + +#ifndef ENABLE_IM_FONT_ATLAS_SNAPSHOT + ImFontAtlasSnapshot snapshot{}; + snapshot.Snap(); + + if (FILE* file = fopen("im_font_atlas.bin", "wb")) + { + fwrite(snapshot.data.data(), 1, snapshot.data.size(), file); + fclose(file); + } + + ddspp::Header header; + ddspp::HeaderDXT10 headerDX10; + ddspp::encode_header(ddspp::R8G8B8A8_UNORM, width, height, 1, ddspp::Texture2D, 1, 1, header, headerDX10); + + if (FILE* file = fopen("im_font_atlas.dds", "wb")) + { + fwrite(&ddspp::DDS_MAGIC, 4, 1, file); + fwrite(&header, sizeof(header), 1, file); + fwrite(&headerDX10, sizeof(headerDX10), 1, file); + fwrite(pixels, 4, width * height, file); + fclose(file); + } +#endif + + IsInit = true; +} + +ImFont* ImFontAtlasSnapshot::GetFont(ImFontType in_fontType) +{ + const auto& rFileName = g_fonts[in_fontType].pFileName; + + const auto pFontAtlas = ImGui::GetIO().Fonts; + + for (const auto& rConfigData : pFontAtlas->ConfigData) + { + if (strstr(rConfigData.Name, rFileName) != nullptr) + { + assert(rConfigData.DstFont != nullptr); + + return rConfigData.DstFont; + } + } + + LOGFN_ERROR("Failed to locate font in the font atlas: {}", rFileName); + #ifdef ENABLE_IM_FONT_ATLAS_SNAPSHOT - assert(false && "Unable to locate equivalent font in the atlas file."); + assert(false && "Failed to locate font in the font atlas."); #endif - return fontAtlas->AddFontFromFileTTF(name, 24.0f, nullptr, g_glyphRanges.data()); + return nullptr; } diff --git a/MarathonRecomp/gpu/imgui/imgui_snapshot.h b/MarathonRecomp/gpu/imgui/imgui_snapshot.h index a77745165..b5bcf3853 100644 --- a/MarathonRecomp/gpu/imgui/imgui_snapshot.h +++ b/MarathonRecomp/gpu/imgui/imgui_snapshot.h @@ -5,12 +5,24 @@ // characters in the locale get added to the atlas. #define ENABLE_IM_FONT_ATLAS_SNAPSHOT +struct GuestTexture; + +enum class ImFontType +{ + Rodin, + NewRodin, + Arial +}; + struct ImFontAtlasSnapshot { std::vector data; ankerl::unordered_dense::map objects; std::vector offsets; + static inline bool IsInit{}; + static inline std::unique_ptr FontTexture; + template void SnapPointer(size_t offset, const T1& value, const T2& ptr, size_t count); @@ -24,8 +36,8 @@ struct ImFontAtlasSnapshot static ImFontAtlas* Load(); - static void GenerateGlyphRanges(); + static void Init(bool in_isInstaller = false); // When ENABLE_IM_FONT_ATLAS_SNAPSHOT is undefined, this creates the font runtime instead. - static ImFont* GetFont(const char* name); + static ImFont* GetFont(ImFontType in_fontType); }; diff --git a/MarathonRecomp/gpu/video.cpp b/MarathonRecomp/gpu/video.cpp index 6c8199516..31b388a8d 100644 --- a/MarathonRecomp/gpu/video.cpp +++ b/MarathonRecomp/gpu/video.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -368,47 +367,6 @@ static constexpr uint32_t CONDITIONAL_SURVEY_MAX = 64; static std::unique_ptr g_conditionalSurveyBuffer; static std::unique_ptr g_conditionalSurveyDescriptorSet; -enum -{ - TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D, - TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D_ARRAY, - TEXTURE_DESCRIPTOR_NULL_TEXTURE_CUBE, - TEXTURE_DESCRIPTOR_NULL_COUNT -}; - -struct TextureDescriptorAllocator -{ - Mutex mutex; - uint32_t capacity = TEXTURE_DESCRIPTOR_NULL_COUNT; - std::vector freed; - - uint32_t allocate() - { - std::lock_guard lock(mutex); - - uint32_t value; - if (!freed.empty()) - { - value = freed.back(); - freed.pop_back(); - } - else - { - value = capacity; - ++capacity; - } - - return value; - } - - void free(uint32_t value) - { - assert(value != NULL); - std::lock_guard lock(mutex); - freed.push_back(value); - } -}; - static std::unique_ptr g_blankTextures[TEXTURE_DESCRIPTOR_NULL_COUNT]; static std::unique_ptr g_blankTextureViews[TEXTURE_DESCRIPTOR_NULL_COUNT]; @@ -1604,26 +1562,21 @@ static bool DetectWine() static constexpr size_t TEXTURE_DESCRIPTOR_SIZE = 32768; static constexpr size_t SAMPLER_DESCRIPTOR_SIZE = 1024; -static std::unique_ptr g_imFontTexture; static std::unique_ptr g_imPipelineLayout; static std::unique_ptr g_imPipeline; static std::unique_ptr g_imAdditivePipeline; -template -static void ExecuteCopyCommandList(const T& function) +void Video::ExecuteCopyCommandList(const std::function& function) { std::lock_guard lock(g_copyMutex); g_copyCommandList->begin(); - function(); + function(g_copyCommandList.get()); g_copyCommandList->end(); g_copyQueue->executeCommandLists(g_copyCommandList.get(), g_copyCommandFence.get()); g_copyQueue->waitForCommandFence(g_copyCommandFence.get()); } -static constexpr uint32_t PITCH_ALIGNMENT = 0x100; -static constexpr uint32_t PLACEMENT_ALIGNMENT = 0x200; - struct ImGuiPushConstants { ImVec2 boundsMin{}; @@ -1642,8 +1595,6 @@ struct ImGuiPushConstants float outline{}; }; -extern ImFontBuilderIO g_fontBuilderIO; - static void CreateImGuiBackend() { ImGuiIO& io = ImGui::GetIO(); @@ -1651,89 +1602,12 @@ static void CreateImGuiBackend() io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; -#ifdef ENABLE_IM_FONT_ATLAS_SNAPSHOT - IM_DELETE(io.Fonts); - io.Fonts = ImFontAtlasSnapshot::Load(); -#else - io.Fonts->AddFontDefault(); - ImFontAtlasSnapshot::GenerateGlyphRanges(); -#endif - InitImGuiUtils(); OptionsMenu::Init(); InstallerWizard::Init(); ImGui_ImplSDL3_InitForOther(GameWindow::s_pWindow); -#ifdef ENABLE_IM_FONT_ATLAS_SNAPSHOT - g_imFontTexture = LoadTexture( - decompressZstd(g_im_font_atlas_texture, g_im_font_atlas_texture_uncompressed_size).get(), g_im_font_atlas_texture_uncompressed_size); -#else - io.Fonts->FontBuilderIO = &g_fontBuilderIO; - io.Fonts->Build(); - - g_imFontTexture = std::make_unique(ResourceType::Texture); - - uint8_t* pixels; - int width, height; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - - RenderTextureDesc textureDesc; - textureDesc.dimension = RenderTextureDimension::TEXTURE_2D; - textureDesc.width = width; - textureDesc.height = height; - textureDesc.depth = 1; - textureDesc.mipLevels = 1; - textureDesc.arraySize = 1; - textureDesc.format = RenderFormat::R8G8B8A8_UNORM; - - g_imFontTexture->textureHolder = g_device->createTexture(textureDesc); - g_imFontTexture->texture = g_imFontTexture->textureHolder.get(); - - uint32_t rowPitch = (width * 4 + PITCH_ALIGNMENT - 1) & ~(PITCH_ALIGNMENT - 1); - uint32_t slicePitch = (rowPitch * height + PLACEMENT_ALIGNMENT - 1) & ~(PLACEMENT_ALIGNMENT - 1); - auto uploadBuffer = g_device->createBuffer(RenderBufferDesc::UploadBuffer(slicePitch)); - uint8_t* mappedMemory = reinterpret_cast(uploadBuffer->map()); - - if (rowPitch == (width * 4)) - { - memcpy(mappedMemory, pixels, slicePitch); - } - else - { - for (size_t i = 0; i < height; i++) - { - memcpy(mappedMemory, pixels, width * 4); - pixels += width * 4; - mappedMemory += rowPitch; - } - } - - uploadBuffer->unmap(); - - ExecuteCopyCommandList([&] - { - g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(g_imFontTexture->texture, RenderTextureLayout::COPY_DEST)); - - g_copyCommandList->copyTextureRegion( - RenderTextureCopyLocation::Subresource(g_imFontTexture->texture, 0), - RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), RenderFormat::R8G8B8A8_UNORM, width, height, 1, rowPitch / 4, 0)); - }); - - g_imFontTexture->layout = RenderTextureLayout::COPY_DEST; - - RenderTextureViewDesc textureViewDesc; - textureViewDesc.format = textureDesc.format; - textureViewDesc.dimension = RenderTextureViewDimension::TEXTURE_2D; - textureViewDesc.mipLevels = 1; - g_imFontTexture->textureView = g_imFontTexture->texture->createTextureView(textureViewDesc); - - g_imFontTexture->descriptorIndex = g_textureDescriptorAllocator.allocate(); - g_textureDescriptorSet->setTexture(g_imFontTexture->descriptorIndex, g_imFontTexture->texture, RenderTextureLayout::SHADER_READ, g_imFontTexture->textureView.get()); -#endif - - io.Fonts->SetTexID(g_imFontTexture.get()); - RenderPipelineLayoutBuilder pipelineLayoutBuilder; pipelineLayoutBuilder.begin(false, true); @@ -1778,32 +1652,6 @@ static void CreateImGuiBackend() pipelineDesc.renderTargetBlend[0].dstBlend = RenderBlend::ONE; g_imAdditivePipeline = g_device->createGraphicsPipeline(pipelineDesc); - -#ifndef ENABLE_IM_FONT_ATLAS_SNAPSHOT - ImFontAtlasSnapshot snapshot; - snapshot.Snap(); - - FILE* file = fopen("im_font_atlas.bin", "wb"); - if (file) - { - fwrite(snapshot.data.data(), 1, snapshot.data.size(), file); - fclose(file); - } - - ddspp::Header header; - ddspp::HeaderDXT10 headerDX10; - ddspp::encode_header(ddspp::R8G8B8A8_UNORM, width, height, 1, ddspp::Texture2D, 1, 1, header, headerDX10); - - file = fopen("im_font_atlas.dds", "wb"); - if (file) - { - fwrite(&ddspp::DDS_MAGIC, 4, 1, file); - fwrite(&header, sizeof(header), 1, file); - fwrite(&headerDX10, sizeof(headerDX10), 1, file); - fwrite(pixels, 4, width * height, file); - fclose(file); - } -#endif } static void CheckSwapChain() @@ -2037,6 +1885,11 @@ static bool TryCreateHostDevice(std::vector& interface return true; } +RenderDevice* Video::GetRenderDevice() +{ + return g_device.get(); +} + bool Video::CreateHostDevice(const char* sdlVideoDriver, bool graphicsApiRetry) { for (uint32_t i = 0; i < 16; i++) @@ -2580,10 +2433,10 @@ static void UnlockBuffer(GuestBuffer* buffer, bool useCopyQueue) if (useCopyQueue) { - ExecuteCopyCommandList([&] - { - g_copyCommandList->copyBufferRegion(buffer->buffer->at(0), uploadBuffer->at(0), buffer->dataSize); - }); + Video::ExecuteCopyCommandList([&](RenderCommandList* in_pCommandList) + { + in_pCommandList->copyBufferRegion(buffer->buffer->at(0), uploadBuffer->at(0), buffer->dataSize); + }); } else { @@ -2715,10 +2568,10 @@ static void DrawProfiler() if (!g_profilerVisible) return; - ImFont* font = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); - const auto defaultFontScale = font->Scale; - font->Scale = ImGui::GetDefaultFont()->FontSize / font->FontSize; - ImGui::PushFont(font); + const auto pFont = g_pFntRodin; + const auto defaultFontScale = pFont->Scale; + pFont->Scale = ImGui::GetDefaultFont()->FontSize / pFont->FontSize; + ImGui::PushFont(pFont); #define IMGUI_GENERIC_ROW(name, value, ...) \ do \ @@ -2955,7 +2808,7 @@ static void DrawProfiler() ImGui::End(); ImGui::PopFont(); - font->Scale = defaultFontScale; + pFont->Scale = defaultFontScale; } static void DrawFPS() @@ -2983,16 +2836,15 @@ static void DrawFPS() auto drawList = ImGui::GetBackgroundDrawList(); auto fmt = fmt::format("FPS: {:.2f}", fps); - auto font = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); auto fontSize = Scale(10); - auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, fmt.c_str()); + auto textSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, fmt.c_str()); ImVec2 min = { Scale(40), Scale(30) }; ImVec2 max = { min.x + std::max(Scale(75), textSize.x + Scale(10)), min.y + Scale(15) }; ImVec2 textPos = { min.x + Scale(2), CENTRE_TEXT_VERT(min, max, textSize) + Scale(0.2f) }; drawList->AddRectFilled(min, max, IM_COL32(0, 0, 0, 200)); - drawList->AddText(font, fontSize, textPos, IM_COL32_WHITE, fmt.c_str()); + drawList->AddText(g_pFntRodin, fontSize, textPos, IM_COL32_WHITE, fmt.c_str()); } static std::vector> g_imguiDrawQueue{}; @@ -3004,6 +2856,9 @@ void Video::EnqueueImGuiDraw(const std::function& in_rEvent) static void DrawImGui() { + if (!ImFontAtlasSnapshot::IsInit) + return; + ImGui_ImplSDL3_NewFrame(); auto& io = ImGui::GetIO(); @@ -3193,7 +3048,7 @@ static void ProcDrawImGui(const RenderCommand& cmd) descriptorIndex = texture->descriptorIndex; - if (texture == g_imFontTexture.get()) + if (texture == ImFontAtlasSnapshot::FontTexture.get()) descriptorIndex |= 0x80000000; setPushConstants(&pushConstants.texture2DDescriptorIndex, &descriptorIndex, sizeof(descriptorIndex)); @@ -6878,19 +6733,19 @@ static bool LoadTexture(GuestTexture& texture, const uint8_t* data, size_t dataS uploadBuffer->unmap(); - ExecuteCopyCommandList([&] - { - g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(texture.texture, RenderTextureLayout::COPY_DEST)); + Video::ExecuteCopyCommandList([&](RenderCommandList* in_pCommandList) + { + in_pCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(texture.texture, RenderTextureLayout::COPY_DEST)); - for (size_t i = 0; i < slices.size(); i++) - { - auto& slice = slices[i]; + for (size_t i = 0; i < slices.size(); i++) + { + auto& slice = slices[i]; - g_copyCommandList->copyTextureRegion( - RenderTextureCopyLocation::Subresource(texture.texture, i % desc.mipLevels, i / desc.mipLevels), - RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), desc.format, slice.width, slice.height, slice.depth, (slice.dstRowPitch * 8) / ddsDesc.bitsPerPixelOrBlock * ddsDesc.blockWidth, slice.dstOffset)); - } - }); + in_pCommandList->copyTextureRegion( + RenderTextureCopyLocation::Subresource(texture.texture, i % desc.mipLevels, i / desc.mipLevels), + RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), desc.format, slice.width, slice.height, slice.depth, (slice.dstRowPitch * 8) / ddsDesc.bitsPerPixelOrBlock * ddsDesc.blockWidth, slice.dstOffset)); + } + }); return true; } @@ -6935,14 +6790,14 @@ static bool LoadTexture(GuestTexture& texture, const uint8_t* data, size_t dataS stbi_image_free(stbImage); - ExecuteCopyCommandList([&] - { - g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(texture.texture, RenderTextureLayout::COPY_DEST)); + Video::ExecuteCopyCommandList([&](RenderCommandList* in_pCommandList) + { + in_pCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(texture.texture, RenderTextureLayout::COPY_DEST)); - g_copyCommandList->copyTextureRegion( - RenderTextureCopyLocation::Subresource(texture.texture, 0), - RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), RenderFormat::R8G8B8A8_UNORM, width, height, 1, rowPitch / 4, 0)); - }); + in_pCommandList->copyTextureRegion( + RenderTextureCopyLocation::Subresource(texture.texture, 0), + RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), RenderFormat::R8G8B8A8_UNORM, width, height, 1, rowPitch / 4, 0)); + }); return true; } diff --git a/MarathonRecomp/gpu/video.h b/MarathonRecomp/gpu/video.h index 6d18d0108..cec7149d6 100644 --- a/MarathonRecomp/gpu/video.h +++ b/MarathonRecomp/gpu/video.h @@ -21,6 +21,9 @@ #define LOAD_ZSTD_TEXTURE(name) LoadTexture(decompressZstd(name, name##_uncompressed_size).get(), name##_uncompressed_size) +static inline constexpr uint32_t PITCH_ALIGNMENT = 0x100; +static inline constexpr uint32_t PLACEMENT_ALIGNMENT = 0x200; + using namespace plume; struct Video @@ -30,6 +33,10 @@ struct Video static inline bool s_needsResize; + // TODO: unspaghettify. + static RenderDevice* GetRenderDevice(); + static void ExecuteCopyCommandList(const std::function& function); + static void EnqueueImGuiDraw(const std::function& in_rEvent); static bool CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry); static void WaitOnSwapChain(); @@ -47,6 +54,50 @@ enum class Backend METAL }; +enum +{ + TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D, + TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D_ARRAY, + TEXTURE_DESCRIPTOR_NULL_TEXTURE_CUBE, + TEXTURE_DESCRIPTOR_NULL_COUNT +}; + +struct TextureDescriptorAllocator +{ + Mutex mutex; + uint32_t capacity = TEXTURE_DESCRIPTOR_NULL_COUNT; + std::vector freed; + + uint32_t allocate() + { + std::lock_guard lock(mutex); + + uint32_t value; + if (!freed.empty()) + { + value = freed.back(); + freed.pop_back(); + } + else + { + value = capacity; + ++capacity; + } + + return value; + } + + void free(uint32_t value) + { + assert(value != NULL); + std::lock_guard lock(mutex); + freed.push_back(value); + } +}; + +extern TextureDescriptorAllocator g_textureDescriptorAllocator; +extern std::unique_ptr g_textureDescriptorSet; + struct GuestSamplerState { be data[6]; diff --git a/MarathonRecomp/main.cpp b/MarathonRecomp/main.cpp index 951a7397b..b7855cb77 100644 --- a/MarathonRecomp/main.cpp +++ b/MarathonRecomp/main.cpp @@ -4,6 +4,7 @@ #include #endif #include +#include #include #include #include @@ -317,6 +318,8 @@ int main(int argc, char *argv[]) std::_Exit(1); } + ImFontAtlasSnapshot::Init(true); + if (!InstallerWizard::Run(GetGamePath(), isGameInstalled && forceDLCInstaller)) std::_Exit(0); } diff --git a/MarathonRecomp/os/linux/locale_linux.cpp b/MarathonRecomp/os/linux/locale_linux.cpp index b771f3ef7..b6a5b5e28 100644 --- a/MarathonRecomp/os/linux/locale_linux.cpp +++ b/MarathonRecomp/os/linux/locale_linux.cpp @@ -1,5 +1,5 @@ -#include #include +#include #define ICONV_INVALID (reinterpret_cast(-1)) @@ -54,13 +54,12 @@ std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& i { switch (in_encoding) { - case Encoding::ShiftJIS: - { - thread_local IconvContext context("UTF-8", "CP932"); - return context.Convert(in_rStr); - } - default: - assert(false && "Unimplemented."); - return ""; + case Encoding::ShiftJIS: + thread_local IconvContext context("UTF-8", "CP932"); + return context.Convert(in_rStr); } + + assert(false && "Unimplemented."); + + return ""; } diff --git a/MarathonRecomp/os/macos/locale_macos.cpp b/MarathonRecomp/os/macos/locale_macos.cpp index 46460826d..5c936763d 100644 --- a/MarathonRecomp/os/macos/locale_macos.cpp +++ b/MarathonRecomp/os/macos/locale_macos.cpp @@ -1,14 +1,16 @@ -#include #include +#include std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& in_rStr) { CFStringEncoding cfEncoding; + switch (in_encoding) { case Encoding::ShiftJIS: cfEncoding = kCFStringEncodingDOSJapanese; break; + default: assert(false && "Unimplemented."); return ""; @@ -16,6 +18,7 @@ std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& i CFDataRef cfData = CFDataCreateWithBytesNoCopy(nullptr, reinterpret_cast(in_rStr.data()), static_cast(in_rStr.size()), kCFAllocatorNull); + assert(data && "CFDataCreateWithBytesNoCopy failed"); CFStringRef cfStr = CFStringCreateFromExternalRepresentation(nullptr, cfData, cfEncoding); diff --git a/MarathonRecomp/os/win32/locale_win32.cpp b/MarathonRecomp/os/win32/locale_win32.cpp index 0ea1bb3ce..7a44bcea6 100644 --- a/MarathonRecomp/os/win32/locale_win32.cpp +++ b/MarathonRecomp/os/win32/locale_win32.cpp @@ -21,10 +21,12 @@ std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& i std::string result = std::string(lengthU8, 0); WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), lengthU16, &result[0], lengthU8, NULL, NULL); + return result; } - default: - assert(false && "Unimplemented."); - return ""; } + + assert(false && "Unimplemented."); + + return ""; } diff --git a/MarathonRecomp/patches/debug_patches.cpp b/MarathonRecomp/patches/debug_patches.cpp index 351389d2a..d17a50409 100644 --- a/MarathonRecomp/patches/debug_patches.cpp +++ b/MarathonRecomp/patches/debug_patches.cpp @@ -27,9 +27,14 @@ PPC_FUNC(sub_8289CB70) Video::EnqueueImGuiDraw([=]() { - const auto drawList = ImGui::GetBackgroundDrawList(); - - drawList->AddText(g_pFntArialUni, Scale(12), { AspectRatioPatches::AlignX(Scale(x)), Scale(y) }, IM_COL32(colour->R(), colour->G(), colour->B(), colour->A()), text.c_str()); + ImGui::GetBackgroundDrawList()->AddText + ( + g_pFntArialUni, + Scale(12), + { AspectRatioPatches::AlignX(Scale(x)), Scale(y) }, + IM_COL32(colour->R(), colour->G(), colour->B(), colour->A()), + text.c_str() + ); }); } diff --git a/MarathonRecomp/patches/misc_patches.cpp b/MarathonRecomp/patches/misc_patches.cpp index 27c5d9a76..0d59f85e1 100644 --- a/MarathonRecomp/patches/misc_patches.cpp +++ b/MarathonRecomp/patches/misc_patches.cpp @@ -24,12 +24,12 @@ void SetLifeBarAnimation(PPCRegister& r3, PPCRegister& r4, PPCRegister& r5, PPCR if (Config::RestoreContextualHUDColours) { - *Sonicteam::Globals::ms_apMainDisplayColours[Sonicteam::Character_Shadow] = 1.0f; - *Sonicteam::Globals::ms_apMainDisplayColours[Sonicteam::Character_Omega] = 1.0f; - *Sonicteam::Globals::ms_apMainDisplayColours[Sonicteam::Character_Rouge] = 1.0f; - *Sonicteam::Globals::ms_apMainDisplayColours[Sonicteam::Character_Silver] = 2.0f; - *Sonicteam::Globals::ms_apMainDisplayColours[Sonicteam::Character_Amy] = 2.0f; - *Sonicteam::Globals::ms_apMainDisplayColours[Sonicteam::Character_Blaze] = 2.0f; + *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Shadow] = 1.0f; + *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Omega] = 1.0f; + *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Rouge] = 1.0f; + *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Silver] = 2.0f; + *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Amy] = 2.0f; + *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Blaze] = 2.0f; } s_initContextualHUD = true; diff --git a/MarathonRecomp/ui/imgui_utils.cpp b/MarathonRecomp/ui/imgui_utils.cpp index 4bd0b8f61..7862980e5 100644 --- a/MarathonRecomp/ui/imgui_utils.cpp +++ b/MarathonRecomp/ui/imgui_utils.cpp @@ -19,6 +19,7 @@ #include #include +static bool g_initFonts{}; ImFont* g_pFntRodin; ImFont* g_pFntNewRodin; ImFont* g_pFntArialUni; @@ -37,10 +38,6 @@ std::unique_ptr g_upTexArrow; void InitImGuiUtils() { - g_pFntRodin = ImFontAtlasSnapshot::GetFont("FOT-RodinPro-DB.otf"); - g_pFntNewRodin = ImFontAtlasSnapshot::GetFont("FOT-NewRodinPro-UB.otf"); - g_pFntArialUni = ImFontAtlasSnapshot::GetFont("ArialUni.ttf"); - g_upTexButtonWindow = LOAD_ZSTD_TEXTURE(g_button_window); g_upTexController = LOAD_ZSTD_TEXTURE(g_controller); g_upTexKbm = LOAD_ZSTD_TEXTURE(g_kbm); @@ -54,6 +51,14 @@ void InitImGuiUtils() void UpdateImGuiUtils() { + if (!g_initFonts) + { + g_pFntRodin = ImFontAtlasSnapshot::GetFont(ImFontType::Rodin); + g_pFntNewRodin = ImFontAtlasSnapshot::GetFont(ImFontType::NewRodin); + g_pFntArialUni = ImFontAtlasSnapshot::GetFont(ImFontType::Arial); + g_initFonts = true; + } + g_fntRodinSize = Scale(Config::Language == ELanguage::Japanese ? 28 : 27, true); } From bcd588f79d410392394910683969a2165a844e2c Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 29 Oct 2026 17:44:02 -0700 Subject: [PATCH 12/15] Fix assert typo in macOS locale code --- MarathonRecomp/os/macos/locale_macos.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MarathonRecomp/os/macos/locale_macos.cpp b/MarathonRecomp/os/macos/locale_macos.cpp index 5c936763d..7836e0680 100644 --- a/MarathonRecomp/os/macos/locale_macos.cpp +++ b/MarathonRecomp/os/macos/locale_macos.cpp @@ -18,8 +18,7 @@ std::string os::locale::ConvertToUTF8(Encoding in_encoding, const std::string& i CFDataRef cfData = CFDataCreateWithBytesNoCopy(nullptr, reinterpret_cast(in_rStr.data()), static_cast(in_rStr.size()), kCFAllocatorNull); - - assert(data && "CFDataCreateWithBytesNoCopy failed"); + assert(cfData && "CFDataCreateWithBytesNoCopy failed"); CFStringRef cfStr = CFStringCreateFromExternalRepresentation(nullptr, cfData, cfEncoding); CFRelease(cfData); From b93166e6a3729f0d0ef616c18385803ea276be83 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Fri, 30 Oct 2026 01:05:01 +0000 Subject: [PATCH 13/15] Move player debug hooks to debug_patches.cpp --- MarathonRecomp/patches/debug_patches.cpp | 106 ++++++++++++++++++++++ MarathonRecomp/patches/player_patches.cpp | 105 --------------------- 2 files changed, 106 insertions(+), 105 deletions(-) diff --git a/MarathonRecomp/patches/debug_patches.cpp b/MarathonRecomp/patches/debug_patches.cpp index d17a50409..3949ca235 100644 --- a/MarathonRecomp/patches/debug_patches.cpp +++ b/MarathonRecomp/patches/debug_patches.cpp @@ -4,6 +4,7 @@ #include #include #include +#include // Sonicteam::SoX::Graphics::Xenon::DeviceXenon::DrawText PPC_FUNC(sub_8289CB70) @@ -54,3 +55,108 @@ PPC_FUNC(sub_82161AB8) ctx.r3.u32 = g_memory.MapVirtual(pDoc->SetDocState(StateDevTitle::GetInstance())); } + +// Sonicteam::Player::Object::Update +PPC_FUNC_IMPL(__imp__sub_82195500); +PPC_FUNC(sub_82195500) +{ + auto pPlayer = (Sonicteam::Player::Object*)(base + ctx.r3.u32); + auto pInputManager = pPlayer->GetInputManager(); + + if (pPlayer->m_IsPlayer && pInputManager) + { + if (Config::EnableDebugMode) + { + switch (pPlayer->m_SetupModuleIndexPostfix) + { + case 1: + { + // Toggle debug posture on Select press. + if (pPlayer->m_SetupModuleIndexPrefix == 1 && pInputManager->PadState.IsPressed(Sonicteam::SoX::Input::PadButton_Back)) + { + pPlayer->m_SetupModuleIndexPostfix = 2; + + LOGN("Debug Mode: Enabled"); + } + + break; + } + + case 2: + { + // Toggle camera volume collision on B press. + if (pInputManager->PadState.IsPressed(Sonicteam::SoX::Input::PadButton_East)) + { + auto pGame = App::s_pApp->GetGame(); + auto pZock = pPlayer->GetPlugIn("zock"); + auto collisionFilterInfo = pZock->m_spPhantomA->m_pRigidBody->m_collidable.m_broadPhaseHandle.m_collisionFilterInfo == 6 ? 0x383 : 6; + + LOGFN("Camera Volumes: {}", collisionFilterInfo != 6 ? "Enabled" : "Disabled"); + + pZock->m_spPhantomA->m_pRigidBody->m_collidable.m_broadPhaseHandle.m_collisionFilterInfo = collisionFilterInfo; + pGame->GetPhysicsWorld()->m_pWorld->updateCollisionFilterOnWorld(1, 1); + } + + break; + } + } + } + + // Toggle demo camera on right stick press. + if (Config::RestoreDemoCameraMode && pInputManager->PadState.IsPressed(Sonicteam::SoX::Input::PadButton_RightStick)) + { + auto pCameraman = static_cast(pPlayer->m_pCameraman.get()); + + if (auto pCameraMode = pCameraman->m_spCameraModeManager->m_spCameraMode.get()) + { + guest_stack_var msgChangeMode; + msgChangeMode->PadID = pInputManager->ID; + msgChangeMode->TargetActorID = pPlayer->m_ActorID; + msgChangeMode->IsDemoCamera = pCameraMode->m_pVftable.ptr != 0x82002004; + + LOGFN("Demo Camera: {}", msgChangeMode->IsDemoCamera ? "Enabled" : "Disabled"); + + pCameraman->ProcessMessage(msgChangeMode.get()); + } + } + } + + __imp__sub_82195500(ctx, base); +} + +// Add missing SetupModuleDebug to table. +void PlayerDebugMode_RegisterLuaSetup(PPCRegister& str, PPCRegister& index) +{ + if (!Config::EnableDebugMode) + return; + + auto pString = (stdx::string*)g_memory.Translate(str.u32); + + switch (index.u32) + { + case 0: + *pString = "SetupModuleDebug"; + break; + + case 1: + *pString = "SetupModule"; + break; + + case 2: + *pString = "SetupModuleDebug"; + break; + } +} + +bool PlayerDebugMode_RemapDebugExitButton(PPCRegister& r30) +{ + auto pPadState = (Sonicteam::SoX::Input::PadState*)g_memory.Translate(r30.u32); + + if (pPadState->IsPressed(Sonicteam::SoX::Input::PadButton_Back)) + { + LOGN("Debug Mode: Disabled"); + return true; + } + + return false; +} diff --git a/MarathonRecomp/patches/player_patches.cpp b/MarathonRecomp/patches/player_patches.cpp index 96526ce19..7dab0751c 100644 --- a/MarathonRecomp/patches/player_patches.cpp +++ b/MarathonRecomp/patches/player_patches.cpp @@ -14,74 +14,6 @@ void OnRegisterPlayerActor(PPCRegister& r25) pUser->BroadcastMessage(pObject->m_Name.c_str()); } -// Sonicteam::Player::Object::Update -PPC_FUNC_IMPL(__imp__sub_82195500); -PPC_FUNC(sub_82195500) -{ - auto pPlayer = (Sonicteam::Player::Object*)(base + ctx.r3.u32); - auto pInputManager = pPlayer->GetInputManager(); - - if (pPlayer->m_IsPlayer && pInputManager) - { - if (Config::EnableDebugMode) - { - switch (pPlayer->m_SetupModuleIndexPostfix) - { - case 1: - { - // Toggle debug posture on Select press. - if (pPlayer->m_SetupModuleIndexPrefix == 1 && pInputManager->PadState.IsPressed(Sonicteam::SoX::Input::PadButton_Back)) - { - pPlayer->m_SetupModuleIndexPostfix = 2; - - LOGN("Debug Mode: Enabled"); - } - - break; - } - - case 2: - { - // Toggle camera volume collision on B press. - if (pInputManager->PadState.IsPressed(Sonicteam::SoX::Input::PadButton_East)) - { - auto pGame = App::s_pApp->GetGame(); - auto pZock = pPlayer->GetPlugIn("zock"); - auto collisionFilterInfo = pZock->m_spPhantomA->m_pRigidBody->m_collidable.m_broadPhaseHandle.m_collisionFilterInfo == 6 ? 0x383 : 6; - - LOGFN("Camera Volumes: {}", collisionFilterInfo != 6 ? "Enabled" : "Disabled"); - - pZock->m_spPhantomA->m_pRigidBody->m_collidable.m_broadPhaseHandle.m_collisionFilterInfo = collisionFilterInfo; - pGame->GetPhysicsWorld()->m_pWorld->updateCollisionFilterOnWorld(1, 1); - } - - break; - } - } - } - - // Toggle demo camera on right stick press. - if (Config::RestoreDemoCameraMode && pInputManager->PadState.IsPressed(Sonicteam::SoX::Input::PadButton_RightStick)) - { - auto pCameraman = static_cast(pPlayer->m_pCameraman.get()); - - if (auto pCameraMode = pCameraman->m_spCameraModeManager->m_spCameraMode.get()) - { - guest_stack_var msgChangeMode; - msgChangeMode->PadID = pInputManager->ID; - msgChangeMode->TargetActorID = pPlayer->m_ActorID; - msgChangeMode->IsDemoCamera = pCameraMode->m_pVftable.ptr != 0x82002004; - - LOGFN("Demo Camera: {}", msgChangeMode->IsDemoCamera ? "Enabled" : "Disabled"); - - pCameraman->ProcessMessage(msgChangeMode.get()); - } - } - } - - __imp__sub_82195500(ctx, base); -} - // Sonicteam::Player::State::TailsContext::Update PPC_FUNC_IMPL(__imp__sub_8221A7D8); PPC_FUNC(sub_8221A7D8) @@ -149,43 +81,6 @@ PPC_FUNC(sub_821E8C48) __imp__sub_821E8C48(ctx, base); } -// Add missing SetupModuleDebug to table. -void PlayerDebugMode_RegisterLuaSetup(PPCRegister& str, PPCRegister& index) -{ - if (!Config::EnableDebugMode) - return; - - auto pString = (stdx::string*)g_memory.Translate(str.u32); - - switch (index.u32) - { - case 0: - *pString = "SetupModuleDebug"; - break; - - case 1: - *pString = "SetupModule"; - break; - - case 2: - *pString = "SetupModuleDebug"; - break; - } -} - -bool PlayerDebugMode_RemapDebugExitButton(PPCRegister& r30) -{ - auto pPadState = (Sonicteam::SoX::Input::PadState*)g_memory.Translate(r30.u32); - - if (pPadState->IsPressed(Sonicteam::SoX::Input::PadButton_Back)) - { - LOGN("Debug Mode: Disabled"); - return true; - } - - return false; -} - // Sonicteam::Player::Object::ProcMsgAmigoChange PPC_FUNC_IMPL(__imp__sub_8219A5B0); PPC_FUNC(sub_8219A5B0) From 25a802a9d6d25cfca4afb63864a24b363ca8bd62 Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Fri, 30 Oct 2026 01:36:29 +0000 Subject: [PATCH 14/15] Fix title code having long delay without "Disable Title Input Delay" enabled --- MarathonRecomp/patches/TitleTask_patches.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/MarathonRecomp/patches/TitleTask_patches.cpp b/MarathonRecomp/patches/TitleTask_patches.cpp index 5604f9e0d..2fa576ef3 100644 --- a/MarathonRecomp/patches/TitleTask_patches.cpp +++ b/MarathonRecomp/patches/TitleTask_patches.cpp @@ -108,8 +108,6 @@ PPC_FUNC(sub_825126A0) // Skip open animation. if (Config::DisableTitleInputDelay) GuestToHostFunction(sub_82511CA0, pTitleTask, int(Sonicteam::TitleTask::TitleState_Wait)); - - break; } case Sonicteam::TitleTask::TitleState_Wait: @@ -117,7 +115,6 @@ PPC_FUNC(sub_825126A0) if (OptionsMenu::s_isDebugUnlocked) break; - // TODO: fix this being delayed for too long without "Disable Title Input Delay" code. if (auto& spInputManager = App::s_pApp->m_pDoc->m_InputManagers[0]) { auto& rPadState = spInputManager->PadState; From bfa6ee6e5e0a2cb3b4c15bd07f6c5e51dcdf32ec Mon Sep 17 00:00:00 2001 From: Hyper <34012267+hyperbx@users.noreply.github.com> Date: Fri, 30 Oct 2026 01:43:01 +0000 Subject: [PATCH 15/15] Update resources submodule --- MarathonRecompResources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MarathonRecompResources b/MarathonRecompResources index 3d6c2ffb1..59ba8f86f 160000 --- a/MarathonRecompResources +++ b/MarathonRecompResources @@ -1 +1 @@ -Subproject commit 3d6c2ffb109441e42d08e7440dd1b5df2561141c +Subproject commit 59ba8f86fa42b1f2c1ce845378d97670fa25fe6e