Poll
Question:
Restored: ROM space allowing, should attacks be tallied for each weapon type used in combat? (support multiple weapon EXP for weapon swapping)
Option 1: No. Restored already has passive growth, only end-of-combat weapon should receive bonus.
Option 2: Yes. All weapons used in combat should receive this bonus credit.
FINAL FANTASY II RESTORED (https://www.romhacking.net/hacks/5893/)Current Versions Above! (Playtesters Wanted)Bug Fix To-Do List
- Fine-tune Ultima? Without a PSP Final Fantasy disASM or at least analysis, there's no way of knowing the exact formula.
- Parts of Palamecia Castle 7F do not block teleport spells. The offending Map ID is $71.
- Warping out of Mysidian Tower results in the player being drawn back in by the tower beam animation. Either exit coordinates need to be adjusted, or the entire animation sequence/script removed for all entrances after the first time.
- Healing items in combat are bound to casting on self, rather than allowing a target to be chosen. This renders Phoenix Down, Gold Needle, and Maiden's Kiss useless in combat. Consider implementing new routine for targetting with these items.
- In the dialogue immediately before and after fighting the boss monster when retaking Fynn castle, there's a graphics glitch after closing the dialogue boxes that causes Hilda and Gordon to appear on the thrones for a few frames.
- Killing a large monster (e.g. a Fire Gigas) while Leon is dead results in Leon's feet being temporarily cut off.
- You are able to continue interacting with NPCs that have been removed until you exit the conversation. These NPCs never have any interactions AT ALL (meaningful or otherwise) after the point at which you're "expected" to close the UI. Can a window close be forced after the last dialog in these instances?
- Remakes suggest that dual-wielding is supposed to actually increase your number of hits, or at the very least, run a second damage calculation. More testing is needed on this and how feasible it would be to put in the patch.
- When flashing the background during spell casting, there's a 10x3 rectangle at the bottom-left of the screen and a line between the battle area and the info area that do not flash; either the entire screen background should flash or the entire info area background should not flash.
- The floor parts of the table, stool, counter, and barrel graphics only match up well with one of the floor patterns they appear on (table matches Bofsk Sewers, others match towns) and look out of place on other floor patterns.
- The BGM pauses for a frame when closing the row menu.
- Attempting to send your first non-KO/Stone character to the back row when all other non-KO/Stone characters are in the back row should play the error SFX.
- Sprite tiles covered or uncovered by an opening or closing window should disappear or appear on the same frame as the changed background tiles rather than the entire sprite disappearing when the window starts opening or the entire sprite appearing after the window finishes closing.
- Sprites have a 3px vertical offset from their background tile, resulting in the tops of sprites located 1 tile below the bottom of a window overlapping with the window.
Restored Improvements
- IMPLEMENTED On top of fixing the Paloom-Poft ticket cost issue for Bug Fix, Restored also moves the boat outside the current town when the ticket seller is spoken to.
- IMPLEMENTED Running works 100% of the time during first round of surprise attacks.
- IMPLEMENTED B Button Dash patch. (courtesy of SpiderDave made for ChaosRush translation)
- IMPLEMENTED The 4 Mysidian Tower Orbs are buffed to give all 4 characters their stat boosts.
- IMPLEMENTED Passive skill growth for weapons and spells, adding 1 "use" of each weapon and spell per battle to take advantage of the battle rank bonus.
- IMPLEMENTED Iron Giant is now an inescapable encounter. Iron Giant can still run from your party if your HP is too high in the original version.
- IMPLEMENTED Preemptive Strike notification previously appeared after the first round of player command selection. The notification now appears at the beginning of battle.
- IMPLEMENTED Holy Lance casts Holy 16 rather than Holy 8 for a more meaningful effect by the end of the game, when you obtain it.
- IMPLEMENTED Dead characters have their row preserved after battle, rather than automatically being assigned to the back row.
- IMPLEMENTED Certain key items take up valuable inventory slots long after they are relevant to the game or story, when they should be consumed within the context of the story. There is no way to dispose of them. Certain key items are now deleted once their relevance has expired. Goddess's Bell once it is used to open Kashuan Keep. Pass after you use it to gain access to the landed Dreadnought. The Egil's Torch after acquiring Sunfire. Sunfire after being thrown into the Dreadnought. White Mask after it is placed upon the Goddess Statue. Black Mask after it is placed upon the Doppelganger. Pendant after it is used to summon the Wyvern. Crystal Rod after first time entering Mysidian Tower. 8 of 32 inventory slots are thus freed up.
- Change post-liberation BGM of Castle Fynn to Rebel Army Theme?
- Change battle windows to Final Fantasy blue?
- Only display key items in the NPC Item UI.
- Update battle messages, adding failure or # of success messages for buffs and such spells.
- Add level up messages for Evasion/Magic Resist.
- Re-order level up messages to match the order of stats as displayed in the status menu. (HP, MP, Strength, Agility, Stamina, Intellect, Spirit, M.Power)
- Consider tallying attack counter and converting to weapon experience the moment weapons are switched in combat, to more accurately increase weapon experience. See if a JSR can be fit in before the actual change to weapon type changes. This would be an improvement on the remakes, even.
- Add a row indicator on the main menu screen.
- Update the status (/equip, but more work) menu screen to display magic penalty, armour resistances, attack elements/families, and weapon special effect.
- Use different battle messages for Flee failure when the battle can't be escaped vs. when the character failed their success roll.
- Display a message for inescapable battles at the beginning of combat.
- Display spell animations for all targets simultaneously.
- Consider demakes of BGMs used in remakes?
- New title screen using the series standard font/style, used in all current entries and added to Final Fantasy Restored. Seeking interested NES artists who can make use of compression techniques similar to those incorporated by koitsu here (https://www.romhacking.net/?page=translations&action=reflect&id=66). More specific details as presented in koitsu's email to me can be provided on request.
ResourcesGame Systems Breakdown with Bugs Mentioned (https://tinyurl.com/wmwcec3t)Game Systems Discussion @ GameFAQs (https://tinyurl.com/tyxysa9)abw's Drive (https://tinyurl.com/ycp42agr) (disassemblies included)
FF2 CDL (https://tinyurl.com/td4wda9)Updated SRAM Map (https://tinyurl.com/uhph5vy)Map ID Table (https://tinyurl.com/yczl8t35)Spell IDs
C0 Fire CA Confuse D4 Cure DE Mini
C1 Thunder CB Blind D5 Life DF Silence
C2 Blizzard CC Curse D6 Basuna E0 Sap
C3 Scourge CD Toad D7 Esuna E1 Fog
C4 Drain CE Break D8 Barrier E2 Slow
C5 Osmose CF Death D9 Blink E3 Swap
C6 Flare D0 Warp DA Protect E4 Fear
C7 Sleep D1 Berserk DB Shell E5 Holy
C8 Stun D2 Haste DC Wall E6 Teleport
C9 Stop D3 Aura DD Dispel E7 Ultima
ContributorsRed Mage Joe - Minor Coding, Project Organizing, Playtesting, Scutwork
abw - Primary Coding, Disassembly, Playtesting, and countless other contributions without which this project wouldn't be possible
Jiggers - SRAM Mapping, Map Data Decoding/Tools, other contributions
Leviathan Mist - Playtesting, Restored Ideas
Special Thanks to Cyneprepou4uk, Disch, Chaos Rush, Demiforce, for their contributions to this project, both directly and indirectly.
Kids these days, can't do anything without a disassembly.
A complete disassembly was not made. I should know. I do have some hacking bytes, but it's not complete, or anything like you're looking for. The only code I've tangled with is what made the DTE Fixer Upper patch. I haven't really hacked FF2 in a while.
Quote from: Cyneprepou4uk on December 10, 2019, 04:18:01 AM
Kids these days, can't do anything without a disassembly.
Sorry :(
I would like to figure out how people who make the disassemblies in the first place do this stuff, how they tear down these binaries built on esoteric, low-level language. The concept just fascinates me, it does, but I just don't have the time anymore (and probably the patience even if I did) to dedicate to the projects. It saddens me, because I always used to think this sort of thing is what I'd be doing as my primary activity. Alas, at best I can scrounge up enough free time to make tweaks and very minor hacks. Things that I feel I have the experience, or at least the aptitude to figure out and dedicate my resources to rather than taking time away from these profound romhacks some of the members of this community are giving their 100% on. I always feel guilty then when I hit a roadblock like this, but it's such a shame when I think that, from a programming standpoint, it MUST be an easy fix, if only I had the means to analyze it.
tl;dr I'm too inexperienced and lacking in time to try to dissect the binary, and I realize at best what I do is tweaks but I'd really like to try and fix some things in that list.
Quote from: Lenophis on December 10, 2019, 08:57:46 AM
A complete disassembly was not made. I should know. I do have some hacking bytes, but it's not complete, or anything like you're looking for. The only code I've tangled with is what made the DTE Fixer Upper patch. I haven't really hacked FF2 in a while.
That's a bummer. If I were to try and figure out where to begin finding the addresses I'm looking for then... I guess I would have to set a breakpoint and analyze the RAM addresses being read/written to?
There's a disassembly of Final Fantasy around. A lot of code in Final Fantasy 2 and 3 is reused code from Final Fantasy. So although this is not a full solution, it will give you a nice idea how many thing works in the game. Even the RAM map is similar.
I might not mind helping out once there's a disassembly. I looked into how to make one and... couldn't really figure it out either. I got the assembly code out in a big list, but it also tried to turn data into code, and I couldn't tell where one bank ended and the next began...
I need a break from my own things, so I might take a look at FF2 and map out some RAM, that'll help someone get started maybe?
One thing you'll want to do is make sure that a disassembly can be re-assembled after edits. I've been absolutely spoiled by FF1's, but its SO much easier to work with than trying to make changes where you can't just shift the whole game by 2 bytes and change every single pointer and jump address in the process.
Any help at all in any form would be appreciated. It would be a great boon not just to fixes I'd like to try to make to the game, but for future hacking of the game.
Quote from: Jiggers on December 13, 2019, 04:24:04 PM
One thing you'll want to do is make sure that a disassembly can be re-assembled after edits. I've been absolutely spoiled by FF1's, but its SO much easier to work with than trying to make changes where you can't just shift the whole game by 2 bytes and change every single pointer and jump address in the process.
Yeah, but try asking Disch how much time it took to create that disassembly :(.
NES ROMs are tricky things to disassemble. Mostly that's due to mappers, but there are other reasons too.
Assuming you've played the game through in FCEUX at some point, did you happen to have FCEUX's Code/Data Logger running (preferably on the unaltered Japanese ROM, preferably without making any changes via the debugger/hex editor/etc.)?
The reason I ask is that one of my various WIP pet projects is a NES disassembler that uses FCEUX's CDL file to automatically distinguish between code and data (and stuff that is both code and data) and the ROM's iNES header to do some primitive mapper-based things like dividing the ROM into banks based on the mapper's (smallest) bank size and working out the RAM addresses for each of those banks (or at least trying to). With manual work, you can also incorporate comments and labels into the generated disassembly and tell it about things like pointers/pointer tables and what bytes that the CDL didn't log are for. You can see an example from Dragon Warrior II on the wiki (https://datacrystal.romhacking.net/wiki/Dragon_Warrior_II::ROM_map/ASM).
Actually, a quick search of the internet turned up https://taotao54321.github.io/appsouko/work/CDL/pub/, which appears to have a mostly complete CDL file of "FF2", which I'm hoping means "Final Fantasy II". I plugged it in and played the game for a couple of minutes and FCEUX didn't change anything, so maybe it's legit? How does this (https://drive.google.com/file/d/1CKLK3oc6xMuIsPUWfKXe_YcsE0eCrlxN/view) look?
Quote from: abw on December 13, 2019, 06:24:13 PM
Actually, a quick search of the internet turned up https://taotao54321.github.io/appsouko/work/CDL/pub/, which appears to have a mostly complete CDL file of "FF2", which I'm hoping means "Final Fantasy II". I plugged it in and played the game for a couple of minutes and FCEUX didn't change anything, so maybe it's legit? How does this (https://drive.google.com/file/d/1CKLK3oc6xMuIsPUWfKXe_YcsE0eCrlxN/view) look?
https://taotao54321.github.io/appsouko/work/CDL/pub/FF2.txt
@rom_md5 374ed97be8bfd628f6b359a720549ecd
Went to my ROM repo and found the Final Fantasy II (J) [!] and ran it through RHDN's online Hasher-js.
ROM MD5 374ED97BE8BFD628F6B359A720549ECD
We have a match! Good find, abw! :)
Here's my SRAM testing so far:
6000
00 - $01 = Pirate Ship Visible, triggers animation/fight scene? $02 = in control of pirate ship
01 - Pirate Ship X position
02 - Pirate Ship Y position
03 - $C0 when riding ship in animation, $10 when in control of pirate ship?
04 - $01 - Airship Visible. $02 = trigger animation? $04 = in control of airship
05 - Airship X position
06 - Airship Y position
07 - Airship Flyover Animation Counter?
08 - $02 = On chocobo! $80 = Chocobo Animation?
09 - Chocobo X position (for animation?)
0A - Chocobo Y position (for animation?)
0B -
0C - Ship Usable
0D - Ship X position
0E - Ship Y position
0F -
10 - Player Overworld Position X (for saving)
11 - Player Overworld Position Y (for saving)
12 - Constantly read when moving in town ($36)
13 -
14 - Giant Airship Visible
15 - Giant Airship X position
16 - Giant Airship Y position
17 -
18 -
19 -
1A - Key Item inventory? $02 when given Scott's Ring
1B -
1C - Gold low byte
1D - Gold middle byte
1E - Gold high byte
1F - Battle Text Speed (-1 from option chosen on game start)
20-3F - Seems to be treasure chest contents. Everything starts full, at $FF. Opening the chest south of the room you start in decrements 6022 to $FE. #256 chests total in the game?
40-5F - Game event flags
6042 - Starts $8B, turns to $8A when given Scott's Ring
6046 - Starts $7F, turns to $7E when using Wild Rose on bartender in third town. Then to $7C when given Scott's Ring. Flags character sprites vanishing?
60-7F - Items
80-8F - Passwords - Password IDs starts at $F1 and ends at $FF
6100 - Character stats, $40 per character
00 - Character ID - portrait, sprite
00: Firion
01: Maria
02: Guy
03: Minwu
04: Josef
05: Gordon
06: Leila
07: Ricard
08: Leon
01 - Ailment
01: Blind
02:
04: Venom
08: Cursed
10: Amnesia
20: Toad
40: Stone
80: Dead
02 - Name Letter 1
03 - Name Letter 2
04 - Name Letter 3
05 - Name Letter 4
06 - Name Letter 5
07 - Name Letter 6
08 - Current HP low byte
09 - Current HP high byte
0A - Max HP low byte
0B - Max HP high byte
0C - Current MP low byte
0D - Current MP high byte
0E - Max MP low byte
0F - Max MP high byte
10 - Strength - not shown in menu
11 - Agility - not shown in menu
12 - Stamina - not shown in menu
13 - Intellect - not shown in menu
14 - Spirit - not shown in menu
15 - M. Power - not shown in menu
16 - Min Accuracy %
17 - Max Accuracy %
18 - Attack
19 - Head (Item ID)
1A - Body (Item ID)
1B - Hands (Item ID)
1C - Right Hand (Item ID)
1D - Left Hand (Item ID)
1E - Item 1 (Item ID)
1F - Item 2 (Item ID)
20 - Strength
21 - Agility
22 - Stamina
23 - Intellect
24 - Spirit
25 - M. Power
26 - Defense
27 -
28 -
29 -
2A - Min Evasion %
2B - Max Evasion %
2C - Min M. Resist %
2D - Max M. Resist %
2E -
2F -
30-3F - Known spell slots (Usable spell IDs start at $C0, end at $E7?)
Weapon skills:
$10 per character, in $40 byte intervals
Levels are -1, where 00 = level 1
Both should cap at $63
6200 - Firion's weapon Skills
6240 - Maria's weapon skills
6280 - Guy's weapon skills
62C0 - Character 4's weapon skills?
00 - Unarmed level
01 - Unarmed experience
02 - Shield level
03 - Shield experience
04 - Dagger level
05 - Dagger experience
06 - Staff level
07 - Staff experience
08 - Spear level
09 - Spear experience
0A - Sword level
0B - Sword experience
0C - Axe level
0D - Axe experience
0E - Bow level
0F - Bow experience
6235
6275
62B5
62F5 - Row ; 0 = back row, 01 = front row, 80 = no character portrait visible in menus
Quote
Weapon skills:
$10 per character, in $40 byte intervals
Levels are -1, where 00 = level 1
Both should cap at $63
Shouldn't the levels cap at 0F, for 15? Max spell and weapon levels are 16.
On that note, having tampered with SRAM myself a bit to "fix" overmaxed HP, I thought it might be helpful to outline what hex values are needed as "caps" that the game doesn't already impose. While the game DOES prevent stats like Strength, Agility, Stamina, Spirit, Intellect, and presumably M.Power from going above 99, it still does level-up checks on them.
Spirit, Strength, and Intellect, at least, need to have their checks for leveling up skipped when they are at $63 (99), since there's no reason for them to be continuing to try to level up, and it results in you often losing Strength, Intellect, and Stamina accordingly. This can be applied to M.Power, Stamina and Agility as well, but they don't have any unwanted side effects of continuing to "level up" other than it being inconsistent were the other 3 stats fixed.
Weapon Skills aren't problematic either, other than the end-of-battle spam. Since they are using -1 value and starting at $00, $0F should translate to 16, at which point the check for weapon levels should not happen anymore, but they still do. There's also some sort of underflow bug based on battle rank with the weapon levels specifically at $0D and $0E, resulting in a single attack on a Rank 1 battle immediately leveling up the weapon skill.
Spell levels do appear to cap properly, as the experience counter will still go up to 98 or 99, but will never go up beyond that. Scratch that, spells will also "level up" even at 16 if used enough times in battle, though it will simply reset the experience counter back to 00 in the magic window. This game just simply does not have hard caps for most things, with only a few mechanics in place to ensure that weapon and spell levels don't increment above 16 and stats don't increment above 99, while the math is still being calculated and messages displayed as if there were no caps.
Evasion and Magic Defense should cap at 16 (whether that's $0F or $10 I don't know), and I haven't tested if they can still go up. There'd be no easy way to test that, as there's no message stating they go up and you'd only be able to tell from looking at your stat windows 2 battles later.
For HP and MP, having already modified my SRAM to fix overmax, I can say that HP should cap at $0F27 (low byte then high byte) for 9999, and MP should cap at $E703 for 999. Currently it seems likely that these values would continue to level up to $FFFF and would no doubt overflow.
Quote from: redmagejoe on December 13, 2019, 08:39:29 PM
Shouldn't the levels cap at 0F, for 15? Max spell and weapon levels are 16.
Oh, yeah, probably! Last time I played FF2 was I think the Wanderswan version over 10 years ago, so I'm not as familiar with its systems. I guessed 99 was the level cap since that's the highest number there's room to print on the status screen.
QuoteEvasion and Magic Defense should cap at 16 (whether that's $0F or $10 I don't know), and I haven't tested if they can still go up. There'd be no easy way to test that, as there's no message stating they go up and you'd only be able to tell from looking at your stat windows 2 battles later.
For all this stuff, what I'd do to find where the routines are change them, is: Starting on the overworld, find one character's evasion stat in Mesen's memory viewer, right click and set a breakpoint on read, then get into a battle. There's probably a bit of code that copies over stats to temporary battle stats. So then you step through it until you see the evasion stat written to somewhere else in memory shortly after that? If it doesn't break there, even better: just set a "write" breakpoint on the same evasion stat, and when it goes up in or after battle, that's the thing that does it! And if its temporary battle stats, set the "write" breakpoint on the new location.
Right now I can't even figure out how to get Chaos Rush's translation patched in without graphical errors...
Here's an IPS you can just slap on the (J) [!] ROM. It's a patch I just pulled using Lunar. Everything works and is retranslated, though it doesn't have the (sometimes buggy) NeoDemiforce title screen.
https://drive.google.com/open?id=1jB61-EFQaoZkxCZnxld4vp0gV9SH8in_
It looks like there are a couple of things already documented on the wiki (https://datacrystal.romhacking.net/wiki/Final_Fantasy_II:RAM_map).
A quick search in my generated disassembly for $6100 turns up (among lots of other things) what looks like code for initializing party stats ($00:$8F80 - $00:$8FBF does appear to contain character #1's initial stats) and other stuff:
0x001C7F|$00:$9C6F:A2 3F LDX #$3F
; control flow target (from $9CA2)
0x001C81|$00:$9C71:BD 80 8F LDA $8F80,X
0x001C84|$00:$9C74:9D 00 61 STA $6100,X
0x001C87|$00:$9C77:BD 00 90 LDA $9000,X
0x001C8A|$00:$9C7A:9D 40 61 STA $6140,X
0x001C8D|$00:$9C7D:BD 80 90 LDA $9080,X
0x001C90|$00:$9C80:9D 80 61 STA $6180,X
0x001C93|$00:$9C83:BD 80 8F LDA $8F80,X
0x001C96|$00:$9C86:9D C0 61 STA $61C0,X
0x001C99|$00:$9C89:BD C0 8F LDA $8FC0,X
0x001C9C|$00:$9C8C:9D 00 62 STA $6200,X
0x001C9F|$00:$9C8F:BD 40 90 LDA $9040,X
0x001CA2|$00:$9C92:9D 40 62 STA $6240,X
0x001CA5|$00:$9C95:BD C0 90 LDA $90C0,X
0x001CA8|$00:$9C98:9D 80 62 STA $6280,X
0x001CAB|$00:$9C9B:BD C0 8F LDA $8FC0,X
0x001CAE|$00:$9C9E:9D C0 62 STA $62C0,X
0x001CB1|$00:$9CA1:CA DEX
0x001CB2|$00:$9CA2:10 CD BPL $9C71
0x001CB4|$00:$9CA4:60 RTS
Later on, there's what looks like possibly some code for capping stats at 99 (#$63), which the CDL didn't log, so I guess our awesome taotao friend didn't manage/bother to max those stats:
; indirect control flow target
0x03A1BF|$0E:$A1AF:A9 10 LDA #$10
0x03A1C1|$0E:$A1B1:D0 0A BNE $A1BD
; indirect control flow target
0x03A1C3|$0E:$A1B3:A9 11 LDA #$11
0x03A1C5|$0E:$A1B5:D0 06 BNE $A1BD
; indirect control flow target
0x03A1C7|$0E:$A1B7:A9 14 LDA #$14
0x03A1C9|$0E:$A1B9:D0 02 BNE $A1BD
; indirect control flow target
0x03A1CB|$0E:$A1BB:A9 13 LDA #$13
; control flow target (from $A1B1, $A1B5, $A1B9)
0x03A1CD|$0E:$A1BD:85 80 STA $80
0x03A1CF|$0E:$A1BF:A5 F0 LDA $F0
0x03A1D1|$0E:$A1C1:29 C0 AND #$C0
0x03A1D3|$0E:$A1C3:05 80 ORA $80
0x03A1D5|$0E:$A1C5:AA TAX
0x03A1D6|$0E:$A1C6:BD 00 61 LDA $6100,X
0x03A1D9|$0E:$A1C9:18 CLC
0x03A1DA|$0E:$A1CA:69 0A ADC #$0A
0x03A1DC|$0E:$A1CC:C9 64 CMP #$64
0x03A1DE|$0E:$A1CE:90 02 BCC $A1D2
; code -> unknown
0x03A1E0|$0E:$A1D0:A9 63 ; LDA #$63
0x03A1E1|$0E:$A1D1:63 ; INVALID OPCODE
; unknown -> code
; control flow target (from $A1CE)
0x03A1E2|$0E:$A1D2:9D 00 61 STA $6100,X
0x03A1E5|$0E:$A1D5:BD 10 61 LDA $6110,X
0x03A1E8|$0E:$A1D8:18 CLC
0x03A1E9|$0E:$A1D9:69 0A ADC #$0A
0x03A1EB|$0E:$A1DB:C9 64 CMP #$64
0x03A1ED|$0E:$A1DD:90 02 BCC $A1E1
; code -> unknown
0x03A1EF|$0E:$A1DF:A9 63 ; LDA #$63
0x03A1F0|$0E:$A1E0:63 ; INVALID OPCODE
; unknown -> code
; control flow target (from $A1DD)
0x03A1F1|$0E:$A1E1:9D 10 61 STA $6110,X
I can also provide my .sav if anyone would like more tangible resources to play with. I currently have everyone at 99 Strength, Agility, Stamina, and Spirit, with Intellect being between 1 and 3 due to the aforementioned gripe about maxed stats leveling up and decreasing other stats. Firion has 9999 HP (was 10030 until I "fixed" it in SRAM) and 999 MP ("fixed"), while the other two characters have ~7400 and 247, and ~6400 and 999 ("fixed"). This was achieved through some extremely obsessive and tedious leveling for the purposes of testing the limits of the game systems. I imagine this might be helpful rather than having to artificially manipulate those stats, though I suppose that isn't in and of itself a difficult task.
Here's the .sav. Characters have above-listed stats, and most of the spells in the game. Cure is at 16 on Firion and Guy, in case that is of benefit to testing spell leveling or experience.
https://drive.google.com/open?id=10j57hRiNoy-tCxq3YoyTwaP5CCJZC8Zs
Thanks for the patch! Is it a bug that the flowing text (Programmed by Nasir and so on) has like a whole line of each letter across the screen as it draws in?
Another possible bug is that after being given Scott's ring, and asking "Wild Rose" a second time, will loop all his previous dialogue again, including the ring giving message? Perhaps a forced exit from dialogue when sprites vanish, after pressing any button... unless that would break something else.
I found a few more SRAM bytes, and the $20 bytes that I thought might be game event flags certainly must be. Instead of using up $100 bytes and only using 2 bits or so per byte like FF1, they just crammed it all in there, which I guess is how they made room for 4 save slots, with each save being $300 bytes. I'll update my other post with the things I find as I slog through the game's beginning...
That weird drawing effect is, I think, the intended behavior of the title screen. It's just not a very "pretty" title screen, which is why a new one was made for the Neodemiforce patch. Would be nice to get that as a standalone patch someday. Also I provided my .sav if you get to a point where you want to see how it's handling stat behaviors past the beginning game slog.
I put a few hours into creating an updated version of my disassembly (https://drive.google.com/file/d/1apgMx_eYEU3r319ZX4PueixjpgCljqil/view), marking in some CHR, the main dialogue, unused code paths, a bunch of pointer tables, probably most of the inter-bank references, formatting some of the larger data blocks, a teeny-tiny bit of documentation on a couple of routines (mostly the ones involving bank swaps), stuff like that. It's still pretty rough, but should make any code changes that you want easier to make.
Quote from: abw on December 15, 2019, 04:35:56 PM
I put a few hours into creating an updated version of my disassembly (https://drive.google.com/file/d/1apgMx_eYEU3r319ZX4PueixjpgCljqil/view), marking in some CHR, the main dialogue, unused code paths, a bunch of pointer tables, probably most of the inter-bank references, formatting some of the larger data blocks, a teeny-tiny bit of documentation on a couple of routines (mostly the ones involving bank swaps), stuff like that. It's still pretty rough, but should make any code changes that you want easier to make.
This is great, abw. I'm going to start looking through this and see if I can work with what's provided while I've got some downtime between work projects. The main issue is knowing where to even begin looking to make changes, I'll admit. Once I have an idea of where the relevant routines are for handling stat level up checks at the end of battle, I'll have a much better idea how to move forward.
Found an issue with the ChaosRush translation that can very easily be fixed, but... I noticed that using DTE, he made some spell names use 5 spaces instead of 4, and as a result, in battle any spells above level 9 will be pushed out of the box that displays during the cast. So Scourge 16 becomes [Scrge 1]. This is an easy enough fix, where the space after a spell name is removed so that in the case of 5-character spells it would be [Scrge1 ] or [Scrge16], and for other spells that already only use 4 spaces, it would be [Cure1 ] to [Cure16 ]. Doesn't look too troublesome, but I'd need to find where the combat display boxes handle spell names and levels to adjust that. Removing the space would be easier than trying to check the spell level and formatting based upon that. It wouldn't look too inconsistent with NES era formatting I don't think.
I discovered potentially another bug. When one's Agility and thus Evasion are high enough, and Shield skill is high/maxed, it appears that one of two things are happening. Either Evasion % is rolling over, or it is somehow going above 99% and is only displaying the two least significant digits in the stat window; this seems less likely given the other bugs in this game involving lack of caps.
To elaborate, with Level 16 Shields, 99 Agility, and Dragon Shield equipped on, say, Guy, who has Level 8 Evasion, he goes from 8-99% without the shield equipped to 8-05% with the shield equipped.
D'oh, and apparently I missed the extremely small window to access Paul's Secret Stash. I guess you have to talk to him right after the Cyclone appears and say Cyclone to open it up, but I'm not sure how it's managed. Does it become an object you can permanently interact with? Does it become an open door you can walk through? Does it allow you to interact with it until the access window closes? I'm curious now where the flag is for this value, as I'd like to see if it's possible to manipulate that, or the one involving talking to Paul about the Cyclone all the way up to the end of the game. Another consideration for a patch.
Quote from: redmagejoe on December 15, 2019, 05:15:26 PM
This is great, abw. I'm going to start looking through this and see if I can work with what's provided while I've got some downtime between work projects. The main issue is knowing where to even begin looking to make changes, I'll admit. Once I have an idea of where the relevant routines are for handling stat level up checks at the end of battle, I'll have a much better idea how to move forward.
Like Jiggers said, if you already know a RAM address (e.g. character stats start at $6100) related to the code you're looking for and you already know approximately when the code you're looking for runs, setting a breakpoint on that address and then playing through the part of the game where the code runs is probably the best way to go. (And if you don't know a RAM address, you can also use a trace logger and PPU breakpoint and backtrack from when something related to the code you're looking for appears on screen.)
For character stats, I did just that, setting a write breakpoint on $6110-$6115 and playing until Firion gained a stat. The breakpoint fired at $05:$A8A0, and poking around that section of the disassembly suggests $05:$A874-$05:$A903 is the code for increasing Spirit/Strength/Intellect at the expense of Strength/Intellect/Stamina (capped at 99 for increases and 1 for decreases). $05:$A7E9-$05:$A841 looks like it handles HP/MP increases (no caps, so probably with enough grinding you could overflow the 16-bit storage and end up with 0 Max HP!), and $05:$A842-$05:$A86C is probably for skill level increases (capped at 15). All those routines get called from code starting around $05:$A5F1 (or possibly earlier, I haven't really looked). I added some more rough notes to my disassembly, so hopefully that will help a bit!
Every little bit definitely helps. I admit that even with finding where the code fires, it's a little overwhelming thinking of trying to insert into that the necessary routines to prevent the code, such as checking for what the stat currently is before doing the increment/decrement and subsequent message display. My guess is that it runs through all of that and then after the fact it simply sets the stat back to 99 if the value at the end of the math is above $#63.
Also I'm going to leave what Jiggers found and PM'd me regarding the flag for Paul's Secret Stash: "Paul's Secret stash, my guess is the flag for it will be in the range of $6040 to $605F"
It'll most likely be just one bit changing from 1 to 0. There's 256 bits there, and I'm really confident that covers every change in the game. Every time an NPC vanishes, or shows up on another map. Using an item to unlock a specific story-progress door...
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!
https://drive.google.com/open?id=1gNi7FehppURCHvebVmFhNv8-S_GqL6PN Here's a table file for hex editors and anything else that can use 'em.
I've been using Mesen's debugger to start labeling bits here and there as I played through the start. If anyone else uses that emulator, I can try to set up some kind of sharing thing so we can all keep each other updated?
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?
Also, I'm not sure if this could be of help to anyone, but since there's already a fix for the Weapon/Magic leveling exploit (which I haven't looked at personally and haven't used in my playthrough, but I would assume only finalizes points towards exp after the round of turns is submitted, and thus should also prevent stats from going up by attacking and cancelling 46 times or such), a diff of a before and after ROM with the fix may be of use to narrowing down exactly what data is being manipulated once an attack order is sent. I don't know, just throwing out thoughts I had while thinking about this game's bugs.
Anyway, I decided to play around with this in FCEUX's 6502 debugger with breakpoint set on Read $6113, which is Firion's Intellect. I cast/cancelled Fire 26 times to guarantee an Int increase, and as soon as the battle ended, "You Won!" faded, "x Gil" drew then faded, breakpoint fired. Using Step Into, it appeared to go through the following lines:
05:A898:B1 7A LDA ($7A),Y @ $6113 = #$06
05:A89A:AA TAX
05:A89B:E8 INX
05:A89C:8A TXA
05:A89D:20 3A AA JSR $AA3A
05:AA3A:C9 64 CMP #$64
05:AA3C:90 02 BCC $AA40
05:AA3E:A9 63 LDA #$63 (skips this step because my INT isn't high enough, but this is important)
05:AA40:60 RTS -----------------------------------------
05:A8AC:91 7A STA ($7A),Y @ $6123 = #$06 (becomes #$07 upon Stepping forward 1, likely this is indicating that after this instruction, the new value of INT, which was 6, is now 7)
05:A8AE:A6 AD LDX $00AD = #$00
05:A8B0:A5 47 LDA $0047 = #$13
...
Not sure how helpful this is, but it does appear to do a comparison of the stat to see if it's gone up to 100, and if so, sets it back to 99. It seems that by this point in the instructions, it has already done the increment. Obviously the ideal would be to do a compare earlier to 99 and somehow skip the dialogue box and level up dialog altogether rather than doing the math first and correcting after the fact. I'm assuming that on the first line, LDA ($7A),Y @ $6113, the next line should be CMP #$63 if we were to want to have it do a comparison before anything else is looked at.
Sorry if this reads rambly, I'm still very inexperienced working with such low level programming and I'm trying to put words to concepts I know I'm trying to make happen with a toolkit I'm unfamiliar with. Essentially it does a conditional to set a stat >99 back to 99 after incrementing the stat, but the ideal behavior would be to have a conditional earlier before anything is written to 6113 to see if writing should even happen, so as to avoid not only the dialog, but more importantly, skip the routine that checks for the opposite stat to decrement.
For Firion, his base stats are 6110 (Strength), 6111 (Agility), 6112, (Stamina), 6113 (Intellect), 6114 (Spirit), 6115 (M.Power). Whenever 6114 goes up, there's a chance for 6110 to go down, 6113 -> 6112, and 6110 -> 6113. So 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.
So I set a breakpoint for reading 6110 and 6113. Curiously, at the start of a battle, it reads strength twice. It reads through the five stats, I would assume, but then there's a second read of Strength, but not Int, before the battle proceeds with no more breaks. I am currently trying to get a battle where I have a Strength increase and an Int decrease so that I can step through the process. 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. For instance, attacking 46 times guarantees a Strength increase, which means that without the cancel exploit fix, every time Firion attacks there should be a variable incrementing. Interestingly, the "Intellect down" happens before the "Strength up"...
QuoteI 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.
There are several ways. If you simply want to view ROM address, you can use ROM offset checkmark in debugger, or hover your cursor over instruction and the address will be shown at the bottom of the window.
If you want to actually go there in ROM, the quickest way is to right click to the left of the instruction line in debugger (grey vertical line). Fceux must be paused beforehand.
Quote from: Cyneprepou4uk on December 25, 2019, 07:33:29 PM
There are several ways. If you simply want to view ROM address, you can use ROM offset checkmark in debugger, or hover your cursor over instruction and the address will be shown at the bottom of the window.
If you want to actually go there in ROM, the quickest way is to right click to the left of the instruction line in debugger (grey vertical line). Fceux must be paused beforehand.
I must be blind because I didn't even notice that checkbox. Thank you again Cyne! This will help immensely! :thumbsup:
Right now I'm trying to figure out how to set a breakpoint in a way that catches the increment of the variable that handles number of times Firion has attacked, since that appears to be calculated even before Strength is read, to determine whether or not Strength does, in fact, increase.
That is a helpful post, when I feel well enough to poke at this game again.
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.
So make sure you're watching for the other set of stats as well?
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, and displays as A9 in the status window ($6D), and in the post-battle level up of Strength, my Read 6120 breakpoint fired twice rather than just once, so it appears that a few things are happening that involves reading that value.
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. What we need to find is what instructions run before the LDA ($7A), Y before jumping the gun and trying to stick the CMP between the LDA and the TAX, INX, TXA instructions. In this case, my aforementioned suggestion of testing both a before and after of the cancel exploit fix may be helpful. Obviously for the purposes of testing, it's better to use a non-fixed version since it makes the stat ups easier to accomplish.
That sounds like a bug to me. Is there ANY game that purposefully displays hex numbers?
Here's some more RAM mapping.
7D7A - Battle Stats, 48 bytes per entity
7D7A offsets:
00 - Min Evasion
01 - Max Evasion
02 - Char Stat $29
03 - Min Magic Resist
04 - Max Magic Resist
05 - Char Stat $2E
06 - 0 at start, then...?
07 - Always #$14 for players
08 - Ailment
09 - 0 at start, then...?
0A - Current HP low
0B - Current HP high
0C - Current MP low
0D - Current MP high
0E - Max HP low
0F - Max HP high
10 - Max MP low
11 - Max MP high
12 - 0 at start, then Intellect
13 - 0 at start, then Spirit
14 -
15 -
16 -
17 -
18 - Min Accuracy
19 - Max Accuracy
1A - Attack
1B -
1C -
1D -
1E - Defense
1F - Char Stat $27
20 - Char Stat $28
21 -
22 -
23 -
24 - Char Stat $2F
25 -
26 -
27 -
28 -
29 - Command (00=attack) (FE=flee)
2A - Target (high bit set for targeting enemies)
2B -
2C - Ailment backup
2D - 0 at start, then backup of 09?
2E - ??
2F - ??
7CF3 - Firion's Attack counter (rises when confirming attack, does not reset between turns)
7CF4 - Maria's attack counter
7CF5 - Guys's attack counter
Not insanely helpful at the moment, but if you hex-edit the 7CFx things, it saves time selecting and canceling over and over?
There's still a handful of stats I don't understand. And this game feels a lot more convoluted for loading up stats for battle than FF1. It overwrites the save RAM stats as it re-calculates everything from scratch I think. And it sets a bunch of battle stats to 0 before calculating them, so what's that about?
I should be hunting down where stats change after battle next.
I will do my very best to break down everything I can about stats and combat, having reached the end of the game now and been min-maxing, in a way that lends itself to figuring out how to fully disassemble the system. I should have provided a link in this thread's first post to a page that breaks down the way stats behave in the game, but I'm going to try to summarize them here and correlate them to what the code is doing.
Strength is used for determining Attack Power alongside weapon's attack value, which impacts the damage value of a single hit, in a system which, not unlike FF1, has multi-hits. It has a 1 in 5 chance of decreasing by 1 at the end of a battle if and only if the criteria for Spirit increasing by 1 is met. The formula for determining whether or not Strength increases can be simplified as being X/46 chance where X is number of times that character attacked in a single battle (so 46 attack/cancels will guarantee a Strength increase).
Strength+ -> Intellect-
Spirit+ -> Strength-
Agility has a singular purpose in that it is the baseline value for determining Evasion, before armor weight and shields are taken into account. Agility's chance to increase is directly correlated to your character's Evasion, and does not have a see-saw interaction with any stat, thus will never decrease.
Stamina is used to determine how much HP the character gains when HP does increase. That's it. Its chance to increase is based on seemingly the same, but a separate formula, based on the difference between the character's HP at the start of the fight and the end of the fight. The bigger the difference, the more likely Stamina and/or HP will increase. As it appears to be two of the same/similar formulas, it is very possible for a character to gain Stamina but not HP, or vis versa, with it being unlikely to only get one or the other if the character has lost, say, 90% of their HP. The criteria appears to be at least 1/8 of their Max HP lost, but don't have hard numbers. Stamina has the same 1 in 5 chance to decrease if Intellect increases, but does not itself decrease another stat.
Intellect+ -> Stamina-
Intellect is ASSUMEDLY used to determine your Black Magic damage (and possibly success chance of spells?), and correlates to the number of times a Black Magic spell is cast, X/26. It has a 1 in 5 chance of decreasing when Strength increases, and Stamina has a 1 in 5 chance of decreasing when Intellect increases. This is the stat that has spent the most time in my playthrough being at single digits on account of the bug where maxed stats still do a level-up check.
Intellect+ -> Stamina-
Strength+ -> Intellect-
Spirit is used to determine your White Magic damage/healing (and possibly success chance of spells?), and correlates to the number of times White Magic spells are cast, X/16. This is by far the easiest stat to raise in a single battle even without the exploit, given its low criteria. For whatever reason, target-all spells cast by enemies on the party will somehow interact with Firion's Spirit up counter, or perhaps it has its own counter that is simply referring to the wrong address. Strength has a 1 in 5 chance of decreasing when Spirit increases.
Spirit+ -> Strength-
M.Power determines how much MP a character gains when it does increase, and thus, like Stamina and HP, has the same criteria for increasing. The difference between MP at the start and at the end of battle affects the chance, again two different chances, for MP and/or M.Power to increase. M.Power may also affect the power of spells alongside Intellect and Spirit? Supposedly it also factors into Magic Defense.
Evasion Levels and %, from my understanding, correlates to the number of hits you can dodge. So if an enemy were to hit, say, 16 times, and your Evasion is only 8-99%, you could only dodge 8 of the 16 hits. Likewise, I believe the % pertains to how likely you are to avoid hits under that threshold. Having a high % alone seems to increase your chance of increasing Agility, but as it is a roll between 0 and 255, you'd still only have a 99/255 chance of gaining Agility. Evasion Levels, on the other hand, only increase when an enemy targets that character. This, like the cancel exploit, applies regardless of whether the enemy actually gets the chance to attack the character, as Preemptive 1-turn wipes on enemies that, ASSUMEDLY, are all targetting Firion for instance, still sees Firion gaining an Evasion Level from my testing. Given the 1-battle delay on updating its display in the menu though, this is not conclusive testing. I would assume 16 is the highest level of Evasion, but I have not gotten that far.
Magic Defense and % operates similar to Evasion, with magic targetting characters having the effect of increasing Magic Defense levels, and the % pertaining to how many "hits" of a spell level above 1 are successful. This is harder to increase currently due to the target-all Firion Spirit bug.
Accuracy is affected by your Weapon Skill in the weapon you are wielding. Thus a Level 16 Sword wielder has a high chance of hitting 14-16 times, with an Accuracy level listed as 16 in their window, and then the actual % being their chance for each of those hits, modified by a particular weapon's accuracy malus.
Weapon Skill has a chance to increase in the same way as Strength, though without the X/46. It increases by 1 per use of an attack, with the battle rank determining how a static bonus or malus. So trying to level a Weapon Skill at 10 from 0 exp on a Goblin at the start of the game would require you to attack ~107 times rather than 100. Not exact numbers, just an example. Spell levels work with this mechanic as well, demanding using them on higher threat enemies to maintain leveling efficiency of your weapons and spells.
I found the much more analytical source I'd used a while ago, and I'll add the link to the other one in the first post.
I think this might be what you're looking for? (Edit: Oh, yeah, this is near the same address you posted!)
There's also some stuff at 05:A675 to look at -- it sets some variables and jumps to this next block to do things. It does 3 of these, no comparing if it needs to do them at all first or not. My head hurts too much to go through more of it, but I think this is the right track!
05:A874: ($16874)
CLC ;
LDA $47 ; = #$10 <- Strength stat
ADC #$10 ; Add #$10
STA $48 ; = #$20 now; secondary Strength stat
CLC ;
LDA $4A ; = #$13 <- Intelligence stat
ADC #$10 ;
STA $4B ; = $23
LDX #$00 ;
LDA $46 ; = $2D
JSR $FD11 ; Unsure what happens here exactly. Involves another jump to some kind of division routine?
STA $46 ; now = $26
LDY $9E ; $9E seems to be character counter in a lot of this code, currently $00 for Firion. May be doubled as an index (Maria, usually $01, would become $02; and Guy, $04)
SEC ;
LDA ($44),Y ; pointer to $7CF3 = #$FF
BEQ L16903 ; If $0 then goto this address
SBC $46 ; Subtract #$26 from #$FF (carry is set)
BCC L16903 ; If carry clear then goto this address
LDY $47 ; = #$10 <- Strength stat
LDA ($6100),Y ; Load stat
TAX ; Transfer to X
INX ; Increment X (add 1 to stat)
TXA ; Transfer back to accumulator
JSR CMP_100 ; ($AA3A) - compares to #100, and if carry is set, loads #99 into A
STA ($6100),Y ; Save stat
LDY $48 ; = #$20 ; secondary Strength stat
LDA ($6100),Y ; Do all the same things!
TAX ;
INX ;
TXA ;
JSR CMP_100 ;
STA ($6100),Y ; So now the main and backup Strength stat is +1 and capped at 99.
LDX $AD ; = #$01 ; this is the counter for stat upgrades
LDA $47 ; = #$10
SEC ;
SBC #$13 ; Subtract #$13 from #$10, resulting in #$FD
BEQ L168BD ; If $0 then do this
BCS L168C1 ; If carry set, then do this
LDA #$5F ; Neither branched; Loads #$5F into A
BNE L168C3 ; Jump ahead
L168BD:
LDA #$60 ; Subtraction resulted in #$0, so load #$60 into A
BNE L168C3 ; Jump ahead
L168C1:
LDA #$61 ; Subtraction resulted in carry being set, so load #$61 into A
L168C3: (05:A8C3 ($168C3))
STA $7FBA,X ; $7FBA seems to be a string of FFs until its set here; its being saved as 5F now
INC $AD ; Increase the counter
LDX #$00 ;
LDA $49 ; = #$05
JSR $FD11 ; The weird math thing again. Output: A = 1, X = 1, Y = 20
BNE L16903 ; A = 1 so jump to end... but if it DIDN'T, then:
LDY $4A ; = #$13
LDA ($6100),Y ; Load Intelligence stat
CMP #$01 ;
BEQ L16903 ; If Intelligence = 1 then jump ahead to end
TAX ;
DEX ; Transfer to X, decrement, transfer back
TXA ;
STA ($6100),Y ; Save Intelligence stat
LDY $4B ; = #$23 ; other Intelligence stat
LDA ($6100),Y ; Note that it doesn't check if its 1 here
TAX ; Otherwise, does the same stuff again.
DEX ;
TXA ;
STA ($6100),Y ;
LDX $AD ; Load the counter for the string of FFs at 7FBA
LDA $4A ; = #$13 still (I accidentally pressed the wrong button here and skipped past all this so I don't know how accurate I'll be.)
SEC ;
SBC #$12 ; Subtract #$12 from the stat being lowered
BEQ $A8F6 ; If $0 then do this
BCS $A8FA ; If carry set then do this
[A8F2]
LDA #$5F ; If neither then load #$5F
BNE $A8FC ; and jump ahead
[A8F6]
LDA #$63 ;
BNE $A8FC ;
[A8FA]
LDA #$60 ;
[A8FC]
ORA #$80 ; Set high bit
STA $7FBA,X ; Save in the list of FF
INC $AD ; Increment stat counting thing
[A903]
RTS ;
I've also got an assembly project working! Right now its just each bank's data slapped into .asm files with the .incbin command, so its... not really helpful at all. And since its not technically disassembled code, I don't know about the legality of uploading it yet.
Also its based off the patched English version with B-button dash so its not super authentic either.
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". Just so much commentary to sift through to see what's really there.
That's the area I was looking at/for, yeah. As for trying to make sense of abw's disassembly, perhaps it would be advantageous to work with him on that? If you host it on Google Drive or GitHub or somesuch and link it here, it would definitely be of benefit to myself and others attempting to work on Final Fantasy II patching.
Really awesome to hear that you've begun work on a disassembly, Jiggers! And it's okay that it's not a disassembly of the original for the purposes of romhacking, I'm sure. I'm going to keep digging around through breakpoints and see if I can't narrow down specifically what needs to be changed. As for the legality of uploading a disassembly, you could do what Steve_Hacks did for the Crystalis Randomizer that I used, which was write a strip script that spits out the disassembly for you the individual user. The resulting Crystalis.s file is what I used to navigate and make changes to the Crystalis ROM for the Sky Tower Exit hack. He said his reasoning behind doing it that way was to avoid legal issues.
https://github.com/crystalis-randomizer/crystalis-randomizer/blob/master/scripts/strip
...I got no idea how to do anything like that, ahah...
I feel like I screwed this up enough to be safe though. https://drive.google.com/open?id=13PyWNbKl-QZKbjvsrdY5R4jY9oDWom5o
Having trouble compiling because of the branch operations. They spit out range errors. The first one for instance:
BEQ $0F ; $C066 ; C055 $F0 $0F
Originally it was "BEQ $C066" which, reading it from the debugger window, makes sense. You want to see exactly what it does: branch to address $C066. Assembling it, I thought, no wonder its a range error, it should only be one byte. But even with $0F there, it does it. *shrug* So I'm stumped. I got to around $E000 with my macro to fix every BEQ, BNE, BCC, BCS, BMI, and BPL I came across, but why keep trying to fix them if this fix isn't working? And it just gives another kind of error if I try #$F0.
So the only thing I can think of it do is to go through the whole file and do the labels manually. I already spent basically all day cleaning this one up (only bank 0F so far) and I'm clueless about how to do things efficiently. I can do macros in Notepad++ but I still need to point to where the macro starts.
Hm. So I can undo the branch instruction edits, and the ones with the labels should be fine, its just the ones with $xxxx that I need to do manually. Or someone could figure out a way to script it? Edit again: I got Python Script for Notepad++ but I have no idea how Python works.
Example:
BEQ $C066 ; C055 $F0 $0F
LDA #$77 ; C057 $A9 $77
STA $FA ; C059 $85 $FA
JSR $F476 ; C05B $20 $76 $F4
LDA #$0E ; C05E $A9 $0E
JSR L3FE03 ; C060 $20 $03 $FE
JSR $B890 ; C063 $20 $90 $B8
LDA #$0E ; C066 $A9 $0E
Every BEQ, BNE, BCC, BCS, BMI, and BPL that has a $ after it... remove the $, copy the 4 characters, find them in the comments (if they exist; if not do nothing), go back one line, and make a label. Like so:
BEQ C066 ; C055 $F0 $0F
LDA #$77 ; C057 $A9 $77
STA $FA ; C059 $85 $FA
JSR $F476 ; C05B $20 $76 $F4
LDA #$0E ; C05E $A9 $0E
JSR L3FE03 ; C060 $20 $03 $FE
JSR $B890 ; C063 $20 $90 $B8
C066:
LDA #$0E ; C066 $A9 $0E
http://npppythonscript.sourceforge.net/
def colon(m):
return \:
def branch(m):
return r"\1"
def address(m):
return r"\2"
def searchfor(m):
editor.copyRange
editor.research(r"(BEQ|BCC|BNE|BMI|BPL|BCS)\s\$(....)", r"\1 \2")
editor.copyRange(10, 14)
editor.research(r"\; searchfor", r"1\")
editor.lineUp()
editor.home()
editor.paste()
editor.addText(colon)
Gave it my best shot but its clear I don't know what I'm doing with this language. The only thing that actually ever worked was "editor.rereplace(r"(BEQ|BCC|BNE|BMI|BPL|BCS)\s\$(....)",r"\1 \2")"
You probably should include BVC and BVS as well
Oh, yeah. Luckily there's not many of them and they all use labels Mesen pre-made.
I managed to work out a macro and used Control+F3 to do the searching, now I'm sifting through to find mistakes. 6 bytes still off. Will update the zip file when its done. And then I'm never doing this again. :( Like, I'll go through and comment when I discover how things work, and help identify variables and edit the code when its time to fix the bugs, but I am NOT going to convert another bank to assembly code...* Even though it seems we'll need to do Bank 05 next...
Update! https://drive.google.com/open?id=13PyWNbKl-QZKbjvsrdY5R4jY9oDWom5o
And a github I barely know how to use. :-[ https://github.com/JiggeryPonkery/FF2-Disassembly
Its a start!
* Another Update: Slight change of heart, because the Bank 05 stuff that I think is the stat level up code that's most important is only like $1000 bytes and therefore only a quarter the size of the fixed bank...? Should just be... a lot easier to sort through and tidy up. So I'm doing that now...
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.
Learned a lot more about the structure of the game just by watching which banks are swapped in while playing. Going to use the table file I made and see if I can spot some text data and mark that down. Next thing I'll do after that, someday hopefully soon, is to step through Bank 05 and update the variables and labels so its easier to see what its doing.
This is amazing work, Jiggers. I admire the energy you're putting into this, and wish I could contribute more than just my working knowledge of the game from an end user standpoint. I think I've figured out where to make the necessary changes, but of course the issue now is space. Moving things around, updating pointers...
In bank_05, starting at line 3414 is the area we were looking at, with INX being the increment that increases the stat, and the two instructions DEX further down at line 3452 and 3456 are the decrements of the opposing stat.
QuoteAnd a github I barely know how to use
Try GitHub Desktop (https://desktop.github.com), it's quite simple. Make changes in your local folder and commit updates through it.
QuoteI managed to work out a macro and used Control+F3 to do the searching
I'd like to hear more details.
GitHub desktop's what I use. I had to manually remove the .gitignore files from the folder before it would take them out. Just silly things like that always tripping me up.
To get the branch labels set right, I first used the regex from earlier:
(BEQ|BCC|BNE|BMI|BPL|BCS|BVS|BVC)\s\$(....)Then used the Mark function, checked "Bookmark line" and did Mark All. Then in the Search menu > Bookmarks > Copy Bookmarked lines. Scroll to the end of the file and paste. Hold down Alt and drag the mouse cursor down the front, to go into column editing mode, and add in a ;
Then re-use the Regex and do a search and replace, where replace is "$1 $2 " to remove the $ from the branch operations.
Then scroll down to the last line of the file, the bottom of the address list, and highlight it. Use Control+F3 to copy that text and search for the next instance.
Then the macro is:
Control+C
Home (twice, because sometimes it doesn't go to the very start of the line and just goes to the first character)
Up
Enter
Control+V
:
Home (twice)
Delete (twice) - removes the "; "
Control+End - goes back to the end of the file
Shift+Home - highlights the next address to search for
And repeat until done. Then if there's any addresses that are 8xxx or 9xxx I just put a B in front of them to mark them as Branch labels. I did that manually since there weren't many in Bank 05, but that's going to be a bigger step for banks with more code around that area.
QuoteThis is amazing work, Jiggers. I admire the energy you're putting into this, and wish I could contribute more than just my working knowledge of the game from an end user standpoint.
Thanks! And you're welcome I think too? XD And don't worry about it. Your experience is needed to know what to do too!
There's a file in the Github called rom layout notes... I'll be updating that while I cruise through data, making note of possible free space. Also filled out the DTE table last night.
Cool, thanks for the manual
Alright, so I just have to try to wrap my brain around what I'm looking at. Again have a tendency to think in high-level, so looking at each instruction and trying to imagine it in the context of a (comparatively simple) instruction, I'm trying to figure out how I might rearrange instructions as needed, but first I need to know what each thing is doing. Thus my need for a disassembly. Since there are 4 JSRs to AA3A (which does the comparison to 100 and if so, sets back to 99), I'm referencing those sections of code below.
CLC ; A874 $18
LDA $47 ; A875 $A5 $47
ADC #$10 ; A877 $69 $10
STA $48 ; A879 $85 $48
CLC ; A87B $18
LDA $4A ; A87C $A5 $4A
ADC #$10 ; A87E $69 $10
STA $4B ; A880 $85 $4B
LDX #$00 ; A882 $A2 $00
LDA $46 ; A884 $A5 $46
JSR $FD11 ; A886 $20 $11 $FD
STA $46 ; A889 $85 $46
LDY $9E ; A88B $A4 $9E
SEC ; A88D $38
LDA ($44),Y ; A88E $B1 $44
BEQ L16903 ; A890 $F0 $71
SBC $46 ; A892 $E5 $46
BCC L16903 ; A894 $90 $6D
LDY $47 ; A896 $A4 $47
LDA ($7A),Y ; A898 $B1 $7A
TAX ; A89A $AA
INX ; A89B $E8
TXA ; A89C $8A
JSR $AA3A ; A89D $20 $3A $AA
STA ($7A),Y ; A8A0 $91 $7A
LDY $48 ; A8A2 $A4 $48
LDA ($7A),Y ; A8A4 $B1 $7A
TAX ; A8A6 $AA
INX ; A8A7 $E8
TXA ; A8A8 $8A
JSR $AA3A ; A8A9 $20 $3A $AA
STA ($7A),Y ; A8AC $91 $7A
LDX $AD ; A8AE $A6 $AD
LDA $47 ; A8B0 $A5 $47
SEC ; A8B2 $38
SBC #$13 ; A8B3 $E9 $13
BEQ L168BD ; A8B5 $F0 $06
BCS L168C1 ; A8B7 $B0 $08
LDA #$5F ; A8B9 $A9 $5F
BNE L168C3 ; A8BB $D0 $06
L168BD:
LDA #$60 ; A8BD $A9 $60
BNE L168C3 ; A8BF $D0 $02
L168C1:
LDA #$61 ; A8C1 $A9 $61
L168C3:
STA $7FBA,X ; A8C3 $9D $BA $7F
INC $AD ; A8C6 $E6 $AD
LDX #$00 ; A8C8 $A2 $00
LDA $49 ; A8CA $A5 $49
JSR $FD11 ; A8CC $20 $11 $FD
BNE L16903 ; A8CF $D0 $32
LDY $4A ; A8D1 $A4 $4A
LDA ($7A),Y ; A8D3 $B1 $7A
CMP #$01 ; A8D5 $C9 $01
BEQ L16903 ; A8D7 $F0 $2A
TAX ; A8D9 $AA
DEX ; A8DA $CA
TXA ; A8DB $8A
STA ($7A),Y ; A8DC $91 $7A
LDY $4B ; A8DE $A4 $4B
LDA ($7A),Y ; A8E0 $B1 $7A
TAX ; A8E2 $AA
DEX ; A8E3 $CA
TXA ; A8E4 $8A
STA ($7A),Y ; A8E5 $91 $7A
LDX $AD ; A8E7 $A6 $AD
LDA $4A ; A8E9 $A5 $4A
SEC ; A8EB $38
SBC #$12 ; A8EC $E9 $12
BEQ A8F6 ; A8EE $F0 $06
BCS A8FA ; A8F0 $B0 $08
LDA #$5F ; A8F2 $A9 $5F
BNE A8FC ; A8F4 $D0 $06
A8F6:
LDA #$63 ; A8F6 $A9 $63
BNE A8FC ; A8F8 $D0 $02
A8FA:
LDA #$60 ; A8FA $A9 $60
A8FC:
ORA #$80 ; A8FC $09 $80
STA $7FBA,X ; A8FE $9D $BA $7F
INC $AD ; A901 $E6 $AD
L16903:
RTS ; A903 $60
;; sub start ;;
CLC ; A904 $18
LDA $46 ; A905 $A5 $46
ADC #$10 ; A907 $69 $10
STA $47 ; A909 $85 $47
LDX #$00 ; A90B $A2 $00
LDA $44 ; A90D $A5 $44
JSR $FD11 ; A90F $20 $11 $FD
STA $48 ; A912 $85 $48
SEC ; A914 $38
LDA $45 ; A915 $A5 $45
BEQ L16955 ; A917 $F0 $3C
SBC $48 ; A919 $E5 $48
BCS L16955 ; A91B $B0 $38
LDY $46 ; A91D $A4 $46
CPY #$11 ; A91F $C0 $11
BEQ L1695B ; A921 $F0 $38
L16923:
LDA ($7A),Y ; A923 $B1 $7A
TAX ; A925 $AA
INX ; A926 $E8
TXA ; A927 $8A
JSR $AA3A ; A928 $20 $3A $AA
STA ($7A),Y ; A92B $91 $7A
LDY $47 ; A92D $A4 $47
LDA ($7A),Y ; A92F $B1 $7A
TAX ; A931 $AA
INX ; A932 $E8
TXA ; A933 $8A
JSR $AA3A ; A934 $20 $3A $AA
STA ($7A),Y ; A937 $91 $7A
LDX $AD ; A939 $A6 $AD
LDA $46 ; A93B $A5 $46
SEC ; A93D $38
SBC #$12 ; A93E $E9 $12
BEQ A948 ; A940 $F0 $06
BCS A94C ; A942 $B0 $08
LDA #$62 ; A944 $A9 $62
BNE A94E ; A946 $D0 $06
A948:
LDA #$63 ; A948 $A9 $63
BNE A94E ; A94A $D0 $02
A94C:
LDA #$64 ; A94C $A9 $64
A94E:
STA $7FBA,X ; A94E $9D $BA $7F
INC $AD ; A951 $E6 $AD
BNE L1695B ; A953 $D0 $06
L16955:
LDY $46 ; A955 $A4 $46
CPY #$11 ; A957 $C0 $11
BEQ L16923 ; A959 $F0 $C8
L1695B:
RTS ; A95B $60
I'm assuming, probably incorrectly, that this entire section is the end-of-battle tallying of the "commands issued" counters, using that to determine if a stat is going up, and then increasing it and decrementing the opposing stat. As for the context of what the game is trying to do, that's where I get lost. Like I can refer to opcodes and figure out what each instruction are doing in a vacuum, but correlating that to the actual outcome in the game's parameters confuses me. You stated before that at A896, Memory @ $47 holds Strength, and then the next line loads that value into the accumulator @ $7A, and then the next line Transfers A into X, then Increments X, then Transfers X to A. So in these 4 lines, data is simply being moved around before the JSR is done to the comparison. In essence, the ideal is for the comparison to be done, I'm assuming, directly after A898?
I'm going to wrack my brain a bit and try to read up on opcodes some more to see if I can't build an image of what's happening here in what order alongside the disassembly you've provided. Bear with me while I spitball. After A898, I would want to compare the stat to 99. IF TRUE THEN jump to a point that skips the increment and decrement code entirely. Which looks to be jumping to... somewhere past A8E9? I still have to figure out what the other instructions are doing before I can make a more educated guess.
Actually, you can see more or less that the code is already doing this to avoid going below 1, so it would just need to be repurposed and applied on the opposite end of the spectrum.
LDA ($7A),Y ; A8D3 $B1 $7A
CMP #$01 ; A8D5 $C9 $01
BEQ L16903 ; A8D7 $F0 $2A
So I would want to have the line directly after A898 be CMP #$63 (A898 $C9 $63), and a BEQ to... somewhere. The side effect to doing these comparisons on the fly and avoiding the increment altogether is that AA3A could be cannibalized, since if code branches on = 99, there'd be no reason to have those several bytes in place to ensure that a stat doesn't go up to 100. That's at least 19 bytes by my count (removing the 4 JSR lines @ 3 bytes apiece, and then the AA3A section itself which is 7 more bytes), so that's a nice chunk of space. If I figure out where the BEQ is pointing (I'd assume Line 16903, but that doesn't correlate to the current format of the disassembly), then I can use that as a guide for how I would fill in the repurposed BEQ and possibly rewrite the Strength Up / Intellect down section. If I can figure that out and test it, then I can start working on the other 2 see-saw stats (Spirit+ / Strength- & Intellect+ / Stamina-).
EDIT: Alright, well I think I see where those branches are pointing to, but...
L16903:
RTS ; A903 $60
The 4 BNE, BEQ, BCC pointing to that address have different values after the opcode. My limited work with branches suggested to me that the address after the opcode should be a pointer which... again, forgive me for being so fresh, points to a specific address? But then those values should be the same for all 4 branch instructions, shouldn't they? Basically I just need to know what hex address to put after the $F0 in my above-mentioned test fix.
Never mind, I'm an idiot. Basic deduction (though I probably could have gone and read the manual) led me to find that it's the number of bytes skipped ahead by the branch. So assuming nothing else moves currently (which won't be the case)... 105 bytes between the new BEQ and the RTS we want to skip to. That's $69 so...
Test fix:
...
LDY $47 ; A896 $A4 $47
LDA ($7A),Y ; A898 $B1 $7A
CMP #$63 ; A89A $C9 $63
BEQ L16903 ; A89C $F0 $69
...
Obviously this alone can't be plugged in or the whole thing breaks. I may need to analyze how to nudge this in or put it in some blank space and add a JSR to test it before worrying about moving data around. The issue right now in the way of testing this is offsets. The new instructions require inserting 4 bytes in a place that isn't an issue at first, because the JSR 3 bytes can be removed... But then there's still 1 extra byte offset. Even considering that the 4 JSRs are removed (12 bytes), we're adding 16 bytes, and the longer we wait to fix that offset, the more problems pile up. AA3A is another 7 bytes that can be removed which puts us at -3 bytes (which can be padded easily), but that's quite a ways away. I suppose it's feasible to go through the instructions and make sure there's no bad pointers to test this, which for those 4 I'm guessing it's just... Strength and... 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, and there's surely something that checks a table or such to see if it even needs to decrement another stat.
EDIT: Actually upon further analyzing the comparison that prevents a stat from going below 1, I may be able to save more space, as it only does the comparison once on account of the Branch. I would assume this means the two copies of a stat, despite being separate, will always be identical, because the existing CMP-BEQ only compares once, and if it clears, it does the DEX on each without a second CMP. I may be able to utilize this to save even more space, though that also mandates even more padding. To move as little data as possible, I am now operating under the idea that I will leave AA3A alone (though it will never be used), and to replace the two JSRs per section, (3 + 3) I will simply replace the first one with the new CMP-BEQ (4) and the second one with 2 NOPs (2). This will have a minimal impact on pointers and addresses, and should fix the issue. Going to try and mock up a patch and test it now.
Test Fix:
0x168AA: C9 63 F0 65 AA E8 8A 91 7A A4 48 B1 7A AA E8 8A EA EA
0x16935: C9 63 F0 32 AA E8 8A 91 7A A4 47 B1 7A AA E8 8A EA EA
EDIT: HUZZAH! The above fix appears to work! 16 white magic casts, 46 attacks, and Firion gained neither Spirit nor Strength, which means 99 Spirit = no chance of Strength loss, and 99 Strength = no chance of Intellect loss (and though untested this should also mean 99 Int = no Stamina loss)! I'll make a quick IPS and set it aside to be uploaded later separately or used as part of a bug fix omnibus. With this patch, any stat that reaches 99 will no longer try to increase after a battle if criteria are met. This should apply to Strength, Agility, Stamina, Intellect, Spirit, and M.Power. I don't think this affects Magic Defense or Evasion since they are capped at 16 rather than 99.
FFII Maxed Stat Increase Fix.ips (https://drive.google.com/open?id=1Wppnhwk93fFU0Yz_oB_lClcInYkz2XyF)
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...
Quote from: Jiggers on December 31, 2019, 01:20:07 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.
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.
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.
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
Wow, I see it's been a busy week here in FF2 land :D. Congrats on all the progress!
Quote from: Jiggers on December 23, 2019, 05:11:47 PM
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.
Quote from: redmagejoe on December 25, 2019, 03:22:31 PM
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.
Quote from: redmagejoe on December 25, 2019, 03:22:31 PM
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.
Quote from: redmagejoe on December 25, 2019, 03:22:31 PM
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.
Quote from: Jiggers on December 25, 2019, 08:29:35 PM
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.
Quote from: redmagejoe on December 25, 2019, 09:35:35 PM
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.
Quote from: redmagejoe on December 25, 2019, 09:35:35 PM
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:
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
Quote from: redmagejoe on December 26, 2019, 09:08:21 PM
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.
Quote from: Jiggers on December 26, 2019, 11:17:37 PM
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.
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.
Quote from: Jiggers on December 26, 2019, 11:17:37 PM
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!
Quote from: Jiggers on December 29, 2019, 08:01:29 PM
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 :/.
Quote from: Jiggers on December 29, 2019, 08:01:29 PM
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.
Quote from: redmagejoe on December 30, 2019, 09:18:30 PM
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:
Quote from: abw on December 23, 2019, 02:32:07 PM
$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.
I will keep that arithmetic method in mind, abw. Thank you for the advice.
QuoteI'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.
QuoteThe 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.
QuoteDo 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.
Quotebut 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.
Quote from: abw on December 31, 2019, 11:59:14 PMThis 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...?
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.
Quote from: Jiggers on January 01, 2020, 05:53:22 PM
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...
;; 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?
Quote from: redmagejoe on January 01, 2020, 11:49:07 AM
Quote from: abw on December 31, 2019, 11:59:14 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.
Quote from: redmagejoe on January 01, 2020, 11:49:07 AM
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.
Quote from: redmagejoe on January 01, 2020, 11:49:07 AM
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.
Quote from: redmagejoe on January 01, 2020, 06:31:28 PM
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.
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)
;; 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)
;; 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)
;; 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)
;; 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
;; 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.
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...
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.
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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; 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
Quote from: redmagejoe on January 09, 2020, 05:01:22 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.
Quote from: redmagejoe on January 09, 2020, 05:01:22 PM
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.
Quote from: redmagejoe on January 09, 2020, 05:01:22 PM
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 (https://drive.google.com/uc?export=download&id=1RO6E3zDLDdSpeIX0EbdN5xUJ94aGKth4)!
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
Previous post edited - I need to learn to make my links more visible ;).
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:
;; 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:
; $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.
Quote from: redmagejoe on January 10, 2020, 02:08:14 AM
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!
After After:
; $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?
; 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.
Quote from: redmagejoe on January 11, 2020, 10:17:17 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.
Quote from: redmagejoe on January 11, 2020, 10:17:17 PM
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 (https://drive.google.com/file/d/1apgMx_eYEU3r319ZX4PueixjpgCljqil/view) with some more pointers and notes, which might come in handy.
Quote from: redmagejoe on January 11, 2020, 10:17:17 PM
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.
Quote from: redmagejoe on January 11, 2020, 10:17:17 PM
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!
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?
Quote from: abw on January 12, 2020, 03:27:52 PM
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.
Quote from: abw on January 12, 2020, 03:27:52 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.
Out of curiosity though, abw, on the note of stat up messages at $A623, I'm assuming it's somewhere in this area?
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...
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.
Quote from: redmagejoe on January 12, 2020, 03:34:28 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.
Quote from: redmagejoe on January 12, 2020, 03:34:28 PM
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.
Quote from: Jiggers on January 12, 2020, 04:14:09 PM
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.
Quote from: redmagejoe 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?
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).
Quote from: redmagejoe on January 13, 2020, 03:20:50 PM
EDIT: Just checked your disassembly, and looking at this section...
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.
Quote from: abw on January 13, 2020, 09:25:40 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.
Quote from: abw on January 13, 2020, 09:25:40 PM
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
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
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?
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.
; 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.
Quote from: redmagejoe on January 13, 2020, 09:46:09 PM
Interesting... seems that 7D43 is written to when closing the menu on the overworld...?
I've also noticed that some parts of RAM are used for different purposes in battle, e.g. the $7A48-$7B47 is only used for the RNG table during battle and gets overwritten with something else outside of battle, so it's possible the same thing might be happening with $7D43 etc.
Quote from: redmagejoe on January 13, 2020, 09:46:09 PM
In spite of being told to increment 7D3B, debugger says it's incrementing 7D43... what could be causing that?
Quote from: redmagejoe on January 13, 2020, 09:46:09 PM
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.
This. "INC foo,X" means "increment the address 'foo + X'", so in the case where X = #$08, INC $7D3B,X affects $7D43; the debugger's helpfully telling you the effective address.
Quote from: redmagejoe on January 13, 2020, 09:46:09 PM
So I chased the control flow around to the two addresses indicated, but it was A57B that got my attention.
Yeah, that call from $A75B does look suspicious. It looks like they wanted to set A to #$08 for the eventual STA ($44),Y (probably #$08 means target all?) but forgot about the intermediate TAX + INC $7D3B,X. For this specific item, I would also expect a loop that increments the entire party's counters for being magically attacked. On the other hand, this entire action counter system all seems to be happening in the wrong place - all the counters are updated when the actions are determined, not when they happen. So if you manage to kill both Sorcerers before they get a chance to act, you still get credit for having been hit by a magic spell even though you actually weren't.
Yes, while the target/cancel exploit fix addresses that issue with the party, I somehow doubt it also corrects the same behavior on enemies. The current behavior is indeed that at the start of a turn before you issue orders to your party, the enemies have already decided their attacks and credit to your attacked counters is already incremented. I might suggest looking at that down the line once the more prominent bugs are fixed, and will add that to the first post. I've been busy today but also wracking my brain about how to possibly optimize the existing code so that a fix could be fit in the available space as outlined at the bottom of my previous post. We at least know what's causing the Firion Spirit bug now, it's just a matter of how to fix it.
Here's a hint: once you take out the completely useless BEQ $A55F at $0C:$A55B, the sections of code at $0C:$A54A-$0C:$A55E and $0C:$A582-$0C:$A594 are identical. If you extract that code to a separate function and JSR to it from the original places, you'll free up more than enough space to add a short INC loop.
Quote from: abw on January 14, 2020, 11:31:51 PM
Here's a hint: once you take out the completely useless BEQ $A55F at $0C:$A55B, the sections of code at $0C:$A54A-$0C:$A55E and $0C:$A582-$0C:$A594 are identical. If you extract that code to a separate function and JSR to it from the original places, you'll free up more than enough space to add a short INC loop.
You're certain it's absolutely useless? What is it supposed to be doing, or was supposed to do, do you think? I will, however, mock up a solution and post it here before I tamper with anything for review. I appreciate your patience while I try to work this out.
EDIT: Moved mock-up down to latest post.
Quote from: redmagejoe on January 15, 2020, 12:11:08 AM
You're certain it's absolutely useless? What is it supposed to be doing, or was supposed to do, do you think?
Aside from cycle and byte counts, this:
BEQ +
BNE label
+
is functionally equivalent to this:
BNE label
Either way, you end up branching to label if Z is clear and executing the line after BNE otherwise. The same thing goes for combinations of BxC and BxS like this:
0x032574|$0C:$A564:90 02 BCC $A568
0x032576|$0C:$A566:B0 E2 BCS $A54A
; control flow target (from $A564)
0x032578|$0C:$A568:A5 9E LDA $9E
which is just a longer and slower way of saying:
0x032576|$0C:$A566:B0 E2 BCS $A54A
0x032578|$0C:$A568:A5 9E LDA $9E
Quote from: redmagejoe on January 15, 2020, 12:11:08 AM
I will, however, mock up a solution and post it here before I tamper with anything for review. I appreciate your patience while I try to work this out.
[...]
0x0325A1|$0C:$A591:A2 00 LDX #$00
0x0325A3|$0C:$A593:FE 3B 7D INC $7D3B,X ; Character #1 counter for times magically attacked by enemy
0x0325A6|$0C:$A596:CA INX
0x0325A7|$0C:$A597:E0 04 CPX #$04
OUT OF BYTES:$????:D0 F9 BNE $A593 <- need 2 more bytes somewhere, or to make this sub-routine optimized
It's often better to start high and count down to 0 than to start at 0 and count up since that lets you take advantage of the decrement instructions automatically doing a comparison to 0 and updating the Z flag for you. This:
LDX #$03
-
INC $7D3B,X ; Character #1 counter for times magically attacked by enemy
DEX
BNE - ; loop to increment all characters' counter
does the same thing but eliminates the CPX #$04 to save 2 bytes. Also, you've still got this part to deal with:
0x032578|$0C:$A568:A9 08 LDA #$08
0x03257A|$0C:$A56A:4C 0C A6 JMP $A60C
so you can combine the two sections into one and eliminate the CPX #$08 and BNE $A5?? to save 4 more bytes.
Here's the version I put together (https://drive.google.com/uc?export=download&id=1GT3YyJ5swe0TlzlwkBYrFr-q_m-I5KF6) - try giving that a shot!
Done. This is actually the most extensively I've worked with ASM, not only finding what instructions I needed to do what, but reorganizing code and working out solutions. This is the fifth iteration of this workspace, but I think I've got it a point ready for review. Forgive my inexperience, but please point out any issues with my approach.
Oh... you had a post almost an hour ago while I was still touching this up and working through it. I'm going to take your advice on the DEX and try to move this around so it's closer to the original structure (for minimal address changes), and... if you could find any faults in my changes functionally (I'm still working on making it cleaner), I'd really like to try to use my fix. 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. I'd hate to feel like I was being spoonfed solutions. 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.
Completed Mock-up:
; 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
Ready for review. May have to fill $A595 to $A599 with $EA (NOP)s. Any opcodes I failed to change? Does this look ready for a test-run? I'm pretty pleased with what I learned from this process, and I'm feeling more confident about making other fix attempts once I narrow down the locations of the other bugs. Your input and assistance has been tremendously helpful, abw.
EDIT: Also tested it, had to change FB to FA (oops, didn't BNE back enough bytes and caused a crash), and no more white magic breaks! It seems to successfully cycle through the party, except Firion's getting skipped over, I guess I need to set LDX to 4 and set the DEX above the INC to avoid this. Well shit, that just made it jump back up to 7D3F (thought this might happen so set a break for it) and endlessly loop. Checking the debugger with breaks set for all the relevant addresses. Huh, in spite of the Z flag being cleared, the BNE is still branching. Isn't BNE supposed to fail if Z = 0? Seems I still have to have a CPX #$00. Maybe I misunderstood, but for some reason I thought you were suggesting that DEX would automatically do a CPX #$00. Adding in the CPX caused the BNE to be passed properly, and fortunately we have 5 bytes to spare (now 3). Updating my code above accordingly.
The problem is that INC also affects Z status.
You are correct, if you want to recieve Z status specifically from current X after executing INC instruction, then CPX #$00 is a way to go.
Next time watch flag status in Debugger when you do Step Into.
Quote from: Cyneprepou4uk on January 15, 2020, 12:52:50 PM
The problem is that INC also affects Z.
You are correct, if you want to recieve Z status specifically from X after INC instruction, then CPX #$00 is a way to go.
Next time watch flag status in Debugger when you do Step Into.
Yeah I noticed that upon second glance. ASM and flag status still makes my head spin. Also, thanks for checking in Cyne. As you can see, I'm trying my best to dive headfirst into the confusing world of ASM, but I feel like I'm making genuine progress, and being able to work a fix like this feels rewarding. :)
Yes, nice speed in code writing experience. It was longer in my case since there was no one around to give me some tips.
So, do you have any questions while abw is offline? I don't really care or paying attention about what exactly are you trying to accomplish in this game, I'm simply interested in coding details.
Quote from: Cyneprepou4uk on January 15, 2020, 01:02:48 PM
Yes, nice speed in code writing experience. It was longer in my case since there was no one around to give me some tips.
So, do you have any questions while abw is offline? I don't really care or paying attention about what exactly are you trying to accomplish in this game, I'm simply interested in coding.
Well without getting to into the details of the game itself, as you'd said, my fix appears to have worked up to a point. However I'm now at the end of a turn and the game is softlocked. Battle music playing, menu won't come up, stuck in the transition between all orders finished and actions taken at the end of a turn, and the menu eventually appearing for the new turn. I did a step into, and it appears to be stuck on an RTS. Do you have an idea why code execution would hang on an RTS? Being pointed into the code not from a JSR is the only speculation I can come up with, so it doesn't know what to return to. But I don't think anything in my code changes had anything to do with this address.
032723:A0 09 LDY #$09
032725:B1 44 LDA ($44),Y @ $7E43 = #$00
> 032727:60 RTS -----------------------------------------
Maybe my opcodes are off? I was doing a lot of changing of instructions, addresses, etc, and it's possible that what I have in my test ROM right now is not equal to what I have written in that code block above, or I've got bad bytes somewhere.
Code being stuck at RTS is almost impossible, so I think you mean something else.
Show me a screenshot from Debugger while it's trying to execute RTS
Oh I see what you mean. It only shows what instruction it's on the moment you hit Step Into. So it's soft-locked because it's continually looping through instructions. A screenshot would probably not help in that case? Should I put up a dump of the instructions it's stuck cycling through by going through Step Into?
Yes, show me that log, and also a screen of that code in debugger just in case
Quoteis almost impossible
Actually I take that back. It's just impossible
https://drive.google.com/open?id=1LoHvh5q0NlFtosAA2IT_pDIbyiIJeYrL
Found the Tracelogger. Trimmed it down significantly, but this appears to be the loop it's stuck in.
My phone can't handle a text file this big
Set an execute breakpoint at the start of your routine. When it hits, start logging, then press 128 Lines button to make sure your loop was executed several times, then stop logging.
Or you can wait an hour until I get to my computer.
January 15, 2020, 02:04:01 PM - (Auto Merged - Double Posts are not allowed before 7 days.)
Nevermind that, I was able to see a problem
January 15, 2020, 02:10:09 PM - (Auto Merged - Double Posts are not allowed before 7 days.)
Code loops because of BPL at $A525. You read a value from LDA ($44),Y. This value appears to be positive (#$00-#$7F range), so BPL branches to the same loop and starts over.
Looks like you wanted to create loop by using INY, but since you execute LDY #$09, it becomes infinite
Quote from: Cyneprepou4uk on January 15, 2020, 02:00:16 PM
Code loops because of BPL at $A525. You read a value from LDA ($44),Y. This value appears to be positive (#$00-#$7F range), so BPL branches to the same loop and starts over.
I wonder what caused that... Did I tamper with that in my code somehow? I'll take a closer look.
QuoteLooks like you wanted to create loop by using INY, but since you execute LDY #$09, it becomes infinite
You need to watch registers values as well as flags when you Step Into
That's bizarre... The code I changed in the block above didn't involve Y in any way, I don't think. The instructions you've described are the original code. I must have done something that had an indirect effect on Y in some way. I'll sift through the code and see what I broke. Actually been poring over this for a while and it's making my head hurt for now. I'll come back to this, and hopefully abw will have a more contextual idea of why something I did was a bad idea.
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. Sure enough that fixed the problem. Pretty sure I had JSRs to parts of memory that the game wasn't supposed to be accessing at that point. Two typos in the test ROM remedied, and now COMPLETELY reflect the assembly solution I posted above. Would just like to have abw give it a review and make sure there's no other surprises down the line, but otherwise, I believe we've fixed the Magic-All Spirit bug! :)
Completed Mock-up:
; 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
EDIT: I decided to revert (and thus swap the instructions) the DEX to an INX, since a CPX has to be used anyway. This also allows this patch to be in the appropriate order should we decide to try to fix the increment counters first, do battle actions last behavior down the line. After all, in party-wide enemy casts, it starts from the top character and works down, so we'd want the counters to increase in the same order.
Hey there, nice work so far on this project. Not sure if you're aware but the Gil counter isn't capped properly either. It should be capped at 9999999, but instead goes all the way up to 16777215 before it rolls over.
Quote from: Leviathan Mist on January 15, 2020, 03:08:59 PM
Hey there, nice work so far on this project. Not sure if you're aware but the Gil counter isn't capped properly either. It should be capped at 9999999, but instead goes all the way up to 16777215 before it rolls over.
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. :)
Quote from: redmagejoe on January 15, 2020, 03:10:29 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.
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.
; 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
; 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
; 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
; 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.
(https://i.kym-cdn.com/photos/images/facebook/000/491/680/171.jpg)
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.
Quote from: redmagejoe on January 15, 2020, 10:11:26 AM
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.
Quote from: redmagejoe on January 15, 2020, 10:11:26 AM
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 (http://www.romhacking.net/documents/423/) (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!
Quote from: redmagejoe on January 15, 2020, 02:50:15 PM
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.
Quote from: redmagejoe on January 15, 2020, 02:50:15 PM
Would just like to have abw give it a review and make sure there's no other surprises down the line,
[...]
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.
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:
; 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.
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?
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.
Quote from: redmagejoe on January 15, 2020, 11:16:11 PM
If the BCC failed, then clearly the condition for BCS must be met
This part is true.
Quote from: redmagejoe on January 15, 2020, 11:16:11 PM
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.
Quote from: redmagejoe 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.
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.
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.
@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?
Quote from: Cyneprepou4uk 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?
Edit bytes manually in TinyHexer. :-[
Why don't you edit bytes via hex editor in fceux directly?
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.
I see. And how do you determine what bytes for opcodes need to be written?
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
I was using this (https://www.zophar.net/fileuploads/2/10532krzvs/6502.txt) manual before. Then I made my own opcodes list (https://iromhacker.ru/6502.png), maybe it will suit you as well
Quote from: Cyneprepou4uk on January 16, 2020, 12:15:07 PM
I was using this (https://www.zophar.net/fileuploads/2/10532krzvs/6502.txt) manual before. Then I made my own opcodes list (https://iromhacker.ru/6502.png), 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...
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.
; 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.
; 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:
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:
; 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:
; 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:
; 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.
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.
Quote from: redmagejoe on January 16, 2020, 12:17:46 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 :(
Quote from: redmagejoe on January 16, 2020, 12:17:46 PM
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.
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?
; 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
; 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.
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
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.
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.
Quote from: redmagejoe on January 17, 2020, 11:45:54 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.
Quote from: redmagejoe on January 17, 2020, 11:45:54 PM
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.
Quote from: redmagejoe on January 17, 2020, 11:45:54 PM
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.
Quote from: redmagejoe on January 17, 2020, 11:45:54 PM
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):
$05:$A4C9:
LDA $7CC0
STA $80
LDA $7CC1
STA $80
JSR $EFCF
Quote from: redmagejoe on January 17, 2020, 11:45:54 PM
In the meantime, I figured out how to stop Weapon Level Up messages from occurring on maxed weapons.
Good job!
Quote from: redmagejoe on January 17, 2020, 11:45:54 PM
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:
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
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:
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! (https://drive.google.com/uc?export=download&id=1N7JcD2cPQrAllsidBWWmkSKhycWl2OEl)
For patching weapon levels, I took the
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.
Quote from: abw on January 18, 2020, 04:04:44 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.
Quote from: abw on January 18, 2020, 04:04:44 PM
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
Quote from: abw on January 18, 2020, 04:04:44 PM
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
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.
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
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.
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
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.
Quote from: redmagejoe on January 18, 2020, 04:29:02 PM
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
If you NOP those out, you're disabling the low byte comparison (you know BNE didn't happen, so Z is set, which leaves you with A = #$96, so at this point you now know the high 2 bytes of party gold are equal to the high 2 bytes of the cap and it's time to check the low byte), so gold amounts between $989600 (9,999,872) and $9896FF (10,000,127) won't be handled correctly.
Quote from: redmagejoe on January 18, 2020, 04:29:02 PM
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?
Looks pretty close to me, only a couple of things to note:
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
Unless you're intentionally changing it, that one should stay ADC #$0A to match the original - since you only reach the ADC when the BCS isn't taken, you know C is clear just like it was in the original after the CLC.
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
You only need one of these BCCs - INY doesn't change the value of C.
Quote from: redmagejoe on January 18, 2020, 04:29:02 PM
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.
I agree with this - working overflow protection into all the calculations that need it would be annoying.
I will make the changes accordingly. Thank you for the info. Gil Fix and Spell Up fix will be updated, and then re-uploaded / linked. I appreciate your review of my code. Significantly less changes to the original code in my new version. :)
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 0A ADC #$0A ; 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:C8 INY ; offset for spell experience
0x0165D8|$05:$A5C8:90 25 BCC $A5F1 ; if credit < penalty, no growth, otherwise grow by the remainder
0x0165DA|$05:$A5CA:18 CLC
0x0165DB|$05:$A5CB:71 7E ADC ($7E),Y ; spell experience
0x0165DD|$05:$A5CD:C9 64 CMP #$64
0x0165DF|$05:$A5CF:90 1E BCC $A5EF ; if experience < 100, just update experience, otherwise increase lvl
0x0165E1|$05:$A5D1:88 DEY ; offset for spell level
^^^
^^^
^^^
0x0165E2|$05:$A5D2:EA NOP
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
Everything after the NOP (only need 1 now for buffer space, since I put the second CLC back) is the same, the TAX and INX was moved up, and a single BCS was added. All the other changes were reverted as per your review.
As for the gold thing, that code that I NOP'd out, I'm assuming, can still run because even though we changed the branches, they're a BCC followed by a BNE. So if C=1 and Z=1, then the program can still run those three lines of code, yeah?
0x03F005|$0F:$EFF5:F0 02 BEQ $EFF9 ; BNE $F000 would be better since then you could eliminate the following BCS
0x03F007|$0F:$EFF7:B0 07 BCS $F000 ; cap party gold at 9,999,999
; control flow target (from $EFF5)
0x03F009|$0F:$EFF9:AD 1C 60 LDA $601C ; Party gold byte 0
0x03F00C|$0F:$EFFC:C9 80 CMP #$80
0x03F00E|$0F:$EFFE:90 0F BCC $F00F
Yeah, I see it now. I'm reverting the NOPs (except for the removed BCS) as we speak. And updated!
Are you planning to do anything about the ability to switch weapons mid-battle in order to level up different weapons? This is quite abusable if you start with shields and then switch to another weapon like swords after attacking a few times with the shields, as the game will apply all shield weapon exp to the swords at the end of battle.
Quote from: Leviathan Mist on January 19, 2020, 02:11:16 PM
Are you planning to do anything about the ability to switch weapons mid-battle in order to level up different weapons? This is quite abusable if you start with shields and then switch to another weapon like swords after attacking a few times with the shields, as the game will apply all shield weapon exp to the swords at the end of battle.
That is significantly more difficult to amend, as there are not separate counters in the game for each weapon type, only a "numbers of time attack orders were issued" counter. I'm not sure if there's even enough room in memory for an additional 15 (per character) counters to be added. That may be an issue that needs to remain due to technical limitations, but I'll put it on my investigation to-do list. Was that behavior fixed in the PSP version?
Note there will be divergences, as even in the PSP version, spells and levels still seem to fill the experience bar even at Level 16. I haven't tested enough to see if it fills and resets to 0 like it did in the NES before this fix, but there will be improvements that strive to fix behaviors that even weren't remedied in the latest remakes. Likewise, technical limitations may result in fixes made in the remakes to not be feasible to put in this patch.
I just tested on the PSP version, and it also only tallies experience for the weapon that was worn at the end of battle. Therefore, due to technical limitations and the effort required, I am going to leave that behavior as is rather than label it a bug. You and I both know that it doesn't make sense, but I'm going to label it "working as intended".
So now I'm looking for where in code the 5 spells usable outside of combat handle experience. The spells are Cure, Life, Esuna, Teleport, and Warp.
I would think the only feasible way to do it, short of recoding the entire weapon leveling system, would be to disable the ability to switch weapons during battle.
Anyway, this is getting to the point where I feel comfortable attempting a playthrough using your patches. I will probably be streaming it live when I do.
That's awesome, Leviathan! Plugging the project is much appreciated! I'd like to make more progress on this if you're thinking of just using the existing patches, so I'll try to focus a bit more. I haven't posted the max spell level up fix yet because I still want to tack down the out-of-combat experience increase. It's usable right now, and all my desired change would do is stop spells from displaying "16-02" or the like if you use them outside of combat, but... I'm a bit of a perfectionist.
If you do decide you want to move forward with it soon, I'll go ahead and link the current version of that patch. I do want to get those spells working, so maybe I'll look at those after I finish this and the Evasion/MDef delay that I got distracted from. ::) Also updated my last post with my findings on the weapon skills.
Instead of disabling weapon switching, you can add gained exp to the weapon you were using before it switches, and start counting exp for a new weapon from zero
First post has been updated with Spell IDs, as well as expanding on important memory values alongside those provided by Jiggers. Spell IDs I want to look for are D0 (Warp), D4 (Cure), D5 (Life), D7 (Esuna), and E6 (Teleport).
; control flow target (from $ABBF, $ABE5, $AC5A)
0x03AC95|$0E:$AC85:A9 42 LDA #$42
0x03AC97|$0E:$AC87:85 E0 STA $E0
0x03AC99|$0E:$AC89:A6 6E LDX $6E
0x03AC9B|$0E:$AC8B:A5 80 LDA $80
0x03AC9D|$0E:$AC8D:0A ASL
0x03AC9E|$0E:$AC8E:05 6E ORA $6E
0x03ACA0|$0E:$AC90:A8 TAY
0x03ACA1|$0E:$AC91:BD 0C 61 LDA $610C,X ; Character #1 Current MP low byte
0x03ACA4|$0E:$AC94:18 CLC
0x03ACA5|$0E:$AC95:F9 10 62 SBC $6210,Y ; Character #1 spell slot #1 level
0x03ACA8|$0E:$AC98:9D 0C 61 STA $610C,X ; Character #1 Current MP low byte
0x03ACAB|$0E:$AC9B:BD 0D 61 LDA $610D,X ; Character #1 Current MP high byte
0x03ACAE|$0E:$AC9E:E9 00 SBC #$00
0x03ACB0|$0E:$ACA0:9D 0D 61 STA $610D,X ; Character #1 Current MP high byte
0x03ACB3|$0E:$ACA3:B9 11 62 LDA $6211,Y ; Character #1 spell slot #1 experience
0x03ACB6|$0E:$ACA6:18 CLC
0x03ACB7|$0E:$ACA7:69 02 ADC #$02
0x03ACB9|$0E:$ACA9:C9 64 CMP #$64
0x03ACBB|$0E:$ACAB:B0 04 BCS $ACB1
0x03ACBD|$0E:$ACAD:99 11 62 STA $6211,Y ; Character #1 spell slot #1 experience
0x03ACC0|$0E:$ACB0:60 RTS
Think I found the code I was looking for! Guessing we need to have a JSR (need 2 bytes, we can remove CLC most likely and have a BCC or BCS as needed) to the previous routine for handling spell levels, or simply use free space to make a new sub-routine. Let me see if I can figure this out before abw swoops in with a delicious spoon to pop into my mouth. :P Would want it to be before LDA $6211,Y, and branch down to $ACB0... Let's see how many bytes I need. 7 bytes... we can steal one back by removing that CLC since if this doesn't branch, it means the Carry is already Clear. But we're in need of 6 bytes. Perhaps I should simply JSR here to the free space in ROM? Even then, we'd need to steal 2 more bytes for the 3 our JSR needs... I wonder if I can get away with just dragging more of the existing code into the sub-routine.
; code -> free
0x03FFDD|$0F:$FFCD:B9 10 62 LDA $6210,Y ; Character #1 spell slot #1 level
0x03FFE0|$0F:$FFD0:C9 0E CMP #$0E
0x03FFE2|$0F:$FFD2:AA TAX
0x03FFE3|$0F:$FFD3:B0 03 BCS $FFD8
0x03FFE5|$0F:$FFD5:B9 11 62 LDA $6211,Y ; Character #1 spell slot #1 experience
0x03FFE8|$0F:$FFD8:60 RTS
0x03ACB3|$0E:$ACA3:20 CD FF JSR $FFCD
0x03ACB6|$0E:$ACA6:B0 09 BCS $ACB1
0x03ACB8|$0E:$ACA8:69 02 ADC #$02
0x03ACBA|$0E:$ACAA:C9 64 CMP #$64
0x03ACBC|$0E:$ACAC:B0 05 BCS $ACB3
0x03ACBE|$0E:$ACAE:99 11 62 STA $6211,Y ; Character #1 spell slot #1 experience
0x03ACC1|$0E:$ACB1:60 RTS
0x03ACC1|$0E:$ACB1:EA NOP
0x03ACC3|$0E:$ACB3:8A TXA
0x03ACC4|$0E:$ACB4:18 CLC
0x03ACC5|$0E:$ACB5:69 01 ADC #$01
0x03ACC7|$0E:$ACB7:C9 10 CMP #$10
0x03ACC9|$0E:$ACB9:B0 08 BCS $ACC3
0x03ACCB|$0E:$ACBB:99 10 62 STA $6210,Y ; Character #1 spell slot #1 level
0x03ACCE|$0E:$ACBE:A9 00 LDA #$00
0x03ACD0|$0E:$ACC0:99 11 62 STA $6211,Y ; Character #1 spell slot #1 experience
0x03ACD3|$0E:$ACC3:60 RTS
Does this look like an acceptable solution? This is the way with the least amount of address-shifting I could think to work in the LDA $6210,Y => CMP #$0E => BCS
As for where to find free space should I have to resort to sub-routines, I'm assuming it's best to try and find the smallest free space available for the smallest additions, so that larger regions of memory can be used for more ambitious changes. I found a 19-byte region of free space near the end. I'll try to work within that for anything that I absolutely cannot cram into rewritten/optimized code, and beyond that there's a 21-byte and a 68-byte chunk, before I go anywhere near the 200+/400+/600+ regions. There's also two or three 3-byte chunks but there's not much you can do with 3 chunks in terms of sub-routines.
It appears to work, though I wish I could figure out a way to squish it in. It is what it is. If someone thinks it could be better optimized or there's a better way to apply this fix, I'll wait to finalize and put the IPS up. If this is the best solution, I'm thinking I'll also stick in 5 bytes after the TAX in my sub-routine with LDA #$00 and STA $6211,Y to force experience to 00. I only did that since I'm testing on a file where the exp is already above 00, but it's probably redundant and a waste of 5 bytes in what remains of the 7 bytes in that free space.
@abw I belive 0x03AB18 may be the start of the routine for swapping the positions of spells around in the menu, if you're still labeling.
There's surely some unused RAM somewhere. FF2 does seem to make better use of it than FF1, so I'm not sure where... In FF1, a bunch of map stuff is reloaded after battle, but the battle part of the game never touches it, so it could be overwritten and nothing would change on the map when returning (actually I should double-check that a battle doesn't reset bats in dungeons...)
In the code that backs up character stats before the battle begins, some of the player RAM might not be fully used. You could possibly save the starting weapon type there, then reference that at the end. If you start with a sword, switch to an axe... all the experience goes to the sword, still. Or would that just swap the issue? Start with a level 1 weapon, swap it in battle, and level it up faster by using the higher level weapon?
Quote from: Cyneprepou4uk on January 19, 2020, 04:44:23 PM
Instead of disabling weapon switching, you can add gained exp to the weapon you were using before it switches, and start counting exp for a new weapon from zero
I'll add it to my improvements list and see how feasible that is. The negative side effect is that because of how the math works, there's a barrier you have to overcome based on growth penalties, and by forcing the math then and there, if you were to, say, switch from Daggers to Swords back to Daggers, you would have to overcome that penalty multiple times. This would be the same for the Swords. In essence it would make it, in scenarios where you're swapping weapons in battle, more difficult to level both weapon types, and possibly even give you no experience, as opposed to simply sacrificing the experience of one to boost the other (current behavior). I'll have to think carefully about this idea.
Quote from: Jiggers on January 19, 2020, 08:32:04 PM
There's surely some unused RAM somewhere. FF2 does seem to make better use of it than FF1, so I'm not sure where... In FF1, a bunch of map stuff is reloaded after battle, but the battle part of the game never touches it, so it could be overwritten and nothing would change on the map when returning (actually I should double-check that a battle doesn't reset bats in dungeons...)
In the code that backs up character stats before the battle begins, some of the player RAM might not be fully used. You could possibly save the starting weapon type there, then reference that at the end. If you start with a sword, switch to an axe... all the experience goes to the sword, still. Or would that just swap the issue? Start with a level 1 weapon, swap it in battle, and level it up faster by using the higher level weapon?
Current behavior is that the weapon you are wearing at the end of battle gets all the experience, regardless what you started with. And yes, we don't want to swap the issue, and as stated above, trying to force it to track each weapon's experience would ultimately lead to making it harder for any one of the weapons swapped to gain experience. We would have to think about touching up the current formula to accommodate this new behavior. I'd rather wait until all the bugs are fixed before even thinking of such an undertaking, but I will give it serious consideration.
Updated my previous post with potential solutions to the out-of-combat experience gain.And we're back to Evasion/Magic Defense delay! Boy it sure seems like I've let myself get distracted a lot from this. abw was kind enough to update the comments on them as well, so maybe I can actually finish this fix this time.
; level up Evasion
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 ; base Evasion credit for being physically attacked
0x016616|$05:$A606:85 46 STA $46 ; base credit amount for being attacked at all
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
; level up Magic Resist
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 ; base Magic Resist credit for being magically attacked
0x01662A|$05:$A61A:85 46 STA $46 ; base credit amount for being attacked at all
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
; 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 ; counter for times physically/magically attacked
0x016856|$05:$A846:F0 24 BEQ $A86C ; no pain => no gain
0x016858|$05:$A848:18 CLC
0x016859|$05:$A849:65 22 ADC $22 ; battle rank?
0x01685B|$05:$A84B:65 46 ADC $46 ; base credit amount for being attacked at all
0x01685D|$05:$A84D:A4 47 LDY $47 ; offset for Evasion/Magic Resist skill level, whichever one we're dealing with at the moment
0x01685F|$05:$A84F:F1 7E SBC ($7E),Y ; Evasion/Magic Resist skill level
0x016861|$05:$A851:E9 0A SBC #$0A ; your first 10 points don't count for anything
0x016863|$05:$A853:90 17 BCC $A86C ; not enough pain => no gain
0x016865|$05:$A855:C8 INY ; offset for Evasion/Magic Resist skill experience, whichever one we're dealing with at the moment
0x016866|$05:$A856:71 7E ADC ($7E),Y ; Evasion/Magic Resist skill experience
0x016868|$05:$A858:C9 64 CMP #$64
0x01686A|$05:$A85A:90 0E BCC $A86A ; if experience < 100, just update experience, otherwise increase level
0x01686C|$05:$A85C:88 DEY ; offset for Evasion/Magic Resist skill level, whichever one we're dealing with at the moment
0x01686D|$05:$A85D:B1 7E LDA ($7E),Y ; Evasion/Magic Resist skill level
0x01686F|$05:$A85F:AA TAX
0x016870|$05:$A860:E8 INX ; level++
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 ; save new Evasion/Magic Resist skill level
0x016877|$05:$A867:C8 INY ; offset for Evasion/Magic Resist skill experience, whichever one we're dealing with at the moment
0x016878|$05:$A868:A9 00 LDA #$00 ; reset experience to zero
; control flow target (from $A85A)
0x01687A|$05:$A86A:91 7E STA ($7E),Y ; save new Evasion/Magic Resist skill experience
; control flow target (from $A846, $A853)
0x01687C|$05:$A86C:60 RTS
; 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
As I'm understanding more and more as I work with ASM and this game in particular, I find myself asking more pointed questions. It seems like this delay could be avoided altogether if instead of using indirect addressing in the STA ($7E),Y it used absolute addressing. Is there a reason, other than the extra byte needed, that we're not saving directly to the necessary values? Is there another step perhaps in "handing over" the value in $7E to its necessary memory location, or is $7E merely acting as a pointer to the correct place? What ramifications would there be in simply changing from indirect to absolute here? Would help if we actually knew what that absolute address was... Can't even tell from RAM mapping.
Interestingly, changing equipment or doing anything else that reinitializes stats also updates the MDef/Evasion level. We want this change to happen the moment we return from battle though, as UI elements shouldn't require the player to incite an update.
Distracted again! But I may have an overflow protection for combat counters.
Replace each INC $7xxx,X with a JSR to separate routines, or same routine if some clever addressing can be worked out.
0x031478|$0C:$9468:FE F3 7C INC $7CF3,X ; Character #1 physical attack counter
0x031E68|$0C:$9E58:FE F7 7C INC $7CF7,X ; Character #1 spell slot #1 battle use counter
0x031E75|$0C:$9E65:FE 3F 7D INC $7D3F,X ; Character #1 black magic use counter
0x031E7A|$0C:$9E6A:FE 43 7D INC $7D43,X ; Character #1 white magic use counter
0x03257B|$0C:$A56B:FE 37 7D INC $7D37,X ; Character #1 counter for times physically attacked by enemy
0x03261D|$0C:$A60D:FE 3B 7D INC $7D3B,X ; Character #1 counter for times magically attacked by enemy
BD F3 7C LDA $7CF3,X <<< this is the crux of whether we can use a single sub-routine or need 6
C9 08 CMP #$C8 <<< cap counter at 200 so overflow won't happen in any scenario
B0 03 BCS +3
FE F3 7C INC $7CF3,X <<< this too
60 RTS
@abw: Please see my previous post and this one and give your input. I know it's a lot to read, but I feel like I'm thriving with your support. :)
Quote from: redmagejoe on January 19, 2020, 08:41:11 PM
is $7E merely acting as a pointer to the correct place?
This.
STA ($7E),Y
does not write to $7E+Y, it reads a pointer from $7E,$7F, adds Y to that pointer, and writes to that address.
Changing this to absolute likely isn't possible unless $7E/7F contain the exact same values every time this code is run.
PS: Yes, this is Disch. Computer problems made my several years long "automatically sign me in" thing drop, I have no idea what my password is, and I can't reset it because I created the account like a decade ago and have no clue what email I used with it or even if I still have that email, so I had to make a new account
QuoteAs I'm understanding more and more as I work with ASM and this game in particular, I find myself asking more pointed questions. It seems like this delay could be avoided altogether if instead of using indirect addressing in the STA ($7E),Y it used absolute addressing. Is there a reason, other than the extra byte needed, that we're not saving directly to the necessary values? Is there another step perhaps in "handing over" the value in $7E to its necessary memory location, or is $7E merely acting as a pointer to the correct place? What ramifications would there be in simply changing from indirect to absolute here? Would help if we actually knew what that absolute address was... Can't even tell from RAM mapping.
Absolute is a direct $0000-$FFFF range.
Indirect LDA($7E),Y is when you put low byte of address in 007E, and high in $007F. Then you add Y value like in indexed addressing.
If you change indirect to absolute to make code work the same exact way as it currently does, it won't actually change anything in a scale of a frame. Speaking of magic delays or whatever.
Hmm, so it's not an issue of addressing... I just can't wrap my head around what instruction is missing that's carried out when you change equipment or enter a battle. All other stats update on the stat screen, and even the value on RAM Watch for the level reads 1 less than it should be until something triggers the update. So I don't think it's that the value isn't being loaded, simply that it's not actually been stored. Also, hey Disch! Lots of legacies popping into this thread, it's pretty humbling. :D
If I better understood how exactly indirect addressing was utilized (i.e. How are the absolute addresses being pointed to by those pointers in the first place? Surely it's not implicit?), I could actually probably use that exact idea for my proposed fix to the combat counters, and have a single subroutine that simply uses an indirect indexed (Y) Load Accumulator and then Increment with all six counters initializing (?) to the same pointer before going to the JSR. It's not the end of the world if I have to make six nearly identical sub-routines, but I'd like to avoid taking up more of our available free bytes than necessary.
In my own disassembly, I can't find "STA $7E" in the one and a half banks I sorta set up... but in my notes, $7E seems to be the pointer to character stats... No idea if that's what is being in the code you're talking about. But either way it works the same! So I'll see if I can help explain it. And hopefully not come off as condescending... just trying to help!
So first, the first byte of each character's stats has an absolute address in RAM:
$6100 - character 1
$6140 - character 2
$6180 - character 3
$61C0 - character 4
So there might be a table with the bytes laid out, 61, 00, 61, 40...
There's usually a variable that's just 0 to 3, depending on what character is being looked at, whose turn it is. That number gets doubled, then the pointers get loaded like so:
LDA CharacterNumber ; (0-3)
ASL A ; double it
TAX
LDA CharacterPointerTable, X ; Gets $00, $40, $80, or $C0
STA $7E ; becomes low byte of pointer
LDA CharacterPointerTable+1, X ; gets $61
STA $7F ; becomes high byte of pointer
Then Y can be an offset to a specific stat, $0 to $3F
So that LDA ($7E), Y (where Y is 9) is then looking at $6109, or $6149, or so on!
I see, I see! So a table will be predefined in the code itself for using the indirect addressing... So basically if my desired absolute address doesn't already have a table and pointer setup routine already... I won't be able to use indirect addressing for it. Hmm, that may force me to use more space for routines than I'd like to, but I'll start searching the disassembly to see if those base addresses I listed above (all six counters one after another) have any place in a table. Or I guess I could consider inserting a routine that does this, though at that point it comes down to whether it would save more space to do that than simply have 6x 11-byte routines (which would become a single 9-byte routine otherwise). So 66 bytes vs 9+X, X would have to be less than or equal to 57 bytes for it to be worth writing up a table and/or routine.
I haven't made a test patch yet, because while I'm confident that my mock-up would address the issue (as it's a pretty simple change), I'm hoping I can figure out some way to avoid eating up 66 bytes in our precious free space. After all, depending on where this project goes, I may need all the free space I can get.
For improvements, would it be possible to group certain spells and weapons together to reduce grinding? This was done for Secret of Evermore for a reference.
Another method could be to check a char's skill with a school of magic and make a new spell start at a higher level based on that.
The other big thing would be the many trap rooms in dungeons, they suck.
Quote from: PresidentLeever on January 20, 2020, 07:30:20 PM
For improvements, would it be possible to group certain spells and weapons together to reduce grinding? This was done for Secret of Evermore for a reference.
Another method could be to check a char's skill with a school of magic and make a new spell start at a higher level based on that.
The other big thing would be the many trap rooms in dungeons, they suck.
While I'm not averse to the concept of the suggestion, I'd rather not depart from the original or remake's behaviors enough for it to come off as a brand new feature. This patch's purpose is to restore the game to what the devs likely intended in behavior, rather than to enhance. Having said that, your proposal may be more feasible as a result of this project and the progression of the existing disassembly.
Quote from: redmagejoe on January 19, 2020, 05:01:28 PM
It appears to work, though I wish I could figure out a way to squish it in.
Not bad, young padawan, but I think we can still do this without using any extra free space. Try the below, which also saves 3 bytes compared to the original:
org $3ACB3
base $ACA3
LDA $6210,Y ; Character #1 spell slot #1 level
CMP #$0F
BCS done ; already at least level 16 => no change
LDA $6211,Y ; Character #1 spell slot #1 experience
ADC #$02 ; +2 spell experience; we know carry is clear
CMP #$64
BCC up_exp ; new spell experience < 100 => just update spell experience
LDA $6210,Y ; Character #1 spell slot #1 level
ADC #$00 ; +1 spell level; we know carry is set
STA $6210,Y ; Character #1 spell slot #1 level
LDA #$00 ; reset spell experience to 0
up_exp:
STA $6211,Y ; Character #1 spell slot #1 experience
done:
RTS
Quote from: redmagejoe on January 19, 2020, 05:01:28 PM
@abw I belive 0x03AB18 may be the start of the routine for swapping the positions of spells around in the menu, if you're still labeling.
Thanks, I'll be sure to update that!
Quote from: redmagejoe on January 19, 2020, 08:41:11 PM
Current behavior is that the weapon you are wearing at the end of battle gets all the experience, regardless what you started with.
I like the idea of keeping track of each weapon type's usage separately, but I don't think our RAM map is fleshed out enough at the moment to be able to do this safely. Each spell slot already has its own counter, so I think we'd need an extra 7 weapon types * 4 characters = 28 bytes. That should be possible, it'll just take some more analysis work.
Quote from: redmagejoe on January 19, 2020, 08:41:11 PM
@abw: Please see my previous post and this one and give your input. I know it's a lot to read, but I feel like I'm thriving with your support. :)
Looks like you've got plenty of feedback on this one already - there's not much more for me to add ;).
Quote from: Disch2 on January 20, 2020, 02:37:55 AM
PS: Yes, this is Disch. Computer problems made my several years long "automatically sign me in" thing drop, I have no idea what my password is, and I can't reset it because I created the account like a decade ago and have no clue what email I used with it or even if I still have that email, so I had to make a new account
I'm assuming you thought of this already, but just checking the obvious options: do you get email notices for PMs or posts on threads you're involved in? Otherwise Contact Staff seems like the next best alternative.
In other news...
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
- The middle two characters, Guy and Maria, are biased by the enemy targetting algorithm, taking significantly more enemy focus than Firion or Character #4. Investigate cause and improve RNG, which is likely the cause.
Want to give this (https://drive.google.com/uc?export=download&id=1ReXx5kMt-hy3foQhaflvfDeVP0TfAMJq) a shot? I'm not sure whether this is enough to completely fix the issue, but the RNG was definitely biased, so at the very least it's a necessary step in the right direction (assuming your goal is a "fair" RNG, anyway).
Amazing as always, abw. I compared yours side by side with the original and against mine, and I see how you managed to get that space. Clever use of rearranging conditionals to clear allowed you to essentially compress the entire other subroutine into the one above it. Now I definitely have to list you as the main credit for this patch. It looks awesome though, plus then we don't have to eat into any of our free space chunks yet! Which is good, because I have a pretty ambitious intention for some of those...
So do you think that for my counter solution, the better choice is to use 6x 11 byte subroutines? I don't even know how to begin looking for where those table and pointer setup routines are in the disassembly, but I'm guessing that they simply don't exist for the addresses we want if they were using absolute addressing. Do you have an idea how many bytes it would take to create something like that for our counters, and at that point, is it better for me to just make 6 almost identical subroutines like my mockups in free space?
Looking at the RNG fix now. And yes, the idea was to make a fair RNG that gives all 4 characters as close to an equal chance of being targeted as possible. If this does indeed cause a more uniform distribution as opposed to what before could be described as 12.5% (0-63), 37.5% (64-127), 37.5% (128-191), 12.5% (192-255), then it should solve the problem outright. I can't imagine it will harm any other mechanics in the game, as RNG should aim to be uniform anyway in this game's combat systems. Would you like to keep this under review as per your notes about avoiding that free space, or do you want me to slap it on the first post?
Replace each INC $7xxx,X with a JSR to separate routines, or same routine if some clever addressing can be worked out.
0x031478|$0C:$9468:FE F3 7C INC $7CF3,X ; Character #1 physical attack counter
0x031E68|$0C:$9E58:FE F7 7C INC $7CF7,X ; Character #1 spell slot #1 battle use counter
0x031E75|$0C:$9E65:FE 3F 7D INC $7D3F,X ; Character #1 black magic use counter
0x031E7A|$0C:$9E6A:FE 43 7D INC $7D43,X ; Character #1 white magic use counter
0x03257B|$0C:$A56B:FE 37 7D INC $7D37,X ; Character #1 counter for times physically attacked by enemy
0x03261D|$0C:$A60D:FE 3B 7D INC $7D3B,X ; Character #1 counter for times magically attacked by enemy
BD F3 7C LDA $7CF3,X <<< this is the crux of whether we can use a single sub-routine or need 6
C9 08 CMP #$C8 <<< cap counter at 200 so overflow won't happen in any scenario
B0 03 BCS +3
FE F3 7C INC $7CF3,X <<< this too
60 RTS
66 for 6 11-byte sub-routines similar to the above. If indirect addressing can be used, it becomes 9, bringing us to 57 as the magic number. Based on Jiggers's statement about the way the data table would be laid out if we needed to make a new table, we'd be looking at 7C F3 7C F7 7D 37 7D 3B 7D 3F 7D 43... That's another 12, bringing us to 45. So if the code for indirect addressing can be fit into 45 bytes, then that would be the optimal way to approach this fix. I just have to find an example of how it works in the disassembly. Just searching the HEX in the ROM I already know no table already exists for these addresses, as 7C F3 in that order only occurred twice and they didn't seem relevant based on the surrounding bytes.
total 29 bytes for all players and all your 6 addresses
code = 23 bytes
A005B98888186500AABDF37CC9C8B003FEF37C8810EC60
table = 6 bytes
000444484C50
00:8100: A0 05 LDY #$05 ; loop counter
00:8102: B9 88 88 LDA $8888,Y ; load base offset from table (put somewhere bytes 00 04 44 48 4C 50 and read them)
00:8105: 18 CLC ; clear C for normal addition
00:8106: 65 00 ADC $00 ; add player id for offset (don't know the actual address)
00:8108: AA TAX ; put offset into X
00:8109: BD F3 7C LDA $7CF3,X ; load value
00:810C: C9 C8 CMP #$C8 ; compare to overflow
00:810E: B0 03 BCS $8113 ; skip increasing
00:8110: FE F3 7C INC $7CF3,X ; increase value
00:8113: 88 DEY ; next check
00:8114: 10 EC BPL $8102 ; repeat loop
00:8116: 60 RTS ; if code is used as a subroutine
This looks really thorough. I just have to make sense of what's happening here...
We only want the INC to happen on any one of the given counters at any given time, thus why I was thinking of using the subroutine. I can't tell but it seems like this loop increases more than one counter at a time? Sorry, I'm still learning, bear with me. I'm not sure I understand the addresses in the table, either, or if they're just placeholders. Essentially what I need to make the table and indirect addressing routine for was so that I can turn my LDA $7CF3,X and INC $7CF3,X in that subroutine into, say, LDA $01,X and INC $01,X (just as an example, I don't know if those addresses are being used or what).
I'll try to make sense of and play around with this example, but knowing that it would be less than 45 bytes has me convinced that this method is the optimal way to go.
Yes, this loop is for increasing all 6 address at the same time.
For example, we take byte #$04 from the table, add #$01 from player id, and we get LDA $7CF3,X @ $7CF8 (Character #2 spell slot or something)
If you want to increase a single address instead of all, get rid of the loop and load a needed Y value before JSR to this subroutine. For example, if you need to increase "Character black magic use counter", then LDY #$04 + JSR
Quoteif they're just placeholders
Just placeholders. I'm using my own rom which is designed for quick code testing.
Okay, I get it. Let me try doing a mock-up below...
EDIT: Mock-up moved down.
Should I jump in now or you can handle it yourself?
Actually, maybe you should. I feel like I may be going down the wrong path here, and I don't want to end up wasting time chasing wild geese when you probably are furrowing your brow at what I'm trying to do right now.
It looks like you want to use current X instead of adding ID from $009E.
Since you can't add X directly, transfer it to A, then ADC,Y from the table and transfer result back to X.
And don't forget to clear C somewhere before executing addition
Quote from: Cyneprepou4uk on January 21, 2020, 12:22:40 PM
It looks like you want to use current X instead of adding ID from $009E.
Since you can't add X directly, transfer it to A, then ADC,Y from the table and transfer result back to X.
And don't forget to clear C somewhere before executing addition
Oh I actually see what you're saying. I'm doing things the hard way rather than the smart way, plus I'm constrained by the modes available for each instruction. I'll have to take special consideration for spell slots, which already have some funky things done to X, but I'm going to update my mock-up with something a bit more concise...
WIP
Fit in 2 bytes for LDA #$?? (offset in bottom code block), try to wiggle 2 bytes in.
Replace INCs in original with JSR to below.
00:8101: 18 CLC
00:8102: 65 9E ADC $9E ; adds character ID offset with the offset loaded into A before the JSR
00:8104: AA TAX ; put offset into X
00:8105: BD F3 7C LDA $7CF3,X ; load value from counter address now modified by counter offset and player id
00:8107: C9 C8 CMP #$C8 ; compare to overflow protection (200)
00:8109: B0 03 BCS $810E ; skip increasing
00:810B: FE F3 7C INC $7CF3,X ; increase value
00:810E: 60 RTS ; if code is used as a subroutine
0x031478|$0C:$9468: $7CF3 << $00 ; Character #1 physical attack counter
0x031E68|$0C:$9E58: $7CF7 << $04 ; Character #1 spell slot #1 battle use counter (this one may cause trouble)
0x03257B|$0C:$A56B: $7D37 << $40; Character #1 counter for times physically attacked by enemy
0x03261D|$0C:$A60D: $7D3B << $44; Character #1 counter for times magically attacked by enemy
0x031E75|$0C:$9E65: $7D3F << $4C; Character #1 black magic use counter
0x031E7A|$0C:$9E6A: $7D43 << $50; Character #1 white magic use counter
I know there's still a few considerations I need to look at, like how the state of registers or CPU flags will affect things returning from this sub-routine. I also need to take a look at the special case going into the JSR from spell slot use counter... Below is the base code for spell slot use counter.
; control flow target (from $9E42)
0x031E56|$0C:$9E46:A5 9E LDA $9E
0x031E58|$0C:$9E48:0A ASL
0x031E59|$0C:$9E49:0A ASL
0x031E5A|$0C:$9E4A:0A ASL
0x031E5B|$0C:$9E4B:0A ASL
0x031E5C|$0C:$9E4C:85 00 STA $00
0x031E5E|$0C:$9E4E:A5 54 LDA $54
0x031E60|$0C:$9E50:0A ASL
0x031E61|$0C:$9E51:0A ASL
0x031E62|$0C:$9E52:18 CLC
0x031E63|$0C:$9E53:65 53 ADC $53
0x031E65|$0C:$9E55:65 00 ADC $00
0x031E67|$0C:$9E57:AA TAX
0x031E68|$0C:$9E58:FE F7 7C INC $7CF7,X ; Character #1 spell slot #1 battle use counter
0x031E6B|$0C:$9E5B:A6 9E LDX $9E
0x031E6D|$0C:$9E5D:A0 2A LDY #$2A
0x031E6F|$0C:$9E5F:B1 80 LDA ($80),Y
0x031E71|$0C:$9E61:C9 15 CMP #$15
0x031E73|$0C:$9E63:B0 05 BCS $9E6A
0x031E75|$0C:$9E65:FE 3F 7D INC $7D3F,X ; Character #1 black magic use counter
0x031E78|$0C:$9E68:D0 03 BNE $9E6D
; control flow target (from $9E63)
0x031E7A|$0C:$9E6A:FE 43 7D INC $7D43,X ; Character #1 white magic use counter
; control flow target (from $9E68)
0x031E7D|$0C:$9E6D:A0 20 LDY #$20
0x031E7F|$0C:$9E6F:20 9B 94 JSR $949B
0x031E82|$0C:$9E72:F0 02 BEQ $9E76
; control flow target (from $9E21)
0x031E84|$0C:$9E74:C6 9E DEC $9E
; control flow target (from $9E00, $9E72)
0x031E86|$0C:$9E76:20 90 9A JSR $9A90
0x031E89|$0C:$9E79:20 4A 98 JSR $984A
0x031E8C|$0C:$9E7C:20 3E 9B JSR $9B3E
0x031E8F|$0C:$9E7F:A9 00 LDA #$00
0x031E91|$0C:$9E81:8D BA 7C STA $7CBA
0x031E94|$0C:$9E84:60 RTS
This also shows the pre-JSR code for three of the counters, so this would be a good way for me to figure out where I can find space for the 2-4 bytes I need before the INC I'd be replacing with a JSR. Maybe I should have LDX #$00 along with the LDA #$<offset> going into the JSR for the other 5 counters, and incorporate an addition of X into the routine so that spell slots offsets are properly maintained? I'm not sure if that's possible. I keep ending up with the correct offset in A from the spell slot counter, but because I'm trying to use a single sub-routine, getting my LDA $7CF3,X to essentially be $7CF7,X is a roadblock. I'm trying to figure out how I can add that #$04 when A is $9E + whatever all that math above $9E57 spits out, but I don't know how I can add into the Accumulator the value stored in, say, Y, since ADC uses Y as an offset.
Don't remove TAX before JSR, A = $9E + spell offset already. Find way to convert below to allow F3 instead of F7, working in the #$04 somehow. Current layout works if we are only replacing the INC with a JSR to a separate sub-routine listed below. Try to find a way to make it feasible to combine this with the one used for the other 5.
00:8105: BD F7 7C LDA $7CF7,X ; load value from counter address now modified by counter offset and player id
00:8107: C9 C8 CMP #$C8 ; compare to overflow protection (200)
00:8109: B0 03 BCS $810E ; skip increasing
00:810B: FE F7 7C INC $7CF7,X ; increase value
00:810E: 60 RTS ; if code is used as a subroutine
00:8101: 18 CLC
00:8102: 65 9E ADC $9E
00:8104: A8 TAY
So you've deleted the table and decided to input base offset to the sub itself, yeah, that's much better.
I don't see what is so special about $9E58.
No, wait, I see what it is.
Well, the are several ways to bypass that :)
Screw this topic with its communication by editing messages. Send a pm or contact me via my site, and we'll figure something out
Quote from: Cyneprepou4uk on January 21, 2020, 04:08:09 PM
Screw this topic with its communication by editing messages. Send a pm or contact me via my site, and we'll figure something out
No, please continue. I'm learning a lot by following the discussion. :)
Quote from: Leviathan Mist on January 21, 2020, 05:32:13 PM
No, please continue. I'm learning a lot by following the discussion. :)
It's all good, he gave me some awesome advice (when doesn't Cyne give awesome advice?) and I'm going to post the mock-up here once it's good to go and walk through the suggestions he gave me to improve upon my previous method.
0x013B33: 221
0x013D5D: 689
0x015D77: 409
0x03400D: 3
0x0387CB: 69
0x03F75E: 178
0x03F83B: 21
0x03F858: 184
0x03FEFC: 196
0x03FFCD: 3
0x03FFDD: 19
Meanwhile, I decided to take a look at all the blocks of free space available in ROM before we start sticking our patches in them. So let's suppose I wanted to shift a small chunk of game code up 3 bytes, to turn our very last 19-byte region into a 22-byte region... Good idea or bad idea? I see the control flow so in theory, I should be able to chase down anything pointing to that code and amend it, yes?
; code -> free
0x03FFCD|$0F:$FFBD:00
; ... skipping $1 00 bytes
0x03FFCF|$0F:$FFBF:00
; free -> code
; call $01:$BFB0
; external control flow target (from $09:$BBB9)
0x03FFD0|$0F:$FFC0:A9 01 LDA #$01
0x03FFD2|$0F:$FFC2:20 03 FE JSR $FE03 ; swap in PRG bank specified by A
; call to code in a different bank ($01:$BFB0)
0x03FFD5|$0F:$FFC5:20 B0 BF JSR $BFB0
0x03FFD8|$0F:$FFC8:A9 09 LDA #$09
0x03FFDA|$0F:$FFCA:4C 03 FE JMP $FE03 ; swap in PRG bank specified by A
; code -> free
0x03FFDD|$0F:$FFCD:00
; ... skipping $11 00 bytes
0x03FFEF|$0F:$FFDF:00
Here we are.
0x031478|$0C:$9468:20 4E F8 JSR $F84E ; Character #1 physical attack counter
0x031E68|$0C:$9E58:20 48 F8 JSR $F848 ; Character #1 spell slot #1 battle use counter
0x031E75|$0C:$9E65:20 5A F8 JSR $F85A ; Character #1 black magic use counter
0x031E7A|$0C:$9E6A:20 5E F8 JSR $F85E ; Character #1 white magic use counter
0x032569|$0C:$A559:20 52 F8 JSR $F852 ; Character #1 counter for times physically attacked by enemy
0x03257B|$0C:$A56B:20 56 F8 JSR $F856 ; Character #1 counter for times magically attacked by enemy
0x03F858|$0C:$F848:8A TXA ; JSR here from $9E58
0x03F859|$0C:$F849:18 CLC
0x03F85A|$0C:$F84A:69 04 ADC #$04
0x03F85C|$0C:$F84C:D0 15 BNE $F863 ; finish necessary math and jump down to rest of subroutine
0x03F85E|$0C:$F84E:A9 00 LDA #$00 ; JSR here from $9468
0x03F860|$0C:$F850:F0 0E BEQ $F860
0x03F862|$0C:$F852:A9 40 LDA #$40 ; JSR here from $A559
0x03F864|$0C:$F854:D0 0A BNE $F860
0x03F866|$0C:$F856:A9 44 LDA #$44 ; JSR here from $A56B
0x03F868|$0C:$F858:D0 06 BNE $F860
0x03F86A|$0C:$F85A:A9 4C LDA #$4C ; JSR here from $9E65
0x03F86C|$0C:$F85C:D0 02 BNE $F860
0x03F86E|$0C:$F85E:A9 50 LDA #$50 ; JSR here from $9E6A
0x03F870|$0C:$F860:18 CLC
0x03F871|$0C:$F861:65 9E ADC $9E ; adds character ID offset with the offset loaded into A before the JSR
0x03F873|$0C:$F863:AA TAX
0x03F874|$0C:$F864:BD F3 7C LDA $7CF3,X ; load value from counter address now modified by counter offset and player id
0x03F877|$0C:$F867:C9 C8 CMP #$C8 ; compare to overflow protection (200)
0x03F879|$0C:$F869:B0 03 BCS $F873 ; skip increasing
0x03F87B|$0C:$F86B:FE F3 7C INC $7CF3,X ; increase value
0x03F87E|$0C:$F86E:60 RTS
I have to remember that $A56B and $A60D become $A559 and $A56B with my Enemy Magic-All Firion Spirit fix. I'd rather not make a separate standalone version of the patch as these fixes are intended to be comprehensive and come as a package, so I'll put a note when I put up the counter fix patch to only use it after the Firion Spirit fix.
Basically, to avoid having to try to find space in the game's main code and simply replace the INCs with JSRs, Cyne had the great idea of pointing the JSRs to different parts of the subroutine, and simply having the LDAs for the offsets within the subroutine, using branches to ensure that no more than one ever runs. There's a special case at the top for spell slot uses, since all of the math has already been done for which counter needs to be raised going into the JSR. In this way we can use a single 39-byte subroutine for all our counters, and overflow should no longer occur.
I believe that 120 is the highest you would have to raise a counter in any scenario to max out the potential benefit, be it spell or weapon levels, Spirit Up, Intellect Up, Strength Up, and while I don't know the nuances of Evasion or Magic Defense yet (man I am good at getting distracted from that project!), I can't imagine that you wouldn't gain a level in either from being hit 120 times. So 200 is a generous cap, while being far enough away from 255 that no scenario will possibly lead to overflow.
EDIT: Hmm... crashes when an action is taken. Wish I could figure out what's happening before that point, as when it's crashed, it's locked at an IRQ/BRK vector. Ah, I see... It's jumping to $87C1 in bank 0C. I need it to jump to $87C1 in bank 0E... How do I specify this?
So I've only been sparsely paying attention to this conversation, but my understanding is that code is being analyzed largely in-emu and modifications are being done by hand in a hex editor? No disassembly?
Is this correct?
If so, I might consider making a disassembly if one isn't available. Not a fully commented one (doing that would take way too long -- the FF1 project spanned years), but a bare-bones functional disassembly should only take a few days.
I don't really want to repeat work that's already been done though, so is there a disassembly already out there that you guys are using?
Also a CDL file would be very useful, but not strictly necessary. As I've never actually played this game before and know next to nothing about it, I'm not really up to the task of making one myself. Is there one out there? And if not, would anyone be willing to make one?
As far as assemblers go, I went with ca65 for the FF1 project because I liked the way it did local symbols and had the most consistent and clear syntax of other assemblers I looked at (and was modern enough to run on my machine at the time -- things like x816 wouldn't even run without an emulator). Is everyone happy with ca65 or is there another preferred assembler I should shoot for?
(PS: Thanks to MoN for helping me recover this account!)
Quote from: Disch on January 21, 2020, 08:02:27 PM
So I've only been sparsely paying attention to this conversation, but my understanding is that code is being analyzed largely in-emu and modifications are being done by hand in a hex editor? No disassembly?
Is this correct?
If so, I might consider making a disassembly if one isn't available. Not a fully commented one (doing that would take way too long -- the FF1 project spanned years), but a bare-bones functional disassembly should only take a few days.
I don't really want to repeat work that's already been done though, so is there a disassembly already out there that you guys are using?
Also a CDL file would be very useful, but not strictly necessary. As I've never actually played this game before and know next to nothing about it, I'm not really up to the task of making one myself. Is there one out there? And if not, would anyone be willing to make one?
As far as assemblers go, I went with ca65 for the FF1 project because I liked the way it did local symbols and had the most consistent and clear syntax of other assemblers I looked at (and was modern enough to run on my machine at the time -- things like x816 wouldn't even run without an emulator). Is everyone happy with ca65 or is there another preferred assembler I should shoot for?
(PS: Thanks to MoN for helping me recover this account!)
Oh yeah, I put a link to abw's disassembly that he's been updating and I've been using and identifying for him to comment on, and he also has a CDL that he found on that same first page. I'll update my first post to include a link to the CDL. And yeah, I've been doing debugging and patching in a pretty rudimentary way, as you've described. :-[
And whatever tools are most comfortable for you Disch! I'm really humbled to see so many Final Fantasy romhackers peeking into this thread and offering to pitch in. I'm learning, but I feel like what I'd like to do may be more ambitious than my skillset allows. I'll keep doing everything I can to help though, and I'm immensely grateful to any contributions you'd be willing to make! There's going to be a lot of names on this project when it's all said and done. :)
Ah! Shame on me for missing all those links right in the first post! :thumbsup:
Looks like abw and Jiggers both made disassemblies already -- and Jiggers' even looks to be reassemblable! So yeah I guess you guys have it covered already.
I think Jiggers has her hands full though with her own projects, so comments have been going into abw's disassembly, though I'd like to update the GitHub one at some point. I don't know if I have edit permissions though.
Seems I'm at a bit of a conundrum. Apparently I can't use that juicy free space because it's outside of the fixed bank. I'll have to use space that's within $C000-$FFFF, which means that currently I can only use that 184, 196, and the 3 and 19 that abw and I are already trying to use. Which reminds me, did anyone weigh in on shifting that code up three spaces and adjusting pointers accordingly?
I'm going to use 39 bytes at 0x03F858 to 0x03F87E for the combat counters fix.
Quote from: redmagejoe on January 21, 2020, 08:16:00 PM
I think Jiggers has her hands full though with her own projects, so comments have been going into abw's disassembly, though I'd like to update the GitHub one at some point. I don't know if I have edit permissions though.
I *THINK* you can fork the repo even without write permissions, and make your changes there. Then Jiggers (or someone else with write permission) could merge your fork back into the main repo if they want.
I don't really know how to do either of those things offhand, though -- I'd have to look it up =x
But whatever. Do things however it is easiest for you :thumbsup:
Curious issue... Now the game doesn't break at least, and the counter overflow protection DOES work. However, with spells at least, it appears some other spell counters are raising besides the one being used. I will record my observations while I test. All of these are being done on Firion, and I'm confident the issue will be the same on other characters, but I will test them when I have more room in my RAM watch list.
Spell Slot 1 (Fire) @ $7CF7, then @ $7CF4
Spell Slot 2 (Thunder) @ $7CF8, then @ $7CF5
Spell Slot 3 (Blizzard) @ $7CF9, then @ $7CF6
Spell Slot 4 (Flare) @ $7CFA, then @ $7CFA
Spell Slot 5 (Cure) @ $7CFB, then @ $7CF3
Clearly there are some shenanigans happening here with the spell slot counter code. Could it be that it doesn't handle the carry properly, and as a result, it's wrapping around back to a $7Cxx address when it should be at a $7Dxx address? Did I handle the additions wrong somewhere? I should set some breakpoints and see what exact values I'm getting. It looks like I'm somehow ending up 3 less than my previous value, when it should be tallying black magic or white magic counter at 3F or 43.
Quote from: redmagejoe on January 21, 2020, 12:19:10 AM
If this does indeed cause a more uniform distribution as opposed to what before could be described as 12.5% (0-63), 37.5% (64-127), 37.5% (128-191), 12.5% (192-255), then it should solve the problem outright. I can't imagine it will harm any other mechanics in the game, as RNG should aim to be uniform anyway in this game's combat systems. Would you like to keep this under review as per your notes about avoiding that free space, or do you want me to slap it on the first post?
The actual distribution was 43/256 (16.8%), 85/256 (33.2%), 85/256 (33.2%), 43/256 (16.8%). Scanning the disassembly for uses of $42 (the RNG LUT index), I see a lot of places where the game sets it to a specific value (e.g. LDA #$08 STA $42), which kind of makes the lookup table not so random anymore, so it's possible there might be deeper RNG issues that simply fixing the overall distribution wouldn't address. Might as well add it to the first post for now; if I get around to analyzing the couple of calls where I can't easily verify that X <= A and find out that assuming the upper bound really is not smaller than the lower bound is safe, then I can make a significant code size reduction later.
Quote from: redmagejoe on January 21, 2020, 05:34:46 PM
Meanwhile, I decided to take a look at all the blocks of free space available in ROM before we start sticking our patches in them.
Caveat emptor: the only reason those blocks are marked as "free" is because I saw a run of identical bytes that hadn't been logged by taotao's CDL file and didn't have any immediately obvious pointers into them. It is in no way a guarantee that they are actually unused, just an educated guess.
Quote from: Disch on January 21, 2020, 08:02:27 PM
Is everyone happy with ca65 or is there another preferred assembler I should shoot for?
For no particularly strong reason, I've been using Asar for generating patches, which I hear is not one of ca65's strong points. Asar's not the greatest for 6502 as it doesn't complain about me accidentally mixing 65816 with 6502 ("INC A" and "BRA" are my weaknesses :P) and disabling its default SNES memory mapping has some unwanted side effects like also disabling bounds checking, so I'm not super attached to it.
P.S. Welcome back!
Quote from: redmagejoe on January 21, 2020, 08:16:00 PM
Which reminds me, did anyone weigh in on shifting that code up three spaces and adjusting pointers accordingly?
If you can find all the pointers, sure, go right ahead. I've automatically labelled all the explicit direct control flow sources/targets and manually identified hopefully most/all of the indirect jumps, but there are some calculated and otherwise sneaky ones in there such as dynamically changing the NMI vector at $0100, pushing a return address before JMP to some routine that ends with RTS, or just plain writing the bytes for JMP $addr to RAM and executing that.
Quote from: redmagejoe on January 21, 2020, 09:13:33 PM
Curious issue... [...] with spells at least, it appears some other spell counters are raising besides the one being used.
One thing to note is that you aren't saving and restoring the registers and processor flags that you're modifying in your new routine. Is that a problem? Well, it depends. When the original code does stuff like this:
0x031E68|$0C:$9E58:FE F7 7C INC $7CF7,X ; Character #1 spell slot #1 battle use counter
0x031E6B|$0C:$9E5B:A6 9E LDX $9E
0x031E6D|$0C:$9E5D:A0 2A LDY #$2A
0x031E6F|$0C:$9E5F:B1 80 LDA ($80),Y
0x031E71|$0C:$9E61:C9 15 CMP #$15
then it doesn't matter what state you leave A, X, Y, or C/Z in, but when the original code does stuff like this:
0x031E75|$0C:$9E65:FE 3F 7D INC $7D3F,X ; Character #1 black magic use counter
0x031E78|$0C:$9E68:D0 03 BNE $9E6D
then it makes a difference what state you leave Z in. Similarly, when the original code does stuff like this:
0x03261D|$0C:$A60D:FE 3B 7D INC $7D3B,X ; Character #1 counter for times magically attacked by enemy
; control flow target (from $A537, $A56E, $A5A2, $A5AB, $A5CA, $A609)
0x032620|$0C:$A610:A0 2B LDY #$2B
0x032622|$0C:$A612:91 44 STA ($44),Y
then returning from your new routine with a different value in A than when you entered results in unintentional changes to other things in the game.
The problem was actually that I'm an idiot. I got lost counting bytes and subconsciously shifted to decimal in my head instead of hexadecimal. 60 and 64 should be 5A and 5E, so I was pointing the code in the wrong direction. Also, I'll try to avoid messing with pointers and shifting code around, since it sounds like it could get messy to only give us 3 more contiguous bytes in a space of 19 bytes. Of course we also have those two other large regions to work with.
I'll let you analyze the RNG fix as you see fit before committing it since it sounds like you still have some ideas in mind. Once I test this combat counter overflow protection thoroughly maybe I'll finally be able to sit down and focus on the Evasion / Magic Defense delay. ;D
Quote from: Disch on January 21, 2020, 08:02:27 PMIf so, I might consider making a disassembly if one isn't available. Not a fully commented one (doing that would take way too long -- the FF1 project spanned years), but a bare-bones functional disassembly should only take a few days.
I don't really want to repeat work that's already been done though, so is there a disassembly already out there that you guys are using?
Also a CDL file would be very useful, but not strictly necessary. As I've never actually played this game before and know next to nothing about it, I'm not really up to the task of making one myself. Is there one out there? And if not, would anyone be willing to make one?
As far as assemblers go, I went with ca65 for the FF1 project because I liked the way it did local symbols and had the most consistent and clear syntax of other assemblers I looked at (and was modern enough to run on my machine at the time -- things like x816 wouldn't even run without an emulator). Is everyone happy with ca65 or is there another preferred assembler I should shoot for?
I would love it if you could take the one I started with and flesh it out more. I spent way too long on just the one and a half banks that actually have code written out--everything else is still in hex blocks! I don't know how to extract the disassembled code and put it in, I was copy-pasting from Mesen's debugger and then doing all kinds of macros to get it looking pretty, then more half-manual macros to fix branch labels, then comparing it 500 times until I fix every minor detail and get a 100% match with the rom... and still end up with a horrible ugly mess. I'd be interested in going through it and commenting and figuring out what things are, but the actual building part was frustrating me too much. Sounds like you'd be a lot more efficient at it!
Unless I was getting frustrated from being exactly as efficient as anyone else would be. :P
I haven't been keeping up with abw's work to see if its gotten any easier for me to read and copy over to the re-assemble-able version... Still trying to catch up with my work obligations. :-[ Just really happy to see its careening on ahead with so much work being put into it!
Quote from: Jiggers on January 22, 2020, 12:30:37 AM
I don't know how to extract the disassembled code and put it in, I was copy-pasting from Mesen's debugger and then doing all kinds of macros to get it looking pretty, then more half-manual macros to fix branch labels, then comparing it 500 times until I fix every minor detail and get a 100% match with the rom... and still end up with a horrible ugly mess.
Yeah I probably would have gone the exact opposite way and disassembled the whole ROM as if it were all code, then gone through and replaced data blocks with binary.
BUT, since there's a CDL available, we don't even need to do that. We can just selectively disassemble the code blocks according to the CDL. I dunno if there's a usable disassembler available that works with CDLs and is also mapper aware, though, so I'll probably have to just whip something up in Python (which, IIRC, is what I did for FF1).
I hope you're not too offended, but I think it would actually be significantly easier for me to build a new disassembly from scratch than it would be to try to build off of what you have. :( And in the process I might be able to make an actually re-usable disassembler.
I'll start on this tomorrow!
EDIT:
Just to verify, after skimming the thread it looks like everyone here is using the Chaos Rush translation. Is that correct?
Is that what the CDL is built on, too?
I am certainly using the Chaos Rush translation, which I have an unofficial v1.8 for (went through and fixed up typos, but he's retired from ROM hacking, and standards prevents me from updating his rather than as an addendum). I believe abw said his disassembly is based on the original JP ROM though.
Quotea usable disassembler available that works with CDLs and is also mapper aware
IDA 5.2 can do it if you have scripts (https://tcrf.net/User:Cah4e3) for it.
So while you at it, can you record a video of how you make a disassembly from scratch?
Quote from: redmagejoe on January 22, 2020, 06:56:18 AM
I am certainly using the Chaos Rush translation, which I have an unofficial v1.8 for
Hrm. Any chance I could get that translation patch off you? (without any of the other fixes you made in this thread) --- assuming there's no distribution issues. Is Chaos Rush OK with that version being passed around? I feel like that would be the most preferable starting point.
Either that or I could use the 1.7 patch hosted on RHDN. But getting the latest version possible seems like a better idea. I really don't want to use the original Japanese ROM -- I don't think the target audience here is likely to use that version.
Quote from: Cyneprepou4uk on January 22, 2020, 07:38:49 AM
IDA 5.2 can do it if you have scripts (https://tcrf.net/User:Cah4e3) for it.
Oooo, neat. I figured someone must have done something like this already. However I don't know if I can go this route.
The big problem with IDA is that it's not free. That's kind of a deal breaker for me. Also it's kind of a huge dependency for something a few python scripts could do. Kind of like using a sledgehammer to drive a nail.
QuoteSo while you at it, can you record a video of how you make a disassembly from scratch?
I mean, I'm probably just going to write some python to do the bulk of it. I'm going to try to make something that's a bit extensible so that it can be reused for other games... but once I get the basic program done you'll be able to just run it to get a ~70% functional disassembly of whatever.
I say ~70% because I can't think of a good way to reliably automate replacing indirect memory accesses with proper labels. And without that, you won't really be able to "move code around" without breaking everything. So I'm still going to have to go through the disassembly by hand and look for all those and tweak them manually. But that's not [usually] particularly difficult, it's just tedious.
For example, there might be code like this in the disassembly:
LDA #$20
STA $10
LDA #$80
STA $11
LDA ($10),Y
It's obvious to a human, but it's really hard to get a disassembler smart enough to realize that SHOULD be disassembled to this:
LDA #<Label_8020
STA $10
LDA #>Label_8020
STA $11
LDA ($10),Y
Especially when there is a gap between where the pointer is set and where it's used. And Indirect JMPs are even worse, as those pointers are usually pulled from a LUT.
There are probably one or two more other things that'll have to be done manually, too.
QuoteThe big problem with IDA is that it's not free
Ghidra is free if that's your concern. But I don't know much about it. I think I saw some projects on GitHub like this one (https://github.com/fortenbt/Ghidra-SMB3INES-Loader). You take it from here.
I haven't distributed it or tried to upload it as an addendum because I didn't get any feedback from Chaos Rush one way or another. I reached out to him, but the only response I got was "I don't do ROM hacking any more". I don't want to make it widely available unless I ascertain his feelings on the matter, but I can PM you v1.8. It still has a minor issue that 1.7 does with spell names using DTE to be more clear, but he expanded some of them to 5 characters instead of 4. This causes spell levels in combat above 9 to get pushed outside the bounds of the display box. So Thunder 10 becomes |Thunder 1|. I don't know how to fix that though.
Also, I am going to be taking a VERY brief hiatus on this project (less than a week) while I focus on a work project. I feel like I'm at a good stopping point with nothing up in the air other than the Evasion/MDef thing, so I'll get right back to it once I finish my obligations.
Quote from: Disch on January 22, 2020, 03:53:37 AM
I dunno if there's a usable disassembler available that works with CDLs and is also mapper aware, though, so I'll probably have to just whip something up in Python (which, IIRC, is what I did for FF1).
I hope you're not too offended, but I think it would actually be significantly easier for me to build a new disassembly from scratch than it would be to try to build off of what you have. :( And in the process I might be able to make an actually re-usable disassembler.
One of my pet projects has been a NES disassembler that leverages a CDL file to provide (with many caveats) code/data separation, detects and labels intra-bank absolute and indexed control flow and data load targets, and utilizes a separate file to augment that with various other information such as assumptions for unknown bytes, targeting info for inter-bank operations and indirect jump/pointer tables, comments for blocks and lines of code/data, etc. with the end goal being the generation of a fully labelled and commented re-assemblable disassembly. Most of the time it works out pretty well, but there are some annoying edge cases to deal with, and in practice I've been more interested in the intermediate results I'm getting than in the final product, so re-assemblable output is still WIP.
That said, I guess I ought to post the re-assemblable version (https://drive.google.com/drive/folders/1vCBYRM8JhcB29sHorRTyWBleVs__3Up6?usp=sharing) of what I've got. There are plenty of missing labels, so use with caution.
In any case, I am totally fine with having an another option available (especially if it's better than mine!), so go right ahead :thumbsup:.
Quote from: Disch on January 22, 2020, 03:53:37 AM
Just to verify, after skimming the thread it looks like everyone here is using the Chaos Rush translation. Is that correct?
Is that what the CDL is built on, too?
The CDL file found on the internet was for the Japanese ROM (MD5: 374ED97BE8BFD628F6B359A720549ECD), so that's what I've been working from. If the translation also touches mechanics or uses any of the free space, there could be conflicts with the fixes made in this thread, but so far we've haven't done anything specifically related to text, so I'm thinking these patches
should be widely compatible with translations.
Quote from: Disch on January 22, 2020, 10:56:35 AM
For example, there might be code like this in the disassembly:
Yeah, automatic labelling is hard. Here's an actual expanded example from FF2:
; call $0C:$8F49
; external control flow target (from $0B:$9645)
0x03FB27|$0F:$FB17:A9 49 LDA #$49 ; $0C:$8F49
0x03FB29|$0F:$FB19:D0 16 BNE $FB31
; call $0C:$8F4C
; external control flow target (from $05:$A14E, $05:$A52A, $05:$A745, $05:$A7B8)
0x03FB2B|$0F:$FB1B:A9 4C LDA #$4C ; $0C:$8F4C
0x03FB2D|$0F:$FB1D:D0 12 BNE $FB31
; call $0C:$8F4F
; external control flow target (from $05:$9FBD, $05:$A0F7, $05:$A13A, $05:$A413)
0x03FB2F|$0F:$FB1F:A9 4F LDA #$4F ; $0C:$8F4F
0x03FB31|$0F:$FB21:D0 0E BNE $FB31
; call $0C:$8F52
; external control flow target (from $05:$A54A)
0x03FB33|$0F:$FB23:A9 52 LDA #$52 ; $0C:$8F52
0x03FB35|$0F:$FB25:D0 0A BNE $FB31
; call $0C:$8F55
; external control flow target (from $05:$A4E7)
0x03FB37|$0F:$FB27:A9 55 LDA #$55 ; $0C:$8F55
0x03FB39|$0F:$FB29:D0 06 BNE $FB31
; call $0C:$8F58
0x03FB3B|$0F:$FB2B:A9 58 LDA #$58 ; $0C:$8F58
0x03FB3D|$0F:$FB2D:D0 02 BNE $FB31
; call $0C:$8F5B
; external control flow target (from $05:$A1CD, $05:$A3E3)
0x03FB3F|$0F:$FB2F:A9 5B LDA #$5B ; $0C:$8F5B
; control flow target (from $FB19, $FB1D, $FB21, $FB25, $FB29, $FB2D)
0x03FB41|$0F:$FB31:85 40 STA $40
0x03FB43|$0F:$FB33:A9 8F LDA #$8F
0x03FB45|$0F:$FB35:85 41 STA $41
0x03FB47|$0F:$FB37:A9 0C LDA #$0C
0x03FB49|$0F:$FB39:4C 88 FA JMP $FA88 ; swap in PRG bank in A, saving $3E to $3F and A to $3E, then JSR ($0040)
On the bright side, MMC1 is a pretty simple mapper, so probably the RAM addresses and targetting info (what there is of it, at least) are all correct.
Quote from: abw on January 22, 2020, 11:53:43 AM
One of my pet projects has been a NES disassembler that leverages a CDL file to provide (with many caveats) [a ton of stuff]
Yeah this is sort of like the holy grail. Though I'm dubious as to how much of it can be reasonably automated reliably. I think a reasonable goal (at least at the start) would be something that can be reassembled out of the box, but with the caveat that if you add/remove bytes and move data around, some stuff might break.
A heavy duty static analysis tool like IDA/Ghidra might be able to do a better job of this, but I just spent the past hour looking over docs and tutorials of how to create a custom loader for Ghidra and it's not exactly straightforward. I'm probably going to stick with my idea of just doing something custom in Python.
QuoteThat said, I guess I ought to post the re-assemblable version (https://drive.google.com/drive/folders/1vCBYRM8JhcB29sHorRTyWBleVs__3Up6?usp=sharing) of what I've got. There are plenty of missing labels, so use with caution. [snip] The CDL file found on the internet was for the Japanese ROM (MD5: 374ED97BE8BFD628F6B359A720549ECD), so that's what I've been working from.
Nice! I like some of your ideas here, like adding a line to tell what parts of the code are jumping to each label. Also, did you automatically detect the PRG swapping routine or did you add those notes manually?
I suppose if you're working with the J Rom and I'm working with the Chaos Rush translation, then I'm not COMPLETELY duplicating your work :laugh:. I'm torn between really wanting to do this because it's fun, but also recognizing that I'd be redoing a lot of stuff that you've done already.
Maybe instead of FF2, I should work on FF3? Though I'd still be duplicating the work on the disassembler...
... or maybe I can do some kind of automated CDL generator? That'd be something new.
Quote from: redmagejoe on January 22, 2020, 11:43:01 AM
I haven't distributed it or tried to upload it as an addendum because I didn't get any feedback from Chaos Rush one way or another.
Thanks, but if you're not comfortable handing it out publicly, then neither am I. I'll stick to using the 1.7 release... if I continue with the FF2 idea at all.
Quote from: redmagejoe on January 22, 2020, 11:43:01 AM
Also, I am going to be taking a VERY brief hiatus on this project (less than a week) while I focus on a work project.
Yeah, this is the first day in a couple of weeks where there have been fewer than a dozen posts in this thread :P.
Quote from: Disch on January 22, 2020, 01:02:30 PM
I think a reasonable goal (at least at the start) would be something that can be reassembled out of the box, but with the caveat that if you add/remove bytes and move data around, some stuff might break.
Assuming my disassembler works at all*, I've got that part done.
*: FCEUX's CDL format leaves a couple of things to be desired (e.g. it assumes 4 RAM slots regardless of what the mapper actually does, it would be great to log info about code/data accesses from code running in NES or cartridge RAM, etc.), and some mappers are tricky.
- As an example, I was running into issues with Crystalis (an MMC3 game with $2000 bank size) where bank $12 gets loaded into $8000-$9FFF and bank $13 gets loaded into $A000-$BFFF, but there's a STA $04 that crosses the bank boundary and screws everything up.
- ROM banks that get loaded into multiple RAM banks are also problematic.
- I'm not sure what the right way is to label things like LDA $8000,X in a $C000-$FFFF fixed bank where that legitimately refers to any of several banks potentially loaded into $8000.
- Similarly, I'm not sure how to handle labelling bytes that get chopped up into a pointer, e.g. the high 3 bits select the bank and the low 5 bits select the N'th pointer in a pointer table in the selected bank, especially when there's extra math and conditional logic involved. Probably I'll just define those as a constant or something.
- Sometimes code also gets logged as data (e.g. checksums, RNG reading from random addresses, list scanning routines going way beyond their data) and sometimes data is actually code (e.g. bytes get copied to RAM and then executed).
- Anything that gets executed in RAM will require much better static analysis!
It's far enough along to not completely suck, but basically it's still a WIP project.
Quote from: Disch on January 22, 2020, 01:02:30 PM
Nice! I like some of your ideas here, like adding a line to tell what parts of the code are jumping to each label.
I
really like knowing what's going on with the control flow when mucking about with ASM changes. Without that, it's too easy to look at a bunch of code and say "oh, I can make this little change and everything will be awesome" only to find out that some other code is branching into the middle of your changes and crashing the system :(.
Quote from: Disch on January 22, 2020, 01:02:30 PM
Also, did you automatically detect the PRG swapping routine or did you add those notes manually?
I guess it wouldn't be too hard to pick out swap routines automatically, but at the moment I'm just identifying those manually. My setup involves an extra file that augments the CDL with assumptions for unknown bytes (e.g. byte X is code even though it wasn't logged as code), targeting info for inter-bank/calculated/partial pointers, all the other comments (one fun thing is that I can have comments act a bit like labels, e.g. add a comment on a routine and have the comment appear inline on all calls to the routine; they can also be transitive on pointers, e.g. when $8000 is a pointer to $9ABC and $9ABC has a comment-label, the comment appears on $8000 and anything that points to $8000), and formatting info for data tables.
Quote from: Disch on January 22, 2020, 01:02:30 PM
I suppose if you're working with the J Rom and I'm working with the Chaos Rush translation, then I'm not COMPLETELY duplicating your work :laugh:. I'm torn between really wanting to do this because it's fun, but also recognizing that I'd be redoing a lot of stuff that you've done already.
Maybe instead of FF2, I should work on FF3? Though I'd still be duplicating the work on the disassembler...
... or maybe I can do some kind of automated CDL generator? That'd be something new.
Dealer's choice here ;). Do you have any idea what the breakdown of work for your FF1 disassembly was?
QuoteWithout that, it's too easy to look at a bunch of code and say "oh, I can make this little change and everything will be awesome" only to find out that some other code is branching into the middle of your changes and crashing the system
Oh please, you are exaggerating. In most cases it's enough to scroll debugger up/down a few pages looking for nearby jumps and branches. And even if the worst does happen, this issues can be located very easy.
Talking about a skillful romhacker of course, with a bunch of backups >:D
abw: I started a new thread since we're pretty far off of topic with regard to redmagejoe's project.
http://www.romhacking.net/forum/index.php?topic=29943.0
I'm not sure how relevant this is, but Hironobu Sakaguchi explained why the Ultima bug is a thing: when he first discovered it, he wanted the bug fixed, but some jerk programmer said no, because the bug reflected how legendary weapons/items in real life rarely live up to their own hype. Then, he ciphered the bug, so Sakaguchi couldn't fix it himself. So, here's to hoping you don't have too much trouble fixing Ultima.
Quote from: CoolCatBomberMan on January 23, 2020, 08:29:49 PM
I'm not sure how relevant this is, but Hironobu Sakaguchi explained why the Ultima bug is a thing: when he first discovered it, he wanted the bug fixed, but some jerk programmer said no, because the bug reflected how legendary weapons/items in real life rarely live up to their own hype. Then, he ciphered the bug, so Sakaguchi couldn't fix it himself. So, here's to hoping you don't have too much trouble fixing Ultima.
Still working on my work project, but thought I'd pop in to say that, given that Ultima behaves the way Sakaguchi wanted it to in the remake, and knowing that was his original intent, it is definitely a priority to attempt to make it work in this version. Perhaps that will be our Final Challenge. :)
I went and looked into this, and it was the source code that was ciphered apparently. Fortunately we're reverse-engineering it, so I'm not sure it would be an issue, though if by "source code" it means the assembly which we're turning the binary back into... Then yeah, we may have trouble. Hopefully we can make sense of most of the rest of the disassembly such that the only things left by the time we get to it must, by process of elimination, be related to Ultima.
I still lurk on RHDN because I like reading about programming stuff so I thought I'd give a response to something that is related to me.
Quote from: redmagejoe on January 22, 2020, 11:43:01 AM
I haven't distributed it or tried to upload it as an addendum because I didn't get any feedback from Chaos Rush one way or another. I reached out to him, but the only response I got was "I don't do ROM hacking any more".
Well to be fair, you didn't say upfront what you wanted to do, and once you did you left the chatroom before I was able to give a proper response, preventing me from giving a proper reply (since apparently Discord only lets you message people that you share a chatroom with). (EDIT: it was before I saw that you PM'd me here too)
Anyways if you want to make a v1.8, go ahead. I thought I made it rather clear in my last post in the FF NES trilogy translations thread that I'm "passing it on" to the community and people can update it from there, though I apologize if that wasn't made clear. Though I'm not sure if RHDN's system allows owner transfership of projects, if not then you would have to post it as an addendum (I think).
But also I want to say, around the time I said that I'm done with working on those projects, some people harassed me on Discord with questions like:
"Are you going to release the source code for your tools?"
"So you're never going to translate the Wonderswan versions of FF then?"
I want to say, I appreciate people's support for my past projects, but I also wish people could understand that I'm not obligated to work on them until perfection, nor am I obligated to work on projects I never even started to begin with (despite expressing interest in the past to do it - such as a hypothetical translation of Wonderswan FF1). I don't want to be held a slave to projects that I simply don't want to work on anymore.
Also I understand that my text editors might be useful but for personal reasons I don't wish to release the source code (and I no longer have access to the older ones anyways since they were on my old laptop which I no longer have access to).
At this point I want to devote my creative energies fully to my own game that I'm working on, and I am confident that if I work hard on it that it can be successful. There's a good reason why you don't see former ROM hackers like the Undertale dev or the Sonic Mania devs returning to ROM hacking (I'm not gonna state why I think at this time but I will say it's related to ethics).
So yes, feel free to update any of my old projects. However, I will not -cannot- update any of my old text editors, and I hope the community can respect my decision regarding that. I
am done with ROM hacking.
EDIT: One more thing I want to add, my FF2 translation was the first one I did, so it's also the least polished under the hood. It used the Demiforce translation as an "engine" (since it already contained DTE code and re-arranged the in-game special terms menu), but redid all of the text. There may or may not be some changes unaccounted for, and also unused data from the Demiforce title screen (which I used a patch to remove), and also just data offsets shifts of various tables to increase space. If you're making a disassembly, I think you should work with the Japanese version and just make a new English version from there - and as stated before I'm okay with you guys using/updating the script from my FF2 translation.
Thanks for chiming in, ChaosRush! :beer:
Quote from: Chaos Rush on January 24, 2020, 10:12:22 AM
Anyways if you want to make a v1.8, go ahead.
Great! Thanks. I'll keep that in mind if/when I get around to actually making a new disassembly.
Quotebut I also wish people could understand that I'm not obligated to work on them until perfection, nor am I obligated to work on projects I never even started to begin with
I came to the same conclusion long ago. What makes these projects fun for me is that I can work on them as much or as little as I want without the pressure of expectations. If you are working under a deadline, it is no longer a hobby, it's a job -- but one that you're not getting paid for.
QuoteIf you're making a disassembly, I think you should work with the Japanese version and just make a new English version from there
Ehhhhh, gonna disagree with you there.
I'm sure you're aware that "just make[ing] a new English version" is a
very large undertaking. And suggesting people who want to make a hack of this game, no matter how small, should completely retranslate the whole thing is completely unrealistic.
The only people a disassembly of the J ROM would be useful to would be the Japanese hacking community (who I'm not targeting) and people looking to retranslate a game that already has several serviceable translations. Which I'm not opposed to, but doesn't strike me as a very large audience. It would be next to useless for hackers looking to make other modifications to the game -- as they're almost certainly going to be using an English translation as a base.
Thank you for weighing in on the matter, Chaos Rush. I completely understand and respect your desire to move on, and thank you for entrusting your legacy to us, the community. :)
I have attempted once more, now with Chaos Rush's own words as evidence, to submit v1.8 to the site. The previous reason I was given involved getting direct consent from Chaos Rush, which I believe his post gives. That said, if anyone wishes to make a new side-project of dissecting his patch and "polishing it up" as he says, condensing space used, cleaning up anything else that may have changed not critical to the script itself, and maybe helping figure out the spell name DTE and its use of 5 characters instead of 4 (thus causing spell levels above 9 to be pushed outside the box in combat), then I strongly encourage it.
At this time, I cannot give that top priority as I try to work out the base game mechanic bugs. That said, if no one wishes to take up that gauntlet by the time this project nears completion, then I will definitely add that to my to-do list. I would like to have the Restored patch function off the JP ROM, with Chaos Rush's v2.0 being the recommended English patch to pair with it. :)
If RMJ wants to update this to 1.8 or whatever then it should just be an addendum and not a replacement for Chaos Rush's work, so what's the issue?
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
- Tested: Stats cannot go above 99 with stat-raising equipment in the Anniversary Edition. Cap stats at 99 no matter what for this patch.
- Tested: Stats can stack bonuses from multiple equipment in the remakes. Giant's Helm + Power Sash + Giant's Glove will yield +30 Strength in the remake. Try to implement this behavior into this patch.
Had some downtime, so thought I'd try to find the area relevant to a seemingly simple fix, and wow, how easy it was to track down stat bonuses from equipment. All I did was Ctrl+F ADC #$0A (+10), and behold, it's literally the only Add Carry of 10 in the entire code. Sure enough, I changed it to $14 (+20) and all equipment gave +20 instead of +10. So the routine at 0x003F23 handles stat bonuses from equipment. Should be easy to figure out how to apply these two fixes. It's just a matter of utilizing space effectively. Don't forget that we're currently using the following spaces. Or I'll work on it when I finish my work project.
0x013B33: 221
0x013D5D: 689
0x015D77: 409
0x03400D: 3
0x0387CB: 69
0x03F75E: 178
0x03F83B: 21
... Fixed bank below, can't use above addresses
0x03F858: 184 <- 39/184 used by my combat counter fix
0x03FEFC: 196
0x03FFCD: 3
0x03FFDD: 19 <- abw's RNG WIP uses 12/19
EDIT: Also, v1.8of Chaos Rush's translation is now available on its usual page, as the submission was accepted! :)
Quote from: redmagejoe on January 24, 2020, 12:17:21 AM
Still working on my work project, but thought I'd pop in to say that, given that Ultima behaves the way Sakaguchi wanted it to in the remake, and knowing that was his original intent, it is definitely a priority to attempt to make it work in this version. Perhaps that will be our Final Challenge. :)
I appreciate your restraint in not calling it our ULTIMAte challenge ;D. Sounds like it should be fun when we get to it!
Quote from: Disch on January 24, 2020, 11:16:49 AM
Ehhhhh, gonna disagree with you there.
[...]
The only people a disassembly of the J ROM would be useful to would be the Japanese hacking community (who I'm not targeting) and people looking to retranslate a game that already has several serviceable translations. Which I'm not opposed to, but doesn't strike me as a very large audience. It would be next to useless for hackers looking to make other modifications to the game -- as they're almost certainly going to be using an English translation as a base.
As a counter-point, translations come and go, but the base ROM is forever. Look at the number of Final Fantasy VI translations, for instance; I would argue that a disassembly of any one of them is generally less useful than a disassembly of the original ROM would be.
How's this for an idea? Instead of a binary ROM and various patches, say we have a git repo where one branch contains a disassembly of the base ROM and other branches contain the changes that a patch introduces. Each of the mechanics bugfixes from this thread could have its own branch, each translation could have its own branch, and so on for every other kind of graphics/sound/level/etc. patch.
Quote from: redmagejoe on January 24, 2020, 02:55:44 PM
Had some downtime, so thought I'd try to find the area relevant to a seemingly simple fix, and wow, how easy it was to track down stat bonuses from equipment. All I did was Ctrl+F ADC #$0A (+10), and behold, it's literally the only Add Carry of 10 in the entire code. Sure enough, I changed it to $14 (+20) and all equipment gave +20 instead of +10. So the routine at 0x003F23 handles stat bonuses from equipment. Should be easy to figure out how to apply these two fixes. It's just a matter of utilizing space effectively.
That should be easy enough - it looks like $00:$BF13 is only called from one place, so you can save 4 bytes (JSR $BF13 and its RTS) by inlining the function and you can save 2 more bytes by flipping LDX #$00/INX/INX/CPX #$06 to LDX #$06/DEX/DEX, which is enough to add CMP #$64/BCC +2/LDA #$63 before writing the updated stat. If you really want more space for some reason, I'll note that the CLC at $00:$BF1F is useless since we know A < 128 (because we didn't take the BMI), so A + 16 can't overflow, and as long as the current stat value from the LDA ($7A),Y at $00:$BF1D is less than 246 (and if it's not, something has gone seriously wrong somewhere), we also know that adding 10 to it won't overflow, so the CLC at $00:$BF24 is also not needed.
I will make sure that my changes are independent of any Chaos Rush changes, in that case, to stick to conventions, and release the final patch in IPS form that does not include Chaos Rush's translation.
Quote from: abw on January 25, 2020, 02:28:34 PM
That should be easy enough - it looks like $00:$BF13 is only called from one place, so you can save 4 bytes (JSR $BF13 and its RTS) by inlining the function and you can save 2 more bytes by flipping LDX #$00/INX/INX/CPX #$06 to LDX #$06/DEX/DEX, which is enough to add CMP #$64/BCC +2/LDA #$63 before writing the updated stat. If you really want more space for some reason, I'll note that the CLC at $00:$BF1F is useless since we know A < 128 (because we didn't take the BMI), so A + 16 can't overflow, and as long as the current stat value from the LDA ($7A),Y at $00:$BF1D is less than 246 (and if it's not, something has gone seriously wrong somewhere), we also know that adding 10 to it won't overflow, so the CLC at $00:$BF24 is also not needed.
Yes, but is this also the only routine that handles +10 stat granting equipment? It might be a good idea to try and address both fixes here, including the bonus stacking one. Not that I'm averse to trying out that suggested fix, but I don't want to commit it into IPS and mark it FIXED if we'll just have to change it again.
Also, progress update: I should be finished with my work project before the end of tomorrow, so I can focus all my energy on this by Sunday night at the latest. :)
Original:
0x003F0F|$00:$BEFF:A2 00 LDX #$00
; control flow target (from $BF10)
0x003F11|$00:$BF01:B5 44 LDA $44,X
0x003F13|$00:$BF03:85 5E STA $5E
0x003F15|$00:$BF05:B5 45 LDA $45,X
0x003F17|$00:$BF07:85 5F STA $5F
0x003F19|$00:$BF09:20 13 BF JSR $BF13
0x003F1C|$00:$BF0C:E8 INX
0x003F1D|$00:$BF0D:E8 INX
0x003F1E|$00:$BF0E:E0 06 CPX #$06
0x003F20|$00:$BF10:D0 EF BNE $BF01
0x003F22|$00:$BF12:60 RTS
; control flow target (from $BF09)
0x003F23|$00:$BF13:A0 04 LDY #$04
0x003F25|$00:$BF15:B1 5E LDA ($5E),Y
0x003F27|$00:$BF17:30 12 BMI $BF2B
0x003F29|$00:$BF19:18 CLC
0x003F2A|$00:$BF1A:69 10 ADC #$10
0x003F2C|$00:$BF1C:A8 TAY
0x003F2D|$00:$BF1D:B1 7A LDA ($7A),Y
0x003F2F|$00:$BF1F:18 CLC
0x003F30|$00:$BF20:69 0A ADC #$0A
0x003F32|$00:$BF22:48 PHA
0x003F33|$00:$BF23:98 TYA
0x003F34|$00:$BF24:18 CLC
0x003F35|$00:$BF25:69 10 ADC #$10
0x003F37|$00:$BF27:A8 TAY
0x003F38|$00:$BF28:68 PLA
0x003F39|$00:$BF29:91 7A STA ($7A),Y
; control flow target (from $BF17)
0x003F3B|$00:$BF2B:60 RTS
My mock-up:
0x003F0F|$00:$BEFF:A2 06 LDX #$06
; control flow target (from $BF10)
0x003F11|$00:$BF01:B5 44 LDA $44,X
0x003F13|$00:$BF03:85 5E STA $5E
0x003F15|$00:$BF05:B5 45 LDA $45,X
0x003F17|$00:$BF07:85 5F STA $5F
0x003F19|$00:$BF09:A0 04 LDY #$04
0x003F1B|$00:$BF0B:B1 5E LDA ($5E),Y
0x003F1D|$00:$BF0D:30 16 BMI $BF25
0x003F1F|$00:$BF0F:18 CLC
0x003F20|$00:$BF10:69 10 ADC #$10
0x003F22|$00:$BF12:A8 TAY
0x003F23|$00:$BF13:B1 7A LDA ($7A),Y
0x003F25|$00:$BF15:69 0A ADC #$0A
0x003F27|$00:$BF17:48 PHA
0x003F28|$00:$BF18:98 TYA
0x003F29|$00:$BF19:69 10 ADC #$10
0x003F2B|$00:$BF1B:A8 TAY
0x003F2C|$00:$BF1C:68 PLA
0x003F2D|$00:$BF1D:C9 64 CMP #$64
0x003F2F|$00:$BF1F:90 02 BCC $BF23
0x003F31|$00:$BF21:A9 63 LDA #$63
0x003F33|$00:$BF23:91 7A STA ($7A),Y
0x003F35|$00:$BF25:CA DEX
0x003F36|$00:$BF26:CA DEX
0x003F37|$00:$BF27:D0 D8 BNE $BF01
0x003F39|$00:$BF29:60 RTS
0x003F3A|$00:$BF2A:EA NOP
0x003F3B|$00:$BF2B:EA NOP
Anything I missed? Pretty sure I fixed all the branches. Also do the other CLCs after BMI need to remain?
Quote from: redmagejoe on January 25, 2020, 03:00:40 PM
Also do the other CLCs after BMI need to remain?
That CLC needs to be there, yes. Unless you know for certain that C is clear coming into this code (which, based on what you posted, we can't make that determination).
I'd still like to take a look at what the game does (or doesn't do) about multiple equips that give stat bonuses. At most, it should be possible for one to gain +10 Spirit (only 1 Spirit chestpiece), +10 Intellect (only 1 Intellect chestpiece), +30 Strength (there are helms, gloves, and chestpieces that give +10 Strength), and +30 Agility (helms, gloves, and chestpieces). In the cases of Agility and Strength (which judging by the code, it doesn't even really care what the stats are), we want to take into account 1, 2, or 3 of these pieces being equipped at the same time.
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
- Ripper appears to display incorrect damage values, which was fixed in remakes. Investigate cause of display and actual damage differing.
; control flow target (from $B096)
0x0330B2|$0C:$B0A2:18 CLC
0x0330B3|$0C:$B0A3:A5 44 LDA $44
0x0330B5|$0C:$B0A5:69 14 ADC #$14
0x0330B7|$0C:$B0A7:85 44 STA $44
0x0330B9|$0C:$B0A9:A5 45 LDA $45
0x0330BB|$0C:$B0AB:69 00 ADC #$00
0x0330BD|$0C:$B0AD:85 45 STA $45
Also, this only ADC #$14 (+20) in the code may be related to the Ripper bug, displaying 20 more damage x number of hits in combat, while not actually adding this bonus damage.
Quotebased on what you posted, we can't make that determination
I disagree. There is CMP at the end of the loop, so he definetly has to clear C from the beginning.
QuoteAnything I missed?
Comparing to original, your new loop will not execute its body with X = #$00, and it has excess execution with X = #$06.
Had to take my eye off of the stat gear issue for a moment due to a bug resulting from combining the combat counter fix with the Firion Spirit Enemy Magic fix. I'm going to bundle them together for compatibility after I tweak the code to account for the new behavior. Freed up a bit more space in the process. Fixed the issue. I'd done some math wrong, and also caught some other oversights. Enemy target-all magic on the party now properly increments the "hit by magic" counters for all party members and does the overflow comparison, while single-target magic had failed to check against the new overflow protection, but is now properly pointed to the sub-routine. Similarly, the wrong counters were being updated for being hit by magic and being hit physically because of a math error on my part.
These two patches have been merged into one, and the first post has been updated accordingly.
Quote from: Cyneprepou4uk on January 25, 2020, 06:42:11 PM
Comparing to original, your new loop will not execute its body with X = #$00, and it has excess execution with X = #$06.
I think I see what you mean. Since I removed those two CLCs, I have the 2 bytes I need to revert it to its previous behavior.
0x003F0F|$00:$BEFF:A2 00 LDX #$00
0x003F11|$00:$BF01:B5 44 LDA $44,X
0x003F13|$00:$BF03:85 5E STA $5E
0x003F15|$00:$BF05:B5 45 LDA $45,X
0x003F17|$00:$BF07:85 5F STA $5F
0x003F19|$00:$BF09:A0 04 LDY #$04
0x003F1B|$00:$BF0B:B1 5E LDA ($5E),Y
0x003F1D|$00:$BF0D:30 16 BMI $BF25
0x003F1F|$00:$BF0F:18 CLC
0x003F20|$00:$BF10:69 10 ADC #$10
0x003F22|$00:$BF12:A8 TAY
0x003F23|$00:$BF13:B1 7A LDA ($7A),Y
0x003F25|$00:$BF15:69 0A ADC #$0A
0x003F27|$00:$BF17:48 PHA
0x003F28|$00:$BF18:98 TYA
0x003F29|$00:$BF19:69 10 ADC #$10
0x003F2B|$00:$BF1B:A8 TAY
0x003F2C|$00:$BF1C:68 PLA
0x003F2D|$00:$BF1D:C9 64 CMP #$64
0x003F2F|$00:$BF1F:90 02 BCC $BF23
0x003F31|$00:$BF21:A9 63 LDA #$63
0x003F33|$00:$BF23:91 7A STA ($7A),Y
0x003F35|$00:$BF25:E8 INX
0x003F36|$00:$BF26:E8 INX
0x003F37|$00:$BF27:E0 06 CPX #$06
0x003F39|$00:$BF29:D0 D8 BNE $BF01
0x003F3B|$00:$BF2B:60 RTS
No, I meant that your loop is not properly configured. But reverting to original works as well.
I'm going to leave that block as WIP, since I'd like to figure out the stacking of gears before I commit that. Snooped into Ripper behavior, possibly Mysidian Orbs behavior, abw was working on RNG improvement last, and I still haven't gotten around to that Evasion / MDef delay. I need to focus on putting the finishing touches on my project so I can sort my thoughts and sit down and tackle one thing at a time.
EDIT: Misread something. Ignore me!
And my work project is finished. I'll be back to work on this tomorrow. :)
0x0399B5|$0E:$99A5:AF A1 ; $0E:$A1AF << bottom left orb, Fire Crystal, +10 Strength
0x0399B7|$0E:$99A7:B3 A1 ; $0E:$A1B3 << bottom right orb, Wind Crystal, +10 Agility
0x0399B9|$0E:$99A9:B7 A1 ; $0E:$A1B7 << top left orb, Earth Crystal, +10 Spirit
0x0399BB|$0E:$99AB:BB A1 ; $0E:$A1BB << top right orb, Water Crystal, +10 Intellect
...
; indirect control flow target (via $99A5)
0x03A1BF|$0E:$A1AF:A9 10 LDA #$10 << Strength offset: 6110
0x03A1C1|$0E:$A1B1:D0 0A BNE $A1BD
; indirect control flow target (via $99A7)
0x03A1C3|$0E:$A1B3:A9 11 LDA #$11 << Agility offset: 6111
0x03A1C5|$0E:$A1B5:D0 06 BNE $A1BD
; indirect control flow target (via $99A9)
0x03A1C7|$0E:$A1B7:A9 14 LDA #$14 << Spirit offset: 6114
0x03A1C9|$0E:$A1B9:D0 02 BNE $A1BD
; indirect control flow target (via $99AB)
0x03A1CB|$0E:$A1BB:A9 13 LDA #$13 << Intellect offset: 6113
; control flow target (from $A1B1, $A1B5, $A1B9)
0x03A1CD|$0E:$A1BD:85 80 STA $80 << store offset in $80 based on which orb and thus stat is being increased
0x03A1CF|$0E:$A1BF:A5 F0 LDA $F0 << load A with whatever's in $F0 (RNG output to determine character?)
0x03A1D1|$0E:$A1C1:29 C0 AND #$C0 << bitwise AND against $F0 (???? ????) with #$C0 (1100 0000)
0x03A1D3|$0E:$A1C3:05 80 ORA $80 << bitwise OR with resultant A value from AND above against $80 (stat offset $10, $11, $13, or $14)
0x03A1D5|$0E:$A1C5:AA TAX << store this A value in X to be used as the determined offset for which character gets the bonus
0x03A1D6|$0E:$A1C6:BD 00 61 LDA $6100,X << load A with baseline for character stats, X as offset
0x03A1D9|$0E:$A1C9:18 CLC
0x03A1DA|$0E:$A1CA:69 0A ADC #$0A << adds 10 to the appropriate stat
0x03A1DC|$0E:$A1CC:C9 64 CMP #$64 << prevent stat from going above 99, compare to 100
0x03A1DE|$0E:$A1CE:90 02 BCC $A1D2 << does not exceed cap, proceed to store new stat value
0x03A1E0|$0E:$A1D0:A9 63 LDA #$63 << set stat to 99 if >99
; control flow target (from $A1CE)
0x03A1E2|$0E:$A1D2:9D 00 61 STA $6100,X << store new value of stat, baseline for character stats, X as offset
0x03A1E5|$0E:$A1D5:BD 10 61 LDA $6110,X << base character stats that display in stat window (base + equipment bonuses), X as offset
0x03A1E8|$0E:$A1D8:18 CLC
0x03A1E9|$0E:$A1D9:69 0A ADC #$0A << add 10 to window stats as well
0x03A1EB|$0E:$A1DB:C9 64 CMP #$64 << prevent stat from going above 99, compare to 100
0x03A1ED|$0E:$A1DD:90 02 BCC $A1E1 << does not exceed cap, proceed to store new stat value
0x03A1EF|$0E:$A1DF:A9 63 LDA #$63 << set stat to 99 if >99
; control flow target (from $A1DD)
0x03A1F1|$0E:$A1E1:9D 10 61 STA $6110,X << store new value of stat, baseline for character's window stats, X as offset
0x03A1F4|$0E:$A1E4:A4 A0 LDY $A0
0x03A1F6|$0E:$A1E6:20 07 99 JSR $9907
0x03A1F9|$0E:$A1E9:AD 00 7B LDA $7B00
0x03A1FC|$0E:$A1EC:60 RTS
This appears to be related to the orbs at the top of the Mysidian Tower. Not trying to lose sight of individual goals, but deciphering the disassembly is also beneficial.
So having looked at playthroughs on YouTube of all versions, it seems that in every version, NES, GBA, WonderSwan, GBA, PSP, the orbs randomly assign the bonus to a single character. I guess the question now is: Since this is not listed as a bug, but a possible improvement, what everyone's opinion on this adjustment? Disrespecting the legacy and precedent of the game, or a sensible improvement upon the original concept? A friend provided me with two suggestions: My personal bias is towards giving all 4 characters the stat bonuses, though that may be TOO much of an improvement, or at the least limiting the randomly chosen character to the permanent 3.
EDIT: I believe I've deciphered this routine sufficiently, with the only uncertainty being $F0, which I can only assume is what's responsible for choosing the character to randomly get the bonus. Looking at $F0 in RAM watch, it's clearly related to the RNG. I may come back to this, as much as I know about it now, when I have a more solid idea of HOW I would want to change this, rather than WHAT to change.
I still need to make more sense of what's happening in my code block above, namely with the memory addresses and what they represent being loaded and moved around ($5E, $5F, $7A, $44, $45). I'm going through the menu with stepping through and breakpoints on reading $44 and $45, and going through the control flow, but I'm just having a difficult time understanding the context of everything and what the actual values I'm seeing mean. I'll try to focus my mind and jot notes and observations here that may allow other more savvy and experienced romhackers to offer insight.
QuoteLooking at $F0 in RAM watch, it's clearly related to the RNG.
F0/F1 look like a frame counter to me.
Which, yeah drawing from the frame counter is usually either linked to animation or RNG.
Quote from: Disch on January 26, 2020, 09:52:18 PM
F0/F1 look like a frame counter to me.
Which, yeah drawing from the frame counter is usually either linked to animation or RNG.
Yeah, I should have clarified, sorry. What I meant by "clearly" was that it was rapidly cycling from 00 to FF and repeating, in a rate that must have been at least if not higher than 60 Hertz. So frame counter, yeah. I'm assuming that would only be important to the RNG unless there's some other nuanced application I'm not thinking of (maybe timing checks in other games like Final Fantasy VI or fighting games?). So I guess in the case of the orbs, there's no arbitrarily manipulated RNG like in some games that are based on inputs. The character chosen for the orb bonus is apparently primarily determined by what frame upon which you activate it.
Either way, I'd like to set this aside until I have taken care of the game's bugs first. Hopefully my commenting will at least be useful to abw's progress on his disassembly.
Modified 99 Cap Patch
; control flow target (from $98BE)
0x003EF8|$00:$BEE8:A0 10 LDY #$10
; control flow target (from $BEFD)
0x003EFA|$00:$BEEA:B1 7A LDA ($7A),Y
0x003EFC|$00:$BEEC:48 PHA
0x003EFD|$00:$BEED:98 TYA
0x003EFE|$00:$BEEE:18 CLC
0x003EFF|$00:$BEEF:69 10 ADC #$10
0x003F01|$00:$BEF1:A8 TAY
0x003F02|$00:$BEF2:68 PLA
0x003F03|$00:$BEF3:91 7A STA ($7A),Y
0x003F05|$00:$BEF5:C8 INY
0x003F06|$00:$BEF6:98 TYA
0x003F07|$00:$BEF7:38 SEC
0x003F08|$00:$BEF8:E9 10 SBC #$10
0x003F0A|$00:$BEFA:A8 TAY
0x003F0B|$00:$BEFB:C0 15 CPY #$15
0x003F0D|$00:$BEFD:D0 EB BNE $BEEA
0x003F0F|$00:$BEFF:A2 00 LDX #$00
; control flow target (from $BF10)
0x003F11|$00:$BF01:B5 44 LDA $44,X
0x003F13|$00:$BF03:85 5E STA $5E
0x003F15|$00:$BF05:B5 45 LDA $45,X
0x003F17|$00:$BF07:85 5F STA $5F
0x003F19|$00:$BF09:A0 04 LDY #$04
0x003F1B|$00:$BF0B:B1 5E LDA ($5E),Y
0x003F1D|$00:$BF0D:30 10 BMI $BF1F
0x003F1F|$00:$BF0F:18 CLC
0x003F20|$00:$BF10:69 20 ADC #$20
0x003F22|$00:$BF12:A8 TAY
0x003F23|$00:$BF13:B1 7A LDA ($7A),Y
0x003F25|$00:$BF15:69 0A ADC #$0A
0x003F27|$00:$BF17:C9 64 CMP #$64
0x003F29|$00:$BF19:90 02 BCC $BF23
0x003F2B|$00:$BF1B:A9 63 LDA #$63
0x003F2D|$00:$BF1D:91 7A STA ($7A),Y
0x003F2F|$00:$BF1F:E8 INX
0x003F30|$00:$BF20:E8 INX
0x003F31|$00:$BF21:E0 06 CPX #$06
0x003F33|$00:$BF23:D0 DC BNE $BF01
0x003F35|$00:$BF25:60 RTS
0x003F36|$00:$BF26:EA NOP
0x003F37|$00:$BF27:EA NOP
0x003F38|$00:$BF28:EA NOP
0x003F39|$00:$BF29:EA NOP
0x003F3A|$00:$BF2A:EA NOP
0x003F3B|$00:$BF2B:EA NOP
I think I see the problem watching RAM. When I try to equip a second piece of equipment and the execution breaks, once it arrives at $BF13, the value loaded is $6110 @ $45 (69 Strength), which is the base Strength for Firion. As such it's performing the math that sets the value of $6120 (window/effective Strength) based upon the base Strength, rather than evaluating what $6120 is from the beginning. So it's not that they're deliberately not stacking, it's that the arithmetic is done in such a way that it always assumes your effective stat is the same as your base stat any time you put on one of these pieces of equipment. Let's see if we can't modify that...
EDIT: I cannot BELIEVE what an easy fix that was. And it doesn't APPEAR to have any drawbacks, as the subtraction routine operates properly even with this change. I even anticipated the case of "what does it do when one of the equips pushes you up to 99 when it would usually go over?" and it still seems to by default revert the stat to the correct amount rather than docking you an extra point (i.e. 90 -> 99 -> 90 rather than 89). Updated code block above, trying to figure out what to do with these 6 new bytes I've created. I feel like when this is all said and done, some code shuffling may be in order to make use of the free space we're creating. If you think you can get that RNG fix you wanted to squish down to 6 bytes instead of 12, abw, we can stick it in these bytes I just freed up. :)
The return of... the Evasion/Magic Defense Level Up Delay! ::)
At this rate, I've delayed fixing this almost as long as the delay bug itself.
; level up Evasion
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 ; base Evasion credit for being physically attacked
0x016616|$05:$A606:85 46 STA $46 ; base credit amount for being attacked at all
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
; level up Magic Resist
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 ; base Magic Resist credit for being magically attacked
0x01662A|$05:$A61A:85 46 STA $46 ; base credit amount for being attacked at all
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
; 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 ; counter for times physically/magically attacked
0x016856|$05:$A846:F0 24 BEQ $A86C ; no pain => no gain
0x016858|$05:$A848:18 CLC
0x016859|$05:$A849:65 22 ADC $22 ; battle rank?
0x01685B|$05:$A84B:65 46 ADC $46 ; base credit amount for being attacked at all
0x01685D|$05:$A84D:A4 47 LDY $47 ; offset for Evasion/Magic Resist skill level, whichever one we're dealing with at the moment
0x01685F|$05:$A84F:F1 7E SBC ($7E),Y ; Evasion/Magic Resist skill level
0x016861|$05:$A851:E9 0A SBC #$0A ; your first 10 points don't count for anything
0x016863|$05:$A853:90 17 BCC $A86C ; not enough pain => no gain
0x016865|$05:$A855:C8 INY ; offset for Evasion/Magic Resist skill experience, whichever one we're dealing with at the moment
0x016866|$05:$A856:71 7E ADC ($7E),Y ; Evasion/Magic Resist skill experience
0x016868|$05:$A858:C9 64 CMP #$64
0x01686A|$05:$A85A:90 0E BCC $A86A ; if experience < 100, just update experience, otherwise increase level
0x01686C|$05:$A85C:88 DEY ; offset for Evasion/Magic Resist skill level, whichever one we're dealing with at the moment
0x01686D|$05:$A85D:B1 7E LDA ($7E),Y ; Evasion/Magic Resist skill level
0x01686F|$05:$A85F:AA TAX
0x016870|$05:$A860:E8 INX ; level++
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 ; save new Evasion/Magic Resist skill level
0x016877|$05:$A867:C8 INY ; offset for Evasion/Magic Resist skill experience, whichever one we're dealing with at the moment
0x016878|$05:$A868:A9 00 LDA #$00 ; reset experience to zero
; control flow target (from $A85A)
0x01687A|$05:$A86A:91 7E STA ($7E),Y ; save new Evasion/Magic Resist skill experience
; control flow target (from $A846, $A853)
0x01687C|$05:$A86C:60 RTS
; 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
Neither $7D7D (Firion's combat temporary MDef level holder) nor $612C (Firion's permanent MDef level stat) are updated from 07 to 08 until a piece of equipment is changed or a battle is entered. Despite the STA ($7E),Y at $A865, $612C is not being immediately updated. The question then becomes where is this value being stored, and what instruction is executed to "refresh" or update stats in either of these cases? Would it not make more sense to save to $612C immediately?
Hmm... do the Mysidian Orbs actually work right? The code looks solid enough, but I recall seeing HCBailly's ancient Let's Play of Final Fantasy II and I distinctly recall that he had "A1" intelligence on one of his characters.
Ah, found it. In the video his Firion has A1 Soul after using the Orbs. https://youtu.be/XcNNK2I_NVM?list=PL31D24C1307A71BD4&t=409
Quote from: redmagejoe on January 26, 2020, 09:56:47 PM
If you think you can get that RNG fix you wanted to squish down to 6 bytes instead of 12, abw, we can stick it in these bytes I just freed up. :)
Except that your new free space is in bank 0, so I'd have to add code to swap that bank in and out to reach the free space :P. Once I get around to figuring out possible value ranges on the 2 RNG calls that use calculated lower and upper bounds, hopefully it'll be safe to assume that the bounds make sense and I can scrap the bounds checking code, which will free up plenty of space. I'm just worried that in a game where so many upper bounds don't get checked, the fact that they added checking code here might mean they really need it for something.
In other news, I've updated both versions of my disassembly with more labels and comments including redmagejoe's findings.
Quote from: Grimoire LD on January 29, 2020, 08:18:05 AM
Hmm... do the Mysidian Orbs actually work right? The code looks solid enough, but I recall seeing HCBailly's ancient Let's Play of Final Fantasy II and I distinctly recall that he had "A1" intelligence on one of his characters.
Ah, found it. In the video his Firion has A1 Soul after using the Orbs. https://youtu.be/XcNNK2I_NVM?list=PL31D24C1307A71BD4&t=409
It's likely that came from equipment, not the orbs, due to the bug we just recently fixed. There's a compare to 100, and if so, set to 99 set of instructions already in the Mysidian Orb code. EDIT: Yeah, he has White Robe on Firion, meaning he has 91 base Spirit, and then that robe is giving him +10 Spirit. The orbs work fine by default it looks like. It also checks both base stat and effective stat and raises them both by 10, checking and adjusting them as necessary. So if Firion had gotten the Spirit bonus, his Spirit would have been set back to 99. Of course without the bug fix, it would just become A9 once he unequipped and re-equipped the robe. :P
Ah, I see. I was unaware that it was the equipment that allowed for this breaking of parameters. Thank you for the succinct explanation.
Hey all. I just wanted to pop in and say how awesome it is that you guys are working on this project.
FFII is a pretty underappreciated game, and it has a lot of potential as a basis for hacking.
Thank you all for your hard work!
The positive feedback is enough to make me want to keep going. I feel like I'm the wrong person to tackle such a project, honestly, with how inexperienced I am compared to the big names behind other such ambitious projects. Still, it's been a learning experience, I didn't want to keep begging for someone to fix it up when I felt I could roll up my sleeves and take a crack at it, and it's really, really rewarding when I manage to figure out how the game ticks enough to fix another bug.
While this started from my own desires, everyone's support is super appreciated and makes this project all the more worthwhile to me. :)
Quote from: redmagejoe on January 29, 2020, 09:43:58 PM
The positive feedback is enough to make me want to keep going. I feel like I'm the wrong person to tackle such a project, honestly, with how inexperienced I am compared to the big names behind other such ambitious projects. Still, it's been a learning experience, I didn't want to keep begging for someone to fix it up when I felt I could roll up my sleeves and take a crack at it, and it's really, really rewarding when I manage to figure out how the game ticks enough to fix another bug.
While this started from my own desires, everyone's support is super appreciated and makes this project all the more worthwhile to me. :)
You gotta start somewhere. Just think of this as the beginning of your ROM hacking career. ASM hacking opens up a whole new realm of possibilities.
+1
There is no better way to expand your skills and grow as a person than to immerse yourself in a project that you enjoy.
Seems like the current popular vote is for giving the bonus to all 4 characters. I'll leave the poll going until all the bugs are fixed and work on that down the line.
I'm stumped regarding the Evasion/MDef delay again though. I just don't know what address is being referenced by STA ($7E),Y, but it's clearly neither of the addresses that SHOULD be holding Evasion or MDef level. Stats should be refreshed, I think, the moment that a character's stat window is opened, rather than when something forces it to update. I'll have to look into what routine is running to refresh stats when you change equipment or anything of that nature.
So watching the relevant addresses in RAM for MDef in the case I'm testing (7D7D for Firion's MDef in combat and 612C for what appears in stat window), it appears to break upon leaving combat here,
03D3C8:91 82 STA ($82),Y @ $7D7D = #$07
in what looks like combat stats are being stored before it returns to the overworld with no further breaks. So it doesn't actually store the new value in 612C until something prompts it. Strangely though, stats are not handled in this way, as they immediately write to the relevant permanent stat addresses the moment they increase. As such rather than tampering with that routine and causing potentially harmful side-effects, I should see if I can't simply insert a JSR to the routine that refreshes stats. I'll see what I can do about either putting that at the end of a battle, or having it run the moment the menu screen opens.
I think 0x0018B2 is what I want to JSR to when the menu screen opens, as it is run everytime one tries to change equipment, enters a battle (when these stats get refreshed), and appears to be what's in charge of refreshing stats. I just have to figure out where in code the menu screen opening is handled...
So I just spent the last hour or two chasing down control flow and setting breakpoints, and FINALLY found the routine that handles going to the pause menu. It's at
0x03ACD4 in the disassembly, fortuitously right below the routine that handles spell levels off of which I shaved 3 bytes (just enough for a JSR!) with the Spell Max Level Fix. I also determined precisely where to point a JSR if I want stats to be refreshed,
0x001890. Alas, likely again due to fixed bank, I cannot use JSR $9880, as it points to
0x039890 instead of
0x001890. How frustrating... Anyone can think of a workaround? Also, more specifically, the very beginning of this entire rabbit hole starts the moment the Start button is hit at
0x03C175|$0F:$C165:A9 00 LDA #$00
Another solution I'm looking at is if I could have this stat refresh apply immediately at the end of a battle, or at least where there's stat gains. I'll see if I made enough room with changes to the stat ups to fit a JSR in, or if it will even be able to point to the correct bank. I tried moving my first fix, which had some blank space inside it, around a bit, and stick a JSR inside the evasion/mdef up code. Alas, still limited by banks. I'm stumped on how or where to stick this stat refreshing function call currently, so I'm going to start looking into some of the other bugs on the list.
Quote- Investigate possible overflow on Evasion % when Agility/Evasion level and Shield equipped modifier add up.
I believe that the issue here is that the code does not take two shields into account, and it is simply setting the character's Evasion to whatever bonus the second shield gives, rather than attempting to ADC it to current evasion and then comparing the new value to 99.
; control flow target (from $9A19)
0x001A4B|$00:$9A3B:18 CLC
0x001A4C|$00:$9A3C:A5 56 LDA $56
0x001A4E|$00:$9A3E:65 6F ADC $6F
0x001A50|$00:$9A40:85 56 STA $56
0x001A52|$00:$9A42:A5 57 LDA $57
0x001A54|$00:$9A44:69 00 ADC #$00
0x001A56|$00:$9A46:85 57 STA $57
0x001A58|$00:$9A48:A0 01 LDY #$01
0x001A5A|$00:$9A4A:18 CLC
0x001A5B|$00:$9A4B:B1 44 LDA ($44),Y
0x001A5D|$00:$9A4D:71 46 ADC ($46),Y
0x001A5F|$00:$9A4F:71 48 ADC ($48),Y
0x001A61|$00:$9A51:85 58 STA $58
0x001A63|$00:$9A53:A9 00 LDA #$00
0x001A65|$00:$9A55:69 00 ADC #$00
0x001A67|$00:$9A57:85 59 STA $59
0x001A69|$00:$9A59:38 SEC
0x001A6A|$00:$9A5A:A5 56 LDA $56
0x001A6C|$00:$9A5C:E5 58 SBC $58
0x001A6E|$00:$9A5E:85 56 STA $56
0x001A70|$00:$9A60:A5 57 LDA $57
0x001A72|$00:$9A62:E5 59 SBC $59
0x001A74|$00:$9A64:85 57 STA $57
0x001A76|$00:$9A66:B0 04 BCS $9A6C
0x001A78|$00:$9A68:A9 00 LDA #$00
0x001A7A|$00:$9A6A:85 56 STA $56
; control flow target (from $9A66)
0x001A7C|$00:$9A6C:A0 2B LDY #$2B
0x001A7E|$00:$9A6E:A5 56 LDA $56
0x001A80|$00:$9A70:20 84 9B JSR $9B84 ; cap A at 99
0x001A83|$00:$9A73:91 7A STA ($7A),Y <<< Evasion is being set here, rather than added and capped
...
; cap A at 99
; control flow target (from $9949, $995E, $9A70, $9AC1)
0x001B94|$00:$9B84:C9 64 CMP #$64
0x001B96|$00:$9B86:90 02 BCC $9B8A
0x001B98|$00:$9B88:A9 63 LDA #$63
; control flow target (from $9B86)
0x001B9A|$00:$9B8A:60 RTS
I don't know if it's been mentioned in this thread, but here's a possible inclusion for a Restored-esque update to the game: more music. The Origins-exclusive extra boss themes could be added to the game, adding the extra sections to the Prelude or Chocobo theme (it desperately needs it), or perhaps even the unused music that Uematsu wrote but ultimately discarded.
https://youtu.be/Xj7a91dlI4c?t=1379
Quote from: BlazeHeatnix on January 31, 2020, 01:14:10 AM
I don't know if it's been mentioned in this thread, but here's a possible inclusion for a Restored-esque update to the game: more music. The Origins-exclusive extra boss themes could be added to the game, or perhaps even the unused music that Uematsu wrote but ultimately discarded.
https://youtu.be/Xj7a91dlI4c?t=1379
I have consulted with Leviathan Mist about the feasibility of new music, space-wise, and it's certainly not outside the realm of possibility from what he explained to me. Having said that, whether he decides to actually take a crack at it or not is completely up to him, and I don't wish to impose upon him one way or the other. Battle Scene 3 might be neat to use specifically for the Behemoth encounter in Coliseum, though I was also toying with the idea of a demake of the Dawn of Souls / Anniversary boss music to replace the current boss music, which is used only for the final boss of the game. Ideally that would be the same case for this patch. The prelude would be the most feasible, requiring only at most 80 bytes. The major limitation on our available free space is the MMC1 mapper, I think. I don't know enough about mappers though to speak with any real authority on that matter.
Like this, but only with the RP2A03: https://www.youtube.com/watch?v=2Yqtrvc71ns
I would like to get the bugs hammered out before I look into enhancements. As such I'm holding off on the Mysidian Orbs improvement (which I know what needs to be done, just not quite the how), I'm at a standstill on the Evasion/MDef delay (again, know the what, just a matter of the how), and I don't know as much as I'd like to about that code handling the shield equipping and evasion going down rather than up or staying where it is.
I've decided to start looking at the spell bugs, because abw actually did an excellent job labeling those and it was very easy to find their locations in the disassembly. I'm doing some RAM watching and testing right now to see if I can't fix Protect. The offset from $7D7A (combat stats) for bonus defense from Protect is $27, and indeed only Guy's increases. Locating where it's written to, I can see that there are no writes to other character's offsets.
Protect $7E01 Breaks
0x33478
0x3357E
0x334BF
0x33518
Breaks to writing at $7E01 (Guy/Char3's bonus defense from Protect) show that during the animation for cast-all, these 4 breaks trigger on every character "handled" by the spell. So it is not an issue of repeating, but rather adjusting the offset. My theory is that it should be reading in the target of the spell, but it is only reading in the caster's character index. I'll put up the relevant code surrounding these addresses momentarily. It cycles through the following code 1 time per character cast upon.
; control flow target (from $B3EA)
0x03341D|$0C:$B40D:A0 25 LDY #$25
0x03341F|$0C:$B40F:B1 9F LDA ($9F),Y
0x033421|$0C:$B411:85 29 STA $29
0x033423|$0C:$B413:A5 6C LDA $6C
0x033425|$0C:$B415:AA TAX
0x033426|$0C:$B416:CA DEX
0x033427|$0C:$B417:8A TXA
0x033428|$0C:$B418:85 6C STA $6C
0x03342A|$0C:$B41A:18 CLC
0x03342B|$0C:$B41B:69 64 ADC #$64
0x03342D|$0C:$B41D:85 E4 STA $E4
0x03342F|$0C:$B41F:A5 6C LDA $6C
0x033431|$0C:$B421:85 00 STA $00
0x033433|$0C:$B423:A9 07 LDA #$07
0x033435|$0C:$B425:85 02 STA $02
0x033437|$0C:$B427:A9 00 LDA #$00
0x033439|$0C:$B429:85 01 STA $01
0x03343B|$0C:$B42B:85 03 STA $03
; call to code in a different bank ($0F:$FC98)
0x03343D|$0C:$B42D:20 98 FC JSR $FC98 ; 16-bit multiplication (little endian): $00-$01 * $02-$03, product stored in $04-$07; leaves X at #$00, Y not changed
0x033440|$0C:$B430:A5 04 LDA $04
0x033442|$0C:$B432:69 D9 ADC #$D9
0x033444|$0C:$B434:85 44 STA $44
0x033446|$0C:$B436:A5 05 LDA $05
0x033448|$0C:$B438:69 85 ADC #$85
0x03344A|$0C:$B43A:85 45 STA $45
0x03344C|$0C:$B43C:A0 00 LDY #$00
0x03344E|$0C:$B43E:B1 44 LDA ($44),Y
0x033450|$0C:$B440:85 5E STA $5E
0x033452|$0C:$B442:C8 INY
0x033453|$0C:$B443:B1 44 LDA ($44),Y
0x033455|$0C:$B445:85 46 STA $46
0x033457|$0C:$B447:C8 INY
0x033458|$0C:$B448:B1 44 LDA ($44),Y
0x03345A|$0C:$B44A:85 47 STA $47
0x03345C|$0C:$B44C:C8 INY
0x03345D|$0C:$B44D:B1 44 LDA ($44),Y
0x03345F|$0C:$B44F:85 48 STA $48
0x033461|$0C:$B451:C8 INY
0x033462|$0C:$B452:B1 44 LDA ($44),Y
0x033464|$0C:$B454:85 49 STA $49
0x033466|$0C:$B456:C8 INY
0x033467|$0C:$B457:B1 44 LDA ($44),Y
0x033469|$0C:$B459:8D B1 7C STA $7CB1
0x03346C|$0C:$B45C:C8 INY
0x03346D|$0C:$B45D:B1 44 LDA ($44),Y
0x03346F|$0C:$B45F:8D B0 7C STA $7CB0
0x033472|$0C:$B462:A0 26 LDY #$26
0x033474|$0C:$B464:A2 00 LDX #$00
; control flow target (from $B46E)
0x033476|$0C:$B466:B5 46 LDA $46,X
0x033478|$0C:$B468:91 9F STA ($9F),Y <<< 1st break
0x03347A|$0C:$B46A:C8 INY
0x03347B|$0C:$B46B:E8 INX
0x03347C|$0C:$B46C:E0 04 CPX #$04
0x03347E|$0C:$B46E:D0 F6 BNE $B466
0x033480|$0C:$B470:A5 6C LDA $6C
0x033482|$0C:$B472:C9 14 CMP #$14
0x033484|$0C:$B474:B0 08 BCS $B47E
0x033486|$0C:$B476:A0 12 LDY #$12
0x033488|$0C:$B478:20 51 B5 JSR $B551
0x03348B|$0C:$B47B:4C 87 B4 JMP $B487
; control flow target (from $B474)
0x03348E|$0C:$B47E:C9 28 CMP #$28
0x033490|$0C:$B480:B0 05 BCS $B487
0x033492|$0C:$B482:A0 13 LDY #$13
0x033494|$0C:$B484:20 51 B5 JSR $B551
; control flow target (from $B40A, $B47B, $B480)
0x033497|$0C:$B487:20 76 AF JSR $AF76
0x03349A|$0C:$B48A:29 C0 AND #$C0
0x03349C|$0C:$B48C:F0 0D BEQ $B49B
0x03349E|$0C:$B48E:A5 6C LDA $6C
0x0334A0|$0C:$B490:C9 15 CMP #$15
0x0334A2|$0C:$B492:F0 07 BEQ $B49B
0x0334A4|$0C:$B494:C9 17 CMP #$17
0x0334A6|$0C:$B496:F0 03 BEQ $B49B
0x0334A8|$0C:$B498:4C 73 BE JMP $BE73
; control flow target (from $B48C, $B492, $B496)
0x0334AB|$0C:$B49B:A5 A6 LDA $A6
0x0334AD|$0C:$B49D:F0 12 BEQ $B4B1
0x0334AF|$0C:$B49F:A0 26 LDY #$26
0x0334B1|$0C:$B4A1:B1 9F LDA ($9F),Y
0x0334B3|$0C:$B4A3:85 70 STA $70
0x0334B5|$0C:$B4A5:4A LSR
0x0334B6|$0C:$B4A6:91 9F STA ($9F),Y
0x0334B8|$0C:$B4A8:C8 INY
0x0334B9|$0C:$B4A9:B1 9F LDA ($9F),Y
0x0334BB|$0C:$B4AB:85 71 STA $71
0x0334BD|$0C:$B4AD:4A LSR
0x0334BE|$0C:$B4AE:4A LSR
0x0334BF|$0C:$B4AF:91 9F STA ($9F),Y <<< 3rd break
; control flow target (from $B49D)
0x0334C1|$0C:$B4B1:20 76 AF JSR $AF76
0x0334C4|$0C:$B4B4:29 C0 AND #$C0
0x0334C6|$0C:$B4B6:F0 0B BEQ $B4C3
0x0334C8|$0C:$B4B8:A5 6C LDA $6C
0x0334CA|$0C:$B4BA:C9 15 CMP #$15
0x0334CC|$0C:$B4BC:F0 05 BEQ $B4C3
0x0334CE|$0C:$B4BE:C9 17 CMP #$17
0x0334D0|$0C:$B4C0:F0 01 BEQ $B4C3
0x0334D2|$0C:$B4C2:60 RTS
; control flow target (from $B4B6, $B4BC, $B4C0)
0x0334D3|$0C:$B4C3:A5 6C LDA $6C
0x0334D5|$0C:$B4C5:C9 14 CMP #$14
0x0334D7|$0C:$B4C7:B0 03 BCS $B4CC
0x0334D9|$0C:$B4C9:20 12 BC JSR $BC12
; control flow target (from $B4C7)
0x0334DC|$0C:$B4CC:A0 28 LDY #$28
0x0334DE|$0C:$B4CE:B1 9F LDA ($9F),Y
0x0334E0|$0C:$B4D0:A0 17 LDY #$17
0x0334E2|$0C:$B4D2:31 A1 AND ($A1),Y
0x0334E4|$0C:$B4D4:F0 03 BEQ $B4D9
0x0334E6|$0C:$B4D6:4C FB BC JMP $BCFB
; control flow target (from $B4D4)
0x0334E9|$0C:$B4D9:A5 5E LDA $5E
0x0334EB|$0C:$B4DB:0A ASL
0x0334EC|$0C:$B4DC:18 CLC
0x0334ED|$0C:$B4DD:69 8A ADC #$8A
0x0334EF|$0C:$B4DF:85 44 STA $44
0x0334F1|$0C:$B4E1:A9 00 LDA #$00
0x0334F3|$0C:$B4E3:69 BE ADC #$BE
0x0334F5|$0C:$B4E5:85 45 STA $45
0x0334F7|$0C:$B4E7:A0 00 LDY #$00
0x0334F9|$0C:$B4E9:B1 44 LDA ($44),Y
0x0334FB|$0C:$B4EB:85 46 STA $46
0x0334FD|$0C:$B4ED:C8 INY
0x0334FE|$0C:$B4EE:B1 44 LDA ($44),Y
0x033500|$0C:$B4F0:85 47 STA $47
0x033502|$0C:$B4F2:20 87 BE JSR $BE87
0x033505|$0C:$B4F5:A0 0A LDY #$0A
0x033507|$0C:$B4F7:B1 9F LDA ($9F),Y
0x033509|$0C:$B4F9:C8 INY
0x03350A|$0C:$B4FA:11 9F ORA ($9F),Y
0x03350C|$0C:$B4FC:D0 06 BNE $B504
0x03350E|$0C:$B4FE:A0 08 LDY #$08
0x033510|$0C:$B500:A9 80 LDA #$80
0x033512|$0C:$B502:91 9F STA ($9F),Y
; control flow target (from $B4FC)
0x033514|$0C:$B504:A5 71 LDA $71
0x033516|$0C:$B506:A0 27 LDY #$27
0x033518|$0C:$B508:91 9F STA ($9F),Y <<< 4th break
0x03351A|$0C:$B50A:88 DEY
0x03351B|$0C:$B50B:A5 70 LDA $70
0x03351D|$0C:$B50D:91 9F STA ($9F),Y
0x03351F|$0C:$B50F:A5 4A LDA $4A
0x033521|$0C:$B511:85 00 STA $00
0x033523|$0C:$B513:A5 4B LDA $4B
0x033525|$0C:$B515:85 01 STA $01
0x033527|$0C:$B517:A9 86 LDA #$86
0x033529|$0C:$B519:85 03 STA $03
0x03352B|$0C:$B51B:A9 9F LDA #$9F
0x03352D|$0C:$B51D:85 02 STA $02
0x03352F|$0C:$B51F:20 FC 8F JSR $8FFC
0x033532|$0C:$B522:90 08 BCC $B52C
0x033534|$0C:$B524:A9 86 LDA #$86
0x033536|$0C:$B526:85 4B STA $4B
0x033538|$0C:$B528:A9 9F LDA #$9F
0x03353A|$0C:$B52A:85 4A STA $4A
; control flow target (from $B522)
0x03353C|$0C:$B52C:A5 4A LDA $4A
0x03353E|$0C:$B52E:85 9A STA $9A
0x033540|$0C:$B530:85 AE STA $AE
0x033542|$0C:$B532:A5 4B LDA $4B
0x033544|$0C:$B534:85 9B STA $9B
0x033546|$0C:$B536:85 AF STA $AF
0x033548|$0C:$B538:A0 0A LDY #$0A
0x03354A|$0C:$B53A:B1 A1 LDA ($A1),Y
0x03354C|$0C:$B53C:C8 INY
0x03354D|$0C:$B53D:11 A1 ORA ($A1),Y
0x03354F|$0C:$B53F:D0 0D BNE $B54E
0x033551|$0C:$B541:20 76 AF JSR $AF76
0x033554|$0C:$B544:A0 2C LDY #$2C
0x033556|$0C:$B546:91 A1 STA ($A1),Y
0x033558|$0C:$B548:A0 08 LDY #$08
0x03355A|$0C:$B54A:09 80 ORA #$80
0x03355C|$0C:$B54C:91 A1 STA ($A1),Y
; control flow target (from $B53F)
0x03355E|$0C:$B54E:4C 9A BF JMP $BF9A
; control flow target (from $B478, $B484)
0x033561|$0C:$B551:B1 9F LDA ($9F),Y
0x033563|$0C:$B553:85 48 STA $48
0x033565|$0C:$B555:A0 24 LDY #$24
0x033567|$0C:$B557:38 SEC
0x033568|$0C:$B558:F1 9F SBC ($9F),Y
0x03356A|$0C:$B55A:B0 02 BCS $B55E
0x03356C|$0C:$B55C:A9 00 LDA #$00
; control flow target (from $B55A)
0x03356E|$0C:$B55E:18 CLC
0x03356F|$0C:$B55F:65 46 ADC $46
0x033571|$0C:$B561:A0 26 LDY #$26
0x033573|$0C:$B563:91 9F STA ($9F),Y
0x033575|$0C:$B565:A5 48 LDA $48
0x033577|$0C:$B567:4A LSR
0x033578|$0C:$B568:4A LSR
0x033579|$0C:$B569:18 CLC
0x03357A|$0C:$B56A:65 47 ADC $47
0x03357C|$0C:$B56C:A0 27 LDY #$27
0x03357E|$0C:$B56E:91 9F STA ($9F),Y <<< 2nd break
0x033580|$0C:$B570:60 RTS
In particular, it runs through that 4-time loop on the first break for each character, so I'm not sure what it's meant to be doing. At first I thought it was running 4 times for target all, but it does that per single target. I'm not sure why but it cycles from the bonus defense byte through the $28, the $29 (supposedly command), and the $2A (supposedly target high bit).
Quote from: BlazeHeatnix on January 31, 2020, 01:14:10 AM
I don't know if it's been mentioned in this thread, but here's a possible inclusion for a Restored-esque update to the game: more music. The Origins-exclusive extra boss themes could be added to the game, adding the extra sections to the Prelude or Chocobo theme (it desperately needs it), or perhaps even the unused music that Uematsu wrote but ultimately discarded.
https://youtu.be/Xj7a91dlI4c?t=1379
Thank you for posting this. I had completely forgotten about the unused music for this game. Now the question remains: does the music data in raw format exist online? If not, then it would be up to me to interpret it and code by ear. I would imagine it would sound a lot closer to the source material than did my FF1 tracks, as it would use the same sound engine.
Quote from: Leviathan Mist on January 31, 2020, 11:05:46 AM
Thank you for posting this. I had completely forgotten about the unused music for this game. Now the question remains: does the music data in raw format exist online? If not, then it would be up to me to interpret it and code by ear. I would imagine it would sound a lot closer to the source material than did my FF1 tracks, as it would use the same sound engine.
Unfortunately, I don't think it exists in raw format. It was only released on the soundtrack in 1988. Tracking this down to listen to the tracks in anything other than compressed Youtube quality might be difficult.
https://finalfantasy.fandom.com/wiki/Original_soundtracks_of_Final_Fantasy_I_%26_II#All_Sounds_of_Final_Fantasy_I_.26_II
Even if these are transcribed...what would you do with them? These were cut out for creative reasons, not technical ones. Also, where would you put the Dungeon theme seeing as there already is a dungeon theme in the game? Would it go in some dungeons but not others? And I assume Battle Scene 3 would be reserved for the final boss.
As I stated in my previous post, I feel like the current boss theme is the most appropriate for the final boss, especially considering how it was used in remakes. I wouldn't honestly use any of the others besides perhaps Battle Scene 3, which I'd feel would fit appropriately as a one-time use for the Behemoth fight in Coliseum. Were it feasible to fit a demake of Battle Scene A in the ROM, I'd like to use that for bosses, and use the current boss theme exclusively for the final boss.
Quote from: redmagejoe on January 31, 2020, 03:11:32 PM
As I stated in my previous post, I feel like the current boss theme is the most appropriate for the final boss, especially considering how it was used in remakes. I wouldn't honestly use any of the others besides perhaps Battle Scene 3, which I'd feel would fit appropriately as a one-time use for the Behemoth fight in Coliseum. Were it feasible to fit a demake of Battle Scene A in the ROM, I'd like to use that for bosses, and use the current boss theme exclusively for the final boss.
I personally have an issue with using any tracks not originally composed by Uematsu, as they sound sorely out of place in Origins. As Battle Scene 2 has become synonymous with the Emperor over the years, I sort of understand wanting to use it for the final boss, but I still think Battle Scene 3 is too sinister and too long to be used in only one throwaway fight. I would recommend using it for the very first fight against Mateus as a human, as it's a way to fool the player into thinking "This is it! The final boss!" before the actual final boss happens. (Personally I'd use Scene 2 for the human fight and Scene 3 for the final boss since I don't think Scene 2 sounds like a proper final boss theme, but I get how that'd be alienating to some people so I won't argue for it.)
It might sound like a waste of time, a lot of work, and something you understandably don't want to think about before all the bugs are fixed, but it would make sense to me to offer players the choice of whether they want the original soundtrack, the unused soundtrack, an Origins-style soundtrack, or a custom soundtrack chosen by you, RMJ, which would be the default, and all except the original would have an improved Prelude and Chocobo theme.
It comes down to feasibility of inclusion more than anything. My default is to simply have none of the battle music changed at this moment, because until the code is cleaned up and we start looking at space, the only thing I'd like to see done is the Prelude given its 80-byte treatment. If I were to explore optional patch setups, I would do so only after I saw what could and could not fit into the space available, and that only if Leviathan Mist were willing to put the time and energy into those tracks in the first place.
There's a lot to be said for the creative reasons behind not using certain tracks, so whether Uematsu made Battle Scene 3 or didn't make Battle Scene A should have just as much impact on the final decision about what setups to use as the very valid argument that changing the final boss theme could be alienating to people who played the original and associate that theme with the final boss more than any of the other bosses in the game.
When all is said and done, I'll worry about what battles use what songs in what optional patch setups if and when the two tracks in question even exist, let alone fit in 256kb. At the end of the day, I'm not going to make a unilateral choice or take creative liberties over what should or shouldn't be changed outside of bug fixes. I didn't like that Astral Esper did that with the Peninsula of Power and shop lists in Restored (that's why I made the Final Fantasy Restored Debalance patch!), and I'm not about to do that here. The Mysidian Orbs change, for instance, will likely be a separate IPS included as an optional addition.
Quote from: BlazeHeatnix on January 31, 2020, 03:43:59 PM
I personally have an issue with using any tracks not originally composed by Uematsu, as they sound sorely out of place in Origins. As Battle Scene 2 has become synonymous with the Emperor over the years, I sort of understand wanting to use it for the final boss, but I still think Battle Scene 3 is too sinister and too long to be used in only one throwaway fight. I would recommend using it for the very first fight against Mateus as a human, as it's a way to fool the player into thinking "This is it! The final boss!" before the actual final boss happens. (Personally I'd use Scene 2 for the human fight and Scene 3 for the final boss since I don't think Scene 2 sounds like a proper final boss theme, but I get how that'd be alienating to some people so I won't argue for it.)
It might sound like a waste of time, a lot of work, and something you understandably don't want to think about before all the bugs are fixed, but it would make sense to me to offer players the choice of whether they want the original soundtrack, the unused soundtrack, an Origins-style soundtrack, or a custom soundtrack chosen by you, RMJ, which would be the default, and all except the original would have an improved Prelude and Chocobo theme.
I have read your review of Final Fantasy Restored, and I appreciate your honest input about the new tracks. I originally created them as a proof-of-concept while playtesting AstralEsper's other hack, Final Fantasy Rebalanced. Since there were no true 8-bit renditions of the tracks (not counting the Wonderswan Color versions), I had to take some liberties for some of the tunes, and add my own personal flair to some of them to prevent them from sounding stale. I do agree that the music patches should be optional though.
As for FF2, I'm waiting for more space to be freed up, and a confirmation that the space can be properly utilized for music, before I dig into the FF2 sound engine. I will have to analyze it myself to see if I can make sense of it, hoping it's not too different from the way FF1's sound engine was written. If that part goes smoothly, I will start my first test track, and then post a NSF file for feedback. Maybe you could post a poll asking which new track to work on first, when the time comes.
Also take into account that FF2j has a completely different distribution of the boss theme than the remakes. The boss theme is used for the first time against Behemoth, and then here and there including the final boss and EXCLUDING the Human Mateus fight.
The remakes use only Battle Scene B as the only boss theme at first, then Battle Scene A is used for the first time against Lamia Queen, then it depends on the boss (the very next boss, Behemoth, uses type B)
I just took a quick look at the FF2 NSF file. Looks like a completely different system. Which means it would take some time to decode everything. I wouldn't hold my breath on a timely release of any ported songs.
Quote from: Leviathan Mist on February 01, 2020, 09:32:24 AM
I just took a quick look at the FF2 NSF file. Looks like a completely different system. Which means it would take some time to decode everything. I wouldn't hold my breath on a timely release of any ported songs.
(http://i.imgur.com/bQsKDwJ.gif)
Just an update that I've had my hands full with work, but I'll be coming back to this soon. I'd kind of like to get abw's insight on all the new information as well before I charge ahead, as he seems to have a better grasp of what's happening than I do in code, and I have a tendency to trial-and-error stumble my way through things. :P
Based on the poll, I've decided to make an optional patch that gives all 4 characters the stat boosts. I just need to find the space for an LD*, an IN*, a CMP/CPX/CPY, and a BNE. So about 7-8 bytes.
; handler for bottom left orb, Fire Crystal
; indirect control flow target (via $99A5)
0x03A1BF|$0E:$A1AF:A9 10 LDA #$10 ; Stat offset for base Strength
0x03A1C1|$0E:$A1B1:D0 0A BNE $A1BD
; handler for bottom right orb, Wind Crystal
; indirect control flow target (via $99A7)
0x03A1C3|$0E:$A1B3:A9 11 LDA #$11 ; Stat offset for base Agility
0x03A1C5|$0E:$A1B5:D0 06 BNE $A1BD
; handler for top left orb, Earth Crystal
; indirect control flow target (via $99A9)
0x03A1C7|$0E:$A1B7:A9 14 LDA #$14 ; Stat offset for base Spirit
0x03A1C9|$0E:$A1B9:D0 02 BNE $A1BD
; handler for top right orb, Water Crystal
; indirect control flow target (via $99AB)
0x03A1CB|$0E:$A1BB:A9 13 LDA #$13 ; Stat offset for base Intellect
; control flow target (from $A1B1, $A1B5, $A1B9)
0x03A1CD|$0E:$A1BD:85 80 STA $80 ; store offset in $80 based on which orb and thus stat is being increased
0x03A1CF|$0E:$A1BF:A5 F0 LDA $F0 ; load A with whatever's in $F0 (RNG output to determine character?)
0x03A1D1|$0E:$A1C1:29 C0 AND #$C0 ; bitwise AND against $F0 (???? ????) with #$C0 (1100 0000)
0x03A1D3|$0E:$A1C3:05 80 ORA $80 ; bitwise OR with resultant A value from AND above against $80 (stat offset $10, $11, $13, or $14)
0x03A1D5|$0E:$A1C5:AA TAX ; store this A value in X to be used as the determined offset for which character gets the bonus
0x03A1D6|$0E:$A1C6:BD 00 61 LDA $6100,X ; Character #1 ID/portrait/sprite (00: Firion, 01: Maria, 02: Guy, 03: Minwu, 04: Josef, 05: Gordon, 06: Leila, 07: Ricard, 08: Leon); load A with baseline for character stats, X as offset
0x03A1D9|$0E:$A1C9:18 CLC
0x03A1DA|$0E:$A1CA:69 0A ADC #$0A ; adds 10 to the appropriate stat
0x03A1DC|$0E:$A1CC:C9 64 CMP #$64 ; prevent stat from going above 99, compare to 100
0x03A1DE|$0E:$A1CE:90 02 BCC $A1D2 ; does not exceed cap, proceed to store new stat value
0x03A1E0|$0E:$A1D0:A9 63 LDA #$63 ; set stat to 99 if >99
; control flow target (from $A1CE)
0x03A1E2|$0E:$A1D2:9D 00 61 STA $6100,X ; Character #1 ID/portrait/sprite (00: Firion, 01: Maria, 02: Guy, 03: Minwu, 04: Josef, 05: Gordon, 06: Leila, 07: Ricard, 08: Leon); store new value of stat, baseline for character stats, X as offset
0x03A1E5|$0E:$A1D5:BD 10 61 LDA $6110,X ; Character #1 Base Strength; base character stats that display in stat window (base + equipment bonuses), X as offset
0x03A1E8|$0E:$A1D8:18 CLC
0x03A1E9|$0E:$A1D9:69 0A ADC #$0A ; add 10 to window stats as well
0x03A1EB|$0E:$A1DB:C9 64 CMP #$64 ; prevent stat from going above 99, compare to 100
0x03A1ED|$0E:$A1DD:90 02 BCC $A1E1 ; does not exceed cap, proceed to store new stat value
0x03A1EF|$0E:$A1DF:A9 63 LDA #$63 ; set stat to 99 if >99
; control flow target (from $A1DD)
0x03A1F1|$0E:$A1E1:9D 10 61 STA $6110,X ; Character #1 Base Strength; store new value of stat, baseline for character's window stats, X as offset
0x03A1F4|$0E:$A1E4:A4 A0 LDY $A0
0x03A1F6|$0E:$A1E6:20 07 99 JSR $9907
0x03A1F9|$0E:$A1E9:AD 00 7B LDA $7B00
0x03A1FC|$0E:$A1EC:60 RTS
; handler for bottom left orb, Fire Crystal
; indirect control flow target (via $99A5)
0x03A1BF|$0E:$A1AF:A9 10 LDA #$10 ; Stat offset for base Strength
0x03A1C1|$0E:$A1B1:D0 0A BNE $A1BD
; handler for bottom right orb, Wind Crystal
; indirect control flow target (via $99A7)
0x03A1C3|$0E:$A1B3:A9 11 LDA #$11 ; Stat offset for base Agility
0x03A1C5|$0E:$A1B5:D0 06 BNE $A1BD
; handler for top left orb, Earth Crystal
; indirect control flow target (via $99A9)
0x03A1C7|$0E:$A1B7:A9 14 LDA #$14 ; Stat offset for base Spirit
0x03A1C9|$0E:$A1B9:D0 02 BNE $A1BD
; handler for top right orb, Water Crystal
; indirect control flow target (via $99AB)
0x03A1CB|$0E:$A1BB:A9 13 LDA #$13 ; Stat offset for base Intellect
; control flow target (from $A1B1, $A1B5, $A1B9)
0x03A1CD|$0E:$A1BD:AA TAX <<<
0x03A1CE|$0E:$A1BE:BD 00 61 LDA $6100,X ; Character #1 ID/portrait/sprite (00: Firion, 01: Maria, 02: Guy, 03: Minwu, 04: Josef, 05: Gordon, 06: Leila, 07: Ricard, 08: Leon); load A with baseline for character stats, X as offset
0x03A1D1|$0E:$A1C1:18 CLC
0x03A1D2|$0E:$A1C2:69 0A ADC #$0A ; adds 10 to the appropriate stat
0x03A1D4|$0E:$A1C4:C9 64 CMP #$64 ; prevent stat from going above 99, compare to 100
0x03A1D6|$0E:$A1C6:90 02 BCC $A1CA ; does not exceed cap, proceed to store new stat value
0x03A1D8|$0E:$A1C8:A9 63 LDA #$63 ; set stat to 99 if >99
0x03A1DA|$0E:$A1CA:9D 00 61 STA $6100,X ; Character #1 ID/portrait/sprite (00: Firion, 01: Maria, 02: Guy, 03: Minwu, 04: Josef, 05: Gordon, 06: Leila, 07: Ricard, 08: Leon); store new value of stat, baseline for character stats, X as offset
0x03A1DD|$0E:$A1CD:BD 10 61 LDA $6110,X ; Character #1 Base Strength; base character stats that display in stat window (base + equipment bonuses), X as offset
0x03A1E0|$0E:$A1D0:18 CLC
0x03A1E1|$0E:$A1D1:69 0A ADC #$0A ; add 10 to window stats as well
0x03A1E3|$0E:$A1D3:C9 64 CMP #$64 ; prevent stat from going above 99, compare to 100
0x03A1E5|$0E:$A1D5:90 02 BCC $A1D9 ; does not exceed cap, proceed to store new stat value
0x03A1E7|$0E:$A1D7:A9 63 LDA #$63 ; set stat to 99 if >99
0x03A1E9|$0E:$A1D9:9D 10 61 STA $6110,X ; Character #1 Base Strength; store new value of stat, baseline for character's window stats, X as offset
0x03A1EC|$0E:$A1DC:8A TXA <<<
0x03A1ED|$0E:$A1DD:18 CLC <<<
0x03A1EE|$0E:$A1DE:69 40 ADC #$40 <<<
0x03A1F0|$0E:$A1E0:90 DB BCC $A1BD <<<
0x03A1F2|$0E:$A1E2:A4 A0 LDY $A0
0x03A1F4|$0E:$A1E4:20 07 99 JSR $9907
0x03A1F7|$0E:$A1E7:AD 00 7B LDA $7B00
0x03A1FA|$0E:$A1EA:60 RTS
0x03A1FB|$0E:$A1EB:EA NOP
0x03A1FC|$0E:$A1EC:EA NOP
I need 3 more bytes to make this mock-up work. I had thought of not using LDY and INY originally, but I couldn't think of a consistent way to handle a CMP when the offset can vary between 0 and 4 (for the stat), so I decided to use Y as a counter. If anyone's more clever than me about how to use CMP to compare it against D0, D1, D3, or D4, I'm all ears (00 + 10/11/13/14 + 40x3 to cycle through the 4 characters). Nevermind, I figured it out. Since I have it doing the stat adjustment first, ADC for character offset last before the comparison, I should be able to make it work. 10-14, increase Firion's stat, increase offset to 50-54, compare, branch up to increase Maria's stat, increase offset to 90-94, compare, branch up to increase Guy's stat, increase offset to D0-D4, compare, branch up to increase Char4's stat, increase offset to... well, 0114, but that should set the carry flag... compare, break out of loop. So I should be able to do CMP #$D5 with a BCC so that after the 4th character, it will increment above D4 and break out of the loop. Unless the CMP breaks the carry flag being set, which may cause problems.
Could I remove the CMP altogether and achieve the same result? CLC is run before the ADC, so unless it's above FF, it should always branch, right? In the case where it increments from D0-D4 up to >FF, it should set the Carry Flag, and thus not branch, correct? Adjusted mock-up accordingly, as I'm pretty sure that should work.
Quote from: Leviathan Mist on February 01, 2020, 09:32:24 AM
I just took a quick look at the FF2 NSF file. Looks like a completely different system. Which means it would take some time to decode everything. I wouldn't hold my breath on a timely release of any ported songs.
I'm surprised you're surprised of this. You can hear it right away. FF1's sound engine is very limited, even compared to other 1987 NES games, while FF2 is much more featured. However FF3 sound engine sounds like an upgrade of FF2's. And other games such as Minna no Taabou no Nakayoshi Dai Sakusen, sounds the same as FF2 or FF3. I didn't look at any line of code to assert such a thing, though.
For the music my opinion is to not touch it, unless it is a bugfix. The remixes used in Danws of Souls are not by Uematsu. But also, it's part of history of the series that the Prelude and Chocobo theme were originally composed simpler as their later versions, and were expanded in FF4 and FF3 respectively. FF1 and FF2 not having boss themes (except for some bosses in FF2) is also part of the series.
Could use someone with a save state in the Ultima room to playtest the patch I may put up soon to ensure that it grants the bonus to all 4 characters, and doesn't break anything. Sadly, I don't have anything remotely close to that point (right after in fact is the earliest I have), and I'd really rather not try to play through the game all the way up to that point again...
Quote from: redmagejoe on February 02, 2020, 05:15:53 PM
Could use someone with a save state in the Ultima room to playtest the patch I may put up soon to ensure that it grants the bonus to all 4 characters, and doesn't break anything. Sadly, I don't have anything remotely close to that point (right after in fact is the earliest I have), and I'd really rather not try to play through the game all the way up to that point again...
I would probably have one for you by now if you hadn't told me to hold off on my playthrough :P
I'd hate to have you streaming up to a point where it breaks though, unless you're okay with it being a proof-of-concept / playtesting stream. I'd feel awful if people were watching you and you got to the orbs, and then the game just crashed. :x
Having said that though, if you're not averse to playtesting, I think the patches that are up now are a pretty good start if you'd like to give it a whirl! First post has been updated with the "WIP" patch. All other patches should be good to go, but playtesting is always nice. If you are willing to collect Save States, I could use one for:
- Before opening Kashuan Keep with the Goddess's Bell.
- Before collecting the Sunfire from Kashuan Keep with Egil's Torch in hand.
- Before stepping onto the Dreadnought and showing the Pass to the guard.
- Before throwing the Sunfire into the Dreadnought's core.
- Before placing White Mask on the Goddess Statue.
- Before placing Black Mask on the frozen Doppelganger.
- Before touching any of the orbs at the top of the Mysidian Tower.
- Before speaking to Elina in Deist Castle one last time before she and her son vanish.
I looked through the disassembly and found at least one other instance where a BCC is used directly after an ADC, so I think I've got the right idea at least.
Upon further investigation, it seems like Protect at different levels and which target is chosen is completely buggy. Level 8 Protect (which should be 99/4 = 24 x # of successes based on spell level) is giving Guy 0 defense when cast on himself, and 24 ($18) to himself when cast on party. Spells may become more complicated than I'd initially thought.
Also it seems I was incorrect about that ADC #$14 being Ripper. Just adjusted the value to 00 and still displays # of hits x 20 over what Firion actually takes when he hits himself with Ripper. Going to have to think more critically about how to find this bug.
; control flow target (from $AF9A)
0x032FCD|$0C:$AFBD:A0 18 LDY #$18
0x032FCF|$0C:$AFBF:B1 9F LDA ($9F),Y
0x032FD1|$0C:$AFC1:A0 07 LDY #$07
0x032FD3|$0C:$AFC3:18 CLC
0x032FD4|$0C:$AFC4:71 9F ADC ($9F),Y
0x032FD6|$0C:$AFC6:85 46 STA $46
0x032FD8|$0C:$AFC8:A9 00 LDA #$00
0x032FDA|$0C:$AFCA:69 00 ADC #$00
0x032FDC|$0C:$AFCC:85 47 STA $47
0x032FDE|$0C:$AFCE:38 SEC
0x032FDF|$0C:$AFCF:A5 46 LDA $46
0x032FE1|$0C:$AFD1:E9 14 SBC #$14 <<< -20 from # of hits counter... why?
0x032FE3|$0C:$AFD3:85 46 STA $46
0x032FE5|$0C:$AFD5:A5 47 LDA $47
0x032FE7|$0C:$AFD7:E9 00 SBC #$00
0x032FE9|$0C:$AFD9:85 47 STA $47
0x032FEB|$0C:$AFDB:B0 07 BCS $AFE4
0x032FED|$0C:$AFDD:A2 01 LDX #$01
0x032FEF|$0C:$AFDF:86 46 STX $46
0x032FF1|$0C:$AFE1:CA DEX
0x032FF2|$0C:$AFE2:86 47 STX $47
While poking around I did find that setting that $14 to 0 made characters hit 36 times instead of 16. No idea what math is going on there that such a specific subtraction is necessary, but this routine clearly has something to do with number of hits.
EDIT: I think I'm on the right track. Reloading the same state over and over where damage is already determined, and tracking down the hex values for the two damage bytes. I have found where there is an addition of Ripper's damage being added, and my theory is that it's using two different values for actual damage calculations and display damage.
; control flow target (from $B177, $B227)
0x033341|$0C:$B331:18 CLC
0x033342|$0C:$B332:A5 9A LDA $9A
0x033344|$0C:$B334:65 04 ADC $04
0x033346|$0C:$B336:85 9A STA $9A
0x033348|$0C:$B338:A5 9B LDA $9B
0x03334A|$0C:$B33A:65 05 ADC $05
0x03334C|$0C:$B33C:85 9B STA $9B
0x03334E|$0C:$B33E:60 RTS
Right here is where the adjustment is made. Firion hits 15x for 2381 (but it's actually 2081, 300 / 15 = 20 per hit of Ripper) which is $4D $09 as opposed to $21 $08. 300 is $2C $01.
033342:A5 9A LDA $009A = #$21
033344:65 04 ADC $0004 = #$2C
033346:85 9A STA $009A = #$4D
033348:A5 9B LDA $009B = #$08
03334A:65 05 ADC $0005 = #$01
03334C:85 9B STA $009B = #$09
As you can see, the discrepancy happens here. It may be that by the time this is stored, the game has already calculated the damage to deal, while this number is now only being used for display. $04 and $05 are used for a lot of ROR through the RNG routine, so I couldn't very well step through until I found when Ripper was using these values, but the issue appears to be that Ripper's damage is calculated too late to be used for actual damage. The first time $9A and $9B, our damage numbers, are read or written to is likely when the actual damage calculation is done.
; control flow target (from $B0A0)
0x0330BF|$0C:$B0AF:A0 02 LDY #$02
0x0330C1|$0C:$B0B1:B1 A1 LDA ($A1),Y
0x0330C3|$0C:$B0B3:85 46 STA $46
0x0330C5|$0C:$B0B5:A9 00 LDA #$00
0x0330C7|$0C:$B0B7:85 47 STA $47
0x0330C9|$0C:$B0B9:A5 9C LDA $9C
0x0330CB|$0C:$B0BB:85 48 STA $48
0x0330CD|$0C:$B0BD:20 88 BC JSR $BC88
0x0330D0|$0C:$B0C0:A5 4A LDA $4A
0x0330D2|$0C:$B0C2:85 9A STA $9A
0x0330D4|$0C:$B0C4:A5 4B LDA $4B
0x0330D6|$0C:$B0C6:85 9B STA $9B
0x0330D8|$0C:$B0C8:A5 9B LDA $9B
0x0330DA|$0C:$B0CA:29 80 AND #$80
0x0330DC|$0C:$B0CC:F0 06 BEQ $B0D4
0x0330DE|$0C:$B0CE:A9 00 LDA #$00
0x0330E0|$0C:$B0D0:85 9A STA $9A
0x0330E2|$0C:$B0D2:85 9B STA $9B
So Ripper's damage needs to be called upon before this final STA $9A and STA $9B to actually apply the damage. After chasing the control flow backwards from where Ripper damage IS added in, I found this little routine which I do believe is the Ripper routine.
; indirect control flow target (via $B129)
0x033224|$0C:$B214:A5 9C LDA $9C
0x033226|$0C:$B216:85 00 STA $00
0x033228|$0C:$B218:A9 00 LDA #$00
0x03322A|$0C:$B21A:85 01 STA $01
0x03322C|$0C:$B21C:A9 14 LDA #$14
0x03322E|$0C:$B21E:85 02 STA $02
0x033230|$0C:$B220:A9 00 LDA #$00
0x033232|$0C:$B222:85 03 STA $03
; call to code in a different bank ($0F:$FC98)
0x033234|$0C:$B224:20 98 FC JSR $FC98 ; 16-bit multiplication (little endian): $00-$01 * $02-$03, product stored in $04-$07; leaves X at #$00, Y not changed
0x033237|$0C:$B227:4C 31 B3 JMP $B331
This flows directly into that final write into $9A and $9B, after which damage has already been locked in. So it's a matter of changing control flow somewhere, to have this routine called upon earlier, or perhaps have wherever damage is finalized called on later.
QuoteI'd really rather not try to play through the game all the way up to that point again
Make a movie of your walkthrough in fceux, and also record it on video. Then you can check with that video and start playing the movie from necessary frame.
Disassembly updated (re-assemblable version is currently dead until I get around to finding out what ca65 is complaining about) with more labels and comments, including finding the weapon/armor and music data and correcting some parts of the RAM map. There's an extra byte in the weapon/armor data that somebody more familiar with game mechanics might recognize: the 3rd byte of all your armor data and the 5th byte of all your weapon data get summed and capped at #$FF, and this value gets copied to both in-battle (offset #$24) and out-of-battle (offset #$2F) stats but isn't displayed anywhere. I see it gets subtracted from your Intellect/Spirit spell stat when casting in battle, but haven't traced it any further than that.
Quote from: redmagejoe on January 26, 2020, 09:56:47 PM
The return of... the Evasion/Magic Defense Level Up Delay! ::)
($7E),Y refers to the character's Evasion/Magic Resist SKILL level/experience, which is different from the character's Evasion/Magic Resist attempts/success % STAT in ($7A). So it looks like the skill is levelling correctly, but then the skill increase doesn't get applied to the stat until the code you found earlier at $00:$9A7B runs and sets the attempts stat to the skill level +1.
Looking more closely at the Evasion/Magic Resist level up routine, however, it appears to share the same underflow bug that affects levelling up weapon skills; I haven't tested it, but it looks like if your Evasion/Magic Resist is level 10+, being attacked once in a rank 1 battle ought to cause the SBC ($7E),Y to underflow, resulting in SBC #$0A not clearing C and you gaining enough experience to increase your level.
Quote from: Leviathan Mist on February 01, 2020, 09:32:24 AM
I just took a quick look at the FF2 NSF file. Looks like a completely different system. Which means it would take some time to decode everything. I wouldn't hold my breath on a timely release of any ported songs.
It looks like BGM stuff starts around $0D:$9800, with the pointers to music data starting at $0D:$9E0D. Battle SFX stuff appears to start at $05:$BA00.
Quote from: Bregalad on February 02, 2020, 04:13:11 PM
I'm surprised you're surprised of this. You can hear it right away. FF1's sound engine is very limited, even compared to other 1987 NES games, while FF2 is much more featured. However FF3 sound engine sounds like an upgrade of FF2's. And other games such as Minna no Taabou no Nakayoshi Dai Sakusen, sounds the same as FF2 or FF3. I didn't look at any line of code to assert such a thing, though.
For the music my opinion is to not touch it, unless it is a bugfix. The remixes used in Danws of Souls are not by Uematsu. But also, it's part of history of the series that the Prelude and Chocobo theme were originally composed simpler as their later versions, and were expanded in FF4 and FF3 respectively. FF1 and FF2 not having boss themes (except for some bosses in FF2) is also part of the series.
It'd be an optional patch anyway. It's not like you have to use it and the bugfixes at the same time.
Quote from: abw on February 02, 2020, 11:42:10 PM
Disassembly updated (re-assemblable version is currently dead until I get around to finding out what ca65 is complaining about) with more labels and comments, including finding the weapon/armor and music data and correcting some parts of the RAM map. There's an extra byte in the weapon/armor data that somebody more familiar with game mechanics might recognize: the 3rd byte of all your armor data and the 5th byte of all your weapon data get summed and capped at #$FF, and this value gets copied to both in-battle (offset #$24) and out-of-battle (offset #$2F) stats but isn't displayed anywhere. I see it gets subtracted from your Intellect/Spirit spell stat when casting in battle, but haven't traced it any further than that.
I guess this is the total malus value from heavy armors against your spell's power. Certain weapons and heavy armors make you less effective as a caster, encouraging magic main characters to be almost naked or wear light armor and sit in the back row.
Quote from: abw on February 02, 2020, 11:42:10 PM
($7E),Y refers to the character's Evasion/Magic Resist SKILL level/experience, which is different from the character's Evasion/Magic Resist attempts/success % STAT in ($7A). So it looks like the skill is levelling correctly, but then the skill increase doesn't get applied to the stat until the code you found earlier at $00:$9A7B runs and sets the attempts stat to the skill level +1.
Watching RAM at the time it runs the STA ($7E),Y in the state where Firion is about to finish a battle where he gains a MDef level, I see that it's actually setting Firion's $6232 (rather than $612C, wtf?) from 06 to 07. So it's actually pushing $6232 + 1 into $621C later? I feel like this could still be resolved by finding a way to force a total stat refresh at the end of a battle or when going to the pause menu or something.
Quote from: abw on February 02, 2020, 11:42:10 PM
Looking more closely at the Evasion/Magic Resist level up routine, however, it appears to share the same underflow bug that affects levelling up weapon skills; I haven't tested it, but it looks like if your Evasion/Magic Resist is level 10+, being attacked once in a rank 1 battle ought to cause the SBC ($7E),Y to underflow, resulting in SBC #$0A not clearing C and you gaining enough experience to increase your level.
I will have to think about how to address this, then. Also abw, I'm hoping you could be a second set of eyes to see if my orbs change is airtight. And chasing around the Ripper control flow gave me a headache, as I can't actually figure out when its bonus damage is being loaded into $04 and $05, but it's clearly not early enough for the game to use it for real damage numbers rather than just display purposes. Sorry about the bad info on that ADC #$14, I was grasping at straws.
0x01685F|$05:$A84F:F1 7E SBC ($7E),Y ; Evasion/Magic Resist skill level
0x016861|$05:$A851:E9 0A SBC #$0A ; your first 10 points don't count for anything
0x016863|$05:$A853:90 17 BCC $A86C ; not enough pain => no gain
Could I simply use a solution similar to what we used for weapon levels and spell levels? Put a BCC under the first SBC, and then set the 2nd SBC up 1 (#$0B) to act as a CLC into SBC #$0A? Of course would have to make 2 bytes of room for the new BCC but, would that work? I really should try to find a way to stick TAX and INX into the $A86D subroutine, since all 3 calls to it in the entire code go into the JSR with TAX and INX. I could free up 2 bytes in 3 places, but here at least, I'd need those 2 bytes for the TAX and INX in the subroutine for my BCC (the cap at 15 sub is right below the evasion code).
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
Actually, in all 3 calls to $A86D it has these exact same instructions. I think I may rewrite that subroutine and open up / shift a lot of space in those 3 routines and push it into a separate IPS or combine it with the Weapon and Spell fixes because of its dependency. I'll play around with it. That would free up 9 bytes per routine that calls to $A86D for a net gain of 18 bytes.
EDIT: This will likely free up more than 18 bytes, and would also essentially merge the Weapon Max Level fix, Spell Max Level fix, and Max Stat Increase fix as they're in the same addresses. Assuming I fix all branches and account for all pointers, however, this will free up a good chunk of space to be used for other subroutines we may need, especially since there's a number of rogue NOPs from my early fixes where I was trying to avoid moving things around. I feel confident enough to consolidate now, but I'm going to be working on it for a bit and will post it for review. At some point it may benefit abw if I were to provide him with my full stable IPS for comparison to his current disassembly so we're operating with the same addresses and open space.
Quote from: redmagejoe on February 03, 2020, 04:09:59 AM
I guess this is the total malus value from heavy armors against your spell's power. Certain weapons and heavy armors make you less effective as a caster, encouraging magic main characters to be almost naked or wear light armor and sit in the back row.
Yeah, I figured it was probably something like that, but I didn't feel like tracking it down myself :P.
Quote from: redmagejoe on February 03, 2020, 04:09:59 AM
Watching RAM at the time it runs the STA ($7E),Y in the state where Firion is about to finish a battle where he gains a MDef level, I see that it's actually setting Firion's $6232 (rather than $612C, wtf?) from 06 to 07. So it's actually pushing $6232 + 1 into $621C later? I feel like this could still be resolved by finding a way to force a total stat refresh at the end of a battle or when going to the pause menu or something.
I gave this one a quick test and confirmed that Evasion does underflow and cause you to gain a level. Given the lack of post-battle message and the delay in your stat being updated from the skill increase, it would have been much harder to notice this bug than the weapon underflow bug. For the fix, I'd probably adjust the value of Y to match the stat offset and then INC ($7A),Y when the skill level increases.
Quote from: redmagejoe on February 03, 2020, 04:09:59 AM
Also abw, I'm hoping you could be a second set of eyes to see if my orbs change is airtight.
The Orb fix looks fine to me. If you really wanted to, you could extract
CLC
ADC #$0A ; adds 10 to the appropriate stat
CMP #$64 ; prevent stat from going above 99, compare to 100
BCC $A1CA ; does not exceed cap, proceed to store new stat value
LDA #$63 ; set stat to 99 if >99
into a separate function and JSR to it, which would free up another 2 bytes.
Quote from: redmagejoe on February 03, 2020, 04:09:59 AM
And chasing around the Ripper control flow gave me a headache, as I can't actually figure out when its bonus damage is being loaded into $04 and $05, but it's clearly not early enough for the game to use it for real damage numbers rather than just display purposes. Sorry about the bad info on that ADC #$14, I was grasping at straws.
For the Ripper bug, here's a longer straw: the 8th byte of weapon data looks kind of like a bit field, the Ripper is the only weapon that sets bit 4, and if I set bit 4 on Maria's starting bow, the game says she does 20+ damage against creatures with < 10 HP, but they don't die. Try changing $0C:$8184 from 10 to 00 and see what the Ripper does. That ought to make the displayed value match the actual damage, but in this case probably the right fix is to make the damage match the displayed value.
As another point of interest, assuming that 8th byte is some kind of racial/elemental/status/etc. attack modifier, I notice the Blood Sword sets bit 2, which might be what causes the extra damage against the Emperor, and that the Poison Axe is the only other weapon that also sets bit 2; if you have a save handy, want to try attacking the Emperor with a Poison Axe and see if you get any bonus damage?
Quote from: redmagejoe on February 03, 2020, 04:09:59 AM
Actually, in all 3 calls to $A86D it has these exact same instructions. I think I may rewrite that subroutine and open up / shift a lot of space in those 3 routines
Given the volume of fixes we're making to the level up routines, I'm also thinking it's becoming worthwhile to just take the entire section from $05:$A47C to $05:$A991 and optimize it to free up space for bugfixes.
Yep, I'm in the painful process of doing that as we speak. I'll be rolling our 3 aforementioned fixes into a single patch, as well as any change to the evasion/MDef underflow. and I'm also seeing better ways to approach some of our fixes now that I have a better understanding of what the instructions are doing. I'm looking at a net gain of possibly ~30 bytes of space we can use for new subroutines if needed. It will be much cleaner overall.
Also would the underflow fix as I suggested work, using the same sort of fix you used to avoid underflow on weapons, which I also applied to spell levels? Having an extra branch after the first SBC?
Once I finish this time-consuming code-shifting/pointer-adjusting/optimizing/consolidating project, I'll take a look at the other suggestions. I didn't realize Blood Sword did extra damage to the Emperor, I was only aware that it did # of hits x 1/16 of target's Max HP, ensuring that 16 hits 1-shots anything. I'll look into all that later. I'd like to send you my stable version IPS soon so we're working on the same base, since these independent IPS files won't be isolated in the final product. I've only got them that way for the purposes of recovering if I break something and forget how to undo it.
@abw: Did you change this following chunk for either your HP/MP cap fix or the weapon level up fix? I have a ROM with only the HP/MP, weapon levels, spell levels, and max stat increase fixes applied, but I don't recall touching this section, and I don't even know what it does. It's outside the range I'm shifting up though, so I'm not that concerned, but I want to make sure I know which patch it came from when I bundle them together.
Original
; set or clear C based on 16-bit little endian subtraction: $00-$01 - $02-$03
; control flow target (from $A814)
0x0169C6|$05:$A9B6:38 SEC
0x0169C7|$05:$A9B7:A5 00 LDA $00
0x0169C9|$05:$A9B9:E5 02 SBC $02
0x0169CB|$05:$A9BB:85 04 STA $04 ; completely pointless op, unnecessary and calling code doesn't care about this
0x0169CD|$05:$A9BD:A5 01 LDA $01
0x0169CF|$05:$A9BF:E5 03 SBC $03
0x0169D1|$05:$A9C1:05 04 ORA $04 ; completely pointless op, unnecessary and calling code doesn't care about this
0x0169D3|$05:$A9C3:60 RTS
Changed version in HEX
DD 39 A8 08 88 B1 7A 28 D0 03 DD 38 A8
Finally finished... I did it the way hard way, but I double-checked every pointer, every branch, adjusted as necessary, and even had to take account for pointers from outside the scope of what was changing. But managed to free up a chunk of 18 bytes in the middle of all our level up routines. I want to utilize some of it for the Evasion/MDef underflow fix before I post it up.
Quote from: redmagejoe on February 03, 2020, 09:56:52 PM
Also would the underflow fix as I suggested work, using the same sort of fix you used to avoid underflow on weapons, which I also applied to spell levels? Having an extra branch after the first SBC?
Yup, adding a BCC after the SBC to check for underflow is what I'd do to fix that issue.
Quote from: redmagejoe on February 03, 2020, 09:56:52 PM
I didn't realize Blood Sword did extra damage to the Emperor, I was only aware that it did # of hits x 1/16 of target's Max HP, ensuring that 16 hits 1-shots anything.
Ah, that must be what I was mis-remembering then - there's no bonus damage, it's just that the Emperor's Max HP is so high that hitting for 1/16 of it is over powered.
Quote from: redmagejoe on February 03, 2020, 09:56:52 PM
@abw: Did you change this following chunk for either your HP/MP cap fix or the weapon level up fix?
Yes, that section was part of the HP/MP level up routine, and I cannibalized it for checking current HP/MP against the appropriate cap.
Quote from: redmagejoe on February 03, 2020, 09:56:52 PM
Yep, I'm in the painful process of doing that as we speak.
[...]
Once I finish this time-consuming code-shifting/pointer-adjusting/optimizing/consolidating project
[..]
Finally finished... I did it the way hard way, but I double-checked every pointer, every branch, adjusted as necessary, and even had to take account for pointers from outside the scope of what was changing.
You might want to think about taking an hour to get an assembler and learn its syntax. Letting the assembler do all the pointer calculations for you avoids major headaches :P.
I will probably sit down and do that soon. For today I have to take a mental break. That was draining. I worked the Evasion/MDef underflow fix in, updated pointers and branch labels (and a couple offsets on the branches) again, and we still have 16 bytes to work with.
Also just finally got around to updating the Gil Cap Fix, don't know how it took me so long to get back to that. Reviewing my other fixes for consistency while I'm at it.
EDIT: All my fixes seem good. If the redone Level Up Rework patch checks out, I'll look at how to approach the Evasion/MDef delay. abw's presented solution works conceptually, but I have to think if there's still a need for a stat refresh. My save is from before a lot of the fixes, so it has old stats, so that may be affecting my view on whether a full stat refresh is needed. I guess the question is if any stats aren't already updated besides Evasion and MDef before the player goes to their stat window, since in my case it has to apply the post-fixed values to a pre-fixed save.
Quote from: abw on February 03, 2020, 09:49:25 PM
That ought to make the displayed value match the actual damage, but in this case probably the right fix is to make the damage match the displayed value.
Yeah, we want it to actually do that bonus damage.
Quote from: abw on February 03, 2020, 09:49:25 PM
For the fix, I'd probably adjust the value of Y to match the stat offset and then INC ($7A),Y when the skill level increases.
I'm just trying to think of how to do that. 30 and 32 are used for Evasion Level and MDef Level from 6200 ($7E), but they're 2A and 2C from 6100 ($7A), so at least they're both offset by 6. Would I simply...
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 ; save new Evasion/Magic Resist skill level
0x016877|$05:$A867:C8 INY ; offset for Evasion/Magic Resist skill experience, whichever one we're dealing with at the moment
0x016878|$05:$A868:A9 00 LDA #$00 ; reset experience to zero
; control flow target (from $A85A)
0x01687A|$05:$A86A:91 7E STA ($7E),Y ; save new Evasion/Magic Resist skill experience
; control flow target (from $A846, $A853)
0x01687C|$05:$A86C:60 RTS
Since the new code takes TXA, STA, and INY into the JSR, that's unavoidable, so maybe before the RTS, since X register is still holding a copy of our new skill level, and Y doesn't need to be preserved returning from sub, could insert (with the 16 bytes we've freed up)...
TYA 1 byte
SBC #$06 2 bytes (this sets it to 2A or 2C as needed)
TAY 1 byte
TXA 1 byte
STA ($7A),Y 2 bytes (should now, in Firion's case, be setting the new skill level to $612A or $612C)
Don't know if that works right.
Wait, what is/was wrong with the gil cap/level up patches that you needed to rework them?
Quote from: BlazeHeatnix on February 04, 2020, 06:11:35 PM
Wait, what is/was wrong with the Gil cap/level up patches that you needed to rework them?
For the Gil Cap patch, I had mistakenly NOP'd out some code that accounted for the last 128 Gil before the cap, but I've since reverted those changes with only the three branch fixes. The only thing that needed to be changed where the type of branch and number of bytes to skip in three of the branches. The version available in the first post link is now the updated version, and should work properly in all cases. If you're concerned because you've applied the previous version, the new version can be patched directly on top of your ROM with no adverse effects.
As for the level up patches, they were made with respect to available space, but as we're crunching code and optimizing it, there's a lot of otherwise unused space that, if it were side-by-side to the other free space, could be used for bug fixes that require expanding the existing routines. So that's currently what we're doing, is crunching the code, but that also involves updating pointers to ensure that we don't have code pointing to instructions they shouldn't and crashing the game. All of the fixes we've made to Weapon Levels, Spell Levels, Maxed Stat Level Ups, Capping HP/MP, as well as underflows with respect to those first 3, are in the same general vicinity, so we're consolidating the code, optimizing it using better ways using less bytes to accomplish the same things, and freeing up 16+ bytes to be used for further fixes in the future or to just give us more room to work with while improving the code.
EDIT: Just corrected a small mistake where I forgot to update an OPcode for a data table read, but it wasn't game-breaking. Just temporarily bugged the HP/MP cap fixes. I'm putting a link to the updated bytes here, and I sent one to abw for review. Assuming everything works as it should, I can bundle 4 patches into one, and also address that Evasion/MDef delay.
https://drive.google.com/open?id=1W-B-13h1aeSUdA4pLN_Nvs400gtRwjcA
My above proposed fix for the evasion/mdef delay seems to have had no effect. It seems contingent upon a stat refresh being forced somehow, and there's no way I can think to do that automatically without the player changing equipment or entering another battle on account of the different banks.
EDIT: Since we're already getting in the habit of optimizing, and we may need to free up and move around as much space as possible down the road, I've decided to update the optional Mysidian Orbs patch as per abw's suggestion. Link in first post has been updated.
; handler for bottom left orb, Fire Crystal
; indirect control flow target (via $99A5)
0x03A1BF|$0E:$A1AF:A9 10 LDA #$10 ; Stat offset for base Strength
0x03A1C1|$0E:$A1B1:D0 0A BNE $A1BD
; handler for bottom right orb, Wind Crystal
; indirect control flow target (via $99A7)
0x03A1C3|$0E:$A1B3:A9 11 LDA #$11 ; Stat offset for base Agility
0x03A1C5|$0E:$A1B5:D0 06 BNE $A1BD
; handler for top left orb, Earth Crystal
; indirect control flow target (via $99A9)
0x03A1C7|$0E:$A1B7:A9 14 LDA #$14 ; Stat offset for base Spirit
0x03A1C9|$0E:$A1B9:D0 02 BNE $A1BD
; handler for top right orb, Water Crystal
; indirect control flow target (via $99AB)
0x03A1CB|$0E:$A1BB:A9 13 LDA #$13 ; Stat offset for base Intellect
; control flow target (from $A1B1, $A1B5, $A1B9, $A1D4)
0x03A1CD|$0E:$A1BD:AA TAX <<<
0x03A1CE|$0E:$A1BE:BD 00 61 LDA $6100,X ; Character #1 ID/portrait/sprite (00: Firion, 01: Maria, 02: Guy, 03: Minwu, 04: Josef, 05: Gordon, 06: Leila, 07: Ricard, 08: Leon); load A with baseline for character stats, X as offset
0x03A1D1|$0E:$A1C1:20 DF A1 JSR $A1DF <<<
0x03A1D4|$0E:$A1C4:9D 00 61 STA $6100,X ; Character #1 ID/portrait/sprite (00: Firion, 01: Maria, 02: Guy, 03: Minwu, 04: Josef, 05: Gordon, 06: Leila, 07: Ricard, 08: Leon); store new value of stat, baseline for character stats, X as offset
0x03A1D7|$0E:$A1C7:BD 10 61 LDA $6110,X ; Character #1 Base Strength; base character stats that display in stat window (base + equipment bonuses), X as offset
0x03A1DA|$0E:$A1CA:20 DF A1 JSR $A1DF <<<
0x03A1DD|$0E:$A1CD:9D 10 61 STA $6110,X ; Character #1 Base Strength; store new value of stat, baseline for character's window stats, X as offset
0x03A1E0|$0E:$A1D0:8A TXA <<<
0x03A1E1|$0E:$A1D1:18 CLC <<<
0x03A1E2|$0E:$A1D2:69 40 ADC #$40 <<<
0x03A1E4|$0E:$A1D4:90 E7 BCC $A1BD <<<
0x03A1E6|$0E:$A1D6:A4 A0 LDY $A0
0x03A1E8|$0E:$A1D8:20 07 99 JSR $9907
0x03A1EB|$0E:$A1DB:AD 00 7B LDA $7B00
0x03A1EE|$0E:$A1DE:60 RTS
; control flow target (from $A1C1, $A1CA)
0x03A1EF|$0E:$A1DF:18 CLC
0x03A1F0|$0E:$A1E0:69 0A ADC #$0A ; adds 10 to the appropriate stat
0x03A1F2|$0E:$A1E2:C9 64 CMP #$64 ; prevent stat from going above 99, compare to 100
0x03A1F4|$0E:$A1E4:90 02 BCC $A1E8 ; does not exceed cap, proceed to store new stat value
0x03A1F6|$0E:$A1E6:A9 63 LDA #$63 ; set stat to 99 if >99
0x03A1F8|$0E:$A1E8:60 RTS
0x03A1F9|$0E:$A1E9:EA NOP
0x03A1FA|$0E:$A1EA:EA NOP
0x03A1FB|$0E:$A1EB:EA NOP
0x03A1FC|$0E:$A1EC:EA NOP
@abw Is there an easy way to test for the bounds of your new RNG, or is it likely that the current form of the RNG patch is as small as it's going to get?
For Ripper, by the time the code gets to 0x033341, $9A and $9B have already been put somewhere else to be used for damage, so the code below is purely for display numbers.
; control flow target (from $B177, $B227)
0x033341|$0C:$B331:18 CLC
0x033342|$0C:$B332:A5 9A LDA $9A ; low byte of damage dealt to target before animations
0x033344|$0C:$B334:65 04 ADC $04 ; add Ripper damage low byte
0x033346|$0C:$B336:85 9A STA $9A ; store low byte of damage to DISPLAY
0x033348|$0C:$B338:A5 9B LDA $9B ; high byte of damage dealt to target before animations
0x03334A|$0C:$B33A:65 05 ADC $05 ; add Ripper damage high byte
0x03334C|$0C:$B33C:85 9B STA $9B ; store high byte of damage to DISPLAY
0x03334E|$0C:$B33E:60 RTS
I'll see if I can't figure out the moment damage is finalized and see what would have to be done to have Ripper damage checked before this. This makes me wonder if any weapons with special qualities (are there weapons that deal bonus damage to certain types like in 1?) suffer this same issue. https://strategywiki.org/wiki/Final_Fantasy_II/Weapons
Based on values in RAM, I believe that the code below is where the enemy's HP is calculated, prior to any application of Ripper damage. The LDAs and STAs from 330E6 down in RAM hold $7ED4 and $7ED5, which I'm assuming to be that particular enemy's HP in combat?
; control flow target (from $B0A0)
0x0330BF|$0C:$B0AF:A0 02 LDY #$02
0x0330C1|$0C:$B0B1:B1 A1 LDA ($A1),Y
0x0330C3|$0C:$B0B3:85 46 STA $46
0x0330C5|$0C:$B0B5:A9 00 LDA #$00
0x0330C7|$0C:$B0B7:85 47 STA $47
0x0330C9|$0C:$B0B9:A5 9C LDA $9C
0x0330CB|$0C:$B0BB:85 48 STA $48
0x0330CD|$0C:$B0BD:20 88 BC JSR $BC88
0x0330D0|$0C:$B0C0:A5 4A LDA $4A
0x0330D2|$0C:$B0C2:85 9A STA $9A
0x0330D4|$0C:$B0C4:A5 4B LDA $4B
0x0330D6|$0C:$B0C6:85 9B STA $9B
0x0330D8|$0C:$B0C8:A5 9B LDA $9B
0x0330DA|$0C:$B0CA:29 80 AND #$80
0x0330DC|$0C:$B0CC:F0 06 BEQ $B0D4
0x0330DE|$0C:$B0CE:A9 00 LDA #$00
0x0330E0|$0C:$B0D0:85 9A STA $9A
0x0330E2|$0C:$B0D2:85 9B STA $9B
; control flow target (from $B0CC)
0x0330E4|$0C:$B0D4:A0 0A LDY #$0A
0x0330E6|$0C:$B0D6:B1 A1 LDA ($A1),Y
0x0330E8|$0C:$B0D8:38 SEC
0x0330E9|$0C:$B0D9:E5 9A SBC $9A
0x0330EB|$0C:$B0DB:91 A1 STA ($A1),Y
0x0330ED|$0C:$B0DD:C8 INY
0x0330EE|$0C:$B0DE:B1 A1 LDA ($A1),Y
0x0330F0|$0C:$B0E0:E5 9B SBC $9B
0x0330F2|$0C:$B0E2:91 A1 STA ($A1),Y
0x0330F4|$0C:$B0E4:90 08 BCC $B0EE
0x0330F6|$0C:$B0E6:B1 A1 LDA ($A1),Y
0x0330F8|$0C:$B0E8:88 DEY
0x0330F9|$0C:$B0E9:11 A1 ORA ($A1),Y
0x0330FB|$0C:$B0EB:D0 1F BNE $B10C
0x0330FD|$0C:$B0ED:C8 INY
Note that this occurs before the final break on read/write $9A/$9B, which is when Ripper damage is calculated. I assume the issue is that the final calculation should be done BEFORE ($A1) has math performed on it.
Quote from: redmagejoe on February 04, 2020, 10:26:44 AM
30 and 32 are used for Evasion Level and MDef Level from 6200 ($7E), but they're 2A and 2C from 6100 ($7A), so at least they're both offset by 6.
Yeah, it would have been nicer for us if they had made the offsets line up, but alas :P.
Quote from: redmagejoe on February 04, 2020, 10:26:44 AM
TYA 1 byte
SBC #$06 2 bytes (this sets it to 2A or 2C as needed)
TAY 1 byte
TXA 1 byte
STA ($7A),Y 2 bytes (should now, in Firion's case, be setting the new skill level to $621A or $621C)
Don't know if that works right.
Quote from: redmagejoe on February 04, 2020, 06:29:13 PM
My above proposed fix for the evasion/mdef delay seems to have had no effect.
You'll also want to SEC since C could be clear or set at this point, and you'll want to INX since X has the skill level, which is 1 less than the stat level. If you just write the skill level, you're actually making things worse, since now your stat level will always be wrong after a battle :P.
Quote from: redmagejoe on February 04, 2020, 06:29:13 PM
@abw Is there an easy way to test for the bounds of your new RNG, or is it likely that the current form of the RNG patch is as small as it's going to get?
I haven't gotten around to this yet. If you want to help, it's the 2 calls to $0F:$FD11 at $0B:$B21B and $0B:$B232 that I'm concerned about - the values for X and A are the results of arithmetic operations that may or may not overflow based on the contents of RAM locations whose meaning we haven't identified yet. If we can establish that X <= A in all cases, my RNG fix can be shortened significantly.
Quote from: redmagejoe on February 04, 2020, 06:29:13 PM
This makes me wonder if any weapons with special qualities (are there weapons that deal bonus damage to certain types like in 1?) suffer this same issue.
Family and Elemental bonus damage gets added to the total before reducing the target's HP, so that looks like it's working. All the special effects come after applying damage, however, so they each need to update the applied damage according to their effect.
Quote from: redmagejoe on February 04, 2020, 06:29:13 PM
For Ripper, by the time the code gets to 0x033341, $9A and $9B have already been put somewhere else to be used for damage, so the code below is purely for display numbers.
I've updated my disassembly with more labels and comments. It looks like you're right about the Ripper's effect taking place after damage is dealt - target HP gets reduced at $0C:$B0D4 and during the drain HP special effect. Based on the code, I think I'm seeing a couple more bugs here:
- if you manage to kill a monster with the drain bonus damage, you don't get the usual death message
- attacking Undead with a Heal Staff has the same issue with updating the displayed damage but not the actual damage, which is more severe here since the heal effect adds between 30 and 60 bonus damage per hit compared to Ripper's +20
Ripper and Heal Staff can likely both be fixed with the same approach then, when I finish up my latest work project. Heal Staff is another documented bug, but it's nice to know that we may be able to address two at once. I will have to try and sort my thoughts and clear some things off my to-do list, as I've got too many things bouncing around in my head right now, like whether I can call the level up optimization "stable", the testing the RNG fix, and the Evasion/MDef delay thing, even though I opened a can of worms with chasing Ripper behavior around.
Have you had a chance to have a look at the optimized level-up fix? I'll also take a look at those calls to the RNG to see if I can't make sense of what they're doing. I will also give your suggestion regarding the update to Evasion/MDef a try. As I said before, I don't THINK that there's any other stats that need to be force-updated, and it may simply be due to my pre-fix save.
Ah, the INX was what I forgot before. I don't know what math is being done down the line on the value when a stat refresh is done, but I was afraid to increment for fear of a double increase. But I managed to increment the value and not cause it to go up twice, so... I have no idea what's been frontloaded. Is it simply comparing $6233 and $621C during equipment switching or starting a battle, and if they don't match, set 621C to 6233? Bizarre. I'll rework the optimized level up patch with a more elegant version of my current jury-rigged test fix, and that should be one less thing on the to-do list. I'll try to use up as little of the 16 bytes as possible, after all the work I went through to free it up. :)
EDIT: Google Drive link to optimization rewrite has been updated. I'm hoping that someone with sharp eyes might see where I can fit this in... In it's current state, I'd have to essentially hijack the current JSR into a new JSR to this fix, with the old JSR at the head. It's just such a headache with how many bytes are being wasted moving data and offsets around. There must be a more elegant way to apply this, but I've also pigeon-holed myself by pulling so many common elements into the previously small Cap at 15 sub. Still, that saved far more space than this is trying to take up, so... We're limited. I'll take another look at this when I'm fresh and at 100%.
I'm having a hard time even finding where $0B:$B21B and $0B:$B232 fire. Set breakpoints on execute for both of them, and haven't had a break yet in combat at least. Trying to think of all possible scenarios where RNG would be used. I'll keep trying. EDIT: Ah! Just had $B21B fire followed by $B232... Let's see what these are tied to. Huh... They seem to be RNG used to determine where the arrow sprites appear on the enemy when a bow weapon is fired. It fired 4 times before each of the 4 arrow animations on a 16x hit, so 4 sets of arrows (and 4 of them per set). So I guess those 2 calls to the RNG are used to determine where each of the up to 16 arrow sprites appears on the enemy. Maybe X position followed by Y position? That's it. Not sure that changing the RNG is going to break that somehow, but mystery solved, abw.
I'm realizing that there's a large block of free space that could be consolidated in the end of the ROM, if we were theoretically going to do code crunch to make room for enhancements down the line, where right before the "FINAL FANTASY 2" we could push those two 13-byte blocks down and adjust pointers, so that that 3-byte and 19-byte could be combined with the 194-byte block for a 216-byte contiguous block. That's just some musing that may not be relevant until later, though I may also need to make an update to Chaos Rush's translation, as it appears to use 0x33F10 to 0x33FBB, which falls right in the center of the largest block. Just needs to be shifted back about 20 bytes so we still have 196 bytes to work with, but I have no idea what it's supposed to be. It doesn't show up in his CastleFynn list, so I'm assuming it's data (possibly for spell name DTE?). Still, it's promising to see such a large chunk of available space in fixed bank.
Quote from: redmagejoe on February 09, 2020, 10:36:33 PM
Ripper and Heal Staff can likely both be fixed with the same approach then, when I finish up my latest work project.
I was actually working on fixing the special effects, but it's going to take some more work to track down all the missing parts. Related to that, here's one more special effect bug:
- HP changes from a special effect should update the target's critical HP status, but do not
I still need to test this, but it looks like another thing with the Heal Staff against a non-Undead target is that first you lose HP based on its normal damage and then you gain HP based on its normal damage + its bonus damage. So, say a hit inflicts 100 normal damage and 50 bonus damage. For a target with 300 HP, the target loses 100 HP and then gains 150 HP for a net gain of 50 HP. For a target with only 10 HP, however, the 100 normal damage kills them, setting their HP to 0, and then they gain 150 HP for a net gain of 140 HP. This should probably also be considered a bug, though it wouldn't be too hard to come up with some sort of life-force-y/more-powerful-the-closer-you-are-to-death explanation for the behaviour.
Quote from: redmagejoe on February 09, 2020, 10:36:33 PM
Have you had a chance to have a look at the optimized level-up fix?
Ah, no, sorry, not yet. I've been kind of tied up with other things lately :(.
Quote from: redmagejoe on February 09, 2020, 10:36:33 PM
Is it simply comparing $6233 and $621C during equipment switching or starting a battle, and if they don't match, set 621C to 6233?
No, it's simply always setting the stat level (1-16) to the skill level (0-15) +1.
Quote from: redmagejoe on February 09, 2020, 10:36:33 PM
Ah! Just had $B21B fire followed by $B232... Let's see what these are tied to. Huh... They seem to be RNG used to determine where the arrow sprites appear on the enemy when a bow weapon is fired. It fired 4 times before each of the 4 arrow animations on a 16x hit, so 4 sets of arrows (and 4 of them per set). So I guess those 2 calls to the RNG are used to determine where each of the up to 16 arrow sprites appears on the enemy. Maybe X position followed by Y position?
Huh, that's neat, I had no idea the arrow animations were that intricate, potentially showing each individual arrow that hits, spread out over multiple volleys, with each arrow given its own random position (possibly overlapping with other arrows) based on the location and apparent size of the target, and displayed for different distances. I didn't trace backwards through all the possible branching, but it looks like the source data for those calculations is based on numbers that are small but not too small, so while I'm still not 100% sure, I'm reasonably confident that the calculations should end up setting X <= A, which means removing the bounds checking on the RNG is probably safe. I've update the original link with the new version, which is now a total of 9 bytes shorter than the original code.
I've also updated the rework, seeing where I'd forgotten to incorporate the max spells outside combat still gaining experience fix. Nothing's exploding, I'm just worried that I've overlooked a branch or a JSR somewhere that's going to have subtle ramifications. Also, awesome to hear ABW! I'll add this to the latest stable ROM I'm working on and update the first post. I feel like our project is coming along nicely. :beer:
Hope you don't mind, but I NOP'd out those 9 freed up bytes as a breadcrumb for us for future code-shifting should we need it. Updated the first post.
EDIT: There's a minor graphical bug (not game-breaking and only appears for 1 or 2 frames, but still jarring) that occurs with your RNG fix, where after all party member orders are issued and the battle windows shift to prepare to execute the actions, a bunch of horizontal line fragments appear for the briefest moment. Any idea how the RNG change could be causing this? I isolated the RNG patch and tried your base version and my post-NOP version, with the same effect. Stepping through line by line to see the exact frame/instruction that the glitch appears on, though it's dealing with the RNG so... it's going to take a LONG time and probably hundreds of steps. So I set the breakpoint to execute $FD11, and after issuing orders, MANY calls to the RNG happen. However, the graphical glitch always occurs almost immediately at the 11th call to RNG, and does not clear up until the 14th call (and who knows how many more calls happen after that), but the point is that for call 11 of the RNG, and through calls 12 and 13, this fix causes weird graphic bugs. I'm guessing it has something to do with how the general-use RAM addresses ($01 - $08) are used compared to the original code, but hell if I have any idea exactly how. I'm assuming that after the 10th call to RNG and before the 11th, graphics are being updated using a value in RAM that isn't what it should be, and the graphics update again only after 4 more runs of the RNG.
Watching the same breaks and RAM values in the unmodified version, the only "wildcard" values appear to be $06 and $05. But $05 isn't used in the patched version. All I can think is that something we're doing with $06 may be an issue? I'll try to analyze the values of $06 at calls 10 in both of them and see if I can't determine a correlation. Added a read/write to $06 break after the 10th call to RNG and followed it until the moment the graphics bugged, and as much as I hate to say it, I'm wondering if it's an overflow issue. The values in the original don't get very high when LDA $06 and ADC $02 are happening, usually around $27 + $3F. In this case, $06 is going up pretty high...
> 03FD02:A5 06 LDA $0006 = #$D9 <<<
03FD04:65 02 ADC $0002 = #$28 <<<
03FD06:85 06 STA $0006 = #$D9
03FD08:A5 07 LDA $0007 = #$FF
03FD0A:65 03 ADC $0003 = #$00
03FD0C:85 07 STA $0007 = #$FF
03FD0E:18 CLC
Work keeps piling up, but I'm trying to find time to focus on our current items. Mostly want to be sure that I haven't overlooked anything on the Level Up Rework before I call that done, and even then, I need to find out if I've created the most efficient Evasion/MDef delay fix that uses as little of the free space as possible. I fear that the abstraction between instructions and outcome is making it difficult for me to think how to cleverly work it in other than the instructions I wrote, which rely on working around the existing framework rather than trying to cleverly make use of the behavior to fit my fix in. That has been my biggest hurdle so far, and I'm hoping that I get comfortable enough to write changes that don't throw a wrench in the current behavior. If possible, I'd like to try and come up with a way to work that fix in before it changes offsets during the level-up check, rather than after the fact. I just don't know if I can make it any more concise than its current form.
Google Drive link has been updated to reflect my current WIP test fix. I've already tested with mentioned changed JSRs and Branch offsets, I just don't know if I want to commit this change yet if someone can see a cleaner way to do this.
https://drive.google.com/open?id=1W-B-13h1aeSUdA4pLN_Nvs400gtRwjcA
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
- Aura 8 does not grant Undead bonus attack, and Barrier 8 does not grant Ice protection. Investigate cause.
Also, maybe I'm just a fool, but isn't this an easy fix? Is this not simply a case of the offset of spell levels ($00 = Level 1) not matching up with the math required for the level 8 effects of these spells? Would not changing the CPX #$08 / LDA #$07 at $B751 onward to $09 and $08 fix these bugs?
; handler for Spell IDs #$D7: Esuna, #$D6: Basuna, #$D8: Barrier, #$D3: Aura
; indirect control flow target (via $BE9A, $BE9C, $BEA0, $BEA2)
0x0336DD|$0C:$B6CD:20 E9 BD JSR $BDE9
0x0336E0|$0C:$B6D0:A5 5E LDA $5E
0x0336E2|$0C:$B6D2:C9 0A CMP #$0A
0x0336E4|$0C:$B6D4:90 03 BCC $B6D9
0x0336E6|$0C:$B6D6:4C 2C B7 JMP $B72C
; control flow target (from $B6D4)
0x0336E9|$0C:$B6D9:48 PHA
0x0336EA|$0C:$B6DA:18 CLC
0x0336EB|$0C:$B6DB:A0 28 LDY #$28 ; Battle stat offset for Mystery Battle Stat #$28
0x0336ED|$0C:$B6DD:B1 9F LDA ($9F),Y ; pointer to actor's battle stats
0x0336EF|$0C:$B6DF:65 48 ADC $48
0x0336F1|$0C:$B6E1:85 48 STA $48
0x0336F3|$0C:$B6E3:68 PLA
0x0336F4|$0C:$B6E4:C9 08 CMP #$08
0x0336F6|$0C:$B6E6:D0 13 BNE $B6FB
0x0336F8|$0C:$B6E8:A0 08 LDY #$08 ; Battle stat offset for Ailment
0x0336FA|$0C:$B6EA:84 5E STY $5E
0x0336FC|$0C:$B6EC:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x0336FE|$0C:$B6EE:A0 2C LDY #$2C ; Battle stat offset for Ailment backup
0x033700|$0C:$B6F0:84 5F STY $5F
0x033702|$0C:$B6F2:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x033704|$0C:$B6F4:A0 08 LDY #$08
0x033706|$0C:$B6F6:A6 48 LDX $48
0x033708|$0C:$B6F8:4C 0D B7 JMP $B70D
; control flow target (from $B6E6)
0x03370B|$0C:$B6FB:A0 09 LDY #$09 ; Battle stat offset for Mystery Battle Stat #$09 (bit field; 02:critical HP)
0x03370D|$0C:$B6FD:84 5E STY $5E
0x03370F|$0C:$B6FF:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x033711|$0C:$B701:A0 2D LDY #$2D ; Battle stat offset for Mystery Battle Stat #$2D (backup of Mystery Battle Stat #$09?)
0x033713|$0C:$B703:84 5F STY $5F
0x033715|$0C:$B705:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x033717|$0C:$B707:A0 09 LDY #$09 ; Battle stat offset for Mystery Battle Stat #$09 (bit field; 02:critical HP)
0x033719|$0C:$B709:A6 48 LDX $48
0x03371B|$0C:$B70B:E8 INX
0x03371C|$0C:$B70C:E8 INX
; control flow target (from $B6F8)
0x03371D|$0C:$B70D:E0 08 CPX #$08
0x03371F|$0C:$B70F:90 02 BCC $B713
0x033721|$0C:$B711:A2 07 LDX #$07
; control flow target (from $B70F)
0x033723|$0C:$B713:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
; control flow target (from $B719)
0x033725|$0C:$B715:20 0E 90 JSR $900E
0x033728|$0C:$B718:CA DEX
0x033729|$0C:$B719:10 FA BPL $B715
0x03372B|$0C:$B71B:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x03372D|$0C:$B71D:A4 5E LDY $5E
0x03372F|$0C:$B71F:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x033731|$0C:$B721:85 4E STA $4E
0x033733|$0C:$B723:A4 5F LDY $5F
0x033735|$0C:$B725:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x033737|$0C:$B727:85 4F STA $4F
0x033739|$0C:$B729:4C 84 B7 JMP $B784
; control flow target (from $B6D6)
0x03373C|$0C:$B72C:48 PHA
0x03373D|$0C:$B72D:A9 49 LDA #$49 ; Battle Message ID #$49: 'White '
0x03373F|$0C:$B72F:85 5C STA $5C
0x033741|$0C:$B731:A9 D1 LDA #$D1 ; Battle Message ID #$51: 'Aura'
0x033743|$0C:$B733:85 5D STA $5D
0x033745|$0C:$B735:68 PLA
0x033746|$0C:$B736:C9 0C CMP #$0C
0x033748|$0C:$B738:F0 0D BEQ $B747
0x03374A|$0C:$B73A:A9 40 LDA #$40 ; Battle Message ID #$40: 'Ice'
0x03374C|$0C:$B73C:85 5C STA $5C
0x03374E|$0C:$B73E:A9 C8 LDA #$C8 ; Battle Message ID #$48: ' Df'
0x033750|$0C:$B740:85 5D STA $5D
0x033752|$0C:$B742:A0 05 LDY #$05 ; Battle stat offset for Armor Resistance bits (80:Ice, 40:Body, 20:Poison, 10:Death, 08:Lightning, 04:Mind, 02:Fire, 01:Matter)
0x033754|$0C:$B744:4C 4D B7 JMP $B74D
; control flow target (from $B738)
0x033757|$0C:$B747:A0 1C LDY #$1C ; Battle stat offset for Primary Hand Weapon Monster Family Bonus (80:Undead, 40:Werebeast, 20: Dragon, 10:Spellcaster, 08:Giant, 04:Earth, 02:Aquatic, 01:Magic Beast)
0x033759|$0C:$B749:20 4D B7 JSR $B74D
0x03375C|$0C:$B74C:60 RTS
; control flow target (from $B744, $B749)
0x03375D|$0C:$B74D:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x03375F|$0C:$B74F:A6 48 LDX $48
0x033761|$0C:$B751:E0 08 CPX #$08
0x033763|$0C:$B753:90 02 BCC $B757
0x033765|$0C:$B755:A2 07 LDX #$07
; control flow target (from $B753)
0x033767|$0C:$B757:CA DEX
0x033768|$0C:$B758:30 24 BMI $B77E
0x03376A|$0C:$B75A:86 48 STX $48
; control flow target (from $B760)
0x03376C|$0C:$B75C:20 0A 90 JSR $900A
0x03376F|$0C:$B75F:CA DEX
0x033770|$0C:$B760:10 FA BPL $B75C
0x033772|$0C:$B762:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x033774|$0C:$B764:A2 00 LDX #$00
0x033776|$0C:$B766:86 5F STX $5F
; control flow target (from $B77C)
0x033778|$0C:$B768:A5 5C LDA $5C
0x03377A|$0C:$B76A:20 92 BF JSR $BF92 ; add battle message ID A to the list of battle messages to display
0x03377D|$0C:$B76D:A5 5D LDA $5D
0x03377F|$0C:$B76F:20 92 BF JSR $BF92 ; add battle message ID A to the list of battle messages to display
0x033782|$0C:$B772:A5 48 LDA $48
0x033784|$0C:$B774:C5 5F CMP $5F
0x033786|$0C:$B776:F0 09 BEQ $B781
0x033788|$0C:$B778:E6 5F INC $5F
0x03378A|$0C:$B77A:E6 5C INC $5C
0x03378C|$0C:$B77C:D0 EA BNE $B768
; control flow target (from $B758)
0x03378E|$0C:$B77E:20 73 BE JSR $BE73
; control flow target (from $B776)
0x033791|$0C:$B781:4C 7E BE JMP $BE7E
More updates to the disassembly posted in the original link!
Quote from: redmagejoe on February 14, 2020, 12:06:19 AM
EDIT: There's a minor graphical bug (not game-breaking and only appears for 1 or 2 frames, but still jarring) that occurs with your RNG fix
Well, that's depressing. Comparing trace logs of the original and modified ROMs, it looks like the new RNG routine is taking too long (I knew it was longer, but didn't know how much that would matter), and when called too many times per frame it was causing code to run into vblank time. I've updated the link with a faster version that's closer to the original code; the downside is that the output is non-uniform for ranges that don't divide into 256 evenly, but the variation from uniform is never more than 1/256, so it's still far closer to uniform than the original code's distribution, which could vary from uniform by as much as 43/256. Also, the new RNG value is never more than 1 different from the old RNG value, so it's definitely a less disruptive change.
Quote from: redmagejoe on February 14, 2020, 12:06:19 AM
I'm wondering if it's an overflow issue. The values in the original don't get very high when LDA $06 and ADC $02 are happening, usually around $27 + $3F. In this case, $06 is going up pretty high...
> 03FD02:A5 06 LDA $0006 = #$D9 <<<
03FD04:65 02 ADC $0002 = #$28 <<<
03FD06:85 06 STA $0006 = #$D9
03FD08:A5 07 LDA $0007 = #$FF
03FD0A:65 03 ADC $0003 = #$00
03FD0C:85 07 STA $0007 = #$FF
03FD0E:18 CLC
That's part of the division routine; possible overflow there is expected and the carry needs to make its way into $07 in order for the math to work properly.
Quote from: redmagejoe on February 14, 2020, 12:06:19 AM
Mostly want to be sure that I haven't overlooked anything on the Level Up Rework before I call that done, and even then, I need to find out if I've created the most efficient Evasion/MDef delay fix that uses as little of the free space as possible.
The more I look at this, the more I feel like forcing a full stat refresh at the end of battle is the way to go. In addition to the delay on Evasion and Magic Resist attempts, it looks like none of the other calculated values are getting updated. For instance, the increases to Attack Power and Hit % when gaining Strength don't appear until the next stat refresh, nor do the increases to Hit attempts or Evasion Success % when gaining a weapon/shield level, and similarly for the other calculated stats. Probably a JSR $FAFB at the end of the base stat level up routine (i.e. somewhere around $05:$A706) should do the trick.
Quote from: redmagejoe on February 14, 2020, 12:06:19 AM
I fear that the abstraction between instructions and outcome is making it difficult for me to think how to cleverly work it in other than the instructions I wrote, which rely on working around the existing framework rather than trying to cleverly make use of the behavior to fit my fix in. That has been my biggest hurdle so far, and I'm hoping that I get comfortable enough to write changes that don't throw a wrench in the current behavior.
This is one of the hard parts about working with other people's code in general, and it's even more difficult when working with assembly than when working with some high-level language. Scoping rules make code more manageable :P.
Quote from: redmagejoe on February 14, 2020, 12:06:19 AM
Also, maybe I'm just a fool, but isn't this an easy fix? Is this not simply a case of the offset of spell levels ($00 = Level 1) not matching up with the math required for the level 8 effects of these spells? Would not changing the CPX #$08 / LDA #$07 at $B751 onward to $09 and $08 fix these bugs?
Hmm, yup, I agree. The code is checking to see if the number of successes is >= 8, and if so, setting it to 7. I'm pretty sure they meant to say LDX #$08. You can also change the CPX to #$09 if you want, but it's not necessary. Some fixes are easier than others :D.
So there's only a slight loss of uniform distribution with the new version, but it's significantly more uniform, isn't going to result in Maria and Guy getting 50% more focus, and isn't going to cause graphic bugs? I don't know dude, doesn't sound like much of a downside to me. :) If it's all tested and ready, I'll go ahead and update the first post and add it to stable. Also, again, looking at control flow, it's alright for me to NOP out those 19 freed up bytes after the RTS for use as free space for later, right?
As for the fix to Barrier/Aura, on Barrier at least, it seems to go through the battle messages for 7 checks, including "Freeze Resist" / "Ice Df", but isn't actually applying it, I guess? So it says you're getting ice protection, but you're not, similar to Ripper I guess. I don't know how to properly check that since I have no idea what values are being manipulated on successful applications of Barrier. That said, I don't know if there's supposed to be a message for Matter element, as it always seems to start at "Fire Resist", going all the way up to "Freeze Resist" suggesting it's doing Fire ("Fire Resist"), Mind ("Confuse Resist"), Lightning ("Thunder Resist"), Death ("Death Resist"), Poison ("Poison Resist"), Body, ("Paralyze Resist"), and Ice ("Freeze Resist") on full successes. So maybe there's an issue with the battle message, but not the actual buff for Matter defense? Maybe the counter starts 1 too high. I don't even know what the battle message for Matter is supposed to be.
And after testing, it seems that at lower levels "Freeze Resist" is the first thing to show up when no other messages do. It would help if I actually understood what this spell is meant to do in the first place. I'm not sure if Freeze means "Ice" or if it means Matter elements "Paralyze/Stop" effect. The wording in Chaos Rush's translation is confusing, and I may need to test this with the Demiforce translation. If that is the case, it would mean that it is, in essence, doing Matter first, and the final message that isn't showing up should be "Ice", which is currently not showing up. That would make more sense. I will test this with the Demiforce translation, and consider updating the messages in Chaos Rush's translation to be more clear.
Hmm, no, it does seem like it's at least trying to give you Ice Resist at lower levels. Reading the resources online, I'm now completely confused how this spell is SUPPOSED to behave versus how the game is actually executing it. Because currently it seems to behave in the exact opposite manner as outlined online. I am going to come back to this later.
Do all the branches and JSRs (and opcodes) check out in my rework? I didn't overlook anything? I'll go ahead and throw out the current Evasion/MDef fix WIP then since, as I feared, there's more things that need an update. I'll start working on a full stat refresh fix instead. If you're willing to give me the go-ahead on the Level Up Rework, I'll go ahead and add it to stable. Disregard, I have to move things around again to add this call to stat refresh at the end of everything. I need to figure out how it's handling these individual subroutines, if there's a primary control flow that does a conditional check and then JSRs to them (so the new JSR would have to be in that routine) or if it goes through each individual sub and checks within them (new JSR would have to be at the end of the last). I'll post updates here.
So looking at $05:$A553, while the comment is indeed true about leveling weapons, I'm assuming that this is the branch taken to begin the entire level up routine set. Or at least, it either levels weapons, or it invariably jumps down to the middle of level up routines. I'm just trying to chase around control flow to find when the absolute last stat level up is finished and control is handed back to any other instructions to be done before going back to the overworld.
I think I have an idea of when this stat refresh should take place, and since the existing one only seems to do one character at a time anyway, I'd need it to run with A holding the character ID. So probably would have it happen in the neighborhood of $05:$A559 ?
Quote from: redmagejoe on February 18, 2020, 12:53:26 PM
So there's only a slight loss of uniform distribution with the new version, but it's significantly more uniform, isn't going to result in Maria and Guy getting 50% more focus, and isn't going to cause graphic bugs? I don't know dude, doesn't sound like much of a downside to me. :)
Yeah, it's better, just not the best. Of course, the entire thing is based on one of two static lookup tables, so it would be tough to come up with something non-trivial that was significantly worse :P. When the number of possible outputs divides evenly into 256 (as is the case when picking a random number in the range [0..3]), the distribution actually is uniform (so enemy targetting is fine), but it becomes non-uniform for other output sizes. E.g., when asking for a number between 1 and 100, there are 256 mod 100 = 56 numbers that are more likely to occur than they would in a truly uniform distribution.
Quote from: redmagejoe on February 18, 2020, 12:53:26 PM
As for the fix to Barrier/Aura, on Barrier at least, it seems to go through the battle messages for 7 checks, including "Freeze Resist" / "Ice Df", but isn't actually applying it, I guess?
It looks like the battle messages are just plain backwards. The actual resistances are in the order (using Chaos Rush's translation) 80: Freeze, 40: Paralyze, 20: Poison, 10: Death, 08: Thunder, 04: Confuse, 02: Fire, 01: Transform, but the battle messages are in the opposite order 80: Transform, 40: Fire, 20: Confuse, 10: Thunder, 08: Death, 04: Poison, 02: Paralyze, 01: Freeze, so with 1 success you actually get Transform resistance but the battle message says Freeze resistance.
For comparison, Demiforce's terms are 80: Change, 40: Fire, 20: Soul, 10: Bolt, 08: Death, 04: Poison, 02: Critical Hit!, 01: Ice, and the Japanese is 80: へんかこうげき, 40: ほのおこうげき, 20: せいしんこうげき, 10: いなづまこうげき, 08: し, 04: どくこうげき, 02: しんけいこうげき, 01: れいきこうげき. Google Translate of the Japanese is not particularly helpful, alas :(.
Quote from: redmagejoe on February 18, 2020, 12:53:26 PM
So looking at $05:$A553, while the comment is indeed true about leveling weapons, I'm assuming that this is the branch taken to begin the entire level up routine set.
Just before $05:$A553 is the check for persistent status conditions, and if you have any (except whatever bit 0 indicates), it sets $E1 to #$FF and then jumps way down to near the end of the level up routine. $A559 is on that path of no growth, so you'll definitely want to trigger the refresh at the end of the other path.
So wait, is this actually yet another bug? The order of the messages is reversed? I compared Neodemi's and Chaos's translations, and they're in the same memory addresses so it's not the translation like I first thought. I'm looking at the list of messages in your disassembly, and sure enough... Ugh, that's a headache, as now this change is going to conflict with translation patches that rely on changing the message offsets for changed names... Headache...
0x017325|$05:$B315:C7 B5 ; $05:$B5C7; Battle Message ID #$40: 'Ice'
0x017327|$05:$B317:CF B5 ; $05:$B5CF; Battle Message ID #$41: 'Critical Hit!'
0x017329|$05:$B319:D8 B5 ; $05:$B5D8; Battle Message ID #$42: '[Poison]'
0x01732B|$05:$B31B:DF B5 ; $05:$B5DF; Battle Message ID #$43: 'Death'
0x01732D|$05:$B31D:E1 B5 ; $05:$B5E1; Battle Message ID #$44: 'Bolt'
0x01732F|$05:$B31F:EA B5 ; $05:$B5EA; Battle Message ID #$45: 'Soul'
0x017331|$05:$B321:F3 B5 ; $05:$B5F3; Battle Message ID #$46: 'Fire'
0x017333|$05:$B323:FB B5 ; $05:$B5FB; Battle Message ID #$47: 'Change'
0x017325|$05:$B315:FB B5 ; $05:$B5FB; Battle Message ID #$40: 'れいきこうげき' (reiki(?) kogeki / cold air attack)
0x017327|$05:$B317:F3 B5 ; $05:$B5F3; Battle Message ID #$41: 'しんけいこうげき' (shinkei kogeki / nerve killing attack)
0x017329|$05:$B319:EA B5 ; $05:$B5EA; Battle Message ID #$42: 'どくこうげき' (yomi kogeki / poison attack)
0x01732B|$05:$B31B:E1 B5 ; $05:$B5E1; Battle Message ID #$43: 'し' (shin / death)
0x01732D|$05:$B31D:DF B5 ; $05:$B5DF; Battle Message ID #$44: 'いなづまこうげき' (inazuma kogeki / lightning attack)
0x01732F|$05:$B31F:D8 B5 ; $05:$B5D8; Battle Message ID #$45: 'せいしんこうげき' (seishin kogeki / soul/mind attack)
0x017331|$05:$B321:CF B5 ; $05:$B5CF; Battle Message ID #$46: 'ほのおこうげき' (ho no o kogeki / fire attack)
0x017333|$05:$B323:C7 B5 ; $05:$B5C7; Battle Message ID #$47: 'へんかこうげき' (hen ka kogeki / transform attack)
So I pulled up the same message offsets in the Japanese ROM using CastleFynn, and I have here the Japanese names of the messages. In the second block, as you can see, I reversed the pointers. Because of the natures of both messages and pointers being changed by translation patches (at least in Chaos Rush's case, the condensed words I believe are shifted around rather than filled in with blank space), making this change would demand that Chaos Rush's translation be updated again, and controversially, would render it the only "working" English translation patch from here on out. Though currently, all the translations are wrong anyway and based on improper behavior in the base ROM, so...
If I were already going to change Chaos Rush's translation again though, I'd want to address the issue with the spell names, their use of DTE (which would require graphics editing), and the 5-characters instead of 4 that causes the issue with spell levels in combat windows above level 10 I'd mentioned previously in this thread.
Chased down the values in my Chaos Rush translated version (which are of course different), flipped the offsets, and lo and behold, it displays the right messages now, starting at the highest level's resistance and working its way down, so now a success on Level 1 Barrier should yield Transform Resist rather than Freeze Resist. I'll pair this less confusing fix with testing the LDX #$08 fix and see if I can now get Freeze Resist to appear.
EDIT: Works for both Aura and Barrier. Such a ridiculously simple fix, it's almost painful to think this was a bug that made it into release. Off by one indeed. I'll roll both of these changes (the message pointers for Barrier as well as the 7 -> 8 ) into a fix. The patch will change the Japanese pointers. I've already made a separate fix for the Chaos Rush translation to my version of the IPS on my end. I'll look into fixing the spell name length issue while I'm at it and think about submitting a v1.9 / 2.0 when those get fixed.
I'd like to find someone comfortable working with graphics to redo the special DTE characters used for spell names in Chaos Rush's translation, as they look nice and are pretty tightly packed together, but again, they're based on a 5-character length instead of a 4-character length.
What do you think would be the best place to sneak in a JSR $FB00? I'll try not to go cross-eyed chasing down the control flow off of the BEQ at $A553 and see where it seems to be finished handling stat-ups for that character. There surely must be a final, always-run set of instructions post stat ups regardless of whether a certain stat goes up or not, but with all the JSRs and JMPs my eyes are going crossed. I'll do my best though.
I'm thinking after $05:$A703 ? Though we also have to LDA the character index, don't we, given that's what the comments suggest is already the case for Accumulator when going into $FB00. So I'm guessing we need an LDA $9E followed by a JSR $FB00. I'll do a test fix without shifting addresses by taking $05:$A706 and $A709 into a JSR (+NOP) that points down to our 16-byte free space, but preceding them with LDA $9E and JSR $FB00 and see if that results in Firion coming out of the battle with his new MDef. If it works and if it sounds like an appropriate fix to you, abw, I'll rewrite the Level Up Rework again. If we feel comfortable that we've made all the fixes to level up routines necessary, we can crunch the code again and move that free space (will be 11 bytes of the current 16 left after the JSR and LDA get worked in) to the end of our level up routines to be used or re-crunched towards the end of the project.
EDIT: Problem is that the JSR to $FB00 goes to $0F:$FB00, but rather than jumping to $00:$9880 as it would usually do, it's jumping to $05:$9880. Again, I don't know if there's any way within bank 05 (which I assume is used for combat) to get to bank 00 to make this stat refresh happen. Could some instructions be set to run the moment the overworld comes back up after leaving a battle? The jump goes to the correct bank at the start of a battle, yet when we're in bank 05 for stat level ups and such, I guess we're... stuck there? Shouldn't jumping over to bank 0F first allow us to properly jump to bank 00? Or am I not understanding how hand-offs between banks work?
Started looking at Dispel, and already even if it DID work, it seems to suffer the same LDX #$07 issue. Given that Basuna and Esuna have 5 and 7 status cures respectively, the LDX #$07 works there. But Dispel is supposed to act on 8 different resists, so once more I'll be changing that 7 to 8. But this may be more difficult to test, as I'm assuming (haven't tested yet) that it displays messages properly while not actually affecting any stats, so I'll have to figure out what exact RAM addresses are supposed to be affected, whether the wrong addresses are being affected, or the correct ones are simply having no change applied... The description of the bug doesn't really tell me exactly WHY it doesn't work, so going to have to do some detective work.
EDIT: Hmm, actually all 8 messages work without changing 7 to 8, so now I'm curious what makes that bug specific to Aura/Barrier. Is it the DEX immediately after the LDX? Probably. Also took this opportunity to fix a few more awkward battle messages in Chaos's translation (Like Atk.Up up!, changed Atk.Up and Def.Up to Attack and Defend), and made the janky "FireBar.fell" to "Fire ResDown".
Quote from: redmagejoe on February 18, 2020, 11:48:43 PM
Ugh, that's a headache, as now this change is going to conflict with translation patches that rely on changing the message offsets for changed names...
The alternative to changing the text to match the effect is to change the effect to match the text. Instead of one success setting bit 0 (Transform) and printing the message for bit 7 (Freeze), we could flip it around so that one success sets bit 7, two successes set bit 7 and 6, and so forth. Is gaining resistances in one order better than the other order?
Quote from: redmagejoe on February 18, 2020, 11:48:43 PM
I've already made a separate fix for the Chaos Rush translation to my version of the IPS on my end. I'll look into fixing the spell name length issue while I'm at it and think about submitting a v1.9 / 2.0 when those get fixed.
If you're going to make another update to Chaos Rush's translation, it might be worth giving the entire script another pass to check for line wrapping. I've been playing through v1.8 to refresh my memory of this game and noticed the following (in addition to all of Guy's dialogue, which appears to be intentionally bad), which could easily fit into 2 lines instead of 3:
Spoiler
(After Josef stops the boulder while leaving the Snow Cavern:)
Josef: It's up to...you
now...[Firion]. My sweet
Nelly
Quote from: redmagejoe on February 18, 2020, 11:48:43 PM
What do you think would be the best place to sneak in a JSR $FB00?
Sorry, $FB00 was a typo - I meant to say $FAFB, which first swaps in bank 0 before calling $9880. I gave it a quick test by manually jumping to $FAFB in the debugger, but the game crashes at the next NMI since the NMI vector is still pointing to the $A9A2 that code in bank 5 set, and $A9A2 in bank 0 is not even code, let alone a useful NMI handler. During battle setup, the NMI handler is set to $FA3C and gets called a couple of times without issue, so at $05:$A706 I think we'd need to:
JSR $FA2A ; update the NMI vector to $FA3C
LDA $9E ; load the current character ID
JSR $FAFB ; swap in bank 0, call the stat refresh code, then swap bank 5 back in
JSR $A992 ; set the NMI vector back to $A9A2
Quote from: redmagejoe on February 18, 2020, 11:48:43 PM
Started looking at Dispel, [...] The description of the bug doesn't really tell me exactly WHY it doesn't work, so going to have to do some detective work.
Oh, this one is a little bit hilarious. The routine loads the target's resistance bits from ($A1),Y into A, clears a bunch of them in A, and then
never writes the result back to ($A1),Y. Conveniently, there are 2 useless bytes at $0C:$B807, so shifting $0C:$B809-$0C:$B81C 2 bytes earlier (making sure the BMI still branches to $B83B) and then inserting STA ($A1),Y at $0C:$B81B should do the trick.
I'm not really sure I want to flip the order of resistances though, since that begins to enter the territory of fiddling with game balance. Whether it's better one way or the other isn't something I'm in a position to arbitrarily decide, and I'd rather leave that alone. I'm pretty sure the specific elements are chosen based on the types of enemies you'd encounter at the point in the game where you have the spell at a high enough level to get those resists from it.
The more I think about messing up old existing translation patches, the more I realize that it's better that this overhaul patch inspire updated/new projects to account for a fully working version of the game. After a great deal of internal debate, I've decided to leave the Aura/Barrier patch as it is, as well as the Chaos Rush change I'll eventually upload.
I'll also see about fixing that Dispel issue. The thing is, I have no idea how to test whether it works or not. What addresses should I be watching in RAM? Or is it safe to assume that the code will do what it's meant to so long as that STA is inserted? Actually, come to think of it, I could set a break and watch it in the debugger. I keep forgetting that it tells you what address is being referenced as it steps through those instructions.
Quote from: abw on February 19, 2020, 10:57:56 PM
Oh, this one is a little bit hilarious. The routine loads the target's resistance bits from ($A1),Y into A, clears a bunch of them in A, and then never writes the result back to ($A1),Y. Conveniently, there are 2 useless bytes at $0C:$B807, so shifting $0C:$B809-$0C:$B81C 2 bytes earlier (making sure the BMI still branches to $B83B) and then inserting STA ($A1),Y at $0C:$B81B should do the trick.
"What does your robot do, Sam?"
"It collects data from the surrounding environment, then discards it and drives into walls."
I shouldn't need to worry about the BMI, since it's being shifted up by 2, and then 2 bytes are being added before where it branches to. It should still be $30 $30 in the end. I'm watching RAM now, using WerePanthers (with $50 resist bit, for Body and Death), and already tested that it indeed does nothing. Going to apply the test fix now and see if it gets properly set to $50 (for 4 or less successes), $40 (for 5 or 6 successes), or $00 (for 7+ successes). If that works, I may need to go looking for other monsters with different resistances to fully test this on. That said, even if it always clears all resistances regardless what the messages say, it's still working more than it would have untouched. Doesn't mean I won't be thorough in my fix though. :P
EDIT: What I assume you meant with your advisory was to ensure that the BPL at $0C:$B81B (which will become $B81D) still points back to $B817, since now there's 2 extra bytes that caused a bug with my fix. I have to change the $10 $FA to a $10 $F8 :P
Well, we've got it removing resists now, though I think there's an issue where it handles checks in the opposite way of Aura and Barrier, and it should be running the highest resist first rather than lowest... I need to test this spell on the PSP version (along with Aura and Barrier) and see how it's handling what resists are considered "higher" or "lower" relative to one another. Cool, PSP version doesn't even tell you what effects you got from the spell success. Simply says "Barrier Lv.16" does the animation, end of story. Frustrating... According to resources as relates to Dawn of Souls version, at least, Dispel and Barrier should handle resists in the same order. So I'd actually need to look at reversing the current order of Dispel's layers. It's treating Matter as the highest and Ice as the lowest. I need to figure out if this is another message issue, or actual behavior... What a headache, these layer-based spells.
EDIT: It appears, at least, that it's just another message reverse issue, as I got "Ice Res.Down" and "Body Res.Down" with level manipulation, but $44 was still set on the Bomb enemy, showing that what had actually been affected was its non-set Matter and Fire resists. Should be an easy fix at least. Since Dispel uses its own routine, in that case rather than changing the pointers, I could simply make the INC instructions for the battle message DEC, and change the first loaded offset from #$52 to #$59, so that it works backwards through the messages. Looking at the gymnastics that would have to be done, however, and the fact that I'm already putting this fix in a position to deprecate existing translations, it would really just be easier for me to change the text pointers.
First post updated with the Japanese-based Dispel fix. I've already made an adjustment to my WIP Chaos Rush translation pointers as well.
So back to Protect, I'm watching the relevant places in RAM and the steps it's taking... I think the reason that only the caster is getting the bonus is because of the fact that $B7CF calls on the target's +$27, but only the caster's +$27 is set. Following all the math it's meant to be doing, it appears that the value stored in the caster's +$27 should be caster's Spirit / 4 / # of targets * number of successes. Also even if you get 0 success at a low level, it still says "Defense up!" rather than "Not effective" which doesn't help with misleading the user into thinking they're getting no bonuses when they should. Anyway, the solution as I see it is to either ensure that we're loading the caster's +$27, or somehow load all targets' +$27 with the same value. Because right now the issue is that the multiplication is happening with a 0 going in, so obviously their bonus is going to be 0.
; handler for Spell ID #$DA: Protect
; indirect control flow target (via $BE9E)
0x0337DA|$0C:$B7CA:20 E9 BD JSR $BDE9
0x0337DD|$0C:$B7CD:A0 27 LDY #$27 ; Battle stat offset for Mystery Battle Stat #$27
0x0337DF|$0C:$B7CF:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x0337E1|$0C:$B7D1:85 00 STA $00
0x0337E3|$0C:$B7D3:A9 00 LDA #$00
0x0337E5|$0C:$B7D5:85 01 STA $01
0x0337E7|$0C:$B7D7:A5 48 LDA $48
0x0337E9|$0C:$B7D9:85 02 STA $02
0x0337EB|$0C:$B7DB:A5 49 LDA $49
0x0337ED|$0C:$B7DD:85 03 STA $03
; call to code in a different bank ($0F:$FC98)
0x0337EF|$0C:$B7DF:20 98 FC JSR $FC98 ; 16-bit multiplication (little endian): $00-$01 * $02-$03, product stored in $04-$07; leaves X at #$00, C clear, $00-$01 and Y not changed
0x0337F2|$0C:$B7E2:A0 02 LDY #$02 ; Battle stat offset for Defense
0x0337F4|$0C:$B7E4:18 CLC
0x0337F5|$0C:$B7E5:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x0337F7|$0C:$B7E7:65 04 ADC $04
0x0337F9|$0C:$B7E9:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x0337FB|$0C:$B7EB:90 07 BCC $B7F4
0x0337FD|$0C:$B7ED:A9 FF LDA #$FF
0x0337FF|$0C:$B7EF:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x033801|$0C:$B7F1:4C 73 BE JMP $BE73
I'm going to try changing the LDA ($A1),Y to LDA ($9F),Y instead. That SHOULD be the actor's battle stats rather than the target's. EDIT: ... I CANNOT BELIEVE how simple that fix was. These guys must have been sitting around a table at 3am with no coffee when they were coding this game. Also, it appears that # of successes is calculated for each target rather than before the spell, so a Level 16 Protect from 99 Spirit Guy (99 / 24 / 4 = 6) became 3 successes on Firion (+18), 1 success on Maria (+6), 4 successes on Guy (+24), 6 successes on Char4 (+36). Give this, it seems like Protect is a spell you're much better off using 1 person per turn.
Quote from: abw on February 19, 2020, 10:57:56 PM
If you're going to make another update to Chaos Rush's translation, it might be worth giving the entire script another pass to check for line wrapping. I've been playing through v1.8 to refresh my memory of this game and noticed the following (in addition to all of Guy's dialogue, which appears to be intentionally bad), which could easily fit into 2 lines instead of 3.
I will definitely be giving it a read-over and seeing about consolidating space, though the changes I've already made to it tried to use the available space without moving so as to avoid changing pointers, so there's some excess spaces here and there. Making it use space as efficiently as possible may become its own separate project down the line.
I'll try to update, test, and then shift the Level Up routine before posting the new mock-up for review with the stat refresh code. I'd really like to get these items out of the way so I can focus 100% of my energy on the other items on our list. As always, thank you for pointing me in the right direction. If you get a chance to sit down and peer review my Level Up Rework mock-up at some point, it would be of great help. That's been kind of looming over me for a while and I'd like to commit that to my stable version if possible.
EDIT: I may have misunderstood precisely where to apply this fix. Doing some experimenting though and will report back my results here. So I turned the original JSR $FA2A into a JSR to my free block for testing, and put those instructions. The stat refresh works, BUT there's a graphical glitch that happens for a second (~60 frames) before it returns to normal. So now it's just a matter of figuring out if it has something to do with that second JSR that mentions waiting for sprite 0 and PPU / VRAM control. I'll try dragging that into the test block and/or working around it.
EDIT2: EGADS, it works! Or rather, changing the JSR $FD46 to my test block (and then having the test block start with JSR $FD46 rather than $FA2A, which already runs beforehand) results in a stat refresh, no graphics bugs... So basically to force a stat refresh, we simply insert between $05:$A711 and $05:$A714:
LDA $9E ; load the current character ID
JSR $FAFB ; swap in bank 0, call the stat refresh code, then swap bank 5 back in
JSR $A992 ; set the NMI vector back to $A9A2
Now assuming this doesn't cause other bugs when there's multiple characters gaining level-ups... I can finally rewrite my Level Up Rework and crunch the code again (with the soon-to-be only 8 free bytes outside the level up routines). Updated the rewrite below. I'm pretty sure I didn't miss anything but I could be wrong. I'd like to commit this to stable soon.
https://drive.google.com/open?id=1W-B-13h1aeSUdA4pLN_Nvs400gtRwjcA
Quote from: redmagejoe on February 19, 2020, 11:52:47 PM
I'm not really sure I want to flip the order of resistances though, since that begins to enter the territory of fiddling with game balance.
Yup, just tossing out another idea to consider. The messages for Aura are just meaningless colour names, so "White Aura" is just as good a message for gaining damage against Undead as is is for gaining damage against Magic Beasts. It does affect balance, but some of the other bugfixes have a much greater impact on balance; for instance, the RNG fix doubles the chance of losing Intellect/Stamina/Strength when gaining Strength/Intellect/Spirit, and the bugfix for enemy target-all spells increasing Firion's white magic use counter makes the entire party less susceptible to adverse status effects.
Quote from: redmagejoe on February 19, 2020, 11:52:47 PM
I shouldn't need to worry about the BMI, since it's being shifted up by 2, and then 2 bytes are being added before where it branches to.
The BMI is being shifted up by 2, but its target isn't, so it now needs to skip 2 more bytes than before. This is a total non-issue with labels and an assembler, but it's an extra thing you need to watch out for if you're manually editing hex.
Quote from: redmagejoe on February 19, 2020, 11:52:47 PM
EDIT: What I assume you meant with your advisory was to ensure that the BPL at $0C:$B81B (which will become $B81D) still points back to $B817, since now there's 2 extra bytes that caused a bug with my fix. I have to change the $10 $FA to a $10 $F8 :P
No, I meant what I said. The BPL is fine, since the distance between it and its target doesn't change when they both get shifted by the same amount. If you change it to branch to the LDA ($A1),Y, then you're resetting the current resistances to their original value every time through the loop, so only the final iteration will actually accomplish anything.
I had no idea that there was such a drastic change involved with the RNG fix. Should we try to adjust the ratio then for losing a stat? It's supposed to be roughly 1/5 chance to lose a stat when a stat is gained. We only have 5-8 bytes to work with post rework. I don't know if that requires changing an existing ratio somewhere, or if we'd have to make one. In the second case, I'm assuming you mean that the party is less susceptible as a result of having higher MDef. That's intended though if it had worked properly from the start, so I'm not concerned with that, but I AM concerned with the (what I can only assume to be) intended chance to lose a stat being higher than usual.
EDIT: I see in the code where in data it's calling for those chances from $AC41, $AC42, and $AC43. I'll look at the numbers, contemplate the new math (uniform distribution rather than 16/33/33/16) and figure out how they should be adjusted. Would this be as simple as increasing them to 10 ($0A) to yield the same result?
; chance to lose Intellect when gaining Strength
; data load target (from $A682)
0x016C51|$05:$AC41:05
; chance to lose Stamina when gaining Intellect
; data load target (from $A69F)
0x016C52|$05:$AC42:05
; chance to lose Strength when gaining Spirit
; data load target (from $A6BC)
0x016C53|$05:$AC43:05
Dispel Fix has been updated. Just had to change a 30 to a 32. The Protect fix, at least, was literally a single byte change. Also, first version of the complete Level Up Rework is finally up. There's a couple of questions I have regarding some SBCs, as well the necessity of a specific JSR, but this is a mostly working product at least and clears another item off our list. I can always shift code back by 3 and update all the pointers now that I have abw's version that I can do a direct byte comparison to. This has been a comprehensive effort between abw and myself (learning as I go!), and will be credited as such.
https://drive.google.com/open?id=1W-B-13h1aeSUdA4pLN_Nvs400gtRwjcA
Looking at Ripper again, I'm wondering how feasible it is to simply rearrange instructions here such that the special effects are determined before the damage is calculated. In other words, can the code from 0x03311C be run prior to 0x0330E4?
; control flow target (from $B0CC)
0x0330E4|$0C:$B0D4:A0 0A LDY #$0A ; Battle stat offset for Current HP low byte
0x0330E6|$0C:$B0D6:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x0330E8|$0C:$B0D8:38 SEC
0x0330E9|$0C:$B0D9:E5 9A SBC $9A
0x0330EB|$0C:$B0DB:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x0330ED|$0C:$B0DD:C8 INY ; Battle stat offset for Current HP high byte
0x0330EE|$0C:$B0DE:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x0330F0|$0C:$B0E0:E5 9B SBC $9B
0x0330F2|$0C:$B0E2:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x0330F4|$0C:$B0E4:90 08 BCC $B0EE
0x0330F6|$0C:$B0E6:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x0330F8|$0C:$B0E8:88 DEY ; Battle stat offset for Current HP low byte
0x0330F9|$0C:$B0E9:11 A1 ORA ($A1),Y ; pointer to target's battle stats
0x0330FB|$0C:$B0EB:D0 1F BNE $B10C
0x0330FD|$0C:$B0ED:C8 INY ; Battle stat offset for Current HP high byte
; control flow target (from $B0E4)
0x0330FE|$0C:$B0EE:A9 00 LDA #$00
0x033100|$0C:$B0F0:88 DEY ; Battle stat offset for Current HP low byte
0x033101|$0C:$B0F1:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x033103|$0C:$B0F3:C8 INY ; Battle stat offset for Current HP high byte
0x033104|$0C:$B0F4:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x033106|$0C:$B0F6:20 9A BF JSR $BF9A
0x033109|$0C:$B0F9:20 76 AF JSR $AF76 ; set A = Ailment (persistent) (80: KO, 40: Stone, 20: Toad, 10: Amnesia, 08: Curse, 04: Venom, 02: Blind, 01: ??) of entity at ($A1); leaves Y at #$08
0x03310C|$0C:$B0FC:A0 2C LDY #$2C ; Battle stat offset for previous Ailment (persistent) (80: KO, 40: Stone, 20: Toad, 10: Amnesia, 08: Curse, 04: Venom, 02: Blind, 01: ??)
0x03310E|$0C:$B0FE:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x033110|$0C:$B100:A0 08 LDY #$08 ; Battle stat offset for Ailment (persistent) (80: KO, 40: Stone, 20: Toad, 10: Amnesia, 08: Curse, 04: Venom, 02: Blind, 01: ??)
0x033112|$0C:$B102:09 80 ORA #$80 ; KO
0x033114|$0C:$B104:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x033116|$0C:$B106:A5 28 LDA $28
0x033118|$0C:$B108:09 20 ORA #$20
0x03311A|$0C:$B10A:85 28 STA $28
; control flow target (from $B0EB)
0x03311C|$0C:$B10C:A0 1D LDY #$1D ; Battle stat offset for Primary Hand Weapon Special Effect (20:Heal, 10:Ripper, 08:Drain MP, 04:Drain HP, 02:inflict temporary Ailment based on bits 7-2, 01:inflict persistent Ailment based on bits 7-2)
0x03311E|$0C:$B10E:B1 9F LDA ($9F),Y ; pointer to actor's battle stats
0x033120|$0C:$B110:F0 12 BEQ $B124
0x033122|$0C:$B112:85 60 STA $60 ; Primary Hand Weapon Special Effect
0x033124|$0C:$B114:29 01 AND #$01
0x033126|$0C:$B116:F0 03 BEQ $B11B
0x033128|$0C:$B118:4C 51 B3 JMP $B351
; control flow target (from $B116)
0x03312B|$0C:$B11B:A5 60 LDA $60 ; Primary Hand Weapon Special Effect
0x03312D|$0C:$B11D:29 02 AND #$02
0x03312F|$0C:$B11F:F0 0C BEQ $B12D
0x033131|$0C:$B121:4C 5F B3 JMP $B35F
; control flow target (from $B110)
0x033134|$0C:$B124:60 RTS
Quote from: redmagejoe on February 22, 2020, 05:00:14 PM
I had no idea that there was such a drastic change involved with the RNG fix.
The RNG affects a lot of stuff, and the smaller the RNG output range is, the greater the impact of fixing it to be balanced is. In the case of stat loss, the game rolls a random number in the range [0..5] and you only lose a stat on 0, so the intent appears to have been to have a 1/6 chance to lose a stat. But with the original unbalanced RNG, 0 and 5 were only 50% as likely to occur as 1..4, so the actual chance was more like 1/12. If you wanted to maintain the 1/12 chance with a balanced RNG, then you'd want to roll numbers in the range [0..11], so updating the loss chances at $05:$AC41-$05:$AC43 to 0B would be the thing to do.
Quote from: redmagejoe on February 22, 2020, 05:00:14 PM
Looking at Ripper again, I'm wondering how feasible it is to simply rearrange instructions here such that the special effects are determined before the damage is calculated. In other words, can the code from 0x03311C be run prior to 0x0330E4?
I like this idea, and gave it a try. Battle poses are still not getting updated properly when the Drain effect changes your critical status; after checking, that is also another error in the original game. It'll take some more investigation to find where the battle poses are controlled from, but for now, try this (https://drive.google.com/open?id=1TsgX5PShWBDcCQuRjIUIgrcYlYOEF8L0).
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
- Investigate possible overflow on Evasion % when Agility/Evasion level and Shield equipped modifier add up.
In other news, I took a peek at this one, and I think the current code is okay - it's doing 16-bit arithmetic, and a quick test with 2 Aegis Shields, level 16 Shield skill, and 99 agility still gets me Evasion % capped at 99.
First post has been updated with new RNG Fix (with adjusted stat loss chance values) and the final version of the Level Up Routines Rework.
Let me see if I can figure out precisely what you changed. Didn't see an ASM in the 7z, but I am nothing if not comfortable in deciphering hex now. :)
https://pastebin.com/5QxgHa19
Mostly done, but need sleep now... Still commenting this referencing the original disASM to see what's doing what, but this should be all the instructions at least.
Also, I'll need to double check, but I was definitely noticing some bizarre behavior with the value of my evasion on one of my characters. I'll do some more testing.
EDIT: Yeah, 7-99% on Firion with 99 Agility, and a Diamond Shield. I put on a second Diamond Shield, and suddenly he's 7-50%. There's SOMETHING fishy happening with shields and Evasion. Watching RAM now, and sure enough, with no shields he's at $44, one shield he's at $63, and then at two shields he's at $32.
Quote from: redmagejoe on February 24, 2020, 12:51:15 AM
Let me see if I can figure out precisely what you changed. Didn't see an ASM in the 7z, but I am nothing if not comfortable in deciphering hex now. :)
Ah, sorry, I've updated the link to include the ASM file too.
Quote from: redmagejoe on February 24, 2020, 12:51:15 AM
EDIT: Yeah, 7-99% on Firion with 99 Agility, and a Diamond Shield. I put on a second Diamond Shield, and suddenly he's 7-50%. There's SOMETHING fishy happening with shields and Evasion. Watching RAM now, and sure enough, with no shields he's at $44, one shield he's at $63, and then at two shields he's at $32.
I haven't been able to reproduce this myself. What's your shield skill level and other equipment?
I'll grab a few screenshots, give me a moment.
https://imgur.com/a/MRR2CsR
And oddly, now it's at 85% ($68) with no shields. There's some strange math happening when equipping shields, methinks. Note that this has been happening since before I touched bug fixes, so it's at least not a side effect of the equipment stat bonus stacking patch.
Sounds like Evasion% (the actual percentage) continues to store values above 99, even though it caps at 99, and then overflows when it reaches 256.
Quote from: redmagejoe on February 24, 2020, 08:37:05 PM
I'll grab a few screenshots, give me a moment.
Okay, that definitely helps. The calculation itself is 16-bit, but the high byte of the result isn't taken into account when capping the low byte (which is what gets written back to character stats at ($7A),Y) at 99. Give this (https://drive.google.com/open?id=1HN5PGbZ3Be56UpuxUHgzQam3Cu-Qqvd8) a try!
I'll take a look when I can. Got my hands full fixing the Combat Counter Overflow / Firion Spirit Magic Counter patch. LeviathanMist found an oversight in my code that led to (as you'd warned me in the past), back row characters getting attacked physically. I think the issue is with that minor difference in those two looping checks to acquire a target, where the first one has a BEQ and a BNE, and the second only has a BNE. I'm testing taking the branches out of the subroutine I made (which then only has the identical instructions of the two subs), and I JUST BARELY have enough space to make that work. Hopefully this fixes it. I'll post my updated mock-up here before putting up the fixed patch.
I seem to have narrowed the problem down to having something to do with my JSRs to the combat counter overflow sub... Odd. I'll see if I can't figure out where the problem is. EDIT: I see the problem! I'm manipulating A beyond what the game is expecting, so rather than having the JSR to the counter overflow check in place of the old INC, I put it BEFORE A is loaded with $9E (character index), so that once it returns from doing its counter overflow check, A is properly loaded. After all, I don't need $9E in A going into my sub, as it ADCs $9E already. Sure enough, this fixed the issue.
Finalized / Fixed Combat Counter Overflow Fix
; control flow target (from $A525)
0x03254A|$0C:$A53A:A0 2A LDY #$2A
0x03254C|$0C:$A53C:B1 44 LDA ($44),Y
0x03254E|$0C:$A53E:D0 23 BNE $A563
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
; control flow target (from $A545, $A54F, $A558)
0x03255A|$0C:$A54A:20 88 A5 JSR $A588
0x03255D|$0C:$A54D:F0 02 BEQ $A551
0x03255F|$0C:$A54F:D0 F9 BNE $A54A
; control flow target (from $A54D)
0x032561|$0C:$A551:A0 35 LDY #$35
0x032563|$0C:$A553:B1 7E LDA ($7E),Y
0x032565|$0C:$A555:4A LSR
0x032566|$0C:$A556:90 02 BCC $A55A
0x032568|$0C:$A558:B0 F0 BCS $A54A
; control flow target (from $A556)
0x03256D|$0C:$A55A:20 52 F8 JSR $F852
0x03256A|$0C:$A55D:A5 9E LDA $9E
0x03256C|$0C:$A55F:AA TAX
0x032570|$0C:$A560:4C 10 A6 JMP $A610
; control flow target (from $A53E)
0x032573|$0C:$A563:A0 2B LDY #$2B
0x032575|$0C:$A565:B1 44 LDA ($44),Y
0x032577|$0C:$A567:C9 01 CMP #$01
0x032579|$0C:$A569:D0 0F BNE $A57A
;; New Increment Loop only used after LDA #$08
0x03257B|$0C:$A56B:A2 48 LDX #$48
0x03257D|$0C:$A56D:20 64 F8 JSR $F864
0x032580|$0C:$A570:E8 INX
0x032581|$0C:$A571:E0 4C CPX #$4C
0x032583|$0C:$A573:D0 F8 BNE $A56F
0x032585|$0C:$A575:A9 08 LDA #$08
0x032587|$0C:$A577:4C 10 A6 JMP $A610
; control flow target (from $A569)
0x03258A|$0C:$A57A:C9 02 CMP #$02
0x03258C|$0C:$A57C:D0 1C BNE $A59A
; control flow target (from $A57D)
0x03258E|$0C:$A57E:20 88 A5 JSR $A588
0x032591|$0C:$A581:D0 FB BNE $A57E
0x032593|$0C:$A583:A5 9E LDA $9E
0x032595|$0C:$A585:4C 0C A6 JMP $A60C
; control flow target (from $A54A, $A57A, $A595)
0x032598|$0C:$A588:A2 00 LDX #$00
0x03259A|$0C:$A58A:A9 03 LDA #$03
0x03259C|$0C:$A58C:20 11 FD JSR $FD11
0x03259F|$0C:$A58F:85 9E STA $9E
0x0325A1|$0C:$A591:20 E1 96 JSR $96E1
0x0325A4|$0C:$A594:20 6C AF JSR $AF6C
0x0325A7|$0C:$A597:29 C0 AND #$C0
0x0325A9|$0C:$A599:60 RTS
0x031478|$0C:$9468:20 4E F8 JSR $F84E ; Character #1 physical attack counter
0x031E68|$0C:$9E58:20 48 F8 JSR $F848 ; Character #1 spell slot #1 battle use counter
0x031E75|$0C:$9E65:20 5A F8 JSR $F85A ; Character #1 black magic use counter
0x031E7A|$0C:$9E6A:20 5E F8 JSR $F85E ; Character #1 white magic use counter
0x03256A|$0C:$A55A:20 52 F8 JSR $F852 ; Character #1 counter for times physically attacked by enemy
0x03257D|$0C:$A56D:20 64 F8 JSR $F864 ; Character #1 counter for times magically attacked by enemy (full party)
0x03261D|$0C:$A60D:20 56 F8 JSR $F856 ; Character #1 counter for times magically attacked by enemy
0x03F858|$0C:$F848:8A TXA ; JSR here from $9E58
0x03F859|$0C:$F849:18 CLC
0x03F85A|$0C:$F84A:69 04 ADC #$04
0x03F85C|$0C:$F84C:D0 15 BNE $F863 ; finish necessary math and jump down to rest of subroutine
0x03F85E|$0C:$F84E:A9 00 LDA #$00 ; JSR here from $9468
0x03F860|$0C:$F850:F0 0E BEQ $F860
0x03F862|$0C:$F852:A9 44 LDA #$44 ; JSR here from $A55A
0x03F864|$0C:$F854:D0 0A BNE $F860
0x03F866|$0C:$F856:A9 48 LDA #$48 ; JSR here from $A60D
0x03F868|$0C:$F858:D0 06 BNE $F860
0x03F86A|$0C:$F85A:A9 4C LDA #$4C ; JSR here from $9E65
0x03F86C|$0C:$F85C:D0 02 BNE $F860
0x03F86E|$0C:$F85E:A9 50 LDA #$50 ; JSR here from $9E6A
0x03F870|$0C:$F860:18 CLC
0x03F871|$0C:$F861:65 9E ADC $9E ; adds character ID offset with the offset loaded into A before the JSR
0x03F873|$0C:$F863:AA TAX
0x03F874|$0C:$F864:BD F3 7C LDA $7CF3,X ; JSR here from $A56D, load value from counter address now modified by counter offset and player id
0x03F877|$0C:$F867:C9 C8 CMP #$C8 ; compare to overflow protection (200)
0x03F879|$0C:$F869:B0 03 BCS $F873 ; skip increasing
0x03F87B|$0C:$F86B:FE F3 7C INC $7CF3,X ; increase value
0x03F87E|$0C:$F86E:60 RTS
Back to the newest items, I'll take a look at the evasion fix more closely since that seems easier to digest than the special weapons effect fix and how we can address the battle pose situation. Having said that, I am in awe of how quickly you were able to take a simple musing and turn it into a working reality abw. Nice work on the patch! :)
Quote from: redmagejoe on February 25, 2020, 01:24:18 AM
0x03257B|$0C:$A56B:A9 08 LDA #$08
;; New Increment Loop only used after LDA #$08
0x03257D|$0C:$A56D:A2 48 LDX #$48
0x03257F|$0C:$A56F:20 74 F8 JSR $F874
0x032582|$0C:$A572:E8 INX
0x032583|$0C:$A573:E0 4C CPX #$4C
0x032585|$0C:$A575:D0 F8 BNE $A56F
0x032587|$0C:$A577:4C 10 A6 JMP $A610
[...]
0x03261D|$0C:$A60D:20 74 F8 JSR $F874 ; Character #1 counter for times magically attacked by enemy
I think you meant JSR $F8
64 in both places instead. It's really past time to start using an assembler :P. Also, you need to do the LDA #$08
after the loop, otherwise everybody gets credit for a target-all spell but the spell actually hits an entity determined by character #4's counter, which will probably crash the game once character #4's counter gets past 8, and will probably have bizarre effects even before that.
If you still need more space, you can save at least 6 more bytes by moving the BNEs after each JSR $A588 call into the $A588 routine (it's just looping until it finds a non-dead, non-stone target) and by eliminating some useless branches:
0x03255D|$0C:$A54D:F0 02 BEQ $A551
0x032566|$0C:$A556:90 02 BCC $A55A
Quote from: redmagejoe on February 25, 2020, 01:24:18 AM
Back to the newest items, I'll take a look at the evasion fix more closely since that seems easier to digest than the special weapons effect fix and how we can address the battle pose situation. Having said that, I am in awe of how quickly you were able to take a simple musing and turn it into a working reality abw. Nice work on the patch! :)
It would have been faster if I hadn't also been watching TV at the time :D. Thanks for taking a look - it's always nice to get a second set of eyes on these changes, especially in cases where I've been coding while distracted!
Updated my documentation. I'd actually failed to update the documentation above (now corrected) but applied the actual behavior that is listed there. No, I wanted F864 (you're right there) for only the target all situation, and the F856 for a single-target spell. Sorry about the sloppiness. Everything should reflect what the IPS is doing now. I didn't catch the LDA 08 issue, so thank you for noticing that.
One more thing:
0x031E75|$0C:$9E65:20 5A F8 JSR $F85A ; Character #1 black magic use counter
0x031E78|$0C:$9E68:D0 03 BNE $9E6D
; control flow target (from $9E63)
0x031E7A|$0C:$9E6A:20 5E F8 JSR $F85E ; Character #1 white magic use counter
This results in credit for both black and white magic use when $F85A exits with Z set, which happens when $7CF3,X is exactly #$C8; the original code has the same problem when $7CF3,X wraps around to 0. Not a huge issue in practical terms, but something to consider. Conversely,
0x03261D|$0C:$A60D:20 74 F8 JSR $F856 ; Character #1 counter for times magically attacked by enemy
; control flow target (from $A537, $A56E, $A5A2, $A5AB, $A5CA, $A609)
0x032620|$0C:$A610:A0 2B LDY #$2B
0x032622|$0C:$A612:91 44 STA ($44),Y
ends up using the old value of the counter as the command's target, which is a bigger problem. One possible fix is something like this:
; $0C:$F856; JSR here from $A60D
PHA
LDA #$48
JSR $F860
PLA
RTS
Since that's an issue with the original code, I'm not too worried, especially for how unlikely it is to even reach that cap. But just to be thorough, what would be the best way to address this? I'm assuming the idea of the original BNE is to always be taken so that white and black magic counters aren't both incrementing. Is there a better way to achieve an "always branch" situation here?
As for the returning A issue, what if I were to simply put LDA $9E before the RTS, such that no matter what, $9E is properly loaded back into the Accumulator once it returns from that subroutine? I have the room to do that since it's in our free space. Going into any of these counters, it's assumed that A currently holds $9E, correct? That way I could also remove the LDA $9E and TAX from the increment loop I wrote for magic-on-whole-party situation.
Hmm, no watching RAM, that doesn't quite work. I guess it should involve PHA and PLA. Let me play with that and test it while watching RAM.
Quote from: redmagejoe on February 25, 2020, 08:50:15 PM
Is there a better way to achieve an "always branch" situation here?
If this were 65816 on the SNES, BRA would be the way to go, but for 6502, JMP is the shortest alternative, which doesn't fit into the 2 bytes available. The first thing that comes to mind is expanding the capping routine:
; $0C:$F85A; JSR here from $9E65
LDA #$4C
JSR $F860
LDA #$01
RTS
Quote from: redmagejoe on February 25, 2020, 08:50:15 PM
Going into any of these counters, it's assumed that A currently holds $9E, correct?
Not necessarily, no. A is set to different things at different calls. Most of the time the value of A after the call to the capping routine doesn't matter, but in a couple of places it does.
I will expand my capping code then. Would you review and confirm or refute my assumptions about these branches, as outlined below kind sir?
0x031478|$0C:$9468:20 4E F8 --- is fine as it is, A is set before it's needed again
0x031E68|$0C:$9E58:20 48 F8 --- is fine as it is, A is set before it's needed again
0x031E75|$0C:$9E65:20 5A F8 --- is fine as it is, A is set before it's needed again
0x031E7A|$0C:$9E6A:20 5E F8 --- is fine as it is, A is set before it's needed again
0x03256A|$0C:$A55A:20 52 F8 --- is fine, as the LDA $9E needed for original code is shifted (the TAX isn't needed though, right?)
0x03257D|$0C:$A56D:20 64 F8 --- is being set to #$08 now, which I ASSUME is what A needs to be heading back into original code
0x03261D|$0C:$A60D:20 56 F8 --- is the only one having A modified by us before it is needed by original code, so PHA and PLA in that single branch should suffice
Assuming that my assumptions about the other two "not an issue" branches are true, how does this look?
; control flow target (from $A525)
0x03254A|$0C:$A53A:A0 2A LDY #$2A
0x03254C|$0C:$A53C:B1 44 LDA ($44),Y
0x03254E|$0C:$A53E:D0 23 BNE $A563
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
; control flow target (from $A545, $A54F, $A558)
0x03255A|$0C:$A54A:20 88 A5 JSR $A588
0x03255D|$0C:$A54D:F0 02 BEQ $A551
0x03255F|$0C:$A54F:D0 F9 BNE $A54A
; control flow target (from $A54D)
0x032561|$0C:$A551:A0 35 LDY #$35
0x032563|$0C:$A553:B1 7E LDA ($7E),Y
0x032565|$0C:$A555:4A LSR
0x032566|$0C:$A556:90 02 BCC $A55A
0x032568|$0C:$A558:B0 F0 BCS $A54A
; control flow target (from $A556)
0x03256A|$0C:$A55A:20 52 F8 JSR $F852
0x03256D|$0C:$A55D:A5 9E LDA $9E
0x03256F|$0C:$A55F:AA TAX
0x032570|$0C:$A560:4C 10 A6 JMP $A610
; control flow target (from $A53E)
0x032573|$0C:$A563:A0 2B LDY #$2B
0x032575|$0C:$A565:B1 44 LDA ($44),Y
0x032577|$0C:$A567:C9 01 CMP #$01
0x032579|$0C:$A569:D0 0F BNE $A57A
;; New Increment Loop only used after LDA #$08
0x03257B|$0C:$A56B:A2 48 LDX #$48
0x03257D|$0C:$A56D:20 6C F8 JSR $F86C
0x032580|$0C:$A570:E8 INX
0x032581|$0C:$A571:E0 4C CPX #$4C
0x032583|$0C:$A573:D0 F8 BNE $A56F
0x032585|$0C:$A575:A9 08 LDA #$08
0x032587|$0C:$A577:4C 10 A6 JMP $A610
; control flow target (from $A569)
0x03258A|$0C:$A57A:C9 02 CMP #$02
0x03258C|$0C:$A57C:D0 1C BNE $A59A
; control flow target (from $A581)
0x03258E|$0C:$A57E:20 88 A5 JSR $A588
0x032591|$0C:$A581:D0 FB BNE $A57E
0x032593|$0C:$A583:A5 9E LDA $9E
0x032595|$0C:$A585:4C 0C A6 JMP $A60C
; control flow target (from $A54A, $A57A, $A595)
0x032598|$0C:$A588:A2 00 LDX #$00
0x03259A|$0C:$A58A:A9 03 LDA #$03
0x03259C|$0C:$A58C:20 11 FD JSR $FD11
0x03259F|$0C:$A58F:85 9E STA $9E
0x0325A1|$0C:$A591:20 E1 96 JSR $96E1
0x0325A4|$0C:$A594:20 6C AF JSR $AF6C
0x0325A7|$0C:$A597:29 C0 AND #$C0
0x0325A9|$0C:$A599:60 RTS
0x031478|$0C:$9468:20 4E F8 JSR $F84E ; Character #1 physical attack counter
0x031E68|$0C:$9E58:20 48 F8 JSR $F848 ; Character #1 spell slot #1 battle use counter
0x031E75|$0C:$9E65:20 5E F8 JSR $F85E ; Character #1 black magic use counter
0x031E7A|$0C:$9E6A:20 66 F8 JSR $F866 ; Character #1 white magic use counter
0x03256A|$0C:$A55A:20 52 F8 JSR $F852 ; Character #1 counter for times physically attacked by enemy
0x03257D|$0C:$A56D:20 6C F8 JSR $F86C ; Character #1 counter for times magically attacked by enemy (full party)
0x03261D|$0C:$A60D:20 56 F8 JSR $F856 ; Character #1 counter for times magically attacked by enemy
0x03F858|$0C:$F848:8A TXA ; JSR here from $9E58
0x03F859|$0C:$F849:18 CLC
0x03F85A|$0C:$F84A:69 04 ADC #$04
0x03F85C|$0C:$F84C:D0 1D BNE $F86B ; finish necessary math and jump down to rest of subroutine
0x03F85E|$0C:$F84E:A9 00 LDA #$00 ; JSR here from $9468
0x03F860|$0C:$F850:F0 16 BEQ $F868
0x03F862|$0C:$F852:A9 44 LDA #$44 ; JSR here from $A55A
0x03F864|$0C:$F854:D0 12 BNE $F868
0x03F866|$0C:$F856:48 PHA
0x03F867|$0C:$F857:A9 48 LDA #$48 ; JSR here from $A60D
0x03F869|$0C:$F859:20 68 F8 JSR $F868
0x03F86C|$0C:$F85C:68 PLA
0x03F86D|$0C:$F85D:60 RTS
0x03F86E|$0C:$F85E:A9 4C LDA #$4C ; JSR here from $9E65
0x03F870|$0C:$F860:20 68 F8 JSR $F868
0x03F873|$0C:$F863:A9 01 LDA #$01
0x03F875|$0C:$F865:60 RTS
0x03F876|$0C:$F866:A9 50 LDA #$50 ; JSR here from $9E6A
0x03F878|$0C:$F868:18 CLC
0x03F879|$0C:$F869:65 9E ADC $9E ; adds character ID offset with the offset loaded into A before the JSR
0x03F87B|$0C:$F86B:AA TAX
0x03F87C|$0C:$F86C:BD F3 7C LDA $7CF3,X ; JSR here from $A56D, load value from counter address now modified by counter offset and player id
0x03F87F|$0C:$F86F:C9 C8 CMP #$C8 ; compare to overflow protection (200)
0x03F881|$0C:$F871:B0 03 BCS $F876 ; skip increasing
0x03F883|$0C:$F873:FE F3 7C INC $7CF3,X ; increase value
0x03F886|$0C:$F876:60 RTS
Let's see here... for $A55A, you do need at least the LDA $9E, since that's the target to use after $A610. I don't see X being used for a while afterwards, so I'm not sure whether that TAX is still needed or not. $A56D definitely needs to set A to #$08 before $A610 in order for target-all spells to work. In this revised code, I think there's only one path that goes through $A60D now, and it already has A set correctly, so I guess we don't actually need the PHA/PLA and I was just getting mixed up with the original code :P.
Alright, so I can crunch the code back a little, though I'm still going to have that one JSR to avoid the black+white magic counter situation. This should be good to go, yeah? I'm leaving that TAX alone because it's one byte and not worth the energy crunching until we get to a point where we're done with bug-fixes and just need to do some major code crunch for more major enhancements.
I'll have a closer look at the new patches you dropped tomorrow. Got a lot on my plate in the morning. Just to be clear, your special effects patch currently makes Ripper actually do its +20 per hit damage, and has what effect upon Blood Sword (Drain # of hits / 16 target's max HP) and undead hit by Heal Staff (meant to do 0 damage and Cure 1 * level = number of hits to non-undead, but 78 base attack modified by family bonus against undead, and then an ADDITIONAL bonus damage on top of that, but NOT HEAL THEM like it currently does)?
Quote from: abw on February 25, 2020, 11:24:08 PM
In this revised code, I think there's only one path that goes through $A60D now, and it already has A set correctly, so I guess we don't actually need the PHA/PLA and I was just getting mixed up with the original code :P.
Actually, I take that back - I was right the first time and we do need the PHA/PLA on the call from $A60D. A is set correctly as input for $F856, but $F856's output in A is incorrect for setting the spell's target at $A612. Sorry about the confusion :(.
Quote from: redmagejoe on February 25, 2020, 11:26:04 PM
Just to be clear, your special effects patch currently makes Ripper actually do its +20 per hit damage, and has what effect upon Blood Sword (Drain # of hits / 16 target's max HP) and undead hit by Heal Staff (meant to do 0 damage and Cure 1 * level = number of hits to non-undead, but 78 base attack modified by family bonus against undead, and then an ADDITIONAL bonus damage on top of that, but NOT HEAL THEM like it currently does)?
Let me do another pass on this one first - I just noticed I accidentally omitted the section of code for killing the attacker when Blood Sword vs. Undead drains the attacker dry, and I want to check whether the game displays any message in that case.
Mechanically, the intent is to make Ripper and Heal Staff (vs. Undead only) actually inflict the damage their battle messages display (Heal Staff doesn't actually heal Undead; only the base damage is inflicted, and like Ripper, the bonus damage is only displayed, not inflicted). As a consequence of moving the special effect code up, Heal Staff against non-Undead now sets its base damage to 0 and directly heals the target instead of inflicting the weapon's base damage and then negating it and adding the heal effect, which means Heal Staff will no longer be extra effective against targets that were killed by its base damage.
GUI fixes should include displaying the usual death message when a target gets killed by the bonus damage from a special effect (currently messages are only displayed if the target is killed by the base damage) and updating their critical HP/battle pose (still WIP), and now that I've noticed it, I'm also going to investigate death messages for when an attacker commits suicide via trying to drain Undead.
Is this all compatible with ChaosRush's translation? I figure it probably is, but when I did the patches I ended up with a weird issue.
When I'd get attacked, the enemy would sometimes have hit sparks on them. I think it might be evasion related since I'd take no damage, but I have no idea. I'm not sure if this is an already known issue or not, so I wanted to report it. I'm using an Everdrive N8 in an AVS not an emu. When I tried a translated only (no patches from this topic) ROM, I had no issues with it.
Also, are you going to have an optional patch to get rid of the penalties to INT that most equipment has? It makes sense for it to be there, but it really ruins magic.
Quote from: Rabite890 on February 26, 2020, 10:57:12 AM
Is this all compatible with ChaosRush's translation? I figure it probably is, but when I did the patches I ended up with a weird issue.
When I'd get attacked, the enemy would sometimes have hit sparks on them. I think it might be evasion related since I'd take no damage, but I have no idea. I'm not sure if this is an already known issue or not, so I wanted to report it. I'm using an Everdrive N8 in an AVS not an emu. When I tried a translated only (no patches from this topic) ROM, I had no issues with it.
That issue is most likely due to the reversal on a change I'd made that abw just pointed out. I need to go back and fix that... It's on the Combat Counter Overflow Fix. Please bear with me while I revert the change. As for the Chaos Rush translation, it should be applied after all other patches listed here.
EDIT: Thankfully Google Drive keeps past versions. Just deleted the newest version, as the previous version should be the working one. Please apply the patches in this thread, including the fixed Combat Counter Overflow Fix, to a fresh (J) [!] ROM, then Chaos Rush's latest available translation. If you are still having bugs with enemy attacks on the party, please report them here. Also bear in mind that Dispel and Barrier will report the wrong (see: in reverse) resistances compared to what resists they actually affect. This is a bug native to the game, Japanese version and all translations. I have a version of Chaos Rush's translation with the messages properly reversed/fixed, but would like to make some other changes before I release it onto the site.
Quote from: Rabite890 on February 26, 2020, 10:57:12 AM
Also, are you going to have an optional patch to get rid of the penalties to INT that most equipment has? It makes sense for it to be there, but it really ruins magic.
I will add this to the first post, and consider it as an optional patch since we're already looking at improvements to bring things in line with the remakes. It would indeed be optional and not part of the main patch.
@abw
So as of now, Ripper works properly, as it deals its shown damage of +20 per hit. Heal Staff works properly on non-undead targets, as it does no damage and heals them.
Are you saying that Heal Staff was NOT actually healing undead before, or that you've FIXED it to not heal them? Also, is it doing its family damage and bonus damage to Undead either pre- or post- fix? I'm curious about what the code is currently doing versus what it's doing in your ASM, as I'm trying to better navigate around the instructions and commit certain batches of instructions to memory, as to what they're doing. I can already recognize certain things the game is doing just based on the instructions. As for the Blood Sword, I'll leave you to that and the other WIPs while I mentally unpack the Evasion Overflow fix. :) Work is being very distracting, so apologies if it's slow progress. After that Combat Counter Overflow Fix, I've become super overly cautious about commits to stable.
"That issue is most likely due to the reversal on a change I'd made that abw just pointed out. I need to go back and fix that... It's on the Combat Counter Overflow Fix. Please bear with me while I revert the change. As for the Chaos Rush translation, it should be applied after all other patches listed here.
EDIT: Thankfully Google Drive keeps past versions. Just deleted the newest version, as the previous version should be the working one. Please apply the patches in this thread, including the fixed Combat Counter Overflow Fix, to a fresh (J) [!] ROM, then Chaos Rush's latest available translation. If you are still having bugs with enemy attacks on the party, please report them here. Also bear in mind that Dispel and Barrier will report the wrong (see: in reverse) resistances compared to what resists they actually affect. This is a bug native to the game, Japanese version and all translations. I have a version of Chaos Rush's translation with the messages properly reversed/fixed, but would like to make some other changes before I release it onto the site.
I will add this to the first post, and consider it as an optional patch since we're already looking at improvements to bring things in line with the remakes. It would indeed be optional and not part of the main patch."
That's what I figured. It looked like some sort of overflow. Also, good to know on the correct order for the translation. I'll make sure to redownload it. Thanks.
I agree fully on the "fix" being an optional patch. There's going to some purists out there who don't want that feature and shouldn't have to play with it.
I'm just glad someone is taking the time to fix the issues, unlike Demonic Pandemonium which was a good attempt but didn't fix the big issues. Thank you for your work.
Thank you for your interest in the project! And I, for one, am okay with the intention behind INT/SPR penalties. Given that they were removed in remakes, however, and it's a controversial change, I don't see why we can't work on an optional patch so people can choose whether they want to play "the original", an NES version of the remake, or an "enhanced original", since we've got a Mysidian Tower Orbs buff and consideration for a weapon-switching experience tracking feature, among other things.
My hope is for this to serve as another definitive version similar to Final Fantasy Restored for the first game, even if neither one has Dawn of Souls content.
Watching LeviathanMist's playtest stream, it seems the RNG may still be taking too long, abw. He gained... I'm not sure how many stats, but it seems like whenever a character's stats went up he would get that same graphics glitch. Is the new RNG still taking too many cycles compared to the old one? I'm assuming that's what the issue is? Supposedly the bug was happening before every character's stat ups, but when Leviathan and I were watching specifically for it, I must have blinked as I only saw it happen before Minwu (Char4). EDIT: He's streaming at 30fps, but he's playing it at 60fps obviously, so I didn't see the frames he did. Assume it happens for every character. It seems to happen after or as their name box is written before the actual stat up messages.
So, two things I noticed:
1. RNG seems to still favor the middle two characters for enemy targeting. Firion's HP is much lower than Maria's and Guy's.
2. Weapon exp is not going up uniformly for every character, despite the fact that I am always using the attack command with all four characters at the same time, otherwise casting with the top 3 characters and attacking with Minwu. This is not due to target-canceling either, as it's Maria with the highest weapon exp, and the only target-cancels I've done are with Firion by accident. Also, shouldn't Minwu have much higher weapon exp than the other four characters, since he's attacking while the others are casting? For reference, my weapon exp values are as follows: Firion 1-87%, Maria 1-91%, Guy 1-87%, Minru 1-88%
I haven't done extensive testing on the RNG to figure out what the actual math will come out to for bias towards the characters, but it may simply be a case of too small sample size at this point in the game? I don't know. I do know we may have to review the RNG anyway to see if we can't address the graphics bug on stat ups.
As for the Weapon Experience, with the numbers and situations you provided, I think that's normal behavior. At least, there's no mods we've made to weapon exp that should cause that outcome and the way the formula the game uses for giving credit is such that those variations could be simply due to the slight differences in actions. Remember that at level 1 weapon skill, in Rank 1 battles, there's a bonus for the first use in that battle of an attack (of +2 I think), and then +1 for every attack after that. As your weapon level goes up, the bonus becomes a malus, and it becomes harder and harder to clear the threshold to get ANY weapon exp. I think that #2 is working as intended, as I did extensive testing on the bonus/malus before. It's understandable that you'd think that though given the weird mechanic they use and unless you were testing it very strictly, it's easy to misconstrue what the game is doing with it.
In essence, what it looks like has happened from a glance is that Minwu has attacked 1 more time than Firion and Guy, and Maria has attacked 3-4 more times than Minwu and Firion/Guy respectively. In reality though, what likely has happened is Maria attacked 1 or 2 more times across all battles where Firion and Guy cast, and Minwu attacked 1 more time, or any combination of situations. Or to frame it another way, Guy and Firion cast a single time more than Maria did, perhaps.
Quote from: redmagejoe on February 26, 2020, 07:15:14 PM
I haven't done extensive testing on the RNG to figure out what the actual math will come out to for bias towards the characters, but it may simply be a case of too small sample size at this point in the game? I don't know. I do know we may have to review the RNG anyway to see if we can't address the graphics bug on stat ups.
As for the Weapon Experience, with the numbers and situations you provided, I think that's normal behavior. At least, there's no mods we've made to weapon exp that should cause that outcome and the way the formula the game uses for giving credit is such that those variations could be simply due to the slight differences in actions. Remember that at level 1 weapon skill, in Rank 1 battles, there's a bonus for the first use in that battle of an attack (of +2 I think), and then +1 for every attack after that. As your weapon level goes up, the bonus becomes a malus, and it becomes harder and harder to clear the threshold to get ANY weapon exp. I think that #2 is working as intended, as I did extensive testing on the bonus/malus before. It's understandable that you'd think that though given the weird mechanic they use and unless you were testing it very strictly, it's easy to misconstrue what the game is doing with it.
In essence, what it looks like has happened from a glance is that Minwu has attacked 1 more time than Firion and Guy, and Maria has attacked 3-4 more times than Minwu and Firion/Guy respectively. In reality though, what likely has happened is Maria attacked 1 or 2 more times across all battles where Firion and Guy cast, and Minwu attacked 1 more time, or any combination of situations. Or to frame it another way, Guy and Firion cast a single time more than Maria did, perhaps.
I thought the game awarded all weapon/spell exp upon selecting the action, in which case, whether or not the action was carried out in a battle would become irrelevant. Unless I am misunderstanding that portion of it. But if I'm not, then I never attacked with Maria while casting with Firion and Guy, I always chose cast with all three of them, or attacked with all three of them. If anyone should have more weapon exp, it's Firion because I target-canceled with him once or twice.
Anyway, I will continue playing and report any other oddities. I stopped streaming because I was having frame drop issues, but I am recording my grinding sessions so if anything weird happens, I can just post a video of it.
I know it's way to early to say, considering the ongoing efforts on this project, but I hope someone does a "Restored" version for Final Fantasy III (Famicom) as well, knowing that this entry from the series is more obscure than the rest, even after receiving countless of modern remakes and ports. Also, due to the fact that, before being remade, FFIII was one of the particular entries that Square lost the source code from. The game hasn't been disassembled either unlike FF I & II.
I'm fairly confident that weapon and spell exp works properly as according to the formulas. The RNG is definitely something I'll ask if abw can look at, since I haven't the foggiest how the algorithm is working or how it relates to amount of processing time needed to avoid bumping into VRAM time. I've just finished looking over the evasion cap fix, and it seems like a pretty simple change. Essentially we've eliminated the unnecessary math on the second byte that was causing problems, and this new code should have no unintended side effects, yes?
For instance, on pre-fix stable, I have 85% with no shields on, yet post, it's 68%. At the very least, the fix does cap evasion at 99% with two shields, but I'm curious if the math is being done properly. Is the 85% an unintended boost as a result of the old evasion calculation, and 68% is indeed correct, or is there a side effect taking place? I'd have to track down the exact formula for evasion.Quote
Evasion: A number of factors go into calculating evasion. Each character has a base evasion percentage equal to their agility score. From there, you add the evasion bonus from the items held in each hand by the appropriate skill level for that hand plus one. So if you have a sword with an evasion bonus of 1 at level 2, you gain (2 + 1) × 1, or 3 evasion from it. Similarly, if you have a shield with an evasion bonus of 5 at level 3, you gain (3 + 1) × 5, or 20 evasion. Finally, subtract the weight of each piece of armor worn from your evasion.
Disregard, I forgot weapons have an evasion bonus, and I still had Ripper equipped. Sure enough, both versions are at 68% with Firion completely unarmed. This looks like a pretty clean fix abw, and I'm ready to commit it to stable if you feel confident in it. I just spent several passes through debugger stepping through and watching values, and I don't see anything that raises any red flags. :)
The more I test buff spells, the more I realize how ridiculous they are at Level 16 when they actually work properly. Blink can raise a single party members number of evade chances well past the 16 it maxes at for their stat, with Firion having gotten up to $25 evade chances from three or four turns of having Blink cast on him. Assuming he has 99% evade rate, he's essentially untouchable. It's too bad so many of them didn't work before we got to them.
Quote from: redmagejoe on February 26, 2020, 11:33:08 AM
Are you saying that Heal Staff was NOT actually healing undead before, or that you've FIXED it to not heal them? Also, is it doing its family damage and bonus damage to Undead either pre- or post- fix?
The original code for the Heal Staff effect used against Undead does not inflict special effect damage. Like Ripper, only the base damage (including the +20 family bonus) is subtracted from the target's current HP; the special effect damage is only added to the displayed number.
Quote from: redmagejoe on February 26, 2020, 11:33:08 AM
Work is being very distracting, so apologies if it's slow progress.
No worries, I'm actually in a similar boat myself, so expect slow/no progress from me for the next little while :(.
Quote from: redmagejoe on February 26, 2020, 03:12:09 PM
I, for one, am okay with the intention behind INT/SPR penalties. Given that they were removed in remakes, however, and it's a controversial change, I don't see why we can't work on an optional patch so people can choose whether they want to play "the original", an NES version of the remake, or an "enhanced original", since we've got a Mysidian Tower Orbs buff and consideration for a weapon-switching experience tracking feature, among other things.
This should be a simple change: just set the spell power penalty for each piece of equipment to 0. The equipment data starts at $0C:$8000, and spell power penalty is the 3rd byte for armour and 5th byte for weapons/shields.
Quote from: Leviathan Mist on February 26, 2020, 06:06:41 PM
1. RNG seems to still favor the middle two characters for enemy targeting. Firion's HP is much lower than Maria's and Guy's.
Assuming you continue to use their starting equipment, Firion is definitely the best protected member of the party, which means that even with (non-random) perfectly balanced enemy targetting, he tends to take the least amount of damage and thus is less likely to gain HP, so having lower max HP is still expected. That said, the RNG isn't great to start with, and while I've addressed one of its problems, that might not be enough.
Quote from: abw on February 27, 2020, 09:11:58 AM
This should be a simple change: just set the spell power penalty for each piece of equipment to 0. The equipment data starts at $0C:$8000, and spell power penalty is the 3rd byte for armour and 5th byte for weapons/shields.
Alternatively, lower the INT/Spirit penalty on the armor with low evade penalties, knives, etc. Maybe give staves a bonus to INT/Spirit that are more focused on being spell caster equipment.
Looking at the stats, it almost looks like what I was saying is already in place. Though I would like to see the staves thing happen. If you're using magic why not be able to have a staff equipped for the power boost?
Oh yes, I forgot that Firion starts with a shield and a sword, which I believe gives him at least a 6% evasion advantage over his allies? Double checking, Firion actually starts with 14% Evasion, Maria with 10%, and Guy with 1%. I have to check the stream again to see what Leviathan was running. With his starting equips, Firion has 14%, Maria has 20%, and Guy has 9%. So Guy is mathematically most likely to have the most HP (he did), Firion higher than Maria (though he starts with 10 more HP than Maria), and Maria with the lowest. By the end of the stream, Firion had 41 HP, Maria 56, and Guy 90. While Guy's seems fine given the lack of evasion and the starting HP being 30, 20, and 40 (Guy gained 50 HP, Maria gained 36 HP, and Firion gained 11 HP), how much their HP increases can't alone be used to determine whether the RNG for targeting is working or not. We also have to consider how finicky the Stamina and HP up RNG is. Because the exact same conditions must be met for Stamina or HP to go up, but they're STILL determined by separate rolls, it's possible to get 3 Stamina ups in a row with no HP ups, and vis versa.
At the end of the day, I'm willing to bet that the new RNG is giving a better outcome with the current systems than previously, where Guy and Maria would no doubt be much further away from Firion under the same conditions. A better test would see all characters with the same Evasion %, which means gear tailored to their different starting Agility. EDIT: So I got all three characters to have 9% Evasion through various equipment swapping shenanigans, and then I began my journey outside the first town. I can tell you right away that the enemies had an affinity for Firion, but there was still a few misses. In the end though, he still ended up neck and neck with Guy on max HP, though Maria got a lucky streak of HP ups while Guy got deprived a few he should have gotten, in my opinion, and Firion was my highest HP party member after only about 20 minutes of grinding.
The only concern I really have about the RNG right now is the fact that it's still causing the VRAM bugs on stat ups.
Quick question about modifying the counter overflow fix: Does the counter apply to every counter combined, or individually? E.g., if I were to change the overflow value from 200 to, say, 8, would I then be able to only accrue 8 attacks + 8 enemy targets/evasion points in a single battle, or would it combine both of them and only allow 4 attacks + 4 enemy targets, or some other combination that adds up to 8? Also, does it combine weapon attacks and spell casts into the same counter?
Each type of action (white magic casts, black magic casts, each individual spells slot use per character, attacks, being hit by spells, being hit by attacks) has its own counter. Though given the action cancel exploit, because of the way the counters are tallied the moment an action is issued, it's actually attempts, attempts, attempts, attempts, enemy targets you with spells, enemy targets you with attacks. But they are all individual, and thus the overflow protection is placed on each one. So while highly unlikely to ever happen in a single battle, your counter for any of those actions will not go above 200 each. No combination whatsoever. Yes, you could in theory set it much lower, though I'm not sure why you would want to.
Also I should clarify what I said earlier about the nature of stat increases. Stamina, HP, M.Power, and MP are all very finicky compared to Strength, Spirit, and Intellect, because while the last 3 have a threshold over which they are GUARANTEED to increase (56 attacks, 16 white magic casts, 25 black magic casts, all within a single battle of course), the former 4 are not guaranteed even if you go from 100% HP to 1 HP. Because it's calculated based on proportion of max HP lost, the only way to guarantee it would be to end at 0 HP. Of course, if you're at 0 HP... you're dead and so you can't get the stat increase. Having said that, you COULD guarantee an M.Power and MP gain (I think, if the formula isn't still stupid) by going from your max MP to 0. Agility is also not guaranteed since evasion can only go to 99, and the chance is rolled 0-255. So even if your evasion % is 99, you still only have a 99/255 chance of gaining Agility at the end of battle.
Quote from: redmagejoe on February 27, 2020, 08:34:06 PM
Each type of action (white magic casts, black magic casts, each individual spells slot use per character, attacks, being hit by spells, being hit by attacks) has its own counter. Though given the action cancel exploit, because of the way the counters are tallied the moment an action is issued, it's actually attempts, attempts, attempts, attempts, enemy targets you with spells, enemy targets you with attacks. But they are all individual, and thus the overflow protection is placed on each one. So while highly unlikely to every happen in a single battle, your counter for any of those actions is 200 each. No combination whatsoever. Yes, you could in theory set it much lower, though I'm not sure why you would want to.
Also I should clarify what I said earlier about the nature of stat increases. Stamina, HP, M.Power, and MP are all very finicky compared to Strength, Spirit, and Intellect, because while the last 3 have a threshold over which they are GUARANTEED to increase (56 attacks, 16 white magic casts, 25 black magic casts, all within a single battle of course), the former 4 are not guaranteed even if you go from 100% HP to 1 HP. Because it's calculated based on proportion of max HP lost, the only way to guarantee it would be to end at 0 HP. Of course, if you're at 0 HP... you're dead and so you can't get the stat increase. Having said that, you COULD guarantee an M.Power and MP gain (I think, if the formula isn't still stupid) by going from your max MP to 0. Agility is also not guaranteed since evasion can only go to 99, and the chance is rolled 0-255. So even if your evasion % is 99, you still only have a 99/255 chance of gaining Agility at the end of battle.
Thanks for the info. My idea would be to set it so low that it's impossible to max out certain stats (like weapon level) on rank 1 enemies by repeating the same action over and over. Ideally the only way to reach level 16 would be by fighting rank 7 enemies. I'm sure there's probably a better way to make that happen though, but it's an idea that could help discourage it. Another thing I've toyed with is removing the ability to target allies with damage attacks/spells, to prevent the whole "beat up on yourself to gain stats" mechanic. This certainly wouldn't fix every problem, and might actually introduce some new ones (can't remove sleep or confusion on an ally without Basuna, for example) but it would take away another cheap leveling tactic. It wouldn't prevent people from wasting turns in battle for the sake of stat gains. Not sure how feasible it would be to reprogram the game so that attacks on dead/escaped enemies are redirected and don't just hit air, that's another cheesy way to prolong a battle and get more commands in, but not too worried about that one. There will always be ways to game the system, but some of them are more exploitable than others.
By the way, I have been doing a bit of off-stream grinding on my file, and I found that my biggest issue with his lack of HP growth was that I unequipped all my armor, so my evasion on Firion is now too high for him to be hit by most enemies I'm fighting. I'll have to unequip shields later so I can naturally train his HP a bit more.
Mind you, the counters are for a single battle and don't carry over, and if we were to fix the current behavior of counters to happen after, rather than the current before, actions are carried out, it won't be a problem anyway. There will be no action cancel exploit, and actually making a battle last long enough to grind that much experience for a stat/spell/weapon will be significantly less feasible. A more effective way to achieve what you're suggesting that carries over multiple battles is to increase the malus from battle rank. As to your desire to lower Firion's evasion, weapons and shields both raise evasion, while armor always reduces it of varying degrees. If you wish to train Firion's HP without beating him up with your other party members, I'd suggest making him unarmed and wear the heaviest armors you can find.
Anyway, I'm not sure I'm really willing to put the energy into patches of that nature even as optional after all the things on the to-do list are tackled, but I would encourage any other romhackers who might want to take on such a rebalancing project to use this project and the disassembly that abw has been so kind to maintain to lay the groundwork.
It seems like you're getting the wrong impression from my discussion. I'm not asking you to do anything. Nor am I asking you to include my ideas in your patch. I sense a tone of annoyance in your wording, so I will simply bow out. These ideas are solely for my own personal modification.
If I conveyed that, it was not my intention. I have been told I can come off as curt, so I apologize. I received a few other PMs asking about my intentions to include some of the proposed ideas and simply wished to clarify in this thread, rather than shut your ideas down wholesale. I don't think the ideas themselves should be too hard to achieve though, even checking the source of an action and its target in the case of friendly fire. The issue will most likely be clever use of space more than anything.
As for preventing self-targeting with spells and attacks, preventing the cursor from moving beyond a certain point in certain scenarios doesn't sound itself like a foreign concept for NES game interface behavior, though I don't think this game itself has any restrictions on who you can select with what. No doubt you'd have to write that routine from scratch. Redirecting attacks to remove the ineffective hits could be easy enough to program, though one would have to look at FF3 to see how it's handled since that was the first in the series to do away with attacking air, I believe. I'm sure someone in this community who's more savvy than I could figure out just by giving it a few moments thought how to achieve it as well, but I can't even begin to think with my limited ASM experience how to go about it.
Just to add, after seeing the graphics bugs in Leviathan's stream, I went and tested on our current stable myself with the RNG fix, and it does indeed throw the graphics bug before each character's stat ups without fail.
Quote from: Rabite890 on February 27, 2020, 11:48:49 AM
Alternatively, lower the INT/Spirit penalty on the armor with low evade penalties, knives, etc. Maybe give staves a bonus to INT/Spirit that are more focused on being spell caster equipment.
Yup, each piece of equipment's spell power penalty can be individually adjusted to anything in the range [0..255], 0 was just an example. Currently only armour can boost core stats, so adding that to weapons would take a bit of work; if you wanted to go down that path, I'd be tempted to add varying boost amounts instead of everything being +10, and maybe even let one item affect multiple stats.
Quote from: redmagejoe on February 27, 2020, 12:07:25 PM
Because the exact same conditions must be met for Stamina or HP to go up, but they're STILL determined by separate rolls, it's possible to get 3 Stamina ups in a row with no HP ups, and vis versa.
Actually, you have a slightly better chance to gain Stamina/Magic Power than HP/MP - Stamina/Magic Power requires at least a 1/9 loss ratio, but HP/MP requires at least a 1/8 loss ratio.
Quote from: redmagejoe on February 27, 2020, 08:34:06 PM
Stamina, HP, M.Power, and MP are all very finicky [...] [they] are not guaranteed even if you go from 100% HP to 1 HP. Because it's calculated based on proportion of max HP lost, the only way to guarantee it would be to end at 0 HP. Of course, if you're at 0 HP... you're dead and so you can't get the stat increase. Having said that, you COULD guarantee an M.Power and MP gain (I think, if the formula isn't still stupid) by going from your max MP to 0.
Even that wouldn't be enough - a 100% loss ratio is 1, so if you roll a 0, you still don't get an increase :(.
Quote from: redmagejoe on February 27, 2020, 08:34:06 PM
Agility is also not guaranteed since evasion can only go to 99, and the chance is rolled 0-255. So even if your evasion % is 99, you still only have a 99/255 chance of gaining Agility at the end of battle.
It's actually Evasion Success % >> 2, so with 99%, you only have a 24/256 chance to gain Agility.
Quote from: Leviathan Mist on February 27, 2020, 08:45:08 PM
My idea would be to set it so low that it's impossible to max out certain stats (like weapon level) on rank 1 enemies by repeating the same action over and over. [+ more ideas about making grinding less profitable]
Coming at this problem from a different angle, one idea I've been toying around with is removing the minimum of 1 action in order to have a chance at growth. Particularly for spells, I always find myself stuck with level 1 spells when I really need level 6 spells; removing the casting requirement for growth would allow your spells to slowly gain experience up to a limit determined by the battle rank. You could apply the same idea to weapons, which would encourage people to explore the full range of options instead of refusing to switch weapon types because they don't want to go back to a maximum of 1 hit attempt, especially against enemies with decent evasion.
Quote from: redmagejoe on February 28, 2020, 01:26:07 AM
Anyway, I'm not sure I'm really willing to put the energy into patches of that nature even as optional after all the things on the to-do list are tackled, but I would encourage any other romhackers who might want to take on such a rebalancing project to use this project and the disassembly that abw has been so kind to maintain to lay the groundwork.
Yeah, there's enough work to do with the bugfixes at the moment, but if anyone else wants to work on other changes, they should feel free to use any of the resources linked to in the first post; either way, it can be fun to talk about ideas for improvements :).
Work is still being oppressive, but I'd like to see if I can't poke my nose around the special effects weapon code and see if I can't help narrow down the battle poses and such. But I also wish there was some way I could contribute to the RNG timing issue leading to the graphics bug, I'm just completely unlearned in the concept. I'm assuming the issue is that the code has to work within a specific number of CPU cycles to not elbow in on VRAM time, and thus one has to know how many CPU cycles the currently updated RNG is taking. Is the reason the old RNG was so poorly optimized because it was taking inherently less time?
Quote from: abw on February 29, 2020, 06:43:35 PM
Coming at this problem from a different angle, one idea I've been toying around with is removing the minimum of 1 action in order to have a chance at growth. Particularly for spells, I always find myself stuck with level 1 spells when I really need level 6 spells; removing the casting requirement for growth would allow your spells to slowly gain experience up to a limit determined by the battle rank. You could apply the same idea to weapons, which would encourage people to explore the full range of options instead of refusing to switch weapon types because they don't want to go back to a maximum of 1 hit attempt, especially against enemies with decent evasion.
This is one of the best ideas I've seen for the game and something the remakes never addressed. The tedium of leveling up spells and weapons is enormous and Battle Rank seems to mean next to nothing. I think Battle Rank bonus/malus should be applied across the board for any action taken so that you can't successfully grind off of Battle Rank 1 to get 9999 HP, but that you aren't still leveling up Flare to Level 4 in Pandemonium.
It would take a bit of a rewrite and a lot of checks to the Battle Rank but I would love to see a hack which addressed these flaws. It would change the flow of the game completely, yes, but it might also cut down on the tedium of grinding endlessly.
(Also I wouldn't mind changing some of the spells into combat moves as a reward for leveling up weapon types. (I mean Sap, Swap? What were they even thinking with Swap other than a way to abuse their poor mechanical setup?) get rid of Toad, Mini, and Frog, the game does not need three more instant death spells... Wall is weird in the first place. Apparently it blocks a singular magical attack... sometimes. I do know that there are some enemy only spells as well which could be used for that.
Quote from: Rabite890 on February 27, 2020, 11:48:49 AM
Alternatively, lower the INT/Spirit penalty on the armor with low evade penalties, knives, etc. Maybe give staves a bonus to INT/Spirit that are more focused on being spell caster equipment.
Looking at the stats, it almost looks like what I was saying is already in place. Though I would like to see the staves thing happen. If you're using magic why not be able to have a staff equipped for the power boost?
Not sure what redmagejoe has planned for the int/spr penalties, but I did create a patch that addresses this. You can find it here (https://www.dropbox.com/s/vspijebhdi2t5a3/FF2%20Modified%20Equipment%20Penalties.zip?dl=0), though I have not done any testing with it since I'm still working on beta testing the Restored project by itself.
I think what I've settled on is simply fixing the bugs and leaving things like the INT/SPR penalties alone. I'm going to trust the rest of the community to come up with rebalancing patches, and since Leviathan already has a patch that acts as a guide for where to change data in order to affect the INT/SPR penalties, the work doesn't need to be done on our end to locate the necessary bytes. I am hoping work settles down soon so that abw and I can move towards the finish line on this project and provide a good foundation for romhackers to give Final Fantasy II the love it deserves. :)
@redmagejoe
I think that is absolutely the right approach. Provide a perfect bugfree base and let others (or maybe you down the road) create and compile the rebalances. I'm extremely excited about this and will be playing the hell out of it.
For the v2.0 of Chaos Rush translation, I'm wondering if it would be easier to simply extend the display box for spells by one more character rather than trying to redo the graphics used for the 5-character long spell names. This box extension would be specific to the translation patch, not the Restored patch.
Actually, looking at it, the game already creates 3 spaces after the spell name, so that the level is always right-aligned, and there's 2 spaces for a <10 level spell, and 1 for a 10+ spell. It would probably be easier to simply remove one of the spaces in the Chaos Rush translation. This would probably result in the alignment looking odd on the few spells that still use 4 characters instead of 5 (|Cure 1 | -> |Cure15 |) but at least the spell levels would fit into the box for all spells (currently |Thunder | -> |Thunder 1|, want to change it to |Thunder 1| -> |Thunder15|).
This was a simple fix. I simply changed the INX below to a NOP, with the INX seeming to be what moves the horizontal position forward by one, creating a space.
0x032D2A|$0C:$AD1A:E8 INX
This is the easiest fix currently, even if it's not the cleanest looking.
So I've been focusing on leveling evasion, and I noticed that my evasion level is going up, but it's not showing that it went up in battle. I know vanilla had a delayed level-up message where it wouldn't appear on the battle it went up, but at the end of the next battle. In this case, it's not appearing at all. In fact, I don't recall ever seeing a message for it, and my evasion level is 4 on all 4 characters, so I should have seen it quite a few times by now.
The reason that Evasion and Magic Resist "got away" with the sneaky level up difference from others is that they don't actually show a message. So it initializing when the following battle starts, or when equipment was switched, worked out because it was harder for the end user to notice. The only way you'd know if it did or didn't go up at a particular time is if you're watching the value in RAM, which was how I determined that it was being set at the start of the proceeding battle (and it was obvious with equipment switching since you can see it change right in the stat window).
Assuming that it was only the messages you were worried about, it's working as intended, since there's no "Evasion/Magic Resist up!" messages programmed even in vanilla. Let me know though if you've noticed the value not appropriately adjusting when watched in RAM, as I was pretty sure I'd tested that, but did not extensively test to see if there were any strange outlier scenarios. If we had more room in ROM, I might consider having messages for those leveling up, but since it didn't do that in the remakes (I think, would have to test this on PSP version), and it may become involved, I'll put that at the end of the Possible Improvements list. I think it's a worthwhile improvement, I just don't know how feasible it is to implement with limited space, but the groundwork to implement it is definitely there.
Ah okay. I thought it showed a message in vanilla, that's all I was posting about. I'll continue to update if I see any more weird behavior though.
Status Update: I should be free by Monday and will begin looking into the Ripper/Drain/Heal Staff fix and see if I can't help locate where battle poses are being updated in code, and see if there's any way to fit those into the existing Drain fix. Hoping to hear from abw soon and see if he can answer any of my questions about the RNG thing, since as I said, I'm really not that learned on CPU timing and how it plays into the code.
Quote from: redmagejoe on February 29, 2020, 06:48:33 PM
But I also wish there was some way I could contribute to the RNG timing issue leading to the graphics bug, I'm just completely unlearned in the concept. I'm assuming the issue is that the code has to work within a specific number of CPU cycles to not elbow in on VRAM time, and thus one has to know how many CPU cycles the currently updated RNG is taking.
I wasn't able to reproduce any graphical glitches (during level ups or otherwise) using the RNG patch, but I was able to consistently trigger glitches on level ups using the Level Up Routines Rework patch; I added another sprite 0 check there, and I'm not seeing glitches anymore, but I haven't really delved into that part of the code, so it might still require further tweaking. Just in case it was an issue somewhere, I also updated the RNG patch to restore the early kickout when the lower and upper bounds are the same, which drastically speeds up those cases and better reflects the original code's consumption of numbers from the base RNG lookup table.
Quote from: redmagejoe on February 29, 2020, 06:48:33 PM
Is the reason the old RNG was so poorly optimized because it was taking inherently less time?
From an optimization perspective, the old RNG wasn't that bad; the worst thing that comes to mind is that it performs 16-bit multiplication when all it really needs is 8-bit multiplication, so that part could easily be done twice as quickly.
Quote from: Grimoire LD on February 29, 2020, 09:38:18 PM
This is one of the best ideas I've seen for the game and something the remakes never addressed. The tedium of leveling up spells and weapons is enormous and Battle Rank seems to mean next to nothing. I think Battle Rank bonus/malus should be applied across the board for any action taken so that you can't successfully grind off of Battle Rank 1 to get 9999 HP, but that you aren't still leveling up Flare to Level 4 in Pandemonium.
It would take a bit of a rewrite and a lot of checks to the Battle Rank but I would love to see a hack which addressed these flaws. It would change the flow of the game completely, yes, but it might also cut down on the tedium of grinding endlessly.
Currently, exp gain for any skill with at least 1 action (this includes Evasion and Magic Resist skill levels in addition to weapon/spell levels) is calculated as
(base growth amount for type of skill) + (# of actions) + (battle rank) - (base growth barrier for type of skill) - (skill level)
where the result is subject to various quirks caused by the game's missing under/overflow checks. For spells specifically, allowing a chance for growth with 0 actions reduces that to
2 + (battle rank) - (skill level)
so spells with no actions would passively level up to battle rank + 2 over time, which is at least in the neighbourhood of reasonable and can be adjusted fairly easily.
For stat growth,
- HP/MP/Stamina/Magic Power growth chance is based only on whether HP/MP loss ratio beats a random number, with HP/MP growth amount = Stamina/Magic Power
- Strength/Intellect/Spirit growth chance is based only on whether your attack counter beats a random number
- If you gained the opposing stat, Intellect/Stamina/Strength loss chance is based only on whether a random number beats 0
Working battle rank into those calculations wouldn't be too hard; higher battle ranks could give you a lower minimum HP/MP damage ratio and a more favourable required number of attacks. On the other hand, stat growth doesn't feel like as much of a problem to me as skill growth does. Although it's not directly used in the calculation, I find that HP/Stamina in particular tends to scale pretty well with battle rank already since higher battle ranks tend to dish out more damage.
Quote from: Grimoire LD on February 29, 2020, 09:38:18 PM
(Also I wouldn't mind changing some of the spells into combat moves as a reward for leveling up weapon types. (I mean Sap, Swap? What were they even thinking with Swap other than a way to abuse their poor mechanical setup?) get rid of Toad, Mini, and Frog, the game does not need three more instant death spells... Wall is weird in the first place. Apparently it blocks a singular magical attack... sometimes. I do know that there are some enemy only spells as well which could be used for that.
I haven't tested it myself yet, but based on reading the code, it looks like casting Wall on somebody with a given number of successes should negate all black magic with up to that number of successes; Wall should persist until its target gets hit by some black magic with a higher number of successes, so e.g. Wall with 16 successes ought to make its target immune to all black magic for an entire battle, making it pretty great (unless you want to use beneficial black magic like Berserk/Haste/Aura :P).
Quote from: redmagejoe on March 04, 2020, 09:50:20 AM
For the v2.0 of Chaos Rush translation, I'm wondering if it would be easier to simply extend the display box for spells by one more character rather than trying to redo the graphics used for the 5-character long spell names. This box extension would be specific to the translation patch, not the Restored patch.
This is probably the "right" way to do it, but will require figuring out how the display boxes are drawn, what determines their width, and presumably whether whatever RAM space they're using to buffer the text is large enough to hold more tiles. Definitely worth investigating, though.
Quote from: redmagejoe on March 04, 2020, 07:18:52 PM
If we had more room in ROM, I might consider having messages for those leveling up, but since it didn't do that in the remakes (I think, would have to test this on PSP version), and it may become involved, I'll put that at the end of the Possible Improvements list. I think it's a worthwhile improvement, I just don't know how feasible it is to implement with limited space, but the groundwork to implement it is definitely there.
Battle messages are stored and used in bank 5, which does have enough free space to be useful (and we've made more), so this might not be too difficult.
In other news, I've also gone through and commented large swaths of the bank C code including the player-castable spells; this led to the discovery of some more bugs:
- The bonus amount from Protect and Berserk can overflow, but the code doesn't check the high byte of the multiplication result, so if you score +256, you actually get +0.
- Like Aura and Barrier, Dispel's battle messages are in reverse order compared to its effects.
- Aura, Barrier, and Dispel all display battle messages based only on their number of successes, not on whether their successes actually made any difference. E.g. if you already resist Fire and score at least 2 successes, Barrier still says your Fire defense increased even though it's exactly the same as it was before.
- After determining how much HP to restore, Life only writes the low byte of the result to your HP, which means it can leave you with 0 HP. In this case, the target's death message is displayed before Life's success message and the target remains dead.
Quote from: redmagejoe on January 24, 2020, 12:17:21 AM
Quote from: CoolCatBomberMan on January 23, 2020, 08:29:49 PM
I'm not sure how relevant this is, but Hironobu Sakaguchi explained why the Ultima bug is a thing: when he first discovered it, he wanted the bug fixed, but some jerk programmer said no, because the bug reflected how legendary weapons/items in real life rarely live up to their own hype. Then, he ciphered the bug, so Sakaguchi couldn't fix it himself. So, here's to hoping you don't have too much trouble fixing Ultima.
Still working on my work project, but thought I'd pop in to say that, given that Ultima behaves the way Sakaguchi wanted it to in the remake, and knowing that was his original intent, it is definitely a priority to attempt to make it work in this version. Perhaps that will be our Final Challenge. :)
I went and looked into this, and it was the source code that was ciphered apparently. Fortunately we're reverse-engineering it, so I'm not sure it would be an issue, though if by "source code" it means the assembly which we're turning the binary back into... Then yeah, we may have trouble. Hopefully we can make sense of most of the rest of the disassembly such that the only things left by the time we get to it must, by process of elimination, be related to Ultima.
Ultima does appear to be intentionally bugged, but its final assembly is no more complicated than any other spell's, so if there was any ciphering going on, it must have happened at a higher level in their build process. Ultima's problem is that, aside from the modifications to Spell Success % and Spell Power based on the caster's INT/SPR that apply to every spell, it does not scale at all. In particular, it does not scale with the level of your other abilities as in later remakes nor does it even scale with its own level like other spells do; basically, instead of using spell level as the number of success attempts, you get 1 guaranteed success + 1 or 2 resistable successes.
These aren't specifically related to spells, but there are some other things I've noticed that could be considered bugs:
- HP/MP loss ratios are calculated based on net HP/MP loss rather than total HP/MP loss, which has the effect of punishing the player for healing during battle. It looks like there might be some unused bytes in each character's skill data, so possibly we could use 4 of them per character as a running total of inflicted HP/MP damage, optionally restricted to damage inflicted by a hostile source (for HP anyway; only giving credit for enemy-inflicted MP damage would be game-breaking), and optionally reset when gaining a stat instead of at the start of every battle. Restrictng HP/MP loss ratios to damage inflicted by an enemy would eliminate attacking your own party and abusing Swap as grinding exploits. Carrying losses across battles would allow them to accumulate over time and eventually guarantee stat increases, as opposed to the current system where you can suffer 10% losses per battle for 10 battles with 0% chance for growth.
- There's a buffer overflow when more than 20 battle messages are queued (which is possible with e.g. 2 weapon skill increases + 16 spell increases + 8 stat increases + 3 stat decreases = 29 queued messages); the game reserves $7FBA-$7FCD for battle messages but uses $7FCE+ for other currently unknown things and none of the code for adding battle messages to $7FBA,X checks for X > #$13. Based on analysis so far, I think the only time you can trigger overflow is at the end of a battle and I think $7FCE+ isn't used afterwards, so maybe it's harmless?
- Lending further support to the idea that dual-wielding should increase your combat offense, Joseph's initialization data starts him with 2 fist attacks at level 2 = 4 hit attempts, but that gets reduced to 2 hit attempts at the first time his calculated stats get updated.
God that's a lot to take in. I could swear that I just ran a version of the base game with the RNG fix and nothing else and saw those bugs but I could check again... Anyway, if you've got an updated Level Up Routines Rework patch, I'd like to have a look and play around with it. I updated your RNG fix with the stat loss value changes I put in the previous one, to avoid the increased chance to lose stats. I just finished my work project and I'm kind of... turning my brain off for a bit so I don't get burnt out. Tomorrow I'll probably work on this stuff again.
As stated, I'm settling with just removing the space in the Chaos Rush version for now, as I really don't want to redo the spell name character graphics, and I don't want to mess with window borders either. I'm glad to hear that we have room for our messages, so I'll keep that in mind when we get near the end of our bug fix part of the project.
More bugs, more bugs... Have to add overflow protection to the calculated bonus of Protect and Berserk... I already fixed the message order of messages for Aura, Barrier, AND Dispel, both for this patch (to JP version), and for the Chaos Rush patch... Already know about their unconditional messages, but didn't take into account how it interacts with your existing resistances or enemy's non-existing resistances... Not sure that part is too important, since whether they were already vulnerable to a resist or not, if the spell says they are "NOW vulnerable" to X status, or you're protected... in the end, it's all the same, yeah? I don't know, again, head hurts, not thinking clearly so just rambling...
Life bug definitely needs to be addressed... I will give serious consideration to fully implementing dual wielding, and as for healing offsetting HP/MP growth, I'll add that to the list before I go take a nap. The fact that we have some bytes to work with makes that more appealing a prospect.
EDIT: Yeah my mistake, it seems that the RNG fix alone doesn't cause the problem. Maybe it was a combination of the two working together? I don't know. Anyway, do I need to insert a wait for sprite 0 hit (JSR $FD5B) or sprite 0 clear (JSR $FD46)?
Thanks, as always, to your awesome commenting of the disASM, abw, I think the Life bug fix is easily made. I'm assuming that, space allowing, we need only insert after 0x0338DD:
C8 INY ; increments Y to #$0B, Battle stat offset for Current HP high byte
A5 05 LDA $05
91 A1 STA ($A1),Y ; pointer to target's battle stats
Question is... where to get 5 bytes? A lot of spell bugs need to be fixed in this area, Sap (right after Life) included, so maybe we can make some space by virtue of fixing some of these? Though some fixes may need MORE rather than less space, so... Let's plan out fixes for them, then try to trim them down as much as possible, then look at how we can work with the available space versus the space we need.
0x0338FD|$0C:$B8ED:A0 0C LDY #$0C ; Battle stat offset for Current MP low byte
0x0338FF|$0C:$B8EF:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x033901|$0C:$B8F1:85 00 STA $00
0x033903|$0C:$B8F3:A9 00 LDA #$00 ; BUG: should operate on high byte of target's current MP too
0x033905|$0C:$B8F5:85 01 STA $01
===>
0x0338FD|$0C:$B8ED:A0 0C LDY #$0C ; Battle stat offset for Current MP low byte
0x0338FF|$0C:$B8EF:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x033901|$0C:$B8F1:85 00 STA $00
0x033903|$0C:$B8F3:C8 INY ; increment Y to #$0D, Battle stat offset for Current MP high byte
0x033904|$0C:$B8F4:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x033906|$0C:$B8F6:85 01 STA $01
Need +1 byte for the Sap fix, +5 bytes for the Life fix... what else can we quickly fix? Osmose fix may require enough space that we may need to hijack the JSR to a new one, and pass the old JSR through that one before returning back to our original control flow. Drain and Osmose use the same control block, and branches if we're dealing with Drain. Question is, do we have similar bugs with Drain (awarding more HP than taken from the enemy) or is that coded properly? I recall you saying that there was an issue with Drain not setting battle pose properly, so since we're already looking at it... Also likely need a JSR to a new sub from both Protect and Berserk to handle the case where Attack/Defense would be above 255 (or is it the bonus itself that's the issue?) since they both seem to use the same math.
Also, I suppose I should ask if the only issue with the weapon special effects fix is the Drain issue (which is inherent to this spell handler code rather than the weapon code itself), or did you manage to get Heal Staff working properly on Undead (doesn't heal them like it does non-Undead, deals the base attack damage * successful hits rather than the 0 it does on non-Undead, ALSO deals bonus dmg * successful hits to Undead, similar to Ripper)? If the only issue is with Drain, I'd like to add that patch to stable, assuming you feel it's working as intended.
; handler for ????, Spell ID #$D1: Berserk
Yoichi's Bow can be used in combat to cast Berserk on the party, if that's what the ???? is referring to.
Quote from: redmagejoe on March 08, 2020, 06:54:42 PM
Anyway, if you've got an updated Level Up Routines Rework patch, I'd like to have a look and play around with it.
It's available at the same link as before, but since those are spread all over the place, here's a single link to everything I've uploaded for FF2 (https://drive.google.com/drive/folders/11reya_EH8eqqnIwQwLu_DRZVtO3PXPeh?usp=sharing)!
Quote from: redmagejoe on March 08, 2020, 06:54:42 PM
As stated, I'm settling with just removing the space in the Chaos Rush version for now, as I really don't want to redo the spell name character graphics, and I don't want to mess with window borders either.
Yup, that's fine. I'm just saying that based on other games I've looked at, we might get lucky and be able to expand the width of the spell name window by changing a single byte somewhere.
Quote from: redmagejoe on March 08, 2020, 06:54:42 PM
More bugs, more bugs... Have to add overflow protection to the calculated bonus of Protect and Berserk...
As you've seen, the good news is that most of the bugs are simple to fix; more good news is that there is some easily shrinkable code in bank C. Ultima in particular is basically just inlining a bunch of other routines that already exist elsewhere, so rewriting that to act more like the remakes frees up a lot of space.
Quote from: redmagejoe on March 08, 2020, 06:54:42 PM
I already fixed the message order of messages for Aura, Barrier, AND Dispel, both for this patch (to JP version), and for the Chaos Rush patch... Already know about their unconditional messages, but didn't take into account how it interacts with your existing resistances or enemy's non-existing resistances... Not sure that part is too important, since whether they were already vulnerable to a resist or not, if the spell says they are "NOW vulnerable" to X status, or you're protected... in the end, it's all the same, yeah? I don't know, again, head hurts, not thinking clearly so just rambling...
Yeah, it depends a lot on the exact text of the messages. It looks like Chaos Rush's translations should be fine, but some of Demiforce's suggest that multiple applications of the spell might stack their effects, which is not the case.
Quote from: redmagejoe on March 08, 2020, 06:54:42 PM
Question is... where to get 5 bytes?
The closest source of that many free bytes looks like Swap. When it's capping the actor's and target's current HP/MP at their max HP/MP, it first sets $5C/$5D for HP then sets $5E/$5F to the actor, caps, then sets $5E/$5F to the target, caps, and then sets $5C/$5D for MP. At that point, $5E/$5F still refer to the target so you could save 8 bytes by immediately capping, but instead the game switches to actor, caps, switches to target, caps. I made a couple of notes about some other spots where space could be saved (search for e.g. $B571), but in general, a lot of the spell code is pretty similar and could be abstracted to save space. Take Protect/Berserk for example: aside from the constants for the offset of the stat to buff and the battle message to display, they're basically the exact same routine duplicated.
Quote from: redmagejoe on March 08, 2020, 06:54:42 PM
Also, I suppose I should ask if the only issue with the weapon special effects fix is the Drain issue (which is inherent to this spell handler code rather than the weapon code itself), or did you manage to get Heal Staff working properly on Undead (doesn't heal them like it does non-Undead, deals the base attack damage * successful hits rather than the 0 it does on non-Undead, ALSO deals bonus dmg * successful hits to Undead, similar to Ripper)? If the only issue is with Drain, I'd like to add that patch to stable, assuming you feel it's working as intended.
I still haven't got back to looking at the special effects fix yet. The healing from Drain/Osmose is equal to the damage inflicted, which can indeed exceed the target's max HP/MP, so we'll want to cap that. Heal Staff should be working correctly, but I'll need to retest all the special effects once I'm done updating.
Quote from: redmagejoe on March 08, 2020, 06:54:42 PM
; handler for ????, Spell ID #$D1: Berserk
Yoichi's Bow can be used in combat to cast Berserk on the party, if that's what the ???? is referring to.
I'm pretty sure the spell jump table also covers items, weapons, and enemy attacks, I just haven't hooked them all up yet. Still plenty of work to do!
When I've got some downtime, I'll compare the versions of the level up routine rework, and then see what can be done with spell code to make room for changes and fixes as per your suggestion.
I'm going to compare and play with the Level Up Routine update first to see that we get rid of that nasty graphics bug. After that, I'll start working with the spell code to clean it up and see if Life and Sap and Swap can't be toyed with. If enough space can be made with abstraction, it may give us more space overall for down the line when we need as much space for enhancements as possible, not just the spell bug fixes.
Yep, just that one change seemed to fix everything. Uploaded it to link in first post. Hopefully we're done with that Level Up Routine Rework patch for real this time. I'll look at the spell code tomorrow. I will also consider changing the way the Chaos Rush fix is handled after this project finishes and v2.0 of Chaos Rush becomes my priority. I will admit I'm not happy with the workaround I used, but for now at least it's workable. I don't like to take shortcuts if I don't have to. Hopefully you're right and it's a very easy fix.
; handler for Spell ID #$E3: Swap
; indirect control flow target (via $BEAE)
0x03391B|$0C:$B90B:20 23 BE JSR $BE23 ; calculate number of spell attack successes in $52-$53 and number of spell resist successes in $54-$55
0x03391E|$0C:$B90E:20 D0 B5 JSR $B5D0 ; $52-$53 -= $54-$55, i.e. net spell successes
0x033921|$0C:$B911:90 50 BCC $B963 ; negative successes => Ineffective
0x033923|$0C:$B913:F0 4E BEQ $B963 ; 0 successes => Ineffective
0x033925|$0C:$B915:A0 0A LDY #$0A ; Battle stat offset for Current HP low byte
0x033927|$0C:$B917:20 66 B9 JSR $B966 ; given a (current HP or MP) stat offset in Y, swap actor's and target's Y and Y + 1 stats
0x03392A|$0C:$B91A:A0 0C LDY #$0C ; Battle stat offset for Current MP low byte
0x03392C|$0C:$B91C:20 66 B9 JSR $B966 ; given a (current HP or MP) stat offset in Y, swap actor's and target's Y and Y + 1 stats
; cap actor's and target's current HP at their max HP
0x03392F|$0C:$B91F:A9 0A LDA #$0A ; Battle stat offset for Current HP low byte
0x033931|$0C:$B921:85 5C STA $5C
0x033933|$0C:$B923:A9 0E LDA #$0E ; Battle stat offset for Max HP low byte
0x033935|$0C:$B925:85 5D STA $5D
; copy actor's battle stats pointer to $5E-$5F
0x033937|$0C:$B927:A5 9F LDA $9F ; pointer to actor's battle stats
0x033939|$0C:$B929:85 5E STA $5E
0x03393B|$0C:$B92B:A5 A0 LDA $A0
0x03393D|$0C:$B92D:85 5F STA $5F
0x03393F|$0C:$B92F:20 83 B9 JSR $B983 ; given an offset to low byte of current HP/MP in $5C, an offset to low byte of max HP/MP in $5D, and a pointer to some entity's battle stats in $5E-$5F, cap entity's current HP/MP at their max HP/MP
; copy target's battle stats pointer to $5E-$5F
0x033942|$0C:$B932:A5 A1 LDA $A1 ; pointer to target's battle stats
0x033944|$0C:$B934:85 5E STA $5E
0x033946|$0C:$B936:A5 A2 LDA $A2
0x033948|$0C:$B938:85 5F STA $5F
0x03394A|$0C:$B93A:20 83 B9 JSR $B983 ; given an offset to low byte of current HP/MP in $5C, an offset to low byte of max HP/MP in $5D, and a pointer to some entity's battle stats in $5E-$5F, cap entity's current HP/MP at their max HP/MP
; cap actor's and target's current MP at their max MP
0x03394D|$0C:$B93D:A9 0C LDA #$0C ; Battle stat offset for Current MP low byte
0x03394F|$0C:$B93F:85 5C STA $5C
0x033951|$0C:$B941:A9 10 LDA #$10 ; Battle stat offset for Max MP low byte
0x033953|$0C:$B943:85 5D STA $5D
; copy actor's battle stats pointer to $5E-$5F
0x033955|$0C:$B945:A5 9F LDA $9F ; pointer to actor's battle stats
0x033957|$0C:$B947:85 5E STA $5E
0x033959|$0C:$B949:A5 A0 LDA $A0
0x03395B|$0C:$B94B:85 5F STA $5F
0x03395D|$0C:$B94D:20 83 B9 JSR $B983 ; given an offset to low byte of current HP/MP in $5C, an offset to low byte of max HP/MP in $5D, and a pointer to some entity's battle stats in $5E-$5F, cap entity's current HP/MP at their max HP/MP
; copy target's battle stats pointer to $5E-$5F
0x033960|$0C:$B950:A5 A1 LDA $A1 ; pointer to target's battle stats
0x033962|$0C:$B952:85 5E STA $5E
0x033964|$0C:$B954:A5 A2 LDA $A2
0x033966|$0C:$B956:85 5F STA $5F
0x033968|$0C:$B958:20 83 B9 JSR $B983 ; given an offset to low byte of current HP/MP in $5C, an offset to low byte of max HP/MP in $5D, and a pointer to some entity's battle stats in $5E-$5F, cap entity's current HP/MP at their max HP/MP
0x03396B|$0C:$B95B:A9 37 LDA #$37 ; Battle Message ID #$37: 'Swapped stats'
0x03396D|$0C:$B95D:20 92 BF JSR $BF92 ; add battle message ID A to the list of battle messages to display
0x033970|$0C:$B960:4C 7E BE JMP $BE7E ; set $4A-$4B to 0
Took me a couple read-throughs of your explanation, but I think I see what you mean. You're saying that I could simply remove B945 up to B94B so that it JSRs immediately after B943, and then replace the following (redundant) LDAs and STAs with the removed ones. In other words...
; copy target's battle stats pointer to $5E-$5F
0x03394A|$0C:$B93A:A5 A1 LDA $A1 ; pointer to target's battle stats
0x03394C|$0C:$B93C:85 5E STA $5E
0x03394E|$0C:$B93E:A5 A2 LDA $A2
0x033950|$0C:$B940:85 5F STA $5F
0x033952|$0C:$B942:20 83 B9 JSR $B983 ; given an offset to low byte of current HP/MP in $5C, an offset to low byte of max HP/MP in $5D, and a pointer to some entity's battle stats in $5E-$5F, cap entity's current HP/MP at their max HP/MP
; cap actor's and target's current MP at their max MP
0x033955|$0C:$B945:A9 0C LDA #$0C ; Battle stat offset for Current MP low byte
0x033957|$0C:$B947:85 5C STA $5C
0x033959|$0C:$B949:A9 10 LDA #$10 ; Battle stat offset for Max MP low byte
0x03395B|$0C:$B94B:85 5D STA $5D
0x03395D|$0C:$B94D:20 83 B9 JSR $B983 ; given an offset to low byte of current HP/MP in $5C, an offset to low byte of max HP/MP in $5D, and a pointer to some entity's battle stats in $5E-$5F, cap entity's current HP/MP at their max HP/MP
; copy actor's battle stats pointer to $5E-$5F
0x033960|$0C:$B950:A5 9F LDA $9F ; pointer to actor's battle stats
0x033962|$0C:$B952:85 5E STA $5E
0x033964|$0C:$B954:A5 A0 LDA $A0
0x033966|$0C:$B956:85 5F STA $5F
0x033968|$0C:$B958:20 83 B9 JSR $B983 ; given an offset to low byte of current HP/MP in $5C, an offset to low byte of max HP/MP in $5D, and a pointer to some entity's battle stats in $5E-$5F, cap entity's current HP/MP at their max HP/MP
0x03396B|$0C:$B95B:A9 37 LDA #$37 ; Battle Message ID #$37: 'Swapped stats'
0x03396D|$0C:$B95D:20 92 BF JSR $BF92 ; add battle message ID A to the list of battle messages to display
0x033970|$0C:$B960:4C 7E BE JMP $BE7E ; set $4A-$4B to 0
Addresses changed to reflect if I were to shift it down the full 8 bytes. I'd like to try and future-proof by only moving each section as much as necessary, so more likely, assuming it would only shift 6 bytes until I can find other spell code that needs to be cleaned up.
EDIT: Urgh, I just noticed that second "BUG" comment in Sap for the quotient, so I actually need another 4 bytes for and LDA $05 and STA ($A1),Y. That's 2 more bytes than I get from the change to Swap. Guess I might have to do a little more cleaning of spell code than I'd initially intended to fix Sap. Life can be fixed right now, but I'd rather have them both in the same patch since the same area of code is being modified.
Disassembly updated with more comments, more bugs noticed:
- The logic for determining whether a monster will run or not calculates (total party HP - total monster HP) >> 5, but then ignores the high byte of the result and the possible carry from adding the monster's fear level.
- Status-curing items don't set the variable containing the target's previous ailments when calling the code for determining which battle message to display, resulting in an incorrect message being displayed (e.g. if you suffer from both Amnesia and Curse, curing Curse in battle with a Cross will display the message for curing Amnesia.
- Cyclone/Quake/Tsunami should be resisted by certain monster families, but the branching code checks the wrong processor flag (C instead of Z), which means family resistance is broken...
- ... but that bug is cancelled out by another bug where after calculating the target's number of successful resists, the result is never used, which means resistance for these spells is just totally broken.
- Imbibe decreases the target's evasion % by 1, but there is no check for underflow, so you could end up going from 0 to 255 % evasion; having 255 evasion probably breaks other logic elsewhere.
- In the dialogue immediately before and after fighting the boss monster when retaking Fynn castle, there's a graphics glitch after closing the dialogue boxes that causes Hilda and Gordon to appear on the thrones for a few frames.
Quote from: redmagejoe on March 08, 2020, 06:54:42 PM
; handler for ????, Spell ID #$D1: Berserk
Yoichi's Bow can be used in combat to cast Berserk on the party, if that's what the ???? is referring to.
It turns out that extra ??? was for Imbibe calling the Berserk handler.
Quote from: redmagejoe on March 11, 2020, 04:37:06 PM
I'd like to try and future-proof by only moving each section as much as necessary,
I like the intent, but the more bug fixes we make, the more things are going to shift, and the deeper we look into this code, the more bugs we're going to find and want to fix, so this could be tough to achieve.
In other news, I finally got back to looking at the weapon special effects, and have updated it to include capping the drain amount at the target's current HP/MP. I think making sure the battle poses get updated in a timely manner is the only thing left to incorporate, but that's an issue that applies to more than just weapon special effects.
I also uploaded a rewrite of the Ultima code to make it more awesome :D.
I think that I'm going to have to just go ahead and remove the strike-outs for fixed bugs in the first post with all these new bugs coming in. I'm constantly skirting the character limit of posts, and while I'd like people to be able to review in post what the bugs were and what's been fixed, I need the room. I can't wait to see what you've done with Ultima. Would you say the Weapon Special Effects patch is ready for stable? I"m assuming that the battle pose update isn't tied directly to special weapons code, yeah?
I also wrote an automatic combat program for this game. Simplify the operation in the game, improve the quality of the system, send me a message, send the game source file
[email protected]
Quote from: redmagejoe on March 14, 2020, 05:54:53 PM
I think that I'm going to have to just go ahead and remove the strike-outs for fixed bugs in the first post with all these new bugs coming in. I'm constantly skirting the character limit of posts, and while I'd like people to be able to review in post what the bugs were and what's been fixed, I need the room.
I can help with that a little - I added my SRAM map to https://datacrystal.romhacking.net/wiki/Final_Fantasy_II:RAM_map; it was based on Jiggers' but has since been corrected and expanded, so you could link to that instead to save a few thousand characters.
Quote from: redmagejoe on March 14, 2020, 05:54:53 PM
I can't wait to see what you've done with Ultima.
My Ultima patch might actually be too OP - at 99 Spirit, < 49% spell success % penalty, and all skills level 16, it averages 24,006 damage and tops out at 46,464 damage, which seems excessive considering the strongest enemy in the game only has 10,000 HP. On the other hand, if you've spent however many hours it would take to max out all your skills, maybe being able to end any battle in one move should be your reward :P.
Quote from: redmagejoe on March 14, 2020, 05:54:53 PM
Would you say the Weapon Special Effects patch is ready for stable? I"m assuming that the battle pose update isn't tied directly to special weapons code, yeah?
It might need to be updated once we get around to tracking down the battle pose stuff, but I'd say it's ready for now.
Yikes, I mean... it's better than it currently is, though I think maybe we should try to figure out the formula the remake uses. Specifically, the version that you use in the normal game, since apparently Minwu's version in Rebirth of Souls is significantly more powerful and has a different formula.
I'll go ahead and add the Weapon Special Effects fix to stable then, update the first pose, and modify the SRAM listing. I appreciate you taking care of that. Sadly, I already removed those items and I can't be assed to rewrite it all. I think it looks more clean now anyway, and as we make patches I'm adding a comprehensive explanation in a text file to be used as a readme someday.
Quote from: abw on March 10, 2020, 09:21:32 PM
Take Protect/Berserk for example: aside from the constants for the offset of the stat to buff and the battle message to display, they're basically the exact same routine duplicated.
I'm comparing them side-by-side, but there's enough differences in instructions that I'd have to see if branches alone can save enough space to justify rewriting one or both.
0x033806|$0C:$B7F6:20 92 BF JSR $BF92 ; add battle message ID A to the list of battle messages to display
0x033809|$0C:$B7F9:A9 E6 LDA #$E6 ; Battle Message ID #$66: ' up!'
0x03380B|$0C:$B7FB:20 92 BF JSR $BF92 ; add battle message ID A to the list of battle messages to display
0x03380E|$0C:$B7FE:4C 7E BE JMP $BE7E ; set $4A-$4B to 0
...
0x033A78|$0C:$BA68:20 92 BF JSR $BF92 ; add battle message ID A to the list of battle messages to display
0x033A7B|$0C:$BA6B:A9 E6 LDA #$E6 ; Battle Message ID #$66: ' up!'
0x033A7D|$0C:$BA6D:20 92 BF JSR $BF92 ; add battle message ID A to the list of battle messages to display
0x033A80|$0C:$BA70:4C 7E BE JMP $BE7E ; set $4A-$4B to 0
At the very least, these parts are identical and I can save 8 bytes by replacing the JSR in the first block (Protect) with a JMP to $BA68 since it should more or less be the exact same control flow. This gives me 8 more bytes to work with and comes before Life, Sap, and Swap, so I can shift all of that code up those 8 bytes, have the space I need, and 6 leftover bytes for expanding. At some point will need that and probably then some for preventing Protect/Berserk bonus from overflowing. I'll work with all of this for a bit with NOP padding before I start committing to moving anything around, need to see how I can consolidate spell code before I shift everything and change pointers to spell handlers.
; handler for Battle Commands #$15: Cast Spell ID #$D4: Cure and #$29: Cast Heal
; indirect control flow target (via $BE8E)
0x0335A5|$0C:$B595:A0 15 LDY #$15 ; Battle stat offset for Monster Family (80:Undead, 40:Werebeast, 20: Dragon, 10:Spellcaster, 08:Giant, 04:Earth, 02:Aquatic, 01:Magic Beast)
0x0335A7|$0C:$B597:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x0335A9|$0C:$B599:29 80 AND #$80 ; Undead
0x0335AB|$0C:$B59B:D0 06 BNE $B5A3 ; attacked Undead with Cure; why not call $B571 directly?
0x0335AD|$0C:$B59D:20 FB BC JSR $BCFB ; heal target's HP by unresistable spell damage amount, set $7CB0 to #$0B, queue battle message, set $4A-$4B to 0
0x0335B0|$0C:$B5A0:4C A6 B5 JMP $B5A6 ; RTS; useless op; above JSR should just JMP instead
; attacked Undead with Cure; why not call $B571 directly?
; control flow target (from $B59B)
0x0335B3|$0C:$B5A3:20 52 BD JSR $BD52 ; calculate and inflict resistable spell damage; routine is identical to $B571
; control flow target (from $B5A0)
0x0335B6|$0C:$B5A6:60 RTS ; useless op; above JSR should just JMP instead
I'm assuming based on your comments here that the code should look more like...
; handler for Battle Commands #$15: Cast Spell ID #$D4: Cure and #$29: Cast Heal
; indirect control flow target (via $BE8E)
0x0335A5|$0C:$B595:A0 15 LDY #$15 ; Battle stat offset for Monster Family (80:Undead, 40:Werebeast, 20: Dragon, 10:Spellcaster, 08:Giant, 04:Earth, 02:Aquatic, 01:Magic Beast)
0x0335A7|$0C:$B597:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x0335A9|$0C:$B599:29 80 AND #$80 ; Undead
0x0335AB|$0C:$B59B:D0 03 BNE $B5A0 ; attacked Undead with Cure; why not call $B571 directly?
0x0335AD|$0C:$B59D:4C FB BC JMP $BCFB ; heal target's HP by unresistable spell damage amount, set $7CB0 to #$0B, queue battle message, set $4A-$4B to 0
0x0335B0|$0C:$B5A0:4C 71 B5 JMP $B571 ; calculate and inflict resistable spell damage
0x0335B3|$0C:$B5A3:EA NOP
0x0335B4|$0C:$B5A4:EA NOP
0x0335B5|$0C:$B5A5:EA NOP
0x0335B6|$0C:$B5A6:EA NOP
Also, I'm assuming that it's safe to remove (pad currently) these 8 bytes and simply replace all control flow redirects to it to $B571 instead:
; calculate and inflict resistable spell damage; routine is identical to $B571
; control flow target (from $B5A3, $BB17)
0x033D62|$0C:$BD52:20 7B BD JSR $BD7B ; calculate resistable spell damage in $4A-$4B
0x033D65|$0C:$BD55:A0 0A LDY #$0A ; Battle stat offset for Current HP low byte
0x033D67|$0C:$BD57:4C 79 B5 JMP $B579 ; subtract amount in $4A-$4B from ($A1),Y-($A1),(Y+1), set to 0 if negative; called with Y = #$0A to reduce target's current HP
I am curious, is there a reason this line even exists? It doesn't seem like it's necessary as it's literally jumping down to the next line, unless there's some implicit side effects of a JMP instruction I'm not understanding. Otherwise, couldn't we take these 3 bytes back?
0x033586|$0C:$B576:4C 79 B5 JMP $B579
Also, is it safe to assume that your 16 "useless OP" comments means I can NOP out those bytes for when we do code-shifting and consolidation in the future? I'm also aiming once more to move as little code as possible until we've fixed all bugs, make a Bug Fix Only patch to be used as a base for any future hackers for Final Fantasy II, and then after that is stable and ready and safely backed up in an IPS file, begin the drastic overhaul of code for the rest of Restored's enhancements.
Here's my trimming in progress, so you can see my thought pattern before any addresses are changed: https://drive.google.com/open?id=1GcTbDCK9J9YCJEJabSCSA_RN1Ku5z4jD
0x0337EF|$0C:$B7DF:20 98 FC JSR $FC98 ; 16-bit multiplication (little endian): $00-$01 * $02-$03, product stored in $04-$07; leaves X at #$00, C clear, $00-$01 and Y not changed
0x0337F2|$0C:$B7E2:A0 02 LDY #$02 ; Battle stat offset for Defense
0x0337F4|$0C:$B7E4:18 CLC ; add product to target's Defense; BUG: if product > 255, high byte is not checked, so if you score +256, you actually get +0 :(
0x0337F5|$0C:$B7E5:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x0337F7|$0C:$B7E7:65 04 ADC $04
0x0337F9|$0C:$B7E9:91 A1 STA ($A1),Y ; pointer to target's battle stats
0x0337FB|$0C:$B7EB:90 07 BCC $B7F4 ; cap Defence at 255; BUG: incorrectly displays 'Ineffective' when buffing from <= 255 to > 255
0x0337FD|$0C:$B7ED:A9 FF LDA #$FF
0x0337FF|$0C:$B7EF:91 A1 STA ($A1),Y ; pointer to target's battle stats
Was trying to think of the best way to address this, and my current spitball is to have a BCS before the CLC? I mean, is it not safe to assume that if the high byte was set with anything other than 0, that Carry is Set, and thus it means that the bonus you're getting is >255? The fix for Protect and Berserk could be as simple as saying if we return from FC98 with a carry, then jump straight to LDA #$FF, right? Or am I misinterpreting the math? Should I be doing a LDA $05, CMP #$00, BEQ instead?
Quote from: redmagejoe on March 15, 2020, 02:40:46 PM
Yikes, I mean... it's better than it currently is, though I think maybe we should try to figure out the formula the remake uses. Specifically, the version that you use in the normal game, since apparently Minwu's version in Rebirth of Souls is significantly more powerful and has a different formula.
Even counting skill levels from 0, 24 skills * 15 skill level + 100 base power + 24 power from Spirit = 484 base damage per success, which works out to an average of 750.2 total damage per success; assuming a 100% success rate, a level 16 spell gets 32 successes, so that's 24006 damage. Maybe just divide spell power by 2? Do the remakes use the same enemy HP or incorporate any magic damage defence like the existing physical damage defence?
Quote from: redmagejoe on March 15, 2020, 02:40:46 PM
I'm comparing them side-by-side, but there's enough differences in instructions that I'd have to see if branches alone can save enough space to justify rewriting one or both.
I was thinking more along the lines of this, which is still 24 bytes shorter than the original even after fixing 3 bugs:
; The Protect and Berserk spells are essentially the same code operating on different stats; this patch combines both routines to save space and fixes the following bugs:
; 1) the original code calculates the product of Spell Power * Spell Successes but then ignores the high byte, so scoring e.g. +256 actually gets you +0;
; 2) the original code displays the "Ineffective" battle message whenever the new stat value > 255, which is incorrect when the old stat value was < 255;
; 3) the original code does not display the "Ineffective" battle message in cases where the stat's value did not change.
; from $0C:$BE9E
Protect_handler:
LDA #$02 ; Battle stat offset for Defense
STA $5E
LDA #$5C ; Battle Message ID #$5C: 'Def.'
STA $5F
BNE Protect_Berserk_common
; from $0C:$BEB4
Berserk_handler:
LDA #$1A ; Battle stat offset for Primary Hand Attack Power
STA $5E
LDA #$5B ; Battle Message ID #$5B: 'Att.'
STA $5F
Protect_Berserk_common:
JSR $BDE9 ; calculate number of spell attack attempts in $46 and spell attack successes in $48
LDY #$27 ; Battle stat offset for (modified) Spell Power
LDA ($9F),Y ; pointer to actor's battle stats
; calculate spell power * spell successes
STA $00
LDA #$00
STA $01
LDA $48
STA $02
LDA $49
STA $03
JSR $FC98 ; 16-bit multiplication (little endian): $00-$01 * $02-$03, product stored in $04-$07; leaves X at #$00, C clear, $00-$01 and Y not changed
LDY $5E ; Battle stat offset for stat to raise
LDA ($A1),Y ; pointer to target's battle stats
STA $5E ; save old stat value
LDA $05 ; high byte of product
BNE .cap ; if product > 255, cap stat at 255
; add product to target's stat; we know C is clear thanks to the CMP #$FF
LDA ($A1),Y ; pointer to target's battle stats
ADC $04 ; low byte of product
BCC .write_stat ; if new stat > 255, cap stat at 255
.cap:
LDA #$FF
.write_stat:
STA ($A1),Y ; pointer to target's battle stats
CMP $5E
BEQ .ineffective ; if new stat value == old stat value, display "Ineffective"
LDA $5F ; Battle Message ID for stat to raise
JSR $BF92 ; add battle message ID A to the list of battle messages to display
LDA #$E6 ; Battle Message ID #$66: ' up!'
JSR $BF92 ; add battle message ID A to the list of battle messages to display
JMP $BE7E ; set $4A-$4B to 0
.ineffective:
JMP $BE73 ; clear bit 6 of $28, queue Battle Message ID #$14: 'Ineffective', set $4A-$4B to 0
but if you want to keep them in their original locations, there's enough free space to toss in some JMPs between the two regions.
Quote from: redmagejoe on March 15, 2020, 02:40:46 PM
I'm assuming based on your comments here that the code should look more like...
; handler for Battle Commands #$15: Cast Spell ID #$D4: Cure and #$29: Cast Heal
; indirect control flow target (via $BE8E)
0x0335A5|$0C:$B595:A0 15 LDY #$15 ; Battle stat offset for Monster Family (80:Undead, 40:Werebeast, 20: Dragon, 10:Spellcaster, 08:Giant, 04:Earth, 02:Aquatic, 01:Magic Beast)
0x0335A7|$0C:$B597:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
0x0335A9|$0C:$B599:29 80 AND #$80 ; Undead
0x0335AB|$0C:$B59B:D0 03 BNE $B5A0 ; attacked Undead with Cure; why not call $B571 directly?
0x0335AD|$0C:$B59D:4C FB BC JMP $BCFB ; heal target's HP by unresistable spell damage amount, set $7CB0 to #$0B, queue battle message, set $4A-$4B to 0
0x0335B0|$0C:$B5A0:4C 71 B5 JMP $B571 ; calculate and inflict resistable spell damage
0x0335B3|$0C:$B5A3:EA NOP
0x0335B4|$0C:$B5A4:EA NOP
0x0335B5|$0C:$B5A5:EA NOP
0x0335B6|$0C:$B5A6:EA NOP
You can also get rid of the JMP $B571 if you just BNE $B571 directly instead of BNE $B5A0 -> JMP $B571.
Quote from: redmagejoe on March 15, 2020, 02:40:46 PM
Also, I'm assuming that it's safe to remove (pad currently) these 8 bytes and simply replace all control flow redirects to it to $B571 instead:
; calculate and inflict resistable spell damage; routine is identical to $B571
; control flow target (from $B5A3, $BB17)
0x033D62|$0C:$BD52:20 7B BD JSR $BD7B ; calculate resistable spell damage in $4A-$4B
0x033D65|$0C:$BD55:A0 0A LDY #$0A ; Battle stat offset for Current HP low byte
0x033D67|$0C:$BD57:4C 79 B5 JMP $B579 ; subtract amount in $4A-$4B from ($A1),Y-($A1),(Y+1), set to 0 if negative; called with Y = #$0A to reduce target's current HP
Yup, $0C:$BD52 is a complete waste of space.
Quote from: redmagejoe on March 15, 2020, 02:40:46 PM
I am curious, is there a reason this line even exists? It doesn't seem like it's necessary as it's literally jumping down to the next line, unless there's some implicit side effects of a JMP instruction I'm not understanding. Otherwise, couldn't we take these 3 bytes back?
0x033586|$0C:$B576:4C 79 B5 JMP $B579
That one's also useless, though only because $B579 (which is called from a lot of places) just happens to be next address.
Quote from: redmagejoe on March 15, 2020, 02:40:46 PM
Also, is it safe to assume that your 16 "useless OP" comments means I can NOP out those bytes for when we do code-shifting and consolidation in the future?
Sometimes NOP is fine, sometimes nearby code might need to be tweaked. It depends on the situation, but the right thing to do should be fairly obvious in each case - I was more concerned with figuring out what the code was doing than with looking at how inefficient it was, so those "useless op" notes tend to be about superficial things.
Quote from: redmagejoe on March 15, 2020, 02:40:46 PM
The fix for Protect and Berserk could be as simple as saying if we return from FC98 with a carry, then jump straight to LDA #$FF, right? Or am I misinterpreting the math? Should I be doing a LDA $05, CMP #$00, BEQ instead?
$FC98 always leaves C clear, so you will have to check $05, but LDA updates Z for you, so you don't need an explicit CMP #$00 and can just BEQ.
I'll leave the "useless" alone for now and it can be something we review later when we need to code shift. For now, I've taken back the three bytes for the useless JMP, and as per your suggestion, removed the JMP and turned the BNE 03 into a BNE D4 to go back up to $B571 to give us another three bytes. I'm going to try that rewrite like you suggested, as 24 bytes is extremely appealing. The rewrite should be functionally the same with no surprise side effects, yes? :P
I'm going to try and study the remake's Ultima before I sit down and tackle that. As for the Protect/Berserk >255 fix... well, I guess your rewrite already addresses that, from a cursory glance? The $A1 change to $9E that fixes Protect not working on other party members I assume is also in, and... what was the third bug again? Been working on this so long I'm forgetting what bugs we fixed already. ;D
So original Berserk code is 51 bytes, and original Protect code is 55 bytes (total of 106 bytes). Your rewritten code is 82 bytes. That doesn't quite fit in the original space for either of them, but considering the use of branch demands that they be within a certain range of each other, I think it would be best to move Berserk and Protect right up into the same space and shift everything else down. A lot of pointers are going to be rewritten for this, but I think it will be worth the effort in the long run. I'll get to work and update the txt file in the link as soon as I can. This will become quite involved and will involve rewriting a large portion of spell handler code, so I'm going to grab the three spell-related fixes, extract the relevant instructions and set them aside to be incorporated into another comprehensive patch. If you spot any other places where code can be made more efficient that I might overlook, let me know.
EDIT: Updated txt, still WIP. I haven't worked in the redone Protect/Berserk code yet as I'd like to look at other places that spell code can be cleaned up before we settle on shifting everything. For example, the excerpt of code now contains the Wall code, the Drain/Osmose code (let's look at that battle pose bug, shall we?), the Cyclone/Quake/Tsunami code, and will soon contain the Imbibe code. This seems like a good opportunity to rewrite this entire bank just about and make sure the entire spell system is bug free and efficient.
Quote from: redmagejoe on March 17, 2020, 09:10:58 PM
The rewrite should be functionally the same with no surprise side effects, yes? :P
That's the plan, yes, though I haven't tested any of it yet :P.
Quote from: redmagejoe on March 17, 2020, 09:10:58 PM
The $A1 change to $9E that fixes Protect not working on other party members I assume is also in, and... what was the third bug again? Been working on this so long I'm forgetting what bugs we fixed already. ;D
Oh, right, I forgot to list the "Protect only working on the caster" bug, so that's actually 4 bugs fixed then. The other 3 I listed in comments:
- the original code calculates the product of Spell Power * Spell Successes but then ignores the high byte, so scoring e.g. +256 actually gets you +0;
- the original code displays the "Ineffective" battle message whenever the new stat value > 255, which is incorrect when the old stat value was < 255;
- the original code does not display the "Ineffective" battle message in cases where the stat's value did not change.
I also forgot to mention that I finally got around to fixing up the reassemblable version of the disassembly (https://drive.google.com/open?id=1JTrqhDW6kcxrujX5htiBaCLV1utPQZau), so for a large-scale rewrite like this is shaping up to be, it's probably better to work from that than the non-assemblable version.
And since I'm up to that point in my playthrough now, I noticed another place where calculated stats don't get updated after gaining base stats: the stat boosts from the Mysidian Tower Orbs!
That's probably a good place to insert another stat refresh, though I'll look at that alongside the optional patch after this big project. I'm going to take a look at the reassemble version and add that bug to the list. Also I meant 9F, not 9E.
0x033825|$0C:$B815:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
; control flow target (from $B81B)
0x033827|$0C:$B817:20 0E 90 JSR $900E ; clear the X'th bit (0-7) of A
0x03382A|$0C:$B81A:CA DEX
0x03382B|$0C:$B81B:10 FA BPL $B817
; BUG: STA ($A1),Y needs to go here! Without this, the spell has no effect!
0x03382D|$0C:$B81D:A2 00 LDX #$00
My Dispel fix actually has the STA ($A1),Y above the DEX. Should it be after the branch instead? I'm trying to remember why I thought it needed to go there... I moved the transcribe WIP down to directly above the Dispel code so you can review if I did something wonky with it. Updated the txt again, this is as far as I'm comfortable shifting the mock-up until I look at the code a bit more closely and see where I can find inefficiencies or bugs that will need space. I moved only what seemed to be as clean as it could be thus far.
For the sake of as accurate an Ultima as possible to the remade version, I am doing some testing. First tests are with a 99 Spirit Firion (equipment doesn't matter as there's no stat penalties on equipment in remakes, to my knowledge) with 8/8 weapons at 16, and 16/16 spells at 16 including Ultima. I will use cheats to adjust levels as necessary and post damage values on the lowest magic resist enemies available (1-50%). Thankfully Kashuan Keep still has pre-Cyclone enemies to test on.
Weapons 8x 16 - Spells 16x 16
7867, 7876, 7899, 7918, 7934, 7941, 7969, 9999 (crit?)
Weapons 8x 16 - Spells 16x 1
244, 288, 299, 319, 325, 355
Weapons 8x 1 - Spells 16x 1
138, 142, 152, 184, 230
Weapons 8x 0 - Spells 16x 0
121, 122, 126, 147, 155, 157, 168
Weapons 7x 0, 1x 16 - Spells 15x 0, Ultima 2
157, 172, 180, 188, 195, 248
First note: Ultima completely ignores Spirit, Intellect, Magic Power. It seems to only be affected by its own level and skill levels, as stats made no difference between 0 and 99. Otherwise, it's very difficult to tell exactly how it's behaving without digging into the source of the game itself (which is a whole other Pandora's box) I would gladly welcome the interest of anyone who might want to do some extensive testing of Ultima on the PSP version, since it seems like it could become a very time-consuming process to reverse engineer. I have available the cheats file and a save file for all necessary testing, but I don't want to get too distracted from the bug-fixing at the moment.
Quote from: redmagejoe on March 17, 2020, 11:16:28 PM
My Dispel fix actually has the STA ($A1),Y above the DEX. Should it be after the branch instead?
Both places will work, but you save cycles by only writing the final result once after the loop finishes instead of writing all the intermediate results inside the loop.
Quote from: redmagejoe on March 17, 2020, 11:16:28 PM
For the sake of as accurate an Ultima as possible to the remade version, I am doing some testing.
On the NES, spell damage per success is calculated the same way as physical damage per success, except the target's defense value is always 0. So each success deals (spell power) + random([0..(spell power)]) with a 5% chance to critical hit, which adds (spell power) to the total; that works out to anywhere between (spell power) and 3 * (spell power) with an average value of (spell power) * 1.55.
Spell power gets modified by the caster's Intellect/Spirit before jumping to the individual spell handlers, so if we want to undo that and use Ultima's base power, we'll have to fetch it again.
I suppose no matter what we do, Ultima's damage should get capped at 9999 anyway.
Yeah, I'm going to set aside Ultima testing until everything else just about in the spell block is ready to go. It seems like it may become more complicated, and it would be nice if I could find someone who wanted to do some exhaustive testing on it on PSP.
So having revised my Dispel fix as I'm polishing up spell code, I am assuming that the way to best fix this is to A) remove the first BPL, B) change the LDX #$07 to LDX #$08, and C) add the STA ($A1),Y, which for the sake of saving cycles will be moved to after the BPL that jumps up two instructions.
0x033817 10 02 30 30 E0 08 90 02 A2 07 86 52 A0 05 B1 A1 20 0E 90 CA 10 FA
>>>
0x033817 30 32 E0 08 90 02 A2 08 86 52 A0 05 B1 A1 20 0E 90 CA 10 F8 91 A1
From
0x033817|$0C:$B807:10 02 BPL $B80B
0x033819|$0C:$B809:30 30 BMI $B83B ; 0 successes => Ineffective
; control flow target (from $B807)
0x03381B|$0C:$B80B:E0 08 CPX #$08
0x03381D|$0C:$B80D:90 02 BCC $B811 ; cap X at 7
0x03381F|$0C:$B80F:A2 07 LDX #$07
; control flow target (from $B80D)
0x033821|$0C:$B811:86 52 STX $52
0x033823|$0C:$B813:A0 05 LDY #$05 ; Battle stat offset for Armor Resistance bits (80:Ice, 40:Body, 20:Poison, 10:Death, 08:Lightning, 04:Mind, 02:Fire, 01:Matter)
0x033825|$0C:$B815:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
; control flow target (from $B81B)
0x033827|$0C:$B817:20 0E 90 JSR $900E ; clear the X'th bit (0-7) of A
0x03382A|$0C:$B81A:CA DEX
0x03382B|$0C:$B81B:10 FA BPL $B817
to
0x033817|$0C:$B807:30 32 BMI $B83B ; 0 successes => Ineffective
0x033819|$0C:$B809:E0 08 CPX #$08
0x03381B|$0C:$B80B:90 02 BCC $B80F ; cap X at 8
0x03381D|$0C:$B80D:A2 08 LDX #$08
; control flow target (from $B80B)
0x03381F|$0C:$B80F:86 52 STX $52
0x033821|$0C:$B811:A0 05 LDY #$05 ; Battle stat offset for Armor Resistance bits (80:Ice, 40:Body, 20:Poison, 10:Death, 08:Lightning, 04:Mind, 02:Fire, 01:Matter)
0x033823|$0C:$B813:B1 A1 LDA ($A1),Y ; pointer to target's battle stats
; control flow target (from $B819)
0x033825|$0C:$B815:20 0E 90 JSR $900E ; clear the X'th bit (0-8) of A
0x033828|$0C:$B818:CA DEX
0x033829|$0C:$B819:10 FA BPL $B815
0x03382B|$0C:$B81B:91 A1 STA ($A1),Y
Quote from: redmagejoe on March 18, 2020, 08:18:20 PM
So having revised my Dispel fix as I'm polishing up spell code, I am assuming that the way to best fix this is to A) remove the first BPL, B) change the LDX #$07 to LDX #$08, and C) add the STA ($A1),Y, which for the sake of saving cycles will be moved to after the BPL that jumps up two instructions.
Yes to A and C, no to B, unless you're also going to rewrite $900E to correctly handle clearing the 9th bit of a byte :P.
I spent some more time commenting on code and have updated both versions of the disassembly with the results, including progress on working through the battle pose mechanics, which is tied in to status ailments and their display, which opened up a rabbit hole I didn't feel like going down at the time. So instead I rewrote the spell handlers (https://drive.google.com/open?id=1_uT421YZgeUhJgZqhKXLZZxCXOPqw9gS) to fix the following known bugs:
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
- Life only writes the low byte of HP to be restored on success, which may result in leaving the target dead due to 16-bit math error.
- Sap only acts on the least significant byte rather than the whole of a target's MP. Investigate and correct.
- Osmose drains MP even if the target has none. Needs to check source MP before awarding it.
- Protect and Berserk bonus may overflow before being added to Defense or Attack, resulting in low or no effect.
- Wall can be exploited by casting it on monsters to ensure that Toad, Break, Death, and Warp work on them. Wall's effects should likely be checked before any routines handling animation or other effects take place. Investigate.
- Cyclone/Quake/Tsunami should be resisted by certain monster families, but the branching code checks the wrong processor flag (C instead of Z), which means family resistance is broken but that bug is cancelled out by another bug where after calculating the target's number of successful resists, the result is never used, which means resistance for these spells is just totally broken.
- Imbibe decreases the target's evasion % by 1, but there is no check for underflow, so you could end up going from 0 to 255 % evasion; having 255 evasion probably breaks other logic elsewhere.
- Status-curing items don't set the variable containing the target's previous ailments when calling the code for determining which battle message to display, resulting in an incorrect message being displayed (e.g. if you suffer from both Amnesia and Curse, curing Curse in battle with a Cross will display the message for curing Amnesia.
and the following further bugs I came across while doing so:
- Basuna always clears the Critical HP flag
- Basuna always cures Poison, even with 0 successes
- Slow and Fear are slightly too powerful, only failing with negative net successes instead of also failing with 0 net successes
- Fear calculates spell power * net successes, but does not check the high byte of the product
as well as incorporating the earlier Level 8 Aura / Barrier Fix, Dispel Fix, and Protect Fix. Testing all of that is going to be fun :P.
That is... a LOT of changes. Wish I'd known you were tackling some of these before I got halfway through my fixes. :(
Nonetheless, I am once again in awe of your efforts. I, uh, don't suppose you have your ASM for it handy? I'd really like to try and familiarize myself with the changes so I can understand what the instructions are doing. Not that I haven't transcribed hex to code before, but if I can avoid it... :D
And ooh, 54 bytes left over at the end of everything? That's a LOT of nice juicy space! We'll probably need at least some of it for the Ultima fix, once we figure out what specifically it should be doing. I suppose since you went to all the trouble to rewrite the code, I could go back to testing Ultima in Anniversary and see if I can't narrow down its behavior...
The wild fluctuation in damage makes it very difficult to pin down differences. I am convinced though that stats play absolutely no role in damage. Attacking only Leg Eaters over and over again, damage ranges from 111 to 143, with the odd 164 and 173, and a 244 what I assume is a crit. All with Ultima 1, everything else at 0. Weapon Levels do appear to affect the damage as well as spell levels, though by how much I cannot quite tell. Certainly not enough to make a patch with. I don't know how shy of running this through some sort of simulation that does over 1000 trials we could get precise enough math to make an educated guess for our patch.
Quote from: redmagejoe on March 22, 2020, 11:27:00 PM
That is... a LOT of changes. Wish I'd known you were tackling some of these before I got halfway through my fixes. :(
Ah, sorry. Switching from documenting to coding was sort of a spur-of-the-moment thing. Once I had the reassembleable version working again, mostly all I had to do was the actual code changes, which didn't take long. Apparently I've successfully found and labelled enough of the game's pointers that making large changes in bank C does not crash the game, so that was a nice bonus ;D. I had to do some more digging to track down the Wall bug: both the Wall spell handler and the code for preventing blocked spells from running their handlers appeared to be correct; it actually was the spell animation handlers for instant-death spells that were killing the target! Blocked spells now all use the same harmless (hopefully, anyway) animation. In any case, having independent versions makes it easier to cross-check them for errors :thumbsup:.
Quote from: redmagejoe on March 22, 2020, 11:27:00 PM
Nonetheless, I am once again in awe of your efforts. I, uh, don't suppose you have your ASM for it handy? I'd really like to try and familiarize myself with the changes so I can understand what the instructions are doing. Not that I haven't transcribed hex to code before, but if I can avoid it... :D
ASM! (https://drive.google.com/open?id=106PCOr_OxeUVlhbGzndOAFBlqAo4woEL) Diff it against the reassembleable version to see the rewrite changes. I've also updated a couple of things after some testing and regenerated the patch, so you'll want to download the latest version.
Quote from: redmagejoe on March 22, 2020, 11:27:00 PM
And ooh, 54 bytes left over at the end of everything? That's a LOT of nice juicy space! We'll probably need at least some of it for the Ultima fix, once we figure out what specifically it should be doing. I suppose since you went to all the trouble to rewrite the code, I could go back to testing Ultima in Anniversary and see if I can't narrow down its behavior...
Oh, I also threw in my OP Ultima change, capped at 9999 damage, just for fun. Should be easy enough to slot in something more accurate once we have it. Another thing I should mention is that I made code changes to fix the order of the battle messages for Aura/Barrier/Dispel, so it'll be compatible with any translations.
Quote from: redmagejoe on March 22, 2020, 11:27:00 PM
The wild fluctuation in damage makes it very difficult to pin down differences. I am convinced though that stats play absolutely no role in damage. Attacking only Leg Eaters over and over again, damage ranges from 111 to 143, with the odd 164 and 173, and a 244 what I assume is a crit. All with Ultima 1, everything else at 0. Weapon Levels do appear to affect the damage as well as spell levels, though by how much I cannot quite tell. Certainly not enough to make a patch with. I don't know how shy of running this through some sort of simulation that does over 1000 trials we could get precise enough math to make an educated guess for our patch.
I think the NES version can do anywhere between 100 and 1116 damage depending on the RNG and your Spell Power boost, and I would not want to try to determine that damage formula observationally :banghead:! If you're able to hack it, try setting Ultima's base power to 0 (or 1 if that doesn't work); assuming the damage calculation algorithm is similar to the NES version's, that should mostly eliminate the damage fluctuations, leaving only the 5% chance per hit for a crit, which should be easy to identify within a couple of repetitions.
It's alright, I'd rather work with a more comprehensive rewrite than my in-progress scrappy one. What, other than Wall, have you tested so far that we can cross it off our bug list? I wouldn't even begin to know how to hack the PSP version of the game. I looked at its code in the disassembler built into ppsspp, and... it's a completely different ball game there. :o
Should I revert the reversal of messages for Dispel and Barrier on my ChaosRush translation, then, if you've fixed the code to make it play nice with current translations?
There's a lot of fixes to test... I'll need to wait until I can devote some time away from work before I can begin tackling playtesting.
It also occurs to me that while the actual behavior of Ultima in remakes is still an enigma, there's some fairly easy math that could be used to make the final spell fairly balanced. 1 point of guaranteed damage per level in a skill. Consider 16 spells (Ultima included) and 8 weapon types, all with 16 levels. That's 24 x 16 = 384. Make the base damage of Ultima something like 100 (or 116 for the sake of nice round numbers), and you end up with 500 damage per hit, with 16 hits on a Level 16 Ultima giving ~8000 damage. Might be an easier scaling spell that way rather than having it do obscene damage and then forcing a 9999 cap.
Quote from: redmagejoe on March 23, 2020, 06:31:00 PM
What, other than Wall, have you tested so far that we can cross it off our bug list?
At this point I think I've got everything except Tsunami and friends tested. I'll update the patch once I've got those tested. I was having second thoughts about whether the current behaviour of Haste/Slow was really a bug or not, but I think I'm going to stick with 0 net successes not negating the opposing spell's effect. I also noticed a few more bugs:
- Casting Basuna on a target with only the Critical HP ailment results in displaying the message for Fear. (This is a consequence of the other bug where Basuna mistakenly clears the Critical HP flag.)
- If Esuna/Basuna fail to clear the highest present ailment, they display the Ineffective message even if they did clear other ailments.
- Sap does not reduce target's MP with 0 successes but still displays its success message.
It's not exactly a bug, but I also converted the code for displaying the message for the highest-cured Ailment into a loop, so Esuna/Basuna now display messages for each Ailment they cured.
Quote from: redmagejoe on March 23, 2020, 06:31:00 PM
Should I revert the reversal of messages for Dispel and Barrier on my ChaosRush translation, then, if you've fixed the code to make it play nice with current translations?
Yup, there's no need to re-order the strings anymore, so those can be reverted. While you're there, though, Chaos Rush's battle message for curing Amnesia is just "Cured of", which is not particularly helpful. Maybe we could use something like "Memory returned" instead?
Quote from: redmagejoe on March 23, 2020, 06:31:00 PM
It also occurs to me that while the actual behavior of Ultima in remakes is still an enigma, there's some fairly easy math that could be used to make the final spell fairly balanced. 1 point of guaranteed damage per level in a skill. Consider 16 spells (Ultima included) and 8 weapon types, all with 16 levels. That's 24 x 16 = 384. Make the base damage of Ultima something like 100 (or 116 for the sake of nice round numbers), and you end up with 500 damage per hit, with 16 hits on a Level 16 Ultima giving ~8000 damage. Might be an easier scaling spell that way rather than having it do obscene damage and then forcing a 9999 cap.
On the other hand, unless you've hacked your skill levels, massively abused the target/cancel bug, or spent probably hundreds of hours grinding, how high are your endgame skill levels realistically going to be?
Updated first post, will try to help with testing everything in the spell package when work settles down, and I will try to fix the Amnesia message in the Chaos translation.
Taking a closer look at Cyclone/Quake/Tsunami, it's a little hard to tell what the intended behaviour actually was. The original code
- calculates the number of hits as spell level + successes
- tries (but fails) to set the number of hits to 0 if the target is a member of a family included in the spell's data byte 3
- calculates the target's number of resist successes (but does nothing with them)
- calculates damage based on number of hits and spell power
- doubles the damage if the target is a member of a family included in the spell's data byte 4
- inflicts the damage
Since the spell's data byte 4 pretty obviously means families that are weak to the spell, I'm assuming byte 3 should mean families that resist the spell, but more on that in a moment. This code is unusual in that the target appears to be intended to resist the total number of hits, not just the successes, suggesting that full resistance should result in 0 hits, which logically and in agreement with physical attacks ought to mean a miss or 0 damage. However, because of the way the damage calculation loop works (LDY $hits/calculate damage for current hit and add to total damage/DEY/BNE @loop), calling it with 0 hits calculates 256 hits, resulting in extremely high (as in 2000 per target) damage, which is pretty much the opposite of what resistance should mean :P. On the other hand, only monsters have families and the party's only access to these spells is via 2 breakable items, so maybe being strong against some families and OP against others isn't a mistake? It would possibly make Earth Drums and Wind Flutes worth their inventory space.
So, we've got some choices here:
- we can leave these spells with their current unresistable behaviour
- we can let resistance work against the total number of hits instead of just successes, but fix it so that 0 hits gives full immunity (probably with the Ineffective message, but 0 damage would work too) instead of OP damage
- as above for normal spell resistance, but make the spell's data byte 3 mean OP damage against the corresponding family
In any case, families included in the spell's data byte 4 should still take double damage.
Thoughts?
I can try to test them out in Anniversary. Quake comes from Gaia Drum, Cyclone comes from Wind Flute, and what gives Tsunami? Also, what families should resist what?
I'd like to try and make them behave as close to intended behavior as possible. The second one sounds like the most in-line with what the code was trying to do, just from how you've explained it.
Quote from: redmagejoe on March 25, 2020, 08:22:11 PM
I can try to test them out in Anniversary. Quake comes from Gaia Drum, Cyclone comes from Wind Flute, and what gives Tsunami? Also, what families should resist what?
Wind Flute casts Cyclone 13, Earth Drum casts Quake 10, and the party has no access to Tsunami (except if you can confuse a monster that casts it, I guess).
Cyclone deals double damage to Magic Beast and ??? damage to Earth;
Quake deals double damage to Earth and ??? damage to Magic Beast;
Tsunami deals double damage to Aquatic and ??? damage to Magic Beast.
Quote from: redmagejoe on March 25, 2020, 08:22:11 PM
I'd like to try and make them behave as close to intended behavior as possible. The second one sounds like the most in-line with what the code was trying to do, just from how you've explained it.
Yeah, probably, although of the three options, the first one seems like the most in-line with the rest of the game. There is code for calculating unresistable amounts, but it's only used by healing spells, not attack spells. This would be the only set of spells where resist attempts are able to reduce hits from the spell level. No other damage spell is able to miss or inflict 0 damage, and 256 hits is just plain ridiculous. So, they're weird any way. They're also fairly uncommon, so it probably doesn't matter too much which way they're adjusted.
I'll play around with it in Anniversary and get back to you.
EDIT: So I've noticed that "Magic Beasts" sometimes seem to be synonymous with "Flying" types. So Hornets and Queen Bees are considered whatever this family is. Quake 10 missed on all 5 of them in a group, while doing massive damage to Leg Eaters and Vampire Thorns, who are considered Earth. I will assume this to mean that Quake should be completely null against Flying/Magic Beasts/whatever that bit is on monster family. I will continue testing on the same enemy formation with Cyclone this time, but it seems clear to me that the behavior as intended is the ??? should be immunity. And indeed, Wind Flute missed 7 Leg Eaters / Vampire Thorns.
I think that settles it in terms of how we should fix it. Also, I really apologize that my contributions have slowed down. I feel guilty that it seems like you've tackled this huge part of the project while I'm trying to find time. The current crisis has complicated work, but I'm doing my best to clear my table, so to speak, so I can focus on this project once more. This project wouldn't even be possible without your awesome contributions, abw. :thumbsup:
Quote from: redmagejoe on March 25, 2020, 09:55:59 PM
So I've noticed that "Magic Beasts" sometimes seem to be synonymous with "Flying" types.
Yeah, I noticed that too, but it includes a decent number of non-fliers and some things that are also Earth family, so I wasn't quite sure what to make of it.
Quote from: redmagejoe on March 25, 2020, 09:55:59 PM
I think that settles it in terms of how we should fix it.
Okay, spell data byte 3 now grants immunity to the corresponding family. For non-immune families, I've made the number of hits be calculated the same as other non-elemental damage spells, i.e. spell level + resistable successes, and have kept double damage to the families in spell data byte 4. I've uploaded the latest files; the spell fixes currently weigh in at 81 bytes shorter than the original!
Quote from: redmagejoe on March 25, 2020, 09:55:59 PM
Also, I really apologize that my contributions have slowed down. I feel guilty that it seems like you've tackled this huge part of the project while I'm trying to find time. The current crisis has complicated work, but I'm doing my best to clear my table, so to speak, so I can focus on this project once more. This project wouldn't even be possible without your awesome contributions, abw. :thumbsup:
No worries - I've actually got more free time now that I don't have to commute back and forth every day, so that helps ;). I was already familiar with the spell code thanks to going through and documenting it, so the testing has actually taken longer than the coding :P.
Quote
Bug Fix To-Do List:
- WIP Life only writes the low byte of HP to be restored on success, which may result in leaving the target dead due to 16-bit math error.
- WIP Sap only acts on the least significant byte rather than the whole of a target's MP. Investigate and correct.
- WIP Osmose drains MP even if the target has none. Needs to check source MP before awarding it.
- WIP Protect and Berserk bonus may overflow before being added to Defense or Attack, resulting in low or no effect.
- WIP Wall can be exploited by casting it on monsters to ensure that Toad, Break, Death, and Warp work on them. Wall's effects should likely be checked before any routines handling animation or other effects take place. Investigate.
- WIP Cyclone/Quake/Tsunami should be resisted by certain monster families, but the branching code checks the wrong processor flag (C instead of Z), which means family resistance is broken but that bug is cancelled out by another bug where after calculating the target's number of successful resists, the result is never used, which means resistance for these spells is just totally broken.
- WIP Imbibe decreases the target's evasion % by 1, but there is no check for underflow, so you could end up going from 0 to 255 % evasion; having 255 evasion probably breaks other logic elsewhere.
- WIP Basuna always clears the Critical HP flag
- WIP Basuna always cures Poison, even with 0 successes
- WIP Slow and Fear are slightly too powerful, only failing with negative net successes instead of also failing with 0 net successes
- WIP Fear calculates spell power * net successes, but does not check the high byte of the product
- WIP Status-curing items don't set the variable containing the target's previous ailments when calling the code for determining which battle message to display, resulting in an incorrect message being displayed (e.g. if you suffer from both Amnesia and Curse, curing Curse in battle with a Cross will display the message for curing Amnesia.
I'm assuming that these are all good to go and mark off our list?
Quote from: redmagejoe on March 26, 2020, 12:06:47 PM
I'm assuming that these are all good to go and mark off our list?
Yes indeed, and these ones too:
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
- WIP Casting Basuna on a target with only the Critical HP ailment results in displaying the message for Fear. (This is a consequence of the other bug where Basuna mistakenly clears the Critical HP flag.)
- WIP If Esuna/Basuna fail to clear the highest present ailment, they display the Ineffective message even if they did clear other ailments.
- WIP Sap does not reduce target's MP with 0 successes but still displays its success message.
It's so amazing to see the list trimmed down so much. I grew anxious when the list started growing that it would only grow faster than it shrunk. I can't wait to get the beta patch distributed for playtesting before long. Do you think once we've finished all the bug fixes (which will be a separate patch made first and asked to have playtesting done on it), and need to start making space for enhancements, it would be of benefit to make a new disASM of that version? Just trying to think down the road, but I should probably try to focus on what's right in front of us.
EDIT: Did some quick testing and found a crash that results from, after some narrowing down work, pairing the Spell Handlers Rewrite with the Weapons Effect Fix. It only seems to occur when I have Maria cast Berserk. I'm assuming this is due to the fact that the Weapons Effect fix addresses Berserk (Yoichi's Bow), but it's using the pre-rewrite pointers? If my speculation is correct, I'm sure that Drain's new address will need to be taken into consideration as well, since I believe those were the only 2 spell handlers from weapons that were touched.
Presumably that'll be caused by the call to $BF9A in the weapon special effects patch, since $BF9A is now a bunch of 00 in the spell handlers rewrite patch :P. Since I've got the ASM files for both patches, gluing them together is easy (another reason why coding in text files is better than direct hex editing!); I only ran a quick test, but this (https://drive.google.com/open?id=1xPIQpnF-WPfeiXDOzRo-IscFhI2PYif3) ought to fix the problem.
Awesome. Is this a rewritten Weapons Effects + Spell Handlers Rewrite, or a stopgap? Obviously we're going to bundle everything together eventually so it doesn't matter too much, but I want to make sure I've got load order taken into account. In other words, does that IPS replace both Spell Handlers and Weapon Effects?
That's a replacement for both patches. I guess you could call it a rewrite, but really all I did was copy the Weapon Special Effects ASM, switch the syntax from Asar to ca65, and then paste it into the full assembly containing the Spell Handlers Rewrite changes.
I've updated the Weapons Effects + Spell Handlers Rewrite patch (same link as above) to include the rest of the previous bugfixes, merging them all together into a single patch that should be compatible with the Chaos Rush translation patch. I've also included fixes for the following:
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
- The logic for determining whether a monster will run or not calculates (total party HP - total monster HP) >> 5, but then ignores the high byte of the result and the possible carry from adding the monster's fear level.
As a consequence of the fear calculation fix, monsters are now probably running too often; a party with a total of around 6500 HP is quite capable of scaring away single monsters in the final room of the game.
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
- Combat engine gives credit for actions from both player characters and enemies before they've happened. Causes party to receive Magic Defense and Evasion credit even if the enemy is killed before it acts. Change counter increment behavior to happen after actions take place. May require significant rewriting of combat routine. Target/cancel exploit should be addressed through a solution.
By the time commands are executed, there doesn't appear to be anything left in RAM to indicate whether the command came from a spell or an item, so as a consequence only giving credit to commands that actually execute, items that cast spells will give credit for casting those spells, but only if the character already knows the spell being cast. I think I'm okay with that, as a mage gaining experience with a spell by observing an item that casts that spell still sounds reasonably logical.
In other news, I came across some more bugs:
- If a character is killed before their turn happens, their battle command and target are not cleared, which means that if the character gets revived before their turn comes up (even in a later round!), they will still try to execute their battle command.
- Monsters that are killed by poison/venom have their current HP set to 0 and KO Ailment bit set, but are not actually removed from battle; they remain valid targets (you can even cast Life on them to bring them back!) and need to attacked again in order to remove them from battle.
- When Leon rejoins, his starting stats don't match his calculated stats; the first time his stats are recalculated, he loses 8 primary hand damage, gains 1% primary hand accuracy, gains 10 secondary hand damage, loses 7% secondary hand accuracy, and loses 15% evasion.
And here's another formatting error in the Chaos Rush translation: when meeting up with Leon in Palamecia, the last letter of Maria's first line of dialogue ("Stop! Both of you! Why") gets chopped off ("Stop! Both of you! Wh") if her name is 6 characters long.
Thinking a little more about the running total of HP/MP loss, out-of-battle MP use should be included too.
Other than merging all our current fixes, was anything else changed, or any addresses moved around? I think I was trying to avoid merging everything until towards the end when it was clear we wouldn't need to move too much any more, but I guess we're getting close to that point. Also I knew about that dead character battle command thing early on, and yet somehow I neglected to mention it or add it to the list this entire time... No idea how the hell I let that happen.
I'll go ahead and update the first post to start using the combined patch and rename it simply to "Final Fantasy Restored (Bug Fixes Only) Beta" since that seems like the path we're taking this close to the finish line. I'll also add those bugs to the list. If possible it would be nice if we could fine-tune the fleeing of enemies if it is, indeed, too frequent compared to usual.
And did you actually manage to fix the target cancel exploit? I'm genuinely curious what approach you took to make that happen. Comparing our versions side by side and I see some places where I did some pretty rough fixes early on that it looks like you improved on, though the entire 0x1687A to 0x16A4B block looks like it's been significantly redone so I couldn't follow what all changed there.
At 0x16C51, there's 3 $05 which if I recall were the chance to lose stats that needed to be changed to $0B for the RNG change. Does that still hold or did you find a way to redo the RNG that can use the original values? Wow, there's a lot of mess in some of my early work I'm seeing now side-by-side, like leftover bytes and such. :-[
I'll make a note to fix that issue in the Chaos Rush translation in short order.
Quote from: redmagejoe on March 31, 2020, 09:57:01 AM
Other than merging all our current fixes, was anything else changed, or any addresses moved around? I think I was trying to avoid merging everything until towards the end when it was clear we wouldn't need to move too much any more, but I guess we're getting close to that point.
Oh, plenty of stuff got moved around - trying to maintain the original addresses of every pointer target was just too annoying. That said, there are about 90 new NOPs scattered throughout the rewritten code in order to keep certain things at the addresses expected by the Chaos Rush translation patch.
Quote from: redmagejoe on March 31, 2020, 09:57:01 AM
Also I knew about that dead character battle command thing early on, and yet somehow I neglected to mention it or add it to the list this entire time... No idea how the hell I let that happen.
:D
Quote from: redmagejoe on March 31, 2020, 09:57:01 AM
I'll go ahead and update the first post to start using the combined patch and rename it simply to "Final Fantasy Restored (Bug Fixes Only) Beta" since that seems like the path we're taking this close to the finish line. I'll also add those bugs to the list. If possible it would be nice if we could fine-tune the fleeing of enemies if it is, indeed, too frequent compared to usual.
Yeah, that'll require some more play-testing to find the right balance. The funny part about scaring endgame monsters away is that the monsters aren't wrong: by that point, most lone monsters can easily be killed in less than a round, very possibly before they have a chance to act :P.
Quote from: redmagejoe on March 31, 2020, 09:57:01 AM
And did you actually manage to fix the target cancel exploit? I'm genuinely curious what approach you took to make that happen. Comparing our versions side by side and I see some places where I did some pretty rough fixes early on that it looks like you improved on, though the entire 0x1687A to 0x16A4B block looks like it's been significantly redone so I couldn't follow what all changed there.
Yes, although after some further investigation, it looks like there are some variables still set that are related to determining whether a used item is consumed or not, so we could use those to distinguish between spells cast from the Item command and spells cast from the Magic command. However, that led to the discovery of another bug:
- There is an approximately 1% chance (1/256 with the original unbalanced RNG) for "non-breakable" items to be destroyed anyway when used in battle. It sure sucks to lose the Masmune that way :(
As for the fix itself, it was basically just moving the counter increments from the code for entering commands to the code for executing commands, with a little bit of extra work to reconstruct some information that was no longer as easily accessible during command execution, such as which of the current character's spell slots needed to have its use counter incremented.
Quote from: redmagejoe on March 31, 2020, 09:57:01 AM
At 0x16C51, there's 3 $05 which if I recall were the chance to lose stats that needed to be changed to $0B for the RNG change. Does that still hold or did you find a way to redo the RNG that can use the original values? Wow, there's a lot of mess in some of my early work I'm seeing now side-by-side, like leftover bytes and such. :-[
I left those as $05 for the time being on the assumption that the 1/6 chance to lose a stat was the original intent; changing it to 1/12 to match the actual behaviour is easy enough, but that might be more suited to a rebalance patch than a strictly bugfix patch. On that note, how strongly do you feel that equipment stat bonuses not stacking is actually a bug?
Quote from: abw on March 31, 2020, 11:24:45 PM
On that note, how strongly do you feel that equipment stat bonuses not stacking is actually a bug?
I'm using the Anniversary version as a basis to say that it was a bug.
I've got a bit more time to focus on this project at the moment, so I'm going to start looking at the following for now since it seems like it should be a relatively easy fix.
Quote
- Stat refresh needed after Mysidian Tower Orbs
Quote from: redmagejoe on April 01, 2020, 12:07:10 AM
I'm using the Anniversary version as a basis to say that it was a bug.
Alright, we'll leave it in then. On the subject of bugs, I've updated the item breaking code in the bug fix patch to ensure that items with a 0% chance to break actually won't break. However, I also noticed another couple of bugs:
- In addition to not being notified about your party surprising the enemy party until after you've entered commands (and knowing you have a free turn could definitely influence your command choices), it looks like the game still runs through the enemy AI, which in the original game results in your party gaining credit for being attacked even though the monsters didn't actually do anything.
- When viewing the party's inventory during NPC interactions, attempting to scroll 1 row up from the bottom page scrolls an entire page up.
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
- Investigate possibility of issue in event of >20 battle messages in queue.
On the plus side, I think we can cross this one off the list - battle messages can only overflow at the end of a battle, and $7FCE-$7FDD are used for tracking item use during battle, so overwriting them at the end of a battle is fine.
I've also updated the assembly with more commentary; of particular interest to the bug list are:
Quote from: abw on March 10, 2020, 09:21:32 PM
Quote from: redmagejoe on March 08, 2020, 06:54:42 PM
As stated, I'm settling with just removing the space in the Chaos Rush version for now, as I really don't want to redo the spell name character graphics, and I don't want to mess with window borders either.
Yup, that's fine. I'm just saying that based on other games I've looked at, we might get lucky and be able to expand the width of the spell name window by changing a single byte somewhere.
The position/size of the 5 battle message windows are set at $05:$AC02-$05:$AC1F, but the game determines the interior width separately; for the right two message windows (hits/spell level and damage), look for all the LDA #$07 scattered across the various cases between $0C:$A9F7 and $0C:$AA79.
Quote from: redmagejoe on December 10, 2019, 03:09:14 AM
- Certain key items take up valuable inventory slots long after they are relevant to the game or story, when they should be consumed within the context of the story. There is no way to dispose of them. Consider implementing an item delete of Goddess's Bell once it is used to open Kashuan Keep, the Pass after you use it to gain access to the landed Dreadnought, the Egil's Torch after acquiring Sunfire OR after Sunfire is thrown into the Dreadnought's core, and of course the Sunfire after the same event, White Mask after it is placed upon the Goddess Statue, Black Mask after it is placed upon the Doppelganger, and Pendant after it is used to summon the Wyvern, or after Elina and her son vanish from Deist if there's some interaction they use. Investigate if any of these items still have any even mundane interactions with NPCs, as beyond these points they no longer are crucial to game progression. This will free up to 7 inventory slots of the 11 that key items take up, of your 32 total inventory slots.
$0E:$9381-$0E:$94DA is where using key items is handled; TXA / JSR $957C / LDA #$00 (or whatever item ID you want, e.g. #$08 if you want to replace Egil's Torch by Sunfire) / STA $6060,X in the appropriate sections should get rid of the used key item. Based on story events, Egil's Torch, Sunfire, and the two Masks should definitely not still be in your inventory after you use them, but it's not clear that you actually lose the Goddess's Bell, Pass, or Pendant after using them. The Pass is an interesting case: you don't need to acquire it in the first place or use it once you have it since you can instead choose to fight your way past the Captain to get access to the Dreadnought. I don't believe the Crystal Rod is actually required for anything after Ricard joins; it ought to be used to gain entry to the Tower, but on re-entries at least (still need to verify your initial entry) you can still walk in and out with the entry animation even if you don't have a Crystal Rod in your inventory.
Awesome finds all around. I'll have to find some time to tamper with the Chaos Rush patch and try adjusting the window length by 1 to see if that remedies the issue. I've also updated the first post. When I get a chance, my main priorities will be the Mysidian Orbs refresh and the key items changes.
Well... clearly that wasn't what I was meant to do with those 4 LDA #$07s. I changed them to #$08, and the box itself didn't stretch, it just shifted the text over 1 space to the right, cutting off 1 more character than it was already. :D
Also, fun fact: Changing it to #$06 shift the first character up to the line above at the far right, so apparently the boxes are actually 2 character spaces tall. You could actually fit two whole lines in each box. :P I think actually changing the data that determines box size is the way to go, as the text seems to conform to the box.
; pointers to battle message window positioning data (width, height, top-left PPU address, bottom-left PPU address)
; indirect data load target (via $AA5A, $AA5E, $AB52, $AB56)
0x016C08|$05:$ABF8:02 AC ; $05:$AC02
0x016C0A|$05:$ABFA:08 AC ; $05:$AC08
0x016C0C|$05:$ABFC:0E AC ; $05:$AC0E
0x016C0E|$05:$ABFE:14 AC ; $05:$AC14
0x016C10|$05:$AC00:1A AC ; $05:$AC1A
; indirect data load target (via $ABF8)
0x016C12|$05:$AC02:0A 04 60 22 C0 22
; indirect data load target (via $ABFA)
0x016C18|$05:$AC08:0A 04 C0 22 20 23
; indirect data load target (via $ABFC)
0x016C1E|$05:$AC0E:09 04 69 22 C9 22
; indirect data load target (via $ABFE)
0x016C24|$05:$AC14:09 04 C9 22 29 23
; indirect data load target (via $AC00)
0x016C2A|$05:$AC1A:12 04 20 23 80 23
Sure enough that did the trick. Changing the 09s to 0As makes the box one character width longer. That said, now I DO have to change those LDA 07s to 08s, so it will start on the first space of the "second" line. The only thing about this solution that bugs me is that it covers up the first digit of character HP >999 for the moment it's up, whereas before it was all perfectly formatted to show all the pertinent data... So I'm left once more with the choice between leaving it with the number crammed against the 5th character in the case of 5-character spell names, or always covering up the HP digit regardless the situation. Hmm...
I'm kind of leaning towards the current fix, though it would be nice without shoving an extra space in every 4-character spell name to have the spell level not crammed against the name like is necessary for the 5-character spell names. Anyway, I fixed the message you mentioned by simply starting the "Why" on the next line with the rest of the sentence and some clever DTE usage. It also looks like Chaos Rush didn't quite run his changed banks through CastleFynn enough times. Careful not to upset any of the changes that required putting one or two entries in another bank (Beelzbub and Astaroth had to be stuck at the end of the Battle Messages bank due to the extra 15 bytes needed), and with the very minute typo corrections and changes to text, I was able to shave only 3 bytes off the HUGE bank 1 (amazing that it was so few considering this is where most of the game's story script is), but 75 bytes off the battle messages bank. That will be helpful if/when I need space at the end of this project.
3 freed bytes in Bank 1
6 freed bytes in Bank 2
9 freed bytes in Bank 3
75 freed bytes in Battle Messages
13 freed bytes in Misc 1, passed on to Misc 2 due to banks being consecutive
96 freed bytes in Misc 2, not including 13 inherited from Misc 1
3 freed bytes in Enemy Names
4 freed bytes in Attacks
Reminder that these only apply to the ChaosRush translation and not our current WIP project.
v2.0 of ChaosRush Translation has been submitted to the queue and will hopefully be up in the next day or two.
Quote from: redmagejoe on April 06, 2020, 01:28:58 AM
Well... clearly that wasn't what I was meant to do with those 4 LDA #$07s. I changed them to #$08, and the box itself didn't stretch, it just shifted the text over 1 space to the right, cutting off 1 more character than it was already. :D
Yeah, it's a little annoying that the window drawing code and the text layout code don't talk to each other - the game lays text out in the nametable buffer at $7600
before reading the window dimensions, so it takes twice as much work to resize windows :'(. The interior space is definitely 2 tiles high, though; in Japanese, the first row is used for diacritics and the second row for base characters.
Quote from: redmagejoe on April 06, 2020, 01:28:58 AM
The only thing about this solution that bugs me is that it covers up the first digit of character HP >999 for the moment it's up, whereas before it was all perfectly formatted to show all the pertinent data... So I'm left once more with the choice between leaving it with the number crammed against the 5th character in the case of 5-character spell names, or always covering up the HP digit regardless the situation. Hmm...
Some further food for thought:
- Seeing HP is definitely nice, but it's not like you can actually do anything about it during a round, and it will still be visible between actions;
- Damage amounts > 999 lose the "DMG" text and only display the damage amount (this is a bug in the J ROM too);
- Bigger windows allow for longer text; it would take further work (mostly on the in-battle spell selection UI), but you could have e.g. 8-letter spell names and 18-letter monster names, which would basically eliminate all of the current abbreviations.
Trimming 200+ bytes from the script, huh? That's always nice :D.
Eh, I opted to leave it the "easy" fix way, for consistency with the aesthetics. Honestly it's more of a nitpick to see "Cure16" instead of "Cure 16", and it seems in line with the style of NES RPGs of the time. If I obsess too much about it looking "pretty" over how it fits into the conventions of the time, I'd just end up on a slippery slope that would see me spending a lot of effort on completely overhauling the interface, which I'm not willing to set out to do.
Besides, calling it a day on that allows me to put the Chaos Rush project behind me and focus all my energy when I'm not working on this project of ours.
Chaos Rush Translation v2.0 is up!
https://www.romhacking.net/translations/2656/
As you can imagine, work has kept me away from this project for a bit, but I'm looking forward to get back into things soon. I updated the first post, as I can't recall why I hadn't removed the WIP from the combat counter / target cancel exploit fix. I'm assuming that all behaviors relating to counters and enemy/party targetting is working properly in your bug fix patch so far? Also in case I failed to mention, I've got the first post pointing to the link for your bug fix IPS, abw, since it seems like you have a better handle on the compilation now than I do. I will continue to test out patches and submit them to you as I find solutions to the items remaining on our list.
Quick question.
Have you guys looked into the spell targeting routines at all?
I'm curious about it for the FF1 project.
I feel confident that abw's thorough commenting has narrowed down the spell targeting code, but I haven't looked yet personally. When I have a bit of free time I'll see if I can't pinpoint it.
After an enemy decides what attack it's going to use, the code for taking the targeting byte and ending up with an actual target starts at $0C:$A571 and is at least somewhat documented in my disassembly, though not to the level you'll be used to from Disch's FF1 disassembly. From the player's side, the code for loading up the magic menu, picking a spell, and picking its target(s) starts at $0C:$9D44, but I haven't yet had a reason to care about it, so the documentation there is still pretty sparse.
Cool. Thanks for pointing me in the right direction!
Time for an update: I've tweaked the key item usage code to delete 8 useless key items from your inventory after their last known use. I suppose I ought to dig deeper and go through the data for what key terms and items a given NPC has text for, not just the code for doing non-text stuff; work, work, work :P. The new code seems okay in the few spots I've tested, but it'll probably require a complete playthrough for full confidence. I've roughed out a bunch of NPC and event flag stuff, mostly in bank E; if we ever need large amounts of space in banks 3 or E, those banks each have a pair of 256-byte lookup tables (for a total of 1024 bytes) to map 8-bit bit indexes to the corresponding byte and bit (e.g. to map flag #$5A to bit 1 of byte #$0B) that could be replaced by about 20 bytes of code (which might already exist somewhere!).
The Mythril and Wyvern Egg were already removed once you were done with them; now the Pass, Goddess's Bell, Egil's Torch, Sunfire, Pendant, White Mask, Black Mask, and Crystal Rod are also removed, leaving you with only the Ring, Canoe, Snowcraft, and Wyvern summon. If you decide to fight your way into the Dreadnought, the Pass now gets removed from your inventory and the chest containing the pass gets marked as open (whether you actually opened it to not) in order to prevent picking up the Pass after the window for getting it out of your inventory closes. The Wyvern summon is a potentially useful battle item (and you can decide to have Ricard take it with him when he goes), so it can stay, but for the others, the game only checks the list of obtained key items to determine whether their abilities apply on the world map, so if we wanted to we could basically convert the Canoe and Snowcraft to non-inventory items. The Ring could be removed from your inventory after you show it to Hilda, but that would be at odds with her dialogue where she explicitly tells you to keep it; I'm tempted to suggest some light script revision there, either to have Hilda take the Ring or maybe just ha