Bump for new information.
So I've been investigating the procedure at length with LemASM and Halkun's
PSX guide and I think I have a fix.
Firstly, it looks like my earlier information about the position of the opcode was incorrect. The instruction at DB00 is actually part of the 'isDefending' check, which is probably why messing around with it quartered my damage. No, the procedure for checking back-row-ness starts at 800ADA28 (DA28) and terminates at DAC4. As NFITC1 says, it has a flaw, namely that if an attack is an enemy action, the branch for a long range attack is effectively exited. Here's my own low-level reverse (this took a while to get the logic right; I didn't realize jump and branch opcodes had a one-instruction delay):
// Start at DA28
// R4 contains result of first damage equation
R3 = load word from [r5 + 208] // gets value of offset 208 from temporary scenario array stored in R5
R2 = R3 * 2
R2 = R2 + R3
R2 = R2 * 4
R2 = R2 + R3
R2 = R2 * 8
R1 = 8010 0000
R1 = R1 + R2
R2 = load word from R1 + 83E4
// this seems to get the target unit flags specified at http://sourceforge.net/p/q-gears/code/ci/default/tree/reversing/ffvii/ffvii_battle/ffvii_address.txt#l655
// I *think* these are flags for the target rather than the actor
R3 = load word from [R5 + 0050] // get attack target flags
R2 = R2 && 0040 // R2 = target.isBackRow
R3 = R3 && 0020 // R3 = attack.isShortRange
R6 = (0 < R2) // reducedDamageFlag = target.isInBackRow
IF R3 != 0, BRANCH TO DA74
// DA64
R3 = load word from [r5 + 28] // get attack type data
R2 = 0 || 20 // 'enemy attack' command
IF R3 != R2, BRANCH TO DAB4
// DA74
R2 = load word from [r5 + 0]
R3 = R2 * 2
R3 = R2 + R3
R3 = R3 * 4
R3 = R3 + R2
R3 = R3 * 8
R1 = 8010 0000
R1 = R1 + R3
R2 = load word from [R1 + 83E4]
// seems to be unit flags for actor
R2 = R2 && 0040 // R2 = self.isBackRow
IF R2 = 0, BRANCH TO DAB8
R6 = 1 // reducedDamageFlag = true
JUMP TO DAB8
// DAB4
R6 = 0
// DAB8
R2 = R4 >> 1F
// right shift R4 - current damage - by 31 places, leaving upper bit
IF R6 = 0, BRANCH TO DAC8
R2 = R2 + R4
// add upper bit to get negative damage to bitshift divide properly?
R4 = R2 / 2
// DAC8
// Seems to be start of ‘target defend’ check
This is a bit hard to follow. Here's a digested version:
applyDamageReduction = target.isBackRow;
if (attack.shortRange) {
checkActorRow();
}
else {
// if attack is long range
if (attack.enemyCommand == false) {
applyDamageReduction = false;
}
else {
checkActorRow();
}
}
function checkActorRow() {
if (actor.isBackRow) {
applyDamageReduction = true;
}
}
if (applyDamageReduction) {
divideCurrentDamageByTwo();
}
Basically, if the attack is long range and is an enemy attack, the flag is not cleared, and the actor's row is still checked regardless of the fact the attack shouldn't care about rows.
This won't do - as it means enemy attacks are having their long-range status ignored. What we actually want is this:
My fixThis is the fix I'm trying, at a high level. It's pretty basic:
- Get attack target data; if long range, skip to defend check script (DAC8)
- Store self.backRow in R1
- Store target.backRow in R2
- If R1 || R2 = false, skip to defend check script
- Apply damage reduction to R4
There's a bit of redundancy here, but this is my first ASM patch, so I really don't care about a few wasted cycles.
Here's how I think I can write the assembler:
// Check for long range attack
LW R1, $0028 (R5) ;load R5+28 (target flags?) into R1
ORI R2, R0, $0020 ;don't know why the original code used this method, but I'll preserve it
BNE R1, R2, $0000DAC8 ;goto defend script
NOP ;do I need to include these after jumps?
// Load self.backRow into R1
LW R1 $0000 (R5) ;load R5+0 (self unit flags?) into R1
NOP ;apparently loads are delayed by one cycle
SLL R2, R1, $1
ADDU R2, R2, R1
SLL R2, R2, $2
ADDU R2, R2, R1
SLL R2, R2, $3
LW R1 $83E4 (R2) ;get offset and load
NOP
ANDI R1, R1, $0040 ;this should put the AND of R1 && 0x0040 into R1
// Load target.backRow into R2
LW R2 $0208 (R5) ; load R5+208 (target unit flags?) into R2
NOP
SLL R3, R2, $1
ADDU R3, R3, R2
SLL R3, R3, $2
ADDU R3, R3, R2
SLL R3, R3, $3
LW R2 $83E4 (R3) ;get offset and load
NOP
ANDI R2, R2, $0040 ;this should put the AND of R2 && 0x0040 into R2
// Do comparison
// If either self or target are back row, their register should contain 0x0040
OR R3, R2, R1 ;will contain 0x0040 if either party in back row, should contain 0x0000 if not
BLEZ R3, $0000DAC8 ;skip to defending script if zero
// Continue with existing 'reduce damage' implementation, ignoring test for R6 != 0
I'll try the above this afternoon. I'll double check, but I think this should be right. My only hope is that I don't make the procedure longer; I'd rather not mess around moving the file. I think it should be about the same, though. I assume ARMIPS will just overwrite from the address I specify, so I won't need to update any other jumps in the procedure? In terms of lines, it'll certainly make things smaller. Might be an idea for me to fill in any unused space with NOPs though.
I should probably also double check that enemy attacks get their target flags copied the same way too...
One final question: if I write branches, jumps or any other instruction-delayed opcodes in my assembly and pass it to ARMIPS, will the assembler insert the NOPs automatically or do I need to do this myself?
EditExcellent! I was able to compile my assembler, and it looks as though it was written to the file just fine. Had to make a couple of changes here and there (I didn't realize that branches had a reach limit, for example), but it seems to compile without trouble. Here's the actual script that I used with ARMIPS:
.open "BATTLE",0x800A0000
.psx
.org 0x800ADA28
.area $9C
//check_long_range_attack
lw v1, $0050(a1)
nop
andi v1, v1, $0020
bgtz v1, check_self_backrow ; if short range attack, check self/target backrow
nop
j 0x800ADAC8 ; else skip to next sub
check_self_backrow:
lw v0, $0000(a1)
nop
sll v1, v0, 1
addu v1, v1, v0
sll v1, v1, 2
addu v1, v1, v0
sll v1, v1, 3
lui at, $8010
addu at, at, v1
lw v0, $83E4(at)
lw v1, $0208(a1) ; start loading word for check_target_backrow (save space)
andi v0, v0, $0040 ;v0 now contains self.isBackRow
//check_target_backrow
sll at, v1, 1
addu at, at, v1
sll at, at, 2
addu at, at, v1
sll at, at, 3
lui v1, $8010
addu v1, v1, at ;v1 contains address for lookup
lw at, $83E4(v1)
nop
andi v1, at, $0040 ;v1 now contains target.isBackRow
//do_comparison
or at, v0, v1
bgtz at, reduceDmg ; if target.isBackRow || self.isBackRow, go to reduceDmg branch
nop
j 0x000ADAC8 ;skip to next sub
nop
reduceDmg:
j 0x000ADABC ; sends to damage reduction subfunction
nop
.endarea
.close
The endarea tests for the size of the overwrite (quite a handy feature). It looks as though my writeup is actually the same size.
Unfortunately, I haven't been able to test it in battle yet, as I'm having trouble reinserting the file. The game doesn't seem to like my gzipped insert; starting a battle freezes the game and looking in memory for the updated assembler suggests the decompression has gone *badly* wrong. Anyone have any ideas?Edit again: I fixed the issue with the file inserts (I was _overwriting_ the first 8 bytes of the gzipped file with the x file header, rather than _inserting_ the original 8 bytes back in again). It's now working, except the logic seems to be off. I think I got the address wrong in one case - looks like R5+0050 is what gets the targeting flags, whereas R5+0028 gets the command type. Changing my script makes things work better, but now it looks like everything is being treated as long range...I will trace and report on this soon.
Edit2: I have fixed the issue using the updated code above. I have raised a new thread about this fix (http://forums.qhimm.com/index.php?topic=14998.0). I'll also share an end-to-end guide to how I performed this hack, so others wondering about PSX assembly edits can get some much-needed information.