From 1a2476d389e6b6a82e41583d9870827e656f5fee Mon Sep 17 00:00:00 2001 From: Maximilien Noal Date: Sat, 23 May 2026 14:13:43 +0200 Subject: [PATCH 1/2] chore: plan for reversing logo --- ...ReverseEngineeringAvaloniaPlayer.prompt.md | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/prompts/plan-logoReverseEngineeringAvaloniaPlayer.prompt.md diff --git a/.github/prompts/plan-logoReverseEngineeringAvaloniaPlayer.prompt.md b/.github/prompts/plan-logoReverseEngineeringAvaloniaPlayer.prompt.md new file mode 100644 index 0000000..dfdcfc6 --- /dev/null +++ b/.github/prompts/plan-logoReverseEngineeringAvaloniaPlayer.prompt.md @@ -0,0 +1,86 @@ +## Plan: Full LOGO Reverse Engineering And Independent Avalonia Player + +Deliver a complete understanding of LOGO.EXE and LOGO.HNM by combining static analysis, runtime tracing with Spice86 MCP using UseCodeOverride false, and deterministic data extraction using the UnHsq workflow, then build a standalone Avalonia 11.2.x MVVM player that reproduces playback behavior without emulator dependencies. For MCP, rely primarily on CFG CPU graph analysis (`read_cfg_cpu_graph`) and use other MCP tools as supporting evidence. + +**Steps** +1. Phase 1 - Ground truth setup and reproducibility +1.0 Execution precedence for this plan: complete Phase 1 and steps 2.1-2.3 before executing step 3.3 and later dynamic trace steps. +1.1 Verify exact inputs and hashes for LOGO.EXE and LOGO.HNM, then lock a reproducible baseline run profile with emulator overrides disabled (UseCodeOverride false). +1.2 Produce a run manifest that records CLI options, Spice86 version, MCP endpoint and ports, and capture locations for traces and screenshots. +1.3 Define canonical playback checkpoints (for example first frame, palette transition points, end frame, early keypress exit path) to compare all future outputs against. + +2. Phase 2 - Artifact preparation and static inventory (2.1-2.3 precede 3.3+, and 2.4 overlaps with Phase 3) +2.1 Decompress LOGO.HNM with the existing UnHsq tool workflow and generate LOGO.UNHSQ as the primary offline artifact. +2.2 Build binary maps for both LOGO.HNM and LOGO.UNHSQ: offsets, repeating patterns, candidate headers, candidate frame boundaries, and entropy regions. +2.3 Create a function inventory from existing dump files and generated code references: startup path, file I/O handlers, decompression path, VGA palette writes, frame blit path, and keyboard polling path. +2.4 Tag unknown functions by role hypotheses and confidence levels so dynamic tracing can explicitly confirm or reject each hypothesis. + +3. Phase 3 - Dynamic behavior tracing via Spice86 MCP with no override code (starts after 2.1-2.3; overlaps only with 2.4) +3.0 MCP priority: use CFG CPU graph (`read_cfg_cpu_graph`) as the primary discovery and control-flow truth source; treat disassembly, memory reads, and screenshots as secondary validation signals. +3.1 Start Spice86 MCP and run LOGO.EXE with UseCodeOverride false to observe original behavior only. +3.1.1 Capture CFG snapshots over time (startup, first decode loop, mid-playback, termination) and diff them to identify newly activated nodes/edges. +3.1.2 Cluster CFG nodes into behavior regions (file I/O, decode, palette, blit, input polling) before deep-diving with any other MCP tool. +3.2 Capture baseline CPU and DOS state at entry, before file open, during each file read, during decode, and during render loops. +3.3 Add execution breakpoints around known hot addresses from dump files and around interrupt handlers (INT 21h and INT 10h paths) to capture call chronology. +3.4 At each read/decode/render step, use CFG-selected hotspots first, then snapshot memory ranges used as source and destination buffers; correlate with file offsets to reconstruct data flow. +3.5 Capture VGA DAC palette state and periodic screenshots across timeline checkpoints; store paired evidence with cycle counts. +3.6 Build a chronological event ledger: read chunk, decode call, palette load, frame presentation, keyboard check, loop control decision. + +4. Phase 4 - Reconstruction and complete specification (depends on 2 and 3) +4.1 Produce an executable behavior spec for LOGO.EXE: startup, resource open, decode loop, render loop, input handling, and termination state machine. +4.2 Produce a binary format spec for LOGO.HNM and LOGO.UNHSQ: header schema, chunk types, frame payload encoding, palette encoding, timing semantics, and termination markers. +4.3 Resolve all previously unknown high-impact functions by mapping each to a named responsibility and documented side effects. +4.4 Validate spec completeness by replaying full animation from spec-derived parser outputs and confirming all playback-critical bytes are explained; any non-critical unknown regions are explicitly documented. + +5. Phase 5 - Standalone player architecture and implementation plan (depends on 4) +5.1 Create a separate desktop app architecture with clear layers: format parser, decode pipeline, playback engine, and Avalonia MVVM presentation. +5.2 Define domain models: container metadata, frame descriptor, palette command stream, decoded frame surface, and playback cursor/timing model. +5.3 Implement parser and decoder as pure library components with deterministic outputs and no UI dependencies. +5.4 Implement playback engine that reproduces original sequencing rules: frame cadence, palette transition timing, key interrupt semantics, and end-of-stream behavior. +5.5 Build Avalonia 11.2.x MVVM app shell with controls for open/play/pause/stop/seek/frame-step, timeline state, and diagnostics panel for frame and palette indices. +5.6 Integrate pixel pipeline for indexed-color rendering and palette application to display frame-accurate output. + +6. Phase 6 - Parity validation and hardening (depends on 5) +6.1 Create golden baseline set from Spice86 run: selected screenshots, palette snapshots, and event timestamps. +6.2 Compare standalone output against baseline at checkpoints: frame content parity (pixel diff = 0 at checkpoint frames), palette parity (byte-exact DAC values), ordering parity (identical checkpoint sequence), and total playback duration tolerance (within +/-50 ms). +6.3 Test edge paths: malformed inputs, truncated file, unexpected markers, and user interruption behavior. +6.4 Document residual differences if any and classify as intentional, acceptable variance, or bug. + +7. Phase 7 - Documentation and handoff (depends on 6) +7.1 Write reverse engineering dossier with evidence links from each behavioral claim to static and dynamic proof. +7.2 Write format specification document for LOGO.HNM and LOGO.UNHSQ suitable for independent implementation. +7.3 Write developer guide for the standalone player architecture, extension points, and verification workflow. +7.4 Publish a repeatable validation checklist so future refactors preserve parity with original LOGO.EXE behavior. + +**Relevant files** +- c:/Users/noalm/source/repos/LOGO/README.md — baseline behavior narrative and run entry guidance. +- c:/Users/noalm/source/repos/LOGO/Program.cs — current Spice86 launch entry point and checksum guard. +- c:/Users/noalm/source/repos/LOGO/CodeGeneratorConfig.json — known injection and event-check anchor addresses useful for trace waypoints. +- c:/Users/noalm/source/repos/LOGO/GeneratedCode.cs — current manually lifted behavior reference for naming and semantic hints. +- c:/Users/noalm/source/repos/LOGO/GeneratedCode_OriginalAsm.cs — closest generated representation of original executable control flow. +- c:/Users/noalm/source/repos/LOGO/spice86dumpExecutionFlow.json — observed execution graph and path coverage hints. +- c:/Users/noalm/source/repos/LOGO/spice86dumpFunctionsDetails.txt — callers/callees/return contract map for prioritizing investigation. +- c:/Users/noalm/source/repos/LOGO/spice86dumpGhidraSymbols.txt — symbol naming anchors for mapping unknown functions. +- c:/Users/noalm/source/repos/LOGO/tools/UnHsq/Program.cs — authoritative HSQ decompression implementation already used in this repo. +- c:/Users/noalm/source/repos/LOGO/.github/skills/dune-dat-and-hsq-decompression/SKILL.md — required decompression workflow and reporting discipline. +- c:/Users/noalm/source/repos/LOGO/.github/skills/spice86-mcp/SKILL.md — MCP handshake and tool usage requirements. +- c:/Users/noalm/source/repos/LOGO/.github/instructions/reverse-engineering.instructions.md — evidence-first and contract-preservation rules. +- Primary MCP tool emphasis: `read_cfg_cpu_graph` for coverage-first analysis, then `read_disassembly` / `read_memory` for node-level validation. + +**Verification** +1. Reproducibility check: two independent runs with UseCodeOverride false produce the same event ledger structure and equivalent frame checkpoint outputs. +2. Format coverage check: parser can account for all bytes in LOGO.HNM and LOGO.UNHSQ relevant to playback, with unknown regions explicitly documented. +3. Behavioral parity check: standalone player matches checkpoint screenshots and palette states from Spice86 with checkpoint pixel diff = 0, byte-exact palette values, and total playback duration within +/-50 ms. +4. Control-flow completeness check: all high-frequency runtime functions in dump data have assigned semantic roles and evidence citations. +5. Robustness check: corrupted and truncated input cases fail gracefully with deterministic errors. + +**Decisions** +- Included scope: full reverse-engineering of playback path and complete standalone playback implementation for logo.hnm. +- Included scope: no dependency on Spice86 or override code at runtime for the final player. +- Excluded scope: adding new emulator features inside Spice86 itself. +- Excluded scope: broad Dune engine reverse engineering outside LOGO.EXE and logo.hnm playback concerns. + +**Further Considerations** +1. Timing fidelity default: dual mode with switchable timing profile; default runtime mode is strict original cadence emulation. +2. Packaging default: produce both self-contained and framework-dependent builds using a CI matrix. +3. Decoder strategy default: strict by default with an explicit optional recovery mode for damaged files. From a3af142f35b15c46205f9426aad2e94e80d58848 Mon Sep 17 00:00:00 2001 From: Maximilien Noal Date: Sat, 23 May 2026 14:16:08 +0200 Subject: [PATCH 2/2] chore: LOGO.EXE and HNM files commit --- LOGO.EXE | Bin 0 -> 5390 bytes LOGO.HNM | Bin 0 -> 18417 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 LOGO.EXE create mode 100644 LOGO.HNM diff --git a/LOGO.EXE b/LOGO.EXE new file mode 100644 index 0000000000000000000000000000000000000000..3f1598e4b5bf898fa0d203952c2bc6a76bfdc250 GIT binary patch literal 5390 zcmeHLe{2)i9siy$J_p+g37uHmb+NOiUL~^|KGdG->P&+#kS zZT_u_svRR+v%m`_!NLI+tS?z`(JFj#^}AiQfRE~VwypinI8zyPH{2@nfqsGkRGK21pGb$Gx4TQ z-5$|B8Cj6~)?@&z^cg=KNup$EDl|nWk0j}N(NeRM@xn2ekCXc0x12A2Le;Q)mslXn z!bCbF%jeyuV>U&xuQxKI6VZL+VynN5a;trZ*P}r3M7c3aX#U0CNJ2Bwg3ZFM1$Kn_ zQ0*t}4>kTvm635GziJjP#J=jMsN%cEKt@9o?pV$C_odPwh|d+~X3m#U1N))h0)0n@ z9aD-nWn`vxkfVb>i}DlMkVwr%5pmh-XQC$((%De`!< z{Yj|7D%gz=(zj>C0wy=RT*=c7PzvxW`OEa?5liI7}_J4wNxM)i+dNYei zqz_Y_hJxqJo~|d?ylO_d9^YlC=TX zEOfmiALO1_!G8gMEAFCj1Ke?%V{ z#_e=T^>!h*4?AS;cXWxRn*UQm#Me_e2k?)9r$j$PbU-Y)u(1vtBZ@wh=tBYZDF4!| z9~3-|--14LwGV~UYsV*_XaIC#-7oInMnX7qw-3^TE}!G8)oo-Epii~30`T_ z^u&lAJQuoUeZq}~CIn`QW1rEX(DSt0Bhmhk+i$cFx&BpN9`AA8K-00m^*IBXT~ z?YC9JO%!%J14vYl3ZXf4Mc3dMRv52{a&d@zL!U|FU*<-+8_uxvGWvpkDBs>SdBHqP zLu_2E+ld~XKLm7AzR_UlHXSgy*`vzJNTOknn6z*{vgb$}8?&U(-~D-rq(T;v3vvKP zoF3|G%rc6bFIXSp4?yhb;CzS2O~^`hlu4t^+| zRO1KIF402;*YZs9-QZgCQZ3$WZLpARB<*C8=m$Hx=GhV~)LHX32U=FO`;?IF^2H)3 zkm&G~1`9hyuY&x8u&Pm5W!WwSWV+t1Tw%$eIv=Q`$r;J!I4lZesk3+R^&JJ#F z)ET%_WI$Wqcta!c-?5wY0$mYFxJ`OqWy;~{-pFIJb=^@!Zl-^ekWE#EQU<58(xm^T z+O*gDxaiC3-q<8c^{e6Jwnu;Ff5P41#`?3tZ7R1irivEDI@P-MR*NamwmozwG=^em zUhd_GzV*VjKhR;v7(_xb_G|TCBzoUk#xM8kaO64?8&0616Cw$LG5xAVQS_x)4=$J_ z^DakSdtmQ={Q;}B*{W}D(%YH^>%If}gTH7zkfSczDvLMiw^i*(OY}|6y#8Rb(6qk@ YU;|lF%k$;+F>`INuB=|B|6lt50EYTiZ~y=R literal 0 HcmV?d00001 diff --git a/LOGO.HNM b/LOGO.HNM new file mode 100644 index 0000000000000000000000000000000000000000..43d9d4ac8915c205c1cc9065cd3d437eeb1cf090 GIT binary patch literal 18417 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 L|DC}9o9q7{IShx> literal 0 HcmV?d00001