Switch case using EA in ROP - Ep.1
Basic introduction to switch case using EA
Notes: All addresses mentioned below is for verA
EA Branch Method
The EA Branch Method relies on the two-bytes traversal function using EA and ER2 by calling 0:9C20
.l_01C:
0:9C20H 9252 L ER2, [EA+]
0:9C22H C903 BEQ .l_026
0:9C24H F207 CMP ER2, ER0
0:9C26H C8FC BNE .l_01C
0:9C28H E000 MOV ER0, #00H
.l_026:
0:9C2AH F28E POP PC
This function traverses the content at the EA address and checks if it matches the value in ER0. If it does, ER0 is set to 0, and the function executes a POP PC to return. If it reads 0000, it ends the function immediately (BEQ .l_026) by executing POP PC. Thus, the address stored in EA correspond to the memmory address of ER0 + 2 (because the EA+ instruction is executed)
Setup Steps for Branching
- Create a Jump Table
- Create a case - value table where the first 2 bytes are the case, and the last 2 bytes are the value.
- The last case must be set to
0000to terminate the traversal, and serves as the default case.
These tables can be stored anywhere in memory, but storing them after the program is recommended to avoid corruption from PUSH instructions. Before use, the tables should be copied to another location since the [EA+] instruction in
0:9C20will alter them.Your table should looks like this
01 10 80 E9 ; case 1001, return E980 03 30 69 E9 ; case 3003, return E969 00 00 E0 E9 ; default case, return E9E0 - Load the Value into ER0 and Traverse the Table After initializing
EAto point to the table’s starting address, traverse the table using0:9C20. After traversing, the value stored inEAis the pointer of the return value (since the case is exactly 1 word long, the current EA will perfectly aligned to the return value in the memory)
Before Calling 0:9C20 (Table traversal)
Before calling the table traversal function, we have to set EA to the starting address of the case table.
Set EA using MOV by calling 1:7CA6 Use
1:7CA6to setEAwithER14:f_17CA6: 1:7CA6H F0EA LEA [ER14] 1:7CA8H 9056 L QR0, [EA+] 1:7CAAH 9852 L ER8, [EA+] 1:7CACH F0CA LEA [ER12] 1:7CAEH 9057 ST QR0, [EA+] 1:7CB0H 9853 ST ER8, [EA+] 1:7CB2H FE1F RTYou could use
POP XR12to set bothER12andER14at once (this is recommended). IfER12 = ER14, this setsEAto*[ER14] + 0xAwithout causing any memory corruption. Be noted that this function ends withRTsosetlris required.Alternatively, you can call
1:7CACto directly setEAto*[ER12] + 0xA, but this will overwrite the 10 bytes after*[ER2]. Use it with caution.Set EA using POP by calling 1:0E80 Before calling
1:0E80, setR6to0x01to prevent you from looping back to the beginning of the function. Note that this function ends with aPOP XR4, so 4 bytes of space must be reserved.1:0E80H F18E POP EA 1:0E82H 16FF ADD R6, #-1H 1:0E84H C8F4 BNE .l_010 1:0E86H F42E POP XR4 1:0E88H F28E POP PC(Explanation: After the
POP EAwhich pops the next 2 bytes after the call, the function subtractsR6by0x01, thus settings theZ flag. IfR6 = 0x01, after subtracting, theZ flagwill equals to1, which avoids theBNE .l_010which is triggered whenZ = 0)
Padding values to EA
While there’s no direct ADD EA, ERn instruction in ROM, you can call from 1:973A to 1:9742 to achieve a 2 bytes pad per instruction.
1:973AH 9053 ST ER0, [EA+]
1:973CH 9253 ST ER2, [EA+]
1:973EH 9253 ST ER2, [EA+]
1:9740H 9253 ST ER2, [EA+]
1:9742H 9053 ST ER0, [EA+]
1:9744H FE1F RT
This function repeatedly executes [EA+], for each 9n53 it increases EA by 0x02, ultimately padding EA by 0x0A bytes. These set of instructions will allow you to adjust EA by a specific even value from 0x02 to 0x0A. Although the ST instruction alters the table, the content at EA address remains unaffected.
Alternatively, you can call from 1:C654 to 1:C662 to pad an odd number of bytes to EA
1:C654H 9D51 ST R13, [EA+]
1:C656H 9C51 ST R12, [EA+]
1:C658H 9B51 ST R11, [EA+]
1:C65AH 9A51 ST R10, [EA+]
1:C65CH 9951 ST R9, [EA+]
1:C65EH 9851 ST R8, [EA+]
1:C660H 9751 ST R7, [EA+]
1:C662H 9651 ST R6, [EA+]
1:C664H F28E POP PC
Reading Content at EA
Use 1:C64A to read the content at *[EA]:
1:C64AH 9652 L ER6, [EA+]
1:C64CH 9856 L QR8, [EA+]
1:C64EH F00CD000 LEA D000H
.l_00C:
1:C652H 9E53 ST ER14, [EA+]
1:C654H 9D51 ST R13, [EA+]
1:C656H 9C51 ST R12, [EA+]
1:C658H 9B51 ST R11, [EA+]
1:C65AH 9A51 ST R10, [EA+]
1:C65CH 9951 ST R9, [EA+]
1:C65EH 9851 ST R8, [EA+]
1:C660H 9751 ST R7, [EA+]
1:C662H 9651 ST R6, [EA+]
1:C664H F28E POP PC
This function reads the content:
- at
EA, stores intoER6 - at
EA + 0x2, stores intoER8 - at
EA + 0x4, stores intoER10 - at
EA + 0x6, stores intoER12 - at
EA + 0x8, stores intoER14
From 1:C64E onwards, the LEA D000H instruction gets executed and the function executes the rest of instructions at D000H. Since our program typically doesn’t operate at D000H, it won’t be affected.
Assign EA to ER0
Although the ROM does not have the MOV ER0, EA command, we can call 2:08E8 to assign EA to ER0 through a pair of PUSH and POP.
2:08E8H F1CE PUSH EA
2:08EAH F01E POP ER0
2:08ECH 9209FFFF ST R2, -1H[ER0]
2:08F0H FE1F RT
Afterward, the instruction ST R2, -1H[ER0] will alter the memory at [ER0] - 1, but the memory at [ER0] will remain unaffected.
This address is particularly useful in cases where multiple parameters need to be associated with a single keycode. If a keycode needs to link to several related parameters, additional corresponding parameter tables can be added after the main table.
Furthermore, this address can be called to backup the EA, since when you use 1:C64A to read the content at *[EA], EA is moved to D000, making it inaccessible for further reads. Once backed up, further reads can be performed using mentioned instructions.
Additionally, if EA needs to be compared with a specific value, 2:08E8 can be used to assign the value of EA to ER0, then ER0 can be used for comparison.
Similar Structures and Extended Applications
In the next post, I will discuss about 1:BCCC, which shares a similar structure. It sets EA to a fixed value, allowing you to verify whether EA has been altered. This can be applied in a pixel editor to check if the cursor position exceeds the screen boundaries.