Skip to content

Emulate Heap Fragmentation - #3863

Closed
inspectredc wants to merge 8 commits into
HarbourMasters:developfrom
inspectredc:stairs-emulator
Closed

Emulate Heap Fragmentation#3863
inspectredc wants to merge 8 commits into
HarbourMasters:developfrom
inspectredc:stairs-emulator

Conversation

@inspectredc

@inspectredc inspectredc commented Jan 15, 2024

Copy link
Copy Markdown
Member

????????????????????????????

This is a system for simulating how the heap would be allocated on original hardware. Actors are then checked against this heap to see if it would be possible to load the actor on hardware, before it gets loaded. Allocated memory to this heap is all zeroed and not used for any actual data used by the game.

The most common use case for this is the 'stairs' glitch where the Royal Tomb is not loaded after switching between the two 'rooms' on the stairs in the graveyard scene, allowing you to enter the Tomb without Zelda's Lullaby.

Would love any feedback on the system and how I can improve the stairs.cpp in particular (or any better renames).

There are a few things which can be done differently to get a better alignment to console, including a better method for calculating the original heap size to start with. I'm not quite sure how much the skybox, for example, should be allocating. The other shortcoming is that actor struct sizes may differ in SoH than on console, which should only lead to minor differences itself.

I've also added some watches in the Value Viewer since it seemed appropriate and helped me debug a lot.

Build Artifacts

@@ -0,0 +1,675 @@
#include "stairs.h"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this file is basically simulating the entire heap, which could be used for more than just this graveyard use case, I would recommend renaming this file and all associated heap functions that aren't specific to just this use case to something more general, like heap_sim or simulated_heap.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes makes sense!

#undef DEFINE_ACTOR

// Original size subtracted by various constant allocations which are always made on init, so those allocations don't need to be made there
size_t stairsAllocSize = 0x1D4790 - 0x26960 - 0x2200 - (0x55 * 0x60) - (3 * (48 * 16) / 2) - (4 * (32 * 32 * 4)) - 0x1000 - (20 * sizeof(MtxF));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to mention what all these magic numbers correspond to so that future uses will know if and how they need to do something similar.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, this section is already something which isnt the most accurate so may replace it with a more correct system anyway

Comment thread soh/src/code/z_actor.c
actorCtx->absoluteSpace = NULL;
}

if (CVarGetInteger("gStairs", 0)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would make more sense to me to have all these simulated heap functions controlled by the gSimulateHeap cvar, and then use your gStairs one ONLY for the graveyard use case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the "gStairs" cvar is just a silent variable (i.e. the user has no control over it) and then gSimulateHeap cvar will set this on/off at the start of play init. this is so that the heap is not allocated memory when it shouldnt, and that the memory always gets freed properly at the end of the play loop

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the reasoning here, however, if this is going to be useful for emulating more than the Royal Tomb Grave unloading glitch, this variable should have a better name.

Comment thread soh/src/code/z_play.c

// #Region [SoH] Stairs
if (CVarGetInteger("gSimulateHeap", 0)) {
CVarSetInteger("gStairs", 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By placing this in Play_Init, changes to the checkbox in the UI won't apply until the next time you change areas, since that's when Play_Init is run, right? It might be nice to mention that in the tooltip that that checking or unchecking the box won't apply until the next area load.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes will add that to the tooltip!

@leggettc18

Copy link
Copy Markdown
Contributor

Would this cause any issues with how we've been handling (i.e. removing) Object Dependency? I'll need to deep dive before I can properly review this because I don't fully understand it yet, but that question just popped into my head upon reading your description. I'm not sure if it's really connected in anyway at all so if you're not sure the answer is probably no. I'll do a full review at some point soon.

@inspectredc

Copy link
Copy Markdown
Member Author

Nah fortunately this is unrelated to object dependency, this is in regards to the heap rather than the object list that has been loaded. The actors don't actually use this memory anyway, it's all done so that a condition which checks 'do i have space to load this actor' returns as false

@inspectredc

Copy link
Copy Markdown
Member Author

Also thank you @jbodner09 ! I will try and find some time this week to address your review comments

@leggettc18

Copy link
Copy Markdown
Contributor

Nah fortunately this is unrelated to object dependency, this is in regards to the heap rather than the object list that has been loaded. The actors don't actually use this memory anyway, it's all done so that a condition which checks 'do i have space to load this actor' returns as false

OK cool, and this is behind a toggle right? It looks like it is just making sure. Otherwise I would worry about this potentially limiting modding potential.

@inspectredc

Copy link
Copy Markdown
Member Author

Yeah it has a kind of locking system, so you can toggle it, but the toggle only takes effect when play init happens (i.e. new scene load). otherwise you would need to worry about freeing the heap or issues allocating to a heap with no initialised size

@leggettc18

Copy link
Copy Markdown
Contributor

Yeah it has a kind of locking system, so you can toggle it, but the toggle only takes effect when play init happens (i.e. new scene load). otherwise you would need to worry about freeing the heap or issues allocating to a heap with no initialised size

OK, as long as that's mentioned somewhere I think that's OK. It wouldn't be the first enhancement that has issues toggling without a scene reload. I'm about to start an actual review but it might take a bit for me to parse it all.

@leggettc18 leggettc18 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks reasonable to the best of my understanding, although I have asked for some clarification on naming, a few commented out chunks that need either an explanation for why they are commented or to just be removed, and some magic numbers that I would like some explanation for.

As a followup, not necessarily needed for this PR, would it be possible to enable the heap emulation and visualization, without it actually affecting gameplay? I could see that being useful for research purposes (although I suppose it could still be useful for research without decoupling the visualization from the gameplay). That may not be reasonable or all that useful, just a thought that popped into my head.

Arena sStairsArena;

// using sizes from the debug rom
std::unordered_map<u16, size_t> actorOverlaySizes = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To the best of my understanding, this should probably be static const. So static const std::unordered_map.... Assuming you aren't changing this at runtime any. Shouldn't impact functionality, more of a correctness factor for storage/type specification.

return true;
}

void* StairsArena_Malloc(size_t size) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might just be lack of knowledge on my part, but I don't really understand what the difference is between Malloc, MallocR, MallocGeneral, MallocRGeneral, etc. are. If that's the kind of thing that can be briefly explained in a comment somewhere in here I think that would be good, if not I'll ask you at some point later because it's probably just some deep memory management knowledge that I'm missing.

Comment thread soh/soh/SohMenuBar.cpp
Comment on lines +1150 to +1151
UIWidgets::PaddedEnhancementCheckbox("Simulate N64 Heap", "gSimulateHeap", true, false);
UIWidgets::Tooltip("This restores heap fragmentation glitches, such as causing the Royal Tomb to not load when reloading its graveyard room.");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a note about requiring a scene reload for this to take effect.

Comment thread soh/soh/SohMenuBar.cpp
Comment on lines +1472 to +1473
UIWidgets::EnhancementCheckbox("Stairs heap display", "gStairsDisplay");
UIWidgets::Tooltip("Visualises the stair heap");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC there was some talk about this being used for more than just the stairs outside shadow temple, so genericizing this language would be good (or do the other places where this is relevant require a separate visualizer?)

Comment thread soh/src/code/z_actor.c
actorCtx->absoluteSpace = NULL;
}

if (CVarGetInteger("gStairs", 0)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the reasoning here, however, if this is going to be useful for emulating more than the Royal Tomb Grave unloading glitch, this variable should have a better name.

Comment thread soh/src/code/z_play.c

#include <time.h>
#include <assert.h>
// #include <stdlib.h>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is truly not needed, should probably delete it instead of commenting it out.

Comment thread soh/src/code/z_vr_box.c
Comment on lines +610 to +618

// todo: figure out correct 'size' rather than 0x10000 (seems quite large?)
// if (CVarGetInteger("gStairs", 0)) {
// if (skyboxId == SKYBOX_NORMAL_SKY || skyboxId == SKYBOX_OVERCAST_SUNSET || skyboxId == SKYBOX_CUTSCENE_MAP) {
// Stairs_DecreaseSize(0x10000 * 4);
// } else {
// Stairs_DecreaseSize(0x10000 * 2);
// }
// }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete or explain why this is commented out instead of being deleted

Comment thread soh/src/code/z_vr_box.c
Comment on lines +983 to +986
if (CVarGetInteger("gStairs", 0)) {
Stairs_DecreaseSize(8 * 150 * sizeof(Gfx));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would appreciate some explanation of these magic numbers.

@briaguya0

Copy link
Copy Markdown
Contributor

i'm not fully following why gStairs checks can't just use gSimulateHeap, but assuming that is required

since gStairs is completely controlled by gSimulateHeap, i'm not sure it makes sense for it to be a cvar (and therefore saved to shipofharkinian.json)

i can't remember where off the top of my head but i'm pretty sure we have some ship specific global vars somewhere, it probably makes sense to follow that pattern for gStairs

@Pepper0ni

Copy link
Copy Markdown
Contributor

Am i right in remembering that this is being put on hold to implement it in a better way? If so it should be closed or marked do not merge.

@aMannus

aMannus commented Feb 4, 2025

Copy link
Copy Markdown
Contributor

Going to close this for now. This generally feels like a LOT just to accomodate one or 2 glitches, but if there's no other way, let's revisit this some other point when it's updated again.

@aMannus aMannus closed this Feb 4, 2025
@serprex serprex mentioned this pull request Jul 22, 2026
@serprex

serprex commented Jul 22, 2026

Copy link
Copy Markdown
Member

inspectredc#4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants