while (FMC_Read(BOOTLOADER_MAGIC_ADDR) != BOOTLOADER_MAGIC)
;
FMC_Close();
}
void main(void) {
SYS_UnlockReg();
SYS_Init();
SYS_Bootloader_Check();
// ...
}
```
In our custom APROM, defining `ENABLE_BOOTLOADER_CHECK` (`tasoller.h`) will perform this check. Our custom bootloader (not included in this repository) uses a custom scatter file to include this magic value, safely asserting that the code does not overflow into these bytes.
### Firmware modification
The following bytes in the uploaded firmware are modified before writing them to flash:
| Offset | XOR value |
| ------ | --------- |
| `C0` | `FF` |
| `C1` | `FF` |
| `C2` | `FF` |
| `CC` | `2F` |
These values are specifically chosen as the end of the vector table. The code at this offset is part of ARMC5's initialisation code, with `__main` being the entrypoint jumped to after register initialisation.
```asm
__main:
_main_stk:
LDR R0, =__initial_sp ; Offset C0~C1
MOV SP, R0 ; Offset C2~C3
_main_scatterload
BL __scatterload_rt2 ; Offset C4~C7
__main_after_scatterload:
_main_clock:
_main_cpp_init:
_main_init:
LDR R0, =main ; Offset C8~C9
BR R0 ; Offset CA~CB
DCD main ; Offset CC~CF, auto-generated by =main
```
Note that our custom firmware is compiled using GCC rather than ARMC5. Our linkerscript is not configured to place any initialisation code after the vectors, and as such this protection mechanism would be likely to cause random crashes rather than a complete inability to execute the firmware.
Rather than implement this as a post-processing step after compiling firmware, our linkerscript is configured to place the compile timestamp within this region, and we account for the DRM modifications when comparing timestamps.
The disassembly at these offsets is the same as with the host APROM. Likewise, we opt to bypass this protection by placing the compile timestamp in this region.