Hey everyone, Malleo here. For about 5 years, a small group in the Paper
Mario 64 community has been working to achieve a state called Arbitrary Code Execution or
ACE. ACE is notorious in the speedrunning community,
as it has the potential to allow the player to take total control over the game’s code
and reprogram it. A great example is Masterjun’s Super Mario
World ACE demonstration, in which code for Snake and Pong is written and executed within
Super Mario World. As you can imagine, one of the most well-known
applications for ACE in speedruns is the potential to perform a credits warp. In this scenario, you find a way to change
the flow of execution in the game’s code to load the credits sequence. A credits warp is the holy grail in any speedrun,
as it is almost always the last major timeskip that will ever be found in a game. Rain, Fray and MrCheeze, with the help of
several others in the community, finally accomplished Arbitrary Code Execution and showcased it
by writing code to warp to the credits, drawing their names on-screen, and changing the background
music. Huge congratulations to the Paper Mario 64
team on this amazing discovery. I’ll have links regarding their work in
the description. Two days after the discovery of Paper Mario
64’s credits warp, I went live on Twitch to showcase something that SolidifiedGaming
and I had just completed. This is what happened on stream. Just a day after the Paper Mario 64 Credits
Warp discovery, SolidifiedGaming and I managed to exploit a buffer overflow and warp to the
credits in TTYD a mere 25 minutes into the game. This video will explain the theory and planning
behind how this came to be, and discuss what this means for the future of TTYD speedrunning
as we know it. Hope you enjoy! To begin our story, let’s first talk about
events, or EVTs for short. Each event represents a set of commands and
stores information about the current state of the event execution. Examples of events include everything from
setting up objects and NPCs upon loading a room to orchestrating cutscenes. When the game runs an event, the commands
specified in the event are executed based off of a scripting language defined within
the game’s code. Therefore, we can think of events as a running
instance of script code. To manage the order in which events should
be run on a particular frame, the game uses a queueing system, through which it starts
running events from the start of the queue and works its way towards the end of the queue. The game has room in memory to store at most
256 events on any given frame. This may sound like plenty, until glitchhunters
get involved. We knew for a while you could pause and hammer
at the same time, resulting in Mario continuously hammering while in the pause menu. As you could guess, we call this pause hammering,
and we’ll see this technique come up a couple times later in the video. While in the pause menu, hammer events are
added to the queue, and since we’re paused, the events are not yet executed. Thus, they remain in the queue every frame
until shortly after unpausing. Pause-hammering long enough allows you to
fill up all 256 slots of the queue. Afterwards, any subsequent events are not
added to the queue since it is now full. In mid-2020, SolidifiedGaming found that certain
events, such as opening doors, spawn additional events in such a way that the additional events
are added to the queue even if the tables are full. SG played around with this mechanic for a
while until one day, her game crashed. This had never happened before, even after
months of experimentation. Thrown off by this, SG began investigating. As it turns out, there was an invalid memory
read. While trying to execute events in the queue,
the game tried to access a region of memory that isn’t actually accessible. What does that mean? As SG soon learned, the game started reading
from memory located *way* past the normal event data region and was instead trying to
reach data that occurred much later in memory. Since it eventually reached the end of the
main valid memory region, the game crashed. But what if it hadn’t? Then the game would have started reading data
at this location as if it was event data… It was at this exact moment that SolidifiedGaming
realized the significance of what she had just uncovered and knew that if there were
ever a way to pull off a credits warp, it would be through this method. If we could control where the game started
reading event data from, and manipulate that data in a particular way, then the game could
interpret that data as whatever script command we wanted. The game uses events to load a new map or
area of the game, so that means we could run script code to change the map to the credits
sequence. From that point onward, SG sought to reverse
engineer the inner-workings of events even further in order to explore how we could manipulate
and run event data to jump to the credits. Let’s start by discussing all the relevant
information that is required to understand the steps necessary to jump to the credits
sequence. First, let’s discuss the two tables involved
in the event queue. The first table is an array of unique identifiers
for each event that the game wants to queue up. Let’s call this table the “ID table”. The second table is an array of indices that
are used to determine where in memory the data for a given event actually exists. Let’s call this the “Index Table”. There is a one-to-one mapping between an ID
and an index. Strictly speaking, the ID in the first slot
corresponds to the index in the first slot, the ID in the second slot corresponds to the
index in the second slot, and so on. As I mentioned earlier, both tables can hold
up to 256 elements. Second, the event or EVT is the actual data
structure that contains information about the commands that should run and information
about the current state of the event’s execution. For simplicity’s sake, I’ll just show
the fields that are relevant for the purposes of this video. There is a field located 8 bytes into the
structure that we’ll call “Flags”. It’s a bit field, meaning the game may read
particular bits at that address to determine information about the current state of the
event’s execution. In order for a queued event to run, the first,
fourth, and seventh bit must be set to 0, and the eighth bit must be set to 1. The other bits can be either 1 or 0. Based on this rule, the flags byte must be
one of the following values [on-screen]. At offset 0xA is a field we’ll call “opcode”. Short for operation code, the opcode is always
the first component of a script command and just specifies what kind of operation to perform. The opcode refers to a set of operations that
the game’s built-in scripting language can perform. Examples of opcodes include an add, which
just takes two numbers and sums then, and user_func, which allows the game to call one
of many functions that exist within the game’s code. At offset 0x14 is what we’ll call “NextCmdPtr”. This field acts as a pointer to the next command
that this event will execute. Further into the structure at offset 0x15C,
we have a unique identifier for this event. As I mentioned earlier, an ID is placed in
the ID Table when an event is queued. However, the game also stores that same ID
in the event itself. If the ID in the ID table does not match the
ID in the event itself, then the event does not run. This check is likely implemented to avoid
certain problems with running and terminating events in the queue. Lastly, there are two floating point numbers
we’ll call “Timescale” and “TimeScheduledToRun” located at offsets 0x164 and 0x168 respectively. Skipping over the details, as long as Timescale
and TimeScheduledToRun sum to 1 or greater, then the event can execute. Just to reiterate, the game checks for valid
flags, ID, timeScale, and TimeScheduledToRun. Only when all of those checks pass does a
queued event run. Now let’s walk through how the game actually
queues events. To start, given the fact that we need to be
able to uniquely identify events, the game has a counter which tracks the next ID to
be assigned. Let’s just call this the “Next Event ID”. This counter is incremented every time an
event is queued. This way, there is no fear that a given ID
will accidentally refer to multiple events. If the game wants to queue up an event, it
first finds where in the queue it can place it, takes “Next Event ID”, adds it to
the ID table, and increments “Next Event ID”. Then the game determines where in memory it
can store the new event, and stores an index in the index table that corresponds with this
memory location. Event indices represent what position in memory
an event is located at. To find where the event is stored, you first
take the index and multiply by 0x1B0, which is the size of one event. It then adds this result with the start of
the event memory region. The result is the location of the desired
event. The game will then read the event information
to determine if it should run and what script code it should execute. So let’s simulate what SolidifiedGaming
did by queueing a door, pause-hammering, filling up the event queue, and then un-pausing. When we unpause and the door event generates
two additional events, the IDs for the additional events are accidentally written into the first
two slots of the index table. The ID table and the index tables are right
next to each other in memory, so when event IDs are written past the end of the ID table,
they are instead written to the start of the index table. Now that the door event ran in the first slot
of the queue, the game will now try to run the second event in the queue. When the game does so, it will associate the
second ID in the ID table with the second index in the index table, which is now an
incredibly high value compared to what the indices normally are. The game will take this really large number,
multiply by 0x1B0, and add it to the start of the event memory region. As a result, it will start reading memory
located way past the normal event memory region. The game is now reading data from other data
structures as if it’s event data. For example, it’s possible that the game
starts reading event data from Mario’s data structure and interprets Mario’s coordinates
as the event flags, opcode, or nextCmdPtr. Now that we know that it is possible for the
game to read event data from unintended memory locations, the question is: What exactly can
we do with that knowledge? Through reverse-engineering, we know that
to change to a different map, the game calls the mapchange function and specifies the desired
room to load. One such map is the credits sequence. If we can cause the game to read event data
that is interpreted as a mapchange function call to the credits sequence, then we would
be able to warp to the credits. With all of that out of the way, let’s talk
about the route that we began looking into. Shortly after discussion about a potential
credits warp began, PistonMiner was quick to suggest using something called the EFF
heap to manipulate data for this scenario. Effects, or EFFs for short, represent what
are essentially particle effects. This includes dust from walking around, the
exclamation mark that appears over enemy’s heads, and grass flying up in the air when
walking around the grass patches in Petal Meadows. Though the abbreviation is similar, they are
not at all associated with events besides the fact that some events do actually generate
effects. The effect heap allows the game to allocate
space for the effect data and free up that memory when the effect is despawned so other
effects can use that space later. If multiple effects are loaded at the same
time, then more of the heap will be filled with data. If we overflow the ID table such that a particular
ID is written to the index table, the game can begin reading event data from the effect
heap. We quickly realized that this could be a viable
route; we have control over what effects are loaded and in what order, and we can also
slightly vary where in the heap the game reads event data from. Should we manage to find some combination
of effects such that we have control over what data is written to the fake event data
structure, then it is entirely possible that we could call the mapchange function. To ultimately call the mapchange function
after running fake event data, SolidifiedGaming determined the best course of action would
be to set the opcode to 0x0, and set nextCmdPtr to some predetermined memory address. Opcode 0x0 simply fetches the next command,
which is located where nextCmdPtr points to. The plan was to set nextCmdPtr to some manipulable
memory region that can be interpreted as a mapchange function call. So, how exactly could we go about pulling
all of this off? We set out to find a region of the heap that
we could rely upon to write event data. Let’s cover how the heap works so we can
understand why caution needs to be taken. When the game needs to place effect data in
this heap, it searches the heap from start to end until it finds the first free slot
to allocate data to. If the data in a slot is no longer needed,
then the slot is freed up and the game can use that slot to allocate new data again. However, the data previously written in that
slot persists as long as a new effect doesn’t overwrite it. The main issue we wanted to avoid was writing
event data in a slot too close to the start of the heap. If we read data at the very start of the effect
heap, then that data would be more susceptible to changing, every time a single effect is
loaded into memory. We want our data to persist until we have
an opportunity to execute the event, so we don’t want any of our data to be overwritten. If we use a portion of the heap further away
from the start, then it will take quite a few loaded effects to overwrite our data. We found a location far enough into the heap
such that we can manipulate it in a sufficient way and not worry about the data being accidentally
overwritten. With the memory location decided, let’s
discuss how we began to write the necessary data for the fake event data structure. I apologize in advance if the order of explanations
is confusing, as certain pieces of this puzzle may not be explained until slightly later. However, I think the best way to go about
this is to explain this warp chronologically. To start, we buy a Mushroom and soft-reset
the game. We reset the game in order to reset “Next
Event ID” to 1 and I’ll explain why a bit later. When we load back in-game, we now care about
setting up our event data structure. We want to set flags, opcode, ID, timeScale,
and timeScheduledToRun all to values that will pass the necessary checks and set us
up to fetch instructions from a manipulable memory region. To start, we pause hammer and use a Mushroom
while doing so. This results in Mario continuously hammering,
which generates effects, while the Mushroom is consumed, which also generates effects. If done at the right time, this, in combination
with effects generated by butterflies floating around Petalburg, results in flags being set
to 0x41 and opcode being set to 0xC1. Since 0x41 is one of the valid flag values
that I mentioned, the flags check passes. However, this opcode doesn’t match the fetch
opcode I mentioned earlier. As it turns out, in the event an invalid opcode
is provided, the game basically treats this the same way as a fetch, so it will still
look at nextCmdPtr to retrieve the next script command. Thus, this opcode is satisfactory for our
situation. With those fields satisfied, we now make our
way left. We stop moving every so often to despawn effects. If we generate too many effects, we may end
up overwriting flags and opcode in the heap. However, we also need to generate enough dust
effects to write a suitable ID to our fake event data structure while leaving flags and
the opcode intact. Coincidentally, loading dust effects in combination
with the butterfly effects in a certain order writes data to the heap such that ID is written,
but flags and opcode are not modified. Once we leave the room, ID is set to 0xFF,
timeScale is set to 1 and timeScheduledToRun is set to 76.727. Because the sum of timeScale and timeScheduledToRun
is greater than 1, this check is satisfied. Recall how the ID in an event needs to match
the corresponding ID in the ID table. If the two IDs don’t match, then the game
knows something odd has occurred and it prevents the event from running. For now it’s a bit unclear why exactly we
set the fake event’s ID to 0xFF, but we’ll get to that in a bit. Let’s review. The flags field is set properly, opcode is
set properly, ID is set properly, even if I haven’t explained why yet, and timeScale
and timeScheduledToRun are set properly. What’s left? NextCmdPtr, which quickly became the most
difficult component of the credits warp setup. Just like we’ve been doing with the other
fake event fields, we now need to spawn particle effects in some order such that a consistently
manipulable value is written to nextCmdPtr in the fake structure. Not only that, but we had to make sure that
the particular combination of effects, when written to the heap, does not overwrite the
flags, opcode, ID, timeScale, or timeScheduledToRun. It was quickly realized that finding such
a viable combination would be a tough task. This took several weeks where we mostly documented
effect data structures and tried writing programs to simulate the heap. After weeks of effort, we weren’t successful
in finding a viable combination… until SolidifiedGaming had a stroke of luck. While messing around in-game, she somehow
wrote Mario’s position to the nextCmdPtr field by loading some precise combination
of effects into memory. After analyzing the heap, SG was able to determine
what combination of effects were used to write Mario’s position to nextCmdPtr. We now have a replicable method by which we
can control what’s written to nextCmdPtr by precisely setting Mario along the X-axis
when spawning this particular combination of effects. Maybe we could use Mario’s X-position to
write a valid memory pointer to nextCmdPtr? Now the question became: What memory location
could we take advantage of, so that we have sufficient control over enough bytes of data
to write script code to call the mapchange function with the credits sequence? SolidifiedGaming found a viable memory location
in which GameCube controller inputs are stored. At this particular location, starting at memory
address 0x8040F0D0, controller data is stored in the following way: Byte 1 is a bitfield, where the left 3 bits
are 0, followed by Start, Y,X,B, and A where a bit is set to 1 if the button is being pressed. Byte 2 is also a bitfield, where the first
bit is 1, followed by L, R, Z, D-Up, D-Down, D-Right, and D-Left. Byte 3 is the X component of the Analog Stick,
which is a value in the range of 0-255. Byte 4 is the Y component of the Analog Stick,
also in a range of 0-255. Byte 5 is the X component of the C-Stick with
range 0-255. Byte 6 is the Y component of the C-Stick with
range 0-255. Byte 7 is the L-Shoulder Analog input, where
a value between 0-255 represents how hard you are pressing down the shoulder button,
and Byte 8 is, similarly, the R-Shoulder Analog
input. These 8 bytes repeat 4 times for each of the
4 controller ports. It’s important to mention the following
constraints, as a result of the layouts of the bytes mentioned: For the first bitfield,
it can only have values between 0x00 and 0x1F due to the limited number of buttons that
are encoded in that bitfield, and for the second bitfield, if you aren’t holding any
buttons the byte is, for some reason, 0x80. That is to say, because the top bit is 1,
we can’t produce any value lower than 0x80. Because of these constraints, we need to determine
where in the controller data we want to start reading bytes from. Let’s look at the typical script command
the game runs to change maps: “User_func [evt_bero_mapchange] [map_name]
[loading_zone_name]” Again, the user_func opcode is used to call
the function specified right after, which in this case is evt_bero_mapchange. To change to a specific map, we provide a
memory location that contains the desired map name. Lastly, you can specify a memory pointer that
signifies what loading zone to enter the map from. Now let’s look at how this is represented
in the game’s code. “0000005B 80079FE4 805B4C7C 00000000”. Let’s look at the first 4 bytes. The fourth byte in this data is the opcode. 0x5B is used to represent user_func. Next, 0x80079FE4 is the location of the mapchange
function. After that, 0x805B4C7C is the location of
the map name, in this case “end_00” and that’s followed by all zeroes. When the game doesn’t need to specify a
loading zone to enter the map from, like in the case of the credits, the field is left
as zero. If we were to encode this data with controller
inputs starting at Controller 2, then we would have enough bytes to fit this command. However, to simplify things a bit more, it
actually turns out that the loading zone field doesn’t actually matter at all. Even if it’s an invalid memory pointer,
the game is still able to run the mapchange command. So now we’re left caring only about the
first 12 bytes. One last thing to mention is that if there
are two memory locations that contain “end_00”, then you can use either one and the command
will still function the same way. For no particular reason, we chose to use
memory address 0x802ECC84 instead of the intended one. Now with a simplified and concrete command,
let’s encode it! We don’t really want to read from Controller
1’s data since we’re actively using that to play the game. To make things go a bit smoother, let’s
try starting from Controller 2’s data. Controller 2 will be used to write the opcode
and the mapchange function pointer. But now we have a problem. We need to specify the memory location of
the map_name. However, the first byte of Controller 3 cannot
have a value larger than 0x1F. Thus we are unable to write a valid memory
pointer. Instead, let’s try reading starting from
Controller 2 byte 2. Then we end up using Controller 3 byte 1 to
encode the last byte of the mapchange function pointer. We need to encode 0xE4, but we can’t have
values larger than 0x1F in that first byte, so let’s try starting from Controller 2
byte 3. We face the same issue. We need to encode 0x9F but we can’t use
the first byte of Controller 3 to do so. Let’s try to start at Controller 2 byte
4. And… It works. If we start at 0x8040F0DB, which is Port 2’s
analog stick Y position, then we are able to encode the mapchange function call along
with the pointer to the “end_00” string to specify the credits sequence. The inputs look like this. So, that’s fantastic! One of the last things we need to do is determine
what X position we need to have Mario stand at in order to write 0x8040F0DB to nextCmdPtr. Mario’s coordinates are encoded as floating-point
numbers. If we want to encode 0x8040F0DB as a float,
this yields a position of -5.96e-39. If you assume that a position change of 1
unit represents a meter, then the distance between our desired position and 0 is smaller
than a Planck length. That is such an unfathomably small position
that it is not at all realistic to ever achieve this position. It’s a dead-end. We can’t use Mario’s x position to write
the controller data location to nextCmdPtr. In fact, any valid memory pointer between
0x80000000 and 0x817FFFFF, which is the range for the game’s main memory region, is inaccessible
for the same reason. So this must mean that we can’t use Mario’s
x position to write any valid memory location to nextCmdPtr. Except, this isn’t the only valid memory
range. The range I just provided actually refers
to the GameCube’s cached main memory. The cache is essentially related to how memory
is transferred between main memory and the CPU. There is actually an uncached main memory
region located between addresses 0xC0000000 and 0xC17FFFFF. The values stored here are usually identical
to what is stored in cached main memory, and that means controller data is also stored
here. So instead of using 0x8040F0DB, is it possible
to write 0xC040F0DB to nextCmdPtr? 0xC040F0DB corresponds with an X-position
of -3.01470065117. Because this is a much larger number, this
is entirely reasonable to reach. So let’s try to get to that position. I found two different analog stick arrangements
in opposite directions along the X-axis. The left arrangement gives an X speed of -.12486267,
and the right arrangement gives an X speed of +.01785278. Holding the left arrangement for one frame
and the right arrangement for 7 frames results in a position offset of -.00010681 units,
or 0x1A3. This is a pretty small position offset, so
I can get pretty close to the position we need, but not quite. I need to offset my position by less than
0x1A3, so I had to find some other way to change my position. For whatever reason, possibly due to an ever-so-slightly
rotated camera angle, holding straight down on the analog stick results in my X position
memory value incrementing by exactly 1 and when I hold up it doesn't change. So, I repeatedly moved down to increment my
position to 0xC040F0DB. Our next goal is to write this position to
memory. By spawning the precise combination of effects
that SG found, we manage to write 0xC040F0DB to nextCmdPtr. So, our fake data structure is set up, and
when the game fetches a script command from the controller data, we know what controller
inputs are needed to encode the mapchange function call. All that's left is making the game execute
this fake event. We head back to the right. Before reaching Petalburg, we shake the bush
to increment “Next Event ID”. Recall that to pass the ID check for our fake
event struct, we need to have the ID stored in the ID table match the ID stored in our
fake event struct. However, we also somehow need to overflow
a very high event ID into the index table in order for the game to start reading from
our fake event structure. So the question is, how can we keep 0xFF in
the ID table while overflowing a much, much higher ID into the index table? It just so happens that there is a particular
event pertaining to the item shop that is spawned upon entering Petalburg and remains
in the queue so long as we are in the current room. We entered Petalburg after incrementing the
bush a few times such that this persistent event now has an ID of 0xFF in the queue. Even though this event is persistent, loading
other events in the room causes the persistent event to be shifted to the second slot in
the queue. Loading subsequent events causes the persistent
event to remain in the second slot. Moving forward, when we go to set up the overflow,
we need to keep in mind that the 0xFF ID will be in the second slot of the ID table, and
thus we need to make sure we have the index for our fake event structure in the second
slot of the index table. With the 0xFF ID set up, we enter the Petalburg
shop and approach the shop sign. You can press A to open a menu and press B
right after to close the menu. The menu generates and queues an event, which
causes the “Next Event ID” to be incremented by 1. The index that corresponds with the region
of the effect heap that we used to set up our fake event structure is 17,509. We want to read this sign repeatedly until
“Next Event ID” gets close to 17,509. Unfortunately, this takes about 9 minutes
when done perfectly. But honestly the memes as a result of this
were pretty funny. Once we reach an event index of around 17,000,
we can get started filling up the event queue so we can overflow IDs into the index table. One main concern is the game actually refreshes
the event queue every frame. We need to somehow queue up 256 events to
fill up the queue without any of them despawning. To do so we can make use of a pause hammer. We used a pause hammer earlier to set our
flags and opcode, but that ultimately occurred because the hammer spawns effects whose data
was written to the effect heap. Hammers also happen to generate events, and
pause hammering continuously queues up hammer events without removing them from the queue. So we can start to fill up the table. When we start pause hammering, we also activate
the door. Doing so buffers the door opening until we
unpause. As I mentioned near the start of the video,
it’s not possible to overflow the event queue tables unless an event itself generates
more events, but doors can be used to overflow the table as they generate additional events. While in the pause menu, hammer events are
constantly being queued up. Once the queue is full, we need to start paying
attention to “Next Event ID”. Even though the game does not queue events
if the table is full, it still increments “Next Event ID” when trying to do so. Thus, we can sit and wait in the pause menu
as “Next Event ID” gets closer and closer to 17,509. Eventually we want to unpause such that, right
before the game runs the queued events, ”Next Event ID” is 17,508. If done correctly, then the following order
of events occurs. The door event is executed, during which it
spawns two additional events. The first additional event retrieves “Next
Event ID”, which is 17,508, and adds that to the ID table in the queue. Because the table is full, the ID is placed
into the first slot of the index table. “Next Event ID” is then incremented to
17,509. The second additional event retrieves “Next
Event ID”, 17,509, and places that ID right after the first additional event ID, which
means it is placed in the second slot of the index table. The game has finished executing the door event
and now looks at the second event in the queue. The ID is 0xFF because of the persistent event
that spawned upon entering Petalburg, and the index is 17,509 because we overflowed
the second additional event ID into the index table. The game takes 17,509, multiplies it by 0x1B0,
and adds it to the start of the event data region to find where it should read event
data from. As a result, it navigates to our fake event
structure. It compares the ID of 0xFF from the second
slot of the ID table with the ID inside our fake event struct. The check passes. Then it checks for the flags, which is one
of the accepted values, so the check passes. Then it sums timeScale and timeScheduledToRun
which is greater than 1. The check passes. All checks have passed, thus the event is
executed. The fetch opcode is read, so the game fetches
the script command from the memory address specified by nextCmdPtr. Because we set nextCmdPtr to the address of
port 2 controller data starting at the Y component of the analog stick, we can encode the mapchange
command and specify the credits map ID. This command is executed and the mapchange
is successful. The screen begins to fade out, and we are
presented with the credits screen. As a side effect of still having Frankly in
our party, he appears for the entirety of the credits behind any images on-screen. After only 25 minutes, the game has been beaten. Proceeding to save your file after the credits
and load it results in Mario, Goombella, and Frankly returning to Rogueport in a post-game
state. The game recognizes that you have beaten the
Shadow Queen and completed the credits sequence. This leaves us with two main questions, the
first being, Is this RTA viable? By no stretch of the imagination is this humanly
possible. There are far too many details that go into
this route that could never be replicated by a human, namely the floating point perfect
position that’s required. The second question is whether or not a faster
route can be found. We see no reason to believe otherwise. In fact, we believe that there will at some
point be an RTA viable route for Credits Warp. It’s just a matter of continuing to document
the game’s source code and discover other routes to use. What happens to the future of the TTYD speedrun? Should a human perform a credits warp in this
game, the TTYD speedrunning community will split any% into two separate categories, with
the credits warp taking the place of any% and a separate category called “any% no
arbitrary script execution” being created. As far as speedrunning competition is concerned,
very little will change. For the credits warp, it’s also worth keeping
in mind that we don’t need to rely on the particle effect heap. We could always use a different region of
the game’s memory, should we find that it is manipulatable in just the right way to
satisfy all the game’s checks. Most significantly, that would prevent the
need to read the sign for 9 minutes because we wouldn’t need “Next Event ID” to
be as large when it overflows into the index table. All of that being said, I expect there to
still be a good chunk of timesave once we wander upon a new route. It’s worth making the distinction that the
method through which we wrote script commands is not considered Arbitrary Code Execution. We aren’t actually executing arbitrary code. We’re not actually encoding assembly instructions
to reprogram the game. Instead, we’re simply encoding a single
script command. Because we’re limited to the bounds of the
scripting language, we don’t have total control over the game, yet. We do expect we could use this method to reach
an arbitrary code execution state, but that will require some more planning and research,
but it’s something I’d like to help make happen in the future. I’ve seen a lot of people say that they
actually lose interest in a speedgame that has a credits warp. I can understand that. The idea that some ridiculous glitch allows
you to skip all entertaining and memorable parts of a game just ruins the fun. But… Isn’t it entertaining and memorable in itself,
that as a result of community-wide efforts to reverse-engineer the game’s code and
mess around with events, a vulnerability was found that could be used to execute any script
command at will? Isn’t it memorable to know that there will
never be a more significant timesave found for this game? Isn’t it memorable that, since I helped
make the first TTYD TAS more than 7 years ago, that we have lowered the speedrun by
4 hours and 27 minutes? To me, this has been one of the most memorable
moments in my entire online career. It has been the most entertaining excursion
I have ever gone on in my entire online career. All that being said, for me, this only strengthens
my appreciation and interest for Paper Mario: The Thousand-Year Door. I want to give a huge thank you to SolidifiedGaming. SG discovered the crash due to the event queue
overflowing, determined why it happened, realized the potential of this exploit, and began routing
a Credits Warp. She was monumental in the planning process
and knew what we had to do along every step of the way. It was a pleasure to work alongside her and
tackle some of the tough aspects of this route, particularly achieving the floating point
perfect position. I’ve always wanted to help discover a credits
warp, and it’s because of SG that I had the opportunity to be along for the ride. Thank you to PistonMiner for theorizing that
we could use the effect heap for our fake event structure. Additionally, he confirmed for us that Mario’s
float position can be interpreted as a memory pointer into uncached main memory. Thank you to JMC4789, and booto for verifying
on-console that we can reliably access uncached GameCube memory for this route, as this is
something that hasn’t been explored very extensively before. Thank you to PistonMiner and Zephiles for
their development of the TTYD practice codes. They help make speedrunning practice for TTYD
a lot easier, and were pretty useful as we experimented with different memory locations
to set up our fake event structure in. Thank you to all the friends and community
members that looked over and helped edit this video script. Lastly, thank you for watching. The topic of this video is more complex than
what I normally feature in my other videos, but I hope you enjoyed it nonetheless. And with that, have a good one!