Source Files
The reverse engineering effort produced several source files that document the RS-UV3 V2.4a firmware internals. All files are available in the project repository.
Primary Sources
Section titled “Primary Sources”Annotated Assembly
Section titled “Annotated Assembly”src/rs-uv3-v2.4.asm — 19,053 lines
The complete firmware in PIC18 assembly, annotated for readability:
- 97 named functions with descriptive labels
- 34 section headers dividing the code into logical regions
- 71 RAM variable definitions with meaningful names
- 247 ASCII character annotations on XOR comparisons
- Byte-identical to original — verified via
make verify
; Example: Command dispatcher entrycmd_dispatch_a: ; address: 0x0042b0 movff 0x59a, FSR0L ; Load serial buffer pointer movff 0x59b, FSR0H movf POSTINC0, W ; Read first command character xorlw 0x50 ; 'P' btfss STATUS, Z bra not_P_command ; ... handle P commands (PD, PW) ...Readable C Port
Section titled “Readable C Port”src/rs-uv3-v2.4-readable.c — 12,020 lines
Ghidra’s decompiled C output, cleaned up for human readability:
- 89 function renames from
FUN_CODE_XXXXXXto descriptive names - 130 variable renames from
DAT_DATA_XXXXto register names - 34 section headers matching the assembly
- 35 algorithm annotations explaining complex routines
// Example: Frequency setting functionvoid set_frequency(void) { // Calculate PLL divider from frequency in kHz math_op_a = freq_main; math_op_b = 0x00001388; // 5000 (scaling factor) math_multiply_32();
// Write to RDA1846S via I2C i2c_addr = 0x29; // RX frequency register i2c_data = math_result >> 16; i2c_write_word(); // ...}Analysis Tools
Section titled “Analysis Tools”annotate.py
Section titled “annotate.py”analysis/annotate.py — 250 lines
Transforms raw gpdasm output into annotated assembly:
- Applies the code/data boundary (code ends at
0x8B38) - Replaces misinterpreted data bytes with
dbdirectives - Adds ASCII character annotations to XOR/SUB instructions
- Inserts function labels from
label-mapping.py
# Key function: annotate_instructiondef annotate_instruction(line, address): # Add ASCII annotation to xorlw/sublw if 'xorlw' in line or 'sublw' in line: match = re.search(r'0x([0-9a-f]{2})', line) if match: value = int(match.group(1), 16) if 0x20 <= value <= 0x7E: return f"{line.rstrip():<52}; '{chr(value)}'" return linec-port.py
Section titled “c-port.py”analysis/c-port.py — 690 lines
Transforms Ghidra’s decompiled C into readable code:
- Renames functions using mappings from
label-mapping.py - Renames data references to meaningful variable names
- Handles thunk functions (trampolines to real functions)
- Adds section headers and documentation comments
# Thunk handlingTHUNK_NAMES = { "thunk_FUN_CODE_001344": "thunk_parse_number", "thunk_FUN_CODE_002aee": "thunk_set_baud_rate", # ...}label-mapping.py
Section titled “label-mapping.py”analysis/label-mapping.py — 241 lines
Central database of all named symbols:
- Function names — 97 entries with addresses and descriptions
- Thunk mappings — 4 trampolines to their targets
- RAM variables — 71 data addresses with names
- Section boundaries — 34 code region markers
FUNCTION_NAMES = { "function_001": ("reset_vector", "0x000000", "Reset vector - jumps to init"), "function_002": ("high_isr", "0x000008", "High-priority interrupt"), "function_003": ("low_isr", "0x000018", "Low-priority interrupt"), "function_004": ("init_radio", "0x000400", "Main init and event loop"), "function_005": ("cmd_dispatch_a", "0x0042b0", "Dispatcher A: PD,PW,RC,RR,RS,ST,CP,FD,BL"), "function_006": ("cmd_dispatch_b", "0x005972", "Dispatcher B: AF through X1"), # ... 91 more entries}Build System
Section titled “Build System”Makefile
Section titled “Makefile”src/Makefile
Builds and verifies the assembly:
TARGET = rs-uv3-v2.4.hexORIGBIN = ../downloads/firmware/RS-UV3_V2-4.bin
build: gpasm -p p18f46j11 -o $(TARGET) rs-uv3-v2.4.asm
verify: build @objcopy -I ihex -O binary $(TARGET) /tmp/rs-uv3-verify.bin @cmp $(ORIGBIN) /tmp/rs-uv3-verify.bin && \ echo "MATCH: Output identical to original firmware" || \ (echo "MISMATCH: Differences found"; exit 1)
clean: rm -f $(TARGET) *.lst *.codRun make verify to confirm the assembly produces byte-identical output.
Supporting Files
Section titled “Supporting Files”Analysis Documents
Section titled “Analysis Documents”| File | Description |
|---|---|
analysis/command-map.md | Complete command dispatch documentation |
analysis/communicator-analysis.md | Windows control software analysis |
analysis/collaboration-post.md | Reverse engineering project summary |
Ghidra Project
Section titled “Ghidra Project”| Path | Contents |
|---|---|
ghidra-project/RS-UV3-firmware.gpr | Ghidra project file |
ghidra-project/RS-UV3-firmware.rep/ | Analysis database |
ghidra-project/export/ | Exported listings and decompilation |
Original Firmware
Section titled “Original Firmware”| File | Description |
|---|---|
downloads/firmware/RS-UV3_V2-4.hex | Original Intel HEX |
downloads/firmware/RS-UV3_V2-4.bin | Raw binary (for verification) |
Verification
Section titled “Verification”The annotated assembly is verified byte-identical to the original firmware after every modification:
$ cd src && make verifygpasm -p p18f46j11 -o rs-uv3-v2.4.hex rs-uv3-v2.4.asmMATCH: Output identical to original firmwareLabels, comments, and section headers don’t generate machine code — they’re stripped during assembly. This allows aggressive annotation without any risk of changing the firmware behavior.
Contributing
Section titled “Contributing”To add annotations or improve the documentation:
- Edit
analysis/label-mapping.pyto add/rename symbols - Run
python analysis/annotate.pyto regenerate assembly - Run
make verifyto confirm no byte changes - Run
python analysis/c-port.pyto regenerate C port
The verification step is critical — it ensures your changes don’t accidentally affect the assembled output.