Author Topic: Max HP Bonus hacking guide  (Read 161 times)

inuksuk

  • Earthbound (+15)
  • *
  • Posts: 42
    • View Profile
Max HP Bonus hacking guide
« on: August 04, 2024, 12:05:39 am »
Introduction

This is a guide to adding the +25% HP bonus that appears on Silver Earring to other accessories. It is intended to double as an assembly hacking tutorial. The guide is targeted at modders who find they want to make changes to the game that aren’t already addressed by the excellent modding tools available for Chrono Trigger. Sometimes the only way to make your change will be to modify the game’s code yourself!

The guide assumes the reader can use debugging tools such as bsnes+ to set breakpoints so the game’s code can be inspected. Some background knowledge about SNES assembly programming and SNES architecture is also assumed.

If you’ve already familiarized yourself with the modding tools available for Chrono Trigger and are looking to take your skills to the next level by learning to modify the game’s code yourself, then this is the guide for you!


Investigating

We begin knowing very little about how the game applies Silver Earring’s HP bonus. It seems to be applied in an ad hoc way and not in the systematic way other item data is processed. That makes it difficult for the creators of modding tools to include it. If we want to design a new accessory with an HP bonus, we’ll have to do it ourselves. So, let’s dive in!

There must be sections of RAM devoted to storing information about the PCs, but where exactly in RAM? Lucky for us, Stephen Geiger, the creator of Temporal Flux, audited the ROM way back in 2007 and that gives us a great starting point (download the Chrono Trigger Database directly from Geiger's website). According to the memory map, each PC has $50 bytes of data starting at $7e2600 in RAM. Crono’s max HP is stored at $7e2605. This is a good place to start when investigating a bonus that grants max HP!

Set a Write breakpoint at $7e2605, Crono’s max HP, and then equip Crono with the Silver Earring.
Nothing happens! Whatever change occurred, it didn’t write Crono’s new max HP where we expected, and the breakpoint wasn’t triggered. Not to worry: the first idea you have isn’t always the right one. It pays to be patient and try different things. It’s maybe not so surprising that the change is happening in another location in memory, since the Equip menu is able to show a stat preview when you’re browsing equipment. The menu code probably works on its own version of Crono’s data.

Let’s instead set a Read breakpoint at $7e2605 and see if Crono’s max HP is being read and used elsewhere. This time we get a hit:

Code: [Select]
Address Instruction Comment

c2/8853 TAX ; Transfer source address to X.
c2/8854 LDY #$9a90 ; Load destination address to Y.
c2/8857 LDA #$004f ; Repeat $4f times.
c2/885a MVN $7e, $7e ; Copy a block of memory. <--- Break

The breakpoint triggered while copying Crono’s $50 bytes of data from $7e2600 to $7e9a90. That means his max HP is also being stored at $7e9a95. If the menu is using its own version of Crono’s data at $9a90, let’s find out more about that. Set a Read breakpoint on Crono’s max HP at $7e9a95 and try to equip the Silver Earring. We repeatedly hit the same two breakpoints once per frame:

Code: [Select]
Address Instruction Comment

c2/9297 LDX $9aba ; Load a memory address to X.
c2/929a LDA $9a95 ; Load max HP. <--- Break
c2/929d CPX #$a1 ; If X’s value is $a1,
c2/929f BEQ $08 ; then branch ahead 8 bytes.
c2/92a1 CPX #$a0 ; If X’s value is $a0,
c2/92a3 BEQ $03 ; then branch ahead 3 bytes.
c2/92a5 LDA #$0000 ; Load zero.
c2/92a8 LSR A ; /2
c2/92a9 LSR A ; /2
c2/92aa CLC
c2/92ab ADC $9a95 ; Add accumulator to max HP. <--- Break
c2/92ae CMP #$03e7 ; If new max HP < 999,
c2/92b1 BCC $03 ; then branch ahead 3 bytes.
c2/92b3 LDA #$03e7 ; Load 999.
c2/92b6 STA $9b25 ; Store new max HP.

This code is very interesting! It’s not immediately clear what the value in X represents, but the context tells us something is being added to Crono’s max HP. That’s what we want to see, since we’re trying to figure out how Silver Earring’s HP bonus is applied! Just by following the flow of the code, we can deduce that a bonus of 0, (maxHP / 2), or (maxHP / 4) is being added to Crono’s normal max HP. That sounds a lot like Silver Earring and Gold Earring, which provide 25% and 50% bonuses.

We can confirm our hunch by investigating how X is used in more detail. We have two questions to answer: 1) What does the data loaded from $9aba represent? 2) What do the values $a1 and $a0 represent? It’s very likely $a1 and $a0 represent the Silver and Gold Earrings. With Mauron’s Itemizer plugin for Temporal Flux we can verify those are in fact the item IDs for those two accessories. That means $9aba should be where Crono’s accessory ID is being stored. We can confirm by observing $7e9aba is within $50 bytes of $7e9a90, the address where Crono’s data was copied to. Careful counting tells us $9aba was copied from $7e262a, which Geiger’s RAM map confirms is the address of Crono’s equipped accessory. 

There might still be more to the story, but this seems to be exactly the code we were looking for! Crono’s accessory ID is read, and depending on the result either 0%, 25%, or 50% is added to his max HP. So now we’ve found the code that adjusts Crono’s max HP when the Silver or Gold Earring are equipped. How would we get that HP bonus on another accessory?


Hacking

The Muscle Ring is a humble accessory, granting a measly 6 Stamina. Why don’t we add an HP bonus on there to liven it up? Now that we’ve located the right code, it’s just a matter of writing and inserting new instructions. The most obvious strategy is to add another comparison to check for Muscle Ring (ID number $b4) and branch as desired:

Code: [Select]
Address Instruction Comment

c2/9297 LDX $9aba ; Load accessory ID.
c2/929a LDA $9a95 ; Load max HP.
c2/929d CPX #$a1 ; If Gold Earring is equipped,
c2/929f BEQ $0c ; then branch ahead 12 bytes.
c2/92a1 CPX #$a0 ; If Silver Earring is equipped,
c2/92a3 BEQ $07 ; then branch ahead 7 bytes.
c2/92a5 CPX #$b4 ; If Muscle Ring is equipped, <--- New code
c2/92a7 BEQ $03 ; then branch ahead 3 bytes.
c2/92a9 LDA #$0000 ; Load zero.
c2/92ac LSR A ; /2
c2/92ad LSR A ; /2
c2/92ae CLC
c2/92af ADC $9a95 ; Add accumulator to max HP.

This solution won’t work, however. Adding new instructions to detect the Muscle Ring takes 4 bytes of assembly code. Jamming in some new instructions means you’re going to write over the instructions already there in the ROM. That can cause a lot of problems. Writing new instructions in place can be desirable, but it doesn’t look like a good solution for this situation. We will instead jump to another section of the ROM and add our instructions there, then return and pick up where we left off.

There’s several hundred bytes of unused space at file address $027de4 according to Geiger’s NA offsets. That’s the same bank we’re working in, so let’s go ahead and jump there with a JSR. We’ll also add some filler instructions as padding in the original routine to ensure a smooth logical flow to the program.

Code: [Select]
Address Instruction Comment

c2/9297 LDX $9aba ; Load accessory ID.
c2/929a LDA $9a95 ; Load max HP.
c2/929d JSR $7de4 ; Jump to subroutine in free space.
c2/92a0 BRA $08 ; Branch ahead 8 bytes.
c2/92a2 NOP
c2/92a3 NOP
c2/92a4 NOP
c2/92a5 NOP
c2/92a6 NOP
c2/92a7 NOP
c2/92a8 NOP
c2/92a9 NOP ; No operation x8.
c2/92aa CLC
c2/92ab ADC $9a95 ; Add accumulator to max HP.

[…]

c2/7de4 CPX #$a1 ; If Gold Earring is equipped,
c2/7de6 BEQ $0c ; then branch ahead 12 bytes.
c2/7de8 CPX #$a0 ; If Silver Earring is equipped,
c2/7dea BEQ $07 ; then branch ahead 7 bytes.
c2/7dec CPX #$b4 ; If Muscle Ring is equipped,
c2/7dee BEQ $03 ; then branch ahead 3 bytes.
c2/7df0 LDA #$0000 ; Load zero.
c2/7df3 LSR A ; /2
c2/7df4 LSR A ; /2
c2/7df5 RTS ; Return.

This solution is very similar to the last one, but now we’ve moved instructions to free space so that there’s room to check if Muscle Ring is equipped. The original routine now has a branch instruction  for a seamless flow back into the original code. Notice that the CLC instruction is at address $c292aa as in the original code.

N.B.: If you’re claiming free space in the ROM like this for your project and you’re using Temporal Flux, be sure to add it in the Window > Custom Data menu. Temporal Flux will otherwise use free space until it’s all filled up and cause conflicts.
An alternative would be to find a section marked “junk” to jump to, since Temporal Flux won’t use that space.

This code works perfectly for the menu screen. Unfortunately, the change isn’t yet fully functional. As it turns out, this change only applies to the menu screen! In battle, Crono doesn’t get the bonus max HP.


Hacking Pt. 2

Let's use the debugger to investigate how accessories are used in battle.

Equip Crono with the Muscle Ring, make sure he’s leading the party, then set a Read breakpoint on Crono’s accessory in battle at address $7e5e57 (thanks to Geiger's offsets) and start a battle. The first breakpoint is checking whether the Wallet is equipped, so click Run to see what else we hit. It’s not immediately clear what the second breakpoint is, but it appears to be a loop that reads the item data for each piece of equipment. This isn’t what we want either. The third breakpoint is testing whether the accessory is Silver Earring, though! This is the code we're looking for.

Code: [Select]
Address Instructions Comment
fd/b3aa LDA $5e57, X [7e5e57] ; Load equipped accessory ID. <--- Break
fd/b3ad CMP #$a0 ; If Silver Earring is not equipped,
fd/b3af BNE $b3c9 ; Branch ahead to test Gold Earring.
fd/b3b1 REP #$20 ; 16-bit mode A.
fd/b3b3 LDA $5e32, X [7e5e32] ; Load max HP.
fd/b3b6 LSR A ; /2
fd/b3b7 LSR A ; /2
fd/b3b8 CLC
fd/b3b9 ADC $5e32, X [7e5e32] ; Add accumulator to max HP.
fd/b3bc CMP #$03e7
fd/b3bf BCC $b3c4
fd/b3c1 LDA #$03e7 ; 999 ceiling.
fd/b3c4 STA $5e32, X [7e5e32] ; Store new max HP.
fd/b3c7 BRA $b3e7 ; Branch to cleanup.
fd/b3c9 SEP #$20 ; 8-bit mode A.
fd/b3cb LDA 5e57, X [7e5e57] ; Load equipped accessory ID. <--- Break
fd/b3ce CMP #$a1 ; If Gold Earring is not equipped,
fd/b3d0 BNE $b3e7 ; Branch to cleanup.
[…]
[Gold Earring section omitted.]
fd/b3e7 TDC
fd/b3e8 SEP #$20
fd/b3ea RTL ; Return.

(These breakpoints are followed by code to detect the Berserker and then the Sightscope.)

The pattern of this code should look familiar by now. We’ll add a check for the Muscle Ring and apply the appropriate bonus to Crono’s max HP. I omitted the section of the code that adds Gold Earring’s HP bonus for brevity, but the calculation is written out entirely rather than branching to the first or second LSR like in the code from the menu we looked at.
We can take advantage of the repetition in this code to add handling for Muscle Ring without having to find free space elsewhere in the ROM. We’ll use the same strategy found in the menu code and condense the routine. Editing routines in place like this is often convenient, since you don’t need to worry about conflicts with other modifications quite as much.

The X register is being used as an index to read the PC data here, so we’ll load the accessory ID into the Y register.

Code: [Select]
Address Instruction Comment
fd/b3aa LDA $5e57, X ; Load accessory ID.
fd/b3ad TAY ; Transfer to Y.
fd/b3ae REP #$20 ; 16-bit mode A.
fd/b3b0 LDA $5e32, X ; Load max HP.
fd/b3b3 CPY #$00a1 ; If Gold Earring is equipped,
fd/b3b6 BEQ $0d ; then branch ahead 14 bytes.
fd/b3b8 CPY #$00a0 ; If Silver Earring is equipped,
fd/b3bb BEQ $08 ; then branch ahead 08 bytes.
fd/b3bd CPY #$00b4 ; If Muscle Ring is equipped,
fd/b3c0 BEQ $03 ; then branch ahead 03 bytes.
fd/b3c2 LDA #$0000 ; Load zero.
fd/b3c5 LSR A ; /2
fd/b3c6 LSR A ; /2
fd/b3c7 CLC
fd/b3c8 ADC $5e32, X ; Add to max HP.
fd/b3cb CMP #$03e7
fd/b3ce BCC $03
fd/b3d0 LDA #$0e37 ; 999 ceiling.
fd/b3d3 STA $5e32, X ; Store max HP.
fd/b3d6 TDC
fd/b3d7 SEP #$20
fd/b3d9 RTL ; Return.

We managed to add some logic to detect Muscle Ring while shortening the overall routine, which now returns at address $b3d9. That’s not much new room to work with, since the old routine finished at $b3ea, but at least we know room to accommodate more accessories if we ever want to come back and add the max HP bonus to more items. I’ll typically write $FF to any space I free up, and make a note of what addresses were affected by my edit.

Well, that should do it! We’ve now written code to handle the max HP bonus in the menu preview and in the actual battle data.

I highly recommend the use of an assembler such as asar to inject your code. Its use is beyond the scope of this guide, however. With some elbow grease and determination, you can instead convert your assembly to bytecode and enter it manually with a hex editor. That way is very slow and error prone, but it’s how I started out—which is why I recommend asar.


Loose Ends

Those two changes we made to the code make for a fully functional Muscle Ring with an HP bonus, but there is one subtlety that we haven't addressed. I didn't realize until a player pointed it out to me, but events that restore the party’s HP, such as staying at an inn or using the shining star at the End of Time, don’t respect the Muscle Ring’s HP bonus.

Some things you can only learn by doing, so if you’d like to apply some of this lesson, try to tie up that loose end yourself and fix the bug with HP restore events yourself. It's a good exercise to apply some of what this guide taught.

Think about what the code must be doing to restore the party's HP and try to set a breakpoint that will allow you to inspect that code. You'll be looking for code that reads a PC’s accessory and HP, and compares the accessory to $a0 and $a1 to detect the Silver and Gold Earrings.

I’ve attached a .txt file to this post that can be used with asar to apply all the modifications to the Muscle Belt discussed in this guide, including the fix for the restore point. If you’d like to inspect it or modify the code to your own ends, just open it in an text editor. I recommend Notepad++, which can readily support asar.


Conclusion

Assembly hacking can take a lot of determination, but there’s virtually no limit to what you can accomplish once you’ve added all these tools to your belt. There are excellent modding tools already available for Chrono Trigger, but there’s nothing quite like digging through the game’s systems and making changes to add your own personal touches.

There’s a lot to learn, but there’s also lots of information out there you can learn from. Start small and if you get stuck, take a break, ask for help, and try again with a fresh perspective!

« Last Edit: August 04, 2024, 12:11:50 am by inuksuk »

Mauron

  • Guru of Reason Emeritus
  • Errare Explorer (+1500)
  • *
  • Posts: 1767
  • Nu-chan
    • View Profile
    • Maurtopia
Re: Max HP Bonus hacking guide
« Reply #1 on: August 11, 2024, 02:42:12 pm »
Excellent tutorial. For battle hacking, I recommend looking at the Battle Data Format. It uses the 0x80 data shared between PCs and enemies in battle.

(?) are likely based on data, but not checked in game. Unused are confirmed as not checked, and unknowns post 0x50 are likely unused, but not confirmed.