Skip to content

Changes

Nathan edited this page Mar 17, 2024 · 7 revisions

We set out to create Ocarina Unlocked with two specific goals: opening up possible dungeon progression and providing natural in-game context to our changes.

In this alternate progression order, Child Link can complete either Dodongo's Cavern or Inside Jabu Jabu as the second dungeon without any soft gates in the way. Adult Link can then tackle the Forest, Fire, and Water Temples in any order. The Shadow Temple becomes accessible after completing Forest and Fire, and conversely, the Spirit Temple becomes accessible after Forest and Water.

These logical progression changes are then supported by a small number of unintrusive modifications to the game's original dialogue and map screen logic—just enough to inform the player that other progress options are available that were not already directly stated by the game.

ROM Hack Version Control

Soon after starting our ROM hack, we ran into issues with tracking changes to our ROM. Without any tools, applying our changes meant modifying a small chunk of data directly in the ROM, and manually tracking those changes. Without manually tracking all of our changes and maintaining a constant stream backup copies, this was asking for trouble, as many of our earlier attempts at changes would crash the game.

Coming from a software engineering background, this whole process felt really unnecessary. It would be great if we could use a version control system like Git to track our changes, especially if we could edit ‘just one level’ instead of the entire game file.

Well it turns out there’s a tool to help with that! ZZRTL is a file system management utility for Ocarina of Time and Majora's Mask. Filesystems are dumped and injected by way of the highly customizable scripts it interprets. This means that instead of writing a change to the full ROM file, we can dump all of the levels, objects, and other assets, then modify them individually–and more importantly track those modifications individually. After making all of our changes, we can inject them back into the original ROM for testing! We specifically used the ZZRL-WAAS version, which supports the 1.0 ROM.

Using ZZRTL, we were able to track our file changes with Git, work on changes in parallel, revert problematic changes; basically all the reasons to use a version control system!

Progression Changes

Jabu Jabu (Zora’s River boulders)

We moved the boulders blocking the path through Zora’s River as Child Link out of the way. This opens Zora’s Domain and Jabu Jabu up to be completed as the second dungeon, since bombs are only necessary to get past the entrance of Zora’s River.


OoT Interactive Map

An interesting note is that Kaepora Gaebora (the owl), can be found at the entrance of Zora’s River immediately meeting Zelda for the first time. Here, they remind the player to use Zelda’s Lullaby to solve the waterfall puzzle ahead–even though you wouldn’t be able to because of the boulders!

Level Editing with SceneNavi

For basic level editing, we used SceneNavi. SceneNavi can manipulate actors, spawn points, waypoints, etc., can edit certain data tables in the ROM, and scene metadata. We utilized this in a number of places, like moving boulders around in Zora’s River, or changing the types of switches present for dungeon puzzles. SceneNavi also supports loading individual scene binaries, making it compatible with how ZZRTL dumps the ROM. Nifty!

For Zora's River, the change was pretty simple. We opted to move the boulders below the level geometry to hide them instead of deleting them. Deleting them would change the output size of the scene data, potentially causing issues.

Fire & Water Temples

In the Fire Temple there is a single Bow target puzzle which the dungeon map is locked behind. We replaced this with a crystal switch. Our philosophy for this hack was that any dungeon that can be accessed should be completable outside of secrets like Gold Skulltulas. After a bit of debate we decided that “complete” should include things that would greatly assist a first time player like the map and compass. The crystal switch also has the benefit of being hit multiple ways, letting the player figure out their own solution! This also fits with the other crystal switch puzzles in the Fire Temple.


OoT Interactive Map

Level Editing Continued

In the same way that we modified the Zora's River boulders, we used SceneNavi to change the dungeon switch type. Dungeon switch actors have an assignable type of either floor switch, rusted floor switch, eye switch, crystal switch, or targetable crystal switch.

For our update, we changed the switch type to crystal, disabled toggling on and off (since you can't 'unshoot' the vanilla eye switch), and then updated the rotation such that the crystal geometry is attached to the wall.

The Water Temple however, incorporates bow target puzzles into its central path. We felt replacing these with crystal switches would also be more in the spirit of the dungeon design, as the distance to hit the crystals would require the Longshot. These bow targets also felt particularly arbitrary to us as they are mid-dungeon, meaning the player could get halfway through and hit an impassable wall without backtracking out and doing the Forest Temple.


OoT Interactive Map

Collision Detection Flags (The Odd Switch Out)

In the central room of the Water Temple, there’s a bow target switch inset into the wall near the bottom floor. We changed this to a crystal switch, but at first glance, this didn’t seem to work like it did in other cases: you couldn’t hit the new crystal switch with the Hookshot, yet the Bow still worked!

It turns out this particular switch has collision geometry in front of it to make the wall collision geometry continuous. Presumably this is so Link can’t swim into the alcove containing the switch.

Anyhow, this collision geometry has a special collision detection flag applied to it. In particular, the flag indicates that the surface should ignore projectile collisions. Projectiles in this case includes slingshot seeds, arrows, Bombchus, and a few other miscellaneous things like the Octorok rocks, but not the Hookshot. Without the Bow, the primary puzzle solving tool for the player here would be the Hookshot, so this poses a problem. Lucky for us, there is an alternative flag that ignores most entities (Hookshot included) that we could use instead.

However, editing the level collision geometry is a lot more of a hassle than most of our other changes. The only tool we’re aware of that can modify and export existing level and collision geometry is Fast64, though like many other tools, it’s designed to work with the Debug Master Quest ROM. Since we are modifying a dungeon, the geometry will have significant differences being a Master Quest dungeon, so this was off the table since we’re working on a 1.0 hack.

Once again Decompiled came to our rescue! Since this geometry is in both Master Quest and 1.0, We were able to locate this particular geometry in the Water Temple’s collision header (located in MIZUsin_scene.c). The collision flags for a collision triangle are stored in the first vertex ID–they are combined together with a logical AND operation.

Vertex A (0x1FFF), Collision Detection Flags (& 0xE000), where the flags may be:

  • & 0x2000; if 1, ignored by Camera collision detection
  • & 0x4000; if 1, ignored by most entities
  • & 0x8000; if 1, ignored by projectiles (slingshot seeds, arrows), bombchus (for bombchu bowling)

In our case, the original hex value in Decompiled was 0x8162, meaning the vertex ID is 0x0162, and the flag is 0x8000.

With that figured out, it was just a matter of finding the 0x8162 hex values in the 1.0 ROM data, then changing the 8 to a 4! Thankfully these two triangles are the only geometry with this flag in the entire Water Temple, it could have been a whole lot more work otherwise.

Shadow Temple

Uniquely, the Shadow Temple is the only dungeon in the main progression to directly require the completion of prior dungeons in the same timeline. The Nocturne of Shadow cutscene with Sheik that precedes the Shadow Temple is a hard gate: it only triggers after the Forest, Fire, and Water medallions are acquired. This stands in contrast to the Spirit Temple which—while requiring the Forest Temple to be completed so the player can return to the past—only gates the player with the Longshot from the Water Temple.

To bring the two “final” dungeons closer together in prerequisites, we decided to remove the Water Temple as a requirement from Shadow. This was one of our most dramatic changes and it came about after a lot of debate over how best to open up the progression in the Adult Link dungeons. We like the symmetry this gives to temples, pairing Fire with Shadow and Water with Spirit, which are also linked geographically. Earlier on in the project, we attempted to give earlier access to Spirit Temple by removing the Longshot requirement from the entrance to the desert, but this proved difficult to modify in a way that felt natural. Instead, giving earlier access to Shadow Temple should allow for more flexibility in progression while maintaining a tiered dungeon difficulty.

Implementing Earlier Shadow Temple

Before the Shadow Temple can be accessed, Link has to learn the Nocturne of Shadow from Sheik. This happens in a cutscene at Kakariko Village that is triggered after the Forest, Fire, and Water Temples have been completed. We wanted to remove the Water Temple check from the logic.

The code responsible for triggering this cutscene in Decompiled is in z_demo.c, in the function Cutscene_HandleConditionalTriggers:

else if ((gSaveContext.entranceIndex == ENTR_KAKARIKO_VILLAGE_0) && 
    LINK_IS_ADULT && GET_EVENTCHKINF(EVENTCHKINF_48) && 
    GET_EVENTCHKINF(EVENTCHKINF_49) && 
    GET_EVENTCHKINF(EVENTCHKINF_4A) && 
    !Flags_GetEventChkInf(EVENTCHKINF_AA)) {
        Flags_SetEventChkInf(EVENTCHKINF_AA);
        gSaveContext.cutsceneIndex = 0xFFF0;
}

source

This conditional check requires the following:

  1. The player has entered Kakariko Village through the main entrance
  2. That Link is an adult
  3. That the following event info flags have been set ( see TCRF for event table)
  • 0x38; this is set whenever you use the blue warp out of the Forest Temple
  • 0x39; this is set whenever you use the blue warp out of the Fire Temple
  • 0x3A; this is set whenever you use the blue warp out of the Water Temple
  1. That the event info flag 0xAA has not been set; this is set whenever Bongo Bongo escapes the well in the cutscene these conditions trigger (essentially just checking if the cutscene has already played)

These flags are stored in an array of unsigned 16-bit integers, where each bit of each integer in the array represents a different flag. A breakdown of these flags is available on TCRF. We initially tried to make our logic change by replacing the Water Temple event info flag check with a duplicate Fire Temple check. Whether because of compiler optimizations or the way the boolean logic gets folded together into hexadecimal values, this changed a larger chunk of the resultant hex code than expected.

We ended up getting around this by replacing the Water Temple completion check with a sure to be completed, and still timely check for having opened the Door of Time. This approach yielded a single byte change–much more in line with what we were looking for.

Testing this change in 1.0 after the usual compare-and-copy its own unique challenge. In the MQ Debug ROM, there exists an event editor tool that can toggle the various event info flags. This tool is not present in 1.0 however. We got around this by following the save data documentation of Zelda modders past, and directly modifying the memory contents at runtime the same way the event tool would. Comparatively, this was kind of a pain, as we had to locate the event info flag array in memory, convert the values from base 16 to base 2, change the flags, then convert it back to base 16 just to test, but it all worked out! It was certainly a lot less work than playing through the game up until Shadow Temple!

As a consequence of this change, a small change did need to be made to the Shadow Temple itself. To our surprise, there was only a single target requiring the Longshot in the temple which would need to be modified. However, the various shot targets in the game are not as easily edited as switches since they are actually just geometry flagged to be hookshot-able. Luckily for us, this room contained a chest that could be moved to substitute for the Longshot target and be reachable with the base Hookshot.


OoT Interactive Map

Fun fact: The room never actually required the Longshot to begin with! Backflipping onto the chest in its original position actually gives the player enough height to Hookshot to the intended target, but we felt this was a bit too much to ask of a player on a potential first playthrough.

Spirit Temple

The Spirit Temple contains two optional rusted switches that require the Megaton Hammer, yet even in the vanilla game, the temple can be accessed without doing the Fire Temple prior. In the original game this works as a way to reward players for completing dungeons in the intended order even if it wasn’t strictly necessary. Within the context of our changes to the Shadow Temple and Fire/Water though, we felt that these should be changed to regular switches.


OoT Interactive Map

Contextual Changes

While the primary goal of the ROM hack is to remove the gates in Ocarina of Time’s progression, we also decided to make minor additional changes to the game's dialogue, cutscenes, and interface to more naturally integrate the nonlinearity into the game. Not making these changes would have technically accomplished our goals for this ROM hack, but we felt that taking these extra steps better fit our philosophy for the hack: not just to make the changes, but to show how simply they could have been a part of the original game.

The main ways the game provides context to progression are dialogue and the map submenu. We updated both for our change, allowing earlier access to Zora’s Domain. We tried to keep these changes minimal, and keep any dialogue updates as close to the original style as possible.

Impa Cutscene

After meeting Zelda for the first time, Link is taught Zelda’s Lullaby by Impa, who then sends them on their way to Death Mountain. This dialogue, and Navi’s subsequent ‘reminder’ dialogue, was our first set of contextual info to modify.

In Impa’s speech to Link, she introduces the player to Death Mountain as a location:

Take a good look at that mountain. That is Death Mountain, home of the Gorons. They hold the Spiritual Stone of Fire.

In Ocarina Unlocked, we follow up this dialogue with a similar mention of the Zora’s and the Spiritual Stone of Water:

The Spiritual Stone of Water is held by the Zora. The river that runs through Hyrule flows from their home, Zora’s Domain.

impa-child-cutscene.mp4
Dialogue Editing with Zelda’s Letter

For dialogue editing, we used Zelda's Letter. Zelda’s Letter is an in-browser dialogue editor for Ocarina of Time that provides an easy interface to make and preview changes directly to a Zelda 64 text box. It includes both a message find and goto feature (using the message address), which makes finding the dialogue really easy.

Zelda’s Letter supports both saving the changes directly to the ROM, or to a separate group of binaries. These binaries can be injected using ZZRTL also!

Impa delivers this dialogue as part of a cutscene, where the Death Mountain mention occurs in a shot particularly framing the mountain in the distance. In order to deliver our added dialogue seamlessly, we incorporated an additional camera shot highlighting the entrance to Zora’s River. Making this change was quite the challenge!

Cutscene Editing

For our update to the Impa cutscene, we added both a new line of dialogue and a camera shot. This is in contrast to our other contextual changes so far, where we have modified a pre-existing set of data.

For the new dialogue, we had a couple options: make the dialogue table larger, or utilize existing empty spaces in the table. We went with the latter since the process is simpler. Like the other dialogue edits, we utilized Zelda's Letter as our editor. We just went to an empty location in the table (specifically 0x0109) and added our new line.

Actually modifying the cutscene is a lot more work though. We started off by locating the cutscene implementation in Decompiled. Cutscenes in Ocarina of Time are composed of a list of different commands defining different cutscene actions. In the Impa cutscene, we can see commands like actor animation actions, text lists, and camera positions, as well as general commands describing the cutscene duration.

CS_BEGIN_CUTSCENE(15, 1500),
CS_UNK_DATA_LIST(0xD, 1),
    CS_UNK_DATA(0x00010000, 0x05DC0000, 0x00000000, 0x00000000, 0xFFFFFFFE, 0x00000010, 0x00000000, 0xFFFFFFFE,
                0x00000010, 0x00000000, 0x00000000, 0x00000000),
CS_PLAYER_ACTION_LIST(5),
    CS_PLAYER_ACTION(5, 0, 240, 0x0000, 0x4000, 0x0000, 87, 0, 1320, 87, 0, 1320, 0.00000000000e+00f,
                     0.00000000000e+00f, 1.40129846432e-45f),
    CS_PLAYER_ACTION(1, 240, 310, 0x0000, 0x4000, 0x0000, 87, 0, 1320, 186, 0, 1327, 0.00000000000e+00f,
                     0.00000000000e+00f, 1.40129846432e-45f),
    CS_PLAYER_ACTION(5, 310, 457, 0x0000, 0x4000, 0x0000, 186, 0, 1327, 186, 0, 1327, 0.00000000000e+00f,
                     0.00000000000e+00f, 1.40129846432e-45f),
    CS_PLAYER_ACTION(6, 457, 470, 0x0000, 0x4000, 0x0000, 186, 0, 1327, 221, 0, 1327, 0.00000000000e+00f,
                     0.00000000000e+00f, 1.40129846432e-45f),
    CS_PLAYER_ACTION(5, 470, 586, 0x0000, 0xAAAB, 0x0000, 221, 0, 1327, 221, 0, 1327, 0.00000000000e+00f,
                     0.00000000000e+00f, 1.40129846432e-45f),
CS_NPC_ACTION_LIST(0x02C, 4),
    CS_NPC_ACTION(1, 0, 10, 0x0000, 0x4000, 0x0000, 142, 0, 1274, 142, 0, 1274, 0.00000000000e+00f,
                  0.00000000000e+00f, 1.40129846432e-45f),
    CS_NPC_ACTION(2, 10, 421, 0x0000, 0x4000, 0x0000, 142, 0, 1274, 142, 0, 1274, 0.00000000000e+00f,
                  0.00000000000e+00f, 1.40129846432e-45f),
    CS_NPC_ACTION(10, 421, 454, 0x0000, 0x4000, 0x0000, 142, 0, 1274, 142, 0, 1274, 0.00000000000e+00f,
                  0.00000000000e+00f, 1.40129846432e-45f),
    CS_NPC_ACTION(11, 454, 455, 0x0000, 0x0000, 0x0000, 142, 0, 1274, 142, 0, 1274, 0.00000000000e+00f,
                  0.00000000000e+00f, 0.00000000000e+00f),
				  
and so on...

First we had to add our new dialogue into the cutscene. The text content of a cutscene is described by a few different textbox altering commands, like…

// Comments added for readability
CS_TEXT_LIST(8),
    CS_TEXT_NONE(0, 130),
    CS_TEXT_DISPLAY_TEXTBOX(0x2060, 130, 160, 0, 0x0, 0x0), // [IMPA] You brave lad...
    CS_TEXT_NONE(160, 180),
    CS_TEXT_DISPLAY_TEXTBOX(0x204F, 180, 240, 0, 0x0, 0x0), // [IMPA] Take a good look at that mountain...
    CS_TEXT_NONE(240, 280),
    CS_TEXT_DISPLAY_TEXTBOX(0x2062, 280, 340, 0, 0x0, 0x0), // [IMPA] At the foot of Death Mountain...
    CS_TEXT_NONE(340, 370),
    CS_TEXT_DISPLAY_TEXTBOX(0x2063, 370, 410, 0, 0x0, 0x0), // [IMPA] You should talk to some of the villagers...
CS_TERMINATOR(115, 565, 566),

We added an additional set of ‘No Text’ and ‘Display Text’ commands for our new dialogue (represented in Decompiled as CS_TEXT_NONE and CS_TEXT_DISPLAY_TEXTBOX macros), and then added an additional frame duration to the subsequent text command’s frame arguments.

CS_TEXT_LIST(10),
    CS_TEXT_NONE(0, 130),
    CS_TEXT_DISPLAY_TEXTBOX(0x2060, 130, 160, 0, 0x0, 0x0),
    CS_TEXT_NONE(160, 180),
    CS_TEXT_DISPLAY_TEXTBOX(0x204F, 180, 240, 0, 0x0, 0x0),
    CS_TEXT_NONE(160+IMPA_CUTSCENE_EXTENSION, 180+IMPA_CUTSCENE_EXTENSION),                                   // Pause before new dialogue
    CS_TEXT_DISPLAY_TEXTBOX(0x0109, 180+IMPA_CUTSCENE_EXTENSION, 240+IMPA_CUTSCENE_EXTENSION, 0, 0x0, 0x0),   // Display new dialogue
    CS_TEXT_NONE(240+IMPA_CUTSCENE_EXTENSION, 280+IMPA_CUTSCENE_EXTENSION),
    CS_TEXT_DISPLAY_TEXTBOX(0x2062, 280+IMPA_CUTSCENE_EXTENSION, 340+IMPA_CUTSCENE_EXTENSION, 0, 0x0, 0x0),
    CS_TEXT_NONE(340+IMPA_CUTSCENE_EXTENSION, 370+IMPA_CUTSCENE_EXTENSION),
    CS_TEXT_DISPLAY_TEXTBOX(0x2063, 370+IMPA_CUTSCENE_EXTENSION, 410+IMPA_CUTSCENE_EXTENSION, 0, 0x0, 0x0),
CS_TERMINATOR(115, 565+IMPA_CUTSCENE_EXTENSION, 566+IMPA_CUTSCENE_EXTENSION),

To add a new camera shot, we essentially did the same thing but with camera commands. There are camera commands for positions and focus points, with each type being stored in a list of positions or focus points. We used SharpOcarina’s cutscene editing tools to view the original cutscene data (this alone took a bit of work, we had to locate the hex values representing the cutscene in the ROM), and then ‘preview’ our own camera commands. Once we were satisfied, we then wrote our new camera command values back into the Decompiled code.

CS_CAM_POS_LIST(170+IMPA_CUTSCENE_EXTENSION, 431+IMPA_CUTSCENE_EXTENSION), // New shot! Frame Zora's river entrance and pan right
    CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 55.806320f, 2579, 537, 1778, 0x0021),
    CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 55.806320f, 2579, 537, 1778, 0x00B6),
    CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 55.806320f, 2522, 537, 2032, 0x00C7),
    CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 55.806320f, 2522, 537, 2032, 0x00D8),
    CS_CAM_POS(CS_CMD_STOP, 0x00, 0, 55.806320f, 2522, 537, 2032, 0x0115),

We did this because we’d ultimately need to adjust the camera commands even further. This was the one instance in our ROM hack where we explicitly added content, rather than modifying pre-existing content. As described previously, there are technical challenges with increasing the output size of the final program, and adding the new cutscene commands does just that. However, due to the way cutscene lookup works, we can make the cutscene take up less or equal space, filling any unused space with 0s. To make room for the new commands, we had to 'optimize' the existing ones to fit in a smaller footprint. Essentially we just removed some redundant keyframes from other camera pans in the same cutscene.

From there, it’s the usual compare-and-copy to apply the change to 1.0, albeit with a bit more caution since the cutscene is a lot more data than most of our other changes. With our cutscene ‘optimization’, we were able to include our new content and still have 30 bytes of empty space to spare!

One interesting note, Impa closes out her speech by reminding the player to use Zelda’s Lullaby to solve upcoming puzzles:

The song I just taught you has some mysterious power. Only Royal Family members are allowed to learn this song.

This reminder to the player is not only applicable for retrieving the Sun’s Song and speaking to Darunia on Goron City, but also for opening the entrance to Zora’s Domain!

Navi Dialogue

In the base game, after Impa sends you off to Kakariko Village to look for the Spiritual Stone of Fire, Navi will remind the player what Impa said:

Impa said that the Spiritual Stone of Fire is somewhere on Death Mountain.

To support our changes to the Impa cutscene, we modified Navi's dialogue here to:

Impa said that the Spiritual Stones are somewhere on Death Mountain and in Zora’s Domain.

Sheik Cutscene

Upon becoming an adult, the player is greeted by Sheik, who introduces the concept of the five sages and their temples. Sheik alludes to Saria and the Forest Temple as the next steps in the game’s main plot:

One Sage is waiting for the time of awakening in the Forest Temple. The Sage is a girl I am sure you know... Because of the evil power in the temple, she cannot hear the awakening call from the Sacred Realm...

Unfortunately, equipped as you currently are, you cannot even enter the temple... But, if you believe what I'm saying, you should head to Kakariko Village...

In Ocarina Unlocked, the player can tackle the Forest, Fire, or Water temple as their first Adult Link dungeon. We considered modifying the dialogue here to either directly mention Darunia and Ruto and their temples, or making the dialogue more vague as to apply to all three of the Forest, Fire, and Water sages. We ultimately decided not to do so, as either would significantly extend the cutscene and dilute the original emphasis on the Link’s relation to Saria and the Kokiri Forest.

We instead settled on the following change:

Unfortunately, equipped as you currently are, you cannot enter any of the temples... But, if you believe what I'm saying, you should head to Kakariko Village…

sheik.mp4

Update Map Submenu

Ocarina of Time’s map sub-menu includes location pins that flash when the player should travel there.

Whether or not the location marker is flashing is determined by a sequence of inventory and event checks. One of our goals for this ROM hack was to update these markers to match our changes to the play order of the game. Our main change was to highlight the Zora’s Domain marker after receiving Zelda’s Lullaby, to indicate to the player that they can progress either to Kakariko Village or Zora’s River.

Map Marker Implementation

A map marker has 3 possible states: off (0), visible (1), or blinking (2). Whether a marker is flashing is determined by a sequence of inventory and event checks. In the base game, Zora's Domain already becomes visible after receiving Zelda's Lullaby, it just doesn't blink.

So all we needed to do was change the flag from 1 to 2, locate the changes in the modified MQ debug ROM generated by Decompiled, then transfer that over to our 1.0 ROM data.

// Where index 11 is Zora's Domain
// And 2 is the flag for a flashing marker
if (CHECK_QUEST_ITEM(QUEST_SONG_LULLABY)) {
  pauseCtx->worldMapPoints[11] = 2;
}

source

Additionally, we had planned to update the map markers for Death Mountain and Zora’s Domain to reflect earlier access to both the Fire and Water temples, but base game already does this! Both Fire and Water temple (and their immediate prerequisites like Ice Cavern) blink their respective map markers based on inventory checks, so no work needed!