I use these guidelines when compiling code. 99% of the time I will be patching a function, or jumping out of the end of a call tree into my own code so I can either use the registers that hightec assigns or I can change them out to not modify existing ones. I like to just use a new function per code segment i'm writing because ghidra makes it easy to find it.
You will need to disassemble (obviously) and then inspect the asm before patching.
#define CUSTOM_VARIABLE_ONE (*((volatile unsigned char *) 0xD0003F11))
#define CUSTOM_VARIABLE_TWO (*((volatile unsigned char *) 0xD0003F12))
#define CUSTOM_VARIABLE_THREE (*((volatile unsigned char *) 0xD0003F13))
void PROCESS_EXAMPLE() {
unsigned char adc = CUSTOM_VARIABLE_ONE ;
if(adc > CUSTOM_VARIABLE_TWO ) {
CUSTOM_VARIABLE_THREE = 2;
return;
} else {
CUSTOM_VARIABLE_THREE = 1;
return;
}
}
If you don't need to repeatedly call the address, just make a pointer to the address.
void COPY_PARAM_TO_RAM() {
volatile short *delay_ms = (int *)0xD0003F24;
volatile short *const_delay_ms = (int *)0x800A02F8;
*delay_ms = *const_delay_ms;
}
Also, ive found multiple functions that process 2D and 3D tables, returning varying variables ( chars, shorts, longs, signed, unsigned, etc). I created a faux function that calls to the map interpolation function that i want to use, and give it the same arguments so that i can call it and then just replace the call address when i decompile and patch it in. (hopefully that made sense, im just trying to interpolate my own 2d/3d tables so i use the built in functions).
Once I compile it and then disassemble it in Ghidra, i know the call address to the actual interpolation functions, i will replace those in my code asm and verify it is putting the correct variables in the registers before it calls.
unsigned char FUNC_PROCESS_2D_TABLE_8BIT(unsigned char AXIS_LENGTH, unsigned char MONITOR ,volatile unsigned char *AXIS_VALUES, volatile unsigned char *TABLE_VALUES)
{
return MONITOR; // Ignore this, it isnt actually used when i disassemble (only here as a "placeholder")
}
unsigned char FUNC_PROCESS_2D_TABLE(unsigned char MONITOR, volatile unsigned char *TABLE)
{
return MONITOR; // Ignore this, it isnt actually used when i disassemble (only here as a "placeholder")
}
unsigned char INTERPOLATE_ALL_TABLE_ETHANOL(volatile unsigned char * TABLE_ONE, volatile unsigned char * TABLE_TWO) {
FLEX_SWITCH_T1V = FUNC_PROCESS_2D_TABLE(ECT, TABLE_ONE);
FLEX_SWITCH_T2V = FUNC_PROCESS_2D_TABLE(ECT, TABLE_TWO);
return FUNC_PROCESS_2D_TABLE_8BIT(0x2, FLEX_ETH_PERC, &FLEX_SWITCH_AXIS, &FLEX_SWITCH_T1V);
}
As a final example, i'm working on adding turn signals to a SxS, something that didnt originally come with the function. See below for my code. It might not be 100% correct but it works and give me the least amount of asm.
void PROCESS_TURN_SIGNALS() {
volatile char *tmp_status = (int *)0xD0003F20;
volatile char *flash_pos = (int *)0xD0003F21;
volatile char *flash_count = (int *)0xD0003F22;
volatile short *delay_ms = (int *)0xD0003F24;
volatile short *timer = (int *)0xD0003F26;
volatile short *num_flashes = (int *)0xD0003F28;
volatile short *triple_timer = (int *)0xD0003F2C;
switch(TURN_ADC_IN >> 2) {
case 257 ... 512: // Left turn
*flash_pos = 1;
*flash_count = 0;
*tmp_status = (*tmp_status & ~0b00000011) | (*flash_pos & 0b00000011);
goto enable;
case 513 ... 768: // Right turn
*flash_pos = 2;
*flash_count = 0;
*tmp_status = (*tmp_status & ~0b00000011) | (*flash_pos & 0b00000011);
goto enable;
case 769 ... 1024: // Hazards
*flash_pos = 3;
*flash_count = 0;
*tmp_status = (*tmp_status & ~0b00000011) | (*flash_pos & 0b00000011);
goto enable;
default:
if(*flash_count > 0) {
goto enable;
}
if(*num_flashes == 0 && *triple_timer >= *timer) {
*flash_count = 5;
goto enable;
}
*tmp_status = 12; // sets outputs to high at the start of flashing.
*timer = 0;
*num_flashes = 0;
goto end;
}
enable:
if(*delay_ms >= *timer) {
*timer += 1;
goto end;
} else {
*timer = 0;
if(*flash_count != 0) *flash_count -= 1;
*tmp_status ^= (*flash_pos << 2);
*num_flashes++;
goto end;
}
end:
return;
}
In the end I will decompile my code, verify the flow looks correct and then patch it in, Make sure it isn't overwriting any registers that are needed, and then try it out. The above code needs 0 modifying before patching in and trying.
EDIT:
See below for some macros to work with bits. I have needed these quite a bit and just got the storebit working.
These will translate into:
JZ.T/JNZ.T - For getting a bit
ST.T - For storing a bit
#define GETBIT(var, bit) (((char)(var) >> (bit)) & 1)
#define STOREBIT(addr,bpos,b) __asm("st.t %0,%1,%2"::"i"(addr),"i"(bpos),"i"(b))
Examples:
#define NOTAREALADDRESS (*((volatile unsigned char *) 0xD00017AA))
/Getting a bit
char brakeStatus = GETBIT(NOTAREALADDRESS, 3);
//Setting a bit
STOREBIT(&NOTAREALADDRESS, 0x3, 0x1);