Been thinking about this more and studying code. IAM ranges from max 1.0 + max KCA all the way down to 0.0 at -5 tuning, by which time it will have cut out boost, AVCS, and rescaled the throttle. But it'll keep trying to probe upwards from "learned IAM" any time you do a WOT pull (whether to force it to or just to enjoy an onramp), which is why modifying Base Timing is ideal - a strategy that works less well in facelift era, since it's a 4D map linearly scaled between Base Timing A (IAM 1.0) and B (IAM 0.0), just like Calculated Torque is.
The code cheats and uses FLDI1 to set the max IAM of 1.0, so it isn't a 'normal' variable that can be patched in, but if you're curious to see it in the code yourself, it's here:
A02G- @6e8f0; K00G- @72990; V00C- @
7A6d4;
a0 02 fd 82 00 29 81 12 fc d0
f6 9d f6 c5 8b 00 f6 cc fc 6c e0 01 a0 18 80 1b 30 11 90 02 70 01 40 85 a0 33 81 12 84 19
However, since the entire routine's purpose is to set IAM, and it's a void-void call from one of the main runloops, it's possible to patch it to
instead set the max IAM to 'Advance Multiplier Initial' or to whatever float we prefer. I think Advance Multiplier Initial is a fine expression of "max IAM" when one has modded this code into place.
The 'set current IAM' function is called by only a single place, one of the main runloops of the vehicle:
Code:
void __stdcall FUN_0007a642 (void )
XREF[2]: FUN_00040574:00040690 (c) , 00040820 (*)
0004068e d2 64 mov.l @(-> FUN_0007a642 ,pc ),r2 = 0007a642
00040690 42 4b jsr/n R2 => FUN_0007a642 void FUN_0007a642(void)
XREF[1]: FUN_00040574:0004068e (R)
00040820 00 07 a6 addr FUN_0007a642
42
So, copying the entire function to a new location and changing the single pointer to it lets us patch it - which will extend it by a few bytes and require some tweaking of indirect offsets. I chose 12d000:
@40820- 00 12 d0 00
And simply copy-pasted the IAM function's bytes to that location.
@12d000- 4a f0 ff cb ff db ff eb ff fb d2 59 42 4b 6d 0c d6 59 blah blah etc.
Sadly, it's not so easy as that, but it's close! Next: Ignoring all the broken indirect pointers, instead I'm going to patch it to read a float we store. I considered reusing the Initial IAM but tuning them separately is what I couldn't do to begin with, so may as well.
Code:
; modify 2 bytes, insert +2+ new bytes
@12d09c- c7 30 mova @(+0x30,pc), r0 ; guessed at a safe offset, align-4
@12d09e +f5 +08 fmov.s @r0, fr6
@12d160- 3f 33 33 33 0.69999999
And now I see the expected code change:
Code:
fVar9 = 0.7;
if (fVar10 < 0.7) {
fVar9 = fVar10;
}
Time to fix up all those indirect pointers I smashed! Precisely one of them is
not a pointer, and it's the decimal 0.5 which I
think is the slowdown factor for advancing IAM (but it's hard to say for sure). The rest are just boring pointers that can be copy-pasted one at a time the locations defined by their offsets. But. There are so many pointers and this is very clumsy. Hrm.
We have 10 bytes in which to jump into the distance and back. The difficulty is doing an indirect jump to an address while having room for a pointer, and then having it resume control in the right place. 2 bytes for MOV.L +x,pc into r2, 2 bytes to jsr/n r2, 2 bytes to goto (bra) over the pointer, 4 bytes for the pointer. 10 bytes == 10 bytes. Phew.
So, instead, let's put just the 10 bytes we're modifying at 12d000:
Code:
@7A6de- fc d0 fadd fr13,fr12
e0- xx xx mov.l
e2- xx xx jsr/n
e4- 09 00 nop
e6- xx xx bra
e8- 00 12 d0 00 pointer ; OVERFLOW by 2
4 bytes too many. Hrm. Okay, fine, goto, and we'll shuffle the _fadd to occur under the delayed branch.
Code:
@7A6de- d2 04 mov.l @(#0x4,pc),r2
e0- 42 2b jmp @R2
e2- fc d0 _fadd fr13,fr12
e4- 00 12 d0 00 pointer
e6- 00 09 nop
2 bytes to spare. Heh. And now, the comparison:
Code:
; load our new maximum into fr6, replacing the 1.0f previously set by fldi1 fr6
@12d000- d2 0e mov.l @(+0xe,pc), r2 ; float (+0x0) after this code block
02- f5 28 fmov.s @R2, fr6 ; r2 is constantly overwritten for long-distance pointers
; is the new IAM value greater than the maximum we loaded?
04- f6 c5 fcmp/gt fr12, fr6 ; fr12 set by _fadd during jmp prep
06- 8b 00 bf +0x2,pc ; skip the next instruction if false
; it is not. we choose to keep it, by writing it to fr6.
08- f6 cc fmov fr12, fr6
; and now back to your regularly scheduled engine control program.
0a- d2 02 mov.l @(+0x6,pc), r2 ; pointer (+0x4) after this code block
0c- 42 2b jmp @R2
; copy either the new maximum (true branch) or the newly calculated IAM value (false branch) into fr12.
0e- fc 6c _fmov fr6, fr12 ; executes while jmp is underway
I originally guessed +0x10, which was correct it turns out
except I forgot to offset for pc increment, so c7 0e above instead, hrmf. Except I forgot the nop, but I should put the fmov under the jmp, so, hrmf. Fixed. (Ah, but this will turn out to be wrong, because I forgot to divide by 0x4 and align. Oops again.)
Code:
@12d010- 3f 33 33 33 0.7
14- 00 07 a6 de pointer
A few rounds of disassembler and patching later, it looks I need to MOVA @
Target with a 4-byte offsets from the program counter. Yep, oops. Also, my listings above had the wrong start address, which I've shifted here to correct. And MOVA only works with r0 so I need MOV.L r2, fine.
Code:
7A6dc- d2 01 mova @(+0x1,pc), r2
de- 42 2b jmp R2
e0- fc d0 _fadd fr13,fr12
e2- 00 00 align(2)
e4- 00 12 d0 00 pointer
And here's the offset-patched 12d000 code, with f5 28 repaired to f6 28:
Code:
@12d000- d2 03 mov.l @(+0x3,pc), r2 ; float (+0x0) after this code block
02- f6 28 fmov.s R2, fr6 ; r2 is constantly overwritten for long-distance pointers
; is the new IAM value greater than the maximum we loaded?
04- f6 c5 fcmp/gt fr12, fr6 ; fr12 set by _fadd during jmp prep
06- 8b 00 bf +0x2,pc ; skip the next instruction if false
; it is not. we choose to keep it, by writing it to fr6.
08- f6 cc fmov fr12, fr6
; and now back to your regularly scheduled engine control program.
0a- d2 02 mov.l @(+0x6,pc), r2 ; pointer (+0x4) after this code block
0c- 42 2b jmp R2
; copy either the new maximum (true branch) or the newly calculated IAM value (false branch) into fr12.
0e- fc 6c _fmov fr6, fr12 ; executes while jmp is underway
@12d010- 3f 33 33 33 0.7f
14- 00 07 a6 e8 pointer 0x7a6e8
And now everything looks good! Except that Ghidra can't decompile it properly anymore because all my unconditional jumping has given it heartburn. If I wanted to put in a JSR I'd need two more bytes, and since it's followed by a loop, that would mean extending upwards and migrating more code into 12d000, which is more than I'm willing to do right now. This was a lot!
Unfortunately I don't have a bench setup nor an emulator setup, so I can't test this code except by uploading it to my actual car, which would either work or panic the ECU, and I'm not quite sure what happens to the ECU if it panics. Probably nothing good for my ability to recover it with flashing the boring ways. But since I put in the effort to learn, duly shared for others to follow along! I'll update someday if I actually get confirmation that it works
EDIT: Alright, I slept on it and figured out how to patch it in cleanly: just trap the IAM write at the end. See attached. This code hasn't change from A01G through V00C so the specific memory address may change,
but for those looking to limit their IAM due to bad gas when it's not productive for the engine to keep striving for IAM 1.0, this
ought to (pending testing! no warranty provided, buyer beware) permit capping IAM at something sensible for local gasoline. Which should, in
theory, permit it to learn advance properly at the max without constantly resetting it due to IAM advance attempts, rather than having to manually tune everything.