Advertisement
SBDWolf

Castlevania Credits Warp Explanation (2024-05-12)

May 12th, 2024 (edited)
138
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.04 KB | None | 0 0
  1. Video Demonstration referenced: https://youtu.be/3FD9O2sxPqM
  2. Inputs file (for Bizhawk 2.9.1): https://drive.google.com/file/d/1Xn8EyUuJABDqMr3uiXg0mOjsra3v6ftX/view?usp=sharing
  3.  
  4. It's May 12th, 2024, and I (SBDWolf) have just discovered a Credits Warp setup for the Famicom Disk System 1.0 version of Castlevania. Here is an explanation of how it works.
  5.  
  6. There are several Glitch Rooms accessible in Castlevania. Most of them are accessible thanks to the use of a scroll glitch on the top or the bottom of a staircase, allowing Simon to transition to invalid substages. For example, scroll glitching the blocks on top of any staircase in Stage 01 and then climbing it would bring Simon to a "Stage 01, substage 02", which makes the game just read garbage as a screen data.
  7. It has also been observed that some of these screens would make the game crash, or sometimes do weird effects.
  8. The reason for these effects seems to be mostly, if not exclusively, due to the garbage screen data getting read containing some invalid Enemy Spawner types. The code responsible for their logic at runtime has no error checking and will just blindly look up a pointer from a lookup table corresponding to the spawner index it has to process, and execute code at that pointer. This means that all sorts of unintended code will run whenever there's an invalid spawner type in a room.
  9.  
  10. The simplest way to exploit this vulnerability would be to find a spawner type that ends up getting a pointer to RAM, and then manipulate the contents of RAM to execute arbitrary code (ACE). I have looked through all of the accessible Glitch Rooms in all the cartridge releases, and I couldn't find a single screen that had a spawner of that type (however my process wasn't super rigorous, so it is possible I missed something). I did find that Stage 18, substage 02, had a spawner that moved execution to RAM $00A9, however that screen isn't accessible without hacking to my knowledge.
  11.  
  12. The screen where I ended up finding a spawner of that kind, however, was a screen that was known about and accessible well before the scroll glitch was discovered. At the end of Stage 10, for reasons that to this day I'm not totally sure about, if you use a bat to boost up to the exit platform and then touch the stage exit, you will end up in "Stage 11, substage 255".
  13.  
  14. This screen contains spawner index 0x59 (which gets truncated to 0x19 by an AND #$3F), which will execute code from ROM corresponding to the routine that executes code for objects currently on screen in a similar fashion to that of spawners, by just (almost) blindly taking a pointer corresponding to the object index being processed. However, this routine uses the X register to read the object to process from memory, and the routine has been jumped to with a really high X register (0xFF). Since this routine works its way backwards from the end to the beginning of the object table, it will essentially end up trying to process anything from RAM $0532 to $0434 and read the values in those addresses as object indexes.
  15.  
  16. There seems to be a hard stop at $04EB, where that address will always contain the value 0x01 and will cause a crash. However, anything between $04EC and $0532 that is manipulable can then basically be used to make the code responsible for processing object indexes jump to a series of different pointers, though any values above 0x7F are ignored. The most manipulable addresses in that area are:
  17. - $530: "Simon Attack State". I am not sure what this address is used for exactly, however it seems to change by a multiple of 4 every time simon moves, and if Simon whips in mid-air it changes to a 0x01, setting it back to a 0x00 if the whip finishes animating before he lands.
  18. - $052B-$052E: Seems to be some pixel values related to the moving platform in Stage 10. The biggest range of values is at $052D, which can take a value between 0xC1 and 0x3F.
  19. - $050F-$0512: Subpixel values of the moving platforms in Stage 10. Changes in multiples of 4.
  20.  
  21. I ended up using $0530 as the entry point. By whipping and not finishing the animation before landing, the value will stay at 0x01, and then by moving around a bit (hence the stalling after boosting in the video linked on top) this value can be changed to values of "4n+1". This can allow the value of 0x71 to be written before exiting to Stage 11, which will make the object code take a pointer to $17C9 (which is mirror RAM).
  22. In this RAM location there are a few values here that are related to the PPU buffer area, and they seem to change depending on the exact timing the stage exit is taken. They often either contain instructions that will mess with the stack (bad), or instructions that will hang the CPU (worse), but sometimes they will contain harmless instructions that can be executed without any much trouble.
  23. Now, the interesting part is once execution moves to $17D0. This part of RAM contains data regarding the 3 save files that the FDS version provides. The struct for these save files is the following:
  24.  
  25. xx xx xx xx xx xx xx 00 yy zz
  26. Where:
  27. xx = Name of save file
  28. 00 - Unused? Padding?
  29. yy = Farthest Stage reached
  30. zz = Game Over count
  31.  
  32. Unfortunately, there are two big limitations with the name of the save files. The name has to be filled out completely (meaning, it HAS to be 7 letters long), and more importantly, the character set is limited to the values between $E0 and $F9, excluding the value $F0, plus the values $C2 and $C3. This makes the range of opcodes that can be executed very limited.
  33. However, the Game Over count is pretty versatile on the other hand. There seems to be no cap or even overflow checking: it will go as high as you want it to be. This makes it possible to execute whatever one opcode desired.
  34.  
  35. The objective is to jump to $7952, which is the code that starts the credits sequence. Other addresses that lead there can work too, however there's no way to write that with the limitations present (and neither is that pointer readable in any accessible ROM addresses, it seems).
  36.  
  37. With that in mind, here is the "payload" that I ended up using in the demonstration:
  38.  
  39. E5 F8 E5 F7 E9 E8 E8 00 00 85 = FYF.JII, Stage 00, 133 game overs
  40. F9 F7 F7 F7 F7 F7 F7 00 00 6C = Z......, Stage 00, 108 game overs
  41. E1 F1 F7 F7 F7 F7 F7 00 00 00 = BR....., Stage 00, 0 game overs
  42.  
  43. Or in code:
  44. SBC $F8
  45. SBC $F7
  46. SBC #$E8
  47. INX
  48. BRK
  49. STA $F9
  50. ISC $F7,x
  51. ISC $F7,x
  52. ISC $F7,x
  53. BRK
  54. JMP ($F1E1)
  55.  
  56. $F7 and $F8 correspond to inputs held on controller 1 and controller 2, respectively. For this setup, the inputs "Up, Down, B" are held on controller 1, and "Left, Start, B" are held on controller 2, providing the values 0x4C and 0x52 respectively.
  57.  
  58. When the first file data ends up getting executed, some SBC instructions are performed to manipulate the value in the accumulator to 0x79. Then, an INX just to make the Program Counter line up, and a BRK (which basically does nothing). The first key opcode is STA $F9, which will store 0x79 at $F9. This is going to be part of a pointer for later.
  59. The "ISC $F7"s do basically nothing, and after another BRK, the JMP ($F1E1) ends up reading a pointer of $00EB from ROM, and moving the Program counter to there. Eventually, execution gets to $00F7, which corresponds to the inputs that are being held. With the value of 0x79 that was written to $F9 before, the opcode JMP $7952 gets executed, starting the credits sequence.
  60.  
  61. Obviously, the big downside of this setup is that executing this credits warp requires taking 241 game overs total, or 964 deaths. Almost 2 hours have to be spent accumulating the required game over count.
  62.  
  63. I have spent quite a bit of time trying to find a more convenient setup, however the available RAM areas to jump to are a little limited, and so is the amount of useful instructions that can be written to them. Castlevania doesn't use much RAM at all, and likes to pad out values by a lot. So a lot of the more controllable memory addresses end up actually being quite isolated and not usable as part of any opcode.
  64.  
  65. An idea for improvements could be to try and find a payload that requires only one opcode in the form of game over counts.
  66.  
  67. Jumping to Controller inputs directly and using the "newly pressed inputs" addresses coupled with the "total pressed inputs" won't work due to the same limitations outlines here: https://tasvideos.org/7273S
  68. Basically, using that method, the bits for newly pressed inputs would have to be contained within the total button presses, and unfortunately, 0x4C is not contained within 0x79. There are some other pointers that can be jumped to instead of 0x7952, but they all start with 0x78 or 0x77 and have the same issue.
  69.  
  70. Another idea could be to jump to a different part of memory and somehow write the instruction needed there. An area with good potential could be some kind of object data area. However, most of this data gets cleared upon the screen transition to Stage 11. The area for subpixel values, however, does not get cleared, but this has the limitation of only containing values that are multiples of 4, which could be pretty tricky to make use of.
  71.  
  72. This should cover everything. If anyone has any further questions on this topic, feel free to ask me.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement