News: 11 March 2016 - Forum Rules
Current Moderators - DarkSol, KingMike, MathOnNapkins, Azkadellia, Danke

Author Topic: Final Fantasy II Restored  (Read 15506 times)

Leviathan Mist

  • Jr. Member
  • **
  • Posts: 32
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #80 on: January 15, 2020, 05:17:31 pm »
I'm not going to lie, after discovering the HP/MP thing, I wholly assumed this to be the case. I don't think it's reasonable to expect a person to get that much money, but I'd still prefer to cap it anyway. Thank you for confirming this, and I'll add it to the list. Thank you for the kind words, Leviathian. Means a lot to me coming from such an awesome dude behind Final Fantasy hacking. I love your RP2A03 demakes. :)

It's perfectly reasonable to hit the cap, and actually doesn't take much longer than maxing out HP and MP if you know where to grind. I've actually had these fixes on the back burner for some time, but haven't had the time or motivation to dissect the ROM in order to fix them. So for that, I thank you, and will definitely be using these fixes in conjunction with Chaos Rush's improved translation patch for the game.

As for music sequencing, I can compose tracks for FF2 as well, but the issue would be finding space to put the music data, as was the case with FF1.

redmagejoe

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: Final Fantasy II Restored
« Reply #81 on: January 15, 2020, 05:23:58 pm »
Honestly I'd just love to hear even just on YouTube a demake of the Dawn of Souls / Anniversary boss theme. Not the one already in the game which I think is considered the final boss theme in the remakes, but in the original it's used for all boss fights. This new one would probably sound great demade: https://www.youtube.com/watch?v=DtbKkY26iiM

No pressure, just think it would sound amazing. And really, thank Jiggers and abw more than me. Without their interest and resources, I certainly wouldn't have had the motivation to tackle this. I was just obnoxiously persistent and kept harping about it until people actually took an interest. ;D



Fun fact I learned while watching breakpoints for "hit by magic" counters while doing more testing on my patch. It seems even "physical" abilities like Bow used by soldier type enemies qualifies as magic attacks for the counter, meaning that Magic Defense will actually get experience from being hit by Bow attacks. Interesting. I am now using relative searching to see if I can track down where Gil is stored, or at least the lowest byte. I'm assuming that similar to precedent, it will be low byte, middle byte, high byte in order.

Aha, found it already. Was as simple as a RAM search of my Gil's lowest byte, was literally the only 7E in memory. $601C, $601D, and $601E are the low, mid, and high bytes respectively. Let me dig around in the disassembly and see where increases are handled, which will probably be in a number of places... shops, winning battles... Will most likely need to make a sub-routine for all those scenarios to jump to. All relevant places in code will be listed below.

Code: [Select]
; control flow target (from $A4C3)
0x0164C7|$05:$A4B7:BD 62 7B LDA $7B62,X
0x0164CA|$05:$A4BA:20 D3 A9 JSR $A9D3 
0x0164CD|$05:$A4BD:E6 0C    INC $0C   
0x0164CF|$05:$A4BF:A6 0C    LDX $0C   
0x0164D1|$05:$A4C1:E0 10    CPX #$10   
0x0164D3|$05:$A4C3:D0 F2    BNE $A4B7 
0x0164D5|$05:$A4C5:20 64 A4 JSR $A464 
0x0164D8|$05:$A4C8:18      CLC       
0x0164D9|$05:$A4C9:AD C0 7C LDA $7CC0 
0x0164DC|$05:$A4CC:85 62    STA $62   
0x0164DE|$05:$A4CE:6D 1C 60 ADC $601C  ; Gold byte 0
0x0164E1|$05:$A4D1:8D 1C 60 STA $601C  ; Gold byte 0
0x0164E4|$05:$A4D4:AD C1 7C LDA $7CC1 
0x0164E7|$05:$A4D7:85 63    STA $63   
0x0164E9|$05:$A4D9:6D 1D 60 ADC $601D  ; Gold byte 1
0x0164EC|$05:$A4DC:8D 1D 60 STA $601D  ; Gold byte 1
0x0164EF|$05:$A4DF:A9 00    LDA #$00   
0x0164F1|$05:$A4E1:6D 1E 60 ADC $601E  ; Gold byte 2
0x0164F4|$05:$A4E4:8D 1E 60 STA $601E  ; Gold byte 2

Code: [Select]
; data -> code
; external control flow target (from $0F:$EB92)
0x0389C5|$0E:$89B5:AD 1C 60 LDA $601C  ; Gold byte 0
0x0389C8|$0E:$89B8:85 80    STA $80   
0x0389CA|$0E:$89BA:AD 1D 60 LDA $601D  ; Gold byte 1
0x0389CD|$0E:$89BD:85 81    STA $81   
0x0389CF|$0E:$89BF:AD 1E 60 LDA $601E  ; Gold byte 2
0x0389D2|$0E:$89C2:85 82    STA $82   
0x0389D4|$0E:$89C4:4C FE 89 JMP $89FE 

Code: [Select]
; control flow target (from $8C74, $8DAC, $8E25, $8F74)
0x039091|$0E:$9081:AD 1E 60 LDA $601E  ; Gold byte 2
0x039094|$0E:$9084:D0 14    BNE $909A 
0x039096|$0E:$9086:AD 1D 60 LDA $601D  ; Gold byte 1
0x039099|$0E:$9089:C5 81    CMP $81   
0x03909B|$0E:$908B:90 0B    BCC $9098 
0x03909D|$0E:$908D:F0 02    BEQ $9091 
0x03909F|$0E:$908F:B0 09    BCS $909A 
; control flow target (from $908D)
0x0390A1|$0E:$9091:AD 1C 60 LDA $601C  ; Gold byte 0
0x0390A4|$0E:$9094:C5 80    CMP $80   
0x0390A6|$0E:$9096:B0 02    BCS $909A 
; control flow target (from $908B)
0x0390A8|$0E:$9098:38      SEC       
0x0390A9|$0E:$9099:60      RTS       

; control flow target (from $9084, $908F, $9096)
0x0390AA|$0E:$909A:AD 1C 60 LDA $601C  ; Gold byte 0
0x0390AD|$0E:$909D:38      SEC       
0x0390AE|$0E:$909E:E5 80    SBC $80   
0x0390B0|$0E:$90A0:8D 1C 60 STA $601C  ; Gold byte 0
0x0390B3|$0E:$90A3:AD 1D 60 LDA $601D  ; Gold byte 1
0x0390B6|$0E:$90A6:E5 81    SBC $81   
0x0390B8|$0E:$90A8:8D 1D 60 STA $601D  ; Gold byte 1
0x0390BB|$0E:$90AB:AD 1E 60 LDA $601E  ; Gold byte 2
0x0390BE|$0E:$90AE:E9 00    SBC #$00   
0x0390C0|$0E:$90B0:8D 1E 60 STA $601E  ; Gold byte 2
0x0390C3|$0E:$90B3:18      CLC       
0x0390C4|$0E:$90B4:60      RTS

Code: [Select]
; control flow target (from $EFA0)
; external control flow target (from $0E:$900E)
0x03EFDF|$0F:$EFCF:AD 1C 60 LDA $601C  ; Gold byte 0
0x03EFE2|$0F:$EFD2:18      CLC       
0x03EFE3|$0F:$EFD3:65 80    ADC $80   
0x03EFE5|$0F:$EFD5:8D 1C 60 STA $601C  ; Gold byte 0
0x03EFE8|$0F:$EFD8:AD 1D 60 LDA $601D  ; Gold byte 1
0x03EFEB|$0F:$EFDB:65 81    ADC $81   
0x03EFED|$0F:$EFDD:8D 1D 60 STA $601D  ; Gold byte 1
0x03EFF0|$0F:$EFE0:AD 1E 60 LDA $601E  ; Gold byte 2
0x03EFF3|$0F:$EFE3:69 00    ADC #$00   
0x03EFF5|$0F:$EFE5:8D 1E 60 STA $601E  ; Gold byte 2
0x03EFF8|$0F:$EFE8:C9 98    CMP #$98   
0x03EFFA|$0F:$EFEA:B0 14    BCS $F000 
0x03EFFC|$0F:$EFEC:90 21    BCC $F00F 
0x03EFFE|$0F:$EFEE:AD 1D 60 LDA $601D  ; Gold byte 1
0x03F001|$0F:$EFF1:C9 96    CMP #$96   
0x03F003|$0F:$EFF3:90 1A    BCC $F00F 
0x03F005|$0F:$EFF5:F0 02    BEQ $EFF9 
0x03F007|$0F:$EFF7:B0 07    BCS $F000 
; control flow target (from $EFF5)
0x03F009|$0F:$EFF9:AD 1C 60 LDA $601C  ; Gold byte 0
0x03F00C|$0F:$EFFC:C9 80    CMP #$80   
0x03F00E|$0F:$EFFE:90 0F    BCC $F00F 
; control flow target (from $EFEA, $EFF7)
0x03F010|$0F:$F000:A9 7F    LDA #$7F   
0x03F012|$0F:$F002:8D 1C 60 STA $601C  ; Gold byte 0
0x03F015|$0F:$F005:A9 96    LDA #$96   
0x03F017|$0F:$F007:8D 1D 60 STA $601D  ; Gold byte 1
0x03F01A|$0F:$F00A:A9 98    LDA #$98   
0x03F01C|$0F:$F00C:8D 1E 60 STA $601E  ; Gold byte 2
; control flow target (from $EFEC, $EFF3, $EFFE)
0x03F01F|$0F:$F00F:60      RTS   

Ah, a brand new set of code with only deductive reasoning and the context of the game to determine what each chunk handles. Yes sir, now comes the crucial first step for me with each new chunk of code.



If I had to wager, I'd say one section deals with winning battles, another section deals with buying and/or selling, and the other section (two if buying and selling are handled in the same routine) would be paying for the boat ride between Paloom and Poft and paying for the Airship ride. Currently, the highest value (with no cap and thus overflow, mind), is $FFFFFF ($FF $FF $FF), and we want it to be 9,999,999 which is $98967F ($7F $96 $98). So the first step would be imposing a hard cap on $601E at $98. If the math works in our favor (which it probably doesn't), we could probably just refuse to add carry if it would increase $601E above $98. Someone who understands the math being done here can tell me whether this means that the previous bytes will still go up if this operation fails, or if a limit has to be manually imposed upon them as well.
« Last Edit: January 19, 2020, 04:58:41 pm by redmagejoe »

abw

  • Sr. Member
  • ****
  • Posts: 331
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #82 on: January 15, 2020, 10:51:56 pm »
It's not that I don't appreciate you providing a change, but I'd really like to learn how to do this before I look at what you linked.
No worries - there's nothing wrong with having multiple approaches to solving a problem :). And especially as a learning exercise, it's good to try it your own way.

That said, I did not know that quality of DEX setting the Z flag, and until I looked it up, I did not realize that the way BEQ actually worked was ensuring the Z flag was 0. I'm learning more by the minute.
At some point you'll definitely want a good 6502 opcode reference for checking on stuff like that. I usually prefer Programming the 65816 (the 65816 used on the SNES is a superset of the 6502 used on the NES, so most resources for 65816 will also apply to 6502) which among other things covers which processor flags are affected by each opcode and the applicable addressing modes.

Oh, since I forgot to mention it earlier, +1 for tracking down the cause of this problem in the first place. It takes some people quite a while to get that far successfully!

EDIT: Oops... Yep, I have a D0 0F to a577 listed above, it was D0 0D in my ROM. Also the FE 37 7D was 70 in my ROM. I'm just going to go through and input the bytes again. Typos are probably the cause of all this trouble.

 :banghead:  :banghead:  :banghead:  :banghead:

God I feel like such an idiot.
:D At some point you're also going to want to switch to writing code as text instead of hex and letting an assembler do all the error-prone opcode translations and address calculations. Learning the syntax for your chosen assembler is a little bit of a detour, but the larger your ASM hacks become, the more you're going to want to be able to automate the tedious stuff.

Would just like to have abw give it a review and make sure there's no other surprises down the line,
[...]
Code: [Select]
0x03255A|$0C:$A54A:20 83 A5 JSR $A583   <- changed address to new sub-routine

0x03255D|$0C:$A54D:A0 35    LDY #$35    <- shifted this and all following code up, sub-routine comes later
0x03255F|$0C:$A54F:B1 7E    LDA ($7E),Y
0x032561|$0C:$A551:4A      LSR
0x032562|$0C:$A552:90 03    BCC $A557   <- changed reference address and increased 02 to 03 to skip new JSR
0x032564|$0C:$A554:20 83 A5    JSR $A583   <- changed address to new sub-routine

0x032567|$0C:$A557:A5 9E    LDA $9E   
You might want to double-check this part. The original code keeps looping through randomly chosen physical attack targets until it finds one that's not in the back row, but you've dropped the BCS $A54A that did the looping, which means this patch probably makes back row characters able to be hit. NOP-ing out the unused bytes isn't strictly necessary, but it is a nice advertisement that the space is now free. Otherwise your code looks pretty close to what I had.

redmagejoe

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: Final Fantasy II Restored
« Reply #83 on: January 15, 2020, 11:16:11 pm »
I removed the BCS because it seemed similar to the BEQ, BNE issue. If the BCC failed, then clearly the condition for BCS must be met and it's redundant, right? Or are you saying that in this case, the issue is that it will only JSR that one time and then move on when it returns? Should I simply insert the BCS back in to point it back the 5 bytes to the JSR again until it fails the BCS? Or should I have it point further back to the BCC, which I can add 2 to the bytes to branch to skip past both the JSR and the new BCS? I have 3 bytes to work with, so it's not like I can't stick those 2 bytes back in.

Better yet, reviewing it, I suppose I could simply turn the duplicate JSR after the BCC back into a BCS that points back up to the first instance of the JSR $A582, since it would more or less accomplish the same thing as the original code. Though in that case I've added yet another empty byte, but that actually kind of works out to do something that was nitpicky to me, where a lot of the code towards the end of the change is the same as the original, but offset by one byte. I just like when I compare in TinyHexer seeing as little of that code highlighted as necessary, so this would allow that part to line up and not be different. ;D

Completed Mock-up:
Code: [Select]
; control flow target (from $A525)
0x03254A|$0C:$A53A:A0 2A    LDY #$2A   
0x03254C|$0C:$A53C:B1 44    LDA ($44),Y
0x03254E|$0C:$A53E:D0 1F    BNE $A55F    <- changed reference address and 31 to 1F to account for changes
0x032550|$0C:$A540:A6 76    LDX $76   
0x032552|$0C:$A542:BD 5A 7B LDA $7B5A,X
0x032555|$0C:$A545:F0 03 BEQ $A54A
0x032557|$0C:$A547:20 20 A7 JSR $A720
0x03255A|$0C:$A54A:20 82 A5 JSR $A582   <- changed address to new sub-routine

0x03255D|$0C:$A54D:A0 35    LDY #$35    <- shifted this and all following code up, sub-routine comes later
0x03255F|$0C:$A54F:B1 7E    LDA ($7E),Y
0x032561|$0C:$A551:4A      LSR
0x032562|$0C:$A552:90 02    BCC $A556   <- changed reference address
0x032564|$0C:$A554:B0 F4    BCS $A54A

0x032566|$0C:$A556:A5 9E    LDA $9E   
0x032568|$0C:$A558:AA      TAX       
0x032569|$0C:$A559:FE 37 7D INC $7D37,X ; Character #1 counter for times physically attacked by enemy
0x03256C|$0C:$A55C:4C 10 A6 JMP $A610 

0x03256F|$0C:$A55F:A0 2B    LDY #$2B   
0x032571|$0C:$A561:B1 44    LDA ($44),Y
0x032573|$0C:$A563:C9 01    CMP #$01   
0x032575|$0C:$A565:D0 0F    BNE $A576     <- change opcode and reference address to point past the new loop
0x032577|$0C:$A567:A9 08    LDA #$08

;; New Increment Loop only used after LDA #$08
0x032579|$0C:$A569:A2 00    LDX #$00
0x03257B|$0C:$A56B:FE 3B 7D INC $7D3B,X ; Character #1 counter for times magically attacked by enemy
0x03257E|$0C:$A56E:E8    INX
0x03257F|$0C:$A56F:E0 04    CPX #$04
0x032581|$0C:$A571:D0 F8    BNE $A56B
0x032583|$0C:$A573:4C 10 A6    JMP $A610     <- changed to jump past the TAX and INC commands 

0x032586|$0C:$A576:C9 02    CMP #$02   
0x032588|$0C:$A578:D0 20    BNE $A59A    <- 18 increased to 20 due to code shift
0x03258A|$0C:$A57A:20 82 A5     JSR $A582    <- added JSR here
0x03258D|$0C:$A57D:A5 9E    LDA $9E 
0x03258F|$0C:$A57F:4C 0C A6 JMP $A60C

; control flow target (from $A54A and $A57A)         <- added control flow from new JSR sources
0x032592|$0C:$A582:A2 00    LDX #$00   
0x032594|$0C:$A584:A9 03    LDA #$03   
0x032596|$0C:$A586:20 11 FD JSR $FD11  ; given A and X, return a random value in A: X == #$FF => #$FF, A == #$00 or X => A, else X + the third byte of (A-X) * (#$80 + 256 * $7A48,$42) rounded based on its second byte
0x032599|$0C:$A589:85 9E    STA $9E   
0x03259B|$0C:$A58B:20 E1 96 JSR $96E1 
0x03259E|$0C:$A58E:20 6C AF JSR $AF6C 
0x0325A1|$0C:$A591:29 C0    AND #$C0   
0x0325A3|$0C:$A593:D0 ED    BNE $A582
0x0325A5|$0C:$A595:60    RTS

0x0325A6|$0C:$A596:EA    NOP
0x0325A7|$0C:$A597:EA    NOP
0x0325A8|$0C:$A598:EA    NOP
0x0325A9|$0C:$A599:EA    NOP

Modified in last two posts, and copied to this one. I think this should more or less take care of any unintended consequences from original program behavior, yes? And you're right, I should probably be doing this in a way other than changing bytes in the binary, though I started doing that because of my very small tweaks when I started. Eventually I'll get tired of making silly mistakes like this and let an assembler do the work for me. Unless you spot any other whoopsies, abw, I'm confident that this takes care of the bug with no unfortunate side effects, and I'm ready to look at Gil capping.
« Last Edit: January 19, 2020, 04:58:57 pm by redmagejoe »

Cyneprepou4uk

  • Sr. Member
  • ****
  • Posts: 303
  • I am the baldest romhacker
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #84 on: January 16, 2020, 06:31:25 am »
Is your mockup compilable by default?

Also, if you, for instance, need to add a NOP instruction at $A532, which forces you to move the whole thing down, would you have to manually change addresses from the left and from the right, or is there some kind of program to do that for you?
iromhacker.ru - NES ROM hacking tutorials for beginners. Please use Google Translate browser extension

redmagejoe

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: Final Fantasy II Restored
« Reply #85 on: January 16, 2020, 08:40:38 am »
You'd tear out your hair (assuming your signature wasn't true), if I told you this, but... I actually just used the mockup as sort of a scratchpad to present my changes here while also giving myself a readable comparison to the original disassembly, and then find the bytes in the binary and change them manually via hex editor. Thus the typos. I fully acknowledge it's the most tedious way to do this, and the more I think about it, the more I consider that I should just learn to use a compiler/assembler rather than trying to bypass the whole process of conversion and making the changes directly, often resulting in typos or losing track of changes that have been made and still need to be made.
« Last Edit: January 19, 2020, 04:59:22 pm by redmagejoe »

abw

  • Sr. Member
  • ****
  • Posts: 331
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #86 on: January 16, 2020, 09:18:47 am »
If the BCC failed, then clearly the condition for BCS must be met
This part is true.

and [the BCS is] redundant, right?
This part is false. Since the condition for the BCS is met, the branch is always taken and control flow loops back to $A54A.

At the start of $A552, there are only two options for C: either it is set or it is clear. So it's pretty easy to trace through the code and see what happens in either case. When C is set, the BCC is not taken and the BCS is, so you end up at $A54A. When C is clear, the BCC is taken and you end up at $A556.

Now try the same thing but with the BCC $A556 replaced by NOP NOP. When C is set, the BCS is taken and you end up at $A54A. When C is clear, the BCS is not taken so you end up at $A556. So regardless of whether the BCC is there or not, you still end up going to the exact same places in either case, and (in combination with it not having any side effects like changing processor flags) that's what makes the BCC useless.

You'd tear out your hair (assuming your signature wasn't true), if I told you this, but... I actually just used the mockup as sort of a scratchpad to present my changes here while also giving myself a readable comparison to the original disassembly, and then find the bytes in the binary and change them manually via hex editor. Thus the typos. I fully acknowledge it's the most tedious way to do this, and the more I think about it, the more I consider that I should just learn to use a compiler/assembler rather than trying to bypass the whole process of conversion and making the changes directly, often resulting in typos or losing track of changes that have been made and still need to be made.
Direct hex editing is okay-ish for tiny changes (but even there you generally lose out on version control and documenting why you made the change so that future you doesn't have to re-figure everything out all over again), but the bigger your project becomes, the more important project management becomes. It's a sad fact of life :P.

redmagejoe

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: Final Fantasy II Restored
« Reply #87 on: January 16, 2020, 09:24:11 am »
Yeah, I saw the ramifications upon further investigation of my changes. The way it is now, I believe, should work fine. I could NOP NOP the BCC, but again, it's in a good place to modify as few addresses as possible. If for whatever reason down the line those 2 bytes are needed, I can come back to them, but for now they don't hurt anything, redundant as the BCC is.

Do you have any insight on the gold routines? I admit I haven't looked it over, as I've been reaching out to contact Demiforce and koitsu about the title screen, and cleaning up Chaos Rush's translation since he seemed to wipe his hands of it after the last release, for resources that could be used at the tail end of this project. I'm wondering if it would be best to have this patch not include any translation at all, so people can have their bug fixes / title screen and apply translation of their choice. Either way, I'd like to submit the updated translation to Chaos Rush anyway.
« Last Edit: January 19, 2020, 04:59:31 pm by redmagejoe »

Cyneprepou4uk

  • Sr. Member
  • ****
  • Posts: 303
  • I am the baldest romhacker
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #88 on: January 16, 2020, 11:46:03 am »
@redmagejoe, I wasn't pushing you into using assembler, especially since I've never used it myself. Did you know you can add comments to code and RAM addresses in debugger?

And I wonder how exactly do you change code in ROM. Do you use inline assembler or edit bytes manually?
iromhacker.ru - NES ROM hacking tutorials for beginners. Please use Google Translate browser extension

redmagejoe

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: Final Fantasy II Restored
« Reply #89 on: January 16, 2020, 11:52:28 am »
@redmagejoe, I wasn't pushing you into using assembler, especially since I've never used it myself. Did you know you can add comments to code and RAM addresses in debugger?

And I wonder how exactly do you change code in ROM. Do you use inline assembler or edit bytes manually?

Edit bytes manually in TinyHexer. :-[
« Last Edit: January 19, 2020, 04:59:37 pm by redmagejoe »

Cyneprepou4uk

  • Sr. Member
  • ****
  • Posts: 303
  • I am the baldest romhacker
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #90 on: January 16, 2020, 11:58:19 am »
Why don't you edit bytes via hex editor in fceux directly?
iromhacker.ru - NES ROM hacking tutorials for beginners. Please use Google Translate browser extension

redmagejoe

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: Final Fantasy II Restored
« Reply #91 on: January 16, 2020, 12:01:08 pm »
I'd thought about that, but I got really comfortable with always having the base, unmodified (or in this case with all the changes, STABLE version) ROM in comparison mode with the "test" ROM I'm working with, so I can have the changes highlighted and can immediately refer to the base to revert any change if it causes a problem.
« Last Edit: January 19, 2020, 04:59:43 pm by redmagejoe »

Cyneprepou4uk

  • Sr. Member
  • ****
  • Posts: 303
  • I am the baldest romhacker
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #92 on: January 16, 2020, 12:03:36 pm »
I see. And how do you determine what bytes for opcodes need to be written?
iromhacker.ru - NES ROM hacking tutorials for beginners. Please use Google Translate browser extension

redmagejoe

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: Final Fantasy II Restored
« Reply #93 on: January 16, 2020, 12:06:38 pm »
A combination of these links as resources. Some of them seem pretty basic, so I feel like kind of a scrub using them, but they've been very helpful.

http://www.6502.org/tutorials/6502opcodes.html
https://wiki.nesdev.com/w/index.php/CPU_unofficial_opcodes
https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
« Last Edit: January 19, 2020, 04:59:50 pm by redmagejoe »

Cyneprepou4uk

  • Sr. Member
  • ****
  • Posts: 303
  • I am the baldest romhacker
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #94 on: January 16, 2020, 12:15:07 pm »
I was using this manual before. Then I made my own opcodes list, maybe it will suit you as well
iromhacker.ru - NES ROM hacking tutorials for beginners. Please use Google Translate browser extension

redmagejoe

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: Final Fantasy II Restored
« Reply #95 on: January 16, 2020, 12:17:46 pm »
I was using this manual before. Then I made my own opcodes list, maybe it will suit you as well

Oh! These are really helpful! Thank you, I will definitely use these moving forward. The PNG you made is nice and compact and has all the information I need when trying to decide which variant of an opcode to use. :)



I've reached out to a few FF2 contributors for permission to use their works in the comprehensive patch. koitsu suggested that for a standalone title patch, or at least as part of this project, I look into putting a Help Wanted for an NES artist on this site, as not only would I need to get Demiforce's permission to use the screen, but only the CHR files are available from koitsu even if I did. I think I'll take his advice and update the first post as a to-do list item to recruit someone for a new title screen. I've also gotten Parasyte's permission to use his Target/Cancel exploit fix patch, though I think it's worth reviewing and possibly improving the patch looking at the review on his page stating that it creates another bug. I'm sure it can be polished up.

https://www.romhacking.net/reviews/1267/#review

I've also reached out to Chaos Rush about his opinion on using a translation as part of this patch, as well as permission in the case it is advised, though I'm not averse to simply making mechanics changes in the patch to attach to the original JP ROM, and letting people use whatever translation patch they desire. I'll be sure to ask Chaos Rush if it's okay to use his B Button Dash patch as well.



Analyzing Parasyte's Target/Cancel exploit fix...

Code: [Select]
0x311F2: E1 96 => 1E F8
0x3F82E: 86 05 A5 64 4A 4A 18 65 => A6 9E DE F3 7C 4C E1 96

Now let's see where and what these changes are doing in the context of the disassembly, and why it could be causing the weird bug described in the review.

Code: [Select]
; control flow target (from $91D2)
0x0311EB|$0C:$91DB:A5 9E    LDA $9E   
0x0311ED|$0C:$91DD:F0 28    BEQ $9207 
0x0311EF|$0C:$91DF:C6 9E    DEC $9E   
0x0311F1|$0C:$91E1:20 E1 96 JSR $96E1  <<<
0x0311F4|$0C:$91E4:20 08 92 JSR $9208 
0x0311F7|$0C:$91E7:D0 05    BNE $91EE 
0x0311F9|$0C:$91E9:20 11 92 JSR $9211 
0x0311FC|$0C:$91EC:F0 19    BEQ $9207 

So a JSR $96E1 has been changed to a JSR $F8E1.

Code: [Select]
; free -> unknown
0x03F810|$0F:$F800:2C 02 20 ; BIT $2002 
0x03F811|$0F:$F801:02      ; INVALID OPCODE
0x03F812|$0F:$F802:20 A5 FF ; JSR $FFA5 
0x03F813|$0F:$F803:A5 FF    ; LDA $FF   
0x03F814|$0F:$F804:FF      ; INVALID OPCODE
0x03F815|$0F:$F805:8D 00 20 ; STA $2000 
0x03F816|$0F:$F806:00      ; BRK       
0x03F817|$0F:$F807:20 60 C0 ; JSR $C060 
0x03F818|$0F:$F808:60      ; RTS       
0x03F819|$0F:$F809:C0 0A    ; CPY #$0A   
0x03F81A|$0F:$F80A:0A      ; ASL       
0x03F81B|$0F:$F80B:0A      ; ASL       
0x03F81C|$0F:$F80C:85 04    ; STA $04   
0x03F81D|$0F:$F80D:04      ; INVALID OPCODE
0x03F81E|$0F:$F80E:A2 20    ; LDX #$20   
0x03F81F|$0F:$F80F:20 A5 63 ; JSR $63A5 
0x03F820|$0F:$F810:A5 63    ; LDA $63   
0x03F821|$0F:$F811:63      ; INVALID OPCODE
0x03F822|$0F:$F812:C9 FF    ; CMP #$FF   
0x03F823|$0F:$F813:FF      ; INVALID OPCODE
0x03F824|$0F:$F814:F0 2A    ; BEQ $F840 
0x03F825|$0F:$F815:2A      ; ROL       
0x03F826|$0F:$F816:A2 20    ; LDX #$20   
0x03F827|$0F:$F817:20 C9 AF ; JSR $AFC9 
0x03F828|$0F:$F818:C9 AF    ; CMP #$AF   
0x03F829|$0F:$F819:AF      ; INVALID OPCODE
0x03F82A|$0F:$F81A:F0 02    ; BEQ $F81E 
0x03F82B|$0F:$F81B:02      ; INVALID OPCODE
0x03F82C|$0F:$F81C:A2 10    ; LDX #$10   
0x03F82D|$0F:$F81D:10 86    ; BPL $F7A5 
0x03F82E|$0F:$F81E:86 05    ; STX $05          <<<
0x03F82F|$0F:$F81F:05 A5    ; ORA $A5          xxx  (this isn't in my ROM and looking at the address, doesn't seem like this fits here?)
0x03F830|$0F:$F820:A5 64    ; LDA $64          <<<
0x03F831|$0F:$F821:64      ; INVALID OPCODE   xxx  (not in my ROM)
0x03F832|$0F:$F822:4A      ; LSR              <<<
0x03F833|$0F:$F823:4A      ; LSR              <<<
0x03F834|$0F:$F824:18      ; CLC              <<<
0x03F835|$0F:$F825:65 05    ; ADC $05          <<<
0x03F836|$0F:$F826:05 AA    ; ORA $AA   
0x03F837|$0F:$F827:AA      ; TAX       
0x03F838|$0F:$F828:4C 40 F8 ; JMP $F840 
0x03F839|$0F:$F829:40      ; RTI       
0x03F83A|$0F:$F82A:F8      ; SED   

The changes to these lines should come out to these instructions:

Code: [Select]
0x03F82E|$0F:$F81E:A6 9E    ; LDX $9E
0x03F830|$0F:$F820:DE F3 7C    ; DEC $7CF3,X
0x03F833|$0F:$F823:4C E1 96    ; JMP $96E1

It looks like he moved what was dictated as free space (don't know how reliable that is?) into a function that is redirected to before continuing the usual way back to $96E1. I guess this is supposed to prevent any counters from incrementing for the time being? But I feel like the bugs are occurring because originally, it would JSR to $96E1, and then return from that subroutine. This new chunk is JSR'd to, but then jumps absolute to the subroutine... and so it maybe fails to properly return to the original control flow? I don't know, I've traced the path it's taking, but I still don't really understand what it's storing in the X register, but I can see that $7CF3 is Firion's attack counter, and F3-F6 would be the 4 party members, and then F7-FA are the party's spell uses... Okay, I think I understand. This is a VERY janky approach. If I'm reading this right, this is subtracting the already added credit for the ability used when you cancel, but it's a catch-all. The problem with this is that each action you issue has a counter, EXCEPT for Flee and Item use. This code runs when you cancel regardless of the action you issued, and since there's no counter pertaining to item use or fleeing, it just uses that character's attack counter, decrements it, and can result in underflow. There must be a better way to approach this. I can't use this patch in Restored, I'm afraid.



I was going to focus on the gil cap, but since it's a bit overwhelming right now, and I'm a little inebriated, I'll focus on something I felt like I had a grasp of, the Evasion / Magic Defense delay.

End of battle stat up routine:
Code: [Select]
; control flow target (from $A5A7, $A5C2)
0x016601|$05:$A5F1:E6 44    INC $44   
0x016603|$05:$A5F3:E6 00    INC $00   
0x016605|$05:$A5F5:A5 00    LDA $00   
0x016607|$05:$A5F7:C9 10    CMP #$10   
0x016609|$05:$A5F9:D0 A7    BNE $A5A2 
0x01660B|$05:$A5FB:A9 7D    LDA #$7D    ; $7D37; Character #1 counter for times physically attacked by enemy
0x01660D|$05:$A5FD:85 45    STA $45   
0x01660F|$05:$A5FF:A9 37    LDA #$37    ; $7D37; Character #1 counter for times physically attacked by enemy
0x016611|$05:$A601:85 44    STA $44   
0x016613|$05:$A603:AD 37 AC LDA $AC37 
0x016616|$05:$A606:85 46    STA $46   
0x016618|$05:$A608:A0 30    LDY #$30    ; Skill offset for Evasion level
0x01661A|$05:$A60A:84 47    STY $47   
0x01661C|$05:$A60C:20 42 A8 JSR $A842  ; level up Evasion/Magic Resist if applicable
0x01661F|$05:$A60F:A9 7D    LDA #$7D    ; $7D3B; Character #1 counter for times magically attacked by enemy
0x016621|$05:$A611:85 45    STA $45   
0x016623|$05:$A613:A9 3B    LDA #$3B    ; $7D3B; Character #1 counter for times magically attacked by enemy
0x016625|$05:$A615:85 44    STA $44   
0x016627|$05:$A617:AD 38 AC LDA $AC38 
0x01662A|$05:$A61A:85 46    STA $46   
0x01662C|$05:$A61C:A0 32    LDY #$32    ; Skill offset for Magic Resist level
0x01662E|$05:$A61E:84 47    STY $47   
0x016630|$05:$A620:20 42 A8 JSR $A842  ; level up Evasion/Magic Resist if applicable
... Rest is just the other stats, not worth spamming up this section.

Evasion / Magic Defense up routine:
Code: [Select]
; level up Evasion/Magic Resist if applicable
; control flow target (from $A60C, $A620)
0x016852|$05:$A842:A4 9E    LDY $9E    ; Character index
0x016854|$05:$A844:B1 44    LDA ($44),Y
0x016856|$05:$A846:F0 24    BEQ $A86C 
0x016858|$05:$A848:18      CLC       
0x016859|$05:$A849:65 22    ADC $22   
0x01685B|$05:$A84B:65 46    ADC $46   
0x01685D|$05:$A84D:A4 47    LDY $47   
0x01685F|$05:$A84F:F1 7E    SBC ($7E),Y
0x016861|$05:$A851:E9 0A    SBC #$0A   
0x016863|$05:$A853:90 17    BCC $A86C 
0x016865|$05:$A855:C8      INY       
0x016866|$05:$A856:71 7E    ADC ($7E),Y
0x016868|$05:$A858:C9 64    CMP #$64   
0x01686A|$05:$A85A:90 0E    BCC $A86A 
0x01686C|$05:$A85C:88      DEY       
0x01686D|$05:$A85D:B1 7E    LDA ($7E),Y
0x01686F|$05:$A85F:AA      TAX       
0x016870|$05:$A860:E8      INX       
0x016871|$05:$A861:20 6D A8 JSR $A86D  ; cap X at 15
0x016874|$05:$A864:8A      TXA       
0x016875|$05:$A865:91 7E    STA ($7E),Y
0x016877|$05:$A867:C8      INY       
0x016878|$05:$A868:A9 00    LDA #$00    ; reset experience to zero
; control flow target (from $A85A)
0x01687A|$05:$A86A:91 7E    STA ($7E),Y
; control flow target (from $A846, $A853)
0x01687C|$05:$A86C:60      RTS       

Ensure max level is 16 routine:
Code: [Select]
; cap X at 15
; control flow target (from $A5D0, $A861, $A97A)
0x01687D|$05:$A86D:E0 10    CPX #$10   
0x01687F|$05:$A86F:90 02    BCC $A873 
0x016881|$05:$A871:A2 0F    LDX #$0F   
; control flow target (from $A86F)
0x016883|$05:$A873:60      RTS   

So $7D37 is being stored in A at $45 and $44 in memory, something else is being stored in A at $46 in memory, Evasion level (or maybe experience? not sure if Evasion has an experience bar or just a chance to increase) is stored in Y register at $47 in memory, and then we jump to the level-up routine. LDY to use the character index in the following LDA to essentially set $7D37 up 0-3 places depending on the character getting the Evasion up, load the low byte of being hit counter ($44) into the accumulator at this location... Branch on Equal down to an RTS. I'm guessing this means if the character was not hit at all, don't bother doing any math.

Then I start to lose sight of what's happening, why Carry is being set to 0, what the add carry is doing, and so on... I understand the "no hit, end sub-routine" and now we're checking the criteria to see if Evasion increases. I'll try to sober up a bit and make more sense of what I'm looking at. It appears the exact same code is running for Evasion or Magic Resist, just setting different memory start points before the JSR. So figure one out, figure them both out. I can see from commenting near the bottom that $A861 ensures that level caps at 16 (final displayed level is X + 1, so 1 to 16 is X = 0 to 15), and either Carry = 0, or Carry = 1 and we decrease X back down to 15, and either way we return back to the previous flow. X -> A, I guess Evasion Level is stored in the accumulator...? Y (which holds Evasion level at this point?) is incremented, meaning it ends up being X + 1, Accumulator is holding Evasion experience, which is set back to 0, new Evasion Level Y is saved in the Accumulator, and then we're done.

Had RAM search on with $612A, and imagine my surprise when after getting into a random battle, as it started, my Evasion went up from level 7 to 8. So perhaps there's a step missing in the Evasion up routine? Perhaps it's not being pushed to $612A/C? Yet it's pushed during the battle setup routine. How do we fix this, I wonder... Using RAM Search and breakpoints, it appears that the moment Magic Resist (easier to track where I'm at with all the mage enemies, plus suffers the same symptoms and follows same routines) actually increases is at $9A80, as MDef was level 7 before the battle but SHOULD have been raised to 8 after the last battle with all the Sorcerors.

Code: [Select]
0x001A8B|$00:$9A7B:B1 7E    LDA ($7E),Y
0x001A8D|$00:$9A7D:AA      TAX       
0x001A8E|$00:$9A7E:E8      INX       
0x001A8F|$00:$9A7F:8A      TXA       
0x001A90|$00:$9A80:A0 2C    LDY #$2C   
0x001A92|$00:$9A82:91 7A    STA ($7A),Y @ $612C = #$08

It seems like this block above, which happens at the start of the next battle after all the Evasion/MDef math was done, is where Evasion and MDef level is actually incremented. It appears that the math being done is actually on a value that is your new Ev/MDef level MINUS ONE, which seems like a ridiculous way to handle it. I'm trying to think of a way these 9 bytes could be moved up into that Ev/MDef up routine, and maybe just outright rewrite that section to not be operating on 7E, but on the actual level. I see no reason that there should be a perpetual variable that is only an operand for a stat. I just can't figure out the best way to do it. Stuck the above code block into some free space and NOP'd out the INX in the original for testing, and now trying to work a JSR into the Evasion/MDef up routine. I can steal one byte from the ensure max 16 routine by turning the LDX #$0F into DEX, but I still need 2 more bytes for the 3-byte JSR.
« Last Edit: January 19, 2020, 05:00:06 pm by redmagejoe »

abw

  • Sr. Member
  • ****
  • Posts: 331
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #96 on: January 17, 2020, 11:39:14 pm »
Analyzing Parasyte's Target/Cancel exploit fix...
[...]
The problem with this is that each action you issue has a counter, EXCEPT for Flee and Item use. This code runs when you cancel regardless of the action you issued, and since there's no counter pertaining to item use or fleeing, it just uses that character's attack counter, decrements it, and can result in underflow. There must be a better way to approach this. I can't use this patch in Restored, I'm afraid.
It can also cause underflow when you cancel a spell, since spell casting increments a character's black/white magic use counter, not their physical attack counter. That said, underflow looks an awful lot like attacking 255 times... I think this patch for fixing the existing Target/Cancel exploit actually creates much more powerful No-Target/Cancel exploit; underflowing by (11 + battle rank) appears to be enough to guarantee a level up, which is a lot faster than cancelling (89 - battle rank) times! I still say updating the counters on command execution is the way to go here.

However, this reveals another bug to add to the list: the game doesn't cap the battle action counters either, so if a fight goes on long enough for you to attack 250 times, you're probably not going to gain any weapon experience at all :(

I was going to focus on the gil cap, but since it's a bit overwhelming right now, and I'm a little inebriated, I'll focus on something I felt like I had a grasp of, the Evasion / Magic Defense delay.
[...]
Then I start to lose sight of what's happening
I've updated my disassembly with some possibly helpful commentary on this routine.

I also took a peek at the gold code. It looks like $05:$A4C9-$05:$A4E4 is for gaining gold after a battle, the routine starting at $0E:$89B5 is for displaying your 24-bit gold as a 7-digit decimal number, $0E:$9081-$0E:$90B4 is for any time you lose gold (e.g. buying items), and the routine at $0F:$EFCF is for gaining gold outside of battle (e.g. selling items). The fun part is that $0F:$EFCF actually tries to cap your gold at 9,999,999 but fails in a slightly hilarious way: after determining that byte 2 of your gold amount is at least #$98 (i.e. you have at least 9,961,472 gold), it messes up its branching and ends up writing $98967F (9,999,999) to your gold. That's probably the quickest way to gain 38,000+ gold in the entire game :P.

redmagejoe

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: Final Fantasy II Restored
« Reply #97 on: January 17, 2020, 11:45:54 pm »
That sounds like an easy fix then: They already tried to cap gold, all I have to do is make it work properly. I don't think it's even remotely feasible for a person to be in a situation where a battle lasts long enough for a single character to attack 255 times, but I will add it to the list nonetheless. I fully agree with your idea that the counter should only update when an action takes place, but that will probably require a great deal of code rewriting, and I'm going to put that later in my workflow when I feel more comfortable with what I'm doing.

I'll work on the gold fix, and then check your updated disassembly. I did find a way to push that block of code into free space, JSR to it, and it does indeed increase your MDef level at the end of the battle... but I must be doing something bad with memory, because after finishing the following battle, the game graphically glitches and crashes. So I'm on the right track, I just need to get a better understanding of how to incorporate the MDef/Evasion fix.

Were your comments on the gold bytes supposed to be 10,000,000, and 9,999,999 when mentioning the bugs? Right now they say 999,999. I'm just trying to determine what the intended cap for gold actually was. Not made easier by the PSP ISO save I have, where the party has over 12 mil... so I wonder what the remake's cap is. I think it would be reasonable, however, to go ahead and make it 9,999,999 for the NES version. Looking at the byte values used, it seems clear they intended it to be 9,999,999. Just to avoid confusion, might I request those comments be slightly updated in your disassembly?

Also, so far this fixes the bug of the money jumping up to the intended cap after gaining money when crossing over from $97FFFF to >$980000. Going to set my money to the cap and try battles and selling to ensure both work. Either way, I'm crediting you for this patch since you essentially did all the actual detective work here, abw. :P

Okay, so as you stated, those fixes work for selling items. Now I just have to address the battle victory routine. Based on your comments, is it safe to assume that I can NOP out all 9 bytes, not only those 2 in the BCS that you said can be removed, but the next 7 in the following block that shouldn't ever be executed with that change? Or should I just leave them alone?

Code: [Select]
; given a 16-bit amount in $80-$81, add the amount to the party gold, capped at 10,000,000\nBUG: if party gold >= 9,961,472, party gold gets set to 9,999,999
; control flow target (from $EFA0)
; external control flow target (from $0E:$900E)
0x03EFDF|$0F:$EFCF:AD 1C 60 LDA $601C  ; Gold byte 0
0x03EFE2|$0F:$EFD2:18      CLC       
0x03EFE3|$0F:$EFD3:65 80    ADC $80   
0x03EFE5|$0F:$EFD5:8D 1C 60 STA $601C  ; Gold byte 0
0x03EFE8|$0F:$EFD8:AD 1D 60 LDA $601D  ; Gold byte 1
0x03EFEB|$0F:$EFDB:65 81    ADC $81   
0x03EFED|$0F:$EFDD:8D 1D 60 STA $601D  ; Gold byte 1
0x03EFF0|$0F:$EFE0:AD 1E 60 LDA $601E  ; Gold byte 2
0x03EFF3|$0F:$EFE3:69 00    ADC #$00   
0x03EFF5|$0F:$EFE5:8D 1E 60 STA $601E  ; Gold byte 2
0x03EFF8|$0F:$EFE8:C9 98    CMP #$98   
0x03EFFA|$0F:$EFEA:90 23    BCC $F00F       <<<<<<<
0x03EFFC|$0F:$EFEC:D0 12    BNE $F000       <<<<<<<
0x03EFFE|$0F:$EFEE:AD 1D 60 LDA $601D  ; Gold byte 1
0x03F001|$0F:$EFF1:C9 96    CMP #$96   
0x03F003|$0F:$EFF3:90 1A    BCC $F00F 
0x03F005|$0F:$EFF5:D0 09    BNE $F000       <<<<<<<
0x03F007|$0F:$EFF7:B0 07    BCS $F000  ; cap party gold at 9,999,999; since then you could eliminate these bytes
; control flow target (from $EFF5)
0x03F009|$0F:$EFF9:AD 1C 60 LDA $601C  ; Gold byte 0
0x03F00C|$0F:$EFFC:C9 80    CMP #$80   
0x03F00E|$0F:$EFFE:90 0F    BCC $F00F 
; cap party gold at 999,999
; control flow target (from $EFEA, $EFF7)
0x03F010|$0F:$F000:A9 7F    LDA #$7F   
0x03F012|$0F:$F002:8D 1C 60 STA $601C  ; Gold byte 0
0x03F015|$0F:$F005:A9 96    LDA #$96   
0x03F017|$0F:$F007:8D 1D 60 STA $601D  ; Gold byte 1
0x03F01A|$0F:$F00A:A9 98    LDA #$98   
0x03F01C|$0F:$F00C:8D 1E 60 STA $601E  ; Gold byte 2
; control flow target (from $EFEC, $EFF3, $EFFE)
0x03F01F|$0F:$F00F:60      RTS       

Highlighted updated code, going to use it for reference to see about repurposing it to use for

Code: [Select]
; control flow target (from $A4C3)
0x0164C7|$05:$A4B7:BD 62 7B LDA $7B62,X
0x0164CA|$05:$A4BA:20 D3 A9 JSR $A9D3
0x0164CD|$05:$A4BD:E6 0C    INC $0C   
0x0164CF|$05:$A4BF:A6 0C    LDX $0C   
0x0164D1|$05:$A4C1:E0 10    CPX #$10   
0x0164D3|$05:$A4C3:D0 F2    BNE $A4B7
0x0164D5|$05:$A4C5:20 64 A4 JSR $A464
0x0164D8|$05:$A4C8:18      CLC       
0x0164D9|$05:$A4C9:AD C0 7C LDA $7CC0
0x0164DC|$05:$A4CC:85 62    STA $62   
0x0164DE|$05:$A4CE:6D 1C 60 ADC $601C  ; Gold byte 0
0x0164E1|$05:$A4D1:8D 1C 60 STA $601C  ; Gold byte 0
0x0164E4|$05:$A4D4:AD C1 7C LDA $7CC1
0x0164E7|$05:$A4D7:85 63    STA $63   
0x0164E9|$05:$A4D9:6D 1D 60 ADC $601D  ; Gold byte 1
0x0164EC|$05:$A4DC:8D 1D 60 STA $601D  ; Gold byte 1
0x0164EF|$05:$A4DF:A9 00    LDA #$00   
0x0164F1|$05:$A4E1:6D 1E 60 ADC $601E  ; Gold byte 2
0x0164F4|$05:$A4E4:8D 1E 60 STA $601E  ; Gold byte 2

Actually looking at the code... Could I not simply put a JSR in place of what's at $05:$A4E4 to $0F:$EFE5? It would execute the exact same instruction that the JSR replaced, plus then do the capping routine and return back to the original control flow when it hit the RTS. This seems like a perfect solution, I just wonder if one can JSR to another bank? Seems I can, and it worked! I'll put the IPS up shortly! :)

Back to working on Evasion/Magic Defense... The comments help clarify that section, but it still seems like the problem lies in $00:$9A7B onward.



In the meantime, I figured out how to stop Weapon Level Up messages from occurring on maxed weapons. Using the same Max X at 15 subroutine that Evasion and MDef uses, weapon skills point to this. Since both Eva/MDef and weapon skill jumps to that RTS back to a TXA, I just changed the LDX #$0F (2 bytes) to a DEX (1 byte) and then put the TXA in that freed up byte. The BCC I set to 01 instead of 02, so no matter what the TXA always runs before the RTS, branch or no. This frees up 1 byte in both the Eva/MDef up routine and the Weapon Skill up routine. I can get 1 more byte in the weapon up routine by removing the Clear Carry, and putting a BCS after the INY to skip the entire battle message instructions down to where weapon experience is set back to 00. In this way, maxed weapons no longer give level up messages, and the exp counter is reset. I'd like to have exp not tally at all on maxed weapons, but that would involve more space most likely.

Code: [Select]
0x01687D|$05:$A86D:E0 10    CPX #$10   
0x01687F|$05:$A86F:90 01    BCC $A872      <<< changed to 01 to skip to TXA 
0x016881|$05:$A871:CA           DEX            <<< changed from LDX #$0F
0x016882|$05:$A872:8A           TXA            <<< added in place of second byte of LDX
0x016883|$05:$A873:60      RTS   

Code: [Select]
0x01698A|$05:$A97A:20 6D A8 JSR $A86D  ; cap X at 15
... removed TXA and shifted code up 1 byte
0x01698D|$05:$A97D:91 7E    STA ($7E),Y
0x01698F|$05:$A97F:C8      INY       
0x016990|$05:$A980:B0 0B      BCS $A98D      <<< added BCS to skip battle message code       
... removed CLC and shifted LDX down 1 to make room for new BCS
0x016992|$05:$A982:A6 AD    LDX $AD    ; battle message index
0x016994|$05:$A984:A5 0A    LDA $0A   
0x016996|$05:$A986:69 6C    ADC #$6C   
0x016998|$05:$A988:9D BA 7F STA $7FBA,X ; start of list of string IDs battle messages
0x01699B|$05:$A98B:E6 AD    INC $AD    ; battle message index
0x01699D|$05:$A98D:A9 00    LDA #$00   
0x01699F|$05:$A98F:91 7E    STA ($7E),Y

For now, I'm going to try and figure out how to work this into spell levels.

Code: [Select]
0x0165E0|$05:$A5D0:20 6D A8 JSR $A86D  ; cap X at 15
0x0165E3|$05:$A5D3:8A      TXA             <<< remove and move $A5D4-5 up to $A5D3-4
0x0165E4|$05:$A5D4:91 7E    STA ($7E),Y     <<< move up
0x0165E6|$05:$A5D6:C8      INY             <<< move up
                                                <<< ? insert BCS $A5?? here at what will become $A5D7-8 ?
0x0165E9|$05:$A5D9:98      TYA             <<< just stores our spell exp from Y into A
0x0165EA|$05:$A5DA:48      PHA             <<< just stores our spell exp from A into stack
0x0165EB|$05:$A5DB:A6 AD    LDX $AD    ; battle message index   <<< message stuff, can skip or ignore
0x0165ED|$05:$A5DD:18      CLC             <<< remove and move addresses accordingly
0x0165EC|$05:$A5DC:A5 00    LDA $00         <<< message stuff, can skip or ignore
0x0165EE|$05:$A5DE:69 30    ADC #$30        <<< message stuff, should be skipped if CLC is removed, affects Carry
0x0165F0|$05:$A5E0:A8      TAY             <<< ?
0x0165F1|$05:$A5E1:B1 7A    LDA ($7A),Y     <<< ?
0x0165F3|$05:$A5E3:38      SEC             <<< carry = 1
0x0165F4|$05:$A5E4:E9 C0    SBC #$C0        <<< carry is 0 or 1 depending on results, affects Carry
0x0165F6|$05:$A5E6:9D BA 7F STA $7FBA,X ; start of list of string IDs battle messages  <<< we want to skip this with our branch
0x0165F9|$05:$A5E9:E6 AD    INC $AD    ; battle message index                         <<< we want to skip this with our branch
0x0165FB|$05:$A5EB:68      PLA             <<< just gives us spell exp back from stack into A
0x0165FC|$05:$A5EC:A8      TAY             <<< just gives us spell exp back from A into Y
0x0165FD|$05:$A5ED:A9 00    LDA #$00        <<< would like to BCS here, but Carry flag is a problem
; control flow target (from $A5C9)
0x0165FF|$05:$A5EF:91 7E    STA ($7E),Y

EDIT: So a pal has helped me better understand what's happening, and the issue I now face is applying a similar fix to that which I used for the weapon messages. The issue of branching is that at the end of everything, Carry = 1 for sure (used a BCS). However, the final decision as to what Carry SHOULD equal without my fix is SBC #$C0. Applying a B0 13 to the bytes I freed up by removing the TXA and CLC near the top (similar to my weapon fix), causes the game to crash with graphics bugs at the end of my save stated test battle where 1 spell and 2 weapons should go up. The weapons work fine, no battle message, but I'm not certain what ramifications if any that will have with the carry flag being set to 1, as there's an ADC in that routine as well. It goes to the overworld and the game carries on fine. So the issue for now is with spells. I need to adjust my branch or figure out a way to determine what the carry flag SHOULD be at the end of my branch, so as to emulate its pre-fix behavior.

I decided to test the pre-fix weapon flag situation, and my in-progress fix. At the same state, leaving battle and going into the overworld in both, Carry = 1. So it doesn't seem to be a problem for Carry to be set in the weapon fix when weapons level after spells level. Now what impact this might have going to ANOTHER character leveling, I haven't tested. I'm not sure if I have to have some sort of way to set the carry based on what came before, or if I should just opt for a CLC or what.
« Last Edit: January 19, 2020, 05:00:20 pm by redmagejoe »

abw

  • Sr. Member
  • ****
  • Posts: 331
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #98 on: January 18, 2020, 04:04:44 pm »
I don't think it's even remotely feasible for a person to be in a situation where a battle lasts long enough for a single character to attack 255 times, but I will add it to the list nonetheless.
The current behaviour also means that even if you earned enough experience to gain multiple levels, you can only gain one skill level and then you lose any excess experience. I'm not sure whether that should count as a bug or not.

Were your comments on the gold bytes supposed to be 10,000,000, and 9,999,999 when mentioning the bugs? Right now they say 999,999.
Ha, yeah, I guess it was past my bedtime when I wrote that :P. Gold is definitely supposed to be capped at 7 digits for display, so 9,999,999 max. I've updated my file to fix that and added some commenting on the weapon/spell level up code.

Based on your comments, is it safe to assume that I can NOP out all 9 bytes, not only those 2 in the BCS that you said can be removed, but the next 7 in the following block that shouldn't ever be executed with that change? Or should I just leave them alone?
No, you can leave those alone. $0F:$EFEA/$0F:$EFEC are the only instructions that actually need fixing; $0F:$EFF5/$0F:$EFF7 are merely inefficient, not incorrect.

I just wonder if one can JSR to another bank? Seems I can, and it worked!
Indeed you can, as long as both banks are visible in RAM at the same time. There's a whole bunch of memory mapper stuff to care about in the general case, but for FF2 it's safe to make calls to places in the fixed bank ($0F, loaded into $C000-$FFFF). If you find you need more space in bank 5, you could free up 17 bytes by doing it this way instead (NOP out anything you don't need):
Code: [Select]
$05:$A4C9:
LDA $7CC0
STA $80
LDA $7CC1
STA $80
JSR $EFCF

In the meantime, I figured out how to stop Weapon Level Up messages from occurring on maxed weapons.
Good job!

I'd like to have exp not tally at all on maxed weapons, but that would involve more space most likely.
I decided to take a stab at this myself, and I think I managed to accomplish avoiding both the fake level up spam messages and increasing experience on maxed weapons as well as finding and fixing this bug:
Possible underflow of weapon experience for Weapon Skills >= 14. A single attack instantly level them if attacking a Rank 1 enemy formation, perhaps because of the Weapon Skill experience malus. Investigate and prevent possible underflow.
The problem code is this:
Code: [Select]
0x016975|$05:$A965:A5 52    LDA $52    ; combined usage + base + battle rank bonus
0x016977|$05:$A967:E5 53    SBC $53    ; skill level; growth barrier to overcome
0x016979|$05:$A969:E9 0A    SBC #$0A    ; base growth barrier
0x01697B|$05:$A96B:90 24    BCC $A991  ; not enough growth => no experience gain
In battles with high skill levels, low weapon usage counts, and low battle ranks, $52 - $53 underflows, but there is no check for that, so SBC #$0A operates on a high value of A (e.g. #$FF) and does not underflow, which means C stays set, the BCC is not taken, and your skill levels up. Check it out!

For patching weapon levels, I took the
Code: [Select]
LDA ($7E),Y ; skill level
TAX
INX     ; INC skill level
JSR $A86D ; cap X at 15
from the section for increasing the skill level and moved it up to where we already had the skill level loaded (the first LDA ($7E),Y); since X doesn't change between there and where we use it, we don't need to LDA ($7E),Y anymore, which frees up 2 bytes to use for a BCS after the JSR $A86D to skip all the level up and messaging when we're already at the level cap. The same idea should work in the spell level up code too.

redmagejoe

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: Final Fantasy II Restored
« Reply #99 on: January 18, 2020, 04:29:02 pm »
The current behaviour also means that even if you earned enough experience to gain multiple levels, you can only gain one skill level and then you lose any excess experience. I'm not sure whether that should count as a bug or not.

You should only be able to get 1 level per hand per battle. If you dual wield, the routine runs twice, so you get double the experience in the base game. It's fine to leave this as it is.

No, you can leave those alone. $0F:$EFEA/$0F:$EFEC are the only instructions that actually need fixing; $0F:$EFF5/$0F:$EFF7 are merely inefficient, not incorrect.

I already NOPd them out in my gold fix IPS because nothing else seemed to point to that code. Is it fine for them to have been NOP'd? I didn't see any way that it could be used after all the branches basically got cut off from it, leaving those 7 bytes isolated. Let me know if I should revert those NOPs

I decided to take a stab at this myself

I'm taking a look at what you've got and going to compare it to my fix, but something tells me yours is cleaner and more efficient. I've been banging my head over how to move things around, and I was just about to try to move

Code: [Select]
0x01698E|$05:$A97E:91 7E    STA ($7E),Y ; write the new skill level
0x016990|$05:$A980:C8      INY        ; offset for skill experience

into the $A86D like I did with turning the LDX into DEX, and moving the TXA. Because in all three jumps to that routine, those are the common instructions. But then I was trying to think about shifting code up to expand the room in that subroutine, and making sure branch pointers lined up, and using the 3 more bytes I'd created for myself... It got headachey, so yours is probably more efficient. I'll compare them and more than likely incorporate yours or throw mine out outright. If nothing else I feel like I've learned from my efforts on that particular issue, so it won't be a waste even if I use yours in lieu of mine.

Reformatted it for side-by-side comparison. First is original, second is yours.

Code: [Select]
0x01696C|$05:$A95C:85 0A    STA $0A    ; weapon type
0x01696E|$05:$A95E:0A      ASL        ; skill data is stored as 1 byte level followed by 1 byte experience
0x01696F|$05:$A95F:A8      TAY        ; offset for skill level
0x016970|$05:$A960:B1 7E    LDA ($7E),Y ; skill level
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+++++++++++++++++++++++++++++++++++++++++
0x016972|$05:$A962:85 53    STA $53    ; skill level
0x016974|$05:$A964:38      SEC       
0x016975|$05:$A965:A5 52    LDA $52    ; combined usage + base + battle rank bonus
0x016977|$05:$A967:E5 53    SBC $53    ; skill level; growth barrier to overcome
+++++++++++++++++++++++++++++++++++++++++
0x016979|$05:$A969:E9 0A    SBC #$0A    ; base growth barrier
0x01697B|$05:$A96B:90 24    BCC $A991  ; not enough growth => no experience gain
0x01697D|$05:$A96D:18      CLC       
0x01697E|$05:$A96E:C8      INY        ; offset for skill experience
0x01697F|$05:$A96F:71 7E    ADC ($7E),Y ; skill experience value
0x016981|$05:$A971:C9 64    CMP #$64   
0x016983|$05:$A973:90 1A    BCC $A98F  ; if skill experience < 100, just update experience
0x016985|$05:$A975:88      DEY        ; offset for skill level
0x016986|$05:$A976:B1 7E    LDA ($7E),Y ; skill level
0x016988|$05:$A978:AA      TAX       
0x016989|$05:$A979:E8      INX        ; INC skill level
0x01698A|$05:$A97A:20 6D A8 JSR $A86D  ; cap X at 15
0x01698D|$05:$A97D:8A      TXA       
0x01698E|$05:$A97E:91 7E    STA ($7E),Y ; write the new skill level
0x016990|$05:$A980:C8      INY        ; offset for skill experience
0x016991|$05:$A981:A6 AD    LDX $AD    ; battle message index
0x016993|$05:$A983:18      CLC       
0x016994|$05:$A984:A5 0A    LDA $0A    ; weapon type
0x016996|$05:$A986:69 6C    ADC #$6C   
0x016998|$05:$A988:9D BA 7F STA $7FBA,X ; start of list of string IDs battle messages
0x01699B|$05:$A98B:E6 AD    INC $AD    ; battle message index
0x01699D|$05:$A98D:A9 00    LDA #$00    ; reset skill experience to 0
0x01699F|$05:$A98F:91 7E    STA ($7E),Y ; write the new skill experience
0x0169A1|$05:$A991:60      RTS

Code: [Select]
0x01696C|$05:$A95C:85 0A    STA $0A    ; weapon type
0x01696E|$05:$A95E:0A      ASL        ; skill data is stored as 1 byte level followed by 1 byte experience
0x01696F|$05:$A95F:A8      TAY        ; offset for skill level
0x016970|$05:$A960:B1 7E    LDA ($7E),Y ; skill level
0x016972|$05:$A962:AA TAX ; precalculate level + 1
0x016973|$05:$A963:E8      INX        ; INC skill level
0x016974|$05:$A964:20 6D A8 JSR $A86D  ; cap X at 15
0x016977|$05:$A967:B0 28 BCS $A991 ; if we hit the cap, don't increase level or experience
0x016979|$05:$A969:85 53 STA $53 ; skill level
-----------------------------------------
0x01697B|$05:$A96B:A5 52    LDA $52    ; combined usage + base + battle rank bonus
0x01697D|$05:$A96D:E5 53    SBC $53    ; skill level; growth barrier to overcome
0x01697F|$05:$A96F:90 20 BCC $A991 ; not enough growth => no experience gain
>>0x016981|$05:$A971:E9 0B SBC #$0B ; base growth barrier; C is set so this is equivalent to CLC SBC #$0A
0x016983|$05:$A973:90 1C BCC $A991 ; not enough growth => no experience gain
0x016985|$05:$A975:18 CLC
0x016986|$05:$A976:C8      INY        ; offset for skill experience
0x016987|$05:$A977:71 7E    ADC ($7E),Y ; skill experience value
0x016989|$05:$A979:C9 64    CMP #$64   
0x01698B|$05:$A97B:90 12    BCC $A98F ; if skill experience < 100, just update experience
0x01698D|$05:$A97D:88      DEY        ; offset for skill level
-----------------------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
0x01698E|$05:$A97E:8A      TXA
0x01698F|$05:$A97F:91 7E    STA ($7E),Y ; write the new skill level
0x016991|$05:$A981:C8      INY        ; offset for skill experience
0x016992|$05:$A982:A6 AD    LDX $AD    ; battle message index
-----------------------------------------
0x016994|$05:$A984:A5 0A    LDA $0A    ; weapon type
>>0x016996|$05:$A986:69 6B ADC #$6B ; C is set so this is equivalent to CLC ADC #$6C
0x016998|$05:$A988:9D BA 7F STA $7FBA,X ; start of list of string IDs battle messages
0x01699B|$05:$A98B:E6 AD    INC $AD    ; battle message index
0x01699D|$05:$A98D:A9 00    LDA #$00    ; reset skill experience to 0
0x01699F|$05:$A98F:91 7E    STA ($7E),Y ; write the new skill experience
0x0169A1|$05:$A991:60      RTS

It definitely looks like you made this WAY more efficient than I had the knowledge to attempt to do. Or at least, it would have taken me days to figure out how to get to this point. You removed an unnecessary CLC and SEC because of more efficient use of ADC and SBC, eliminated an LDA with clever placement within program flow, and then moved the entire TAX INX JSR block up to set us up for way more efficient checking of weapon experience, levels, and then set the TXA later. I wish I had the knowledge to have thought of this, but I'm always afraid to touch any LDA or STA because of my lack of full understanding of what exactly they're calling from and storing into memory. Awesome, tested and it works great. Even makes sure that a Level 16 weapon will always display as being at 00 experience, which addresses another project I was afraid I'd have to tackle. This is way better than what I could have come up with, abw! :o

I'm currently mocking up a solution for spell experience/levels based on the example you provided here. Would you be so kind as to review it for errors? Same as before, first block is original, second block is mockup in format that allows the two to be compared side-by-side.

Code: [Select]
0x0165C5|$05:$A5B5:B1 7E    LDA ($7E),Y ; spell level
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+++++++++++++++++++++++++++++++++++++++++
0x0165C7|$05:$A5B7:18      CLC       
0x0165C8|$05:$A5B8:69 0A    ADC #$0A    ; base growth penalty
0x0165CA|$05:$A5BA:85 54    STA $54    ; total spell growth penalty (= spell level + 10)
0x0165CC|$05:$A5BC:38      SEC       
0x0165CD|$05:$A5BD:A5 53    LDA $53    ; total spell use credit
0x0165CF|$05:$A5BF:E5 54    SBC $54    ; total spell growth penalty (= spell level + 10)
+++++++++++++++++++++++++++++++++++++++++
0x0165D1|$05:$A5C1:C8      INY        ; offset for spell experience
0x0165D2|$05:$A5C2:90 2D    BCC $A5F1  ; if credit < penalty, no growth, otherwise grow by the remainder
0x0165D4|$05:$A5C4:18      CLC       
0x0165D5|$05:$A5C5:71 7E    ADC ($7E),Y ; spell experience
0x0165D7|$05:$A5C7:C9 64    CMP #$64   
0x0165D9|$05:$A5C9:90 24    BCC $A5EF  ; if experience < 100, just update experience, otherwise increase lvl
0x0165DB|$05:$A5CB:88      DEY        ; offset for spell level
0x0165DC|$05:$A5CC:B1 7E    LDA ($7E),Y ; spell level
0x0165DE|$05:$A5CE:AA      TAX       
0x0165DF|$05:$A5CF:E8      INX        ; increase spell level
0x0165E0|$05:$A5D0:20 6D A8 JSR $A86D  ; cap X at 15
0x0165E3|$05:$A5D3:8A      TXA       
0x0165E4|$05:$A5D4:91 7E    STA ($7E),Y ; write the new spell level
0x0165E6|$05:$A5D6:C8      INY        ; offset for spell experience
0x0165E7|$05:$A5D7:98      TYA       
0x0165E8|$05:$A5D8:48      PHA        ; save offset for spell experience
0x0165E9|$05:$A5D9:A6 AD    LDX $AD    ; battle message index
0x0165EB|$05:$A5DB:18      CLC       
0x0165EC|$05:$A5DC:A5 00    LDA $00    ; counter for number of spell slots processed
0x0165EE|$05:$A5DE:69 30    ADC #$30    ; spell IDs start at #$30 within character stat data
0x0165F0|$05:$A5E0:A8      TAY        ; offset for spell ID
0x0165F1|$05:$A5E1:B1 7A    LDA ($7A),Y ; spell ID
0x0165F3|$05:$A5E3:38      SEC       
0x0165F4|$05:$A5E4:E9 C0    SBC #$C0   
0x0165F6|$05:$A5E6:9D BA 7F STA $7FBA,X ; start of list of string IDs battle messages
0x0165F9|$05:$A5E9:E6 AD    INC $AD    ; battle message index
0x0165FB|$05:$A5EB:68      PLA        ; restore offset for spell experience
0x0165FC|$05:$A5EC:A8      TAY       
0x0165FD|$05:$A5ED:A9 00    LDA #$00    ; reset spell experience to 0
0x0165FF|$05:$A5EF:91 7E    STA ($7E),Y ; write the new spell experience
0x016601|$05:$A5F1:E6 44    INC $44    ; offset for current character's current spell slot use counter

Code: [Select]
0x0165C5|$05:$A5B5:B1 7E    LDA ($7E),Y ; spell level
0x0165C7|$05:$A5B7:AA      TAX       
0x0165C8|$05:$A5B8:E8      INX        ; increase spell level
0x0165C9|$05:$A5B9:20 6D A8 JSR $A86D  ; cap X at 15
0x0165CC|$05:$A5BC:B0 33 BCS $A5F1   ; if we hit the cap, don't increase level or experience
-----------------------------------------
>>0x0165CE|$05:$A5BE:69 09    ADC #$09    ; base growth penalty
0x0165D0|$05:$A5C0:85 54    STA $54    ; total spell growth penalty (= spell level + 10)
0x0165D2|$05:$A5C2:38      SEC       
0x0165D3|$05:$A5C3:A5 53    LDA $53    ; total spell use credit
0x0165D5|$05:$A5C5:E5 54    SBC $54    ; total spell growth penalty (= spell level + 10)
0x0165D7|$05:$A5C7:90 28 BCC $A5F1 ; not enough growth => no experience gain
0x0165D9|$05:$A5C9:C8      INY        ; offset for spell experience
0x0165DA|$05:$A5CA:90 25    BCC $A5F1  ; if credit < penalty, no growth, otherwise grow by the remainder
0x0165DC|$05:$A5CC:18      CLC       
0x0165DD|$05:$A5CD:71 7E    ADC ($7E),Y ; spell experience
0x0165DF|$05:$A5CF:C9 64    CMP #$64   
0x0165E1|$05:$A5D1:90 1C    BCC $A5EF  ; if experience < 100, just update experience, otherwise increase lvl
0x0165E3|$05:$A5D3:88      DEY        ; offset for spell level
-----------------------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
0x0165E4|$05:$A5D4:8A      TXA       
0x0165E5|$05:$A5D5:91 7E    STA ($7E),Y ; write the new spell level
0x0165E7|$05:$A5D7:C8      INY        ; offset for spell experience
0x0165E8|$05:$A5D8:98      TYA       
0x0165E9|$05:$A5D9:48      PHA        ; save offset for spell experience
0x0165EA|$05:$A5DA:A6 AD    LDX $AD    ; battle message index
-----------------------------------------
0x0165EC|$05:$A5DC:A5 00    LDA $00    ; counter for number of spell slots processed
>>0x0165EE|$05:$A5DE:69 2F    ADC #$2F    ; spell IDs start at #$30 within character stat data
0x0165F0|$05:$A5E0:A8      TAY        ; offset for spell ID
0x0165F1|$05:$A5E1:B1 7A    LDA ($7A),Y ; spell ID
0x0165F3|$05:$A5E3:38      SEC       
0x0165F4|$05:$A5E4:E9 C0    SBC #$C0   
0x0165F6|$05:$A5E6:9D BA 7F STA $7FBA,X ; start of list of string IDs battle messages
0x0165F9|$05:$A5E9:E6 AD    INC $AD    ; battle message index
0x0165FB|$05:$A5EB:68      PLA        ; restore offset for spell experience
0x0165FC|$05:$A5EC:A8      TAY       
0x0165FD|$05:$A5ED:A9 00    LDA #$00    ; reset spell experience to 0
0x0165FF|$05:$A5EF:91 7E    STA ($7E),Y ; write the new spell experience
0x016601|$05:$A5F1:E6 44    INC $44    ; offset for current character's current spell slot use counter

I think it works. Testing the underflow control on your patch and mine. Underflow protection works great, testing overflow protection now. Will also add overflow protection for the 5 combat counters to my to-do list, as all 5 are vulnerable to overflow. Finished testing spells and weapons both with the (Rank + Attacks - Level - 1) and (Rank + Spells - Level + 3) formulae. Overflow is possible here, even if the counters were capped at 255, so we need to cap our $52 (in the weapon fix) and $53 (in the spell fix) at $FF. Will have to look further up in that routine above where these snippets for the patches start.

Alternatively, and my personal preference, we could ignore overflow protection here, call this part done, and focus on capping the counters $7CF3-$7CF6 and $7CF7-$7D36 to a number well below 255, say 200 ($C8). There's absolutely no benefit to having any of the counters that high since spells, skills, and stats can only increase by 1 per battle, and it is unnecessary to reach 200 to attain any of these increases.
« Last Edit: January 19, 2020, 05:00:39 pm by redmagejoe »