From 14181ee864e888cfe84ac993358ab9514d203e90 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Wed, 6 May 2026 21:04:06 -0700 Subject: [PATCH 01/58] feat(restoration): Scaffold N64 heap fragmentation shadow arena --- .../N64HeapFragmentation.cpp | 33 +++++++++++++++++++ .../N64HeapFragmentation.hpp | 16 +++++++++ soh/soh/SohGui/SohMenuEnhancements.cpp | 12 ++++--- soh/soh/config/ConfigUpdaters.cpp | 1 + 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.cpp create mode 100644 soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.hpp diff --git a/soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.cpp b/soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.cpp new file mode 100644 index 00000000000..007c19cb094 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.cpp @@ -0,0 +1,33 @@ +#include + +#include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "N64HeapFragmentation.hpp" + +#include "global.h" +} + +#define CVAR_NAME CVAR_ENHANCEMENT("N64HeapFragmentation") +#define CVAR_DEFAULT 0 +#define CVAR_VALUE CVarGetInteger(CVAR_NAME, CVAR_DEFAULT) + +static s32 sIsActive = 0; + +void N64HeapFrag_Reset() +{ + sIsActive = CVAR_VALUE; +} + +s32 N64HeapFrag_IsActive() +{ + return sIsActive; +} + +void RegisterN64HeapFragmentation() +{ + // #TODO: Shadow arena initialization +} + +static RegisterShipInitFunc initFunc(RegisterN64HeapFragmentation, {CVAR_NAME}); diff --git a/soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.hpp b/soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.hpp new file mode 100644 index 00000000000..15532efe180 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Reset state and cache CVar. Call after arena reinitialization. +void N64HeapFrag_Reset(void); + +s32 N64HeapFrag_IsActive(void); + +#ifdef __cplusplus +} +#endif diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index fc4f9f95c80..3866cf520b3 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1190,10 +1190,6 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("HoverFishing")) .Options(CheckboxOptions().Tooltip( "Restore a bug from NTSC 1.0 that allows casting the Fishing Rod while using the Hover Boots.")); - AddWidget(path, "N64 Weird Frames", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("N64WeirdFrames")) - .Options(CheckboxOptions().Tooltip( - "Restores N64 Weird Frames allowing weirdshots and weirdslides to behave the same as N64.")); AddWidget(path, "Bombchus Out of Bounds", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("BombchusOOB")) .Options( @@ -1212,6 +1208,14 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Restores a bug from NTSC 1.0/1.1 that allows you to obtain the eyeball frog from King Zora " "instead of the Zora Tunic by Holding Shield.")); + AddWidget(path, "N64 Weird Frames", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("N64WeirdFrames")) + .Options(CheckboxOptions().Tooltip( + "Restores N64 Weird Frames allowing weirdshots and weirdslides to behave the same as N64.")); + AddWidget(path, "N64 Heap Fragmentation", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("N64HeapFragmentation")) + .Options(CheckboxOptions().Tooltip( + "Restores N64 heap fragmentation.")); AddWidget(path, "Misc Restorations", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Fix L&Z Page Switch in Pause Menu", WIDGET_CVAR_CHECKBOX) diff --git a/soh/soh/config/ConfigUpdaters.cpp b/soh/soh/config/ConfigUpdaters.cpp index 8249bb48d31..d47d789c9d0 100644 --- a/soh/soh/config/ConfigUpdaters.cpp +++ b/soh/soh/config/ConfigUpdaters.cpp @@ -294,6 +294,7 @@ static const Migration version3Migrations[] = { { "gGameplayStats.ShowIngameTimer", "gGameplayStats.ShowInGameTimer" }, { "gGameplayStats.TimestampsReverse", "gGameplayStats.ReverseTimestamps" }, { "gMirroredWorld", "gEnhancements.MirroredWorld" }, + { "gN64HeapFragmentation", "gEnhancements.N64HeapFragmentation" }, { "gBetaQuestWorld", "gCheats.BetaQuestWorld" }, { "gBombTimerMultiplier", "gCheats.BombTimerMultiplier" }, { "gCheatEasyInputBufferingEnabled", "gCheats.EasyInputBuffer" }, From 0a21eabca87c4cb113646fe0bac48776ab9e319a Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Wed, 6 May 2026 21:51:47 -0700 Subject: [PATCH 02/58] feat(debugger): Add ZeldaArena heap viewer --- soh/include/functions.h | 4 + .../debugger/HeapViewerWindow.cpp | 255 ++++++++++++++++++ .../debugger/HeapViewerWindow.hpp | 19 ++ soh/soh/SohGui/SohGui.cpp | 7 +- soh/soh/SohGui/SohMenuDevTools.cpp | 21 +- soh/soh/config/ConfigUpdaters.cpp | 1 + soh/src/code/z_malloc.c | 6 + 7 files changed, 306 insertions(+), 7 deletions(-) create mode 100644 soh/soh/Enhancements/debugger/HeapViewerWindow.cpp create mode 100644 soh/soh/Enhancements/debugger/HeapViewerWindow.hpp diff --git a/soh/include/functions.h b/soh/include/functions.h index 54f2c2b5dde..be0cd78da61 100644 --- a/soh/include/functions.h +++ b/soh/include/functions.h @@ -1023,6 +1023,10 @@ void ZeldaArena_FreeDebug(void* ptr, const char* file, s32 line); void* ZeldaArena_Calloc(size_t num, size_t size); void ZeldaArena_Display(); void ZeldaArena_GetSizes(u32* outMaxFree, u32* outFree, u32* outAlloc); + +// [SOH] Enhancement - Heap Viewer +ArenaNode* ZeldaArena_GetHead(void); + void ZeldaArena_Check(); void ZeldaArena_Init(void* start, size_t size); void ZeldaArena_Cleanup(); diff --git a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp new file mode 100644 index 00000000000..bcd5a9b767e --- /dev/null +++ b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp @@ -0,0 +1,255 @@ +#include "HeapViewerWindow.hpp" + +#include + +#include + +#include "soh/OTRGlobals.h" + +extern "C" { +#include "z64.h" +#include "functions.h" +} + +struct BlockInfo +{ + std::uintptr_t address; + std::size_t size; + bool isFree; +}; + +static std::vector sBlocks; +static u32 sAllocTotal = 0; +static u32 sFreeTotal = 0; +static u32 sLargestFree = 0; +static u32 sNodeCount = 0; +static u32 sArenaSize = 0; +static u32 sPreviousAlloc = 0; +static u32 sCycleCount = 0; +static s32 sLastDelta = 0; + +static void CollectBlocks() +{ + sBlocks.clear(); + sAllocTotal = 0; + sFreeTotal = 0; + sLargestFree = 0; + sNodeCount = 0; + sArenaSize = 0; + + ArenaNode* node = ZeldaArena_GetHead(); + if (!node) + { + return; + } + + while (node) + { + BlockInfo block; + block.address = reinterpret_cast(node) + sizeof(ArenaNode); + block.size = node->size; + block.isFree = node->isFree; + + sBlocks.push_back(block); + sArenaSize += sizeof(ArenaNode) + node->size; + sNodeCount++; + + if (node->isFree) + { + sFreeTotal += node->size; + if (node->size > sLargestFree) + { + sLargestFree = node->size; + } + } + else + { + sAllocTotal += node->size; + } + + node = node->next; + } + + if (sPreviousAlloc != 0 && sAllocTotal != sPreviousAlloc) + { + sLastDelta = static_cast(sAllocTotal) - static_cast(sPreviousAlloc); + ++sCycleCount; + } + + sPreviousAlloc = sAllocTotal; +} + +static ImU32 ColorAlloc() +{ + return IM_COL32(200, 60, 60, 255); +} + +static ImU32 ColorFree() +{ + return IM_COL32(60, 180, 80, 255); +} + +static ImU32 ColorNode() +{ + return IM_COL32(80, 80, 80, 255); +} + +static ImU32 ColorBorder() +{ + return IM_COL32(40, 40, 40, 255); +} + +void HeapViewerWindow::DrawElement() +{ + CollectBlocks(); + + if (sBlocks.empty()) + { + ImGui::Text("ZeldaArena is not initialized."); + return; + } + + // --- Stats ---- + const f32 fragPercent = sFreeTotal > 0 + ? (1.0f - static_cast(sLargestFree) / static_cast(sFreeTotal)) * 100.0f + : 0.0f; + + ImGui::Text("Arena: 0x%X (%u KB) | Nodes: %u", sArenaSize, sArenaSize / 1024, sNodeCount); + ImGui::Text("Alloc: 0x%X (%u KB) | Free: 0x%X (%u KB) | Largest: 0x%X (%u KB)", + sAllocTotal, sAllocTotal / 1024, + sFreeTotal, sFreeTotal / 1024, + sLargestFree, sLargestFree / 1024); + ImGui::Text("Fragmentation: %.1f%%", fragPercent); + ImGui::Text("Cycle: %u | Last delta: %s0x%X (%d bytes)", + sCycleCount, + sLastDelta >= 0 ? "+" : "-", + static_cast(std::abs(sLastDelta)), + sLastDelta); + + // --- Utilization bar --- + const f32 utilization = sArenaSize > 0 ? static_cast(sAllocTotal) / static_cast(sArenaSize) : 0.0f; + ImGui::ProgressBar(utilization, ImVec2(-1, 0), + fmt::format("{:.1f}% utilized", utilization * 100.0f).c_str()); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + // --- Block map --- + ImGui::Text("Block Map"); + + constexpr f32 mapHeight = 40.0f; + const f32 availWidth = ImGui::GetContentRegionAvail().x; + const ImVec2 mapPos = ImGui::GetCursorScreenPos(); + + // Reserve space for the map + ImGui::Dummy(ImVec2(availWidth, mapHeight)); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + // Background + drawList->AddRectFilled(mapPos, ImVec2(mapPos.x + availWidth, mapPos.y + mapHeight), ColorBorder()); + + // Draw blocks proportionally + f32 xCursor = 0.0f; + s32 hoveredBlock = -1; + + for (std::size_t i = 0; i < sBlocks.size(); ++i) + { + // Node header + f32 nodeWidth = static_cast(sizeof(ArenaNode)) / static_cast(sArenaSize) * availWidth; + // Block data + f32 blockWidth = static_cast(sBlocks[i].size) / static_cast(sArenaSize) * availWidth; + + // Ensure minimum 1px visibility for non-zero blocks + if (nodeWidth < 1.0f) + { + nodeWidth = 1.0f; + } + + if (blockWidth < 1.0f && sBlocks[i].size > 0) + { + blockWidth = 1.0f; + } + + // Node header segment (dark gray) + f32 x0 = mapPos.x + xCursor; + f32 x1 = x0 + nodeWidth; + drawList->AddRectFilled(ImVec2(x0, mapPos.y), ImVec2(x1, mapPos.y + mapHeight), ColorNode()); + xCursor += nodeWidth; + + // Block data segment + x0 = mapPos.x + xCursor; + x1 = x0 + blockWidth; + const ImU32 color = sBlocks[i].isFree ? ColorFree() : ColorAlloc(); + drawList->AddRectFilled(ImVec2(x0, mapPos.y), ImVec2(x1, mapPos.y + mapHeight), color); + + // Hover detection over the full block (node + data) + const f32 fullX0 = mapPos.x + xCursor - nodeWidth; + ImVec2 blockMin(fullX0, mapPos.y); + if (ImVec2 blockMax(x1, mapPos.y + mapHeight); ImGui::IsMouseHoveringRect(blockMin, blockMax)) + { + hoveredBlock = static_cast(i); + } + + xCursor += blockWidth; + } + + // Tooltip for hovered block + if (hoveredBlock >= 0) + { + const auto& [address, size, isFree] = sBlocks[hoveredBlock]; + ImGui::BeginTooltip(); + ImGui::Text("Block %d: %s", hoveredBlock, isFree ? "FREE" : "ALLOCATED"); + ImGui::Text("Address: 0x%llX", static_cast(address)); + ImGui::Text("Size: 0x%X (%u bytes)", static_cast(size), static_cast(size)); + ImGui::EndTooltip(); + } + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + // --- Block list --- + ImGui::Text("Block List (%u blocks)", sNodeCount); + + if (ImGui::BeginTable("##blocks", 4, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable, + ImVec2(0, ImGui::GetContentRegionAvail().y))) + { + ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, 40.0f); + ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 140.0f); + ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableHeadersRow(); + + for (std::size_t i = 0; i < sBlocks.size(); ++i) + { + const auto& [address, size, isFree] = sBlocks[i]; + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + ImGui::Text("%zu", i); + + ImGui::TableNextColumn(); + if (isFree) + { + ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.3f, 1.0f), "FREE"); + } + else + { + ImGui::TextColored(ImVec4(0.8f, 0.25f, 0.25f, 1.0f), "ALLOC"); + } + + ImGui::TableNextColumn(); + ImGui::Text("0x%llX", static_cast(address)); + + ImGui::TableNextColumn(); + ImGui::Text("0x%X (%u)", static_cast(size), static_cast(size)); + } + + ImGui::EndTable(); + } +} diff --git a/soh/soh/Enhancements/debugger/HeapViewerWindow.hpp b/soh/soh/Enhancements/debugger/HeapViewerWindow.hpp new file mode 100644 index 00000000000..62979b9049b --- /dev/null +++ b/soh/soh/Enhancements/debugger/HeapViewerWindow.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +class HeapViewerWindow : public Ship::GuiWindow +{ +public: + using GuiWindow::GuiWindow; + + void InitElement() override + { + } + + void DrawElement() override; + + void UpdateElement() override + { + } +}; diff --git a/soh/soh/SohGui/SohGui.cpp b/soh/soh/SohGui/SohGui.cpp index 04c47b4b7e6..c3bd27d4545 100644 --- a/soh/soh/SohGui/SohGui.cpp +++ b/soh/soh/SohGui/SohGui.cpp @@ -26,6 +26,7 @@ #include "soh/Enhancements/TimeDisplay/TimeDisplay.h" #include "soh/Enhancements/mod_menu.h" #include "soh/Network/Anchor/Anchor.h" +#include "soh/Enhancements/debugger/HeapViewerWindow.hpp" namespace SohGui { @@ -78,6 +79,7 @@ std::shared_ptr mHookDebuggerWindow; std::shared_ptr mDLViewerWindow; std::shared_ptr mValueViewerWindow; std::shared_ptr mMessageViewerWindow; +std::shared_ptr mHeapViewerWindow; std::shared_ptr mGameplayStatsWindow; std::shared_ptr mCheckTrackerSettingsWindow; std::shared_ptr mCheckTrackerWindow; @@ -166,7 +168,9 @@ void SetupGuiElements() { gui->AddGuiWindow(mMessageViewerWindow); mGameplayStatsWindow = std::make_shared(CVAR_WINDOW("GameplayStats"), "Gameplay Stats", ImVec2(480, 550)); - gui->AddGuiWindow(mGameplayStatsWindow); + mHeapViewerWindow = + std::make_shared(CVAR_WINDOW("HeapViewer"), "Heap Viewer", ImVec2(520, 600)); + gui->AddGuiWindow(mHeapViewerWindow); mCheckTrackerWindow = std::make_shared(CVAR_WINDOW("CheckTracker"), "Check Tracker", ImVec2(400, 540)); gui->AddGuiWindow(mCheckTrackerWindow); @@ -211,6 +215,7 @@ void Destroy() { mEntranceTrackerSettingsWindow = nullptr; mCheckTrackerWindow = nullptr; mCheckTrackerSettingsWindow = nullptr; + mHeapViewerWindow = nullptr; mGameplayStatsWindow = nullptr; mDLViewerWindow = nullptr; mValueViewerWindow = nullptr; diff --git a/soh/soh/SohGui/SohMenuDevTools.cpp b/soh/soh/SohGui/SohMenuDevTools.cpp index c5b88ce8fbc..4bec3157ac1 100644 --- a/soh/soh/SohGui/SohMenuDevTools.cpp +++ b/soh/soh/SohGui/SohMenuDevTools.cpp @@ -175,6 +175,15 @@ void SohMenu::AddMenuDevTools() { .HideInSearch(true) .Options(WindowButtonOptions().Tooltip("Enables the separate Hook Debugger Window.")); + // Gfx Debugger + path.sidebarName = "Gfx Debugger"; + AddSidebarEntry("Dev Tools", path.sidebarName, 1); + AddWidget(path, "Popout Gfx Debugger", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("SohGfxDebugger")) + .WindowName("GfxDebugger##SoH") + .HideInSearch(true) + .Options(WindowButtonOptions().Tooltip("Enables the separate Gfx Debugger Window.")); + // Collision Viewer path.sidebarName = "Collision Viewer"; AddSidebarEntry("Dev Tools", path.sidebarName, 2); @@ -220,14 +229,14 @@ void SohMenu::AddMenuDevTools() { .HideInSearch(true) .Options(WindowButtonOptions().Tooltip("Enables the separate Message Viewer Window.")); - // Gfx Debugger - path.sidebarName = "Gfx Debugger"; + // Heap Viewer + path.sidebarName = "Heap Viewer"; AddSidebarEntry("Dev Tools", path.sidebarName, 1); - AddWidget(path, "Popout Gfx Debugger", WIDGET_WINDOW_BUTTON) - .CVar(CVAR_WINDOW("SohGfxDebugger")) - .WindowName("GfxDebugger##SoH") + AddWidget(path, "Popout Heap Viewer", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("HeapViewer")) + .WindowName("Heap Viewer") .HideInSearch(true) - .Options(WindowButtonOptions().Tooltip("Enables the separate Gfx Debugger Window.")); + .Options(WindowButtonOptions().Tooltip("Enables the separate Heap Viewer Window.")); } } // namespace SohGui diff --git a/soh/soh/config/ConfigUpdaters.cpp b/soh/soh/config/ConfigUpdaters.cpp index d47d789c9d0..6eefdfd50d0 100644 --- a/soh/soh/config/ConfigUpdaters.cpp +++ b/soh/soh/config/ConfigUpdaters.cpp @@ -49,6 +49,7 @@ static const Migration version3Migrations[] = { { "gControllerReorderingWindowEnabled", "gOpenWindows.ControllerReorderingWindow" }, { "gGfxDebuggerEnabled", "gOpenWindows.GfxDebugger" }, { "gStatsEnabled", "gOpenWindows.Stats" }, + { "gHeapViewerEnabled", "gOpenWindows.HeapViewer" }, { "gDisableChangingSettings", "gSettings.DisableChanges" }, { "gExtraLatencyThreshold", "gSettings.ExtraLatencyThreshold" }, { "gImGuiScale", "gSettings.ImGuiScale" }, diff --git a/soh/src/code/z_malloc.c b/soh/src/code/z_malloc.c index 26d2fb855ff..a2142a8d8c9 100644 --- a/soh/src/code/z_malloc.c +++ b/soh/src/code/z_malloc.c @@ -91,6 +91,12 @@ void ZeldaArena_GetSizes(u32* outMaxFree, u32* outFree, u32* outAlloc) { ArenaImpl_GetSizes(&sZeldaArena, outMaxFree, outFree, outAlloc); } +// SOH [Enhancement] - Heap Viewer +ArenaNode* ZeldaArena_GetHead() +{ + return sZeldaArena.head; +} + void ZeldaArena_Check() { __osCheckArena(&sZeldaArena); } From b07fd2a5665ed3f92b25c2d3476569f509de04e1 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Wed, 6 May 2026 22:26:32 -0700 Subject: [PATCH 03/58] refactor(enhancements): N64 memory model, not just heap fragmentation --- .../N64MemoryModel.cpp} | 12 ++++++------ .../N64MemoryModel.hpp} | 4 ++-- soh/soh/SohGui/SohMenuEnhancements.cpp | 8 +++++--- soh/soh/config/ConfigUpdaters.cpp | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) rename soh/soh/Enhancements/Restorations/{N64HeapFragmentation/N64HeapFragmentation.cpp => N64MemoryModel/N64MemoryModel.cpp} (60%) rename soh/soh/Enhancements/Restorations/{N64HeapFragmentation/N64HeapFragmentation.hpp => N64MemoryModel/N64MemoryModel.hpp} (75%) diff --git a/soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp similarity index 60% rename from soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.cpp rename to soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 007c19cb094..eb3a57b580c 100644 --- a/soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -4,30 +4,30 @@ #include "soh/ShipInit.hpp" extern "C" { -#include "N64HeapFragmentation.hpp" +#include "N64MemoryModel.hpp" #include "global.h" } -#define CVAR_NAME CVAR_ENHANCEMENT("N64HeapFragmentation") +#define CVAR_NAME CVAR_ENHANCEMENT("N64MemoryModel") #define CVAR_DEFAULT 0 #define CVAR_VALUE CVarGetInteger(CVAR_NAME, CVAR_DEFAULT) static s32 sIsActive = 0; -void N64HeapFrag_Reset() +void N64Mem_Reset() { sIsActive = CVAR_VALUE; } -s32 N64HeapFrag_IsActive() +s32 N64Mem_IsActive() { return sIsActive; } -void RegisterN64HeapFragmentation() +void RegisterN64MemoryModel() { // #TODO: Shadow arena initialization } -static RegisterShipInitFunc initFunc(RegisterN64HeapFragmentation, {CVAR_NAME}); +static RegisterShipInitFunc initFunc(RegisterN64MemoryModel, {CVAR_NAME}); diff --git a/soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp similarity index 75% rename from soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.hpp rename to soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index 15532efe180..a13e3a43698 100644 --- a/soh/soh/Enhancements/Restorations/N64HeapFragmentation/N64HeapFragmentation.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -7,9 +7,9 @@ extern "C" { #endif // Reset state and cache CVar. Call after arena reinitialization. -void N64HeapFrag_Reset(void); +void N64Mem_Reset(void); -s32 N64HeapFrag_IsActive(void); +s32 N64Mem_IsActive(void); #ifdef __cplusplus } diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 3866cf520b3..2135a0c5c9f 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1212,10 +1212,12 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("N64WeirdFrames")) .Options(CheckboxOptions().Tooltip( "Restores N64 Weird Frames allowing weirdshots and weirdslides to behave the same as N64.")); - AddWidget(path, "N64 Heap Fragmentation", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("N64HeapFragmentation")) + AddWidget(path, "Authentic N64 Memory", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("N64MemoryModel")) .Options(CheckboxOptions().Tooltip( - "Restores N64 heap fragmentation.")); + "Simulates N64 memory constraints using a shadow arena with the original allocator and struct sizes." + "Enabled memory-dependent behaviors such as actor spawn failures from heap fragmentation during repeated" + "room transitions.")); AddWidget(path, "Misc Restorations", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Fix L&Z Page Switch in Pause Menu", WIDGET_CVAR_CHECKBOX) diff --git a/soh/soh/config/ConfigUpdaters.cpp b/soh/soh/config/ConfigUpdaters.cpp index 6eefdfd50d0..6d690f7f4de 100644 --- a/soh/soh/config/ConfigUpdaters.cpp +++ b/soh/soh/config/ConfigUpdaters.cpp @@ -295,7 +295,7 @@ static const Migration version3Migrations[] = { { "gGameplayStats.ShowIngameTimer", "gGameplayStats.ShowInGameTimer" }, { "gGameplayStats.TimestampsReverse", "gGameplayStats.ReverseTimestamps" }, { "gMirroredWorld", "gEnhancements.MirroredWorld" }, - { "gN64HeapFragmentation", "gEnhancements.N64HeapFragmentation" }, + { "gN64MemoryModel", "gEnhancements.N64MemoryModel" }, { "gBetaQuestWorld", "gCheats.BetaQuestWorld" }, { "gBombTimerMultiplier", "gCheats.BombTimerMultiplier" }, { "gCheatEasyInputBufferingEnabled", "gCheats.EasyInputBuffer" }, From 6dd560f76ca42f795996de36779609b8f00b4598 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Wed, 6 May 2026 23:33:11 -0700 Subject: [PATCH 04/58] feat(restoration): Shadow arena allocator for N64 memory model --- .../N64MemoryModel/ShadowArena/shadow_arena.c | 346 ++++++++++++++++++ .../N64MemoryModel/ShadowArena/shadow_arena.h | 46 +++ 2 files changed, 392 insertions(+) create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.c create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.c new file mode 100644 index 00000000000..fbd91236320 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.c @@ -0,0 +1,346 @@ +#include "shadow_arena.h" + +#include +#include + +// -------------------------------------------------------------------------------------------------------------------- +// Internal node layout (not exposed -- heap viewer uses offset + size queries, not direct struct access). +// +// Matches N64 retail ArenaNode exactly at 0x30 bytes: +// 0x00 s16 magic +// 0x02 s16 isFree +// 0x04 u32 size (Payload bytes, excluding node header) +// 0x08 u32 next (Offset into buffer, SHADOW_NULL = end) +// 0x0C u32 prev (Offset into buffer, SHADOW_NULL = head) +// 0x10 u8[0x20] (Dead space matching N64 debug fields) +// -------------------------------------------------------------------------------------------------------------------- + +typedef struct ShadowNode +{ + s16 magic; + s16 isFree; + u32 size; + u32 next; + u32 prev; + u8 _dead[0x20]; +} ShadowNode; // 0x30 + +static_assert(sizeof(ShadowNode) == SHADOW_NODE_SIZE, "ShadowNode must be exactly 0x30 bytes to match N64 ArenaNode"); + +#define NODE_MAGIC 0x7373 +#define ALIGN16(x) (((x) + 0xF) & ~0xF) + +// -------------------------------------------------------------------------------------------------------------------- +// Offset helpers +// -------------------------------------------------------------------------------------------------------------------- + +static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) +{ + return (ShadowNode*)(arena->buffer + offset); +} + +static s32 NodeIsValid(ShadowArena* arena, u32 offset) +{ + if (offset == SHADOW_NULL) + { + return 0; + } + + if (offset + SHADOW_NODE_SIZE > arena->bufferSize) + { + return 0; + } + + return NodeAt(arena, offset)->magic == NODE_MAGIC; +} + +static u32 NodeGetNext(ShadowArena* arena, u32 offset) +{ + const ShadowNode* node = NodeAt(arena, offset); + if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) + { + return node->next; + } + + return SHADOW_NULL; +} + +static u32 NodeGetPrev(ShadowArena* arena, u32 offset) +{ + const ShadowNode* node = NodeAt(arena, offset); + if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) + { + return node->prev; + } + + return SHADOW_NULL; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Init / Destroy +// -------------------------------------------------------------------------------------------------------------------- + +void ShadowArena_Init(ShadowArena* arena, u32 size) +{ + // Match N64's alignment: Round start up to 16, round size down to 16. Since we control the buffer, start is + // effectively offset 0 after alignment. We just ensure the usable size is 16-byte aligned. + const u32 alignedSize = size & ~0xF; + + arena->buffer = (u8*)malloc(alignedSize); + if (!arena->buffer) + { + memset(arena, 0, sizeof(ShadowArena)); + return; + } + + memset(arena->buffer, 0, alignedSize); + arena->bufferSize = alignedSize; + + // Single free node spanning the entire buffer minus one header. + ShadowNode* first = NodeAt(arena, 0); + first->magic = NODE_MAGIC; + first->isFree = 1; + first->size = alignedSize - SHADOW_NODE_SIZE; + first->next = SHADOW_NULL; + first->prev = SHADOW_NULL; + memset(first->_dead, 0, sizeof(first->_dead)); + + arena->head = 0; +} + +void ShadowArena_Destroy(ShadowArena* arena) +{ + if (arena->buffer) + { + free(arena->buffer); + } + + memset(arena, 0, sizeof(ShadowArena)); +} + +// -------------------------------------------------------------------------------------------------------------------- +// Malloc: First-fit forward (matches N64 __osMalloc) +// -------------------------------------------------------------------------------------------------------------------- + +u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) +{ + u32 iterOff = 0; + ShadowNode* iter = NULL; + u32 blockSize = 0; + + size = ALIGN16(size); + blockSize = size + SHADOW_NODE_SIZE; + + iterOff = arena->head; + while (iterOff != SHADOW_NULL) + { + iter = NodeAt(arena, iterOff); + if (iter->isFree && iter->size >= size) + { + // Split if remainder can hold a new node + payload. + if (blockSize < iter->size) + { + const u32 newOff = iterOff + blockSize; + ShadowNode* newNode = NodeAt(arena, newOff); + + newNode->magic = NODE_MAGIC; + newNode->isFree = 1; + newNode->size = iter->size - blockSize; + newNode->next = iter->next; + newNode->prev = iterOff; + memset(newNode->_dead, 0, sizeof(newNode->_dead)); + + if (newNode->next != SHADOW_NULL) + { + NodeAt(arena, newNode->next)->prev = newOff; + } + + iter->next = newOff; + iter->size = size; + } + + iter->isFree = 0; + memset(iter->_dead, 0, sizeof(iter->_dead)); + + // Return offset to data area (past the node header). + return iterOff + SHADOW_NODE_SIZE; + } + + iterOff = NodeGetNext(arena, iterOff); + } + + return SHADOW_NULL; +} + +// -------------------------------------------------------------------------------------------------------------------- +// MallocR: First-fit backward (matches N64 __osMallocR) +// -------------------------------------------------------------------------------------------------------------------- + +u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) +{ + u32 iterOff = 0; + u32 nextOff = 0; + ShadowNode* iter = NULL; + u32 blockSize = 0; + + size = ALIGN16(size); + + // Walk to tail. + iterOff = arena->head; + nextOff = NodeGetNext(arena, iterOff); + while (nextOff != SHADOW_NULL) + { + iterOff = nextOff; + nextOff = NodeGetNext(arena, nextOff); + } + + // Walk backward looking for a fit. + while (iterOff != SHADOW_NULL) + { + iter = NodeAt(arena, iterOff); + if (iter->isFree && iter->size >= size) + { + blockSize = size + SHADOW_NODE_SIZE; + + // Split: Carve the allocation from the TOP of this free block. + if (blockSize < iter->size) + { + const u32 newOff = iterOff + (iter->size - size); + ShadowNode* newNode = NodeAt(arena, newOff); + + newNode->magic = NODE_MAGIC; + newNode->isFree = 0; + newNode->size = size; + newNode->next = iter->next; + newNode->prev = iterOff; + memset(newNode->_dead, 0, sizeof(newNode->_dead)); + + if (newNode->next != SHADOW_NULL) + { + NodeAt(arena, newNode->next)->prev = newOff; + } + + iter->next = newOff; + iter->size -= blockSize; + + return newOff + SHADOW_NODE_SIZE; + } + + // No split, use the whole block. + iter->isFree = 0; + memset(iter->_dead, 0, sizeof(iter->_dead)); + + return iterOff + SHADOW_NODE_SIZE; + } + + iterOff = NodeGetPrev(arena, iterOff); + } + + return SHADOW_NULL; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Free: Adjacent block coalescing (matches N64 _osFree) +// -------------------------------------------------------------------------------------------------------------------- + +void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) +{ + u32 nodeOff = 0; + ShadowNode* node = NULL; + u32 nextOff = 0; + u32 prevOff = 0; + + if (dataOffset == SHADOW_NULL) + { + return; + } + + nodeOff = dataOffset - SHADOW_NODE_SIZE; + node = NodeAt(arena, nodeOff); + + if (node->magic != NODE_MAGIC) + { + return; + } + + if (node->isFree) + { + return; + } + + node->isFree = 1; + memset(node->_dead, 0, sizeof(node->_dead)); + + // Forward coalesce: Merge with next if it's free. + nextOff = node->next; + if (nextOff != SHADOW_NULL) + { + const ShadowNode* next = NodeAt(arena, nextOff); + if (next->isFree) + { + if (next->next != SHADOW_NULL) + { + NodeAt(arena, next->next)->prev = nodeOff; + } + + node->size += next->size + SHADOW_NODE_SIZE; + node->next = next->next; + } + } + + // Backward coalesce: Merge into prev if it's free. + prevOff = node->prev; + if (prevOff != SHADOW_NULL) + { + ShadowNode* prev = NodeAt(arena, prevOff); + if (prev->isFree) + { + prev->size += node->size + SHADOW_NODE_SIZE; + prev->next = node->next; + + if (node->next != SHADOW_NULL) + { + NodeAt(arena, node->next)->prev = prevOff; + } + } + } +} + +// -------------------------------------------------------------------------------------------------------------------- +// Query +// -------------------------------------------------------------------------------------------------------------------- + +void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) +{ + u32 iterOff = 0; + + *outMaxFree = 0; + *outFree = 0; + *outAlloc = 0; + + iterOff = arena->head; + while (iterOff != SHADOW_NULL) + { + const ShadowNode* node = NodeAt(arena, iterOff); + if (node->isFree) + { + *outFree += node->size; + + if (node->size > *outMaxFree) + { + *outMaxFree = node->size; + } + } + else + { + *outAlloc += node->size; + } + + iterOff = NodeGetNext(arena, iterOff); + } +} + +u32 ShadowArena_GetHead(ShadowArena* arena) +{ + return arena->head; +} diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h new file mode 100644 index 00000000000..1b413c27464 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Sentinel value for null offsets (no valid node can live at 0xFFFFFFFF in a buffer that's only ~245KB). +#define SHADOW_NULL 0xFFFFFFFF + +// Matches N64 retail ArenaNode: 0x10 bookkeeping + 0x20 debug fields. +#define SHADOW_NODE_SIZE 0x30 + +typedef struct shadow_arena +{ + u8* buffer; + u32 head; // Offset to first node + u32 bufferSize; +} ShadowArena; + +// Allocate backing buffer and initialize with a single fee node. Size should be the N64 ZeldaArena size (0x3D550). +void ShadowArena_Init(ShadowArena* arena, u32 size); + +// Free the backing buffer and zero the struct. +void ShadowArena_Destroy(ShadowArena* arena); + +// First-fit forward allocation. Returns offset to data area, or SHADOW_NULL on failure. Matches N64 __osMalloc. +u32 ShadowArena_Malloc(ShadowArena* arena, u32 size); + +// First-fit backward allocation. Returns offset to data area, or SHADOW_NULL on failure. Matches N64 __osMallocR. +u32 ShadowArena_MallocR(ShadowArena* arena, u32 size); + +// Free a shadow allocation by data offset. Coalesces adjacent free blocks. Matches N64 __osFree. +void ShadowArena_Free(ShadowArena* arena, u32 dataOffset); + +// Query arena statistics. +void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc); + +// Get the head node offset for external traversal (e.g., heap viewer). +u32 ShadowArena_GetHead(ShadowArena* arena); + +#ifdef __cplusplus +} +#endif From e796770bfe0ca9da46138139651a2bfbb9dc87e7 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 7 May 2026 00:39:01 -0700 Subject: [PATCH 05/58] feat(restoration): N64 memory model orchestration layer --- .../N64MemoryModel/N64MemoryModel.cpp | 274 ++++++++++- .../N64MemoryModel/N64MemoryModel.hpp | 50 +- .../ShadowArena/actor_overlay_sizes.c | 430 +++++++++++++++++ .../ShadowArena/actor_overlay_sizes.h | 5 + .../ShadowArena/effect_overlay_sizes.c | 41 ++ .../ShadowArena/effect_overlay_sizes.h | 5 + .../ShadowArena/instance_sizes.c | 433 ++++++++++++++++++ .../ShadowArena/instance_sizes.h | 5 + 8 files changed, 1240 insertions(+), 3 deletions(-) create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index eb3a57b580c..39b3877323c 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -5,19 +5,66 @@ extern "C" { #include "N64MemoryModel.hpp" - -#include "global.h" +#include "ShadowArena/shadow_arena.h" +#include "ShadowArena/actor_overlay_sizes.h" +#include "ShadowArena/effect_overlay_sizes.h" +#include "ShadowArena/instance_sizes.h" } #define CVAR_NAME CVAR_ENHANCEMENT("N64MemoryModel") #define CVAR_DEFAULT 0 #define CVAR_VALUE CVarGetInteger(CVAR_NAME, CVAR_DEFAULT) +// N64 ZeldaArena size: THA remainder on retail NTSC 1.2. Measured via IS-Viewer on decomp build with ISV ungated, +// debug features off. +#define N64_ZELDA_ARENA_SIZE 0x3D550 + +// -------------------------------------------------------------------------------------------------------------------- +// State +// -------------------------------------------------------------------------------------------------------------------- + static s32 sIsActive = 0; +static ShadowArena sShadow; + +// Shadow offsets for actor overlays, keyed by actor ID. SHADOW_NULL means no shadow allocation exists for that type. +static u32 sOverlayShadows[ACTOR_ID_MAX]; + +// Shadow offsets for effect overlays, keyed by effect type. +static u32 sEffectOverlayShadows[EFFECT_SS_TYPE_MAX]; + +// Shadow offset for the shared absolute-space overlay buffer. +static u32 sAbsoluteSpaceShadow = SHADOW_NULL; + +// Maps real pointers (instances and subsidiaries) to their shadow offsets. +static std::unordered_map sShadowMap; + +// -------------------------------------------------------------------------------------------------------------------- +// Lifecycle +// -------------------------------------------------------------------------------------------------------------------- void N64Mem_Reset() { + // Tear down previous shadow state unconditionally -- the real ZeldaArena has already been reinitialized by + // Play_Init. + ShadowArena_Destroy(&sShadow); + sShadowMap.clear(); + sAbsoluteSpaceShadow = SHADOW_NULL; + + for (std::size_t i = 0; i < ACTOR_ID_MAX; ++i) + { + sOverlayShadows[i] = SHADOW_NULL; + } + + for (std::size_t i = 0; i < EFFECT_SS_TYPE_MAX; ++i) + { + sEffectOverlayShadows[i] = SHADOW_NULL; + } + sIsActive = CVAR_VALUE; + if (sIsActive) + { + ShadowArena_Init(&sShadow, N64_ZELDA_ARENA_SIZE); + } } s32 N64Mem_IsActive() @@ -25,6 +72,229 @@ s32 N64Mem_IsActive() return sIsActive; } +// -------------------------------------------------------------------------------------------------------------------- +// Actor overlays +// -------------------------------------------------------------------------------------------------------------------- + +s32 N64Mem_AllocOverlay(ActorID actorId, AllocType allocType) +{ + if (!sIsActive) + { + return 1; + } + + if (actorId < 0 || actorId >= ACTOR_ID_MAX) + { + return 1; + } + + const u32 overlaySize = gN64ActorOverlaySizes[actorId]; + if (overlaySize == 0) + { + return 1; + } + + + // ABSOLUTE: Shared fixed-size buffer, allocated once via MallocR. + if (allocType & ALLOCTYPE_ABSOLUTE) + { + if (sAbsoluteSpaceShadow == SHADOW_NULL) + { + sAbsoluteSpaceShadow = ShadowArena_MallocR(&sShadow, AM_FIELD_SIZE); + if (sAbsoluteSpaceShadow == SHADOW_NULL) + { + SPDLOG_ERROR("[N64MemoryModel] Shadow absolute space failed (need 0x{:X})", AM_FIELD_SIZE); + return 0; + } + } + + return 1; + } + + // Already shadowed for this type. + if (sOverlayShadows[actorId] != SHADOW_NULL) + { + return 1; + } + + u32 shadow = SHADOW_NULL; + if (allocType & ALLOCTYPE_PERMANENT) + { + shadow = ShadowArena_MallocR(&sShadow, overlaySize); + } + else + { + shadow = ShadowArena_Malloc(&sShadow, overlaySize); + } + + if (shadow == SHADOW_NULL) + { + SPDLOG_ERROR("[N64MemoryModel] Shadow overlay failed for actor 0x{:04X} (need 0x{:X})", + static_cast(actorId), overlaySize); + return 0; + } + + sOverlayShadows[actorId] = shadow; + return 1; +} + +void N64Mem_FreeOverlay(ActorID actorId, AllocType allocType) +{ + if (!sIsActive) + { + return; + } + + if (actorId < 0 || actorId >= ACTOR_ID_MAX) + { + return; + } + + // PERMANENT: Overlays that are never freed. + if (allocType & ALLOCTYPE_PERMANENT) + { + return; + } + + ShadowArena_Free(&sShadow, sOverlayShadows[actorId]); + sOverlayShadows[actorId] = SHADOW_NULL; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Actor instances +// -------------------------------------------------------------------------------------------------------------------- + +s32 N64Mem_AllocInstance(ActorID actorId, void* realPtr) +{ + if (!sIsActive) + { + return 1; + } + + if (actorId < 0 || actorId >= ACTOR_ID_MAX) + { + return 1; + } + + const u32 instanceSize = gN64InstanceSizes[actorId]; + if (instanceSize == 0) + { + return 1; + } + + const u32 shadow = ShadowArena_Malloc(&sShadow, instanceSize); + if (shadow == SHADOW_NULL) + { + SPDLOG_ERROR("[N64MemoryModel] Shadow instance failed for actor 0x{:04X} (need 0x{:X})", + static_cast(actorId), instanceSize); + return 0; + } + + sShadowMap[realPtr] = shadow; + return 1; +} + +void N64Mem_FreeInstance(void* realPtr) +{ + if (!sIsActive || !realPtr) + { + return; + } + + const auto i = sShadowMap.find(realPtr); + if (i == sShadowMap.end()) + { + return; + } + + ShadowArena_Free(&sShadow, i->second); + sShadowMap.erase(i); +} + +// -------------------------------------------------------------------------------------------------------------------- +// Subsidiaries +// -------------------------------------------------------------------------------------------------------------------- + +s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) +{ + if (!sIsActive) + { + return 1; + } + + const u32 shadow = ShadowArena_Malloc(&sShadow, n64Size); + if (shadow == SHADOW_NULL) + { + SPDLOG_ERROR("[N64MemoryModel] Shadow subsidiary failed (need 0x{:X})", n64Size); + return 0; + } + + sShadowMap[realPtr] = shadow; + return 1; +} + +void N64Mem_FreeSubsidiary(void* realPtr) +{ + if (!sIsActive || !realPtr) + { + return; + } + + const auto i = sShadowMap.find(realPtr); + if (i == sShadowMap.end()) + { + return; + } + + ShadowArena_Free(&sShadow, i->second); + sShadowMap.erase(i); +} + + +// -------------------------------------------------------------------------------------------------------------------- +// Effect overlays +// -------------------------------------------------------------------------------------------------------------------- + +s32 N64Mem_AllocEffectOverlay(EffectSsType type) +{ + if (!sIsActive) + { + return 1; + } + + if (type < 0 || type >= EFFECT_SS_TYPE_MAX) + { + return 1; + } + + if (sEffectOverlayShadows[type] != SHADOW_NULL) + { + return 1; + } + + u32 overlaySize = gN64EffectOverlaySizes[type]; + if (overlaySize == 0) + { + return 1; + } + + const u32 shadow = ShadowArena_MallocR(&sShadow, overlaySize); + if (shadow == SHADOW_NULL) + { + SPDLOG_ERROR("[N64MemoryModel] Shadow effect overlay failed for type 0x{:02X} (need 0x{:X})", + static_cast(type), overlaySize); + return 0; + } + + sEffectOverlayShadows[type] = shadow; + return 1; +} + + +// -------------------------------------------------------------------------------------------------------------------- +// Registration +// -------------------------------------------------------------------------------------------------------------------- + void RegisterN64MemoryModel() { // #TODO: Shadow arena initialization diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index a13e3a43698..b74163812a9 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -6,11 +6,59 @@ extern "C" { #endif -// Reset state and cache CVar. Call after arena reinitialization. +// -------------------------------------------------------------------------------------------------------------------- +// Lifecycle +// -------------------------------------------------------------------------------------------------------------------- + +// Reset shadow state and reread CVar. Call from Play_Init after ZeldaArena reinitialization -- the shadow arena +// reinitializes in lockstep with the real one. void N64Mem_Reset(void); +// Returns whether the N64 memory model is currently active. s32 N64Mem_IsActive(void); +// -------------------------------------------------------------------------------------------------------------------- +// Actor overlays: Keyed by actor ID, shadow-only allocations. +// +// On N64, overlays load into ZeldaArena from ROM on first spawn and free when no instances remain. SoH compiles them +// into the binary, so they never touch ZeldaArena. The shadow restores this pressure. +// +// Call AllocOverlay when numLoaded transitions 0 -> 1. +// Call FreeOverlay when numLoaded transitions 1 -> 0. +// -------------------------------------------------------------------------------------------------------------------- + +s32 N64Mem_AllocOverlay(ActorID actorId, AllocType allocType); +void N64Mem_FreeOverlay(ActorID actorId, AllocType allocType); + +// -------------------------------------------------------------------------------------------------------------------- +// Actor instances: Paired with real ZeldaArena allocations. +// +// Call AllocInstance after the real allocation succeeds. If the shadow cannot satisfy the N64-sized allocation, +// returns 0 and the caller should treat the spawn as failed. +// +// Call FreeInstance when the actor is deleted. +// -------------------------------------------------------------------------------------------------------------------- + +s32 N64Mem_AllocInstance(ActorID actorId, void* realPtr); +void N64Mem_FreeInstance(void* realPtr); + +// -------------------------------------------------------------------------------------------------------------------- +// Subsidiaries (colliders, camera, skin, etc.): Paired. +// +// Same pattern as instances -- shadow-alloc at N64 size, gate on failure, free when the real allocation is freed. +// -------------------------------------------------------------------------------------------------------------------- + +s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size); +void N64Mem_FreeSubsidiary(void* realPtr); + +// -------------------------------------------------------------------------------------------------------------------- +// Effect overlays: Shadow-only, persist for scene lifetime. +// +// On N64, effect overlays load via MallocR on first spawn and are never freed until the GameState is torn down. +// -------------------------------------------------------------------------------------------------------------------- + +s32 N64Mem_AllocEffectOverlay(EffectSsType type); + #ifdef __cplusplus } #endif diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c new file mode 100644 index 00000000000..5a75a1dd20d --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c @@ -0,0 +1,430 @@ +#include "actor_overlay_sizes.h" + +const u32 gN64ActorOverlaySizes[ACTOR_ID_MAX] = { + [ACTOR_EN_TEST] = 0x58B0, + [ACTOR_EN_GIRLA] = 0x2920, + [ACTOR_EN_PART] = 0x1610, + [ACTOR_EN_LIGHT] = 0xDF0, + [ACTOR_EN_DOOR] = 0xE40, + [ACTOR_EN_BOX] = 0x1B40, + [ACTOR_BG_DY_YOSEIZO] = 0x2E00, + [ACTOR_BG_HIDAN_FIREWALL] = 0x760, + [ACTOR_EN_POH] = 0x4190, + [ACTOR_EN_OKUTA] = 0x25E0, + [ACTOR_BG_YDAN_SP] = 0x1770, + [ACTOR_EN_BOM] = 0xED0, + [ACTOR_EN_WALLMAS] = 0x1A10, + [ACTOR_EN_DODONGO] = 0x2DA0, + [ACTOR_EN_FIREFLY] = 0x2170, + [ACTOR_EN_HORSE] = 0xC260, + [ACTOR_EN_ARROW] = 0x16F0, + [ACTOR_EN_ELF] = 0x49C0 + [ACTOR_EN_NIW] = 0x3330, + [ACTOR_EN_TITE] = 0x2DA0, + [ACTOR_EN_REEBA] = 0x1A70, + [ACTOR_EN_PEEHAT] = 0x3700, + [ACTOR_EN_BUTTE] = 0x15D0, + [ACTOR_EN_INSECT] = 0x2520, + [ACTOR_EN_FISH] = 0x2110, + [ACTOR_EN_HOLL] = 0xFD0, + [ACTOR_EN_SCENE_CHANGE] = 0x130, + [ACTOR_EN_ZF] = 0x6B00, + [ACTOR_EN_HATA] = 0x590, + [ACTOR_BOSS_DODONGO] = 0x9AE0, + [ACTOR_BOSS_GOMA] = 0x5F80, + [ACTOR_EN_ZL1] = 0x3E00, + [ACTOR_EN_VIEWER] = 0x2ED0, + [ACTOR_EN_GOMA] = 0x2C90, + [ACTOR_BG_PUSHBOX] = 0x300, + [ACTOR_EN_BUBBLE] = 0x1420, + [ACTOR_DOOR_SHUTTER] = 0x2280, + [ACTOR_EN_DODOJR] = 0x1EA0, + [ACTOR_EN_BDFIRE] = 0xB90, + [ACTOR_EN_BOOM] = 0x8C0, + [ACTOR_EN_TORCH2] = 0x27A0, + [ACTOR_EN_BILI] = 0x22D0, + [ACTOR_EN_TP] = 0x1E50, + [ACTOR_EN_ST] = 0x2C70, + [ACTOR_EN_BW] = 0x3370, + [ACTOR_EN_EIYER] = 0x1C60, + [ACTOR_EN_RIVER_SOUND] = 0x990, + [ACTOR_EN_HORSE_NORMAL] = 0x2620, + [ACTOR_EN_OSSAN] = 0x65E0, + [ACTOR_BG_TREEMOUTH] = 0x1660, + [ACTOR_BG_DODOAGO] = 0xDB0, + [ACTOR_BG_HIDAN_DALM] = 0x850, + [ACTOR_BG_HIDAN_HROCK] = 0x830, + [ACTOR_EN_HORSE_GANON] = 0xD80, + [ACTOR_BG_HIDAN_ROCK] = 0x10F0, + [ACTOR_BG_HIDAN_RSEKIZOU] = 0xBE0, + [ACTOR_BG_HIDAN_SEKIZOU] = 0x1450, + [ACTOR_BG_HIDAN_SIMA] = 0xF20, + [ACTOR_BG_HIDAN_SYOKU] = 0x460, + [ACTOR_EN_XC] = 0x6790, + [ACTOR_BG_HIDAN_CURTAIN] = 0xAA0, + [ACTOR_BG_SPOT00_HANEBASI] = 0x1110, + [ACTOR_EN_MB] = 0x4230, + [ACTOR_EN_BOMBF] = 0x1470, + [ACTOR_EN_ZL2] = 0x4730, + [ACTOR_BG_HIDAN_FSLIFT] = 0x4D0, + [ACTOR_EN_OE2] = 0xE0, + [ACTOR_BG_YDAN_HASI] = 0x7B0, + [ACTOR_BG_YDAN_MARUTA] = 0x6E0, + [ACTOR_BOSS_GANONDROF] = 0x4D70, + [ACTOR_EN_AM] = 0x2400, + [ACTOR_EN_DEKUBABA] = 0x3AA0, + [ACTOR_EN_M_FIRE1] = 0x1A0, + [ACTOR_EN_M_THUNDER] = 0x15F0, + [ACTOR_BG_DDAN_JD] = 0x650, + [ACTOR_BG_BREAKWALL] = 0xE70, + [ACTOR_EN_JJ] = 0x15D0, + [ACTOR_EN_HORSE_ZELDA] = 0xAF0, + [ACTOR_BG_DDAN_KD] = 0x8F0, + [ACTOR_DOOR_WARP1] = 0x42B0, + [ACTOR_OBJ_SYOKUDAI] = 0xC40, + [ACTOR_ITEM_B_HEART] = 0x3F0, + [ACTOR_EN_DEKUNUTS] = 0x1800, + [ACTOR_BG_MENKURI_KAITEN] = 0x190, + [ACTOR_BG_MENKURI_EYE] = 0x4A0, + [ACTOR_EN_VALI] = 0x26A0, + [ACTOR_BG_MIZU_MOVEBG] = 0x11A0, + [ACTOR_BG_MIZU_WATER] = 0xCD0, + [ACTOR_ARMS_HOOK] = 0xD60, + [ACTOR_EN_FHG] = 0x2930, + [ACTOR_BG_MORI_HINERI] = 0xD00, + [ACTOR_EN_BB] = 0x3CD0, + [ACTOR_BG_TOKI_HIKARI] = 0xDA0, + [ACTOR_EN_YUKABYUN] = 0x610, + [ACTOR_BG_TOKI_SWD] = 0x1650, + [ACTOR_EN_FHG_FIRE] = 0x2620, + [ACTOR_BG_MJIN] = 0x3E0, + [ACTOR_BG_HIDAN_KOUSI] = 0x580, + [ACTOR_DOOR_TOKI] = 0x160, + [ACTOR_BG_HIDAN_HAMSTEP] = 0xEB0, + [ACTOR_EN_BIRD] = 0x4C0, + [ACTOR_EN_WOOD02] = 0x11E0, + [ACTOR_EN_LIGHTBOX] = 0x480, + [ACTOR_EN_PU_BOX] = 0x340, + [ACTOR_EN_TRAP] = 0x12A0, + [ACTOR_EN_AROW_TRAP] = 0x150, + [ACTOR_EN_VASE] = 0x100, + [ACTOR_EN_TA] = 0x39C0, + [ACTOR_EN_TK] = 0x1E30, + [ACTOR_BG_MORI_BIGST] = 0x930, + [ACTOR_BG_MORI_ELEVATOR] = 0xAF0, + [ACTOR_BG_MORI_KAITENKABE] = 0x660, + [ACTOR_BG_MORI_RAKKATENJO] = 0x970, + [ACTOR_EN_VM] = 0x18B0, + [ACTOR_DEMO_EFFECT] = 0x5AF0, + [ACTOR_DEMO_KANKYO] = 0x3D00, + [ACTOR_BG_HIDAN_FWBIG] = 0xCE0, + [ACTOR_EN_FLOORMAS] = 0x33E0, + [ACTOR_EN_HEISHI1] = 0x1510, + [ACTOR_EN_RD] = 0x28B0, + [ACTOR_EN_PO_SISTERS] = 0x4CF0, + [ACTOR_BG_HEAVY_BLOCK] = 0x18F0, + [ACTOR_BG_PO_EVENT] = 0x1E40, + [ACTOR_OBJ_MURE] = 0x1010, + [ACTOR_EN_SW] = 0x37F0, + [ACTOR_BOSS_FD] = 0x7330, + [ACTOR_OBJECT_KANKYO] = 0x3220, + [ACTOR_EN_DU] = 0x1AA0, + [ACTOR_EN_FD] = 0x2CC0, + [ACTOR_EN_HORSE_LINK_CHILD] = 0x1E00, + [ACTOR_DOOR_ANA] = 0x670, + [ACTOR_BG_SPOT02_OBJECTS] = 0x1350, + [ACTOR_BG_HAKA] = 0x6C0, + [ACTOR_MAGIC_WIND] = 0x1D00, + [ACTOR_MAGIC_FIRE] = 0x22D0, + [ACTOR_EN_RU1] = 0x76A0, + [ACTOR_BOSS_FD2] = 0x3D30, + [ACTOR_EN_FD_FIRE] = 0xD10, + [ACTOR_EN_DH] = 0x1AD0, + [ACTOR_EN_DHA] = 0x1000, + [ACTOR_EN_RL] = 0xEE0, + [ACTOR_EN_ENCOUNT1] = 0xB60, + [ACTOR_DEMO_DU] = 0x37E0, + [ACTOR_DEMO_IM] = 0x3F70, + [ACTOR_DEMO_TRE_LGT] = 0x710, + [ACTOR_EN_FW] = 0x17B0, + [ACTOR_BG_VB_SIMA] = 0x710, + [ACTOR_EN_VB_BALL] = 0x11A0, + [ACTOR_BG_HAKA_MEGANE] = 0x400, + [ACTOR_BG_HAKA_MEGANEBG] = 0x6C0, + [ACTOR_BG_HAKA_SHIP] = 0xA40, + [ACTOR_BG_HAKA_SGAMI] = 0xC20, + [ACTOR_EN_HEISHI2] = 0x2200, + [ACTOR_EN_ENCOUNT2] = 0x1230, + [ACTOR_EN_FIRE_ROCK] = 0x1110, + [ACTOR_EN_BROB] = 0x10F0, + [ACTOR_MIR_RAY] = 0x18C0, + [ACTOR_BG_SPOT09_OBJ] = 0x510, + [ACTOR_BG_SPOT18_OBJ] = 0x8D0, + [ACTOR_BOSS_VA] = 0x171F0, + [ACTOR_BG_HAKA_TUBO] = 0xA20, + [ACTOR_BG_HAKA_TRAP] = 0x15D0, + [ACTOR_BG_HAKA_HUTA] = 0xAA0, + [ACTOR_BG_HAKA_ZOU] = 0x11F0, + [ACTOR_BG_SPOT17_FUNEN] = 0x250, + [ACTOR_EN_SYATEKI_ITM] = 0xDA0, + [ACTOR_EN_SYATEKI_MAN] = 0xDC0, + [ACTOR_EN_TANA] = 0x2A0, + [ACTOR_EN_NB] = 0x45D0, + [ACTOR_BOSS_MO] = 0x100B0, + [ACTOR_EN_SB] = 0x1440, + [ACTOR_EN_BIGOKUTA] = 0x2B10, + [ACTOR_EN_KAREBABA] = 0x18F0, + [ACTOR_BG_BDAN_OBJECTS] = 0x12D0, + [ACTOR_DEMO_SA] = 0x2B20, + [ACTOR_DEMO_GO] = 0xD60, + [ACTOR_EN_IN] = 0x2DA0, + [ACTOR_EN_TR] = 0x1900, + [ACTOR_BG_SPOT16_BOMBSTONE] = 0x1540, + [ACTOR_BG_HIDAN_KOWARERUKABE] = 0xED0, + [ACTOR_BG_BOMBWALL] = 0x8C0, + [ACTOR_BG_SPOT08_ICEBLOCK] = 0x1040, + [ACTOR_EN_RU2] = 0x2D80, + [ACTOR_OBJ_DEKUJR] = 0x640, + [ACTOR_BG_MIZU_UZU] = 0x1D0, + [ACTOR_BG_SPOT06_OBJECTS] = 0x1410, + [ACTOR_BG_ICE_OBJECTS] = 0xF40, + [ACTOR_BG_HAKA_WATER] = 0x7E0, + [ACTOR_EN_MA2] = 0x1060, + [ACTOR_EN_BOM_CHU] = 0x16A0, + [ACTOR_EN_HORSE_GAME_CHECK] = 0x10D0, + [ACTOR_BOSS_TW] = 0x15B00, + [ACTOR_EN_RR] = 0x2530, + [ACTOR_EN_BA] = 0x1ED0, + [ACTOR_EN_BX] = 0xAF0, + [ACTOR_EN_ANUBICE] = 0x12B0, + [ACTOR_EN_ANUBICE_FIRE] = 0xDC0, + [ACTOR_BG_MORI_HASHIGO] = 0x8C0, + [ACTOR_BG_MORI_HASHIRA4] = 0x590, + [ACTOR_BG_MORI_IDOMIZU] = 0x640, + [ACTOR_BG_SPOT16_DOUGHNUT] = 0x5B0, + [ACTOR_BG_BDAN_SWITCH] = 0x1430, + [ACTOR_EN_MA1] = 0x12E0, + [ACTOR_BOSS_GANON] = 0x25DE0, + [ACTOR_BOSS_SST] = 0xC5C0, + [ACTOR_EN_NY] = 0x1930, + [ACTOR_EN_FR] = 0x2A90, + [ACTOR_ITEM_SHIELD] = 0xA10, + [ACTOR_BG_ICE_SHELTER] = 0x1230, + [ACTOR_EN_ICE_HONO] = 0x11F0, + [ACTOR_ITEM_OCARINA] = 0x7D0, + [ACTOR_MAGIC_DARK] = 0x1850, + [ACTOR_DEMO_6K] = 0x2D10, + [ACTOR_EN_ANUBICE_TAG] = 0x2D0, + [ACTOR_BG_HAKA_GATE] = 0x1090, + [ACTOR_BG_SPOT15_SAKU] = 0x340, + [ACTOR_BG_JYA_GOROIWA] = 0x780, + [ACTOR_BG_JYA_ZURERUKABE] = 0x6B0, + [ACTOR_BG_JYA_COBRA] = 0x1D20, + [ACTOR_BG_JYA_KANAAMI] = 0x3B0, + [ACTOR_FISHING] = 0x1AAB0, + [ACTOR_OBJ_OSHIHIKI] = 0x1AB0, + [ACTOR_BG_GATE_SHUTTER] = 0x480, + [ACTOR_EFF_DUST] = 0x13E0, + [ACTOR_BG_SPOT01_FUSYA] = 0x2A0, + [ACTOR_BG_SPOT01_IDOHASHIRA] = 0xC00, + [ACTOR_BG_SPOT01_IDOMIZU] = 0x310, + [ACTOR_BG_PO_SYOKUDAI] = 0x950, + [ACTOR_BG_GANON_OTYUKA] = 0x2640, + [ACTOR_BG_SPOT15_RRBOX] = 0xDE0, + [ACTOR_BG_UMAJUMP] = 0x190, + [ACTOR_ARROW_FIRE] = 0x1EC0, + [ACTOR_ARROW_ICE] = 0x1EE0, + [ACTOR_ARROW_LIGHT] = 0x1EF0, + [ACTOR_ITEM_ETCETERA] = 0x8D0, + [ACTOR_OBJ_KIBAKO] = 0xD00, + [ACTOR_OBJ_TSUBO] = 0xFF0, + [ACTOR_EN_WONDER_ITEM] = 0xD30, + [ACTOR_EN_IK] = 0x4640, + [ACTOR_DEMO_IK] = 0x1510, + [ACTOR_EN_SKJ] = 0x3940, + [ACTOR_EN_SKJNEEDLE] = 0x310, + [ACTOR_EN_G_SWITCH] = 0x1830, + [ACTOR_DEMO_EXT] = 0x940, + [ACTOR_DEMO_SHD] = 0x2410, + [ACTOR_EN_DNS] = 0x1390, + [ACTOR_ELF_MSG] = 0x5F0, + [ACTOR_EN_HONOTRAP] = 0x1550, + [ACTOR_EN_TUBO_TRAP] = 0xCA0, + [ACTOR_OBJ_ICE_POLY] = 0x9B0, + [ACTOR_BG_SPOT03_TAKI] = 0x8F0, + [ACTOR_BG_SPOT07_TAKI] = 0x5C0, + [ACTOR_EN_FZ] = 0x2010, + [ACTOR_EN_PO_RELAY] = 0x1710, + [ACTOR_BG_RELAY_OBJECTS] = 0x7B0, + [ACTOR_EN_DIVING_GAME] = 0x19B0, + [ACTOR_EN_KUSA] = 0x14E0, + [ACTOR_OBJ_BEAN] = 0x2790, + [ACTOR_OBJ_BOMBIWA] = 0x570, + [ACTOR_OBJ_SWITCH] = 0x1DC0, + [ACTOR_OBJ_ELEVATOR] = 0x3C0, + [ACTOR_OBJ_LIFT] = 0xA20, + [ACTOR_OBJ_HSBLOCK] = 0x5D0, + [ACTOR_EN_OKARINA_TAG] = 0x1500, + [ACTOR_EN_YABUSAME_MARK] = 0x6D0, + [ACTOR_EN_GOROIWA] = 0x23C0, + [ACTOR_EN_EX_RUPPY] = 0x10C0, + [ACTOR_EN_TORYO] = 0xC90, + [ACTOR_EN_DAIKU] = 0x1740, + [ACTOR_EN_NWC] = 0xA40, + [ACTOR_EN_BLKOBJ] = 0x560, + [ACTOR_ITEM_INBOX] = 0x160, + [ACTOR_EN_GE1] = 0x2030, + [ACTOR_OBJ_BLOCKSTOP] = 0x1A0, + [ACTOR_EN_SDA] = 0x1700, + [ACTOR_EN_CLEAR_TAG] = 0xB5A0, + [ACTOR_EN_NIW_LADY] = 0x1900, + [ACTOR_EN_GM] = 0xD30, + [ACTOR_EN_MS] = 0x6F0, + [ACTOR_EN_HS] = 0xBA0, + [ACTOR_BG_INGATE] = 0x390, + [ACTOR_EN_KANBAN] = 0x3150, + [ACTOR_EN_HEISHI3] = 0x9D0, + [ACTOR_EN_SYATEKI_NIW] = 0x2090, + [ACTOR_EN_ATTACK_NIW] = 0x1260, + [ACTOR_BG_SPOT01_IDOSOKO] = 0x210, + [ACTOR_EN_SA] = 0x2270, + [ACTOR_EN_WONDER_TALK] = 0x690, + [ACTOR_BG_GJYO_BRIDGE] = 0x500, + [ACTOR_EN_DS] = 0xC20, + [ACTOR_EN_MK] = 0xE90, + [ACTOR_EN_BOM_BOWL_MAN] = 0x1540, + [ACTOR_EN_BOM_BOWL_PIT] = 0x970, + [ACTOR_EN_OWL] = 0x3BA0, + [ACTOR_EN_ISHI] = 0x9150, + [ACTOR_OBJ_HANA] = 0x310, + [ACTOR_OBJ_LIGHTSWITCH] = 0x1430, + [ACTOR_OBJ_MURE2] = 0xA20, + [ACTOR_EN_GO] = 0x4640, + [ACTOR_EN_FU] = 0xD50, + [ACTOR_EN_CHANGER] = 0x9E0, + [ACTOR_BG_JYA_MEGAMI] = 0x11E0, + [ACTOR_BG_JYA_LIFT] = 0x550, + [ACTOR_BG_JYA_BIGMIRROR] = 0x840, + [ACTOR_BG_JYA_BOMBCHUIWA] = 0xB30, + [ACTOR_BG_JYA_AMISHUTTER] = 0x390, + [ACTOR_BG_JYA_BOMBIWA] = 0x5C0, + [ACTOR_BG_SPOT18_BASKET] = 0xFF0, + [ACTOR_EN_GANON_ORGAN] = 0x7000, + [ACTOR_EN_SIOFUKI] = 0xDB0, + [ACTOR_EN_STREAM] = 0x590, + [ACTOR_EN_MM] = 0x1620, + [ACTOR_EN_KO] = 0x4140, + [ACTOR_EN_KZ] = 0x15A0, + [ACTOR_EN_WEATHER_TAG] = 0xEF0, + [ACTOR_BG_SST_FLOOR] = 0x560, + [ACTOR_EN_ANI] = 0xD70, + [ACTOR_EN_EX_ITEM] = 0x1170, + [ACTOR_BG_JYA_IRONOBJ] = 0xDB0, + [ACTOR_EN_JS] = 0x9D0, + [ACTOR_EN_JSJUTAN] = 0x5920, + [ACTOR_EN_CS] = 0x1230, + [ACTOR_EN_MD] = 0x2670, + [ACTOR_EN_HY] = 0x3940, + [ACTOR_EN_GANON_MANT] = 0x4220, + [ACTOR_EN_OKARINA_EFFECT] = 0x3C0, + [ACTOR_EN_MAG] = 0x4F10, + [ACTOR_DOOR_GERUDO] = 0x5F0, + [ACTOR_ELF_MSG2] = 0x470, + [ACTOR_DEMO_GT] = 0x5600, + [ACTOR_EN_PO_FIELD] = 0x3A70, + [ACTOR_EFC_ERUPC] = 0xAE0, + [ACTOR_BG_ZG] = 0x470, + [ACTOR_EN_HEISHI4] = 0xF00, + [ACTOR_EN_ZL3] = 0x7E50, + [ACTOR_BOSS_GANON2] = 0x12E20, + [ACTOR_EN_KAKASI] = 0xD40, + [ACTOR_EN_TAKARA_MAN] = 0x8C0, + [ACTOR_OBJ_MAKEOSHIHIKI] = 0x490, + [ACTOR_OCEFF_SPOT] = 0xF30, + [ACTOR_END_TITLE] = 0x4130, + [ACTOR_EN_TORCH] = 0xF0, + [ACTOR_DEMO_EC] = 0x3860, + [ACTOR_SHOT_SUN] = 0x6C0, + [ACTOR_EN_DY_EXTRA] = 0x580, + [ACTOR_EN_WONDER_TALK2] = 0x6A0, + [ACTOR_EN_GE2] = 0x19A0, + [ACTOR_OBJ_ROOMTIMER] = 0x250, + [ACTOR_EN_SSH] = 0x25F0, + [ACTOR_EN_STH] = 0x40B0, + [ACTOR_OCEFF_WIPE] = 0xD50, + [ACTOR_OCEFF_STORM] = 0x1BA0, + [ACTOR_EN_WEIYER] = 0x1A00, + [ACTOR_BG_SPOT05_SOKO] = 0x320, + [ACTOR_BG_JYA_1FLIFT] = 0x690, + [ACTOR_BG_JYA_HAHENIRON] = 0x7F0, + [ACTOR_BG_SPOT12_GATE] = 0x410, + [ACTOR_BG_SPOT12_SAKU] = 0x4C0, + [ACTOR_EN_HINTNUTS] = 0x1A30, + [ACTOR_EN_NUTSBALL] = 0x620, + [ACTOR_BG_SPOT00_BREAK] = 0x1A0, + [ACTOR_EN_SHOPNUTS] = 0xF10, + [ACTOR_EN_IT] = 0x190, + [ACTOR_EN_GELDB] = 0x53B0, + [ACTOR_OCEFF_WIPE2] = 0x1770, + [ACTOR_OCEFF_WIPE3] = 0x1750, + [ACTOR_EN_NIW_GIRL] = 0xAD0, + [ACTOR_EN_DOG] = 0x11B0, + [ACTOR_EN_SI] = 0x500, + [ACTOR_BG_SPOT01_OBJECTS2] = 0x4C0, + [ACTOR_OBJ_COMB] = 0x860, + [ACTOR_BG_SPOT11_BAKUDANKABE] = 0x640, + [ACTOR_OBJ_KIBAKO2] = 0x6C0, + [ACTOR_EN_DNT_DEMO] = 0xD20, + [ACTOR_EN_DNT_JIJI] = 0x1510, + [ACTOR_EN_DNT_NOMAL] = 0x2E10, + [ACTOR_EN_GUEST] = 0x9A0, + [ACTOR_BG_BOM_GUARD] = 0x220, + [ACTOR_EN_HS2] = 0x5E0, + [ACTOR_DEMO_KEKKAI] = 0x12E0, + [ACTOR_BG_SPOT08_BAKUDANKABE] = 0x6A0, + [ACTOR_BG_SPOT17_BAKUDANKABE] = 0x6E0, + [ACTOR_OBJ_MURE3] = 0x7D0, + [ACTOR_EN_TG] = 0x6D0, + [ACTOR_EN_MU] = 0x920, + [ACTOR_EN_GO2] = 0x6020, + [ACTOR_EN_WF] = 0x4310, + [ACTOR_EN_SKB] = 0x18F0, + [ACTOR_DEMO_GJ] = 0x3CB0, + [ACTOR_DEMO_GEFF] = 0x820, + [ACTOR_BG_GND_FIREMEIRO] = 0x540, + [ACTOR_BG_GND_DARKMEIRO] = 0x7C0, + [ACTOR_BG_GND_SOULMEIRO] = 0x860, + [ACTOR_BG_GND_NISEKABE] = 0x170, + [ACTOR_BG_GND_ICEBLOCK] = 0x1100, + [ACTOR_EN_GB] = 0x1730, + [ACTOR_EN_GS] = 0x1EA0, + [ACTOR_BG_MIZU_BWALL] = 0x14D0, + [ACTOR_BG_MIZU_SHUTTER] = 0x800, + [ACTOR_EN_DAIKU_KAKARIKO] = 0x13C0, + [ACTOR_BG_BOWL_WALL] = 0x980, + [ACTOR_EN_WALL_TUBO] = 0x4F0, + [ACTOR_EN_PO_DESERT] = 0xDC0, + [ACTOR_EN_CROW] = 0x16A0, + [ACTOR_DOOR_KILLER] = 0x1570, + [ACTOR_BG_SPOT11_OASIS] = 0x730, + [ACTOR_BG_SPOT18_FUTA] = 0x1A0, + [ACTOR_BG_SPOT18_SHUTTER] = 0x550, + [ACTOR_EN_MA3] = 0xFB0, + [ACTOR_EN_COW] = 0x1460, + [ACTOR_BG_ICE_TURARA] = 0x830, + [ACTOR_BG_ICE_SHUTTER] = 0x470, + [ACTOR_EN_KAKASI2] = 0x720, + [ACTOR_EN_KAKASI3] = 0x10E0, + [ACTOR_OCEFF_WIPE4] = 0xFE0, + [ACTOR_EN_EG] = 0x1B0, + [ACTOR_BG_MENKURI_NISEKABE] = 0x150, + [ACTOR_EN_ZO] = 0x25B0, + [ACTOR_OBJ_MAKEKINSUTA] = 0x150, + [ACTOR_EN_GE3] = 0xB50, + [ACTOR_OBJ_TIMEBLOCK] = 0xC40, + [ACTOR_OBJ_HAMISHI] = 0x850, + [ACTOR_EN_ZL4] = 0x4A30, + [ACTOR_EN_MM2] = 0xDC0, + [ACTOR_BG_JYA_BLOCK] = 0x270, + [ACTOR_OBJ_WARP2BLOCK] = 0xB30, +}; diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h new file mode 100644 index 00000000000..386758b6bb5 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h @@ -0,0 +1,5 @@ +#pragma once + +#include "global.h" + +extern const u32 gN64ActorOverlaySizes[]; \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c new file mode 100644 index 00000000000..9fdae60ea29 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c @@ -0,0 +1,41 @@ +#include "effect_overlay_sizes.h" + +const u32 gN64EffectOverlaySizes[EFFECT_SS_TYPE_MAX] = { + [EFFECT_SS_DUST] = 0x830, + [EFFECT_SS_KIRAKIRA] = 0x670, + [EFFECT_SS_BOMB] = 0x420, + [EFFECT_SS_BOMB2] = 0x930, + [EFFECT_SS_BLAST] = 0x390, + [EFFECT_SS_G_SPK] = 0x5B0, + [EFFECT_SS_D_FIRE] = 0x4F0, + [EFFECT_SS_BUBBLE] = 0x480, + [EFFECT_SS_UNSET] = 0x000, + [EFFECT_SS_G_RIPPLE] = 0x560, + [EFFECT_SS_G_SPLASH] = 0x4B0, + [EFFECT_SS_G_MAGMA] = 0x260, + [EFFECT_SS_G_FIRE] = 0x290, + [EFFECT_SS_LIGHTNING] = 0x6D0, + [EFFECT_SS_DT_BUBBLE] = 0x590, + [EFFECT_SS_HAHEN] = 0x640, + [EFFECT_SS_STICK] = 0x3A0, + [EFFECT_SS_SIBUKI] = 0x6D0, + [EFFECT_SS_SIBUKI2] = 0x330, + [EFFECT_SS_G_MAGMA2] = 0x510, + [EFFECT_SS_STONE1] = 0x390, + [EFFECT_SS_HITMARK] = 0x550, + [EFFECT_SS_FHG_FLASH] = 0xF80, + [EFFECT_SS_K_FIRE] = 0x430, + [EFFECT_SS_SOLDER_SRCH_BALL] = 0x1B0, + [EFFECT_SS_KAKERA] = 0x1090, + [EFFECT_SS_ICE_PIECE] = 0x440, + [EFFECT_SS_EN_ICE] = 0x8C0, + [EFFECT_SS_FIRE_TAIL] = 0x700, + [EFFECT_SS_EN_FIRE] = 0x740, + [EFFECT_SS_EXTRA] = 0x3C0, + [EFFECT_SS_FCIRCLE] = 0x4B0, + [EFFECT_SS_DEAD_DB] = 0x4E0, + [EFFECT_SS_DEAD_DD] = 0x590, + [EFFECT_SS_DEAD_DS] = 0x480, + [EFFECT_SS_DEAD_SOUND] = 0x140, + [EFFECT_SS_ICE_SMOKE] = 0x4C0, +}; diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h new file mode 100644 index 00000000000..d9b803ac0dd --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h @@ -0,0 +1,5 @@ +#pragma once + +#include "global.h" + +extern const u32 gN64EffectOverlaySizes[]; \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c new file mode 100644 index 00000000000..1b5ba1a6a04 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c @@ -0,0 +1,433 @@ +#include "instance_sizes.h" + +const u32 gN64InstanceSizes[ACTOR_ID_MAX] = { + [ACTOR_PLAYER] = 0xA94, + [ACTOR_EN_TEST] = 0x0928, + [ACTOR_EN_GIRLA] = 0x01D4, + [ACTOR_EN_PART] = 0x015C, + [ACTOR_EN_LIGHT] = 0x0164, + [ACTOR_EN_DOOR] = 0x01D8, + [ACTOR_EN_BOX] = 0x01FC, + [ACTOR_BG_DY_YOSEIZO] = 0x38B4, + [ACTOR_BG_HIDAN_FIREWALL] = 0x01A0, + [ACTOR_EN_POH] = 0x03A8, + [ACTOR_EN_OKUTA] = 0x03BC, + [ACTOR_BG_YDAN_SP] = 0x0248, + [ACTOR_EN_BOM] = 0x0208, + [ACTOR_EN_WALLMAS] = 0x0314, + [ACTOR_EN_DODONGO] = 0x0728, + [ACTOR_EN_FIREFLY] = 0x0374, + [ACTOR_EN_HORSE] = 0x03FC, + [ACTOR_EN_ITEM00] = 0x1AC, + [ACTOR_EN_ARROW] = 0x0260, + [ACTOR_EN_ELF] = 0x02D0, + [ACTOR_EN_NIW] = 0x07B8, + [ACTOR_EN_TITE] = 0x0378, + [ACTOR_EN_REEBA] = 0x02DC, + [ACTOR_EN_PEEHAT] = 0x042C, + [ACTOR_EN_BUTTE] = 0x0268, + [ACTOR_EN_INSECT] = 0x032C, + [ACTOR_EN_FISH] = 0x0254, + [ACTOR_EN_HOLL] = 0x0154, + [ACTOR_EN_SCENE_CHANGE] = 0x0150, + [ACTOR_EN_ZF] = 0x0568, + [ACTOR_EN_HATA] = 0x027C, + [ACTOR_BOSS_DODONGO] = 0x1820, + [ACTOR_BOSS_GOMA] = 0x0B1C, + [ACTOR_EN_ZL1] = 0x020C, + [ACTOR_EN_VIEWER] = 0x05F8, + [ACTOR_EN_GOMA] = 0x03A4, + [ACTOR_BG_PUSHBOX] = 0x0168, + [ACTOR_EN_BUBBLE] = 0x0260, + [ACTOR_DOOR_SHUTTER] = 0x0178, + [ACTOR_EN_DODOJR] = 0x02C0, + [ACTOR_EN_BDFIRE] = 0x01E4, + [ACTOR_EN_BOOM] = 0x01FC, + [ACTOR_EN_TORCH2] = sizeof(Player), + [ACTOR_EN_BILI] = 0x0220, + [ACTOR_EN_TP] = 0x01D8, + [ACTOR_EN_ST] = 0x057C, + [ACTOR_EN_BW] = 0x032C, + [ACTOR_EN_A_OBJ] = 0x1C8, + [ACTOR_EN_EIYER] = 0x02D4, + [ACTOR_EN_RIVER_SOUND] = 0x0150, + [ACTOR_EN_HORSE_NORMAL] = 0x0328, + [ACTOR_EN_OSSAN] = 0x02D8, + [ACTOR_BG_TREEMOUTH] = 0x0170, + [ACTOR_BG_DODOAGO] = 0x0250, + [ACTOR_BG_HIDAN_DALM] = 0x02FC, + [ACTOR_BG_HIDAN_HROCK] = 0x0244, + [ACTOR_EN_HORSE_GANON] = 0x02A8, + [ACTOR_BG_HIDAN_ROCK] = 0x01C8, + [ACTOR_BG_HIDAN_RSEKIZOU] = 0x0308, + [ACTOR_BG_HIDAN_SEKIZOU] = 0x0314, + [ACTOR_BG_HIDAN_SIMA] = 0x020C, + [ACTOR_BG_HIDAN_SYOKU] = 0x016C, + [ACTOR_EN_XC] = 0x033C, + [ACTOR_BG_HIDAN_CURTAIN] = 0x01A4, + [ACTOR_BG_SPOT00_HANEBASI] = 0x0180, + [ACTOR_EN_MB] = 0x050C, + [ACTOR_EN_BOMBF] = 0x0210, + [ACTOR_EN_ZL2] = 0x0280, + [ACTOR_BG_HIDAN_FSLIFT] = 0x016C, + [ACTOR_EN_OE2] = 0x0194, + [ACTOR_BG_YDAN_HASI] = 0x016C, + [ACTOR_BG_YDAN_MARUTA] = 0x0244, + [ACTOR_BOSS_GANONDROF] = 0x0578, + [ACTOR_EN_AM] = 0x038C, + [ACTOR_EN_DEKUBABA] = 0x0418, + [ACTOR_EN_M_FIRE1] = 0x019C, + [ACTOR_EN_M_THUNDER] = 0x01CC, + [ACTOR_BG_DDAN_JD] = 0x0170, + [ACTOR_BG_BREAKWALL] = 0x01F0, + [ACTOR_EN_JJ] = 0x0314, + [ACTOR_EN_HORSE_ZELDA] = 0x02A8, + [ACTOR_BG_DDAN_KD] = 0x01C8, + [ACTOR_DOOR_WARP1] = 0x01F0, + [ACTOR_OBJ_SYOKUDAI] = 0x01FC, + [ACTOR_ITEM_B_HEART] = 0x016C, + [ACTOR_EN_DEKUNUTS] = 0x0314, + [ACTOR_BG_MENKURI_KAITEN] = 0x0164, + [ACTOR_BG_MENKURI_EYE] = 0x01B0, + [ACTOR_EN_VALI] = 0x0448, + [ACTOR_BG_MIZU_MOVEBG] = 0x0188, + [ACTOR_BG_MIZU_WATER] = 0x0160, + [ACTOR_ARMS_HOOK] = 0x0218, + [ACTOR_EN_FHG] = 0x0294, + [ACTOR_BG_MORI_HINERI] = 0x016C, + [ACTOR_EN_BB] = 0x0328, + [ACTOR_BG_TOKI_HIKARI] = 0x0154, + [ACTOR_EN_YUKABYUN] = 0x01A0, + [ACTOR_BG_TOKI_SWD] = 0x019C, + [ACTOR_EN_FHG_FIRE] = 0x0204, + [ACTOR_BG_MJIN] = 0x016C, + [ACTOR_BG_HIDAN_KOUSI] = 0x016C, + [ACTOR_DOOR_TOKI] = 0x0168, + [ACTOR_BG_HIDAN_HAMSTEP] = 0x0248, + [ACTOR_EN_BIRD] = 0x01DC, + [ACTOR_EN_WOOD02] = 0x01A4, + [ACTOR_EN_LIGHTBOX] = 0x0164, + [ACTOR_EN_PU_BOX] = 0x0168, + [ACTOR_EN_TRAP] = 0x01EC, + [ACTOR_EN_AROW_TRAP] = 0x0154, + [ACTOR_EN_VASE] = 0x014C, + [ACTOR_EN_TA] = 0x02E8, + [ACTOR_EN_TK] = 0x0770, + [ACTOR_BG_MORI_BIGST] = 0x016C, + [ACTOR_BG_MORI_ELEVATOR] = 0x0174, + [ACTOR_BG_MORI_KAITENKABE] = 0x0188, + [ACTOR_BG_MORI_RAKKATENJO] = 0x0178, + [ACTOR_EN_VM] = 0x03B4, + [ACTOR_DEMO_EFFECT] = 0x01A0, + [ACTOR_DEMO_KANKYO] = 0x0604, + [ACTOR_BG_HIDAN_FWBIG] = 0x01A0, + [ACTOR_EN_FLOORMAS] = 0x0314, + [ACTOR_EN_HEISHI1] = 0x02AC, + [ACTOR_EN_RD] = 0x036C, + [ACTOR_EN_PO_SISTERS] = 0x0338, + [ACTOR_BG_HEAVY_BLOCK] = 0x0178, + [ACTOR_BG_PO_EVENT] = 0x0248, + [ACTOR_OBJ_MURE] = 0x01AC, + [ACTOR_EN_SW] = 0x04D8, + [ACTOR_BOSS_FD] = 0x43A0, + [ACTOR_OBJECT_KANKYO] = 0x1660, + [ACTOR_EN_DU] = 0x021C, + [ACTOR_EN_FD] = 0x31E0, + [ACTOR_EN_HORSE_LINK_CHILD] = 0x02A4, + [ACTOR_DOOR_ANA] = 0x019C, + [ACTOR_BG_SPOT02_OBJECTS] = 0x0174, + [ACTOR_BG_HAKA] = 0x0168, + [ACTOR_MAGIC_WIND] = 0x0174, + [ACTOR_MAGIC_FIRE] = 0x01AC, + [ACTOR_EN_RU1] = 0x039C, + [ACTOR_BOSS_FD2] = 0x167C, + [ACTOR_EN_FD_FIRE] = 0x01AC, + [ACTOR_EN_DH] = 0x0324, + [ACTOR_EN_DHA] = 0x0360, + [ACTOR_EN_RL] = 0x01AC, + [ACTOR_EN_ENCOUNT1] = 0x0170, + [ACTOR_DEMO_DU] = 0x01B4, + [ACTOR_DEMO_IM] = 0x02FC, + [ACTOR_DEMO_TRE_LGT] = 0x017C, + [ACTOR_EN_FW] = 0x0700, + [ACTOR_BG_VB_SIMA] = 0x017C, + [ACTOR_EN_VB_BALL] = 0x01B4, + [ACTOR_BG_HAKA_MEGANE] = 0x016C, + [ACTOR_BG_HAKA_MEGANEBG] = 0x016C, + [ACTOR_BG_HAKA_SHIP] = 0x0178, + [ACTOR_BG_HAKA_SGAMI] = 0x0338, + [ACTOR_EN_HEISHI2] = 0x03E4, + [ACTOR_EN_ENCOUNT2] = 0x0A20, + [ACTOR_EN_FIRE_ROCK] = 0x01E0, + [ACTOR_EN_BROB] = 0x02C0, + [ACTOR_MIR_RAY] = 0x02B0, + [ACTOR_BG_SPOT09_OBJ] = 0x0168, + [ACTOR_BG_SPOT18_OBJ] = 0x016C, + [ACTOR_BOSS_VA] = 0x03B8, + [ACTOR_BG_HAKA_TUBO] = 0x0204, + [ACTOR_BG_HAKA_TRAP] = 0x029C, + [ACTOR_BG_HAKA_HUTA] = 0x016C, + [ACTOR_BG_HAKA_ZOU] = 0x01B8, + [ACTOR_BG_SPOT17_FUNEN] = 0x014C, + [ACTOR_EN_SYATEKI_ITM] = 0x01D8, + [ACTOR_EN_SYATEKI_MAN] = 0x022C, + [ACTOR_EN_TANA] = 0x014C, + [ACTOR_EN_NB] = 0x0328, + [ACTOR_BOSS_MO] = 0x158C, + [ACTOR_EN_SB] = 0x0208, + [ACTOR_EN_BIGOKUTA] = 0x0384, + [ACTOR_EN_KAREBABA] = 0x0290, + [ACTOR_BG_BDAN_OBJECTS] = 0x01BC, + [ACTOR_DEMO_SA] = 0x01B4, + [ACTOR_DEMO_GO] = 0x01A0, + [ACTOR_EN_IN] = 0x03A8, + [ACTOR_EN_TR] = 0x02E8, + [ACTOR_BG_SPOT16_BOMBSTONE] = 0x0218, + [ACTOR_BG_HIDAN_KOWARERUKABE] = 0x01C4, + [ACTOR_BG_BOMBWALL] = 0x02A4, + [ACTOR_BG_SPOT08_ICEBLOCK] = 0x019C, + [ACTOR_EN_RU2] = 0x0314, + [ACTOR_OBJ_DEKUJR] = 0x01A4, + [ACTOR_BG_MIZU_UZU] = 0x0168, + [ACTOR_BG_SPOT06_OBJECTS] = 0x01D0, + [ACTOR_BG_ICE_OBJECTS] = 0x0174, + [ACTOR_BG_HAKA_WATER] = 0x0154, + [ACTOR_EN_MA2] = 0x0284, + [ACTOR_EN_BOM_CHU] = 0x01E4, + [ACTOR_EN_HORSE_GAME_CHECK] = 0x01A4, + [ACTOR_BOSS_TW] = 0x06B4, + [ACTOR_EN_RR] = 0x23C4, + [ACTOR_EN_BA] = 0x03C0, + [ACTOR_EN_BX] = 0x0298, + [ACTOR_EN_ANUBICE] = 0x0314, + [ACTOR_EN_ANUBICE_FIRE] = 0x01F4, + [ACTOR_BG_MORI_HASHIGO] = 0x01D0, + [ACTOR_BG_MORI_HASHIRA4] = 0x016C, + [ACTOR_BG_MORI_IDOMIZU] = 0x0160, + [ACTOR_BG_SPOT16_DOUGHNUT] = 0x0154, + [ACTOR_BG_BDAN_SWITCH] = 0x01E0, + [ACTOR_EN_MA1] = 0x0210, + [ACTOR_BOSS_GANON] = 0x071C, + [ACTOR_BOSS_SST] = 0x0A98, + [ACTOR_EN_NY] = 0x02B8, + [ACTOR_EN_FR] = 0x03C4, + [ACTOR_ITEM_SHIELD] = 0x020C, + [ACTOR_BG_ICE_SHELTER] = 0x0204, + [ACTOR_EN_ICE_HONO] = 0x01BC, + [ACTOR_ITEM_OCARINA] = 0x0154, + [ACTOR_MAGIC_DARK] = 0x0164, + [ACTOR_DEMO_6K] = 0x0294, + [ACTOR_EN_ANUBICE_TAG] = 0x0158, + [ACTOR_BG_HAKA_GATE] = 0x0174, + [ACTOR_BG_SPOT15_SAKU] = 0x0180, + [ACTOR_BG_JYA_GOROIWA] = 0x01BC, + [ACTOR_BG_JYA_ZURERUKABE] = 0x0170, + [ACTOR_BG_JYA_COBRA] = 0x11A4, + [ACTOR_BG_JYA_KANAAMI] = 0x016C, + [ACTOR_FISHING] = 0x0550, + [ACTOR_OBJ_OSHIHIKI] = 0x01D4, + [ACTOR_BG_GATE_SHUTTER] = 0x017C, + [ACTOR_EFF_DUST] = 0x0568, + [ACTOR_BG_SPOT01_FUSYA] = 0x0160, + [ACTOR_BG_SPOT01_IDOHASHIRA] = 0x0174, + [ACTOR_BG_SPOT01_IDOMIZU] = 0x0158, + [ACTOR_BG_PO_SYOKUDAI] = 0x01B0, + [ACTOR_BG_GANON_OTYUKA] = 0x018C, + [ACTOR_BG_SPOT15_RRBOX] = 0x0184, + [ACTOR_BG_UMAJUMP] = 0x0164, + [ACTOR_ARROW_FIRE] = 0x016C, + [ACTOR_ARROW_ICE] = 0x016C, + [ACTOR_ARROW_LIGHT] = 0x016C, + [ACTOR_ITEM_ETCETERA] = 0x0160, + [ACTOR_OBJ_KIBAKO] = 0x019C, + [ACTOR_OBJ_TSUBO] = 0x01A0, + [ACTOR_EN_WONDER_ITEM] = 0x01D0, + [ACTOR_EN_IK] = 0x04DC, + [ACTOR_DEMO_IK] = 0x01B4, + [ACTOR_EN_SKJ] = 0x0300, + [ACTOR_EN_SKJNEEDLE] = 0x01E8, + [ACTOR_EN_G_SWITCH] = 0x12F8, + [ACTOR_DEMO_EXT] = 0x0184, + [ACTOR_DEMO_SHD] = 0x0154, + [ACTOR_EN_DNS] = 0x02C8, + [ACTOR_ELF_MSG] = 0x0150, + [ACTOR_EN_HONOTRAP] = 0x0244, + [ACTOR_EN_TUBO_TRAP] = 0x01AC, + [ACTOR_OBJ_ICE_POLY] = 0x01EC, + [ACTOR_BG_SPOT03_TAKI] = 0x0178, + [ACTOR_BG_SPOT07_TAKI] = 0x0168, + [ACTOR_EN_FZ] = 0x0BD4, + [ACTOR_EN_PO_RELAY] = 0x02DC, + [ACTOR_BG_RELAY_OBJECTS] = 0x016C, + [ACTOR_EN_DIVING_GAME] = 0x0398, + [ACTOR_EN_KUSA] = 0x01A0, + [ACTOR_OBJ_BEAN] = 0x01F8, + [ACTOR_OBJ_BOMBIWA] = 0x0198, + [ACTOR_OBJ_SWITCH] = 0x0258, + [ACTOR_OBJ_ELEVATOR] = 0x0174, + [ACTOR_OBJ_LIFT] = 0x0170, + [ACTOR_OBJ_HSBLOCK] = 0x0168, + [ACTOR_EN_OKARINA_TAG] = 0x0160, + [ACTOR_EN_YABUSAME_MARK] = 0x0210, + [ACTOR_EN_GOROIWA] = 0x01D4, + [ACTOR_EN_EX_RUPPY] = 0x0164, + [ACTOR_EN_TORYO] = 0x02E0, + [ACTOR_EN_DAIKU] = 0x034C, + [ACTOR_EN_NWC] = 0x0734, + [ACTOR_EN_BLKOBJ] = 0x016C, + [ACTOR_ITEM_INBOX] = 0x0150, + [ACTOR_EN_GE1] = 0x02BC, + [ACTOR_OBJ_BLOCKSTOP] = 0x014C, + [ACTOR_EN_SDA] = 0x014C, + [ACTOR_EN_CLEAR_TAG] = 0x0204, + [ACTOR_EN_NIW_LADY] = 0x02FC, + [ACTOR_EN_GM] = 0x02D0, + [ACTOR_EN_MS] = 0x0250, + [ACTOR_EN_HS] = 0x02B0, + [ACTOR_BG_INGATE] = 0x0168, + [ACTOR_EN_KANBAN] = 0x01EC, + [ACTOR_EN_HEISHI3] = 0x02C8, + [ACTOR_EN_SYATEKI_NIW] = 0x0460, + [ACTOR_EN_ATTACK_NIW] = 0x02E8, + [ACTOR_BG_SPOT01_IDOSOKO] = 0x0168, + [ACTOR_EN_SA] = 0x02EC, + [ACTOR_EN_WONDER_TALK] = 0x0168, + [ACTOR_BG_GJYO_BRIDGE] = 0x0168, + [ACTOR_EN_DS] = 0x01F0, + [ACTOR_EN_MK] = 0x0288, + [ACTOR_EN_BOM_BOWL_MAN] = 0x0264, + [ACTOR_EN_BOM_BOWL_PIT] = 0x3704, + [ACTOR_EN_OWL] = 0x0414, + [ACTOR_EN_ISHI] = 0x019C, + [ACTOR_OBJ_HANA] = 0x0198, + [ACTOR_OBJ_LIGHTSWITCH] = 0x01C4, + [ACTOR_OBJ_MURE2] = 0x0188, + [ACTOR_EN_GO] = 0x06C8, + [ACTOR_EN_FU] = 0x02B0, + [ACTOR_EN_CHANGER] = 0x016C, + [ACTOR_BG_JYA_MEGAMI] = 0x033C, + [ACTOR_BG_JYA_LIFT] = 0x016C, + [ACTOR_BG_JYA_BIGMIRROR] = 0x0174, + [ACTOR_BG_JYA_BOMBCHUIWA] = 0x01B8, + [ACTOR_BG_JYA_AMISHUTTER] = 0x0168, + [ACTOR_BG_JYA_BOMBIWA] = 0x01C8, + [ACTOR_BG_SPOT18_BASKET] = 0x021C, + [ACTOR_EN_GANON_ORGAN] = 0x0150, + [ACTOR_EN_SIOFUKI] = 0x01A0, + [ACTOR_EN_STREAM] = 0x0158, + [ACTOR_EN_MM] = 0x0320, + [ACTOR_EN_KO] = 0x0324, + [ACTOR_EN_KZ] = 0x02D8, + [ACTOR_EN_WEATHER_TAG] = 0x0154, + [ACTOR_BG_SST_FLOOR] = 0x016C, + [ACTOR_EN_ANI] = 0x02B4, + [ACTOR_EN_EX_ITEM] = 0x0184, + [ACTOR_BG_JYA_IRONOBJ] = 0x01B4, + [ACTOR_EN_JS] = 0x0290, + [ACTOR_EN_JSJUTAN] = 0x0178, + [ACTOR_EN_CS] = 0x0344, + [ACTOR_EN_MD] = 0x0324, + [ACTOR_EN_HY] = 0x0334, + [ACTOR_EN_GANON_MANT] = 0x1708, + [ACTOR_EN_OKARINA_EFFECT] = 0x0154, + [ACTOR_EN_MAG] = 0xE328, + [ACTOR_DOOR_GERUDO] = 0x016C, + [ACTOR_ELF_MSG2] = 0x0150, + [ACTOR_DEMO_GT] = 0x01A8, + [ACTOR_EN_PO_FIELD] = 0x02DC, + [ACTOR_EFC_ERUPC] = 0x18CC, + [ACTOR_BG_ZG] = 0x016C, + [ACTOR_EN_HEISHI4] = 0x0308, + [ACTOR_EN_ZL3] = 0x0420, + [ACTOR_BOSS_GANON2] = 0x08E4, + [ACTOR_EN_KAKASI] = 0x020C, + [ACTOR_EN_TAKARA_MAN] = 0x0238, + [ACTOR_OBJ_MAKEOSHIHIKI] = 0x014C, + [ACTOR_OCEFF_SPOT] = 0x0180, + [ACTOR_END_TITLE] = 0x0150, + [ACTOR_EN_TORCH] = 0x014C, + [ACTOR_DEMO_EC] = 0x01A8, + [ACTOR_SHOT_SUN] = 0x01A8, + [ACTOR_EN_DY_EXTRA] = 0x0174, + [ACTOR_EN_WONDER_TALK2] = 0x0170, + [ACTOR_EN_GE2] = 0x030C, + [ACTOR_OBJ_ROOMTIMER] = 0x0154, + [ACTOR_EN_SSH] = 0x05D4, + [ACTOR_EN_STH] = 0x02BC, + [ACTOR_OCEFF_WIPE] = 0x0150, + [ACTOR_OCEFF_STORM] = 0x0158, + [ACTOR_EN_WEIYER] = 0x02D0, + [ACTOR_BG_SPOT05_SOKO] = 0x016C, + [ACTOR_BG_JYA_1FLIFT] = 0x01BC, + [ACTOR_BG_JYA_HAHENIRON] = 0x01B4, + [ACTOR_BG_SPOT12_GATE] = 0x016C, + [ACTOR_BG_SPOT12_SAKU] = 0x016C, + [ACTOR_EN_HINTNUTS] = 0x0260, + [ACTOR_EN_NUTSBALL] = 0x01A0, + [ACTOR_BG_SPOT00_BREAK] = 0x0164, + [ACTOR_EN_SHOPNUTS] = 0x02BC, + [ACTOR_EN_IT] = 0x019C, + [ACTOR_EN_GELDB] = 0x04E4, + [ACTOR_OCEFF_WIPE2] = 0x0150, + [ACTOR_OCEFF_WIPE3] = 0x0150, + [ACTOR_EN_NIW_GIRL] = 0x02FC, + [ACTOR_EN_DOG] = 0x0290, + [ACTOR_EN_SI] = 0x01A0, + [ACTOR_BG_SPOT01_OBJECTS2] = 0x0180, + [ACTOR_OBJ_COMB] = 0x01B4, + [ACTOR_BG_SPOT11_BAKUDANKABE] = 0x01B0, + [ACTOR_OBJ_KIBAKO2] = 0x01B8, + [ACTOR_EN_DNT_DEMO] = 0x0200, + [ACTOR_EN_DNT_JIJI] = 0x02A8, + [ACTOR_EN_DNT_NOMAL] = 0x0360, + [ACTOR_EN_GUEST] = 0x0310, + [ACTOR_BG_BOM_GUARD] = 0x0178, + [ACTOR_EN_HS2] = 0x02B0, + [ACTOR_DEMO_KEKKAI] = 0x01FC, + [ACTOR_BG_SPOT08_BAKUDANKABE] = 0x0244, + [ACTOR_BG_SPOT17_BAKUDANKABE] = 0x0164, + [ACTOR_OBJ_MURE3] = 0x0170, + [ACTOR_EN_TG] = 0x020C, + [ACTOR_EN_MU] = 0x024C, + [ACTOR_EN_GO2] = 0x05A0, + [ACTOR_EN_WF] = 0x04DC, + [ACTOR_EN_SKB] = 0x0344, + [ACTOR_DEMO_GJ] = 0x0278, + [ACTOR_DEMO_GEFF] = 0x0168, + [ACTOR_BG_GND_FIREMEIRO] = 0x0178, + [ACTOR_BG_GND_DARKMEIRO] = 0x0170, + [ACTOR_BG_GND_SOULMEIRO] = 0x01A0, + [ACTOR_BG_GND_NISEKABE] = 0x014C, + [ACTOR_BG_GND_ICEBLOCK] = 0x0174, + [ACTOR_EN_GB] = 0x0438, + [ACTOR_EN_GS] = 0x0208, + [ACTOR_BG_MIZU_BWALL] = 0x02BC, + [ACTOR_BG_MIZU_SHUTTER] = 0x0190, + [ACTOR_EN_DAIKU_KAKARIKO] = 0x0308, + [ACTOR_BG_BOWL_WALL] = 0x0188, + [ACTOR_EN_WALL_TUBO] = 0x0170, + [ACTOR_EN_PO_DESERT] = 0x0284, + [ACTOR_EN_CROW] = 0x0298, + [ACTOR_DOOR_KILLER] = 0x0284, + [ACTOR_BG_SPOT11_OASIS] = 0x0154, + [ACTOR_BG_SPOT18_FUTA] = 0x0164, + [ACTOR_BG_SPOT18_SHUTTER] = 0x0168, + [ACTOR_EN_MA3] = 0x0284, + [ACTOR_EN_COW] = 0x0280, + [ACTOR_BG_ICE_TURARA] = 0x01B8, + [ACTOR_BG_ICE_SHUTTER] = 0x0168, + [ACTOR_EN_KAKASI2] = 0x01F8, + [ACTOR_EN_KAKASI3] = 0x020C, + [ACTOR_OCEFF_WIPE4] = 0x0150, + [ACTOR_EN_EG] = 0x0154, + [ACTOR_BG_MENKURI_NISEKABE] = 0x014C, + [ACTOR_EN_ZO] = 0x06A8, + [ACTOR_OBJ_MAKEKINSUTA] = 0x0154, + [ACTOR_EN_GE3] = 0x0314, + [ACTOR_OBJ_TIMEBLOCK] = 0x017C, + [ACTOR_OBJ_HAMISHI] = 0x01A8, + [ACTOR_EN_ZL4] = 0x02F0, + [ACTOR_EN_MM2] = 0x02BC, + [ACTOR_BG_JYA_BLOCK] = 0x0164, + [ACTOR_OBJ_WARP2BLOCK] = 0x0178, +}; diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h new file mode 100644 index 00000000000..4c57e5c4daa --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h @@ -0,0 +1,5 @@ +#pragma once + +#include "global.h" + +extern const u32 gN64InstanceSizes[]; \ No newline at end of file From c26695cea90fd92cba35bc56f45783190b31737b Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 7 May 2026 01:36:00 -0700 Subject: [PATCH 06/58] feat(restoration): Actor overlay and instance hooks --- soh/soh/ActorDB.cpp | 12 +++++-- soh/soh/ActorDB.h | 3 ++ .../N64MemoryModel/N64MemoryModel.cpp | 15 ++++----- .../N64MemoryModel/N64MemoryModel.hpp | 8 ++--- .../ShadowArena/actor_overlay_sizes.c | 2 +- soh/soh/SohGui/SohMenuEnhancements.cpp | 2 +- soh/src/code/z_actor.c | 33 +++++++++++++++++++ soh/src/code/z_malloc.c | 3 +- 8 files changed, 61 insertions(+), 17 deletions(-) diff --git a/soh/soh/ActorDB.cpp b/soh/soh/ActorDB.cpp index 70ea29675e6..e8a15d0d503 100644 --- a/soh/soh/ActorDB.cpp +++ b/soh/soh/ActorDB.cpp @@ -17,10 +17,15 @@ ActorDB* ActorDB::Instance; struct AddPair { const char* name; ActorInit& init; + // #region SOH [Enhancement] - N64 Memory Model + AllocType allocType; + // #endregion }; -#define DEFINE_ACTOR_INTERNAL(name, _1, allocType) { #name, name##_InitVars }, -#define DEFINE_ACTOR(name, _1, allocType) { #name, name##_InitVars }, +// #region [SOH] Enhancement - N64 Memory Model +#define DEFINE_ACTOR_INTERNAL(name, _1, allocType) { #name, name##_InitVars, allocType }, +#define DEFINE_ACTOR(name, _1, allocType) { #name, name##_InitVars, allocType }, +// #endregion #define DEFINE_ACTOR_UNSET(_0) static constexpr AddPair initialActorTable[] = { @@ -470,6 +475,9 @@ ActorDB::ActorDB() { db.reserve(ACTOR_NUMBER_MAX); // reserve size for all initial entries so we don't do it for each for (const AddPair& pair : initialActorTable) { Entry& entry = AddEntry(pair.name, actorDescriptions[pair.init.id], pair.init); + // #region SOH [Enhancement] - N64 Memory Model + entry.entry.allocType = pair.allocType; + // #endregion } } diff --git a/soh/soh/ActorDB.h b/soh/soh/ActorDB.h index dd8ecf90153..3fe5c4021dc 100644 --- a/soh/soh/ActorDB.h +++ b/soh/soh/ActorDB.h @@ -16,6 +16,9 @@ typedef struct { ActorFunc draw; ActorResetFunc reset; s32 numLoaded; + // #region SOH [Enhancement] - N64 Memory Model + AllocType allocType; + // #endregion } ActorDBEntry; #ifdef __cplusplus diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 39b3877323c..1e3393eec64 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -76,7 +76,7 @@ s32 N64Mem_IsActive() // Actor overlays // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocOverlay(ActorID actorId, AllocType allocType) +s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { if (!sIsActive) { @@ -138,7 +138,7 @@ s32 N64Mem_AllocOverlay(ActorID actorId, AllocType allocType) return 1; } -void N64Mem_FreeOverlay(ActorID actorId, AllocType allocType) +void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { if (!sIsActive) { @@ -164,7 +164,7 @@ void N64Mem_FreeOverlay(ActorID actorId, AllocType allocType) // Actor instances // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocInstance(ActorID actorId, void* realPtr) +s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { if (!sIsActive) { @@ -185,8 +185,7 @@ s32 N64Mem_AllocInstance(ActorID actorId, void* realPtr) const u32 shadow = ShadowArena_Malloc(&sShadow, instanceSize); if (shadow == SHADOW_NULL) { - SPDLOG_ERROR("[N64MemoryModel] Shadow instance failed for actor 0x{:04X} (need 0x{:X})", - static_cast(actorId), instanceSize); + SPDLOG_ERROR("[N64MemoryModel] Shadow instance failed for actor 0x{:04X} (need 0x{:X})", actorId, instanceSize); return 0; } @@ -255,7 +254,7 @@ void N64Mem_FreeSubsidiary(void* realPtr) // Effect overlays // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocEffectOverlay(EffectSsType type) +s32 N64Mem_AllocEffectOverlay(s32 type) { if (!sIsActive) { @@ -281,8 +280,8 @@ s32 N64Mem_AllocEffectOverlay(EffectSsType type) const u32 shadow = ShadowArena_MallocR(&sShadow, overlaySize); if (shadow == SHADOW_NULL) { - SPDLOG_ERROR("[N64MemoryModel] Shadow effect overlay failed for type 0x{:02X} (need 0x{:X})", - static_cast(type), overlaySize); + SPDLOG_ERROR("[N64MemoryModel] Shadow effect overlay failed for type 0x{:02X} (need 0x{:X})", type, + overlaySize); return 0; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index b74163812a9..e1e119eb377 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -27,8 +27,8 @@ s32 N64Mem_IsActive(void); // Call FreeOverlay when numLoaded transitions 1 -> 0. // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocOverlay(ActorID actorId, AllocType allocType); -void N64Mem_FreeOverlay(ActorID actorId, AllocType allocType); +s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType); +void N64Mem_FreeOverlay(s16 actorId, u16 allocType); // -------------------------------------------------------------------------------------------------------------------- // Actor instances: Paired with real ZeldaArena allocations. @@ -39,7 +39,7 @@ void N64Mem_FreeOverlay(ActorID actorId, AllocType allocType); // Call FreeInstance when the actor is deleted. // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocInstance(ActorID actorId, void* realPtr); +s32 N64Mem_AllocInstance(s16 actorId, void* realPtr); void N64Mem_FreeInstance(void* realPtr); // -------------------------------------------------------------------------------------------------------------------- @@ -57,7 +57,7 @@ void N64Mem_FreeSubsidiary(void* realPtr); // On N64, effect overlays load via MallocR on first spawn and are never freed until the GameState is torn down. // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocEffectOverlay(EffectSsType type); +s32 N64Mem_AllocEffectOverlay(s32 type); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c index 5a75a1dd20d..69a0d3ed51d 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c @@ -18,7 +18,7 @@ const u32 gN64ActorOverlaySizes[ACTOR_ID_MAX] = { [ACTOR_EN_FIREFLY] = 0x2170, [ACTOR_EN_HORSE] = 0xC260, [ACTOR_EN_ARROW] = 0x16F0, - [ACTOR_EN_ELF] = 0x49C0 + [ACTOR_EN_ELF] = 0x49C0, [ACTOR_EN_NIW] = 0x3330, [ACTOR_EN_TITE] = 0x2DA0, [ACTOR_EN_REEBA] = 0x1A70, diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 2135a0c5c9f..a1ecb485313 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1212,7 +1212,7 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("N64WeirdFrames")) .Options(CheckboxOptions().Tooltip( "Restores N64 Weird Frames allowing weirdshots and weirdslides to behave the same as N64.")); - AddWidget(path, "Authentic N64 Memory", WIDGET_CVAR_CHECKBOX) + AddWidget(path, "N64 Memory Model", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("N64MemoryModel")) .Options(CheckboxOptions().Tooltip( "Simulates N64 memory constraints using a shadow arena with the original allocator and struct sizes." diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index ef249ae9e83..29fc3995c75 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -14,6 +14,7 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/Enhancements/nametag.h" +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" #include "soh/ActorDB.h" #include "soh/OTRGlobals.h" @@ -3353,6 +3354,17 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos return NULL; } + // #region SOH [Enhancement] - N64 Memory Model + if (dbEntry->numLoaded == 0) + { + if (!N64Mem_AllocOverlay(actorId, dbEntry->allocType)) + { + Actor_FreeOverlay(dbEntry); + return NULL; + } + } + // #endregion + actor = ZELDA_ARENA_MALLOC_DEBUG(dbEntry->instanceSize); if (actor == NULL) { @@ -3363,6 +3375,15 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos return NULL; } + // #region SOH [Enhancement] - N64 Memory Model + if (!N64Mem_AllocInstance(actorId, actor)) + { + ZELDA_ARENA_FREE_DEBUG(actor); + Actor_FreeOverlay(dbEntry); + return NULL; + } + // #endregion + // #region SOH [ObjectExtension] SetActorListIndex(actor, -1); // #endregion @@ -3523,9 +3544,21 @@ Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) { ObjectExtension_Free(actor); // #endregion + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_FreeInstance(actor); + // #endregion + ZELDA_ARENA_FREE_DEBUG(actor); dbEntry->numLoaded--; + + // #region SOH [Enhancement] - N64 Memory Model + if (dbEntry->numLoaded == 0) + { + N64Mem_FreeOverlay(actor->id, dbEntry->allocType); + } + // #endregion + Actor_FreeOverlay(dbEntry); return newHead; diff --git a/soh/src/code/z_malloc.c b/soh/src/code/z_malloc.c index a2142a8d8c9..9f5bfbc6d69 100644 --- a/soh/src/code/z_malloc.c +++ b/soh/src/code/z_malloc.c @@ -91,11 +91,12 @@ void ZeldaArena_GetSizes(u32* outMaxFree, u32* outFree, u32* outAlloc) { ArenaImpl_GetSizes(&sZeldaArena, outMaxFree, outFree, outAlloc); } -// SOH [Enhancement] - Heap Viewer +// #region SOH [Enhancement] - Heap Viewer ArenaNode* ZeldaArena_GetHead() { return sZeldaArena.head; } +// #endregion void ZeldaArena_Check() { __osCheckArena(&sZeldaArena); From 09cbafbbda38df5d4048bfe580646d7e85e225e0 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 7 May 2026 02:06:31 -0700 Subject: [PATCH 07/58] feat(restoration): Collider and effect overlay hooks --- .../N64MemoryModel/N64MemoryModel.hpp | 4 ++ soh/soh/SohGui/SohMenuEnhancements.cpp | 4 +- soh/src/code/z_collision_check.c | 61 +++++++++++++++++++ soh/src/code/z_effect_soft_sprite.c | 8 +++ 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index e1e119eb377..d96edaa6533 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -6,6 +6,10 @@ extern "C" { #endif +// N64 subsidiary struct sizes (32-bit, from decomp build artifacts). +#define N64_SIZEOF_COLLIDER_JNT_SPH_ELEM 0x40 +#define N64_SIZEOF_COLLIDER_TRIS_ELEM 0x5C + // -------------------------------------------------------------------------------------------------------------------- // Lifecycle // -------------------------------------------------------------------------------------------------------------------- diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index a1ecb485313..db120ecbfc6 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1215,8 +1215,8 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "N64 Memory Model", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("N64MemoryModel")) .Options(CheckboxOptions().Tooltip( - "Simulates N64 memory constraints using a shadow arena with the original allocator and struct sizes." - "Enabled memory-dependent behaviors such as actor spawn failures from heap fragmentation during repeated" + "Simulates N64 memory constraints using a shadow arena with the original allocator and struct sizes. " + "Enables memory-dependent behaviors such as actor spawn failures from heap fragmentation during repeated " "room transitions.")); AddWidget(path, "Misc Restorations", WIDGET_SEPARATOR_TEXT); diff --git a/soh/src/code/z_collision_check.c b/soh/src/code/z_collision_check.c index aa466e412eb..56fd0071add 100644 --- a/soh/src/code/z_collision_check.c +++ b/soh/src/code/z_collision_check.c @@ -2,6 +2,7 @@ #include "vt.h" #include "overlays/effects/ovl_Effect_Ss_HitMark/z_eff_ss_hitmark.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" #include typedef s32 (*ColChkResetFunc)(PlayState*, Collider*); @@ -337,6 +338,10 @@ s32 Collider_FreeJntSph(PlayState* play, ColliderJntSph* collider) { collider->count = 0; if (collider->elements != NULL) { + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_FreeSubsidiary(collider->elements); + // #endregion + ZELDA_ARENA_FREE_DEBUG(collider->elements); } collider->elements = NULL; @@ -378,6 +383,16 @@ s32 Collider_SetJntSphToActor(PlayState* play, ColliderJntSph* dest, ColliderJnt return 0; } + // #region SOH [Enhancement] - N64 Memory Model + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) + { + ZELDA_ARENA_FREE_DEBUG(dest->elements); + dest->elements = NULL; + dest->count = 0; + return 0; + } + // #endregion + for (destElem = dest->elements, srcElem = src->elements; destElem < dest->elements + dest->count; destElem++, srcElem++) { Collider_InitJntSphElement(play, destElem); @@ -406,6 +421,16 @@ s32 Collider_SetJntSphAllocType1(PlayState* play, ColliderJntSph* dest, Actor* a return 0; } + // #region SOH [Enhancement] - N64 Memory Model + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) + { + ZELDA_ARENA_FREE_DEBUG(dest->elements); + dest->elements = NULL; + dest->count = 0; + return 0; + } + // #endregion + for (destElem = dest->elements, srcElem = src->elements; destElem < dest->elements + dest->count; destElem++, srcElem++) { Collider_InitJntSphElement(play, destElem); @@ -433,6 +458,17 @@ s32 Collider_SetJntSphAlloc(PlayState* play, ColliderJntSph* dest, Actor* actor, osSyncPrintf(VT_RST); return 0; } + + // #region SOH [Enhancement] - N64 Memory Model + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) + { + ZELDA_ARENA_FREE_DEBUG(dest->elements); + dest->elements = NULL; + dest->count = 0; + return 0; + } + // #endregion + for (destElem = dest->elements, srcElem = src->elements; destElem < dest->elements + dest->count; destElem++, srcElem++) { Collider_InitJntSphElement(play, destElem); @@ -700,6 +736,10 @@ s32 Collider_FreeTris(PlayState* play, ColliderTris* tris) { tris->count = 0; if (tris->elements != NULL) { + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_FreeSubsidiary(tris->elements); + // #endregion + ZELDA_ARENA_FREE_DEBUG(tris->elements); } tris->elements = NULL; @@ -740,6 +780,17 @@ s32 Collider_SetTrisAllocType1(PlayState* play, ColliderTris* dest, Actor* actor osSyncPrintf(VT_RST); return 0; } + + // #region SOH [Enhancement] - N64 Memory Model + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) + { + ZELDA_ARENA_FREE_DEBUG(dest->elements); + dest->elements = NULL; + dest->count = 0; + return 0; + } + // #endregion + for (destElem = dest->elements, srcElem = src->elements; destElem < dest->elements + dest->count; destElem++, srcElem++) { Collider_InitTrisElement(play, destElem); @@ -768,6 +819,16 @@ s32 Collider_SetTrisAlloc(PlayState* play, ColliderTris* dest, Actor* actor, Col return 0; } + // #region SOH [Enhancement] - N64 Memory Model + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) + { + ZELDA_ARENA_FREE_DEBUG(dest->elements); + dest->elements = NULL; + dest->count = 0; + return 0; + } + // #endregion + for (destElem = dest->elements, srcElem = src->elements; destElem < dest->elements + dest->count; destElem++, srcElem++) { Collider_InitTrisElement(play, destElem); diff --git a/soh/src/code/z_effect_soft_sprite.c b/soh/src/code/z_effect_soft_sprite.c index 06263304cb3..78d6e170bb6 100644 --- a/soh/src/code/z_effect_soft_sprite.c +++ b/soh/src/code/z_effect_soft_sprite.c @@ -2,6 +2,7 @@ #include "vt.h" #include "soh/frame_interpolation.h" +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" #include EffectSsInfo sEffectSsInfo = { 0 }; // "EffectSS2Info" @@ -183,6 +184,13 @@ void EffectSs_Spawn(PlayState* play, s32 type, s32 priority, void* initParams) { return; } + // #region SOH [Enhancement] - N64 Memory Model + if (!N64Mem_AllocEffectOverlay(type)) + { + return; + } + // #endregion + sEffectSsInfo.searchStartIndex = index + 1; overlaySize = (uintptr_t)overlayEntry->vramEnd - (uintptr_t)overlayEntry->vramStart; From 6984087b3b9b13cb6b1fe350715821fea3a88a51 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 7 May 2026 02:50:05 -0700 Subject: [PATCH 08/58] feat(restoration): Remaining subsidiary hooks --- .../N64MemoryModel/N64MemoryModel.hpp | 3 +++ soh/src/code/z_camera.c | 13 ++++++++++ soh/src/code/z_play.c | 6 +++++ soh/src/code/z_skin_awb.c | 25 +++++++++++++++++++ .../actors/ovl_player_actor/z_player.c | 11 +++++++- 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index d96edaa6533..af0cb696492 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -9,6 +9,9 @@ extern "C" { // N64 subsidiary struct sizes (32-bit, from decomp build artifacts). #define N64_SIZEOF_COLLIDER_JNT_SPH_ELEM 0x40 #define N64_SIZEOF_COLLIDER_TRIS_ELEM 0x5C +#define N64_SIZEOF_CAMERA 0x16C +#define N64_SIZEOF_SKIN_LIMB_VTX 0x0C +#define N64_SIZEOF_GI_OBJECT_SEGMENT 0x1008 // -------------------------------------------------------------------------------------------------------------------- // Lifecycle diff --git a/soh/src/code/z_camera.c b/soh/src/code/z_camera.c index 0d30dcb2e60..0fa886fd0c2 100644 --- a/soh/src/code/z_camera.c +++ b/soh/src/code/z_camera.c @@ -8,6 +8,7 @@ #include "soh/frame_interpolation.h" #include "soh/Enhancements/controls/Mouse.h" +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" s16 Camera_ChangeSettingFlags(Camera* camera, s16 setting, s16 flags); s32 Camera_ChangeModeFlags(Camera* camera, s16 mode, u8 flags); @@ -6943,6 +6944,14 @@ Camera* Camera_Create(View* view, CollisionContext* colCtx, PlayState* play) { Camera* newCamera = ZELDA_ARENA_MALLOC_DEBUG(sizeof(*newCamera)); if (newCamera != NULL) { + // #region SOH [Enhancement] - N64 Memory Model + if (!N64Mem_AllocSubsidiary(newCamera, N64_SIZEOF_CAMERA)) + { + ZELDA_ARENA_FREE_DEBUG(newCamera); + return NULL; + } + // #endregion + osSyncPrintf(VT_FGCOL(BLUE) "camera: create --- allocate %d byte" VT_RST "\n", sizeof(*newCamera) * 4); Camera_Init(newCamera, view, colCtx, play); } else { @@ -6954,6 +6963,10 @@ Camera* Camera_Create(View* view, CollisionContext* colCtx, PlayState* play) { void Camera_Destroy(Camera* camera) { if (camera != NULL) { osSyncPrintf(VT_FGCOL(BLUE) "camera: destroy ---" VT_RST "\n"); + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_FreeSubsidiary(camera); + // #endregion + ZELDA_ARENA_FREE_DEBUG(camera); } else { osSyncPrintf(VT_COL(YELLOW, BLACK) "camera: destroy: already cleared\n" VT_RST); diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index f73d076de42..d46a514591d 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -9,6 +9,7 @@ #include #include "soh/Enhancements/enhancementTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/SaveManager.h" @@ -19,6 +20,7 @@ #include #include + TransitionUnk sTrnsnUnk; s32 gTrnsnUnkState; VisMono gPlayVisMono; @@ -575,6 +577,10 @@ void Play_Init(GameState* thisx) { osSyncPrintf("ゼルダヒープ %08x-%08x\n", zAllocAligned, (u8*)zAllocAligned + zAllocSize - (s32)(zAllocAligned - zAlloc)); + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_Reset(); + // #endregion + Fault_AddClient(&D_801614B8, ZeldaArena_Display, NULL, NULL); // In order to keep masks equipped on first load, we need to pre-set the age reqs for the item and slot diff --git a/soh/src/code/z_skin_awb.c b/soh/src/code/z_skin_awb.c index 504b6376d41..fc6c15a84b7 100644 --- a/soh/src/code/z_skin_awb.c +++ b/soh/src/code/z_skin_awb.c @@ -2,6 +2,7 @@ #include "overlays/actors/ovl_En_fHG/z_en_fhg.h" #include #include "soh/ResourceManagerHelpers.h" +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" /** * Initialises the Vtx buffers used for limb at index `limbIndex` @@ -57,6 +58,10 @@ void Skin_Init(PlayState* play, Skin* skin, SkeletonHeader* skeletonHeader, Anim assert(skin->vtxTable != NULL); + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_AllocSubsidiary(skin->vtxTable, limbCount * N64_SIZEOF_SKIN_LIMB_VTX); + // #endregion + for (i = 0; i < limbCount; i++) { SkinLimbVtx* vtxEntry = &skin->vtxTable[i]; SkinLimb* limb = SEGMENTED_TO_VIRTUAL(skeleton[i]); @@ -74,9 +79,17 @@ void Skin_Init(PlayState* play, Skin* skin, SkeletonHeader* skeletonHeader, Anim vtxEntry->buf[0] = ZELDA_ARENA_MALLOC_DEBUG(animatedLimbData->totalVtxCount * sizeof(Vtx)); assert(vtxEntry->buf[0] != NULL); + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_AllocSubsidiary(vtxEntry->buf[0], animatedLimbData->totalVtxCount * sizeof(Vtx)); + // #endregion + vtxEntry->buf[1] = ZELDA_ARENA_MALLOC_DEBUG(animatedLimbData->totalVtxCount * sizeof(Vtx)); assert(vtxEntry->buf[1] != NULL); + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_AllocSubsidiary(vtxEntry->buf[1], animatedLimbData->totalVtxCount * sizeof(Vtx)); + // #endregion + Skin_InitAnimatedLimb(play, skin, i); } } @@ -93,16 +106,28 @@ void Skin_Free(PlayState* play, Skin* skin) { for (i = 0; i < skin->limbCount; i++) { if (skin->vtxTable[i].buf[0] != NULL) { + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_FreeSubsidiary(skin->vtxTable[i].buf[0]); + // #endregion + ZELDA_ARENA_FREE_DEBUG(skin->vtxTable[i].buf[0]); skin->vtxTable[i].buf[0] = NULL; } if (skin->vtxTable[i].buf[1] != NULL) { + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_FreeSubsidiary(skin->vtxTable[i].buf[1]); + // #endregion + ZELDA_ARENA_FREE_DEBUG(skin->vtxTable[i].buf[1]); skin->vtxTable[i].buf[1] = NULL; } } if (skin->vtxTable != NULL) { + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_FreeSubsidiary(skin->vtxTable); + // #endregion + ZELDA_ARENA_FREE_DEBUG(skin->vtxTable); } diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 7ffe8af7f43..751396c20d6 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -29,6 +29,7 @@ #include "soh/Enhancements/enhancementTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/Enhancements/randomizer/randomizer_grotto.h" +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" #include "soh/frame_interpolation.h" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" @@ -37,6 +38,7 @@ #include #include + // Some player animations are played at this reduced speed, for reasons yet unclear. // This is called "adjusted" for now. #define PLAYER_ANIM_ADJUSTED_SPEED (2.0f / 3.0f) @@ -10844,7 +10846,14 @@ void Player_Init(Actor* thisx, PlayState* play2) { // `giObjectSegment` is used for both "get item" objects and title cards. The maximum size for // get item objects is 0x2000 (see the assert in func_8083AE40), and the maximum size for // title cards is 0x1000 * LANGUAGE_MAX since each title card image includes all languages. - this->giObjectSegment = (void*)(((uintptr_t)ZELDA_ARENA_MALLOC_DEBUG(0x3008) + 8) & ~0xF); + + // #region SOH [Enhancement] - N64 Memory Model + { + void* giRaw = ZELDA_ARENA_MALLOC_DEBUG(0x3008); + N64Mem_AllocSubsidiary(giRaw, N64_SIZEOF_GI_OBJECT_SEGMENT); + this->giObjectSegment = (void*)((uintptr_t)giRaw + 8 & ~0xF); + } + // #endregion respawnFlag = gSaveContext.respawnFlag; From 50faa7c6dc818ad793921494e36f8fc23a31fdd7 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 7 May 2026 03:18:05 -0700 Subject: [PATCH 09/58] feat(restoration): ShadowArena via heap viewer --- .../N64MemoryModel/N64MemoryModel.cpp | 32 +++ .../N64MemoryModel/N64MemoryModel.hpp | 3 + .../N64MemoryModel/ShadowArena/shadow_arena.c | 24 +++ .../N64MemoryModel/ShadowArena/shadow_arena.h | 10 +- .../debugger/HeapViewerWindow.cpp | 191 ++++++++++++++---- 5 files changed, 217 insertions(+), 43 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 1e3393eec64..444057c0241 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -38,12 +38,39 @@ static u32 sAbsoluteSpaceShadow = SHADOW_NULL; // Maps real pointers (instances and subsidiaries) to their shadow offsets. static std::unordered_map sShadowMap; +// -------------------------------------------------------------------------------------------------------------------- +// Diagnostics +// -------------------------------------------------------------------------------------------------------------------- + +static void LogShadowState(const char* context) +{ + u32 maxFree = 0; + u32 totalFree = 0; + u32 totalAlloc = 0; + + ShadowArena_GetSizes(&sShadow, &maxFree, &totalFree, &totalAlloc); + SPDLOG_INFO("[N64MemoryModel] ({}): alloc=0x{:X}, free=0x{:X}, largest=0x{:X}", context, totalAlloc, totalFree, + maxFree); +} + // -------------------------------------------------------------------------------------------------------------------- // Lifecycle // -------------------------------------------------------------------------------------------------------------------- void N64Mem_Reset() { + // Log shadow state before teardown for per-scene diagnostics. + if (sIsActive && sShadow.buffer) + { + u32 maxFree = 0; + u32 totalFree = 0; + u32 totalAlloc = 0; + + ShadowArena_GetSizes(&sShadow, &maxFree, &totalFree, &totalAlloc); + SPDLOG_INFO("[N64MemoryModel] Teardown: alloc=0x{:X}, free=0x{:X}, largest=0x{:X}, ptrs={}", totalAlloc, + totalFree, maxFree, sShadowMap.size()); + } + // Tear down previous shadow state unconditionally -- the real ZeldaArena has already been reinitialized by // Play_Init. ShadowArena_Destroy(&sShadow); @@ -72,6 +99,11 @@ s32 N64Mem_IsActive() return sIsActive; } +ShadowArena* N64Mem_GetShadowArena() +{ + return &sShadow; +} + // -------------------------------------------------------------------------------------------------------------------- // Actor overlays // -------------------------------------------------------------------------------------------------------------------- diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index af0cb696492..029489f0164 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -24,6 +24,9 @@ void N64Mem_Reset(void); // Returns whether the N64 memory model is currently active. s32 N64Mem_IsActive(void); +// Returns the shadow arena pointer for debug visualization. Only valid when N64Mem_IsActive() is true. +struct ShadowArena* N64Mem_GetShadowArena(void); + // -------------------------------------------------------------------------------------------------------------------- // Actor overlays: Keyed by actor ID, shadow-only allocations. // diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.c index fbd91236320..39cc5c83110 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.c @@ -344,3 +344,27 @@ u32 ShadowArena_GetHead(ShadowArena* arena) { return arena->head; } + +s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) +{ + if (offset == SHADOW_NULL || offset + SHADOW_NODE_SIZE > arena->bufferSize) + { + return 0; + } + + ShadowNode* node = NodeAt(arena, offset); + if (node->magic != NODE_MAGIC) + { + return 0; + } + + *outIsFree = node->isFree; + *outSize = node->size; + *outNext = node->next != SHADOW_NULL && NodeIsValid(arena, node->next) ? node->next : SHADOW_NULL; + return 1; +} + +u32 ShadowArena_GetBufferSize(ShadowArena* arena) +{ + return arena->bufferSize; +} diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h index 1b413c27464..ec453b79959 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h @@ -4,6 +4,7 @@ #ifdef __cplusplus extern "C" { + #endif @@ -13,7 +14,7 @@ extern "C" { // Matches N64 retail ArenaNode: 0x10 bookkeeping + 0x20 debug fields. #define SHADOW_NODE_SIZE 0x30 -typedef struct shadow_arena +typedef struct ShadowArena { u8* buffer; u32 head; // Offset to first node @@ -41,6 +42,13 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 // Get the head node offset for external traversal (e.g., heap viewer). u32 ShadowArena_GetHead(ShadowArena* arena); +// Query a node's info by offset. Returns 1 on success, 0 if invalid. Used by the heap viewer to walk the shadow +// without exposing internals. +s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext); + +// Get the total buffer size. +u32 ShadowArena_GetBufferSize(ShadowArena* arena); + #ifdef __cplusplus } #endif diff --git a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp index bcd5a9b767e..443e8fb93f7 100644 --- a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp +++ b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp @@ -1,6 +1,7 @@ #include "HeapViewerWindow.hpp" #include +#include #include @@ -9,10 +10,16 @@ extern "C" { #include "z64.h" #include "functions.h" + +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h" + +extern ArenaNode* ZeldaArena_GetHead(); } struct BlockInfo { + u32 offset; // For shadow: offset into buffer. For ZeldaArena: not used. std::uintptr_t address; std::size_t size; bool isFree; @@ -28,6 +35,17 @@ static u32 sPreviousAlloc = 0; static u32 sCycleCount = 0; static s32 sLastDelta = 0; +// Shadow arena view state +static std::vector sShadowBlocks; +static u32 sShadowAllocTotal = 0; +static u32 sShadowFreeTotal = 0; +static u32 sShadowLargestFree = 0; +static u32 sShadowNodeCount = 0; +static u32 sShadowArenaSize = 0; +static u32 sShadowPreviousAlloc = 0; +static u32 sShadowCycleCount = 0; +static s32 sShadowLastDelta = 0; + static void CollectBlocks() { sBlocks.clear(); @@ -75,10 +93,71 @@ static void CollectBlocks() sLastDelta = static_cast(sAllocTotal) - static_cast(sPreviousAlloc); ++sCycleCount; } - sPreviousAlloc = sAllocTotal; } +static void CollectShadowBlocks() +{ + sShadowBlocks.clear(); + sShadowAllocTotal = 0; + sShadowFreeTotal = 0; + sShadowLargestFree = 0; + sShadowNodeCount = 0; + + ShadowArena* shadow = N64Mem_GetShadowArena(); + if (!shadow) + { + sShadowArenaSize = 0; + return; + } + + sShadowArenaSize = ShadowArena_GetBufferSize(shadow); + u32 offset = ShadowArena_GetHead(shadow); + + while (offset != SHADOW_NULL) + { + s32 isFree = 0; + u32 size = 0; + u32 next = SHADOW_NULL; + + if (!ShadowArena_GetNodeInfo(shadow, offset, &isFree, &size, &next)) + { + break; + } + + BlockInfo block; + block.offset = offset; + block.address = offset + SHADOW_NODE_SIZE; + block.size = size; + block.isFree = isFree != 0; + + sShadowBlocks.push_back(block); + sShadowNodeCount++; + + if (isFree) + { + sShadowFreeTotal += size; + if (size > sShadowLargestFree) + { + sShadowLargestFree = size; + } + } + else + { + sShadowAllocTotal += size; + } + + offset = next; + } + + if (sShadowPreviousAlloc != 0 && sShadowAllocTotal != sShadowPreviousAlloc) + { + sShadowLastDelta = static_cast(sShadowAllocTotal) - static_cast(sShadowPreviousAlloc); + ++sShadowCycleCount; + } + sShadowPreviousAlloc = sShadowAllocTotal; +} + static ImU32 ColorAlloc() { return IM_COL32(200, 60, 60, 255); @@ -99,35 +178,35 @@ static ImU32 ColorBorder() return IM_COL32(40, 40, 40, 255); } -void HeapViewerWindow::DrawElement() +static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u32 allocTotal, + u32 freeTotal, u32 largestFree, u32 nodeCount, u32 cycleCount, + s32 lastDelta, u32 nodeSize, const char* id) { - CollectBlocks(); - - if (sBlocks.empty()) + if (blocks.empty()) { - ImGui::Text("ZeldaArena is not initialized."); + ImGui::Text("Arena is not initialized."); return; } - // --- Stats ---- - const f32 fragPercent = sFreeTotal > 0 - ? (1.0f - static_cast(sLargestFree) / static_cast(sFreeTotal)) * 100.0f + // --- Stats --- + const f32 fragPercent = freeTotal > 0 + ? (1.0f - static_cast(largestFree) / static_cast(freeTotal)) * 100.0f : 0.0f; - ImGui::Text("Arena: 0x%X (%u KB) | Nodes: %u", sArenaSize, sArenaSize / 1024, sNodeCount); + ImGui::Text("Arena: 0x%X (%u KB) | Nodes: %u", arenaSize, arenaSize / 1024, nodeCount); ImGui::Text("Alloc: 0x%X (%u KB) | Free: 0x%X (%u KB) | Largest: 0x%X (%u KB)", - sAllocTotal, sAllocTotal / 1024, - sFreeTotal, sFreeTotal / 1024, - sLargestFree, sLargestFree / 1024); + allocTotal, allocTotal / 1024, + freeTotal, freeTotal / 1024, + largestFree, largestFree / 1024); ImGui::Text("Fragmentation: %.1f%%", fragPercent); ImGui::Text("Cycle: %u | Last delta: %s0x%X (%d bytes)", - sCycleCount, - sLastDelta >= 0 ? "+" : "-", - static_cast(std::abs(sLastDelta)), - sLastDelta); + cycleCount, + lastDelta >= 0 ? "+" : "-", + static_cast(std::abs(lastDelta)), + lastDelta); // --- Utilization bar --- - const f32 utilization = sArenaSize > 0 ? static_cast(sAllocTotal) / static_cast(sArenaSize) : 0.0f; + const f32 utilization = arenaSize > 0 ? static_cast(allocTotal) / static_cast(arenaSize) : 0.0f; ImGui::ProgressBar(utilization, ImVec2(-1, 0), fmt::format("{:.1f}% utilized", utilization * 100.0f).c_str()); @@ -142,49 +221,39 @@ void HeapViewerWindow::DrawElement() const f32 availWidth = ImGui::GetContentRegionAvail().x; const ImVec2 mapPos = ImGui::GetCursorScreenPos(); - // Reserve space for the map ImGui::Dummy(ImVec2(availWidth, mapHeight)); ImDrawList* drawList = ImGui::GetWindowDrawList(); - - // Background drawList->AddRectFilled(mapPos, ImVec2(mapPos.x + availWidth, mapPos.y + mapHeight), ColorBorder()); - // Draw blocks proportionally f32 xCursor = 0.0f; s32 hoveredBlock = -1; - for (std::size_t i = 0; i < sBlocks.size(); ++i) + for (std::size_t i = 0; i < blocks.size(); ++i) { - // Node header - f32 nodeWidth = static_cast(sizeof(ArenaNode)) / static_cast(sArenaSize) * availWidth; - // Block data - f32 blockWidth = static_cast(sBlocks[i].size) / static_cast(sArenaSize) * availWidth; + f32 nodeWidth = static_cast(nodeSize) / static_cast(arenaSize) * availWidth; + f32 blockWidth = static_cast(blocks[i].size) / static_cast(arenaSize) * availWidth; - // Ensure minimum 1px visibility for non-zero blocks if (nodeWidth < 1.0f) { nodeWidth = 1.0f; } - if (blockWidth < 1.0f && sBlocks[i].size > 0) + if (blockWidth < 1.0f && blocks[i].size > 0) { blockWidth = 1.0f; } - // Node header segment (dark gray) f32 x0 = mapPos.x + xCursor; f32 x1 = x0 + nodeWidth; drawList->AddRectFilled(ImVec2(x0, mapPos.y), ImVec2(x1, mapPos.y + mapHeight), ColorNode()); xCursor += nodeWidth; - // Block data segment x0 = mapPos.x + xCursor; x1 = x0 + blockWidth; - const ImU32 color = sBlocks[i].isFree ? ColorFree() : ColorAlloc(); + const ImU32 color = blocks[i].isFree ? ColorFree() : ColorAlloc(); drawList->AddRectFilled(ImVec2(x0, mapPos.y), ImVec2(x1, mapPos.y + mapHeight), color); - // Hover detection over the full block (node + data) const f32 fullX0 = mapPos.x + xCursor - nodeWidth; ImVec2 blockMin(fullX0, mapPos.y); if (ImVec2 blockMax(x1, mapPos.y + mapHeight); ImGui::IsMouseHoveringRect(blockMin, blockMax)) @@ -195,13 +264,12 @@ void HeapViewerWindow::DrawElement() xCursor += blockWidth; } - // Tooltip for hovered block if (hoveredBlock >= 0) { - const auto& [address, size, isFree] = sBlocks[hoveredBlock]; + const auto& [offset, address, size, isFree] = blocks[hoveredBlock]; ImGui::BeginTooltip(); ImGui::Text("Block %d: %s", hoveredBlock, isFree ? "FREE" : "ALLOCATED"); - ImGui::Text("Address: 0x%llX", static_cast(address)); + ImGui::Text("Offset: 0x%X", static_cast(address)); ImGui::Text("Size: 0x%X (%u bytes)", static_cast(size), static_cast(size)); ImGui::EndTooltip(); } @@ -211,23 +279,24 @@ void HeapViewerWindow::DrawElement() ImGui::Spacing(); // --- Block list --- - ImGui::Text("Block List (%u blocks)", sNodeCount); + ImGui::Text("Block List (%u blocks)", nodeCount); - if (ImGui::BeginTable("##blocks", 4, + const std::string tableId = fmt::format("##blocks_{}", id); + if (ImGui::BeginTable(tableId.c_str(), 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable, ImVec2(0, ImGui::GetContentRegionAvail().y))) { ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, 40.0f); ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 80.0f); - ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 140.0f); + ImGui::TableSetupColumn("Offset", ImGuiTableColumnFlags_WidthFixed, 140.0f); ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); - for (std::size_t i = 0; i < sBlocks.size(); ++i) + for (std::size_t i = 0; i < blocks.size(); ++i) { - const auto& [address, size, isFree] = sBlocks[i]; + const auto& [offset, address, size, isFree] = blocks[i]; ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -244,7 +313,7 @@ void HeapViewerWindow::DrawElement() } ImGui::TableNextColumn(); - ImGui::Text("0x%llX", static_cast(address)); + ImGui::Text("0x%X", static_cast(address)); ImGui::TableNextColumn(); ImGui::Text("0x%X (%u)", static_cast(size), static_cast(size)); @@ -253,3 +322,41 @@ void HeapViewerWindow::DrawElement() ImGui::EndTable(); } } + +void HeapViewerWindow::DrawElement() +{ + if (ImGui::BeginTabBar("##heap_tabs")) + { + if (ImGui::BeginTabItem("ZeldaArena")) + { + CollectBlocks(); + DrawArenaView(sBlocks, sArenaSize, sAllocTotal, sFreeTotal, sLargestFree, + sNodeCount, sCycleCount, sLastDelta, + sizeof(ArenaNode), "zelda"); + ImGui::EndTabItem(); + } + + if (N64Mem_IsActive()) + { + if (ImGui::BeginTabItem("ShadowArena (N64)")) + { + CollectShadowBlocks(); + + if (ImGui::Button("Reset Tracking")) + { + sShadowPreviousAlloc = 0; + sShadowCycleCount = 0; + sShadowLastDelta = 0; + } + + DrawArenaView(sShadowBlocks, sShadowArenaSize, sShadowAllocTotal, + sShadowFreeTotal, sShadowLargestFree, sShadowNodeCount, + sShadowCycleCount, sShadowLastDelta, + SHADOW_NODE_SIZE, "shadow"); + ImGui::EndTabItem(); + } + } + + ImGui::EndTabBar(); + } +} From ec2e46851b6860d11c0a61b75d16f4a790beb9d3 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 7 May 2026 06:27:01 -0700 Subject: [PATCH 10/58] feat(restoration): Dynamic arena sizing via THA remainder --- .../N64MemoryModel/N64MemoryModel.cpp | 23 +++++++--- .../N64MemoryModel/N64MemoryModel.hpp | 11 +++-- .../N64MemoryModel/ShadowArena/shadow_arena.h | 3 +- soh/src/code/z_play.c | 22 ++++++--- soh/src/code/z_skelanime.c | 45 +++++++++++++++++++ 5 files changed, 87 insertions(+), 17 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 444057c0241..bd0c9706e21 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -15,16 +15,13 @@ extern "C" { #define CVAR_DEFAULT 0 #define CVAR_VALUE CVarGetInteger(CVAR_NAME, CVAR_DEFAULT) -// N64 ZeldaArena size: THA remainder on retail NTSC 1.2. Measured via IS-Viewer on decomp build with ISV ungated, -// debug features off. -#define N64_ZELDA_ARENA_SIZE 0x3D550 - // -------------------------------------------------------------------------------------------------------------------- // State // -------------------------------------------------------------------------------------------------------------------- static s32 sIsActive = 0; static ShadowArena sShadow; +static u32 sSohThaRemainder = 0; // Shadow offsets for actor overlays, keyed by actor ID. SHADOW_NULL means no shadow allocation exists for that type. static u32 sOverlayShadows[ACTOR_ID_MAX]; @@ -57,7 +54,12 @@ static void LogShadowState(const char* context) // Lifecycle // -------------------------------------------------------------------------------------------------------------------- -void N64Mem_Reset() +void N64Mem_StoreThaRemainder(u32 sohRemainder) +{ + sSohThaRemainder = sohRemainder; +} + +void N64Mem_Reset(PlayState* play) { // Log shadow state before teardown for per-scene diagnostics. if (sIsActive && sShadow.buffer) @@ -88,9 +90,16 @@ void N64Mem_Reset() } sIsActive = CVAR_VALUE; - if (sIsActive) + if (sIsActive && play != nullptr) { - ShadowArena_Init(&sShadow, N64_ZELDA_ARENA_SIZE); + // Compute N64-equivalent arena size: SoH's THA remainder minus THA consumers that SoH bypasses, but N64 + // performs. + u32 n64SceneFileSize = play->loadedScene->sceneFile.vromEnd - play->loadedScene->sceneFile.vromStart; + u32 shadowArenaSize = sSohThaRemainder - n64SceneFileSize; + + SPDLOG_INFO("[N64MemoryModel] SoH THA remainder=0x{:X}, scene file size=0x{:X}, shadow arena=0x{:X}", + sSohThaRemainder, n64SceneFileSize, shadowArenaSize); + ShadowArena_Init(&sShadow, shadowArenaSize); } } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index 029489f0164..b3c58705011 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -17,9 +17,14 @@ extern "C" { // Lifecycle // -------------------------------------------------------------------------------------------------------------------- -// Reset shadow state and reread CVar. Call from Play_Init after ZeldaArena reinitialization -- the shadow arena -// reinitializes in lockstep with the real one. -void N64Mem_Reset(void); +// Store SoH's THA remainder before ZeldaArena_Init consumes it. Call from Play_Init immediately after +// THA_GetRemaining. +void N64Mem_StoreThaRemainder(u32 sohRemainder); + +// Reset shadow state, compute N64-equivalent arena size from stored THA remainder minus N64-specific consumers +// (room buffers, etc.), and reread CVar. Call from Play_Init after ZeldaArena_Init. +struct PlayState; +void N64Mem_Reset(PlayState* play); // Returns whether the N64 memory model is currently active. s32 N64Mem_IsActive(void); diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h index ec453b79959..e1cc389fb3f 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h @@ -4,7 +4,6 @@ #ifdef __cplusplus extern "C" { - #endif @@ -37,7 +36,7 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size); void ShadowArena_Free(ShadowArena* arena, u32 dataOffset); // Query arena statistics. -void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc); +void ShadowArena_GetSizes(ShadowArena * arena, u32 * outMaxFree, u32 * outFree, u32 * outAlloc); // Get the head node offset for external traversal (e.g., heap viewer). u32 ShadowArena_GetHead(ShadowArena* arena); diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index d46a514591d..a6db893fa9c 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -404,10 +404,17 @@ void Play_Init(GameState* thisx) { SystemArena_Display(); - // OTRTODO allocate double the normal amount of memory - // This is to avoid some parts of the game, like loading actors, causing OoM - // This is potionally unavoidable due to struct size differences, but is x2 the right amount? - GameState_Realloc(&play->state, 0x1D4790 * 2); + // #region SOH [Enhancement] - N64 Memory Model + if (CVarGetInteger(CVAR_ENHANCEMENT("N64MemoryModel"), 0)) { + GameState_Realloc(&play->state, 0x1D4790); + } else { + // OTRTODO allocate double the normal amount of memory + // This is to avoid some parts of the game, like loading actors, causing OoM + // This is potionally unavoidable due to struct size differences, but is x2 the right amount? + GameState_Realloc(&play->state, 0x1D4790 * 2); + } + // #endregion + KaleidoManager_Init(play); View_Init(&play->view, gfxCtx); Audio_SetExtraFilter(0); @@ -570,6 +577,11 @@ void Play_Init(GameState* thisx) { osSyncPrintf("ZELDA ALLOC SIZE=%x\n", THA_GetSize(&play->state.tha)); zAllocSize = THA_GetSize(&play->state.tha); + + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_StoreThaRemainder(zAllocSize); + // #endregion + zAlloc = (uintptr_t)GAMESTATE_ALLOC_MC(&play->state, zAllocSize); zAllocAligned = (zAlloc + 8) & ~0xF; ZeldaArena_Init((void*)zAllocAligned, zAllocSize - (zAllocAligned - zAlloc)); @@ -578,7 +590,7 @@ void Play_Init(GameState* thisx) { (u8*)zAllocAligned + zAllocSize - (s32)(zAllocAligned - zAlloc)); // #region SOH [Enhancement] - N64 Memory Model - N64Mem_Reset(); + N64Mem_Reset(play); // #endregion Fault_AddClient(&D_801614B8, ZeldaArena_Display, NULL, NULL); diff --git a/soh/src/code/z_skelanime.c b/soh/src/code/z_skelanime.c index 3449c2ed91a..a8fb1d5aa38 100644 --- a/soh/src/code/z_skelanime.c +++ b/soh/src/code/z_skelanime.c @@ -5,6 +5,7 @@ #include #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" #define ANIM_INTERP 1 @@ -1128,7 +1129,16 @@ void SkelAnime_InitLink(PlayState* play, SkelAnime* skelAnime, FlexSkeletonHeade if (jointTable == NULL) { skelAnime->jointTable = ZELDA_ARENA_MALLOC_DEBUG(allocSize); + + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_AllocSubsidiary(skelAnime->jointTable, allocSize); + // #endregion + skelAnime->morphTable = ZELDA_ARENA_MALLOC_DEBUG(allocSize); + + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_AllocSubsidiary(skelAnime->morphTable, allocSize); + // #endregion } else { assert(limbBufCount == limbCount); @@ -1457,7 +1467,16 @@ s32 SkelAnime_Init(PlayState* play, SkelAnime* skelAnime, SkeletonHeader* skelet skelAnime->skeleton = SEGMENTED_TO_VIRTUAL(skeletonHeader->segment); if (jointTable == NULL) { skelAnime->jointTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->jointTable)); + + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_AllocSubsidiary(skelAnime->jointTable, skelAnime->limbCount * sizeof(*skelAnime->jointTable)); + // #endregion + skelAnime->morphTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->morphTable)); + + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_AllocSubsidiary(skelAnime->morphTable, skelAnime->limbCount * sizeof(*skelAnime->morphTable)); + // #endregion } else { assert(limbCount == skelAnime->limbCount); skelAnime->jointTable = jointTable; @@ -1492,7 +1511,15 @@ s32 SkelAnime_InitFlex(PlayState* play, SkelAnime* skelAnime, FlexSkeletonHeader if (jointTable == NULL) { skelAnime->jointTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->jointTable)); + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_AllocSubsidiary(skelAnime->jointTable, skelAnime->limbCount * sizeof(*skelAnime->jointTable)); + // #endregion + skelAnime->morphTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->morphTable)); + + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_AllocSubsidiary(skelAnime->morphTable, skelAnime->limbCount * sizeof(*skelAnime->morphTable)); + // #endregion } else { assert(limbCount == skelAnime->limbCount); skelAnime->jointTable = jointTable; @@ -1524,7 +1551,17 @@ s32 SkelAnime_InitSkin(PlayState* play, SkelAnime* skelAnime, SkeletonHeader* sk skelAnime->limbCount = skeletonHeader->limbCount + 1; skelAnime->skeleton = SEGMENTED_TO_VIRTUAL(skeletonHeader->segment); skelAnime->jointTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->jointTable)); + + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_AllocSubsidiary(skelAnime->jointTable, skelAnime->limbCount * sizeof(*skelAnime->jointTable)); + // #endregion + skelAnime->morphTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->morphTable)); + + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_AllocSubsidiary(skelAnime->morphTable, skelAnime->limbCount * sizeof(*skelAnime->morphTable)); + // #endregion + if ((skelAnime->jointTable == NULL) || (skelAnime->morphTable == NULL)) { osSyncPrintf(VT_FGCOL(RED)); // "Memory allocation error" @@ -1927,12 +1964,20 @@ s32 Animation_OnFrame(SkelAnime* skelAnime, f32 frame) { */ void SkelAnime_Free(SkelAnime* skelAnime, PlayState* play) { if (skelAnime->jointTable != NULL) { + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_FreeSubsidiary(skelAnime->jointTable); + // #endregion + ZELDA_ARENA_FREE_DEBUG(skelAnime->jointTable); } else { osSyncPrintf("now_joint あきまへん!!\n"); // "now_joint is freed! !" } if (skelAnime->morphTable != NULL) { + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_FreeSubsidiary(skelAnime->morphTable); + // #endregion + ZELDA_ARENA_FREE_DEBUG(skelAnime->morphTable); } else { osSyncPrintf("morf_joint あきまへん!!\n"); // "morf_joint is freed !!" From 580adb36c176f33c895a1fa4d08ea8074cfe4f75 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 7 May 2026 18:25:26 -0700 Subject: [PATCH 11/58] feat(restoration): NTSC 1.2 arena sizing --- .../N64MemoryModel/N64MemoryModel.cpp | 20 +- .../ShadowArena/actor_overlay_sizes.c | 2 + .../ShadowArena/actor_overlay_sizes.h | 2 +- .../N64MemoryModel/ShadowArena/arena_sizing.c | 291 ++++++++++++++++++ .../N64MemoryModel/ShadowArena/arena_sizing.h | 44 +++ .../ShadowArena/effect_overlay_sizes.c | 2 + .../ShadowArena/effect_overlay_sizes.h | 2 +- .../ShadowArena/instance_sizes.c | 2 + .../ShadowArena/instance_sizes.h | 2 +- 9 files changed, 356 insertions(+), 11 deletions(-) create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index bd0c9706e21..aa7be546523 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -6,6 +6,7 @@ extern "C" { #include "N64MemoryModel.hpp" #include "ShadowArena/shadow_arena.h" +#include "ShadowArena/arena_sizing.h" #include "ShadowArena/actor_overlay_sizes.h" #include "ShadowArena/effect_overlay_sizes.h" #include "ShadowArena/instance_sizes.h" @@ -92,13 +93,17 @@ void N64Mem_Reset(PlayState* play) sIsActive = CVAR_VALUE; if (sIsActive && play != nullptr) { - // Compute N64-equivalent arena size: SoH's THA remainder minus THA consumers that SoH bypasses, but N64 - // performs. - u32 n64SceneFileSize = play->loadedScene->sceneFile.vromEnd - play->loadedScene->sceneFile.vromStart; - u32 shadowArenaSize = sSohThaRemainder - n64SceneFileSize; + // Compute N64-equivalent arena size from first principles. + // #TODO: Select version constants based on detected ROM version. + u32 shadowArenaSize = ArenaSizing_ComputeN64ArenaSize(play, &gVersionConstantsNtsc12); + if (shadowArenaSize == 0) + { + SPDLOG_ERROR("[N64MemoryModel] Arena sizing returned 0 -- THA budget exceeded, disabling."); + sIsActive = 0; + return; + } - SPDLOG_INFO("[N64MemoryModel] SoH THA remainder=0x{:X}, scene file size=0x{:X}, shadow arena=0x{:X}", - sSohThaRemainder, n64SceneFileSize, shadowArenaSize); + SPDLOG_INFO("[N64MemoryModel] Shadow arena size=0x{:X} for scene 0x{:X}", shadowArenaSize, play->sceneNum); ShadowArena_Init(&sShadow, shadowArenaSize); } } @@ -170,8 +175,7 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) if (shadow == SHADOW_NULL) { - SPDLOG_ERROR("[N64MemoryModel] Shadow overlay failed for actor 0x{:04X} (need 0x{:X})", - static_cast(actorId), overlaySize); + SPDLOG_ERROR("[N64MemoryModel] Shadow overlay failed for actor 0x{:04X} (need 0x{:X})", actorId, overlaySize); return 0; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c index 69a0d3ed51d..979d6011467 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c @@ -1,5 +1,7 @@ #include "actor_overlay_sizes.h" +#include "global.h" + const u32 gN64ActorOverlaySizes[ACTOR_ID_MAX] = { [ACTOR_EN_TEST] = 0x58B0, [ACTOR_EN_GIRLA] = 0x2920, diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h index 386758b6bb5..2395c8c3a31 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h @@ -1,5 +1,5 @@ #pragma once -#include "global.h" +#include extern const u32 gN64ActorOverlaySizes[]; \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c new file mode 100644 index 00000000000..22d9f921cbe --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -0,0 +1,291 @@ +#include "arena_sizing.h" + +#include "global.h" + +// Declared in z_bgcheck.c but not exposed via header. +s32 BgCheck_IsSpotScene(PlayState* play); +s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); + +// -------------------------------------------------------------------------------------------------------------------- +// N64 THA budget +// +// GameState_Realloc is called with this value in Play_Init when the memory model CVar is active. This is the total +// pool from which every THA consumer draws; whatever remains becomes ZeldaArena. +// -------------------------------------------------------------------------------------------------------------------- + +#define N64_THA_BUDGET 0x1D4790 + +// -------------------------------------------------------------------------------------------------------------------- +// N64 struct sizes (32-bit, from decomp headers and linker map) +// -------------------------------------------------------------------------------------------------------------------- + +#define N64_SIZEOF_CHAR_PTR 4 // sizeof(char*) on N64 MIPS +#define N64_SIZEOF_COLLISION_CONTEXT 0x1464 // CollisionContext size = 0x1464 (from decomp header comment) + +// Struct sizes that are identical on N64 and SoH (no pointer members): +// sizeof(MtxF) = 0x40 +// sizeof(GFx) = 0x08 +// sizeof(Vtx) = 0x10 +// sizeof(Vec3s) = 0x06 +// sizeof(SSNode) = 0x04 +// sizeof(CollisionPoly) = 0x10 +// sizeof(StaticLookup) = 0x06 + +// -------------------------------------------------------------------------------------------------------------------- +// Fixed THA consumers (same value on every N64 scene) +// -------------------------------------------------------------------------------------------------------------------- + +#define N64_MATRIX_STACK_SIZE (20 * 0x40) // sys_matrix.c: 20 * sizeof(MtxF) +#define N64_TEXT_BOX_SIZE 0x2200 // Message_Init: Constant +#define N64_DO_ACTION_SIZE (3 * N64_SIZEOF_CHAR_PTR) // z_construct.c: 3 * sizeof(char*) +#define N64_ICON_ITEM_SIZE (0x1000 * 4) // z_construct.c: 0x1000 * 4 (buttonItems on N64) +#define N64_MAP_SEGMENT_SIZE (2 * N64_SIZEOF_CHAR_PTR) // z_map_exp.c: 2 * sizeof(char*) + +// -------------------------------------------------------------------------------------------------------------------- +// Skybox N64-unique THA consumers +// +// On N64, skybox textures and palettes are DMA'd into THA-allocated staticSegment buffers. SoH loads from OTR via the +// ResourceManager, never touching THA. +// +// SKYBOX_NORMAL_SKY: 2 texture banks * 0xC000 * 2 palettes * 0x100 +// Indoor skyboxes: Varying sizes per skybox type +// SKYBOX_NONE: 0 +// +// #TODO: Other skybox types need the same treatment from decomp map's vr_*_static segments. +// -------------------------------------------------------------------------------------------------------------------- + +static u32 GetN64SkyboxTextureSize(s16 skyboxId) +{ + switch (skyboxId) + { + case SKYBOX_NORMAL_SKY: + // Fall-through + case SKYBOX_OVERCAST_SUNSET: + return 2 * 0xC000 + 2 * 0x100; + + case SKYBOX_NONE: + return 0; + + default: + // Indoor skyboxes: Conservative estimate. + // #TODO: Populate exact sizes per skybox ID from decomp map. + return 3 * 0x8000 + 3 * 0x200; + } +} + +// -------------------------------------------------------------------------------------------------------------------- +// Skybox dListBuf and roomVtx (shared -- same size on N64 and SoH, no pointer members) +// -------------------------------------------------------------------------------------------------------------------- + +static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) +{ + if (skyboxId == SKYBOX_NONE) + { + *outDlistSize = 0; + *outVtxSize = 0; + return; + } + + // unk_140 != 0 means indoor/single-room skybox. + // #TODO: Determine unk_140 from ID more precisely. + const bool isIndoor = skyboxId != SKYBOX_NORMAL_SKY && skyboxId != SKYBOX_OVERCAST_SUNSET && skyboxId != + SKYBOX_CUTSCENE_MAP; + if (isIndoor) + { + *outDlistSize = 8 * 150 * sizeof(Gfx); + *outVtxSize = 256 * sizeof(Vtx); + } + else if (skyboxId == SKYBOX_CUTSCENE_MAP) + { + *outDlistSize = 12 * 150 * sizeof(Gfx); + *outVtxSize = 192 * sizeof(Vtx); + } + else + { + *outDlistSize = 12 * 150 * sizeof(Gfx); + *outVtxSize = 160 * sizeof(Vtx); + } +} + +// -------------------------------------------------------------------------------------------------------------------- +// BgCheck THA Total +// +// The BgCheck system allocates 6 items from THA: lookupTbl, SSNode tbl, polyCheckTbl, dyna polyList, dyna vtxList, +// dyna polyNodes. +// +// By algebra, the total simplifies to: bgcheck_memSize - sizeof(CollisionContext) +// +// This is because the tblMax formula (z_bgcheck.c:1628) is defined as: +// tblMax = (memSize - overhead) / sizeof(SSNode) +// +// where overhead includes all the other consumers. When you sum all 6 THA allocations, every term cancels except +// bgcheck_memSize and sizeof(CollisionContext). +// -------------------------------------------------------------------------------------------------------------------- + +static u32 GetBgCheckMemSize(PlayState* play) +{ + const s16 sceneNum = play->sceneNum; + + if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) + { + return sceneNum == SCENE_STABLE ? 0x3520 : 0x4E20; + } + + if (BgCheck_IsSpotScene(play)) + { + return 0xF000; + } + + u32 customMemSize = 0; + if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) + { + return customMemSize; + } + + return 0x1CC00; +} + +static u32 GetBgCheckThaTotal(PlayState* play) +{ + return GetBgCheckMemSize(play) - N64_SIZEOF_COLLISION_CONTEXT; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Object bank size (mirrors z_scene.c Object_InitBlank) +// -------------------------------------------------------------------------------------------------------------------- + +static u32 GetObjectBankSize(PlayState* play) +{ + const s16 sceneNum = play->sceneNum; + if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) + { + return 1177600; + } + + if (sceneNum == SCENE_SPIRIT_TEMPLE_BOSS || sceneNum == SCENE_CHAMBER_OF_THE_SAGES || + sceneNum == SCENE_GANONDORF_BOSS) + { + return 1075200; + } + + return 1024000; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Elf message size +// +// On N64, loaded via Play_LoadFile into THA. SoH loads from OTR via ResourceManager. Only present if the scene's +// SpecialFiles command has cUpElfMsgNum != 0. +// -------------------------------------------------------------------------------------------------------------------- + +static u32 GetElfMessageSize(PlayState* play) +{ + if (play->cUpElfMsgs != NULL) + { + // elf_message_field is 0x70, elf_message_ydan is 0x10 on NTSC 1.2. + // #TODO: Per-version data. + return 0x80; + } + + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Room buffer max size (mirrors func_80096FE8 logic) +// -------------------------------------------------------------------------------------------------------------------- + +static u32 GetMaxRoomSize(PlayState* play) +{ + u32 maxRoomSize = 0; + + for (size_t i = 0; i < play->numRooms; ++i) + { + const u32 roomSize = play->roomList[i].vromEnd - play->roomList[i].vromStart; + if (roomSize > maxRoomSize) + { + maxRoomSize = roomSize; + } + } + + if (play->transiActorCtx.numActors != 0) + { + const TransitionActorEntry* transitionActor = &play->transiActorCtx.list[0]; + + for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) + { + const s8 frontRoom = transitionActor->sides[0].room; + const s8 backRoom = transitionActor->sides[1].room; + const size_t frontSize = frontRoom < 0 + ? 0 + : play->roomList[frontRoom].vromEnd - play->roomList[frontRoom].vromStart; + const u32 backSize = backRoom < 0 + ? 0 + : play->roomList[backRoom].vromEnd - play->roomList[backRoom].vromStart; + + const u32 cumulSize = frontRoom != backRoom ? frontSize + backSize : frontSize; + if (cumulSize > maxRoomSize) + { + maxRoomSize = cumulSize; + } + + transitionActor++; + } + } + + return maxRoomSize; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Main computation +// -------------------------------------------------------------------------------------------------------------------- + +const VersionConstants gVersionConstantsNtsc12 = { + 0x26740, // kaleidoOverlayVramSize: max(kaleido_scope = 0x1CA00, player_actor = 0x26740) + 0x3B00, // parameterStaticSize + 0x60, // effectSsSize: N64 sizeof(EffectSs) +}; + +u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) +{ + u32 total = 0; + + // Per-version constants (N64-unique THA consumers SoH skips) + total += vc->kaleidoOverlayVramSize; + total += vc->parameterStaticSize; + + // Fixed consumers + total += N64_MATRIX_STACK_SIZE; + total += 0x55 * vc->effectSsSize; + total += N64_TEXT_BOX_SIZE; + total += N64_DO_ACTION_SIZE; + total += N64_ICON_ITEM_SIZE; + total += N64_MAP_SEGMENT_SIZE; + + // Scene-dependent consumers + total += GetObjectBankSize(play); + total += play->loadedScene->sceneFile.vromEnd - play->loadedScene->sceneFile.vromStart; + total += GetMaxRoomSize(play); + + // Skybox + { + u32 dListSize = 0; + u32 vtxSize = 0; + GetSkyboxDlistAndVtxSize(play->skyboxId, &dListSize, &vtxSize); + total += dListSize; + total += vtxSize; + total += GetN64SkyboxTextureSize(play->skyboxId); + } + + // BgCheck + total += GetBgCheckThaTotal(play); + + // Elf message + total += GetElfMessageSize(play); + + if (total >= N64_THA_BUDGET) + { + return 0; + } + + return N64_THA_BUDGET - total; +} diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h new file mode 100644 index 00000000000..9080dfee859 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h @@ -0,0 +1,44 @@ +#pragma once + +#include "z64.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// Per version constants that cannot be derived at SoH runtime. +// +// These come from the decomp linker map or DMA table for a given ROM version. SoH zeroes out or bypasses the code +// paths that would expose them (e.g, kaleido overlays compiled in, skybox textures loaded from OTR, scene files loaded +// via ResourceManager instead of THA, etc.) +// +// #TODO: For multi-version support, generate one of these per supported ROM version. +// -------------------------------------------------------------------------------------------------------------------- + +typedef struct +{ + u32 kaleidoOverlayVramSize; // max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) + u32 parameterStaticSize; // _parameter_staticSegmentRomEnd - RomStart + u32 effectSsSize; // N64 sizeof(EffectSs) -- 0x60 on N64, larger on SoH +} VersionConstants; + + +// Hardcoded version constants for NTSC 1.2. +extern const VersionConstants gVersionConstantsNtsc12; + +// -------------------------------------------------------------------------------------------------------------------- +// Arena size computation +// +// Computes the N64 ZeldaArena size from first principles: +// arena = THA_BUDGET - sum(all THA consumers at N64 sizes) +// +// Called from N64Mem_Reset after Play_Init has populated scene data. Returns 0 if THA consumption exceeds budget +// (should not happen on valid scenes). +// -------------------------------------------------------------------------------------------------------------------- + +u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc); + +#ifdef __cplusplus +} +#endif diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c index 9fdae60ea29..edcabbbe4e8 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c @@ -1,5 +1,7 @@ #include "effect_overlay_sizes.h" +#include "global.h" + const u32 gN64EffectOverlaySizes[EFFECT_SS_TYPE_MAX] = { [EFFECT_SS_DUST] = 0x830, [EFFECT_SS_KIRAKIRA] = 0x670, diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h index d9b803ac0dd..d6b696c6d37 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h @@ -1,5 +1,5 @@ #pragma once -#include "global.h" +#include extern const u32 gN64EffectOverlaySizes[]; \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c index 1b5ba1a6a04..88d053b1568 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c @@ -1,5 +1,7 @@ #include "instance_sizes.h" +#include "global.h" + const u32 gN64InstanceSizes[ACTOR_ID_MAX] = { [ACTOR_PLAYER] = 0xA94, [ACTOR_EN_TEST] = 0x0928, diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h index 4c57e5c4daa..a94a23972d3 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h @@ -1,5 +1,5 @@ #pragma once -#include "global.h" +#include extern const u32 gN64InstanceSizes[]; \ No newline at end of file From 4124f948744cdb282dc542cac7f2fd5573b6b20d Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 7 May 2026 19:46:49 -0700 Subject: [PATCH 12/58] feat(restoration): Exactly 16 unloads for Sun's Song grave --- .../N64MemoryModel/ShadowArena/arena_sizing.c | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c index 22d9f921cbe..874e5c23de4 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -21,6 +21,8 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); #define N64_SIZEOF_CHAR_PTR 4 // sizeof(char*) on N64 MIPS #define N64_SIZEOF_COLLISION_CONTEXT 0x1464 // CollisionContext size = 0x1464 (from decomp header comment) +#define N64_SIZEOF_GFX 8 // sizeof(Gfx) on N64: Two u32 words -- SoH is 16 +#define N64_SIZEOF_VTX 0x10 // sizeof(Vtx) on N64: Same on both platforms // Struct sizes that are identical on N64 and SoH (no pointer members): // sizeof(MtxF) = 0x40 @@ -92,18 +94,18 @@ static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVt SKYBOX_CUTSCENE_MAP; if (isIndoor) { - *outDlistSize = 8 * 150 * sizeof(Gfx); - *outVtxSize = 256 * sizeof(Vtx); + *outDlistSize = 8 * 150 * N64_SIZEOF_GFX; + *outVtxSize = 256 * N64_SIZEOF_VTX; } else if (skyboxId == SKYBOX_CUTSCENE_MAP) { - *outDlistSize = 12 * 150 * sizeof(Gfx); - *outVtxSize = 192 * sizeof(Vtx); + *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; + *outVtxSize = 192 * N64_SIZEOF_VTX; } else { - *outDlistSize = 12 * 150 * sizeof(Gfx); - *outVtxSize = 160 * sizeof(Vtx); + *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; + *outVtxSize = 160 * N64_SIZEOF_VTX; } } @@ -261,31 +263,57 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) total += N64_ICON_ITEM_SIZE; total += N64_MAP_SEGMENT_SIZE; + + const u32 fixed = total; + LUSLOG_INFO("[ArenaSizing] fixed=0x%X (kaleido=0x%X, param=0x%X)", fixed, vc->kaleidoOverlayVramSize, + vc->parameterStaticSize); + // Scene-dependent consumers - total += GetObjectBankSize(play); - total += play->loadedScene->sceneFile.vromEnd - play->loadedScene->sceneFile.vromStart; - total += GetMaxRoomSize(play); + { + const u32 objBank = GetObjectBankSize(play); + // HACK: vromStart/vromEnd are zeroed in SoH's scene table (OTR filenames replace ROM addresses). + // Hardcoded for graveyard validation only. ZAPDTR exporter replaces this. + const u32 sceneFile = play->sceneNum == SCENE_GRAVEYARD ? 0xBC80 : 0; + const u32 roomBuf = GetMaxRoomSize(play); + total += objBank; + total += sceneFile; + total += roomBuf; + LUSLOG_INFO("[ArenaSizing] objBank=0x%X, sceneFile=0x%X, roomBuf=0x%x", (u32)objBank, (u32)sceneFile, + (u32)roomBuf); + } // Skybox { u32 dListSize = 0; u32 vtxSize = 0; + u32 texSize = 0; GetSkyboxDlistAndVtxSize(play->skyboxId, &dListSize, &vtxSize); + texSize = GetN64SkyboxTextureSize(play->skyboxId); total += dListSize; total += vtxSize; - total += GetN64SkyboxTextureSize(play->skyboxId); + total += texSize; + LUSLOG_INFO("[ArenaSizing] skyboxId=%d, dList=0x%X, vtx=0x%X, tex=0x%X", play->skyboxId, dListSize, vtxSize, + texSize); } // BgCheck - total += GetBgCheckThaTotal(play); + { + const u32 bgCheck = GetBgCheckThaTotal(play); + total += bgCheck; + LUSLOG_INFO("[ArenaSizing] bgCheck=0x%X (memSize=0x%X)", bgCheck, GetBgCheckMemSize(play)); + } // Elf message - total += GetElfMessageSize(play); + { + const u32 elfMsg = GetElfMessageSize(play); + total += elfMsg; + LUSLOG_INFO("[ArenaSizing] elfMsg=0x%X, total=0x%X, arena=0x%X", elfMsg, total, N64_THA_BUDGET - total); + } if (total >= N64_THA_BUDGET) { return 0; } - return N64_THA_BUDGET - total; + return N64_THA_BUDGET - total - 0x1000; } From 10777611a826e32a9ad5bcbb8660f386cac91242 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 7 May 2026 22:32:37 -0700 Subject: [PATCH 13/58] chore(repo): OTRExporter and ZAPDTR submodules --- OTRExporter | 2 +- ZAPDTR | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OTRExporter b/OTRExporter index 32e088e28c8..deccf9cf7ab 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit 32e088e28c8cdd055d4bb8f3f219d33ad37963f3 +Subproject commit deccf9cf7ab75031ba1a6dd5aa0982f42f206baa diff --git a/ZAPDTR b/ZAPDTR index ee3397a365c..054d54c02da 160000 --- a/ZAPDTR +++ b/ZAPDTR @@ -1 +1 @@ -Subproject commit ee3397a365c5f350a60538c88f0643f155944836 +Subproject commit 054d54c02da801f124e513070d304f6550d6cd01 From 9f75b81f6c9248c1e006b96bb83bdf3beac6c803 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 8 May 2026 00:00:59 -0700 Subject: [PATCH 14/58] feat(restoration): Load scene file sizes from OTR via DMA size table --- .../N64MemoryModel/N64MemoryModel.cpp | 8 +-- .../N64MemoryModel/ShadowArena/DmaSizes.cpp | 56 +++++++++++++++++++ .../N64MemoryModel/ShadowArena/DmaSizes.hpp | 20 +++++++ .../N64MemoryModel/ShadowArena/arena_sizing.c | 7 +-- .../N64MemoryModel/ShadowArena/arena_sizing.h | 1 + .../ShadowArena/instance_sizes.h | 2 +- 6 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.cpp create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.hpp diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index aa7be546523..9d06c77afea 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -80,14 +80,14 @@ void N64Mem_Reset(PlayState* play) sShadowMap.clear(); sAbsoluteSpaceShadow = SHADOW_NULL; - for (std::size_t i = 0; i < ACTOR_ID_MAX; ++i) + for (u32& sOverlayShadow : sOverlayShadows) { - sOverlayShadows[i] = SHADOW_NULL; + sOverlayShadow = SHADOW_NULL; } - for (std::size_t i = 0; i < EFFECT_SS_TYPE_MAX; ++i) + for (u32& sEffectOverlayShadow : sEffectOverlayShadows) { - sEffectOverlayShadows[i] = SHADOW_NULL; + sEffectOverlayShadow = SHADOW_NULL; } sIsActive = CVAR_VALUE; diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.cpp new file mode 100644 index 00000000000..daf9feebae0 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.cpp @@ -0,0 +1,56 @@ +#include "DmaSizes.hpp" + +// -------------------------------------------------------------------------------------------------------------------- +// DMA file size loader +// +// Loads misc/dma_sizes from the OTR and caches the result. The blob was written by OTRExporter during extraction and +// contains per-file VROM sizes from the ROM's DMA table. +// -------------------------------------------------------------------------------------------------------------------- + +static std::unordered_map sDmaFileSizes; +static bool sIsLoaded = false; + +static void LoadDmaFileSizes() +{ + if (sIsLoaded) + { + return; + } + + sIsLoaded = true; + + const auto file = + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/dma_sizes"); + if (!file || !file->IsLoaded) + { + SPDLOG_ERROR("[DmaSizes] Failed to load misc/dma_sizes from OTR."); + return; + } + + auto stream = std::make_shared(file->Buffer->data(), file->Buffer->size()); + const auto reader = std::make_shared(stream); + reader->SetEndianness(Ship::Endianness::Big); + + const u32 entryCount = reader->ReadUInt32(); + for (std::size_t i = 0; i < entryCount; ++i) + { + const u32 vromSize = reader->ReadUInt32(); + const std::string name = reader->ReadString(); + sDmaFileSizes[name] = vromSize; + } + + SPDLOG_INFO("[DmaSizes] Loaded {} DMA file sizes from OTR.", sDmaFileSizes.size()); +} + +extern "C" u32 DmaSizes_GetFileSize(const char* name) +{ + LoadDmaFileSizes(); + + if (const auto i = sDmaFileSizes.find(name); i != sDmaFileSizes.end()) + { + return i->second; + } + + SPDLOG_WARN("[DmaSizes] File size not found for '{}'", name); + return 0; +} diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.hpp new file mode 100644 index 00000000000..765dadfd35c --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// DMA file size lookup +// +// Queries the DMA file size table from the OTR (misc/dma_sizes). Returns the VROM size (virtEnd - virtStart) for the +// given filename, or 0 if not found. This table is loaded once on first call and cached. +// -------------------------------------------------------------------------------------------------------------------- + +u32 DmaSizes_GetFileSize(const char* name); + +#ifdef __cplusplus +} +#endif diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c index 874e5c23de4..cae366941ad 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -1,4 +1,5 @@ #include "arena_sizing.h" +#include "DmaSizes.hpp" #include "global.h" @@ -271,9 +272,7 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) // Scene-dependent consumers { const u32 objBank = GetObjectBankSize(play); - // HACK: vromStart/vromEnd are zeroed in SoH's scene table (OTR filenames replace ROM addresses). - // Hardcoded for graveyard validation only. ZAPDTR exporter replaces this. - const u32 sceneFile = play->sceneNum == SCENE_GRAVEYARD ? 0xBC80 : 0; + const u32 sceneFile = DmaSizes_GetFileSize(play->loadedScene->sceneFile.fileName); const u32 roomBuf = GetMaxRoomSize(play); total += objBank; total += sceneFile; @@ -315,5 +314,5 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) return 0; } - return N64_THA_BUDGET - total - 0x1000; + return N64_THA_BUDGET - total - 0x2000; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h index 9080dfee859..1b4169f7c3d 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h @@ -4,6 +4,7 @@ #ifdef __cplusplus extern "C" { + #endif // -------------------------------------------------------------------------------------------------------------------- diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h index a94a23972d3..3bb685a8ad8 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h @@ -2,4 +2,4 @@ #include -extern const u32 gN64InstanceSizes[]; \ No newline at end of file +extern const u32 gN64InstanceSizes[]; From bea5bf0215ab05345e3b524cd5e8c94927206b16 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 8 May 2026 00:55:06 -0700 Subject: [PATCH 15/58] chore(repo): Update OTRExporter --- OTRExporter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OTRExporter b/OTRExporter index deccf9cf7ab..b833c6398d5 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit deccf9cf7ab75031ba1a6dd5aa0982f42f206baa +Subproject commit b833c6398d583ef71eabed0235f008a4e7ec9cde From 16d88b6e514f27da2f66cd6af9d176a6dc2a1d15 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 8 May 2026 04:58:42 -0700 Subject: [PATCH 16/58] feat(restoration): Load overlay sizes from OTR instead of hardcoded tables --- OTRExporter | 2 +- .../N64MemoryModel/N64MemoryModel.cpp | 7 +- .../N64MemoryModel/ShadowArena/DmaSizes.cpp | 56 --- .../N64MemoryModel/ShadowArena/DmaSizes.hpp | 20 - .../ShadowArena/N64SizeData.cpp | 165 +++++++ .../ShadowArena/N64SizeData.hpp | 26 ++ .../ShadowArena/actor_overlay_sizes.c | 432 ------------------ .../ShadowArena/actor_overlay_sizes.h | 5 - .../N64MemoryModel/ShadowArena/arena_sizing.c | 6 +- .../ShadowArena/effect_overlay_sizes.c | 43 -- .../ShadowArena/effect_overlay_sizes.h | 5 - 11 files changed, 198 insertions(+), 569 deletions(-) delete mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.cpp delete mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.hpp create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp create mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp delete mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c delete mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h delete mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c delete mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h diff --git a/OTRExporter b/OTRExporter index b833c6398d5..7710e4f87b5 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit b833c6398d583ef71eabed0235f008a4e7ec9cde +Subproject commit 7710e4f87b52f76fcc55a1350df3b5f62ef0360e diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 9d06c77afea..832bceff1b9 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -5,10 +5,9 @@ extern "C" { #include "N64MemoryModel.hpp" +#include "ShadowArena/N64SizeData.hpp" #include "ShadowArena/shadow_arena.h" #include "ShadowArena/arena_sizing.h" -#include "ShadowArena/actor_overlay_sizes.h" -#include "ShadowArena/effect_overlay_sizes.h" #include "ShadowArena/instance_sizes.h" } @@ -134,7 +133,7 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) return 1; } - const u32 overlaySize = gN64ActorOverlaySizes[actorId]; + const u32 overlaySize = N64SizeData_GetActorOverlaySize(actorId); if (overlaySize == 0) { return 1; @@ -316,7 +315,7 @@ s32 N64Mem_AllocEffectOverlay(s32 type) return 1; } - u32 overlaySize = gN64EffectOverlaySizes[type]; + u32 overlaySize = N64SizeData_GetEffectOverlaySize(type); if (overlaySize == 0) { return 1; diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.cpp deleted file mode 100644 index daf9feebae0..00000000000 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "DmaSizes.hpp" - -// -------------------------------------------------------------------------------------------------------------------- -// DMA file size loader -// -// Loads misc/dma_sizes from the OTR and caches the result. The blob was written by OTRExporter during extraction and -// contains per-file VROM sizes from the ROM's DMA table. -// -------------------------------------------------------------------------------------------------------------------- - -static std::unordered_map sDmaFileSizes; -static bool sIsLoaded = false; - -static void LoadDmaFileSizes() -{ - if (sIsLoaded) - { - return; - } - - sIsLoaded = true; - - const auto file = - Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/dma_sizes"); - if (!file || !file->IsLoaded) - { - SPDLOG_ERROR("[DmaSizes] Failed to load misc/dma_sizes from OTR."); - return; - } - - auto stream = std::make_shared(file->Buffer->data(), file->Buffer->size()); - const auto reader = std::make_shared(stream); - reader->SetEndianness(Ship::Endianness::Big); - - const u32 entryCount = reader->ReadUInt32(); - for (std::size_t i = 0; i < entryCount; ++i) - { - const u32 vromSize = reader->ReadUInt32(); - const std::string name = reader->ReadString(); - sDmaFileSizes[name] = vromSize; - } - - SPDLOG_INFO("[DmaSizes] Loaded {} DMA file sizes from OTR.", sDmaFileSizes.size()); -} - -extern "C" u32 DmaSizes_GetFileSize(const char* name) -{ - LoadDmaFileSizes(); - - if (const auto i = sDmaFileSizes.find(name); i != sDmaFileSizes.end()) - { - return i->second; - } - - SPDLOG_WARN("[DmaSizes] File size not found for '{}'", name); - return 0; -} diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.hpp deleted file mode 100644 index 765dadfd35c..00000000000 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/DmaSizes.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// -------------------------------------------------------------------------------------------------------------------- -// DMA file size lookup -// -// Queries the DMA file size table from the OTR (misc/dma_sizes). Returns the VROM size (virtEnd - virtStart) for the -// given filename, or 0 if not found. This table is loaded once on first call and cached. -// -------------------------------------------------------------------------------------------------------------------- - -u32 DmaSizes_GetFileSize(const char* name); - -#ifdef __cplusplus -} -#endif diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp new file mode 100644 index 00000000000..15753d98372 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp @@ -0,0 +1,165 @@ +#include "N64SizeData.hpp" + +// -------------------------------------------------------------------------------------------------------------------- +// DMA file sizes (misc/dma_sizes) +// +// Format: u32 entryCount, then per entry: u32 vromSize, length-prefixed string name +// -------------------------------------------------------------------------------------------------------------------- + +static std::unordered_map sDmaFileSizes; +static bool sIsDmaLoaded = false; + +static void LoadDmaFileSizes() +{ + if (sIsDmaLoaded) + { + return; + } + + sIsDmaLoaded = true; + + const auto file = + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/dma_sizes"); + if (!file || !file->IsLoaded) + { + SPDLOG_ERROR("[N64SizeData] Failed to load misc/dma_sizes from OTR."); + return; + } + + auto stream = std::make_shared(file->Buffer->data(), file->Buffer->size()); + const auto reader = std::make_shared(stream); + reader->SetEndianness(Ship::Endianness::Big); + + const u32 entryCount = reader->ReadUInt32(); + for (std::size_t i = 0; i < entryCount; ++i) + { + const u32 vromSize = reader->ReadUInt32(); + const std::string name = reader->ReadString(); + sDmaFileSizes[name] = vromSize; + } + + SPDLOG_INFO("[N64SizeData] Loaded {} DMA file sizes from OTR.", sDmaFileSizes.size()); +} + +// -------------------------------------------------------------------------------------------------------------------- +// Actor overlay VRAM sizes (misc/actor_overlay_sizes) +// +// Format: u32 entryCount, then entryCount consecutive u32 values indexed by actor ID +// -------------------------------------------------------------------------------------------------------------------- + +static std::vector sActorOverlaySizes; +static bool sIsActorOverlayLoaded = false; + +static void LoadActorOverlaySizes() +{ + if (sIsActorOverlayLoaded) + { + return; + } + + sIsActorOverlayLoaded = true; + + const auto file = + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/actor_overlay_sizes"); + if (!file || !file->IsLoaded) + { + SPDLOG_ERROR("[N64SizeData] Failed to load misc/actor_overlay_sizes from OTR."); + return; + } + + auto stream = std::make_shared(file->Buffer->data(), file->Buffer->size()); + const auto reader = std::make_shared(stream); + reader->SetEndianness(Ship::Endianness::Big); + + const u32 entryCount = reader->ReadUInt32(); + sActorOverlaySizes.resize(entryCount); + + for (std::size_t i = 0; i < entryCount; ++i) + { + sActorOverlaySizes.at(i) = reader->ReadUInt32(); + } + + SPDLOG_INFO("[N64SizeData] Loaded {} actor overlay sizes.", sActorOverlaySizes.size()); +} + +// -------------------------------------------------------------------------------------------------------------------- +// Effect overlay VRAM sizes (misc/effect_overlay_sizes) +// +// Format: u32 entryCount, then entryCount consecutive u32 values indexed by effect type +// -------------------------------------------------------------------------------------------------------------------- + +static std::vector sEffectOverlaySizes; +static bool sIsEffectOverlayLoaded = false; + +static void LoadEffectOverlaySizes() +{ + if (sIsEffectOverlayLoaded) + { + return; + } + + sIsEffectOverlayLoaded = true; + + const auto file = + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/effect_overlay_sizes"); + if (!file || !file->IsLoaded) + { + SPDLOG_ERROR("[N64SizeData] Failed to load misc/effect_overlay_sizes from OTR."); + return; + } + + auto stream = std::make_shared(file->Buffer->data(), file->Buffer->size()); + const auto reader = std::make_shared(stream); + reader->SetEndianness(Ship::Endianness::Big); + + const u32 entryCount = reader->ReadUInt32(); + sEffectOverlaySizes.resize(entryCount); + + for (std::size_t i = 0; i < entryCount; ++i) + { + sEffectOverlaySizes.at(i) = reader->ReadUInt32(); + } + + SPDLOG_INFO("[N64SizeData] Loaded {} effect overlay sizes.", sEffectOverlaySizes.size()); +} + +// -------------------------------------------------------------------------------------------------------------------- +// API +// -------------------------------------------------------------------------------------------------------------------- + +extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) +{ + LoadDmaFileSizes(); + + if (const auto i = sDmaFileSizes.find(name); i != sDmaFileSizes.end()) + { + return i->second; + } + + SPDLOG_WARN("[N64SizeData] DMA file size not found for '{}'", name); + return 0; +} + +extern "C" u32 N64SizeData_GetActorOverlaySize(u16 actorId) +{ + LoadActorOverlaySizes(); + + if (actorId < sActorOverlaySizes.size()) + { + return sActorOverlaySizes.at(actorId); + } + + return 0; +} + +extern "C" u32 N64SizeData_GetEffectOverlaySize(u16 effectType) +{ + LoadEffectOverlaySizes(); + + if (effectType < sEffectOverlaySizes.size()) + { + return sEffectOverlaySizes.at(effectType); + } + + return 0; +} diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp new file mode 100644 index 00000000000..e1fe310263f --- /dev/null +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// N64 size data loaded from the OTR archive. +// +// All data is extracted per-version by OTRExporter During ROM extraction: +// misc/dma_sizes DMA file VROM sizes keyed by filename +// misc/actor_overlay_sizes Actor overlay VRAM sizes indexed by actor ID +// misc/effect_overlay_sizes Effect overlay VRAM sizes indexed by effect type +// +// Each table is loaded lazily on first query and cached for the lifetime of the process. +// -------------------------------------------------------------------------------------------------------------------- + +u32 N64SizeData_GetDmaFileSize(const char* name); +u32 N64SizeData_GetActorOverlaySize(u16 actorId); +u32 N64SizeData_GetEffectOverlaySize(u16 effectType); + +#ifdef __cplusplus +} +#endif diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c deleted file mode 100644 index 979d6011467..00000000000 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.c +++ /dev/null @@ -1,432 +0,0 @@ -#include "actor_overlay_sizes.h" - -#include "global.h" - -const u32 gN64ActorOverlaySizes[ACTOR_ID_MAX] = { - [ACTOR_EN_TEST] = 0x58B0, - [ACTOR_EN_GIRLA] = 0x2920, - [ACTOR_EN_PART] = 0x1610, - [ACTOR_EN_LIGHT] = 0xDF0, - [ACTOR_EN_DOOR] = 0xE40, - [ACTOR_EN_BOX] = 0x1B40, - [ACTOR_BG_DY_YOSEIZO] = 0x2E00, - [ACTOR_BG_HIDAN_FIREWALL] = 0x760, - [ACTOR_EN_POH] = 0x4190, - [ACTOR_EN_OKUTA] = 0x25E0, - [ACTOR_BG_YDAN_SP] = 0x1770, - [ACTOR_EN_BOM] = 0xED0, - [ACTOR_EN_WALLMAS] = 0x1A10, - [ACTOR_EN_DODONGO] = 0x2DA0, - [ACTOR_EN_FIREFLY] = 0x2170, - [ACTOR_EN_HORSE] = 0xC260, - [ACTOR_EN_ARROW] = 0x16F0, - [ACTOR_EN_ELF] = 0x49C0, - [ACTOR_EN_NIW] = 0x3330, - [ACTOR_EN_TITE] = 0x2DA0, - [ACTOR_EN_REEBA] = 0x1A70, - [ACTOR_EN_PEEHAT] = 0x3700, - [ACTOR_EN_BUTTE] = 0x15D0, - [ACTOR_EN_INSECT] = 0x2520, - [ACTOR_EN_FISH] = 0x2110, - [ACTOR_EN_HOLL] = 0xFD0, - [ACTOR_EN_SCENE_CHANGE] = 0x130, - [ACTOR_EN_ZF] = 0x6B00, - [ACTOR_EN_HATA] = 0x590, - [ACTOR_BOSS_DODONGO] = 0x9AE0, - [ACTOR_BOSS_GOMA] = 0x5F80, - [ACTOR_EN_ZL1] = 0x3E00, - [ACTOR_EN_VIEWER] = 0x2ED0, - [ACTOR_EN_GOMA] = 0x2C90, - [ACTOR_BG_PUSHBOX] = 0x300, - [ACTOR_EN_BUBBLE] = 0x1420, - [ACTOR_DOOR_SHUTTER] = 0x2280, - [ACTOR_EN_DODOJR] = 0x1EA0, - [ACTOR_EN_BDFIRE] = 0xB90, - [ACTOR_EN_BOOM] = 0x8C0, - [ACTOR_EN_TORCH2] = 0x27A0, - [ACTOR_EN_BILI] = 0x22D0, - [ACTOR_EN_TP] = 0x1E50, - [ACTOR_EN_ST] = 0x2C70, - [ACTOR_EN_BW] = 0x3370, - [ACTOR_EN_EIYER] = 0x1C60, - [ACTOR_EN_RIVER_SOUND] = 0x990, - [ACTOR_EN_HORSE_NORMAL] = 0x2620, - [ACTOR_EN_OSSAN] = 0x65E0, - [ACTOR_BG_TREEMOUTH] = 0x1660, - [ACTOR_BG_DODOAGO] = 0xDB0, - [ACTOR_BG_HIDAN_DALM] = 0x850, - [ACTOR_BG_HIDAN_HROCK] = 0x830, - [ACTOR_EN_HORSE_GANON] = 0xD80, - [ACTOR_BG_HIDAN_ROCK] = 0x10F0, - [ACTOR_BG_HIDAN_RSEKIZOU] = 0xBE0, - [ACTOR_BG_HIDAN_SEKIZOU] = 0x1450, - [ACTOR_BG_HIDAN_SIMA] = 0xF20, - [ACTOR_BG_HIDAN_SYOKU] = 0x460, - [ACTOR_EN_XC] = 0x6790, - [ACTOR_BG_HIDAN_CURTAIN] = 0xAA0, - [ACTOR_BG_SPOT00_HANEBASI] = 0x1110, - [ACTOR_EN_MB] = 0x4230, - [ACTOR_EN_BOMBF] = 0x1470, - [ACTOR_EN_ZL2] = 0x4730, - [ACTOR_BG_HIDAN_FSLIFT] = 0x4D0, - [ACTOR_EN_OE2] = 0xE0, - [ACTOR_BG_YDAN_HASI] = 0x7B0, - [ACTOR_BG_YDAN_MARUTA] = 0x6E0, - [ACTOR_BOSS_GANONDROF] = 0x4D70, - [ACTOR_EN_AM] = 0x2400, - [ACTOR_EN_DEKUBABA] = 0x3AA0, - [ACTOR_EN_M_FIRE1] = 0x1A0, - [ACTOR_EN_M_THUNDER] = 0x15F0, - [ACTOR_BG_DDAN_JD] = 0x650, - [ACTOR_BG_BREAKWALL] = 0xE70, - [ACTOR_EN_JJ] = 0x15D0, - [ACTOR_EN_HORSE_ZELDA] = 0xAF0, - [ACTOR_BG_DDAN_KD] = 0x8F0, - [ACTOR_DOOR_WARP1] = 0x42B0, - [ACTOR_OBJ_SYOKUDAI] = 0xC40, - [ACTOR_ITEM_B_HEART] = 0x3F0, - [ACTOR_EN_DEKUNUTS] = 0x1800, - [ACTOR_BG_MENKURI_KAITEN] = 0x190, - [ACTOR_BG_MENKURI_EYE] = 0x4A0, - [ACTOR_EN_VALI] = 0x26A0, - [ACTOR_BG_MIZU_MOVEBG] = 0x11A0, - [ACTOR_BG_MIZU_WATER] = 0xCD0, - [ACTOR_ARMS_HOOK] = 0xD60, - [ACTOR_EN_FHG] = 0x2930, - [ACTOR_BG_MORI_HINERI] = 0xD00, - [ACTOR_EN_BB] = 0x3CD0, - [ACTOR_BG_TOKI_HIKARI] = 0xDA0, - [ACTOR_EN_YUKABYUN] = 0x610, - [ACTOR_BG_TOKI_SWD] = 0x1650, - [ACTOR_EN_FHG_FIRE] = 0x2620, - [ACTOR_BG_MJIN] = 0x3E0, - [ACTOR_BG_HIDAN_KOUSI] = 0x580, - [ACTOR_DOOR_TOKI] = 0x160, - [ACTOR_BG_HIDAN_HAMSTEP] = 0xEB0, - [ACTOR_EN_BIRD] = 0x4C0, - [ACTOR_EN_WOOD02] = 0x11E0, - [ACTOR_EN_LIGHTBOX] = 0x480, - [ACTOR_EN_PU_BOX] = 0x340, - [ACTOR_EN_TRAP] = 0x12A0, - [ACTOR_EN_AROW_TRAP] = 0x150, - [ACTOR_EN_VASE] = 0x100, - [ACTOR_EN_TA] = 0x39C0, - [ACTOR_EN_TK] = 0x1E30, - [ACTOR_BG_MORI_BIGST] = 0x930, - [ACTOR_BG_MORI_ELEVATOR] = 0xAF0, - [ACTOR_BG_MORI_KAITENKABE] = 0x660, - [ACTOR_BG_MORI_RAKKATENJO] = 0x970, - [ACTOR_EN_VM] = 0x18B0, - [ACTOR_DEMO_EFFECT] = 0x5AF0, - [ACTOR_DEMO_KANKYO] = 0x3D00, - [ACTOR_BG_HIDAN_FWBIG] = 0xCE0, - [ACTOR_EN_FLOORMAS] = 0x33E0, - [ACTOR_EN_HEISHI1] = 0x1510, - [ACTOR_EN_RD] = 0x28B0, - [ACTOR_EN_PO_SISTERS] = 0x4CF0, - [ACTOR_BG_HEAVY_BLOCK] = 0x18F0, - [ACTOR_BG_PO_EVENT] = 0x1E40, - [ACTOR_OBJ_MURE] = 0x1010, - [ACTOR_EN_SW] = 0x37F0, - [ACTOR_BOSS_FD] = 0x7330, - [ACTOR_OBJECT_KANKYO] = 0x3220, - [ACTOR_EN_DU] = 0x1AA0, - [ACTOR_EN_FD] = 0x2CC0, - [ACTOR_EN_HORSE_LINK_CHILD] = 0x1E00, - [ACTOR_DOOR_ANA] = 0x670, - [ACTOR_BG_SPOT02_OBJECTS] = 0x1350, - [ACTOR_BG_HAKA] = 0x6C0, - [ACTOR_MAGIC_WIND] = 0x1D00, - [ACTOR_MAGIC_FIRE] = 0x22D0, - [ACTOR_EN_RU1] = 0x76A0, - [ACTOR_BOSS_FD2] = 0x3D30, - [ACTOR_EN_FD_FIRE] = 0xD10, - [ACTOR_EN_DH] = 0x1AD0, - [ACTOR_EN_DHA] = 0x1000, - [ACTOR_EN_RL] = 0xEE0, - [ACTOR_EN_ENCOUNT1] = 0xB60, - [ACTOR_DEMO_DU] = 0x37E0, - [ACTOR_DEMO_IM] = 0x3F70, - [ACTOR_DEMO_TRE_LGT] = 0x710, - [ACTOR_EN_FW] = 0x17B0, - [ACTOR_BG_VB_SIMA] = 0x710, - [ACTOR_EN_VB_BALL] = 0x11A0, - [ACTOR_BG_HAKA_MEGANE] = 0x400, - [ACTOR_BG_HAKA_MEGANEBG] = 0x6C0, - [ACTOR_BG_HAKA_SHIP] = 0xA40, - [ACTOR_BG_HAKA_SGAMI] = 0xC20, - [ACTOR_EN_HEISHI2] = 0x2200, - [ACTOR_EN_ENCOUNT2] = 0x1230, - [ACTOR_EN_FIRE_ROCK] = 0x1110, - [ACTOR_EN_BROB] = 0x10F0, - [ACTOR_MIR_RAY] = 0x18C0, - [ACTOR_BG_SPOT09_OBJ] = 0x510, - [ACTOR_BG_SPOT18_OBJ] = 0x8D0, - [ACTOR_BOSS_VA] = 0x171F0, - [ACTOR_BG_HAKA_TUBO] = 0xA20, - [ACTOR_BG_HAKA_TRAP] = 0x15D0, - [ACTOR_BG_HAKA_HUTA] = 0xAA0, - [ACTOR_BG_HAKA_ZOU] = 0x11F0, - [ACTOR_BG_SPOT17_FUNEN] = 0x250, - [ACTOR_EN_SYATEKI_ITM] = 0xDA0, - [ACTOR_EN_SYATEKI_MAN] = 0xDC0, - [ACTOR_EN_TANA] = 0x2A0, - [ACTOR_EN_NB] = 0x45D0, - [ACTOR_BOSS_MO] = 0x100B0, - [ACTOR_EN_SB] = 0x1440, - [ACTOR_EN_BIGOKUTA] = 0x2B10, - [ACTOR_EN_KAREBABA] = 0x18F0, - [ACTOR_BG_BDAN_OBJECTS] = 0x12D0, - [ACTOR_DEMO_SA] = 0x2B20, - [ACTOR_DEMO_GO] = 0xD60, - [ACTOR_EN_IN] = 0x2DA0, - [ACTOR_EN_TR] = 0x1900, - [ACTOR_BG_SPOT16_BOMBSTONE] = 0x1540, - [ACTOR_BG_HIDAN_KOWARERUKABE] = 0xED0, - [ACTOR_BG_BOMBWALL] = 0x8C0, - [ACTOR_BG_SPOT08_ICEBLOCK] = 0x1040, - [ACTOR_EN_RU2] = 0x2D80, - [ACTOR_OBJ_DEKUJR] = 0x640, - [ACTOR_BG_MIZU_UZU] = 0x1D0, - [ACTOR_BG_SPOT06_OBJECTS] = 0x1410, - [ACTOR_BG_ICE_OBJECTS] = 0xF40, - [ACTOR_BG_HAKA_WATER] = 0x7E0, - [ACTOR_EN_MA2] = 0x1060, - [ACTOR_EN_BOM_CHU] = 0x16A0, - [ACTOR_EN_HORSE_GAME_CHECK] = 0x10D0, - [ACTOR_BOSS_TW] = 0x15B00, - [ACTOR_EN_RR] = 0x2530, - [ACTOR_EN_BA] = 0x1ED0, - [ACTOR_EN_BX] = 0xAF0, - [ACTOR_EN_ANUBICE] = 0x12B0, - [ACTOR_EN_ANUBICE_FIRE] = 0xDC0, - [ACTOR_BG_MORI_HASHIGO] = 0x8C0, - [ACTOR_BG_MORI_HASHIRA4] = 0x590, - [ACTOR_BG_MORI_IDOMIZU] = 0x640, - [ACTOR_BG_SPOT16_DOUGHNUT] = 0x5B0, - [ACTOR_BG_BDAN_SWITCH] = 0x1430, - [ACTOR_EN_MA1] = 0x12E0, - [ACTOR_BOSS_GANON] = 0x25DE0, - [ACTOR_BOSS_SST] = 0xC5C0, - [ACTOR_EN_NY] = 0x1930, - [ACTOR_EN_FR] = 0x2A90, - [ACTOR_ITEM_SHIELD] = 0xA10, - [ACTOR_BG_ICE_SHELTER] = 0x1230, - [ACTOR_EN_ICE_HONO] = 0x11F0, - [ACTOR_ITEM_OCARINA] = 0x7D0, - [ACTOR_MAGIC_DARK] = 0x1850, - [ACTOR_DEMO_6K] = 0x2D10, - [ACTOR_EN_ANUBICE_TAG] = 0x2D0, - [ACTOR_BG_HAKA_GATE] = 0x1090, - [ACTOR_BG_SPOT15_SAKU] = 0x340, - [ACTOR_BG_JYA_GOROIWA] = 0x780, - [ACTOR_BG_JYA_ZURERUKABE] = 0x6B0, - [ACTOR_BG_JYA_COBRA] = 0x1D20, - [ACTOR_BG_JYA_KANAAMI] = 0x3B0, - [ACTOR_FISHING] = 0x1AAB0, - [ACTOR_OBJ_OSHIHIKI] = 0x1AB0, - [ACTOR_BG_GATE_SHUTTER] = 0x480, - [ACTOR_EFF_DUST] = 0x13E0, - [ACTOR_BG_SPOT01_FUSYA] = 0x2A0, - [ACTOR_BG_SPOT01_IDOHASHIRA] = 0xC00, - [ACTOR_BG_SPOT01_IDOMIZU] = 0x310, - [ACTOR_BG_PO_SYOKUDAI] = 0x950, - [ACTOR_BG_GANON_OTYUKA] = 0x2640, - [ACTOR_BG_SPOT15_RRBOX] = 0xDE0, - [ACTOR_BG_UMAJUMP] = 0x190, - [ACTOR_ARROW_FIRE] = 0x1EC0, - [ACTOR_ARROW_ICE] = 0x1EE0, - [ACTOR_ARROW_LIGHT] = 0x1EF0, - [ACTOR_ITEM_ETCETERA] = 0x8D0, - [ACTOR_OBJ_KIBAKO] = 0xD00, - [ACTOR_OBJ_TSUBO] = 0xFF0, - [ACTOR_EN_WONDER_ITEM] = 0xD30, - [ACTOR_EN_IK] = 0x4640, - [ACTOR_DEMO_IK] = 0x1510, - [ACTOR_EN_SKJ] = 0x3940, - [ACTOR_EN_SKJNEEDLE] = 0x310, - [ACTOR_EN_G_SWITCH] = 0x1830, - [ACTOR_DEMO_EXT] = 0x940, - [ACTOR_DEMO_SHD] = 0x2410, - [ACTOR_EN_DNS] = 0x1390, - [ACTOR_ELF_MSG] = 0x5F0, - [ACTOR_EN_HONOTRAP] = 0x1550, - [ACTOR_EN_TUBO_TRAP] = 0xCA0, - [ACTOR_OBJ_ICE_POLY] = 0x9B0, - [ACTOR_BG_SPOT03_TAKI] = 0x8F0, - [ACTOR_BG_SPOT07_TAKI] = 0x5C0, - [ACTOR_EN_FZ] = 0x2010, - [ACTOR_EN_PO_RELAY] = 0x1710, - [ACTOR_BG_RELAY_OBJECTS] = 0x7B0, - [ACTOR_EN_DIVING_GAME] = 0x19B0, - [ACTOR_EN_KUSA] = 0x14E0, - [ACTOR_OBJ_BEAN] = 0x2790, - [ACTOR_OBJ_BOMBIWA] = 0x570, - [ACTOR_OBJ_SWITCH] = 0x1DC0, - [ACTOR_OBJ_ELEVATOR] = 0x3C0, - [ACTOR_OBJ_LIFT] = 0xA20, - [ACTOR_OBJ_HSBLOCK] = 0x5D0, - [ACTOR_EN_OKARINA_TAG] = 0x1500, - [ACTOR_EN_YABUSAME_MARK] = 0x6D0, - [ACTOR_EN_GOROIWA] = 0x23C0, - [ACTOR_EN_EX_RUPPY] = 0x10C0, - [ACTOR_EN_TORYO] = 0xC90, - [ACTOR_EN_DAIKU] = 0x1740, - [ACTOR_EN_NWC] = 0xA40, - [ACTOR_EN_BLKOBJ] = 0x560, - [ACTOR_ITEM_INBOX] = 0x160, - [ACTOR_EN_GE1] = 0x2030, - [ACTOR_OBJ_BLOCKSTOP] = 0x1A0, - [ACTOR_EN_SDA] = 0x1700, - [ACTOR_EN_CLEAR_TAG] = 0xB5A0, - [ACTOR_EN_NIW_LADY] = 0x1900, - [ACTOR_EN_GM] = 0xD30, - [ACTOR_EN_MS] = 0x6F0, - [ACTOR_EN_HS] = 0xBA0, - [ACTOR_BG_INGATE] = 0x390, - [ACTOR_EN_KANBAN] = 0x3150, - [ACTOR_EN_HEISHI3] = 0x9D0, - [ACTOR_EN_SYATEKI_NIW] = 0x2090, - [ACTOR_EN_ATTACK_NIW] = 0x1260, - [ACTOR_BG_SPOT01_IDOSOKO] = 0x210, - [ACTOR_EN_SA] = 0x2270, - [ACTOR_EN_WONDER_TALK] = 0x690, - [ACTOR_BG_GJYO_BRIDGE] = 0x500, - [ACTOR_EN_DS] = 0xC20, - [ACTOR_EN_MK] = 0xE90, - [ACTOR_EN_BOM_BOWL_MAN] = 0x1540, - [ACTOR_EN_BOM_BOWL_PIT] = 0x970, - [ACTOR_EN_OWL] = 0x3BA0, - [ACTOR_EN_ISHI] = 0x9150, - [ACTOR_OBJ_HANA] = 0x310, - [ACTOR_OBJ_LIGHTSWITCH] = 0x1430, - [ACTOR_OBJ_MURE2] = 0xA20, - [ACTOR_EN_GO] = 0x4640, - [ACTOR_EN_FU] = 0xD50, - [ACTOR_EN_CHANGER] = 0x9E0, - [ACTOR_BG_JYA_MEGAMI] = 0x11E0, - [ACTOR_BG_JYA_LIFT] = 0x550, - [ACTOR_BG_JYA_BIGMIRROR] = 0x840, - [ACTOR_BG_JYA_BOMBCHUIWA] = 0xB30, - [ACTOR_BG_JYA_AMISHUTTER] = 0x390, - [ACTOR_BG_JYA_BOMBIWA] = 0x5C0, - [ACTOR_BG_SPOT18_BASKET] = 0xFF0, - [ACTOR_EN_GANON_ORGAN] = 0x7000, - [ACTOR_EN_SIOFUKI] = 0xDB0, - [ACTOR_EN_STREAM] = 0x590, - [ACTOR_EN_MM] = 0x1620, - [ACTOR_EN_KO] = 0x4140, - [ACTOR_EN_KZ] = 0x15A0, - [ACTOR_EN_WEATHER_TAG] = 0xEF0, - [ACTOR_BG_SST_FLOOR] = 0x560, - [ACTOR_EN_ANI] = 0xD70, - [ACTOR_EN_EX_ITEM] = 0x1170, - [ACTOR_BG_JYA_IRONOBJ] = 0xDB0, - [ACTOR_EN_JS] = 0x9D0, - [ACTOR_EN_JSJUTAN] = 0x5920, - [ACTOR_EN_CS] = 0x1230, - [ACTOR_EN_MD] = 0x2670, - [ACTOR_EN_HY] = 0x3940, - [ACTOR_EN_GANON_MANT] = 0x4220, - [ACTOR_EN_OKARINA_EFFECT] = 0x3C0, - [ACTOR_EN_MAG] = 0x4F10, - [ACTOR_DOOR_GERUDO] = 0x5F0, - [ACTOR_ELF_MSG2] = 0x470, - [ACTOR_DEMO_GT] = 0x5600, - [ACTOR_EN_PO_FIELD] = 0x3A70, - [ACTOR_EFC_ERUPC] = 0xAE0, - [ACTOR_BG_ZG] = 0x470, - [ACTOR_EN_HEISHI4] = 0xF00, - [ACTOR_EN_ZL3] = 0x7E50, - [ACTOR_BOSS_GANON2] = 0x12E20, - [ACTOR_EN_KAKASI] = 0xD40, - [ACTOR_EN_TAKARA_MAN] = 0x8C0, - [ACTOR_OBJ_MAKEOSHIHIKI] = 0x490, - [ACTOR_OCEFF_SPOT] = 0xF30, - [ACTOR_END_TITLE] = 0x4130, - [ACTOR_EN_TORCH] = 0xF0, - [ACTOR_DEMO_EC] = 0x3860, - [ACTOR_SHOT_SUN] = 0x6C0, - [ACTOR_EN_DY_EXTRA] = 0x580, - [ACTOR_EN_WONDER_TALK2] = 0x6A0, - [ACTOR_EN_GE2] = 0x19A0, - [ACTOR_OBJ_ROOMTIMER] = 0x250, - [ACTOR_EN_SSH] = 0x25F0, - [ACTOR_EN_STH] = 0x40B0, - [ACTOR_OCEFF_WIPE] = 0xD50, - [ACTOR_OCEFF_STORM] = 0x1BA0, - [ACTOR_EN_WEIYER] = 0x1A00, - [ACTOR_BG_SPOT05_SOKO] = 0x320, - [ACTOR_BG_JYA_1FLIFT] = 0x690, - [ACTOR_BG_JYA_HAHENIRON] = 0x7F0, - [ACTOR_BG_SPOT12_GATE] = 0x410, - [ACTOR_BG_SPOT12_SAKU] = 0x4C0, - [ACTOR_EN_HINTNUTS] = 0x1A30, - [ACTOR_EN_NUTSBALL] = 0x620, - [ACTOR_BG_SPOT00_BREAK] = 0x1A0, - [ACTOR_EN_SHOPNUTS] = 0xF10, - [ACTOR_EN_IT] = 0x190, - [ACTOR_EN_GELDB] = 0x53B0, - [ACTOR_OCEFF_WIPE2] = 0x1770, - [ACTOR_OCEFF_WIPE3] = 0x1750, - [ACTOR_EN_NIW_GIRL] = 0xAD0, - [ACTOR_EN_DOG] = 0x11B0, - [ACTOR_EN_SI] = 0x500, - [ACTOR_BG_SPOT01_OBJECTS2] = 0x4C0, - [ACTOR_OBJ_COMB] = 0x860, - [ACTOR_BG_SPOT11_BAKUDANKABE] = 0x640, - [ACTOR_OBJ_KIBAKO2] = 0x6C0, - [ACTOR_EN_DNT_DEMO] = 0xD20, - [ACTOR_EN_DNT_JIJI] = 0x1510, - [ACTOR_EN_DNT_NOMAL] = 0x2E10, - [ACTOR_EN_GUEST] = 0x9A0, - [ACTOR_BG_BOM_GUARD] = 0x220, - [ACTOR_EN_HS2] = 0x5E0, - [ACTOR_DEMO_KEKKAI] = 0x12E0, - [ACTOR_BG_SPOT08_BAKUDANKABE] = 0x6A0, - [ACTOR_BG_SPOT17_BAKUDANKABE] = 0x6E0, - [ACTOR_OBJ_MURE3] = 0x7D0, - [ACTOR_EN_TG] = 0x6D0, - [ACTOR_EN_MU] = 0x920, - [ACTOR_EN_GO2] = 0x6020, - [ACTOR_EN_WF] = 0x4310, - [ACTOR_EN_SKB] = 0x18F0, - [ACTOR_DEMO_GJ] = 0x3CB0, - [ACTOR_DEMO_GEFF] = 0x820, - [ACTOR_BG_GND_FIREMEIRO] = 0x540, - [ACTOR_BG_GND_DARKMEIRO] = 0x7C0, - [ACTOR_BG_GND_SOULMEIRO] = 0x860, - [ACTOR_BG_GND_NISEKABE] = 0x170, - [ACTOR_BG_GND_ICEBLOCK] = 0x1100, - [ACTOR_EN_GB] = 0x1730, - [ACTOR_EN_GS] = 0x1EA0, - [ACTOR_BG_MIZU_BWALL] = 0x14D0, - [ACTOR_BG_MIZU_SHUTTER] = 0x800, - [ACTOR_EN_DAIKU_KAKARIKO] = 0x13C0, - [ACTOR_BG_BOWL_WALL] = 0x980, - [ACTOR_EN_WALL_TUBO] = 0x4F0, - [ACTOR_EN_PO_DESERT] = 0xDC0, - [ACTOR_EN_CROW] = 0x16A0, - [ACTOR_DOOR_KILLER] = 0x1570, - [ACTOR_BG_SPOT11_OASIS] = 0x730, - [ACTOR_BG_SPOT18_FUTA] = 0x1A0, - [ACTOR_BG_SPOT18_SHUTTER] = 0x550, - [ACTOR_EN_MA3] = 0xFB0, - [ACTOR_EN_COW] = 0x1460, - [ACTOR_BG_ICE_TURARA] = 0x830, - [ACTOR_BG_ICE_SHUTTER] = 0x470, - [ACTOR_EN_KAKASI2] = 0x720, - [ACTOR_EN_KAKASI3] = 0x10E0, - [ACTOR_OCEFF_WIPE4] = 0xFE0, - [ACTOR_EN_EG] = 0x1B0, - [ACTOR_BG_MENKURI_NISEKABE] = 0x150, - [ACTOR_EN_ZO] = 0x25B0, - [ACTOR_OBJ_MAKEKINSUTA] = 0x150, - [ACTOR_EN_GE3] = 0xB50, - [ACTOR_OBJ_TIMEBLOCK] = 0xC40, - [ACTOR_OBJ_HAMISHI] = 0x850, - [ACTOR_EN_ZL4] = 0x4A30, - [ACTOR_EN_MM2] = 0xDC0, - [ACTOR_BG_JYA_BLOCK] = 0x270, - [ACTOR_OBJ_WARP2BLOCK] = 0xB30, -}; diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h deleted file mode 100644 index 2395c8c3a31..00000000000 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/actor_overlay_sizes.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -extern const u32 gN64ActorOverlaySizes[]; \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c index cae366941ad..85d7ceedf01 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -1,5 +1,5 @@ #include "arena_sizing.h" -#include "DmaSizes.hpp" +#include "N64SizeData.hpp" #include "global.h" @@ -272,7 +272,7 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) // Scene-dependent consumers { const u32 objBank = GetObjectBankSize(play); - const u32 sceneFile = DmaSizes_GetFileSize(play->loadedScene->sceneFile.fileName); + const u32 sceneFile = N64SizeData_GetDmaFileSize(play->loadedScene->sceneFile.fileName); const u32 roomBuf = GetMaxRoomSize(play); total += objBank; total += sceneFile; @@ -314,5 +314,5 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) return 0; } - return N64_THA_BUDGET - total - 0x2000; + return N64_THA_BUDGET - total; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c deleted file mode 100644 index edcabbbe4e8..00000000000 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.c +++ /dev/null @@ -1,43 +0,0 @@ -#include "effect_overlay_sizes.h" - -#include "global.h" - -const u32 gN64EffectOverlaySizes[EFFECT_SS_TYPE_MAX] = { - [EFFECT_SS_DUST] = 0x830, - [EFFECT_SS_KIRAKIRA] = 0x670, - [EFFECT_SS_BOMB] = 0x420, - [EFFECT_SS_BOMB2] = 0x930, - [EFFECT_SS_BLAST] = 0x390, - [EFFECT_SS_G_SPK] = 0x5B0, - [EFFECT_SS_D_FIRE] = 0x4F0, - [EFFECT_SS_BUBBLE] = 0x480, - [EFFECT_SS_UNSET] = 0x000, - [EFFECT_SS_G_RIPPLE] = 0x560, - [EFFECT_SS_G_SPLASH] = 0x4B0, - [EFFECT_SS_G_MAGMA] = 0x260, - [EFFECT_SS_G_FIRE] = 0x290, - [EFFECT_SS_LIGHTNING] = 0x6D0, - [EFFECT_SS_DT_BUBBLE] = 0x590, - [EFFECT_SS_HAHEN] = 0x640, - [EFFECT_SS_STICK] = 0x3A0, - [EFFECT_SS_SIBUKI] = 0x6D0, - [EFFECT_SS_SIBUKI2] = 0x330, - [EFFECT_SS_G_MAGMA2] = 0x510, - [EFFECT_SS_STONE1] = 0x390, - [EFFECT_SS_HITMARK] = 0x550, - [EFFECT_SS_FHG_FLASH] = 0xF80, - [EFFECT_SS_K_FIRE] = 0x430, - [EFFECT_SS_SOLDER_SRCH_BALL] = 0x1B0, - [EFFECT_SS_KAKERA] = 0x1090, - [EFFECT_SS_ICE_PIECE] = 0x440, - [EFFECT_SS_EN_ICE] = 0x8C0, - [EFFECT_SS_FIRE_TAIL] = 0x700, - [EFFECT_SS_EN_FIRE] = 0x740, - [EFFECT_SS_EXTRA] = 0x3C0, - [EFFECT_SS_FCIRCLE] = 0x4B0, - [EFFECT_SS_DEAD_DB] = 0x4E0, - [EFFECT_SS_DEAD_DD] = 0x590, - [EFFECT_SS_DEAD_DS] = 0x480, - [EFFECT_SS_DEAD_SOUND] = 0x140, - [EFFECT_SS_ICE_SMOKE] = 0x4C0, -}; diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h deleted file mode 100644 index d6b696c6d37..00000000000 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/effect_overlay_sizes.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -extern const u32 gN64EffectOverlaySizes[]; \ No newline at end of file From 7c2d538ca904206c023e81d4da2bfb6dcfd0061f Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 8 May 2026 10:18:48 -0700 Subject: [PATCH 17/58] fix(restoration): Use N64 DMA buffer sizes for doAction and mapSegment --- .../Restorations/N64MemoryModel/ShadowArena/arena_sizing.c | 7 +++---- .../Restorations/N64MemoryModel/ShadowArena/arena_sizing.h | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c index 85d7ceedf01..aabe150028a 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -20,7 +20,6 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); // N64 struct sizes (32-bit, from decomp headers and linker map) // -------------------------------------------------------------------------------------------------------------------- -#define N64_SIZEOF_CHAR_PTR 4 // sizeof(char*) on N64 MIPS #define N64_SIZEOF_COLLISION_CONTEXT 0x1464 // CollisionContext size = 0x1464 (from decomp header comment) #define N64_SIZEOF_GFX 8 // sizeof(Gfx) on N64: Two u32 words -- SoH is 16 #define N64_SIZEOF_VTX 0x10 // sizeof(Vtx) on N64: Same on both platforms @@ -40,9 +39,9 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); #define N64_MATRIX_STACK_SIZE (20 * 0x40) // sys_matrix.c: 20 * sizeof(MtxF) #define N64_TEXT_BOX_SIZE 0x2200 // Message_Init: Constant -#define N64_DO_ACTION_SIZE (3 * N64_SIZEOF_CHAR_PTR) // z_construct.c: 3 * sizeof(char*) -#define N64_ICON_ITEM_SIZE (0x1000 * 4) // z_construct.c: 0x1000 * 4 (buttonItems on N64) -#define N64_MAP_SEGMENT_SIZE (2 * N64_SIZEOF_CHAR_PTR) // z_map_exp.c: 2 * sizeof(char*) +#define N64_DO_ACTION_SIZE 0x480 // z_construct.c: 3 * DO_ACTION_TEX_SIZE (48x16 IA4 = 0x180 each) +#define N64_ICON_ITEM_SIZE (0x1000 * 4) // z_construct.c: 4 * ITEM_ICON_SIZE (32x32 RGBA32) +#define N64_MAP_SEGMENT_SIZE 0x1000 // z_map_exp.c: DMA target buffer for minimap textures // -------------------------------------------------------------------------------------------------------------------- // Skybox N64-unique THA consumers diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h index 1b4169f7c3d..9080dfee859 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h @@ -4,7 +4,6 @@ #ifdef __cplusplus extern "C" { - #endif // -------------------------------------------------------------------------------------------------------------------- From df57ad2735bba795611c583318e7b7833e0db820 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 8 May 2026 10:47:51 -0700 Subject: [PATCH 18/58] feat(restoration): Account for ovl_map_mark_data THA consumer in dungeons --- .../N64MemoryModel/ShadowArena/arena_sizing.c | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c index aabe150028a..9b6ed0056cf 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -43,6 +43,32 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); #define N64_ICON_ITEM_SIZE (0x1000 * 4) // z_construct.c: 4 * ITEM_ICON_SIZE (32x32 RGBA32) #define N64_MAP_SEGMENT_SIZE 0x1000 // z_map_exp.c: DMA target buffer for minimap textures +// -------------------------------------------------------------------------------------------------------------------- +// ovl_map_mark_data: N64-unique THA consumer for dungeon map marks +// +// On N64, the map mark data overlay (chest/boss/dungeon icons on the pause map) is loaded into THA +// via GAME_STATE_ALLOC in MapMark_Init. SoH compiles this data in directly. +// VRAM size from decomp linker map: 0x8085D460 - 0x80856900 = 0x6B60. +// Constant across OoT versions (dungeon map mark positions don't change). +// Only loaded for the 10 main dungeons (Deku Tree through Ice Cavern) and their boss rooms. +// -------------------------------------------------------------------------------------------------------------------- + +#define N64_MAP_MARK_DATA_VRAM_SIZE 0x6B60 + +static u32 GetMapMarkDataOverlaySize(PlayState* play) +{ + // Mirrors the condition in z_map_exp.c Map_Init: the dungeon case block's inner guard. + // Main dungeons: SCENE_DEKU_TREE (0x00) through SCENE_ICE_CAVERN (0x09) + // Boss rooms: SCENE_DEKU_TREE_BOSS (0x11) through SCENE_SHADOW_TEMPLE_BOSS (0x18) + if (play->sceneNum <= SCENE_ICE_CAVERN || + (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) + { + return N64_MAP_MARK_DATA_VRAM_SIZE; + } + + return 0; +} + // -------------------------------------------------------------------------------------------------------------------- // Skybox N64-unique THA consumers // @@ -305,7 +331,15 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) { const u32 elfMsg = GetElfMessageSize(play); total += elfMsg; - LUSLOG_INFO("[ArenaSizing] elfMsg=0x%X, total=0x%X, arena=0x%X", elfMsg, total, N64_THA_BUDGET - total); + LUSLOG_INFO("[ArenaSizing] elfMsg=0x%X", elfMsg); + } + + // Map mark data overlay (dungeons only) + { + const u32 mapMarkData = GetMapMarkDataOverlaySize(play); + total += mapMarkData; + LUSLOG_INFO("[ArenaSizing] mapMarkData=0x%X, total=0x%X, arena=0x%X", mapMarkData, total, + N64_THA_BUDGET - total); } if (total >= N64_THA_BUDGET) From c81d0bb056c9800aecfa8a3e10acbcee6d09e034 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 8 May 2026 14:46:08 -0700 Subject: [PATCH 19/58] feat(restoration): Load actor instance sizes from OTR --- OTRExporter | 2 +- .../N64MemoryModel/N64MemoryModel.cpp | 3 +- .../ShadowArena/N64SizeData.cpp | 54 +++ .../ShadowArena/N64SizeData.hpp | 6 +- .../ShadowArena/instance_sizes.c | 435 ------------------ .../ShadowArena/instance_sizes.h | 5 - 6 files changed, 60 insertions(+), 445 deletions(-) delete mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c delete mode 100644 soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h diff --git a/OTRExporter b/OTRExporter index 7710e4f87b5..6c223f05cef 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit 7710e4f87b52f76fcc55a1350df3b5f62ef0360e +Subproject commit 6c223f05cefc334c618b33f587366cde97cd3e7d diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 832bceff1b9..8175f5963f8 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -8,7 +8,6 @@ extern "C" { #include "ShadowArena/N64SizeData.hpp" #include "ShadowArena/shadow_arena.h" #include "ShadowArena/arena_sizing.h" -#include "ShadowArena/instance_sizes.h" } #define CVAR_NAME CVAR_ENHANCEMENT("N64MemoryModel") @@ -220,7 +219,7 @@ s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) return 1; } - const u32 instanceSize = gN64InstanceSizes[actorId]; + const u32 instanceSize = N64SizeData_GetActorInstanceSize(actorId); if (instanceSize == 0) { return 1; diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp index 15753d98372..054fb94de64 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp @@ -123,6 +123,48 @@ static void LoadEffectOverlaySizes() SPDLOG_INFO("[N64SizeData] Loaded {} effect overlay sizes.", sEffectOverlaySizes.size()); } +// -------------------------------------------------------------------------------------------------------------------- +// Actor instance sizes (misc/actor_instance_sizes) +// +// Format: u32 entryCount, then entryCount consecutive u32 values indexed by actor ID +// Each value is the N64 sizeof the actor's instance struct, read from ActorProfile.instanceSize. +// -------------------------------------------------------------------------------------------------------------------- + +static std::vector sActorInstanceSizes; +static bool sIsActorInstanceLoaded = false; + +static void LoadActorInstanceSizes() +{ + if (sIsActorInstanceLoaded) + { + return; + } + + sIsActorInstanceLoaded = true; + + const auto file = + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/actor_instance_sizes"); + if (!file || !file->IsLoaded) + { + SPDLOG_ERROR("[N64SizeData] Failed to load misc/actor_instance_sizes from OTR."); + return; + } + + auto stream = std::make_shared(file->Buffer->data(), file->Buffer->size()); + const auto reader = std::make_shared(stream); + reader->SetEndianness(Ship::Endianness::Big); + + const u32 entryCount = reader->ReadUInt32(); + sActorInstanceSizes.resize(entryCount); + + for (std::size_t i = 0; i < entryCount; ++i) + { + sActorInstanceSizes.at(i) = reader->ReadUInt32(); + } + + SPDLOG_INFO("[N64SizeData] Loaded {} actor instance sizes.", sActorInstanceSizes.size()); +} + // -------------------------------------------------------------------------------------------------------------------- // API // -------------------------------------------------------------------------------------------------------------------- @@ -163,3 +205,15 @@ extern "C" u32 N64SizeData_GetEffectOverlaySize(u16 effectType) return 0; } + +extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) +{ + LoadActorInstanceSizes(); + + if (actorId < sActorInstanceSizes.size()) + { + return sActorInstanceSizes.at(actorId); + } + + return 0; +} diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp index e1fe310263f..ccf25d3710c 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp @@ -9,10 +9,11 @@ extern "C" { // -------------------------------------------------------------------------------------------------------------------- // N64 size data loaded from the OTR archive. // -// All data is extracted per-version by OTRExporter During ROM extraction: +// All data is extracted per-version by OTRExporter during ROM extraction: // misc/dma_sizes DMA file VROM sizes keyed by filename // misc/actor_overlay_sizes Actor overlay VRAM sizes indexed by actor ID // misc/effect_overlay_sizes Effect overlay VRAM sizes indexed by effect type +// misc/actor_instance_sizes Actor instance struct sizes indexed by actor ID // // Each table is loaded lazily on first query and cached for the lifetime of the process. // -------------------------------------------------------------------------------------------------------------------- @@ -20,7 +21,8 @@ extern "C" { u32 N64SizeData_GetDmaFileSize(const char* name); u32 N64SizeData_GetActorOverlaySize(u16 actorId); u32 N64SizeData_GetEffectOverlaySize(u16 effectType); +u32 N64SizeData_GetActorInstanceSize(u16 actorId); #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c deleted file mode 100644 index 88d053b1568..00000000000 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.c +++ /dev/null @@ -1,435 +0,0 @@ -#include "instance_sizes.h" - -#include "global.h" - -const u32 gN64InstanceSizes[ACTOR_ID_MAX] = { - [ACTOR_PLAYER] = 0xA94, - [ACTOR_EN_TEST] = 0x0928, - [ACTOR_EN_GIRLA] = 0x01D4, - [ACTOR_EN_PART] = 0x015C, - [ACTOR_EN_LIGHT] = 0x0164, - [ACTOR_EN_DOOR] = 0x01D8, - [ACTOR_EN_BOX] = 0x01FC, - [ACTOR_BG_DY_YOSEIZO] = 0x38B4, - [ACTOR_BG_HIDAN_FIREWALL] = 0x01A0, - [ACTOR_EN_POH] = 0x03A8, - [ACTOR_EN_OKUTA] = 0x03BC, - [ACTOR_BG_YDAN_SP] = 0x0248, - [ACTOR_EN_BOM] = 0x0208, - [ACTOR_EN_WALLMAS] = 0x0314, - [ACTOR_EN_DODONGO] = 0x0728, - [ACTOR_EN_FIREFLY] = 0x0374, - [ACTOR_EN_HORSE] = 0x03FC, - [ACTOR_EN_ITEM00] = 0x1AC, - [ACTOR_EN_ARROW] = 0x0260, - [ACTOR_EN_ELF] = 0x02D0, - [ACTOR_EN_NIW] = 0x07B8, - [ACTOR_EN_TITE] = 0x0378, - [ACTOR_EN_REEBA] = 0x02DC, - [ACTOR_EN_PEEHAT] = 0x042C, - [ACTOR_EN_BUTTE] = 0x0268, - [ACTOR_EN_INSECT] = 0x032C, - [ACTOR_EN_FISH] = 0x0254, - [ACTOR_EN_HOLL] = 0x0154, - [ACTOR_EN_SCENE_CHANGE] = 0x0150, - [ACTOR_EN_ZF] = 0x0568, - [ACTOR_EN_HATA] = 0x027C, - [ACTOR_BOSS_DODONGO] = 0x1820, - [ACTOR_BOSS_GOMA] = 0x0B1C, - [ACTOR_EN_ZL1] = 0x020C, - [ACTOR_EN_VIEWER] = 0x05F8, - [ACTOR_EN_GOMA] = 0x03A4, - [ACTOR_BG_PUSHBOX] = 0x0168, - [ACTOR_EN_BUBBLE] = 0x0260, - [ACTOR_DOOR_SHUTTER] = 0x0178, - [ACTOR_EN_DODOJR] = 0x02C0, - [ACTOR_EN_BDFIRE] = 0x01E4, - [ACTOR_EN_BOOM] = 0x01FC, - [ACTOR_EN_TORCH2] = sizeof(Player), - [ACTOR_EN_BILI] = 0x0220, - [ACTOR_EN_TP] = 0x01D8, - [ACTOR_EN_ST] = 0x057C, - [ACTOR_EN_BW] = 0x032C, - [ACTOR_EN_A_OBJ] = 0x1C8, - [ACTOR_EN_EIYER] = 0x02D4, - [ACTOR_EN_RIVER_SOUND] = 0x0150, - [ACTOR_EN_HORSE_NORMAL] = 0x0328, - [ACTOR_EN_OSSAN] = 0x02D8, - [ACTOR_BG_TREEMOUTH] = 0x0170, - [ACTOR_BG_DODOAGO] = 0x0250, - [ACTOR_BG_HIDAN_DALM] = 0x02FC, - [ACTOR_BG_HIDAN_HROCK] = 0x0244, - [ACTOR_EN_HORSE_GANON] = 0x02A8, - [ACTOR_BG_HIDAN_ROCK] = 0x01C8, - [ACTOR_BG_HIDAN_RSEKIZOU] = 0x0308, - [ACTOR_BG_HIDAN_SEKIZOU] = 0x0314, - [ACTOR_BG_HIDAN_SIMA] = 0x020C, - [ACTOR_BG_HIDAN_SYOKU] = 0x016C, - [ACTOR_EN_XC] = 0x033C, - [ACTOR_BG_HIDAN_CURTAIN] = 0x01A4, - [ACTOR_BG_SPOT00_HANEBASI] = 0x0180, - [ACTOR_EN_MB] = 0x050C, - [ACTOR_EN_BOMBF] = 0x0210, - [ACTOR_EN_ZL2] = 0x0280, - [ACTOR_BG_HIDAN_FSLIFT] = 0x016C, - [ACTOR_EN_OE2] = 0x0194, - [ACTOR_BG_YDAN_HASI] = 0x016C, - [ACTOR_BG_YDAN_MARUTA] = 0x0244, - [ACTOR_BOSS_GANONDROF] = 0x0578, - [ACTOR_EN_AM] = 0x038C, - [ACTOR_EN_DEKUBABA] = 0x0418, - [ACTOR_EN_M_FIRE1] = 0x019C, - [ACTOR_EN_M_THUNDER] = 0x01CC, - [ACTOR_BG_DDAN_JD] = 0x0170, - [ACTOR_BG_BREAKWALL] = 0x01F0, - [ACTOR_EN_JJ] = 0x0314, - [ACTOR_EN_HORSE_ZELDA] = 0x02A8, - [ACTOR_BG_DDAN_KD] = 0x01C8, - [ACTOR_DOOR_WARP1] = 0x01F0, - [ACTOR_OBJ_SYOKUDAI] = 0x01FC, - [ACTOR_ITEM_B_HEART] = 0x016C, - [ACTOR_EN_DEKUNUTS] = 0x0314, - [ACTOR_BG_MENKURI_KAITEN] = 0x0164, - [ACTOR_BG_MENKURI_EYE] = 0x01B0, - [ACTOR_EN_VALI] = 0x0448, - [ACTOR_BG_MIZU_MOVEBG] = 0x0188, - [ACTOR_BG_MIZU_WATER] = 0x0160, - [ACTOR_ARMS_HOOK] = 0x0218, - [ACTOR_EN_FHG] = 0x0294, - [ACTOR_BG_MORI_HINERI] = 0x016C, - [ACTOR_EN_BB] = 0x0328, - [ACTOR_BG_TOKI_HIKARI] = 0x0154, - [ACTOR_EN_YUKABYUN] = 0x01A0, - [ACTOR_BG_TOKI_SWD] = 0x019C, - [ACTOR_EN_FHG_FIRE] = 0x0204, - [ACTOR_BG_MJIN] = 0x016C, - [ACTOR_BG_HIDAN_KOUSI] = 0x016C, - [ACTOR_DOOR_TOKI] = 0x0168, - [ACTOR_BG_HIDAN_HAMSTEP] = 0x0248, - [ACTOR_EN_BIRD] = 0x01DC, - [ACTOR_EN_WOOD02] = 0x01A4, - [ACTOR_EN_LIGHTBOX] = 0x0164, - [ACTOR_EN_PU_BOX] = 0x0168, - [ACTOR_EN_TRAP] = 0x01EC, - [ACTOR_EN_AROW_TRAP] = 0x0154, - [ACTOR_EN_VASE] = 0x014C, - [ACTOR_EN_TA] = 0x02E8, - [ACTOR_EN_TK] = 0x0770, - [ACTOR_BG_MORI_BIGST] = 0x016C, - [ACTOR_BG_MORI_ELEVATOR] = 0x0174, - [ACTOR_BG_MORI_KAITENKABE] = 0x0188, - [ACTOR_BG_MORI_RAKKATENJO] = 0x0178, - [ACTOR_EN_VM] = 0x03B4, - [ACTOR_DEMO_EFFECT] = 0x01A0, - [ACTOR_DEMO_KANKYO] = 0x0604, - [ACTOR_BG_HIDAN_FWBIG] = 0x01A0, - [ACTOR_EN_FLOORMAS] = 0x0314, - [ACTOR_EN_HEISHI1] = 0x02AC, - [ACTOR_EN_RD] = 0x036C, - [ACTOR_EN_PO_SISTERS] = 0x0338, - [ACTOR_BG_HEAVY_BLOCK] = 0x0178, - [ACTOR_BG_PO_EVENT] = 0x0248, - [ACTOR_OBJ_MURE] = 0x01AC, - [ACTOR_EN_SW] = 0x04D8, - [ACTOR_BOSS_FD] = 0x43A0, - [ACTOR_OBJECT_KANKYO] = 0x1660, - [ACTOR_EN_DU] = 0x021C, - [ACTOR_EN_FD] = 0x31E0, - [ACTOR_EN_HORSE_LINK_CHILD] = 0x02A4, - [ACTOR_DOOR_ANA] = 0x019C, - [ACTOR_BG_SPOT02_OBJECTS] = 0x0174, - [ACTOR_BG_HAKA] = 0x0168, - [ACTOR_MAGIC_WIND] = 0x0174, - [ACTOR_MAGIC_FIRE] = 0x01AC, - [ACTOR_EN_RU1] = 0x039C, - [ACTOR_BOSS_FD2] = 0x167C, - [ACTOR_EN_FD_FIRE] = 0x01AC, - [ACTOR_EN_DH] = 0x0324, - [ACTOR_EN_DHA] = 0x0360, - [ACTOR_EN_RL] = 0x01AC, - [ACTOR_EN_ENCOUNT1] = 0x0170, - [ACTOR_DEMO_DU] = 0x01B4, - [ACTOR_DEMO_IM] = 0x02FC, - [ACTOR_DEMO_TRE_LGT] = 0x017C, - [ACTOR_EN_FW] = 0x0700, - [ACTOR_BG_VB_SIMA] = 0x017C, - [ACTOR_EN_VB_BALL] = 0x01B4, - [ACTOR_BG_HAKA_MEGANE] = 0x016C, - [ACTOR_BG_HAKA_MEGANEBG] = 0x016C, - [ACTOR_BG_HAKA_SHIP] = 0x0178, - [ACTOR_BG_HAKA_SGAMI] = 0x0338, - [ACTOR_EN_HEISHI2] = 0x03E4, - [ACTOR_EN_ENCOUNT2] = 0x0A20, - [ACTOR_EN_FIRE_ROCK] = 0x01E0, - [ACTOR_EN_BROB] = 0x02C0, - [ACTOR_MIR_RAY] = 0x02B0, - [ACTOR_BG_SPOT09_OBJ] = 0x0168, - [ACTOR_BG_SPOT18_OBJ] = 0x016C, - [ACTOR_BOSS_VA] = 0x03B8, - [ACTOR_BG_HAKA_TUBO] = 0x0204, - [ACTOR_BG_HAKA_TRAP] = 0x029C, - [ACTOR_BG_HAKA_HUTA] = 0x016C, - [ACTOR_BG_HAKA_ZOU] = 0x01B8, - [ACTOR_BG_SPOT17_FUNEN] = 0x014C, - [ACTOR_EN_SYATEKI_ITM] = 0x01D8, - [ACTOR_EN_SYATEKI_MAN] = 0x022C, - [ACTOR_EN_TANA] = 0x014C, - [ACTOR_EN_NB] = 0x0328, - [ACTOR_BOSS_MO] = 0x158C, - [ACTOR_EN_SB] = 0x0208, - [ACTOR_EN_BIGOKUTA] = 0x0384, - [ACTOR_EN_KAREBABA] = 0x0290, - [ACTOR_BG_BDAN_OBJECTS] = 0x01BC, - [ACTOR_DEMO_SA] = 0x01B4, - [ACTOR_DEMO_GO] = 0x01A0, - [ACTOR_EN_IN] = 0x03A8, - [ACTOR_EN_TR] = 0x02E8, - [ACTOR_BG_SPOT16_BOMBSTONE] = 0x0218, - [ACTOR_BG_HIDAN_KOWARERUKABE] = 0x01C4, - [ACTOR_BG_BOMBWALL] = 0x02A4, - [ACTOR_BG_SPOT08_ICEBLOCK] = 0x019C, - [ACTOR_EN_RU2] = 0x0314, - [ACTOR_OBJ_DEKUJR] = 0x01A4, - [ACTOR_BG_MIZU_UZU] = 0x0168, - [ACTOR_BG_SPOT06_OBJECTS] = 0x01D0, - [ACTOR_BG_ICE_OBJECTS] = 0x0174, - [ACTOR_BG_HAKA_WATER] = 0x0154, - [ACTOR_EN_MA2] = 0x0284, - [ACTOR_EN_BOM_CHU] = 0x01E4, - [ACTOR_EN_HORSE_GAME_CHECK] = 0x01A4, - [ACTOR_BOSS_TW] = 0x06B4, - [ACTOR_EN_RR] = 0x23C4, - [ACTOR_EN_BA] = 0x03C0, - [ACTOR_EN_BX] = 0x0298, - [ACTOR_EN_ANUBICE] = 0x0314, - [ACTOR_EN_ANUBICE_FIRE] = 0x01F4, - [ACTOR_BG_MORI_HASHIGO] = 0x01D0, - [ACTOR_BG_MORI_HASHIRA4] = 0x016C, - [ACTOR_BG_MORI_IDOMIZU] = 0x0160, - [ACTOR_BG_SPOT16_DOUGHNUT] = 0x0154, - [ACTOR_BG_BDAN_SWITCH] = 0x01E0, - [ACTOR_EN_MA1] = 0x0210, - [ACTOR_BOSS_GANON] = 0x071C, - [ACTOR_BOSS_SST] = 0x0A98, - [ACTOR_EN_NY] = 0x02B8, - [ACTOR_EN_FR] = 0x03C4, - [ACTOR_ITEM_SHIELD] = 0x020C, - [ACTOR_BG_ICE_SHELTER] = 0x0204, - [ACTOR_EN_ICE_HONO] = 0x01BC, - [ACTOR_ITEM_OCARINA] = 0x0154, - [ACTOR_MAGIC_DARK] = 0x0164, - [ACTOR_DEMO_6K] = 0x0294, - [ACTOR_EN_ANUBICE_TAG] = 0x0158, - [ACTOR_BG_HAKA_GATE] = 0x0174, - [ACTOR_BG_SPOT15_SAKU] = 0x0180, - [ACTOR_BG_JYA_GOROIWA] = 0x01BC, - [ACTOR_BG_JYA_ZURERUKABE] = 0x0170, - [ACTOR_BG_JYA_COBRA] = 0x11A4, - [ACTOR_BG_JYA_KANAAMI] = 0x016C, - [ACTOR_FISHING] = 0x0550, - [ACTOR_OBJ_OSHIHIKI] = 0x01D4, - [ACTOR_BG_GATE_SHUTTER] = 0x017C, - [ACTOR_EFF_DUST] = 0x0568, - [ACTOR_BG_SPOT01_FUSYA] = 0x0160, - [ACTOR_BG_SPOT01_IDOHASHIRA] = 0x0174, - [ACTOR_BG_SPOT01_IDOMIZU] = 0x0158, - [ACTOR_BG_PO_SYOKUDAI] = 0x01B0, - [ACTOR_BG_GANON_OTYUKA] = 0x018C, - [ACTOR_BG_SPOT15_RRBOX] = 0x0184, - [ACTOR_BG_UMAJUMP] = 0x0164, - [ACTOR_ARROW_FIRE] = 0x016C, - [ACTOR_ARROW_ICE] = 0x016C, - [ACTOR_ARROW_LIGHT] = 0x016C, - [ACTOR_ITEM_ETCETERA] = 0x0160, - [ACTOR_OBJ_KIBAKO] = 0x019C, - [ACTOR_OBJ_TSUBO] = 0x01A0, - [ACTOR_EN_WONDER_ITEM] = 0x01D0, - [ACTOR_EN_IK] = 0x04DC, - [ACTOR_DEMO_IK] = 0x01B4, - [ACTOR_EN_SKJ] = 0x0300, - [ACTOR_EN_SKJNEEDLE] = 0x01E8, - [ACTOR_EN_G_SWITCH] = 0x12F8, - [ACTOR_DEMO_EXT] = 0x0184, - [ACTOR_DEMO_SHD] = 0x0154, - [ACTOR_EN_DNS] = 0x02C8, - [ACTOR_ELF_MSG] = 0x0150, - [ACTOR_EN_HONOTRAP] = 0x0244, - [ACTOR_EN_TUBO_TRAP] = 0x01AC, - [ACTOR_OBJ_ICE_POLY] = 0x01EC, - [ACTOR_BG_SPOT03_TAKI] = 0x0178, - [ACTOR_BG_SPOT07_TAKI] = 0x0168, - [ACTOR_EN_FZ] = 0x0BD4, - [ACTOR_EN_PO_RELAY] = 0x02DC, - [ACTOR_BG_RELAY_OBJECTS] = 0x016C, - [ACTOR_EN_DIVING_GAME] = 0x0398, - [ACTOR_EN_KUSA] = 0x01A0, - [ACTOR_OBJ_BEAN] = 0x01F8, - [ACTOR_OBJ_BOMBIWA] = 0x0198, - [ACTOR_OBJ_SWITCH] = 0x0258, - [ACTOR_OBJ_ELEVATOR] = 0x0174, - [ACTOR_OBJ_LIFT] = 0x0170, - [ACTOR_OBJ_HSBLOCK] = 0x0168, - [ACTOR_EN_OKARINA_TAG] = 0x0160, - [ACTOR_EN_YABUSAME_MARK] = 0x0210, - [ACTOR_EN_GOROIWA] = 0x01D4, - [ACTOR_EN_EX_RUPPY] = 0x0164, - [ACTOR_EN_TORYO] = 0x02E0, - [ACTOR_EN_DAIKU] = 0x034C, - [ACTOR_EN_NWC] = 0x0734, - [ACTOR_EN_BLKOBJ] = 0x016C, - [ACTOR_ITEM_INBOX] = 0x0150, - [ACTOR_EN_GE1] = 0x02BC, - [ACTOR_OBJ_BLOCKSTOP] = 0x014C, - [ACTOR_EN_SDA] = 0x014C, - [ACTOR_EN_CLEAR_TAG] = 0x0204, - [ACTOR_EN_NIW_LADY] = 0x02FC, - [ACTOR_EN_GM] = 0x02D0, - [ACTOR_EN_MS] = 0x0250, - [ACTOR_EN_HS] = 0x02B0, - [ACTOR_BG_INGATE] = 0x0168, - [ACTOR_EN_KANBAN] = 0x01EC, - [ACTOR_EN_HEISHI3] = 0x02C8, - [ACTOR_EN_SYATEKI_NIW] = 0x0460, - [ACTOR_EN_ATTACK_NIW] = 0x02E8, - [ACTOR_BG_SPOT01_IDOSOKO] = 0x0168, - [ACTOR_EN_SA] = 0x02EC, - [ACTOR_EN_WONDER_TALK] = 0x0168, - [ACTOR_BG_GJYO_BRIDGE] = 0x0168, - [ACTOR_EN_DS] = 0x01F0, - [ACTOR_EN_MK] = 0x0288, - [ACTOR_EN_BOM_BOWL_MAN] = 0x0264, - [ACTOR_EN_BOM_BOWL_PIT] = 0x3704, - [ACTOR_EN_OWL] = 0x0414, - [ACTOR_EN_ISHI] = 0x019C, - [ACTOR_OBJ_HANA] = 0x0198, - [ACTOR_OBJ_LIGHTSWITCH] = 0x01C4, - [ACTOR_OBJ_MURE2] = 0x0188, - [ACTOR_EN_GO] = 0x06C8, - [ACTOR_EN_FU] = 0x02B0, - [ACTOR_EN_CHANGER] = 0x016C, - [ACTOR_BG_JYA_MEGAMI] = 0x033C, - [ACTOR_BG_JYA_LIFT] = 0x016C, - [ACTOR_BG_JYA_BIGMIRROR] = 0x0174, - [ACTOR_BG_JYA_BOMBCHUIWA] = 0x01B8, - [ACTOR_BG_JYA_AMISHUTTER] = 0x0168, - [ACTOR_BG_JYA_BOMBIWA] = 0x01C8, - [ACTOR_BG_SPOT18_BASKET] = 0x021C, - [ACTOR_EN_GANON_ORGAN] = 0x0150, - [ACTOR_EN_SIOFUKI] = 0x01A0, - [ACTOR_EN_STREAM] = 0x0158, - [ACTOR_EN_MM] = 0x0320, - [ACTOR_EN_KO] = 0x0324, - [ACTOR_EN_KZ] = 0x02D8, - [ACTOR_EN_WEATHER_TAG] = 0x0154, - [ACTOR_BG_SST_FLOOR] = 0x016C, - [ACTOR_EN_ANI] = 0x02B4, - [ACTOR_EN_EX_ITEM] = 0x0184, - [ACTOR_BG_JYA_IRONOBJ] = 0x01B4, - [ACTOR_EN_JS] = 0x0290, - [ACTOR_EN_JSJUTAN] = 0x0178, - [ACTOR_EN_CS] = 0x0344, - [ACTOR_EN_MD] = 0x0324, - [ACTOR_EN_HY] = 0x0334, - [ACTOR_EN_GANON_MANT] = 0x1708, - [ACTOR_EN_OKARINA_EFFECT] = 0x0154, - [ACTOR_EN_MAG] = 0xE328, - [ACTOR_DOOR_GERUDO] = 0x016C, - [ACTOR_ELF_MSG2] = 0x0150, - [ACTOR_DEMO_GT] = 0x01A8, - [ACTOR_EN_PO_FIELD] = 0x02DC, - [ACTOR_EFC_ERUPC] = 0x18CC, - [ACTOR_BG_ZG] = 0x016C, - [ACTOR_EN_HEISHI4] = 0x0308, - [ACTOR_EN_ZL3] = 0x0420, - [ACTOR_BOSS_GANON2] = 0x08E4, - [ACTOR_EN_KAKASI] = 0x020C, - [ACTOR_EN_TAKARA_MAN] = 0x0238, - [ACTOR_OBJ_MAKEOSHIHIKI] = 0x014C, - [ACTOR_OCEFF_SPOT] = 0x0180, - [ACTOR_END_TITLE] = 0x0150, - [ACTOR_EN_TORCH] = 0x014C, - [ACTOR_DEMO_EC] = 0x01A8, - [ACTOR_SHOT_SUN] = 0x01A8, - [ACTOR_EN_DY_EXTRA] = 0x0174, - [ACTOR_EN_WONDER_TALK2] = 0x0170, - [ACTOR_EN_GE2] = 0x030C, - [ACTOR_OBJ_ROOMTIMER] = 0x0154, - [ACTOR_EN_SSH] = 0x05D4, - [ACTOR_EN_STH] = 0x02BC, - [ACTOR_OCEFF_WIPE] = 0x0150, - [ACTOR_OCEFF_STORM] = 0x0158, - [ACTOR_EN_WEIYER] = 0x02D0, - [ACTOR_BG_SPOT05_SOKO] = 0x016C, - [ACTOR_BG_JYA_1FLIFT] = 0x01BC, - [ACTOR_BG_JYA_HAHENIRON] = 0x01B4, - [ACTOR_BG_SPOT12_GATE] = 0x016C, - [ACTOR_BG_SPOT12_SAKU] = 0x016C, - [ACTOR_EN_HINTNUTS] = 0x0260, - [ACTOR_EN_NUTSBALL] = 0x01A0, - [ACTOR_BG_SPOT00_BREAK] = 0x0164, - [ACTOR_EN_SHOPNUTS] = 0x02BC, - [ACTOR_EN_IT] = 0x019C, - [ACTOR_EN_GELDB] = 0x04E4, - [ACTOR_OCEFF_WIPE2] = 0x0150, - [ACTOR_OCEFF_WIPE3] = 0x0150, - [ACTOR_EN_NIW_GIRL] = 0x02FC, - [ACTOR_EN_DOG] = 0x0290, - [ACTOR_EN_SI] = 0x01A0, - [ACTOR_BG_SPOT01_OBJECTS2] = 0x0180, - [ACTOR_OBJ_COMB] = 0x01B4, - [ACTOR_BG_SPOT11_BAKUDANKABE] = 0x01B0, - [ACTOR_OBJ_KIBAKO2] = 0x01B8, - [ACTOR_EN_DNT_DEMO] = 0x0200, - [ACTOR_EN_DNT_JIJI] = 0x02A8, - [ACTOR_EN_DNT_NOMAL] = 0x0360, - [ACTOR_EN_GUEST] = 0x0310, - [ACTOR_BG_BOM_GUARD] = 0x0178, - [ACTOR_EN_HS2] = 0x02B0, - [ACTOR_DEMO_KEKKAI] = 0x01FC, - [ACTOR_BG_SPOT08_BAKUDANKABE] = 0x0244, - [ACTOR_BG_SPOT17_BAKUDANKABE] = 0x0164, - [ACTOR_OBJ_MURE3] = 0x0170, - [ACTOR_EN_TG] = 0x020C, - [ACTOR_EN_MU] = 0x024C, - [ACTOR_EN_GO2] = 0x05A0, - [ACTOR_EN_WF] = 0x04DC, - [ACTOR_EN_SKB] = 0x0344, - [ACTOR_DEMO_GJ] = 0x0278, - [ACTOR_DEMO_GEFF] = 0x0168, - [ACTOR_BG_GND_FIREMEIRO] = 0x0178, - [ACTOR_BG_GND_DARKMEIRO] = 0x0170, - [ACTOR_BG_GND_SOULMEIRO] = 0x01A0, - [ACTOR_BG_GND_NISEKABE] = 0x014C, - [ACTOR_BG_GND_ICEBLOCK] = 0x0174, - [ACTOR_EN_GB] = 0x0438, - [ACTOR_EN_GS] = 0x0208, - [ACTOR_BG_MIZU_BWALL] = 0x02BC, - [ACTOR_BG_MIZU_SHUTTER] = 0x0190, - [ACTOR_EN_DAIKU_KAKARIKO] = 0x0308, - [ACTOR_BG_BOWL_WALL] = 0x0188, - [ACTOR_EN_WALL_TUBO] = 0x0170, - [ACTOR_EN_PO_DESERT] = 0x0284, - [ACTOR_EN_CROW] = 0x0298, - [ACTOR_DOOR_KILLER] = 0x0284, - [ACTOR_BG_SPOT11_OASIS] = 0x0154, - [ACTOR_BG_SPOT18_FUTA] = 0x0164, - [ACTOR_BG_SPOT18_SHUTTER] = 0x0168, - [ACTOR_EN_MA3] = 0x0284, - [ACTOR_EN_COW] = 0x0280, - [ACTOR_BG_ICE_TURARA] = 0x01B8, - [ACTOR_BG_ICE_SHUTTER] = 0x0168, - [ACTOR_EN_KAKASI2] = 0x01F8, - [ACTOR_EN_KAKASI3] = 0x020C, - [ACTOR_OCEFF_WIPE4] = 0x0150, - [ACTOR_EN_EG] = 0x0154, - [ACTOR_BG_MENKURI_NISEKABE] = 0x014C, - [ACTOR_EN_ZO] = 0x06A8, - [ACTOR_OBJ_MAKEKINSUTA] = 0x0154, - [ACTOR_EN_GE3] = 0x0314, - [ACTOR_OBJ_TIMEBLOCK] = 0x017C, - [ACTOR_OBJ_HAMISHI] = 0x01A8, - [ACTOR_EN_ZL4] = 0x02F0, - [ACTOR_EN_MM2] = 0x02BC, - [ACTOR_BG_JYA_BLOCK] = 0x0164, - [ACTOR_OBJ_WARP2BLOCK] = 0x0178, -}; diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h deleted file mode 100644 index 3bb685a8ad8..00000000000 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/instance_sizes.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -extern const u32 gN64InstanceSizes[]; From fce6d5868c44a52c3196effa6ff10cfad520d04d Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 8 May 2026 18:05:14 -0700 Subject: [PATCH 20/58] feat(restoration): ALIGN16 each THA consumer individually --- .../N64MemoryModel/N64MemoryModel.cpp | 41 +++++++++++++++ .../N64MemoryModel/N64MemoryModel.hpp | 3 ++ .../ShadowArena/N64SizeData.hpp | 2 +- .../N64MemoryModel/ShadowArena/arena_sizing.c | 52 +++++++++++++------ soh/src/code/z_actor.c | 1 + 5 files changed, 81 insertions(+), 18 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 8175f5963f8..de832bc1978 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -8,6 +8,7 @@ extern "C" { #include "ShadowArena/N64SizeData.hpp" #include "ShadowArena/shadow_arena.h" #include "ShadowArena/arena_sizing.h" +#include "global.h" } #define CVAR_NAME CVAR_ENHANCEMENT("N64MemoryModel") @@ -38,6 +39,8 @@ static std::unordered_map sShadowMap; // Diagnostics // -------------------------------------------------------------------------------------------------------------------- +static s32 sTraceEnabled = 0; + static void LogShadowState(const char* context) { u32 maxFree = 0; @@ -49,6 +52,23 @@ static void LogShadowState(const char* context) maxFree); } +static void TraceAlloc(const char* tag, u32 id, u32 size) +{ + if (sTraceEnabled) + { + u32 consumed = ((size + 0xF) & ~0xF) + SHADOW_NODE_SIZE; + SPDLOG_INFO("[N64Trace] +{} id=0x{:X} sz=0x{:X} cost=0x{:X}", tag, id, size, consumed); + } +} + +static void TraceFree(const char* tag, u32 id) +{ + if (sTraceEnabled) + { + SPDLOG_INFO("[N64Trace] -{} id=0x{:X}", tag, id); + } +} + // -------------------------------------------------------------------------------------------------------------------- // Lifecycle // -------------------------------------------------------------------------------------------------------------------- @@ -103,6 +123,8 @@ void N64Mem_Reset(PlayState* play) SPDLOG_INFO("[N64MemoryModel] Shadow arena size=0x{:X} for scene 0x{:X}", shadowArenaSize, play->sceneNum); ShadowArena_Init(&sShadow, shadowArenaSize); + + sTraceEnabled = (play->sceneNum == SCENE_GRAVEYARD); } } @@ -116,6 +138,14 @@ ShadowArena* N64Mem_GetShadowArena() return &sShadow; } +void N64Mem_LogState(const char* context) +{ + if (sIsActive) + { + LogShadowState(context); + } +} + // -------------------------------------------------------------------------------------------------------------------- // Actor overlays // -------------------------------------------------------------------------------------------------------------------- @@ -178,6 +208,7 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) } sOverlayShadows[actorId] = shadow; + TraceAlloc("ovl", actorId, overlaySize); return 1; } @@ -201,6 +232,7 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) ShadowArena_Free(&sShadow, sOverlayShadows[actorId]); sOverlayShadows[actorId] = SHADOW_NULL; + TraceFree("ovl", actorId); } // -------------------------------------------------------------------------------------------------------------------- @@ -233,6 +265,7 @@ s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) } sShadowMap[realPtr] = shadow; + TraceAlloc("inst", actorId, instanceSize); return 1; } @@ -249,6 +282,12 @@ void N64Mem_FreeInstance(void* realPtr) return; } + if (sTraceEnabled) + { + Actor* actor = (Actor*)realPtr; + TraceFree("inst", actor->id); + } + ShadowArena_Free(&sShadow, i->second); sShadowMap.erase(i); } @@ -272,6 +311,7 @@ s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) } sShadowMap[realPtr] = shadow; + TraceAlloc("sub", 0, n64Size); return 1; } @@ -329,6 +369,7 @@ s32 N64Mem_AllocEffectOverlay(s32 type) } sEffectOverlayShadows[type] = shadow; + TraceAlloc("efx", type, overlaySize); return 1; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index b3c58705011..d1fdbb6ad08 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -32,6 +32,9 @@ s32 N64Mem_IsActive(void); // Returns the shadow arena pointer for debug visualization. Only valid when N64Mem_IsActive() is true. struct ShadowArena* N64Mem_GetShadowArena(void); +// Log the current shadow arena state (alloc/free/largest) with the given context label. +void N64Mem_LogState(const char* context); + // -------------------------------------------------------------------------------------------------------------------- // Actor overlays: Keyed by actor ID, shadow-only allocations. // diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp index ccf25d3710c..a822cdcf10d 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp @@ -25,4 +25,4 @@ u32 N64SizeData_GetActorInstanceSize(u16 actorId); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c index 9b6ed0056cf..3e01a786f70 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -275,20 +275,25 @@ const VersionConstants gVersionConstantsNtsc12 = { u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) { + // Each THA consumer is allocated via GAME_STATE_ALLOC -> THA_AllocTailAlign16, which consumes ALIGN16(size) bytes. + // We must align each consumer individually before summing; aligning the sum would under-count when individual + // sizes are not 16-byte aligned (common for DMA file sizes). BgCheck is the exception -- its internal allocations + // use mixed alignment, but the tblMax computation absorbs the internal waste, so the simplified total + // (memSize - sizeof(CollisionContext)) is used as-is. + u32 total = 0; // Per-version constants (N64-unique THA consumers SoH skips) - total += vc->kaleidoOverlayVramSize; - total += vc->parameterStaticSize; + total += ALIGN16(vc->kaleidoOverlayVramSize); + total += ALIGN16(vc->parameterStaticSize); // Fixed consumers - total += N64_MATRIX_STACK_SIZE; - total += 0x55 * vc->effectSsSize; - total += N64_TEXT_BOX_SIZE; - total += N64_DO_ACTION_SIZE; - total += N64_ICON_ITEM_SIZE; - total += N64_MAP_SEGMENT_SIZE; - + total += ALIGN16(N64_MATRIX_STACK_SIZE); + total += ALIGN16(0x55 * vc->effectSsSize); + total += ALIGN16(N64_TEXT_BOX_SIZE); + total += ALIGN16(N64_DO_ACTION_SIZE); + total += ALIGN16(N64_ICON_ITEM_SIZE); + total += ALIGN16(N64_MAP_SEGMENT_SIZE); const u32 fixed = total; LUSLOG_INFO("[ArenaSizing] fixed=0x%X (kaleido=0x%X, param=0x%X)", fixed, vc->kaleidoOverlayVramSize, @@ -299,28 +304,41 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) const u32 objBank = GetObjectBankSize(play); const u32 sceneFile = N64SizeData_GetDmaFileSize(play->loadedScene->sceneFile.fileName); const u32 roomBuf = GetMaxRoomSize(play); - total += objBank; - total += sceneFile; - total += roomBuf; + total += ALIGN16(objBank); + total += ALIGN16(sceneFile); + total += ALIGN16(roomBuf); LUSLOG_INFO("[ArenaSizing] objBank=0x%X, sceneFile=0x%X, roomBuf=0x%x", (u32)objBank, (u32)sceneFile, (u32)roomBuf); } + // ---------------------------------------------------------------------------------------------------------------- // Skybox + // + // On N64 these are 3-5 separate GAME_STATE_ALLOC calls (tex0, tex1, palette, dList, vtx), each independently + // aligned. GetN64SkyboxTextureSize returns the combined raw size of the texture + palette allocations. + // For NORMAL_SKY this is 2×0xC000 + 2×0x100, all already 16-aligned, so ALIGN16 is a no-op here. The dList and + // vtx are separate allocations. + // ---------------------------------------------------------------------------------------------------------------- { u32 dListSize = 0; u32 vtxSize = 0; u32 texSize = 0; GetSkyboxDlistAndVtxSize(play->skyboxId, &dListSize, &vtxSize); texSize = GetN64SkyboxTextureSize(play->skyboxId); - total += dListSize; - total += vtxSize; - total += texSize; + total += ALIGN16(dListSize); + total += ALIGN16(vtxSize); + total += ALIGN16(texSize); LUSLOG_INFO("[ArenaSizing] skyboxId=%d, dList=0x%X, vtx=0x%X, tex=0x%X", play->skyboxId, dListSize, vtxSize, texSize); } + // ---------------------------------------------------------------------------------------------------------------- // BgCheck + // + // Uses the algebraic simplification (memSize - sizeof(CollisionContext)). BgCheck's internal allocations use + // THA_AllocTailAlign with 2-byte alignment, and the tblMax formula absorbs internal alignment waste. No separate + // ALIGN16 needed here. + // ---------------------------------------------------------------------------------------------------------------- { const u32 bgCheck = GetBgCheckThaTotal(play); total += bgCheck; @@ -330,14 +348,14 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) // Elf message { const u32 elfMsg = GetElfMessageSize(play); - total += elfMsg; + total += ALIGN16(elfMsg); LUSLOG_INFO("[ArenaSizing] elfMsg=0x%X", elfMsg); } // Map mark data overlay (dungeons only) { const u32 mapMarkData = GetMapMarkDataOverlaySize(play); - total += mapMarkData; + total += ALIGN16(mapMarkData); LUSLOG_INFO("[ArenaSizing] mapMarkData=0x%X, total=0x%X, arena=0x%X", mapMarkData, total, N64_THA_BUDGET - total); } diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 29fc3995c75..a4419c44fa3 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2597,6 +2597,7 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { } play->numSetupActors = 0; GameInteractor_ExecuteOnSceneSpawnActors(); + N64Mem_LogState("room actors spawned"); } if (actorCtx->unk_02 != 0) { From 1f342ea3bd2c2207a57104d89e46e708ef1bd165 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 8 May 2026 18:53:29 -0700 Subject: [PATCH 21/58] feat(restoration): elfMsg size from DMA blob --- .../N64MemoryModel/N64MemoryModel.cpp | 13 ++++++++- .../N64MemoryModel/N64MemoryModel.hpp | 7 +++++ .../N64MemoryModel/ShadowArena/arena_sizing.c | 28 +++++++++++++------ soh/soh/z_scene_otr.cpp | 5 ++++ 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index de832bc1978..8f2d1b06605 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -22,6 +22,7 @@ extern "C" { static s32 sIsActive = 0; static ShadowArena sShadow; static u32 sSohThaRemainder = 0; +static u8 sElfMsgNum = 0; // Shadow offsets for actor overlays, keyed by actor ID. SHADOW_NULL means no shadow allocation exists for that type. static u32 sOverlayShadows[ACTOR_ID_MAX]; @@ -56,7 +57,7 @@ static void TraceAlloc(const char* tag, u32 id, u32 size) { if (sTraceEnabled) { - u32 consumed = ((size + 0xF) & ~0xF) + SHADOW_NODE_SIZE; + u32 consumed = (size + 0xF & ~0xF) + SHADOW_NODE_SIZE; SPDLOG_INFO("[N64Trace] +{} id=0x{:X} sz=0x{:X} cost=0x{:X}", tag, id, size, consumed); } } @@ -78,6 +79,16 @@ void N64Mem_StoreThaRemainder(u32 sohRemainder) sSohThaRemainder = sohRemainder; } +void N64Mem_StoreElfMsgNum(u8 num) +{ + sElfMsgNum = num; +} + +u8 N64Mem_GetElfMsgNum() +{ + return sElfMsgNum; +} + void N64Mem_Reset(PlayState* play) { // Log shadow state before teardown for per-scene diagnostics. diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index d1fdbb6ad08..7f01d743785 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -21,6 +21,13 @@ extern "C" { // THA_GetRemaining. void N64Mem_StoreThaRemainder(u32 sohRemainder); +// Store which elf message file was loaded by Scene_CommandSpecialFiles (1 = elf_message_field, 2 = elf_message_ydan, +// 0 = none). Called from z_scene.c during scene command processing. +void N64Mem_StoreElfMsgNum(u8 num); + +// Returns the elf message number stored by N64Mem_StoreElfMsgNum. +u8 N64Mem_GetElfMsgNum(void); + // Reset shadow state, compute N64-equivalent arena size from stored THA remainder minus N64-specific consumers // (room buffers, etc.), and reread CVar. Call from Play_Init after ZeldaArena_Init. struct PlayState; diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c index 3e01a786f70..694ddba5799 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -1,5 +1,6 @@ #include "arena_sizing.h" #include "N64SizeData.hpp" +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" #include "global.h" @@ -203,19 +204,29 @@ static u32 GetObjectBankSize(PlayState* play) // Elf message size // // On N64, loaded via Play_LoadFile into THA. SoH loads from OTR via ResourceManager. Only present if the scene's -// SpecialFiles command has cUpElfMsgNum != 0. +// SpecialFiles command has cUpElfMsgNum != 0. The file name is determined by cUpElfMsgNum (1-indexed). // -------------------------------------------------------------------------------------------------------------------- +static const char* sElfMsgDmaNames[] = { + "elf_message_field", + "elf_message_ydan", +}; + static u32 GetElfMessageSize(PlayState* play) { - if (play->cUpElfMsgs != NULL) + if (play->cUpElfMsgs == NULL) { - // elf_message_field is 0x70, elf_message_ydan is 0x10 on NTSC 1.2. - // #TODO: Per-version data. - return 0x80; + return 0; } - return 0; + const u8 elfMsgNum = N64Mem_GetElfMsgNum(); + if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) + { + LUSLOG_WARN("[ArenaSizing] elfMsg: cUpElfMsgs non-NULL but elfMsgNum=%d out of range", elfMsgNum); + return 0; + } + + return N64SizeData_GetDmaFileSize(sElfMsgDmaNames[elfMsgNum - 1]); } // -------------------------------------------------------------------------------------------------------------------- @@ -356,10 +367,11 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) { const u32 mapMarkData = GetMapMarkDataOverlaySize(play); total += ALIGN16(mapMarkData); - LUSLOG_INFO("[ArenaSizing] mapMarkData=0x%X, total=0x%X, arena=0x%X", mapMarkData, total, - N64_THA_BUDGET - total); } + LUSLOG_INFO("[ArenaSizing] total=0x%X, arena=0x%X (budget=0x%X)", total, N64_THA_BUDGET - total, + (u32)N64_THA_BUDGET); + if (total >= N64_THA_BUDGET) { return 0; diff --git a/soh/soh/z_scene_otr.cpp b/soh/soh/z_scene_otr.cpp index 2ac7b8e3839..ffa64b70909 100644 --- a/soh/soh/z_scene_otr.cpp +++ b/soh/soh/z_scene_otr.cpp @@ -1,4 +1,5 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" #include "ResourceManagerHelpers.h" #include #include "soh/resource/type/Scene.h" @@ -105,6 +106,10 @@ bool Scene_CommandSpecialFiles(PlayState* play, SOH::ISceneCommand* cmd) { play->objectCtx.subKeepIndex = Object_Spawn(&play->objectCtx, specialCmd->specialObjects.globalObject); } + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_StoreElfMsgNum(specialCmd->specialObjects.elfMessage); + // #endregion + if (specialCmd->specialObjects.elfMessage != 0) { auto res = (Ship::Blob*)OTRPlay_LoadFile(play, sNaviMsgFiles[specialCmd->specialObjects.elfMessage - 1].fileName); From 8c4490545a5244896e15b4fa15ce349c72d573c1 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 8 May 2026 19:29:08 -0700 Subject: [PATCH 22/58] feat(restoration): Derive parameterStaticSize and effectiveSsSize --- .../N64MemoryModel/ShadowArena/arena_sizing.c | 24 ++++++++++--------- .../N64MemoryModel/ShadowArena/arena_sizing.h | 16 +++++++------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c index 694ddba5799..c84f3248c2b 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -24,6 +24,7 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); #define N64_SIZEOF_COLLISION_CONTEXT 0x1464 // CollisionContext size = 0x1464 (from decomp header comment) #define N64_SIZEOF_GFX 8 // sizeof(Gfx) on N64: Two u32 words -- SoH is 16 #define N64_SIZEOF_VTX 0x10 // sizeof(Vtx) on N64: Same on both platforms +#define N64_SIZEOF_EFFECT_SS 0x60 // sizeof(EffectSs) on N64: No pointer members, constant across all N64 versions // Struct sizes that are identical on N64 and SoH (no pointer members): // sizeof(MtxF) = 0x40 @@ -49,9 +50,9 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); // // On N64, the map mark data overlay (chest/boss/dungeon icons on the pause map) is loaded into THA // via GAME_STATE_ALLOC in MapMark_Init. SoH compiles this data in directly. -// VRAM size from decomp linker map: 0x8085D460 - 0x80856900 = 0x6B60. -// Constant across OoT versions (dungeon map mark positions don't change). -// Only loaded for the 10 main dungeons (Deku Tree through Ice Cavern) and their boss rooms. +// VRAM size from decomp linker map: 0x8085D460 - 0x80856900 = 0x6B60. Constant across OoT versions +// (dungeon map mark positions don't change). Only loaded for the 10 main dungeons (Deku Tree through Ice Cavern) and +// their boss rooms. // -------------------------------------------------------------------------------------------------------------------- #define N64_MAP_MARK_DATA_VRAM_SIZE 0x6B60 @@ -59,8 +60,8 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); static u32 GetMapMarkDataOverlaySize(PlayState* play) { // Mirrors the condition in z_map_exp.c Map_Init: the dungeon case block's inner guard. - // Main dungeons: SCENE_DEKU_TREE (0x00) through SCENE_ICE_CAVERN (0x09) - // Boss rooms: SCENE_DEKU_TREE_BOSS (0x11) through SCENE_SHADOW_TEMPLE_BOSS (0x18) + // Main dungeons: SCENE_DEKU_TREE (0x00) through SCENE_ICE_CAVERN (0x09) + // Boss rooms: SCENE_DEKU_TREE_BOSS (0x11) through SCENE_SHADOW_TEMPLE_BOSS (0x18) if (play->sceneNum <= SCENE_ICE_CAVERN || (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) { @@ -280,8 +281,6 @@ static u32 GetMaxRoomSize(PlayState* play) const VersionConstants gVersionConstantsNtsc12 = { 0x26740, // kaleidoOverlayVramSize: max(kaleido_scope = 0x1CA00, player_actor = 0x26740) - 0x3B00, // parameterStaticSize - 0x60, // effectSsSize: N64 sizeof(EffectSs) }; u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) @@ -294,13 +293,16 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) u32 total = 0; - // Per-version constants (N64-unique THA consumers SoH skips) + // Per-version constant (N64-unique THA consumer SoH skips) total += ALIGN16(vc->kaleidoOverlayVramSize); - total += ALIGN16(vc->parameterStaticSize); + + // parameter_static: DMA file size, version-specific but available in the OTR blob. + const u32 parameterStaticSize = N64SizeData_GetDmaFileSize("parameter_static"); + total += ALIGN16(parameterStaticSize); // Fixed consumers total += ALIGN16(N64_MATRIX_STACK_SIZE); - total += ALIGN16(0x55 * vc->effectSsSize); + total += ALIGN16(0x55 * N64_SIZEOF_EFFECT_SS); total += ALIGN16(N64_TEXT_BOX_SIZE); total += ALIGN16(N64_DO_ACTION_SIZE); total += ALIGN16(N64_ICON_ITEM_SIZE); @@ -308,7 +310,7 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) const u32 fixed = total; LUSLOG_INFO("[ArenaSizing] fixed=0x%X (kaleido=0x%X, param=0x%X)", fixed, vc->kaleidoOverlayVramSize, - vc->parameterStaticSize); + parameterStaticSize); // Scene-dependent consumers { diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h index 9080dfee859..5a868fb7c69 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h @@ -4,23 +4,25 @@ #ifdef __cplusplus extern "C" { + + + #endif // -------------------------------------------------------------------------------------------------------------------- -// Per version constants that cannot be derived at SoH runtime. +// Per-version constants that cannot be derived from existing OTR blobs at runtime. // -// These come from the decomp linker map or DMA table for a given ROM version. SoH zeroes out or bypasses the code -// paths that would expose them (e.g, kaleido overlays compiled in, skybox textures loaded from OTR, scene files loaded -// via ResourceManager instead of THA, etc.) +// kaleidoOverlayVramSize requires the kaleido overlay table from the code segment (VRAM spans, not DMA sizes). +// Other former members have been replaced: +// parameterStaticSize -> N64SizeData_GetDmaFileSize("parameter_static") +// effectSsSize -> N64_SIZEOF_EFFECT_SS (constant 0x60 across all N64 versions) // -// #TODO: For multi-version support, generate one of these per supported ROM version. +// #TODO: Extract kaleidoOverlayVramSize from the OTR exporter per ROM version. // -------------------------------------------------------------------------------------------------------------------- typedef struct { u32 kaleidoOverlayVramSize; // max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) - u32 parameterStaticSize; // _parameter_staticSegmentRomEnd - RomStart - u32 effectSsSize; // N64 sizeof(EffectSs) -- 0x60 on N64, larger on SoH } VersionConstants; From 0b9f5278349f3c8e09476aaedc9e1360d1f1cccd Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 8 May 2026 19:44:43 -0700 Subject: [PATCH 23/58] feat(restoration): Skyblox per-ID DMA lookups --- .../N64MemoryModel/ShadowArena/arena_sizing.c | 113 ++++++++++++++---- .../N64MemoryModel/ShadowArena/arena_sizing.h | 2 - 2 files changed, 88 insertions(+), 27 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c index c84f3248c2b..08bac61c784 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -77,34 +77,100 @@ static u32 GetMapMarkDataOverlaySize(PlayState* play) // On N64, skybox textures and palettes are DMA'd into THA-allocated staticSegment buffers. SoH loads from OTR via the // ResourceManager, never touching THA. // -// SKYBOX_NORMAL_SKY: 2 texture banks * 0xC000 * 2 palettes * 0x100 -// Indoor skyboxes: Varying sizes per skybox type -// SKYBOX_NONE: 0 +// The allocation pattern depends on the skybox type: +// SKYBOX_NORMAL_SKY / OVERCAST_SUNSET: 2 texture banks + 2 palettes (all banks are 0xC000/0x100) +// SKYBOX_CUTSCENE_MAP: 2 different tex files + 2 palettes +// Indoor skyboxes: 1 tex file + 1 palette file (sizes vary per skybox) +// SKYBOX_NONE: Nothing // -// #TODO: Other skybox types need the same treatment from decomp map's vr_*_static segments. +// The dList/vtx buffer pattern depends on drawType: +// SKYBOX_DRAW_128 (outdoor): dList=12×150×Gfx, vtx=5×32×Vtx (6×32 for CUTSCENE_MAP) +// SKYBOX_DRAW_256 (indoor, 3- or 4-face): dList=8×150×Gfx, vtx=8×32×Vtx // -------------------------------------------------------------------------------------------------------------------- +// DMA file name for a single-file indoor skybox (1 tex + 1 pal). +typedef struct +{ + const char* texName; + const char* palName; +} SkyboxDmaEntry; + +// Indexed by skybox ID. NULL texName means the ID isn't a single-file indoor skybox (handled separately). +static const SkyboxDmaEntry sSkyboxDmaTable[] = { + [SKYBOX_NONE] = {NULL, NULL}, + [SKYBOX_NORMAL_SKY] = {NULL, NULL}, // gNormalSkyFiles, hardcoded + [SKYBOX_BAZAAR] = {"vr_SP1a_static", "vr_SP1a_pal_static"}, + [SKYBOX_OVERCAST_SUNSET] = {NULL, NULL}, // same as NORMAL_SKY + [SKYBOX_MARKET_ADULT] = {"vr_RUVR_static", "vr_RUVR_pal_static"}, + [SKYBOX_CUTSCENE_MAP] = {NULL, NULL}, // Two tex files, handled separately + [SKYBOX_HOUSE_LINK] = {"vr_LHVR_static", "vr_LHVR_pal_static"}, + [SKYBOX_MARKET_CHILD_DAY] = {"vr_MDVR_static", "vr_MDVR_pal_static"}, + [SKYBOX_MARKET_CHILD_NIGHT] = {"vr_MNVR_static", "vr_MNVR_pal_static"}, + [SKYBOX_HAPPY_MASK_SHOP] = {"vr_FCVR_static", "vr_FCVR_pal_static"}, + [SKYBOX_HOUSE_KNOW_IT_ALL_BROTHERS] = {"vr_KHVR_static", "vr_KHVR_pal_static"}, + [SKYBOX_HOUSE_OF_TWINS] = {"vr_K3VR_static", "vr_K3VR_pal_static"}, + [SKYBOX_STABLES] = {"vr_MLVR_static", "vr_MLVR_pal_static"}, + [SKYBOX_HOUSE_KAKARIKO] = {"vr_KKRVR_static", "vr_KKRVR_pal_static"}, + [SKYBOX_KOKIRI_SHOP] = {"vr_KSVR_static", "vr_KSVR_pal_static"}, + [SKYBOX_GORON_SHOP] = {"vr_GLVR_static", "vr_GLVR_pal_static"}, + [SKYBOX_ZORA_SHOP] = {"vr_ZRVR_static", "vr_ZRVR_pal_static"}, + [SKYBOX_POTION_SHOP_KAKARIKO] = {"vr_DGVR_static", "vr_DGVR_pal_static"}, + [SKYBOX_POTION_SHOP_MARKET] = {"vr_ALVR_static", "vr_ALVR_pal_static"}, + [SKYBOX_BOMBCHU_SHOP] = {"vr_NSVR_static", "vr_NSVR_pal_static"}, + [SKYBOX_HOUSE_RICHARD] = {"vr_IPVR_static", "vr_IPVR_pal_static"}, + [SKYBOX_HOUSE_IMPA] = {"vr_LBVR_static", "vr_LBVR_pal_static"}, + [SKYBOX_TENT] = {"vr_TTVR_static", "vr_TTVR_pal_static"}, + [SKYBOX_HOUSE_MIDO] = {"vr_K4VR_static", "vr_K4VR_pal_static"}, + [SKYBOX_HOUSE_SARIA] = {"vr_K5VR_static", "vr_K5VR_pal_static"}, + [SKYBOX_HOUSE_ALLEY] = {"vr_KR3VR_static", "vr_KR3VR_pal_static"}, +}; + static u32 GetN64SkyboxTextureSize(s16 skyboxId) { - switch (skyboxId) + if (skyboxId == SKYBOX_NONE) + { + return 0; + } + + // NORMAL_SKY and OVERCAST_SUNSET both load 2 texture banks + 2 palettes from the vr_fine/vr_cloud files. + // All 16 banks are exactly 0xC000 and all 16 palettes are exactly 0x100, so the total is constant. + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { - case SKYBOX_NORMAL_SKY: - // Fall-through - case SKYBOX_OVERCAST_SUNSET: return 2 * 0xC000 + 2 * 0x100; + } - case SKYBOX_NONE: - return 0; + // CUTSCENE_MAP loads two different texture files + 2 palette copies. + if (skyboxId == SKYBOX_CUTSCENE_MAP) + { + const u32 tex0 = N64SizeData_GetDmaFileSize("vr_holy0_static"); + const u32 tex1 = N64SizeData_GetDmaFileSize("vr_holy1_static"); + const u32 pal = N64SizeData_GetDmaFileSize("vr_holy0_pal_static"); + return tex0 + tex1 + pal * 2; + } - default: - // Indoor skyboxes: Conservative estimate. - // #TODO: Populate exact sizes per skybox ID from decomp map. - return 3 * 0x8000 + 3 * 0x200; + // Indoor skyboxes: 1 texture + 1 palette, looked up from the DMA blob. + if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) + { + const SkyboxDmaEntry* entry = &sSkyboxDmaTable[skyboxId]; + if (entry->texName != NULL) + { + const u32 tex = N64SizeData_GetDmaFileSize(entry->texName); + const u32 pal = N64SizeData_GetDmaFileSize(entry->palName); + return tex + pal; + } } + + return 0; } // -------------------------------------------------------------------------------------------------------------------- -// Skybox dListBuf and roomVtx (shared -- same size on N64 and SoH, no pointer members) +// Skybox dListBuf and roomVtx +// +// Allocation sizes depend on the drawType, which is determined by the skybox ID: +// SKYBOX_DRAW_128 (NORMAL_SKY, OVERCAST_SUNSET, CUTSCENE_MAP): 12-face dList, 5- or 6-face vtx +// SKYBOX_DRAW_256 (all indoor skyboxes): 8-face dList, 8-face vtx + +// Gfx is 8 bytes on N64, Vtx is 0x10. // -------------------------------------------------------------------------------------------------------------------- static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) @@ -116,24 +182,21 @@ static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVt return; } - // unk_140 != 0 means indoor/single-room skybox. - // #TODO: Determine unk_140 from ID more precisely. - const bool isIndoor = skyboxId != SKYBOX_NORMAL_SKY && skyboxId != SKYBOX_OVERCAST_SUNSET && skyboxId != - SKYBOX_CUTSCENE_MAP; - if (isIndoor) + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { - *outDlistSize = 8 * 150 * N64_SIZEOF_GFX; - *outVtxSize = 256 * N64_SIZEOF_VTX; + *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; + *outVtxSize = 5 * 32 * N64_SIZEOF_VTX; } else if (skyboxId == SKYBOX_CUTSCENE_MAP) { *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; - *outVtxSize = 192 * N64_SIZEOF_VTX; + *outVtxSize = 6 * 32 * N64_SIZEOF_VTX; } else { - *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; - *outVtxSize = 160 * N64_SIZEOF_VTX; + // Indoor skyboxes: SKYBOX_DRAW_256_4FACE or SKYBOX_DRAW_256_3FACE, both use the same allocation. + *outDlistSize = 8 * 150 * N64_SIZEOF_GFX; + *outVtxSize = 8 * 32 * N64_SIZEOF_VTX; } } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h index 5a868fb7c69..1f876d5b83d 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h @@ -5,8 +5,6 @@ #ifdef __cplusplus extern "C" { - - #endif // -------------------------------------------------------------------------------------------------------------------- From e5f184c4dbcc99601ef6bc6186d009f47a2d4779 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 8 May 2026 22:06:14 -0700 Subject: [PATCH 24/58] feat(restoration): Derive kaleidoOverlayVramSize from OTR, eliminate VersionConstants --- OTRExporter | 2 +- .../N64MemoryModel/N64MemoryModel.cpp | 8 ++-- .../ShadowArena/N64SizeData.cpp | 41 +++++++++++++++++++ .../ShadowArena/N64SizeData.hpp | 2 + .../N64MemoryModel/ShadowArena/arena_sizing.c | 15 +++---- .../N64MemoryModel/ShadowArena/arena_sizing.h | 28 +++---------- 6 files changed, 60 insertions(+), 36 deletions(-) diff --git a/OTRExporter b/OTRExporter index 6c223f05cef..d7816f58d97 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit 6c223f05cefc334c618b33f587366cde97cd3e7d +Subproject commit d7816f58d977d0d92b0c21e11d5c20c0f06f3016 diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 8f2d1b06605..04c6534a252 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -122,9 +122,9 @@ void N64Mem_Reset(PlayState* play) sIsActive = CVAR_VALUE; if (sIsActive && play != nullptr) { - // Compute N64-equivalent arena size from first principles. - // #TODO: Select version constants based on detected ROM version. - u32 shadowArenaSize = ArenaSizing_ComputeN64ArenaSize(play, &gVersionConstantsNtsc12); + // Compute N64-equivalent arena size from first principles. All per-version constants are + // derived from the OTR blob, which was extracted from the user's specific ROM version. + u32 shadowArenaSize = ArenaSizing_ComputeN64ArenaSize(play); if (shadowArenaSize == 0) { SPDLOG_ERROR("[N64MemoryModel] Arena sizing returned 0 -- THA budget exceeded, disabling."); @@ -135,7 +135,7 @@ void N64Mem_Reset(PlayState* play) SPDLOG_INFO("[N64MemoryModel] Shadow arena size=0x{:X} for scene 0x{:X}", shadowArenaSize, play->sceneNum); ShadowArena_Init(&sShadow, shadowArenaSize); - sTraceEnabled = (play->sceneNum == SCENE_GRAVEYARD); + sTraceEnabled = play->sceneNum == SCENE_GRAVEYARD; } } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp index 054fb94de64..8087c318c3c 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp @@ -217,3 +217,44 @@ extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) return 0; } + +// -------------------------------------------------------------------------------------------------------------------- +// Kaleido overlay max VRAM size (misc/kaleido_vram_size) +// +// Format: single u32 — max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) +// -------------------------------------------------------------------------------------------------------------------- + +static u32 sKaleidoVramSize = 0; +static bool sIsKaleidoLoaded = false; + +static void LoadKaleidoVramSize() +{ + if (sIsKaleidoLoaded) + { + return; + } + + sIsKaleidoLoaded = true; + + const auto file = + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/kaleido_vram_size"); + if (!file || !file->IsLoaded) + { + SPDLOG_ERROR("[N64SizeData] Failed to load misc/kaleido_vram_size from OTR."); + return; + } + + auto stream = std::make_shared(file->Buffer->data(), file->Buffer->size()); + const auto reader = std::make_shared(stream); + reader->SetEndianness(Ship::Endianness::Big); + + sKaleidoVramSize = reader->ReadUInt32(); + + SPDLOG_INFO("[N64SizeData] Kaleido max VRAM size: 0x{:X}.", sKaleidoVramSize); +} + +extern "C" u32 N64SizeData_GetKaleidoVramSize() +{ + LoadKaleidoVramSize(); + return sKaleidoVramSize; +} diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp index a822cdcf10d..4a91506825e 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp @@ -14,6 +14,7 @@ extern "C" { // misc/actor_overlay_sizes Actor overlay VRAM sizes indexed by actor ID // misc/effect_overlay_sizes Effect overlay VRAM sizes indexed by effect type // misc/actor_instance_sizes Actor instance struct sizes indexed by actor ID +// misc/kaleido_vram_size max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) // // Each table is loaded lazily on first query and cached for the lifetime of the process. // -------------------------------------------------------------------------------------------------------------------- @@ -22,6 +23,7 @@ u32 N64SizeData_GetDmaFileSize(const char* name); u32 N64SizeData_GetActorOverlaySize(u16 actorId); u32 N64SizeData_GetEffectOverlaySize(u16 effectType); u32 N64SizeData_GetActorInstanceSize(u16 actorId); +u32 N64SizeData_GetKaleidoVramSize(void); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c index 08bac61c784..020028a95c8 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -5,7 +5,7 @@ #include "global.h" // Declared in z_bgcheck.c but not exposed via header. -s32 BgCheck_IsSpotScene(PlayState* play); +s32 BgCheck_IsSpotScene(PlayState * play); s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); // -------------------------------------------------------------------------------------------------------------------- @@ -342,11 +342,7 @@ static u32 GetMaxRoomSize(PlayState* play) // Main computation // -------------------------------------------------------------------------------------------------------------------- -const VersionConstants gVersionConstantsNtsc12 = { - 0x26740, // kaleidoOverlayVramSize: max(kaleido_scope = 0x1CA00, player_actor = 0x26740) -}; - -u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) +u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { // Each THA consumer is allocated via GAME_STATE_ALLOC -> THA_AllocTailAlign16, which consumes ALIGN16(size) bytes. // We must align each consumer individually before summing; aligning the sum would under-count when individual @@ -356,8 +352,9 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) u32 total = 0; - // Per-version constant (N64-unique THA consumer SoH skips) - total += ALIGN16(vc->kaleidoOverlayVramSize); + // Kaleido overlay buffer: max(ovl_kaleido_scope, ovl_player_actor) VRAM span, from OTR blob. + const u32 kaleidoVramSize = N64SizeData_GetKaleidoVramSize(); + total += ALIGN16(kaleidoVramSize); // parameter_static: DMA file size, version-specific but available in the OTR blob. const u32 parameterStaticSize = N64SizeData_GetDmaFileSize("parameter_static"); @@ -372,7 +369,7 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc) total += ALIGN16(N64_MAP_SEGMENT_SIZE); const u32 fixed = total; - LUSLOG_INFO("[ArenaSizing] fixed=0x%X (kaleido=0x%X, param=0x%X)", fixed, vc->kaleidoOverlayVramSize, + LUSLOG_INFO("[ArenaSizing] fixed=0x%X (kaleido=0x%X, param=0x%X)", fixed, kaleidoVramSize, parameterStaticSize); // Scene-dependent consumers diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h index 1f876d5b83d..7265b234603 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h @@ -4,40 +4,24 @@ #ifdef __cplusplus extern "C" { - #endif -// -------------------------------------------------------------------------------------------------------------------- -// Per-version constants that cannot be derived from existing OTR blobs at runtime. -// -// kaleidoOverlayVramSize requires the kaleido overlay table from the code segment (VRAM spans, not DMA sizes). -// Other former members have been replaced: -// parameterStaticSize -> N64SizeData_GetDmaFileSize("parameter_static") -// effectSsSize -> N64_SIZEOF_EFFECT_SS (constant 0x60 across all N64 versions) -// -// #TODO: Extract kaleidoOverlayVramSize from the OTR exporter per ROM version. -// -------------------------------------------------------------------------------------------------------------------- - -typedef struct -{ - u32 kaleidoOverlayVramSize; // max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) -} VersionConstants; - - -// Hardcoded version constants for NTSC 1.2. -extern const VersionConstants gVersionConstantsNtsc12; - // -------------------------------------------------------------------------------------------------------------------- // Arena size computation // // Computes the N64 ZeldaArena size from first principles: // arena = THA_BUDGET - sum(all THA consumers at N64 sizes) // +// All per-version constants are derived from OTR blobs at runtime: +// kaleidoOverlayVramSize -> N64SizeData_GetKaleidoVramSize() (misc/kaleido_vram_size) +// parameterStaticSize -> N64SizeData_GetDmaFileSize(...) (misc/dma_sizes) +// effectSsSize -> N64_SIZEOF_EFFECT_SS (constant 0x60, all N64 versions) +// // Called from N64Mem_Reset after Play_Init has populated scene data. Returns 0 if THA consumption exceeds budget // (should not happen on valid scenes). // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play, const VersionConstants* vc); +u32 ArenaSizing_ComputeN64ArenaSize(PlayState * play); #ifdef __cplusplus } From 7352d5c15b74b813b49ade12349f7fa0508ce7ca Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 03:05:59 -0700 Subject: [PATCH 25/58] feat(restoration): Force immediate Actor_Delete in room teardown --- soh/src/code/z_actor.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index a4419c44fa3..7671df3f75c 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2597,7 +2597,6 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { } play->numSetupActors = 0; GameInteractor_ExecuteOnSceneSpawnActors(); - N64Mem_LogState("room actors spawned"); } if (actorCtx->unk_02 != 0) { @@ -2656,7 +2655,9 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { CollisionCheck_ResetDamage(&actor->colChkInfo); actor = actor->next; } else if (actor->update == NULL) { - if (!actor->isDrawn) { + // #region SOH [Enhancement] - N64 Memory Model + if (N64Mem_IsActive() || !actor->isDrawn) { + // #endregion actor = Actor_Delete(&play->actorCtx, actor, play); } else { Actor_Destroy(actor, play); @@ -3195,7 +3196,16 @@ void func_80031B14(PlayState* play, ActorContext* actorCtx) { while (actor != NULL) { if ((actor->room >= 0) && (actor->room != play->roomCtx.curRoom.num) && (actor->room != play->roomCtx.prevRoom.num)) { - if (!actor->isDrawn) { + // #region [SOH] Enhancement - N64 Memory Model + // + // On N64, most departing-room actors are off-screen (isDrawn = false) during the transition frame, so + // they take the immediate Actor_Delete path. SoH's extended draw distance (Ship_CalcShouldDrawAndUpdate) + // keeps more actors "drawn," pushing them into the deferred Actor_Kill path instead. This changes the + // free ordering seen by the heap, producing different fragmentation geometry. + // + // When the N64 Memory Model is active, force the immediate path to match N64's free ordering. + if (N64Mem_IsActive() || !actor->isDrawn) { + // #endregion actor = Actor_Delete(actorCtx, actor, play); } else { Actor_Kill(actor); From 30bb02ef534953452507868918ece234f3e9bb79 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 03:30:06 -0700 Subject: [PATCH 26/58] feat(restoration): MallocR for actor overlay shadow allocations --- .../N64MemoryModel/N64MemoryModel.cpp | 19 ++++++------------- .../N64MemoryModel/ShadowArena/arena_sizing.c | 2 +- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 04c6534a252..0c6ad574517 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -122,8 +122,8 @@ void N64Mem_Reset(PlayState* play) sIsActive = CVAR_VALUE; if (sIsActive && play != nullptr) { - // Compute N64-equivalent arena size from first principles. All per-version constants are - // derived from the OTR blob, which was extracted from the user's specific ROM version. + // Compute N64-equivalent arena size from first principles. All per-version constants are derived from the OTR + // blob, which was extracted from the user's specific ROM version. u32 shadowArenaSize = ArenaSizing_ComputeN64ArenaSize(play); if (shadowArenaSize == 0) { @@ -202,16 +202,9 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) return 1; } - u32 shadow = SHADOW_NULL; - if (allocType & ALLOCTYPE_PERMANENT) - { - shadow = ShadowArena_MallocR(&sShadow, overlaySize); - } - else - { - shadow = ShadowArena_Malloc(&sShadow, overlaySize); - } - + // N64 allocates ALL actor overlays via ZeldaArena_MallocR (from the arena top). Using Malloc (bottom-up) placed + // overlays interleaved with instances at the bottom, preventing proper coalescing when overlays are freed. + const u32 shadow = ShadowArena_MallocR(&sShadow, overlaySize); if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[N64MemoryModel] Shadow overlay failed for actor 0x{:04X} (need 0x{:X})", actorId, overlaySize); @@ -295,7 +288,7 @@ void N64Mem_FreeInstance(void* realPtr) if (sTraceEnabled) { - Actor* actor = (Actor*)realPtr; + const auto* actor = static_cast(realPtr); TraceFree("inst", actor->id); } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c index 020028a95c8..00aa2fc967f 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c @@ -5,7 +5,7 @@ #include "global.h" // Declared in z_bgcheck.c but not exposed via header. -s32 BgCheck_IsSpotScene(PlayState * play); +s32 BgCheck_IsSpotScene(PlayState* play); s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); // -------------------------------------------------------------------------------------------------------------------- From e149bc4fe9c382d18c6868b2f0f3e8e32778ddd2 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 04:28:19 -0700 Subject: [PATCH 27/58] feat(restoration): Instance-region gap correction for Object_Kankyo leak --- .../N64MemoryModel/N64MemoryModel.cpp | 42 +++++++++++++++++++ .../N64MemoryModel/N64MemoryModel.hpp | 3 ++ soh/src/code/z_actor.c | 1 + 3 files changed, 46 insertions(+) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 0c6ad574517..296acb0b9b7 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -70,6 +70,30 @@ static void TraceFree(const char* tag, u32 id) } } +// -------------------------------------------------------------------------------------------------------------------- +// Graveyard benchmark +// -------------------------------------------------------------------------------------------------------------------- + +static s32 sGraveyardTransitionCount = 0; + +void N64Mem_BenchmarkTransition(PlayState* play) +{ + if (!sIsActive || play->sceneNum != SCENE_GRAVEYARD) + { + return; + } + + sGraveyardTransitionCount++; + + u32 maxFree = 0; + u32 totalFree = 0; + u32 totalAlloc = 0; + ShadowArena_GetSizes(&sShadow, &maxFree, &totalFree, &totalAlloc); + + SPDLOG_INFO("[N64Benchmark] transition={}, largest_free=0x{:X}, total_free=0x{:X}", + sGraveyardTransitionCount, maxFree, totalFree); +} + // -------------------------------------------------------------------------------------------------------------------- // Lifecycle // -------------------------------------------------------------------------------------------------------------------- @@ -101,8 +125,15 @@ void N64Mem_Reset(PlayState* play) ShadowArena_GetSizes(&sShadow, &maxFree, &totalFree, &totalAlloc); SPDLOG_INFO("[N64MemoryModel] Teardown: alloc=0x{:X}, free=0x{:X}, largest=0x{:X}, ptrs={}", totalAlloc, totalFree, maxFree, sShadowMap.size()); + + if (sGraveyardTransitionCount > 0) + { + SPDLOG_INFO("[N64Benchmark] RESULT transitions={}", sGraveyardTransitionCount); + } } + sGraveyardTransitionCount = 0; + // Tear down previous shadow state unconditionally -- the real ZeldaArena has already been reinitialized by // Play_Init. ShadowArena_Destroy(&sShadow); @@ -132,6 +163,17 @@ void N64Mem_Reset(PlayState* play) return; } + // On N64, the instance region develops internal fragmentation gaps from alloc/free cycling during room + // transitions (partially caused by Bg_Spot02_Objects being in Room 0 on N64 but Room 1 on SoH, and by + // En_Firefly/En_Sw transient overlay churn). These gaps absorb the per-cycle Object_Kankyo instance leak + // (~0x1680 each) without shrinking the main free block. The model's clean coalescing has no such gaps, so + // leaked instances eat the main block directly. This correction accounts for the gap-structure mismatch + // until the upstream scene-data divergences are resolved. + if (constexpr u32 instanceGapCorrection = 0x1680; shadowArenaSize > instanceGapCorrection) + { + shadowArenaSize -= instanceGapCorrection; + } + SPDLOG_INFO("[N64MemoryModel] Shadow arena size=0x{:X} for scene 0x{:X}", shadowArenaSize, play->sceneNum); ShadowArena_Init(&sShadow, shadowArenaSize); diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index 7f01d743785..07374fc99c6 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -42,6 +42,9 @@ struct ShadowArena* N64Mem_GetShadowArena(void); // Log the current shadow arena state (alloc/free/largest) with the given context label. void N64Mem_LogState(const char* context); +// Log Graveyard benchmark data (transition count, largest_free, total_free). Call after room actors are spawned. +void N64Mem_BenchmarkTransition(PlayState* play); + // -------------------------------------------------------------------------------------------------------------------- // Actor overlays: Keyed by actor ID, shadow-only allocations. // diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 7671df3f75c..2a8589f9ef8 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2597,6 +2597,7 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { } play->numSetupActors = 0; GameInteractor_ExecuteOnSceneSpawnActors(); + N64Mem_BenchmarkTransition(play); } if (actorCtx->unk_02 != 0) { From 88fac9a852242d9e2c4637356d873b2a956632ed Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 06:35:58 -0700 Subject: [PATCH 28/58] refactor: Clang format and other cleanups --- .../N64MemoryModel/N64MemoryModel.cpp | 184 ++++++----------- .../{ShadowArena => }/N64SizeData.cpp | 0 .../{ShadowArena => }/N64SizeData.hpp | 0 .../arena_sizing.c => n64_arena_sizing.c} | 188 +++++++----------- .../arena_sizing.h => n64_arena_sizing.h} | 3 +- .../shadow_arena.c => n64_shadow_arena.c} | 139 +++++-------- .../shadow_arena.h => n64_shadow_arena.h} | 7 +- .../debugger/HeapViewerWindow.cpp | 120 ++++------- soh/soh/SohGui/SohMenuEnhancements.cpp | 6 +- 9 files changed, 234 insertions(+), 413 deletions(-) rename soh/soh/Enhancements/Restorations/N64MemoryModel/{ShadowArena => }/N64SizeData.cpp (100%) rename soh/soh/Enhancements/Restorations/N64MemoryModel/{ShadowArena => }/N64SizeData.hpp (100%) rename soh/soh/Enhancements/Restorations/N64MemoryModel/{ShadowArena/arena_sizing.c => n64_arena_sizing.c} (79%) rename soh/soh/Enhancements/Restorations/N64MemoryModel/{ShadowArena/arena_sizing.h => n64_arena_sizing.h} (94%) rename soh/soh/Enhancements/Restorations/N64MemoryModel/{ShadowArena/shadow_arena.c => n64_shadow_arena.c} (79%) rename soh/soh/Enhancements/Restorations/N64MemoryModel/{ShadowArena/shadow_arena.h => n64_shadow_arena.h} (93%) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 296acb0b9b7..cf4b17d1de4 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -5,9 +5,9 @@ extern "C" { #include "N64MemoryModel.hpp" -#include "ShadowArena/N64SizeData.hpp" -#include "ShadowArena/shadow_arena.h" -#include "ShadowArena/arena_sizing.h" +#include "N64SizeData.hpp" +#include "n64_shadow_arena.h" +#include "n64_arena_sizing.h" #include "global.h" } @@ -42,8 +42,7 @@ static std::unordered_map sShadowMap; static s32 sTraceEnabled = 0; -static void LogShadowState(const char* context) -{ +static void LogShadowState(const char* context) { u32 maxFree = 0; u32 totalFree = 0; u32 totalAlloc = 0; @@ -53,20 +52,16 @@ static void LogShadowState(const char* context) maxFree); } -static void TraceAlloc(const char* tag, u32 id, u32 size) -{ - if (sTraceEnabled) - { +static void TraceAlloc(const char* tag, u32 id, u32 size) { + if (sTraceEnabled) { u32 consumed = (size + 0xF & ~0xF) + SHADOW_NODE_SIZE; - SPDLOG_INFO("[N64Trace] +{} id=0x{:X} sz=0x{:X} cost=0x{:X}", tag, id, size, consumed); + SPDLOG_TRACE("[N64Trace] +{} id=0x{:X} sz=0x{:X} cost=0x{:X}", tag, id, size, consumed); } } -static void TraceFree(const char* tag, u32 id) -{ - if (sTraceEnabled) - { - SPDLOG_INFO("[N64Trace] -{} id=0x{:X}", tag, id); +static void TraceFree(const char* tag, u32 id) { + if (sTraceEnabled) { + SPDLOG_TRACE("[N64Trace] -{} id=0x{:X}", tag, id); } } @@ -76,10 +71,8 @@ static void TraceFree(const char* tag, u32 id) static s32 sGraveyardTransitionCount = 0; -void N64Mem_BenchmarkTransition(PlayState* play) -{ - if (!sIsActive || play->sceneNum != SCENE_GRAVEYARD) - { +void N64Mem_BenchmarkTransition(PlayState* play) { + if (!sIsActive || play->sceneNum != SCENE_GRAVEYARD) { return; } @@ -98,26 +91,21 @@ void N64Mem_BenchmarkTransition(PlayState* play) // Lifecycle // -------------------------------------------------------------------------------------------------------------------- -void N64Mem_StoreThaRemainder(u32 sohRemainder) -{ +void N64Mem_StoreThaRemainder(u32 sohRemainder) { sSohThaRemainder = sohRemainder; } -void N64Mem_StoreElfMsgNum(u8 num) -{ +void N64Mem_StoreElfMsgNum(u8 num) { sElfMsgNum = num; } -u8 N64Mem_GetElfMsgNum() -{ +u8 N64Mem_GetElfMsgNum() { return sElfMsgNum; } -void N64Mem_Reset(PlayState* play) -{ +void N64Mem_Reset(PlayState* play) { // Log shadow state before teardown for per-scene diagnostics. - if (sIsActive && sShadow.buffer) - { + if (sIsActive && sShadow.buffer) { u32 maxFree = 0; u32 totalFree = 0; u32 totalAlloc = 0; @@ -126,8 +114,7 @@ void N64Mem_Reset(PlayState* play) SPDLOG_INFO("[N64MemoryModel] Teardown: alloc=0x{:X}, free=0x{:X}, largest=0x{:X}, ptrs={}", totalAlloc, totalFree, maxFree, sShadowMap.size()); - if (sGraveyardTransitionCount > 0) - { + if (sGraveyardTransitionCount > 0) { SPDLOG_INFO("[N64Benchmark] RESULT transitions={}", sGraveyardTransitionCount); } } @@ -140,24 +127,20 @@ void N64Mem_Reset(PlayState* play) sShadowMap.clear(); sAbsoluteSpaceShadow = SHADOW_NULL; - for (u32& sOverlayShadow : sOverlayShadows) - { + for (u32& sOverlayShadow : sOverlayShadows) { sOverlayShadow = SHADOW_NULL; } - for (u32& sEffectOverlayShadow : sEffectOverlayShadows) - { + for (u32& sEffectOverlayShadow : sEffectOverlayShadows) { sEffectOverlayShadow = SHADOW_NULL; } sIsActive = CVAR_VALUE; - if (sIsActive && play != nullptr) - { + if (sIsActive && play != nullptr) { // Compute N64-equivalent arena size from first principles. All per-version constants are derived from the OTR // blob, which was extracted from the user's specific ROM version. u32 shadowArenaSize = ArenaSizing_ComputeN64ArenaSize(play); - if (shadowArenaSize == 0) - { + if (shadowArenaSize == 0) { SPDLOG_ERROR("[N64MemoryModel] Arena sizing returned 0 -- THA budget exceeded, disabling."); sIsActive = 0; return; @@ -169,8 +152,7 @@ void N64Mem_Reset(PlayState* play) // (~0x1680 each) without shrinking the main free block. The model's clean coalescing has no such gaps, so // leaked instances eat the main block directly. This correction accounts for the gap-structure mismatch // until the upstream scene-data divergences are resolved. - if (constexpr u32 instanceGapCorrection = 0x1680; shadowArenaSize > instanceGapCorrection) - { + if (constexpr u32 instanceGapCorrection = 0x1680; shadowArenaSize > instanceGapCorrection) { shadowArenaSize -= instanceGapCorrection; } @@ -181,20 +163,16 @@ void N64Mem_Reset(PlayState* play) } } -s32 N64Mem_IsActive() -{ +s32 N64Mem_IsActive() { return sIsActive; } -ShadowArena* N64Mem_GetShadowArena() -{ +ShadowArena* N64Mem_GetShadowArena() { return &sShadow; } -void N64Mem_LogState(const char* context) -{ - if (sIsActive) - { +void N64Mem_LogState(const char* context) { + if (sIsActive) { LogShadowState(context); } } @@ -203,33 +181,25 @@ void N64Mem_LogState(const char* context) // Actor overlays // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) -{ - if (!sIsActive) - { +s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { + if (!sIsActive) { return 1; } - if (actorId < 0 || actorId >= ACTOR_ID_MAX) - { + if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return 1; } const u32 overlaySize = N64SizeData_GetActorOverlaySize(actorId); - if (overlaySize == 0) - { + if (overlaySize == 0) { return 1; } - // ABSOLUTE: Shared fixed-size buffer, allocated once via MallocR. - if (allocType & ALLOCTYPE_ABSOLUTE) - { - if (sAbsoluteSpaceShadow == SHADOW_NULL) - { + if (allocType & ALLOCTYPE_ABSOLUTE) { + if (sAbsoluteSpaceShadow == SHADOW_NULL) { sAbsoluteSpaceShadow = ShadowArena_MallocR(&sShadow, AM_FIELD_SIZE); - if (sAbsoluteSpaceShadow == SHADOW_NULL) - { + if (sAbsoluteSpaceShadow == SHADOW_NULL) { SPDLOG_ERROR("[N64MemoryModel] Shadow absolute space failed (need 0x{:X})", AM_FIELD_SIZE); return 0; } @@ -239,16 +209,14 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) } // Already shadowed for this type. - if (sOverlayShadows[actorId] != SHADOW_NULL) - { + if (sOverlayShadows[actorId] != SHADOW_NULL) { return 1; } // N64 allocates ALL actor overlays via ZeldaArena_MallocR (from the arena top). Using Malloc (bottom-up) placed // overlays interleaved with instances at the bottom, preventing proper coalescing when overlays are freed. const u32 shadow = ShadowArena_MallocR(&sShadow, overlaySize); - if (shadow == SHADOW_NULL) - { + if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[N64MemoryModel] Shadow overlay failed for actor 0x{:04X} (need 0x{:X})", actorId, overlaySize); return 0; } @@ -258,21 +226,17 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) return 1; } -void N64Mem_FreeOverlay(s16 actorId, u16 allocType) -{ - if (!sIsActive) - { +void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { + if (!sIsActive) { return; } - if (actorId < 0 || actorId >= ACTOR_ID_MAX) - { + if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return; } // PERMANENT: Overlays that are never freed. - if (allocType & ALLOCTYPE_PERMANENT) - { + if (allocType & ALLOCTYPE_PERMANENT) { return; } @@ -285,27 +249,22 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) // Actor instances // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) -{ - if (!sIsActive) - { +s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { + if (!sIsActive) { return 1; } - if (actorId < 0 || actorId >= ACTOR_ID_MAX) - { + if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return 1; } const u32 instanceSize = N64SizeData_GetActorInstanceSize(actorId); - if (instanceSize == 0) - { + if (instanceSize == 0) { return 1; } const u32 shadow = ShadowArena_Malloc(&sShadow, instanceSize); - if (shadow == SHADOW_NULL) - { + if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[N64MemoryModel] Shadow instance failed for actor 0x{:04X} (need 0x{:X})", actorId, instanceSize); return 0; } @@ -315,21 +274,17 @@ s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) return 1; } -void N64Mem_FreeInstance(void* realPtr) -{ - if (!sIsActive || !realPtr) - { +void N64Mem_FreeInstance(void* realPtr) { + if (!sIsActive || !realPtr) { return; } const auto i = sShadowMap.find(realPtr); - if (i == sShadowMap.end()) - { + if (i == sShadowMap.end()) { return; } - if (sTraceEnabled) - { + if (sTraceEnabled) { const auto* actor = static_cast(realPtr); TraceFree("inst", actor->id); } @@ -342,16 +297,13 @@ void N64Mem_FreeInstance(void* realPtr) // Subsidiaries // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) -{ - if (!sIsActive) - { +s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) { + if (!sIsActive) { return 1; } const u32 shadow = ShadowArena_Malloc(&sShadow, n64Size); - if (shadow == SHADOW_NULL) - { + if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[N64MemoryModel] Shadow subsidiary failed (need 0x{:X})", n64Size); return 0; } @@ -361,16 +313,13 @@ s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) return 1; } -void N64Mem_FreeSubsidiary(void* realPtr) -{ - if (!sIsActive || !realPtr) - { +void N64Mem_FreeSubsidiary(void* realPtr) { + if (!sIsActive || !realPtr) { return; } const auto i = sShadowMap.find(realPtr); - if (i == sShadowMap.end()) - { + if (i == sShadowMap.end()) { return; } @@ -383,32 +332,26 @@ void N64Mem_FreeSubsidiary(void* realPtr) // Effect overlays // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocEffectOverlay(s32 type) -{ - if (!sIsActive) - { +s32 N64Mem_AllocEffectOverlay(s32 type) { + if (!sIsActive) { return 1; } - if (type < 0 || type >= EFFECT_SS_TYPE_MAX) - { + if (type < 0 || type >= EFFECT_SS_TYPE_MAX) { return 1; } - if (sEffectOverlayShadows[type] != SHADOW_NULL) - { + if (sEffectOverlayShadows[type] != SHADOW_NULL) { return 1; } u32 overlaySize = N64SizeData_GetEffectOverlaySize(type); - if (overlaySize == 0) - { + if (overlaySize == 0) { return 1; } const u32 shadow = ShadowArena_MallocR(&sShadow, overlaySize); - if (shadow == SHADOW_NULL) - { + if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[N64MemoryModel] Shadow effect overlay failed for type 0x{:02X} (need 0x{:X})", type, overlaySize); return 0; @@ -424,9 +367,8 @@ s32 N64Mem_AllocEffectOverlay(s32 type) // Registration // -------------------------------------------------------------------------------------------------------------------- -void RegisterN64MemoryModel() -{ +void RegisterN64MemoryModel() { // #TODO: Shadow arena initialization } -static RegisterShipInitFunc initFunc(RegisterN64MemoryModel, {CVAR_NAME}); +static RegisterShipInitFunc initFunc(RegisterN64MemoryModel, { CVAR_NAME }); diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp similarity index 100% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.cpp rename to soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp similarity index 100% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/N64SizeData.hpp rename to soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c similarity index 79% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c rename to soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c index 00aa2fc967f..b00881d24d0 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c @@ -1,4 +1,4 @@ -#include "arena_sizing.h" +#include "n64_arena_sizing.h" #include "N64SizeData.hpp" #include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" @@ -50,21 +50,18 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); // // On N64, the map mark data overlay (chest/boss/dungeon icons on the pause map) is loaded into THA // via GAME_STATE_ALLOC in MapMark_Init. SoH compiles this data in directly. -// VRAM size from decomp linker map: 0x8085D460 - 0x80856900 = 0x6B60. Constant across OoT versions -// (dungeon map mark positions don't change). Only loaded for the 10 main dungeons (Deku Tree through Ice Cavern) and -// their boss rooms. +// VRAM size from decomp linker map: 0x8085D460 - 0x80856900 = 0x6B60. Constant across OoT versions (dungeon map mark +// positions don't change). Only loaded for the 10 main dungeons (Deku Tree through Ice Cavern) and their boss rooms. // -------------------------------------------------------------------------------------------------------------------- #define N64_MAP_MARK_DATA_VRAM_SIZE 0x6B60 -static u32 GetMapMarkDataOverlaySize(PlayState* play) -{ +static u32 GetMapMarkDataOverlaySize(PlayState* play) { // Mirrors the condition in z_map_exp.c Map_Init: the dungeon case block's inner guard. // Main dungeons: SCENE_DEKU_TREE (0x00) through SCENE_ICE_CAVERN (0x09) // Boss rooms: SCENE_DEKU_TREE_BOSS (0x11) through SCENE_SHADOW_TEMPLE_BOSS (0x18) if (play->sceneNum <= SCENE_ICE_CAVERN || - (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) - { + (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) { return N64_MAP_MARK_DATA_VRAM_SIZE; } @@ -84,64 +81,59 @@ static u32 GetMapMarkDataOverlaySize(PlayState* play) // SKYBOX_NONE: Nothing // // The dList/vtx buffer pattern depends on drawType: -// SKYBOX_DRAW_128 (outdoor): dList=12×150×Gfx, vtx=5×32×Vtx (6×32 for CUTSCENE_MAP) -// SKYBOX_DRAW_256 (indoor, 3- or 4-face): dList=8×150×Gfx, vtx=8×32×Vtx +// SKYBOX_DRAW_128 (outdoor): dList=12×150×Gfx, vtx=5×32×Vtx (6×32 for CUTSCENE_MAP) +// SKYBOX_DRAW_256 (indoor, 3- or 4-face): dList=8×150×Gfx, vtx=8×32×Vtx // -------------------------------------------------------------------------------------------------------------------- // DMA file name for a single-file indoor skybox (1 tex + 1 pal). -typedef struct -{ +typedef struct { const char* texName; const char* palName; } SkyboxDmaEntry; // Indexed by skybox ID. NULL texName means the ID isn't a single-file indoor skybox (handled separately). static const SkyboxDmaEntry sSkyboxDmaTable[] = { - [SKYBOX_NONE] = {NULL, NULL}, - [SKYBOX_NORMAL_SKY] = {NULL, NULL}, // gNormalSkyFiles, hardcoded - [SKYBOX_BAZAAR] = {"vr_SP1a_static", "vr_SP1a_pal_static"}, - [SKYBOX_OVERCAST_SUNSET] = {NULL, NULL}, // same as NORMAL_SKY - [SKYBOX_MARKET_ADULT] = {"vr_RUVR_static", "vr_RUVR_pal_static"}, - [SKYBOX_CUTSCENE_MAP] = {NULL, NULL}, // Two tex files, handled separately - [SKYBOX_HOUSE_LINK] = {"vr_LHVR_static", "vr_LHVR_pal_static"}, - [SKYBOX_MARKET_CHILD_DAY] = {"vr_MDVR_static", "vr_MDVR_pal_static"}, - [SKYBOX_MARKET_CHILD_NIGHT] = {"vr_MNVR_static", "vr_MNVR_pal_static"}, - [SKYBOX_HAPPY_MASK_SHOP] = {"vr_FCVR_static", "vr_FCVR_pal_static"}, - [SKYBOX_HOUSE_KNOW_IT_ALL_BROTHERS] = {"vr_KHVR_static", "vr_KHVR_pal_static"}, - [SKYBOX_HOUSE_OF_TWINS] = {"vr_K3VR_static", "vr_K3VR_pal_static"}, - [SKYBOX_STABLES] = {"vr_MLVR_static", "vr_MLVR_pal_static"}, - [SKYBOX_HOUSE_KAKARIKO] = {"vr_KKRVR_static", "vr_KKRVR_pal_static"}, - [SKYBOX_KOKIRI_SHOP] = {"vr_KSVR_static", "vr_KSVR_pal_static"}, - [SKYBOX_GORON_SHOP] = {"vr_GLVR_static", "vr_GLVR_pal_static"}, - [SKYBOX_ZORA_SHOP] = {"vr_ZRVR_static", "vr_ZRVR_pal_static"}, - [SKYBOX_POTION_SHOP_KAKARIKO] = {"vr_DGVR_static", "vr_DGVR_pal_static"}, - [SKYBOX_POTION_SHOP_MARKET] = {"vr_ALVR_static", "vr_ALVR_pal_static"}, - [SKYBOX_BOMBCHU_SHOP] = {"vr_NSVR_static", "vr_NSVR_pal_static"}, - [SKYBOX_HOUSE_RICHARD] = {"vr_IPVR_static", "vr_IPVR_pal_static"}, - [SKYBOX_HOUSE_IMPA] = {"vr_LBVR_static", "vr_LBVR_pal_static"}, - [SKYBOX_TENT] = {"vr_TTVR_static", "vr_TTVR_pal_static"}, - [SKYBOX_HOUSE_MIDO] = {"vr_K4VR_static", "vr_K4VR_pal_static"}, - [SKYBOX_HOUSE_SARIA] = {"vr_K5VR_static", "vr_K5VR_pal_static"}, - [SKYBOX_HOUSE_ALLEY] = {"vr_KR3VR_static", "vr_KR3VR_pal_static"}, + [SKYBOX_NONE] = { NULL, NULL }, + [SKYBOX_NORMAL_SKY] = { NULL, NULL }, // gNormalSkyFiles, hardcoded + [SKYBOX_BAZAAR] = { "vr_SP1a_static", "vr_SP1a_pal_static" }, + [SKYBOX_OVERCAST_SUNSET] = { NULL, NULL }, // same as NORMAL_SKY + [SKYBOX_MARKET_ADULT] = { "vr_RUVR_static", "vr_RUVR_pal_static" }, + [SKYBOX_CUTSCENE_MAP] = { NULL, NULL }, // Two tex files, handled separately + [SKYBOX_HOUSE_LINK] = { "vr_LHVR_static", "vr_LHVR_pal_static" }, + [SKYBOX_MARKET_CHILD_DAY] = { "vr_MDVR_static", "vr_MDVR_pal_static" }, + [SKYBOX_MARKET_CHILD_NIGHT] = { "vr_MNVR_static", "vr_MNVR_pal_static" }, + [SKYBOX_HAPPY_MASK_SHOP] = { "vr_FCVR_static", "vr_FCVR_pal_static" }, + [SKYBOX_HOUSE_KNOW_IT_ALL_BROTHERS] = { "vr_KHVR_static", "vr_KHVR_pal_static" }, + [SKYBOX_HOUSE_OF_TWINS] = { "vr_K3VR_static", "vr_K3VR_pal_static" }, + [SKYBOX_STABLES] = { "vr_MLVR_static", "vr_MLVR_pal_static" }, + [SKYBOX_HOUSE_KAKARIKO] = { "vr_KKRVR_static", "vr_KKRVR_pal_static" }, + [SKYBOX_KOKIRI_SHOP] = { "vr_KSVR_static", "vr_KSVR_pal_static" }, + [SKYBOX_GORON_SHOP] = { "vr_GLVR_static", "vr_GLVR_pal_static" }, + [SKYBOX_ZORA_SHOP] = { "vr_ZRVR_static", "vr_ZRVR_pal_static" }, + [SKYBOX_POTION_SHOP_KAKARIKO] = { "vr_DGVR_static", "vr_DGVR_pal_static" }, + [SKYBOX_POTION_SHOP_MARKET] = { "vr_ALVR_static", "vr_ALVR_pal_static" }, + [SKYBOX_BOMBCHU_SHOP] = { "vr_NSVR_static", "vr_NSVR_pal_static" }, + [SKYBOX_HOUSE_RICHARD] = { "vr_IPVR_static", "vr_IPVR_pal_static" }, + [SKYBOX_HOUSE_IMPA] = { "vr_LBVR_static", "vr_LBVR_pal_static" }, + [SKYBOX_TENT] = { "vr_TTVR_static", "vr_TTVR_pal_static" }, + [SKYBOX_HOUSE_MIDO] = { "vr_K4VR_static", "vr_K4VR_pal_static" }, + [SKYBOX_HOUSE_SARIA] = { "vr_K5VR_static", "vr_K5VR_pal_static" }, + [SKYBOX_HOUSE_ALLEY] = { "vr_KR3VR_static", "vr_KR3VR_pal_static" }, }; -static u32 GetN64SkyboxTextureSize(s16 skyboxId) -{ - if (skyboxId == SKYBOX_NONE) - { +static u32 GetN64SkyboxTextureSize(s16 skyboxId) { + if (skyboxId == SKYBOX_NONE) { return 0; } // NORMAL_SKY and OVERCAST_SUNSET both load 2 texture banks + 2 palettes from the vr_fine/vr_cloud files. // All 16 banks are exactly 0xC000 and all 16 palettes are exactly 0x100, so the total is constant. - if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) - { + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { return 2 * 0xC000 + 2 * 0x100; } // CUTSCENE_MAP loads two different texture files + 2 palette copies. - if (skyboxId == SKYBOX_CUTSCENE_MAP) - { + if (skyboxId == SKYBOX_CUTSCENE_MAP) { const u32 tex0 = N64SizeData_GetDmaFileSize("vr_holy0_static"); const u32 tex1 = N64SizeData_GetDmaFileSize("vr_holy1_static"); const u32 pal = N64SizeData_GetDmaFileSize("vr_holy0_pal_static"); @@ -149,11 +141,9 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) } // Indoor skyboxes: 1 texture + 1 palette, looked up from the DMA blob. - if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) - { + if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) { const SkyboxDmaEntry* entry = &sSkyboxDmaTable[skyboxId]; - if (entry->texName != NULL) - { + if (entry->texName != NULL) { const u32 tex = N64SizeData_GetDmaFileSize(entry->texName); const u32 pal = N64SizeData_GetDmaFileSize(entry->palName); return tex + pal; @@ -173,27 +163,20 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) // Gfx is 8 bytes on N64, Vtx is 0x10. // -------------------------------------------------------------------------------------------------------------------- -static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) -{ - if (skyboxId == SKYBOX_NONE) - { +static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) { + if (skyboxId == SKYBOX_NONE) { *outDlistSize = 0; *outVtxSize = 0; return; } - if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) - { + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; *outVtxSize = 5 * 32 * N64_SIZEOF_VTX; - } - else if (skyboxId == SKYBOX_CUTSCENE_MAP) - { + } else if (skyboxId == SKYBOX_CUTSCENE_MAP) { *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; *outVtxSize = 6 * 32 * N64_SIZEOF_VTX; - } - else - { + } else { // Indoor skyboxes: SKYBOX_DRAW_256_4FACE or SKYBOX_DRAW_256_3FACE, both use the same allocation. *outDlistSize = 8 * 150 * N64_SIZEOF_GFX; *outVtxSize = 8 * 32 * N64_SIZEOF_VTX; @@ -215,31 +198,26 @@ static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVt // bgcheck_memSize and sizeof(CollisionContext). // -------------------------------------------------------------------------------------------------------------------- -static u32 GetBgCheckMemSize(PlayState* play) -{ +static u32 GetBgCheckMemSize(PlayState* play) { const s16 sceneNum = play->sceneNum; - if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) - { + if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) { return sceneNum == SCENE_STABLE ? 0x3520 : 0x4E20; } - if (BgCheck_IsSpotScene(play)) - { + if (BgCheck_IsSpotScene(play)) { return 0xF000; } u32 customMemSize = 0; - if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) - { + if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) { return customMemSize; } return 0x1CC00; } -static u32 GetBgCheckThaTotal(PlayState* play) -{ +static u32 GetBgCheckThaTotal(PlayState* play) { return GetBgCheckMemSize(play) - N64_SIZEOF_COLLISION_CONTEXT; } @@ -247,17 +225,14 @@ static u32 GetBgCheckThaTotal(PlayState* play) // Object bank size (mirrors z_scene.c Object_InitBlank) // -------------------------------------------------------------------------------------------------------------------- -static u32 GetObjectBankSize(PlayState* play) -{ +static u32 GetObjectBankSize(PlayState* play) { const s16 sceneNum = play->sceneNum; - if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) - { + if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) { return 1177600; } if (sceneNum == SCENE_SPIRIT_TEMPLE_BOSS || sceneNum == SCENE_CHAMBER_OF_THE_SAGES || - sceneNum == SCENE_GANONDORF_BOSS) - { + sceneNum == SCENE_GANONDORF_BOSS) { return 1075200; } @@ -276,16 +251,13 @@ static const char* sElfMsgDmaNames[] = { "elf_message_ydan", }; -static u32 GetElfMessageSize(PlayState* play) -{ - if (play->cUpElfMsgs == NULL) - { +static u32 GetElfMessageSize(PlayState* play) { + if (play->cUpElfMsgs == NULL) { return 0; } const u8 elfMsgNum = N64Mem_GetElfMsgNum(); - if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) - { + if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) { LUSLOG_WARN("[ArenaSizing] elfMsg: cUpElfMsgs non-NULL but elfMsgNum=%d out of range", elfMsgNum); return 0; } @@ -297,25 +269,20 @@ static u32 GetElfMessageSize(PlayState* play) // Room buffer max size (mirrors func_80096FE8 logic) // -------------------------------------------------------------------------------------------------------------------- -static u32 GetMaxRoomSize(PlayState* play) -{ +static u32 GetMaxRoomSize(PlayState* play) { u32 maxRoomSize = 0; - for (size_t i = 0; i < play->numRooms; ++i) - { + for (size_t i = 0; i < play->numRooms; ++i) { const u32 roomSize = play->roomList[i].vromEnd - play->roomList[i].vromStart; - if (roomSize > maxRoomSize) - { + if (roomSize > maxRoomSize) { maxRoomSize = roomSize; } } - if (play->transiActorCtx.numActors != 0) - { + if (play->transiActorCtx.numActors != 0) { const TransitionActorEntry* transitionActor = &play->transiActorCtx.list[0]; - for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) - { + for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) { const s8 frontRoom = transitionActor->sides[0].room; const s8 backRoom = transitionActor->sides[1].room; const size_t frontSize = frontRoom < 0 @@ -326,8 +293,7 @@ static u32 GetMaxRoomSize(PlayState* play) : play->roomList[backRoom].vromEnd - play->roomList[backRoom].vromStart; const u32 cumulSize = frontRoom != backRoom ? frontSize + backSize : frontSize; - if (cumulSize > maxRoomSize) - { + if (cumulSize > maxRoomSize) { maxRoomSize = cumulSize; } @@ -342,8 +308,7 @@ static u32 GetMaxRoomSize(PlayState* play) // Main computation // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) -{ +u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { // Each THA consumer is allocated via GAME_STATE_ALLOC -> THA_AllocTailAlign16, which consumes ALIGN16(size) bytes. // We must align each consumer individually before summing; aligning the sum would under-count when individual // sizes are not 16-byte aligned (common for DMA file sizes). BgCheck is the exception -- its internal allocations @@ -361,16 +326,18 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) total += ALIGN16(parameterStaticSize); // Fixed consumers - total += ALIGN16(N64_MATRIX_STACK_SIZE); - total += ALIGN16(0x55 * N64_SIZEOF_EFFECT_SS); - total += ALIGN16(N64_TEXT_BOX_SIZE); - total += ALIGN16(N64_DO_ACTION_SIZE); - total += ALIGN16(N64_ICON_ITEM_SIZE); - total += ALIGN16(N64_MAP_SEGMENT_SIZE); - - const u32 fixed = total; - LUSLOG_INFO("[ArenaSizing] fixed=0x%X (kaleido=0x%X, param=0x%X)", fixed, kaleidoVramSize, - parameterStaticSize); + { + total += ALIGN16(N64_MATRIX_STACK_SIZE); + total += ALIGN16(0x55 * N64_SIZEOF_EFFECT_SS); + total += ALIGN16(N64_TEXT_BOX_SIZE); + total += ALIGN16(N64_DO_ACTION_SIZE); + total += ALIGN16(N64_ICON_ITEM_SIZE); + total += ALIGN16(N64_MAP_SEGMENT_SIZE); + + const u32 fixed = total; + LUSLOG_INFO("[ArenaSizing] fixed=0x%X (kaleido=0x%X, param=0x%X)", fixed, kaleidoVramSize, + parameterStaticSize); + } // Scene-dependent consumers { @@ -434,10 +401,9 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) LUSLOG_INFO("[ArenaSizing] total=0x%X, arena=0x%X (budget=0x%X)", total, N64_THA_BUDGET - total, (u32)N64_THA_BUDGET); - if (total >= N64_THA_BUDGET) - { + if (total >= N64_THA_BUDGET) { return 0; } return N64_THA_BUDGET - total; -} +} \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h similarity index 94% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h rename to soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h index 7265b234603..91c5c5075e4 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h @@ -4,6 +4,7 @@ #ifdef __cplusplus extern "C" { + #endif // -------------------------------------------------------------------------------------------------------------------- @@ -21,7 +22,7 @@ extern "C" { // (should not happen on valid scenes). // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState * play); +u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c similarity index 79% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.c rename to soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c index 39cc5c83110..d0f2601cf70 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c @@ -1,4 +1,4 @@ -#include "shadow_arena.h" +#include "n64_shadow_arena.h" #include #include @@ -15,8 +15,7 @@ // 0x10 u8[0x20] (Dead space matching N64 debug fields) // -------------------------------------------------------------------------------------------------------------------- -typedef struct ShadowNode -{ +typedef struct ShadowNode { s16 magic; s16 isFree; u32 size; @@ -34,42 +33,34 @@ static_assert(sizeof(ShadowNode) == SHADOW_NODE_SIZE, "ShadowNode must be exactl // Offset helpers // -------------------------------------------------------------------------------------------------------------------- -static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) -{ +static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) { return (ShadowNode*)(arena->buffer + offset); } -static s32 NodeIsValid(ShadowArena* arena, u32 offset) -{ - if (offset == SHADOW_NULL) - { +static s32 NodeIsValid(ShadowArena* arena, u32 offset) { + if (offset == SHADOW_NULL) { return 0; } - if (offset + SHADOW_NODE_SIZE > arena->bufferSize) - { + if (offset + SHADOW_NODE_SIZE > arena->bufferSize) { return 0; } return NodeAt(arena, offset)->magic == NODE_MAGIC; } -static u32 NodeGetNext(ShadowArena* arena, u32 offset) -{ +static u32 NodeGetNext(ShadowArena* arena, u32 offset) { const ShadowNode* node = NodeAt(arena, offset); - if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) - { + if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) { return node->next; } return SHADOW_NULL; } -static u32 NodeGetPrev(ShadowArena* arena, u32 offset) -{ +static u32 NodeGetPrev(ShadowArena* arena, u32 offset) { const ShadowNode* node = NodeAt(arena, offset); - if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) - { + if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) { return node->prev; } @@ -80,15 +71,13 @@ static u32 NodeGetPrev(ShadowArena* arena, u32 offset) // Init / Destroy // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Init(ShadowArena* arena, u32 size) -{ - // Match N64's alignment: Round start up to 16, round size down to 16. Since we control the buffer, start is - // effectively offset 0 after alignment. We just ensure the usable size is 16-byte aligned. +void ShadowArena_Init(ShadowArena* arena, u32 size) { + // Match N64's alignment: Round start up to 16, round size down to 16. Since we control the buffer, start is + // effectively offset 0 after alignment. We just ensure the usable size is 16-byte aligned. const u32 alignedSize = size & ~0xF; arena->buffer = (u8*)malloc(alignedSize); - if (!arena->buffer) - { + if (!arena->buffer) { memset(arena, 0, sizeof(ShadowArena)); return; } @@ -108,10 +97,8 @@ void ShadowArena_Init(ShadowArena* arena, u32 size) arena->head = 0; } -void ShadowArena_Destroy(ShadowArena* arena) -{ - if (arena->buffer) - { +void ShadowArena_Destroy(ShadowArena* arena) { + if (arena->buffer) { free(arena->buffer); } @@ -122,8 +109,7 @@ void ShadowArena_Destroy(ShadowArena* arena) // Malloc: First-fit forward (matches N64 __osMalloc) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) -{ +u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { u32 iterOff = 0; ShadowNode* iter = NULL; u32 blockSize = 0; @@ -132,14 +118,11 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) blockSize = size + SHADOW_NODE_SIZE; iterOff = arena->head; - while (iterOff != SHADOW_NULL) - { + while (iterOff != SHADOW_NULL) { iter = NodeAt(arena, iterOff); - if (iter->isFree && iter->size >= size) - { + if (iter->isFree && iter->size >= size) { // Split if remainder can hold a new node + payload. - if (blockSize < iter->size) - { + if (blockSize < iter->size) { const u32 newOff = iterOff + blockSize; ShadowNode* newNode = NodeAt(arena, newOff); @@ -150,8 +133,7 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) newNode->prev = iterOff; memset(newNode->_dead, 0, sizeof(newNode->_dead)); - if (newNode->next != SHADOW_NULL) - { + if (newNode->next != SHADOW_NULL) { NodeAt(arena, newNode->next)->prev = newOff; } @@ -176,8 +158,7 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) // MallocR: First-fit backward (matches N64 __osMallocR) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) -{ +u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { u32 iterOff = 0; u32 nextOff = 0; ShadowNode* iter = NULL; @@ -188,23 +169,19 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) // Walk to tail. iterOff = arena->head; nextOff = NodeGetNext(arena, iterOff); - while (nextOff != SHADOW_NULL) - { + while (nextOff != SHADOW_NULL) { iterOff = nextOff; nextOff = NodeGetNext(arena, nextOff); } // Walk backward looking for a fit. - while (iterOff != SHADOW_NULL) - { + while (iterOff != SHADOW_NULL) { iter = NodeAt(arena, iterOff); - if (iter->isFree && iter->size >= size) - { + if (iter->isFree && iter->size >= size) { blockSize = size + SHADOW_NODE_SIZE; // Split: Carve the allocation from the TOP of this free block. - if (blockSize < iter->size) - { + if (blockSize < iter->size) { const u32 newOff = iterOff + (iter->size - size); ShadowNode* newNode = NodeAt(arena, newOff); @@ -215,8 +192,7 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) newNode->prev = iterOff; memset(newNode->_dead, 0, sizeof(newNode->_dead)); - if (newNode->next != SHADOW_NULL) - { + if (newNode->next != SHADOW_NULL) { NodeAt(arena, newNode->next)->prev = newOff; } @@ -243,28 +219,24 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) // Free: Adjacent block coalescing (matches N64 _osFree) // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) -{ +void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { u32 nodeOff = 0; ShadowNode* node = NULL; u32 nextOff = 0; u32 prevOff = 0; - if (dataOffset == SHADOW_NULL) - { + if (dataOffset == SHADOW_NULL) { return; } nodeOff = dataOffset - SHADOW_NODE_SIZE; node = NodeAt(arena, nodeOff); - if (node->magic != NODE_MAGIC) - { + if (node->magic != NODE_MAGIC) { return; } - if (node->isFree) - { + if (node->isFree) { return; } @@ -273,13 +245,10 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) // Forward coalesce: Merge with next if it's free. nextOff = node->next; - if (nextOff != SHADOW_NULL) - { + if (nextOff != SHADOW_NULL) { const ShadowNode* next = NodeAt(arena, nextOff); - if (next->isFree) - { - if (next->next != SHADOW_NULL) - { + if (next->isFree) { + if (next->next != SHADOW_NULL) { NodeAt(arena, next->next)->prev = nodeOff; } @@ -290,16 +259,13 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) // Backward coalesce: Merge into prev if it's free. prevOff = node->prev; - if (prevOff != SHADOW_NULL) - { + if (prevOff != SHADOW_NULL) { ShadowNode* prev = NodeAt(arena, prevOff); - if (prev->isFree) - { + if (prev->isFree) { prev->size += node->size + SHADOW_NODE_SIZE; prev->next = node->next; - if (node->next != SHADOW_NULL) - { + if (node->next != SHADOW_NULL) { NodeAt(arena, node->next)->prev = prevOff; } } @@ -310,8 +276,7 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) // Query // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) -{ +void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) { u32 iterOff = 0; *outMaxFree = 0; @@ -319,20 +284,15 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 *outAlloc = 0; iterOff = arena->head; - while (iterOff != SHADOW_NULL) - { + while (iterOff != SHADOW_NULL) { const ShadowNode* node = NodeAt(arena, iterOff); - if (node->isFree) - { + if (node->isFree) { *outFree += node->size; - if (node->size > *outMaxFree) - { + if (node->size > *outMaxFree) { *outMaxFree = node->size; } - } - else - { + } else { *outAlloc += node->size; } @@ -340,21 +300,17 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 } } -u32 ShadowArena_GetHead(ShadowArena* arena) -{ +u32 ShadowArena_GetHead(ShadowArena* arena) { return arena->head; } -s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) -{ - if (offset == SHADOW_NULL || offset + SHADOW_NODE_SIZE > arena->bufferSize) - { +s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) { + if (offset == SHADOW_NULL || offset + SHADOW_NODE_SIZE > arena->bufferSize) { return 0; } ShadowNode* node = NodeAt(arena, offset); - if (node->magic != NODE_MAGIC) - { + if (node->magic != NODE_MAGIC) { return 0; } @@ -364,7 +320,6 @@ s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* return 1; } -u32 ShadowArena_GetBufferSize(ShadowArena* arena) -{ +u32 ShadowArena_GetBufferSize(ShadowArena* arena) { return arena->bufferSize; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h similarity index 93% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h rename to soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h index e1cc389fb3f..dee79b68c83 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h @@ -4,8 +4,8 @@ #ifdef __cplusplus extern "C" { -#endif +#endif // Sentinel value for null offsets (no valid node can live at 0xFFFFFFFF in a buffer that's only ~245KB). #define SHADOW_NULL 0xFFFFFFFF @@ -13,8 +13,7 @@ extern "C" { // Matches N64 retail ArenaNode: 0x10 bookkeeping + 0x20 debug fields. #define SHADOW_NODE_SIZE 0x30 -typedef struct ShadowArena -{ +typedef struct ShadowArena { u8* buffer; u32 head; // Offset to first node u32 bufferSize; @@ -36,7 +35,7 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size); void ShadowArena_Free(ShadowArena* arena, u32 dataOffset); // Query arena statistics. -void ShadowArena_GetSizes(ShadowArena * arena, u32 * outMaxFree, u32 * outFree, u32 * outAlloc); +void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc); // Get the head node offset for external traversal (e.g., heap viewer). u32 ShadowArena_GetHead(ShadowArena* arena); diff --git a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp index 443e8fb93f7..9fc086f3154 100644 --- a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp +++ b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp @@ -12,13 +12,10 @@ extern "C" { #include "functions.h" #include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" -#include "soh/Enhancements/Restorations/N64MemoryModel/ShadowArena/shadow_arena.h" - -extern ArenaNode* ZeldaArena_GetHead(); +#include "soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h" } -struct BlockInfo -{ +struct BlockInfo { u32 offset; // For shadow: offset into buffer. For ZeldaArena: not used. std::uintptr_t address; std::size_t size; @@ -46,8 +43,7 @@ static u32 sShadowPreviousAlloc = 0; static u32 sShadowCycleCount = 0; static s32 sShadowLastDelta = 0; -static void CollectBlocks() -{ +static void CollectBlocks() { sBlocks.clear(); sAllocTotal = 0; sFreeTotal = 0; @@ -56,13 +52,11 @@ static void CollectBlocks() sArenaSize = 0; ArenaNode* node = ZeldaArena_GetHead(); - if (!node) - { + if (!node) { return; } - while (node) - { + while (node) { BlockInfo block; block.address = reinterpret_cast(node) + sizeof(ArenaNode); block.size = node->size; @@ -72,32 +66,26 @@ static void CollectBlocks() sArenaSize += sizeof(ArenaNode) + node->size; sNodeCount++; - if (node->isFree) - { + if (node->isFree) { sFreeTotal += node->size; - if (node->size > sLargestFree) - { + if (node->size > sLargestFree) { sLargestFree = node->size; } - } - else - { + } else { sAllocTotal += node->size; } node = node->next; } - if (sPreviousAlloc != 0 && sAllocTotal != sPreviousAlloc) - { + if (sPreviousAlloc != 0 && sAllocTotal != sPreviousAlloc) { sLastDelta = static_cast(sAllocTotal) - static_cast(sPreviousAlloc); ++sCycleCount; } sPreviousAlloc = sAllocTotal; } -static void CollectShadowBlocks() -{ +static void CollectShadowBlocks() { sShadowBlocks.clear(); sShadowAllocTotal = 0; sShadowFreeTotal = 0; @@ -105,8 +93,7 @@ static void CollectShadowBlocks() sShadowNodeCount = 0; ShadowArena* shadow = N64Mem_GetShadowArena(); - if (!shadow) - { + if (!shadow) { sShadowArenaSize = 0; return; } @@ -114,14 +101,12 @@ static void CollectShadowBlocks() sShadowArenaSize = ShadowArena_GetBufferSize(shadow); u32 offset = ShadowArena_GetHead(shadow); - while (offset != SHADOW_NULL) - { + while (offset != SHADOW_NULL) { s32 isFree = 0; u32 size = 0; u32 next = SHADOW_NULL; - if (!ShadowArena_GetNodeInfo(shadow, offset, &isFree, &size, &next)) - { + if (!ShadowArena_GetNodeInfo(shadow, offset, &isFree, &size, &next)) { break; } @@ -134,56 +119,45 @@ static void CollectShadowBlocks() sShadowBlocks.push_back(block); sShadowNodeCount++; - if (isFree) - { + if (isFree) { sShadowFreeTotal += size; - if (size > sShadowLargestFree) - { + if (size > sShadowLargestFree) { sShadowLargestFree = size; } - } - else - { + } else { sShadowAllocTotal += size; } offset = next; } - if (sShadowPreviousAlloc != 0 && sShadowAllocTotal != sShadowPreviousAlloc) - { + if (sShadowPreviousAlloc != 0 && sShadowAllocTotal != sShadowPreviousAlloc) { sShadowLastDelta = static_cast(sShadowAllocTotal) - static_cast(sShadowPreviousAlloc); ++sShadowCycleCount; } sShadowPreviousAlloc = sShadowAllocTotal; } -static ImU32 ColorAlloc() -{ +static ImU32 ColorAlloc() { return IM_COL32(200, 60, 60, 255); } -static ImU32 ColorFree() -{ +static ImU32 ColorFree() { return IM_COL32(60, 180, 80, 255); } -static ImU32 ColorNode() -{ +static ImU32 ColorNode() { return IM_COL32(80, 80, 80, 255); } -static ImU32 ColorBorder() -{ +static ImU32 ColorBorder() { return IM_COL32(40, 40, 40, 255); } static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u32 allocTotal, u32 freeTotal, u32 largestFree, u32 nodeCount, u32 cycleCount, - s32 lastDelta, u32 nodeSize, const char* id) -{ - if (blocks.empty()) - { + s32 lastDelta, u32 nodeSize, const char* id) { + if (blocks.empty()) { ImGui::Text("Arena is not initialized."); return; } @@ -229,18 +203,15 @@ static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u f32 xCursor = 0.0f; s32 hoveredBlock = -1; - for (std::size_t i = 0; i < blocks.size(); ++i) - { + for (std::size_t i = 0; i < blocks.size(); ++i) { f32 nodeWidth = static_cast(nodeSize) / static_cast(arenaSize) * availWidth; f32 blockWidth = static_cast(blocks[i].size) / static_cast(arenaSize) * availWidth; - if (nodeWidth < 1.0f) - { + if (nodeWidth < 1.0f) { nodeWidth = 1.0f; } - if (blockWidth < 1.0f && blocks[i].size > 0) - { + if (blockWidth < 1.0f && blocks[i].size > 0) { blockWidth = 1.0f; } @@ -256,16 +227,14 @@ static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u const f32 fullX0 = mapPos.x + xCursor - nodeWidth; ImVec2 blockMin(fullX0, mapPos.y); - if (ImVec2 blockMax(x1, mapPos.y + mapHeight); ImGui::IsMouseHoveringRect(blockMin, blockMax)) - { + if (ImVec2 blockMax(x1, mapPos.y + mapHeight); ImGui::IsMouseHoveringRect(blockMin, blockMax)) { hoveredBlock = static_cast(i); } xCursor += blockWidth; } - if (hoveredBlock >= 0) - { + if (hoveredBlock >= 0) { const auto& [offset, address, size, isFree] = blocks[hoveredBlock]; ImGui::BeginTooltip(); ImGui::Text("Block %d: %s", hoveredBlock, isFree ? "FREE" : "ALLOCATED"); @@ -285,8 +254,7 @@ static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u if (ImGui::BeginTable(tableId.c_str(), 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable, - ImVec2(0, ImGui::GetContentRegionAvail().y))) - { + ImVec2(0, ImGui::GetContentRegionAvail().y))) { ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, 40.0f); ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 80.0f); ImGui::TableSetupColumn("Offset", ImGuiTableColumnFlags_WidthFixed, 140.0f); @@ -294,8 +262,7 @@ static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); - for (std::size_t i = 0; i < blocks.size(); ++i) - { + for (std::size_t i = 0; i < blocks.size(); ++i) { const auto& [offset, address, size, isFree] = blocks[i]; ImGui::TableNextRow(); @@ -303,12 +270,9 @@ static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u ImGui::Text("%zu", i); ImGui::TableNextColumn(); - if (isFree) - { + if (isFree) { ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.3f, 1.0f), "FREE"); - } - else - { + } else { ImGui::TextColored(ImVec4(0.8f, 0.25f, 0.25f, 1.0f), "ALLOC"); } @@ -323,12 +287,9 @@ static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u } } -void HeapViewerWindow::DrawElement() -{ - if (ImGui::BeginTabBar("##heap_tabs")) - { - if (ImGui::BeginTabItem("ZeldaArena")) - { +void HeapViewerWindow::DrawElement() { + if (ImGui::BeginTabBar("##heap_tabs")) { + if (ImGui::BeginTabItem("ZeldaArena")) { CollectBlocks(); DrawArenaView(sBlocks, sArenaSize, sAllocTotal, sFreeTotal, sLargestFree, sNodeCount, sCycleCount, sLastDelta, @@ -336,14 +297,11 @@ void HeapViewerWindow::DrawElement() ImGui::EndTabItem(); } - if (N64Mem_IsActive()) - { - if (ImGui::BeginTabItem("ShadowArena (N64)")) - { + if (N64Mem_IsActive()) { + if (ImGui::BeginTabItem("ShadowArena (N64)")) { CollectShadowBlocks(); - if (ImGui::Button("Reset Tracking")) - { + if (ImGui::Button("Reset Tracking")) { sShadowPreviousAlloc = 0; sShadowCycleCount = 0; sShadowLastDelta = 0; @@ -359,4 +317,4 @@ void HeapViewerWindow::DrawElement() ImGui::EndTabBar(); } -} +} \ No newline at end of file diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index db120ecbfc6..4a090c95537 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1215,9 +1215,9 @@ void SohMenu::AddMenuEnhancements() { AddWidget(path, "N64 Memory Model", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("N64MemoryModel")) .Options(CheckboxOptions().Tooltip( - "Simulates N64 memory constraints using a shadow arena with the original allocator and struct sizes. " - "Enables memory-dependent behaviors such as actor spawn failures from heap fragmentation during repeated " - "room transitions.")); + "Simulates N64 hardware memory limits. Repeated room transitions will eventually prevent new actors " + "from spawning, matching original N64 behavior.\n" + "Required for authentic SRM and heap manipulation techniques.")); AddWidget(path, "Misc Restorations", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Fix L&Z Page Switch in Pause Menu", WIDGET_CVAR_CHECKBOX) From f4b41983f6ce81e0982f54920c0d94792e8cd497 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 17:31:14 -0700 Subject: [PATCH 29/58] fix(restoration): Save actor ID before free in Actor_Delete --- soh/src/code/z_actor.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 2a8589f9ef8..d7fab4a0ad5 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -3558,6 +3558,7 @@ Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) { // #region SOH [Enhancement] - N64 Memory Model N64Mem_FreeInstance(actor); + const s16 n64MemActorId = actor->id; // #endregion ZELDA_ARENA_FREE_DEBUG(actor); @@ -3565,9 +3566,8 @@ Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) { dbEntry->numLoaded--; // #region SOH [Enhancement] - N64 Memory Model - if (dbEntry->numLoaded == 0) - { - N64Mem_FreeOverlay(actor->id, dbEntry->allocType); + if (dbEntry->numLoaded == 0) { + N64Mem_FreeOverlay(n64MemActorId, dbEntry->allocType); } // #endregion From ebbb450326ab4bccd3c43528d2b7ebd3d9bb23d9 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 18:16:45 -0700 Subject: [PATCH 30/58] feat(restoration): Use original actor sizes for enemy randomizer compatibility --- .../ExtraModes/EnemyRandomizer.cpp | 13 +- .../N64MemoryModel/N64MemoryModel.cpp | 29 +++- .../N64MemoryModel/N64MemoryModel.hpp | 17 +- .../N64MemoryModel/N64SizeData.cpp | 84 ++++------ .../N64MemoryModel/n64_arena_sizing.c | 155 +++++++++++------- .../N64MemoryModel/n64_arena_sizing.h | 3 +- .../N64MemoryModel/n64_shadow_arena.c | 133 ++++++++++----- .../N64MemoryModel/n64_shadow_arena.h | 6 +- 8 files changed, 270 insertions(+), 170 deletions(-) diff --git a/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp b/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp index 04514b35205..7fb51e07b53 100644 --- a/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp +++ b/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp @@ -9,6 +9,7 @@ #include "soh/ResourceManagerHelpers.h" #include "soh/SohGui/MenuTypes.h" #include "soh/SohGui/SohMenu.h" +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" extern "C" { #include @@ -458,9 +459,19 @@ uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, s16* posX, s16* po play->sceneNum + *actorId + (int)*posX + (int)*posY + (int)*posZ + *rotX + *rotY + *rotZ + *params; EnemyEntry randomEnemy = GetRandomizedEnemyEntry(seed, play); + // #region SOH [Enhancement] - N64 Memory Model + const s16 originalActorId = *actorId; + // #endregion + *actorId = randomEnemy.id; *params = randomEnemy.params; + // #region SOH [Enhancement] - N64 Memory Model + // Tell the N64 memory model to charge the original actor's N64 sizes for this spawn, preserving authentic + // heap geometry regardless of which enemy the randomizer substitutes. + N64Mem_SetOriginalActorId(originalActorId); + // #endregion + // Straighten out enemies so they aren't flipped on their sides when the original spawn is. *rotX = 0; @@ -986,4 +997,4 @@ void RegisterEnemyRandomizerWidgets() { } static RegisterShipInitFunc initFunc(RegisterEnemyRandomizer, { CVAR_ENEMY_RANDOMIZER_NAME }); -static RegisterMenuInitFunc menuInitFunc(RegisterEnemyRandomizerWidgets); +static RegisterMenuInitFunc menuInitFunc(RegisterEnemyRandomizerWidgets); \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index cf4b17d1de4..add656722c9 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -36,6 +36,17 @@ static u32 sAbsoluteSpaceShadow = SHADOW_NULL; // Maps real pointers (instances and subsidiaries) to their shadow offsets. static std::unordered_map sShadowMap; +// When the enemy randomizer replaces an actor, this holds the ORIGINAL actor ID so that shadow allocations are charged +// at the original N64 size rather than the replacement's size. Set by N64Mem_SetOriginalActorId before Actor_Spawn, +// cleared by N64Mem_ClearOriginalActorId after Actor_Spawn returns. -1 means no override (use the passed actorId). +static s16 sOriginalActorId = -1; + +// Returns the actor ID to use for size lookups. If an original actor ID override is set (enemy randomizer active), +// returns that; otherwise returns the passed actorId unchanged. +static u16 ResolveSizeActorId(s16 actorId) { + return sOriginalActorId >= 0 ? static_cast(sOriginalActorId) : static_cast(actorId); +} + // -------------------------------------------------------------------------------------------------------------------- // Diagnostics // -------------------------------------------------------------------------------------------------------------------- @@ -177,6 +188,14 @@ void N64Mem_LogState(const char* context) { } } +void N64Mem_SetOriginalActorId(s16 actorId) { + sOriginalActorId = actorId; +} + +void N64Mem_ClearOriginalActorId() { + sOriginalActorId = -1; +} + // -------------------------------------------------------------------------------------------------------------------- // Actor overlays // -------------------------------------------------------------------------------------------------------------------- @@ -190,7 +209,7 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { return 1; } - const u32 overlaySize = N64SizeData_GetActorOverlaySize(actorId); + const u32 overlaySize = N64SizeData_GetActorOverlaySize(ResolveSizeActorId(actorId)); if (overlaySize == 0) { return 1; } @@ -251,14 +270,18 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { if (!sIsActive) { + sOriginalActorId = -1; return 1; } + const u16 sizeId = ResolveSizeActorId(actorId); + sOriginalActorId = -1; + if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return 1; } - const u32 instanceSize = N64SizeData_GetActorInstanceSize(actorId); + const u32 instanceSize = N64SizeData_GetActorInstanceSize(sizeId); if (instanceSize == 0) { return 1; } @@ -371,4 +394,4 @@ void RegisterN64MemoryModel() { // #TODO: Shadow arena initialization } -static RegisterShipInitFunc initFunc(RegisterN64MemoryModel, { CVAR_NAME }); +static RegisterShipInitFunc initFunc(RegisterN64MemoryModel, { CVAR_NAME }); \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index 07374fc99c6..eb1d6ebfa08 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -42,6 +42,21 @@ struct ShadowArena* N64Mem_GetShadowArena(void); // Log the current shadow arena state (alloc/free/largest) with the given context label. void N64Mem_LogState(const char* context); +// -------------------------------------------------------------------------------------------------------------------- +// Enemy randomizer compatibility +// +// When the enemy randomizer replaces an actor, the shadow arena should charge the ORIGINAL actor's N64 sizes (overlay +// and instance) rather than the replacement's. This preserves authentic N64 heap geometry — memory-dependent +// behaviors (SRM, ACE, spawn failure thresholds) remain consistent regardless of which enemies are on screen. +// +// Call SetOriginalActorId with the scene's original actor ID before Actor_Spawn, and ClearOriginalActorId after +// Actor_Spawn returns. When set, overlay and instance size lookups use the original ID; all other tracking (overlay +// ref counting, instance pointer mapping) uses the actual spawned actor ID. +// -------------------------------------------------------------------------------------------------------------------- + +void N64Mem_SetOriginalActorId(s16 actorId); +void N64Mem_ClearOriginalActorId(void); + // Log Graveyard benchmark data (transition count, largest_free, total_free). Call after room actors are spawned. void N64Mem_BenchmarkTransition(PlayState* play); @@ -89,4 +104,4 @@ s32 N64Mem_AllocEffectOverlay(s32 type); #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp index 8087c318c3c..7d921cb43fd 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp @@ -9,10 +9,8 @@ static std::unordered_map sDmaFileSizes; static bool sIsDmaLoaded = false; -static void LoadDmaFileSizes() -{ - if (sIsDmaLoaded) - { +static void LoadDmaFileSizes() { + if (sIsDmaLoaded) { return; } @@ -20,8 +18,7 @@ static void LoadDmaFileSizes() const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/dma_sizes"); - if (!file || !file->IsLoaded) - { + if (!file || !file->IsLoaded) { SPDLOG_ERROR("[N64SizeData] Failed to load misc/dma_sizes from OTR."); return; } @@ -31,8 +28,7 @@ static void LoadDmaFileSizes() reader->SetEndianness(Ship::Endianness::Big); const u32 entryCount = reader->ReadUInt32(); - for (std::size_t i = 0; i < entryCount; ++i) - { + for (std::size_t i = 0; i < entryCount; ++i) { const u32 vromSize = reader->ReadUInt32(); const std::string name = reader->ReadString(); sDmaFileSizes[name] = vromSize; @@ -50,10 +46,8 @@ static void LoadDmaFileSizes() static std::vector sActorOverlaySizes; static bool sIsActorOverlayLoaded = false; -static void LoadActorOverlaySizes() -{ - if (sIsActorOverlayLoaded) - { +static void LoadActorOverlaySizes() { + if (sIsActorOverlayLoaded) { return; } @@ -61,8 +55,7 @@ static void LoadActorOverlaySizes() const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/actor_overlay_sizes"); - if (!file || !file->IsLoaded) - { + if (!file || !file->IsLoaded) { SPDLOG_ERROR("[N64SizeData] Failed to load misc/actor_overlay_sizes from OTR."); return; } @@ -74,8 +67,7 @@ static void LoadActorOverlaySizes() const u32 entryCount = reader->ReadUInt32(); sActorOverlaySizes.resize(entryCount); - for (std::size_t i = 0; i < entryCount; ++i) - { + for (std::size_t i = 0; i < entryCount; ++i) { sActorOverlaySizes.at(i) = reader->ReadUInt32(); } @@ -91,10 +83,8 @@ static void LoadActorOverlaySizes() static std::vector sEffectOverlaySizes; static bool sIsEffectOverlayLoaded = false; -static void LoadEffectOverlaySizes() -{ - if (sIsEffectOverlayLoaded) - { +static void LoadEffectOverlaySizes() { + if (sIsEffectOverlayLoaded) { return; } @@ -102,8 +92,7 @@ static void LoadEffectOverlaySizes() const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/effect_overlay_sizes"); - if (!file || !file->IsLoaded) - { + if (!file || !file->IsLoaded) { SPDLOG_ERROR("[N64SizeData] Failed to load misc/effect_overlay_sizes from OTR."); return; } @@ -115,8 +104,7 @@ static void LoadEffectOverlaySizes() const u32 entryCount = reader->ReadUInt32(); sEffectOverlaySizes.resize(entryCount); - for (std::size_t i = 0; i < entryCount; ++i) - { + for (std::size_t i = 0; i < entryCount; ++i) { sEffectOverlaySizes.at(i) = reader->ReadUInt32(); } @@ -133,10 +121,8 @@ static void LoadEffectOverlaySizes() static std::vector sActorInstanceSizes; static bool sIsActorInstanceLoaded = false; -static void LoadActorInstanceSizes() -{ - if (sIsActorInstanceLoaded) - { +static void LoadActorInstanceSizes() { + if (sIsActorInstanceLoaded) { return; } @@ -144,8 +130,7 @@ static void LoadActorInstanceSizes() const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/actor_instance_sizes"); - if (!file || !file->IsLoaded) - { + if (!file || !file->IsLoaded) { SPDLOG_ERROR("[N64SizeData] Failed to load misc/actor_instance_sizes from OTR."); return; } @@ -157,8 +142,7 @@ static void LoadActorInstanceSizes() const u32 entryCount = reader->ReadUInt32(); sActorInstanceSizes.resize(entryCount); - for (std::size_t i = 0; i < entryCount; ++i) - { + for (std::size_t i = 0; i < entryCount; ++i) { sActorInstanceSizes.at(i) = reader->ReadUInt32(); } @@ -169,12 +153,10 @@ static void LoadActorInstanceSizes() // API // -------------------------------------------------------------------------------------------------------------------- -extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) -{ +extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) { LoadDmaFileSizes(); - if (const auto i = sDmaFileSizes.find(name); i != sDmaFileSizes.end()) - { + if (const auto i = sDmaFileSizes.find(name); i != sDmaFileSizes.end()) { return i->second; } @@ -182,36 +164,30 @@ extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) return 0; } -extern "C" u32 N64SizeData_GetActorOverlaySize(u16 actorId) -{ +extern "C" u32 N64SizeData_GetActorOverlaySize(u16 actorId) { LoadActorOverlaySizes(); - if (actorId < sActorOverlaySizes.size()) - { + if (actorId < sActorOverlaySizes.size()) { return sActorOverlaySizes.at(actorId); } return 0; } -extern "C" u32 N64SizeData_GetEffectOverlaySize(u16 effectType) -{ +extern "C" u32 N64SizeData_GetEffectOverlaySize(u16 effectType) { LoadEffectOverlaySizes(); - if (effectType < sEffectOverlaySizes.size()) - { + if (effectType < sEffectOverlaySizes.size()) { return sEffectOverlaySizes.at(effectType); } return 0; } -extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) -{ +extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) { LoadActorInstanceSizes(); - if (actorId < sActorInstanceSizes.size()) - { + if (actorId < sActorInstanceSizes.size()) { return sActorInstanceSizes.at(actorId); } @@ -227,10 +203,8 @@ extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) static u32 sKaleidoVramSize = 0; static bool sIsKaleidoLoaded = false; -static void LoadKaleidoVramSize() -{ - if (sIsKaleidoLoaded) - { +static void LoadKaleidoVramSize() { + if (sIsKaleidoLoaded) { return; } @@ -238,8 +212,7 @@ static void LoadKaleidoVramSize() const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/kaleido_vram_size"); - if (!file || !file->IsLoaded) - { + if (!file || !file->IsLoaded) { SPDLOG_ERROR("[N64SizeData] Failed to load misc/kaleido_vram_size from OTR."); return; } @@ -253,8 +226,7 @@ static void LoadKaleidoVramSize() SPDLOG_INFO("[N64SizeData] Kaleido max VRAM size: 0x{:X}.", sKaleidoVramSize); } -extern "C" u32 N64SizeData_GetKaleidoVramSize() -{ +extern "C" u32 N64SizeData_GetKaleidoVramSize() { LoadKaleidoVramSize(); return sKaleidoVramSize; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c index b00881d24d0..2eab39b6a63 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c @@ -5,7 +5,7 @@ #include "global.h" // Declared in z_bgcheck.c but not exposed via header. -s32 BgCheck_IsSpotScene(PlayState* play); +s32 BgCheck_IsSpotScene(PlayState * play); s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); // -------------------------------------------------------------------------------------------------------------------- @@ -56,12 +56,14 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); #define N64_MAP_MARK_DATA_VRAM_SIZE 0x6B60 -static u32 GetMapMarkDataOverlaySize(PlayState* play) { +static u32 GetMapMarkDataOverlaySize(PlayState* play) +{ // Mirrors the condition in z_map_exp.c Map_Init: the dungeon case block's inner guard. // Main dungeons: SCENE_DEKU_TREE (0x00) through SCENE_ICE_CAVERN (0x09) // Boss rooms: SCENE_DEKU_TREE_BOSS (0x11) through SCENE_SHADOW_TEMPLE_BOSS (0x18) if (play->sceneNum <= SCENE_ICE_CAVERN || - (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) { + (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) + { return N64_MAP_MARK_DATA_VRAM_SIZE; } @@ -86,54 +88,59 @@ static u32 GetMapMarkDataOverlaySize(PlayState* play) { // -------------------------------------------------------------------------------------------------------------------- // DMA file name for a single-file indoor skybox (1 tex + 1 pal). -typedef struct { +typedef struct +{ const char* texName; const char* palName; } SkyboxDmaEntry; // Indexed by skybox ID. NULL texName means the ID isn't a single-file indoor skybox (handled separately). static const SkyboxDmaEntry sSkyboxDmaTable[] = { - [SKYBOX_NONE] = { NULL, NULL }, - [SKYBOX_NORMAL_SKY] = { NULL, NULL }, // gNormalSkyFiles, hardcoded - [SKYBOX_BAZAAR] = { "vr_SP1a_static", "vr_SP1a_pal_static" }, - [SKYBOX_OVERCAST_SUNSET] = { NULL, NULL }, // same as NORMAL_SKY - [SKYBOX_MARKET_ADULT] = { "vr_RUVR_static", "vr_RUVR_pal_static" }, - [SKYBOX_CUTSCENE_MAP] = { NULL, NULL }, // Two tex files, handled separately - [SKYBOX_HOUSE_LINK] = { "vr_LHVR_static", "vr_LHVR_pal_static" }, - [SKYBOX_MARKET_CHILD_DAY] = { "vr_MDVR_static", "vr_MDVR_pal_static" }, - [SKYBOX_MARKET_CHILD_NIGHT] = { "vr_MNVR_static", "vr_MNVR_pal_static" }, - [SKYBOX_HAPPY_MASK_SHOP] = { "vr_FCVR_static", "vr_FCVR_pal_static" }, - [SKYBOX_HOUSE_KNOW_IT_ALL_BROTHERS] = { "vr_KHVR_static", "vr_KHVR_pal_static" }, - [SKYBOX_HOUSE_OF_TWINS] = { "vr_K3VR_static", "vr_K3VR_pal_static" }, - [SKYBOX_STABLES] = { "vr_MLVR_static", "vr_MLVR_pal_static" }, - [SKYBOX_HOUSE_KAKARIKO] = { "vr_KKRVR_static", "vr_KKRVR_pal_static" }, - [SKYBOX_KOKIRI_SHOP] = { "vr_KSVR_static", "vr_KSVR_pal_static" }, - [SKYBOX_GORON_SHOP] = { "vr_GLVR_static", "vr_GLVR_pal_static" }, - [SKYBOX_ZORA_SHOP] = { "vr_ZRVR_static", "vr_ZRVR_pal_static" }, - [SKYBOX_POTION_SHOP_KAKARIKO] = { "vr_DGVR_static", "vr_DGVR_pal_static" }, - [SKYBOX_POTION_SHOP_MARKET] = { "vr_ALVR_static", "vr_ALVR_pal_static" }, - [SKYBOX_BOMBCHU_SHOP] = { "vr_NSVR_static", "vr_NSVR_pal_static" }, - [SKYBOX_HOUSE_RICHARD] = { "vr_IPVR_static", "vr_IPVR_pal_static" }, - [SKYBOX_HOUSE_IMPA] = { "vr_LBVR_static", "vr_LBVR_pal_static" }, - [SKYBOX_TENT] = { "vr_TTVR_static", "vr_TTVR_pal_static" }, - [SKYBOX_HOUSE_MIDO] = { "vr_K4VR_static", "vr_K4VR_pal_static" }, - [SKYBOX_HOUSE_SARIA] = { "vr_K5VR_static", "vr_K5VR_pal_static" }, - [SKYBOX_HOUSE_ALLEY] = { "vr_KR3VR_static", "vr_KR3VR_pal_static" }, + [SKYBOX_NONE] = {NULL, NULL}, + [SKYBOX_NORMAL_SKY] = {NULL, NULL}, // gNormalSkyFiles, hardcoded + [SKYBOX_BAZAAR] = {"vr_SP1a_static", "vr_SP1a_pal_static"}, + [SKYBOX_OVERCAST_SUNSET] = {NULL, NULL}, // same as NORMAL_SKY + [SKYBOX_MARKET_ADULT] = {"vr_RUVR_static", "vr_RUVR_pal_static"}, + [SKYBOX_CUTSCENE_MAP] = {NULL, NULL}, // Two tex files, handled separately + [SKYBOX_HOUSE_LINK] = {"vr_LHVR_static", "vr_LHVR_pal_static"}, + [SKYBOX_MARKET_CHILD_DAY] = {"vr_MDVR_static", "vr_MDVR_pal_static"}, + [SKYBOX_MARKET_CHILD_NIGHT] = {"vr_MNVR_static", "vr_MNVR_pal_static"}, + [SKYBOX_HAPPY_MASK_SHOP] = {"vr_FCVR_static", "vr_FCVR_pal_static"}, + [SKYBOX_HOUSE_KNOW_IT_ALL_BROTHERS] = {"vr_KHVR_static", "vr_KHVR_pal_static"}, + [SKYBOX_HOUSE_OF_TWINS] = {"vr_K3VR_static", "vr_K3VR_pal_static"}, + [SKYBOX_STABLES] = {"vr_MLVR_static", "vr_MLVR_pal_static"}, + [SKYBOX_HOUSE_KAKARIKO] = {"vr_KKRVR_static", "vr_KKRVR_pal_static"}, + [SKYBOX_KOKIRI_SHOP] = {"vr_KSVR_static", "vr_KSVR_pal_static"}, + [SKYBOX_GORON_SHOP] = {"vr_GLVR_static", "vr_GLVR_pal_static"}, + [SKYBOX_ZORA_SHOP] = {"vr_ZRVR_static", "vr_ZRVR_pal_static"}, + [SKYBOX_POTION_SHOP_KAKARIKO] = {"vr_DGVR_static", "vr_DGVR_pal_static"}, + [SKYBOX_POTION_SHOP_MARKET] = {"vr_ALVR_static", "vr_ALVR_pal_static"}, + [SKYBOX_BOMBCHU_SHOP] = {"vr_NSVR_static", "vr_NSVR_pal_static"}, + [SKYBOX_HOUSE_RICHARD] = {"vr_IPVR_static", "vr_IPVR_pal_static"}, + [SKYBOX_HOUSE_IMPA] = {"vr_LBVR_static", "vr_LBVR_pal_static"}, + [SKYBOX_TENT] = {"vr_TTVR_static", "vr_TTVR_pal_static"}, + [SKYBOX_HOUSE_MIDO] = {"vr_K4VR_static", "vr_K4VR_pal_static"}, + [SKYBOX_HOUSE_SARIA] = {"vr_K5VR_static", "vr_K5VR_pal_static"}, + [SKYBOX_HOUSE_ALLEY] = {"vr_KR3VR_static", "vr_KR3VR_pal_static"}, }; -static u32 GetN64SkyboxTextureSize(s16 skyboxId) { - if (skyboxId == SKYBOX_NONE) { +static u32 GetN64SkyboxTextureSize(s16 skyboxId) +{ + if (skyboxId == SKYBOX_NONE) + { return 0; } // NORMAL_SKY and OVERCAST_SUNSET both load 2 texture banks + 2 palettes from the vr_fine/vr_cloud files. // All 16 banks are exactly 0xC000 and all 16 palettes are exactly 0x100, so the total is constant. - if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) + { return 2 * 0xC000 + 2 * 0x100; } // CUTSCENE_MAP loads two different texture files + 2 palette copies. - if (skyboxId == SKYBOX_CUTSCENE_MAP) { + if (skyboxId == SKYBOX_CUTSCENE_MAP) + { const u32 tex0 = N64SizeData_GetDmaFileSize("vr_holy0_static"); const u32 tex1 = N64SizeData_GetDmaFileSize("vr_holy1_static"); const u32 pal = N64SizeData_GetDmaFileSize("vr_holy0_pal_static"); @@ -141,9 +148,11 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) { } // Indoor skyboxes: 1 texture + 1 palette, looked up from the DMA blob. - if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) { + if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) + { const SkyboxDmaEntry* entry = &sSkyboxDmaTable[skyboxId]; - if (entry->texName != NULL) { + if (entry->texName != NULL) + { const u32 tex = N64SizeData_GetDmaFileSize(entry->texName); const u32 pal = N64SizeData_GetDmaFileSize(entry->palName); return tex + pal; @@ -163,20 +172,27 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) { // Gfx is 8 bytes on N64, Vtx is 0x10. // -------------------------------------------------------------------------------------------------------------------- -static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) { - if (skyboxId == SKYBOX_NONE) { +static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) +{ + if (skyboxId == SKYBOX_NONE) + { *outDlistSize = 0; *outVtxSize = 0; return; } - if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) + { *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; *outVtxSize = 5 * 32 * N64_SIZEOF_VTX; - } else if (skyboxId == SKYBOX_CUTSCENE_MAP) { + } + else if (skyboxId == SKYBOX_CUTSCENE_MAP) + { *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; *outVtxSize = 6 * 32 * N64_SIZEOF_VTX; - } else { + } + else + { // Indoor skyboxes: SKYBOX_DRAW_256_4FACE or SKYBOX_DRAW_256_3FACE, both use the same allocation. *outDlistSize = 8 * 150 * N64_SIZEOF_GFX; *outVtxSize = 8 * 32 * N64_SIZEOF_VTX; @@ -198,26 +214,31 @@ static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVt // bgcheck_memSize and sizeof(CollisionContext). // -------------------------------------------------------------------------------------------------------------------- -static u32 GetBgCheckMemSize(PlayState* play) { +static u32 GetBgCheckMemSize(PlayState* play) +{ const s16 sceneNum = play->sceneNum; - if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) { + if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) + { return sceneNum == SCENE_STABLE ? 0x3520 : 0x4E20; } - if (BgCheck_IsSpotScene(play)) { + if (BgCheck_IsSpotScene(play)) + { return 0xF000; } u32 customMemSize = 0; - if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) { + if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) + { return customMemSize; } return 0x1CC00; } -static u32 GetBgCheckThaTotal(PlayState* play) { +static u32 GetBgCheckThaTotal(PlayState* play) +{ return GetBgCheckMemSize(play) - N64_SIZEOF_COLLISION_CONTEXT; } @@ -225,14 +246,17 @@ static u32 GetBgCheckThaTotal(PlayState* play) { // Object bank size (mirrors z_scene.c Object_InitBlank) // -------------------------------------------------------------------------------------------------------------------- -static u32 GetObjectBankSize(PlayState* play) { +static u32 GetObjectBankSize(PlayState* play) +{ const s16 sceneNum = play->sceneNum; - if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) { + if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) + { return 1177600; } if (sceneNum == SCENE_SPIRIT_TEMPLE_BOSS || sceneNum == SCENE_CHAMBER_OF_THE_SAGES || - sceneNum == SCENE_GANONDORF_BOSS) { + sceneNum == SCENE_GANONDORF_BOSS) + { return 1075200; } @@ -251,13 +275,16 @@ static const char* sElfMsgDmaNames[] = { "elf_message_ydan", }; -static u32 GetElfMessageSize(PlayState* play) { - if (play->cUpElfMsgs == NULL) { +static u32 GetElfMessageSize(PlayState* play) +{ + if (play->cUpElfMsgs == NULL) + { return 0; } const u8 elfMsgNum = N64Mem_GetElfMsgNum(); - if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) { + if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) + { LUSLOG_WARN("[ArenaSizing] elfMsg: cUpElfMsgs non-NULL but elfMsgNum=%d out of range", elfMsgNum); return 0; } @@ -269,20 +296,25 @@ static u32 GetElfMessageSize(PlayState* play) { // Room buffer max size (mirrors func_80096FE8 logic) // -------------------------------------------------------------------------------------------------------------------- -static u32 GetMaxRoomSize(PlayState* play) { +static u32 GetMaxRoomSize(PlayState* play) +{ u32 maxRoomSize = 0; - for (size_t i = 0; i < play->numRooms; ++i) { + for (size_t i = 0; i < play->numRooms; ++i) + { const u32 roomSize = play->roomList[i].vromEnd - play->roomList[i].vromStart; - if (roomSize > maxRoomSize) { + if (roomSize > maxRoomSize) + { maxRoomSize = roomSize; } } - if (play->transiActorCtx.numActors != 0) { + if (play->transiActorCtx.numActors != 0) + { const TransitionActorEntry* transitionActor = &play->transiActorCtx.list[0]; - for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) { + for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) + { const s8 frontRoom = transitionActor->sides[0].room; const s8 backRoom = transitionActor->sides[1].room; const size_t frontSize = frontRoom < 0 @@ -293,7 +325,8 @@ static u32 GetMaxRoomSize(PlayState* play) { : play->roomList[backRoom].vromEnd - play->roomList[backRoom].vromStart; const u32 cumulSize = frontRoom != backRoom ? frontSize + backSize : frontSize; - if (cumulSize > maxRoomSize) { + if (cumulSize > maxRoomSize) + { maxRoomSize = cumulSize; } @@ -308,7 +341,8 @@ static u32 GetMaxRoomSize(PlayState* play) { // Main computation // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { +u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) +{ // Each THA consumer is allocated via GAME_STATE_ALLOC -> THA_AllocTailAlign16, which consumes ALIGN16(size) bytes. // We must align each consumer individually before summing; aligning the sum would under-count when individual // sizes are not 16-byte aligned (common for DMA file sizes). BgCheck is the exception -- its internal allocations @@ -401,7 +435,8 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { LUSLOG_INFO("[ArenaSizing] total=0x%X, arena=0x%X (budget=0x%X)", total, N64_THA_BUDGET - total, (u32)N64_THA_BUDGET); - if (total >= N64_THA_BUDGET) { + if (total >= N64_THA_BUDGET) + { return 0; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h index 91c5c5075e4..7265b234603 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h @@ -4,7 +4,6 @@ #ifdef __cplusplus extern "C" { - #endif // -------------------------------------------------------------------------------------------------------------------- @@ -22,7 +21,7 @@ extern "C" { // (should not happen on valid scenes). // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play); +u32 ArenaSizing_ComputeN64ArenaSize(PlayState * play); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c index d0f2601cf70..eeaa9b5dd10 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c @@ -15,7 +15,8 @@ // 0x10 u8[0x20] (Dead space matching N64 debug fields) // -------------------------------------------------------------------------------------------------------------------- -typedef struct ShadowNode { +typedef struct ShadowNode +{ s16 magic; s16 isFree; u32 size; @@ -33,34 +34,42 @@ static_assert(sizeof(ShadowNode) == SHADOW_NODE_SIZE, "ShadowNode must be exactl // Offset helpers // -------------------------------------------------------------------------------------------------------------------- -static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) { +static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) +{ return (ShadowNode*)(arena->buffer + offset); } -static s32 NodeIsValid(ShadowArena* arena, u32 offset) { - if (offset == SHADOW_NULL) { +static s32 NodeIsValid(ShadowArena* arena, u32 offset) +{ + if (offset == SHADOW_NULL) + { return 0; } - if (offset + SHADOW_NODE_SIZE > arena->bufferSize) { + if (offset + SHADOW_NODE_SIZE > arena->bufferSize) + { return 0; } return NodeAt(arena, offset)->magic == NODE_MAGIC; } -static u32 NodeGetNext(ShadowArena* arena, u32 offset) { +static u32 NodeGetNext(ShadowArena* arena, u32 offset) +{ const ShadowNode* node = NodeAt(arena, offset); - if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) { + if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) + { return node->next; } return SHADOW_NULL; } -static u32 NodeGetPrev(ShadowArena* arena, u32 offset) { +static u32 NodeGetPrev(ShadowArena* arena, u32 offset) +{ const ShadowNode* node = NodeAt(arena, offset); - if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) { + if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) + { return node->prev; } @@ -71,13 +80,15 @@ static u32 NodeGetPrev(ShadowArena* arena, u32 offset) { // Init / Destroy // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Init(ShadowArena* arena, u32 size) { +void ShadowArena_Init(ShadowArena* arena, u32 size) +{ // Match N64's alignment: Round start up to 16, round size down to 16. Since we control the buffer, start is // effectively offset 0 after alignment. We just ensure the usable size is 16-byte aligned. const u32 alignedSize = size & ~0xF; arena->buffer = (u8*)malloc(alignedSize); - if (!arena->buffer) { + if (!arena->buffer) + { memset(arena, 0, sizeof(ShadowArena)); return; } @@ -97,8 +108,10 @@ void ShadowArena_Init(ShadowArena* arena, u32 size) { arena->head = 0; } -void ShadowArena_Destroy(ShadowArena* arena) { - if (arena->buffer) { +void ShadowArena_Destroy(ShadowArena* arena) +{ + if (arena->buffer) + { free(arena->buffer); } @@ -109,7 +122,8 @@ void ShadowArena_Destroy(ShadowArena* arena) { // Malloc: First-fit forward (matches N64 __osMalloc) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { +u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) +{ u32 iterOff = 0; ShadowNode* iter = NULL; u32 blockSize = 0; @@ -118,11 +132,14 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { blockSize = size + SHADOW_NODE_SIZE; iterOff = arena->head; - while (iterOff != SHADOW_NULL) { + while (iterOff != SHADOW_NULL) + { iter = NodeAt(arena, iterOff); - if (iter->isFree && iter->size >= size) { + if (iter->isFree && iter->size >= size) + { // Split if remainder can hold a new node + payload. - if (blockSize < iter->size) { + if (blockSize < iter->size) + { const u32 newOff = iterOff + blockSize; ShadowNode* newNode = NodeAt(arena, newOff); @@ -133,7 +150,8 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { newNode->prev = iterOff; memset(newNode->_dead, 0, sizeof(newNode->_dead)); - if (newNode->next != SHADOW_NULL) { + if (newNode->next != SHADOW_NULL) + { NodeAt(arena, newNode->next)->prev = newOff; } @@ -158,7 +176,8 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { // MallocR: First-fit backward (matches N64 __osMallocR) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { +u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) +{ u32 iterOff = 0; u32 nextOff = 0; ShadowNode* iter = NULL; @@ -169,19 +188,23 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { // Walk to tail. iterOff = arena->head; nextOff = NodeGetNext(arena, iterOff); - while (nextOff != SHADOW_NULL) { + while (nextOff != SHADOW_NULL) + { iterOff = nextOff; nextOff = NodeGetNext(arena, nextOff); } // Walk backward looking for a fit. - while (iterOff != SHADOW_NULL) { + while (iterOff != SHADOW_NULL) + { iter = NodeAt(arena, iterOff); - if (iter->isFree && iter->size >= size) { + if (iter->isFree && iter->size >= size) + { blockSize = size + SHADOW_NODE_SIZE; // Split: Carve the allocation from the TOP of this free block. - if (blockSize < iter->size) { + if (blockSize < iter->size) + { const u32 newOff = iterOff + (iter->size - size); ShadowNode* newNode = NodeAt(arena, newOff); @@ -192,7 +215,8 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { newNode->prev = iterOff; memset(newNode->_dead, 0, sizeof(newNode->_dead)); - if (newNode->next != SHADOW_NULL) { + if (newNode->next != SHADOW_NULL) + { NodeAt(arena, newNode->next)->prev = newOff; } @@ -219,24 +243,28 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { // Free: Adjacent block coalescing (matches N64 _osFree) // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { +void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) +{ u32 nodeOff = 0; ShadowNode* node = NULL; u32 nextOff = 0; u32 prevOff = 0; - if (dataOffset == SHADOW_NULL) { + if (dataOffset == SHADOW_NULL) + { return; } nodeOff = dataOffset - SHADOW_NODE_SIZE; node = NodeAt(arena, nodeOff); - if (node->magic != NODE_MAGIC) { + if (node->magic != NODE_MAGIC) + { return; } - if (node->isFree) { + if (node->isFree) + { return; } @@ -245,10 +273,13 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { // Forward coalesce: Merge with next if it's free. nextOff = node->next; - if (nextOff != SHADOW_NULL) { + if (nextOff != SHADOW_NULL) + { const ShadowNode* next = NodeAt(arena, nextOff); - if (next->isFree) { - if (next->next != SHADOW_NULL) { + if (next->isFree) + { + if (next->next != SHADOW_NULL) + { NodeAt(arena, next->next)->prev = nodeOff; } @@ -259,13 +290,16 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { // Backward coalesce: Merge into prev if it's free. prevOff = node->prev; - if (prevOff != SHADOW_NULL) { + if (prevOff != SHADOW_NULL) + { ShadowNode* prev = NodeAt(arena, prevOff); - if (prev->isFree) { + if (prev->isFree) + { prev->size += node->size + SHADOW_NODE_SIZE; prev->next = node->next; - if (node->next != SHADOW_NULL) { + if (node->next != SHADOW_NULL) + { NodeAt(arena, node->next)->prev = prevOff; } } @@ -276,7 +310,8 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { // Query // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) { +void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) +{ u32 iterOff = 0; *outMaxFree = 0; @@ -284,15 +319,20 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 *outAlloc = 0; iterOff = arena->head; - while (iterOff != SHADOW_NULL) { + while (iterOff != SHADOW_NULL) + { const ShadowNode* node = NodeAt(arena, iterOff); - if (node->isFree) { + if (node->isFree) + { *outFree += node->size; - if (node->size > *outMaxFree) { + if (node->size > *outMaxFree) + { *outMaxFree = node->size; } - } else { + } + else + { *outAlloc += node->size; } @@ -300,17 +340,21 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 } } -u32 ShadowArena_GetHead(ShadowArena* arena) { +u32 ShadowArena_GetHead(ShadowArena* arena) +{ return arena->head; } -s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) { - if (offset == SHADOW_NULL || offset + SHADOW_NODE_SIZE > arena->bufferSize) { +s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) +{ + if (offset == SHADOW_NULL || offset + SHADOW_NODE_SIZE > arena->bufferSize) + { return 0; } ShadowNode* node = NodeAt(arena, offset); - if (node->magic != NODE_MAGIC) { + if (node->magic != NODE_MAGIC) + { return 0; } @@ -320,6 +364,7 @@ s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* return 1; } -u32 ShadowArena_GetBufferSize(ShadowArena* arena) { +u32 ShadowArena_GetBufferSize(ShadowArena* arena) +{ return arena->bufferSize; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h index dee79b68c83..f474e6d3085 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h @@ -4,7 +4,6 @@ #ifdef __cplusplus extern "C" { - #endif // Sentinel value for null offsets (no valid node can live at 0xFFFFFFFF in a buffer that's only ~245KB). @@ -13,7 +12,8 @@ extern "C" { // Matches N64 retail ArenaNode: 0x10 bookkeeping + 0x20 debug fields. #define SHADOW_NODE_SIZE 0x30 -typedef struct ShadowArena { +typedef struct ShadowArena +{ u8* buffer; u32 head; // Offset to first node u32 bufferSize; @@ -35,7 +35,7 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size); void ShadowArena_Free(ShadowArena* arena, u32 dataOffset); // Query arena statistics. -void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc); +void ShadowArena_GetSizes(ShadowArena * arena, u32 * outMaxFree, u32 * outFree, u32 * outAlloc); // Get the head node offset for external traversal (e.g., heap viewer). u32 ShadowArena_GetHead(ShadowArena* arena); From f2939319d283738bdd2dbdefd2a7ceaa95e7d262 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 19:19:42 -0700 Subject: [PATCH 31/58] feat(restoration): allocType from original actor for enemy randomizer --- .../Restorations/N64MemoryModel/N64MemoryModel.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index add656722c9..392254aff72 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -2,6 +2,7 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/ShipInit.hpp" +#include "soh/ActorDB.h" extern "C" { #include "N64MemoryModel.hpp" @@ -44,7 +45,7 @@ static s16 sOriginalActorId = -1; // Returns the actor ID to use for size lookups. If an original actor ID override is set (enemy randomizer active), // returns that; otherwise returns the passed actorId unchanged. static u16 ResolveSizeActorId(s16 actorId) { - return sOriginalActorId >= 0 ? static_cast(sOriginalActorId) : static_cast(actorId); + return (sOriginalActorId >= 0) ? static_cast(sOriginalActorId) : static_cast(actorId); } // -------------------------------------------------------------------------------------------------------------------- @@ -214,6 +215,14 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { return 1; } + // When the enemy randomizer is active, use the original actor's allocType so the shadow takes the same path + // N64 would have taken for the unsubstituted actor (i.e., a regular enemy replaced by an ABSOLUTE mini-boss + // should still shadow as ALLOCTYPE_NORMAL). + if (sOriginalActorId >= 0) { + const auto& entry = ActorDB::Instance->RetrieveEntry(sOriginalActorId); + allocType = entry.entry.allocType; + } + // ABSOLUTE: Shared fixed-size buffer, allocated once via MallocR. if (allocType & ALLOCTYPE_ABSOLUTE) { if (sAbsoluteSpaceShadow == SHADOW_NULL) { From a7c807a0092c661d1193b65332f23ce6d8b0d0ae Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 20:18:26 -0700 Subject: [PATCH 32/58] fix(restoration): Skip subsidiary shadow for randomized actors --- .../N64MemoryModel/N64MemoryModel.cpp | 10 ++++++++-- soh/src/code/z_actor.c | 16 ++++++++++------ soh/src/code/z_camera.c | 3 +-- soh/src/code/z_collision_check.c | 15 +++++---------- soh/src/code/z_effect_soft_sprite.c | 3 +-- .../overlays/actors/ovl_player_actor/z_player.c | 8 +++----- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 392254aff72..8c7b9174f8f 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -279,12 +279,10 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { if (!sIsActive) { - sOriginalActorId = -1; return 1; } const u16 sizeId = ResolveSizeActorId(actorId); - sOriginalActorId = -1; if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return 1; @@ -334,6 +332,14 @@ s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) { return 1; } + // When the enemy randomizer is active, the replacement actor's subsidiaries (colliders, skeleton tables, skin + // buffers) have different counts than the original's. Rather than charge the wrong sizes, skip subsidiary + // tracking entirely for randomized actors -- instance and overlay sizes from the original are already correct + // and dominate heap pressure. + if (sOriginalActorId >= 0) { + return 1; + } + const u32 shadow = ShadowArena_Malloc(&sShadow, n64Size); if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[N64MemoryModel] Shadow subsidiary failed (need 0x{:X})", n64Size); diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index d7fab4a0ad5..1e1517303e5 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2597,7 +2597,10 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { } play->numSetupActors = 0; GameInteractor_ExecuteOnSceneSpawnActors(); + + // #region SOH Enhancement - N64 Memory Model N64Mem_BenchmarkTransition(play); + // #endregion } if (actorCtx->unk_02 != 0) { @@ -3367,10 +3370,8 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos } // #region SOH [Enhancement] - N64 Memory Model - if (dbEntry->numLoaded == 0) - { - if (!N64Mem_AllocOverlay(actorId, dbEntry->allocType)) - { + if (dbEntry->numLoaded == 0) { + if (!N64Mem_AllocOverlay(actorId, dbEntry->allocType)) { Actor_FreeOverlay(dbEntry); return NULL; } @@ -3388,8 +3389,7 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocInstance(actorId, actor)) - { + if (!N64Mem_AllocInstance(actorId, actor)) { ZELDA_ARENA_FREE_DEBUG(actor); Actor_FreeOverlay(dbEntry); return NULL; @@ -3439,6 +3439,10 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos Actor_Init(actor, play); gSegments[6] = temp; + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_ClearOriginalActorId(); + // #endregion + GameInteractor_ExecuteOnActorSpawn(actor); return actor; diff --git a/soh/src/code/z_camera.c b/soh/src/code/z_camera.c index 0fa886fd0c2..fe0c910ae86 100644 --- a/soh/src/code/z_camera.c +++ b/soh/src/code/z_camera.c @@ -6945,8 +6945,7 @@ Camera* Camera_Create(View* view, CollisionContext* colCtx, PlayState* play) { if (newCamera != NULL) { // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(newCamera, N64_SIZEOF_CAMERA)) - { + if (!N64Mem_AllocSubsidiary(newCamera, N64_SIZEOF_CAMERA)) { ZELDA_ARENA_FREE_DEBUG(newCamera); return NULL; } diff --git a/soh/src/code/z_collision_check.c b/soh/src/code/z_collision_check.c index 56fd0071add..0bbf640fb15 100644 --- a/soh/src/code/z_collision_check.c +++ b/soh/src/code/z_collision_check.c @@ -384,8 +384,7 @@ s32 Collider_SetJntSphToActor(PlayState* play, ColliderJntSph* dest, ColliderJnt } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) - { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -422,8 +421,7 @@ s32 Collider_SetJntSphAllocType1(PlayState* play, ColliderJntSph* dest, Actor* a } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) - { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -460,8 +458,7 @@ s32 Collider_SetJntSphAlloc(PlayState* play, ColliderJntSph* dest, Actor* actor, } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) - { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -782,8 +779,7 @@ s32 Collider_SetTrisAllocType1(PlayState* play, ColliderTris* dest, Actor* actor } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) - { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -820,8 +816,7 @@ s32 Collider_SetTrisAlloc(PlayState* play, ColliderTris* dest, Actor* actor, Col } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) - { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; diff --git a/soh/src/code/z_effect_soft_sprite.c b/soh/src/code/z_effect_soft_sprite.c index 78d6e170bb6..dd69f4761ae 100644 --- a/soh/src/code/z_effect_soft_sprite.c +++ b/soh/src/code/z_effect_soft_sprite.c @@ -185,8 +185,7 @@ void EffectSs_Spawn(PlayState* play, s32 type, s32 priority, void* initParams) { } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocEffectOverlay(type)) - { + if (!N64Mem_AllocEffectOverlay(type)) { return; } // #endregion diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 751396c20d6..f5ea97c28fe 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -10848,11 +10848,9 @@ void Player_Init(Actor* thisx, PlayState* play2) { // title cards is 0x1000 * LANGUAGE_MAX since each title card image includes all languages. // #region SOH [Enhancement] - N64 Memory Model - { - void* giRaw = ZELDA_ARENA_MALLOC_DEBUG(0x3008); - N64Mem_AllocSubsidiary(giRaw, N64_SIZEOF_GI_OBJECT_SEGMENT); - this->giObjectSegment = (void*)((uintptr_t)giRaw + 8 & ~0xF); - } + void* giRaw = ZELDA_ARENA_MALLOC_DEBUG(0x3008); + N64Mem_AllocSubsidiary(giRaw, N64_SIZEOF_GI_OBJECT_SEGMENT); + this->giObjectSegment = (void*)((uintptr_t)giRaw + 8 & ~0xF); // #endregion respawnFlag = gSaveContext.respawnFlag; From 5e71030ae669f0e3000e433dd2073bb5a0ca05be Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 21:22:48 -0700 Subject: [PATCH 33/58] Revert "fix(restoration): Skip subsidiary shadow for randomized actors" This reverts commit a7c807a0092c661d1193b65332f23ce6d8b0d0ae. --- .../N64MemoryModel/N64MemoryModel.cpp | 10 ++-------- soh/src/code/z_actor.c | 16 ++++++---------- soh/src/code/z_camera.c | 3 ++- soh/src/code/z_collision_check.c | 15 ++++++++++----- soh/src/code/z_effect_soft_sprite.c | 3 ++- .../overlays/actors/ovl_player_actor/z_player.c | 8 +++++--- 6 files changed, 27 insertions(+), 28 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 8c7b9174f8f..392254aff72 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -279,10 +279,12 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { if (!sIsActive) { + sOriginalActorId = -1; return 1; } const u16 sizeId = ResolveSizeActorId(actorId); + sOriginalActorId = -1; if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return 1; @@ -332,14 +334,6 @@ s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) { return 1; } - // When the enemy randomizer is active, the replacement actor's subsidiaries (colliders, skeleton tables, skin - // buffers) have different counts than the original's. Rather than charge the wrong sizes, skip subsidiary - // tracking entirely for randomized actors -- instance and overlay sizes from the original are already correct - // and dominate heap pressure. - if (sOriginalActorId >= 0) { - return 1; - } - const u32 shadow = ShadowArena_Malloc(&sShadow, n64Size); if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[N64MemoryModel] Shadow subsidiary failed (need 0x{:X})", n64Size); diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 1e1517303e5..d7fab4a0ad5 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2597,10 +2597,7 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { } play->numSetupActors = 0; GameInteractor_ExecuteOnSceneSpawnActors(); - - // #region SOH Enhancement - N64 Memory Model N64Mem_BenchmarkTransition(play); - // #endregion } if (actorCtx->unk_02 != 0) { @@ -3370,8 +3367,10 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos } // #region SOH [Enhancement] - N64 Memory Model - if (dbEntry->numLoaded == 0) { - if (!N64Mem_AllocOverlay(actorId, dbEntry->allocType)) { + if (dbEntry->numLoaded == 0) + { + if (!N64Mem_AllocOverlay(actorId, dbEntry->allocType)) + { Actor_FreeOverlay(dbEntry); return NULL; } @@ -3389,7 +3388,8 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocInstance(actorId, actor)) { + if (!N64Mem_AllocInstance(actorId, actor)) + { ZELDA_ARENA_FREE_DEBUG(actor); Actor_FreeOverlay(dbEntry); return NULL; @@ -3439,10 +3439,6 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos Actor_Init(actor, play); gSegments[6] = temp; - // #region SOH [Enhancement] - N64 Memory Model - N64Mem_ClearOriginalActorId(); - // #endregion - GameInteractor_ExecuteOnActorSpawn(actor); return actor; diff --git a/soh/src/code/z_camera.c b/soh/src/code/z_camera.c index fe0c910ae86..0fa886fd0c2 100644 --- a/soh/src/code/z_camera.c +++ b/soh/src/code/z_camera.c @@ -6945,7 +6945,8 @@ Camera* Camera_Create(View* view, CollisionContext* colCtx, PlayState* play) { if (newCamera != NULL) { // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(newCamera, N64_SIZEOF_CAMERA)) { + if (!N64Mem_AllocSubsidiary(newCamera, N64_SIZEOF_CAMERA)) + { ZELDA_ARENA_FREE_DEBUG(newCamera); return NULL; } diff --git a/soh/src/code/z_collision_check.c b/soh/src/code/z_collision_check.c index 0bbf640fb15..56fd0071add 100644 --- a/soh/src/code/z_collision_check.c +++ b/soh/src/code/z_collision_check.c @@ -384,7 +384,8 @@ s32 Collider_SetJntSphToActor(PlayState* play, ColliderJntSph* dest, ColliderJnt } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) + { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -421,7 +422,8 @@ s32 Collider_SetJntSphAllocType1(PlayState* play, ColliderJntSph* dest, Actor* a } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) + { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -458,7 +460,8 @@ s32 Collider_SetJntSphAlloc(PlayState* play, ColliderJntSph* dest, Actor* actor, } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) + { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -779,7 +782,8 @@ s32 Collider_SetTrisAllocType1(PlayState* play, ColliderTris* dest, Actor* actor } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) + { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -816,7 +820,8 @@ s32 Collider_SetTrisAlloc(PlayState* play, ColliderTris* dest, Actor* actor, Col } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) + { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; diff --git a/soh/src/code/z_effect_soft_sprite.c b/soh/src/code/z_effect_soft_sprite.c index dd69f4761ae..78d6e170bb6 100644 --- a/soh/src/code/z_effect_soft_sprite.c +++ b/soh/src/code/z_effect_soft_sprite.c @@ -185,7 +185,8 @@ void EffectSs_Spawn(PlayState* play, s32 type, s32 priority, void* initParams) { } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocEffectOverlay(type)) { + if (!N64Mem_AllocEffectOverlay(type)) + { return; } // #endregion diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index f5ea97c28fe..751396c20d6 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -10848,9 +10848,11 @@ void Player_Init(Actor* thisx, PlayState* play2) { // title cards is 0x1000 * LANGUAGE_MAX since each title card image includes all languages. // #region SOH [Enhancement] - N64 Memory Model - void* giRaw = ZELDA_ARENA_MALLOC_DEBUG(0x3008); - N64Mem_AllocSubsidiary(giRaw, N64_SIZEOF_GI_OBJECT_SEGMENT); - this->giObjectSegment = (void*)((uintptr_t)giRaw + 8 & ~0xF); + { + void* giRaw = ZELDA_ARENA_MALLOC_DEBUG(0x3008); + N64Mem_AllocSubsidiary(giRaw, N64_SIZEOF_GI_OBJECT_SEGMENT); + this->giObjectSegment = (void*)((uintptr_t)giRaw + 8 & ~0xF); + } // #endregion respawnFlag = gSaveContext.respawnFlag; From 991c849a440d802945c7958c92af34ba36df1f63 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 21:23:51 -0700 Subject: [PATCH 34/58] Reapply "fix(restoration): Skip subsidiary shadow for randomized actors" This reverts commit 5e71030ae669f0e3000e433dd2073bb5a0ca05be. --- .../N64MemoryModel/N64MemoryModel.cpp | 10 ++++++++-- soh/src/code/z_actor.c | 16 ++++++++++------ soh/src/code/z_camera.c | 3 +-- soh/src/code/z_collision_check.c | 15 +++++---------- soh/src/code/z_effect_soft_sprite.c | 3 +-- .../overlays/actors/ovl_player_actor/z_player.c | 8 +++----- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 392254aff72..8c7b9174f8f 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -279,12 +279,10 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { if (!sIsActive) { - sOriginalActorId = -1; return 1; } const u16 sizeId = ResolveSizeActorId(actorId); - sOriginalActorId = -1; if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return 1; @@ -334,6 +332,14 @@ s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) { return 1; } + // When the enemy randomizer is active, the replacement actor's subsidiaries (colliders, skeleton tables, skin + // buffers) have different counts than the original's. Rather than charge the wrong sizes, skip subsidiary + // tracking entirely for randomized actors -- instance and overlay sizes from the original are already correct + // and dominate heap pressure. + if (sOriginalActorId >= 0) { + return 1; + } + const u32 shadow = ShadowArena_Malloc(&sShadow, n64Size); if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[N64MemoryModel] Shadow subsidiary failed (need 0x{:X})", n64Size); diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index d7fab4a0ad5..1e1517303e5 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2597,7 +2597,10 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { } play->numSetupActors = 0; GameInteractor_ExecuteOnSceneSpawnActors(); + + // #region SOH Enhancement - N64 Memory Model N64Mem_BenchmarkTransition(play); + // #endregion } if (actorCtx->unk_02 != 0) { @@ -3367,10 +3370,8 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos } // #region SOH [Enhancement] - N64 Memory Model - if (dbEntry->numLoaded == 0) - { - if (!N64Mem_AllocOverlay(actorId, dbEntry->allocType)) - { + if (dbEntry->numLoaded == 0) { + if (!N64Mem_AllocOverlay(actorId, dbEntry->allocType)) { Actor_FreeOverlay(dbEntry); return NULL; } @@ -3388,8 +3389,7 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocInstance(actorId, actor)) - { + if (!N64Mem_AllocInstance(actorId, actor)) { ZELDA_ARENA_FREE_DEBUG(actor); Actor_FreeOverlay(dbEntry); return NULL; @@ -3439,6 +3439,10 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos Actor_Init(actor, play); gSegments[6] = temp; + // #region SOH [Enhancement] - N64 Memory Model + N64Mem_ClearOriginalActorId(); + // #endregion + GameInteractor_ExecuteOnActorSpawn(actor); return actor; diff --git a/soh/src/code/z_camera.c b/soh/src/code/z_camera.c index 0fa886fd0c2..fe0c910ae86 100644 --- a/soh/src/code/z_camera.c +++ b/soh/src/code/z_camera.c @@ -6945,8 +6945,7 @@ Camera* Camera_Create(View* view, CollisionContext* colCtx, PlayState* play) { if (newCamera != NULL) { // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(newCamera, N64_SIZEOF_CAMERA)) - { + if (!N64Mem_AllocSubsidiary(newCamera, N64_SIZEOF_CAMERA)) { ZELDA_ARENA_FREE_DEBUG(newCamera); return NULL; } diff --git a/soh/src/code/z_collision_check.c b/soh/src/code/z_collision_check.c index 56fd0071add..0bbf640fb15 100644 --- a/soh/src/code/z_collision_check.c +++ b/soh/src/code/z_collision_check.c @@ -384,8 +384,7 @@ s32 Collider_SetJntSphToActor(PlayState* play, ColliderJntSph* dest, ColliderJnt } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) - { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -422,8 +421,7 @@ s32 Collider_SetJntSphAllocType1(PlayState* play, ColliderJntSph* dest, Actor* a } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) - { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -460,8 +458,7 @@ s32 Collider_SetJntSphAlloc(PlayState* play, ColliderJntSph* dest, Actor* actor, } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) - { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -782,8 +779,7 @@ s32 Collider_SetTrisAllocType1(PlayState* play, ColliderTris* dest, Actor* actor } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) - { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; @@ -820,8 +816,7 @@ s32 Collider_SetTrisAlloc(PlayState* play, ColliderTris* dest, Actor* actor, Col } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) - { + if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; dest->count = 0; diff --git a/soh/src/code/z_effect_soft_sprite.c b/soh/src/code/z_effect_soft_sprite.c index 78d6e170bb6..dd69f4761ae 100644 --- a/soh/src/code/z_effect_soft_sprite.c +++ b/soh/src/code/z_effect_soft_sprite.c @@ -185,8 +185,7 @@ void EffectSs_Spawn(PlayState* play, s32 type, s32 priority, void* initParams) { } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocEffectOverlay(type)) - { + if (!N64Mem_AllocEffectOverlay(type)) { return; } // #endregion diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 751396c20d6..f5ea97c28fe 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -10848,11 +10848,9 @@ void Player_Init(Actor* thisx, PlayState* play2) { // title cards is 0x1000 * LANGUAGE_MAX since each title card image includes all languages. // #region SOH [Enhancement] - N64 Memory Model - { - void* giRaw = ZELDA_ARENA_MALLOC_DEBUG(0x3008); - N64Mem_AllocSubsidiary(giRaw, N64_SIZEOF_GI_OBJECT_SEGMENT); - this->giObjectSegment = (void*)((uintptr_t)giRaw + 8 & ~0xF); - } + void* giRaw = ZELDA_ARENA_MALLOC_DEBUG(0x3008); + N64Mem_AllocSubsidiary(giRaw, N64_SIZEOF_GI_OBJECT_SEGMENT); + this->giObjectSegment = (void*)((uintptr_t)giRaw + 8 & ~0xF); // #endregion respawnFlag = gSaveContext.respawnFlag; From 01b99e858d15df509fee2d63d72f0a10689bfa0b Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 22:33:03 -0700 Subject: [PATCH 35/58] feat(restoration): Skip child shadow tracking for randomized actors --- .../N64MemoryModel/N64MemoryModel.cpp | 47 +++++++++++++++++-- .../N64MemoryModel/N64MemoryModel.hpp | 5 ++ .../N64MemoryModel/N64SizeData.cpp | 2 +- soh/src/code/z_actor.c | 7 ++- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 8c7b9174f8f..54ba263a5b9 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -42,10 +42,15 @@ static std::unordered_map sShadowMap; // cleared by N64Mem_ClearOriginalActorId after Actor_Spawn returns. -1 means no override (use the passed actorId). static s16 sOriginalActorId = -1; +// Set when AllocInstance consumes a non-negative sOriginalActorId, cleared after Actor_Init returns. While set, ALL +// shadow allocations (overlay, instance, subsidiary) from child actors spawned during the replacement actor's init are +// skipped — on N64 these children don't exist because the original actor never spawned them. +static s32 sInsideRandomizedInit = 0; + // Returns the actor ID to use for size lookups. If an original actor ID override is set (enemy randomizer active), // returns that; otherwise returns the passed actorId unchanged. static u16 ResolveSizeActorId(s16 actorId) { - return (sOriginalActorId >= 0) ? static_cast(sOriginalActorId) : static_cast(actorId); + return sOriginalActorId >= 0 ? static_cast(sOriginalActorId) : static_cast(actorId); } // -------------------------------------------------------------------------------------------------------------------- @@ -138,6 +143,8 @@ void N64Mem_Reset(PlayState* play) { ShadowArena_Destroy(&sShadow); sShadowMap.clear(); sAbsoluteSpaceShadow = SHADOW_NULL; + sOriginalActorId = -1; + sInsideRandomizedInit = 0; for (u32& sOverlayShadow : sOverlayShadows) { sOverlayShadow = SHADOW_NULL; @@ -197,6 +204,14 @@ void N64Mem_ClearOriginalActorId() { sOriginalActorId = -1; } +s32 N64Mem_GetRandomizedInit() { + return sInsideRandomizedInit; +} + +void N64Mem_SetRandomizedInit(s32 value) { + sInsideRandomizedInit = value; +} + // -------------------------------------------------------------------------------------------------------------------- // Actor overlays // -------------------------------------------------------------------------------------------------------------------- @@ -206,6 +221,11 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { return 1; } + // Child actors spawned during a randomized enemy's init don't exist on N64 — skip shadow tracking. + if (sInsideRandomizedInit) { + return 1; + } + if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return 1; } @@ -279,11 +299,25 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { if (!sIsActive) { + sOriginalActorId = -1; + return 1; + } + + // Child actors spawned during a randomized enemy's init don't exist on N64 -- skip shadow tracking. + if (sInsideRandomizedInit) { return 1; } const u16 sizeId = ResolveSizeActorId(actorId); + // Consume the override and enter the randomized-init phase. Subsidiaries allocated during Actor_Init will be + // skipped (they belong to the replacement, not the original). Child Actor_Spawn calls during init will also + // be skipped via the sInsideRandomizedInit check above. + if (sOriginalActorId >= 0) { + sOriginalActorId = -1; + sInsideRandomizedInit = 1; + } + if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return 1; } @@ -293,6 +327,10 @@ s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { return 1; } + if (instanceSize == 0x1A0) { + SPDLOG_INFO("[N64MemoryModel] 0x1A0 instance: actorId=0x{:X}", actorId); + } + const u32 shadow = ShadowArena_Malloc(&sShadow, instanceSize); if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[N64MemoryModel] Shadow instance failed for actor 0x{:04X} (need 0x{:X})", actorId, instanceSize); @@ -332,11 +370,10 @@ s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) { return 1; } - // When the enemy randomizer is active, the replacement actor's subsidiaries (colliders, skeleton tables, skin + // When inside a randomized enemy's init, the replacement actor's subsidiaries (colliders, skeleton tables, skin // buffers) have different counts than the original's. Rather than charge the wrong sizes, skip subsidiary - // tracking entirely for randomized actors -- instance and overlay sizes from the original are already correct - // and dominate heap pressure. - if (sOriginalActorId >= 0) { + // tracking entirely — instance and overlay sizes from the original are already correct and dominate heap pressure. + if (sInsideRandomizedInit) { return 1; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index eb1d6ebfa08..f22f5566a5c 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -57,6 +57,11 @@ void N64Mem_LogState(const char* context); void N64Mem_SetOriginalActorId(s16 actorId); void N64Mem_ClearOriginalActorId(void); +// Save/restore for the randomized-init skip flag. Actor_Spawn saves the current value at entry and restores it +// after Actor_Init returns, so child spawns during a randomized actor's init don't clobber the parent's flag. +s32 N64Mem_GetRandomizedInit(void); +void N64Mem_SetRandomizedInit(s32 value); + // Log Graveyard benchmark data (transition count, largest_free, total_free). Call after room actors are spawned. void N64Mem_BenchmarkTransition(PlayState* play); diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp index 7d921cb43fd..860c8264c1d 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp @@ -229,4 +229,4 @@ static void LoadKaleidoVramSize() { extern "C" u32 N64SizeData_GetKaleidoVramSize() { LoadKaleidoVramSize(); return sKaleidoVramSize; -} +} \ No newline at end of file diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 1e1517303e5..2ece788c686 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -3339,6 +3339,10 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos s32 objBankIndex; u32 temp; + // #region SOH [Enhancement] - N64 Memory Model + const s32 n64MemSavedInit = N64Mem_GetRandomizedInit(); + // #endregion + ActorDBEntry* dbEntry = ActorDB_Retrieve(actorId); assert(dbEntry->valid); @@ -3441,6 +3445,7 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos // #region SOH [Enhancement] - N64 Memory Model N64Mem_ClearOriginalActorId(); + N64Mem_SetRandomizedInit(n64MemSavedInit); // #endregion GameInteractor_ExecuteOnActorSpawn(actor); @@ -6534,4 +6539,4 @@ s32 func_80038290(PlayState* play, Actor* actor, Vec3s* arg2, Vec3s* arg3, Vec3f func_80037FC8(actor, &sp24, arg2, arg3); return true; -} +} \ No newline at end of file From 3e033c1b25944bce13f3e60355556eab69a5d829 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sat, 9 May 2026 23:11:06 -0700 Subject: [PATCH 36/58] fix(restoration): Track overlays by original actor ID for enemy randomizer --- .../N64MemoryModel/N64MemoryModel.cpp | 30 +++++++++++++++---- .../N64MemoryModel/N64MemoryModel.hpp | 3 +- soh/src/code/z_actor.c | 4 +-- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 54ba263a5b9..4d6e39b9ace 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -37,6 +37,10 @@ static u32 sAbsoluteSpaceShadow = SHADOW_NULL; // Maps real pointers (instances and subsidiaries) to their shadow offsets. static std::unordered_map sShadowMap; +// Maps real actor pointers to their original actor ID (before enemy randomizer substitution). Used by FreeOverlay to +// free the correct overlay shadow entry. Only populated for randomized actors. +static std::unordered_map sActorOriginalIds; + // When the enemy randomizer replaces an actor, this holds the ORIGINAL actor ID so that shadow allocations are charged // at the original N64 size rather than the replacement's size. Set by N64Mem_SetOriginalActorId before Actor_Spawn, // cleared by N64Mem_ClearOriginalActorId after Actor_Spawn returns. -1 means no override (use the passed actorId). @@ -142,6 +146,7 @@ void N64Mem_Reset(PlayState* play) { // Play_Init. ShadowArena_Destroy(&sShadow); sShadowMap.clear(); + sActorOriginalIds.clear(); sAbsoluteSpaceShadow = SHADOW_NULL; sOriginalActorId = -1; sInsideRandomizedInit = 0; @@ -256,8 +261,12 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { return 1; } + // Resolve the actor ID for overlay tracking. When the enemy randomizer is active, overlays are tracked by + // the ORIGINAL actor ID so that multiple replacements of the same original type share one overlay shadow. + const u16 overlayTrackId = ResolveSizeActorId(actorId); + // Already shadowed for this type. - if (sOverlayShadows[actorId] != SHADOW_NULL) { + if (sOverlayShadows[overlayTrackId] != SHADOW_NULL) { return 1; } @@ -269,8 +278,8 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { return 0; } - sOverlayShadows[actorId] = shadow; - TraceAlloc("ovl", actorId, overlaySize); + sOverlayShadows[overlayTrackId] = shadow; + TraceAlloc("ovl", overlayTrackId, overlaySize); return 1; } @@ -338,18 +347,26 @@ s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { } sShadowMap[realPtr] = shadow; + sActorOriginalIds[realPtr] = static_cast(sizeId); TraceAlloc("inst", actorId, instanceSize); return 1; } -void N64Mem_FreeInstance(void* realPtr) { +s16 N64Mem_FreeInstance(void* realPtr) { if (!sIsActive || !realPtr) { - return; + return -1; } const auto i = sShadowMap.find(realPtr); if (i == sShadowMap.end()) { - return; + return -1; + } + + // Retrieve the stored original actor ID before erasing. Used by Actor_Delete to free the correct overlay shadow. + s16 originalId = -1; + if (const auto j = sActorOriginalIds.find(realPtr); j != sActorOriginalIds.end()) { + originalId = j->second; + sActorOriginalIds.erase(j); } if (sTraceEnabled) { @@ -359,6 +376,7 @@ void N64Mem_FreeInstance(void* realPtr) { ShadowArena_Free(&sShadow, i->second); sShadowMap.erase(i); + return originalId; } // -------------------------------------------------------------------------------------------------------------------- diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index f22f5566a5c..c842f0f95b7 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -88,7 +88,8 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType); // -------------------------------------------------------------------------------------------------------------------- s32 N64Mem_AllocInstance(s16 actorId, void* realPtr); -void N64Mem_FreeInstance(void* realPtr); +// Returns the stored original actor ID for overlay free-path tracking, or -1 if not tracked. +s16 N64Mem_FreeInstance(void* realPtr); // -------------------------------------------------------------------------------------------------------------------- // Subsidiaries (colliders, camera, skin, etc.): Paired. diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 2ece788c686..59634a516c3 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -3566,8 +3566,8 @@ Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) { // #endregion // #region SOH [Enhancement] - N64 Memory Model - N64Mem_FreeInstance(actor); - const s16 n64MemActorId = actor->id; + const s16 n64MemOriginalId = N64Mem_FreeInstance(actor); + const s16 n64MemActorId = n64MemOriginalId >= 0 ? n64MemOriginalId : actor->id; // #endregion ZELDA_ARENA_FREE_DEBUG(actor); From 9d3d6fa539830071e54e679bdf3cc0d0fd4cec7a Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sun, 10 May 2026 00:11:57 -0700 Subject: [PATCH 37/58] feat(debugger): Heap viewer block identity, type coloring, and actor names --- .../N64MemoryModel/N64MemoryModel.cpp | 43 ++- .../N64MemoryModel/N64MemoryModel.hpp | 19 +- .../N64MemoryModel/N64SizeData.cpp | 2 +- .../debugger/HeapViewerWindow.cpp | 353 ++++++++++++------ .../debugger/HeapViewerWindow.hpp | 11 +- 5 files changed, 301 insertions(+), 127 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 4d6e39b9ace..6be36b7b81f 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -41,6 +41,15 @@ static std::unordered_map sShadowMap; // free the correct overlay shadow entry. Only populated for randomized actors. static std::unordered_map sActorOriginalIds; +// Block metadata for the heap viewer. Keyed by shadow DATA offset (node offset + SHADOW_NODE_SIZE). +// Populated in each Alloc function, erased in each Free, cleared in N64Mem_Reset. +struct BlockMeta { + u8 type; + s16 actorId; +}; + +static std::unordered_map sBlockMetaMap; + // When the enemy randomizer replaces an actor, this holds the ORIGINAL actor ID so that shadow allocations are charged // at the original N64 size rather than the replacement's size. Set by N64Mem_SetOriginalActorId before Actor_Spawn, // cleared by N64Mem_ClearOriginalActorId after Actor_Spawn returns. -1 means no override (use the passed actorId). @@ -48,7 +57,7 @@ static s16 sOriginalActorId = -1; // Set when AllocInstance consumes a non-negative sOriginalActorId, cleared after Actor_Init returns. While set, ALL // shadow allocations (overlay, instance, subsidiary) from child actors spawned during the replacement actor's init are -// skipped — on N64 these children don't exist because the original actor never spawned them. +// skipped -- on N64 these children don't exist because the original actor never spawned them. static s32 sInsideRandomizedInit = 0; // Returns the actor ID to use for size lookups. If an original actor ID override is set (enemy randomizer active), @@ -147,6 +156,7 @@ void N64Mem_Reset(PlayState* play) { ShadowArena_Destroy(&sShadow); sShadowMap.clear(); sActorOriginalIds.clear(); + sBlockMetaMap.clear(); sAbsoluteSpaceShadow = SHADOW_NULL; sOriginalActorId = -1; sInsideRandomizedInit = 0; @@ -226,7 +236,7 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { return 1; } - // Child actors spawned during a randomized enemy's init don't exist on N64 — skip shadow tracking. + // Child actors spawned during a randomized enemy's init don't exist on N64 -- skip shadow tracking. if (sInsideRandomizedInit) { return 1; } @@ -256,6 +266,7 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { SPDLOG_ERROR("[N64MemoryModel] Shadow absolute space failed (need 0x{:X})", AM_FIELD_SIZE); return 0; } + sBlockMetaMap[sAbsoluteSpaceShadow] = { N64MEM_BLOCK_ABSOLUTE, -1 }; } return 1; @@ -279,6 +290,7 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { } sOverlayShadows[overlayTrackId] = shadow; + sBlockMetaMap[shadow] = { N64MEM_BLOCK_OVERLAY, static_cast(overlayTrackId) }; TraceAlloc("ovl", overlayTrackId, overlaySize); return 1; } @@ -297,6 +309,7 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { return; } + sBlockMetaMap.erase(sOverlayShadows[actorId]); ShadowArena_Free(&sShadow, sOverlayShadows[actorId]); sOverlayShadows[actorId] = SHADOW_NULL; TraceFree("ovl", actorId); @@ -348,6 +361,7 @@ s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { sShadowMap[realPtr] = shadow; sActorOriginalIds[realPtr] = static_cast(sizeId); + sBlockMetaMap[shadow] = { N64MEM_BLOCK_INSTANCE, static_cast(sizeId) }; TraceAlloc("inst", actorId, instanceSize); return 1; } @@ -374,6 +388,7 @@ s16 N64Mem_FreeInstance(void* realPtr) { TraceFree("inst", actor->id); } + sBlockMetaMap.erase(i->second); ShadowArena_Free(&sShadow, i->second); sShadowMap.erase(i); return originalId; @@ -390,7 +405,7 @@ s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) { // When inside a randomized enemy's init, the replacement actor's subsidiaries (colliders, skeleton tables, skin // buffers) have different counts than the original's. Rather than charge the wrong sizes, skip subsidiary - // tracking entirely — instance and overlay sizes from the original are already correct and dominate heap pressure. + // tracking entirely -- instance and overlay sizes from the original are already correct and dominate heap pressure. if (sInsideRandomizedInit) { return 1; } @@ -402,6 +417,7 @@ s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) { } sShadowMap[realPtr] = shadow; + sBlockMetaMap[shadow] = { N64MEM_BLOCK_SUBSIDIARY, -1 }; TraceAlloc("sub", 0, n64Size); return 1; } @@ -416,6 +432,7 @@ void N64Mem_FreeSubsidiary(void* realPtr) { return; } + sBlockMetaMap.erase(i->second); ShadowArena_Free(&sShadow, i->second); sShadowMap.erase(i); } @@ -451,11 +468,31 @@ s32 N64Mem_AllocEffectOverlay(s32 type) { } sEffectOverlayShadows[type] = shadow; + sBlockMetaMap[shadow] = { N64MEM_BLOCK_EFFECT, static_cast(type) }; TraceAlloc("efx", type, overlaySize); return 1; } +// -------------------------------------------------------------------------------------------------------------------- +// Heap viewer metadata +// -------------------------------------------------------------------------------------------------------------------- + +s32 N64Mem_GetBlockInfo(u32 dataOffset, u8* outType, s16* outActorId) { + const auto it = sBlockMetaMap.find(dataOffset); + if (it == sBlockMetaMap.end()) { + return 0; + } + + if (outType) { + *outType = it->second.type; + } + if (outActorId) { + *outActorId = it->second.actorId; + } + return 1; +} + // -------------------------------------------------------------------------------------------------------------------- // Registration // -------------------------------------------------------------------------------------------------------------------- diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index c842f0f95b7..705319e219a 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -46,7 +46,7 @@ void N64Mem_LogState(const char* context); // Enemy randomizer compatibility // // When the enemy randomizer replaces an actor, the shadow arena should charge the ORIGINAL actor's N64 sizes (overlay -// and instance) rather than the replacement's. This preserves authentic N64 heap geometry — memory-dependent +// and instance) rather than the replacement's. This preserves authentic N64 heap geometry -- memory-dependent // behaviors (SRM, ACE, spawn failure thresholds) remain consistent regardless of which enemies are on screen. // // Call SetOriginalActorId with the scene's original actor ID before Actor_Spawn, and ClearOriginalActorId after @@ -108,6 +108,23 @@ void N64Mem_FreeSubsidiary(void* realPtr); s32 N64Mem_AllocEffectOverlay(s32 type); +// -------------------------------------------------------------------------------------------------------------------- +// Heap viewer metadata +// +// Query block identity from the shadow arena. Returns 1 if metadata exists for the given data offset, 0 if not. +// Block type constants identify the allocation category; actorId is the original (pre-randomizer) actor ID for +// instance/overlay blocks, or -1 for subsidiary/effect/absolute blocks. +// -------------------------------------------------------------------------------------------------------------------- + +#define N64MEM_BLOCK_FREE 0 +#define N64MEM_BLOCK_INSTANCE 1 +#define N64MEM_BLOCK_OVERLAY 2 +#define N64MEM_BLOCK_SUBSIDIARY 3 +#define N64MEM_BLOCK_EFFECT 4 +#define N64MEM_BLOCK_ABSOLUTE 5 + +s32 N64Mem_GetBlockInfo(u32 dataOffset, u8* outType, s16* outActorId); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp index 860c8264c1d..01f73edd6b9 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp @@ -197,7 +197,7 @@ extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) { // -------------------------------------------------------------------------------------------------------------------- // Kaleido overlay max VRAM size (misc/kaleido_vram_size) // -// Format: single u32 — max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) +// Format: single u32 -- max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) // -------------------------------------------------------------------------------------------------------------------- static u32 sKaleidoVramSize = 0; diff --git a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp index 9fc086f3154..851a3448fbc 100644 --- a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp +++ b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp @@ -6,20 +6,27 @@ #include #include "soh/OTRGlobals.h" +#include "soh/ActorDB.h" + +#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" extern "C" { #include "z64.h" #include "functions.h" - -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" #include "soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h" } +// ----------------------------------------------------------------------------------------------------------------- +// Block data +// ----------------------------------------------------------------------------------------------------------------- + struct BlockInfo { - u32 offset; // For shadow: offset into buffer. For ZeldaArena: not used. - std::uintptr_t address; - std::size_t size; + u32 offset; + u32 size; bool isFree; + u8 type; // N64MEM_BLOCK_* (shadow only) + s16 actorId; // Original actor ID (shadow only), -1 if unknown + const char* actorName; }; static std::vector sBlocks; @@ -31,25 +38,69 @@ static u32 sArenaSize = 0; static u32 sPreviousAlloc = 0; static u32 sCycleCount = 0; static s32 sLastDelta = 0; +static bool sIsShadow = false; + +static const char* GetActorName(s16 actorId) { + if (actorId < 0) { + return nullptr; + } + + const auto& entry = ActorDB::Instance->RetrieveEntry(actorId); + return entry.desc.empty() ? entry.entry.name : entry.desc.c_str(); +} + +static const char* BlockTypeName(u8 type) { + switch (type) { + case N64MEM_BLOCK_INSTANCE: + return "inst"; + + case N64MEM_BLOCK_OVERLAY: + return "ovl"; + + case N64MEM_BLOCK_SUBSIDIARY: + return "sub"; + + case N64MEM_BLOCK_EFFECT: + return "efx"; + + case N64MEM_BLOCK_ABSOLUTE: + return "abs"; + + default: + return "???"; + } +} + +static const char* sEffectNames[] = { + "Dust", "KiraKira", "Bomb", "Bomb2", "Blast", "G_Spk", "D_Fire", "Bubble", + "(unset)", "G_Ripple", "G_Splash", "G_Magma", "G_Fire", "Lightning", "Dt_Bubble", + "Hahen", "Stick", "Sibuki", "Sibuki2", "G_Magma2", "Stone1", "HitMark", + "Fhg_Flash", "K_Fire", "Solder_Srch_Ball", "Kakera", "Ice_Piece", "En_Ice", + "Fire_Tail", "En_Fire", "Extra", "Fcircle", "Dead_Db", "Dead_Dd", "Dead_Ds", + "Dead_Sound", "Ice_Smoke", +}; + +static const char* GetBlockDisplayName(const BlockInfo& block) { + if (block.isFree) { + return nullptr; + } -// Shadow arena view state -static std::vector sShadowBlocks; -static u32 sShadowAllocTotal = 0; -static u32 sShadowFreeTotal = 0; -static u32 sShadowLargestFree = 0; -static u32 sShadowNodeCount = 0; -static u32 sShadowArenaSize = 0; -static u32 sShadowPreviousAlloc = 0; -static u32 sShadowCycleCount = 0; -static s32 sShadowLastDelta = 0; - -static void CollectBlocks() { + if (block.type == N64MEM_BLOCK_EFFECT && block.actorId >= 0 && + block.actorId < static_cast(std::size(sEffectNames))) { + return sEffectNames[block.actorId]; + } + + return block.actorName; +} + +static void CollectZeldaArenaBlocks() { sBlocks.clear(); sAllocTotal = 0; sFreeTotal = 0; sLargestFree = 0; sNodeCount = 0; sArenaSize = 0; + sIsShadow = false; ArenaNode* node = ZeldaArena_GetHead(); if (!node) { @@ -58,9 +109,12 @@ static void CollectBlocks() { while (node) { BlockInfo block; - block.address = reinterpret_cast(node) + sizeof(ArenaNode); + block.offset = static_cast(reinterpret_cast(node) + sizeof(ArenaNode)); block.size = node->size; block.isFree = node->isFree; + block.type = N64MEM_BLOCK_FREE; + block.actorId = -1; + block.actorName = nullptr; sBlocks.push_back(block); sArenaSize += sizeof(ArenaNode) + node->size; @@ -82,23 +136,25 @@ static void CollectBlocks() { sLastDelta = static_cast(sAllocTotal) - static_cast(sPreviousAlloc); ++sCycleCount; } + sPreviousAlloc = sAllocTotal; } static void CollectShadowBlocks() { - sShadowBlocks.clear(); - sShadowAllocTotal = 0; - sShadowFreeTotal = 0; - sShadowLargestFree = 0; - sShadowNodeCount = 0; + sBlocks.clear(); + sAllocTotal = 0; + sFreeTotal = 0; + sLargestFree = 0; + sNodeCount = 0; + sIsShadow = true; ShadowArena* shadow = N64Mem_GetShadowArena(); if (!shadow) { - sShadowArenaSize = 0; + sArenaSize = 0; return; } - sShadowArenaSize = ShadowArena_GetBufferSize(shadow); + sArenaSize = ShadowArena_GetBufferSize(shadow); u32 offset = ShadowArena_GetHead(shadow); while (offset != SHADOW_NULL) { @@ -111,39 +167,78 @@ static void CollectShadowBlocks() { } BlockInfo block; - block.offset = offset; - block.address = offset + SHADOW_NODE_SIZE; + block.offset = offset + SHADOW_NODE_SIZE; block.size = size; block.isFree = isFree != 0; + block.type = N64MEM_BLOCK_FREE; + block.actorId = -1; + block.actorName = nullptr; + + if (!isFree) { + u8 type = 0; + s16 actorId = -1; + if (N64Mem_GetBlockInfo(block.offset, &type, &actorId)) { + block.type = type; + block.actorId = actorId; + block.actorName = GetActorName(actorId); + } + } - sShadowBlocks.push_back(block); - sShadowNodeCount++; + sBlocks.push_back(block); + sNodeCount++; if (isFree) { - sShadowFreeTotal += size; - if (size > sShadowLargestFree) { - sShadowLargestFree = size; + sFreeTotal += size; + if (size > sLargestFree) { + sLargestFree = size; } } else { - sShadowAllocTotal += size; + sAllocTotal += size; } offset = next; } - if (sShadowPreviousAlloc != 0 && sShadowAllocTotal != sShadowPreviousAlloc) { - sShadowLastDelta = static_cast(sShadowAllocTotal) - static_cast(sShadowPreviousAlloc); - ++sShadowCycleCount; + if (sPreviousAlloc != 0 && sAllocTotal != sPreviousAlloc) { + sLastDelta = static_cast(sAllocTotal) - static_cast(sPreviousAlloc); + ++sCycleCount; } - sShadowPreviousAlloc = sShadowAllocTotal; -} -static ImU32 ColorAlloc() { - return IM_COL32(200, 60, 60, 255); + sPreviousAlloc = sAllocTotal; } -static ImU32 ColorFree() { - return IM_COL32(60, 180, 80, 255); +// ----------------------------------------------------------------------------------------------------------------- +// Colors +// ----------------------------------------------------------------------------------------------------------------- + +static ImU32 BlockColor(const BlockInfo& block) { + if (block.isFree) { + return IM_COL32(60, 180, 80, 255); + } + + if (!sIsShadow) { + return IM_COL32(200, 60, 60, 255); + } + + switch (block.type) { + case N64MEM_BLOCK_INSTANCE: + return IM_COL32(200, 60, 60, 255); + + case N64MEM_BLOCK_OVERLAY: + return IM_COL32(220, 140, 40, 255); + + case N64MEM_BLOCK_EFFECT: + return IM_COL32(60, 120, 200, 255); + + case N64MEM_BLOCK_SUBSIDIARY: + return IM_COL32(200, 200, 60, 255); + + case N64MEM_BLOCK_ABSOLUTE: + return IM_COL32(160, 60, 200, 255); + + default: + return IM_COL32(120, 120, 120, 255); + } } static ImU32 ColorNode() { @@ -154,33 +249,54 @@ static ImU32 ColorBorder() { return IM_COL32(40, 40, 40, 255); } -static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u32 allocTotal, - u32 freeTotal, u32 largestFree, u32 nodeCount, u32 cycleCount, - s32 lastDelta, u32 nodeSize, const char* id) { - if (blocks.empty()) { +// ----------------------------------------------------------------------------------------------------------------- +// Drawing +// ----------------------------------------------------------------------------------------------------------------- + +void HeapViewerWindow::DrawElement() { + // Single arena view: Sshadow when active, ZeldaArena when not. + const bool isShadowArena = N64Mem_IsActive(); + const u32 nodeHeaderSize = isShadowArena ? SHADOW_NODE_SIZE : sizeof(ArenaNode); + + if (isShadowArena) { + CollectShadowBlocks(); + } else { + CollectZeldaArenaBlocks(); + } + + if (isShadowArena) { + if (ImGui::Button("Reset Tracking")) { + sPreviousAlloc = 0; + sCycleCount = 0; + sLastDelta = 0; + } + } + + if (sBlocks.empty()) { ImGui::Text("Arena is not initialized."); return; } // --- Stats --- - const f32 fragPercent = freeTotal > 0 - ? (1.0f - static_cast(largestFree) / static_cast(freeTotal)) * 100.0f + const f32 fragPercent = sFreeTotal > 0 + ? (1.0f - static_cast(sLargestFree) / static_cast(sFreeTotal)) * 100.0f : 0.0f; - ImGui::Text("Arena: 0x%X (%u KB) | Nodes: %u", arenaSize, arenaSize / 1024, nodeCount); + ImGui::Text("Arena: 0x%X (%u KB) | Nodes: %u", sArenaSize, sArenaSize / 1024, sNodeCount); ImGui::Text("Alloc: 0x%X (%u KB) | Free: 0x%X (%u KB) | Largest: 0x%X (%u KB)", - allocTotal, allocTotal / 1024, - freeTotal, freeTotal / 1024, - largestFree, largestFree / 1024); + sAllocTotal, sAllocTotal / 1024, + sFreeTotal, sFreeTotal / 1024, + sLargestFree, sLargestFree / 1024); + ImGui::Text("Fragmentation: %.1f%%", fragPercent); ImGui::Text("Cycle: %u | Last delta: %s0x%X (%d bytes)", - cycleCount, - lastDelta >= 0 ? "+" : "-", - static_cast(std::abs(lastDelta)), - lastDelta); + sCycleCount, + sLastDelta >= 0 ? "+" : "-", + static_cast(std::abs(sLastDelta)), + sLastDelta); // --- Utilization bar --- - const f32 utilization = arenaSize > 0 ? static_cast(allocTotal) / static_cast(arenaSize) : 0.0f; + const f32 utilization = sArenaSize > 0 ? static_cast(sAllocTotal) / static_cast(sArenaSize) : 0.0f; ImGui::ProgressBar(utilization, ImVec2(-1, 0), fmt::format("{:.1f}% utilized", utilization * 100.0f).c_str()); @@ -203,15 +319,15 @@ static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u f32 xCursor = 0.0f; s32 hoveredBlock = -1; - for (std::size_t i = 0; i < blocks.size(); ++i) { - f32 nodeWidth = static_cast(nodeSize) / static_cast(arenaSize) * availWidth; - f32 blockWidth = static_cast(blocks[i].size) / static_cast(arenaSize) * availWidth; + for (std::size_t i = 0; i < sBlocks.size(); ++i) { + f32 nodeWidth = static_cast(nodeHeaderSize) / static_cast(sArenaSize) * availWidth; + f32 blockWidth = static_cast(sBlocks[i].size) / static_cast(sArenaSize) * availWidth; if (nodeWidth < 1.0f) { nodeWidth = 1.0f; } - if (blockWidth < 1.0f && blocks[i].size > 0) { + if (blockWidth < 1.0f && sBlocks[i].size > 0) { blockWidth = 1.0f; } @@ -222,8 +338,7 @@ static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u x0 = mapPos.x + xCursor; x1 = x0 + blockWidth; - const ImU32 color = blocks[i].isFree ? ColorFree() : ColorAlloc(); - drawList->AddRectFilled(ImVec2(x0, mapPos.y), ImVec2(x1, mapPos.y + mapHeight), color); + drawList->AddRectFilled(ImVec2(x0, mapPos.y), ImVec2(x1, mapPos.y + mapHeight), BlockColor(sBlocks[i])); const f32 fullX0 = mapPos.x + xCursor - nodeWidth; ImVec2 blockMin(fullX0, mapPos.y); @@ -235,11 +350,21 @@ static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u } if (hoveredBlock >= 0) { - const auto& [offset, address, size, isFree] = blocks[hoveredBlock]; + const auto& block = sBlocks[hoveredBlock]; ImGui::BeginTooltip(); - ImGui::Text("Block %d: %s", hoveredBlock, isFree ? "FREE" : "ALLOCATED"); - ImGui::Text("Offset: 0x%X", static_cast(address)); - ImGui::Text("Size: 0x%X (%u bytes)", static_cast(size), static_cast(size)); + + if (block.isFree) { + ImGui::Text("Block %d: FREE", hoveredBlock); + } else if (sIsShadow && GetBlockDisplayName(block)) { + ImGui::Text("Block %d: %s (%s)", hoveredBlock, GetBlockDisplayName(block), BlockTypeName(block.type)); + } else if (sIsShadow) { + ImGui::Text("Block %d: %s", hoveredBlock, BlockTypeName(block.type)); + } else { + ImGui::Text("Block %d: ALLOCATED", hoveredBlock); + } + + ImGui::Text("Offset: 0x%X", block.offset); + ImGui::Text("Size: 0x%X (%u bytes)", block.size, block.size); ImGui::EndTooltip(); } @@ -248,73 +373,71 @@ static void DrawArenaView(const std::vector& blocks, u32 arenaSize, u ImGui::Spacing(); // --- Block list --- - ImGui::Text("Block List (%u blocks)", nodeCount); + ImGui::Text("Block List (%u blocks)", sNodeCount); - const std::string tableId = fmt::format("##blocks_{}", id); - if (ImGui::BeginTable(tableId.c_str(), 4, - ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | - ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable, - ImVec2(0, ImGui::GetContentRegionAvail().y))) { + if (const s32 columnCount = sIsShadow ? 5 : 4; ImGui::BeginTable("##blocks", columnCount, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_ScrollY | + ImGuiTableFlags_Resizable, + ImVec2(0, ImGui::GetContentRegionAvail().y))) { ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, 40.0f); - ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 80.0f); - ImGui::TableSetupColumn("Offset", ImGuiTableColumnFlags_WidthFixed, 140.0f); - ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthStretch); + + if (sIsShadow) { + ImGui::TableSetupColumn("Actor", ImGuiTableColumnFlags_WidthStretch); + } + + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 60.0f); + ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, 140.0f); + ImGui::TableSetupColumn("Offset", ImGuiTableColumnFlags_WidthFixed, 100.0f); ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); - for (std::size_t i = 0; i < blocks.size(); ++i) { - const auto& [offset, address, size, isFree] = blocks[i]; + for (std::size_t i = 0; i < sBlocks.size(); ++i) { + const auto& block = sBlocks[i]; ImGui::TableNextRow(); + // # ImGui::TableNextColumn(); ImGui::Text("%zu", i); + // Actor (shadow only) + if (sIsShadow) { + ImGui::TableNextColumn(); + if (block.isFree) { + ImGui::TextDisabled("--"); + } else if (const char* displayName = GetBlockDisplayName(block)) { + ImGui::Text("%s", displayName); + if (ImGui::IsItemHovered() && block.actorId >= 0 && block.type != N64MEM_BLOCK_EFFECT) { + const auto& entry = ActorDB::Instance->RetrieveEntry(block.actorId); + ImGui::SetTooltip("%s (0x%04X)", entry.entry.name, block.actorId); + } + } else { + ImGui::TextDisabled("--"); + } + } + + // Type ImGui::TableNextColumn(); - if (isFree) { - ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.3f, 1.0f), "FREE"); + + if (block.isFree) { + ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.3f, 1.0f), "free"); + } else if (sIsShadow) { + const ImU32 color = BlockColor(block); + ImVec4 colorVec = ImGui::ColorConvertU32ToFloat4(color); + ImGui::TextColored(colorVec, "%s", BlockTypeName(block.type)); } else { - ImGui::TextColored(ImVec4(0.8f, 0.25f, 0.25f, 1.0f), "ALLOC"); + ImGui::TextColored(ImVec4(0.8f, 0.25f, 0.25f, 1.0f), "alloc"); } + // Size ImGui::TableNextColumn(); - ImGui::Text("0x%X", static_cast(address)); + ImGui::Text("0x%X (%u)", block.size, block.size); + // Offset ImGui::TableNextColumn(); - ImGui::Text("0x%X (%u)", static_cast(size), static_cast(size)); + ImGui::Text("0x%X", block.offset); } ImGui::EndTable(); } -} - -void HeapViewerWindow::DrawElement() { - if (ImGui::BeginTabBar("##heap_tabs")) { - if (ImGui::BeginTabItem("ZeldaArena")) { - CollectBlocks(); - DrawArenaView(sBlocks, sArenaSize, sAllocTotal, sFreeTotal, sLargestFree, - sNodeCount, sCycleCount, sLastDelta, - sizeof(ArenaNode), "zelda"); - ImGui::EndTabItem(); - } - - if (N64Mem_IsActive()) { - if (ImGui::BeginTabItem("ShadowArena (N64)")) { - CollectShadowBlocks(); - - if (ImGui::Button("Reset Tracking")) { - sShadowPreviousAlloc = 0; - sShadowCycleCount = 0; - sShadowLastDelta = 0; - } - - DrawArenaView(sShadowBlocks, sShadowArenaSize, sShadowAllocTotal, - sShadowFreeTotal, sShadowLargestFree, sShadowNodeCount, - sShadowCycleCount, sShadowLastDelta, - SHADOW_NODE_SIZE, "shadow"); - ImGui::EndTabItem(); - } - } - - ImGui::EndTabBar(); - } } \ No newline at end of file diff --git a/soh/soh/Enhancements/debugger/HeapViewerWindow.hpp b/soh/soh/Enhancements/debugger/HeapViewerWindow.hpp index 62979b9049b..fa8be5b6c46 100644 --- a/soh/soh/Enhancements/debugger/HeapViewerWindow.hpp +++ b/soh/soh/Enhancements/debugger/HeapViewerWindow.hpp @@ -2,18 +2,15 @@ #include -class HeapViewerWindow : public Ship::GuiWindow -{ +class HeapViewerWindow : public Ship::GuiWindow { public: using GuiWindow::GuiWindow; - void InitElement() override - { + void InitElement() override { } void DrawElement() override; - void UpdateElement() override - { + void UpdateElement() override { } -}; +}; \ No newline at end of file From 0d082f81a90fb7dd13e6417e072c058c5e4a1963 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sun, 10 May 2026 03:02:59 -0700 Subject: [PATCH 38/58] feat(debugger): Heap viewer pinning with ghost rows for freed blocks --- .../debugger/HeapViewerWindow.cpp | 142 ++++++++++++++++-- 1 file changed, 130 insertions(+), 12 deletions(-) diff --git a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp index 851a3448fbc..c1380cb9416 100644 --- a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp +++ b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "soh/OTRGlobals.h" #include "soh/ActorDB.h" @@ -27,9 +28,15 @@ struct BlockInfo { u8 type; // N64MEM_BLOCK_* (shadow only) s16 actorId; // Original actor ID (shadow only), -1 if unknown const char* actorName; + bool isPinned; + bool isGhost; // Pinned block that was freed -- shown as a grayed-out row + u32 heapIndex; // Original index in heap order (for block map cross-reference) }; static std::vector sBlocks; +static std::vector sDisplayOrder; // Indices into sBlocks: pinned first, then unpinned +static u32 sPinnedCount = 0; +static std::set> sPinnedBlocks; // {actorId, blockType} pairs static u32 sAllocTotal = 0; static u32 sFreeTotal = 0; static u32 sLargestFree = 0; @@ -81,7 +88,7 @@ static const char* sEffectNames[] = { }; static const char* GetBlockDisplayName(const BlockInfo& block) { - if (block.isFree) { + if (block.isFree && !block.isGhost) { return nullptr; } @@ -207,6 +214,64 @@ static void CollectShadowBlocks() { sPreviousAlloc = sAllocTotal; } +// Build display order: assign heap indices, mark pinned blocks, add ghosts for freed pins, sort pinned to top. +static void BuildDisplayOrder() { + sDisplayOrder.clear(); + sPinnedCount = 0; + + // Track which pin keys have a live block. + std::set> matchedPins; + + for (std::size_t i = 0; i < sBlocks.size(); ++i) { + sBlocks[i].heapIndex = static_cast(i); + sBlocks[i].isGhost = false; + std::pair key = { sBlocks[i].actorId, sBlocks[i].type }; + sBlocks[i].isPinned = !sBlocks[i].isFree && sPinnedBlocks.contains(key); + if (sBlocks[i].isPinned) { + matchedPins.insert(key); + } + } + + // Add ghost entries for pinned blocks that no longer exist. + for (const auto& pin : sPinnedBlocks) { + if (!matchedPins.contains(pin)) { + BlockInfo ghost; + ghost.offset = 0; + ghost.size = 0; + ghost.isFree = true; + ghost.type = pin.second; + ghost.actorId = pin.first; + ghost.actorName = GetActorName(pin.first); + ghost.isPinned = true; + ghost.isGhost = true; + ghost.heapIndex = 0; + sBlocks.push_back(ghost); + } + } + + // Pinned first (heap order preserved within group, ghosts at end of pinned section). + for (std::size_t i = 0; i < sBlocks.size(); ++i) { + if (sBlocks[i].isPinned && !sBlocks[i].isGhost) { + sDisplayOrder.push_back(i); + ++sPinnedCount; + } + } + + for (std::size_t i = 0; i < sBlocks.size(); ++i) { + if (sBlocks[i].isGhost) { + sDisplayOrder.push_back(i); + ++sPinnedCount; + } + } + + // Then unpinned. + for (std::size_t i = 0; i < sBlocks.size(); ++i) { + if (!sBlocks[i].isPinned) { + sDisplayOrder.push_back(i); + } + } +} + // ----------------------------------------------------------------------------------------------------------------- // Colors // ----------------------------------------------------------------------------------------------------------------- @@ -264,12 +329,21 @@ void HeapViewerWindow::DrawElement() { CollectZeldaArenaBlocks(); } + BuildDisplayOrder(); + if (isShadowArena) { if (ImGui::Button("Reset Tracking")) { sPreviousAlloc = 0; sCycleCount = 0; sLastDelta = 0; } + + if (!sPinnedBlocks.empty()) { + ImGui::SameLine(); + if (ImGui::Button("Clear Pins")) { + sPinnedBlocks.clear(); + } + } } if (sBlocks.empty()) { @@ -340,6 +414,12 @@ void HeapViewerWindow::DrawElement() { x1 = x0 + blockWidth; drawList->AddRectFilled(ImVec2(x0, mapPos.y), ImVec2(x1, mapPos.y + mapHeight), BlockColor(sBlocks[i])); + // White outline for pinned blocks. + if (sBlocks[i].isPinned) { + drawList->AddRect(ImVec2(x0, mapPos.y), ImVec2(x1, mapPos.y + mapHeight), + IM_COL32(255, 255, 255, 255), 0.0f, 0, 2.0f); + } + const f32 fullX0 = mapPos.x + xCursor - nodeWidth; ImVec2 blockMin(fullX0, mapPos.y); if (ImVec2 blockMax(x1, mapPos.y + mapHeight); ImGui::IsMouseHoveringRect(blockMin, blockMax)) { @@ -373,7 +453,8 @@ void HeapViewerWindow::DrawElement() { ImGui::Spacing(); // --- Block list --- - ImGui::Text("Block List (%u blocks)", sNodeCount); + ImGui::Text("Block List (%u blocks%s)", sNodeCount, + sPinnedCount > 0 ? fmt::format(", {} pinned", sPinnedCount).c_str() : ""); if (const s32 columnCount = sIsShadow ? 5 : 4; ImGui::BeginTable("##blocks", columnCount, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | @@ -389,21 +470,49 @@ void HeapViewerWindow::DrawElement() { ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 60.0f); ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, 140.0f); ImGui::TableSetupColumn("Offset", ImGuiTableColumnFlags_WidthFixed, 100.0f); - ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableSetupScrollFreeze(0, 1 + static_cast(sPinnedCount)); ImGui::TableHeadersRow(); - for (std::size_t i = 0; i < sBlocks.size(); ++i) { - const auto& block = sBlocks[i]; + for (std::size_t di = 0; di < sDisplayOrder.size(); ++di) { + const std::size_t blockIdx = sDisplayOrder[di]; + const auto& block = sBlocks[blockIdx]; ImGui::TableNextRow(); - // # + // Tint pinned rows. + if (block.isPinned) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, + block.isGhost ? IM_COL32(255, 60, 60, 15) : IM_COL32(255, 255, 255, 20)); + } + + // # -- Click to pin/unpin (allocated shadow blocks and ghosts) ImGui::TableNextColumn(); - ImGui::Text("%zu", i); + + if (sIsShadow && ((!block.isFree && block.actorId >= 0) || block.isGhost)) { + ImGui::PushID(static_cast(di)); + if (ImGui::Selectable(fmt::format("{}{}", block.isPinned ? "\xF0\x9F\x93\x8C " : "", + block.isGhost ? "--" : std::to_string(block.heapIndex)).c_str(), + block.isPinned, + ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) { + std::pair key = { block.actorId, block.type }; + + if (block.isPinned) { + sPinnedBlocks.erase(key); + } else { + sPinnedBlocks.insert(key); + } + } + ImGui::PopID(); + } else { + ImGui::Text("%u", block.heapIndex); + } // Actor (shadow only) if (sIsShadow) { ImGui::TableNextColumn(); - if (block.isFree) { + if (block.isGhost) { + const char* displayName = GetBlockDisplayName(block); + ImGui::TextDisabled("%s", displayName ? displayName : "???"); + } else if (block.isFree) { ImGui::TextDisabled("--"); } else if (const char* displayName = GetBlockDisplayName(block)) { ImGui::Text("%s", displayName); @@ -418,8 +527,9 @@ void HeapViewerWindow::DrawElement() { // Type ImGui::TableNextColumn(); - - if (block.isFree) { + if (block.isGhost) { + ImGui::TextColored(ImVec4(0.6f, 0.2f, 0.2f, 1.0f), "freed"); + } else if (block.isFree) { ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.3f, 1.0f), "free"); } else if (sIsShadow) { const ImU32 color = BlockColor(block); @@ -431,11 +541,19 @@ void HeapViewerWindow::DrawElement() { // Size ImGui::TableNextColumn(); - ImGui::Text("0x%X (%u)", block.size, block.size); + if (block.isGhost) { + ImGui::TextDisabled("--"); + } else { + ImGui::Text("0x%X (%u)", block.size, block.size); + } // Offset ImGui::TableNextColumn(); - ImGui::Text("0x%X", block.offset); + if (block.isGhost) { + ImGui::TextDisabled("--"); + } else { + ImGui::Text("0x%X", block.offset); + } } ImGui::EndTable(); From 9ebd3d4f49d54e271c576b992e1569c9f5c8abfb Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sun, 10 May 2026 04:10:42 -0700 Subject: [PATCH 39/58] fix(restoration): Reallocate cary-over overlay shadows after arena reset --- .../N64MemoryModel/N64MemoryModel.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 6be36b7b81f..68ca793a91b 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -180,19 +180,19 @@ void N64Mem_Reset(PlayState* play) { return; } - // On N64, the instance region develops internal fragmentation gaps from alloc/free cycling during room - // transitions (partially caused by Bg_Spot02_Objects being in Room 0 on N64 but Room 1 on SoH, and by - // En_Firefly/En_Sw transient overlay churn). These gaps absorb the per-cycle Object_Kankyo instance leak - // (~0x1680 each) without shrinking the main free block. The model's clean coalescing has no such gaps, so - // leaked instances eat the main block directly. This correction accounts for the gap-structure mismatch - // until the upstream scene-data divergences are resolved. - if (constexpr u32 instanceGapCorrection = 0x1680; shadowArenaSize > instanceGapCorrection) { - shadowArenaSize -= instanceGapCorrection; - } - SPDLOG_INFO("[N64MemoryModel] Shadow arena size=0x{:X} for scene 0x{:X}", shadowArenaSize, play->sceneNum); ShadowArena_Init(&sShadow, shadowArenaSize); + // On N64, overlays that survive across scene transitions (ALLOCTYPE_PERMANENT, or any type with numLoaded > 0 + // from a prior scene) remain in ZeldaArena. The shadow was just destroyed and recreated, but numLoaded + // persists in the ActorDB. Re-allocate overlay shadows for any actor type still loaded. + for (std::size_t id = 0; id < ACTOR_ID_MAX; id++) { + if (const auto& entry = ActorDB::Instance->RetrieveEntry(id); + entry.entry.valid && entry.entry.numLoaded > 0) { + N64Mem_AllocOverlay(static_cast(id), entry.entry.allocType); + } + } + sTraceEnabled = play->sceneNum == SCENE_GRAVEYARD; } } From 96c630b77ee3751d77ee93f4ca7c149edd39c9df Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sun, 10 May 2026 05:34:30 -0700 Subject: [PATCH 40/58] Revert "fix(restoration): Reallocate cary-over overlay shadows after arena reset" This reverts commit 9ebd3d4f49d54e271c576b992e1569c9f5c8abfb. --- .../N64MemoryModel/N64MemoryModel.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 68ca793a91b..6be36b7b81f 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -180,19 +180,19 @@ void N64Mem_Reset(PlayState* play) { return; } + // On N64, the instance region develops internal fragmentation gaps from alloc/free cycling during room + // transitions (partially caused by Bg_Spot02_Objects being in Room 0 on N64 but Room 1 on SoH, and by + // En_Firefly/En_Sw transient overlay churn). These gaps absorb the per-cycle Object_Kankyo instance leak + // (~0x1680 each) without shrinking the main free block. The model's clean coalescing has no such gaps, so + // leaked instances eat the main block directly. This correction accounts for the gap-structure mismatch + // until the upstream scene-data divergences are resolved. + if (constexpr u32 instanceGapCorrection = 0x1680; shadowArenaSize > instanceGapCorrection) { + shadowArenaSize -= instanceGapCorrection; + } + SPDLOG_INFO("[N64MemoryModel] Shadow arena size=0x{:X} for scene 0x{:X}", shadowArenaSize, play->sceneNum); ShadowArena_Init(&sShadow, shadowArenaSize); - // On N64, overlays that survive across scene transitions (ALLOCTYPE_PERMANENT, or any type with numLoaded > 0 - // from a prior scene) remain in ZeldaArena. The shadow was just destroyed and recreated, but numLoaded - // persists in the ActorDB. Re-allocate overlay shadows for any actor type still loaded. - for (std::size_t id = 0; id < ACTOR_ID_MAX; id++) { - if (const auto& entry = ActorDB::Instance->RetrieveEntry(id); - entry.entry.valid && entry.entry.numLoaded > 0) { - N64Mem_AllocOverlay(static_cast(id), entry.entry.allocType); - } - } - sTraceEnabled = play->sceneNum == SCENE_GRAVEYARD; } } From 2f999e76dbd5facd72e1e1011f48979222653ff6 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sun, 10 May 2026 07:17:40 -0700 Subject: [PATCH 41/58] fix(restoration): Version-specific ArenaNode size for shadow arena --- OTRExporter | 2 +- .../N64MemoryModel/N64MemoryModel.cpp | 26 +-- .../N64MemoryModel/N64SizeData.cpp | 38 ++++ .../N64MemoryModel/N64SizeData.hpp | 3 +- .../N64MemoryModel/n64_arena_sizing.c | 173 +++++++---------- .../N64MemoryModel/n64_arena_sizing.h | 5 +- .../N64MemoryModel/n64_shadow_arena.c | 175 +++++++----------- .../N64MemoryModel/n64_shadow_arena.h | 23 ++- .../debugger/HeapViewerWindow.cpp | 6 +- 9 files changed, 202 insertions(+), 249 deletions(-) diff --git a/OTRExporter b/OTRExporter index d7816f58d97..969c8666421 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit d7816f58d977d0d92b0c21e11d5c20c0f06f3016 +Subproject commit 969c86664210de433ecfbbbef195a01513942490 diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 6be36b7b81f..3b072cac63c 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -41,7 +41,7 @@ static std::unordered_map sShadowMap; // free the correct overlay shadow entry. Only populated for randomized actors. static std::unordered_map sActorOriginalIds; -// Block metadata for the heap viewer. Keyed by shadow DATA offset (node offset + SHADOW_NODE_SIZE). +// Block metadata for the heap viewer. Keyed by shadow DATA offset (node offset + arena->nodeSize). // Populated in each Alloc function, erased in each Free, cleared in N64Mem_Reset. struct BlockMeta { u8 type; @@ -84,7 +84,7 @@ static void LogShadowState(const char* context) { static void TraceAlloc(const char* tag, u32 id, u32 size) { if (sTraceEnabled) { - u32 consumed = (size + 0xF & ~0xF) + SHADOW_NODE_SIZE; + u32 consumed = (size + 0xF & ~0xF) + sShadow.nodeSize; SPDLOG_TRACE("[N64Trace] +{} id=0x{:X} sz=0x{:X} cost=0x{:X}", tag, id, size, consumed); } } @@ -180,18 +180,8 @@ void N64Mem_Reset(PlayState* play) { return; } - // On N64, the instance region develops internal fragmentation gaps from alloc/free cycling during room - // transitions (partially caused by Bg_Spot02_Objects being in Room 0 on N64 but Room 1 on SoH, and by - // En_Firefly/En_Sw transient overlay churn). These gaps absorb the per-cycle Object_Kankyo instance leak - // (~0x1680 each) without shrinking the main free block. The model's clean coalescing has no such gaps, so - // leaked instances eat the main block directly. This correction accounts for the gap-structure mismatch - // until the upstream scene-data divergences are resolved. - if (constexpr u32 instanceGapCorrection = 0x1680; shadowArenaSize > instanceGapCorrection) { - shadowArenaSize -= instanceGapCorrection; - } - SPDLOG_INFO("[N64MemoryModel] Shadow arena size=0x{:X} for scene 0x{:X}", shadowArenaSize, play->sceneNum); - ShadowArena_Init(&sShadow, shadowArenaSize); + ShadowArena_Init(&sShadow, shadowArenaSize, N64SizeData_GetArenaNodeSize()); sTraceEnabled = play->sceneNum == SCENE_GRAVEYARD; } @@ -479,17 +469,19 @@ s32 N64Mem_AllocEffectOverlay(s32 type) { // -------------------------------------------------------------------------------------------------------------------- s32 N64Mem_GetBlockInfo(u32 dataOffset, u8* outType, s16* outActorId) { - const auto it = sBlockMetaMap.find(dataOffset); - if (it == sBlockMetaMap.end()) { + const auto i = sBlockMetaMap.find(dataOffset); + if (i == sBlockMetaMap.end()) { return 0; } if (outType) { - *outType = it->second.type; + *outType = i->second.type; } + if (outActorId) { - *outActorId = it->second.actorId; + *outActorId = i->second.actorId; } + return 1; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp index 01f73edd6b9..54a7d7123b3 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp @@ -229,4 +229,42 @@ static void LoadKaleidoVramSize() { extern "C" u32 N64SizeData_GetKaleidoVramSize() { LoadKaleidoVramSize(); return sKaleidoVramSize; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Arena node size +// +// Format: single u32 -- ArenaNode size for this ROM version (0x10 retail, 0x30 debug) +// -------------------------------------------------------------------------------------------------------------------- + +static u32 sArenaNodeSize = 0x30; // Default to debug (safe fallback -- over-estimates node overhead) +static bool sIsArenaNodeSizeLoaded = false; + +static void LoadArenaNodeSize() { + if (sIsArenaNodeSizeLoaded) { + return; + } + + sIsArenaNodeSizeLoaded = true; + + const auto file = + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/arena_node_size"); + if (!file || !file->IsLoaded) { + SPDLOG_WARN("[N64SizeData] Failed to load misc/arena_node_size from OTR, defaulting to 0x{:X}.", + sArenaNodeSize); + return; + } + + auto stream = std::make_shared(file->Buffer->data(), file->Buffer->size()); + const auto reader = std::make_shared(stream); + reader->SetEndianness(Ship::Endianness::Big); + + sArenaNodeSize = reader->ReadUInt32(); + + SPDLOG_INFO("[N64SizeData] Arena node size: 0x{:X}.", sArenaNodeSize); +} + +extern "C" u32 N64SizeData_GetArenaNodeSize() { + LoadArenaNodeSize(); + return sArenaNodeSize; } \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp index 4a91506825e..67be4212003 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp @@ -24,7 +24,8 @@ u32 N64SizeData_GetActorOverlaySize(u16 actorId); u32 N64SizeData_GetEffectOverlaySize(u16 effectType); u32 N64SizeData_GetActorInstanceSize(u16 actorId); u32 N64SizeData_GetKaleidoVramSize(void); +u32 N64SizeData_GetArenaNodeSize(void); #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c index 2eab39b6a63..f022fa2a64f 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c @@ -5,7 +5,7 @@ #include "global.h" // Declared in z_bgcheck.c but not exposed via header. -s32 BgCheck_IsSpotScene(PlayState * play); +s32 BgCheck_IsSpotScene(PlayState* play); s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); // -------------------------------------------------------------------------------------------------------------------- @@ -56,14 +56,12 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); #define N64_MAP_MARK_DATA_VRAM_SIZE 0x6B60 -static u32 GetMapMarkDataOverlaySize(PlayState* play) -{ +static u32 GetMapMarkDataOverlaySize(PlayState* play) { // Mirrors the condition in z_map_exp.c Map_Init: the dungeon case block's inner guard. // Main dungeons: SCENE_DEKU_TREE (0x00) through SCENE_ICE_CAVERN (0x09) // Boss rooms: SCENE_DEKU_TREE_BOSS (0x11) through SCENE_SHADOW_TEMPLE_BOSS (0x18) if (play->sceneNum <= SCENE_ICE_CAVERN || - (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) - { + (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) { return N64_MAP_MARK_DATA_VRAM_SIZE; } @@ -88,59 +86,54 @@ static u32 GetMapMarkDataOverlaySize(PlayState* play) // -------------------------------------------------------------------------------------------------------------------- // DMA file name for a single-file indoor skybox (1 tex + 1 pal). -typedef struct -{ +typedef struct { const char* texName; const char* palName; } SkyboxDmaEntry; // Indexed by skybox ID. NULL texName means the ID isn't a single-file indoor skybox (handled separately). static const SkyboxDmaEntry sSkyboxDmaTable[] = { - [SKYBOX_NONE] = {NULL, NULL}, - [SKYBOX_NORMAL_SKY] = {NULL, NULL}, // gNormalSkyFiles, hardcoded - [SKYBOX_BAZAAR] = {"vr_SP1a_static", "vr_SP1a_pal_static"}, - [SKYBOX_OVERCAST_SUNSET] = {NULL, NULL}, // same as NORMAL_SKY - [SKYBOX_MARKET_ADULT] = {"vr_RUVR_static", "vr_RUVR_pal_static"}, - [SKYBOX_CUTSCENE_MAP] = {NULL, NULL}, // Two tex files, handled separately - [SKYBOX_HOUSE_LINK] = {"vr_LHVR_static", "vr_LHVR_pal_static"}, - [SKYBOX_MARKET_CHILD_DAY] = {"vr_MDVR_static", "vr_MDVR_pal_static"}, - [SKYBOX_MARKET_CHILD_NIGHT] = {"vr_MNVR_static", "vr_MNVR_pal_static"}, - [SKYBOX_HAPPY_MASK_SHOP] = {"vr_FCVR_static", "vr_FCVR_pal_static"}, - [SKYBOX_HOUSE_KNOW_IT_ALL_BROTHERS] = {"vr_KHVR_static", "vr_KHVR_pal_static"}, - [SKYBOX_HOUSE_OF_TWINS] = {"vr_K3VR_static", "vr_K3VR_pal_static"}, - [SKYBOX_STABLES] = {"vr_MLVR_static", "vr_MLVR_pal_static"}, - [SKYBOX_HOUSE_KAKARIKO] = {"vr_KKRVR_static", "vr_KKRVR_pal_static"}, - [SKYBOX_KOKIRI_SHOP] = {"vr_KSVR_static", "vr_KSVR_pal_static"}, - [SKYBOX_GORON_SHOP] = {"vr_GLVR_static", "vr_GLVR_pal_static"}, - [SKYBOX_ZORA_SHOP] = {"vr_ZRVR_static", "vr_ZRVR_pal_static"}, - [SKYBOX_POTION_SHOP_KAKARIKO] = {"vr_DGVR_static", "vr_DGVR_pal_static"}, - [SKYBOX_POTION_SHOP_MARKET] = {"vr_ALVR_static", "vr_ALVR_pal_static"}, - [SKYBOX_BOMBCHU_SHOP] = {"vr_NSVR_static", "vr_NSVR_pal_static"}, - [SKYBOX_HOUSE_RICHARD] = {"vr_IPVR_static", "vr_IPVR_pal_static"}, - [SKYBOX_HOUSE_IMPA] = {"vr_LBVR_static", "vr_LBVR_pal_static"}, - [SKYBOX_TENT] = {"vr_TTVR_static", "vr_TTVR_pal_static"}, - [SKYBOX_HOUSE_MIDO] = {"vr_K4VR_static", "vr_K4VR_pal_static"}, - [SKYBOX_HOUSE_SARIA] = {"vr_K5VR_static", "vr_K5VR_pal_static"}, - [SKYBOX_HOUSE_ALLEY] = {"vr_KR3VR_static", "vr_KR3VR_pal_static"}, + [SKYBOX_NONE] = { NULL, NULL }, + [SKYBOX_NORMAL_SKY] = { NULL, NULL }, // gNormalSkyFiles, hardcoded + [SKYBOX_BAZAAR] = { "vr_SP1a_static", "vr_SP1a_pal_static" }, + [SKYBOX_OVERCAST_SUNSET] = { NULL, NULL }, // same as NORMAL_SKY + [SKYBOX_MARKET_ADULT] = { "vr_RUVR_static", "vr_RUVR_pal_static" }, + [SKYBOX_CUTSCENE_MAP] = { NULL, NULL }, // Two tex files, handled separately + [SKYBOX_HOUSE_LINK] = { "vr_LHVR_static", "vr_LHVR_pal_static" }, + [SKYBOX_MARKET_CHILD_DAY] = { "vr_MDVR_static", "vr_MDVR_pal_static" }, + [SKYBOX_MARKET_CHILD_NIGHT] = { "vr_MNVR_static", "vr_MNVR_pal_static" }, + [SKYBOX_HAPPY_MASK_SHOP] = { "vr_FCVR_static", "vr_FCVR_pal_static" }, + [SKYBOX_HOUSE_KNOW_IT_ALL_BROTHERS] = { "vr_KHVR_static", "vr_KHVR_pal_static" }, + [SKYBOX_HOUSE_OF_TWINS] = { "vr_K3VR_static", "vr_K3VR_pal_static" }, + [SKYBOX_STABLES] = { "vr_MLVR_static", "vr_MLVR_pal_static" }, + [SKYBOX_HOUSE_KAKARIKO] = { "vr_KKRVR_static", "vr_KKRVR_pal_static" }, + [SKYBOX_KOKIRI_SHOP] = { "vr_KSVR_static", "vr_KSVR_pal_static" }, + [SKYBOX_GORON_SHOP] = { "vr_GLVR_static", "vr_GLVR_pal_static" }, + [SKYBOX_ZORA_SHOP] = { "vr_ZRVR_static", "vr_ZRVR_pal_static" }, + [SKYBOX_POTION_SHOP_KAKARIKO] = { "vr_DGVR_static", "vr_DGVR_pal_static" }, + [SKYBOX_POTION_SHOP_MARKET] = { "vr_ALVR_static", "vr_ALVR_pal_static" }, + [SKYBOX_BOMBCHU_SHOP] = { "vr_NSVR_static", "vr_NSVR_pal_static" }, + [SKYBOX_HOUSE_RICHARD] = { "vr_IPVR_static", "vr_IPVR_pal_static" }, + [SKYBOX_HOUSE_IMPA] = { "vr_LBVR_static", "vr_LBVR_pal_static" }, + [SKYBOX_TENT] = { "vr_TTVR_static", "vr_TTVR_pal_static" }, + [SKYBOX_HOUSE_MIDO] = { "vr_K4VR_static", "vr_K4VR_pal_static" }, + [SKYBOX_HOUSE_SARIA] = { "vr_K5VR_static", "vr_K5VR_pal_static" }, + [SKYBOX_HOUSE_ALLEY] = { "vr_KR3VR_static", "vr_KR3VR_pal_static" }, }; -static u32 GetN64SkyboxTextureSize(s16 skyboxId) -{ - if (skyboxId == SKYBOX_NONE) - { +static u32 GetN64SkyboxTextureSize(s16 skyboxId) { + if (skyboxId == SKYBOX_NONE) { return 0; } // NORMAL_SKY and OVERCAST_SUNSET both load 2 texture banks + 2 palettes from the vr_fine/vr_cloud files. // All 16 banks are exactly 0xC000 and all 16 palettes are exactly 0x100, so the total is constant. - if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) - { + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { return 2 * 0xC000 + 2 * 0x100; } // CUTSCENE_MAP loads two different texture files + 2 palette copies. - if (skyboxId == SKYBOX_CUTSCENE_MAP) - { + if (skyboxId == SKYBOX_CUTSCENE_MAP) { const u32 tex0 = N64SizeData_GetDmaFileSize("vr_holy0_static"); const u32 tex1 = N64SizeData_GetDmaFileSize("vr_holy1_static"); const u32 pal = N64SizeData_GetDmaFileSize("vr_holy0_pal_static"); @@ -148,11 +141,9 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) } // Indoor skyboxes: 1 texture + 1 palette, looked up from the DMA blob. - if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) - { + if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) { const SkyboxDmaEntry* entry = &sSkyboxDmaTable[skyboxId]; - if (entry->texName != NULL) - { + if (entry->texName != NULL) { const u32 tex = N64SizeData_GetDmaFileSize(entry->texName); const u32 pal = N64SizeData_GetDmaFileSize(entry->palName); return tex + pal; @@ -172,27 +163,20 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) // Gfx is 8 bytes on N64, Vtx is 0x10. // -------------------------------------------------------------------------------------------------------------------- -static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) -{ - if (skyboxId == SKYBOX_NONE) - { +static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) { + if (skyboxId == SKYBOX_NONE) { *outDlistSize = 0; *outVtxSize = 0; return; } - if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) - { + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; *outVtxSize = 5 * 32 * N64_SIZEOF_VTX; - } - else if (skyboxId == SKYBOX_CUTSCENE_MAP) - { + } else if (skyboxId == SKYBOX_CUTSCENE_MAP) { *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; *outVtxSize = 6 * 32 * N64_SIZEOF_VTX; - } - else - { + } else { // Indoor skyboxes: SKYBOX_DRAW_256_4FACE or SKYBOX_DRAW_256_3FACE, both use the same allocation. *outDlistSize = 8 * 150 * N64_SIZEOF_GFX; *outVtxSize = 8 * 32 * N64_SIZEOF_VTX; @@ -214,31 +198,26 @@ static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVt // bgcheck_memSize and sizeof(CollisionContext). // -------------------------------------------------------------------------------------------------------------------- -static u32 GetBgCheckMemSize(PlayState* play) -{ +static u32 GetBgCheckMemSize(PlayState* play) { const s16 sceneNum = play->sceneNum; - if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) - { + if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) { return sceneNum == SCENE_STABLE ? 0x3520 : 0x4E20; } - if (BgCheck_IsSpotScene(play)) - { + if (BgCheck_IsSpotScene(play)) { return 0xF000; } u32 customMemSize = 0; - if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) - { + if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) { return customMemSize; } return 0x1CC00; } -static u32 GetBgCheckThaTotal(PlayState* play) -{ +static u32 GetBgCheckThaTotal(PlayState* play) { return GetBgCheckMemSize(play) - N64_SIZEOF_COLLISION_CONTEXT; } @@ -246,17 +225,14 @@ static u32 GetBgCheckThaTotal(PlayState* play) // Object bank size (mirrors z_scene.c Object_InitBlank) // -------------------------------------------------------------------------------------------------------------------- -static u32 GetObjectBankSize(PlayState* play) -{ +static u32 GetObjectBankSize(PlayState* play) { const s16 sceneNum = play->sceneNum; - if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) - { + if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) { return 1177600; } if (sceneNum == SCENE_SPIRIT_TEMPLE_BOSS || sceneNum == SCENE_CHAMBER_OF_THE_SAGES || - sceneNum == SCENE_GANONDORF_BOSS) - { + sceneNum == SCENE_GANONDORF_BOSS) { return 1075200; } @@ -275,16 +251,13 @@ static const char* sElfMsgDmaNames[] = { "elf_message_ydan", }; -static u32 GetElfMessageSize(PlayState* play) -{ - if (play->cUpElfMsgs == NULL) - { +static u32 GetElfMessageSize(PlayState* play) { + if (play->cUpElfMsgs == NULL) { return 0; } const u8 elfMsgNum = N64Mem_GetElfMsgNum(); - if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) - { + if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) { LUSLOG_WARN("[ArenaSizing] elfMsg: cUpElfMsgs non-NULL but elfMsgNum=%d out of range", elfMsgNum); return 0; } @@ -296,37 +269,31 @@ static u32 GetElfMessageSize(PlayState* play) // Room buffer max size (mirrors func_80096FE8 logic) // -------------------------------------------------------------------------------------------------------------------- -static u32 GetMaxRoomSize(PlayState* play) -{ +static u32 GetMaxRoomSize(PlayState* play) { u32 maxRoomSize = 0; - for (size_t i = 0; i < play->numRooms; ++i) - { - const u32 roomSize = play->roomList[i].vromEnd - play->roomList[i].vromStart; - if (roomSize > maxRoomSize) - { + for (size_t i = 0; i < play->numRooms; ++i) { + const uintptr_t roomSize = play->roomList[i].vromEnd - play->roomList[i].vromStart; + if (roomSize > maxRoomSize) { maxRoomSize = roomSize; } } - if (play->transiActorCtx.numActors != 0) - { + if (play->transiActorCtx.numActors != 0) { const TransitionActorEntry* transitionActor = &play->transiActorCtx.list[0]; - for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) - { + for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) { const s8 frontRoom = transitionActor->sides[0].room; const s8 backRoom = transitionActor->sides[1].room; - const size_t frontSize = frontRoom < 0 - ? 0 - : play->roomList[frontRoom].vromEnd - play->roomList[frontRoom].vromStart; - const u32 backSize = backRoom < 0 - ? 0 - : play->roomList[backRoom].vromEnd - play->roomList[backRoom].vromStart; - - const u32 cumulSize = frontRoom != backRoom ? frontSize + backSize : frontSize; - if (cumulSize > maxRoomSize) - { + const uintptr_t frontSize = frontRoom < 0 + ? 0 + : play->roomList[frontRoom].vromEnd - play->roomList[frontRoom].vromStart; + const uintptr_t backSize = backRoom < 0 + ? 0 + : play->roomList[backRoom].vromEnd - play->roomList[backRoom].vromStart; + + const uintptr_t cumulSize = frontRoom != backRoom ? frontSize + backSize : frontSize; + if (cumulSize > maxRoomSize) { maxRoomSize = cumulSize; } @@ -341,8 +308,7 @@ static u32 GetMaxRoomSize(PlayState* play) // Main computation // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) -{ +u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { // Each THA consumer is allocated via GAME_STATE_ALLOC -> THA_AllocTailAlign16, which consumes ALIGN16(size) bytes. // We must align each consumer individually before summing; aligning the sum would under-count when individual // sizes are not 16-byte aligned (common for DMA file sizes). BgCheck is the exception -- its internal allocations @@ -435,8 +401,7 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) LUSLOG_INFO("[ArenaSizing] total=0x%X, arena=0x%X (budget=0x%X)", total, N64_THA_BUDGET - total, (u32)N64_THA_BUDGET); - if (total >= N64_THA_BUDGET) - { + if (total >= N64_THA_BUDGET) { return 0; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h index 7265b234603..e08590997f8 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h @@ -4,6 +4,7 @@ #ifdef __cplusplus extern "C" { + #endif // -------------------------------------------------------------------------------------------------------------------- @@ -21,8 +22,8 @@ extern "C" { // (should not happen on valid scenes). // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState * play); +u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play); #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c index eeaa9b5dd10..4cd1f1dfc3a 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c @@ -6,26 +6,26 @@ // -------------------------------------------------------------------------------------------------------------------- // Internal node layout (not exposed -- heap viewer uses offset + size queries, not direct struct access). // -// Matches N64 retail ArenaNode exactly at 0x30 bytes: +// Fixed header fields (0x10 bytes, identical on retail and debug): // 0x00 s16 magic // 0x02 s16 isFree // 0x04 u32 size (Payload bytes, excluding node header) // 0x08 u32 next (Offset into buffer, SHADOW_NULL = end) // 0x0C u32 prev (Offset into buffer, SHADOW_NULL = head) -// 0x10 u8[0x20] (Dead space matching N64 debug fields) +// +// On debug builds, an additional 0x20 bytes of debug fields follow (filename, line, threadId, arena, time). +// The total node spacing is arena->nodeSize: 0x10 for retail, 0x30 for debug. // -------------------------------------------------------------------------------------------------------------------- -typedef struct ShadowNode -{ +typedef struct ShadowNode { s16 magic; s16 isFree; u32 size; u32 next; u32 prev; - u8 _dead[0x20]; -} ShadowNode; // 0x30 +} ShadowNode; // 0x10 -static_assert(sizeof(ShadowNode) == SHADOW_NODE_SIZE, "ShadowNode must be exactly 0x30 bytes to match N64 ArenaNode"); +static_assert(sizeof(ShadowNode) == 0x10, "ShadowNode header must be exactly 0x10 bytes"); #define NODE_MAGIC 0x7373 #define ALIGN16(x) (((x) + 0xF) & ~0xF) @@ -34,42 +34,34 @@ static_assert(sizeof(ShadowNode) == SHADOW_NODE_SIZE, "ShadowNode must be exactl // Offset helpers // -------------------------------------------------------------------------------------------------------------------- -static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) -{ +static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) { return (ShadowNode*)(arena->buffer + offset); } -static s32 NodeIsValid(ShadowArena* arena, u32 offset) -{ - if (offset == SHADOW_NULL) - { +static s32 NodeIsValid(ShadowArena* arena, u32 offset) { + if (offset == SHADOW_NULL) { return 0; } - if (offset + SHADOW_NODE_SIZE > arena->bufferSize) - { + if (offset + arena->nodeSize > arena->bufferSize) { return 0; } return NodeAt(arena, offset)->magic == NODE_MAGIC; } -static u32 NodeGetNext(ShadowArena* arena, u32 offset) -{ +static u32 NodeGetNext(ShadowArena* arena, u32 offset) { const ShadowNode* node = NodeAt(arena, offset); - if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) - { + if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) { return node->next; } return SHADOW_NULL; } -static u32 NodeGetPrev(ShadowArena* arena, u32 offset) -{ +static u32 NodeGetPrev(ShadowArena* arena, u32 offset) { const ShadowNode* node = NodeAt(arena, offset); - if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) - { + if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) { return node->prev; } @@ -80,38 +72,34 @@ static u32 NodeGetPrev(ShadowArena* arena, u32 offset) // Init / Destroy // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Init(ShadowArena* arena, u32 size) -{ +void ShadowArena_Init(ShadowArena* arena, u32 size, u32 nodeSize) { // Match N64's alignment: Round start up to 16, round size down to 16. Since we control the buffer, start is // effectively offset 0 after alignment. We just ensure the usable size is 16-byte aligned. const u32 alignedSize = size & ~0xF; arena->buffer = (u8*)malloc(alignedSize); - if (!arena->buffer) - { + if (!arena->buffer) { memset(arena, 0, sizeof(ShadowArena)); return; } memset(arena->buffer, 0, alignedSize); arena->bufferSize = alignedSize; + arena->nodeSize = nodeSize; // Single free node spanning the entire buffer minus one header. ShadowNode* first = NodeAt(arena, 0); first->magic = NODE_MAGIC; first->isFree = 1; - first->size = alignedSize - SHADOW_NODE_SIZE; + first->size = alignedSize - nodeSize; first->next = SHADOW_NULL; first->prev = SHADOW_NULL; - memset(first->_dead, 0, sizeof(first->_dead)); arena->head = 0; } -void ShadowArena_Destroy(ShadowArena* arena) -{ - if (arena->buffer) - { +void ShadowArena_Destroy(ShadowArena* arena) { + if (arena->buffer) { free(arena->buffer); } @@ -122,24 +110,20 @@ void ShadowArena_Destroy(ShadowArena* arena) // Malloc: First-fit forward (matches N64 __osMalloc) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) -{ +u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { u32 iterOff = 0; ShadowNode* iter = NULL; u32 blockSize = 0; size = ALIGN16(size); - blockSize = size + SHADOW_NODE_SIZE; + blockSize = size + arena->nodeSize; iterOff = arena->head; - while (iterOff != SHADOW_NULL) - { + while (iterOff != SHADOW_NULL) { iter = NodeAt(arena, iterOff); - if (iter->isFree && iter->size >= size) - { + if (iter->isFree && iter->size >= size) { // Split if remainder can hold a new node + payload. - if (blockSize < iter->size) - { + if (blockSize < iter->size) { const u32 newOff = iterOff + blockSize; ShadowNode* newNode = NodeAt(arena, newOff); @@ -148,10 +132,8 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) newNode->size = iter->size - blockSize; newNode->next = iter->next; newNode->prev = iterOff; - memset(newNode->_dead, 0, sizeof(newNode->_dead)); - if (newNode->next != SHADOW_NULL) - { + if (newNode->next != SHADOW_NULL) { NodeAt(arena, newNode->next)->prev = newOff; } @@ -160,10 +142,9 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) } iter->isFree = 0; - memset(iter->_dead, 0, sizeof(iter->_dead)); // Return offset to data area (past the node header). - return iterOff + SHADOW_NODE_SIZE; + return iterOff + arena->nodeSize; } iterOff = NodeGetNext(arena, iterOff); @@ -176,8 +157,7 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) // MallocR: First-fit backward (matches N64 __osMallocR) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) -{ +u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { u32 iterOff = 0; u32 nextOff = 0; ShadowNode* iter = NULL; @@ -188,23 +168,19 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) // Walk to tail. iterOff = arena->head; nextOff = NodeGetNext(arena, iterOff); - while (nextOff != SHADOW_NULL) - { + while (nextOff != SHADOW_NULL) { iterOff = nextOff; nextOff = NodeGetNext(arena, nextOff); } // Walk backward looking for a fit. - while (iterOff != SHADOW_NULL) - { + while (iterOff != SHADOW_NULL) { iter = NodeAt(arena, iterOff); - if (iter->isFree && iter->size >= size) - { - blockSize = size + SHADOW_NODE_SIZE; + if (iter->isFree && iter->size >= size) { + blockSize = size + arena->nodeSize; // Split: Carve the allocation from the TOP of this free block. - if (blockSize < iter->size) - { + if (blockSize < iter->size) { const u32 newOff = iterOff + (iter->size - size); ShadowNode* newNode = NodeAt(arena, newOff); @@ -213,24 +189,20 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) newNode->size = size; newNode->next = iter->next; newNode->prev = iterOff; - memset(newNode->_dead, 0, sizeof(newNode->_dead)); - if (newNode->next != SHADOW_NULL) - { + if (newNode->next != SHADOW_NULL) { NodeAt(arena, newNode->next)->prev = newOff; } iter->next = newOff; iter->size -= blockSize; - return newOff + SHADOW_NODE_SIZE; + return newOff + arena->nodeSize; } // No split, use the whole block. iter->isFree = 0; - memset(iter->_dead, 0, sizeof(iter->_dead)); - - return iterOff + SHADOW_NODE_SIZE; + return iterOff + arena->nodeSize; } iterOff = NodeGetPrev(arena, iterOff); @@ -243,63 +215,52 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) // Free: Adjacent block coalescing (matches N64 _osFree) // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) -{ +void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { u32 nodeOff = 0; ShadowNode* node = NULL; u32 nextOff = 0; u32 prevOff = 0; - if (dataOffset == SHADOW_NULL) - { + if (dataOffset == SHADOW_NULL) { return; } - nodeOff = dataOffset - SHADOW_NODE_SIZE; + nodeOff = dataOffset - arena->nodeSize; node = NodeAt(arena, nodeOff); - if (node->magic != NODE_MAGIC) - { + if (node->magic != NODE_MAGIC) { return; } - if (node->isFree) - { + if (node->isFree) { return; } node->isFree = 1; - memset(node->_dead, 0, sizeof(node->_dead)); // Forward coalesce: Merge with next if it's free. nextOff = node->next; - if (nextOff != SHADOW_NULL) - { + if (nextOff != SHADOW_NULL) { const ShadowNode* next = NodeAt(arena, nextOff); - if (next->isFree) - { - if (next->next != SHADOW_NULL) - { + if (next->isFree) { + if (next->next != SHADOW_NULL) { NodeAt(arena, next->next)->prev = nodeOff; } - node->size += next->size + SHADOW_NODE_SIZE; + node->size += next->size + arena->nodeSize; node->next = next->next; } } // Backward coalesce: Merge into prev if it's free. prevOff = node->prev; - if (prevOff != SHADOW_NULL) - { + if (prevOff != SHADOW_NULL) { ShadowNode* prev = NodeAt(arena, prevOff); - if (prev->isFree) - { - prev->size += node->size + SHADOW_NODE_SIZE; + if (prev->isFree) { + prev->size += node->size + arena->nodeSize; prev->next = node->next; - if (node->next != SHADOW_NULL) - { + if (node->next != SHADOW_NULL) { NodeAt(arena, node->next)->prev = prevOff; } } @@ -310,8 +271,7 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) // Query // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) -{ +void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) { u32 iterOff = 0; *outMaxFree = 0; @@ -319,20 +279,14 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 *outAlloc = 0; iterOff = arena->head; - while (iterOff != SHADOW_NULL) - { + while (iterOff != SHADOW_NULL) { const ShadowNode* node = NodeAt(arena, iterOff); - if (node->isFree) - { + if (node->isFree) { *outFree += node->size; - - if (node->size > *outMaxFree) - { + if (node->size > *outMaxFree) { *outMaxFree = node->size; } - } - else - { + } else { *outAlloc += node->size; } @@ -340,21 +294,17 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 } } -u32 ShadowArena_GetHead(ShadowArena* arena) -{ +u32 ShadowArena_GetHead(ShadowArena* arena) { return arena->head; } -s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) -{ - if (offset == SHADOW_NULL || offset + SHADOW_NODE_SIZE > arena->bufferSize) - { +s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) { + if (offset == SHADOW_NULL || offset + arena->nodeSize > arena->bufferSize) { return 0; } - ShadowNode* node = NodeAt(arena, offset); - if (node->magic != NODE_MAGIC) - { + const ShadowNode* node = NodeAt(arena, offset); + if (node->magic != NODE_MAGIC) { return 0; } @@ -364,7 +314,6 @@ s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* return 1; } -u32 ShadowArena_GetBufferSize(ShadowArena* arena) -{ +u32 ShadowArena_GetBufferSize(ShadowArena* arena) { return arena->bufferSize; -} +} \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h index f474e6d3085..297b2f549fc 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h @@ -4,23 +4,30 @@ #ifdef __cplusplus extern "C" { + + + #endif // Sentinel value for null offsets (no valid node can live at 0xFFFFFFFF in a buffer that's only ~245KB). #define SHADOW_NULL 0xFFFFFFFF -// Matches N64 retail ArenaNode: 0x10 bookkeeping + 0x20 debug fields. -#define SHADOW_NODE_SIZE 0x30 +// Default node size for the shadow arena. Overridden at init from OTR data. +// Retail N64 (no debug fields): 0x10 +// GC Debug (debug fields): 0x30 +#define SHADOW_NODE_SIZE_RETAIL 0x10 +#define SHADOW_NODE_SIZE_DEBUG 0x30 -typedef struct ShadowArena -{ +typedef struct ShadowArena { u8* buffer; u32 head; // Offset to first node u32 bufferSize; + u32 nodeSize; // Per-version ArenaNode size (set at init from OTR data) } ShadowArena; -// Allocate backing buffer and initialize with a single fee node. Size should be the N64 ZeldaArena size (0x3D550). -void ShadowArena_Init(ShadowArena* arena, u32 size); +// Allocate backing buffer and initialize with a single free node. nodeSize is the N64 ArenaNode size for this ROM +// version (0x10 for retail, 0x30 for debug). +void ShadowArena_Init(ShadowArena* arena, u32 size, u32 nodeSize); // Free the backing buffer and zero the struct. void ShadowArena_Destroy(ShadowArena* arena); @@ -35,7 +42,7 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size); void ShadowArena_Free(ShadowArena* arena, u32 dataOffset); // Query arena statistics. -void ShadowArena_GetSizes(ShadowArena * arena, u32 * outMaxFree, u32 * outFree, u32 * outAlloc); +void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc); // Get the head node offset for external traversal (e.g., heap viewer). u32 ShadowArena_GetHead(ShadowArena* arena); @@ -49,4 +56,4 @@ u32 ShadowArena_GetBufferSize(ShadowArena* arena); #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp index c1380cb9416..26d60a18f11 100644 --- a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp +++ b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp @@ -174,7 +174,7 @@ static void CollectShadowBlocks() { } BlockInfo block; - block.offset = offset + SHADOW_NODE_SIZE; + block.offset = offset + shadow->nodeSize; block.size = size; block.isFree = isFree != 0; block.type = N64MEM_BLOCK_FREE; @@ -319,9 +319,9 @@ static ImU32 ColorBorder() { // ----------------------------------------------------------------------------------------------------------------- void HeapViewerWindow::DrawElement() { - // Single arena view: Sshadow when active, ZeldaArena when not. + // Single arena view: Shadow when active, ZeldaArena when not. const bool isShadowArena = N64Mem_IsActive(); - const u32 nodeHeaderSize = isShadowArena ? SHADOW_NODE_SIZE : sizeof(ArenaNode); + const u32 nodeHeaderSize = isShadowArena ? N64Mem_GetShadowArena()->nodeSize : sizeof(ArenaNode); if (isShadowArena) { CollectShadowBlocks(); From 8d2fcca2394ffede9caffc32194a8a5750dc2b56 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sun, 10 May 2026 08:52:53 -0700 Subject: [PATCH 42/58] fix(restoration): Version-specific GI object segment size --- OTRExporter | 2 +- ZAPDTR | 2 +- .../N64MemoryModel/N64MemoryModel.hpp | 9 +-- .../N64MemoryModel/N64SizeData.cpp | 73 ++++++++++++++++--- .../N64MemoryModel/N64SizeData.hpp | 14 ++-- .../actors/ovl_player_actor/z_player.c | 3 +- 6 files changed, 78 insertions(+), 25 deletions(-) diff --git a/OTRExporter b/OTRExporter index 969c8666421..e7a74f3bd63 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit 969c86664210de433ecfbbbef195a01513942490 +Subproject commit e7a74f3bd63c3335f7e335db511ad3a439ef1e5a diff --git a/ZAPDTR b/ZAPDTR index 054d54c02da..c0b34a6e1a0 160000 --- a/ZAPDTR +++ b/ZAPDTR @@ -1 +1 @@ -Subproject commit 054d54c02da801f124e513070d304f6550d6cd01 +Subproject commit c0b34a6e1a0e47188a199d4e5f95d008b7f32086 diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index 705319e219a..3be8be2ac0b 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -6,12 +6,11 @@ extern "C" { #endif -// N64 subsidiary struct sizes (32-bit, from decomp build artifacts). +// N64 subsidiary struct sizes (32-bit, from decomp headers and linker map). #define N64_SIZEOF_COLLIDER_JNT_SPH_ELEM 0x40 -#define N64_SIZEOF_COLLIDER_TRIS_ELEM 0x5C -#define N64_SIZEOF_CAMERA 0x16C -#define N64_SIZEOF_SKIN_LIMB_VTX 0x0C -#define N64_SIZEOF_GI_OBJECT_SEGMENT 0x1008 +#define N64_SIZEOF_COLLIDER_TRIS_ELEM 0x5C +#define N64_SIZEOF_CAMERA 0x16C +#define N64_SIZEOF_SKIN_LIMB_VTX 0x0C // -------------------------------------------------------------------------------------------------------------------- // Lifecycle diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp index 54a7d7123b3..1bb20ec0f1b 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp @@ -17,9 +17,9 @@ static void LoadDmaFileSizes() { sIsDmaLoaded = true; const auto file = - Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/dma_sizes"); + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/n64_memory/dma_sizes"); if (!file || !file->IsLoaded) { - SPDLOG_ERROR("[N64SizeData] Failed to load misc/dma_sizes from OTR."); + SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/dma_sizes from OTR."); return; } @@ -54,9 +54,10 @@ static void LoadActorOverlaySizes() { sIsActorOverlayLoaded = true; const auto file = - Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/actor_overlay_sizes"); + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( + "misc/n64_memory/actor_overlay_sizes"); if (!file || !file->IsLoaded) { - SPDLOG_ERROR("[N64SizeData] Failed to load misc/actor_overlay_sizes from OTR."); + SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/actor_overlay_sizes from OTR."); return; } @@ -91,9 +92,10 @@ static void LoadEffectOverlaySizes() { sIsEffectOverlayLoaded = true; const auto file = - Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/effect_overlay_sizes"); + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( + "misc/n64_memory/effect_overlay_sizes"); if (!file || !file->IsLoaded) { - SPDLOG_ERROR("[N64SizeData] Failed to load misc/effect_overlay_sizes from OTR."); + SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/effect_overlay_sizes from OTR."); return; } @@ -129,9 +131,10 @@ static void LoadActorInstanceSizes() { sIsActorInstanceLoaded = true; const auto file = - Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/actor_instance_sizes"); + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( + "misc/n64_memory/actor_instance_sizes"); if (!file || !file->IsLoaded) { - SPDLOG_ERROR("[N64SizeData] Failed to load misc/actor_instance_sizes from OTR."); + SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/actor_instance_sizes from OTR."); return; } @@ -211,9 +214,10 @@ static void LoadKaleidoVramSize() { sIsKaleidoLoaded = true; const auto file = - Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/kaleido_vram_size"); + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( + "misc/n64_memory/kaleido_vram_size"); if (!file || !file->IsLoaded) { - SPDLOG_ERROR("[N64SizeData] Failed to load misc/kaleido_vram_size from OTR."); + SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/kaleido_vram_size from OTR."); return; } @@ -248,9 +252,10 @@ static void LoadArenaNodeSize() { sIsArenaNodeSizeLoaded = true; const auto file = - Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/arena_node_size"); + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( + "misc/n64_memory/arena_node_size"); if (!file || !file->IsLoaded) { - SPDLOG_WARN("[N64SizeData] Failed to load misc/arena_node_size from OTR, defaulting to 0x{:X}.", + SPDLOG_WARN("[N64SizeData] Failed to load misc/n64_memory/arena_node_size from OTR, defaulting to 0x{:X}.", sArenaNodeSize); return; } @@ -267,4 +272,48 @@ static void LoadArenaNodeSize() { extern "C" u32 N64SizeData_GetArenaNodeSize() { LoadArenaNodeSize(); return sArenaNodeSize; +} + +// -------------------------------------------------------------------------------------------------------------------- +// N64 LANGUAGE_MAX +// +// Format: single u32 -- LANGUAGE_MAX for this ROM version (2 for NTSC, 3 for PAL) +// Used to compute the GI object segment size: 0x1000 * LANGUAGE_MAX + 8. +// -------------------------------------------------------------------------------------------------------------------- + +static u32 sLanguageMax = 2; // Default to NTSC +static bool sIsLanguageMaxLoaded = false; + +static void LoadLanguageMax() { + if (sIsLanguageMaxLoaded) { + return; + } + + sIsLanguageMaxLoaded = true; + + const auto file = + Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( + "misc/n64_memory/language_max"); + if (!file || !file->IsLoaded) { + SPDLOG_WARN("[N64SizeData] Failed to load misc/n64_memory/n64_language_max from OTR, defaulting to {}.", + sLanguageMax); + return; + } + + auto stream = std::make_shared(file->Buffer->data(), file->Buffer->size()); + const auto reader = std::make_shared(stream); + reader->SetEndianness(Ship::Endianness::Big); + + sLanguageMax = reader->ReadUInt32(); + + SPDLOG_INFO("[N64SizeData] N64 LANGUAGE_MAX: {}.", sLanguageMax); +} + +extern "C" u32 N64SizeData_GetLanguageMax() { + LoadLanguageMax(); + return sLanguageMax; +} + +extern "C" u32 N64SizeData_GetGiObjectSegmentSize() { + return 0x1000 * N64SizeData_GetLanguageMax() + 8; } \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp index 67be4212003..30b9a774145 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp @@ -10,11 +10,13 @@ extern "C" { // N64 size data loaded from the OTR archive. // // All data is extracted per-version by OTRExporter during ROM extraction: -// misc/dma_sizes DMA file VROM sizes keyed by filename -// misc/actor_overlay_sizes Actor overlay VRAM sizes indexed by actor ID -// misc/effect_overlay_sizes Effect overlay VRAM sizes indexed by effect type -// misc/actor_instance_sizes Actor instance struct sizes indexed by actor ID -// misc/kaleido_vram_size max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) +// misc/n64_memory/dma_sizes DMA file VROM sizes keyed by filename +// misc/n64_memory/actor_overlay_sizes Actor overlay VRAM sizes indexed by actor ID +// misc/n64_memory/effect_overlay_sizes Effect overlay VRAM sizes indexed by effect type +// misc/n64_memory/actor_instance_sizes Actor instance struct sizes indexed by actor ID +// misc/n64_memory/kaleido_vram_size max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) +// misc/n64_memory/arena_node_size N64 ArenaNode size (0x10 retail, 0x30 debug) +// misc/n64_memory/language_max LANGUAGE_MAX enum count (2 NTSC, 3 PAL) // // Each table is loaded lazily on first query and cached for the lifetime of the process. // -------------------------------------------------------------------------------------------------------------------- @@ -25,6 +27,8 @@ u32 N64SizeData_GetEffectOverlaySize(u16 effectType); u32 N64SizeData_GetActorInstanceSize(u16 actorId); u32 N64SizeData_GetKaleidoVramSize(void); u32 N64SizeData_GetArenaNodeSize(void); +u32 N64SizeData_GetLanguageMax(void); +u32 N64SizeData_GetGiObjectSegmentSize(void); #ifdef __cplusplus } diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index f5ea97c28fe..01bea9c392e 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -30,6 +30,7 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/Enhancements/randomizer/randomizer_grotto.h" #include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp" #include "soh/frame_interpolation.h" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" @@ -10849,7 +10850,7 @@ void Player_Init(Actor* thisx, PlayState* play2) { // #region SOH [Enhancement] - N64 Memory Model void* giRaw = ZELDA_ARENA_MALLOC_DEBUG(0x3008); - N64Mem_AllocSubsidiary(giRaw, N64_SIZEOF_GI_OBJECT_SEGMENT); + N64Mem_AllocSubsidiary(giRaw, N64SizeData_GetGiObjectSegmentSize()); this->giObjectSegment = (void*)((uintptr_t)giRaw + 8 & ~0xF); // #endregion From f4399be6e27812b08f19b611891b1e974f0eee5a Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sun, 10 May 2026 10:20:38 -0700 Subject: [PATCH 43/58] chore(repo): Update OTRExporter --- OTRExporter | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OTRExporter b/OTRExporter index e7a74f3bd63..3b77fa21f38 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit e7a74f3bd63c3335f7e335db511ad3a439ef1e5a +Subproject commit 3b77fa21f3875c0dd001eb2236acba4af2d33411 From 3b4b8f15b7656876c6a5cead40ab4bfc7721d18f Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sun, 10 May 2026 20:25:00 -0700 Subject: [PATCH 44/58] feat(restoration): Route overlay loads through correct allocator direction --- OTRExporter | 2 +- ZAPDTR | 2 +- .../N64MemoryModel/N64MemoryModel.cpp | 93 ++++++++++++++++++- .../N64MemoryModel/N64MemoryModel.hpp | 2 +- soh/src/code/z_actor.c | 2 +- 5 files changed, 92 insertions(+), 9 deletions(-) diff --git a/OTRExporter b/OTRExporter index 3b77fa21f38..faa554e4039 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit 3b77fa21f3875c0dd001eb2236acba4af2d33411 +Subproject commit faa554e40392853921bf7c04371350387d9bbf66 diff --git a/ZAPDTR b/ZAPDTR index c0b34a6e1a0..7fa0fc16cf8 160000 --- a/ZAPDTR +++ b/ZAPDTR @@ -1 +1 @@ -Subproject commit c0b34a6e1a0e47188a199d4e5f95d008b7f32086 +Subproject commit 7fa0fc16cf87a92e15e656502df3647e81887bf2 diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp index 3b072cac63c..8cc682ad016 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp @@ -1,5 +1,8 @@ #include +#include +#include + #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/ShipInit.hpp" #include "soh/ActorDB.h" @@ -201,6 +204,78 @@ void N64Mem_LogState(const char* context) { } } +void N64Mem_DumpArena(const char* tag) { + if (!sIsActive || sShadow.buffer == nullptr) { + return; + } + + std::string out = fmt::format("\n[N64HeapDump] === {} ===\n", tag); + + u32 offset = ShadowArena_GetHead(&sShadow); + u32 freeCount = 0; + u32 allocCount = 0; + u32 freeTotal = 0; + u32 allocTotal = 0; + + while (offset != SHADOW_NULL) { + s32 isFree = 0; + u32 size = 0; + u32 next = 0; + if (!ShadowArena_GetNodeInfo(&sShadow, offset, &isFree, &size, &next)) { + out += fmt::format(" +0x{:06X} \n", offset); + break; + } + + const u32 dataOff = offset + sShadow.nodeSize; + if (isFree) { + out += fmt::format(" +0x{:06X} 0x{:06X} FREE\n", offset, size); + freeTotal += size; + freeCount++; + } else { + u8 type = 0; + s16 actorId = -1; + const char* typeName = "???"; + if (N64Mem_GetBlockInfo(dataOff, &type, &actorId)) { + switch (type) { + case N64MEM_BLOCK_INSTANCE: + typeName = "inst"; + break; + case N64MEM_BLOCK_OVERLAY: + typeName = "ovl "; + break; + case N64MEM_BLOCK_SUBSIDIARY: + typeName = "sub "; + break; + case N64MEM_BLOCK_EFFECT: + typeName = "efx "; + break; + case N64MEM_BLOCK_ABSOLUTE: + typeName = "abs "; + break; + default: + break; + } + } + + if (actorId >= 0) { + out += fmt::format(" +0x{:06X} 0x{:06X} {} actor=0x{:04X}\n", offset, size, typeName, + static_cast(actorId)); + } else { + out += fmt::format(" +0x{:06X} 0x{:06X} {}\n", offset, size, typeName); + } + allocTotal += size; + allocCount++; + } + + offset = next; + } + + out += fmt::format("[N64HeapDump] {} blocks: {} alloc (0x{:X}B), {} free (0x{:X}B)", freeCount + allocCount, + allocCount, allocTotal, freeCount, freeTotal); + + SPDLOG_INFO("{}", out); +} + void N64Mem_SetOriginalActorId(s16 actorId) { sOriginalActorId = actorId; } @@ -271,9 +346,13 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { return 1; } - // N64 allocates ALL actor overlays via ZeldaArena_MallocR (from the arena top). Using Malloc (bottom-up) placed - // overlays interleaved with instances at the bottom, preventing proper coalescing when overlays are freed. - const u32 shadow = ShadowArena_MallocR(&sShadow, overlaySize); + // Match Actor_LoadOverlay's branching: PERSISTENT/PERMANENT overlays go through MallocR (arena top), default + // (NORMAL) overlays go through forward Malloc (arena bottom). ABSOLUTE is handled above. NORMAL overlays being + // interleaved with instances at the bottom is the authentic N64 fragmentation pattern -- the simulation must + // reproduce it, not paper over it. + const u32 shadow = (allocType & ALLOCTYPE_PERMANENT) + ? ShadowArena_MallocR(&sShadow, overlaySize) + : ShadowArena_Malloc(&sShadow, overlaySize); if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[N64MemoryModel] Shadow overlay failed for actor 0x{:04X} (need 0x{:X})", actorId, overlaySize); return 0; @@ -309,7 +388,7 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { // Actor instances // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { +s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) { if (!sIsActive) { sOriginalActorId = -1; return 1; @@ -345,7 +424,11 @@ s32 N64Mem_AllocInstance(s16 actorId, void* realPtr) { const u32 shadow = ShadowArena_Malloc(&sShadow, instanceSize); if (shadow == SHADOW_NULL) { - SPDLOG_ERROR("[N64MemoryModel] Shadow instance failed for actor 0x{:04X} (need 0x{:X})", actorId, instanceSize); + SPDLOG_ERROR("[N64MemoryModel] Shadow instance failed for actor 0x{:04X} params=0x{:04X} (need 0x{:X})", + actorId, static_cast(params), instanceSize); + N64Mem_DumpArena(fmt::format("instance failure: actor=0x{:04X} params=0x{:04X}", actorId, + static_cast(params)) + .c_str()); return 0; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp index 3be8be2ac0b..8909826fa96 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp +++ b/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp @@ -86,7 +86,7 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType); // Call FreeInstance when the actor is deleted. // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocInstance(s16 actorId, void* realPtr); +s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr); // Returns the stored original actor ID for overlay free-path tracking, or -1 if not tracked. s16 N64Mem_FreeInstance(void* realPtr); diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 59634a516c3..ad1b38d01d2 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -3393,7 +3393,7 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos } // #region SOH [Enhancement] - N64 Memory Model - if (!N64Mem_AllocInstance(actorId, actor)) { + if (!N64Mem_AllocInstance(actorId, params, actor)) { ZELDA_ARENA_FREE_DEBUG(actor); Actor_FreeOverlay(dbEntry); return NULL; From e5d10abf569bf39fae19cb7130e63d1aa4ef2a44 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Sun, 10 May 2026 21:38:42 -0700 Subject: [PATCH 45/58] chore(repo): Update submodules to fix shadowed arena node resolution --- OTRExporter | 2 +- ZAPDTR | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OTRExporter b/OTRExporter index faa554e4039..bbdb60a0bd1 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit faa554e40392853921bf7c04371350387d9bbf66 +Subproject commit bbdb60a0bd112e51c1f42055c7ec5c3c791ce656 diff --git a/ZAPDTR b/ZAPDTR index 7fa0fc16cf8..36540c67ed4 160000 --- a/ZAPDTR +++ b/ZAPDTR @@ -1 +1 @@ -Subproject commit 7fa0fc16cf87a92e15e656502df3647e81887bf2 +Subproject commit 36540c67ed4bdeb0ad47970eea5487b54363deba From fdc99c098707aba65588457a909dfd1add57d72f Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Mon, 11 May 2026 00:07:19 -0700 Subject: [PATCH 46/58] Revert "chore(repo): Update submodules to fix shadowed arena node resolution" This reverts commit e5d10abf569bf39fae19cb7130e63d1aa4ef2a44. --- OTRExporter | 2 +- ZAPDTR | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OTRExporter b/OTRExporter index bbdb60a0bd1..faa554e4039 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit bbdb60a0bd112e51c1f42055c7ec5c3c791ce656 +Subproject commit faa554e40392853921bf7c04371350387d9bbf66 diff --git a/ZAPDTR b/ZAPDTR index 36540c67ed4..7fa0fc16cf8 160000 --- a/ZAPDTR +++ b/ZAPDTR @@ -1 +1 @@ -Subproject commit 36540c67ed4bdeb0ad47970eea5487b54363deba +Subproject commit 7fa0fc16cf87a92e15e656502df3647e81887bf2 From 3f7008e0f11edf159054066c579d238fd72d79fa Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Mon, 11 May 2026 03:00:28 -0700 Subject: [PATCH 47/58] feat(restoration): Replicate N64 culling --- OTRExporter | 2 +- ZAPDTR | 2 +- soh/src/code/z_actor.c | 44 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/OTRExporter b/OTRExporter index faa554e4039..3090d23d99f 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit faa554e40392853921bf7c04371350387d9bbf66 +Subproject commit 3090d23d99f88adcfa16d76133fee7a4e26511d1 diff --git a/ZAPDTR b/ZAPDTR index 7fa0fc16cf8..eb43232b317 160000 --- a/ZAPDTR +++ b/ZAPDTR @@ -1 +1 @@ -Subproject commit 7fa0fc16cf87a92e15e656502df3647e81887bf2 +Subproject commit eb43232b3174a2b02dc6b4ac4b9fd0c6b6a1494b diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index ad1b38d01d2..f1e6966494c 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2660,7 +2660,20 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { actor = actor->next; } else if (actor->update == NULL) { // #region SOH [Enhancement] - N64 Memory Model - if (N64Mem_IsActive() || !actor->isDrawn) { + // Same-draw distance distinction as the room-change kill path: Use N64's culling check to determine + // whether this actor would have been drawn, matching the immediate vs. deferred free oredering that + // shapes N64's heap geometry. + if (N64Mem_IsActive()) { + const s32 n64WouldDraw = actor->init == NULL && actor->draw != NULL && ( + actor->flags & ACTOR_FLAG_DRAW_CULLING_DISABLED || func_800314D4( + play, actor, &actor->projectedPos, actor->projectedW)); + if (!n64WouldDraw) { + actor = Actor_Delete(&play->actorCtx, actor, play); + } else { + Actor_Destroy(actor, play); + actor = actor->next; + } + } else if (!actor->isDrawn) { // #endregion actor = Actor_Delete(&play->actorCtx, actor, play); } else { @@ -3202,14 +3215,29 @@ void func_80031B14(PlayState* play, ActorContext* actorCtx) { (actor->room != play->roomCtx.prevRoom.num)) { // #region [SOH] Enhancement - N64 Memory Model // - // On N64, most departing-room actors are off-screen (isDrawn = false) during the transition frame, so - // they take the immediate Actor_Delete path. SoH's extended draw distance (Ship_CalcShouldDrawAndUpdate) - // keeps more actors "drawn," pushing them into the deferred Actor_Kill path instead. This changes the - // free ordering seen by the heap, producing different fragmentation geometry. + // On N64, departing-room actors take one of two free paths depending on whether they were drawn on the + // previous frame: + // - NOT drawn (off-screen on N64) -> Immediate Actor_Delete -- memory freed this frame + // - Drawn (on-screen on N64) -> Deferred Actor_Kill -- memory freed next frame // - // When the N64 Memory Model is active, force the immediate path to match N64's free ordering. - if (N64Mem_IsActive() || !actor->isDrawn) { - // #endregion + // SoH's extended draw distance (Ship_CalcShouldDrawAndUpdate) keeps more actors drawn than N64 would, + // pushing them into the deferred path and changing the free ordering/coalescing geometry. + // + // When the N64 Memory Model is active, use N64's original culling check (func_800314B0) to decide the + // path instead of SoH's isDrawn flag. + if (N64Mem_IsActive()) { + const s32 n64WouldDraw = actor->init == NULL && actor->draw != NULL && ( + actor->flags & ACTOR_FLAG_DRAW_CULLING_DISABLED || func_800314D4( + play, actor, &actor->projectedPos, actor->projectedW)); + if (!n64WouldDraw) { + actor = Actor_Delete(actorCtx, actor, play); + } else { + Actor_Kill(actor); + Actor_Destroy(actor, play); + actor = actor->next; + } + } else if (!actor->isDrawn) { + // #endregion actor = Actor_Delete(actorCtx, actor, play); } else { Actor_Kill(actor); From 9e34fa391aca42cc63c1b993dcdc3d981a2bb914 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Mon, 11 May 2026 08:47:40 -0700 Subject: [PATCH 48/58] chore(refactor): Comments for explaining memory interceptions --- OTRExporter | 2 +- ZAPDTR | 2 +- soh/soh/ActorDB.cpp | 4 +- soh/soh/ActorDB.h | 2 +- .../ExtraModes/EnemyRandomizer.cpp | 10 +- .../HardwareMemoryLimits.cpp} | 324 +++++++++++------- .../HardwareMemoryLimits.hpp} | 0 .../N64SizeData.cpp | 114 +++--- .../N64SizeData.hpp | 2 - .../n64_arena_sizing.c | 157 +++++---- .../n64_arena_sizing.h | 3 +- .../n64_shadow_arena.c | 133 ++++--- .../n64_shadow_arena.h | 8 +- .../debugger/HeapViewerWindow.cpp | 4 +- soh/soh/SaveManager.cpp | 67 ++-- soh/soh/SohGui/SohMenuEnhancements.cpp | 17 +- soh/soh/z_scene_otr.cpp | 4 +- soh/src/code/z_actor.c | 44 ++- soh/src/code/z_camera.c | 7 +- soh/src/code/z_collision_check.c | 17 +- soh/src/code/z_effect_soft_sprite.c | 6 +- soh/src/code/z_play.c | 18 +- soh/src/code/z_skelanime.c | 22 +- soh/src/code/z_skin_awb.c | 14 +- .../actors/ovl_player_actor/z_player.c | 10 +- 25 files changed, 604 insertions(+), 387 deletions(-) rename soh/soh/Enhancements/Restorations/{N64MemoryModel/N64MemoryModel.cpp => HardwareMemoryLimits/HardwareMemoryLimits.cpp} (72%) rename soh/soh/Enhancements/Restorations/{N64MemoryModel/N64MemoryModel.hpp => HardwareMemoryLimits/HardwareMemoryLimits.hpp} (100%) rename soh/soh/Enhancements/Restorations/{N64MemoryModel => HardwareMemoryLimits}/N64SizeData.cpp (85%) rename soh/soh/Enhancements/Restorations/{N64MemoryModel => HardwareMemoryLimits}/N64SizeData.hpp (92%) rename soh/soh/Enhancements/Restorations/{N64MemoryModel => HardwareMemoryLimits}/n64_arena_sizing.c (83%) rename soh/soh/Enhancements/Restorations/{N64MemoryModel => HardwareMemoryLimits}/n64_arena_sizing.h (94%) rename soh/soh/Enhancements/Restorations/{N64MemoryModel => HardwareMemoryLimits}/n64_shadow_arena.c (81%) rename soh/soh/Enhancements/Restorations/{N64MemoryModel => HardwareMemoryLimits}/n64_shadow_arena.h (93%) diff --git a/OTRExporter b/OTRExporter index 3090d23d99f..1d1a069be7e 160000 --- a/OTRExporter +++ b/OTRExporter @@ -1 +1 @@ -Subproject commit 3090d23d99f88adcfa16d76133fee7a4e26511d1 +Subproject commit 1d1a069be7e7f2a4c7bea1ad6c28b5586b3e3a6b diff --git a/ZAPDTR b/ZAPDTR index eb43232b317..3310bd2ecd0 160000 --- a/ZAPDTR +++ b/ZAPDTR @@ -1 +1 @@ -Subproject commit eb43232b3174a2b02dc6b4ac4b9fd0c6b6a1494b +Subproject commit 3310bd2ecd090666d94f3fae4a9fc68e6fa7efbe diff --git a/soh/soh/ActorDB.cpp b/soh/soh/ActorDB.cpp index e8a15d0d503..a281bfa5f68 100644 --- a/soh/soh/ActorDB.cpp +++ b/soh/soh/ActorDB.cpp @@ -17,7 +17,7 @@ ActorDB* ActorDB::Instance; struct AddPair { const char* name; ActorInit& init; - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits AllocType allocType; // #endregion }; @@ -475,7 +475,7 @@ ActorDB::ActorDB() { db.reserve(ACTOR_NUMBER_MAX); // reserve size for all initial entries so we don't do it for each for (const AddPair& pair : initialActorTable) { Entry& entry = AddEntry(pair.name, actorDescriptions[pair.init.id], pair.init); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits entry.entry.allocType = pair.allocType; // #endregion } diff --git a/soh/soh/ActorDB.h b/soh/soh/ActorDB.h index 3fe5c4021dc..b65d23fe95c 100644 --- a/soh/soh/ActorDB.h +++ b/soh/soh/ActorDB.h @@ -16,7 +16,7 @@ typedef struct { ActorFunc draw; ActorResetFunc reset; s32 numLoaded; - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits AllocType allocType; // #endregion } ActorDBEntry; diff --git a/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp b/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp index 7fb51e07b53..c7e89d7eb06 100644 --- a/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp +++ b/soh/soh/Enhancements/ExtraModes/EnemyRandomizer.cpp @@ -9,7 +9,7 @@ #include "soh/ResourceManagerHelpers.h" #include "soh/SohGui/MenuTypes.h" #include "soh/SohGui/SohMenu.h" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" extern "C" { #include @@ -459,16 +459,16 @@ uint8_t GetRandomizedEnemy(PlayState* play, int16_t* actorId, s16* posX, s16* po play->sceneNum + *actorId + (int)*posX + (int)*posY + (int)*posZ + *rotX + *rotY + *rotZ + *params; EnemyEntry randomEnemy = GetRandomizedEnemyEntry(seed, play); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Save the original actor ID before randomization swap it. The shadow heap needs the original ID to allocate + // the correct overlay and instance sizes. const s16 originalActorId = *actorId; // #endregion *actorId = randomEnemy.id; *params = randomEnemy.params; - // #region SOH [Enhancement] - N64 Memory Model - // Tell the N64 memory model to charge the original actor's N64 sizes for this spawn, preserving authentic - // heap geometry regardless of which enemy the randomizer substitutes. + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_SetOriginalActorId(originalActorId); // #endregion diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp similarity index 72% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp rename to soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp index 8cc682ad016..73dc8a3d0ce 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.cpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp @@ -8,14 +8,14 @@ #include "soh/ActorDB.h" extern "C" { -#include "N64MemoryModel.hpp" +#include "HardwareMemoryLimits.hpp" #include "N64SizeData.hpp" #include "n64_shadow_arena.h" #include "n64_arena_sizing.h" #include "global.h" } -#define CVAR_NAME CVAR_ENHANCEMENT("N64MemoryModel") +#define CVAR_NAME CVAR_ENHANCEMENT("HardwareMemoryLimits") #define CVAR_DEFAULT 0 #define CVAR_VALUE CVarGetInteger(CVAR_NAME, CVAR_DEFAULT) @@ -46,7 +46,8 @@ static std::unordered_map sActorOriginalIds; // Block metadata for the heap viewer. Keyed by shadow DATA offset (node offset + arena->nodeSize). // Populated in each Alloc function, erased in each Free, cleared in N64Mem_Reset. -struct BlockMeta { +struct BlockMeta +{ u8 type; s16 actorId; }; @@ -65,7 +66,8 @@ static s32 sInsideRandomizedInit = 0; // Returns the actor ID to use for size lookups. If an original actor ID override is set (enemy randomizer active), // returns that; otherwise returns the passed actorId unchanged. -static u16 ResolveSizeActorId(s16 actorId) { +static u16 ResolveSizeActorId(s16 actorId) +{ return sOriginalActorId >= 0 ? static_cast(sOriginalActorId) : static_cast(actorId); } @@ -75,25 +77,31 @@ static u16 ResolveSizeActorId(s16 actorId) { static s32 sTraceEnabled = 0; -static void LogShadowState(const char* context) { +static void LogShadowState(const char* context) +{ u32 maxFree = 0; u32 totalFree = 0; u32 totalAlloc = 0; ShadowArena_GetSizes(&sShadow, &maxFree, &totalFree, &totalAlloc); - SPDLOG_INFO("[N64MemoryModel] ({}): alloc=0x{:X}, free=0x{:X}, largest=0x{:X}", context, totalAlloc, totalFree, + SPDLOG_INFO("[HardwareMemoryLimits] ({}): alloc=0x{:X}, free=0x{:X}, largest=0x{:X}", context, totalAlloc, + totalFree, maxFree); } -static void TraceAlloc(const char* tag, u32 id, u32 size) { - if (sTraceEnabled) { +static void TraceAlloc(const char* tag, u32 id, u32 size) +{ + if (sTraceEnabled) + { u32 consumed = (size + 0xF & ~0xF) + sShadow.nodeSize; SPDLOG_TRACE("[N64Trace] +{} id=0x{:X} sz=0x{:X} cost=0x{:X}", tag, id, size, consumed); } } -static void TraceFree(const char* tag, u32 id) { - if (sTraceEnabled) { +static void TraceFree(const char* tag, u32 id) +{ + if (sTraceEnabled) + { SPDLOG_TRACE("[N64Trace] -{} id=0x{:X}", tag, id); } } @@ -104,8 +112,10 @@ static void TraceFree(const char* tag, u32 id) { static s32 sGraveyardTransitionCount = 0; -void N64Mem_BenchmarkTransition(PlayState* play) { - if (!sIsActive || play->sceneNum != SCENE_GRAVEYARD) { +void N64Mem_BenchmarkTransition(PlayState* play) +{ + if (!sIsActive || play->sceneNum != SCENE_GRAVEYARD) + { return; } @@ -124,30 +134,36 @@ void N64Mem_BenchmarkTransition(PlayState* play) { // Lifecycle // -------------------------------------------------------------------------------------------------------------------- -void N64Mem_StoreThaRemainder(u32 sohRemainder) { +void N64Mem_StoreThaRemainder(u32 sohRemainder) +{ sSohThaRemainder = sohRemainder; } -void N64Mem_StoreElfMsgNum(u8 num) { +void N64Mem_StoreElfMsgNum(u8 num) +{ sElfMsgNum = num; } -u8 N64Mem_GetElfMsgNum() { +u8 N64Mem_GetElfMsgNum() +{ return sElfMsgNum; } -void N64Mem_Reset(PlayState* play) { +void N64Mem_Reset(PlayState* play) +{ // Log shadow state before teardown for per-scene diagnostics. - if (sIsActive && sShadow.buffer) { + if (sIsActive && sShadow.buffer) + { u32 maxFree = 0; u32 totalFree = 0; u32 totalAlloc = 0; ShadowArena_GetSizes(&sShadow, &maxFree, &totalFree, &totalAlloc); - SPDLOG_INFO("[N64MemoryModel] Teardown: alloc=0x{:X}, free=0x{:X}, largest=0x{:X}, ptrs={}", totalAlloc, + SPDLOG_INFO("[HardwareMemoryLimits] Teardown: alloc=0x{:X}, free=0x{:X}, largest=0x{:X}, ptrs={}", totalAlloc, totalFree, maxFree, sShadowMap.size()); - if (sGraveyardTransitionCount > 0) { + if (sGraveyardTransitionCount > 0) + { SPDLOG_INFO("[N64Benchmark] RESULT transitions={}", sGraveyardTransitionCount); } } @@ -164,48 +180,59 @@ void N64Mem_Reset(PlayState* play) { sOriginalActorId = -1; sInsideRandomizedInit = 0; - for (u32& sOverlayShadow : sOverlayShadows) { + for (u32& sOverlayShadow : sOverlayShadows) + { sOverlayShadow = SHADOW_NULL; } - for (u32& sEffectOverlayShadow : sEffectOverlayShadows) { + for (u32& sEffectOverlayShadow : sEffectOverlayShadows) + { sEffectOverlayShadow = SHADOW_NULL; } sIsActive = CVAR_VALUE; - if (sIsActive && play != nullptr) { + if (sIsActive && play != nullptr) + { // Compute N64-equivalent arena size from first principles. All per-version constants are derived from the OTR // blob, which was extracted from the user's specific ROM version. u32 shadowArenaSize = ArenaSizing_ComputeN64ArenaSize(play); - if (shadowArenaSize == 0) { - SPDLOG_ERROR("[N64MemoryModel] Arena sizing returned 0 -- THA budget exceeded, disabling."); + if (shadowArenaSize == 0) + { + SPDLOG_ERROR("[HardwareMemoryLimits] Arena sizing returned 0 -- THA budget exceeded, disabling."); sIsActive = 0; return; } - SPDLOG_INFO("[N64MemoryModel] Shadow arena size=0x{:X} for scene 0x{:X}", shadowArenaSize, play->sceneNum); + SPDLOG_INFO("[HardwareMemoryLimits] Shadow arena size=0x{:X} for scene 0x{:X}", shadowArenaSize, + play->sceneNum); ShadowArena_Init(&sShadow, shadowArenaSize, N64SizeData_GetArenaNodeSize()); sTraceEnabled = play->sceneNum == SCENE_GRAVEYARD; } } -s32 N64Mem_IsActive() { +s32 N64Mem_IsActive() +{ return sIsActive; } -ShadowArena* N64Mem_GetShadowArena() { +ShadowArena* N64Mem_GetShadowArena() +{ return &sShadow; } -void N64Mem_LogState(const char* context) { - if (sIsActive) { +void N64Mem_LogState(const char* context) +{ + if (sIsActive) + { LogShadowState(context); } } -void N64Mem_DumpArena(const char* tag) { - if (!sIsActive || sShadow.buffer == nullptr) { +void N64Mem_DumpArena(const char* tag) +{ + if (!sIsActive || sShadow.buffer == nullptr) + { return; } @@ -217,50 +244,60 @@ void N64Mem_DumpArena(const char* tag) { u32 freeTotal = 0; u32 allocTotal = 0; - while (offset != SHADOW_NULL) { + while (offset != SHADOW_NULL) + { s32 isFree = 0; u32 size = 0; u32 next = 0; - if (!ShadowArena_GetNodeInfo(&sShadow, offset, &isFree, &size, &next)) { + if (!ShadowArena_GetNodeInfo(&sShadow, offset, &isFree, &size, &next)) + { out += fmt::format(" +0x{:06X} \n", offset); break; } const u32 dataOff = offset + sShadow.nodeSize; - if (isFree) { + if (isFree) + { out += fmt::format(" +0x{:06X} 0x{:06X} FREE\n", offset, size); freeTotal += size; freeCount++; - } else { + } + else + { u8 type = 0; s16 actorId = -1; const char* typeName = "???"; - if (N64Mem_GetBlockInfo(dataOff, &type, &actorId)) { - switch (type) { - case N64MEM_BLOCK_INSTANCE: - typeName = "inst"; - break; - case N64MEM_BLOCK_OVERLAY: - typeName = "ovl "; - break; - case N64MEM_BLOCK_SUBSIDIARY: - typeName = "sub "; - break; - case N64MEM_BLOCK_EFFECT: - typeName = "efx "; - break; - case N64MEM_BLOCK_ABSOLUTE: - typeName = "abs "; - break; - default: - break; + if (N64Mem_GetBlockInfo(dataOff, &type, &actorId)) + { + switch (type) + { + case N64MEM_BLOCK_INSTANCE: + typeName = "inst"; + break; + case N64MEM_BLOCK_OVERLAY: + typeName = "ovl "; + break; + case N64MEM_BLOCK_SUBSIDIARY: + typeName = "sub "; + break; + case N64MEM_BLOCK_EFFECT: + typeName = "efx "; + break; + case N64MEM_BLOCK_ABSOLUTE: + typeName = "abs "; + break; + default: + break; } } - if (actorId >= 0) { + if (actorId >= 0) + { out += fmt::format(" +0x{:06X} 0x{:06X} {} actor=0x{:04X}\n", offset, size, typeName, static_cast(actorId)); - } else { + } + else + { out += fmt::format(" +0x{:06X} 0x{:06X} {}\n", offset, size, typeName); } allocTotal += size; @@ -276,19 +313,23 @@ void N64Mem_DumpArena(const char* tag) { SPDLOG_INFO("{}", out); } -void N64Mem_SetOriginalActorId(s16 actorId) { +void N64Mem_SetOriginalActorId(s16 actorId) +{ sOriginalActorId = actorId; } -void N64Mem_ClearOriginalActorId() { +void N64Mem_ClearOriginalActorId() +{ sOriginalActorId = -1; } -s32 N64Mem_GetRandomizedInit() { +s32 N64Mem_GetRandomizedInit() +{ return sInsideRandomizedInit; } -void N64Mem_SetRandomizedInit(s32 value) { +void N64Mem_SetRandomizedInit(s32 value) +{ sInsideRandomizedInit = value; } @@ -296,42 +337,51 @@ void N64Mem_SetRandomizedInit(s32 value) { // Actor overlays // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { - if (!sIsActive) { +s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) +{ + if (!sIsActive) + { return 1; } // Child actors spawned during a randomized enemy's init don't exist on N64 -- skip shadow tracking. - if (sInsideRandomizedInit) { + if (sInsideRandomizedInit) + { return 1; } - if (actorId < 0 || actorId >= ACTOR_ID_MAX) { + if (actorId < 0 || actorId >= ACTOR_ID_MAX) + { return 1; } const u32 overlaySize = N64SizeData_GetActorOverlaySize(ResolveSizeActorId(actorId)); - if (overlaySize == 0) { + if (overlaySize == 0) + { return 1; } // When the enemy randomizer is active, use the original actor's allocType so the shadow takes the same path // N64 would have taken for the unsubstituted actor (i.e., a regular enemy replaced by an ABSOLUTE mini-boss // should still shadow as ALLOCTYPE_NORMAL). - if (sOriginalActorId >= 0) { + if (sOriginalActorId >= 0) + { const auto& entry = ActorDB::Instance->RetrieveEntry(sOriginalActorId); allocType = entry.entry.allocType; } // ABSOLUTE: Shared fixed-size buffer, allocated once via MallocR. - if (allocType & ALLOCTYPE_ABSOLUTE) { - if (sAbsoluteSpaceShadow == SHADOW_NULL) { + if (allocType & ALLOCTYPE_ABSOLUTE) + { + if (sAbsoluteSpaceShadow == SHADOW_NULL) + { sAbsoluteSpaceShadow = ShadowArena_MallocR(&sShadow, AM_FIELD_SIZE); - if (sAbsoluteSpaceShadow == SHADOW_NULL) { - SPDLOG_ERROR("[N64MemoryModel] Shadow absolute space failed (need 0x{:X})", AM_FIELD_SIZE); + if (sAbsoluteSpaceShadow == SHADOW_NULL) + { + SPDLOG_ERROR("[HardwareMemoryLimits] Shadow absolute space failed (need 0x{:X})", AM_FIELD_SIZE); return 0; } - sBlockMetaMap[sAbsoluteSpaceShadow] = { N64MEM_BLOCK_ABSOLUTE, -1 }; + sBlockMetaMap[sAbsoluteSpaceShadow] = {N64MEM_BLOCK_ABSOLUTE, -1}; } return 1; @@ -342,7 +392,8 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { const u16 overlayTrackId = ResolveSizeActorId(actorId); // Already shadowed for this type. - if (sOverlayShadows[overlayTrackId] != SHADOW_NULL) { + if (sOverlayShadows[overlayTrackId] != SHADOW_NULL) + { return 1; } @@ -353,28 +404,34 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { const u32 shadow = (allocType & ALLOCTYPE_PERMANENT) ? ShadowArena_MallocR(&sShadow, overlaySize) : ShadowArena_Malloc(&sShadow, overlaySize); - if (shadow == SHADOW_NULL) { - SPDLOG_ERROR("[N64MemoryModel] Shadow overlay failed for actor 0x{:04X} (need 0x{:X})", actorId, overlaySize); + if (shadow == SHADOW_NULL) + { + SPDLOG_ERROR("[HardwareMemoryLimits] Shadow overlay failed for actor 0x{:04X} (need 0x{:X})", actorId, + overlaySize); return 0; } sOverlayShadows[overlayTrackId] = shadow; - sBlockMetaMap[shadow] = { N64MEM_BLOCK_OVERLAY, static_cast(overlayTrackId) }; + sBlockMetaMap[shadow] = {N64MEM_BLOCK_OVERLAY, static_cast(overlayTrackId)}; TraceAlloc("ovl", overlayTrackId, overlaySize); return 1; } -void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { - if (!sIsActive) { +void N64Mem_FreeOverlay(s16 actorId, u16 allocType) +{ + if (!sIsActive) + { return; } - if (actorId < 0 || actorId >= ACTOR_ID_MAX) { + if (actorId < 0 || actorId >= ACTOR_ID_MAX) + { return; } // PERMANENT: Overlays that are never freed. - if (allocType & ALLOCTYPE_PERMANENT) { + if (allocType & ALLOCTYPE_PERMANENT) + { return; } @@ -388,14 +445,17 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { // Actor instances // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) { - if (!sIsActive) { +s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) +{ + if (!sIsActive) + { sOriginalActorId = -1; return 1; } // Child actors spawned during a randomized enemy's init don't exist on N64 -- skip shadow tracking. - if (sInsideRandomizedInit) { + if (sInsideRandomizedInit) + { return 1; } @@ -404,27 +464,32 @@ s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) { // Consume the override and enter the randomized-init phase. Subsidiaries allocated during Actor_Init will be // skipped (they belong to the replacement, not the original). Child Actor_Spawn calls during init will also // be skipped via the sInsideRandomizedInit check above. - if (sOriginalActorId >= 0) { + if (sOriginalActorId >= 0) + { sOriginalActorId = -1; sInsideRandomizedInit = 1; } - if (actorId < 0 || actorId >= ACTOR_ID_MAX) { + if (actorId < 0 || actorId >= ACTOR_ID_MAX) + { return 1; } const u32 instanceSize = N64SizeData_GetActorInstanceSize(sizeId); - if (instanceSize == 0) { + if (instanceSize == 0) + { return 1; } - if (instanceSize == 0x1A0) { - SPDLOG_INFO("[N64MemoryModel] 0x1A0 instance: actorId=0x{:X}", actorId); + if (instanceSize == 0x1A0) + { + SPDLOG_INFO("[HardwareMemoryLimits] 0x1A0 instance: actorId=0x{:X}", actorId); } const u32 shadow = ShadowArena_Malloc(&sShadow, instanceSize); - if (shadow == SHADOW_NULL) { - SPDLOG_ERROR("[N64MemoryModel] Shadow instance failed for actor 0x{:04X} params=0x{:04X} (need 0x{:X})", + if (shadow == SHADOW_NULL) + { + SPDLOG_ERROR("[HardwareMemoryLimits] Shadow instance failed for actor 0x{:04X} params=0x{:04X} (need 0x{:X})", actorId, static_cast(params), instanceSize); N64Mem_DumpArena(fmt::format("instance failure: actor=0x{:04X} params=0x{:04X}", actorId, static_cast(params)) @@ -434,29 +499,34 @@ s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) { sShadowMap[realPtr] = shadow; sActorOriginalIds[realPtr] = static_cast(sizeId); - sBlockMetaMap[shadow] = { N64MEM_BLOCK_INSTANCE, static_cast(sizeId) }; + sBlockMetaMap[shadow] = {N64MEM_BLOCK_INSTANCE, static_cast(sizeId)}; TraceAlloc("inst", actorId, instanceSize); return 1; } -s16 N64Mem_FreeInstance(void* realPtr) { - if (!sIsActive || !realPtr) { +s16 N64Mem_FreeInstance(void* realPtr) +{ + if (!sIsActive || !realPtr) + { return -1; } const auto i = sShadowMap.find(realPtr); - if (i == sShadowMap.end()) { + if (i == sShadowMap.end()) + { return -1; } // Retrieve the stored original actor ID before erasing. Used by Actor_Delete to free the correct overlay shadow. s16 originalId = -1; - if (const auto j = sActorOriginalIds.find(realPtr); j != sActorOriginalIds.end()) { + if (const auto j = sActorOriginalIds.find(realPtr); j != sActorOriginalIds.end()) + { originalId = j->second; sActorOriginalIds.erase(j); } - if (sTraceEnabled) { + if (sTraceEnabled) + { const auto* actor = static_cast(realPtr); TraceFree("inst", actor->id); } @@ -471,37 +541,44 @@ s16 N64Mem_FreeInstance(void* realPtr) { // Subsidiaries // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) { - if (!sIsActive) { +s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) +{ + if (!sIsActive) + { return 1; } // When inside a randomized enemy's init, the replacement actor's subsidiaries (colliders, skeleton tables, skin // buffers) have different counts than the original's. Rather than charge the wrong sizes, skip subsidiary // tracking entirely -- instance and overlay sizes from the original are already correct and dominate heap pressure. - if (sInsideRandomizedInit) { + if (sInsideRandomizedInit) + { return 1; } const u32 shadow = ShadowArena_Malloc(&sShadow, n64Size); - if (shadow == SHADOW_NULL) { - SPDLOG_ERROR("[N64MemoryModel] Shadow subsidiary failed (need 0x{:X})", n64Size); + if (shadow == SHADOW_NULL) + { + SPDLOG_ERROR("[HardwareMemoryLimits] Shadow subsidiary failed (need 0x{:X})", n64Size); return 0; } sShadowMap[realPtr] = shadow; - sBlockMetaMap[shadow] = { N64MEM_BLOCK_SUBSIDIARY, -1 }; + sBlockMetaMap[shadow] = {N64MEM_BLOCK_SUBSIDIARY, -1}; TraceAlloc("sub", 0, n64Size); return 1; } -void N64Mem_FreeSubsidiary(void* realPtr) { - if (!sIsActive || !realPtr) { +void N64Mem_FreeSubsidiary(void* realPtr) +{ + if (!sIsActive || !realPtr) + { return; } const auto i = sShadowMap.find(realPtr); - if (i == sShadowMap.end()) { + if (i == sShadowMap.end()) + { return; } @@ -515,33 +592,39 @@ void N64Mem_FreeSubsidiary(void* realPtr) { // Effect overlays // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocEffectOverlay(s32 type) { - if (!sIsActive) { +s32 N64Mem_AllocEffectOverlay(s32 type) +{ + if (!sIsActive) + { return 1; } - if (type < 0 || type >= EFFECT_SS_TYPE_MAX) { + if (type < 0 || type >= EFFECT_SS_TYPE_MAX) + { return 1; } - if (sEffectOverlayShadows[type] != SHADOW_NULL) { + if (sEffectOverlayShadows[type] != SHADOW_NULL) + { return 1; } u32 overlaySize = N64SizeData_GetEffectOverlaySize(type); - if (overlaySize == 0) { + if (overlaySize == 0) + { return 1; } const u32 shadow = ShadowArena_MallocR(&sShadow, overlaySize); - if (shadow == SHADOW_NULL) { - SPDLOG_ERROR("[N64MemoryModel] Shadow effect overlay failed for type 0x{:02X} (need 0x{:X})", type, + if (shadow == SHADOW_NULL) + { + SPDLOG_ERROR("[HardwareMemoryLimits] Shadow effect overlay failed for type 0x{:02X} (need 0x{:X})", type, overlaySize); return 0; } sEffectOverlayShadows[type] = shadow; - sBlockMetaMap[shadow] = { N64MEM_BLOCK_EFFECT, static_cast(type) }; + sBlockMetaMap[shadow] = {N64MEM_BLOCK_EFFECT, static_cast(type)}; TraceAlloc("efx", type, overlaySize); return 1; } @@ -551,17 +634,21 @@ s32 N64Mem_AllocEffectOverlay(s32 type) { // Heap viewer metadata // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_GetBlockInfo(u32 dataOffset, u8* outType, s16* outActorId) { +s32 N64Mem_GetBlockInfo(u32 dataOffset, u8* outType, s16* outActorId) +{ const auto i = sBlockMetaMap.find(dataOffset); - if (i == sBlockMetaMap.end()) { + if (i == sBlockMetaMap.end()) + { return 0; } - if (outType) { + if (outType) + { *outType = i->second.type; } - if (outActorId) { + if (outActorId) + { *outActorId = i->second.actorId; } @@ -572,8 +659,9 @@ s32 N64Mem_GetBlockInfo(u32 dataOffset, u8* outType, s16* outActorId) { // Registration // -------------------------------------------------------------------------------------------------------------------- -void RegisterN64MemoryModel() { +void RegisterN64MemoryModel() +{ // #TODO: Shadow arena initialization } -static RegisterShipInitFunc initFunc(RegisterN64MemoryModel, { CVAR_NAME }); \ No newline at end of file +static RegisterShipInitFunc initFunc(RegisterN64MemoryModel, {CVAR_NAME}); \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp similarity index 100% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp rename to soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp similarity index 85% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp rename to soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp index 1bb20ec0f1b..870df491fd1 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp @@ -9,8 +9,10 @@ static std::unordered_map sDmaFileSizes; static bool sIsDmaLoaded = false; -static void LoadDmaFileSizes() { - if (sIsDmaLoaded) { +static void LoadDmaFileSizes() +{ + if (sIsDmaLoaded) + { return; } @@ -18,7 +20,8 @@ static void LoadDmaFileSizes() { const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/n64_memory/dma_sizes"); - if (!file || !file->IsLoaded) { + if (!file || !file->IsLoaded) + { SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/dma_sizes from OTR."); return; } @@ -28,7 +31,8 @@ static void LoadDmaFileSizes() { reader->SetEndianness(Ship::Endianness::Big); const u32 entryCount = reader->ReadUInt32(); - for (std::size_t i = 0; i < entryCount; ++i) { + for (std::size_t i = 0; i < entryCount; ++i) + { const u32 vromSize = reader->ReadUInt32(); const std::string name = reader->ReadString(); sDmaFileSizes[name] = vromSize; @@ -46,8 +50,10 @@ static void LoadDmaFileSizes() { static std::vector sActorOverlaySizes; static bool sIsActorOverlayLoaded = false; -static void LoadActorOverlaySizes() { - if (sIsActorOverlayLoaded) { +static void LoadActorOverlaySizes() +{ + if (sIsActorOverlayLoaded) + { return; } @@ -56,7 +62,8 @@ static void LoadActorOverlaySizes() { const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( "misc/n64_memory/actor_overlay_sizes"); - if (!file || !file->IsLoaded) { + if (!file || !file->IsLoaded) + { SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/actor_overlay_sizes from OTR."); return; } @@ -68,7 +75,8 @@ static void LoadActorOverlaySizes() { const u32 entryCount = reader->ReadUInt32(); sActorOverlaySizes.resize(entryCount); - for (std::size_t i = 0; i < entryCount; ++i) { + for (std::size_t i = 0; i < entryCount; ++i) + { sActorOverlaySizes.at(i) = reader->ReadUInt32(); } @@ -84,8 +92,10 @@ static void LoadActorOverlaySizes() { static std::vector sEffectOverlaySizes; static bool sIsEffectOverlayLoaded = false; -static void LoadEffectOverlaySizes() { - if (sIsEffectOverlayLoaded) { +static void LoadEffectOverlaySizes() +{ + if (sIsEffectOverlayLoaded) + { return; } @@ -94,7 +104,8 @@ static void LoadEffectOverlaySizes() { const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( "misc/n64_memory/effect_overlay_sizes"); - if (!file || !file->IsLoaded) { + if (!file || !file->IsLoaded) + { SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/effect_overlay_sizes from OTR."); return; } @@ -106,7 +117,8 @@ static void LoadEffectOverlaySizes() { const u32 entryCount = reader->ReadUInt32(); sEffectOverlaySizes.resize(entryCount); - for (std::size_t i = 0; i < entryCount; ++i) { + for (std::size_t i = 0; i < entryCount; ++i) + { sEffectOverlaySizes.at(i) = reader->ReadUInt32(); } @@ -123,8 +135,10 @@ static void LoadEffectOverlaySizes() { static std::vector sActorInstanceSizes; static bool sIsActorInstanceLoaded = false; -static void LoadActorInstanceSizes() { - if (sIsActorInstanceLoaded) { +static void LoadActorInstanceSizes() +{ + if (sIsActorInstanceLoaded) + { return; } @@ -133,7 +147,8 @@ static void LoadActorInstanceSizes() { const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( "misc/n64_memory/actor_instance_sizes"); - if (!file || !file->IsLoaded) { + if (!file || !file->IsLoaded) + { SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/actor_instance_sizes from OTR."); return; } @@ -145,7 +160,8 @@ static void LoadActorInstanceSizes() { const u32 entryCount = reader->ReadUInt32(); sActorInstanceSizes.resize(entryCount); - for (std::size_t i = 0; i < entryCount; ++i) { + for (std::size_t i = 0; i < entryCount; ++i) + { sActorInstanceSizes.at(i) = reader->ReadUInt32(); } @@ -156,10 +172,12 @@ static void LoadActorInstanceSizes() { // API // -------------------------------------------------------------------------------------------------------------------- -extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) { +extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) +{ LoadDmaFileSizes(); - if (const auto i = sDmaFileSizes.find(name); i != sDmaFileSizes.end()) { + if (const auto i = sDmaFileSizes.find(name); i != sDmaFileSizes.end()) + { return i->second; } @@ -167,30 +185,36 @@ extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) { return 0; } -extern "C" u32 N64SizeData_GetActorOverlaySize(u16 actorId) { +extern "C" u32 N64SizeData_GetActorOverlaySize(u16 actorId) +{ LoadActorOverlaySizes(); - if (actorId < sActorOverlaySizes.size()) { + if (actorId < sActorOverlaySizes.size()) + { return sActorOverlaySizes.at(actorId); } return 0; } -extern "C" u32 N64SizeData_GetEffectOverlaySize(u16 effectType) { +extern "C" u32 N64SizeData_GetEffectOverlaySize(u16 effectType) +{ LoadEffectOverlaySizes(); - if (effectType < sEffectOverlaySizes.size()) { + if (effectType < sEffectOverlaySizes.size()) + { return sEffectOverlaySizes.at(effectType); } return 0; } -extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) { +extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) +{ LoadActorInstanceSizes(); - if (actorId < sActorInstanceSizes.size()) { + if (actorId < sActorInstanceSizes.size()) + { return sActorInstanceSizes.at(actorId); } @@ -206,8 +230,10 @@ extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) { static u32 sKaleidoVramSize = 0; static bool sIsKaleidoLoaded = false; -static void LoadKaleidoVramSize() { - if (sIsKaleidoLoaded) { +static void LoadKaleidoVramSize() +{ + if (sIsKaleidoLoaded) + { return; } @@ -216,7 +242,8 @@ static void LoadKaleidoVramSize() { const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( "misc/n64_memory/kaleido_vram_size"); - if (!file || !file->IsLoaded) { + if (!file || !file->IsLoaded) + { SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/kaleido_vram_size from OTR."); return; } @@ -230,7 +257,8 @@ static void LoadKaleidoVramSize() { SPDLOG_INFO("[N64SizeData] Kaleido max VRAM size: 0x{:X}.", sKaleidoVramSize); } -extern "C" u32 N64SizeData_GetKaleidoVramSize() { +extern "C" u32 N64SizeData_GetKaleidoVramSize() +{ LoadKaleidoVramSize(); return sKaleidoVramSize; } @@ -244,8 +272,10 @@ extern "C" u32 N64SizeData_GetKaleidoVramSize() { static u32 sArenaNodeSize = 0x30; // Default to debug (safe fallback -- over-estimates node overhead) static bool sIsArenaNodeSizeLoaded = false; -static void LoadArenaNodeSize() { - if (sIsArenaNodeSizeLoaded) { +static void LoadArenaNodeSize() +{ + if (sIsArenaNodeSizeLoaded) + { return; } @@ -254,7 +284,8 @@ static void LoadArenaNodeSize() { const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( "misc/n64_memory/arena_node_size"); - if (!file || !file->IsLoaded) { + if (!file || !file->IsLoaded) + { SPDLOG_WARN("[N64SizeData] Failed to load misc/n64_memory/arena_node_size from OTR, defaulting to 0x{:X}.", sArenaNodeSize); return; @@ -269,7 +300,8 @@ static void LoadArenaNodeSize() { SPDLOG_INFO("[N64SizeData] Arena node size: 0x{:X}.", sArenaNodeSize); } -extern "C" u32 N64SizeData_GetArenaNodeSize() { +extern "C" u32 N64SizeData_GetArenaNodeSize() +{ LoadArenaNodeSize(); return sArenaNodeSize; } @@ -284,8 +316,10 @@ extern "C" u32 N64SizeData_GetArenaNodeSize() { static u32 sLanguageMax = 2; // Default to NTSC static bool sIsLanguageMaxLoaded = false; -static void LoadLanguageMax() { - if (sIsLanguageMaxLoaded) { +static void LoadLanguageMax() +{ + if (sIsLanguageMaxLoaded) + { return; } @@ -294,7 +328,8 @@ static void LoadLanguageMax() { const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( "misc/n64_memory/language_max"); - if (!file || !file->IsLoaded) { + if (!file || !file->IsLoaded) + { SPDLOG_WARN("[N64SizeData] Failed to load misc/n64_memory/n64_language_max from OTR, defaulting to {}.", sLanguageMax); return; @@ -307,13 +342,4 @@ static void LoadLanguageMax() { sLanguageMax = reader->ReadUInt32(); SPDLOG_INFO("[N64SizeData] N64 LANGUAGE_MAX: {}.", sLanguageMax); -} - -extern "C" u32 N64SizeData_GetLanguageMax() { - LoadLanguageMax(); - return sLanguageMax; -} - -extern "C" u32 N64SizeData_GetGiObjectSegmentSize() { - return 0x1000 * N64SizeData_GetLanguageMax() + 8; } \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp similarity index 92% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp rename to soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp index 30b9a774145..26289db113e 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp @@ -16,7 +16,6 @@ extern "C" { // misc/n64_memory/actor_instance_sizes Actor instance struct sizes indexed by actor ID // misc/n64_memory/kaleido_vram_size max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) // misc/n64_memory/arena_node_size N64 ArenaNode size (0x10 retail, 0x30 debug) -// misc/n64_memory/language_max LANGUAGE_MAX enum count (2 NTSC, 3 PAL) // // Each table is loaded lazily on first query and cached for the lifetime of the process. // -------------------------------------------------------------------------------------------------------------------- @@ -27,7 +26,6 @@ u32 N64SizeData_GetEffectOverlaySize(u16 effectType); u32 N64SizeData_GetActorInstanceSize(u16 actorId); u32 N64SizeData_GetKaleidoVramSize(void); u32 N64SizeData_GetArenaNodeSize(void); -u32 N64SizeData_GetLanguageMax(void); u32 N64SizeData_GetGiObjectSegmentSize(void); #ifdef __cplusplus diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c similarity index 83% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c rename to soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c index f022fa2a64f..978d4f61c53 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c @@ -1,11 +1,11 @@ #include "n64_arena_sizing.h" #include "N64SizeData.hpp" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" #include "global.h" // Declared in z_bgcheck.c but not exposed via header. -s32 BgCheck_IsSpotScene(PlayState* play); +s32 BgCheck_IsSpotScene(PlayState * play); s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); // -------------------------------------------------------------------------------------------------------------------- @@ -56,12 +56,14 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); #define N64_MAP_MARK_DATA_VRAM_SIZE 0x6B60 -static u32 GetMapMarkDataOverlaySize(PlayState* play) { +static u32 GetMapMarkDataOverlaySize(PlayState* play) +{ // Mirrors the condition in z_map_exp.c Map_Init: the dungeon case block's inner guard. // Main dungeons: SCENE_DEKU_TREE (0x00) through SCENE_ICE_CAVERN (0x09) // Boss rooms: SCENE_DEKU_TREE_BOSS (0x11) through SCENE_SHADOW_TEMPLE_BOSS (0x18) if (play->sceneNum <= SCENE_ICE_CAVERN || - (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) { + (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) + { return N64_MAP_MARK_DATA_VRAM_SIZE; } @@ -86,54 +88,59 @@ static u32 GetMapMarkDataOverlaySize(PlayState* play) { // -------------------------------------------------------------------------------------------------------------------- // DMA file name for a single-file indoor skybox (1 tex + 1 pal). -typedef struct { +typedef struct +{ const char* texName; const char* palName; } SkyboxDmaEntry; // Indexed by skybox ID. NULL texName means the ID isn't a single-file indoor skybox (handled separately). static const SkyboxDmaEntry sSkyboxDmaTable[] = { - [SKYBOX_NONE] = { NULL, NULL }, - [SKYBOX_NORMAL_SKY] = { NULL, NULL }, // gNormalSkyFiles, hardcoded - [SKYBOX_BAZAAR] = { "vr_SP1a_static", "vr_SP1a_pal_static" }, - [SKYBOX_OVERCAST_SUNSET] = { NULL, NULL }, // same as NORMAL_SKY - [SKYBOX_MARKET_ADULT] = { "vr_RUVR_static", "vr_RUVR_pal_static" }, - [SKYBOX_CUTSCENE_MAP] = { NULL, NULL }, // Two tex files, handled separately - [SKYBOX_HOUSE_LINK] = { "vr_LHVR_static", "vr_LHVR_pal_static" }, - [SKYBOX_MARKET_CHILD_DAY] = { "vr_MDVR_static", "vr_MDVR_pal_static" }, - [SKYBOX_MARKET_CHILD_NIGHT] = { "vr_MNVR_static", "vr_MNVR_pal_static" }, - [SKYBOX_HAPPY_MASK_SHOP] = { "vr_FCVR_static", "vr_FCVR_pal_static" }, - [SKYBOX_HOUSE_KNOW_IT_ALL_BROTHERS] = { "vr_KHVR_static", "vr_KHVR_pal_static" }, - [SKYBOX_HOUSE_OF_TWINS] = { "vr_K3VR_static", "vr_K3VR_pal_static" }, - [SKYBOX_STABLES] = { "vr_MLVR_static", "vr_MLVR_pal_static" }, - [SKYBOX_HOUSE_KAKARIKO] = { "vr_KKRVR_static", "vr_KKRVR_pal_static" }, - [SKYBOX_KOKIRI_SHOP] = { "vr_KSVR_static", "vr_KSVR_pal_static" }, - [SKYBOX_GORON_SHOP] = { "vr_GLVR_static", "vr_GLVR_pal_static" }, - [SKYBOX_ZORA_SHOP] = { "vr_ZRVR_static", "vr_ZRVR_pal_static" }, - [SKYBOX_POTION_SHOP_KAKARIKO] = { "vr_DGVR_static", "vr_DGVR_pal_static" }, - [SKYBOX_POTION_SHOP_MARKET] = { "vr_ALVR_static", "vr_ALVR_pal_static" }, - [SKYBOX_BOMBCHU_SHOP] = { "vr_NSVR_static", "vr_NSVR_pal_static" }, - [SKYBOX_HOUSE_RICHARD] = { "vr_IPVR_static", "vr_IPVR_pal_static" }, - [SKYBOX_HOUSE_IMPA] = { "vr_LBVR_static", "vr_LBVR_pal_static" }, - [SKYBOX_TENT] = { "vr_TTVR_static", "vr_TTVR_pal_static" }, - [SKYBOX_HOUSE_MIDO] = { "vr_K4VR_static", "vr_K4VR_pal_static" }, - [SKYBOX_HOUSE_SARIA] = { "vr_K5VR_static", "vr_K5VR_pal_static" }, - [SKYBOX_HOUSE_ALLEY] = { "vr_KR3VR_static", "vr_KR3VR_pal_static" }, + [SKYBOX_NONE] = {NULL, NULL}, + [SKYBOX_NORMAL_SKY] = {NULL, NULL}, // gNormalSkyFiles, hardcoded + [SKYBOX_BAZAAR] = {"vr_SP1a_static", "vr_SP1a_pal_static"}, + [SKYBOX_OVERCAST_SUNSET] = {NULL, NULL}, // same as NORMAL_SKY + [SKYBOX_MARKET_ADULT] = {"vr_RUVR_static", "vr_RUVR_pal_static"}, + [SKYBOX_CUTSCENE_MAP] = {NULL, NULL}, // Two tex files, handled separately + [SKYBOX_HOUSE_LINK] = {"vr_LHVR_static", "vr_LHVR_pal_static"}, + [SKYBOX_MARKET_CHILD_DAY] = {"vr_MDVR_static", "vr_MDVR_pal_static"}, + [SKYBOX_MARKET_CHILD_NIGHT] = {"vr_MNVR_static", "vr_MNVR_pal_static"}, + [SKYBOX_HAPPY_MASK_SHOP] = {"vr_FCVR_static", "vr_FCVR_pal_static"}, + [SKYBOX_HOUSE_KNOW_IT_ALL_BROTHERS] = {"vr_KHVR_static", "vr_KHVR_pal_static"}, + [SKYBOX_HOUSE_OF_TWINS] = {"vr_K3VR_static", "vr_K3VR_pal_static"}, + [SKYBOX_STABLES] = {"vr_MLVR_static", "vr_MLVR_pal_static"}, + [SKYBOX_HOUSE_KAKARIKO] = {"vr_KKRVR_static", "vr_KKRVR_pal_static"}, + [SKYBOX_KOKIRI_SHOP] = {"vr_KSVR_static", "vr_KSVR_pal_static"}, + [SKYBOX_GORON_SHOP] = {"vr_GLVR_static", "vr_GLVR_pal_static"}, + [SKYBOX_ZORA_SHOP] = {"vr_ZRVR_static", "vr_ZRVR_pal_static"}, + [SKYBOX_POTION_SHOP_KAKARIKO] = {"vr_DGVR_static", "vr_DGVR_pal_static"}, + [SKYBOX_POTION_SHOP_MARKET] = {"vr_ALVR_static", "vr_ALVR_pal_static"}, + [SKYBOX_BOMBCHU_SHOP] = {"vr_NSVR_static", "vr_NSVR_pal_static"}, + [SKYBOX_HOUSE_RICHARD] = {"vr_IPVR_static", "vr_IPVR_pal_static"}, + [SKYBOX_HOUSE_IMPA] = {"vr_LBVR_static", "vr_LBVR_pal_static"}, + [SKYBOX_TENT] = {"vr_TTVR_static", "vr_TTVR_pal_static"}, + [SKYBOX_HOUSE_MIDO] = {"vr_K4VR_static", "vr_K4VR_pal_static"}, + [SKYBOX_HOUSE_SARIA] = {"vr_K5VR_static", "vr_K5VR_pal_static"}, + [SKYBOX_HOUSE_ALLEY] = {"vr_KR3VR_static", "vr_KR3VR_pal_static"}, }; -static u32 GetN64SkyboxTextureSize(s16 skyboxId) { - if (skyboxId == SKYBOX_NONE) { +static u32 GetN64SkyboxTextureSize(s16 skyboxId) +{ + if (skyboxId == SKYBOX_NONE) + { return 0; } // NORMAL_SKY and OVERCAST_SUNSET both load 2 texture banks + 2 palettes from the vr_fine/vr_cloud files. // All 16 banks are exactly 0xC000 and all 16 palettes are exactly 0x100, so the total is constant. - if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) + { return 2 * 0xC000 + 2 * 0x100; } // CUTSCENE_MAP loads two different texture files + 2 palette copies. - if (skyboxId == SKYBOX_CUTSCENE_MAP) { + if (skyboxId == SKYBOX_CUTSCENE_MAP) + { const u32 tex0 = N64SizeData_GetDmaFileSize("vr_holy0_static"); const u32 tex1 = N64SizeData_GetDmaFileSize("vr_holy1_static"); const u32 pal = N64SizeData_GetDmaFileSize("vr_holy0_pal_static"); @@ -141,9 +148,11 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) { } // Indoor skyboxes: 1 texture + 1 palette, looked up from the DMA blob. - if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) { + if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) + { const SkyboxDmaEntry* entry = &sSkyboxDmaTable[skyboxId]; - if (entry->texName != NULL) { + if (entry->texName != NULL) + { const u32 tex = N64SizeData_GetDmaFileSize(entry->texName); const u32 pal = N64SizeData_GetDmaFileSize(entry->palName); return tex + pal; @@ -163,20 +172,27 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) { // Gfx is 8 bytes on N64, Vtx is 0x10. // -------------------------------------------------------------------------------------------------------------------- -static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) { - if (skyboxId == SKYBOX_NONE) { +static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) +{ + if (skyboxId == SKYBOX_NONE) + { *outDlistSize = 0; *outVtxSize = 0; return; } - if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) + { *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; *outVtxSize = 5 * 32 * N64_SIZEOF_VTX; - } else if (skyboxId == SKYBOX_CUTSCENE_MAP) { + } + else if (skyboxId == SKYBOX_CUTSCENE_MAP) + { *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; *outVtxSize = 6 * 32 * N64_SIZEOF_VTX; - } else { + } + else + { // Indoor skyboxes: SKYBOX_DRAW_256_4FACE or SKYBOX_DRAW_256_3FACE, both use the same allocation. *outDlistSize = 8 * 150 * N64_SIZEOF_GFX; *outVtxSize = 8 * 32 * N64_SIZEOF_VTX; @@ -198,26 +214,31 @@ static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVt // bgcheck_memSize and sizeof(CollisionContext). // -------------------------------------------------------------------------------------------------------------------- -static u32 GetBgCheckMemSize(PlayState* play) { +static u32 GetBgCheckMemSize(PlayState* play) +{ const s16 sceneNum = play->sceneNum; - if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) { + if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) + { return sceneNum == SCENE_STABLE ? 0x3520 : 0x4E20; } - if (BgCheck_IsSpotScene(play)) { + if (BgCheck_IsSpotScene(play)) + { return 0xF000; } u32 customMemSize = 0; - if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) { + if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) + { return customMemSize; } return 0x1CC00; } -static u32 GetBgCheckThaTotal(PlayState* play) { +static u32 GetBgCheckThaTotal(PlayState* play) +{ return GetBgCheckMemSize(play) - N64_SIZEOF_COLLISION_CONTEXT; } @@ -225,14 +246,17 @@ static u32 GetBgCheckThaTotal(PlayState* play) { // Object bank size (mirrors z_scene.c Object_InitBlank) // -------------------------------------------------------------------------------------------------------------------- -static u32 GetObjectBankSize(PlayState* play) { +static u32 GetObjectBankSize(PlayState* play) +{ const s16 sceneNum = play->sceneNum; - if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) { + if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) + { return 1177600; } if (sceneNum == SCENE_SPIRIT_TEMPLE_BOSS || sceneNum == SCENE_CHAMBER_OF_THE_SAGES || - sceneNum == SCENE_GANONDORF_BOSS) { + sceneNum == SCENE_GANONDORF_BOSS) + { return 1075200; } @@ -251,13 +275,16 @@ static const char* sElfMsgDmaNames[] = { "elf_message_ydan", }; -static u32 GetElfMessageSize(PlayState* play) { - if (play->cUpElfMsgs == NULL) { +static u32 GetElfMessageSize(PlayState* play) +{ + if (play->cUpElfMsgs == NULL) + { return 0; } const u8 elfMsgNum = N64Mem_GetElfMsgNum(); - if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) { + if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) + { LUSLOG_WARN("[ArenaSizing] elfMsg: cUpElfMsgs non-NULL but elfMsgNum=%d out of range", elfMsgNum); return 0; } @@ -269,20 +296,25 @@ static u32 GetElfMessageSize(PlayState* play) { // Room buffer max size (mirrors func_80096FE8 logic) // -------------------------------------------------------------------------------------------------------------------- -static u32 GetMaxRoomSize(PlayState* play) { +static u32 GetMaxRoomSize(PlayState* play) +{ u32 maxRoomSize = 0; - for (size_t i = 0; i < play->numRooms; ++i) { + for (size_t i = 0; i < play->numRooms; ++i) + { const uintptr_t roomSize = play->roomList[i].vromEnd - play->roomList[i].vromStart; - if (roomSize > maxRoomSize) { + if (roomSize > maxRoomSize) + { maxRoomSize = roomSize; } } - if (play->transiActorCtx.numActors != 0) { + if (play->transiActorCtx.numActors != 0) + { const TransitionActorEntry* transitionActor = &play->transiActorCtx.list[0]; - for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) { + for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) + { const s8 frontRoom = transitionActor->sides[0].room; const s8 backRoom = transitionActor->sides[1].room; const uintptr_t frontSize = frontRoom < 0 @@ -293,7 +325,8 @@ static u32 GetMaxRoomSize(PlayState* play) { : play->roomList[backRoom].vromEnd - play->roomList[backRoom].vromStart; const uintptr_t cumulSize = frontRoom != backRoom ? frontSize + backSize : frontSize; - if (cumulSize > maxRoomSize) { + if (cumulSize > maxRoomSize) + { maxRoomSize = cumulSize; } @@ -308,7 +341,8 @@ static u32 GetMaxRoomSize(PlayState* play) { // Main computation // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { +u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) +{ // Each THA consumer is allocated via GAME_STATE_ALLOC -> THA_AllocTailAlign16, which consumes ALIGN16(size) bytes. // We must align each consumer individually before summing; aligning the sum would under-count when individual // sizes are not 16-byte aligned (common for DMA file sizes). BgCheck is the exception -- its internal allocations @@ -401,7 +435,8 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { LUSLOG_INFO("[ArenaSizing] total=0x%X, arena=0x%X (budget=0x%X)", total, N64_THA_BUDGET - total, (u32)N64_THA_BUDGET); - if (total >= N64_THA_BUDGET) { + if (total >= N64_THA_BUDGET) + { return 0; } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h similarity index 94% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h rename to soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h index e08590997f8..364227bd5d8 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h @@ -4,7 +4,6 @@ #ifdef __cplusplus extern "C" { - #endif // -------------------------------------------------------------------------------------------------------------------- @@ -22,7 +21,7 @@ extern "C" { // (should not happen on valid scenes). // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play); +u32 ArenaSizing_ComputeN64ArenaSize(PlayState * play); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c similarity index 81% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c rename to soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c index 4cd1f1dfc3a..6e3513036bd 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.c +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c @@ -17,7 +17,8 @@ // The total node spacing is arena->nodeSize: 0x10 for retail, 0x30 for debug. // -------------------------------------------------------------------------------------------------------------------- -typedef struct ShadowNode { +typedef struct ShadowNode +{ s16 magic; s16 isFree; u32 size; @@ -34,34 +35,42 @@ static_assert(sizeof(ShadowNode) == 0x10, "ShadowNode header must be exactly 0x1 // Offset helpers // -------------------------------------------------------------------------------------------------------------------- -static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) { +static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) +{ return (ShadowNode*)(arena->buffer + offset); } -static s32 NodeIsValid(ShadowArena* arena, u32 offset) { - if (offset == SHADOW_NULL) { +static s32 NodeIsValid(ShadowArena* arena, u32 offset) +{ + if (offset == SHADOW_NULL) + { return 0; } - if (offset + arena->nodeSize > arena->bufferSize) { + if (offset + arena->nodeSize > arena->bufferSize) + { return 0; } return NodeAt(arena, offset)->magic == NODE_MAGIC; } -static u32 NodeGetNext(ShadowArena* arena, u32 offset) { +static u32 NodeGetNext(ShadowArena* arena, u32 offset) +{ const ShadowNode* node = NodeAt(arena, offset); - if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) { + if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) + { return node->next; } return SHADOW_NULL; } -static u32 NodeGetPrev(ShadowArena* arena, u32 offset) { +static u32 NodeGetPrev(ShadowArena* arena, u32 offset) +{ const ShadowNode* node = NodeAt(arena, offset); - if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) { + if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) + { return node->prev; } @@ -72,13 +81,15 @@ static u32 NodeGetPrev(ShadowArena* arena, u32 offset) { // Init / Destroy // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Init(ShadowArena* arena, u32 size, u32 nodeSize) { +void ShadowArena_Init(ShadowArena* arena, u32 size, u32 nodeSize) +{ // Match N64's alignment: Round start up to 16, round size down to 16. Since we control the buffer, start is // effectively offset 0 after alignment. We just ensure the usable size is 16-byte aligned. const u32 alignedSize = size & ~0xF; arena->buffer = (u8*)malloc(alignedSize); - if (!arena->buffer) { + if (!arena->buffer) + { memset(arena, 0, sizeof(ShadowArena)); return; } @@ -98,8 +109,10 @@ void ShadowArena_Init(ShadowArena* arena, u32 size, u32 nodeSize) { arena->head = 0; } -void ShadowArena_Destroy(ShadowArena* arena) { - if (arena->buffer) { +void ShadowArena_Destroy(ShadowArena* arena) +{ + if (arena->buffer) + { free(arena->buffer); } @@ -110,7 +123,8 @@ void ShadowArena_Destroy(ShadowArena* arena) { // Malloc: First-fit forward (matches N64 __osMalloc) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { +u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) +{ u32 iterOff = 0; ShadowNode* iter = NULL; u32 blockSize = 0; @@ -119,11 +133,14 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { blockSize = size + arena->nodeSize; iterOff = arena->head; - while (iterOff != SHADOW_NULL) { + while (iterOff != SHADOW_NULL) + { iter = NodeAt(arena, iterOff); - if (iter->isFree && iter->size >= size) { + if (iter->isFree && iter->size >= size) + { // Split if remainder can hold a new node + payload. - if (blockSize < iter->size) { + if (blockSize < iter->size) + { const u32 newOff = iterOff + blockSize; ShadowNode* newNode = NodeAt(arena, newOff); @@ -133,7 +150,8 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { newNode->next = iter->next; newNode->prev = iterOff; - if (newNode->next != SHADOW_NULL) { + if (newNode->next != SHADOW_NULL) + { NodeAt(arena, newNode->next)->prev = newOff; } @@ -157,7 +175,8 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { // MallocR: First-fit backward (matches N64 __osMallocR) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { +u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) +{ u32 iterOff = 0; u32 nextOff = 0; ShadowNode* iter = NULL; @@ -168,19 +187,23 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { // Walk to tail. iterOff = arena->head; nextOff = NodeGetNext(arena, iterOff); - while (nextOff != SHADOW_NULL) { + while (nextOff != SHADOW_NULL) + { iterOff = nextOff; nextOff = NodeGetNext(arena, nextOff); } // Walk backward looking for a fit. - while (iterOff != SHADOW_NULL) { + while (iterOff != SHADOW_NULL) + { iter = NodeAt(arena, iterOff); - if (iter->isFree && iter->size >= size) { + if (iter->isFree && iter->size >= size) + { blockSize = size + arena->nodeSize; // Split: Carve the allocation from the TOP of this free block. - if (blockSize < iter->size) { + if (blockSize < iter->size) + { const u32 newOff = iterOff + (iter->size - size); ShadowNode* newNode = NodeAt(arena, newOff); @@ -190,7 +213,8 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { newNode->next = iter->next; newNode->prev = iterOff; - if (newNode->next != SHADOW_NULL) { + if (newNode->next != SHADOW_NULL) + { NodeAt(arena, newNode->next)->prev = newOff; } @@ -215,24 +239,28 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { // Free: Adjacent block coalescing (matches N64 _osFree) // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { +void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) +{ u32 nodeOff = 0; ShadowNode* node = NULL; u32 nextOff = 0; u32 prevOff = 0; - if (dataOffset == SHADOW_NULL) { + if (dataOffset == SHADOW_NULL) + { return; } nodeOff = dataOffset - arena->nodeSize; node = NodeAt(arena, nodeOff); - if (node->magic != NODE_MAGIC) { + if (node->magic != NODE_MAGIC) + { return; } - if (node->isFree) { + if (node->isFree) + { return; } @@ -240,10 +268,13 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { // Forward coalesce: Merge with next if it's free. nextOff = node->next; - if (nextOff != SHADOW_NULL) { + if (nextOff != SHADOW_NULL) + { const ShadowNode* next = NodeAt(arena, nextOff); - if (next->isFree) { - if (next->next != SHADOW_NULL) { + if (next->isFree) + { + if (next->next != SHADOW_NULL) + { NodeAt(arena, next->next)->prev = nodeOff; } @@ -254,13 +285,16 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { // Backward coalesce: Merge into prev if it's free. prevOff = node->prev; - if (prevOff != SHADOW_NULL) { + if (prevOff != SHADOW_NULL) + { ShadowNode* prev = NodeAt(arena, prevOff); - if (prev->isFree) { + if (prev->isFree) + { prev->size += node->size + arena->nodeSize; prev->next = node->next; - if (node->next != SHADOW_NULL) { + if (node->next != SHADOW_NULL) + { NodeAt(arena, node->next)->prev = prevOff; } } @@ -271,7 +305,8 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { // Query // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) { +void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) +{ u32 iterOff = 0; *outMaxFree = 0; @@ -279,14 +314,19 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 *outAlloc = 0; iterOff = arena->head; - while (iterOff != SHADOW_NULL) { + while (iterOff != SHADOW_NULL) + { const ShadowNode* node = NodeAt(arena, iterOff); - if (node->isFree) { + if (node->isFree) + { *outFree += node->size; - if (node->size > *outMaxFree) { + if (node->size > *outMaxFree) + { *outMaxFree = node->size; } - } else { + } + else + { *outAlloc += node->size; } @@ -294,17 +334,21 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 } } -u32 ShadowArena_GetHead(ShadowArena* arena) { +u32 ShadowArena_GetHead(ShadowArena* arena) +{ return arena->head; } -s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) { - if (offset == SHADOW_NULL || offset + arena->nodeSize > arena->bufferSize) { +s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) +{ + if (offset == SHADOW_NULL || offset + arena->nodeSize > arena->bufferSize) + { return 0; } const ShadowNode* node = NodeAt(arena, offset); - if (node->magic != NODE_MAGIC) { + if (node->magic != NODE_MAGIC) + { return 0; } @@ -314,6 +358,7 @@ s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* return 1; } -u32 ShadowArena_GetBufferSize(ShadowArena* arena) { +u32 ShadowArena_GetBufferSize(ShadowArena* arena) +{ return arena->bufferSize; } \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h similarity index 93% rename from soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h rename to soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h index 297b2f549fc..21b9c1e30c5 100644 --- a/soh/soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h @@ -4,9 +4,6 @@ #ifdef __cplusplus extern "C" { - - - #endif // Sentinel value for null offsets (no valid node can live at 0xFFFFFFFF in a buffer that's only ~245KB). @@ -18,7 +15,8 @@ extern "C" { #define SHADOW_NODE_SIZE_RETAIL 0x10 #define SHADOW_NODE_SIZE_DEBUG 0x30 -typedef struct ShadowArena { +typedef struct ShadowArena +{ u8* buffer; u32 head; // Offset to first node u32 bufferSize; @@ -42,7 +40,7 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size); void ShadowArena_Free(ShadowArena* arena, u32 dataOffset); // Query arena statistics. -void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc); +void ShadowArena_GetSizes(ShadowArena * arena, u32 * outMaxFree, u32 * outFree, u32 * outAlloc); // Get the head node offset for external traversal (e.g., heap viewer). u32 ShadowArena_GetHead(ShadowArena* arena); diff --git a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp index 26d60a18f11..f2a18bc6073 100644 --- a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp +++ b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp @@ -9,12 +9,12 @@ #include "soh/OTRGlobals.h" #include "soh/ActorDB.h" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" extern "C" { #include "z64.h" #include "functions.h" -#include "soh/Enhancements/Restorations/N64MemoryModel/n64_shadow_arena.h" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h" } // ----------------------------------------------------------------------------------------------------------------- diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 89224c0ab57..876f4946b87 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -504,29 +504,33 @@ void SaveManager::StartupCheckAndInitMeta(int fileNum) { output.close(); saveMtx.unlock(); } - s16 major = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; - s16 minor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; - s16 patch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; - // block loading outdated rando save - if (!(major == gBuildVersionMajor && minor == gBuildVersionMinor && patch == gBuildVersionPatch)) { - std::string newFileName = - Ship::Context::GetPathRelativeToAppDirectory("Save") + - ("/file" + std::to_string(fileNum + 1) + "-" + std::to_string(GetUnixTimestamp()) + ".bak"); + + if (metaSaveBlock["sections"].contains("sohStats") && metaSaveBlock["sections"]["sohStats"].contains("data")) { + s16 major = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; + s16 minor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; + s16 patch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; + // block loading outdated rando save + if (!(major == gBuildVersionMajor && minor == gBuildVersionMinor && patch == gBuildVersionPatch)) { + std::string newFileName = + Ship::Context::GetPathRelativeToAppDirectory("Save") + + ("/file" + std::to_string(fileNum + 1) + "-" + std::to_string(GetUnixTimestamp()) + ".bak"); #if defined(__SWITCH__) || defined(__WIIU__) - copy_file(fileName.c_str(), newFileName.c_str()); - std::filesystem::remove(fileName); + copy_file(fileName.c_str(), newFileName.c_str()); + std::filesystem::remove(fileName); #else - std::filesystem::rename(fileName, newFileName); + std::filesystem::rename(fileName, newFileName); #endif - SohGui::RegisterPopup("Outdated Randomizer Save", - "The SoH version in the file in slot " + std::to_string(fileNum + 1) + - " does not match the currently running version.\n" + - "Non-matching rando saves are unsupported, and the file has been renamed to\n" + - " " + newFileName + "\n" + - "If this was not in error, the file should be deleted."); - return; + SohGui::RegisterPopup("Outdated Randomizer Save", + "The SoH version in the file in slot " + std::to_string(fileNum + 1) + + " does not match the currently running version.\n" + + "Non-matching rando saves are unsupported, and the file has been renamed to\n" + + " " + newFileName + "\n" + + "If this was not in error, the file should be deleted."); + return; + } } } + bool isRando = metaSaveBlock["fileType"] == FILE_TYPE_SAVE_RANDO; fileMetaInfo[fileNum].valid = true; @@ -573,12 +577,19 @@ void SaveManager::StartupCheckAndInitMeta(int fileNum) { fileMetaInfo[fileNum].requiresOriginal = randoBlock["masterQuestDungeonCount"] < 12; } - fileMetaInfo[fileNum].buildVersionMajor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; - fileMetaInfo[fileNum].buildVersionMinor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; - fileMetaInfo[fileNum].buildVersionPatch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; - SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].buildVersion, - metaSaveBlock["sections"]["sohStats"]["data"]["buildVersion"], - ARRAY_COUNT(fileMetaInfo[fileNum].buildVersion)); + if (metaSaveBlock["sections"].contains("sohStats") && metaSaveBlock["sections"]["sohStats"].contains("data")) { + fileMetaInfo[fileNum].buildVersionMajor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; + fileMetaInfo[fileNum].buildVersionMinor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; + fileMetaInfo[fileNum].buildVersionPatch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; + SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].buildVersion, + metaSaveBlock["sections"]["sohStats"]["data"]["buildVersion"], + ARRAY_COUNT(fileMetaInfo[fileNum].buildVersion)); + } else { + fileMetaInfo[fileNum].buildVersionMajor = 0; + fileMetaInfo[fileNum].buildVersionMinor = 0; + fileMetaInfo[fileNum].buildVersionPatch = 0; + fileMetaInfo[fileNum].buildVersion[0] = '\0'; + } } void SaveManager::InitMeta(int fileNum) { @@ -1154,7 +1165,15 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int se sectionHandlerPair.second.func(saveContext, sectionID, true); } } else { + if (auto it = sectionSaveHandlers.find(sectionID); it == sectionSaveHandlers.end()) { + SPDLOG_ERROR("[SaveManager] SaveFileThreaded: sectionID {} not found in sectionSaveHandlers", sectionID); + delete saveContext; + saveMtx.unlock(); + return; + } + SaveFuncInfo svi = sectionSaveHandlers.find(sectionID)->second; + auto& sectionName = svi.name; auto sectionVersion = svi.version; // If section has a parentSection, it is a subsection. Load parentSection version and set sectionBlock to parent diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 4a090c95537..2d0b15a94bf 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -1190,6 +1190,10 @@ void SohMenu::AddMenuEnhancements() { .CVar(CVAR_ENHANCEMENT("HoverFishing")) .Options(CheckboxOptions().Tooltip( "Restore a bug from NTSC 1.0 that allows casting the Fishing Rod while using the Hover Boots.")); + AddWidget(path, "N64 Weird Frames", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("N64WeirdFrames")) + .Options(CheckboxOptions().Tooltip( + "Restores N64 Weird Frames allowing weirdshots and weirdslides to behave the same as N64.")); AddWidget(path, "Bombchus Out of Bounds", WIDGET_CVAR_CHECKBOX) .CVar(CVAR_ENHANCEMENT("BombchusOOB")) .Options( @@ -1208,16 +1212,11 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Restores a bug from NTSC 1.0/1.1 that allows you to obtain the eyeball frog from King Zora " "instead of the Zora Tunic by Holding Shield.")); - AddWidget(path, "N64 Weird Frames", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("N64WeirdFrames")) - .Options(CheckboxOptions().Tooltip( - "Restores N64 Weird Frames allowing weirdshots and weirdslides to behave the same as N64.")); - AddWidget(path, "N64 Memory Model", WIDGET_CVAR_CHECKBOX) - .CVar(CVAR_ENHANCEMENT("N64MemoryModel")) + AddWidget(path, "Hardware Memory Limits", WIDGET_CVAR_CHECKBOX) + .CVar(CVAR_ENHANCEMENT("HardwareMemoryLimits")) .Options(CheckboxOptions().Tooltip( - "Simulates N64 hardware memory limits. Repeated room transitions will eventually prevent new actors " - "from spawning, matching original N64 behavior.\n" - "Required for authentic SRM and heap manipulation techniques.")); + "Restores the memory limits from original hardware. Actors will fail to spawn after repeated room " + "transitions, allowing heap manipulation techniques.")); AddWidget(path, "Misc Restorations", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Fix L&Z Page Switch in Pause Menu", WIDGET_CVAR_CHECKBOX) diff --git a/soh/soh/z_scene_otr.cpp b/soh/soh/z_scene_otr.cpp index ffa64b70909..c856e3042e6 100644 --- a/soh/soh/z_scene_otr.cpp +++ b/soh/soh/z_scene_otr.cpp @@ -1,5 +1,5 @@ #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" #include "ResourceManagerHelpers.h" #include #include "soh/resource/type/Scene.h" @@ -106,7 +106,7 @@ bool Scene_CommandSpecialFiles(PlayState* play, SOH::ISceneCommand* cmd) { play->objectCtx.subKeepIndex = Object_Spawn(&play->objectCtx, specialCmd->specialObjects.globalObject); } - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_StoreElfMsgNum(specialCmd->specialObjects.elfMessage); // #endregion diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index f1e6966494c..9f321edea22 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -14,7 +14,7 @@ #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/Enhancements/nametag.h" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" #include "soh/ActorDB.h" #include "soh/OTRGlobals.h" @@ -2598,7 +2598,8 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { play->numSetupActors = 0; GameInteractor_ExecuteOnSceneSpawnActors(); - // #region SOH Enhancement - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Log shadow heap stats after each room transition for hardware validation. N64Mem_BenchmarkTransition(play); // #endregion } @@ -2659,21 +2660,10 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { CollisionCheck_ResetDamage(&actor->colChkInfo); actor = actor->next; } else if (actor->update == NULL) { - // #region SOH [Enhancement] - N64 Memory Model - // Same-draw distance distinction as the room-change kill path: Use N64's culling check to determine - // whether this actor would have been drawn, matching the immediate vs. deferred free oredering that - // shapes N64's heap geometry. - if (N64Mem_IsActive()) { - const s32 n64WouldDraw = actor->init == NULL && actor->draw != NULL && ( - actor->flags & ACTOR_FLAG_DRAW_CULLING_DISABLED || func_800314D4( - play, actor, &actor->projectedPos, actor->projectedW)); - if (!n64WouldDraw) { - actor = Actor_Delete(&play->actorCtx, actor, play); - } else { - Actor_Destroy(actor, play); - actor = actor->next; - } - } else if (!actor->isDrawn) { + // #region SOH [Enhancement] - Hardware Memory Limits + // Force the immediate Actor_Delete path for killed actors so the shadow arena's free ordering stays + // consistent with original hardware behavior. + if (N64Mem_IsActive() || !actor->isDrawn) { // #endregion actor = Actor_Delete(&play->actorCtx, actor, play); } else { @@ -3367,7 +3357,9 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos s32 objBankIndex; u32 temp; - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Save the randomizer state before spawn. Enemy Randomizer may swap the actor ID, so the shadow needs to know + // both the original and randomized IDs to allocate the correct sizes. const s32 n64MemSavedInit = N64Mem_GetRandomizedInit(); // #endregion @@ -3401,7 +3393,8 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos return NULL; } - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Shadow-allocate the actor's overlay code. If the shadow heap is full, block the spawn. if (dbEntry->numLoaded == 0) { if (!N64Mem_AllocOverlay(actorId, dbEntry->allocType)) { Actor_FreeOverlay(dbEntry); @@ -3420,7 +3413,8 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos return NULL; } - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Shadow-allocate the actor's instance struct. If the shadow heap is full, block the spawn. if (!N64Mem_AllocInstance(actorId, params, actor)) { ZELDA_ARENA_FREE_DEBUG(actor); Actor_FreeOverlay(dbEntry); @@ -3471,7 +3465,8 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos Actor_Init(actor, play); gSegments[6] = temp; - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Restore randomizer state after the actor's Init has run. N64Mem_ClearOriginalActorId(); N64Mem_SetRandomizedInit(n64MemSavedInit); // #endregion @@ -3593,7 +3588,9 @@ Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) { ObjectExtension_Free(actor); // #endregion - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Shadow-free the actor instance. Returns the original (pre-randomizer) actor ID so the overlay free uses the + // correct ID even when Enemy Randomizer swapped it at spawn time. const s16 n64MemOriginalId = N64Mem_FreeInstance(actor); const s16 n64MemActorId = n64MemOriginalId >= 0 ? n64MemOriginalId : actor->id; // #endregion @@ -3602,7 +3599,8 @@ Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) { dbEntry->numLoaded--; - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Shadow-free the overlay code when the last instance using it is deleted. if (dbEntry->numLoaded == 0) { N64Mem_FreeOverlay(n64MemActorId, dbEntry->allocType); } diff --git a/soh/src/code/z_camera.c b/soh/src/code/z_camera.c index fe0c910ae86..272669ada5a 100644 --- a/soh/src/code/z_camera.c +++ b/soh/src/code/z_camera.c @@ -8,7 +8,7 @@ #include "soh/frame_interpolation.h" #include "soh/Enhancements/controls/Mouse.h" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" s16 Camera_ChangeSettingFlags(Camera* camera, s16 setting, s16 flags); s32 Camera_ChangeModeFlags(Camera* camera, s16 mode, u8 flags); @@ -6944,7 +6944,8 @@ Camera* Camera_Create(View* view, CollisionContext* colCtx, PlayState* play) { Camera* newCamera = ZELDA_ARENA_MALLOC_DEBUG(sizeof(*newCamera)); if (newCamera != NULL) { - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Cameras are ZeldaArena allocations on original hardware. Track in the shadow heap. if (!N64Mem_AllocSubsidiary(newCamera, N64_SIZEOF_CAMERA)) { ZELDA_ARENA_FREE_DEBUG(newCamera); return NULL; @@ -6962,7 +6963,7 @@ Camera* Camera_Create(View* view, CollisionContext* colCtx, PlayState* play) { void Camera_Destroy(Camera* camera) { if (camera != NULL) { osSyncPrintf(VT_FGCOL(BLUE) "camera: destroy ---" VT_RST "\n"); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_FreeSubsidiary(camera); // #endregion diff --git a/soh/src/code/z_collision_check.c b/soh/src/code/z_collision_check.c index 0bbf640fb15..baccb127d5c 100644 --- a/soh/src/code/z_collision_check.c +++ b/soh/src/code/z_collision_check.c @@ -2,7 +2,7 @@ #include "vt.h" #include "overlays/effects/ovl_Effect_Ss_HitMark/z_eff_ss_hitmark.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" #include typedef s32 (*ColChkResetFunc)(PlayState*, Collider*); @@ -338,7 +338,8 @@ s32 Collider_FreeJntSph(PlayState* play, ColliderJntSph* collider) { collider->count = 0; if (collider->elements != NULL) { - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Collider element arrays are ZeldaArena allocations. Track frees in the shadow heap. N64Mem_FreeSubsidiary(collider->elements); // #endregion @@ -383,7 +384,7 @@ s32 Collider_SetJntSphToActor(PlayState* play, ColliderJntSph* dest, ColliderJnt return 0; } - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; @@ -420,7 +421,7 @@ s32 Collider_SetJntSphAllocType1(PlayState* play, ColliderJntSph* dest, Actor* a return 0; } - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; @@ -457,7 +458,7 @@ s32 Collider_SetJntSphAlloc(PlayState* play, ColliderJntSph* dest, Actor* actor, return 0; } - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_JNT_SPH_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; @@ -733,7 +734,7 @@ s32 Collider_FreeTris(PlayState* play, ColliderTris* tris) { tris->count = 0; if (tris->elements != NULL) { - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_FreeSubsidiary(tris->elements); // #endregion @@ -778,7 +779,7 @@ s32 Collider_SetTrisAllocType1(PlayState* play, ColliderTris* dest, Actor* actor return 0; } - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; @@ -815,7 +816,7 @@ s32 Collider_SetTrisAlloc(PlayState* play, ColliderTris* dest, Actor* actor, Col return 0; } - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits if (!N64Mem_AllocSubsidiary(dest->elements, src->count * N64_SIZEOF_COLLIDER_TRIS_ELEM)) { ZELDA_ARENA_FREE_DEBUG(dest->elements); dest->elements = NULL; diff --git a/soh/src/code/z_effect_soft_sprite.c b/soh/src/code/z_effect_soft_sprite.c index dd69f4761ae..a097e476a49 100644 --- a/soh/src/code/z_effect_soft_sprite.c +++ b/soh/src/code/z_effect_soft_sprite.c @@ -2,7 +2,7 @@ #include "vt.h" #include "soh/frame_interpolation.h" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" #include EffectSsInfo sEffectSsInfo = { 0 }; // "EffectSS2Info" @@ -184,7 +184,9 @@ void EffectSs_Spawn(PlayState* play, s32 type, s32 priority, void* initParams) { return; } - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Effect overlays are MallocR'd (allocated from the top of the heap) on original hardware. Track in the shadow + // heap so the forward/reverse alloc boundary stays accurate. if (!N64Mem_AllocEffectOverlay(type)) { return; } diff --git a/soh/src/code/z_play.c b/soh/src/code/z_play.c index a6db893fa9c..a79ee0bc7ce 100644 --- a/soh/src/code/z_play.c +++ b/soh/src/code/z_play.c @@ -9,7 +9,7 @@ #include #include "soh/Enhancements/enhancementTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" #include "soh/SaveManager.h" @@ -404,16 +404,18 @@ void Play_Init(GameState* thisx) { SystemArena_Display(); - // #region SOH [Enhancement] - N64 Memory Model - if (CVarGetInteger(CVAR_ENHANCEMENT("N64MemoryModel"), 0)) { + // #region SOH [Enhancement] - Hardware Memory Limits + // SoH doubles the THA budget to avoid OoM from struct size differences. When Hardware Memory Limits is active, + // use the original N64 budget so the ZeldaArena is the correct size. + if (CVarGetInteger(CVAR_ENHANCEMENT("HardwareMemoryLimits"), 0)) { GameState_Realloc(&play->state, 0x1D4790); } else { + // #endregion // OTRTODO allocate double the normal amount of memory // This is to avoid some parts of the game, like loading actors, causing OoM // This is potionally unavoidable due to struct size differences, but is x2 the right amount? GameState_Realloc(&play->state, 0x1D4790 * 2); } - // #endregion KaleidoManager_Init(play); View_Init(&play->view, gfxCtx); @@ -578,7 +580,9 @@ void Play_Init(GameState* thisx) { osSyncPrintf("ZELDA ALLOC SIZE=%x\n", THA_GetSize(&play->state.tha)); zAllocSize = THA_GetSize(&play->state.tha); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Capture the ZeldaArena size (THA remainder) before it's consumed. The shadow arena uses this to compute its own + // size, matching the original hardware's available heap space. N64Mem_StoreThaRemainder(zAllocSize); // #endregion @@ -589,7 +593,9 @@ void Play_Init(GameState* thisx) { osSyncPrintf("ゼルダヒープ %08x-%08x\n", zAllocAligned, (u8*)zAllocAligned + zAllocSize - (s32)(zAllocAligned - zAlloc)); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // Create (or recreate) the shadow arena for this scene. The shadow mirrors every ZeldaArena allocation at + // original hardware sizes, tracking fragmentation independently of SoH's heap. N64Mem_Reset(play); // #endregion diff --git a/soh/src/code/z_skelanime.c b/soh/src/code/z_skelanime.c index a8fb1d5aa38..3b89c2199b9 100644 --- a/soh/src/code/z_skelanime.c +++ b/soh/src/code/z_skelanime.c @@ -5,7 +5,7 @@ #include #include "soh/ResourceManagerHelpers.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" #define ANIM_INTERP 1 @@ -1130,13 +1130,13 @@ void SkelAnime_InitLink(PlayState* play, SkelAnime* skelAnime, FlexSkeletonHeade if (jointTable == NULL) { skelAnime->jointTable = ZELDA_ARENA_MALLOC_DEBUG(allocSize); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_AllocSubsidiary(skelAnime->jointTable, allocSize); // #endregion skelAnime->morphTable = ZELDA_ARENA_MALLOC_DEBUG(allocSize); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_AllocSubsidiary(skelAnime->morphTable, allocSize); // #endregion } else { @@ -1468,13 +1468,13 @@ s32 SkelAnime_Init(PlayState* play, SkelAnime* skelAnime, SkeletonHeader* skelet if (jointTable == NULL) { skelAnime->jointTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->jointTable)); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_AllocSubsidiary(skelAnime->jointTable, skelAnime->limbCount * sizeof(*skelAnime->jointTable)); // #endregion skelAnime->morphTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->morphTable)); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_AllocSubsidiary(skelAnime->morphTable, skelAnime->limbCount * sizeof(*skelAnime->morphTable)); // #endregion } else { @@ -1511,13 +1511,13 @@ s32 SkelAnime_InitFlex(PlayState* play, SkelAnime* skelAnime, FlexSkeletonHeader if (jointTable == NULL) { skelAnime->jointTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->jointTable)); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_AllocSubsidiary(skelAnime->jointTable, skelAnime->limbCount * sizeof(*skelAnime->jointTable)); // #endregion skelAnime->morphTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->morphTable)); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_AllocSubsidiary(skelAnime->morphTable, skelAnime->limbCount * sizeof(*skelAnime->morphTable)); // #endregion } else { @@ -1552,13 +1552,13 @@ s32 SkelAnime_InitSkin(PlayState* play, SkelAnime* skelAnime, SkeletonHeader* sk skelAnime->skeleton = SEGMENTED_TO_VIRTUAL(skeletonHeader->segment); skelAnime->jointTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->jointTable)); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_AllocSubsidiary(skelAnime->jointTable, skelAnime->limbCount * sizeof(*skelAnime->jointTable)); // #endregion skelAnime->morphTable = ZELDA_ARENA_MALLOC_DEBUG(skelAnime->limbCount * sizeof(*skelAnime->morphTable)); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_AllocSubsidiary(skelAnime->morphTable, skelAnime->limbCount * sizeof(*skelAnime->morphTable)); // #endregion @@ -1964,7 +1964,7 @@ s32 Animation_OnFrame(SkelAnime* skelAnime, f32 frame) { */ void SkelAnime_Free(SkelAnime* skelAnime, PlayState* play) { if (skelAnime->jointTable != NULL) { - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_FreeSubsidiary(skelAnime->jointTable); // #endregion @@ -1974,7 +1974,7 @@ void SkelAnime_Free(SkelAnime* skelAnime, PlayState* play) { } if (skelAnime->morphTable != NULL) { - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_FreeSubsidiary(skelAnime->morphTable); // #endregion diff --git a/soh/src/code/z_skin_awb.c b/soh/src/code/z_skin_awb.c index fc6c15a84b7..00842e4cb16 100644 --- a/soh/src/code/z_skin_awb.c +++ b/soh/src/code/z_skin_awb.c @@ -2,7 +2,7 @@ #include "overlays/actors/ovl_En_fHG/z_en_fhg.h" #include #include "soh/ResourceManagerHelpers.h" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" /** * Initialises the Vtx buffers used for limb at index `limbIndex` @@ -58,7 +58,7 @@ void Skin_Init(PlayState* play, Skin* skin, SkeletonHeader* skeletonHeader, Anim assert(skin->vtxTable != NULL); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_AllocSubsidiary(skin->vtxTable, limbCount * N64_SIZEOF_SKIN_LIMB_VTX); // #endregion @@ -79,14 +79,14 @@ void Skin_Init(PlayState* play, Skin* skin, SkeletonHeader* skeletonHeader, Anim vtxEntry->buf[0] = ZELDA_ARENA_MALLOC_DEBUG(animatedLimbData->totalVtxCount * sizeof(Vtx)); assert(vtxEntry->buf[0] != NULL); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_AllocSubsidiary(vtxEntry->buf[0], animatedLimbData->totalVtxCount * sizeof(Vtx)); // #endregion vtxEntry->buf[1] = ZELDA_ARENA_MALLOC_DEBUG(animatedLimbData->totalVtxCount * sizeof(Vtx)); assert(vtxEntry->buf[1] != NULL); - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_AllocSubsidiary(vtxEntry->buf[1], animatedLimbData->totalVtxCount * sizeof(Vtx)); // #endregion @@ -106,7 +106,7 @@ void Skin_Free(PlayState* play, Skin* skin) { for (i = 0; i < skin->limbCount; i++) { if (skin->vtxTable[i].buf[0] != NULL) { - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_FreeSubsidiary(skin->vtxTable[i].buf[0]); // #endregion @@ -114,7 +114,7 @@ void Skin_Free(PlayState* play, Skin* skin) { skin->vtxTable[i].buf[0] = NULL; } if (skin->vtxTable[i].buf[1] != NULL) { - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_FreeSubsidiary(skin->vtxTable[i].buf[1]); // #endregion @@ -124,7 +124,7 @@ void Skin_Free(PlayState* play, Skin* skin) { } if (skin->vtxTable != NULL) { - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits N64Mem_FreeSubsidiary(skin->vtxTable); // #endregion diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 01bea9c392e..f4bb1b89032 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -29,8 +29,8 @@ #include "soh/Enhancements/enhancementTypes.h" #include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" #include "soh/Enhancements/randomizer/randomizer_grotto.h" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64MemoryModel.hpp" -#include "soh/Enhancements/Restorations/N64MemoryModel/N64SizeData.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" +#include "soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp" #include "soh/frame_interpolation.h" #include "soh/OTRGlobals.h" #include "soh/ResourceManagerHelpers.h" @@ -10848,9 +10848,11 @@ void Player_Init(Actor* thisx, PlayState* play2) { // get item objects is 0x2000 (see the assert in func_8083AE40), and the maximum size for // title cards is 0x1000 * LANGUAGE_MAX since each title card image includes all languages. - // #region SOH [Enhancement] - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits + // The GI objct segment holds the title card and get-item data. On original hardware, this is a 0x3008-byte + // ZeldaArena allocation regardless of region. Track it in the shadow. void* giRaw = ZELDA_ARENA_MALLOC_DEBUG(0x3008); - N64Mem_AllocSubsidiary(giRaw, N64SizeData_GetGiObjectSegmentSize()); + N64Mem_AllocSubsidiary(giRaw, 0x3008); this->giObjectSegment = (void*)((uintptr_t)giRaw + 8 & ~0xF); // #endregion From b2f80c39b79dc11b4e4eb84f539722b3c25e68f9 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Mon, 11 May 2026 11:02:07 -0700 Subject: [PATCH 49/58] fix(restoration): Use original N64 culling check for actors --- soh/soh/ActorDB.cpp | 2 +- .../HardwareMemoryLimits/N64SizeData.cpp | 134 ++++----------- .../HardwareMemoryLimits/N64SizeData.hpp | 1 - .../HardwareMemoryLimits/n64_arena_sizing.c | 159 +++++++----------- .../debugger/HeapViewerWindow.cpp | 12 +- soh/src/code/z_actor.c | 40 +++-- 6 files changed, 125 insertions(+), 223 deletions(-) diff --git a/soh/soh/ActorDB.cpp b/soh/soh/ActorDB.cpp index a281bfa5f68..bf6e0b50c5f 100644 --- a/soh/soh/ActorDB.cpp +++ b/soh/soh/ActorDB.cpp @@ -22,7 +22,7 @@ struct AddPair { // #endregion }; -// #region [SOH] Enhancement - N64 Memory Model +// #region SOH [Enhancement] - Hardware Memory Limits #define DEFINE_ACTOR_INTERNAL(name, _1, allocType) { #name, name##_InitVars, allocType }, #define DEFINE_ACTOR(name, _1, allocType) { #name, name##_InitVars, allocType }, // #endregion diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp index 870df491fd1..ba34272d0cd 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp @@ -9,10 +9,8 @@ static std::unordered_map sDmaFileSizes; static bool sIsDmaLoaded = false; -static void LoadDmaFileSizes() -{ - if (sIsDmaLoaded) - { +static void LoadDmaFileSizes() { + if (sIsDmaLoaded) { return; } @@ -20,8 +18,7 @@ static void LoadDmaFileSizes() const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile("misc/n64_memory/dma_sizes"); - if (!file || !file->IsLoaded) - { + if (!file || !file->IsLoaded) { SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/dma_sizes from OTR."); return; } @@ -31,8 +28,7 @@ static void LoadDmaFileSizes() reader->SetEndianness(Ship::Endianness::Big); const u32 entryCount = reader->ReadUInt32(); - for (std::size_t i = 0; i < entryCount; ++i) - { + for (std::size_t i = 0; i < entryCount; ++i) { const u32 vromSize = reader->ReadUInt32(); const std::string name = reader->ReadString(); sDmaFileSizes[name] = vromSize; @@ -50,10 +46,8 @@ static void LoadDmaFileSizes() static std::vector sActorOverlaySizes; static bool sIsActorOverlayLoaded = false; -static void LoadActorOverlaySizes() -{ - if (sIsActorOverlayLoaded) - { +static void LoadActorOverlaySizes() { + if (sIsActorOverlayLoaded) { return; } @@ -62,8 +56,7 @@ static void LoadActorOverlaySizes() const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( "misc/n64_memory/actor_overlay_sizes"); - if (!file || !file->IsLoaded) - { + if (!file || !file->IsLoaded) { SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/actor_overlay_sizes from OTR."); return; } @@ -75,8 +68,7 @@ static void LoadActorOverlaySizes() const u32 entryCount = reader->ReadUInt32(); sActorOverlaySizes.resize(entryCount); - for (std::size_t i = 0; i < entryCount; ++i) - { + for (std::size_t i = 0; i < entryCount; ++i) { sActorOverlaySizes.at(i) = reader->ReadUInt32(); } @@ -92,10 +84,8 @@ static void LoadActorOverlaySizes() static std::vector sEffectOverlaySizes; static bool sIsEffectOverlayLoaded = false; -static void LoadEffectOverlaySizes() -{ - if (sIsEffectOverlayLoaded) - { +static void LoadEffectOverlaySizes() { + if (sIsEffectOverlayLoaded) { return; } @@ -104,8 +94,7 @@ static void LoadEffectOverlaySizes() const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( "misc/n64_memory/effect_overlay_sizes"); - if (!file || !file->IsLoaded) - { + if (!file || !file->IsLoaded) { SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/effect_overlay_sizes from OTR."); return; } @@ -117,8 +106,7 @@ static void LoadEffectOverlaySizes() const u32 entryCount = reader->ReadUInt32(); sEffectOverlaySizes.resize(entryCount); - for (std::size_t i = 0; i < entryCount; ++i) - { + for (std::size_t i = 0; i < entryCount; ++i) { sEffectOverlaySizes.at(i) = reader->ReadUInt32(); } @@ -135,10 +123,8 @@ static void LoadEffectOverlaySizes() static std::vector sActorInstanceSizes; static bool sIsActorInstanceLoaded = false; -static void LoadActorInstanceSizes() -{ - if (sIsActorInstanceLoaded) - { +static void LoadActorInstanceSizes() { + if (sIsActorInstanceLoaded) { return; } @@ -147,8 +133,7 @@ static void LoadActorInstanceSizes() const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( "misc/n64_memory/actor_instance_sizes"); - if (!file || !file->IsLoaded) - { + if (!file || !file->IsLoaded) { SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/actor_instance_sizes from OTR."); return; } @@ -160,8 +145,7 @@ static void LoadActorInstanceSizes() const u32 entryCount = reader->ReadUInt32(); sActorInstanceSizes.resize(entryCount); - for (std::size_t i = 0; i < entryCount; ++i) - { + for (std::size_t i = 0; i < entryCount; ++i) { sActorInstanceSizes.at(i) = reader->ReadUInt32(); } @@ -172,12 +156,10 @@ static void LoadActorInstanceSizes() // API // -------------------------------------------------------------------------------------------------------------------- -extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) -{ +extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) { LoadDmaFileSizes(); - if (const auto i = sDmaFileSizes.find(name); i != sDmaFileSizes.end()) - { + if (const auto i = sDmaFileSizes.find(name); i != sDmaFileSizes.end()) { return i->second; } @@ -185,36 +167,30 @@ extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) return 0; } -extern "C" u32 N64SizeData_GetActorOverlaySize(u16 actorId) -{ +extern "C" u32 N64SizeData_GetActorOverlaySize(u16 actorId) { LoadActorOverlaySizes(); - if (actorId < sActorOverlaySizes.size()) - { + if (actorId < sActorOverlaySizes.size()) { return sActorOverlaySizes.at(actorId); } return 0; } -extern "C" u32 N64SizeData_GetEffectOverlaySize(u16 effectType) -{ +extern "C" u32 N64SizeData_GetEffectOverlaySize(u16 effectType) { LoadEffectOverlaySizes(); - if (effectType < sEffectOverlaySizes.size()) - { + if (effectType < sEffectOverlaySizes.size()) { return sEffectOverlaySizes.at(effectType); } return 0; } -extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) -{ +extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) { LoadActorInstanceSizes(); - if (actorId < sActorInstanceSizes.size()) - { + if (actorId < sActorInstanceSizes.size()) { return sActorInstanceSizes.at(actorId); } @@ -230,10 +206,8 @@ extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) static u32 sKaleidoVramSize = 0; static bool sIsKaleidoLoaded = false; -static void LoadKaleidoVramSize() -{ - if (sIsKaleidoLoaded) - { +static void LoadKaleidoVramSize() { + if (sIsKaleidoLoaded) { return; } @@ -242,8 +216,7 @@ static void LoadKaleidoVramSize() const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( "misc/n64_memory/kaleido_vram_size"); - if (!file || !file->IsLoaded) - { + if (!file || !file->IsLoaded) { SPDLOG_ERROR("[N64SizeData] Failed to load misc/n64_memory/kaleido_vram_size from OTR."); return; } @@ -257,8 +230,7 @@ static void LoadKaleidoVramSize() SPDLOG_INFO("[N64SizeData] Kaleido max VRAM size: 0x{:X}.", sKaleidoVramSize); } -extern "C" u32 N64SizeData_GetKaleidoVramSize() -{ +extern "C" u32 N64SizeData_GetKaleidoVramSize() { LoadKaleidoVramSize(); return sKaleidoVramSize; } @@ -272,10 +244,8 @@ extern "C" u32 N64SizeData_GetKaleidoVramSize() static u32 sArenaNodeSize = 0x30; // Default to debug (safe fallback -- over-estimates node overhead) static bool sIsArenaNodeSizeLoaded = false; -static void LoadArenaNodeSize() -{ - if (sIsArenaNodeSizeLoaded) - { +static void LoadArenaNodeSize() { + if (sIsArenaNodeSizeLoaded) { return; } @@ -284,8 +254,7 @@ static void LoadArenaNodeSize() const auto file = Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( "misc/n64_memory/arena_node_size"); - if (!file || !file->IsLoaded) - { + if (!file || !file->IsLoaded) { SPDLOG_WARN("[N64SizeData] Failed to load misc/n64_memory/arena_node_size from OTR, defaulting to 0x{:X}.", sArenaNodeSize); return; @@ -300,46 +269,7 @@ static void LoadArenaNodeSize() SPDLOG_INFO("[N64SizeData] Arena node size: 0x{:X}.", sArenaNodeSize); } -extern "C" u32 N64SizeData_GetArenaNodeSize() -{ +extern "C" u32 N64SizeData_GetArenaNodeSize() { LoadArenaNodeSize(); return sArenaNodeSize; } - -// -------------------------------------------------------------------------------------------------------------------- -// N64 LANGUAGE_MAX -// -// Format: single u32 -- LANGUAGE_MAX for this ROM version (2 for NTSC, 3 for PAL) -// Used to compute the GI object segment size: 0x1000 * LANGUAGE_MAX + 8. -// -------------------------------------------------------------------------------------------------------------------- - -static u32 sLanguageMax = 2; // Default to NTSC -static bool sIsLanguageMaxLoaded = false; - -static void LoadLanguageMax() -{ - if (sIsLanguageMaxLoaded) - { - return; - } - - sIsLanguageMaxLoaded = true; - - const auto file = - Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager()->LoadFile( - "misc/n64_memory/language_max"); - if (!file || !file->IsLoaded) - { - SPDLOG_WARN("[N64SizeData] Failed to load misc/n64_memory/n64_language_max from OTR, defaulting to {}.", - sLanguageMax); - return; - } - - auto stream = std::make_shared(file->Buffer->data(), file->Buffer->size()); - const auto reader = std::make_shared(stream); - reader->SetEndianness(Ship::Endianness::Big); - - sLanguageMax = reader->ReadUInt32(); - - SPDLOG_INFO("[N64SizeData] N64 LANGUAGE_MAX: {}.", sLanguageMax); -} \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp index 26289db113e..51b80d56d62 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp @@ -26,7 +26,6 @@ u32 N64SizeData_GetEffectOverlaySize(u16 effectType); u32 N64SizeData_GetActorInstanceSize(u16 actorId); u32 N64SizeData_GetKaleidoVramSize(void); u32 N64SizeData_GetArenaNodeSize(void); -u32 N64SizeData_GetGiObjectSegmentSize(void); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c index 978d4f61c53..16fa1077b48 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c @@ -5,7 +5,7 @@ #include "global.h" // Declared in z_bgcheck.c but not exposed via header. -s32 BgCheck_IsSpotScene(PlayState * play); +s32 BgCheck_IsSpotScene(PlayState* play); s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); // -------------------------------------------------------------------------------------------------------------------- @@ -56,14 +56,12 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); #define N64_MAP_MARK_DATA_VRAM_SIZE 0x6B60 -static u32 GetMapMarkDataOverlaySize(PlayState* play) -{ +static u32 GetMapMarkDataOverlaySize(PlayState* play) { // Mirrors the condition in z_map_exp.c Map_Init: the dungeon case block's inner guard. // Main dungeons: SCENE_DEKU_TREE (0x00) through SCENE_ICE_CAVERN (0x09) // Boss rooms: SCENE_DEKU_TREE_BOSS (0x11) through SCENE_SHADOW_TEMPLE_BOSS (0x18) if (play->sceneNum <= SCENE_ICE_CAVERN || - (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) - { + (play->sceneNum >= SCENE_DEKU_TREE_BOSS && play->sceneNum <= SCENE_SHADOW_TEMPLE_BOSS)) { return N64_MAP_MARK_DATA_VRAM_SIZE; } @@ -88,59 +86,54 @@ static u32 GetMapMarkDataOverlaySize(PlayState* play) // -------------------------------------------------------------------------------------------------------------------- // DMA file name for a single-file indoor skybox (1 tex + 1 pal). -typedef struct -{ +typedef struct { const char* texName; const char* palName; } SkyboxDmaEntry; // Indexed by skybox ID. NULL texName means the ID isn't a single-file indoor skybox (handled separately). static const SkyboxDmaEntry sSkyboxDmaTable[] = { - [SKYBOX_NONE] = {NULL, NULL}, - [SKYBOX_NORMAL_SKY] = {NULL, NULL}, // gNormalSkyFiles, hardcoded - [SKYBOX_BAZAAR] = {"vr_SP1a_static", "vr_SP1a_pal_static"}, - [SKYBOX_OVERCAST_SUNSET] = {NULL, NULL}, // same as NORMAL_SKY - [SKYBOX_MARKET_ADULT] = {"vr_RUVR_static", "vr_RUVR_pal_static"}, - [SKYBOX_CUTSCENE_MAP] = {NULL, NULL}, // Two tex files, handled separately - [SKYBOX_HOUSE_LINK] = {"vr_LHVR_static", "vr_LHVR_pal_static"}, - [SKYBOX_MARKET_CHILD_DAY] = {"vr_MDVR_static", "vr_MDVR_pal_static"}, - [SKYBOX_MARKET_CHILD_NIGHT] = {"vr_MNVR_static", "vr_MNVR_pal_static"}, - [SKYBOX_HAPPY_MASK_SHOP] = {"vr_FCVR_static", "vr_FCVR_pal_static"}, - [SKYBOX_HOUSE_KNOW_IT_ALL_BROTHERS] = {"vr_KHVR_static", "vr_KHVR_pal_static"}, - [SKYBOX_HOUSE_OF_TWINS] = {"vr_K3VR_static", "vr_K3VR_pal_static"}, - [SKYBOX_STABLES] = {"vr_MLVR_static", "vr_MLVR_pal_static"}, - [SKYBOX_HOUSE_KAKARIKO] = {"vr_KKRVR_static", "vr_KKRVR_pal_static"}, - [SKYBOX_KOKIRI_SHOP] = {"vr_KSVR_static", "vr_KSVR_pal_static"}, - [SKYBOX_GORON_SHOP] = {"vr_GLVR_static", "vr_GLVR_pal_static"}, - [SKYBOX_ZORA_SHOP] = {"vr_ZRVR_static", "vr_ZRVR_pal_static"}, - [SKYBOX_POTION_SHOP_KAKARIKO] = {"vr_DGVR_static", "vr_DGVR_pal_static"}, - [SKYBOX_POTION_SHOP_MARKET] = {"vr_ALVR_static", "vr_ALVR_pal_static"}, - [SKYBOX_BOMBCHU_SHOP] = {"vr_NSVR_static", "vr_NSVR_pal_static"}, - [SKYBOX_HOUSE_RICHARD] = {"vr_IPVR_static", "vr_IPVR_pal_static"}, - [SKYBOX_HOUSE_IMPA] = {"vr_LBVR_static", "vr_LBVR_pal_static"}, - [SKYBOX_TENT] = {"vr_TTVR_static", "vr_TTVR_pal_static"}, - [SKYBOX_HOUSE_MIDO] = {"vr_K4VR_static", "vr_K4VR_pal_static"}, - [SKYBOX_HOUSE_SARIA] = {"vr_K5VR_static", "vr_K5VR_pal_static"}, - [SKYBOX_HOUSE_ALLEY] = {"vr_KR3VR_static", "vr_KR3VR_pal_static"}, + [SKYBOX_NONE] = { NULL, NULL }, + [SKYBOX_NORMAL_SKY] = { NULL, NULL }, // gNormalSkyFiles, hardcoded + [SKYBOX_BAZAAR] = { "vr_SP1a_static", "vr_SP1a_pal_static" }, + [SKYBOX_OVERCAST_SUNSET] = { NULL, NULL }, // same as NORMAL_SKY + [SKYBOX_MARKET_ADULT] = { "vr_RUVR_static", "vr_RUVR_pal_static" }, + [SKYBOX_CUTSCENE_MAP] = { NULL, NULL }, // Two tex files, handled separately + [SKYBOX_HOUSE_LINK] = { "vr_LHVR_static", "vr_LHVR_pal_static" }, + [SKYBOX_MARKET_CHILD_DAY] = { "vr_MDVR_static", "vr_MDVR_pal_static" }, + [SKYBOX_MARKET_CHILD_NIGHT] = { "vr_MNVR_static", "vr_MNVR_pal_static" }, + [SKYBOX_HAPPY_MASK_SHOP] = { "vr_FCVR_static", "vr_FCVR_pal_static" }, + [SKYBOX_HOUSE_KNOW_IT_ALL_BROTHERS] = { "vr_KHVR_static", "vr_KHVR_pal_static" }, + [SKYBOX_HOUSE_OF_TWINS] = { "vr_K3VR_static", "vr_K3VR_pal_static" }, + [SKYBOX_STABLES] = { "vr_MLVR_static", "vr_MLVR_pal_static" }, + [SKYBOX_HOUSE_KAKARIKO] = { "vr_KKRVR_static", "vr_KKRVR_pal_static" }, + [SKYBOX_KOKIRI_SHOP] = { "vr_KSVR_static", "vr_KSVR_pal_static" }, + [SKYBOX_GORON_SHOP] = { "vr_GLVR_static", "vr_GLVR_pal_static" }, + [SKYBOX_ZORA_SHOP] = { "vr_ZRVR_static", "vr_ZRVR_pal_static" }, + [SKYBOX_POTION_SHOP_KAKARIKO] = { "vr_DGVR_static", "vr_DGVR_pal_static" }, + [SKYBOX_POTION_SHOP_MARKET] = { "vr_ALVR_static", "vr_ALVR_pal_static" }, + [SKYBOX_BOMBCHU_SHOP] = { "vr_NSVR_static", "vr_NSVR_pal_static" }, + [SKYBOX_HOUSE_RICHARD] = { "vr_IPVR_static", "vr_IPVR_pal_static" }, + [SKYBOX_HOUSE_IMPA] = { "vr_LBVR_static", "vr_LBVR_pal_static" }, + [SKYBOX_TENT] = { "vr_TTVR_static", "vr_TTVR_pal_static" }, + [SKYBOX_HOUSE_MIDO] = { "vr_K4VR_static", "vr_K4VR_pal_static" }, + [SKYBOX_HOUSE_SARIA] = { "vr_K5VR_static", "vr_K5VR_pal_static" }, + [SKYBOX_HOUSE_ALLEY] = { "vr_KR3VR_static", "vr_KR3VR_pal_static" }, }; -static u32 GetN64SkyboxTextureSize(s16 skyboxId) -{ - if (skyboxId == SKYBOX_NONE) - { +static u32 GetN64SkyboxTextureSize(s16 skyboxId) { + if (skyboxId == SKYBOX_NONE) { return 0; } // NORMAL_SKY and OVERCAST_SUNSET both load 2 texture banks + 2 palettes from the vr_fine/vr_cloud files. // All 16 banks are exactly 0xC000 and all 16 palettes are exactly 0x100, so the total is constant. - if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) - { + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { return 2 * 0xC000 + 2 * 0x100; } // CUTSCENE_MAP loads two different texture files + 2 palette copies. - if (skyboxId == SKYBOX_CUTSCENE_MAP) - { + if (skyboxId == SKYBOX_CUTSCENE_MAP) { const u32 tex0 = N64SizeData_GetDmaFileSize("vr_holy0_static"); const u32 tex1 = N64SizeData_GetDmaFileSize("vr_holy1_static"); const u32 pal = N64SizeData_GetDmaFileSize("vr_holy0_pal_static"); @@ -148,11 +141,9 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) } // Indoor skyboxes: 1 texture + 1 palette, looked up from the DMA blob. - if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) - { + if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) { const SkyboxDmaEntry* entry = &sSkyboxDmaTable[skyboxId]; - if (entry->texName != NULL) - { + if (entry->texName != NULL) { const u32 tex = N64SizeData_GetDmaFileSize(entry->texName); const u32 pal = N64SizeData_GetDmaFileSize(entry->palName); return tex + pal; @@ -172,27 +163,20 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) // Gfx is 8 bytes on N64, Vtx is 0x10. // -------------------------------------------------------------------------------------------------------------------- -static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) -{ - if (skyboxId == SKYBOX_NONE) - { +static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) { + if (skyboxId == SKYBOX_NONE) { *outDlistSize = 0; *outVtxSize = 0; return; } - if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) - { + if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET) { *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; *outVtxSize = 5 * 32 * N64_SIZEOF_VTX; - } - else if (skyboxId == SKYBOX_CUTSCENE_MAP) - { + } else if (skyboxId == SKYBOX_CUTSCENE_MAP) { *outDlistSize = 12 * 150 * N64_SIZEOF_GFX; *outVtxSize = 6 * 32 * N64_SIZEOF_VTX; - } - else - { + } else { // Indoor skyboxes: SKYBOX_DRAW_256_4FACE or SKYBOX_DRAW_256_3FACE, both use the same allocation. *outDlistSize = 8 * 150 * N64_SIZEOF_GFX; *outVtxSize = 8 * 32 * N64_SIZEOF_VTX; @@ -214,31 +198,26 @@ static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVt // bgcheck_memSize and sizeof(CollisionContext). // -------------------------------------------------------------------------------------------------------------------- -static u32 GetBgCheckMemSize(PlayState* play) -{ +static u32 GetBgCheckMemSize(PlayState* play) { const s16 sceneNum = play->sceneNum; - if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) - { + if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) { return sceneNum == SCENE_STABLE ? 0x3520 : 0x4E20; } - if (BgCheck_IsSpotScene(play)) - { + if (BgCheck_IsSpotScene(play)) { return 0xF000; } u32 customMemSize = 0; - if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) - { + if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) { return customMemSize; } return 0x1CC00; } -static u32 GetBgCheckThaTotal(PlayState* play) -{ +static u32 GetBgCheckThaTotal(PlayState* play) { return GetBgCheckMemSize(play) - N64_SIZEOF_COLLISION_CONTEXT; } @@ -246,17 +225,14 @@ static u32 GetBgCheckThaTotal(PlayState* play) // Object bank size (mirrors z_scene.c Object_InitBlank) // -------------------------------------------------------------------------------------------------------------------- -static u32 GetObjectBankSize(PlayState* play) -{ +static u32 GetObjectBankSize(PlayState* play) { const s16 sceneNum = play->sceneNum; - if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) - { + if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) { return 1177600; } if (sceneNum == SCENE_SPIRIT_TEMPLE_BOSS || sceneNum == SCENE_CHAMBER_OF_THE_SAGES || - sceneNum == SCENE_GANONDORF_BOSS) - { + sceneNum == SCENE_GANONDORF_BOSS) { return 1075200; } @@ -275,16 +251,13 @@ static const char* sElfMsgDmaNames[] = { "elf_message_ydan", }; -static u32 GetElfMessageSize(PlayState* play) -{ - if (play->cUpElfMsgs == NULL) - { +static u32 GetElfMessageSize(PlayState* play) { + if (play->cUpElfMsgs == NULL) { return 0; } const u8 elfMsgNum = N64Mem_GetElfMsgNum(); - if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) - { + if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) { LUSLOG_WARN("[ArenaSizing] elfMsg: cUpElfMsgs non-NULL but elfMsgNum=%d out of range", elfMsgNum); return 0; } @@ -296,25 +269,20 @@ static u32 GetElfMessageSize(PlayState* play) // Room buffer max size (mirrors func_80096FE8 logic) // -------------------------------------------------------------------------------------------------------------------- -static u32 GetMaxRoomSize(PlayState* play) -{ - u32 maxRoomSize = 0; +static uintptr_t GetMaxRoomSize(PlayState* play) { + uintptr_t maxRoomSize = 0; - for (size_t i = 0; i < play->numRooms; ++i) - { + for (size_t i = 0; i < play->numRooms; ++i) { const uintptr_t roomSize = play->roomList[i].vromEnd - play->roomList[i].vromStart; - if (roomSize > maxRoomSize) - { + if (roomSize > maxRoomSize) { maxRoomSize = roomSize; } } - if (play->transiActorCtx.numActors != 0) - { + if (play->transiActorCtx.numActors != 0) { const TransitionActorEntry* transitionActor = &play->transiActorCtx.list[0]; - for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) - { + for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) { const s8 frontRoom = transitionActor->sides[0].room; const s8 backRoom = transitionActor->sides[1].room; const uintptr_t frontSize = frontRoom < 0 @@ -325,8 +293,7 @@ static u32 GetMaxRoomSize(PlayState* play) : play->roomList[backRoom].vromEnd - play->roomList[backRoom].vromStart; const uintptr_t cumulSize = frontRoom != backRoom ? frontSize + backSize : frontSize; - if (cumulSize > maxRoomSize) - { + if (cumulSize > maxRoomSize) { maxRoomSize = cumulSize; } @@ -341,8 +308,7 @@ static u32 GetMaxRoomSize(PlayState* play) // Main computation // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) -{ +u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { // Each THA consumer is allocated via GAME_STATE_ALLOC -> THA_AllocTailAlign16, which consumes ALIGN16(size) bytes. // We must align each consumer individually before summing; aligning the sum would under-count when individual // sizes are not 16-byte aligned (common for DMA file sizes). BgCheck is the exception -- its internal allocations @@ -377,7 +343,7 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { const u32 objBank = GetObjectBankSize(play); const u32 sceneFile = N64SizeData_GetDmaFileSize(play->loadedScene->sceneFile.fileName); - const u32 roomBuf = GetMaxRoomSize(play); + const uintptr_t roomBuf = GetMaxRoomSize(play); total += ALIGN16(objBank); total += ALIGN16(sceneFile); total += ALIGN16(roomBuf); @@ -435,8 +401,7 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) LUSLOG_INFO("[ArenaSizing] total=0x%X, arena=0x%X (budget=0x%X)", total, N64_THA_BUDGET - total, (u32)N64_THA_BUDGET); - if (total >= N64_THA_BUDGET) - { + if (total >= N64_THA_BUDGET) { return 0; } diff --git a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp index f2a18bc6073..c7901cfd5ab 100644 --- a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp +++ b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp @@ -23,7 +23,7 @@ extern "C" { struct BlockInfo { u32 offset; - u32 size; + std::size_t size; bool isFree; u8 type; // N64MEM_BLOCK_* (shadow only) s16 actorId; // Original actor ID (shadow only), -1 if unknown @@ -37,12 +37,12 @@ static std::vector sBlocks; static std::vector sDisplayOrder; // Indices into sBlocks: pinned first, then unpinned static u32 sPinnedCount = 0; static std::set> sPinnedBlocks; // {actorId, blockType} pairs -static u32 sAllocTotal = 0; -static u32 sFreeTotal = 0; -static u32 sLargestFree = 0; +static std::size_t sAllocTotal = 0; +static std::size_t sFreeTotal = 0; +static std::size_t sLargestFree = 0; static u32 sNodeCount = 0; -static u32 sArenaSize = 0; -static u32 sPreviousAlloc = 0; +static std::size_t sArenaSize = 0; +static std::size_t sPreviousAlloc = 0; static u32 sCycleCount = 0; static s32 sLastDelta = 0; static bool sIsShadow = false; diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 9f321edea22..c2ecc56147b 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2661,10 +2661,20 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { actor = actor->next; } else if (actor->update == NULL) { // #region SOH [Enhancement] - Hardware Memory Limits - // Force the immediate Actor_Delete path for killed actors so the shadow arena's free ordering stays - // consistent with original hardware behavior. - if (N64Mem_IsActive() || !actor->isDrawn) { - // #endregion + // Same draw-distance distinction as the room-change kill path. Use N64's culling check to match the + // immediate vs. deferred free ordering that shapes heap coalescing. + if (N64Mem_IsActive()) { + const s32 n64WouldDraw = actor->init == NULL && actor->draw != NULL && + (actor->flags & ACTOR_FLAG_DRAW_CULLING_DISABLED || + func_800314D4(play, actor, &actor->projectedPos, actor->projectedW)); + if (!n64WouldDraw) { + actor = Actor_Delete(&play->actorCtx, actor, play); + } else { + Actor_Destroy(actor, play); + actor = actor->next; + } + } else if (!actor->isDrawn) { + // #endregion actor = Actor_Delete(&play->actorCtx, actor, play); } else { Actor_Destroy(actor, play); @@ -3203,22 +3213,20 @@ void func_80031B14(PlayState* play, ActorContext* actorCtx) { while (actor != NULL) { if ((actor->room >= 0) && (actor->room != play->roomCtx.curRoom.num) && (actor->room != play->roomCtx.prevRoom.num)) { - // #region [SOH] Enhancement - N64 Memory Model + // #region SOH [Enhancement] - Hardware Memory Limits // // On N64, departing-room actors take one of two free paths depending on whether they were drawn on the // previous frame: - // - NOT drawn (off-screen on N64) -> Immediate Actor_Delete -- memory freed this frame - // - Drawn (on-screen on N64) -> Deferred Actor_Kill -- memory freed next frame + // - NOT drawn (off-screen on N64): Immediate Actor_Delete -- memory freed this frame + // - Drawn (on-screen on N64): Deferred Actor_Kill -- memory freed next frame // - // SoH's extended draw distance (Ship_CalcShouldDrawAndUpdate) keeps more actors drawn than N64 would, - // pushing them into the deferred path and changing the free ordering/coalescing geometry. - // - // When the N64 Memory Model is active, use N64's original culling check (func_800314B0) to decide the - // path instead of SoH's isDrawn flag. + // SoH's extended draw distance keeps more actors drawn than N64 would, pushing them into the deferred + // path and changing the free ordering that shapes heap coalescing. Use N64's original culling check + // to decide the path. if (N64Mem_IsActive()) { - const s32 n64WouldDraw = actor->init == NULL && actor->draw != NULL && ( - actor->flags & ACTOR_FLAG_DRAW_CULLING_DISABLED || func_800314D4( - play, actor, &actor->projectedPos, actor->projectedW)); + const bool n64WouldDraw = actor->init == NULL && actor->draw != NULL && + (actor->flags & ACTOR_FLAG_DRAW_CULLING_DISABLED || + func_800314D4(play, actor, &actor->projectedPos, actor->projectedW)); if (!n64WouldDraw) { actor = Actor_Delete(actorCtx, actor, play); } else { @@ -3227,7 +3235,7 @@ void func_80031B14(PlayState* play, ActorContext* actorCtx) { actor = actor->next; } } else if (!actor->isDrawn) { - // #endregion + // #endregion actor = Actor_Delete(actorCtx, actor, play); } else { Actor_Kill(actor); From 4d5421403f2b625ad62165d14109b58fc854cf87 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Mon, 11 May 2026 11:32:50 -0700 Subject: [PATCH 50/58] chore: Remove accidental SaveManager change --- soh/soh/SaveManager.cpp | 67 +++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 43 deletions(-) diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 876f4946b87..89224c0ab57 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -504,33 +504,29 @@ void SaveManager::StartupCheckAndInitMeta(int fileNum) { output.close(); saveMtx.unlock(); } - - if (metaSaveBlock["sections"].contains("sohStats") && metaSaveBlock["sections"]["sohStats"].contains("data")) { - s16 major = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; - s16 minor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; - s16 patch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; - // block loading outdated rando save - if (!(major == gBuildVersionMajor && minor == gBuildVersionMinor && patch == gBuildVersionPatch)) { - std::string newFileName = - Ship::Context::GetPathRelativeToAppDirectory("Save") + - ("/file" + std::to_string(fileNum + 1) + "-" + std::to_string(GetUnixTimestamp()) + ".bak"); + s16 major = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; + s16 minor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; + s16 patch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; + // block loading outdated rando save + if (!(major == gBuildVersionMajor && minor == gBuildVersionMinor && patch == gBuildVersionPatch)) { + std::string newFileName = + Ship::Context::GetPathRelativeToAppDirectory("Save") + + ("/file" + std::to_string(fileNum + 1) + "-" + std::to_string(GetUnixTimestamp()) + ".bak"); #if defined(__SWITCH__) || defined(__WIIU__) - copy_file(fileName.c_str(), newFileName.c_str()); - std::filesystem::remove(fileName); + copy_file(fileName.c_str(), newFileName.c_str()); + std::filesystem::remove(fileName); #else - std::filesystem::rename(fileName, newFileName); + std::filesystem::rename(fileName, newFileName); #endif - SohGui::RegisterPopup("Outdated Randomizer Save", - "The SoH version in the file in slot " + std::to_string(fileNum + 1) + - " does not match the currently running version.\n" + - "Non-matching rando saves are unsupported, and the file has been renamed to\n" + - " " + newFileName + "\n" + - "If this was not in error, the file should be deleted."); - return; - } + SohGui::RegisterPopup("Outdated Randomizer Save", + "The SoH version in the file in slot " + std::to_string(fileNum + 1) + + " does not match the currently running version.\n" + + "Non-matching rando saves are unsupported, and the file has been renamed to\n" + + " " + newFileName + "\n" + + "If this was not in error, the file should be deleted."); + return; } } - bool isRando = metaSaveBlock["fileType"] == FILE_TYPE_SAVE_RANDO; fileMetaInfo[fileNum].valid = true; @@ -577,19 +573,12 @@ void SaveManager::StartupCheckAndInitMeta(int fileNum) { fileMetaInfo[fileNum].requiresOriginal = randoBlock["masterQuestDungeonCount"] < 12; } - if (metaSaveBlock["sections"].contains("sohStats") && metaSaveBlock["sections"]["sohStats"].contains("data")) { - fileMetaInfo[fileNum].buildVersionMajor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; - fileMetaInfo[fileNum].buildVersionMinor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; - fileMetaInfo[fileNum].buildVersionPatch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; - SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].buildVersion, - metaSaveBlock["sections"]["sohStats"]["data"]["buildVersion"], - ARRAY_COUNT(fileMetaInfo[fileNum].buildVersion)); - } else { - fileMetaInfo[fileNum].buildVersionMajor = 0; - fileMetaInfo[fileNum].buildVersionMinor = 0; - fileMetaInfo[fileNum].buildVersionPatch = 0; - fileMetaInfo[fileNum].buildVersion[0] = '\0'; - } + fileMetaInfo[fileNum].buildVersionMajor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; + fileMetaInfo[fileNum].buildVersionMinor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; + fileMetaInfo[fileNum].buildVersionPatch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; + SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].buildVersion, + metaSaveBlock["sections"]["sohStats"]["data"]["buildVersion"], + ARRAY_COUNT(fileMetaInfo[fileNum].buildVersion)); } void SaveManager::InitMeta(int fileNum) { @@ -1165,15 +1154,7 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int se sectionHandlerPair.second.func(saveContext, sectionID, true); } } else { - if (auto it = sectionSaveHandlers.find(sectionID); it == sectionSaveHandlers.end()) { - SPDLOG_ERROR("[SaveManager] SaveFileThreaded: sectionID {} not found in sectionSaveHandlers", sectionID); - delete saveContext; - saveMtx.unlock(); - return; - } - SaveFuncInfo svi = sectionSaveHandlers.find(sectionID)->second; - auto& sectionName = svi.name; auto sectionVersion = svi.version; // If section has a parentSection, it is a subsection. Load parentSection version and set sectionBlock to parent From 3822c309ce58943e1b331267379881dc5533d465 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Mon, 11 May 2026 22:50:34 -0700 Subject: [PATCH 51/58] chore: More descriptive comments about how culling is handled --- soh/src/code/z_actor.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index c2ecc56147b..45f6651e5e5 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2661,10 +2661,15 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { actor = actor->next; } else if (actor->update == NULL) { // #region SOH [Enhancement] - Hardware Memory Limits - // Same draw-distance distinction as the room-change kill path. Use N64's culling check to match the - // immediate vs. deferred free ordering that shapes heap coalescing. + // + // On original hardware, isDrawn reflects N64's draw distance from the previous frame. SoH's extended + // draw distance (Ship_CalcShouldDrawAndUpdate) sets isDrawn more permissively, so actors that N64 + // would consider off-screen appear drawn on SoH. + // + // Recalculate the draw decision using N64's culling check (func_800314D4) so the immediate vs. + // deferred free path (ergo, heap coalescing pattern) matches original hardware. if (N64Mem_IsActive()) { - const s32 n64WouldDraw = actor->init == NULL && actor->draw != NULL && + const bool n64WouldDraw = actor->init == NULL && actor->draw != NULL && (actor->flags & ACTOR_FLAG_DRAW_CULLING_DISABLED || func_800314D4(play, actor, &actor->projectedPos, actor->projectedW)); if (!n64WouldDraw) { @@ -2674,7 +2679,7 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { actor = actor->next; } } else if (!actor->isDrawn) { - // #endregion + // #endregion actor = Actor_Delete(&play->actorCtx, actor, play); } else { Actor_Destroy(actor, play); @@ -3215,14 +3220,12 @@ void func_80031B14(PlayState* play, ActorContext* actorCtx) { (actor->room != play->roomCtx.prevRoom.num)) { // #region SOH [Enhancement] - Hardware Memory Limits // - // On N64, departing-room actors take one of two free paths depending on whether they were drawn on the - // previous frame: - // - NOT drawn (off-screen on N64): Immediate Actor_Delete -- memory freed this frame - // - Drawn (on-screen on N64): Deferred Actor_Kill -- memory freed next frame + // On original hardware, isDrawn reflects N64's draw distance from the previous frame. SoH's extended + // draw distance (Ship_CalcShouldDrawAndUpdate) sets isDrawn more permissively, so actors that N64 + // would consider off-screen appear drawn on SoH. // - // SoH's extended draw distance keeps more actors drawn than N64 would, pushing them into the deferred - // path and changing the free ordering that shapes heap coalescing. Use N64's original culling check - // to decide the path. + // Recalculate the draw decision using N64's culling check (func_800314D4) so the immediate vs. + // deferred free path (ergo, heap coalescing pattern) matches original hardware. if (N64Mem_IsActive()) { const bool n64WouldDraw = actor->init == NULL && actor->draw != NULL && (actor->flags & ACTOR_FLAG_DRAW_CULLING_DISABLED || From 3023fc8b2f16c05f112c733789ca052aa2aa03d3 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Tue, 12 May 2026 00:11:52 -0700 Subject: [PATCH 52/58] fix(save): Guard sohStats access in StartupCheckAndInitMeta --- soh/soh/SaveManager.cpp | 67 ++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/soh/soh/SaveManager.cpp b/soh/soh/SaveManager.cpp index 89224c0ab57..876f4946b87 100644 --- a/soh/soh/SaveManager.cpp +++ b/soh/soh/SaveManager.cpp @@ -504,29 +504,33 @@ void SaveManager::StartupCheckAndInitMeta(int fileNum) { output.close(); saveMtx.unlock(); } - s16 major = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; - s16 minor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; - s16 patch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; - // block loading outdated rando save - if (!(major == gBuildVersionMajor && minor == gBuildVersionMinor && patch == gBuildVersionPatch)) { - std::string newFileName = - Ship::Context::GetPathRelativeToAppDirectory("Save") + - ("/file" + std::to_string(fileNum + 1) + "-" + std::to_string(GetUnixTimestamp()) + ".bak"); + + if (metaSaveBlock["sections"].contains("sohStats") && metaSaveBlock["sections"]["sohStats"].contains("data")) { + s16 major = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; + s16 minor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; + s16 patch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; + // block loading outdated rando save + if (!(major == gBuildVersionMajor && minor == gBuildVersionMinor && patch == gBuildVersionPatch)) { + std::string newFileName = + Ship::Context::GetPathRelativeToAppDirectory("Save") + + ("/file" + std::to_string(fileNum + 1) + "-" + std::to_string(GetUnixTimestamp()) + ".bak"); #if defined(__SWITCH__) || defined(__WIIU__) - copy_file(fileName.c_str(), newFileName.c_str()); - std::filesystem::remove(fileName); + copy_file(fileName.c_str(), newFileName.c_str()); + std::filesystem::remove(fileName); #else - std::filesystem::rename(fileName, newFileName); + std::filesystem::rename(fileName, newFileName); #endif - SohGui::RegisterPopup("Outdated Randomizer Save", - "The SoH version in the file in slot " + std::to_string(fileNum + 1) + - " does not match the currently running version.\n" + - "Non-matching rando saves are unsupported, and the file has been renamed to\n" + - " " + newFileName + "\n" + - "If this was not in error, the file should be deleted."); - return; + SohGui::RegisterPopup("Outdated Randomizer Save", + "The SoH version in the file in slot " + std::to_string(fileNum + 1) + + " does not match the currently running version.\n" + + "Non-matching rando saves are unsupported, and the file has been renamed to\n" + + " " + newFileName + "\n" + + "If this was not in error, the file should be deleted."); + return; + } } } + bool isRando = metaSaveBlock["fileType"] == FILE_TYPE_SAVE_RANDO; fileMetaInfo[fileNum].valid = true; @@ -573,12 +577,19 @@ void SaveManager::StartupCheckAndInitMeta(int fileNum) { fileMetaInfo[fileNum].requiresOriginal = randoBlock["masterQuestDungeonCount"] < 12; } - fileMetaInfo[fileNum].buildVersionMajor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; - fileMetaInfo[fileNum].buildVersionMinor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; - fileMetaInfo[fileNum].buildVersionPatch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; - SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].buildVersion, - metaSaveBlock["sections"]["sohStats"]["data"]["buildVersion"], - ARRAY_COUNT(fileMetaInfo[fileNum].buildVersion)); + if (metaSaveBlock["sections"].contains("sohStats") && metaSaveBlock["sections"]["sohStats"].contains("data")) { + fileMetaInfo[fileNum].buildVersionMajor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMajor"]; + fileMetaInfo[fileNum].buildVersionMinor = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionMinor"]; + fileMetaInfo[fileNum].buildVersionPatch = metaSaveBlock["sections"]["sohStats"]["data"]["buildVersionPatch"]; + SohUtils::CopyStringToCharArray(fileMetaInfo[fileNum].buildVersion, + metaSaveBlock["sections"]["sohStats"]["data"]["buildVersion"], + ARRAY_COUNT(fileMetaInfo[fileNum].buildVersion)); + } else { + fileMetaInfo[fileNum].buildVersionMajor = 0; + fileMetaInfo[fileNum].buildVersionMinor = 0; + fileMetaInfo[fileNum].buildVersionPatch = 0; + fileMetaInfo[fileNum].buildVersion[0] = '\0'; + } } void SaveManager::InitMeta(int fileNum) { @@ -1154,7 +1165,15 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int se sectionHandlerPair.second.func(saveContext, sectionID, true); } } else { + if (auto it = sectionSaveHandlers.find(sectionID); it == sectionSaveHandlers.end()) { + SPDLOG_ERROR("[SaveManager] SaveFileThreaded: sectionID {} not found in sectionSaveHandlers", sectionID); + delete saveContext; + saveMtx.unlock(); + return; + } + SaveFuncInfo svi = sectionSaveHandlers.find(sectionID)->second; + auto& sectionName = svi.name; auto sectionVersion = svi.version; // If section has a parentSection, it is a subsection. Load parentSection version and set sectionBlock to parent From 6ce7a62b1e2fc992949a9f8b207d87718624e397 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 14 May 2026 23:51:03 -0700 Subject: [PATCH 53/58] chore: Clang format --- .../HardwareMemoryLimits.cpp | 297 +++++++----------- .../HardwareMemoryLimits/N64SizeData.cpp | 2 +- .../HardwareMemoryLimits/N64SizeData.hpp | 4 - .../HardwareMemoryLimits/n64_arena_sizing.h | 2 +- .../HardwareMemoryLimits/n64_shadow_arena.c | 133 +++----- .../HardwareMemoryLimits/n64_shadow_arena.h | 5 +- 6 files changed, 154 insertions(+), 289 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp index 73dc8a3d0ce..a7ec1f104c3 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp @@ -46,8 +46,7 @@ static std::unordered_map sActorOriginalIds; // Block metadata for the heap viewer. Keyed by shadow DATA offset (node offset + arena->nodeSize). // Populated in each Alloc function, erased in each Free, cleared in N64Mem_Reset. -struct BlockMeta -{ +struct BlockMeta { u8 type; s16 actorId; }; @@ -66,8 +65,7 @@ static s32 sInsideRandomizedInit = 0; // Returns the actor ID to use for size lookups. If an original actor ID override is set (enemy randomizer active), // returns that; otherwise returns the passed actorId unchanged. -static u16 ResolveSizeActorId(s16 actorId) -{ +static u16 ResolveSizeActorId(s16 actorId) { return sOriginalActorId >= 0 ? static_cast(sOriginalActorId) : static_cast(actorId); } @@ -77,8 +75,7 @@ static u16 ResolveSizeActorId(s16 actorId) static s32 sTraceEnabled = 0; -static void LogShadowState(const char* context) -{ +static void LogShadowState(const char* context) { u32 maxFree = 0; u32 totalFree = 0; u32 totalAlloc = 0; @@ -89,19 +86,15 @@ static void LogShadowState(const char* context) maxFree); } -static void TraceAlloc(const char* tag, u32 id, u32 size) -{ - if (sTraceEnabled) - { +static void TraceAlloc(const char* tag, u32 id, u32 size) { + if (sTraceEnabled) { u32 consumed = (size + 0xF & ~0xF) + sShadow.nodeSize; SPDLOG_TRACE("[N64Trace] +{} id=0x{:X} sz=0x{:X} cost=0x{:X}", tag, id, size, consumed); } } -static void TraceFree(const char* tag, u32 id) -{ - if (sTraceEnabled) - { +static void TraceFree(const char* tag, u32 id) { + if (sTraceEnabled) { SPDLOG_TRACE("[N64Trace] -{} id=0x{:X}", tag, id); } } @@ -112,10 +105,8 @@ static void TraceFree(const char* tag, u32 id) static s32 sGraveyardTransitionCount = 0; -void N64Mem_BenchmarkTransition(PlayState* play) -{ - if (!sIsActive || play->sceneNum != SCENE_GRAVEYARD) - { +void N64Mem_BenchmarkTransition(PlayState* play) { + if (!sIsActive || play->sceneNum != SCENE_GRAVEYARD) { return; } @@ -134,26 +125,21 @@ void N64Mem_BenchmarkTransition(PlayState* play) // Lifecycle // -------------------------------------------------------------------------------------------------------------------- -void N64Mem_StoreThaRemainder(u32 sohRemainder) -{ +void N64Mem_StoreThaRemainder(u32 sohRemainder) { sSohThaRemainder = sohRemainder; } -void N64Mem_StoreElfMsgNum(u8 num) -{ +void N64Mem_StoreElfMsgNum(u8 num) { sElfMsgNum = num; } -u8 N64Mem_GetElfMsgNum() -{ +u8 N64Mem_GetElfMsgNum() { return sElfMsgNum; } -void N64Mem_Reset(PlayState* play) -{ +void N64Mem_Reset(PlayState* play) { // Log shadow state before teardown for per-scene diagnostics. - if (sIsActive && sShadow.buffer) - { + if (sIsActive && sShadow.buffer) { u32 maxFree = 0; u32 totalFree = 0; u32 totalAlloc = 0; @@ -162,8 +148,7 @@ void N64Mem_Reset(PlayState* play) SPDLOG_INFO("[HardwareMemoryLimits] Teardown: alloc=0x{:X}, free=0x{:X}, largest=0x{:X}, ptrs={}", totalAlloc, totalFree, maxFree, sShadowMap.size()); - if (sGraveyardTransitionCount > 0) - { + if (sGraveyardTransitionCount > 0) { SPDLOG_INFO("[N64Benchmark] RESULT transitions={}", sGraveyardTransitionCount); } } @@ -180,24 +165,20 @@ void N64Mem_Reset(PlayState* play) sOriginalActorId = -1; sInsideRandomizedInit = 0; - for (u32& sOverlayShadow : sOverlayShadows) - { + for (u32& sOverlayShadow : sOverlayShadows) { sOverlayShadow = SHADOW_NULL; } - for (u32& sEffectOverlayShadow : sEffectOverlayShadows) - { + for (u32& sEffectOverlayShadow : sEffectOverlayShadows) { sEffectOverlayShadow = SHADOW_NULL; } sIsActive = CVAR_VALUE; - if (sIsActive && play != nullptr) - { + if (sIsActive && play != nullptr) { // Compute N64-equivalent arena size from first principles. All per-version constants are derived from the OTR // blob, which was extracted from the user's specific ROM version. u32 shadowArenaSize = ArenaSizing_ComputeN64ArenaSize(play); - if (shadowArenaSize == 0) - { + if (shadowArenaSize == 0) { SPDLOG_ERROR("[HardwareMemoryLimits] Arena sizing returned 0 -- THA budget exceeded, disabling."); sIsActive = 0; return; @@ -211,28 +192,22 @@ void N64Mem_Reset(PlayState* play) } } -s32 N64Mem_IsActive() -{ +s32 N64Mem_IsActive() { return sIsActive; } -ShadowArena* N64Mem_GetShadowArena() -{ +ShadowArena* N64Mem_GetShadowArena() { return &sShadow; } -void N64Mem_LogState(const char* context) -{ - if (sIsActive) - { +void N64Mem_LogState(const char* context) { + if (sIsActive) { LogShadowState(context); } } -void N64Mem_DumpArena(const char* tag) -{ - if (!sIsActive || sShadow.buffer == nullptr) - { +void N64Mem_DumpArena(const char* tag) { + if (!sIsActive || sShadow.buffer == nullptr) { return; } @@ -244,60 +219,50 @@ void N64Mem_DumpArena(const char* tag) u32 freeTotal = 0; u32 allocTotal = 0; - while (offset != SHADOW_NULL) - { + while (offset != SHADOW_NULL) { s32 isFree = 0; u32 size = 0; u32 next = 0; - if (!ShadowArena_GetNodeInfo(&sShadow, offset, &isFree, &size, &next)) - { + if (!ShadowArena_GetNodeInfo(&sShadow, offset, &isFree, &size, &next)) { out += fmt::format(" +0x{:06X} \n", offset); break; } const u32 dataOff = offset + sShadow.nodeSize; - if (isFree) - { + if (isFree) { out += fmt::format(" +0x{:06X} 0x{:06X} FREE\n", offset, size); freeTotal += size; freeCount++; - } - else - { + } else { u8 type = 0; s16 actorId = -1; const char* typeName = "???"; - if (N64Mem_GetBlockInfo(dataOff, &type, &actorId)) - { - switch (type) - { - case N64MEM_BLOCK_INSTANCE: - typeName = "inst"; - break; - case N64MEM_BLOCK_OVERLAY: - typeName = "ovl "; - break; - case N64MEM_BLOCK_SUBSIDIARY: - typeName = "sub "; - break; - case N64MEM_BLOCK_EFFECT: - typeName = "efx "; - break; - case N64MEM_BLOCK_ABSOLUTE: - typeName = "abs "; - break; - default: - break; + if (N64Mem_GetBlockInfo(dataOff, &type, &actorId)) { + switch (type) { + case N64MEM_BLOCK_INSTANCE: + typeName = "inst"; + break; + case N64MEM_BLOCK_OVERLAY: + typeName = "ovl "; + break; + case N64MEM_BLOCK_SUBSIDIARY: + typeName = "sub "; + break; + case N64MEM_BLOCK_EFFECT: + typeName = "efx "; + break; + case N64MEM_BLOCK_ABSOLUTE: + typeName = "abs "; + break; + default: + break; } } - if (actorId >= 0) - { + if (actorId >= 0) { out += fmt::format(" +0x{:06X} 0x{:06X} {} actor=0x{:04X}\n", offset, size, typeName, static_cast(actorId)); - } - else - { + } else { out += fmt::format(" +0x{:06X} 0x{:06X} {}\n", offset, size, typeName); } allocTotal += size; @@ -313,23 +278,19 @@ void N64Mem_DumpArena(const char* tag) SPDLOG_INFO("{}", out); } -void N64Mem_SetOriginalActorId(s16 actorId) -{ +void N64Mem_SetOriginalActorId(s16 actorId) { sOriginalActorId = actorId; } -void N64Mem_ClearOriginalActorId() -{ +void N64Mem_ClearOriginalActorId() { sOriginalActorId = -1; } -s32 N64Mem_GetRandomizedInit() -{ +s32 N64Mem_GetRandomizedInit() { return sInsideRandomizedInit; } -void N64Mem_SetRandomizedInit(s32 value) -{ +void N64Mem_SetRandomizedInit(s32 value) { sInsideRandomizedInit = value; } @@ -337,51 +298,42 @@ void N64Mem_SetRandomizedInit(s32 value) // Actor overlays // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) -{ - if (!sIsActive) - { +s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { + if (!sIsActive) { return 1; } // Child actors spawned during a randomized enemy's init don't exist on N64 -- skip shadow tracking. - if (sInsideRandomizedInit) - { + if (sInsideRandomizedInit) { return 1; } - if (actorId < 0 || actorId >= ACTOR_ID_MAX) - { + if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return 1; } const u32 overlaySize = N64SizeData_GetActorOverlaySize(ResolveSizeActorId(actorId)); - if (overlaySize == 0) - { + if (overlaySize == 0) { return 1; } // When the enemy randomizer is active, use the original actor's allocType so the shadow takes the same path // N64 would have taken for the unsubstituted actor (i.e., a regular enemy replaced by an ABSOLUTE mini-boss // should still shadow as ALLOCTYPE_NORMAL). - if (sOriginalActorId >= 0) - { + if (sOriginalActorId >= 0) { const auto& entry = ActorDB::Instance->RetrieveEntry(sOriginalActorId); allocType = entry.entry.allocType; } // ABSOLUTE: Shared fixed-size buffer, allocated once via MallocR. - if (allocType & ALLOCTYPE_ABSOLUTE) - { - if (sAbsoluteSpaceShadow == SHADOW_NULL) - { + if (allocType & ALLOCTYPE_ABSOLUTE) { + if (sAbsoluteSpaceShadow == SHADOW_NULL) { sAbsoluteSpaceShadow = ShadowArena_MallocR(&sShadow, AM_FIELD_SIZE); - if (sAbsoluteSpaceShadow == SHADOW_NULL) - { + if (sAbsoluteSpaceShadow == SHADOW_NULL) { SPDLOG_ERROR("[HardwareMemoryLimits] Shadow absolute space failed (need 0x{:X})", AM_FIELD_SIZE); return 0; } - sBlockMetaMap[sAbsoluteSpaceShadow] = {N64MEM_BLOCK_ABSOLUTE, -1}; + sBlockMetaMap[sAbsoluteSpaceShadow] = { N64MEM_BLOCK_ABSOLUTE, -1 }; } return 1; @@ -392,8 +344,7 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) const u16 overlayTrackId = ResolveSizeActorId(actorId); // Already shadowed for this type. - if (sOverlayShadows[overlayTrackId] != SHADOW_NULL) - { + if (sOverlayShadows[overlayTrackId] != SHADOW_NULL) { return 1; } @@ -404,34 +355,29 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) const u32 shadow = (allocType & ALLOCTYPE_PERMANENT) ? ShadowArena_MallocR(&sShadow, overlaySize) : ShadowArena_Malloc(&sShadow, overlaySize); - if (shadow == SHADOW_NULL) - { + if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[HardwareMemoryLimits] Shadow overlay failed for actor 0x{:04X} (need 0x{:X})", actorId, overlaySize); return 0; } sOverlayShadows[overlayTrackId] = shadow; - sBlockMetaMap[shadow] = {N64MEM_BLOCK_OVERLAY, static_cast(overlayTrackId)}; + sBlockMetaMap[shadow] = { N64MEM_BLOCK_OVERLAY, static_cast(overlayTrackId) }; TraceAlloc("ovl", overlayTrackId, overlaySize); return 1; } -void N64Mem_FreeOverlay(s16 actorId, u16 allocType) -{ - if (!sIsActive) - { +void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { + if (!sIsActive) { return; } - if (actorId < 0 || actorId >= ACTOR_ID_MAX) - { + if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return; } // PERMANENT: Overlays that are never freed. - if (allocType & ALLOCTYPE_PERMANENT) - { + if (allocType & ALLOCTYPE_PERMANENT) { return; } @@ -445,17 +391,14 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) // Actor instances // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) -{ - if (!sIsActive) - { +s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) { + if (!sIsActive) { sOriginalActorId = -1; return 1; } // Child actors spawned during a randomized enemy's init don't exist on N64 -- skip shadow tracking. - if (sInsideRandomizedInit) - { + if (sInsideRandomizedInit) { return 1; } @@ -464,31 +407,26 @@ s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) // Consume the override and enter the randomized-init phase. Subsidiaries allocated during Actor_Init will be // skipped (they belong to the replacement, not the original). Child Actor_Spawn calls during init will also // be skipped via the sInsideRandomizedInit check above. - if (sOriginalActorId >= 0) - { + if (sOriginalActorId >= 0) { sOriginalActorId = -1; sInsideRandomizedInit = 1; } - if (actorId < 0 || actorId >= ACTOR_ID_MAX) - { + if (actorId < 0 || actorId >= ACTOR_ID_MAX) { return 1; } const u32 instanceSize = N64SizeData_GetActorInstanceSize(sizeId); - if (instanceSize == 0) - { + if (instanceSize == 0) { return 1; } - if (instanceSize == 0x1A0) - { + if (instanceSize == 0x1A0) { SPDLOG_INFO("[HardwareMemoryLimits] 0x1A0 instance: actorId=0x{:X}", actorId); } const u32 shadow = ShadowArena_Malloc(&sShadow, instanceSize); - if (shadow == SHADOW_NULL) - { + if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[HardwareMemoryLimits] Shadow instance failed for actor 0x{:04X} params=0x{:04X} (need 0x{:X})", actorId, static_cast(params), instanceSize); N64Mem_DumpArena(fmt::format("instance failure: actor=0x{:04X} params=0x{:04X}", actorId, @@ -499,34 +437,29 @@ s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) sShadowMap[realPtr] = shadow; sActorOriginalIds[realPtr] = static_cast(sizeId); - sBlockMetaMap[shadow] = {N64MEM_BLOCK_INSTANCE, static_cast(sizeId)}; + sBlockMetaMap[shadow] = { N64MEM_BLOCK_INSTANCE, static_cast(sizeId) }; TraceAlloc("inst", actorId, instanceSize); return 1; } -s16 N64Mem_FreeInstance(void* realPtr) -{ - if (!sIsActive || !realPtr) - { +s16 N64Mem_FreeInstance(void* realPtr) { + if (!sIsActive || !realPtr) { return -1; } const auto i = sShadowMap.find(realPtr); - if (i == sShadowMap.end()) - { + if (i == sShadowMap.end()) { return -1; } // Retrieve the stored original actor ID before erasing. Used by Actor_Delete to free the correct overlay shadow. s16 originalId = -1; - if (const auto j = sActorOriginalIds.find(realPtr); j != sActorOriginalIds.end()) - { + if (const auto j = sActorOriginalIds.find(realPtr); j != sActorOriginalIds.end()) { originalId = j->second; sActorOriginalIds.erase(j); } - if (sTraceEnabled) - { + if (sTraceEnabled) { const auto* actor = static_cast(realPtr); TraceFree("inst", actor->id); } @@ -541,44 +474,37 @@ s16 N64Mem_FreeInstance(void* realPtr) // Subsidiaries // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) -{ - if (!sIsActive) - { +s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) { + if (!sIsActive) { return 1; } // When inside a randomized enemy's init, the replacement actor's subsidiaries (colliders, skeleton tables, skin // buffers) have different counts than the original's. Rather than charge the wrong sizes, skip subsidiary // tracking entirely -- instance and overlay sizes from the original are already correct and dominate heap pressure. - if (sInsideRandomizedInit) - { + if (sInsideRandomizedInit) { return 1; } const u32 shadow = ShadowArena_Malloc(&sShadow, n64Size); - if (shadow == SHADOW_NULL) - { + if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[HardwareMemoryLimits] Shadow subsidiary failed (need 0x{:X})", n64Size); return 0; } sShadowMap[realPtr] = shadow; - sBlockMetaMap[shadow] = {N64MEM_BLOCK_SUBSIDIARY, -1}; + sBlockMetaMap[shadow] = { N64MEM_BLOCK_SUBSIDIARY, -1 }; TraceAlloc("sub", 0, n64Size); return 1; } -void N64Mem_FreeSubsidiary(void* realPtr) -{ - if (!sIsActive || !realPtr) - { +void N64Mem_FreeSubsidiary(void* realPtr) { + if (!sIsActive || !realPtr) { return; } const auto i = sShadowMap.find(realPtr); - if (i == sShadowMap.end()) - { + if (i == sShadowMap.end()) { return; } @@ -592,39 +518,33 @@ void N64Mem_FreeSubsidiary(void* realPtr) // Effect overlays // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocEffectOverlay(s32 type) -{ - if (!sIsActive) - { +s32 N64Mem_AllocEffectOverlay(s32 type) { + if (!sIsActive) { return 1; } - if (type < 0 || type >= EFFECT_SS_TYPE_MAX) - { + if (type < 0 || type >= EFFECT_SS_TYPE_MAX) { return 1; } - if (sEffectOverlayShadows[type] != SHADOW_NULL) - { + if (sEffectOverlayShadows[type] != SHADOW_NULL) { return 1; } u32 overlaySize = N64SizeData_GetEffectOverlaySize(type); - if (overlaySize == 0) - { + if (overlaySize == 0) { return 1; } const u32 shadow = ShadowArena_MallocR(&sShadow, overlaySize); - if (shadow == SHADOW_NULL) - { + if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[HardwareMemoryLimits] Shadow effect overlay failed for type 0x{:02X} (need 0x{:X})", type, overlaySize); return 0; } sEffectOverlayShadows[type] = shadow; - sBlockMetaMap[shadow] = {N64MEM_BLOCK_EFFECT, static_cast(type)}; + sBlockMetaMap[shadow] = { N64MEM_BLOCK_EFFECT, static_cast(type) }; TraceAlloc("efx", type, overlaySize); return 1; } @@ -634,21 +554,17 @@ s32 N64Mem_AllocEffectOverlay(s32 type) // Heap viewer metadata // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_GetBlockInfo(u32 dataOffset, u8* outType, s16* outActorId) -{ +s32 N64Mem_GetBlockInfo(u32 dataOffset, u8* outType, s16* outActorId) { const auto i = sBlockMetaMap.find(dataOffset); - if (i == sBlockMetaMap.end()) - { + if (i == sBlockMetaMap.end()) { return 0; } - if (outType) - { + if (outType) { *outType = i->second.type; } - if (outActorId) - { + if (outActorId) { *outActorId = i->second.actorId; } @@ -659,9 +575,8 @@ s32 N64Mem_GetBlockInfo(u32 dataOffset, u8* outType, s16* outActorId) // Registration // -------------------------------------------------------------------------------------------------------------------- -void RegisterN64MemoryModel() -{ +void RegisterN64MemoryModel() { // #TODO: Shadow arena initialization } -static RegisterShipInitFunc initFunc(RegisterN64MemoryModel, {CVAR_NAME}); \ No newline at end of file +static RegisterShipInitFunc initFunc(RegisterN64MemoryModel, { CVAR_NAME }); \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp index ba34272d0cd..2c4d207b317 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp @@ -272,4 +272,4 @@ static void LoadArenaNodeSize() { extern "C" u32 N64SizeData_GetArenaNodeSize() { LoadArenaNodeSize(); return sArenaNodeSize; -} +} \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp index 51b80d56d62..55a7ba3b62b 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp @@ -2,10 +2,6 @@ #include -#ifdef __cplusplus -extern "C" { -#endif - // -------------------------------------------------------------------------------------------------------------------- // N64 size data loaded from the OTR archive. // diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h index 364227bd5d8..8f223fa2a28 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h @@ -21,7 +21,7 @@ extern "C" { // (should not happen on valid scenes). // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState * play); +u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c index 6e3513036bd..4cd1f1dfc3a 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c @@ -17,8 +17,7 @@ // The total node spacing is arena->nodeSize: 0x10 for retail, 0x30 for debug. // -------------------------------------------------------------------------------------------------------------------- -typedef struct ShadowNode -{ +typedef struct ShadowNode { s16 magic; s16 isFree; u32 size; @@ -35,42 +34,34 @@ static_assert(sizeof(ShadowNode) == 0x10, "ShadowNode header must be exactly 0x1 // Offset helpers // -------------------------------------------------------------------------------------------------------------------- -static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) -{ +static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) { return (ShadowNode*)(arena->buffer + offset); } -static s32 NodeIsValid(ShadowArena* arena, u32 offset) -{ - if (offset == SHADOW_NULL) - { +static s32 NodeIsValid(ShadowArena* arena, u32 offset) { + if (offset == SHADOW_NULL) { return 0; } - if (offset + arena->nodeSize > arena->bufferSize) - { + if (offset + arena->nodeSize > arena->bufferSize) { return 0; } return NodeAt(arena, offset)->magic == NODE_MAGIC; } -static u32 NodeGetNext(ShadowArena* arena, u32 offset) -{ +static u32 NodeGetNext(ShadowArena* arena, u32 offset) { const ShadowNode* node = NodeAt(arena, offset); - if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) - { + if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) { return node->next; } return SHADOW_NULL; } -static u32 NodeGetPrev(ShadowArena* arena, u32 offset) -{ +static u32 NodeGetPrev(ShadowArena* arena, u32 offset) { const ShadowNode* node = NodeAt(arena, offset); - if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) - { + if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) { return node->prev; } @@ -81,15 +72,13 @@ static u32 NodeGetPrev(ShadowArena* arena, u32 offset) // Init / Destroy // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Init(ShadowArena* arena, u32 size, u32 nodeSize) -{ +void ShadowArena_Init(ShadowArena* arena, u32 size, u32 nodeSize) { // Match N64's alignment: Round start up to 16, round size down to 16. Since we control the buffer, start is // effectively offset 0 after alignment. We just ensure the usable size is 16-byte aligned. const u32 alignedSize = size & ~0xF; arena->buffer = (u8*)malloc(alignedSize); - if (!arena->buffer) - { + if (!arena->buffer) { memset(arena, 0, sizeof(ShadowArena)); return; } @@ -109,10 +98,8 @@ void ShadowArena_Init(ShadowArena* arena, u32 size, u32 nodeSize) arena->head = 0; } -void ShadowArena_Destroy(ShadowArena* arena) -{ - if (arena->buffer) - { +void ShadowArena_Destroy(ShadowArena* arena) { + if (arena->buffer) { free(arena->buffer); } @@ -123,8 +110,7 @@ void ShadowArena_Destroy(ShadowArena* arena) // Malloc: First-fit forward (matches N64 __osMalloc) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) -{ +u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { u32 iterOff = 0; ShadowNode* iter = NULL; u32 blockSize = 0; @@ -133,14 +119,11 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) blockSize = size + arena->nodeSize; iterOff = arena->head; - while (iterOff != SHADOW_NULL) - { + while (iterOff != SHADOW_NULL) { iter = NodeAt(arena, iterOff); - if (iter->isFree && iter->size >= size) - { + if (iter->isFree && iter->size >= size) { // Split if remainder can hold a new node + payload. - if (blockSize < iter->size) - { + if (blockSize < iter->size) { const u32 newOff = iterOff + blockSize; ShadowNode* newNode = NodeAt(arena, newOff); @@ -150,8 +133,7 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) newNode->next = iter->next; newNode->prev = iterOff; - if (newNode->next != SHADOW_NULL) - { + if (newNode->next != SHADOW_NULL) { NodeAt(arena, newNode->next)->prev = newOff; } @@ -175,8 +157,7 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) // MallocR: First-fit backward (matches N64 __osMallocR) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) -{ +u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { u32 iterOff = 0; u32 nextOff = 0; ShadowNode* iter = NULL; @@ -187,23 +168,19 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) // Walk to tail. iterOff = arena->head; nextOff = NodeGetNext(arena, iterOff); - while (nextOff != SHADOW_NULL) - { + while (nextOff != SHADOW_NULL) { iterOff = nextOff; nextOff = NodeGetNext(arena, nextOff); } // Walk backward looking for a fit. - while (iterOff != SHADOW_NULL) - { + while (iterOff != SHADOW_NULL) { iter = NodeAt(arena, iterOff); - if (iter->isFree && iter->size >= size) - { + if (iter->isFree && iter->size >= size) { blockSize = size + arena->nodeSize; // Split: Carve the allocation from the TOP of this free block. - if (blockSize < iter->size) - { + if (blockSize < iter->size) { const u32 newOff = iterOff + (iter->size - size); ShadowNode* newNode = NodeAt(arena, newOff); @@ -213,8 +190,7 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) newNode->next = iter->next; newNode->prev = iterOff; - if (newNode->next != SHADOW_NULL) - { + if (newNode->next != SHADOW_NULL) { NodeAt(arena, newNode->next)->prev = newOff; } @@ -239,28 +215,24 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) // Free: Adjacent block coalescing (matches N64 _osFree) // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) -{ +void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { u32 nodeOff = 0; ShadowNode* node = NULL; u32 nextOff = 0; u32 prevOff = 0; - if (dataOffset == SHADOW_NULL) - { + if (dataOffset == SHADOW_NULL) { return; } nodeOff = dataOffset - arena->nodeSize; node = NodeAt(arena, nodeOff); - if (node->magic != NODE_MAGIC) - { + if (node->magic != NODE_MAGIC) { return; } - if (node->isFree) - { + if (node->isFree) { return; } @@ -268,13 +240,10 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) // Forward coalesce: Merge with next if it's free. nextOff = node->next; - if (nextOff != SHADOW_NULL) - { + if (nextOff != SHADOW_NULL) { const ShadowNode* next = NodeAt(arena, nextOff); - if (next->isFree) - { - if (next->next != SHADOW_NULL) - { + if (next->isFree) { + if (next->next != SHADOW_NULL) { NodeAt(arena, next->next)->prev = nodeOff; } @@ -285,16 +254,13 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) // Backward coalesce: Merge into prev if it's free. prevOff = node->prev; - if (prevOff != SHADOW_NULL) - { + if (prevOff != SHADOW_NULL) { ShadowNode* prev = NodeAt(arena, prevOff); - if (prev->isFree) - { + if (prev->isFree) { prev->size += node->size + arena->nodeSize; prev->next = node->next; - if (node->next != SHADOW_NULL) - { + if (node->next != SHADOW_NULL) { NodeAt(arena, node->next)->prev = prevOff; } } @@ -305,8 +271,7 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) // Query // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) -{ +void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) { u32 iterOff = 0; *outMaxFree = 0; @@ -314,19 +279,14 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 *outAlloc = 0; iterOff = arena->head; - while (iterOff != SHADOW_NULL) - { + while (iterOff != SHADOW_NULL) { const ShadowNode* node = NodeAt(arena, iterOff); - if (node->isFree) - { + if (node->isFree) { *outFree += node->size; - if (node->size > *outMaxFree) - { + if (node->size > *outMaxFree) { *outMaxFree = node->size; } - } - else - { + } else { *outAlloc += node->size; } @@ -334,21 +294,17 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 } } -u32 ShadowArena_GetHead(ShadowArena* arena) -{ +u32 ShadowArena_GetHead(ShadowArena* arena) { return arena->head; } -s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) -{ - if (offset == SHADOW_NULL || offset + arena->nodeSize > arena->bufferSize) - { +s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) { + if (offset == SHADOW_NULL || offset + arena->nodeSize > arena->bufferSize) { return 0; } const ShadowNode* node = NodeAt(arena, offset); - if (node->magic != NODE_MAGIC) - { + if (node->magic != NODE_MAGIC) { return 0; } @@ -358,7 +314,6 @@ s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* return 1; } -u32 ShadowArena_GetBufferSize(ShadowArena* arena) -{ +u32 ShadowArena_GetBufferSize(ShadowArena* arena) { return arena->bufferSize; } \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h index 21b9c1e30c5..4e0f56362f1 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h @@ -15,8 +15,7 @@ extern "C" { #define SHADOW_NODE_SIZE_RETAIL 0x10 #define SHADOW_NODE_SIZE_DEBUG 0x30 -typedef struct ShadowArena -{ +typedef struct ShadowArena { u8* buffer; u32 head; // Offset to first node u32 bufferSize; @@ -40,7 +39,7 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size); void ShadowArena_Free(ShadowArena* arena, u32 dataOffset); // Query arena statistics. -void ShadowArena_GetSizes(ShadowArena * arena, u32 * outMaxFree, u32 * outFree, u32 * outAlloc); +void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc); // Get the head node offset for external traversal (e.g., heap viewer). u32 ShadowArena_GetHead(ShadowArena* arena); From abfbe9d75220b6277a498f8ddf9aab03532f0e36 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Thu, 14 May 2026 23:52:13 -0700 Subject: [PATCH 54/58] chore: Remove from config updater --- soh/soh/config/ConfigUpdaters.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/soh/soh/config/ConfigUpdaters.cpp b/soh/soh/config/ConfigUpdaters.cpp index 6d690f7f4de..8249bb48d31 100644 --- a/soh/soh/config/ConfigUpdaters.cpp +++ b/soh/soh/config/ConfigUpdaters.cpp @@ -49,7 +49,6 @@ static const Migration version3Migrations[] = { { "gControllerReorderingWindowEnabled", "gOpenWindows.ControllerReorderingWindow" }, { "gGfxDebuggerEnabled", "gOpenWindows.GfxDebugger" }, { "gStatsEnabled", "gOpenWindows.Stats" }, - { "gHeapViewerEnabled", "gOpenWindows.HeapViewer" }, { "gDisableChangingSettings", "gSettings.DisableChanges" }, { "gExtraLatencyThreshold", "gSettings.ExtraLatencyThreshold" }, { "gImGuiScale", "gSettings.ImGuiScale" }, @@ -295,7 +294,6 @@ static const Migration version3Migrations[] = { { "gGameplayStats.ShowIngameTimer", "gGameplayStats.ShowInGameTimer" }, { "gGameplayStats.TimestampsReverse", "gGameplayStats.ReverseTimestamps" }, { "gMirroredWorld", "gEnhancements.MirroredWorld" }, - { "gN64MemoryModel", "gEnhancements.N64MemoryModel" }, { "gBetaQuestWorld", "gCheats.BetaQuestWorld" }, { "gBombTimerMultiplier", "gCheats.BombTimerMultiplier" }, { "gCheatEasyInputBufferingEnabled", "gCheats.EasyInputBuffer" }, From cd838360559f0dd3164b12c94b632ab41ce5de23 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 15 May 2026 02:14:51 -0700 Subject: [PATCH 55/58] refactor: Prefer stdint over libultraship types --- .../HardwareMemoryLimits.cpp | 154 +++++++++--------- .../HardwareMemoryLimits.hpp | 30 ++-- .../HardwareMemoryLimits/N64SizeData.cpp | 46 +++--- .../HardwareMemoryLimits/N64SizeData.hpp | 4 + .../HardwareMemoryLimits/n64_arena_sizing.c | 90 +++++----- .../HardwareMemoryLimits/n64_arena_sizing.h | 4 +- .../HardwareMemoryLimits/n64_shadow_arena.c | 72 ++++---- .../HardwareMemoryLimits/n64_shadow_arena.h | 29 ++-- .../debugger/HeapViewerWindow.cpp | 109 +++++++------ 9 files changed, 275 insertions(+), 263 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp index a7ec1f104c3..999d20f9c00 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp @@ -23,62 +23,62 @@ extern "C" { // State // -------------------------------------------------------------------------------------------------------------------- -static s32 sIsActive = 0; +static bool sIsActive = false; static ShadowArena sShadow; -static u32 sSohThaRemainder = 0; -static u8 sElfMsgNum = 0; +static uint32_t sSohThaRemainder = 0; +static uint8_t sElfMsgNum = 0; // Shadow offsets for actor overlays, keyed by actor ID. SHADOW_NULL means no shadow allocation exists for that type. -static u32 sOverlayShadows[ACTOR_ID_MAX]; +static uint32_t sOverlayShadows[ACTOR_ID_MAX]; // Shadow offsets for effect overlays, keyed by effect type. -static u32 sEffectOverlayShadows[EFFECT_SS_TYPE_MAX]; +static uint32_t sEffectOverlayShadows[EFFECT_SS_TYPE_MAX]; // Shadow offset for the shared absolute-space overlay buffer. -static u32 sAbsoluteSpaceShadow = SHADOW_NULL; +static uint32_t sAbsoluteSpaceShadow = SHADOW_NULL; // Maps real pointers (instances and subsidiaries) to their shadow offsets. -static std::unordered_map sShadowMap; +static std::unordered_map sShadowMap; // Maps real actor pointers to their original actor ID (before enemy randomizer substitution). Used by FreeOverlay to // free the correct overlay shadow entry. Only populated for randomized actors. -static std::unordered_map sActorOriginalIds; +static std::unordered_map sActorOriginalIds; // Block metadata for the heap viewer. Keyed by shadow DATA offset (node offset + arena->nodeSize). // Populated in each Alloc function, erased in each Free, cleared in N64Mem_Reset. struct BlockMeta { - u8 type; - s16 actorId; + uint8_t type; + int16_t actorId; }; -static std::unordered_map sBlockMetaMap; +static std::unordered_map sBlockMetaMap; // When the enemy randomizer replaces an actor, this holds the ORIGINAL actor ID so that shadow allocations are charged // at the original N64 size rather than the replacement's size. Set by N64Mem_SetOriginalActorId before Actor_Spawn, // cleared by N64Mem_ClearOriginalActorId after Actor_Spawn returns. -1 means no override (use the passed actorId). -static s16 sOriginalActorId = -1; +static int16_t sOriginalActorId = -1; // Set when AllocInstance consumes a non-negative sOriginalActorId, cleared after Actor_Init returns. While set, ALL // shadow allocations (overlay, instance, subsidiary) from child actors spawned during the replacement actor's init are // skipped -- on N64 these children don't exist because the original actor never spawned them. -static s32 sInsideRandomizedInit = 0; +static int32_t sInsideRandomizedInit = 0; // Returns the actor ID to use for size lookups. If an original actor ID override is set (enemy randomizer active), // returns that; otherwise returns the passed actorId unchanged. -static u16 ResolveSizeActorId(s16 actorId) { - return sOriginalActorId >= 0 ? static_cast(sOriginalActorId) : static_cast(actorId); +static uint16_t ResolveSizeActorId(int16_t actorId) { + return sOriginalActorId >= 0 ? static_cast(sOriginalActorId) : static_cast(actorId); } // -------------------------------------------------------------------------------------------------------------------- // Diagnostics // -------------------------------------------------------------------------------------------------------------------- -static s32 sTraceEnabled = 0; +static int32_t sTraceEnabled = 0; static void LogShadowState(const char* context) { - u32 maxFree = 0; - u32 totalFree = 0; - u32 totalAlloc = 0; + uint32_t maxFree = 0; + uint32_t totalFree = 0; + uint32_t totalAlloc = 0; ShadowArena_GetSizes(&sShadow, &maxFree, &totalFree, &totalAlloc); SPDLOG_INFO("[HardwareMemoryLimits] ({}): alloc=0x{:X}, free=0x{:X}, largest=0x{:X}", context, totalAlloc, @@ -86,14 +86,14 @@ static void LogShadowState(const char* context) { maxFree); } -static void TraceAlloc(const char* tag, u32 id, u32 size) { +static void TraceAlloc(const char* tag, uint32_t id, uint32_t size) { if (sTraceEnabled) { - u32 consumed = (size + 0xF & ~0xF) + sShadow.nodeSize; + uint32_t consumed = (size + 0xF & ~0xF) + sShadow.nodeSize; SPDLOG_TRACE("[N64Trace] +{} id=0x{:X} sz=0x{:X} cost=0x{:X}", tag, id, size, consumed); } } -static void TraceFree(const char* tag, u32 id) { +static void TraceFree(const char* tag, uint32_t id) { if (sTraceEnabled) { SPDLOG_TRACE("[N64Trace] -{} id=0x{:X}", tag, id); } @@ -103,7 +103,7 @@ static void TraceFree(const char* tag, u32 id) { // Graveyard benchmark // -------------------------------------------------------------------------------------------------------------------- -static s32 sGraveyardTransitionCount = 0; +static int32_t sGraveyardTransitionCount = 0; void N64Mem_BenchmarkTransition(PlayState* play) { if (!sIsActive || play->sceneNum != SCENE_GRAVEYARD) { @@ -112,9 +112,9 @@ void N64Mem_BenchmarkTransition(PlayState* play) { sGraveyardTransitionCount++; - u32 maxFree = 0; - u32 totalFree = 0; - u32 totalAlloc = 0; + uint32_t maxFree = 0; + uint32_t totalFree = 0; + uint32_t totalAlloc = 0; ShadowArena_GetSizes(&sShadow, &maxFree, &totalFree, &totalAlloc); SPDLOG_INFO("[N64Benchmark] transition={}, largest_free=0x{:X}, total_free=0x{:X}", @@ -125,24 +125,24 @@ void N64Mem_BenchmarkTransition(PlayState* play) { // Lifecycle // -------------------------------------------------------------------------------------------------------------------- -void N64Mem_StoreThaRemainder(u32 sohRemainder) { +void N64Mem_StoreThaRemainder(uint32_t sohRemainder) { sSohThaRemainder = sohRemainder; } -void N64Mem_StoreElfMsgNum(u8 num) { +void N64Mem_StoreElfMsgNum(uint8_t num) { sElfMsgNum = num; } -u8 N64Mem_GetElfMsgNum() { +uint8_t N64Mem_GetElfMsgNum() { return sElfMsgNum; } void N64Mem_Reset(PlayState* play) { // Log shadow state before teardown for per-scene diagnostics. if (sIsActive && sShadow.buffer) { - u32 maxFree = 0; - u32 totalFree = 0; - u32 totalAlloc = 0; + uint32_t maxFree = 0; + uint32_t totalFree = 0; + uint32_t totalAlloc = 0; ShadowArena_GetSizes(&sShadow, &maxFree, &totalFree, &totalAlloc); SPDLOG_INFO("[HardwareMemoryLimits] Teardown: alloc=0x{:X}, free=0x{:X}, largest=0x{:X}, ptrs={}", totalAlloc, @@ -165,11 +165,11 @@ void N64Mem_Reset(PlayState* play) { sOriginalActorId = -1; sInsideRandomizedInit = 0; - for (u32& sOverlayShadow : sOverlayShadows) { + for (uint32_t& sOverlayShadow : sOverlayShadows) { sOverlayShadow = SHADOW_NULL; } - for (u32& sEffectOverlayShadow : sEffectOverlayShadows) { + for (uint32_t& sEffectOverlayShadow : sEffectOverlayShadows) { sEffectOverlayShadow = SHADOW_NULL; } @@ -177,7 +177,7 @@ void N64Mem_Reset(PlayState* play) { if (sIsActive && play != nullptr) { // Compute N64-equivalent arena size from first principles. All per-version constants are derived from the OTR // blob, which was extracted from the user's specific ROM version. - u32 shadowArenaSize = ArenaSizing_ComputeN64ArenaSize(play); + uint32_t shadowArenaSize = ArenaSizing_ComputeN64ArenaSize(play); if (shadowArenaSize == 0) { SPDLOG_ERROR("[HardwareMemoryLimits] Arena sizing returned 0 -- THA budget exceeded, disabling."); sIsActive = 0; @@ -192,7 +192,7 @@ void N64Mem_Reset(PlayState* play) { } } -s32 N64Mem_IsActive() { +int32_t N64Mem_IsActive() { return sIsActive; } @@ -213,30 +213,30 @@ void N64Mem_DumpArena(const char* tag) { std::string out = fmt::format("\n[N64HeapDump] === {} ===\n", tag); - u32 offset = ShadowArena_GetHead(&sShadow); - u32 freeCount = 0; - u32 allocCount = 0; - u32 freeTotal = 0; - u32 allocTotal = 0; + uint32_t offset = ShadowArena_GetHead(&sShadow); + uint32_t freeCount = 0; + uint32_t allocCount = 0; + uint32_t freeTotal = 0; + uint32_t allocTotal = 0; while (offset != SHADOW_NULL) { - s32 isFree = 0; - u32 size = 0; - u32 next = 0; + int32_t isFree = 0; + uint32_t size = 0; + uint32_t next = 0; if (!ShadowArena_GetNodeInfo(&sShadow, offset, &isFree, &size, &next)) { out += fmt::format(" +0x{:06X} \n", offset); break; } - const u32 dataOff = offset + sShadow.nodeSize; + const uint32_t dataOff = offset + sShadow.nodeSize; if (isFree) { out += fmt::format(" +0x{:06X} 0x{:06X} FREE\n", offset, size); freeTotal += size; freeCount++; } else { - u8 type = 0; - s16 actorId = -1; - const char* typeName = "???"; + uint8_t type = 0; + int16_t actorId = -1; + auto typeName = "???"; if (N64Mem_GetBlockInfo(dataOff, &type, &actorId)) { switch (type) { case N64MEM_BLOCK_INSTANCE: @@ -261,7 +261,7 @@ void N64Mem_DumpArena(const char* tag) { if (actorId >= 0) { out += fmt::format(" +0x{:06X} 0x{:06X} {} actor=0x{:04X}\n", offset, size, typeName, - static_cast(actorId)); + static_cast(actorId)); } else { out += fmt::format(" +0x{:06X} 0x{:06X} {}\n", offset, size, typeName); } @@ -278,7 +278,7 @@ void N64Mem_DumpArena(const char* tag) { SPDLOG_INFO("{}", out); } -void N64Mem_SetOriginalActorId(s16 actorId) { +void N64Mem_SetOriginalActorId(int16_t actorId) { sOriginalActorId = actorId; } @@ -286,11 +286,11 @@ void N64Mem_ClearOriginalActorId() { sOriginalActorId = -1; } -s32 N64Mem_GetRandomizedInit() { +int32_t N64Mem_GetRandomizedInit() { return sInsideRandomizedInit; } -void N64Mem_SetRandomizedInit(s32 value) { +void N64Mem_SetRandomizedInit(int32_t value) { sInsideRandomizedInit = value; } @@ -298,7 +298,7 @@ void N64Mem_SetRandomizedInit(s32 value) { // Actor overlays // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { +int32_t N64Mem_AllocOverlay(int16_t actorId, uint16_t allocType) { if (!sIsActive) { return 1; } @@ -312,7 +312,7 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { return 1; } - const u32 overlaySize = N64SizeData_GetActorOverlaySize(ResolveSizeActorId(actorId)); + const uint32_t overlaySize = N64SizeData_GetActorOverlaySize(ResolveSizeActorId(actorId)); if (overlaySize == 0) { return 1; } @@ -341,7 +341,7 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { // Resolve the actor ID for overlay tracking. When the enemy randomizer is active, overlays are tracked by // the ORIGINAL actor ID so that multiple replacements of the same original type share one overlay shadow. - const u16 overlayTrackId = ResolveSizeActorId(actorId); + const uint16_t overlayTrackId = ResolveSizeActorId(actorId); // Already shadowed for this type. if (sOverlayShadows[overlayTrackId] != SHADOW_NULL) { @@ -352,9 +352,9 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { // (NORMAL) overlays go through forward Malloc (arena bottom). ABSOLUTE is handled above. NORMAL overlays being // interleaved with instances at the bottom is the authentic N64 fragmentation pattern -- the simulation must // reproduce it, not paper over it. - const u32 shadow = (allocType & ALLOCTYPE_PERMANENT) - ? ShadowArena_MallocR(&sShadow, overlaySize) - : ShadowArena_Malloc(&sShadow, overlaySize); + const uint32_t shadow = (allocType & ALLOCTYPE_PERMANENT) + ? ShadowArena_MallocR(&sShadow, overlaySize) + : ShadowArena_Malloc(&sShadow, overlaySize); if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[HardwareMemoryLimits] Shadow overlay failed for actor 0x{:04X} (need 0x{:X})", actorId, overlaySize); @@ -362,12 +362,12 @@ s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType) { } sOverlayShadows[overlayTrackId] = shadow; - sBlockMetaMap[shadow] = { N64MEM_BLOCK_OVERLAY, static_cast(overlayTrackId) }; + sBlockMetaMap[shadow] = { N64MEM_BLOCK_OVERLAY, static_cast(overlayTrackId) }; TraceAlloc("ovl", overlayTrackId, overlaySize); return 1; } -void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { +void N64Mem_FreeOverlay(int16_t actorId, uint16_t allocType) { if (!sIsActive) { return; } @@ -391,7 +391,7 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType) { // Actor instances // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) { +int32_t N64Mem_AllocInstance(int16_t actorId, int16_t params, void* realPtr) { if (!sIsActive) { sOriginalActorId = -1; return 1; @@ -402,7 +402,7 @@ s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) { return 1; } - const u16 sizeId = ResolveSizeActorId(actorId); + const uint16_t sizeId = ResolveSizeActorId(actorId); // Consume the override and enter the randomized-init phase. Subsidiaries allocated during Actor_Init will be // skipped (they belong to the replacement, not the original). Child Actor_Spawn calls during init will also @@ -416,7 +416,7 @@ s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) { return 1; } - const u32 instanceSize = N64SizeData_GetActorInstanceSize(sizeId); + const uint32_t instanceSize = N64SizeData_GetActorInstanceSize(sizeId); if (instanceSize == 0) { return 1; } @@ -425,24 +425,24 @@ s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr) { SPDLOG_INFO("[HardwareMemoryLimits] 0x1A0 instance: actorId=0x{:X}", actorId); } - const u32 shadow = ShadowArena_Malloc(&sShadow, instanceSize); + const uint32_t shadow = ShadowArena_Malloc(&sShadow, instanceSize); if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[HardwareMemoryLimits] Shadow instance failed for actor 0x{:04X} params=0x{:04X} (need 0x{:X})", - actorId, static_cast(params), instanceSize); + actorId, static_cast(params), instanceSize); N64Mem_DumpArena(fmt::format("instance failure: actor=0x{:04X} params=0x{:04X}", actorId, - static_cast(params)) + static_cast(params)) .c_str()); return 0; } sShadowMap[realPtr] = shadow; - sActorOriginalIds[realPtr] = static_cast(sizeId); - sBlockMetaMap[shadow] = { N64MEM_BLOCK_INSTANCE, static_cast(sizeId) }; + sActorOriginalIds[realPtr] = static_cast(sizeId); + sBlockMetaMap[shadow] = { N64MEM_BLOCK_INSTANCE, static_cast(sizeId) }; TraceAlloc("inst", actorId, instanceSize); return 1; } -s16 N64Mem_FreeInstance(void* realPtr) { +int16_t N64Mem_FreeInstance(void* realPtr) { if (!sIsActive || !realPtr) { return -1; } @@ -453,7 +453,7 @@ s16 N64Mem_FreeInstance(void* realPtr) { } // Retrieve the stored original actor ID before erasing. Used by Actor_Delete to free the correct overlay shadow. - s16 originalId = -1; + int16_t originalId = -1; if (const auto j = sActorOriginalIds.find(realPtr); j != sActorOriginalIds.end()) { originalId = j->second; sActorOriginalIds.erase(j); @@ -474,7 +474,7 @@ s16 N64Mem_FreeInstance(void* realPtr) { // Subsidiaries // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) { +int32_t N64Mem_AllocSubsidiary(void* realPtr, uint32_t n64Size) { if (!sIsActive) { return 1; } @@ -486,7 +486,7 @@ s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size) { return 1; } - const u32 shadow = ShadowArena_Malloc(&sShadow, n64Size); + const uint32_t shadow = ShadowArena_Malloc(&sShadow, n64Size); if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[HardwareMemoryLimits] Shadow subsidiary failed (need 0x{:X})", n64Size); return 0; @@ -518,7 +518,7 @@ void N64Mem_FreeSubsidiary(void* realPtr) { // Effect overlays // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocEffectOverlay(s32 type) { +int32_t N64Mem_AllocEffectOverlay(int32_t type) { if (!sIsActive) { return 1; } @@ -531,12 +531,12 @@ s32 N64Mem_AllocEffectOverlay(s32 type) { return 1; } - u32 overlaySize = N64SizeData_GetEffectOverlaySize(type); + uint32_t overlaySize = N64SizeData_GetEffectOverlaySize(type); if (overlaySize == 0) { return 1; } - const u32 shadow = ShadowArena_MallocR(&sShadow, overlaySize); + const uint32_t shadow = ShadowArena_MallocR(&sShadow, overlaySize); if (shadow == SHADOW_NULL) { SPDLOG_ERROR("[HardwareMemoryLimits] Shadow effect overlay failed for type 0x{:02X} (need 0x{:X})", type, overlaySize); @@ -544,7 +544,7 @@ s32 N64Mem_AllocEffectOverlay(s32 type) { } sEffectOverlayShadows[type] = shadow; - sBlockMetaMap[shadow] = { N64MEM_BLOCK_EFFECT, static_cast(type) }; + sBlockMetaMap[shadow] = { N64MEM_BLOCK_EFFECT, static_cast(type) }; TraceAlloc("efx", type, overlaySize); return 1; } @@ -554,7 +554,7 @@ s32 N64Mem_AllocEffectOverlay(s32 type) { // Heap viewer metadata // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_GetBlockInfo(u32 dataOffset, u8* outType, s16* outActorId) { +int32_t N64Mem_GetBlockInfo(uint32_t dataOffset, uint8_t* outType, int16_t* outActorId) { const auto i = sBlockMetaMap.find(dataOffset); if (i == sBlockMetaMap.end()) { return 0; diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp index 8909826fa96..c9b65cf1ba4 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #ifdef __cplusplus extern "C" { @@ -18,14 +18,14 @@ extern "C" { // Store SoH's THA remainder before ZeldaArena_Init consumes it. Call from Play_Init immediately after // THA_GetRemaining. -void N64Mem_StoreThaRemainder(u32 sohRemainder); +void N64Mem_StoreThaRemainder(uint32_t sohRemainder); // Store which elf message file was loaded by Scene_CommandSpecialFiles (1 = elf_message_field, 2 = elf_message_ydan, // 0 = none). Called from z_scene.c during scene command processing. -void N64Mem_StoreElfMsgNum(u8 num); +void N64Mem_StoreElfMsgNum(uint8_t num); // Returns the elf message number stored by N64Mem_StoreElfMsgNum. -u8 N64Mem_GetElfMsgNum(void); +uint8_t N64Mem_GetElfMsgNum(void); // Reset shadow state, compute N64-equivalent arena size from stored THA remainder minus N64-specific consumers // (room buffers, etc.), and reread CVar. Call from Play_Init after ZeldaArena_Init. @@ -33,7 +33,7 @@ struct PlayState; void N64Mem_Reset(PlayState* play); // Returns whether the N64 memory model is currently active. -s32 N64Mem_IsActive(void); +int32_t N64Mem_IsActive(void); // Returns the shadow arena pointer for debug visualization. Only valid when N64Mem_IsActive() is true. struct ShadowArena* N64Mem_GetShadowArena(void); @@ -53,13 +53,13 @@ void N64Mem_LogState(const char* context); // ref counting, instance pointer mapping) uses the actual spawned actor ID. // -------------------------------------------------------------------------------------------------------------------- -void N64Mem_SetOriginalActorId(s16 actorId); +void N64Mem_SetOriginalActorId(int16_t actorId); void N64Mem_ClearOriginalActorId(void); // Save/restore for the randomized-init skip flag. Actor_Spawn saves the current value at entry and restores it // after Actor_Init returns, so child spawns during a randomized actor's init don't clobber the parent's flag. -s32 N64Mem_GetRandomizedInit(void); -void N64Mem_SetRandomizedInit(s32 value); +int32_t N64Mem_GetRandomizedInit(void); +void N64Mem_SetRandomizedInit(int32_t value); // Log Graveyard benchmark data (transition count, largest_free, total_free). Call after room actors are spawned. void N64Mem_BenchmarkTransition(PlayState* play); @@ -74,8 +74,8 @@ void N64Mem_BenchmarkTransition(PlayState* play); // Call FreeOverlay when numLoaded transitions 1 -> 0. // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocOverlay(s16 actorId, u16 allocType); -void N64Mem_FreeOverlay(s16 actorId, u16 allocType); +int32_t N64Mem_AllocOverlay(int16_t actorId, uint16_t allocType); +void N64Mem_FreeOverlay(int16_t actorId, uint16_t allocType); // -------------------------------------------------------------------------------------------------------------------- // Actor instances: Paired with real ZeldaArena allocations. @@ -86,9 +86,9 @@ void N64Mem_FreeOverlay(s16 actorId, u16 allocType); // Call FreeInstance when the actor is deleted. // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocInstance(s16 actorId, s16 params, void* realPtr); +int32_t N64Mem_AllocInstance(int16_t actorId, int16_t params, void* realPtr); // Returns the stored original actor ID for overlay free-path tracking, or -1 if not tracked. -s16 N64Mem_FreeInstance(void* realPtr); +int16_t N64Mem_FreeInstance(void* realPtr); // -------------------------------------------------------------------------------------------------------------------- // Subsidiaries (colliders, camera, skin, etc.): Paired. @@ -96,7 +96,7 @@ s16 N64Mem_FreeInstance(void* realPtr); // Same pattern as instances -- shadow-alloc at N64 size, gate on failure, free when the real allocation is freed. // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocSubsidiary(void* realPtr, u32 n64Size); +int32_t N64Mem_AllocSubsidiary(void* realPtr, uint32_t n64Size); void N64Mem_FreeSubsidiary(void* realPtr); // -------------------------------------------------------------------------------------------------------------------- @@ -105,7 +105,7 @@ void N64Mem_FreeSubsidiary(void* realPtr); // On N64, effect overlays load via MallocR on first spawn and are never freed until the GameState is torn down. // -------------------------------------------------------------------------------------------------------------------- -s32 N64Mem_AllocEffectOverlay(s32 type); +int32_t N64Mem_AllocEffectOverlay(int32_t type); // -------------------------------------------------------------------------------------------------------------------- // Heap viewer metadata @@ -122,7 +122,7 @@ s32 N64Mem_AllocEffectOverlay(s32 type); #define N64MEM_BLOCK_EFFECT 4 #define N64MEM_BLOCK_ABSOLUTE 5 -s32 N64Mem_GetBlockInfo(u32 dataOffset, u8* outType, s16* outActorId); +int32_t N64Mem_GetBlockInfo(uint32_t dataOffset, uint8_t* outType, int16_t* outActorId); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp index 2c4d207b317..712efc42ea5 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp @@ -3,10 +3,10 @@ // -------------------------------------------------------------------------------------------------------------------- // DMA file sizes (misc/dma_sizes) // -// Format: u32 entryCount, then per entry: u32 vromSize, length-prefixed string name +// Format: uint32_t entryCount, then per entry: uint32_t vromSize, length-prefixed string name // -------------------------------------------------------------------------------------------------------------------- -static std::unordered_map sDmaFileSizes; +static std::unordered_map sDmaFileSizes; static bool sIsDmaLoaded = false; static void LoadDmaFileSizes() { @@ -27,9 +27,9 @@ static void LoadDmaFileSizes() { const auto reader = std::make_shared(stream); reader->SetEndianness(Ship::Endianness::Big); - const u32 entryCount = reader->ReadUInt32(); + const uint32_t entryCount = reader->ReadUInt32(); for (std::size_t i = 0; i < entryCount; ++i) { - const u32 vromSize = reader->ReadUInt32(); + const uint32_t vromSize = reader->ReadUInt32(); const std::string name = reader->ReadString(); sDmaFileSizes[name] = vromSize; } @@ -40,10 +40,10 @@ static void LoadDmaFileSizes() { // -------------------------------------------------------------------------------------------------------------------- // Actor overlay VRAM sizes (misc/actor_overlay_sizes) // -// Format: u32 entryCount, then entryCount consecutive u32 values indexed by actor ID +// Format: uint32_t entryCount, then entryCount consecutive uint32_t values indexed by actor ID // -------------------------------------------------------------------------------------------------------------------- -static std::vector sActorOverlaySizes; +static std::vector sActorOverlaySizes; static bool sIsActorOverlayLoaded = false; static void LoadActorOverlaySizes() { @@ -65,7 +65,7 @@ static void LoadActorOverlaySizes() { const auto reader = std::make_shared(stream); reader->SetEndianness(Ship::Endianness::Big); - const u32 entryCount = reader->ReadUInt32(); + const uint32_t entryCount = reader->ReadUInt32(); sActorOverlaySizes.resize(entryCount); for (std::size_t i = 0; i < entryCount; ++i) { @@ -78,10 +78,10 @@ static void LoadActorOverlaySizes() { // -------------------------------------------------------------------------------------------------------------------- // Effect overlay VRAM sizes (misc/effect_overlay_sizes) // -// Format: u32 entryCount, then entryCount consecutive u32 values indexed by effect type +// Format: uint32_t entryCount, then entryCount consecutive uint32_t values indexed by effect type // -------------------------------------------------------------------------------------------------------------------- -static std::vector sEffectOverlaySizes; +static std::vector sEffectOverlaySizes; static bool sIsEffectOverlayLoaded = false; static void LoadEffectOverlaySizes() { @@ -103,7 +103,7 @@ static void LoadEffectOverlaySizes() { const auto reader = std::make_shared(stream); reader->SetEndianness(Ship::Endianness::Big); - const u32 entryCount = reader->ReadUInt32(); + const uint32_t entryCount = reader->ReadUInt32(); sEffectOverlaySizes.resize(entryCount); for (std::size_t i = 0; i < entryCount; ++i) { @@ -116,11 +116,11 @@ static void LoadEffectOverlaySizes() { // -------------------------------------------------------------------------------------------------------------------- // Actor instance sizes (misc/actor_instance_sizes) // -// Format: u32 entryCount, then entryCount consecutive u32 values indexed by actor ID +// Format: uint32_t entryCount, then entryCount consecutive uint32_t values indexed by actor ID // Each value is the N64 sizeof the actor's instance struct, read from ActorProfile.instanceSize. // -------------------------------------------------------------------------------------------------------------------- -static std::vector sActorInstanceSizes; +static std::vector sActorInstanceSizes; static bool sIsActorInstanceLoaded = false; static void LoadActorInstanceSizes() { @@ -142,7 +142,7 @@ static void LoadActorInstanceSizes() { const auto reader = std::make_shared(stream); reader->SetEndianness(Ship::Endianness::Big); - const u32 entryCount = reader->ReadUInt32(); + const uint32_t entryCount = reader->ReadUInt32(); sActorInstanceSizes.resize(entryCount); for (std::size_t i = 0; i < entryCount; ++i) { @@ -156,7 +156,7 @@ static void LoadActorInstanceSizes() { // API // -------------------------------------------------------------------------------------------------------------------- -extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) { +extern "C" uint32_t N64SizeData_GetDmaFileSize(const char* name) { LoadDmaFileSizes(); if (const auto i = sDmaFileSizes.find(name); i != sDmaFileSizes.end()) { @@ -167,7 +167,7 @@ extern "C" u32 N64SizeData_GetDmaFileSize(const char* name) { return 0; } -extern "C" u32 N64SizeData_GetActorOverlaySize(u16 actorId) { +extern "C" uint32_t N64SizeData_GetActorOverlaySize(uint16_t actorId) { LoadActorOverlaySizes(); if (actorId < sActorOverlaySizes.size()) { @@ -177,7 +177,7 @@ extern "C" u32 N64SizeData_GetActorOverlaySize(u16 actorId) { return 0; } -extern "C" u32 N64SizeData_GetEffectOverlaySize(u16 effectType) { +extern "C" uint32_t N64SizeData_GetEffectOverlaySize(uint16_t effectType) { LoadEffectOverlaySizes(); if (effectType < sEffectOverlaySizes.size()) { @@ -187,7 +187,7 @@ extern "C" u32 N64SizeData_GetEffectOverlaySize(u16 effectType) { return 0; } -extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) { +extern "C" uint32_t N64SizeData_GetActorInstanceSize(uint16_t actorId) { LoadActorInstanceSizes(); if (actorId < sActorInstanceSizes.size()) { @@ -200,10 +200,10 @@ extern "C" u32 N64SizeData_GetActorInstanceSize(u16 actorId) { // -------------------------------------------------------------------------------------------------------------------- // Kaleido overlay max VRAM size (misc/kaleido_vram_size) // -// Format: single u32 -- max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) +// Format: single uint32_t -- max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) // -------------------------------------------------------------------------------------------------------------------- -static u32 sKaleidoVramSize = 0; +static uint32_t sKaleidoVramSize = 0; static bool sIsKaleidoLoaded = false; static void LoadKaleidoVramSize() { @@ -230,7 +230,7 @@ static void LoadKaleidoVramSize() { SPDLOG_INFO("[N64SizeData] Kaleido max VRAM size: 0x{:X}.", sKaleidoVramSize); } -extern "C" u32 N64SizeData_GetKaleidoVramSize() { +extern "C" uint32_t N64SizeData_GetKaleidoVramSize() { LoadKaleidoVramSize(); return sKaleidoVramSize; } @@ -238,10 +238,10 @@ extern "C" u32 N64SizeData_GetKaleidoVramSize() { // -------------------------------------------------------------------------------------------------------------------- // Arena node size // -// Format: single u32 -- ArenaNode size for this ROM version (0x10 retail, 0x30 debug) +// Format: single uint32_t -- ArenaNode size for this ROM version (0x10 retail, 0x30 debug) // -------------------------------------------------------------------------------------------------------------------- -static u32 sArenaNodeSize = 0x30; // Default to debug (safe fallback -- over-estimates node overhead) +static uint32_t sArenaNodeSize = 0x30; // Default to debug (safe fallback -- over-estimates node overhead) static bool sIsArenaNodeSizeLoaded = false; static void LoadArenaNodeSize() { @@ -269,7 +269,7 @@ static void LoadArenaNodeSize() { SPDLOG_INFO("[N64SizeData] Arena node size: 0x{:X}.", sArenaNodeSize); } -extern "C" u32 N64SizeData_GetArenaNodeSize() { +extern "C" uint32_t N64SizeData_GetArenaNodeSize() { LoadArenaNodeSize(); return sArenaNodeSize; } \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp index 55a7ba3b62b..51b80d56d62 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp @@ -2,6 +2,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + // -------------------------------------------------------------------------------------------------------------------- // N64 size data loaded from the OTR archive. // diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c index 16fa1077b48..548fce03cb2 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c @@ -6,7 +6,7 @@ // Declared in z_bgcheck.c but not exposed via header. s32 BgCheck_IsSpotScene(PlayState* play); -s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); +s32 BgCheck_TryGetCustomMemsize(s32 sceneId, uint32_t* memSize); // -------------------------------------------------------------------------------------------------------------------- // N64 THA budget @@ -22,9 +22,11 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); // -------------------------------------------------------------------------------------------------------------------- #define N64_SIZEOF_COLLISION_CONTEXT 0x1464 // CollisionContext size = 0x1464 (from decomp header comment) -#define N64_SIZEOF_GFX 8 // sizeof(Gfx) on N64: Two u32 words -- SoH is 16 -#define N64_SIZEOF_VTX 0x10 // sizeof(Vtx) on N64: Same on both platforms -#define N64_SIZEOF_EFFECT_SS 0x60 // sizeof(EffectSs) on N64: No pointer members, constant across all N64 versions +#define N64_SIZEOF_GFX 8 // sizeof(Gfx) on N64: Two uint32_t words -- SoH is 16 +#define N64_SIZEOF_VTX 0x10 // sizeof(Vtx) on N64: Same on both platforms +#define N64_SIZEOF_EFFECT_SS 0x60 // sizeof(EffectSs) on N64: No pointer members, constant across all N64 versions + + // Struct sizes that are identical on N64 and SoH (no pointer members): // sizeof(MtxF) = 0x40 @@ -39,11 +41,11 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); // Fixed THA consumers (same value on every N64 scene) // -------------------------------------------------------------------------------------------------------------------- -#define N64_MATRIX_STACK_SIZE (20 * 0x40) // sys_matrix.c: 20 * sizeof(MtxF) -#define N64_TEXT_BOX_SIZE 0x2200 // Message_Init: Constant -#define N64_DO_ACTION_SIZE 0x480 // z_construct.c: 3 * DO_ACTION_TEX_SIZE (48x16 IA4 = 0x180 each) -#define N64_ICON_ITEM_SIZE (0x1000 * 4) // z_construct.c: 4 * ITEM_ICON_SIZE (32x32 RGBA32) -#define N64_MAP_SEGMENT_SIZE 0x1000 // z_map_exp.c: DMA target buffer for minimap textures +#define N64_MATRIX_STACK_SIZE (20 * 0x40) // sys_matrix.c: 20 * sizeof(MtxF) +#define N64_TEXT_BOX_SIZE 0x2200 // Message_Init: Constant +#define N64_DO_ACTION_SIZE 0x480 // z_construct.c: 3 * DO_ACTION_TEX_SIZE (48x16 IA4 = 0x180 each) +#define N64_ICON_ITEM_SIZE (0x1000 * 4) // z_construct.c: 4 * ITEM_ICON_SIZE (32x32 RGBA32) +#define N64_MAP_SEGMENT_SIZE 0x1000 // z_map_exp.c: DMA target buffer for minimap textures // -------------------------------------------------------------------------------------------------------------------- // ovl_map_mark_data: N64-unique THA consumer for dungeon map marks @@ -56,7 +58,7 @@ s32 BgCheck_TryGetCustomMemsize(s32 sceneId, u32* memSize); #define N64_MAP_MARK_DATA_VRAM_SIZE 0x6B60 -static u32 GetMapMarkDataOverlaySize(PlayState* play) { +static uint32_t GetMapMarkDataOverlaySize(PlayState* play) { // Mirrors the condition in z_map_exp.c Map_Init: the dungeon case block's inner guard. // Main dungeons: SCENE_DEKU_TREE (0x00) through SCENE_ICE_CAVERN (0x09) // Boss rooms: SCENE_DEKU_TREE_BOSS (0x11) through SCENE_SHADOW_TEMPLE_BOSS (0x18) @@ -121,7 +123,7 @@ static const SkyboxDmaEntry sSkyboxDmaTable[] = { [SKYBOX_HOUSE_ALLEY] = { "vr_KR3VR_static", "vr_KR3VR_pal_static" }, }; -static u32 GetN64SkyboxTextureSize(s16 skyboxId) { +static uint32_t GetN64SkyboxTextureSize(int16_t skyboxId) { if (skyboxId == SKYBOX_NONE) { return 0; } @@ -134,18 +136,18 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) { // CUTSCENE_MAP loads two different texture files + 2 palette copies. if (skyboxId == SKYBOX_CUTSCENE_MAP) { - const u32 tex0 = N64SizeData_GetDmaFileSize("vr_holy0_static"); - const u32 tex1 = N64SizeData_GetDmaFileSize("vr_holy1_static"); - const u32 pal = N64SizeData_GetDmaFileSize("vr_holy0_pal_static"); + const uint32_t tex0 = N64SizeData_GetDmaFileSize("vr_holy0_static"); + const uint32_t tex1 = N64SizeData_GetDmaFileSize("vr_holy1_static"); + const uint32_t pal = N64SizeData_GetDmaFileSize("vr_holy0_pal_static"); return tex0 + tex1 + pal * 2; } // Indoor skyboxes: 1 texture + 1 palette, looked up from the DMA blob. - if (skyboxId >= 0 && skyboxId < (s16)ARRAY_COUNT(sSkyboxDmaTable)) { + if (skyboxId >= 0 && skyboxId < (int16_t)ARRAY_COUNT(sSkyboxDmaTable)) { const SkyboxDmaEntry* entry = &sSkyboxDmaTable[skyboxId]; if (entry->texName != NULL) { - const u32 tex = N64SizeData_GetDmaFileSize(entry->texName); - const u32 pal = N64SizeData_GetDmaFileSize(entry->palName); + const uint32_t tex = N64SizeData_GetDmaFileSize(entry->texName); + const uint32_t pal = N64SizeData_GetDmaFileSize(entry->palName); return tex + pal; } } @@ -163,7 +165,7 @@ static u32 GetN64SkyboxTextureSize(s16 skyboxId) { // Gfx is 8 bytes on N64, Vtx is 0x10. // -------------------------------------------------------------------------------------------------------------------- -static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVtxSize) { +static void GetSkyboxDlistAndVtxSize(int16_t skyboxId, uint32_t* outDlistSize, uint32_t* outVtxSize) { if (skyboxId == SKYBOX_NONE) { *outDlistSize = 0; *outVtxSize = 0; @@ -198,8 +200,8 @@ static void GetSkyboxDlistAndVtxSize(s16 skyboxId, u32* outDlistSize, u32* outVt // bgcheck_memSize and sizeof(CollisionContext). // -------------------------------------------------------------------------------------------------------------------- -static u32 GetBgCheckMemSize(PlayState* play) { - const s16 sceneNum = play->sceneNum; +static uint32_t GetBgCheckMemSize(PlayState* play) { + const int16_t sceneNum = play->sceneNum; if (YREG(15) == 0x10 || YREG(15) == 0x20 || YREG(15) == 0x30 || YREG(15) == 0x40) { return sceneNum == SCENE_STABLE ? 0x3520 : 0x4E20; @@ -209,7 +211,7 @@ static u32 GetBgCheckMemSize(PlayState* play) { return 0xF000; } - u32 customMemSize = 0; + uint32_t customMemSize = 0; if (BgCheck_TryGetCustomMemsize(sceneNum, &customMemSize)) { return customMemSize; } @@ -217,7 +219,7 @@ static u32 GetBgCheckMemSize(PlayState* play) { return 0x1CC00; } -static u32 GetBgCheckThaTotal(PlayState* play) { +static uint32_t GetBgCheckThaTotal(PlayState* play) { return GetBgCheckMemSize(play) - N64_SIZEOF_COLLISION_CONTEXT; } @@ -225,8 +227,8 @@ static u32 GetBgCheckThaTotal(PlayState* play) { // Object bank size (mirrors z_scene.c Object_InitBlank) // -------------------------------------------------------------------------------------------------------------------- -static u32 GetObjectBankSize(PlayState* play) { - const s16 sceneNum = play->sceneNum; +static uint32_t GetObjectBankSize(PlayState* play) { + const int16_t sceneNum = play->sceneNum; if (sceneNum == SCENE_GANON_BOSS && gSaveContext.sceneSetupIndex == 4) { return 1177600; } @@ -251,12 +253,12 @@ static const char* sElfMsgDmaNames[] = { "elf_message_ydan", }; -static u32 GetElfMessageSize(PlayState* play) { +static uint32_t GetElfMessageSize(PlayState* play) { if (play->cUpElfMsgs == NULL) { return 0; } - const u8 elfMsgNum = N64Mem_GetElfMsgNum(); + const uint8_t elfMsgNum = N64Mem_GetElfMsgNum(); if (elfMsgNum == 0 || elfMsgNum > ARRAY_COUNT(sElfMsgDmaNames)) { LUSLOG_WARN("[ArenaSizing] elfMsg: cUpElfMsgs non-NULL but elfMsgNum=%d out of range", elfMsgNum); return 0; @@ -283,8 +285,8 @@ static uintptr_t GetMaxRoomSize(PlayState* play) { const TransitionActorEntry* transitionActor = &play->transiActorCtx.list[0]; for (size_t j = 0; j < play->transiActorCtx.numActors; ++j) { - const s8 frontRoom = transitionActor->sides[0].room; - const s8 backRoom = transitionActor->sides[1].room; + const int8_t frontRoom = transitionActor->sides[0].room; + const int8_t backRoom = transitionActor->sides[1].room; const uintptr_t frontSize = frontRoom < 0 ? 0 : play->roomList[frontRoom].vromEnd - play->roomList[frontRoom].vromStart; @@ -308,21 +310,21 @@ static uintptr_t GetMaxRoomSize(PlayState* play) { // Main computation // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { +uint32_t ArenaSizing_ComputeN64ArenaSize(PlayState* play) { // Each THA consumer is allocated via GAME_STATE_ALLOC -> THA_AllocTailAlign16, which consumes ALIGN16(size) bytes. // We must align each consumer individually before summing; aligning the sum would under-count when individual // sizes are not 16-byte aligned (common for DMA file sizes). BgCheck is the exception -- its internal allocations // use mixed alignment, but the tblMax computation absorbs the internal waste, so the simplified total // (memSize - sizeof(CollisionContext)) is used as-is. - u32 total = 0; + uint32_t total = 0; // Kaleido overlay buffer: max(ovl_kaleido_scope, ovl_player_actor) VRAM span, from OTR blob. - const u32 kaleidoVramSize = N64SizeData_GetKaleidoVramSize(); + const uint32_t kaleidoVramSize = N64SizeData_GetKaleidoVramSize(); total += ALIGN16(kaleidoVramSize); // parameter_static: DMA file size, version-specific but available in the OTR blob. - const u32 parameterStaticSize = N64SizeData_GetDmaFileSize("parameter_static"); + const uint32_t parameterStaticSize = N64SizeData_GetDmaFileSize("parameter_static"); total += ALIGN16(parameterStaticSize); // Fixed consumers @@ -334,21 +336,21 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { total += ALIGN16(N64_ICON_ITEM_SIZE); total += ALIGN16(N64_MAP_SEGMENT_SIZE); - const u32 fixed = total; + const uint32_t fixed = total; LUSLOG_INFO("[ArenaSizing] fixed=0x%X (kaleido=0x%X, param=0x%X)", fixed, kaleidoVramSize, parameterStaticSize); } // Scene-dependent consumers { - const u32 objBank = GetObjectBankSize(play); - const u32 sceneFile = N64SizeData_GetDmaFileSize(play->loadedScene->sceneFile.fileName); + const uint32_t objBank = GetObjectBankSize(play); + const uint32_t sceneFile = N64SizeData_GetDmaFileSize(play->loadedScene->sceneFile.fileName); const uintptr_t roomBuf = GetMaxRoomSize(play); total += ALIGN16(objBank); total += ALIGN16(sceneFile); total += ALIGN16(roomBuf); - LUSLOG_INFO("[ArenaSizing] objBank=0x%X, sceneFile=0x%X, roomBuf=0x%x", (u32)objBank, (u32)sceneFile, - (u32)roomBuf); + LUSLOG_INFO("[ArenaSizing] objBank=0x%X, sceneFile=0x%X, roomBuf=0x%x", (uint32_t)objBank, (uint32_t)sceneFile, + (uint32_t)roomBuf); } // ---------------------------------------------------------------------------------------------------------------- @@ -360,9 +362,9 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { // vtx are separate allocations. // ---------------------------------------------------------------------------------------------------------------- { - u32 dListSize = 0; - u32 vtxSize = 0; - u32 texSize = 0; + uint32_t dListSize = 0; + uint32_t vtxSize = 0; + uint32_t texSize = 0; GetSkyboxDlistAndVtxSize(play->skyboxId, &dListSize, &vtxSize); texSize = GetN64SkyboxTextureSize(play->skyboxId); total += ALIGN16(dListSize); @@ -380,26 +382,26 @@ u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play) { // ALIGN16 needed here. // ---------------------------------------------------------------------------------------------------------------- { - const u32 bgCheck = GetBgCheckThaTotal(play); + const uint32_t bgCheck = GetBgCheckThaTotal(play); total += bgCheck; LUSLOG_INFO("[ArenaSizing] bgCheck=0x%X (memSize=0x%X)", bgCheck, GetBgCheckMemSize(play)); } // Elf message { - const u32 elfMsg = GetElfMessageSize(play); + const uint32_t elfMsg = GetElfMessageSize(play); total += ALIGN16(elfMsg); LUSLOG_INFO("[ArenaSizing] elfMsg=0x%X", elfMsg); } // Map mark data overlay (dungeons only) { - const u32 mapMarkData = GetMapMarkDataOverlaySize(play); + const uint32_t mapMarkData = GetMapMarkDataOverlaySize(play); total += ALIGN16(mapMarkData); } LUSLOG_INFO("[ArenaSizing] total=0x%X, arena=0x%X (budget=0x%X)", total, N64_THA_BUDGET - total, - (u32)N64_THA_BUDGET); + (uint32_t)N64_THA_BUDGET); if (total >= N64_THA_BUDGET) { return 0; diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h index 8f223fa2a28..6b9f390d28e 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h @@ -4,6 +4,8 @@ #ifdef __cplusplus extern "C" { + + #endif // -------------------------------------------------------------------------------------------------------------------- @@ -21,7 +23,7 @@ extern "C" { // (should not happen on valid scenes). // -------------------------------------------------------------------------------------------------------------------- -u32 ArenaSizing_ComputeN64ArenaSize(PlayState* play); +uint32_t ArenaSizing_ComputeN64ArenaSize(PlayState* play); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c index 4cd1f1dfc3a..de53714ddf8 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c @@ -7,22 +7,22 @@ // Internal node layout (not exposed -- heap viewer uses offset + size queries, not direct struct access). // // Fixed header fields (0x10 bytes, identical on retail and debug): -// 0x00 s16 magic -// 0x02 s16 isFree -// 0x04 u32 size (Payload bytes, excluding node header) -// 0x08 u32 next (Offset into buffer, SHADOW_NULL = end) -// 0x0C u32 prev (Offset into buffer, SHADOW_NULL = head) +// 0x00 int16_t magic +// 0x02 int16_t isFree +// 0x04 uint32_t size (Payload bytes, excluding node header) +// 0x08 uint32_t next (Offset into buffer, SHADOW_NULL = end) +// 0x0C uint32_t prev (Offset into buffer, SHADOW_NULL = head) // // On debug builds, an additional 0x20 bytes of debug fields follow (filename, line, threadId, arena, time). // The total node spacing is arena->nodeSize: 0x10 for retail, 0x30 for debug. // -------------------------------------------------------------------------------------------------------------------- typedef struct ShadowNode { - s16 magic; - s16 isFree; - u32 size; - u32 next; - u32 prev; + int16_t magic; + int16_t isFree; + uint32_t size; + uint32_t next; + uint32_t prev; } ShadowNode; // 0x10 static_assert(sizeof(ShadowNode) == 0x10, "ShadowNode header must be exactly 0x10 bytes"); @@ -34,11 +34,11 @@ static_assert(sizeof(ShadowNode) == 0x10, "ShadowNode header must be exactly 0x1 // Offset helpers // -------------------------------------------------------------------------------------------------------------------- -static ShadowNode* NodeAt(ShadowArena* arena, u32 offset) { +static ShadowNode* NodeAt(ShadowArena* arena, uint32_t offset) { return (ShadowNode*)(arena->buffer + offset); } -static s32 NodeIsValid(ShadowArena* arena, u32 offset) { +static int32_t NodeIsValid(ShadowArena* arena, uint32_t offset) { if (offset == SHADOW_NULL) { return 0; } @@ -50,7 +50,7 @@ static s32 NodeIsValid(ShadowArena* arena, u32 offset) { return NodeAt(arena, offset)->magic == NODE_MAGIC; } -static u32 NodeGetNext(ShadowArena* arena, u32 offset) { +static uint32_t NodeGetNext(ShadowArena* arena, uint32_t offset) { const ShadowNode* node = NodeAt(arena, offset); if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) { return node->next; @@ -59,7 +59,7 @@ static u32 NodeGetNext(ShadowArena* arena, u32 offset) { return SHADOW_NULL; } -static u32 NodeGetPrev(ShadowArena* arena, u32 offset) { +static uint32_t NodeGetPrev(ShadowArena* arena, uint32_t offset) { const ShadowNode* node = NodeAt(arena, offset); if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) { return node->prev; @@ -72,12 +72,12 @@ static u32 NodeGetPrev(ShadowArena* arena, u32 offset) { // Init / Destroy // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Init(ShadowArena* arena, u32 size, u32 nodeSize) { +void ShadowArena_Init(ShadowArena* arena, uint32_t size, uint32_t nodeSize) { // Match N64's alignment: Round start up to 16, round size down to 16. Since we control the buffer, start is // effectively offset 0 after alignment. We just ensure the usable size is 16-byte aligned. - const u32 alignedSize = size & ~0xF; + const uint32_t alignedSize = size & ~0xF; - arena->buffer = (u8*)malloc(alignedSize); + arena->buffer = (uint8_t*)malloc(alignedSize); if (!arena->buffer) { memset(arena, 0, sizeof(ShadowArena)); return; @@ -94,7 +94,6 @@ void ShadowArena_Init(ShadowArena* arena, u32 size, u32 nodeSize) { first->size = alignedSize - nodeSize; first->next = SHADOW_NULL; first->prev = SHADOW_NULL; - arena->head = 0; } @@ -110,10 +109,10 @@ void ShadowArena_Destroy(ShadowArena* arena) { // Malloc: First-fit forward (matches N64 __osMalloc) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { - u32 iterOff = 0; +uint32_t ShadowArena_Malloc(ShadowArena* arena, uint32_t size) { + uint32_t iterOff = 0; ShadowNode* iter = NULL; - u32 blockSize = 0; + uint32_t blockSize = 0; size = ALIGN16(size); blockSize = size + arena->nodeSize; @@ -124,7 +123,7 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { if (iter->isFree && iter->size >= size) { // Split if remainder can hold a new node + payload. if (blockSize < iter->size) { - const u32 newOff = iterOff + blockSize; + const uint32_t newOff = iterOff + blockSize; ShadowNode* newNode = NodeAt(arena, newOff); newNode->magic = NODE_MAGIC; @@ -157,11 +156,11 @@ u32 ShadowArena_Malloc(ShadowArena* arena, u32 size) { // MallocR: First-fit backward (matches N64 __osMallocR) // -------------------------------------------------------------------------------------------------------------------- -u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { - u32 iterOff = 0; - u32 nextOff = 0; +uint32_t ShadowArena_MallocR(ShadowArena* arena, uint32_t size) { + uint32_t iterOff = 0; + uint32_t nextOff = 0; ShadowNode* iter = NULL; - u32 blockSize = 0; + uint32_t blockSize = 0; size = ALIGN16(size); @@ -181,7 +180,7 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { // Split: Carve the allocation from the TOP of this free block. if (blockSize < iter->size) { - const u32 newOff = iterOff + (iter->size - size); + const uint32_t newOff = iterOff + (iter->size - size); ShadowNode* newNode = NodeAt(arena, newOff); newNode->magic = NODE_MAGIC; @@ -215,11 +214,11 @@ u32 ShadowArena_MallocR(ShadowArena* arena, u32 size) { // Free: Adjacent block coalescing (matches N64 _osFree) // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { - u32 nodeOff = 0; +void ShadowArena_Free(ShadowArena* arena, uint32_t dataOffset) { + uint32_t nodeOff = 0; ShadowNode* node = NULL; - u32 nextOff = 0; - u32 prevOff = 0; + uint32_t nextOff = 0; + uint32_t prevOff = 0; if (dataOffset == SHADOW_NULL) { return; @@ -271,8 +270,8 @@ void ShadowArena_Free(ShadowArena* arena, u32 dataOffset) { // Query // -------------------------------------------------------------------------------------------------------------------- -void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc) { - u32 iterOff = 0; +void ShadowArena_GetSizes(ShadowArena* arena, uint32_t* outMaxFree, uint32_t* outFree, uint32_t* outAlloc) { + uint32_t iterOff = 0; *outMaxFree = 0; *outFree = 0; @@ -294,11 +293,12 @@ void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32 } } -u32 ShadowArena_GetHead(ShadowArena* arena) { +uint32_t ShadowArena_GetHead(ShadowArena* arena) { return arena->head; } -s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext) { +int32_t ShadowArena_GetNodeInfo(ShadowArena* arena, uint32_t offset, int32_t* outIsFree, uint32_t* outSize, + uint32_t* outNext) { if (offset == SHADOW_NULL || offset + arena->nodeSize > arena->bufferSize) { return 0; } @@ -314,6 +314,6 @@ s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* return 1; } -u32 ShadowArena_GetBufferSize(ShadowArena* arena) { +uint32_t ShadowArena_GetBufferSize(ShadowArena* arena) { return arena->bufferSize; } \ No newline at end of file diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h index 4e0f56362f1..d3cd04557e4 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h @@ -1,9 +1,11 @@ #pragma once -#include +#include #ifdef __cplusplus extern "C" { + + #endif // Sentinel value for null offsets (no valid node can live at 0xFFFFFFFF in a buffer that's only ~245KB). @@ -16,40 +18,41 @@ extern "C" { #define SHADOW_NODE_SIZE_DEBUG 0x30 typedef struct ShadowArena { - u8* buffer; - u32 head; // Offset to first node - u32 bufferSize; - u32 nodeSize; // Per-version ArenaNode size (set at init from OTR data) + uint8_t* buffer; + uint32_t head; // Offset to first node + uint32_t bufferSize; + uint32_t nodeSize; // Per-version ArenaNode size (set at init from OTR data) } ShadowArena; // Allocate backing buffer and initialize with a single free node. nodeSize is the N64 ArenaNode size for this ROM // version (0x10 for retail, 0x30 for debug). -void ShadowArena_Init(ShadowArena* arena, u32 size, u32 nodeSize); +void ShadowArena_Init(ShadowArena* arena, uint32_t size, uint32_t nodeSize); // Free the backing buffer and zero the struct. void ShadowArena_Destroy(ShadowArena* arena); // First-fit forward allocation. Returns offset to data area, or SHADOW_NULL on failure. Matches N64 __osMalloc. -u32 ShadowArena_Malloc(ShadowArena* arena, u32 size); +uint32_t ShadowArena_Malloc(ShadowArena* arena, uint32_t size); // First-fit backward allocation. Returns offset to data area, or SHADOW_NULL on failure. Matches N64 __osMallocR. -u32 ShadowArena_MallocR(ShadowArena* arena, u32 size); +uint32_t ShadowArena_MallocR(ShadowArena* arena, uint32_t size); // Free a shadow allocation by data offset. Coalesces adjacent free blocks. Matches N64 __osFree. -void ShadowArena_Free(ShadowArena* arena, u32 dataOffset); +void ShadowArena_Free(ShadowArena* arena, uint32_t dataOffset); // Query arena statistics. -void ShadowArena_GetSizes(ShadowArena* arena, u32* outMaxFree, u32* outFree, u32* outAlloc); +void ShadowArena_GetSizes(ShadowArena* arena, uint32_t* outMaxFree, uint32_t* outFree, uint32_t* outAlloc); // Get the head node offset for external traversal (e.g., heap viewer). -u32 ShadowArena_GetHead(ShadowArena* arena); +uint32_t ShadowArena_GetHead(ShadowArena* arena); // Query a node's info by offset. Returns 1 on success, 0 if invalid. Used by the heap viewer to walk the shadow // without exposing internals. -s32 ShadowArena_GetNodeInfo(ShadowArena* arena, u32 offset, s32* outIsFree, u32* outSize, u32* outNext); +int32_t ShadowArena_GetNodeInfo(ShadowArena* arena, uint32_t offset, int32_t* outIsFree, uint32_t* outSize, + uint32_t* outNext); // Get the total buffer size. -u32 ShadowArena_GetBufferSize(ShadowArena* arena); +uint32_t ShadowArena_GetBufferSize(ShadowArena* arena); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp index c7901cfd5ab..3737dc1a1a9 100644 --- a/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp +++ b/soh/soh/Enhancements/debugger/HeapViewerWindow.cpp @@ -22,32 +22,32 @@ extern "C" { // ----------------------------------------------------------------------------------------------------------------- struct BlockInfo { - u32 offset; - std::size_t size; + uint32_t offset; + size_t size; bool isFree; - u8 type; // N64MEM_BLOCK_* (shadow only) - s16 actorId; // Original actor ID (shadow only), -1 if unknown + uint8_t type; // N64MEM_BLOCK_* (shadow only) + int16_t actorId; // Original actor ID (shadow only), -1 if unknown const char* actorName; bool isPinned; - bool isGhost; // Pinned block that was freed -- shown as a grayed-out row - u32 heapIndex; // Original index in heap order (for block map cross-reference) + bool isGhost; // Pinned block that was freed -- shown as a grayed-out row + uint32_t heapIndex; // Original index in heap order (for block map cross-reference) }; static std::vector sBlocks; -static std::vector sDisplayOrder; // Indices into sBlocks: pinned first, then unpinned -static u32 sPinnedCount = 0; -static std::set> sPinnedBlocks; // {actorId, blockType} pairs -static std::size_t sAllocTotal = 0; -static std::size_t sFreeTotal = 0; -static std::size_t sLargestFree = 0; -static u32 sNodeCount = 0; -static std::size_t sArenaSize = 0; -static std::size_t sPreviousAlloc = 0; -static u32 sCycleCount = 0; -static s32 sLastDelta = 0; +static std::vector sDisplayOrder; // Indices into sBlocks: pinned first, then unpinned +static uint32_t sPinnedCount = 0; +static std::set> sPinnedBlocks; // {actorId, blockType} pairs +static size_t sAllocTotal = 0; +static size_t sFreeTotal = 0; +static size_t sLargestFree = 0; +static uint32_t sNodeCount = 0; +static size_t sArenaSize = 0; +static size_t sPreviousAlloc = 0; +static uint32_t sCycleCount = 0; +static int32_t sLastDelta = 0; static bool sIsShadow = false; -static const char* GetActorName(s16 actorId) { +static const char* GetActorName(int16_t actorId) { if (actorId < 0) { return nullptr; } @@ -56,7 +56,7 @@ static const char* GetActorName(s16 actorId) { return entry.desc.empty() ? entry.entry.name : entry.desc.c_str(); } -static const char* BlockTypeName(u8 type) { +static const char* BlockTypeName(uint8_t type) { switch (type) { case N64MEM_BLOCK_INSTANCE: return "inst"; @@ -93,7 +93,7 @@ static const char* GetBlockDisplayName(const BlockInfo& block) { } if (block.type == N64MEM_BLOCK_EFFECT && block.actorId >= 0 && - block.actorId < static_cast(std::size(sEffectNames))) { + block.actorId < static_cast(std::size(sEffectNames))) { return sEffectNames[block.actorId]; } @@ -116,7 +116,7 @@ static void CollectZeldaArenaBlocks() { while (node) { BlockInfo block; - block.offset = static_cast(reinterpret_cast(node) + sizeof(ArenaNode)); + block.offset = static_cast(reinterpret_cast(node) + sizeof(ArenaNode)); block.size = node->size; block.isFree = node->isFree; block.type = N64MEM_BLOCK_FREE; @@ -140,7 +140,7 @@ static void CollectZeldaArenaBlocks() { } if (sPreviousAlloc != 0 && sAllocTotal != sPreviousAlloc) { - sLastDelta = static_cast(sAllocTotal) - static_cast(sPreviousAlloc); + sLastDelta = static_cast(sAllocTotal) - static_cast(sPreviousAlloc); ++sCycleCount; } @@ -162,12 +162,12 @@ static void CollectShadowBlocks() { } sArenaSize = ShadowArena_GetBufferSize(shadow); - u32 offset = ShadowArena_GetHead(shadow); + uint32_t offset = ShadowArena_GetHead(shadow); while (offset != SHADOW_NULL) { - s32 isFree = 0; - u32 size = 0; - u32 next = SHADOW_NULL; + int32_t isFree = 0; + uint32_t size = 0; + uint32_t next = SHADOW_NULL; if (!ShadowArena_GetNodeInfo(shadow, offset, &isFree, &size, &next)) { break; @@ -182,8 +182,8 @@ static void CollectShadowBlocks() { block.actorName = nullptr; if (!isFree) { - u8 type = 0; - s16 actorId = -1; + uint8_t type = 0; + int16_t actorId = -1; if (N64Mem_GetBlockInfo(block.offset, &type, &actorId)) { block.type = type; block.actorId = actorId; @@ -207,7 +207,7 @@ static void CollectShadowBlocks() { } if (sPreviousAlloc != 0 && sAllocTotal != sPreviousAlloc) { - sLastDelta = static_cast(sAllocTotal) - static_cast(sPreviousAlloc); + sLastDelta = static_cast(sAllocTotal) - static_cast(sPreviousAlloc); ++sCycleCount; } @@ -220,10 +220,10 @@ static void BuildDisplayOrder() { sPinnedCount = 0; // Track which pin keys have a live block. - std::set> matchedPins; + std::set> matchedPins; - for (std::size_t i = 0; i < sBlocks.size(); ++i) { - sBlocks[i].heapIndex = static_cast(i); + for (size_t i = 0; i < sBlocks.size(); ++i) { + sBlocks[i].heapIndex = static_cast(i); sBlocks[i].isGhost = false; std::pair key = { sBlocks[i].actorId, sBlocks[i].type }; sBlocks[i].isPinned = !sBlocks[i].isFree && sPinnedBlocks.contains(key); @@ -250,14 +250,14 @@ static void BuildDisplayOrder() { } // Pinned first (heap order preserved within group, ghosts at end of pinned section). - for (std::size_t i = 0; i < sBlocks.size(); ++i) { + for (size_t i = 0; i < sBlocks.size(); ++i) { if (sBlocks[i].isPinned && !sBlocks[i].isGhost) { sDisplayOrder.push_back(i); ++sPinnedCount; } } - for (std::size_t i = 0; i < sBlocks.size(); ++i) { + for (size_t i = 0; i < sBlocks.size(); ++i) { if (sBlocks[i].isGhost) { sDisplayOrder.push_back(i); ++sPinnedCount; @@ -265,7 +265,7 @@ static void BuildDisplayOrder() { } // Then unpinned. - for (std::size_t i = 0; i < sBlocks.size(); ++i) { + for (size_t i = 0; i < sBlocks.size(); ++i) { if (!sBlocks[i].isPinned) { sDisplayOrder.push_back(i); } @@ -276,7 +276,7 @@ static void BuildDisplayOrder() { // Colors // ----------------------------------------------------------------------------------------------------------------- -static ImU32 BlockColor(const BlockInfo& block) { +static int32_t BlockColor(const BlockInfo& block) { if (block.isFree) { return IM_COL32(60, 180, 80, 255); } @@ -321,7 +321,7 @@ static ImU32 ColorBorder() { void HeapViewerWindow::DrawElement() { // Single arena view: Shadow when active, ZeldaArena when not. const bool isShadowArena = N64Mem_IsActive(); - const u32 nodeHeaderSize = isShadowArena ? N64Mem_GetShadowArena()->nodeSize : sizeof(ArenaNode); + const uint32_t nodeHeaderSize = isShadowArena ? N64Mem_GetShadowArena()->nodeSize : sizeof(ArenaNode); if (isShadowArena) { CollectShadowBlocks(); @@ -356,8 +356,8 @@ void HeapViewerWindow::DrawElement() { ? (1.0f - static_cast(sLargestFree) / static_cast(sFreeTotal)) * 100.0f : 0.0f; - ImGui::Text("Arena: 0x%X (%u KB) | Nodes: %u", sArenaSize, sArenaSize / 1024, sNodeCount); - ImGui::Text("Alloc: 0x%X (%u KB) | Free: 0x%X (%u KB) | Largest: 0x%X (%u KB)", + ImGui::Text("Arena: 0x%llX (%llu KB) | Nodes: %u", sArenaSize, sArenaSize / 1024, sNodeCount); + ImGui::Text("Alloc: 0x%llX (%llu KB) | Free: 0x%llX (%llu KB) | Largest: 0x%llX (%llu KB)", sAllocTotal, sAllocTotal / 1024, sFreeTotal, sFreeTotal / 1024, sLargestFree, sLargestFree / 1024); @@ -366,7 +366,7 @@ void HeapViewerWindow::DrawElement() { ImGui::Text("Cycle: %u | Last delta: %s0x%X (%d bytes)", sCycleCount, sLastDelta >= 0 ? "+" : "-", - static_cast(std::abs(sLastDelta)), + static_cast(std::abs(sLastDelta)), sLastDelta); // --- Utilization bar --- @@ -391,9 +391,9 @@ void HeapViewerWindow::DrawElement() { drawList->AddRectFilled(mapPos, ImVec2(mapPos.x + availWidth, mapPos.y + mapHeight), ColorBorder()); f32 xCursor = 0.0f; - s32 hoveredBlock = -1; + int32_t hoveredBlock = -1; - for (std::size_t i = 0; i < sBlocks.size(); ++i) { + for (size_t i = 0; i < sBlocks.size(); ++i) { f32 nodeWidth = static_cast(nodeHeaderSize) / static_cast(sArenaSize) * availWidth; f32 blockWidth = static_cast(sBlocks[i].size) / static_cast(sArenaSize) * availWidth; @@ -423,7 +423,7 @@ void HeapViewerWindow::DrawElement() { const f32 fullX0 = mapPos.x + xCursor - nodeWidth; ImVec2 blockMin(fullX0, mapPos.y); if (ImVec2 blockMax(x1, mapPos.y + mapHeight); ImGui::IsMouseHoveringRect(blockMin, blockMax)) { - hoveredBlock = static_cast(i); + hoveredBlock = static_cast(i); } xCursor += blockWidth; @@ -444,7 +444,7 @@ void HeapViewerWindow::DrawElement() { } ImGui::Text("Offset: 0x%X", block.offset); - ImGui::Text("Size: 0x%X (%u bytes)", block.size, block.size); + ImGui::Text("Size: 0x%llX (%llu bytes)", block.size, block.size); ImGui::EndTooltip(); } @@ -456,11 +456,12 @@ void HeapViewerWindow::DrawElement() { ImGui::Text("Block List (%u blocks%s)", sNodeCount, sPinnedCount > 0 ? fmt::format(", {} pinned", sPinnedCount).c_str() : ""); - if (const s32 columnCount = sIsShadow ? 5 : 4; ImGui::BeginTable("##blocks", columnCount, - ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | - ImGuiTableFlags_ScrollY | - ImGuiTableFlags_Resizable, - ImVec2(0, ImGui::GetContentRegionAvail().y))) { + if (const int32_t columnCount = sIsShadow ? 5 : 4; ImGui::BeginTable("##blocks", columnCount, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg + | + ImGuiTableFlags_ScrollY | + ImGuiTableFlags_Resizable, + ImVec2(0, ImGui::GetContentRegionAvail().y))) { ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, 40.0f); if (sIsShadow) { @@ -470,11 +471,11 @@ void HeapViewerWindow::DrawElement() { ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 60.0f); ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, 140.0f); ImGui::TableSetupColumn("Offset", ImGuiTableColumnFlags_WidthFixed, 100.0f); - ImGui::TableSetupScrollFreeze(0, 1 + static_cast(sPinnedCount)); + ImGui::TableSetupScrollFreeze(0, 1 + static_cast(sPinnedCount)); ImGui::TableHeadersRow(); - for (std::size_t di = 0; di < sDisplayOrder.size(); ++di) { - const std::size_t blockIdx = sDisplayOrder[di]; + for (size_t di = 0; di < sDisplayOrder.size(); ++di) { + const size_t blockIdx = sDisplayOrder[di]; const auto& block = sBlocks[blockIdx]; ImGui::TableNextRow(); @@ -532,7 +533,7 @@ void HeapViewerWindow::DrawElement() { } else if (block.isFree) { ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.3f, 1.0f), "free"); } else if (sIsShadow) { - const ImU32 color = BlockColor(block); + const int32_t color = BlockColor(block); ImVec4 colorVec = ImGui::ColorConvertU32ToFloat4(color); ImGui::TextColored(colorVec, "%s", BlockTypeName(block.type)); } else { @@ -544,7 +545,7 @@ void HeapViewerWindow::DrawElement() { if (block.isGhost) { ImGui::TextDisabled("--"); } else { - ImGui::Text("0x%X (%u)", block.size, block.size); + ImGui::Text("0x%llX (%llu)", block.size, block.size); } // Offset From 70b66a025bd0541a32757db8086641e9b6b25e2c Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 15 May 2026 08:23:36 -0700 Subject: [PATCH 56/58] chore: Update comments about OTR blob references --- .../HardwareMemoryLimits/N64SizeData.cpp | 12 +++++++----- .../HardwareMemoryLimits/N64SizeData.hpp | 14 +++++++------- .../HardwareMemoryLimits/n64_arena_sizing.h | 1 + .../HardwareMemoryLimits/n64_shadow_arena.h | 5 +++-- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp index 712efc42ea5..6e1b8762dcc 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.cpp @@ -1,7 +1,9 @@ #include "N64SizeData.hpp" +#include + // -------------------------------------------------------------------------------------------------------------------- -// DMA file sizes (misc/dma_sizes) +// DMA file sizes (misc/n64_memory/dma_sizes) // // Format: uint32_t entryCount, then per entry: uint32_t vromSize, length-prefixed string name // -------------------------------------------------------------------------------------------------------------------- @@ -38,7 +40,7 @@ static void LoadDmaFileSizes() { } // -------------------------------------------------------------------------------------------------------------------- -// Actor overlay VRAM sizes (misc/actor_overlay_sizes) +// Actor overlay VRAM sizes (misc/n64_memory/actor_overlay_sizes) // // Format: uint32_t entryCount, then entryCount consecutive uint32_t values indexed by actor ID // -------------------------------------------------------------------------------------------------------------------- @@ -76,7 +78,7 @@ static void LoadActorOverlaySizes() { } // -------------------------------------------------------------------------------------------------------------------- -// Effect overlay VRAM sizes (misc/effect_overlay_sizes) +// Effect overlay VRAM sizes (misc/n64_memory/effect_overlay_sizes) // // Format: uint32_t entryCount, then entryCount consecutive uint32_t values indexed by effect type // -------------------------------------------------------------------------------------------------------------------- @@ -114,7 +116,7 @@ static void LoadEffectOverlaySizes() { } // -------------------------------------------------------------------------------------------------------------------- -// Actor instance sizes (misc/actor_instance_sizes) +// Actor instance sizes (misc/n64_memory/actor_instance_sizes) // // Format: uint32_t entryCount, then entryCount consecutive uint32_t values indexed by actor ID // Each value is the N64 sizeof the actor's instance struct, read from ActorProfile.instanceSize. @@ -198,7 +200,7 @@ extern "C" uint32_t N64SizeData_GetActorInstanceSize(uint16_t actorId) { } // -------------------------------------------------------------------------------------------------------------------- -// Kaleido overlay max VRAM size (misc/kaleido_vram_size) +// Kaleido overlay max VRAM size (misc/n64_memory/kaleido_vram_size) // // Format: single uint32_t -- max(ovl_kaleido_scope VRAM, ovl_player_actor VRAM) // -------------------------------------------------------------------------------------------------------------------- diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp index 51b80d56d62..15de0488ad9 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/N64SizeData.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #ifdef __cplusplus extern "C" { @@ -20,12 +20,12 @@ extern "C" { // Each table is loaded lazily on first query and cached for the lifetime of the process. // -------------------------------------------------------------------------------------------------------------------- -u32 N64SizeData_GetDmaFileSize(const char* name); -u32 N64SizeData_GetActorOverlaySize(u16 actorId); -u32 N64SizeData_GetEffectOverlaySize(u16 effectType); -u32 N64SizeData_GetActorInstanceSize(u16 actorId); -u32 N64SizeData_GetKaleidoVramSize(void); -u32 N64SizeData_GetArenaNodeSize(void); +uint32_t N64SizeData_GetDmaFileSize(const char* name); +uint32_t N64SizeData_GetActorOverlaySize(uint16_t actorId); +uint32_t N64SizeData_GetEffectOverlaySize(uint16_t effectType); +uint32_t N64SizeData_GetActorInstanceSize(uint16_t actorId); +uint32_t N64SizeData_GetKaleidoVramSize(void); +uint32_t N64SizeData_GetArenaNodeSize(void); #ifdef __cplusplus } diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h index 6b9f390d28e..3aee53893c5 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h @@ -6,6 +6,7 @@ extern "C" { + #endif // -------------------------------------------------------------------------------------------------------------------- diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h index d3cd04557e4..097530a209b 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h @@ -6,6 +6,7 @@ extern "C" { + #endif // Sentinel value for null offsets (no valid node can live at 0xFFFFFFFF in a buffer that's only ~245KB). @@ -19,9 +20,9 @@ extern "C" { typedef struct ShadowArena { uint8_t* buffer; - uint32_t head; // Offset to first node + uint32_t head; // Offset to first node uint32_t bufferSize; - uint32_t nodeSize; // Per-version ArenaNode size (set at init from OTR data) + uint32_t nodeSize; // Per-version ArenaNode size (set at init from OTR data) } ShadowArena; // Allocate backing buffer and initialize with a single free node. nodeSize is the N64 ArenaNode size for this ROM From f3941f6fac47e26629ae6f10ec1572826fe06acd Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 15 May 2026 09:29:09 -0700 Subject: [PATCH 57/58] chore: Cleanup comments and includes --- .../HardwareMemoryLimits.cpp | 25 +++++++++---------- .../HardwareMemoryLimits.hpp | 10 ++++---- .../HardwareMemoryLimits/n64_arena_sizing.c | 5 +++- .../HardwareMemoryLimits/n64_arena_sizing.h | 4 +-- .../HardwareMemoryLimits/n64_shadow_arena.c | 8 +++--- .../HardwareMemoryLimits/n64_shadow_arena.h | 5 ++-- 6 files changed, 29 insertions(+), 28 deletions(-) diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp index 999d20f9c00..c07264d2b04 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include "soh/Enhancements/game-interactor/GameInteractor.h" @@ -12,7 +12,6 @@ extern "C" { #include "N64SizeData.hpp" #include "n64_shadow_arena.h" #include "n64_arena_sizing.h" -#include "global.h" } #define CVAR_NAME CVAR_ENHANCEMENT("HardwareMemoryLimits") @@ -23,8 +22,8 @@ extern "C" { // State // -------------------------------------------------------------------------------------------------------------------- -static bool sIsActive = false; -static ShadowArena sShadow; +static int32_t sIsActive = 0; +static ShadowArena sShadow = {}; static uint32_t sSohThaRemainder = 0; static uint8_t sElfMsgNum = 0; @@ -61,7 +60,7 @@ static int16_t sOriginalActorId = -1; // Set when AllocInstance consumes a non-negative sOriginalActorId, cleared after Actor_Init returns. While set, ALL // shadow allocations (overlay, instance, subsidiary) from child actors spawned during the replacement actor's init are // skipped -- on N64 these children don't exist because the original actor never spawned them. -static int32_t sInsideRandomizedInit = 0; +static int32_t sIsInsideRandomizedInit = 0; // Returns the actor ID to use for size lookups. If an original actor ID override is set (enemy randomizer active), // returns that; otherwise returns the passed actorId unchanged. @@ -163,7 +162,7 @@ void N64Mem_Reset(PlayState* play) { sBlockMetaMap.clear(); sAbsoluteSpaceShadow = SHADOW_NULL; sOriginalActorId = -1; - sInsideRandomizedInit = 0; + sIsInsideRandomizedInit = 0; for (uint32_t& sOverlayShadow : sOverlayShadows) { sOverlayShadow = SHADOW_NULL; @@ -287,11 +286,11 @@ void N64Mem_ClearOriginalActorId() { } int32_t N64Mem_GetRandomizedInit() { - return sInsideRandomizedInit; + return sIsInsideRandomizedInit; } void N64Mem_SetRandomizedInit(int32_t value) { - sInsideRandomizedInit = value; + sIsInsideRandomizedInit = value; } // -------------------------------------------------------------------------------------------------------------------- @@ -304,7 +303,7 @@ int32_t N64Mem_AllocOverlay(int16_t actorId, uint16_t allocType) { } // Child actors spawned during a randomized enemy's init don't exist on N64 -- skip shadow tracking. - if (sInsideRandomizedInit) { + if (sIsInsideRandomizedInit) { return 1; } @@ -398,7 +397,7 @@ int32_t N64Mem_AllocInstance(int16_t actorId, int16_t params, void* realPtr) { } // Child actors spawned during a randomized enemy's init don't exist on N64 -- skip shadow tracking. - if (sInsideRandomizedInit) { + if (sIsInsideRandomizedInit) { return 1; } @@ -406,10 +405,10 @@ int32_t N64Mem_AllocInstance(int16_t actorId, int16_t params, void* realPtr) { // Consume the override and enter the randomized-init phase. Subsidiaries allocated during Actor_Init will be // skipped (they belong to the replacement, not the original). Child Actor_Spawn calls during init will also - // be skipped via the sInsideRandomizedInit check above. + // be skipped via the sIsInsideRandomizedInit check above. if (sOriginalActorId >= 0) { sOriginalActorId = -1; - sInsideRandomizedInit = 1; + sIsInsideRandomizedInit = 1; } if (actorId < 0 || actorId >= ACTOR_ID_MAX) { @@ -482,7 +481,7 @@ int32_t N64Mem_AllocSubsidiary(void* realPtr, uint32_t n64Size) { // When inside a randomized enemy's init, the replacement actor's subsidiaries (colliders, skeleton tables, skin // buffers) have different counts than the original's. Rather than charge the wrong sizes, skip subsidiary // tracking entirely -- instance and overlay sizes from the original are already correct and dominate heap pressure. - if (sInsideRandomizedInit) { + if (sIsInsideRandomizedInit) { return 1; } diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp index c9b65cf1ba4..0c10abca9da 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp @@ -70,8 +70,8 @@ void N64Mem_BenchmarkTransition(PlayState* play); // On N64, overlays load into ZeldaArena from ROM on first spawn and free when no instances remain. SoH compiles them // into the binary, so they never touch ZeldaArena. The shadow restores this pressure. // -// Call AllocOverlay when numLoaded transitions 0 -> 1. -// Call FreeOverlay when numLoaded transitions 1 -> 0. +// Call AllocOverlay when numLoaded transitions 0 -> true. +// Call FreeOverlay when numLoaded transitions 1 -> false. // -------------------------------------------------------------------------------------------------------------------- int32_t N64Mem_AllocOverlay(int16_t actorId, uint16_t allocType); @@ -81,7 +81,7 @@ void N64Mem_FreeOverlay(int16_t actorId, uint16_t allocType); // Actor instances: Paired with real ZeldaArena allocations. // // Call AllocInstance after the real allocation succeeds. If the shadow cannot satisfy the N64-sized allocation, -// returns 0 and the caller should treat the spawn as failed. +// returns false and the caller should treat the spawn as failed. // // Call FreeInstance when the actor is deleted. // -------------------------------------------------------------------------------------------------------------------- @@ -110,8 +110,8 @@ int32_t N64Mem_AllocEffectOverlay(int32_t type); // -------------------------------------------------------------------------------------------------------------------- // Heap viewer metadata // -// Query block identity from the shadow arena. Returns 1 if metadata exists for the given data offset, 0 if not. -// Block type constants identify the allocation category; actorId is the original (pre-randomizer) actor ID for +// Query block identity from the shadow arena. Returns true if metadata exists for the given data offset, false if +// not. Block type constants identify the allocation category; actorId is the original (pre-randomizer) actor ID for // instance/overlay blocks, or -1 for subsidiary/effect/absolute blocks. // -------------------------------------------------------------------------------------------------------------------- diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c index 548fce03cb2..436a868fabc 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.c @@ -2,7 +2,10 @@ #include "N64SizeData.hpp" #include "soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp" -#include "global.h" +#include + +#include "macros.h" +#include "variables.h" // Declared in z_bgcheck.c but not exposed via header. s32 BgCheck_IsSpotScene(PlayState* play); diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h index 3aee53893c5..0f3ab4812a8 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_arena_sizing.h @@ -16,8 +16,8 @@ extern "C" { // arena = THA_BUDGET - sum(all THA consumers at N64 sizes) // // All per-version constants are derived from OTR blobs at runtime: -// kaleidoOverlayVramSize -> N64SizeData_GetKaleidoVramSize() (misc/kaleido_vram_size) -// parameterStaticSize -> N64SizeData_GetDmaFileSize(...) (misc/dma_sizes) +// kaleidoOverlayVramSize -> N64SizeData_GetKaleidoVramSize() (misc/n64_memory/kaleido_vram_size) +// parameterStaticSize -> N64SizeData_GetDmaFileSize(...) (misc/n64_memory/dma_sizes) // effectSsSize -> N64_SIZEOF_EFFECT_SS (constant 0x60, all N64 versions) // // Called from N64Mem_Reset after Play_Init has populated scene data. Returns 0 if THA consumption exceeds budget diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c index de53714ddf8..c715d5c31d1 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.c @@ -38,7 +38,7 @@ static ShadowNode* NodeAt(ShadowArena* arena, uint32_t offset) { return (ShadowNode*)(arena->buffer + offset); } -static int32_t NodeIsValid(ShadowArena* arena, uint32_t offset) { +static int32_t IsNodeValid(ShadowArena* arena, uint32_t offset) { if (offset == SHADOW_NULL) { return 0; } @@ -52,7 +52,7 @@ static int32_t NodeIsValid(ShadowArena* arena, uint32_t offset) { static uint32_t NodeGetNext(ShadowArena* arena, uint32_t offset) { const ShadowNode* node = NodeAt(arena, offset); - if (node->next != SHADOW_NULL && NodeIsValid(arena, node->next)) { + if (node->next != SHADOW_NULL && IsNodeValid(arena, node->next)) { return node->next; } @@ -61,7 +61,7 @@ static uint32_t NodeGetNext(ShadowArena* arena, uint32_t offset) { static uint32_t NodeGetPrev(ShadowArena* arena, uint32_t offset) { const ShadowNode* node = NodeAt(arena, offset); - if (node->prev != SHADOW_NULL && NodeIsValid(arena, node->prev)) { + if (node->prev != SHADOW_NULL && IsNodeValid(arena, node->prev)) { return node->prev; } @@ -310,7 +310,7 @@ int32_t ShadowArena_GetNodeInfo(ShadowArena* arena, uint32_t offset, int32_t* ou *outIsFree = node->isFree; *outSize = node->size; - *outNext = node->next != SHADOW_NULL && NodeIsValid(arena, node->next) ? node->next : SHADOW_NULL; + *outNext = node->next != SHADOW_NULL && IsNodeValid(arena, node->next) ? node->next : SHADOW_NULL; return 1; } diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h index 097530a209b..3840c330bba 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h @@ -6,7 +6,6 @@ extern "C" { - #endif // Sentinel value for null offsets (no valid node can live at 0xFFFFFFFF in a buffer that's only ~245KB). @@ -47,8 +46,8 @@ void ShadowArena_GetSizes(ShadowArena* arena, uint32_t* outMaxFree, uint32_t* ou // Get the head node offset for external traversal (e.g., heap viewer). uint32_t ShadowArena_GetHead(ShadowArena* arena); -// Query a node's info by offset. Returns 1 on success, 0 if invalid. Used by the heap viewer to walk the shadow -// without exposing internals. +// Query a node's info by offset. Returns true on success, false if invalid. Used by the heap viewer to walk the +// shadow without exposing internals. int32_t ShadowArena_GetNodeInfo(ShadowArena* arena, uint32_t offset, int32_t* outIsFree, uint32_t* outSize, uint32_t* outNext); From e65e8f0aa15f5810efaebc1adb757bf08256df57 Mon Sep 17 00:00:00 2001 From: Unreference <87878910+unreference@users.noreply.github.com> Date: Fri, 15 May 2026 10:00:38 -0700 Subject: [PATCH 58/58] fix(restoration): Suppress shadow allocations for randomized actor children --- .../HardwareMemoryLimits.cpp | 13 +++++++++++++ .../HardwareMemoryLimits.hpp | 4 ++++ .../HardwareMemoryLimits/n64_shadow_arena.h | 1 + soh/src/code/z_actor.c | 19 +++++++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp index c07264d2b04..3ff21bc736e 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.cpp @@ -285,6 +285,14 @@ void N64Mem_ClearOriginalActorId() { sOriginalActorId = -1; } +int32_t N64Mem_IsRandomizedActor(void* realPtr) { + if (!sIsActive || !realPtr) { + return 0; + } + + return sActorOriginalIds.contains(realPtr) ? 1 : 0; +} + int32_t N64Mem_GetRandomizedInit() { return sIsInsideRandomizedInit; } @@ -522,6 +530,11 @@ int32_t N64Mem_AllocEffectOverlay(int32_t type) { return 1; } + // Effect overlays triggered by a randomized replacement's children or update don't exist on N64 -- skip shadowing. + if (sIsInsideRandomizedInit) { + return 1; + } + if (type < 0 || type >= EFFECT_SS_TYPE_MAX) { return 1; } diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp index 0c10abca9da..85c3e1a84ed 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/HardwareMemoryLimits.hpp @@ -56,6 +56,10 @@ void N64Mem_LogState(const char* context); void N64Mem_SetOriginalActorId(int16_t actorId); void N64Mem_ClearOriginalActorId(void); +// Returns true if the actor at realPtr is a randomized replacement (i.e., has an original-ID entry), false otherwise. +// Used by Actor_UpdateAll to suppress shadow allocations for children spawned during a replacement's update. +int32_t N64Mem_IsRandomizedActor(void* realPtr); + // Save/restore for the randomized-init skip flag. Actor_Spawn saves the current value at entry and restores it // after Actor_Init returns, so child spawns during a randomized actor's init don't clobber the parent's flag. int32_t N64Mem_GetRandomizedInit(void); diff --git a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h index 3840c330bba..c6327896ef4 100644 --- a/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h +++ b/soh/soh/Enhancements/Restorations/HardwareMemoryLimits/n64_shadow_arena.h @@ -6,6 +6,7 @@ extern "C" { + #endif // Sentinel value for null offsets (no valid node can live at 0xFFFFFFFF in a buffer that's only ~245KB). diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 45f6651e5e5..4161d95fa01 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -2711,7 +2711,26 @@ void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) { actor->colorFilterTimer--; } if (GameInteractor_ShouldActorUpdate(actor)) { + // #region SOH [Enhancement] - Hardware Memory Limits + // + // On N64, the original actor would be running here and its children would be different -- we + // already charged the original's overlay and instance sizes, so the replacement children are + // excess. + // + // If the actor is a randomized replacement, suppress shadow allocations for any children it + // spawns during its update (Actor_Spawn, Actor_SpawnAsChild, subsidiaries). + const s32 n64MemSavedUpdate = N64Mem_GetRandomizedInit(); + if (N64Mem_IsRandomizedActor(actor)) { + N64Mem_SetRandomizedInit(1); + } + // #endregion + actor->update(actor, play); + + // #region SOH [Enhancement] - Hardware Memory Limits + N64Mem_SetRandomizedInit(n64MemSavedUpdate); + // #endregion + GameInteractor_ExecuteOnActorUpdate(actor); } func_8003F8EC(play, &play->colCtx.dyna, actor);