theballaam96

Decoupling Moves

Jan 13th, 2022 (edited)
398
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.50 KB | None | 0 0
  1. Part I - How it is in Vanilla DK64
  2. In DK64, the move byte is progressive instead of a bitfield. Lets take DK's moves for example. The byte will be some value between 0 and 3:
  3. 0 - No moves
  4. 1 - Has Baboon Blast
  5. 2 - Has Baboon Blast & Strong Kong
  6. 3 - Has All Moves
  7. This causes a problem for ROM Hacks because changing the sequence that moves are provided would, at best, be a *VERY* hacky fix. Ideally, we want to change this to a bitfield:
  8. 0000 0001 - Baboon Blast
  9. 0000 0010 - Strong Kong
  10. 0000 0100 - Gorilla Grab
  11. With that, you could, for example have Gorilla Grab, but no other moves which isn't possible in vanilla DK64. So, how do we go about hacking that in.
  12.  
  13. Part II - Cranky
  14. First of all, we would have to change how Cranky handles the moves, after all he is the guy who provides the moves to you. There's two sections to this, detecting which moves you have and writing the move.
  15.  
  16. DETECTING THE MOVE
  17. ------------------
  18. Jump_CrankyDecouple:
  19. J CrankyDecouple
  20. NOP
  21.  
  22. PatchCrankyCode:
  23. LUI t3, hi(Jump_CrankyDecouple)
  24. LW t3, lo(Jump_CrankyDecouple) (t3)
  25. LUI t4, 0x8002
  26. SW t3, 0x60E0 (t4) // Store Hook
  27. SW r0, 0x60E4 (t4) // Store NOP
  28. JR ra
  29. NOP
  30.  
  31. CrankyDecouple:
  32. BEQ a0, t0, CrankyDecouple_Bitfield
  33. OR v1, a0, r0
  34. BEQZ a0, CrankyDecouple_Bitfield
  35. NOP
  36.  
  37. CrankyDecouple_Progessive:
  38. J 0x800260E8
  39. NOP
  40.  
  41. CrankyDecouple_Bitfield:
  42. J 0x800260F0
  43. NOP
  44.  
  45. What this does is add moves to the same condition check as the weapon and instrument bitfields. PatchCrankyCode can be run at runtime whenever the currentmap is 5 (Cranky).
  46.  
  47. WRITING THE MOVE
  48. ----------------
  49. *(int*)(0x80025E9C) = 0x0C009751; // Set Bitfield Move
  50.  
  51. This replaces the function call to write the move as progressive with the function call to write the move as a bitfield. Likewise, this can be run at runtime whenever the currentmap is 5 (Cranky).
  52.  
  53. Part III - Object Instance Scripts
  54. For objects of model 2, some of them have a mini script which is run whenever the object is loaded. For example, when you load K. Rool's final battle ship in Isles, it runs a bit of code to check whether you have keys 3 & 8. Likewise, there's a function call within the scripting system to check if you have a particular move. Rather than hand modify every script which contains a move check, we can just modify how the scripting system converts it's system to assembler.
  55.  
  56. InstanceScriptHook:
  57. J InstanceScriptCheck
  58. NOP
  59.  
  60. WriteHook:
  61. LUI t3, hi(InstanceScriptHook)
  62. LW t3, lo(InstanceScriptHook) (t3)
  63. LUI t4, 0x8064
  64. SW t3, 0xEE08 (t4) // Store Hook
  65. JR ra
  66. SW r0, 0xEE0C (t4) // Store NOP
  67.  
  68. InstanceScriptCheck:
  69. ADDIU t1, r0, 1
  70. ADDI t4, t4, -1 // Reduce move_index by 1
  71. SLLV t4, t1, t4 // 1 << move_index
  72. ADDIU t1, r0, 0
  73. AND at, t6, t4 // at = kong_moves & move_index
  74. BEQZ at, InstanceScriptCheck_Fail
  75. NOP
  76.  
  77. InstanceScriptCheck_Success:
  78. J 0x8063EE14
  79. NOP
  80.  
  81. InstanceScriptCheck_Fail:
  82. J 0x8063EE1C
  83. NOP
  84.  
  85. What this does is replace:
  86. if (a <= b) {
  87. With:
  88. if (b & (1 << (a - 1))) {
  89.  
  90. WriteHook can be run during runtime, but it must be run during boot so that the game can be run if dynamically recompiled.
  91.  
  92. Part IV - The Savefile
  93. Donkey Kong 64 has a certain amount of space in EEPROM allocated to each file. Because DK64 stores a tonne of data in the savefile, everything is very tightly packed in terms of the size. Moves only have 2 bits allocated to them in the savefile, which wouldn't be enough for the decoupling system, since this requires 3 bits.
  94. Thankfully, there's an unused variable that is stored inside the savefile which is 3 bits in size, which we can cause the game to write and read from that slot instead.
  95.  
  96. SaveToFileFixesHook:
  97. J SaveToFileFixes
  98. NOP
  99.  
  100. WriteHook:
  101. LUI t3, hi(SaveToFileFixesHook)
  102. LW t3, lo(SaveToFileFixesHook) (t3)
  103. LUI t4, 0x8061
  104. SW t3, 0xDFF4 (t4) // Store Hook
  105. JR ra
  106. SW r0, 0xDFF8 (t4) // Store NOP
  107.  
  108. SaveToFileFixes:
  109. BNEZ s0, SaveToFileFixes_Not0
  110. ANDI a1, s3, 0xFF
  111. B SaveToFileFixes_Finish
  112. ADDIU a0, r0, 10 // Stores it in unused slot
  113.  
  114. SaveToFileFixes_Not0:
  115. ADDIU a0, s0, 4
  116.  
  117. SaveToFileFixes_Finish:
  118. J 0x8060DFFC
  119. NOP
  120.  
  121. *(int*)(0x8060E04C) = 0; // Prevent moves overwrite
  122. *(short*)(0x8060DDAA) = 0; // Writes readfile data to moves
  123.  
  124. The ASM portion of that code changes the save-to-file function so that moves are instead saved to the unused slot. The first line of C prevents another part of the code overwriting that part in the save file slot.
  125. The second line of C changes the readfile function so that the unused slot writes to the moves section
  126. WriteHook and the two lines of C code can be run during runtime, but it must be run during boot so the game can be run if dynamically recompiled.
  127.  
  128. Part V - Unique Cases
  129. There are a few other cases where the game checks the moves byte which we need to amend to now take into account that it is a bitfield
  130.  
  131. CRYSTALS CHECK
  132. --------------
  133. BarrelMovesFixesHook:
  134. J BarrelMovesFixes
  135. NOP
  136. WriteHook:
  137. LUI t3, hi(BarrelMovesFixesHook)
  138. LW t3, lo(BarrelMovesFixesHook) (t3)
  139. LUI t4, 0x806F
  140. SW t3, 0x6EA0 (t4) // Store Hook
  141. JR ra
  142. SW r0, 0x6EA4 (t4) // Store NOP
  143.  
  144. BarrelMovesFixes:
  145. LBU t0, 0x4238 (t0) // Load Barrel Moves Array Slot
  146. ADDI t0, t0, -1 // Reduce move_value by 1
  147. ADDIU v1, r0, 1
  148. SLLV t0, v1, t0 // Get bitfield value
  149. AND v1, t0, t9
  150. BEQZ v1, BarrelMovesFixes_Finish
  151. NOP
  152. ADDIU v1, r0, 1
  153.  
  154. BarrelMovesFixes_Finish:
  155. J 0x806F6EB4
  156. NOP
  157.  
  158. The game checks each of the kong's move byte and compares it against an array which contains the move index for each of the barrel moves the kong learns (eg. Strong Kong is the 2nd move, so the game is checking DK's move byte against 2 to see whether it is equal to or greater than 2).
  159. As such, we have to change that line from:
  160. if (a <= b) {
  161. To:
  162. if (b & (1 << (a - 1))) {
  163.  
  164. CHIMPY CHARGE
  165. ---------------
  166. ChimpyChargeFixHook:
  167. J ChimpyChargeFix
  168. NOP
  169.  
  170. WriteHook:
  171. LUI t3, hi(ChimpyChargeFixHook)
  172. LW t3, lo(ChimpyChargeFixHook) (t3)
  173. LUI t4, 0x806E
  174. SW t3, 0x4930 (t4) // Store Hook
  175. JR ra
  176. SW r0, 0x4934 (t4) // Store NOP
  177.  
  178. ChimpyChargeFix:
  179. ANDI t6, t6, 1
  180. LUI v1, 0x8080
  181. J 0x806E4938
  182. ADDIU v1, v1, 0xBB40
  183.  
  184. What this is doing is just adding a check to see that the lowest bit (& 1) is set rather than the whole byte being non-zero
  185.  
  186. ORANGSTAND
  187. ----------
  188. OStandFixHook:
  189. J OStandFix
  190. NOP
  191.  
  192. WriteHook:
  193. LUI t3, hi(OStandFixHook)
  194. LW t3, lo(OStandFixHook) (t3)
  195. LUI t4, 0x806E
  196. SW t3, 0x48AC (t4) // Store Hook
  197. JR ra
  198. SW r0, 0x48B0 (t4) // Store NOP
  199.  
  200. OStandFix:
  201. LBU t2, 0xCA0C (t2)
  202. ANDI t2, t2, 1
  203. J 0x806E48B4
  204. ADDIU a0, r0, 0x25
  205.  
  206. See "Chimpy Charge" for explanation
  207.  
  208. PONY-TAIL TWIRL
  209. -----------------
  210. *(int*)(0x806E20F0) = 0x31010002; // ANDI $at $t0 2
  211. *(int*)(0x806E20F4) = 0x5020000F; // BEQL $at $r0 0xF
  212.  
  213. PRIMATE PUNCH
  214. -------------
  215. *(int*)(0x806E48F4) = 0x31810002; // ANDI $at $t4 2
  216. *(int*)(0x806E48F8) = 0x50200074; // BEQL $at $r0 0xF
  217.  
  218. BARREL MOVES
  219. ------------
  220. // Strong Kong
  221. *(int*)(0x8067ECFC) = 0x30810002; // ANDI $at $a0 2
  222. *(int*)(0x8067ED00) = 0x50200003; // BEQL $at $r0 3
  223. // Rocketbarrel
  224. *(int*)(0x80682024) = 0x31810002; // ANDI $at $t4 2
  225. *(int*)(0x80682028) = 0x50200006; // BEQL $at $r0 0x6
  226. // OSprint
  227. *(int*)(0x8067ECE0) = 0x30810004; // ANDI $at $a0 4
  228. *(int*)(0x8067ECE4) = 0x10200002; // BEQZ $at, 2
  229. // Mini Monkey
  230. *(int*)(0x8067EC80) = 0x30830001; // ANDI $v1 $a0 1
  231. *(int*)(0x8067EC84) = 0x18600002; // BLEZ $v1 2
  232. // Hunky Chunky (Not Dogadon)
  233. *(int*)(0x8067ECA0) = 0x30810001; // ANDI $at $a0 1
  234. *(int*)(0x8067ECA4) = 0x18200002; // BLEZ $at 2
  235.  
  236. DOGADON 2 HUNKY CHUNKY BARREL
  237. -----------------------------
  238. For some bizarre reason (probably to prevent entry into the barrel without the move), the actor for the Hunky Chunky barrel in Dogadon 2 is a different actor to a regular Hunky Chunky barrel. As such, we have to have a little extra code to account for this edge case.
  239.  
  240. HunkyChunkyFix2Hook:
  241. J HunkyChunkyFix2
  242. NOP
  243.  
  244. WriteHook:
  245. LUI t3, hi(HunkyChunkyFix2Hook)
  246. LW t3, lo(HunkyChunkyFix2Hook) (t3)
  247. LUI t4, 0x8068
  248. SW t3, 0xECB8 (t4) // Store Hook
  249. JR ra
  250. SW r0, 0xECBC (t4) // Store NOP
  251.  
  252. HunkyChunkyFix2:
  253. BNEL v1, at, HunkyChunkyFix2_Finish
  254. LI at, 4
  255. ANDI a2, a0, 1
  256. BLEZL a2, HunkyChunkyFix2_Finish
  257. LI at, 4
  258. J 0x8067ECC8
  259. NOP
  260.  
  261. HunkyChunkyFix2_Finish:
  262. J 0x8067ECD0
  263. NOP
  264.  
  265. MAIN MENU
  266. ---------
  267. .definelabel MainMenuMoves, 0x80033938
  268. extern main_menu_moves_struct MainMenuMoves[8];
  269.  
  270. typedef struct main_menu_moves_struct {
  271. /* 0x000 */ short map;
  272. /* 0x002 */ short kong;
  273. /* 0x004 */ char moves;
  274. /* 0x005 */ char slam_level;
  275. /* 0x006 */ char instrument;
  276. /* 0x007 */ char melons;
  277. } main_menu_moves_struct;
  278.  
  279. static const char moves_values[] = {1,1,3,1,7,1,1,7};
  280.  
  281. *(short*)(0x8002E266) = 7; // Enguarde Arena Movement Write
  282. *(short*)(0x8002F01E) = 7; // Rambi Arena Movement Write
  283. for (int i = 0; i < 8; i++) {
  284. MainMenuMoves[i].moves = moves_values[i];
  285. }
  286.  
  287. The moves you get in the boss fights are sourced from an array. As such, we need to amend this array so that it gives you the correct moves as intended by the game. This should be run during runtime when the currentmap is 0x50 (Main Menu)
  288.  
  289. GIVE_EVERYTHING
  290. ----------------
  291. Finally, there's this function which is unused (probably. It's definitely a remnant of development, and I can't see any references to the function). However, despite it's unused nature, here's a patch just in case:
  292.  
  293. *(short*)(0x806C9CDE) = 7; // GiveEverything, write to bitfield. Seems to be unused but might as well
Add Comment
Please, Sign In to add comment