Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- In OoT, wrong warps are performed by changing the entrance index that is being used when loading into a cutscene. (If you don't know anything about wrong warp, I recommend this video https://m.youtube.com/watch?v=Bgq3aiulecM). As it expects you to be loading into a cutscene, it is looking for cutscene data upon its arrival in the scene, and there are two cases which can occur:
- The destination scene has a cutscene defined already, so the former cutscene pointer is not used and a new cutscene pointer writes over it instead.
- This is what occurs for Ganonless speedruns, for example. Lon Lon ranch with cutscene 3 has a cutscene associated with it. After arriving in ranch, the cutscene pointer overwrites to be the correct ranch credits cutscene pointer, and the game uses the credits cutscene data located at the cutscene pointer to play the credits cutscene.
- The destination scene does not have a cutscene defined already. In this case, it uses the last watched cutscene as where it expects the cutscene data to be.
- This is what occurs in almost every other wrong warp case.
- For example, in Ganondoor, there is no cutscene associated with collapse, so it plays the data last used. Which is Deku Tree Intro cutscene data.
- A full list of these two cases can be found here:
- https://www.zeldaspeedruns.com/oot/wrongwarp/warp-results-by-scene
- Additionally, here is a table of common cutscene pointers.
- One important consideration for wrong warping is what data is present at that former cutscene pointer. For example, for Ganondoor, you cannot just save warp in Deku tree and then wrong warp, as your last cutscene pointer becomes the title screen, and the data located at that pointer in Deku will crash/lock. Instead, you can save warp in Links house, then pause, because pausing links house lines up the pause screen object (graphical and animation data) with the data at the title screen cutscene pointer.
- More details on specifically this here:
- https://pastebin.com/WEkVWVWt
- More details on this later.
- Another consideration for wrong warping is that some cutscenes originate from the scene, (the map you have loaded), whereas some originate from actors (NPCs)
- Actors in OoT are dynamically allocated, so every playthrough can shift their location around a little bit. This results in their cutscene pointer changing depending on the actors loaded when you spawn that NPC.
- This is a major concern for routes which wrong warp from Jabu Jabu, as Ruto meeting cutscene is an actor cutscene. This means the pointer for it will shift up and down depending on your actions beforehand, but what it also means when combined with the previous paragraph is that where the game is looking for a cutscene will now change up and down on the actor heap, which is the space where actors allocate, and after Ruto is unloaded, any variety of data can load into where she used to be, meaning the data there will change depending on player actions.
- This is exactly why runners had to create a specific set of actions to avoid crashing the game with bad cutscene data.
- You may have heard of runners being concerned about the breaking the Shaboms (bubble enemy) and it is for this reason.
- This also manifests for 1080 wrong wap after song sparkle cutscene, as that is also an actor. You may have heard of "you have to quick spin on the fire elevator" or "you can't quick spin on the fire elevator" in reference to this.
- One such specific exanple: https://m.youtube.com/watch?v=K_KfsPMU7U0
- Therefore, all wrong warps to non cutscene scenes strategically plan for what data is located at the cutscene pointer and manipulate this data. Most of the time, we manipulate it so the cutscene terminates immediately and we gain control frame 1.
- This is done by taking ensuring the first two words present at the start of the cutscene data are negative.
- These two words are the cutscene header and are defined as s32 totalEntries and s32 csFrameCount.
- The code contains these variables in Cutscene_ProcessScript where:
- The game stops parsing the commands if the command being run is > totalEntries;. Also, if it hits CS_CMD_STOP, stop.
- The game stops running the cutscene at all if the curFrame is greater than the csFrameCount;
- The list of command entries is being processed every frame within the frame.
- There is some additional stuff on this later.
- The vast majority of speedruns manipulate data to lineup such that both of these are negative and therefore the cutscene stops immediately, but if you were around for the early wrong warp days some Ganondoor backups (Ocarina cutscene) had a positive frame count, so we had to wait out the frames for a few minutes. This never made it into any runs, though. We are however not limited to just these two words.
- As an example of wrong warp which spells out more than just the cutscene header,
- We have a working use case from r0bd0g:
- https://m.youtube.com/watch?v=_xhpW8DkPhM&pp=ygUdQ2FuYWRpYW5zdGlja2RlYXRoIHdyb25nIHdhcnA%3D
- In this case, our last cutscene pointer was Dins Fire,
- 80378C00 on 1.0.
- For the natural cutscene, this is reading Fairy Fountain scene data. However, if you look at this list you will notice something:
- https://www.zeldaspeedruns.com/oot/wrongwarp/cutscene-pointer-values
- Immediately following Dins Cs pointer is Learn Song of Time cutscene pointer, at 803783A0. This means the data for Gerudo Valley cutscene in the Gerudo Valley scene is similarly located to where the data for Song of Time cutscene is located in ToT scene data. As r0bd0g shows, if you load Temple of Time, and wrong warp without passing through a scene which is large enough to overwrite that data (different scenes are different sizes in memory), then that data from Temple of Time is still there and reading cutscene data from a little bit earlier than usual passes over a few safe junk commands and into the natural cutscene. Boss rooms are very small scenes, but Deku Tree is not, so you have to use FW on B to travel to a boss wrong warp without overwriting that stale data. However, as it is located in scene data, it had to rely on the cutscene pointers being similar in location and the junk commands not crashing in-between.
- After r0bd0g succeeded with this, Exodus wanted to do something similar, but instead of lining up data such that we ran a few junk commands and then landed on a natural scene cutscene commands, we run our own manipulable commands on the actor heap using actor cutscenes and actor data.
- https://m.youtube.com/watch?v=m2Q3TnHg560
- Specifically, he wanted to run command 000003e8 which is CS_CMD_DEST (in decomp) which warps you somewhere and each destination has a seperate hard-coded set of actions to run when warping. See below
- Or check out z_cutscene_commands.h / z_cutscene.h
- define CS_DESTINATION(destination, startFrame, endFrame) \
- CS_CMD_DESTINATION, 1, CMD_HH(destination, startFrame), CMD_HH(endFrame, endFrame)
- Notice this is actually spread out over three seperate words, which is quite challenging.
- For example, in the video, he edits actor data at cutscene pointer to spell CS_DEST_FIRE_MEDALLION
- 0x1E */ CS_DEST_CHAMBER_OF_SAGES_FIRE_MEDALLION
- When running this command, we get these effects
- case CS_DEST_CHAMBER_OF_SAGES_FIRE_MEDALLION:
- play->nextEntranceIndex = ENTR_CHAMBER_OF_THE_SAGES_0;
- play->transitionTrigger = TRANS_TRIGGER_START;
- play->transitionType = TRANS_TYPE_FADE_WHITE;
- Item_Give(play, ITEM_MEDALLION_FIRE);
- gSaveContext.chamberCutsceneNum = CHAMBER_CS_FIRE;
- break;
- However, it is very difficult to manually spell actor commands on the actor heap, because a command requires several fully manipulable words in memory directly next to one another. The structure of commands can be found in z_cutscene_commands.h or
- https://wiki.cloudmodding.com/oot/Cutscenes
- Some cutscene commands use two words, others use three, or four or five.
- At minimum, you would need some level of control over 2 words for the header, then three additional words for CS_DEST. Additionally, the structure of valid cutscene commands does NOT resemble realistic floats, so you would be unable to use actor floats to form this data. It is also especially difficult to get data at any specific alignment in memory, as the majority of actor cutscene pointers end in x0, but most conveniently manipulable data in actors is not x0 aligned. The actor cs pointers with non x0 alignment are also not convenient to get and are not relocatable.
- Recently, this has been revisited with this difficulty in mind. It was noticed that in the default case for cutscene commands, which will run for anything not specifically defined by the switch case as a command, the code does the following:
- default:
- MemCpy(&cmdEntries, script, 4);
- script += sizeof(cmdEntries);
- for (j = 0; j < cmdEntries; j++) {
- script += 0x30;
- }
- break;
- You can ignore exactly what the code is doing, just see that it adds a x30 skip for every command entry.
- Effectively this means, that if you have an invalid command, for example, 00000000 00000000 is an invalid command with 0 entries, so it will jump by 00000000*x30, which results in essentially just skipping over only the 00000000 00000000, and immediately picking up the cutscene again. Negative entries are treated the same as zero. Positive entries skip entries*x30. So, if we produce an invalid cutscene command with positive controllable entries, we can skip forward an amount of x30 that we choose. This removes the need for immediate consecutive cutscene command entries, which was a major challenge. With this skip, with a large enough invalid entry, it can skip all the way from the actor heap onto the scene data, where cutscene data is already written out, meaning we can essentially combine r0bd0g and exodus ideas. This greatly reduces what we need to spell out in an actor.
- What we actually ended up going with is the following:
- (totalEntries, csFrameCount) (invalid, invalid) (invalid,controllable)
- Effectively, this can be simplified to
- (Positive, positive) (invalid, (negative or zero)) (invalid, controllable)
- This was satisfied by the first few words of the Actor struct which reads:
- typedef struct Actor {
- /* 0x000 */ s16 id; // Actor ID
- /* 0x002 */ u8 category; // Actor category. Refer to the corresponding enum for values
- /* 0x003 */ s8 room; // Room number the actor is in. -1 denotes that the actor won't despawn on a room change
- /* 0x004 */ u32 flags; // Flags used for various purposes
- /* 0x008 */ PosRot home; // Initial position/rotation when spawned. Can be used for other purposes
- Which for one example bombchu is 00DA03FF 00000050 Xcoordinate Ycoordinate Zcoordinate XrotYrot Zrot0000
- The first two words are already positive which we want, so all we need to do is get a negative or zero Y coordinate on the spawn, 0 X and 0 Z rotation, and our Y rotation is the amount we want to skip by. However, an additional concern is that the csFrameCount is quite low, but by dropping the bombchu onto the floor, its flags become 0x00080000 which is more than enough for pretty much any natural cutscene.
- The means we need to create a heap setup where a bombchu allocates exactly at our cutscene pointer, which is accomplished with the following steps:
- begin with gohma defeated, and FW in 231 room
- savewarp in jabu to load title screen
- load second room
- break both boxes and kill the octorok
- load ruto room - cutscene pointer to 801f6cd0
- game over
- load second room
- kill the octorok (collect drops if necessary)
- enter bubble room
- spawn bugs, fish, first bombchu (in that order) to use heap space
- spawn a second bombchu at 801f6cd0 with angle XXXX, put it down to set its actor flags to a high number
- recollect the fish
- return to FW and wrong warp from gohma's lair to collapse
- Sidenote: game over specifically is needed for this heap as it loads things slightly differently due to the game loading and immediately unloading nayrus love for 1 frame on game over load.
- This process has two considerations: cannot overwrite the cutscene data present in the bombchu anywhere we travel afterwards, and we cannot overwrite the scene data we are using in any scenes we pass through since hyrule field (title screen).
- In the hyrule field scene, the flashback cutscene is located at 80365750 on GCJ. This needs us to use Gohmas warp and WW to collapse specifically to have the appropriately sized scene where this data isn't overwritten. This is also convenient as 801f6CD0 is high enough in memory to not be overwritten by 231/ Gohma room/collapse actors.
- Sidetracking a bit to explain this:
- The scene allocates from the bottom upwards. After the scene is the object space, which has a fixed size of xFA000 for the majority of scenes (much of it will be unused space and not overwritten). See more info here: https://wiki.cloudmodding.com/oot/RAM_Map
- This means it's not always about the absolute size of the scene, but it's also about where specifically the cutscene data is in the scene, and where it falls relative to this uninitialized memory.
- So, what ends up happening is the cutscene post wrong warp starts processing at 801F6cD0, continues until 801F6ce8, and skips x30*Yrot. This means we need this to land on any cutscene command before the flashback ends. There are a variety of angles which work for this, including:
- 79B8
- 7A25
- 7A27
- 7A3D
- 7A41
- 7A42
- 7A44
- 7A45
- 7A66
- Not every angle works because cutscene commands are varying lengths, so in the natural cutscene only some commands are starting at allignment x8, and for others x8 is halfway through a command, leading it to read entirely junk. Additionally, these angles were filtered to exclude cutscene commands which set Links position, as the cutscene coordinates are OoB in collapse, causing Link to void out before the cutscene finishes.
- For LACS, this is the latest into the cutscene we can wrong warp with this, as the cutscene in Temple of Time relies on Zelda actor to give you the light arrows, so if you tried to WW later, it would just play Zelda's speech in collapse but do nothing without Zelda present. It also would not set any ages or entrances if you warped to the very last part.
- This technique is not limited to LACS cutscene. It works for any cutscene data which isn't overwritten in the scene. For example, with this heap and Hyrule Field, you can also use these:
- Credits (Epona):
- 7ABB
- 7AE6
- credits (Zelda in the sky):
- 7B09
- 7B0A
- 7B14
- 7B1D
- 7B1E
- 7B1F
- 7B23
- These angles will change depending on the cutscene pointer you start with and where in the scene cutscene you want to go.
- These were originally manually brute forced by looking for the nearest working angles to LACS cs pointer.
- This technique is also not limited to using scene data for it, which can be a big issue for scenes where the scene data is always overwritten when wrong warping. Instead of scene, we can jump from our first actor to a second actor. However, it has significantly stricter requirements for heap, at least for the cases I have currently found.
- Also, alignment is still an issue as invalid commands can only skip from x8 to x8 or from x0 to x0, meaning x4 and xC manipulable actor data isn't able to be used freely. This is work-around-able as explained later.
- For example, if you wanted to learn a song, you would use:
- CS_TEXT_OCARINA_ACTION
- This is actually just a type of text entry,
- #define CS_TEXT_OCARINA_ACTION(ocarinaAction, startFrame, endFrame, messageId) \
- CS_TEXT(ocarinaAction, startFrame, endFrame, CS_TEXT_OCARINA_ACTION, messageId, CS_TEXT_ID_NONE)
- Which means cs text ocarina action must be prefixed by #define CS_TEXT_LIST(entries) \
- CS_CMD_TEXT, CMD_W(entries)
- In numbers, this means you want 00000013 0000xxxx to start a text list of xxxx size, and within that text list you need an entry with yyyyzzzz zzzz0002 zzzzzzzz, where Y corresponds to the song and z doesn't matter. For example, if we choose ZL, we want need 0017zzzz zzzz0002. This is a bit of a struggle to get 4 words manipulable in one actor, so I planned to do so across two actors. The grass actor, En_Kusa has
- 0x0150 */ ColliderCylinder collider;
- /* 0x019C */ s16 timer;
- /* 0x019E */ s8 requiredObjectSlot;
- } EnKusa; // size = 0x01A0, where the collider has
- Vec3s hitPos; //( Point of contact in its bump collider) just before its timer, leaving us with 2.5 words we have control over (x194 x198 x19c) 0000xxxx yyyyzzzz tttt0aQQ. Because the data at x19F is not initialized, we actually can control that prior at some point, giving us 2.75 words we can control. However, due to alignment of cs pointer and our invalid jumps being limited to x0 and x8, we only have 1.75 words (yyyyzzzz tttt0aQQ). This still is enough to start the text list command, leaving us x0AQQ text entries (where QQ is stale uninitialized data) to form a valid ocarina action. This needs to be at either the xC or x0 alignment as text entries are 3 words long. Spin attack actor En_m_thunder conveniently has 0x014C */ ColliderCylinder collider which ends up in having position shorts alligned xC.
- So, our flow would look like
- Cutscene pointer-> actor (chu) rot ->kusa pos->spin attack pos
- Because text entries have an odd number of words, this can also be used solely for the purpose of changing the alignment in memory. You can treat it as an xC*entries skip. You can use this to point to another actor to spell out a CS_DEST which is not obtainable in scene data.
- I have NOT properly scanned every actor struct, and it is quite possible that there are better aligned or more controllable data to perform this with.
- One last note:
- Due to how it iterates ,
- Both the overall command processing
- for (i = 0; i < totalEntries; i++) {
- MemCpy(&cmdType, script, sizeof(cmdType));
- script += sizeof(cmdType);
- if (cmdType == CS_CMD_STOP) {
- return;
- }
- And the command sub entry processing
- default:
- MemCpy(&cmdEntries, script, 4);
- script += sizeof(cmdEntries);
- for (j = 0; j < cmdEntries; j++) {
- script += 0x30;
- }
- break;
- Both have this effect where
- s16 i;
- s32 totalEntries;
- s32 cmdEntries;
- s16 j;
- Due to the data size differences, neither i nor j are capable of successfully incrementing to totalEntries or cmdEntries in every case. If cmdEntries is 00008000, in s32 this is a positive number, but in s16 it is a negative number, meaning when it counts i++ or j++ it can never meet the condition to stop, and gets stuck in a loop. For command processing, this isn't a big issue as there are two safeguards in place: you either hit a cmd stop cutscene terminator, or your frames exceed the frame count, and it stops. But for within commands themselves, if your cmdEntries are 00008000 to 7fffffff then it will get stuck in a perpetual loop. As mentioned earlier, command processing is happening every frame, but this would get stuck mid frame, meaning the frames would not advance and this is a true lock. Conversely, cmdEntries of 80000000 to 00000000 would all meet the condition as when trying to meet that goal is passes thru 8000 and FFFF. This limits the effective skip, as well as the effective pretty much every cutscene command, to have x7FFF entries.
- TLDR:
- We can redirect where the cutscene engine is looking for cutscenes based on the bombchu angle, and then either form our own cutscenes or skip memory to the game's natural scene cutscenes to play them post wrong warp.
- -natalyahasdied
Advertisement
Add Comment
Please, Sign In to add comment