aaaand that's the last of the month-old videos! A note for the folks following along: Episode 1 was posted a month after recording, right around the time I recorded this one. After seeing how useful the comments were, I decided not to record another until I posted everything I had. So here we are! Going forward, I hope to post these soon after recording to make use of the feedback I've been lucky enough to get in the comment section. Massive thanks to everyone who’s been watching and giving feedback-your comments have been incredibly valuable and will now begin informing the next video. Stay tuned for more code spelunking :)
I looked into this and found a "now playing" OBS plugin. It won't help for "compilation" style music (the SNES compilations) since it can't break down each song within a single video, but should work better once I get a playlist going. Thanks for the suggestion!
the cutscene script is arguably some of the cleanest stuff in the codebase. The only caveat is the cutscene script data itself. The actual implementation of cutscene scripts seem to construct all commands and related data using about 3 or 4 common struct types. There will be cases where a command will use some but not all of the common struct's properties, and garbage data ends up in the unused properties (probably because their tooling never fully initialized the struct).
@@mzxrules yes I was referring to the data. I could never imagine doing that today, i only use resources files with JSON or similar unless it's just a few camera movements for one scene.
22:00 I can't stress the importance for people who work especially with code and monotonous tasks, to take mandatory breaks.. Something I do whenever I want to take a break from my code and sort of 'structure' my thoughts, is to draw the 'flow'. When, how and why something happens, yeah UML diagrams are ok but, i'm mostly just referring to a mind map, to sort out and structure the layout of what's in our head down onto paper. Lastly, good job!
12:00 Actually, switch cases are not necessarily faster than if/else chains. First, a modern compiler may be able to determine that a chain of if/else statements is testing a non-volatile variable against a series of constants, and generate code as equally fast as a switch statement. Second, depending on how the case statements and code blocks are clustered, the compiler might generate machine code the same as if it were written with an equivalent if/else chain 34:16 oh man, ACTOR_CUEs. So in a lot of cutscene scripts, you'll have a CS_ACTOR_CUE_LIST which instructs an entity where to go, when to go, and what action to perform. The first argument in CS_ACTOR_CUE_LIST is a CS_CMD_ACTOR_CUE_X_Y, which is actually a script command. Each CS_CMD_ACTOR_CUE_X_Y seems to be associated with some non-player entity that can be controlled by the script. For example, in the different cutscenes depicting Impa and Zelda escaping from Ganondorf, I think CS_CMD_ACTOR_CUE_0_2 is for manipulating Zelda/her horse/Impa, and CS_CMD_ACTOR_CUE_1_1 is for manipulating Ganondorf/his horse. These CS_CMD_ACTOR_CUE_X_Y commands are currently unnamed because of how hard it has been to trace a command to it's entity. All CS_CMD_ACTOR_CUE_X commands do the same thing, update actorCues[x] to point to the active CS_ACTOR_CUE for that cutsceneFrame (e.g CS_CMD_ACTOR_CUE_0_2 updates actorCues[0]). The actors are responsible for reading their cue "channel" and determining what behavior the cue should actually have. 36:19 that isn't the cutscene you've been looking for :) EntranceCutscenes are a set of cutscenes that are intended to fire usually the first time the player spawns at that entrance (the main exception are the cutscenes for Epona hopping from Lon Lon into Hyrule Field). gHyruleFieldIntroCs is actually the cutscene that plays the first time you leave the Lost Woods and enter Hyrule Field for the very first time. CS_MISC_SHOW_TITLE_CARD is simply displaying the "Hyrule Field" title card on screen.
So true about the switch case statements. The compiler might be able to optimize sequential cases (1,2,3), but if there are large gaps (1,2, 8000, 120), the compiler needs to generate if statements to check if our value can be switched into one of the listed cases. The usual optimization is using a jump-table (sometimes an array) that has a quick look up time. But even then, the compiler will throw some if statements out front to check if our case is in the bounds of our cases. In the case of GC/Wii's MWCC compiler, if there are few cases or large gaps, it will generate a binary search of comparison statements to find where to jump to. Still faster than a bunch of if/elses. That being said, in the case where we have like 100 cutscenes based on an enum with sequential values, I think it was the right decision to use a switch case.
Great content! Just imagine if you built a channel with hundreds of videos like this over the next 5-10 years, especially as more N64 games are decompiled, like Banjo and Mario Kart 64 recently. It could become a valuable resource alongside channels like MVG and Glenn Plant. There are also classic PC games with available source code, like Command & Conquer , Quake 3 and Caesar 3. Consider using an old PS/2 keyboard and CRT screen for your room setup, along with a new name and logo to enhance the channel's aesthetics. Although those don't really matter, the content makes the name. You’ve got a solid foundation to create something special and a fantastic hobby to build on that you look like you enjoy!
I've worked on a lot of tools for OoT's engine as well as a considerable amount of low-level hacking stuff for the game. If you have questions you haven't been able to figure out or just want to sit down for a session where someone fills in some of the blanks for you, feel free to reach out!
@@benvillalobos Sure! I think you've done a pretty good job so far understanding the codebase. It's just that a lot of the documentation of the game engine is a lot older than the decomp, and hosted elsewhere. So folks working with it generally either already know it or know where to look to remind themselves, or don't know the info exists and figure things out from scratch.
At this rate I'm not far off from changing something small and seeing it reflected in-game. As for meaningful changes (mods, etc.), I'm still a ways off.
You definitely can. When this first came out I spent a few hours messing with this and was able to do things like spawn a bomb when an arrow hits its target and other random little things. The code for spawning and updating things is very simple. Also just updating the number of something, the speed of something, etc. are generally all pretty simple. It sounds like Ben might do some stuff like that soon :)
Yep! "Game Programming Patterns" I really appreciate his writing style. His book is available online for free as well: gameprogrammingpatterns.com/contents.html.
I don't think there's any magic to the number 65520 or whatever it was. Check z_select's MapSelect_PrintCutsceneSetting . All the demo scenes are 0xFFFy where y is some number of the demo cutscene. That function is probably the best explanation of the values for cutscenes. Why they chose the top 12 bits all need to be set to designate all the demo scenes... no idea! Maybe it' would just be obvious in a debugger when you see 0xFFF_ that it's a demo cutscene
I've known about this for years and I still don't really have a better answer than "they just felt like doing it that way". But the way it works is like this: gSaveContext.nextCutsceneIndex (u16) defaults to 0xFFEF for no cutscene. Then you have 0xFFF0-0xFFFC for scene cutscenes 0 through 12. Scene cutscenes are special in that scenes have these things now called sceneLayers which are alternate loadouts of a scene, and sceneLayers 4+ (if defined for a scene) are reserved for those scene cutscenes 0 thru up to 12. These cutscenes play immediately when the scene loads. Lastly you have 0xFFFD which is used to enable a scripted cutscene after the scene has been loaded and you're in the middle of gameplay. Cutscenes like pulling or dropping the Master Sword do this (and the CutsceneData is actually stored within the actor itself). EntranceCutscenes like the various short demos that play the first time you enter a new area also do this. Lastly and most weird, there are a lot of cutscenes associated with a scene layer that are actually started by e.g. an actor instead. An example of this is the Learning Serenade cutscene that plays at the end of Ice Cavern; that cutscene is linked to sceneLayer 4 of Ice Cavern, but the layer itself is unused and incomplete.
💡! that totally checks out. I hadn't thought of it in terms of binary, thanks! Edit: I didn't see mzx's comment when I posted this, but it was incredibly useful for episode 5. Thanks!
@benvillalobos I would guess that the index value is used as an offset for a specific range of ROM addresses where cutscene entry points are stored, or something similar to that. It's quite probable that they didn't know how many in-game cutscenes there would be until very late in development, so they may have just decided early to reserve the final 16 addresses for the demo scenes, leaving the first 65,520 available for in-game scenes (which i'm sure they didn't use fully). Plus, like jojodi mentioned, it makes them easy to reference in code, because you only really have to think about the final digit. Of course, I could be completely incorrect about this particular usage :D but if nothing else, maybe this will help provide a new perspective when making sense of magic numbers in the decomp
I think in the end, we can think of lots of good reasons for some of this stuff, and some of them are probably right and some are wrong. In the end, a bunch of this stuff might just be someone leaning over and saying to the person next to them "Hey we probably never need more than 16 demo cutscenes right?" which the person replies "yeah I'm sure that's enough". "Cool, I'll just reserve 16 at the end", and now 30 years later we're trying to read tea leaves on why certain things are the way they are :p Glad to see everyone chime in on these videos.
@@jojodi I don't know if you read my comment (it is hidden from me, but I am getting notifications for this thread), so I might be repeating myself on some things here. In the original source, the developers use the word "demo" to mean "cutscene" and not short for demonstration or something similar, so saying "demo cutscene" doesn't mean much. For example, z_demo and z_onepointdemo deal with two different cutscene systems. Cutscenes that function by assigning cutsceneIndex or nextCutsceneIndex to a value of 0xFFF0+ are called script based cutscenes in decomp. cutsceneIndex values 0xFFF0 to 0xFFFC are reserved for script based cutscenes associated with what's called a sceneLayer. It's an optional feature of the scene format, where you can define alternate loadouts of actors and other things for the scene. When utilized, layers 0-3 are typically reserved for Child Day, Child Night, Adult Day, Adult Night loadouts with some fallthrough mechanics such that you don't have to implement data for all 4 layers combos if you don't need them, then if layers 4+ exist they will have a command that references a cutscene associated with that layer such that 0xFFF0 is cutscene 0, layer 4, 0xFFF1 is cutscene 1, layer 5 and so on. These cutscenes run immediately on scene load. cutsceneIndex value 0xFFFD is reserved for playing the rest of the script based cutscenes not associated with a sceneLayer or even a scene at all, and can be run at any time. There are actually quite a few sceneLayers that go unused, because the cutscene they reference is instead started by some actor, like the cutscene where Sheik teaches Serenade. The only other value with meaning other than 0 is that the nextCutsceneIndex uses 0xFFEF as it's default value. This variable is simply copied over to cutsceneIndex on scene load if it's not the default.
aaaand that's the last of the month-old videos! A note for the folks following along:
Episode 1 was posted a month after recording, right around the time I recorded this one. After seeing how useful the comments were, I decided not to record another until I posted everything I had. So here we are! Going forward, I hope to post these soon after recording to make use of the feedback I've been lucky enough to get in the comment section.
Massive thanks to everyone who’s been watching and giving feedback-your comments have been incredibly valuable and will now begin informing the next video.
Stay tuned for more code spelunking :)
can you also add song names when they change in the corner or something?
I looked into this and found a "now playing" OBS plugin. It won't help for "compilation" style music (the SNES compilations) since it can't break down each song within a single video, but should work better once I get a playlist going.
Thanks for the suggestion!
crazy how everything is hardcoded, sounds like hell to develop
Honestly pretty readable
the cutscene script is arguably some of the cleanest stuff in the codebase. The only caveat is the cutscene script data itself. The actual implementation of cutscene scripts seem to construct all commands and related data using about 3 or 4 common struct types. There will be cases where a command will use some but not all of the common struct's properties, and garbage data ends up in the unused properties (probably because their tooling never fully initialized the struct).
@@mzxrules yes I was referring to the data. I could never imagine doing that today, i only use resources files with JSON or similar unless it's just a few camera movements for one scene.
@@MrBrax you can't imagine using a compiler? or any editing tool that outputs a binary file? like blender?
@@thewhitefalcon8539 writing camera positions in code instead of using external files is madness to me, using binary for that stuff is even worse
22:00 I can't stress the importance for people who work especially with code and monotonous tasks, to take mandatory breaks.. Something I do whenever I want to take a break from my code and sort of 'structure' my thoughts, is to draw the 'flow'. When, how and why something happens, yeah UML diagrams are ok but, i'm mostly just referring to a mind map, to sort out and structure the layout of what's in our head down onto paper.
Lastly, good job!
Agreed!! I love the idea of keeping a mind map. Mermaid does support it 🤔
12:00 Actually, switch cases are not necessarily faster than if/else chains. First, a modern compiler may be able to determine that a chain of if/else statements is testing a non-volatile variable against a series of constants, and generate code as equally fast as a switch statement. Second, depending on how the case statements and code blocks are clustered, the compiler might generate machine code the same as if it were written with an equivalent if/else chain
34:16 oh man, ACTOR_CUEs. So in a lot of cutscene scripts, you'll have a CS_ACTOR_CUE_LIST which instructs an entity where to go, when to go, and what action to perform. The first argument in CS_ACTOR_CUE_LIST is a CS_CMD_ACTOR_CUE_X_Y, which is actually a script command. Each CS_CMD_ACTOR_CUE_X_Y seems to be associated with some non-player entity that can be controlled by the script. For example, in the different cutscenes depicting Impa and Zelda escaping from Ganondorf, I think CS_CMD_ACTOR_CUE_0_2 is for manipulating Zelda/her horse/Impa, and CS_CMD_ACTOR_CUE_1_1 is for manipulating Ganondorf/his horse.
These CS_CMD_ACTOR_CUE_X_Y commands are currently unnamed because of how hard it has been to trace a command to it's entity. All CS_CMD_ACTOR_CUE_X commands do the same thing, update actorCues[x] to point to the active CS_ACTOR_CUE for that cutsceneFrame (e.g CS_CMD_ACTOR_CUE_0_2 updates actorCues[0]). The actors are responsible for reading their cue "channel" and determining what behavior the cue should actually have.
36:19 that isn't the cutscene you've been looking for :)
EntranceCutscenes are a set of cutscenes that are intended to fire usually the first time the player spawns at that entrance (the main exception are the cutscenes for Epona hopping from Lon Lon into Hyrule Field). gHyruleFieldIntroCs is actually the cutscene that plays the first time you leave the Lost Woods and enter Hyrule Field for the very first time. CS_MISC_SHOW_TITLE_CARD is simply displaying the "Hyrule Field" title card on screen.
So true about the switch case statements. The compiler might be able to optimize sequential cases (1,2,3), but if there are large gaps (1,2, 8000, 120), the compiler needs to generate if statements to check if our value can be switched into one of the listed cases. The usual optimization is using a jump-table (sometimes an array) that has a quick look up time. But even then, the compiler will throw some if statements out front to check if our case is in the bounds of our cases.
In the case of GC/Wii's MWCC compiler, if there are few cases or large gaps, it will generate a binary search of comparison statements to find where to jump to. Still faster than a bunch of if/elses.
That being said, in the case where we have like 100 cutscenes based on an enum with sequential values, I think it was the right decision to use a switch case.
Super interesting. Please continue!
Congrats on 1k subs!
Hey thanks a lot! 😁
this series is amazing please continue
Keep these series coming 👌🏼😍
this series is so cool!! love seeing people explore c code and such
Love this game so much and learning more about complex C at the same time!
Great content! Just imagine if you built a channel with hundreds of videos like this over the next 5-10 years, especially as more N64 games are decompiled, like Banjo and Mario Kart 64 recently. It could become a valuable resource alongside channels like MVG and Glenn Plant. There are also classic PC games with available source code, like Command & Conquer , Quake 3 and Caesar 3. Consider using an old PS/2 keyboard and CRT screen for your room setup, along with a new name and logo to enhance the channel's aesthetics. Although those don't really matter, the content makes the name. You’ve got a solid foundation to create something special and a fantastic hobby to build on that you look like you enjoy!
I've worked on a lot of tools for OoT's engine as well as a considerable amount of low-level hacking stuff for the game. If you have questions you haven't been able to figure out or just want to sit down for a session where someone fills in some of the blanks for you, feel free to reach out!
Thanks for the generous offer, I may take you up on that!
@@benvillalobos Sure! I think you've done a pretty good job so far understanding the codebase. It's just that a lot of the documentation of the game engine is a lot older than the decomp, and hosted elsewhere. So folks working with it generally either already know it or know where to look to remind themselves, or don't know the info exists and figure things out from scratch.
Anyone else watching these with pretty much zero knowledge about programing?
Great Works. Is possible to modify a kind of code qnd show its resullts?
At this rate I'm not far off from changing something small and seeing it reflected in-game. As for meaningful changes (mods, etc.), I'm still a ways off.
You definitely can. When this first came out I spent a few hours messing with this and was able to do things like spawn a bomb when an arrow hits its target and other random little things. The code for spawning and updating things is very simple. Also just updating the number of something, the speed of something, etc. are generally all pretty simple. It sounds like Ben might do some stuff like that soon :)
Wait. The book you mention. Do you by any chance mean the one by Robert Nystrom? With the cute doggo on the back?
Yep! "Game Programming Patterns" I really appreciate his writing style. His book is available online for free as well: gameprogrammingpatterns.com/contents.html.
I don't think there's any magic to the number 65520 or whatever it was. Check z_select's MapSelect_PrintCutsceneSetting . All the demo scenes are 0xFFFy where y is some number of the demo cutscene. That function is probably the best explanation of the values for cutscenes. Why they chose the top 12 bits all need to be set to designate all the demo scenes... no idea! Maybe it' would just be obvious in a debugger when you see 0xFFF_ that it's a demo cutscene
I've known about this for years and I still don't really have a better answer than "they just felt like doing it that way". But the way it works is like this:
gSaveContext.nextCutsceneIndex (u16) defaults to 0xFFEF for no cutscene.
Then you have 0xFFF0-0xFFFC for scene cutscenes 0 through 12. Scene cutscenes are special in that scenes have these things now called sceneLayers which are alternate loadouts of a scene, and sceneLayers 4+ (if defined for a scene) are reserved for those scene cutscenes 0 thru up to 12. These cutscenes play immediately when the scene loads.
Lastly you have 0xFFFD which is used to enable a scripted cutscene after the scene has been loaded and you're in the middle of gameplay. Cutscenes like pulling or dropping the Master Sword do this (and the CutsceneData is actually stored within the actor itself). EntranceCutscenes like the various short demos that play the first time you enter a new area also do this. Lastly and most weird, there are a lot of cutscenes associated with a scene layer that are actually started by e.g. an actor instead. An example of this is the Learning Serenade cutscene that plays at the end of Ice Cavern; that cutscene is linked to sceneLayer 4 of Ice Cavern, but the layer itself is unused and incomplete.
💡! that totally checks out. I hadn't thought of it in terms of binary, thanks!
Edit: I didn't see mzx's comment when I posted this, but it was incredibly useful for episode 5. Thanks!
@benvillalobos
I would guess that the index value is used as an offset for a specific range of ROM addresses where cutscene entry points are stored, or something similar to that.
It's quite probable that they didn't know how many in-game cutscenes there would be until very late in development, so they may have just decided early to reserve the final 16 addresses for the demo scenes, leaving the first 65,520 available for in-game scenes (which i'm sure they didn't use fully).
Plus, like jojodi mentioned, it makes them easy to reference in code, because you only really have to think about the final digit.
Of course, I could be completely incorrect about this particular usage :D but if nothing else, maybe this will help provide a new perspective when making sense of magic numbers in the decomp
I think in the end, we can think of lots of good reasons for some of this stuff, and some of them are probably right and some are wrong. In the end, a bunch of this stuff might just be someone leaning over and saying to the person next to them "Hey we probably never need more than 16 demo cutscenes right?" which the person replies "yeah I'm sure that's enough". "Cool, I'll just reserve 16 at the end", and now 30 years later we're trying to read tea leaves on why certain things are the way they are :p Glad to see everyone chime in on these videos.
@@jojodi I don't know if you read my comment (it is hidden from me, but I am getting notifications for this thread), so I might be repeating myself on some things here.
In the original source, the developers use the word "demo" to mean "cutscene" and not short for demonstration or something similar, so saying "demo cutscene" doesn't mean much. For example, z_demo and z_onepointdemo deal with two different cutscene systems. Cutscenes that function by assigning cutsceneIndex or nextCutsceneIndex to a value of 0xFFF0+ are called script based cutscenes in decomp.
cutsceneIndex values 0xFFF0 to 0xFFFC are reserved for script based cutscenes associated with what's called a sceneLayer. It's an optional feature of the scene format, where you can define alternate loadouts of actors and other things for the scene. When utilized, layers 0-3 are typically reserved for Child Day, Child Night, Adult Day, Adult Night loadouts with some fallthrough mechanics such that you don't have to implement data for all 4 layers combos if you don't need them, then if layers 4+ exist they will have a command that references a cutscene associated with that layer such that 0xFFF0 is cutscene 0, layer 4, 0xFFF1 is cutscene 1, layer 5 and so on. These cutscenes run immediately on scene load.
cutsceneIndex value 0xFFFD is reserved for playing the rest of the script based cutscenes not associated with a sceneLayer or even a scene at all, and can be run at any time. There are actually quite a few sceneLayers that go unused, because the cutscene they reference is instead started by some actor, like the cutscene where Sheik teaches Serenade.
The only other value with meaning other than 0 is that the nextCutsceneIndex uses 0xFFEF as it's default value. This variable is simply copied over to cutsceneIndex on scene load if it's not the default.
Hey if you ever need original music for your next project, I'd love to help out!
Sent you an email!
What is the VSCode theme you are running with?
One Dark Pro, I keep coming back to it.