Intel Disassembly

by Michael Decipha Ponthieux
Posted: 2015-03-08
Last Updated: 2022-01-24
This write up specifically pertains to Intel 8061 and 8065 disassembly used in the 1983 to 2004 (up to 2006) EEC-III, EEC-IV and EEC-V Ford, Aston Martin, Jaguar and Mazda ECU's. Although most ford code from 1983 to current is relatively the same. PowerPc, Tricore, Bosch and Siemens ECU's cannot be disassembled using the free disassembler provided below.

If you are considering performing disassembly I highly recommend to check out the HARDWARE SUPPORT LIST first to make sure that you are not disassembling a strategy that may not be needed.

Binary Files

In most cases, the only method of retrieving a clean unmodified bin is to extract the bin from the ecu. See the Getting Started write up for specifics. Tomas Tomin also offers hardware for reading ECU's, more info can be had on his FORDIAG.CZ forum. Direct link to downloading ForDiag Software HERE

Padding (filler) and Bank Order

Before we can even begin to discuss the disassembly process we must first have a valid bin we can disassemble. This in itself can be extremely difficult before even beginning disassembly due to the fact that some software uses a full size bin of 64k per bank where the first 8192 bytes (0x2000 hex) of each bank is padded with null filler, whilst most software uses a non-padded bin of 56k per bank (224k 4 bank file). To fuel the fire, some software will swap banks 8 and 9 while saving the bin. This can make getting a true unaltered bin for disassembly very difficult. For reference a binary size chart is below.

Binary File Sizes
10x 8 00032 768 32 k
10x E 00057 34456 k
10x10 00065 536 64 k
20x16 00090 112 88 k
20x1C 000114 688112 k
20x20 000131 072128 k
20x36 000221 184216 k
40x38 000229 376224 k
40x40 000262 144256 k

Identify Bank Order

Bank order can easily be identified by the beginning bytes of each bank. It is often easy to search for the ascii string to quickly find it.
Bankhex codeascii
0FF,FA,27,FE,...,60,20,63` c
9FF,FA,27,FE,...60,20,65,...` e

Flat Memory Addressing

For reference, I have attached a quick reference table or a "cheat sheet" if you will, that will break down the memory differences between various size bin files and how to get to the actual flat addressing the binary is referencing. This table can be used to cross calculate memory addressing between the more common iterations of bank layouts and file sizing.

Note: 4 Bank EEC-V 256k binaries include the RAM (usually all filler =0xFF) 4 bank bin files are truly 216k with the end of bank 1 being used for RAM.
Quick Reference Conversion Chart from 256k [0-1-8-9] Addressing to many common formats.
256k [0-1-8-9]
224k [0-1-9-8]
216k [0-1-9-8]224k [1-8-0-9]
112k [1-8]
0 2 xxx0 xxx0 xxx1C xxx-
0 4 xxx2 xxx2 xxx1E xxx-
0 A xxx8 xxx8 xxx24 xxx-
0 C xxxA xxxA xxx26 xxx-
0 D xxxB xxxB xxx27 xxx-
0 E xxxC xxxC xxx28 xxx-
0 F xxxD xxxD xxx29 xxx-
1 2 xxxE xxxE xxx0 xxx0 xxx
1 3 xxxF xxxF xxx1 xxx1 xxx
1 4 xxx10 xxx10 xxx2 xxx2 xxx
1 5 xxx11 xxx11 xxx3 xxx3 xxx
1 7 xxx13 xxx13 xxx5 xxx5 xxx
1 8 xxx14 xxx14 xxx6 xxx6 xxx
1 B xxx17 xxx17 xxx9 xxx9 xxx
1 D xxx19 xxx19 xxxB xxxB xxx
1 E xxx1A xxx- C xxxC xxx
1 F xxx1B xxx- D xxxD xxx
2 2 xxx2A xxx28 xxxE xxxE xxx
2 5 xxx2D xxx2B xxx11 xxx11 xxx
2 7 xxx2F xxx2D xxx13 xxx13 xxx
2 F xxx37 xxx35 xxx1B xxx1B xxx
3 2 xxx1C xxx1A xxx2A xxx-
3 3 xxx1D xxx1B xxx2B xxx-
3 5 xxx1F xxx1D xxx2D xxx-
3 7 xxx21 xxx1F xxx2F xxx-
3 9 xxx23 xxx21 xxx31 xxx-
3 C xxx26 xxx24 xxx34 xxx-
3 F xxx29 xxx27 xxx37 xxx-

<<<----- How to Swap Bank Order ------>>>

Most hex editing software like HHD's Hex Editor will not let you cut and insert paste instead paste overwrites so you have to do this as detailed.
PCMflash / Kess 224k [1-8-0-9] Files
to 256k [0-1-8-9] QHfrom 256k [0-1-8-9] QHto 216k [0-1-9-8]to 224k [0-1-9-8]
  • add null filler infront each bank 0x8192 bytes at
    0, 10000, 20000, 30000
  • add 65,536 at 0x000000
  • cut 0x30000 to 3ffff
  • paste at 0x0
  • banks are now ordered 0-1-8-9
  • clear all null filler by setting it to 0xff
  • to move 0 in front 9 do the following:
  • goto 30000 insert 65536
  • goto 0
  • cut 0 to ffff
  • goto 20000
  • paste at 20000
  • remove filler 30000 - 31fff; 20000-21fff; 10000 - 11fff; 0 - 1fff
  • del c000 to dfff
  • move 8 to end;
  • cut c000 to 19fff
  • add 57344 bytes at end
  • paste at 28000
  • move 0 to front;
  • fix c000 == ff
  • cut c000 to 19fff
  • add 57344 bytes at front
  • paste at 0
  • do all listed for
    216k file
    add 8192 bytes at 1a000
    then fill 1a000 to 1bfff
    216k [0-1-9-8] Files
    to 256k [0-1-8-9] QHfrom 256k [0-1-8-9] QHto PCMflash/Kess 224k [1-8-0-9]
    byte 0 should be 0xff, if not set it
  • insert 8192 at 0
  • fill 0-1fff
  • insert 8192 at 10000
  • fill 10000-11fff
  • insert 16384 at 1e000
  • ---> fill 1e000 to 21fff
  • insert 8192 at 30000
  • fill 30000 to 31fff
  • ---> [0198]; cut 20000 to 2ffff
  • insert 65536 at end
  • paste at end (30,000)
  • del fillers
  • swap 8 and 9 by doing:
  • insert 57344 at 1c000
  • cut 38000 to end
  • paste at 1c000
  • del 1a000 to 1bfff
  • add 8192 at 1a000 and fill 1a000 to 1bfff
  • --- 0198 needs to be 1809
  • move 8 to front;
  • insert 57344 bytes at 0
  • cut 38000 to end
  • paste at 0
  • move 1 to front;
  • insert 57344 bytes at 0
  • cut 2a000 to 37fff
  • paste at 0
  • Misc [8-1-0-9] Files
    to 256k [0-1-8-9] QHfrom 256k [0-1-8-9] QHto PCMflash/Kess 224k [1-8-0-9]
    Values below are not where they belong, need to fin.

    256k [0,1,8,9] (TunerPro Read) ---> 224k [0,1,9,8] (typical)

    You need to do 2 steps, 1st- remove filler, then swap banks 8 and 9.
    REMEMBER to always delete filler starting at the highest address and work down.
    fill 00030000 - 00031fff
    fill 00020000 - 00021fff
    fill 00010000 - 00011fff
    fill 0 - 1fff
    Now that you have a 224k bin you can simply swap banks 9 and 8 same as outlined above in the 224k details.
    • insert 57,344 bytes (0xE000) at 0x1C000
    • Cut 0x38000 [FF FA 27] to the end 0x46000
    • Then paste bank 9 there at 0x1C000, If done correctly, 0x1C000 will now have FF FA 27
    • Lastly, verify your bin stops at 0x37FFF for a max file size of 38,000 / 224 kb.

    256k Bin [0,1,9,8] (typ. but w/ flr added) ---> [0,1,8,9] (needed for QH)

    • For a 256k bin add 65,536 bytes at 0x20,000 then
    • cut 0x40000 to the end of the ROM
    • paste it at 0x20000, which will now have FF FA 27

      256k Bin [8,1,0,9] ---> [0,1,8,9] (needed for QH)

      If banks 0 and 8 are swapped:
    • insert 131072 bytes at 0,
    • cut 40000 - 4ffff
    • paste at 0, then
    • cut 30000 - 3ffff
    • paste at 10000

      Be sure to always set the null filler to 0xFF to clear it
      fill 0 - 1fff
      fill 00010000 - 00011fff
      fill 00020000 - 00021fff
      fill 00030000 - 00031fff

    224k Bin [0,1,9,8] (typical)---> [0,1,8,9]

    If you have a 224k bin file with banks ordered 0,1,9,8 as is most common, to swap banks 9 and 8 simply:
    • CUT address 0x2A000 (which will start with FF FA E7) all the way to the end of the rom 0x37FFF and paste it at 0x1C000
      If your hex editor will not let you cut and paste,
    • insert 57,344 bytes (0xE000) at 0x1C000
    • Cut 0x38000 [FF FA 27] to the end 0x46000
    • Then paste bank 9 there at 0x1C000, If done correctly, 0x1C000 will now have FF FA 27
    • Lastly, verify your bin stops at 0x37FFF for a max file size of 38,000 / 224 kb.

    224k [8-1-0-9] (database read) ---> 256k [0-1-8-9] (QH / TunerPro)

    • add null filler infront each bank 0x8192 bytes at (2a000, 1c000, e000, 0)
    • add 65,536 at 0x000000
    • cut 0x30000 to 3ffff, paste at 0x0, banks now ordered 0-8-1-9
    • add 65,536 at 0x10,000
    • cut 0x30000 to 0x3ffff, paste at 0x10000, banks are now ordered 0-1-8-9
    • lastly, go FF out the null filler

    224k [1-8-0-9] (PCMflash / KESS) ---> 224k [0-1-9-8] (224k typical)

    • cut 0x0 to 0x1bfff
    • insert 114,688 at 0xE000
    • paste at 0xE000
    • cut 0x1c000 to 29fff
    • goto end of rom
    • add 57,344
    • paste
    • banks are now ordered 0-1-9-8

    224k [8-1-0-9] ---> 224k [0-1-9-8] (224k typical)

    • add 57,344 bytes at 0
    • cut 0x2a000 to 0x37fff and paste at 0
    • banks now ordered 0-8-1-9
    • add 57,344 bytes at the end of the binary 0x38000
    • cut 0xe000 to 0x1bfff and paste it at 0x2a000
    • banks now ordered 0-1-9-8

    256k >> 112k (readout of 2 bank conversion)

    • delete 0x00000 to 0x11FFF
    • delete 0xe000 to 0xffff
    • delete 0x1c000 to 0x2bfff (end of rom)

    112k >> 128k

    • add 8192 bytes of filler at 0000e000, fill 0000e000 to 0000FFFF with 0xff =
    • add 8192 bytes of filler at 00000000, fill 00000000 to 00001FFF with 0xff =

    112k >> 256k (needed for QH)

    • add 73,728 bytes of filler at 0, fill 0 to 00011FFF with 0xff =
    • add 8192 bytes of filler at 20,000 fill 20,000 to 21,FFF with 0xff =
    • save file and close, then re-open file
    • add 65,536 bytes of filler at end of ROM 30,000
    • fill 30,000 to 3F,FFF with 0xff =

    SAD - Semi-Automatic Disassembler

    Now that we have a valid bin its time to run it through the disassembler. For this example I will be using FBFG2 (2004 Mustang GT) and AJAQ3 (95 Ford Ranger) There's a few disassemblers out there for the Intel 8061/8065 processors but the two most used are the Bill Lawrence Disassembler that has been around since the late 90s and the much much more recent SAD - Semi Automatic Disassembler by Andy "TVRFAN". The Bill Lawrence disassembler has been updated over the years and works flawlessly. The newer SAD Disassembler does a lot of the work for you and is much easier for the beginner to use. Not only that, its updated by the author so we can simply tell him any problem we have and he's pretty good about fixing it. A+++ in my book, the less work we have to do the better!!! So to begin, create a new folder named "disassembly" just to keep everything organized. Toss your extracted stock tune in there and name your binary file stock.bin, be sure to also toss in there a shortcut to the SAD executable.

    SAD can be downloaded from TVRFan's GitHub HERE .

    Run the Disassembler
    Now is the time to run your binary through the disassembler. Simply drag and drop the bin file over the SAD executable and it will begin.

    SAD - _msg.txt - Messages

    First things first, open up the message file stock_msg.txt and make sure theres no major errors. Scroll on down to the 2nd rbase list toward the end of the messages file. Now click just above it and scroll back up to the top, hold down the Shift button and Press A to select all of it and press backspace, you'll now see the rbase list at the top of the msgs file in notepad. Now scroll on down to the end of the subroutine list just beaneath it and deleted everything after the end of the subroutine list. You can do a simple Ctrl + F and pop up the find type in "---" and delete the entire line that those comments are noted. Now save the file as stock_dir.txt You now have a directive file to begin tinkering.

    Directive File

    The directive file is where you will assign labels to addresses you decode, this will make it MUCH EASIER to do disassembly since you can very easily see the PID name where a value is being used. The following is a quick cheat list of the temporary registers used in most EEC-V's. Copy and paste the following into your xxxx_dir.txt file at the top of the list just beneath the rbase parameters and before the subroutines:

    SYM 24 "temp0l"
    SYM 25 "temp0h"
    SYM 26 "temp1l"
    SYM 27 "temp1h"
    SYM 28 "temp2l"
    SYM 29 "temp2h"
    SYM 2A "temp3l"
    SYM 2B "temp3h"
    SYM 2C "temp4l"
    SYM 2D "temp4h"
    SYM 2E "temp5l"
    SYM 2F "temp5h"
    SYM 30 "temp6l"
    SYM 31 "temp6h"
    SYM 32 "temp7l"
    SYM 33 "temp7h"
    SYM 34 "tmp1l"
    SYM 35 "tmp1h"
    SYM 36 "tmp2l"
    SYM 37 "tmp2h"
    SYM 38 "tmp3l"
    SYM 39 "tmp3h"
    SYM 3A "tmp4l"
    SYM 3B "tmp4h"
    SYM 3C "tmp5l"
    SYM 3D "tmp5h"
    SYM 3E "tmp6l"
    SYM 3F "tmp6h"
    SYM 40 "tmp7l"
    SYM 41 "tmp7h"
    SYM 42 "tmp8l"
    SYM 43 "tmp8h"
    SYM 44 "tmp9l"
    SYM 45 "tmp9h"
    SYM 46 "tmp0l"
    SYM 47 "tmp0h"
    SYM 48 "fgtmp0l"
    SYM 49 "fgtmp0h"
    SYM 4A "fgtmp1l"
    SYM 4B "fgtmp1h"
    SYM 4C "fgtmp2l"
    SYM 4D "fgtmp2h"
    SYM 4E "fgtmp3l"
    SYM 4F "fgtmp3h"
    SYM 50 "fgtmp4l"
    SYM 51 "fgtmp4h"
    SYM 52 "fgtmp5l"
    SYM 53 "fgtmp5h"

    SAD - _lst.txt - Listing

    Now go back to SAD and click View >> Output File (or just open the stock_lst.txt file it created). Your disassembly listing should now be opened in notepad. At this time your probably wondering, where do I begin? Well that depends on who you ask, many start at the beginning and work their way through the code, me personally, I'm rather impatient and like to jump right to the good stuff as quickly as possible. The most important function in a Mass Air ECU is the Mass Air Flow transfer curve, so I jump straight to it :) The MAF transfer is VERY EASY to find. AFAIK, in all strategies (at least all that I've worked on) the MAF lookup code is identical. In your listing file, go up top and click on search and type in "LSSI_C". You want to keep searching until you find where interrupts have been disabled right before the LSSI is read. If it has, directly beneath LSSI_C you will see LSSI_B and LSSI_A being read as well and interrupts re-enabled. If you scroll down just below that a few operands, you will see a call to the interpolation routine.

    Here is an example of the maf lookup in a 2 bank strategyHere is an example of the maf lookup in a 4 bank strategy
    Note that it is showing the outdated SAD disassembly listing.
    2 Bank MAF Routine 4 Bank MAF Routine

    Verify MAF Transfer

    On 2 bank ecu's, the call will have the function number in it being called, in the example above SAD automatically named the maf transfer function "Func16", you can verify that is the actual MAF transfer by going to that function number and looking at its values. For 4 bank ecu's the lookup routine will have the maf transfer address loaded into register 36 before the call is made. You can go to that address (2250 in the example above) and verify it is correct as well. In both cases the MAF function will be 30 rows long and start with an FF FF and end with an 00 00. Congrats! you've just found the most important function in the ecu!

    The y axis of the maf transfer is the airmass flow. Depending on your strategy the airmass flow hex value to lbs/min of airflow equation is affected by the processor speed. The table below lists the ecu clock speeds and their typical conversion. In newer ecu's / strategies the native value is already lbs/min.

    ECU Clock Speed

    ECU Clock Speed (XTALHPS) MAF Flow Conversion to lbs/min
    274.0lbs/minX / 1024RZASA
    233.0lbm/tickX / 5.302409021014864 * 2.2 / 60CVAF1
    212.0lbm/tickX / 163.5670949911362CRAI8
    150.0lbm/tickX / 83.75154837603146GUFX
    X / 3.1562283 * 2.2 / 60GSALI

    Directive and Comments Files

    Now that we know the maf transfer's address we can add that to the directive file along with the maf voltage register and name the maf and interpolation routines. In your disassembly folder where your tune is saved, right-click >> new text document, name it stock_dir.txt, while your at it create a stock_cmt.txt file as well.


    In your directive file, you will want to assign the maf a name so it will automatically be labelled in the code, this will make life MUCH easier. To do so, you simply open up your newly created stock_dir.txt text file with notepad and enter the following.
    func12250 122c7"func_MAF_Transfer":WV+12800:WV+1024

    For detailed information reference SAD.pdf for a full breakdown of the disassemblers functions.

    The following quick reference charts are to quickly find the end address of a function and table to input in your directive file.
    Table End Address
    TblY RowsxX Cols
    Table Size
    Decimal SizeHex Size
    Tbl10x12byte(*2)(120-1)= 11977
    Tbl10x10byte(*2)(100-1)= 9963
    Tbl9x11byte(*2)(99-1)= 9862
    Tbl8x12byte(*2)(96-1)= 955F
    Tbl8x10byte(*2)(80-1)= 794F
    Tbl6x12byte(*2)(72-1)= 7147
    Tbl6x6WORD(*4)(72-1)= 7147
    Tbl6x6byte(*2)(36-1)= 3523
    Tbl5x5byte(*2)(25-1)= 2418
    Function End Address
    Function Size
    Decimal SizeHex Size
    Func30WORD(*4)(120-1)= 11977
    Func13WORD(*4)(52-1)= 5133
    Func12WORD(*4)(48-1)= 392F
    Func12byte(*2)(24-1)= 2317
    Func10WORD(*4)(40-1)= 3927
    Func10byte(*2)(20-1)= 1913
    Func8WORD(*4)(32-1)= 311F
    Func8byte(*2)(16-1)= 15F
    Func7WORD(*4)(28-1)= 271B
    Func7byte(*2)(14-1)= 13D
    Func6WORD(*4)(24-1)= 2317
    Func6byte(*2)(12-1)= 11B
    Func5WORD(*4)(20-1)= 1913
    Func5byte(*2)(10-1)= 99
    Func4WORD(*4)(16-1)= 15F
    Func4byte(*2)(8-1)= 77
    ***Add HEX size to Start address to find end address.


    In your comments file, you want to put notes as to what each line of code is doing, that way you can very easily work on the code without having to decipha what you've already deciphered. This will make the process MUCH easier in the long run especially when you walk away from the code for a while and jump back in it months later. So open up your newly created stock_cmt.txt text file with notepad and enter the following.
    09e20|MAF Subroutine
    09e66// MAF Transfer Function
    12250// MAF Transfer Function


    Now that you've updated the comments and directive file, re-run SAD and check out the labels you've made. You will now see that its much easier to follow the code with comments and labels instead of memory address locations. Get used to re-running the disassembler, you will be repeating the disassembler indefinitely to resolve addresses to labels to make the process much easier.

    Understanding Opcodes

    Now that we have a segment of known code, we can go through and discuss the operation codes and what exactly the calculator "ecu" is doing. The following is a listing of the Intel 8096 opcodes adjusted for the 8061/8065 instruction set. The following data is the best known information available, there are no known publically released or available 8061/8065 instruction sets, thus the following was adapted from 8096 over the years.

    8065 Opcodes
    Obvious, any of the bank opcodes will not apply to single bank binaries.

    The following is a very simplified list of the more common opcodes dummied down explaining what they do / how they work. It's most helpful if you are creating your own code or hacking away at the code. As you can infer, its aimed more toward the C / C++ programmers. For those not savvy in C and C++, anytime you see a bang "!" it means false / not (I.E. the result equates to ==0), any value other than 0 will return true.

    HEX OpcodeC++Operation Comments
    00 SKIPjump 1 location, kin to NOP, basically a NopNopjumps over 1 byte or "skips" the next byte, usually the following byte is a clear carry
    01reg CLRWreg = 0sets 2 bytes to 0 clears word
    03reg NEGWreg = -reginverts a word from negative to positive (signed to unsigned) RPMDED is a perfect example where the value can be flipped so only 1 lookup is performed
    05reg DECWreg--reg = reg - 1 used to decrease a value during loops
    07reg INCWreg++increases value by 1 increments a word, 0++ = 1 not 2
    08bitreg SHRWreg /= bit07 = / 128; 04 = /16 bit 7 = 10000000 in binary; usually used with /10 to convert from word scaling to byte for table lookup
    09bitreg SHLWreg *= bit 07 = *128
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 == 1 dec

    0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 == 2048 dec; when bit shifted 11 you effectively *2048
    0abitreg ASRWreg /= bit 03 = /8
    0cbitreg SHRDWreg /= bit 05 = /32; 0a = /1024 0x400
    0dbitreg SHLDWregL *= bit 07 = *128
    10xx RBANKBank = xxReference Bank for next address
    11reg CLRBreg = 0clears byte of data
    17reg INCBreg++increments value by 1 increments one byte
    20jj SJMPjump forwardjump forward jj locationsE7 is a GOTO / JUMP for direct addressing
    22jj SJMPjump forward +200jump forward jj locations THEN jump +200
    23jj SJMPjump forward +300jump forward jj locations THEN jump +300
    24jj SJMPjump back -400jump forward jj THEN jumpBACK -400
    26jj SJMPjump back -200jump forward jj THEN jumpBACK -200
    27jj SJMPjump backjump forwards jj locations THEN jump back -100
    28jj SCALLcall addresspushes current address to stack then jumps forwards jj
    29jj SCALLcall address +100does the same as 28 but then jumps +100 more
    2ajj SCALLcall address +200""
    2bjj SCALLcall address +300""
    2fJ3 SCALLscall lower address -J3
    J3 Subtraction Addresses
    14 = -EC
    1c = -e4
    2f = -d1
    4f = -b1
    7b = -85
    8b = -75
    b2 = -4e
    ba = -46
    c9 = -37
    d4 = -2c
    30regjj JNBif !Bit 0if bit0=0 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    31regjj JNBif !Bit 1if bit1=0 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    32regjj JNBif !Bit 2if bit2=0 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    33regjj JNBif !Bit 3if bit3=0 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    34regjj JNBif !Bit 4if bit4=0 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    35regjj JNBif !Bit 5if bit5=0 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    36regjj JNBif !Bit 6if bit6=0 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    37regjj JNBif !Bit 7if bit7=0 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    38regjj JBif Bit 0if bit0=1 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    39regjj JBif Bit 1if bit1=1 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    3aregjj JBif Bit 2if bit2=1 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    3bregjj JBif Bit 3if bit3=1 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    3cregjj JBif Bit 4if bit4=1 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    3dregjj JBif Bit 5if bit5=1 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    3eregjj JBif Bit 6if bit6=1 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    3fregjj JBif Bit 7if bit7=1 then jj (up to 0x80) signed jump, if exceeds 7F jump backwards
    45xxyyreg3reg2 AD3Wreg2 = [reg3 + yyxx]
    4bptrofreg2reg SB3Wreg = reg2 - [ptr+of] used to subtract a minimum from a current value
    57reg2of2of1adr2 AD3Breg2= ad + [(reg2-1) + of1of2] used to follow up with comparison usually 0quick way to make sure value is greater than 0
    5creg3reg2reg ML3Breg = reg2 * reg3 used to multiply byte registers and save the result as a word
    64reg2reg AD2Wreg += reg2
    65xxyyreg AD2Wreg += yyxx simply does addition on words sister is 75 for byte, subtraction is the opcode 69
    66reg2reg AD2Wreg += [reg2] saves the value of reg2 and then increments reg2
    69xxyyreg SB2Wreg -= yyxx subtraction for words addition is the opcode 65
    6dxxyyreg ML2Wreg *= yyxx simple multiplication for words division is the opcode 8C
    71hexreg AN2B reg (hex)bit=false both the current and hex bit have to be equivalent for the resulting bit to be set true, think of this as AND(1) if both bits are 1 the resulting bit is 1disables / turns off a bit
    fe = bit 0 off
    fd = bit 1 off
    fb = bit 2 off
    f7 = bit 3 off
    ef = bit 4 off
    df = bit 5 off
    bf = bit 6 off
    7f = bit 7 off
    75hexreg AN2B reg += hex adds HEX to reg sister is 65 for word
    7creg2reg ML2Breg *= reg2 multiplies 2 bytes used to multiply a byte by another byte register
    7dxxreg ML2Breg *= xx simple multiplication of a byte by X value used for simple arithmetic
    88regreg2 CMPWreg2, reg compare reg, reg2If reg2=0, instead of R0, just 0 will be compared
    89xxyyreg CMPWreg, yyxx
    8areg2reg CMPWreg, [reg2-1] compare reg, to reg2-1instead of comparing to reg2 and reg2+ (for word) it compares to reg2- and reg2
    8bptrofreg CMPWreg, [ptr+of]
    8creg2reg DIVWreg = regL /= reg2 divides a long by a word this may not be simple division
    8dyyxxreg DIVWreg = reg / xxyy divides a word by a value this is simple division
    90reg2reg ORB reg |= reg2 used to OR compare 2 different bytes
    91hexreg ORB reg ^ HexBits if either the current or hex bit is set, the result is a set bit ; think of this as OR1, if either bit is 1 the resulting bit is 1 used to turn on flags and pins
    1 = bit 0 on
    2 = bit 1 on
    4 = bit 2 on
    8 = bit 3 on
    10 = bit 4 on
    20 = bit 5 on
    40 = bit 6 on
    80 = bit 7 on
    92[ptr]reg ORB reg |= [ptr] used to OR compare 2 different bytes
    93ptrofsreg ORB reg |= ptr+ofs used to OR compare 2 different bytes
    94reg2reg1 XORBreg1 ^ reg2 exclusive OR, think of this as a difference compare, if the bits are not equal the resulting bit is 1, if both bits are equivalent the resulting bit is 0 changes its state and flips it back
    95hexreg XORBreg ^ HexBits toggles a bit changes its state and flips it back
    97ptrofsreg XORBreg = reg^[ptr+ofs] toggles a bit changes its state and flips it back
    98reg2reg CMPB(signed)
    99xxreg CMPBreg, xx
    9aR[ptr]reg CMPBreg, [Rptr] compares byte to pointer
    9bptrofreg CMPBreg,[ptr+of] reg 0 will compare to 0
    a0reg2reg LDWreg = reg2
    a1xxyyreg LDWreg = yyxx could be val or pointer
    a2reg2reg1 LDBreg1 = [reg2-1]; then reg2++ takes the value at that address, then the next loop increments reg2 used to get to the next address in an array
    a3ptrofreg LDWreg = [ptr+of] special function only0xC3 will flip the register and address
    a3ptr(-1)xxyyreg LDWreg = [ptr(-1) + yyxx] loads a RAM value to a scratch register used to compare RAM
    a4reg2reg ADCWreg += reg2 + CY add sum of reg2 and carry to reg
    a5xxyyreg ADCWreg += yyxx + CY add sum of yyxx and carry to reg yyxx is often set to 0000 to get carry only
    acreg2reg LDZBWreg = reg2
    adxxreg LDZBWreg = xx
    b0reg2reg reg = [reg2] (signed)
    b1xxreg LDBreg = xx (byte)
    b301xxyyreg LDBreg = [yyxx] copies the value out that memory [address]
    b3ptrofsreg LDBreg = [ptr+ofs]
    b3ptr(-1)ofs00reg LDBreg = [ptr+ofs] loads a byte of RAM into a scratch register used to get at an index
    b3reg2(-1)AxxAyyreg LDBreg = [ [reg2 + [AyyAxx]] loads a byte of offset ram into a scratch register used in a loop to copy a list
    bcxxreg int
    bdxxreg LDSBWreg = SSxx loads a word with a signed byte, bits 8 through 15 will be filled with the value of bit 7, either all 0 or all 1's
    c0reg1reg2 STWreg1 = reg2 loads value from register 2 in to register 1. Often used as (0x100 shortcut - odd byte of RAM) to copy registers over.
    c2ptrxx STW[address++] = xx loads xx into pointer address, not into this actual address usually used for loops to clear data
    c301xxyyreg2 STW[yyxx] = reg2 used to get to the upper RAM addresses 0xF000 0xA3 01 will flip the operand and register
    c3ptrofreg2 STW[ptr+of] = reg2 used to get to the lower RAM addresses 0x1000 special function only
    c3ptr(-1)of00reg2 STWreg2 = [ptr(-1)+of] used to save a scratch register to a RAM address
    c6ptrxx STB[address++] = xx loads xx into pointer address, not into this actual address usually used for loops to clear data
    c7ptrof0 STB[(ptr+of] = 0 if xx==0 then no reg2 check used to clear a ram address
    c7ptr(-1)ofxxreg2 STB[(ptr-1)+of] = reg2 note the ptr-1 to get lower address
    c9xxyy PUSHWyyxx puts value of x1x2 at top of stack array
    cbptrof PUSHW[ptr+of] puts value at address on the top of the stack array
    cbptrof POP[ptr+of] removes value at the top of the stack array and saves it into the address
    d1jj if <= reciprocol is > inverse opcode is DB
    d2jj if (signed) > reciprocol is <= inverse opcode is DE
    d3jj if < recipricol is >= inverse opcode is D9
    d5jj if (!overflow) jumps if overflow==0
    d6jj if (signed) >= jumps if >= inverse opcode is DA
    d7jj JNEif != jumps if != inverse opcode is DF
    d9jj if > recipricol is <= inverse opcode is D3
    dajj if (signed) <= jumps if <= inverse opcode is D6
    dbjj if >= jumps if < inverse opcode is D1
    dejj if (signed) < reciprocol is >= inverse opcode is D2
    dfjj if == jumps if == inverse opcode is D7
    e0regjj reg--, if !=0, then jj, then jj-100 decrements reg (for loop) if !=0, jump forward jj then jump back -100used for FOR loops
    e7jjj2 JUMPjump jj then j2 jumps forward jj then jumps again j2
    efjjj2 CALLjump forward jj then j2 pushes current address to stack, then jumps forward jj then jumps again j2
    f0 RETcall stack return address returns to the top address of the stack
    f2 pushp (PSW)save PSW saves the processor state word used to save the PSW for fiddling with the stack (passing args)
    f3 popp (PSW)restore PSW returns the processor state word back to normal MUST be followed by a return 0xF0, this is used to restore the psw after args are passed
    f8 clear carry
    fb EIenable interrupts
    ff NOPno output jump self (do nothing), kin to SKIP burns a clock tic, useful for accurate repetitve timing

    J2 Jump Addresses

    <----Negative Jumps---->
    <----Positive Jumps---->
    You get the idea.
    Note: Jumps are words that roll over within the same bank, a jump to 1XXXX will result in the 1 being omitted.

    2's Complement Math

    To understand bits, you need to understand how a byte is structured. A byte is a single register of data with a value from 0-255. A byte consists of 8 bits which is 8 "switches" in binary being that in binary you only have a value of 1 or 0, I.E. switch on (1) or off (0), or true (1) or false (0). 2s complement math can easily be broken down by understand the bits position and value. In a byte, the lowest bit (right most - bit 0) has a multiplier of 1. The first bit has a multiplier of 2, the 2nd bit a multiplier of 4, the 3rd bit a multiplier of 8, the fourth bit a multiplier of 16, the fifth bit a multiplier of 32, the sixth bit a multiplier of 64 and the final seventh bit a multiplier of 128.

    So to break this down, we can do a few examples to make it easier to understand.

    Subroutine / Function Arguments

    Some operations in the ecu such as doing interpolation look up or setting fault codes are very redundant, and to prevent an excessive amount of functions to do the same task, the ecu passes arguments to a function so it can carry out the task. Think of it as being variables modifying variables. In the RZAS0 binary there are only 3 functions that accepts these variable of variables "arguments". To define arguments in your directive file, simply go to the function or subroutine address and add : Y O +2 or : Y O +6. The # defines how many bytes are omitted after the function call and not used as code, that way the disassembler doesn't try to disassemble it. At this point, its not important whether the arguments are words or bytes, whats most important is that the disassembler can decipher code from arguments so you get a clean listing. For example in cbaza, you would have the following line in your directive file

    subr 0717c "subr_717c" : Y O +4

    which tells the disassembler anytime 0x717c subroutine is called to omit the followig 4 bytes since they are arguments and not code.

    Definition File (TunerPro XDF Files)

    A definition file is basically the same across all software. It maps the memory locations to human readable values. In order to add a parameter to your definition file there are a minimum of 6 values you will need to know.

    PIDgood to have but a good name will suffice if need be
    Byte / Wordbyte (1 byte = =8 bits) or word (2 bytes == 16 bits)
    Signed / Unsignedsigned allows negative numbers
    Equation for example: x/2
    Input/UnitLoad, ECT, ACT, VSBAR, etc..
    CommentsAlways good to describe what the paramter does.
    TIP: I HIGHLY RECOMMEND to add your parameters to your definition file in order by memory address to keep them organized. Doing so will help prevent you from accidentally inputting the wrong address, it also makes it significantly easier to navigate, modify, and update your definition file and will save you thousands of hours.

    Categories / Levels (TunerPro XDF Header Info >> Categories)

    I have found the following MAIN categories to be most effective for all parameters to fall under.

    Note: See the latest RZASA xdf for the most up-to-date information.
    VE Model
    ISC Control
    H/W Configuration

    VID Block

    The VID block is typically stored in the same location on all binaries. The VID block should be added to your definition file to give you full access.
    VID Block Parameters
    CAL_IDFF063xFF06ASCIIthe strategy and two letter calibration identification
    PATS_CODEFF133xFF13HEXthe unique vehicle specific pats code
    TAGFF633xFF63ASCIITAG - contains ford copyright info such as year
    VINFF803xFF80ASCIIVIN - used for emissions testing and by some OBD-II scanners for vehicle info
    VID_ENABLESW3xFF95X42 (0xA2) == VID block enabled, 255 (0xFF) == VID Block disabled
    VID_REVMILE3xFF9AXvehicle tire revolutions per mile for information only, not used in any calcs
    VID_RT_AXLE3xFF9CX/1024rear end gear ratio used for vehicle speed calc when the vid gear axle ratio switch is enabled

    Important Addresses

    These addresses are typical in most all 4 bank ecu's.
    1x200ABIN_CHIP_IDcalibation id

    Quick Find Code Blocks

    The EEC's use the same code with tidbits of code added and removed over the years, this is what causes the different strategy names. Below is a quick reference chart of common code in most eec-v ecu's. This can be used as a cheat menu to quickly find segments of code your looking for specifically. This will be aimed more toward locatings payloads but it applies for calibration constants as well.
    Quick Reference Code
    KAMRF180,00,44or 65,80,00,44
    Dashpot Decay FN87968,3c,2call dashpot in routine
    UPDISCUPDATM in following routine
    TQM_SWGear Axle Ratio and Rev Mile to follow
    Pump_Voltagead,09,38RFS routine
    TP_OL_Threshold: 01,3e
    [code] JUMP_FRAC7c,35,34byte in code
    [code] xx09,04,30CL idle modulation
    [code] JUMP_MULT08,02,38
    fn012a0,37,46mbt routine
    hsf91,40,a1fan values
    crank pwb1,00,3a3rd or 4th from top, can find lambse values and a great many functions!!!
    P100037,2a,03mil sw and force good code


    The ecu calculates the checksum to verify the ROM is not corrupt. To calculate the checksum the following addresses are summed up, if their value equals 0, then the checksum is valid.
    Side note: The actual address the checksum is saved to is null since a correct checksum will return 0.
    I recommend rewriting the checksum calc routine to include the higher range on older 4 bank strategies instead of cross calculating.
    StrategyBank 0Bank 1Bank 8Bank 9ROM_TO
    4-BANK (newer)2000 - FFFF12000 - 1DFFF22000 - 2FFFF32000 - 3FEFF12004
    4-BANK (older)2000 - FFFF12000 - 19FFF22000 - 2FFFF32000 - 3FEFF12004
    CDAN42000 - FEFF12000 - 1FFFF1200A
    CBAZA2000 - FFFD200A
    GUFB2000 - 9FFF200A
    In all cases, the checksum is saved as an LSB 2-byte word using 2s compliment math.

    TunerPro Checksum Calc

    TunerPro will apply the checksum calcs STARTING with the FIRST ENTERED and working in order to the LAST ENTERED. The order they are added to the xdf is most important as the order they are displayed is not the order they are executed!!! Keep this in mind as you must ALWAYS enter in your checksum calcs in the correct order otherwise it will not function correctly.
    Special Instructions
    Special instructions include any background manipulation you need to complete to correctly calculate the checksum.
    These must be performed first (ENTERED FIRST) otherwise they will incorrectly excuted after the checksum has been calculated.
    TitleStartEndStoreStore SizeData SizeLSBPlug-inCalcComments
    XX --->Enter any Special Instructions first
    SumBank02000FFFF3FFF022LSBDefaultTWOsSum of bank 0
    SumBank1120001DFFF3FFF222LSBDefaultTWOsSum of Bank 1; Older Strats may only check up to 19FFF
    SumBank8220002FFFF3FFF422LSBDefaultTWOsSum of Bank 8
    SumBank9320003FEFF3FFF622LSBDefaultTWOsSum of Bank 9
    SumErr3FFF03FFF73FFF822LSBDefaultSumSum of All Banks
    SumOld12004120053FFFA22LSBDefaultSumCopy old checksum
    Checksum_ROM3FFF83FFFB1200422LSBDefaultSumSave New Checksum to ROM
    CleanupB32000320003FFFB11LSBDefaultSumSet Null filler back to FF==null
    CleanupA32000320003FFFA11LSBDefaultSumSet Null filler back to FF==null
    Cleanup932000320003FFF911LSBDefaultSumSet Null filler back to FF==null
    Cleanup832000320003FFF811LSBDefaultSumSet Null filler back to FF==null
    Cleanup732000320003FFF711LSBDefaultSumSet Null filler back to FF==null
    Cleanup632000320003FFF611LSBDefaultSumSet Null filler back to FF==null
    Cleanup532000320003FFF511LSBDefaultSumSet Null filler back to FF==null
    Cleanup432000320003FFF411LSBDefaultSumSet Null filler back to FF==null
    Cleanup332000320003FFF311LSBDefaultSumSet Null filler back to FF==null
    Cleanup232000320003FFF211LSBDefaultSumSet Null filler back to FF==null
    Cleanup132000320003FFF111LSBDefaultSumSet Null filler back to FF==null
    Cleanup032000320003FFF011LSBDefaultSumSet Null filler back to FF==null
    Note: 3x2061 typically contains 0x00 on most all EEC-Vs, this is needed to clear the checksum in order to correctly calculate the new.

    Equation (useable fractional digits)

    The following is a quick reference list for useable digits AFTER the decimal, this is important when creating a definition file so users can accurately change values without a loss of resolution.


    0.52^1X / 211typically vehicle speed
    0.252^2X / 40 (or ~2)0 (or ~1)typically spark or RPM, if RPM set decimals to 0
    0.1252^3X / 810 (or ~1)typically timers
    0.06252^4X / 16~0 (or 2)1RPM but also used for scaling =0, volts=2
    0.031252^5X / 3222usually Airmass but also idle speed or RPM
    XX / 502typically relative throttle voltage TPREL used for trans functions
    2^6X / 6422typically trans related specifically TP_REL
    2^7X / 12832typically lambse and tq
    2^8X / 25633 or ~0this is usually used for scaling and trans related, byte for egr fault timers
    2^9X / 51233typically accel rate or multiplier modifiers
    2^10X / 102443typically ratio, byte used for misfire rolavg fault filters
    2^11X / 204846typically dashpot, byte is only used for MAPSLOPE afaik
    2^12X / 409644typically isc airmass, byte is usually fn1353 hego bias
    2^13X / 81924usually hego bias
    XX / 128003typically voltage
    2^14X / 163845typically ratio related to trans
    2^15X / 3276835typically load but also duty cycle=3, gain=5
    2^16X / 655365typically timers
    2^17X / 1310726only XFREPT
    2^18X / 262144XXX
    2^19X / 5242886used for can purge flow
    2^20X / 10485766used for DASPTK, not sure of anything else
    2^21X / 2097152XXX
    2^22X / 41943047xx
    2^24X / 16777216XXX
    2^25X / 335544327typically fuel mass for injector breakpoint
    2^31X / 2147483648Xpayload only; maf clock tick/xx == maf on 27mhz ecu
    XX / 75497472003typically lbs/hr for injector slopes

    Multi-addressed variables

    Many scalars in the EEC-V's have multiple addresses to achieve the same value. This is due to the code actually having the value present rather than performing a look-up to a scalar. This makes the code more efficient. In these cases, I highly recommend using the PATCH type paramater in tunerpro and add all the addresses to the patch so they are all changed at once IF the value is enumerated. The Rear Hego Present scalar is a perfect example, in RZAS0 this scalar is reference in 21 different locations!!!!! You can simply assign the patch with a value of 0 to disable the hegos and a non-patched value of 2 to allow the rear hegos (stock h-pipe). This can be done for any switch type scalar. In dynamic scalars like the injector slopes for example, this cannot be done since the value must be inputted and numerous entries are needed. I denote the entries with a simple x / x for easy reference.


    A "Hack" is simply a modification to the code that was never intended to be performed. It is often common to swap operands when 'hacking' to make a function perform differently. When doing so, an enumerated value should be used, however, I have my own decipha exclusive equation I'll share below.

    Decipha Exclusive Equations / Elude Enumeration

    When adding new scalars for hacks, I use the following equation (this is a decipha exclusive) and not recommended
    (x-lower value) / [remainder of high-low]
    so lets say you have
    22610 stock value
    8584 is your new hacked value

    22610-8584 = 14026, This gives us an equation of:


    a value of 1 = 1*14026; 14026 + 8584 = 22610 (stock)
    a value of 0 = 0*14026; 00000 + 8584 = 8584 (hacked)

    thats how I make my **scalar** switches 0.000 and 1.000 to alter opcodes and what not, if using BE be sure to add at least 3 decimal places and limit the value range from 0 to 1 to prevent any corrupt values from accidentally being loaded. The decimals make it easy to see if the value is corrupt. If using TunerPro YOU CANNOT USE THIS EQUATION since TunerPro will not prevent someone from inserting decimals between 0 and 1. For TunerPro enumerations I recommend using the PATCH data type. Then just assign your data there with an enable/disable button simple enough.

    A **scalar** is a single dependent variable in ROM that defines a specific value such as engine size or injector size.

    TunerPro Value Equation for Automated Hide / View

    When creating hacks such as moving or extending scaling function for more resolution you will need a new table parameter to show the new scaling of your new function. This can be tricky since users that do not have your patch enabled will be viewing the incorrect values. However, decipha has a fool-proof solution that will make it easier for everyone. Tunerpro has an excellent feature that allows you to reference a value for arithmetic. This will solve our patch/hack problems from causing the wrong scaling to be viewed. By creating two exact copies of the same table one with the old scaling (for reverse compatibility) and one with the new, you can use this equation to 0 out the table not in use. This will make it very easy to identify which table scaling is in use. Obviously when someone see's a table is all 0s they can infer that the table is not being used.

    so for example, lets say one of the bytes of your modified code is now 0x04 and the prior stock value was 0x00.
    The following equation will show you how I invert the value so high and low are swapped.

    For example lets say your actual table equation is:
    X / 128

    The following will hide and display the tables automatically based on your manipulated code

    Extended Table Equation Modifier
    Y==4; X / 128 * (Y / 4) = 1 = multiply the equation by 1 = correct viewing
    Y==0; X / 128 * (Y / 4) = 0 = multiply the equation by 0 = result is all 0s, table is 'hidden'

    To INVERT for Stock Table Equation Modifier
    Y==4; X / 128 * ((((Y -4) *-1) +0) / 4) == 0 == result is all 0s, table is 'hidden'
    Y==0; X / 128 * ((((Y -4) *-1) +0) / 4) == 1 == correct viewing

    Why the +0 ? So that the table doesn't display negative values

    Check out the FN904 Sealevel Spark Table and Extended Table in my GUFx def file to see it in action based on the FN071_EXT patch value.

    See the MOATES write up for how datalogging is created and functions within' software and the quarterhorse.