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

Poll

Patching Mysidian Tower Orbs Behavior: http://www.romhacking.net/forum/index.php?topic=29704.msg388989#msg388989

Leave it, flawed as it is; the remakes retained this behavior after all
3 (13.6%)
Keep the random single-character bonus, but remove the 4th character from the RNG
4 (18.2%)
Grant the bonus to all characters, giving them a much-earned buff at this point in the game.
15 (68.2%)

Total Members Voted: 22

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

redmagejoe

  • Full Member
  • ***
  • Posts: 218
    • View Profile
Re: Final Fantasy II Restored
« Reply #40 on: December 31, 2019, 01:39:18 am »
Wow, that was fast. Good job!

So I don't need to keep going through this code to comment and sort it out more? XD I was making progress, but I'm exhausted.

What else was there that needed fixing and doesn't involve this horribly convoluted stat leveling system...

I won't stop you if you have the wherewithall. That said, I've organized the bug list in the first post, and there's a number of other things that could be addressed. As for fast, I had a good idea what I was looking for already, I was just in need of a map to navigate it. Most of what I figured out was through deductive reasoning and chasing down bread crumbs. The AA3A thing helped a lot since those JSRs were bothering me. Having every line labelled with the original bytes along with the actual code made it a lot easier for me to chase the code around. Still working within the stats/combat, Weapon Skills, Spells Levels, and HP/MP are still worth a look. At the very least HP/MP, since it has no cap and really needs one.

Code: [Select]
Line 2875    LDA statup_confirmation,X ; not totally sure... its 3, 2, 2, 2, 2, 2, 2, 2 after just wailing on some hornets.

I've just started browsing, but that reads like possibly Weapon Skill progress. The first attack would get the bonus based on Weapon Level and Battle Rank, while all the others would be the normal progress.



Also, if it's of any help dealing with combat stuff, I diffed the original ROM against one with only the target cancel exploit fix. I'll put the differences here in the hopes it might help with labeling in the disassembly.

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

Bear in mind that may not be a perfect fix, as stated by https://www.romhacking.net/reviews/1267/#review but it might be beneficial. Who knows, maybe we could even improve this fix to make it not cause its own bugs. I'll go ahead and list the B Button Dash changes, minor as they are, here as well. Again, these are all against the unmodified Japanese ROM.

Code: [Select]
0x3D4F3: 85 32 85 34 => 20 50 F7 EA
0x3F760: 00 00 00 00 00 00 00 00 00 00 00 00 00 => 85 32 85 34 A5 20 29 40 F0 02 E6 34 60
« Last Edit: January 19, 2020, 04:55:43 pm by redmagejoe »

abw

  • Sr. Member
  • ****
  • Posts: 372
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #41 on: December 31, 2019, 11:59:14 pm »
Wow, I see it's been a busy week here in FF2 land :D. Congrats on all the progress!

One thing to really look out for is places where the game code is NOT USED. Garbage data and code chunks that never get read during a complete playthrough. The more of those there are, the more room there is to fix stuff!
A word of caution about this, however: even with CDL data, just because you never managed to get the game to execute a section of code or read a chunk of data doesn't mean that those sections are actually unused. Did you really collect every single item in the game and use it in every way possible? Did you hit every single min/max condition and explore every possible path in the game? Conversely, sometimes games end up reading data that they either don't need to read (e.g. scanning too far beyond the end of a list) or don't particularly care about (e.g. reading "random" bytes as part of RNG operation). Proving that something is not used can be tricky.

I must be an idiot, because I cannot for the life of me figure out how to use the address that code is loaded from RAM to determine where to find the routines within ROM. Is there a correlation or do I need to actually view the addresses that fire the breakpoints within the context of the debug tools in the emulator?
In addition to Cyneprepou4uk's advice, you can also translate RAM addresses into ROM addresses with some basic arithmetic, which works even without the debugger. For RAM addresses that get mapped to ROM (anything in the $8000 - $FFFF range), when you know the RAM address and which bank it gets mapped to, the general equation is:

[ROM address] = [ROM bank number] * [bank size] + [RAM address] - [RAM bank number] * [bank size] - $8000 + $10

(and if you don't know which ROM bank a RAM address comes from, then you can use the same equation to get a list of possible addresses by trying different ROM bank numbers). FF2 uses a bank size of $4000 bytes and $A898 is in RAM bank 0 (the $8000 - $BFFF range), so for 05:A898 that would be [$05] * [$4000] + [$A898] - [$00] * [$4000] - $8000 + $10 = $168A8.

For 6110, 6113, and 6114 only, there should be another routine off of those similar to what's listed above that should deal with 6113, 6112, and 6110 respectively.
This actually happens later on in the same routine.

I should also look into what it's doing the moment a command is issued to see what's being incremented for tallying up number of uses for the formula which determines a stat up.
I'm guessing this is what gets stored in the data structure starting at $7A48 that's referenced by the routine at $FD11.

What I'm seeing is that its reading one version of Intelligence ($6113), then writing to the OTHER one ($6123). So one set is a backup...? I noticed in the status menu it only reads one set. I assume the other set is kind of temporary for battle spells do alter. Like if there's a spell that doubles strength, you don't want strength to STAY doubled after battle, so it reads from that, but then leveling up a skill writes to the static set of stats.
I'm not sure what the difference between these two copies are, other than that xx2x seems more "permanent" than xx1x. 6120 is holding 109, which is made possible with 99 Strength + 10 from a piece of equipment
The wiki notes indicated that the lower address (e.g. $6110) holds the character's base stats and the higher address (e.g. $6120) holds the character's effective stats after equipment modifications etc., which seems to agree with your results here.

Looking back with a better understanding of context, I would assume that INX instruction is Increment X, and that is what is increasing the stat before even doing the CMP for 100/99.
Yup. I actually had that part filled in for you already (latest version fixes my earlier min/max typo and incorporates some more of Jiggers' notes), though I guess I could have said "increment" instead of "INC" :P:
Code: [Select]
0x0168A6|$05:$A896:A4 47    LDY $47    ; offset for base stat to raise
0x0168A8|$05:$A898:B1 7A    LDA ($7A),Y ; read the base stat
0x0168AA|$05:$A89A:AA      TAX       
0x0168AB|$05:$A89B:E8      INX        ; INC the base stat
0x0168AC|$05:$A89C:8A      TXA       
0x0168AD|$05:$A89D:20 3A AA JSR $AA3A  ; set A = min(A, 99)
0x0168B0|$05:$A8A0:91 7A    STA ($7A),Y ; write the new base stat

I will do my very best to break down everything I can about stats and combat,
[...]
It has a 1 in 5 chance of decreasing by 1 at the end of a battle
Do we know whether stat changes actually involve any randomness? At the moment I'm not seeing anything in the stat change code that looks like a random number.

I don't really know how to parse the file abw shared either, but that's basically the next step I guess? Mostly I just can't tell if there's missing data in it or not. Like at the very end of it, it says, "; ... skipping $11 00 bytes".
Oh, sorry, I had hoped that was largely self-explanatory, but I have a habit of forgetting that not everybody has the same background knowledge/assumptions as me :P. The basic format is 0x[ROM file address, including iNES header]|$[ROM bank]:$[RAM address]:[byte(s) at that address] and then if the address is code, there's the disassembly of the byte(s) at that address. ; starts a comment which continues to the end of the line, and bytes that aren't known to be code or data are followed by an attempted disassembly as a comment (e.g.
Code: [Select]
0x03FD28|$0F:$FD18:D0 2B    ; BNE $FD45 
). For bytes that I've marked as unused, consecutive runs of the same byte are condensed with a comment showing how long the run of identical bytes is, because who wants to look at 16,384 lines of FF bytes? This version doesn't have labels and isn't re-assemblable, but it does mark a bunch of control flow and data load targets, which makes reasoning about blocks of code much much safer.

Just so much commentary to sift through to see what's really there.
Yup, definitely a lot to go through. At some point a table of contents or something would be a nice addition!

Final Update: Bank 05 is up and running in Github. Mesen didn't have a chance to run all the code so it made some bad guesses, we'll fix 'em up when we get there.
Starting from taotao's CDL file, I marked around $700 extra bytes as code (I think I got most of the missing code) and $1400 extra bytes as data (there's probably a lot more data to go). In bank 5, the first disassembly error I noticed was at $05:$A02A, which is actually code. I see a lot of places in the bank 5 stat code alone where, rather than using a pointer table, the game loads the low and high bytes of a pointer as immediate values and writes them to adjacent RAM addresses in order to make a pointer. Those are going to be annoying to track down and label :/.

Going to use the table file I made and see if I can spot some text data and mark that down.
I marked all of bank 6 as dialogue but haven't gone looking for other text (item/monster names, menus, etc.). It looks like the main dialogue incorporates some script engine stuff for working with the keyword system.

actually upon searching through that bank_05 disassembly, there's not enough CMP #$64s or JSR AA3As to suggest there's a routine for each stat. I think it's safe to assume that this section handles all stat increases
Sort of. As I said earlier:
$05:$A874-$05:$A903 is the code for increasing Spirit/Strength/Intellect at the expense of Strength/Intellect/Stamina
but there are other routines for handling other stats. Another approach to this problem would be to take the calls for increasing one stat at the expense of another and switch them to call the routine for increasing Agility/Stamina/M. Power, which appears to live at $05:$A904-$05:$A95B.

redmagejoe

  • Full Member
  • ***
  • Posts: 218
    • View Profile
Re: Final Fantasy II Restored
« Reply #42 on: January 01, 2020, 11:49:07 am »
I will keep that arithmetic method in mind, abw. Thank you for the advice.

Quote
I'm guessing this is what gets stored in the data structure starting at $7A48 that's referenced by the routine at $FD11.
Quoting this, sounds important.

Quote
The wiki notes indicated that the lower address (e.g. $6110) holds the character's base stats and the higher address (e.g. $6120) holds the character's effective stats after equipment modifications etc., which seems to agree with your results here.
The question then is, is the routine that I fixed checking the higher address without the bonus before performing the increment? Or is it done in such a way that 89 + Strength would never increase because for the purposes of the comparison, it's already 99? This brings up the question that Jiggers posed about the A9 stat, and whether it's intentional for a stat to be able to go above 99 in this version of the game even with equipment. The game uses it as attack power goes up with the 109 Strength, the menu displays it, albeit in a way that suggests it shouldn't, and there's nothing in place at time of equipping that seems to cap Strength (or other stats) at 99. May be something to think about. Right now though, my only concern is if both copies of the stat are being checked by the CMP #$63 / #$64, and if so, does my fix break anything? I forgot I tested before with both 99 and A9 Strength, and just now I tested on my A7 Strength Guy, who gained a Strength (and of course lost Intellect, because that's my luck). It appears to strip away equipment bonuses on the second copy of the stat to get its base value before doing any comparisons or math, so this patch will play nice with the current behavior of the game, equipment bonus, and the higher stat.

Quote
Do we know whether stat changes actually involve any randomness? At the moment I'm not seeing anything in the stat change code that looks like a random number.
I am simply going off the sources I provided in my first post, but having played the game from start to finish and min-maxing, I can tell you that at the very least there is no guarantee that a stat will go down if its counterpart goes up, and much like the chance for the stat to go up even if you only attacked once (when the range is between 0 and 45, 46 attacks guaranteeing Strength up), there is a chance for the other stat to go down with minimal issued orders to correlate, while at other times I have done a guaranteed (46 attacks) stat up criteria, but did not lose the stat. There must be randomness involved in some way.

Admittedly, I have not test Agility or Stamina increasing to see if that fix applied to them, but I'm assuming what you're saying is that only Spirit, Strength, and Intellect are handled by that routine? I have 99 Stamina on my characters, so I'll drop them all to 1 HP in a battle and see if Stamina goes up still.

Quote
but there are other routines for handling other stats. Another approach to this problem would be to take the calls for increasing one stat at the expense of another and switch them to call the routine for increasing Agility/Stamina/M. Power, which appears to live at $05:$A904-$05:$A95B.
I don't want to completely remove the see-saw stats mechanic (as a sort of EasyType patch), but rather simply make it feasible in the face of this mechanic to "lock down" your stats if min-maxed in the appropriate order. Plus it makes my fix a little easier (I think?) with minimal changes. Though I'm sure you'll tell me that it's as simple as changing a pointer or JMP or Branch and then I'll feel dumb having just said that.



Currently testing Stamina with the patch. No easy way to test Agility without knowing where the math is being done on its 0-255 check, and my M.Power is nowhere near 99 on any of my characters. Will report my findings soon. There appears to be a very critical bug in my fix, though I have no idea what, in particular, it correlates to. Stamina does indeed not go up above 99, however, on a character not necessarily the one taking the pummeling, a "Red down" message will appear, and the file in that save slot appears to be erased. This can be remedied by saving again before powering off into any slot, but it is an issue. In the first case, Maria was the one having her Stamina tested, being at 99, and her Stamina did not go up, but her HP did (above 9999 of course), and she got a "Red down" stat message. Reverting to the previous save, I then tried the same on Firion, who was at 98 Stamina, and his Stamina and HP increased without issue. In the next battle, I did the same. No Stamina up, HP went up, and Guy got "Red down". Anyone have any idea how this happened?

EDIT: Oh shit, I did my decimal to hex conversion wrong. The first branch should skip $66 (102), not $69 (105). Fixed and testing... Still inexplicably getting the "Red down" message, but I can't figure out specifically what's causing it. I'm at a loss as to what new problem i've created with this change. I got M.Power up on Maria and Guy, and "Red down" (and of course the save file deleted) on Guy. I never even used MP or lost MP, so clearly I've tampered with the control flow in a way that's causing important shit not to get done. Perhaps branching so far ahead skipped other important instructions?

EDIT2: Seems I didn't fully grasp the math involved in branching. It appears that the value needs to be one byte less than you actually want to move. So $00 would be the byte directly after the $00 byte, so I added 1 too much to the branch bytes, so it was skipping into a different routine. By adjusting the values to $65 and $32, they SHOULD branch to the RTS instruction now... Testing.
« Last Edit: January 19, 2020, 04:55:53 pm by redmagejoe »

Jiggers

  • Sr. Member
  • ****
  • Posts: 307
    • View Profile
    • My Ko-Fi Page
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #43 on: January 01, 2020, 05:53:22 pm »
This version doesn't have labels and isn't re-assemblable, but it does mark a bunch of control flow and data load targets, which makes reasoning about blocks of code much much safer.

That's the part I have trouble parsing actually. Its just so much info to sift through!

I need to take a break from this for a while, but I'll keep checking up. Want to make sure other people can edit the Github stuff if needed. I don't remember if there's something special I have to do to give permission...?
I know exactly what I'm doing. I just don't know what effect it's going to have.

I wrote some NES music! Its a legal ROM file. - I got a Ko-Fi page too.

redmagejoe

  • Full Member
  • ***
  • Posts: 218
    • View Profile
Re: Final Fantasy II Restored
« Reply #44 on: January 01, 2020, 06:31:28 pm »
It appears that my little math error was the issue, and it should work now. I've been testing extensively. I'm also testing it with Stamina, because even though it doesn't have a decrement counterpart, it seems like this routine is used for all six main stats. Are you sure that a different routine handles Stamina, M.Power, and Agility, abw?

Anyway, going to do a little more testing before I update the IPS and put up a new link. Extended testing shows that no matter how much damage done to my 99 Stamina characters (start at 9999+, drop down to 1), they only gain HP but never Stamina at the end of battle with my fix applied. Also it appears I've ironed out the branching too far issue. This patch should now prevent Strength, Agility, Stamina, Intellect, Spirit, and M.Power from going above 99. A new link will be live soon.



I need to take a break from this for a while, but I'll keep checking up. Want to make sure other people can edit the Github stuff if needed. I don't remember if there's something special I have to do to give permission...?

Thank you for all your awesome work, Jiggers. Take your time. I'll do my best to make use of it and get my inexperienced head into it for more bug fixes to show I appreciate it. :thumbsup:



My current priority is finding where HP and MP up is handled, as that's the MORE broken part of the game. Sure the stat ups were annoying, but a value that can actually overflow and causes display bugs is problematic. Obviously this will be a high byte, low byte situation so...

Code: [Select]
;; sub start ;;

CheckSomeStatStuff:
    JSR L3FD46                ; Waits for a sprite 0 hit to do battle message stuff.
A47F:
    LDX #$00                  ; Set X to 0 and clear these variables
    STX entity_counter
    STX tmp
    STX tmp+1
    STX tmp+2
    STX tmp+3
    LDX #$08                  ; check the byte AFTER 8 $FFs
   
@Loop:
    LDA statup_confirmation,X ; not totally sure... its 3, 2, 2, 2, 2, 2, 2, 2 after just wailing on some hornets.
    CMP #$FF                  ; if it equals, jump to incementing X...
    BEQ :+                   
    AND #$7F                  ; otherwise, cut off the high bit
    TAY                       ; and transfer to Y
    LDA $B700,Y               ; get the resulting byte from this LUT in ROM
    CLC                     
    ADC tmp                   ; add tmp and save
    STA tmp                 
    LDA tmp+1                 
    ADC #$00                  ; add carry to tmp+1
    STA tmp+1               
    INC tmp+2                 ; tmp+2 must be a counter, add 1 here

  : INX                       ; add 1 to X (started at 8)
    CPX #$10                  ; so only do 8 iterations of this loop
    BNE @Loop     
   
;; after all that, tmp and tmp+1 is a 16-bit digit, made up of numbers from $B700, added up
;; this next routine divides tmp and tmp+1 by tmp+2 and tmp+3.
   
    JSR DoDivision            ; Scary division routine in Bank F
    LDA tmp+4                 ; tmp+4 is the low byte result
    STA division_result       ; back it up here
    LDX #$08
    STX tmp+$C                ; set another counter... X is gonna get clobbered

;; Go through the list again...

Actually, 16-bit digit... Is it possible this is HP and/or MP? For context, the battle keeps track of how much HP you started the battle with, and then uses the difference between your HP at the end of the battle and your HP at the start to determine whether you get an HP increase. Same with MP. So perhaps the 16-bit digit is HP lost/gained over the course of the battle?
« Last Edit: January 19, 2020, 04:56:01 pm by redmagejoe »

abw

  • Sr. Member
  • ****
  • Posts: 372
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #45 on: January 02, 2020, 04:42:11 pm »
I'm guessing this is what gets stored in the data structure starting at $7A48 that's referenced by the routine at $FD11.
Quoting this, sounds important.
Hmm, nope, it turns out that was a wrong guess. Jiggers had already noted $7CF3-$7CF6 as the counters for how many times each character attacks, and the counters for other battle actions (selecting individual spell slots, receiving physical/magical attacks, and selecting black/white magic) continue down to $7D46. $7A48-$7B47 appears to be used for the battle RNG; code starting at $0B:$97D5 fills that range with 256 different values in one of two possible orders, which means the code at $FD11 will be used for getting random numbers.

The question then is, is the routine that I fixed checking the higher address without the bonus before performing the increment? Or is it done in such a way that 89 + Strength would never increase because for the purposes of the comparison, it's already 99? This brings up the question that Jiggers posed about the A9 stat, and whether it's intentional for a stat to be able to go above 99 in this version of the game even with equipment. The game uses it as attack power goes up with the 109 Strength, the menu displays it, albeit in a way that suggests it shouldn't, and there's nothing in place at time of equipping that seems to cap Strength (or other stats) at 99. May be something to think about.
It increments and then caps the base stat, and then increments and caps the effective stat independently. So, if there are any items in the game that give a stat penalty, you could counteract that by over-levelling a stat - the base stat would stay capped at 99, but the penalized effective stat would continue to rise until it also reached the 99 cap.

Based on the level up code, 99 seems to be the intended cap for both base and effective stats; my guess would be that the code for equipping an item (or maybe just the code for displaying stats?) is missing a section for capping stats at 99. Square clearly underestimated how much grinding people would do :P.

I am simply going off the sources I provided in my first post, but having played the game from start to finish and min-maxing, I can tell you that at the very least there is no guarantee that a stat will go down if its counterpart goes up, and much like the chance for the stat to go up even if you only attacked once (when the range is between 0 and 45, 46 attacks guaranteeing Strength up), there is a chance for the other stat to go down with minimal issued orders to correlate, while at other times I have done a guaranteed (46 attacks) stat up criteria, but did not lose the stat. There must be randomness involved in some way.
Yup, that makes sense. The code for decrementing Intellect/Stamina/Strength (starting at $05:$A8D1) only gets executed if the $FD11 RNG returns 0. Similarly, Strength/Intellect/Spirit only get incremented if the value returned from the $FD11 RNG is <= the associated physical/black/white counter.

Are you sure that a different routine handles Stamina, M.Power, and Agility, abw?
Strength/Intellect/Spirit have their own routine for increasing at the expense of Intellect/Stamina/Strength ($05:$A874-$05:$A903). Evasion/Magic Resist also have their own routine ($05:$A842-$05:$A86C), as do HP/MP ($05:$A7E9-$05:$A841), Agility/Stamina/Magic Power ($05:$A904-$05:$A95B), and left/right-hand weapon skills ($05:$A95C-$05:$A991); spell levels don't have a dedicated routine like the others, they're just handled in a loop ($05:$A5A2-$05:$A5F9) that runs between between weapon skills and evasion.

redmagejoe

  • Full Member
  • ***
  • Posts: 218
    • View Profile
Re: Final Fantasy II Restored
« Reply #46 on: January 02, 2020, 04:47:41 pm »
Thank you for all the information you provided. I'm going to start snooping around in those addresses in the disassembly and see if I can't figure out what makes it tick even without comments/names. It would be nice to stop the weapon level-up spam on maxed out weapons. So JSR $FD11 instructions are basically calls to pull from the RNG?

HP/MP ($05:$A7E9-$05:$A841) (Line 3318 in https://github.com/JiggeryPonkery/FF2-Disassembly/blob/master/bank_05.asm)
Code: [Select]
;; sub start ;;
    LDY $44
    LDA ($7A),Y
    STA $00
    INY
    LDA ($7A),Y
    STA $01
    JSR DoDivision
    LDA $04
    STA $4C
    LDA $05
    STA $4D
    LDX #$00
    LDA $4A
    JSR $FD11
    STA $00
    LDA $4C
    STA $02
    LDA $4D
    STA $03
    LDA #$00
    STA $01
    JSR $A9B6
    BCC L16841
    LDY $48
    LDA ($7A),Y
    LDY $44
    CLC
    ADC ($7A),Y
    STA ($7A),Y
    LDA #$00
    INY
    ADC ($7A),Y
    STA ($7A),Y
    LDX $AD
    LDA $44
    CMP #$0A
    BNE A83A
    LDA #$5D
    STA $7FBA,X
    BNE A83F
A83A:
    LDA #$5E
    STA $7FBA,X
A83F:
    INC $AD
L16841:
    RTS

Evasion/Magic Resist also have their own routine ($05:$A842-$05:$A86C) (Line 3367 in https://github.com/JiggeryPonkery/FF2-Disassembly/blob/master/bank_05.asm)
Code: [Select]
;; sub start ;;
    LDY $9E
    LDA ($44),Y
    BEQ L1686C
    CLC
    ADC $22
    ADC $46
    LDY $47
    SBC ($7E),Y
    SBC #$0A
    BCC L1686C
    INY
    ADC ($7E),Y
    CMP #$64
    BCC A86A
    DEY
    LDA ($7E),Y
    TAX
    INX
    JSR $A86D
    TXA
    STA ($7E),Y
    INY
    LDA #$00
A86A:
    STA ($7E),Y
L1686C:
    RTS

Strength/Intellect/Spirit have their own routine for increasing at the expense of Intellect/Stamina/Strength ($05:$A874-$05:$A903) (Line 3402 in https://github.com/JiggeryPonkery/FF2-Disassembly/blob/master/bank_05.asm)
Code: [Select]
;; sub start ;;
    CLC
    LDA $47
    ADC #$10
    STA $48
    CLC
    LDA $4A
    ADC #$10
    STA $4B
    LDX #$00
    LDA $46
    JSR $FD11
    STA $46
    LDY $9E
    SEC
    LDA ($44),Y
    BEQ L16903
    SBC $46
    BCC L16903
    LDY $47
    LDA ($7A),Y
    TAX
    INX
    TXA
    JSR $AA3A
    STA ($7A),Y
    LDY $48
    LDA ($7A),Y
    TAX
    INX
    TXA
    JSR $AA3A
    STA ($7A),Y
    LDX $AD
    LDA $47
    SEC
    SBC #$13
    BEQ L168BD
    BCS L168C1
    LDA #$5F
    BNE L168C3
L168BD:
    LDA #$60
    BNE L168C3
L168C1:
    LDA #$61
L168C3:
    STA $7FBA,X
    INC $AD
    LDX #$00
    LDA $49
    JSR $FD11
    BNE L16903
    LDY $4A
    LDA ($7A),Y
    CMP #$01
    BEQ L16903
    TAX
    DEX
    TXA
    STA ($7A),Y
    LDY $4B
    LDA ($7A),Y
    TAX
    DEX
    TXA
    STA ($7A),Y
    LDX $AD
    LDA $4A
    SEC
    SBC #$12
    BEQ A8F6
    BCS A8FA
    LDA #$5F
    BNE A8FC
A8F6:
    LDA #$63
    BNE A8FC
A8FA:
    LDA #$60
A8FC:
    ORA #$80
    STA $7FBA,X
    INC $AD
L16903:
    RTS

Agility/Stamina/Magic Power ($05:$A904-$05:$A95B) (Line 3489 in https://github.com/JiggeryPonkery/FF2-Disassembly/blob/master/bank_05.asm)
Code: [Select]
;; sub start ;;
    CLC
    LDA $46
    ADC #$10
    STA $47
    LDX #$00
    LDA $44
    JSR $FD11
    STA $48
    SEC
    LDA $45
    BEQ L16955
    SBC $48
    BCS L16955
    LDY $46
    CPY #$11
    BEQ L1695B
L16923:
    LDA ($7A),Y
    TAX
    INX
    TXA
    JSR $AA3A
    STA ($7A),Y
    LDY $47
    LDA ($7A),Y
    TAX
    INX
    TXA
    JSR $AA3A
    STA ($7A),Y
    LDX $AD
    LDA $46
    SEC
    SBC #$12
    BEQ A948
    BCS A94C
    LDA #$62
    BNE A94E
A948:
    LDA #$63
    BNE A94E
A94C:
    LDA #$64
A94E:
    STA $7FBA,X
    INC $AD
    BNE L1695B
L16955:
    LDY $46
    CPY #$11
    BEQ L16923
L1695B:
    RTS

I see what happened. Because I did a Ctrl+F on JSR $AA3A, I assumed these two routines to be similar, and my fix applied the same JSR-skipping fix to both of them. So my patch actually fixes both routines so that instead of a JSR, a BEQ is used for both the Strength/Spirit/Intellect, and the Agility/Stamina/M.Power routines. Thank you for clarifying what these routines did. This will make it much easier even without comments for me to start messing with fixing the other level up quirks. You and Jiggers have been immensely helpful, abw. :)



Chasing the HP/MP routine down the rabbit hole, some counting tells me that the JSR $A9B6 points to Line 3607
Code: [Select]
;; sub start ;;
    SEC
    LDA $00
    SBC $02
    STA $04
    LDA $01
    SBC $03
    ORA $04
    RTS

I see a Branch on Carry Clear after returning from that JSR, and then what appears to be the instructions that increase HP or MP? So then would we want to add another compare and branch here? There's 7 bytes in the AA3A chunk that have been freed up and can be repurposed if needed, though not sure 7 bytes is enough space.
« Last Edit: January 19, 2020, 04:56:08 pm by redmagejoe »

redmagejoe

  • Full Member
  • ***
  • Posts: 218
    • View Profile
Re: Final Fantasy II Restored
« Reply #47 on: January 09, 2020, 05:01:22 pm »
Lost sight of this a bit during work projects. So let me see if I can try to figure out what's happening step by step in this HP/MP up routine, so as to figure out the best way to impose a cap. I guess the first question I should ask is, in ASM, how does one do a comparison for a 2-byte value? EDIT: Reviewing http://www.6502.org/tutorials/compare_beyond.html at the moment... The parameters within which we'd want any special handling of HP to happen would be between 9901 ($26AD or $AD $26) and 9999 ($270F or $0F $27) since Stamina can go up to 99, and therefore if HP is 9900 before the HP up, 99 Stamina will put the character at 9999 exactly. I'm spit-balling here, but I'm assuming we'd want to check if the high byte is > #$25, and if so, check the low byte. The thing is that we would also have to have the Stamina checked to do what would in higher level languages be elementary. Essentially...

Code: [Select]
IF (Stamina + Current_HP) > 9999 THEN New_HP = 9999
Seems like this may be more complicated to do with the limited space considering you would probably need two different checks. One if HP < 9999 to set the new HP to 9999, and then another to see if HP is already at 9999, and if so, skip the entire HP up! routine. I guess I could look at the FF1 disassembly to see how they keep HP from going above 999 (as with the Fighter/Knight it is possible and I have capped his HP) to at least have a frame of reference. Looking at Disch's disassembly in bank_0B, I see that Max HP is stored as data as 1000 ($E8 $03) for some reason, and he remarked as such that 1 has to be subtracted, and then there's a MultiByteCMP routine later that works through the bytes of it. It's still a bit difficult to wrap my head around, but I'm trying to break it down and figure out how it could be repurposed.

Code: [Select]
    LDA #<data_MaxHPPlusOne     ; copy HP cap to $82,83
    STA $82
    LDA #>data_MaxHPPlusOne
    STA $83
   
    LDY #2 - 1                  ; compare 2 byte value (char max HP to HP cap)
    JSR MultiByteCmp
    BCC @Done                   ; if max HP < cap, jump ahead to @Done

Code: [Select]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;  MultiByteCmp  [$9EB1 :: 0x2DEC1]
;;
;;  input:  $80,81 = ptr to first value
;;          $82,83 = ptr to second value
;;               Y = number of bytes (-1) to compare.  Ex:  Y=1 compares 2 bytes
;;
;;  output:    Z,C = set to result of CMP
;;
;;    C set if ($80) >= ($82)
;;    Z set if they're equal
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

MultiByteCmp:
  @Loop:
    LDA ($80), Y            ; load value
    CMP ($82), Y            ; compare to target value
    BEQ @NextByte           ; if equal, do next byte
   
      PHP                     ; if not equal...
      PLA
      AND #$81                ; clear all flags except N,C.  Presumably this is to clear Z
      PHA                     ;  strangely, this also clears I!  nuts!  Why it preserves N is
      PLP                     ;  is a mystery, as its result here is not reliable.
      RTS                     ; and exit
   
  @NextByte:
    DEY                     ; decrease byte counter to move to next byte
    BNE @Loop               ; loop if more bytes to compare
   
    LDA ($80), Y            ; otherwise, if this is the last byte
    CMP ($82), Y            ; simply do the CMP
    RTS                     ; the 'Z' result will be preserved on this CMP
« Last Edit: January 19, 2020, 04:56:17 pm by redmagejoe »

abw

  • Sr. Member
  • ****
  • Posts: 372
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #48 on: January 09, 2020, 10:19:45 pm »
I guess the first question I should ask is, in ASM, how does one do a comparison for a 2-byte value? EDIT: Reviewing http://www.6502.org/tutorials/compare_beyond.html at the moment...
Example 4.2.1 from your link is a pretty great option, especially in a case like this where you don't need to preserve the values of X or Y.

Code: [Select]
IF (Stamina + Current_HP) > 9999 THEN New_HP = 9999
Alternately, you could check for max HP at 9999 at the start of the routine, skip the level up if so, and otherwise cap max HP at 9999 after its normal increase.

Seems like this may be more complicated to do with the limited space considering you would probably need two different checks.
Fortunately for us, the original code is a bit inefficient, so by rewriting it to be shorter we can reclaim enough space to add some new code. Try this out!
« Last Edit: January 09, 2020, 10:57:22 pm by abw »

redmagejoe

  • Full Member
  • ***
  • Posts: 218
    • View Profile
Re: Final Fantasy II Restored
« Reply #49 on: January 09, 2020, 10:21:30 pm »
That assumes I'm versed enough in ASM to even begin thinking of rewriting the existing code, let alone trying to jury-rig a set of instructions to do my specific fix. :P
« Last Edit: January 19, 2020, 04:56:25 pm by redmagejoe »

abw

  • Sr. Member
  • ****
  • Posts: 372
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #50 on: January 09, 2020, 10:58:15 pm »
Previous post edited - I need to learn to make my links more visible ;).

redmagejoe

  • Full Member
  • ***
  • Posts: 218
    • View Profile
Re: Final Fantasy II Restored
« Reply #51 on: January 10, 2020, 02:08:14 am »
Going to test this out. Was a bit distracted, but I can see that a relatively small number of addresses were changed, and if you're okay with it, I'd like to put the code blocks up here for comparison for ease of viewing and so those of us working on this can see what's being changed.

Before:
Code: [Select]
;; sub start ;;
    LDY $44
    LDA ($7A),Y
    STA $00
    INY
    LDA ($7A),Y
    STA $01
    JSR DoDivision
    LDA $04
    STA $4C
    LDA $05
    STA $4D
    LDX #$00
    LDA $4A
    JSR $FD11
    STA $00
    LDA $4C
    STA $02
    LDA $4D
    STA $03
    LDA #$00
    STA $01
    JSR $A9B6
    BCC L16841
    LDY $48
    LDA ($7A),Y
    LDY $44
    CLC
    ADC ($7A),Y
    STA ($7A),Y
    LDA #$00
    INY
    ADC ($7A),Y
    STA ($7A),Y
    LDX $AD
    LDA $44
    CMP #$0A
    BNE A83A
    LDA #$5D
    STA $7FBA,X
    BNE A83F
A83A:
    LDA #$5E
    STA $7FBA,X
A83F:
    INC $AD
L16841:
    RTS

...

;; sub start ;;
    SEC
    LDA $00
    SBC $02
    STA $04
    LDA $01
    SBC $03
    ORA $04
    RTS

After:
Code: [Select]
; $05:$A7E9
HP_MP_level_up:
LDY $44 ; offset for low byte of Max HP or MP, whichever one we're dealing with at the moment
LDA ($7A),Y ; low byte of Max
STA $00 ; low byte of Max
TAX ; also copy it to X, which we'll use in CMP_9999 soon
INY ; offset for high byte of Max HP or MP, whichever one we're dealing with at the moment
LDA ($7A),Y ; high byte of Max
STA $01 ; high byte of Max
JSR CMP_9999 ; compare Max to 9999
BCS done ; if C is set then we know Max >= 9999, so just return
JSR $FCC3 ; 16-bit division (little endian): $00-$01 / $02-$03, quotient in $04-$05, remainder in $06-$07
LDA $04 ; Max / damage, quotient low byte
STA $4C ; Max / damage, quotient low byte
LDA $05 ; Max / damage, quotient high byte
STA $4D ; Max / damage, quotient high byte
LDX #$00
LDA $4A
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
LDY #$00 ; high byte for comparison
JSR CMP_damage_ratio
BCC done ; if the damage ratio wasn't high enough, just return
LDY $48 ; stat offset for effective Stamina/Magic Power
LDA ($7A),Y ; effective Stamina/Magic Power
LDY $44 ; offset for low byte of Max HP or MP, whichever one we're dealing with at the moment
CLC
ADC ($7A),Y ; low byte of Max
TAX ; also copy it to X, which we'll use in CMP_9999 soon
STA ($7A),Y ; Max HP/MP += effective Stamina/Magic Power (low byte)
LDA #$00
INY ; offset for high byte of Max HP or MP, whichever one we're dealing with at the moment
ADC ($7A),Y ; add the carry from the low byte
STA ($7A),Y ; Max HP/MP += effective Stamina/Magic Power (high byte)
JSR CMP_9999 ; compare Max to 9999
BCC battle_message ; if C is clear then we know Max < 9999, so no need to cap it
; otherwise cap Max at 9999
; since Stamina/Magic Power is capped at 99, we know Max is now in the range 10000 - 10097, i.e. $2710 - $2771,
; which conveniently means we only need to reset the low byte
LDA #$0F
DEY
STA ($7A),Y ; low byte of Max
battle_message:
LDX $AD ; battle message index
LDA $44 ; offset for low byte of Max HP or MP, whichever one we're dealing with at the moment
SEC
SBC #$0A ; offsets for Max HP and Max MP are #$0A and #$0E, so A will be 0 or 4
LSR ; A will be 0 or 2, C is now clear
LSR ; A will be 0 or 1, C is still clear
ADC #$5D ; base offset for HP/MP increase battle message IDs
STA $7FBA,X ; start of list of string IDs battle messages
INC $AD ; battle message index
done:
RTS

...

; overwrite the inefficient damage ratio comparison routine with a much smaller version that reclaims enough free space to add a dedicated 9999 comparison routine!
; $05:$A9B6
CMP_damage_ratio:
CPY $4D ; compare Y to high byte of damage ratio
BNE +
CMP $4C ; compare A to low byte of damage ratio
+
RTS
CMP_9999:
CMP #$27 ; compare high byte of Max to high byte of 9999
BNE +
CPX #$0F ; compare low byte of Max to low byte of 9999
+
RTS

I'm assuming what previous started at $A9B5 was rendered redundant/deprecated by your changes, such that the new routine you wrote could occupy its space? I haven't tested the effects yet, but I'm assuming this makes the cap for both HP and MP 9999, when we'd need MP to cap at 999. Still, half a fix is better than no fix. :)



It appears to work. Coupled with my stat fix, Firion at 99 Stamina was taken from 9999 HP down to 155, and no message for Stamina up! or HP up! occurred. Now I need to take one of the other characters up to near 9999 to test that it caps the HP, but I'm confident looking at your fix (and certain you have way more experience at this than I) that it should work as intended. Would we need a whole separate routine for capping MP at 999? Better yet, is it possible for the code to know which we're dealing with? The first LDY I'm assuming loads a value in memory that has been previously determined prior to the routine.
« Last Edit: January 19, 2020, 04:56:37 pm by redmagejoe »

abw

  • Sr. Member
  • ****
  • Posts: 372
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #52 on: January 11, 2020, 10:04:56 pm »
this makes the cap for both HP and MP 9999, when we'd need MP to cap at 999.
Ha, good point. I guess little mixups like that are a downside to hacking a game I haven't even played in 20+ years :P.

Alright then, we can squeeze out some more of the code's inefficiency and make room for different caps for HP and MP. I've updated the previous link with an improved patch!

redmagejoe

  • Full Member
  • ***
  • Posts: 218
    • View Profile
Re: Final Fantasy II Restored
« Reply #53 on: January 11, 2020, 10:17:17 pm »
After After:
Code: [Select]
; $05:$A7E9
HP_MP_level_up:
LDY $44 ; offset for low byte of Max HP or MP, whichever one we're dealing with at the moment
LDA ($7A),Y ; low byte of Max
STA $00 ; low byte of Max, consumed by math routines
INY ; offset for high byte of Max HP or MP, whichever one we're dealing with at the moment
LDA ($7A),Y ; high byte of Max
STA $01 ; high byte of Max, consumed by math routines
JSR $FCC3 ; 16-bit division (little endian): $00-$01 / $02-$03, quotient in $04-$05, remainder in $06-$07
LDA $05 ; Max / damage, quotient high byte
BNE done ; if the damage ratio can't possibly be high enough, just return
; otherwise, check the damage ratio against a random number
LDA $04 ; Max / damage, quotient low byte
STA $4C ; Max / damage, quotient low byte
; $FCC3 leaves X = #$00 and nothing else has changed it, so we don't need to set it to #$00 ourselves
LDA $4A
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
CMP $4C ; Max / damage, quotient low byte
BCC done ; if the damage ratio wasn't high enough, just return
; otherwise, check to see if Max is already at the cap
TYA ; neither $FCC3 nor $FD11 change the value of Y, so it still has the offset for the high byte of Max
LSR ; high byte offsets are #$0B and #$0F, so A is now #$05 or #$07
TAX ; this is the index into the table of 2-byte cap values
JSR CMP_max ; compare Max to its cap
BCS done ; if Max is already >= cap, just return
; otherwise, increase Max
LDY $48 ; stat offset for effective Stamina/Magic Power
ADC ($7A),Y ; effective Stamina/Magic Power
LDY $44 ; offset for low byte of Max HP or MP, whichever one we're dealing with at the moment
STA ($7A),Y ; Max HP/MP += effective Stamina/Magic Power (low byte)
INY ; offset for high byte of Max HP or MP, whichever one we're dealing with at the moment
LDA #$00
ADC ($7A),Y ; add the carry from the low byte
STA ($7A),Y ; Max HP/MP += effective Stamina/Magic Power (high byte)
JSR CMP_max ; compare Max to its cap
BCC battle_message ; if Max is already less than its cap, there's no need to cap it
; otherwise, cap Max
LDA cap_data-5,X ; low byte of cap
STA ($7A),Y ; low byte of Max
INY
LDA cap_data-4,X ; high byte of cap
STA ($7A),Y ; high byte of Max
battle_message:
TXA ; this is the index into the table of 2-byte cap values, #$05 or #$07
LSR ; A will now be #$02 or #$03, C is set
ADC #$5A ; HP/MP increase battle message IDs are #$5D and #$5E = #$5A + #$02 or #$03 + 1 for the carry
LDX $AD ; battle message index
STA $7FBA,X ; start of list of string IDs battle messages
INC $AD ; battle message index
done:
RTS
cap_data:
dw 9999 ; HP cap
dw 999 ; MP cap

...

; given an offset into ($7A) pointing to the high byte of Max in Y and an offset into the cap list (with a built-in offset of -5) in X, compare Max to its cap
; leaves Y with the offset for Max's low byte and A with the low byte of Max
; $05:$A9B6
CMP_max:
LDA ($7A),Y
CMP cap_data-4,X ; compare high byte of Max to high byte of cap
DEY
LDA ($7A),Y
BCC +
CMP cap_data-5,X ; compare low byte of Max to low byte of cap
+
RTS

Damn, you managed to optimize the code even further? Like I know it's only 2 more bytes, but given the scale of the code, that's impressive. I'll playtest this, but looking at the code, I can see that Square was definitely just doing the best it could at the time with the code. It fascinates me how much more you can fit into the code by just freeing up a couple of bytes. I was going to ask if it was going to be necessary to throw some WORDs (which I assume is used to define that it's simply data and not code) in like FF1 did for its Max HP cap. Awesome work again, abw!

By the way, have you thought about aiding Jiggers with her disassembly now that it's on GitHub? Your documentation is very thorough and would be a great boon I think to what she's trying to do.

EDIT: Ignore all previous ramblings in this section. Stamina/M.Power and HP/MP up are terribly finicky as RNG handles them. The fact that they are still occurring puts my mind at ease that both of these patches are acceptable fixes. I'll link abw's patch in the first post and mark the issue resolved. I guess the next matter, since we have it documented already, is the Evasion and Magic Defense routine. At some point in that code block quoted above, I'm assuming that an increase is being called for, but does it finish then and there and it's simply the menu display code that has the issue, or is there some incomplete instructions that's leaving the increased Evasion or MDef hanging in limbo until a second battle is completed?

EDIT2: The MP fix doesn't appear to handle the <999 situation properly. While MP doesn't ever seem to go up at 999, I changed the values of HP and MP from 9999 and 999 to 9998 and 998. At the end of a battle, Firion was dropped so low on both HP and MP that he got M.Power, Stamina, MP, and HP (in that order strangely). Stamina went from 98 to 99, HP went from 9998 to 9999 (nice!), but MP went from 998 to 9E4 (or I'm guessing 1044). 3 out of 4 of the scenarios this patch was meant to address appear to work, however, having been testing for the last few hours. HP < 9999 and increased beyond 9999 sets it to 9999, HP = 9999 prevents any HP up at all, MP = 999 prevents any MP up at all. Now all that's needed is MP < 999 increased beyond 999 sets it to 999. Looking at your code, abw, I can't figure out where the problem is. Is it possible that the comment you made before is relevant, about how the "too much" range for HP was 10000 to 10097 and used a single high byte, and MP's 1000 to 1097 does not? $03E8 to $0449 is the range for that, so perhaps the fact that the high byte could be $03 or $04 is causing problems with the math?

Code: [Select]
; otherwise, check to see if Max is already at the cap
TYA ; neither $FCC3 nor $FD11 change the value of Y, so it still has the offset for the high byte of Max
LSR ; high byte offsets are #$0B and #$0F, so A is now #$05 or #$07
TAX ; this is the index into the table of 2-byte cap values
JSR CMP_max ; compare Max to its cap
BCS done ; if Max is already >= cap, just return
; otherwise, increase Max
LDY $48 ; stat offset for effective Stamina/Magic Power
ADC ($7A),Y ; effective Stamina/Magic Power ;; 46 M.Power = $2E
LDY $44 ; offset for low byte of Max HP or MP, whichever one we're dealing with at the moment ;; 998 MP = $03E6 so $E6
STA ($7A),Y ; Max HP/MP += effective Stamina/Magic Power (low byte) ;; $E6 + $2E = $14 (with $01 carry?)
INY ; offset for high byte of Max HP or MP, whichever one we're dealing with at the moment ;; shifts Y up to the $03 I'm guessing
LDA #$00
ADC ($7A),Y ; add the carry from the low byte ;; I'm guessing this does $03 + $01 to get $04
STA ($7A),Y ; Max HP/MP += effective Stamina/Magic Power (high byte) ;; $04 + $00
JSR CMP_max ; compare Max to its cap ;; jumping down

...

CMP_max:
LDA ($7A),Y  ;; loads A with $7A/Y which at this point should be $04
CMP cap_data-4,X ; compare high byte of Max to high byte of cap ;; should compare $04 to $03, assuming that cap_data-4 is pointing to the right address
DEY
LDA ($7A),Y  ;; loads A with $7A/Y which at this point should be $14
BCC +
CMP cap_data-5,X ; compare low byte of Max to low byte of cap ;; should compare $E7 to $14 here, is this potentially the problem?
+
RTS

...

BCC battle_message ; if Max is already less than its cap, there's no need to cap it
; otherwise, cap Max
LDA cap_data-5,X ; low byte of cap
STA ($7A),Y ; low byte of Max
INY
LDA cap_data-4,X ; high byte of cap
STA ($7A),Y ; high byte of Max

cap_data:
dw 9999 ; HP cap
dw 999 ; MP cap

The more I read up on what's happening in CMP_max, the more I'm wondering if the same instructions are feasible for both HP and MP. Carry flag is 0 if the first LDA and CMP results in the cap's HB being higher than the HB of the new max. I would think the BCC could simply be moved up then, but I'm just feeling this out. In all cases in HP where it matters, the HB will be $27, so if max HP's HB is less, BCC could simply happen before the DEY, right? Same goes for MP, as $02 would mean there's no need to do further comparisons. In MP's case, however, we want to consider the possibility that MP's HB is equal to or greater than $03, so BCC would still be acceptable before the DEY I believe... Correct me if I'm wrong. However in the case that the carry flag is 1, for MP, we would want to proceed to DEY, then compare the low bytes... But the problem we run into here is that the low byte comparison's result will vary depending on whether the high byte was $03 or $04, which there doesn't appear to be any accounting for since we're only branching on Carry 0. Perhaps another CMP absolute is needed prior to the low byte comparison and to the DEY?

Correct me if I'm wrong, but it seems like the problem is that we need the program flow to behave in two different ways depending on whether the high byte is $03 (if low byte is greater than cap's, prepare to cap MP) or $04 (if low byte is... ANYTHING, prepare to cap). Right now it simply appears to be comparing the low byte once the branch fails, when a more specific comparison is needed.
« Last Edit: January 19, 2020, 04:56:44 pm by redmagejoe »

abw

  • Sr. Member
  • ****
  • Posts: 372
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #54 on: January 12, 2020, 03:27:52 pm »
Damn, you managed to optimize the code even further? Like I know it's only 2 more bytes, but given the scale of the code, that's impressive. I'll playtest this, but looking at the code, I can see that Square was definitely just doing the best it could at the time with the code. It fascinates me how much more you can fit into the code by just freeing up a couple of bytes.
Yeah, a lot of the time a game's code is not particularly well optimized, which can be useful in cases like this where we want to cram more functionality into the same number of bytes. In Square's defence, they still have a few hundred unused bytes in this bank (which we totally could have used, but this way was more fun :P), so I can understand them not spending time/money on reducing the number of required bytes for no visible benefit.

By the way, have you thought about aiding Jiggers with her disassembly now that it's on GitHub? Your documentation is very thorough and would be a great boon I think to what she's trying to do.
I did take a peek at this, but didn't want to commit to anything at this point. Jiggers, do you know which patch(es) were applied to the ROM your disassembly was based on? It looks like something based on Demi's translation, but the reassembly doesn't match up with any version I've got (also, that .incbin "nesheader.bin" in bank_00.asm will make the header count as part of the ROM, which will throw all the bank 0 pointers [once you have bank 0 pointers] off by $10; you'll probably want wait until the the very end of the build process to tack the header on, like Disch did with the FF1 disassembly).

I've also updated my disassembly with some more pointers and notes, which might come in handy.

At the end of a battle, Firion was dropped so low on both HP and MP that he got M.Power, Stamina, MP, and HP (in that order strangely).
I just checked, and that also happens in an unaltered ROM, so I guess it's intentional? If it's a problem, switching the order of the HP/MP level up calls starting at $05:$A623 ought to switch the order of the messages too.

The MP fix doesn't appear to handle the <999 situation properly.
Yup, CMP_max not properly handling the case where Max's high byte was >= the cap's high byte was the problem. I thought I could get away with BCC there since I don't care about equality anywhere else, but it really does need to be BNE. I can't move the BNE up because I'm relying on CMP_max leaving A and Y all set up for low byte math; if the branch were placed immediately after the CMP, there'd be no way to tell whether A and Y were set for the high byte or the low byte, and I'd have to use precious space resetting them to the low byte. Fortunately there were still a couple of bytes to spare in the main body and A and Y are already set correctly before the second call, so kicking the high byte's LDA ($7A),Y out into the main body just before the first call freed up two bytes for a PHP/PLP to preserve Z while CMP_max switches to the low byte settings. I've updated the previous link with the new patch!

redmagejoe

  • Full Member
  • ***
  • Posts: 218
    • View Profile
Re: Final Fantasy II Restored
« Reply #55 on: January 12, 2020, 03:34:28 pm »
Damn abw, you're on point with fixes the moment an issue is pointed out like I am with languages I actually understand. You get mad respect for that. I wish I understood ASM enough to pull that off, but hopefully it's clear from my rambling that as I try to make sense of what's being changed, I'm better understanding it. I was trying to figure out if a BCS needed to be worked in, but I guess BNE does work better in that situation. I'll give this new patch a try.

I'm thinking of compiling all the fixes when this is all done into a Final Fantasy II Restored type patch, with appropriate credit to all parties of course. For now, I'm just posting the links in the first post. As for Jiggers' disassembly, she's most likely using the patch I provided, which had the Chaos Rush translation, I BELIEVE my M.Power message fix to Chaos Rush's translation, B Button Dash and that should be it.

EDIT: Works like a charm, abw! I looked over the ASM, and I have to say that's pretty clever. Updating first post and moving on to Weapon Skill / Spell Level message spam (i.e. level is capped, but message still runs at end of battle unnecessarily). Not game-breaking and really just a nuisance, but since we've got so much space to work with as you've pointed out, might as well polish things up, yeah? Will look at Evasion and Magic Defense as well. Is it increasing at the end of battle, and simply not being updated in the display until after a second battle, or is the increase delayed for another battle for some reason?

I just checked, and that also happens in an unaltered ROM, so I guess it's intentional? If it's a problem, switching the order of the HP/MP level up calls starting at $05:$A623 ought to switch the order of the messages too.

Nah, it's not an issue, just something I noticed. The order of the level up messages is just not what I would have expected based on the displayed order of stats in the window. Maybe that can be tampered with when all else is said and done for a Restored patch.
« Last Edit: January 19, 2020, 04:57:00 pm by redmagejoe »

Jiggers

  • Sr. Member
  • ****
  • Posts: 307
    • View Profile
    • My Ko-Fi Page
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #56 on: January 12, 2020, 04:14:09 pm »
I did take a peek at this, but didn't want to commit to anything at this point. Jiggers, do you know which patch(es) were applied to the ROM your disassembly was based on? It looks like something based on Demi's translation, but the reassembly doesn't match up with any version I've got (also, that .incbin "nesheader.bin" in bank_00.asm will make the header count as part of the ROM, which will throw all the bank 0 pointers [once you have bank 0 pointers] off by $10; you'll probably want wait until the the very end of the build process to tack the header on, like Disch did with the FF1 disassembly).

I got a patch from this thread. http://www.romhacking.net/forum/index.php?topic=29704.msg386700#msg386700

The nesheader stuff... Hopefully won't be a problem, if Bank 0 is just data, like FF1. Pointers go into the variables.inc file for it. I had to set it up like this for my MMC5 project because the nesheader being put in during assembly was making errors or something--The purpose was to get the label file made to work in Mesen's debugger. If Bank 0 has code and a label file isn't important, then I'll fix that up.
I know exactly what I'm doing. I just don't know what effect it's going to have.

I wrote some NES music! Its a legal ROM file. - I got a Ko-Fi page too.

redmagejoe

  • Full Member
  • ***
  • Posts: 218
    • View Profile
Re: Final Fantasy II Restored
« Reply #57 on: January 13, 2020, 03:20:50 pm »
Out of curiosity though, abw, on the note of stat up messages at $A623, I'm assuming it's somewhere in this area?

Code: [Select]
A5EF:
    STA ($7E),Y
L165F1:
    INC $44
    INC $00
    LDA $00
    CMP #$10
    BNE L165A2
    LDA #$7D
    STA $45
    LDA #$37
    STA $44
    LDA $AC37
    STA $46
    LDY #$30
    STY $47
    JSR $A842
    LDA #$7D
    STA $45
    LDA #$3B
    STA $44
    LDA $AC38
    STA $46
    LDY #$32
    STY $47
    JSR $A842
    LDA #$00
    STA $52
    STA $53
    LDY #$0A
    STY $44
    LDA $AC39
    STA $4A
    LDY #$22
    STY $48
    LDA $9E
    ASL A
    TAX
    LDA $7D6A,X
    STA $02
    LDA $7D6B,X
    STA $03
    ORA $02
    BEQ L1664F
    JSR $A7E9
    LDA $4C
    STA $52

Without looking at the individual opcodes I tend to lose track of the address when counting, but I'm guessing $A623 falls in the vicinity of the LDA #$00, STA $52, STA $53?

EDIT: Just checked your disassembly, and looking at this section...
Code: [Select]
0x0166C4|$05:$A6B4:A9 7D    LDA #$7D    ; $7D43; Character #1 white magic use counter
0x0166C6|$05:$A6B6:85 45    STA $45   
0x0166C8|$05:$A6B8:A9 43    LDA #$43    ; $7D43; Character #1 white magic use counter
0x0166CA|$05:$A6BA:85 44    STA $44   

I feel that this is somehow relevant to whatever bug causes Firion's Spirit to go up when the enemy uses party-wide attack spells on Firion's group. Or at the very least, the areas of memory being pointed to here are most likely relevant to the battle code that handles enemy spell casting.
« Last Edit: January 19, 2020, 04:57:07 pm by redmagejoe »

abw

  • Sr. Member
  • ****
  • Posts: 372
    • View Profile
Re: Final Fantasy II Disassembly / Bug Fix Project
« Reply #58 on: January 13, 2020, 09:25:40 pm »
I wish I understood ASM enough to pull that off, but hopefully it's clear from my rambling that as I try to make sense of what's being changed, I'm better understanding it.
Yup, knowing that you were trying to follow along while still learning the language was actually part of the reason why I went through and added the possibly excessive commenting. If you already understand how computers actually work, 6502 is pretty easy to pick up as long as you have a decent opcode reference.

I looked over the ASM, and I have to say that's pretty clever.
Really clever would have been if there weren't any issues in the first place - I'm getting lazy in my old age :P. Thanks for actually testing! I did what I could to keep the byte count low, but it's definitely not a shining example of good software engineering practices - I've introduced dependencies that didn't previously exist on other routines leaving registers in a certain state (i.e. X = #$00 or Y being unchanged) and the math for converting stat offset to message ID would likely have to be completely rewritten if any of the stat offset or message ID values changed.

I got a patch from this thread. http://www.romhacking.net/forum/index.php?topic=29704.msg386700#msg386700

The nesheader stuff... Hopefully won't be a problem, if Bank 0 is just data, like FF1.
That complicates things for me a little bit, as my disassembler is using the CDL file for the base Japanese ROM. I think my re-assemblable version is pretty close to being compatible with ca65, so maybe I'll see if I can put some time into that.

Bank 0 is at least 80% data, but there's also at least 12% code in there, so pointers are definitely an issue.

Out of curiosity though, abw, on the note of stat up messages at $A623, I'm assuming it's somewhere in this area?
Everything down to the BNE L165A2 is the tail end of the spell level up loop. To swap the order of HP and MP, I guess you'd actually want to take $05:$A629-$05:$A64E (HP) and swap it with $05:$A64F-$05:$A674 (MP).

EDIT: Just checked your disassembly, and looking at this section...
Code: [Select]
0x0166C4|$05:$A6B4:A9 7D    LDA #$7D    ; $7D43; Character #1 white magic use counter
0x0166C6|$05:$A6B6:85 45    STA $45   
0x0166C8|$05:$A6B8:A9 43    LDA #$43    ; $7D43; Character #1 white magic use counter
0x0166CA|$05:$A6BA:85 44    STA $44   

I feel that this is somehow relevant to whatever bug causes Firion's Spirit to go up when the enemy uses party-wide attack spells on Firion's group. Or at the very least, the areas of memory being pointed to here are most likely relevant to the battle code that handles enemy spell casting.
As far as I know, that part is fine. The Strength/Intellect/Spirit code uses ($44),Y with Y set to the current character ID, so it's $7D43-$7D46 as appropriate. But if you've got a save state where an enemy hits you with a party-wide spell, try setting a write breakpoint on $7D43 (Firion's white magic use counter) and see what happens.

redmagejoe

  • Full Member
  • ***
  • Posts: 218
    • View Profile
Re: Final Fantasy II Restored
« Reply #59 on: January 13, 2020, 09:46:09 pm »
Everything down to the BNE L165A2 is the tail end of the spell level up loop. To swap the order of HP and MP, I guess you'd actually want to take $05:$A629-$05:$A64E (HP) and swap it with $05:$A64F-$05:$A674 (MP).

Ehh, that will probably be the very last thing I look at if at all. Like I said, it's not an issue at all, just a nitpick, and I'd rather not mess with anything that works until everything else is fixed.

As far as I know, that part is fine. The Strength/Intellect/Spirit code uses ($44),Y with Y set to the current character ID, so it's $7D43-$7D46 as appropriate. But if you've got a save state where an enemy hits you with a party-wide spell, try setting a write breakpoint on $7D43 (Firion's white magic use counter) and see what happens.

Will do just that. Interesting... seems that 7D43 is written to when closing the menu on the overworld...? Also when a battle starts, right before you can issue your first orders to the party, and right before the battle screen returns to the overworld screen, during the fade to black. Those two make sense though, as I imagine the counter is being initialized and then cleared respectively. Trying to find Sorcerers now. Interesting. After the initialize breaks, there were two separate breaks in a battle with two Sorcerers, even before I got to issue my first orders. I'm assuming that enemy attacks are decided during this period if it's not a pre-emptive strike for your party, so I guess those two Sorcerers were queuing up party-wide spells? Let's test that... Yep, multiple breaks off the enemies casting at

Code: [Select]
03261D:FE 3B 7D  INC $7D3B,X @ $7D43 = #$03

I decided to have Firion cast Cure on himself (without the target/cancel exploit fix, so as to test), and the moment I confirmed it, another break at

Code: [Select]
031E7A:FE 43 7D  INC $7D43,X @ $7D43 = #$04

As you can see by the time I get to the only White Magic cast in the battle, already the White Magic counter was at 3. Loaded state, and once again $0C:$A60D (03261D) broke twice, with #$03 and #$04 respectively for what I can assume are the two party-wide casts from the two Sorcerers. Is it supposed to be writing to $7D3B, which is number of times spells have been cast on... Firion or someone, to determine if Magic Defense increases? In spite of being told to increment 7D3B, debugger says it's incrementing 7D43... what could be causing that?

Code: [Select]
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   

As quoted in a post above, similar to the white magic counter, this is indeed supposed to be Firion's number of times attacked by an enemy spell. Yet for some reason, when the spell targets the whole party, it goes towards Firion's white magic counter instead of his hit by spells counter... So now I suppose the question is do any of the other characters get their hit by magic counters increased in these cases? I'll set breakpoints for the other characters as well. Because the end result is one of two scenarios. 1) Firion gets no Magic Defense credit but gets Spirit credit while the rest of his party gets MDef credit during party-wide spells, or 2) Firion gets no MDef credit but gets Spirit credit, and the rest of the party gets screwed out of MDef credit. (it's this one)

Looking at your disassembly and only seeing labels for Character #1... Does the same code apply to all characters, and it's simply that the same code is used with different memory pointers to handle the battle counters? Would that be why the attempt to have an "everyone" address doesn't work, because it's dealing with one character at a time and so it's passed a bad pointer? Sorry for the spit-balling, just trying to speculate about the how and why.

Code: [Select]
; control flow target (from $A53E)
0x032581|$0C:$A571:A0 2B    LDY #$2B   
0x032583|$0C:$A573:B1 44    LDA ($44),Y
0x032585|$0C:$A575:C9 01    CMP #$01   
0x032587|$0C:$A577:D0 05    BNE $A57E 
0x032589|$0C:$A579:A9 08    LDA #$08   
0x03258B|$0C:$A57B:4C 0C A6 JMP $A60C 

; control flow target (from $A577)
0x03258E|$0C:$A57E:C9 02    CMP #$02   
0x032590|$0C:$A580:D0 18    BNE $A59A 
; control flow target (from $A593)
0x032592|$0C:$A582:A2 00    LDX #$00   
0x032594|$0C:$A584:A9 03    LDA #$03   
; call to code in a different bank ($0F:$FD11)
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:A5 9E    LDA $9E   
0x0325A7|$0C:$A597:4C 0C A6 JMP $A60C

...

; control flow target (from $A57B, $A597)
0x03261C|$0C:$A60C:AA      TAX       
0x03261D|$0C:$A60D:FE 3B 7D INC $7D3B,X ; Character #1 counter for times magically attacked by enemy

So I chased the control flow around to the two addresses indicated, but it was A57B that got my attention. If I'm reading the opcode manual properly, what's happening is that INC (FE) $7D3B is incrementing Absolute Indexed with X to increment the ADDRESS $7D3B by the value in X. TAX above that transfers the value in the Accumulator into X, so jump up to A597... I can't see anything amiss here but I also don't fully understand what's happening other than the last Accumulator operation was LDA (A5) $9E... which I think is the character index? What I did find more interesting was the fact that the LDA in $A57B's block used an ABSOLUTE for its LDA (A9) of 8... What is $7D3B + $08? $7D43. Coincidence?

Strangely, when I changed that 08 to a 00, using the exact same save state (where all the attacks are pre-determined so long as I simply attack with all my party members every time), the Sorcerers, which previously used Blizzard on all and Fire on all every single time the state was loaded, and also fired the 7D43 breakpoint, on the 00 version of the ROM, they would only Blizzard and Fire Firion, but also the code that was previously firing 7D43 when it should have been 7D3B firing worked properly. What this implies to me is that the bug occurs because that code not only handles enemies targetting all party members rather than just Firion, but also incorrectly moves the address forward? Did I figure that right? Elsewhere in the code suggests that that the absolute is an outlier. Going to try replacing the A9 08 with an A5 9E and see what happens... Nope, seems A9 08 is necessary for enemies to be able to cast on the whole party. I'm out of ideas for the time being, but hopefully my troubleshooting can be of use to you, abw?



Okay, I understand what's happening with these memory values. The only labelled addresses are for Character #1, because there's a gap of 3 addresses between each counter. I understand now that the implication is that the subsequent values are for Characters 2, 3, and 4. So in the case of $7D3B being Firion's times magic attacked, $3C, $3D, and $3E are Maria, Guy, and Char4. So the idea is that all 4 of these addresses should be getting their counters incremented, but since 08 is used to dictate ALL party members, BUT the math resulting from the code used jumps ahead 2 full counters, some sort of extra instructions are needed to make the case of LDA #$08 lead to an INC $7D3B through $7D3E, rather than INC $7D3B,X. Because in this case, X pushes the address into Firion's white magic use counter rather than what it should be doing. My first thought is to have a compare, and then an incrementing loop that loads 0 into X and increments X terminating on 4 (before the INC $7D3B,X), but there's probably a much more optimal way with limited space to do it.

As far as I can tell, this is the only place in the game where this situation would arise, as no other action by the enemy or players should result in any counters for multiple characters increasing. I can see why this oversight happened in development, as it appears to be a unique case that wasn't given appropriate consideration.
« Last Edit: January 19, 2020, 04:57:14 pm by redmagejoe »