EDIT: I'm putting the Gear Level ASM patches here for convenience. Also, I'm attaching them as an XML at the bottom of this post.
-----Recently I've been thinking about an idea for enemy unit levels scaling with the party and how that affects random equipment. By default, that means enemy gear will scale up with the player's level, meaning that after a certain point, the player is going to be outmatched by equipment he can't obtain except through stealing. On the other side of the coin, if the player manages to level very little and/or use degenerators, he can keep the shop equipment ahead of the enemy equipment and gain an advantage that way. My objective: fix the gear of enemies at a certain tier, depending on the battle... without fixing the equipment itself.
Using the pSX debugger, I was able to find where random equipment was being determined. It happens while you're placing your units before the battle, and there is a subroutine at 0x05cc98 in RAM (0x04d498 in SCUS_942.21) that chooses random equipment. It appears to loop through a list of items and check them against the current unit's (r4,r13) level based on the given equipment type (r5). The unit offset relating to the current unit is loaded into the register r13, and sure enough, we find this:
0x0005cd2c: lbu r14,0x0022(r13)
Loading the current unit's level into a register (r14)! r14 is then used for a check in the loop, so this is the number being used to determine the quality of enemy equipment.
My first thought was that this could be replaced with something relating to the shop availability for items, using some kind of multiplier. ([Shop availability variable] * 2? 3? 2.5?) Once again, I used the pSX debugger and went into a shop, to trace to where a check is made against this number. At first, I find this:
0x001251c0: lw r8,0x0028(r29)
I'm testing at the start of chapter 4, and r8 here is loaded with 0x0d (13). That would be... correct! That's the number it'd check against the item's shop availability, and I can see it doing this in the code:
0x001251bc: lbu r2,0x2ec2(r1) Load in the item's shop availability number... (pSX debugger breakpoint)
0x001251c0: lw r8,0x0028(r29) Load the player's shop availability (based on story progression) from the stack
0x001251c4: nop
0x001251c8: slt r2,r8,r2 If the player's number is less than the item's, the item is too high quality, so...
0x001251cc: bne r2,r0,0x00125238 ...skip adding it to the shop item list for purchase.
But, well, we loaded r8's value (the player's number) from the stack somewhere, it looks like. How did it get there?
0x00125128: jal 0x000ef1a8
(...)
0x00125134: sw r2,0x0028(r29)
r2 changed to the correct value (0x0d) after the call to subroutine 0x0ef1a8.
At least under some circumstances, this subroutine at 0x0ef1a8 seems to load r2 with the variable I wanted... but unfortunately, we're in a shop, so probably we're in WORLD.BIN. When I tried to call this subroutine in a battle, the memory at 0x0ef1a8 looked completely different... the subroutine wasn't even loaded into memory anymore! (Leading to fantastic results). Besides, I wasn't sure of the circumstances under which that subroutine would return the storyline variable (it uses a billion registers and is also recursive, so it's a barrel of fun to try to trace through).
So my next thought... forgetting subroutine 0x0ef1a8... was I overcomplicating things? We already have an offset to the current unit in subroutine 0x05cc98 (where the random equipment is loaded). So what other offsets can I use? Is there some other property of the current unit I can use to check for what the equipment quality should be? Looking at the formula hacking reference, there's basically nothing. I can't use HP, MP, Brave, Faith, etc. So what to do?
Venture into the unknown. In the ENTD in FFTPatcher, there are unknown fields for each unit, in that little box down in the bottom left. Some of them seem to be AI flags, but what of the one down in the very bottom left (last row, leftmost)? I couldn't find a single instance of it being anything other than zero (though I only checked the story battles). So if it's unused... why not use it? This number could function as a "give the enemy gear as if his level was X" variable, without actually changing the unit's level. I located it in RAM at unit offset 0x0169.

This is the number I'm talking about. In my patch, I've made Gafgarion here Party Level + 5, but his "gear level" is 0x12 = 18, which is his default level for this battle. (Gate of Lionel)
We
could simply change the offset checked by subroutine 0x05cc98 to use 0x0169 instead of 0x0022 (level). While that's viable, we'd have to make sure we changed every single instance of this variable in the ENTD. What I really want is for it use 0x0169 unless it's zero, and otherwise use 0x0022.
Well...okay, I can write a block of ASM that does that. Here is the biggest oustanding problem with this whole thing though... where to put it? It just so happened that in my patch, I had ASM hacked formula 41 into something else, and I happened to have a bunch of nop's at the end of it. I used that and wrote my own subroutine at 0x0018a164. (I'll get to this shortly)
If we look at subroutine 0x05cc98, we have to rewrite a little bit to make our own subroutine call work. I want to make the call to my subroutine near the beginning of this one, but the return address wasn't saved yet. Originally, we have:
0x0005cc9c: 00806821 addu r13,r4,r0 r13 = r4 (reference to unit)
0x0005cca0: 30a500ff andi r5,r5,0xff r5 = (Lowest byte of r5)
0x0005cca4: 34020020 ori r2,r0,0x0020 r2 = 0x0020
0x0005cca8: afbf0094 sw r31,0x0094(r29) (Save return address)
I changed mine to this:
0x0005cc9c: afbf0094 sw r31,0x0094(r29) (Save return address)
0x0005cca0: 00806821 addu r13,r4,r0 r13 = r4 (reference to unit)
0x0005cca4: 0c062859 jal 0x0018a164 Call my subroutine!
0x0005cca8: 34020020 ori r2,r0,0x0020 r2 = 0x0020
I had to cut out the andi statement, but I execute that in my subroutine, as shown here:
0x0018a164: 91ae0169 lbu r14,0x0169(r13) Load in unit offset 0x0169
0x0018a168: 140e0002 bne r14,r0,0x0018a174 If it's not zero, use it, so jump to the return statement
0x0018a16c: 30a500ff andi r5,r5,0xff (Statement from calling subroutine that I cut and wanted to execute here)
0x0018a170: 91ae0022 lbu r14,0x0022(r13) Otherwise, load in the unit's level, and
0x0018a174: 03e00008 jr r31 Return
0x0018a178: 00000000 nop
Now r14 (the number used to check quality of equipment) already has what I wanted in it. The only thing left to do is stop it from trying to load in the unit offset later in the subroutine. In other words, changing this:
0x0005cd2c: 91ae0022 lbu r14,0x0022(r13)
to this:
0x0005cd2c: 00000000 nop
And we've done it! Random enemy equipment would now load based on the "gear level" you give them at unit offset 0x0169, analogous to what their gear would be if they were at that level... without changing the actual level. Thus, the level can scale with the party, but the gear can remain fixed at a certain tier.
I implemented this in my own patch swimmingly enough (after lots of debugging, of course)! It's pretty cool, but making it into a general patch anyone can use presents some difficulties. The biggest problems at this point, as I see it, are:
(1) Where do I put the new subroutine? (I can't just assume everyone has a bunch of nop's at the end of their formula 41)
(2) What if unit offset 0x0169 is used by something and now it won't work the same way? (Doubtful, they're all zero, and my testing doesn't show any problems here)
Honestly, the simplest answer to (1) may be to just go with the first approach, just straight changing the instruction at 0x05cd2c to load offset 0x0169 instead of 0x0022, beause that would be a one-line ASM hack, and it would work... it's just that you'd have to change that 0x0169 number in the ENTD for every single unit that loads random equipment, or they'd have none at all (I presume). It's just that I'd prefer the (Use 0x0169 if it's there, otherwise the level) approach, but am not sure where I could put the subroutine that does that.
Thoughts? Concerns? Ideas on other ways to make this happen? Different offsets to use? Random comments?