Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Part I - How it is in Vanilla DK64
- 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:
- 0 - No moves
- 1 - Has Baboon Blast
- 2 - Has Baboon Blast & Strong Kong
- 3 - Has All Moves
- 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:
- 0000 0001 - Baboon Blast
- 0000 0010 - Strong Kong
- 0000 0100 - Gorilla Grab
- 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.
- Part II - Cranky
- 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.
- DETECTING THE MOVE
- ------------------
- Jump_CrankyDecouple:
- J CrankyDecouple
- NOP
- PatchCrankyCode:
- LUI t3, hi(Jump_CrankyDecouple)
- LW t3, lo(Jump_CrankyDecouple) (t3)
- LUI t4, 0x8002
- SW t3, 0x60E0 (t4) // Store Hook
- SW r0, 0x60E4 (t4) // Store NOP
- JR ra
- NOP
- CrankyDecouple:
- BEQ a0, t0, CrankyDecouple_Bitfield
- OR v1, a0, r0
- BEQZ a0, CrankyDecouple_Bitfield
- NOP
- CrankyDecouple_Progessive:
- J 0x800260E8
- NOP
- CrankyDecouple_Bitfield:
- J 0x800260F0
- NOP
- 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).
- WRITING THE MOVE
- ----------------
- *(int*)(0x80025E9C) = 0x0C009751; // Set Bitfield Move
- 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).
- Part III - Object Instance Scripts
- 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.
- InstanceScriptHook:
- J InstanceScriptCheck
- NOP
- WriteHook:
- LUI t3, hi(InstanceScriptHook)
- LW t3, lo(InstanceScriptHook) (t3)
- LUI t4, 0x8064
- SW t3, 0xEE08 (t4) // Store Hook
- JR ra
- SW r0, 0xEE0C (t4) // Store NOP
- InstanceScriptCheck:
- ADDIU t1, r0, 1
- ADDI t4, t4, -1 // Reduce move_index by 1
- SLLV t4, t1, t4 // 1 << move_index
- ADDIU t1, r0, 0
- AND at, t6, t4 // at = kong_moves & move_index
- BEQZ at, InstanceScriptCheck_Fail
- NOP
- InstanceScriptCheck_Success:
- J 0x8063EE14
- NOP
- InstanceScriptCheck_Fail:
- J 0x8063EE1C
- NOP
- What this does is replace:
- if (a <= b) {
- With:
- if (b & (1 << (a - 1))) {
- WriteHook can be run during runtime, but it must be run during boot so that the game can be run if dynamically recompiled.
- Part IV - The Savefile
- 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.
- 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.
- SaveToFileFixesHook:
- J SaveToFileFixes
- NOP
- WriteHook:
- LUI t3, hi(SaveToFileFixesHook)
- LW t3, lo(SaveToFileFixesHook) (t3)
- LUI t4, 0x8061
- SW t3, 0xDFF4 (t4) // Store Hook
- JR ra
- SW r0, 0xDFF8 (t4) // Store NOP
- SaveToFileFixes:
- BNEZ s0, SaveToFileFixes_Not0
- ANDI a1, s3, 0xFF
- B SaveToFileFixes_Finish
- ADDIU a0, r0, 10 // Stores it in unused slot
- SaveToFileFixes_Not0:
- ADDIU a0, s0, 4
- SaveToFileFixes_Finish:
- J 0x8060DFFC
- NOP
- *(int*)(0x8060E04C) = 0; // Prevent moves overwrite
- *(short*)(0x8060DDAA) = 0; // Writes readfile data to moves
- 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.
- The second line of C changes the readfile function so that the unused slot writes to the moves section
- 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.
- Part V - Unique Cases
- 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
- CRYSTALS CHECK
- --------------
- BarrelMovesFixesHook:
- J BarrelMovesFixes
- NOP
- WriteHook:
- LUI t3, hi(BarrelMovesFixesHook)
- LW t3, lo(BarrelMovesFixesHook) (t3)
- LUI t4, 0x806F
- SW t3, 0x6EA0 (t4) // Store Hook
- JR ra
- SW r0, 0x6EA4 (t4) // Store NOP
- BarrelMovesFixes:
- LBU t0, 0x4238 (t0) // Load Barrel Moves Array Slot
- ADDI t0, t0, -1 // Reduce move_value by 1
- ADDIU v1, r0, 1
- SLLV t0, v1, t0 // Get bitfield value
- AND v1, t0, t9
- BEQZ v1, BarrelMovesFixes_Finish
- NOP
- ADDIU v1, r0, 1
- BarrelMovesFixes_Finish:
- J 0x806F6EB4
- NOP
- 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).
- As such, we have to change that line from:
- if (a <= b) {
- To:
- if (b & (1 << (a - 1))) {
- CHIMPY CHARGE
- ---------------
- ChimpyChargeFixHook:
- J ChimpyChargeFix
- NOP
- WriteHook:
- LUI t3, hi(ChimpyChargeFixHook)
- LW t3, lo(ChimpyChargeFixHook) (t3)
- LUI t4, 0x806E
- SW t3, 0x4930 (t4) // Store Hook
- JR ra
- SW r0, 0x4934 (t4) // Store NOP
- ChimpyChargeFix:
- ANDI t6, t6, 1
- LUI v1, 0x8080
- J 0x806E4938
- ADDIU v1, v1, 0xBB40
- 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
- ORANGSTAND
- ----------
- OStandFixHook:
- J OStandFix
- NOP
- WriteHook:
- LUI t3, hi(OStandFixHook)
- LW t3, lo(OStandFixHook) (t3)
- LUI t4, 0x806E
- SW t3, 0x48AC (t4) // Store Hook
- JR ra
- SW r0, 0x48B0 (t4) // Store NOP
- OStandFix:
- LBU t2, 0xCA0C (t2)
- ANDI t2, t2, 1
- J 0x806E48B4
- ADDIU a0, r0, 0x25
- See "Chimpy Charge" for explanation
- PONY-TAIL TWIRL
- -----------------
- *(int*)(0x806E20F0) = 0x31010002; // ANDI $at $t0 2
- *(int*)(0x806E20F4) = 0x5020000F; // BEQL $at $r0 0xF
- PRIMATE PUNCH
- -------------
- *(int*)(0x806E48F4) = 0x31810002; // ANDI $at $t4 2
- *(int*)(0x806E48F8) = 0x50200074; // BEQL $at $r0 0xF
- BARREL MOVES
- ------------
- // Strong Kong
- *(int*)(0x8067ECFC) = 0x30810002; // ANDI $at $a0 2
- *(int*)(0x8067ED00) = 0x50200003; // BEQL $at $r0 3
- // Rocketbarrel
- *(int*)(0x80682024) = 0x31810002; // ANDI $at $t4 2
- *(int*)(0x80682028) = 0x50200006; // BEQL $at $r0 0x6
- // OSprint
- *(int*)(0x8067ECE0) = 0x30810004; // ANDI $at $a0 4
- *(int*)(0x8067ECE4) = 0x10200002; // BEQZ $at, 2
- // Mini Monkey
- *(int*)(0x8067EC80) = 0x30830001; // ANDI $v1 $a0 1
- *(int*)(0x8067EC84) = 0x18600002; // BLEZ $v1 2
- // Hunky Chunky (Not Dogadon)
- *(int*)(0x8067ECA0) = 0x30810001; // ANDI $at $a0 1
- *(int*)(0x8067ECA4) = 0x18200002; // BLEZ $at 2
- DOGADON 2 HUNKY CHUNKY BARREL
- -----------------------------
- 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.
- HunkyChunkyFix2Hook:
- J HunkyChunkyFix2
- NOP
- WriteHook:
- LUI t3, hi(HunkyChunkyFix2Hook)
- LW t3, lo(HunkyChunkyFix2Hook) (t3)
- LUI t4, 0x8068
- SW t3, 0xECB8 (t4) // Store Hook
- JR ra
- SW r0, 0xECBC (t4) // Store NOP
- HunkyChunkyFix2:
- BNEL v1, at, HunkyChunkyFix2_Finish
- LI at, 4
- ANDI a2, a0, 1
- BLEZL a2, HunkyChunkyFix2_Finish
- LI at, 4
- J 0x8067ECC8
- NOP
- HunkyChunkyFix2_Finish:
- J 0x8067ECD0
- NOP
- MAIN MENU
- ---------
- .definelabel MainMenuMoves, 0x80033938
- extern main_menu_moves_struct MainMenuMoves[8];
- typedef struct main_menu_moves_struct {
- /* 0x000 */ short map;
- /* 0x002 */ short kong;
- /* 0x004 */ char moves;
- /* 0x005 */ char slam_level;
- /* 0x006 */ char instrument;
- /* 0x007 */ char melons;
- } main_menu_moves_struct;
- static const char moves_values[] = {1,1,3,1,7,1,1,7};
- *(short*)(0x8002E266) = 7; // Enguarde Arena Movement Write
- *(short*)(0x8002F01E) = 7; // Rambi Arena Movement Write
- for (int i = 0; i < 8; i++) {
- MainMenuMoves[i].moves = moves_values[i];
- }
- 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)
- GIVE_EVERYTHING
- ----------------
- 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:
- *(short*)(0x806C9CDE) = 7; // GiveEverything, write to bitfield. Seems to be unused but might as well
Add Comment
Please, Sign In to add comment