This directory contains analysis of the FNIRSI 2C53T stock firmware, performed via Ghidra decompilation for the purpose of hardware interoperability.
This reverse engineering was conducted for interoperability purposes — to understand the hardware interfaces (FPGA protocol, ADC configuration, peripheral initialization, button matrix, etc.) needed to write compatible replacement firmware.
- US: Reverse engineering for interoperability is protected under fair use (Sega v. Accolade, 9th Cir. 1992; Sony v. Connectix, 9th Cir. 2000)
- EU: Software Directive (2009/24/EC), Article 6 explicitly permits decompilation for interoperability
No FNIRSI source code is redistributed in this repository. The raw Ghidra decompilation output is excluded from version control. What is published here is original analytical work: hardware documentation, protocol specifications, annotated code fragments, and architectural analysis derived from studying the binary.
The replacement firmware in firmware/src/ is a clean-room implementation that does not copy FNIRSI code.
ARCHITECTURE.md— System architecture overview (start here)HARDWARE_PINOUT.md— Complete MCU pin assignmentsFPGA_PROTOCOL_COMPLETE.md— Full FPGA command/data protocol specificationCALIBRATION.md— ADC calibration data format and pipelineHARDWARE_TESTS.md— Hardware probing procedures and resultsCOVERAGE.md— RE coverage tracker (309 functions catalogued)
analysis_v120/— Detailed analysis of the latest firmware versionFPGA_TASK_ANALYSIS.md— FPGA task state machine and sub-functionsFPGA_BOOT_SEQUENCE.md— 53-step boot initialization sequencefpga_task_annotated.c— Annotated FPGA task with commentarybutton_map_confirmed.md— Hardware-confirmed button matrix mappingfunction_names.md— Complete function naming inventoryfunction_map_complete.txt— 309-entry function address map- Recent scope/dispatch notes:
right_panel_handoff_directness_2026_04_08.md— shows thatE1C = 1still looks derived from a priorE1C = 2commit in the downloaded app, and promotes the visible state-5/ state-2shims as the cleaner search surface above the right-panel clusterright_panel_sub2_5_vs_2_split_2026_04_08.md— compares thesub2 = 5andsub2 = 2packed-selector siblings and argues that5is the staged-detail commit bridge while2is the coarse editor entry/exit familyright_panel_internal_branch_choice_2026_04_08.md— explains the downstreamsub2 = 5versussub2 = 2split inside visible state-5, with a later correction that the single-selection versus mass-toggle choice itself appears to come from separate sibling event ownersright_panel_stage_helper_convergence_2026_04_08.md— shows that the single-selection and mass-toggle stage helpers both appear to feed the samesub2 = 5commit family, pushing the remaining distinction back to their entry eventsright_panel_stage_entry_event_split_2026_04_08.md— promotes raw0x0800A120and0x0800A2F8to direct sibling event owners, parallel to adjust-prev / adjust-next, so the remaining unresolved split is higher event routing into those ownersright_panel_scope_choreography_path_2026_04_08.md— compact end-to-end runtime sequence for the visible state-5right-panel family: editor posture, staged detail,E1C = 2 -> 0x2Acommit, packedsub2 = 5 / 2consume-collapses, and the current queue/raw-word gapslogical_event_dispatcher_hypothesis_2026_04_08.md— strongest current two-stage model: button scan produces logical event IDs,key_taskgates and dispatches those IDs through an opaque surface, and the selected semantic owner then branches again on visible runtime statemissing_image_state_hypothesis_ranked_2026_04_08.md— ranks missing MCU-side high-flash data and non-app board state above wrong scope sequencing as the best explanation for the current blocker, and prioritizes W25Q128 boot-bus capture plus second-unit flash comparisonright_panel_event_cluster_2026_04_08.md— unifies the right-panel event peers into one staged cluster: coarseE1Dedits, single-bit staging, mass-toggle staging,E1C = 2 -> 0x2Acommit, and later controller consumptionghidra_project_data_target_shift_2026_04_08.md— proves the current Ghidra project imported the app flat at0x08000000, so absolute flash literals from code must be checked atproject_addr = runtime_literal - 0x4000to land on the right byteskey_task_dispatch_surface_contradiction_2026_04_08.md— shows that rawkey_taskstill doesldr -> blx, but the literal0x08046544/48region looks exidx-like rather than handler-like, sharpening the event-dispatch contradiction in the downloaded appright_panel_event_owner_map_2026_04_08.md— promotes0x080041F8 / 0x080047CCto the stock adjust-prev / adjust-next owner pair, pairs them with the direct stage-side owners at raw0x0800A120 / 0x0800A2F8, and now treats0x08046544/48as an unresolved dispatch surface rather than a confirmed plain handler tableright_panel_event_path_2026_04_08.md— action-level model for the right-panel scope family: state-5entry,E1Dcursor edits,E1Astaged-detail latch, and theE1C = 2 -> 0x2Acommit handoffpanel_subview_action_meaning_2026_04_08.md— interpretsE1C = 1as the coarse right-panel/timebase handoff andE1C = 2as the staged bitmap/toggle handoff, with a caveat that exact UI labels still depend on missing high-flash resourcespanel_subview_writer_bridge_2026_04_08.md— traces concrete runtime writers for+0xE1Cand shows howE1C = 1versusE1C = 2feed the state-5/ state-6controller cases through selector0x2Ascope_runtime_controller_case_map_2026_04_08.md— decodes the0x08004D60jump-table cases and showsstate 5as the stable right-panel editor,state 6as its handoff phase, andstate 7as a transient shim abovestate 4state9_preview_posture_case_map_2026_04_08.md— corrects the later posture-family owner:state 8is only a cleanup shim, while visiblestate 9is the real staged preview/posture owner with anF69phase gate and anF6Alow-nibble subtablestate9_entry_bridges_and_preview_subcases_2026_04_08.md— grounds the concrete visible-state-2 -> state-9promotion helpers and splits theF6A = 3 / 4preview side into heavy rebuild, normalize/apply, and tiny-toggle subcasesruntime_owner_cluster_extension_2026_04_08.md— extends the newer sibling-owner model to include the adjacentsub2 = 5staged-detail family and the packed-state normalizer at raw0x0800A834scope_top_level_gate_writers_2026_04_08.md— promotes0x08004D60to the best current broad runtime owner above thestate 5 / 2 / 1families, with0x08006840reclassified as a reset/entry normalizerruntime_scope_bank_reentry_2026_04_08.md— raw call-site correction showing the shared bank emitter at0x0800B908is re-entered at runtime after staging scope presetsscope_runtime_family_gate_map_2026_04_08.md— maps the three runtime preset families back to their visible top-level gate states:state 5timebase/right-panel,state 2acquisition,state 1mixed trigger-likescope_runtime_preset_action_map_2026_04_08.md— first action-level split of the runtime preset families:sub2=2timebase-side,sub2=3acquisition-side,sub2=4still mixed/trigger-likestate2_promotion_owner_family_map_2026_04_08.md— compares the three visible-state-2 -> state-9promotion owners as sibling runtime families, shows their distinct visible-state-1setup sides, and argues that the real chooser is probably one layer above themscope_owner_ui_action_map_2026_04_08.md— first-pass mapping from the broad owner at0x08003148to likely acquisition, timebase, trigger-like preview, and packed-state commit familiesscope_owner_start_map_2026_04_08.md— corrected owner starts, with0x08003148as the broad packed-scope owner and0x08006418 / 0x08006548as narrower transition shimsmixed_scope_handler_bridge_2026_04_08.md—FUN_08002FE8as the current best bridge betweenDAT_20001060, packed scope state, and selector-bank emissionscope_composite_state_presets_2026_04_08.md— raw 32-bit and 16-bit presets across0xF68..0xF6B, including the9/1/3/0,9/1/2/0, and9/1/4/0transition layoutsscope_preset_owner_families_2026_04_08.md— groups the new preset helpers into one broad submenu owner plus narrower9 -> 2transition families for selector targets2 / 3 / 4 / 5dynamic_scope_word_builder_2026_04_08.md— dynamic0x0500 | low_bytebuilder at0x08006120selector_writer_audit_2026_04_08.md— active writer audit forDAT_20001025 / DAT_2000102Escope_selector_bypass_2026_04_08.md— why the visible scope FSM may bypass the selector familyenclosing_helper_cluster_2026_04_08.md— adjacent helper cluster at0x08006060 / 0x08006120 / 0x080062F8 / 0x08006418mode_selector_writer_map_2026_04_08.md— concrete writer map forDAT_20001060and its restore / transient statesscope_low_byte_2_path_2026_04_08.md— why low byte2is the strongest current scope-active statescope_state_commit_bridge_2026_04_08.md— exact-byte xrefs plus the corrected0x08015848redraw-ladder interpretationscope_cluster_control_bytes_2026_04_08.md—+0xE10 / +0xE11 / +0xE1Boverlay/list control bytes around the right-panel ladderpacked_scope_state_writers_2026_04_08.md— concrete runtime writers for0xF69..0xF6Bin the downloaded appripcord_feedback_bridge_2026_04_08.md— cross-check of theripcordnotes against hardware-confirmed pins and the newer queue-split / packed-state model, with adopt-versus-caution guidancescope_experiment_priorities_after_ripcord_2026_04_08.md— turns the ripcord cross-check into concrete bench priorities: instrument packed state, stage runtime posture first, then test grouped selector familiesmcu_fpga_gap_decisive_bench_plan_2026_04_08.md— bounded four-experiment bench plan to separate selector-vs-wire errors, missing runtime choreography, and missing stock image/state using the current CDC shell and diagnosticspayload_anchor_sweep_results_2026_04_08.md— live Experiment 3 results showing no payload-sensitive change on0x02A0,0x0501, or0x0503, which triggers the current command-side hard stop and shifts priority toward missing stock image/statew25q128_stock_boot_sniff_plan_2026_04_08.md— board-specific logic-analyzer plan for sniffing the SPI2 flash bus during a stock-app boot attempt, with hookup, safe flash/restore loop, and interpretation criteriaunicorn_stock_flash_trace_first_pass_2026_04_08.md— software-only fallback to the W25Q128 boot sniff: first Unicorn pass now reaches the stock0x90flash-ID transaction against the real dump, but still stalls before real0x03block readsunicorn_stock_flash_trace_second_pass_2026_04_08.md— second Unicorn pass pushesmaster_init()through LCD, ADC, SPI3, and passive-key waits, shows that boot still only reaches the SPI20x90ID probe in this harness, and proves directfatfs_init()currently fails on missing allocator bootstrap state at0x20001070unicorn_stock_flash_trace_third_pass_2026_04_08.md— ablation pass on the synthetic high-flash descriptor table: roots and directory roots are the real gates for directfatfs_init(), while both2:/LOGOand0x080BC841only make small traversal differences once those root descriptors are presentvolume1_root_directory_rewrite_path_2026_04_08.md— traces the heavy SPI2 write family back to the boot-time2:/root population sequence, shows it is a deterministic rewrite of the Volume 1 FAT root directory at0x207000..0x207FFF, and explains why it is not evidence of external-flash corruptionfatfs_boot_followon_non_gate_2026_04_08.md— shows thatfatfs_init()is unconditionally called and not checked at its call site, narrowsDAT_20001066to USB MSC bank selection, and promotes the laterFUN_08034878(0x080BC18B)right-panel enumerator as the first strong follow-on consumer of the same descriptor familyoverlay_seed_first_ui_consumer_2026_04_08.md— identifies the right-panel redraw ladder as the first clearly LCD-visible consumer of the overlay state seeded byFUN_08034878(0x080BC18B), which suggests the remaining unknowns are increasingly descriptor-data shaped rather than hidden-branch shapedpanel_overlay_state_writer_gap_2026_04_08.md— historical boundary note for the earlier+0xE10writer hunt; now superseded in part by the later recovered direct=1writerpanel_overlay_state_writer_recovered_2026_04_09.md— raw-store sweep correction proving the downloaded app does contain a direct+0xE10 = 1writer at project0x08006810, tied to the+0xF68 == 2handoff helper just ahead of the broader0x08006840normalizer clusteroverlay_handoff_normalizer_cluster_2026_04_09.md— unifies the adjacent mixed0x02A0 -> ... -> 0x0503gate, the recovered+0xE10 = 1writer, and the broader0x08006840reset/entry normalizer into one tighter transition clustermixed_sub2_4_to_overlay_bridge_2026_04_09.md— shows that the mixedsub2 = 4runtime family, the0x02A0 -> 0x0503gate, the recovered+0xE10 = 1writer, and the0x08006840normalizer all live in one contiguous raw owner blockmixed_cluster_parent_boundary_2026_04_09.md— boundary check showing that the next raw prologue at0x08006954is a different heavy hardware/acquisition owner rather than a linear parent of the compact mixed cluster, which shifts the next search toward indirect chooser/event routingbc859_descriptor_reconstruction_2026_04_08.md— chained Unicorn result showing that reconstructing0x080BC859as2:/Screenshot simple file/%d.binis enough to unlock the later metadata consumer on top of mountedfatfs_init()state, while the same path stays flat without that descriptor familybc859_metadata_lifecycle_2026_04_09.md— persistent-write follow-up proving the%d.binfamily is the real runtime gate: without itFUN_08035ED4()returns2, with it the function returns0, the mutated FAT image now contains a concrete zero-filledScreenshot simple file/1.BINartifact of size0x25A + 5, and the neighboring BMP-side0x080BCAD2 / 0x080BCAE5strings remain insufficient by themselvesbcad2_preview_path_probe_2026_04_09.md— follow-up on the BMP-side preview family showing thatFUN_08036084()needs a concrete preformatted BMP path, not just the descriptor strings, and that with2:/Screenshot file/1.bmpseeded it gets far enough to create a zero-sizeScreenshot file/1.BMPentry before the preview/render side becomes the remaining blockerbcad2_preview_completion_2026_04_09.md— completed BMP-side emulator closure: with a stage-scoped preview stub and loop fast-forwarding, theFUN_08036084()path now runs end-to-end and produces a validScreenshot file/1.BMPartifact (cluster=13,size=153654,BMheader), which shows the remaining BMP-side gap was emulator cost rather than another missing descriptor familybcad2_bc859_coupled_lifecycle_2026_04_09.md— chainedfatfs_init() -> FUN_08036084() -> FUN_08035ED4()run proving the BMP screenshot path and the%d.binmetadata path coexist in one emulated stock session, producing bothScreenshot file/1.BMPandScreenshot simple file/1.BINon the same mutated flash imageoverlay_artifact_owner_family_2026_04_09.md— unifies the solved BMP and%d.binhelpers into one right-panel overlay/runtime family:FUN_08034878()as the shared enumerator/repair bridge,FUN_0802EA08()as the screenshot-directory validator, and the runtime split between single-entry commit, single-slot rebuild, and bitmap/multi-slot rebuildright_panel_shim_case_map_2026_04_08.md— decodes the compact owner at0x08006548and showsstate 6 -> state 5entry,state 5 -> state 2reset, andstate 2 -> 9/1/2/0preset promotionstate6_entry_gate_2026_04_08.md— narrows visiblestate 6to anE1B != 0gate inside the broadstate 5controller, making it look like a transient armed-panel handoff rather than an independent submenuright_panel_resource_owner_map_2026_04_08.md— separates boot-time descriptor enumeration, runtime overlay list rebuild, and the laterpreview build -> metadata mountpath aroundFUN_08034878(),FUN_08036084(), andFUN_08035ED4()raw_app_base_offset_2026_04_08.md— explains the+0x4000address shift needed when comparing decompiled/Ghidra notes against the archived V1.2.0 app-slot binary, and uses that correction to reconcile the key-loop and right-panel redraw cluster with raw objdumpdisplay_mode_latch_map_2026_04_08.md— shows thatDAT_2000012Cis latched earlier in the broad scope controller family and only consumed later by the right-panel redraw/resource ownerdisplay_mode_posture_cluster_2026_04_08.md— expands the latch pair into a larger trigger-side posture cluster keyed byF6B,+0x23A,+0x35,+0x3C, and+0x40, with concrete branches for active-channel, trigger-edge, and run-mode controltrigger_posture_cluster_owner_map_2026_04_08.md— identifies the owning function for the later trigger/posture cluster as the broad runtime controller at raw0x08008D60/ decompiled0x08004D60
strings_with_addresses.txt— 290 firmware strings mapped to addressesvector_table.txt— ARM interrupt vector tabledispatch_table.txt— FPGA command dispatch tablegpio_access_map.txt— All GPIO register accesses
ghidra_scripts/— Java scripts for automated firmware analysis