• Welcome to Final Fantasy Hacktics. Please login or sign up.
 
March 28, 2024, 06:04:35 am

News:

Use of ePSXe before 2.0 is highly discouraged. Mednafen, RetroArch, and Duckstation are recommended for playing/testing, pSX is recommended for debugging.


ASM understanding

Started by Orkney, January 10, 2021, 04:01:49 pm

Orkney

Hi,

I'm trying to write some ASM formulas, and i run into this code (in formula 15)

001891d0: 3402007f ori r2,r0,0x007f
001891d4: 3c038019 lui r3,0x8019
001891d8: 8c632d90 lw r3,0x2d90(r3) r3 = target current action data pointer
001891dc: 00000000 nop
001891e0: a0620013 sb r2,0x0013(r3)  Set target CT to -127.
001891e4: 3c038019 lui r3,0x8019
001891e8: 8c632d90 lw r3,0x2d90(r3)
001891ec: 34020001 ori r2,r0,0x0001
001891f0: a0620025 sb r2,0x0025(r3) 

I understand what it does, but i'm wondering, why there is two sequences ending to lw r3, 0x2d90(r3) ? r3 is not modified by the command sb r2 0x0013(r3) does it ?

Probably a dumb question...
  • Modding version: PSX

nitwit

1. Check if something else branches to the addresses from 1891d0 to 1891dc by searching the wiki for those values
2. Try nop-ing (zero out 4 bytes of an instruction) the first and second instance and see what happens. Do it twice: first time just see if the skill seems to function, second time look at registers and step through it instruction by instruction to see what's happening internally.

Maybe one of them is redundant? *shrugs*

Glain

It's just redundant.

This happens a lot in the vanilla code, where it's like the compiler generated code to do one thing (in this case, set the action's CT), then generated code to do another thing (set the action's type), and just mashed them together without taking into account that it could have just reused the value in r3 instead of reloading the action pointer.

(Vanilla code isn't going to branch into the middle of the routine, and the only branches inside the routine skip to the end of the routine, so those two bits of code will always be run in sequence.)
  • Modding version: Other/Unknown

nitwit

Quote from: Glain on January 11, 2021, 09:19:21 amIt's just redundant.

This happens a lot in the vanilla code, where it's like the compiler generated code to do one thing (in this case, set the action's CT), then generated code to do another thing (set the action's type), and just mashed them together without taking into account that it could have just reused the value in r3 instead of reloading the action pointer.

(Vanilla code isn't going to branch into the middle of the routine, and the only branches inside the routine skip to the end of the routine, so those two bits of code will always be run in sequence.)
1. Do you have any additional advice for spotting redundant code?

2. Is it safe to jump into the middle of another subroutine? Is it advisable (seems no to me, harder to track and debug, almost like a goto)?

Glain

1.  Just off the top of my head, a bunch of code can be made more efficient by keeping values stored in registers instead of reloading or recalculating them, and re-ordering code to remove nops can help quite a bit as well.  Getting a good understanding of what the code is doing in each case gives a good idea of which parts of it seem to be unnecessary.

2.  Generally speaking, it's absolutely not safe, and it should be assumed that a crash would result.  This will usually corrupt the stack pointer and saved register values, unless you've accounted for that.  It can be made to work in very specialized cases with the right (hacky) setup.
  • Modding version: Other/Unknown

nitwit

Quote from: Glain on January 12, 2021, 09:24:27 am2.  Generally speaking, it's absolutely not safe, and it should be assumed that a crash would result.  This will usually corrupt the stack pointer and saved register values, unless you've accounted for that.  It can be made to work in very specialized cases with the right (hacky) setup.
IIRC some of the formulas - I think the stat boosts for Yell and Accumulate - lack a closing jr and nop, and are actually sequential. Are they actually part of a larger routine, or referenced in a pointer table somewhere?

https://ffhacktics.com/wiki/3A_%2BBrave_(Y)
00186d00: 3c028019 lui r2,0x8019         
00186d04: 904238fa lbu r2,0x38fa(r2)     
00186d08: 3c038019 lui r3,0x8019         
00186d0c: 8c632d90 lw r3,0x2d90(r3)     
00186d10: 34420080 ori r2,r2,0x0080     
00186d14: a0620016 sb r2,0x0016(r3)      ; Store Y as 0x80 + Br decrement (boost)
00186d18: 3c038019 lui r3,0x8019         
00186d1c: 8c632d90 lw r3,0x2d90(r3)     
00186d20: 34020001 ori r2,r0,0x0001     
00186d24: 03e00008 jr r31               
00186d28: a0620025 sb r2,0x0025(r3)      ; Store attack type as Psuedo-Status

https://ffhacktics.com/wiki/39_%2BSP_(Y)
00186d2c: 3c028019 lui r2,0x8019
00186d30: 904238fa lbu r2,0x38fa(r2)
00186d34: 3c038019 lui r3,0x8019
00186d38: 8c632d90 lw r3,0x2d90(r3)
00186d3c: 34420080 ori r2,r2,0x0080
00186d40: a0620012 sb r2,0x0012(r3)      Store Y as 0x80 + SP decrement (boost)
00186d44: 3c038019 lui r3,0x8019
00186d48: 8c632d90 lw r3,0x2d90(r3)
00186d4c: 34020001 ori r2,r0,0x0001
00186d50: 03e00008 jr r31
00186d54: a0620025 sb r2,0x0025(r3)      Store attack type as Pseudo-Status

Huh, I didn't misremember.

1. Given that there's a lot of redundancy here, how much space could one save by consolidating these and similar ones and using one of the unknown skill bytes as bit flags to specifiy effects?

2. Do you know if the AI use #1 correctly without modification, or must I investigate that too?

Glain

They're two separate routines that happen to be placed next to each other.  They both have a closing jr (at 0x186d24 and 0x186d50, respectively).  Neither one has a nop, but what's the relevance of that?  They are referenced in the formula pointer table, of course...

1.  I'm unaware of unknown skill bytes.  If you wanted to consolidate the routines into one by, say, providing the action offset as an argument, it looks like you could save some space.  It doesn't seem like anything amazing though, unless there are a bunch more routines like this.

2.  Are you asking if the AI would use Cheer Up?  I haven't tested that, although I can't say I remember enemy mediators using Praise very often...
  • Modding version: Other/Unknown

nitwit

Quote from: Glain on January 12, 2021, 06:00:40 pmThey're two separate routines that happen to be placed next to each other.  They both have a closing jr (at 0x186d24 and 0x186d50, respectively).  Neither one has a nop, but what's the relevance of that?  They are referenced in the formula pointer table, of course...
Ah, I glossed over it and missed the jr's because I expected nop's after them. My bad.

Quote from: Glain on January 12, 2021, 06:00:40 pm1.  I'm unaware of unknown skill bytes.  If you wanted to consolidate the routines into one by, say, providing the action offset as an argument, it looks like you could save some space.  It doesn't seem like anything amazing though, unless there are a bunch more routines like this.
Another instance of "not checking before I speak" syndrome, I was thinking of items. Looking at skills there isn't much free room, maybe one or two bits each that one can hack from the range, AoE, vertical, CT, MP cost, and formula bytes.

Quote from: Glain on January 12, 2021, 06:00:40 pm2.  Are you asking if the AI would use Cheer Up?  I haven't tested that, although I can't say I remember enemy mediators using Praise very often...
I think I saw an enemy mediator use Preach once, but I'm probably mistaken and the brave/faith modification skills seem low priority... but I was asking if the AI would use a skill that consolidates PA/MA/SP/CT/Brave/Faith changes into a new formula that uses bits in the assorted skill bytes to determine the stat affected.

I assume it needs additional changes to the AI routines to work, or a flag consolidation to specify the stat affected for both the skill effects and for the AI to consider.

Given that a lot of skills wouldn't do anything with those flags, it might make more sense to consider a set of flags as parameters to a formula, whose context varies depending on the formula. The things that never change between all formulas would get/retain their own flags whose meaning never varies no matter the formula used, and those that do change between most formulas would be moved to the parameter flags and their specific meaning would vary depending on the formula used.

Dedicating a byte or two to parameter flags and putting them in a position where they can be loaded with the fewest instructions might make a formula de-harcoding and consolidation project easier, if those flags would get checked the most often in formula routines.

If that's not clear, for a game I'm currently working on, byte 0x0c of skill data is the formula, and bytes 0x01 to 0x04 mean any of a number of things depending on the formula. Therefore bytes 0x01 to 0x04 are parameters to byte 0x0c, when you think about it.

Something like that.

Random Question: are some of the tables for skills 0x170 and above smaller than those above them? I just checked the gameshark tab after changing the item used bytes for Potion and Hi-Potion and those bytes are sequential. Seems to indicate that 0x170 and above don't have the full range of bytes under the "Attributes" heading as skills 0x000 to 0x16f.

I ask because I'll need to make data-editing spreadsheets that generate paste-able hex tables (the FFTPatcher tables, in base 16 form in the manner they exist in the game) if I want to complete my master plan RE a commented disassembly. If those skills don't have entries in an "Skill Attributes" table then I need to account for that. I can figure it out on my own if you are busy, I'm just asking in case you already know and can save me some time later.

If those skills have entries in a "Skill Attributes" table, and you have hacks that can reconfigure them to function like other skills, you should consider making those bytes editable in FFTPatcher if it's not a hassle.

Orkney

Hi, i ran into a section of code i don't understand (again)

It's in the routine Weapon_Guard_Usability
0018130c: 27bdffe8 addiu r29,r29,0xffe8
00181310: afb00010 sw r16,0x0010(r29)
00181314: 00808021 addu r16,r4,r0 r16 = Target's Data Pointer
00181318: afbf0014 sw r31,0x0014(r29)
0018131c: 9202005c lbu r2,0x005c(r16) Load 5th set of statuses
00181320: 00000000 nop
00181324: 30420004 andi r2,r2,0x0004
00181328: 14400020 bne r2,r0,0x001813ac Branch if Target has Don't Act
0018132c: 34020001 ori r2,r0,0x0001 r2 = 1
00181330: 92020182 lbu r2,0x0182(r16) Load Mount Info
00181334: 00000000 nop
00181338: 30420040 andi r2,r2,0x0040
0018133c: 1440001b bne r2,r0,0x001813ac Branch if Target is Being Ridden
00181340: 34020001 ori r2,r0,0x0001 r2 = 1
00181344: 0c060428 jal 0x001810a0 Map Location Calculation - (requires r4 = Target Data Pointer)
00181348: 02002021 addu r4,r16,r0
0018134c: 000210c0 sll r2,r2,0x03 Tile ID * 8
00181350: 3c018019 lui r1,0x8019
00181354: 00220821 addu r1,r1,r2
00181358: 9022f8cf lbu r2,-0x0731(r1) Load Height (halves) + Water Depth Flags
0018135c: 00000000 nop
00181360: 00021142 srl r2,r2,0x05 Remove Height aspect (leave Depth)
00181364: 28420002 slti r2,r2,0x0002
00181368: 14400010 bne r2,r0,0x001813ac Branch if Depth is less than 2
0018136c: 00001021 addu r2,r0,r0 r2 = 0
00181370: 9202005a lbu r2,0x005a(r16) Load Target's 3rd set of statuses
00181374: 00000000 nop
00181378: 30420046 andi r2,r2,0x0046
0018137c: 1440000b bne r2,r0,0x001813ac Branch if Target has Chicken/Frog/Float
00181380: 00001021 addu r2,r0,r0 r2 = 0
00181384: 92020095 lbu r2,0x0095(r16) Load Target's 3rd set of Movements
00181388: 00000000 nop
0018138c: 304200c8 andi r2,r2,0x00c8
00181390: 14400006 bne r2,r0,0x001813ac Branch if Target has Move in/Walk on Water/Float
00181394: 00001021 addu r2,r0,r0 r2 = 0
00181398: 92020182 lbu r2,0x0182(r16) Load Target's Chocobo/? Flags
0018139c: 00000000 nop
001813a0: 30420080 andi r2,r2,0x0080 Check for Riding Chocobo Flag
001813a4: 2c420001 sltiu r2,r2,0x0001 r2 = 1 if not riding a chocobo
001813a8: 00021040 sll r2,r2,0x01 r2 * 2
001813ac: 8fbf0014 lw r31,0x0014(r29)
001813b0: 8fb00010 lw r16,0x0010(r29)
001813b4: 27bd0018 addiu r29,r29,0x0018
001813b8: 03e00008 jr r31
001813bc: 00000000 nop
The routine returns r2 = 1 if you can't use weapon gaurd and r2 = 0 if you can

It checks several things :
- If the target as don't act --> r2 = 1  and branch to end
- If the target is being ridden (?) --> r2 = 1 and branch to end
- If depht is < 2 --> r2 = 0 and branch to end
At this point, the rest of the routine check for situations where the depht is >= 2
- If the target as chicken, frog or float --> r2 = 0 : ??? Ok for float but why frog and chicken ?!
- The end checks movements abilities that will get rid of depht

Frog and chicken can't use weapon guard do they ?  :shock:
Is there a game mechanics that i'm missing ?

And the being ridden control ? Which unit can be mounted and il using weapon guard ?
  • Modding version: PSX

nitwit

Quote from: Orkney on February 07, 2021, 08:40:57 amHi, i ran into a section of code i don't understand (again)

It's in the routine Weapon_Guard_Usability

The routine returns r2 = 1 if you can't use weapon gaurd and r2 = 0 if you can

It checks several things :
- If the target as don't act --> r2 = 1  and branch to end
- If the target is being ridden (?) --> r2 = 1 and branch to end
- If depht is < 2 --> r2 = 0 and branch to end
At this point, the rest of the routine check for situations where the depht is >= 2
- If the target as chicken, frog or float --> r2 = 0 : ??? Ok for float but why frog and chicken ?!
- The end checks movements abilities that will get rid of depht

Frog and chicken can't use weapon guard do they ?  :shock:
Is there a game mechanics that i'm missing ?

And the being ridden control ? Which unit can be mounted and is using weapon guard?
There are oversights like this here and there. IIRC frog and chicken prevent reactions and movement skills, so frog/chicken can't trigger weapon guard unless you remove that flag and undo any hard-coding. I could be wrong though, I didn't look at those statuses in FFTPatcher.

Ideally the routine would check all the status effects present for the "Can't use reaction or movement skills" and rely on that; maybe by iterating through them and checking if they're present and if they have that flag, all in a subroutine you can call when dealing with all reactions and things that prevent evasion if you're turning weapon guard into an innate-all form of evasion.

If you want to be even more abstract, you could make a more generalized subroutine that checks if an arbitrary bit in an arbitrary byte in a status effect is set. You could probably rig it to need one register as a parameter, by loading the byte to check in the lower half of the register and the bit to check in the upper half. There are only 2 bytes worth of bits in the flags section of the Status Effects table, though there are apparently 2 unused bytes, and other bytes and bit flags which are used.

Note that the "Can React" flag is set when the bit is NOT set, according to the Gameshark tab of FFTPatcher.

Chocobos can be ridden. None of them have weapon guard or use weapons (hence no W-Ev), but again they probably didn't finalize all that stuff until late in development (or they had poor coordination between developers, or both), by which point no one would strip out useless code unless they needed to meet RAM constraints or something.

The float thing I think has to do with height putting someone out of or into range if you are below or above them and using certain melee weapons or skills. Put float on someone and hang out on a rock in Mandalia to see for yourself.

Water depth >= 2 only exists in the Bethla Garrison Sluice battle, and unless you bring a unit with Walk Under Water (Mindflayers IIRC) or maybe the movement skill that lets you move and act while in water, you shouldn't be able to enter that water. It's another edge case that they prepared for but ultimately never encountered because they didn't create circumstances for it to trigger.

Orkney

Thanks for the reply.

If there is some piece of code that are'nt relevant, it'll make the understanding of it more difficult (for me at least).

For the depht i believe there's at least another river with 2 depht (i remember some character with the nose in the water)... 

But the way it's coded i think that float is checked ok, because the unit will be above water (and can use weapon guard)

For the chocobo part i never mount chocobo, but maybe they can swim and the unit will use weapon guard.

This frog and chicken thing will remain a mystery :) Maybe they swim too so they are checked (Independently of the fact that they can't use reaction ability...)

The end of the code is weird too, why r2 is multiplied by 2 !? in the return routine the only check is r2 <1 --> weapon guard usable (but writing this  i realize there is other return routine, the answer may be over there)
  • Modding version: PSX

nitwit

Quote from: Orkney on February 08, 2021, 02:41:35 pmIf there is some piece of code that are'nt relevant, it'll make the understanding of it more difficult (for me at least).

For the depht i believe there's at least another river with 2 depht (i remember some character with the nose in the water)... 
Finath River has 2 depth water. Thieves Fort, the battle where you kill Miluda, the river valley fight just before the end of Chapter 2... there are a decent amount of 2 depth water areas.

Now that I think about it, the first battle in the game at Orbonne might have water deeper than 2 depth, so maybe that's the other one besides Bethla Sluice (to clarify, Bethla Sluice is the battle where you need to move Ramza to the two switches to open the flood gates).

Quote from: Orkney on February 08, 2021, 02:41:35 pmBut the way it's coded i think that float is checked ok, because the unit will be above water (and can use weapon guard)
I didn't think of that. You're probably right.

Quote from: Orkney on February 08, 2021, 02:41:35 pmFor the chocobo part i never mount chocobo, but maybe they can swim and the unit will use weapon guard.

This frog and chicken thing will remain a mystery :) Maybe they swim too so they are checked (Independently of the fact that they can't use reaction ability...)

The end of the code is weird too, why r2 is multiplied by 2 !? in the return routine the only check is r2 <1 --> weapon guard usable (but writing this  i realize there is other return routine, the answer may be over there)
The easiest way to find out for sure is to set up a test scenario. Staring at code will only do so much, eventually you need to debug it. I'm not yet familiar enough with MIPS to step through the code and understand what it's doing - and emulators don't have print terminals like high level programming languages - so the next best thing is to see what happens when you remove parts of the code. By seeing what happens when a piece of code is removed, you can divine what it seems to do.

1. Edit RAM addresses to give yourself some weapons with very high W-Ev, like the Nagrarock. Give everyone in your party weapon guard. Make sure you have a way to inflict frog and chicken, and you have a chocobo. The gameshark handbook is useful for this:
http://m-l.org/~greerga/fftnet/fftmech/fftgs41.txt

2. Make a save state in a battle with 2 depth water, nerf the enemies so they're harmless, and get everyone positioned in the water.

3. Open the memory viewer in pSX (or duckstation), go to the address where the routine is located, and zero out one line (4 bytes, starting at an address divisible by 4) in the routine. Only zero out the stuff you're not sure of, no point in testing what you already know works.

4. Test out the various scenarios in your post which I quoted above. Make note of what happens in each scenario when you zero out each part of the routine.

5. Reload the save state to undo you zero-ing out, move to the next address divisible by 4, zero out 4 bytes, and repeat the process at step #4.

Orkney

Hmmm

In the Abandon_Calculation routine there is a jump to the weapon guard usability.

If it's no --> branch to the end of the routine.

Since there's no reference to the weapon guard ability itself, this routine is more a physic reaction ability check (at least weapon guard and abandon)...

But i don't get it, why calling this routine 2 times, rather than store the result and use it twice ?
  • Modding version: PSX