View Single Post
Old 08-10-2025, 07:36 AM   #10
callisto
Senior Member
 
Join Date: Nov 2021
Drives: 2019 Subaru BRZ
Location: PNW, US
Posts: 109
Thanks: 93
Thanked 89 Times in 46 Posts
Mentioned: 4 Post(s)
Garage
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.
Attached Images
   

Last edited by callisto; 08-10-2025 at 03:18 PM. Reason: i cannot prevent the forum software from brokenly highlighting the users R2 and 7A in code blocks, sorry.
callisto is offline   Reply With Quote
The Following User Says Thank You to callisto For This Useful Post:
whataboutbob (08-11-2025)