-
Notifications
You must be signed in to change notification settings - Fork 0
RAM Address Map
Every memory address OcarinaCTRComposer reads or writes, in one place. Game: The Legend of
Zelda: Ocarina of Time 3D · Region: USA only · Title ID: 0004000000033500.
All addresses are absolute 32-bit (e.g. 0x0587958), read/written directly with
R8/R16/R32/W8/W16/W32 — no pointer chasing except where noted (base+offset patterns). They were
confirmed on hardware unless flagged otherwise. These addresses are USA-version-specific — on
EUR/JPN they are very likely at different offsets; re-anchor before reusing this map on another
region.
If this map saves you time, a credit back to this repo is appreciated. Corrections and additions are welcome — open an issue or a PR against the wiki.
- Base pointers
- gSaveContext — core save block
- Quest-status word (medallions, songs, stones)
- Inventory array
- Equipment byte (swords / shields / tunics / boots)
- Ammo / consumables
- Player (Link actor) offsets
- GlobalContext (scene / warp control)
- Entrance-warp mechanism
- Respawn struct (reliable position warp)
- actorCtx flags (temp switches / collectibles)
- Room / scene flags (chests, switches, collect)
- Gold Skulltula tokens
- Event / item-get flags
- Misc single addresses
- Code-patch addresses (Invincible)
- Scene ID table
- Entrance index examples
- Methodology notes
| Symbol | Address | Type | Notes |
|---|---|---|---|
| Link actor pointer | 0x005A2E3C |
u32* → Player struct |
Dereference to get the live Player base; 0 when not in-game (menus, loading). All player-relative offsets below are [this] + offset. |
| gSaveContext | 0x0587958 |
struct base | Confirmed by matching the inventory array byte-for-byte against the save file (RAM = file_offset + 0x587958). Everything under "gSaveContext" below is 0x587958 + offset. |
| GlobalContext | 0x0871E840 |
struct base | Candidate, guarded at every use by a sanity check (GctxValid() below) rather than trusted blindly. Confirmed via a working "reload current scene" on hardware. |
Base 0x0587958. Offsets shown as base+N and the resolved absolute address.
| Field | Offset | Address | Type | Notes |
|---|---|---|---|---|
| entranceIndex | +0x00 | 0x0587958 |
s32 | Current entrance index |
| linkAge | +0x04 | 0x058795C |
s32 | 0 = Adult, 1 = Child (the live/current age) |
| cutsceneIndex | +0x08 | 0x0587960 |
s32 | |
| dayTime | +0x0C | 0x0587964 |
u16 | 0x0000 = midnight … 0x8001 = sunrise … 0xC001 = sunset |
| Current Health | 0x058799A |
u16 | "Unlock/Max Hearts" write target (0x140 = 20 hearts) |
|
| Current Health (keep-full) | 0x058799C |
u16 | Same field, written every frame for "Keep Hearts Full" / one-shot heart refill | |
| Magic amount | 0x058799F |
u8 |
0x30 normal magic, 0x60 with Magic Upgrade |
|
| Magic capacity / unlock | 0x05879A6 |
u8 |
0x60 = unlocked |
|
| Double Defense flag | 0x05879A9 |
u8 | bit0 | |
| Large Magic unlock | 0x05879A8 |
u8 |
0x01 = unlocked; also gates the refill-magic amount |
|
| Sticks count | 0x05879FE |
u8 | max 0x63 (99) |
|
| Nuts count | 0x05879FF |
u8 | max 0x63
|
|
| Rupees | 0x05879A0 |
u16 | max 0x3E7 (999) |
|
| Knife/sword durability(?) | 0x05879A2 |
u8 |
0xFF = "no break" |
|
| respawn[0] (RESPAWN_MODE_DOWN) | +0x14F0 | 0x0588E48 |
struct, 0x1C bytes | See Respawn struct |
| respawnFlag | +0x14EC | 0x0588E44 |
u8 |
1 before a scene reload → spawn at respawn[0] instead of the entrance's default point |
| nextCutsceneIndex | +0x15A0 | 0x0588EF8 |
u16 |
0xFFEF = "no cutscene" sentinel used by the warp routine |
| Nayru's Love timer | 0x0588EAA |
u16 |
0xFFFF = always active |
|
| Bottle #1/#2/#3 contents |
0x05879F6 / 0x05879F7 / 0x05879F8
|
u8 each | Item ID per bottle slot; Bottle #3 slot doubles as the "Inventory Modifier" picker target |
A packed bitfield starting at 0x0587A14 (3 bytes). Mapped by save-diffing 48 progressive
community save files (see Methodology) — no guessed bits.
Byte 0x0587A14:
| Bit | Item |
|---|---|
| 0x01 | Forest Medallion |
| 0x02 | Fire Medallion |
| 0x04 | Water Medallion |
| 0x08 | Spirit Medallion |
| 0x10 | Shadow Medallion |
| 0x20 | Light Medallion |
| 0x40 | Minuet of Forest |
| 0x80 | Bolero of Fire |
Byte 0x0587A15:
| Bit | Item |
|---|---|
| 0x01 | Serenade of Water |
| 0x02 | Requiem of Spirit |
| 0x04 | Nocturne of Shadow |
| 0x08 | Prelude of Light |
| 0x10 | Zelda's Lullaby |
| 0x20 | Epona's Song |
| 0x40 | Saria's Song |
| 0x80 | Sun's Song |
Byte 0x0587A16:
| Bit | Item |
|---|---|
| 0x01 | Song of Time |
| 0x02 | Song of Storms |
| 0x04 | Kokiri Emerald |
| 0x08 | Goron Ruby |
| 0x10 | Zora Sapphire |
| — | (upper bits: Gerudo card and other flags — see caveat below) |
Quick unlock-all writes used by the plugin: W8(0x0587A14, 0x3F) (all 6 medallions),
W8(0x0587A16, 0x7C) (all 3 stones, mask includes adjacent unrelated bits — verify before reuse),
W8(0x0587A14 | 0xC0) + W8(0x0587A15, 0xFF) + W8(0x0587A16 | 0x03) (all 10 songs).
⚠️ Gerudo card: reported elsewhere as bit 22 of this word, but in the 48-save diff set that bit only ever appears on already-100% saves — ambiguous, left undetected on purpose rather than risk a false positive.
Upgrade word 0x0587A10 (u32) — packed multi-bit capacity fields (bow/bomb bag/quiver/etc. and
gauntlet color). Example writes: W32(0x0587A10, 0x184D8) = "best equipment" bundle;
W16(0x0587A10, 0xE500 + 0xDB/0x9B/0x5B/0x1B) = Golden/Silver/Silver/Golden gauntlets (see source
for the exact 4 values — Black/Blue/Green/Silver gauntlet upgrade tiers).
Skulltula token count — 0x0587A40 (separate from the per-token flags below).
Fixed 24-slot array, base 0x005879E4, one byte per slot, empty = 0xFF, otherwise the item
ID. Confirmed by matching this array byte-for-byte against the save file's inventory bytes.
| Slot index | Item |
|---|---|
| +3 | Bow |
| +4 | Fire Arrow |
| +5 | Din's Fire |
| +6 | Slingshot |
| +7 | Ocarina |
| +8 | Bombchu |
| +9 | Hookshot / Longshot (shared slot; ID 0x0A vs 0x0B) |
| +10 | Ice Arrow |
| +11 | Farore's Wind |
| +12 | Boomerang |
| +13 | Lens of Truth |
| +14 | Magic Beans |
| +15 | Megaton Hammer |
| +16 | Light Arrow |
| +17 | Nayru's Love |
| +18..21 | Bottles #1-4 |
| +22 | Adult trade-sequence item |
| +23 | Child trade-sequence item |
"Unlock All Items" writes a full 30-byte buffer to 0x5879E4 in one shot (see source for the exact
byte sequence — it's a curated "one of everything" loadout, not derived from a formula).
Trade-sequence complete check: adult trade slot 0x05879FA == 0x37 (Claim Check item ID).
Two adjacent bytes, 0x0587A0E (swords + shields, low/high nibble) and 0x0587A0F (tunics +
boots). Each equipment slot is a nibble bit, toggled independently (TriggerNibble = XOR one
bit within the nibble, so re-selecting removes it).
0x0587A0E:
| Bit | Item |
|---|---|
| 0x01 | Kokiri Sword |
| 0x02 | Master Sword |
| 0x04 | Biggoron's Sword |
| 0x10 | Deku Shield |
| 0x20 | Hylian Shield |
| 0x40 | Mirror Shield |
0x0587A0F:
| Bit | Item |
|---|---|
| 0x01 | Kokiri Tunic |
| 0x02 | Goron Tunic |
| 0x04 | Zora Tunic |
| 0x10 | Kokiri Boots |
| 0x20 | Iron Boots |
| 0x40 | Hover Boots (& 0x40 = Hover Boots owned check) |
| Item | Address | Max write |
|---|---|---|
| Deku Nuts | 0x05879FF |
0x63 |
| Deku Sticks | 0x05879FE |
0x63 |
| Arrows | 0x0587A01 |
0x63 |
| Bombs (save + HUD mirror) |
0x0587A00 + 0x08001AB4
|
0x63 each |
| Bombchu (save + HUD mirror) |
0x0587A06 + 0x08001AD0
|
0x63 each |
| Slingshot seeds | 0x0587A04 |
0x63 |
| Rupees | 0x05879A0 |
0x3E7 |
| Gold Skulltula token count |
0x0587A40 (+ HUD mirror 0x05C2280) |
0x64 |
| Skulltula-house price-halved flag | via 0x0FFFE538 → [q]+0x910 = 0
|
pointer-chased, see source |
Relative to the Link actor pointer [0x005A2E3C].
| Offset | Field | Notes |
|---|---|---|
| +0x18 / +0x1C / +0x20 | Position X / Y / Z | raw float bits, used by both the waypoint save and the debug scale hack |
| +0x64 | Actor scale |
f32; 0x3AD3D70A ≈ Paper Link scale |
| +0x77 | Vertical velocity |
u16; 0xCB40 = an instant strong upward pop (MoonJump / the jump component of Fast Move) |
| +0x134 | Epona pointer | dereference for horse-relative offsets |
| +0xE9C | (Epona) carrots-full field | offset from the Epona pointer, not Link |
| +0x66 | (Epona) horse jump-boost field | offset from the Epona pointer |
| +0x2237 | Sword-glitch state byte |
0x01 = enabled |
| +0x2252 | Spin-attack charge |
f32; 0x3F80 (as u16 write) = fully charged |
| +0x2258 | Deku Stick lit flag |
0xFF = always on fire |
| +0x222C | Horizontal speed cap (during Fast Move's post-jump phase) |
f32; 0x41A00000 = 20.0 |
Base 0x0871E840 (candidate — always gate a write with the validity check below; a wrong base
here can crash the game).
| Field | Offset | Address | Type | Notes |
|---|---|---|---|---|
| sceneNum | +0x104 | 0x0871E944 |
s16 | Current scene ID (see Scene ID table) |
| linkAgeOnLoad | +0x5C00 | 0x0871E5F0* |
u8 | 0 = Adult, 1 = Child — the pending age applied on the next scene load (Toggle Age writes here) |
| sceneLoadFlag / transitionTrigger | +0x5C2D | s8 | Writing 0x14 triggers the load — this is the final write in any warp |
|
| nextEntranceIndex | +0x5C32 | s16 | Idle/no-pending-transition reads 0xFFFF
|
|
| transitionType / fadeOutTransition | +0x5C76 | u8 |
3 used for the fade in this plugin's warp routine |
(offsets are added to 0x0871E840, not restated as absolute addresses above except sceneNum for
illustration — always compute GCTX_BASE + offset.)
Validity guard (run before every write to this block):
sceneNum < 0x6A AND nextEntranceIndex == 0xFFFF AND the Link pointer is non-zero
If any check fails, the plugin refuses to warp rather than writing to a possibly-wrong base.
The full write sequence a "teleport" performs (mirrors the retail game's own EntranceWarp, no
function call — pure memory writes, order matters, last write triggers the load):
-
linkAgeOnLoad(GlobalContext +0x5C00) ← target age (0/1), or the pending age read back if "keep current age" was requested — this makes a Toggle Age done just before a warp carry through. -
gSaveContext.cutsceneIndex(0x0587960) ←0 -
gSaveContext.nextCutsceneIndex(0x0588EF8) ←0xFFEF(the "no cutscene" sentinel) -
nextEntranceIndex(GlobalContext +0x5C32) ← the target entrance index -
transitionType(GlobalContext +0x5C76) ←3 -
sceneLoadFlag(GlobalContext +0x5C2D) ←0x14— this is what actually starts the load
"Reload current scene" = the same sequence with the entrance index re-read live from
gSaveContext.entranceIndex (0x0587958) instead of a fixed target.
gSaveContext.respawn[0] (RESPAWN_MODE_DOWN), base 0x0588E48, 0x1C bytes. Writing this and
setting respawnFlag (0x0588E44 = 1) before triggering an entrance-warp (above) makes the game
spawn Link at this exact position + room instead of the entrance's default point — the mechanism
this plugin uses for "Warp to Saved Position" (reliable across rooms/dungeons, unlike a raw
coordinate write).
| Offset | Field | Type |
|---|---|---|
| +0x00 | pos X | f32 (raw bits) |
| +0x04 | pos Y | f32 |
| +0x08 | pos Z | f32 |
| +0x0C | yaw | u16 (not saved by this plugin — Link faces the default direction on arrival) |
| +0x0E | playerParams | u16 — 0x0DFF = a normal standing spawn |
| +0x10 | entrance index | u16 |
| +0x12 | room index | u8 |
| +0x13 | (padding/data byte) | u8 |
| +0x14 | tempSwch (copied from actorCtx, see below) | u32 |
| +0x18 | tempCollect (copied from actorCtx, see below) | u32 |
Live per-room flags, read at warp time and copied into the respawn struct so a warp doesn't reset switches/collectibles you already triggered in the destination room:
| Field | Address | Notes |
|---|---|---|
| tempSwch (actorCtx.flags +0x1A0) | 0x08720C44 |
current room's temporary switch flags |
| tempCollect (actorCtx.flags +0x1BC) | 0x08720C60 |
current room's temporary collectible flags |
| Chest-opened flags (bulk clear) | 0x08720A78 |
u32 write of 0 = "many chests" reset for the current room |
| Heart-piece-taken flags (bulk clear) | 0x08720A84 |
u16 write of 0
|
| Rain toggle | 0x08721AAF |
u8; 0xFF = force rain |
| Song-playing message mode | 0x087212D0 |
u8; game values 18/19 = playing an ocarina song, forced to 23 to skip the cutscene once recognized |
| roomCtx.curRoom.num | 0x08723470 |
u8; current room index (used to auto-suffix waypoint names in dungeons, e.g. "R2") |
gSaveContext.sceneFlags[124], base offset +0xEC from 0x0587958 (i.e. 0x0587A44), 0x1C
bytes per scene (scene index = the game's own scene number):
| Sub-offset | Field |
|---|---|
| +0x00 | chest flags |
| +0x04 | switch flags |
| +0x08 | clear flags |
| +0x0C | collect flags (heart pieces / misc collectibles) |
| +0x14 | rooms1 |
| +0x18 | rooms2 |
These are per-scene bitfields — isolating exactly which collectible a given bit represents requires either the decomp's own token/flag tables or a save where only one flag changes between two otherwise-identical saves (most progressive test saves gain several at once, which makes this genuinely hard — see the caveat in Methodology).
gsFlags[22], base 0x0058880C (0x0EB4+gSaveContext naively — some public
references list the wrong start offset; this is the value that matches a 100-token popcount on a
100%-complete save). One bit per token; which bit maps to which physical Skulltula is fixed by
the game but not derivable from save-diffing alone (progressive saves gain many tokens in one
block, so the per-bit identity can't be isolated this way — only the decomp's own 100-entry
scene+bit token table resolves that). Byte-index-to-area mapping (bit position within each byte is
a fixed-but-unidentified token of that area):
| Byte | Area |
|---|---|
| 0 | Deku Tree / Kokiri Forest |
| 1 | Dodongo's Cavern / Death Mountain |
| 2 | Hyrule Field / Lost Woods (adult) |
| 3 | Forest Temple |
| 4 | Fire Temple |
| 5 | Water Temple |
| 6 | Spirit Temple |
| 7 | Shadow Temple |
| 8 | Bottom of the Well |
| 9 | Ice Cavern |
| 10..21 | Overworld areas |
All monotonic (bits only ever turn on) across the 48-save diff set — safe to treat "bit set" as "confirmed", but see the block-grant caveat below before relying on them for fine-grained tracking.
| Field | Address | Size |
|---|---|---|
| eventChkInf | 0x0588844 |
14 × u16 |
| itemGetInf | 0x0588860 |
4 × u16 |
| infTable | 0x0588868 |
30 × u16 |
| eventInf | (not in the 5340-byte save file — RAM-only, offset +0x158A from 0x587958) |
4 × u16 |
| Purpose | Address |
|---|---|
| Config file version-check magic (this plugin's own, not the game's) | n/a — 'ZOOT' blob magic in Settings.cfg
|
| Rain toggle | 0x08721AAF |
| Freeze-time capture/hold target |
0x0587964 (same as dayTime above) |
| Ocarina song-recognition message mode | 0x087212D0 |
Four instructions patched (not just data) — the only code-patch cheat in this plugin. Requires
svcControlProcess(..., PROCESSOP_SET_MMU_TO_RWX, ...) once before the first write, and the
original bytes are saved so the patch can be reverted when the cheat is turned off.
| Address | Original (typical) | Patched to | Purpose |
|---|---|---|---|
0x0035D398 |
(damage-apply instruction) |
0xE3A00000 (mov r0, #0) |
zero the incoming damage argument |
0x0035D3A8 |
(branch) |
0xEA000000 (unconditional branch, offset 0 = no-op the following block) |
short-circuit the damage-apply path |
0x00352E24 |
(load) | 0xE1D504B2 |
part of the same patch — see source for full context |
0x00352E28 |
(move) |
0xE1A00000 (mov r0, r0, effectively nop) |
These 4 addresses are proven on hardware but are the most fragile entries in this map — a game update could shift them. If Invincible stops working after a game-version change, this is the first thing to re-verify with Cheat Search or a fresh disassembly.
gGlobalContext->sceneNum (also matches the N64 OoT scene numbering). Sourced from the OoT decomp
scene_table.h; range 0x00–0x64 are reachable in the retail game, 0x65+ are unused
debug/dev scenes.
| ID | Scene | ID | Scene |
|---|---|---|---|
| 0x00 | Inside the Deku Tree | 0x34 | Link's House |
| 0x01 | Dodongo's Cavern | 0x35 | Back Alley House (Dog Lady) |
| 0x02 | Inside Jabu-Jabu's Belly | 0x36 | Stable |
| 0x03 | Forest Temple | 0x37 | Impa's House |
| 0x04 | Fire Temple | 0x38 | Lakeside Laboratory |
| 0x05 | Water Temple | 0x39 | Carpenters' Tent |
| 0x06 | Spirit Temple | 0x3A | Dampe's Gravekeeper's Hut |
| 0x07 | Shadow Temple | 0x3B | Great Fairy's Fountain (Magic) |
| 0x08 | Bottom of the Well | 0x3C | Fairy's Fountain |
| 0x09 | Ice Cavern | 0x3D | Great Fairy's Fountain (Spells) |
| 0x0A | Ganon's Tower | 0x3E | Grottos (generic) |
| 0x0B | Gerudo Training Ground | 0x3F | Grave (ReDead) |
| 0x0C | Thieves' Hideout | 0x40 | Grave w/ Fairy's Fountain |
| 0x0D | Inside Ganon's Castle | 0x41 | Royal Family's Tomb |
| 0x0E | Ganon's Tower (Collapsing) — interior | 0x42 | Shooting Gallery |
| 0x0F | Inside Ganon's Castle (Collapsing) | 0x43 | Temple of Time (interior) |
| 0x10 | Treasure Box Shop | 0x44 | Chamber of the Sages |
| 0x11 | Gohma's Lair | 0x45 | Hyrule Castle Courtyard — Guards (Day) |
| 0x12 | King Dodongo's Lair | 0x46 | Hyrule Castle Courtyard — Guards (Night) |
| 0x13 | Barinade's Lair | 0x47 | Cutscene Map |
| 0x14 | Phantom Ganon's Lair | 0x48 | Windmill & Dampe's Grave |
| 0x15 | Volvagia's Lair | 0x49 | Fishing Pond |
| 0x16 | Morpha's Lair | 0x4A | Hyrule Castle Courtyard — Zelda's Meeting |
| 0x17 | Twinrova's Lair | 0x4B | Bombchu Bowling Alley |
| 0x18 | Bongo Bongo's Lair | 0x4C | Lon Lon Ranch buildings |
| 0x19 | Ganondorf's Lair | 0x4D | Market Guard House |
| 0x1A | Ganon's Tower (Collapsing) — exterior | 0x4E | Granny's Potion Shop (Kakariko) |
| 0x1B | Market Entrance (Day) | 0x4F | Ganon Battle Arena |
| 0x1C | Market Entrance (Night) | 0x50 | House of Skulltula |
| 0x1D | Market Entrance (Ruins) | 0x51 | Hyrule Field |
| 0x1E | Back Alley (Day) | 0x52 | Kakariko Village |
| 0x1F | Back Alley (Night) | 0x53 | Graveyard |
| 0x20 | Market (Day) | 0x54 | Zora's River |
| 0x21 | Market (Night) | 0x55 | Kokiri Forest |
| 0x22 | Market (Ruins) | 0x56 | Sacred Forest Meadow |
| 0x23 | Temple of Time Exterior (Day) | 0x57 | Lake Hylia |
| 0x24 | Temple of Time Exterior (Night) | 0x58 | Zora's Domain |
| 0x25 | Temple of Time Exterior (Ruins) | 0x59 | Zora's Fountain |
| 0x26 | Know-It-All Brothers' House | 0x5A | Gerudo Valley |
| 0x27 | House of Twins | 0x5B | Lost Woods |
| 0x28 | Mido's House | 0x5C | Desert Colossus |
| 0x29 | Saria's House | 0x5D | Gerudo's Fortress |
| 0x2A | Kakariko Village House | 0x5E | Haunted Wasteland |
| 0x2B | Kakariko Back Alley House | 0x5F | Hyrule Castle (exterior) |
| 0x2C | Bazaar | 0x60 | Death Mountain Trail |
| 0x2D | Kokiri Shop | 0x61 | Death Mountain Crater |
| 0x2E | Goron Shop | 0x62 | Goron City |
| 0x2F | Zora Shop | 0x63 | Lon Lon Ranch |
| 0x30 | Kakariko Potion Shop | 0x64 | Outside Ganon's Castle |
| 0x31 | Market Potion Shop | 0x65–0x6D | (debug/dev, unreachable in retail) |
| 0x32 | Bombchu Shop | ||
| 0x33 | Happy Mask Shop |
nextEntranceIndex targets used by the warp mechanism (from gamestabled/oot3d_practice_menu's
entrances.c, cross-checked on hardware for both ages). Each real location has 4 parallel
indices (child-day / child-night / adult-day / adult-night); child-day is the canonical base
listed here — the game resolves the right variant automatically based on the pending age/time.
| Entrance | Index (child-day base) |
|---|---|
| Deku Tree | 0x0000 |
| Dodongo's Cavern | 0x0004 |
| Jabu-Jabu's Belly | 0x0028 |
| Water Temple | 0x0010 |
| Shadow Temple | 0x0037 |
| Ice Cavern | 0x0088 |
| Gerudo Training Ground | 0x0008 |
| Forest Temple | 0x0169 |
| Fire Temple | 0x0165 |
| Spirit Temple | 0x0082 |
| Ganon's Castle | 0x0467 |
| Kokiri Forest | 0x00EE |
| Lost Woods | 0x011E |
| Link's House | 0x0272 |
| Hyrule Field | 0x00CD |
| Market | 0x00B1 |
| Temple of Time | 0x0053 |
| Lon Lon Ranch | 0x0157 |
| Kakariko Village | 0x00DB |
| Death Mountain Trail | 0x013D |
| Death Mountain Crater | 0x0147 |
| Goron City | 0x014D |
| Zora's River | 0x00EA |
| Zora's Domain | 0x0108 |
| Zora's Fountain | 0x010E |
| Lake Hylia | 0x0102 |
| Gerudo Valley | 0x0117 |
| Haunted Wasteland | 0x0130 |
| Desert Colossus | 0x0123 |
Full per-scene tables (every room's every door, ~1500 entries) live in the practice-menu project's
entrances.c — this list is only the plugin's own curated Teleport destinations.
How the quest-word / inventory / flag map was derived — by diffing 48 progressive save files from a community "start-to-finish" save collection (each save is one small, documented step further into the game). Method:
-
Anchor the file to RAM. A field in the
.binsave file was matched byte-for-byte against a known in-RAM buffer (the inventory array) — that single match pins the constant offset between file and RAM (RAM = file_offset + 0x587958), because the save is a raw serialization of the same struct, so bit order is identical in both. - Diff consecutive saves. For each step, the bits that flip on correspond to exactly what that save gained. Lining the diffs up against the collection's own written progress notes gives each bit a name.
- Cross-check. Bits already confirmed by other means (e.g. medallions, independently testable in-game) reproduce exactly via this method — when they do, the rest of what the same method found is trustworthy too.
Known limitation: progressive saves sometimes gain several flags in one step (e.g. a save that nets 15 Gold Skulltulas at once, or several event flags together) — when that happens, the individual bit-to-item mapping can't be isolated from diffing alone, only the fact that a bit in that byte turned on. Per-Skulltula and per-heart-piece identity ultimately needs the decomp's own token tables, not save-diffing. Where this map says "byte→area" instead of "bit→item", that's this limitation showing — treat those as approximate, not exact.
Compiled from source-code comments and dev notes in OcarinaCTRComposer/RawPlugin/sources/main.c.
Corrections welcome.