Task-first workspace ·
September 8 update: The original firmware now reads and clears DTCs on Android through direct Nano USB. This project retains the separate Chipsoft Pro / macOS goal; Nano success does not establish Chipsoft compatibility. Read the verified native results →
Goal: Read ECM identity and software information through Chipsoft Pro, first in a headless test and then from the Rust emulator GUI, with every request and response visible in the console.
Bench path: M4 Mac mini → USB-C data cable / adapter → Chipsoft Pro → powered OBD-II bench harness → ECM. For an NG 9-3 / Trionic 8 target, begin with 500 kbit/s high-speed CAN (P-bus). Confirm the actual ECM part number, model year, connector pinout, supply, ignition feeds, grounds and CAN termination before connecting the bench.
Evidence available: USB captures, corrected Chipsoft frame decoding, a macOS probe scaffold, and Roffe's GoCAN / gateway references. These are a starting point; Chipsoft-specific native channel setup and the emulator-to-adapter bridge still need implementation and hardware validation. The separate Nano Android path has now completed native DTC workflows.
Android approach: Kotlin / Compose owns USB permission, device attachment and bulk I/O; JNI connects it to the shared Rust protocol and emulator core. Use worker threads, framed streaming reads, bounded queues and cancellable deadlines. The Windows DLL is a protocol reference; it does not run on Android. Keep diagnostic traffic local; use the OpenSAAB API separately for vehicle enrichment.
Next acceptance test: Save one timestamped headless session showing adapter identity, a real ECU response and clean recovery when the adapter is unplugged. Repeat from the GUI for Chipsoft; Nano Android results are tracked separately above.
Related: Chipsoft reverse engineering · Android application · GoCAN gateway reference
/dashboard was still the 2026-06-05 overlay. Identity stays VIN 2017 YS3FD49YX41012017 — do not mix unmatched used CIM/SCL/ISM onto that identity.$27 0B is ECM/SAS on $0241. Golden prep knocks 8 SWCAN modules + ECM, not a 3-box IMMO loop.$27 01/02 on $7E0 via stgterm / Trionic Step.cs (algo 0x0339 etc.). SAS/IMMO is $27 0B/0C via dllsecurity (0367 / C4DC / 4EED). Live Trionic sessions show VIN, seed, and CEL because they read the bus — those values are not stored in the saved 1 MB flash. Re-scanned Desktop 2004_BCB_SAAB_93_ECM.bin (1,048,576 B, SHA256 555e14e4…105a2c): real T8 image, no VIN, no C4DC, no 4EED, no 714-byte SSA.$245 27 01, SRAM/flash. Isolated T8 is HS-CAN 500k only — no SWCAN pin-1 @ 33.3. You cannot knock golden SAS $0241 27 0B from a lone T8 tester. $241 on P-bus ≠ pin-1 SAS. Golden $27 0B needs CIM/SAS on SWCAN or a dual-homed gateway.$0541. We have to beat NRC 22. $0541 is the best observed scoreboard, not a GM law. No cold $27 0B success exists without that ready/latched state. Real arounds: consume a latch (lab only), use L1 (wrong door), or find the actual cause Bojer pointed at (GMLAN VIN / session flags) and ignore $0541 if $27 0B then answers.$27 0B. Closest written guidance: Tech2Win expects a full car so isolated ECM may fail; start with CIM on $241/$641 + Tester Present; do not unlock non-CIM modules yet. SCL is never mentioned. Paste the message if it exists.$0541 replay or a 3-box IMMO soak. Immediate missing artifact is still the clean ECM GMLAN VIN proof over $241/$641 RDBI $90, then CIM VIN with Tester Present, then I-bus, then seed/key. Use reports/firmware-re/bojer_alignment_audit_20260617.md and reports/firmware-re/j2534_python_pro_bringup_20260616.md.
$245/$645 cleanly. $245 27 01 returns 67 01 F6 31. The important surprise: $245 27 0B is not silent; it returns 7F 27 78 then 7F 27 22. That means the 0B door is visible through the HS-CAN route, but the security/session precondition is missing.$245 AA 01 01, $245 1A 90, $245 1A 9A, $245 1A 3F, and a minimal HS L1 walk did not satisfy the 0B condition. The walk still expanded the map: $245 27 01 → F631, $7E0 27 01 → 655D, and $7E1 27 01 → D2DC. SX can pull multiple HS L1 seeds; it just cannot cold-open the SAS/IMMO 0B gate with simple physical prep.00, while csdirect.py used 04, causing all 82 BLOCK filters to fail with ERR_0x6E. After patching BLOCK length to 00, clean-hot retests proved AE 00 → EE 00 alone was insufficient: fast golden replay still returned hard 7F 1A 22 and withheld $27 0B.wake → 1A 3F was about 3.0s vs Tech2Win's 11.2s, and AE → 1A 3F was about 63ms vs Tech2Win's 253ms. With -pregate-golden-timing, the hot bench reached AE 00 → EE 00, 1A 3F → 7F 1A 78, extra read-only wait → 5A 3F + VIN, then $27 0B → 7F 27 78 → 67 0B C4 DC. Local security_calc mapped seed C4DC to key 4EED. No 27 0C unlock was sent.timedgate_cold still reached AE 00 → EE 00, but $0541 stayed in the not-ready DPID/UUDT shape 01 7c 06 02 02 fd 04 fc and $0241 1A 3F → 7F 1A 22; $27 0B was withheld. EE-overlap, L1 clear, key-status prelude, $20 ReturnToNormal, and waiting 420 reads for $0541 ready all failed.$0241 AA 01 01 after the HS/SW functional FE 3E pair flips $0541 from 01 7d 06 02 02 fd 04 fc to ready 01 7e 01 01 00 fd 38 fc. Live AA pumping, multi-DPID AA, FE3E+AA pumping, paced read replay, 420-read waits, guarded post-Tech2Win latch checks, three guarded shim-derived ladder replays, Tech2Win open-prologue replay, and exact pre-wake bulk replay did not reproduce that state. Closing Tech2Win enough to free COM7 still returned hard 7F 1A 22; the favorable state was not just process-latched.tri_diff_gate_evidence_20260604.md aligns Tech2Win RAM, the golden USBPcap, failed cold USBPcap, and CSTech2Win shim logs. Golden and shim sessions move $0541 to * 01 01 00 * before AE 00; failed cold sessions stay at * 06 02 02 *. The visible golden bridge is $0101 FE 3E proto 0x0006, $0101 FE 3E proto 0x8007, then $0241 AA 01 01, but standalone FE3E+AA pumping and shim-ladder replay left $0541 pinned at 01 78 06 02 02 fd 04 fc.$0541 readiness once the 16-byte record is decoded as payload8 = data4 + tail[0:4]. analyze_0541_memdump.py reconstructed 73 $0541 rows: 9 not-ready 06 02 02, 5 ready 01 01 00, and 1 latched 00 00 00. That makes $0541 == * 01 01 00 * an independently confirmed Tech2Win RAM predicate, not just a pcap/shim logging artifact.exactprewrite3 replayed the golden bulk OUT stream from CONNECT through final pre-wake READ_MSG byte-for-byte: 1,775 commands, 996 filters, 684 reads, and 1,787/1,787 prewrite bulk OUT frames matched. It still reached AE 00 → EE 00, then hard-failed 1A 3F → 7F 1A 22; $0541 stayed not-ready and $27 0B/$27 0C were not sent.115200 8N1, sets 38400 8N1, toggles DTR/RTS, and runs the first two command-layer open cycles across real COM handle open/close boundaries before the main third-open path. Next bench command is cdc115200_cdcprologue_exactprewrite_require0541 with -CdcPrecondition115200 -CdcGoldenOpenPrologue -Require0541Ready -GoldenTiming -ExactPrewritePcap. First pass/fail is still $0541 == * 01 01 00 * before AE.pydpdu.py now re-issues the persistent num_recv=-1 IS-CYCLIC monitor before every data send and before direct 1A 3F, on physical and functional CLLs, and withholds AE 00 unless $0541 final-ready is observed. Test N passes locally and on .80. Bench command: run_pydpdu_capture.ps1 -TimeoutSec 90 -Tag monitor_rearm.67 0B C4 DC via PDUGetEventItem.pData dereference on 0x1301 STATUS items. Frida was re-solving a solved layer (WOW64 pointer bug). Active edge is pydpdu transition-unit with TU1_WAKE_ATTEMPTS=40 (sustained AA until first $0641 reply). Staged to .80. Next: re-run start_pydpdu_transition_unit_multicll.bat on live bench.csdirect_pregate_timedgate_20260604-185356.pcap; cold failure csdirect_pregate_timedgate_cold_20260604-194106.pcap; shim-ladder failures csdirect_pregate_shim0541_hot1_20260604-211942.pcap, shim0541_hot2, and shim0541_hot3; exact bulk replay failure csdirect_pregate_exactprewrite3_20260604-214417.pcap; RAM readiness report reports/firmware-re/tech2win_memdump_0541_readiness_20260604.md; CDC/control diff reports/firmware-re/usb_control_prewrite_diff_20260604.md; CDC lifecycle recipe reports/firmware-re/cdc_open_lifecycle_discriminator_20260604.md; pydpdu root-cause reports/firmware-re/pydpdu_result_routing_rootcause.md; all-tests reference reports/firmware-re/bench_test_reference_20260604.md.wiki/projects/android-j2534-driver.md, memory/2026-06-04.md, reports/firmware-re/csdirect_adapter_gate_spec.md, reports/firmware-re/tri_diff_gate_evidence_20260604.md
emulator.exe (MD5 d1f05a01…) → SAAB build (f054bd60…) differs by exactly 55 bytes / 5 clusters (ImageBase 0x400000). Author signs it at file 0x113660 ("This patch by tpw_rules…"). Cluster map: 0x58AB0/0x91FD1/0x92E68 = license+VM patches; 0x64C5A (7 bytes) = the I-Bus "communication fix" for 2003+ 9-3s (force mode=2 + swap a .data descriptor). Proof: the Desktop emulator OTHER.exe→emulator SAAB.exe diff is only the 0x64C5A cluster, which pins it as the comms fix.Install-Tech2WinSaab.ps1 byte-patches in place (backup + MD5 verify, idempotent) + installs the Saab NAO card/config + a Desktop shortcut that launches straight into the Saab config (reverse-engineered the emulator.exe flag invocation, bypassing the T2Configurator picker). Setup-SAAB4WIN.ps1 orchestrates the GM OEM installer (silent /quiet) → patch → NoMoreGlobal in one pass. Uninstall-SAAB4WIN.ps1 reverse-applies the 55-byte table to restore stock (works with no backup; stops a running NoMoreGlobal first). inno/SAAB4WIN.iss bundles everything into a 27 MB double-click installer with an Add/Remove-Programs uninstaller..80: compiled (Inno 6.7.1); silent install + silent uninstall both clean; emulator round-trips patched↔stock by MD5; bench restored byte-for-byte (caught + fixed a file-lock bug — uninstaller now stops a running NoMoreGlobal). Only unproven branch = the silent OEM MSI install on a clean box (Chris tests on real HW). Built installer left at .80 Desktop.[[project-saab4win-installer-suite]], [[reference-windows-bench-80]]
E668561 via synthetic mode (no hardware); replays a 101-exchange recording (roster + 12-module VIN walk + seed 67 0b c4 dc); synthetic frames genuinely reach the emulator (Synthetic - RX ch3 deliver $645); AcceptanceId matching deep-copies T2W's expected-response array and stamps the right id (Test G).fe 3e TesterPresent + an empty 0x100 probe, sets up monitor primitives, waits, tears down. It sends 1a 90/27 0b zero times, so the VIN/seed replies sitting ready are never requested. Nothing we changed at the driver/recording/bus layer altered what Tech2Win chooses to transmit → the decision is an upstream gate inside Tech2Win, before any reply of ours matters.emulator.exe — hard wall, parrot never consulted (Test I: F2 made zero driver calls, failed on scenlib.c "no loopback connector"). (2) diagnostic apps (ECU-ID, DTCs, get-seed) DO route through the parrot but are gated by T2W's module-presence logic.[[project-tech2win-parrot]], [[project-saab-vcilib-1124-is-firmware-side]]
2026-05-21-bench-tech2win-copctrl.log shows Tech2Win keeps the EE 00 → 1A 3F window silent — last FE 3E at t=42738, AE 00 at 44405, EE 00 at 44637, 1A 3F fired bare at 44794, FE 3E resuming only at 45086 (~2.0s quiet gap). Tech2Win fires 1A 3F bare, takes the 7F 1A 78 pending, THEN resumes tester-present.warmup_1a3f() injected keepalive_both(cs) (an FE 3E on both SWCAN sides) immediately before the 1A 3F write — justified by a comment claiming "the 5/21 golden shim shows continuous FE 3E through the AE 00 → 1A 3F window." That was a misread of the log — the window is quiet for ~2s. Worse, the code's own 5/24 bench note 15 lines below warns that ANY FE 3E in that window flips the SAS NRC from 0x78 (responsePending) to 0x22 (conditionsNotCorrect) — i.e. csdirect was doing the exact thing its own evidence said causes the failure.keepalive_both(cs) + sleep(0.05) in warmup_1a3f(). Left the post-pending keepalive in the poll loop (fires only after 7F 1A 78, matching the golden cadence). Single-variable discipline: the secondary suspect (send_ae00_preamble's keepalive in the AE 00 → EE 00 wait loop) is left in place — escalate only if this isn't enough.csdirect.py on .80 with Chipsoft + bench car. Confirmed if 1A 3F now returns 7F 1A 78 then 5A 3F (then chase 27 0B → seed). Falsified if still 7F 1A 22 → escalate to the secondary keepalive, then to the continuous filter re-arm theory (Tech2Win issues ~996 SET_FILTER/session vs csdirect's 32 at boot).~/Desktop/HANDOFF_csdirect_7F1A22_2026-05-28.md). Every code + log claim independently verified against disk before applying — unlike the same agent's earlier repo-hygiene audit, which had 2 factual errors (a contradicted exposure claim and a missing-files claim that didn't hold up). Bench-RE reasoning: trustworthy. Provenance/hygiene claims: double-check.[[project-saab-prep-sequence-before-27-0b]] (updated with the FE 3E window finding)
dpdu-saab custom D-PDU driver fork (replaces CSTech2Win.dll, calls Chipsoft's j2534_interface.dll underneath). 8 bench iterations on .80 ran Tech2Win against our driver; each iteration's log diagnosed one layer of bug; each fix cleared that layer but the vcilib.c 1124 1 dialog persisted. Latest fix ad81675 is the highest-probability real fix and is deployed but untested.c631701 NULL-check m_eventCallbackFnc in SignalEvents — fixed access violation crash1ada2a0 Refcount shared channelID across sibling CLLs — fixed ERR_INVALIDCHANNEL cascade when a sibling disconnectsb673b5d Buffer SetComParam pre-Connect + skip Disconnect if never opened — fixed SET_CONFIG spam803b0a4 StartMsgFilter silently accept ISO15765 filter requests + return synthetic FilterId — fixed ERR_ONLY_FLOWCONTROL_FILTER_TYPE_NEEDb56e48a NULL-guard pOutputData in PDUIoCtl — fixed crash at driver.dll+0xfe70aeefec1 + 4b88348 PDUGetVersion returns real MVCI Part 2 = 0x02020000 (2.2.0) + populated HW/FW/Vendor stringsdaea2d8 Emit PDU_CLLST_ONLINE status event on Connect reuse path (sibling CLLs sharing channelID)0954f31 REUSE: diagnostic probes around the STATUS_ONLINE emit — confirmed code wasn't executing despite source containing itad81675 Connect short-circuits when m_running — fixes std::jthread destructor deadlock on re-entryConnect() can be entered twice per CLL — explicit PDUConnect after an implicit Connect() from StartMsgFilter. The reuse-path body reassigns m_runLoop = std::jthread(...), which destroys the old jthread. The destructor calls request_stop() + join(), but our run() loop checks m_running (not std::stop_token), so join() blocks forever. The bench log showed ChannelId 1 RunLoop started... (from the new thread) appear, then NO main-thread log activity — main thread was stuck inside the destructor. Fix: short-circuit Connect if m_running, just re-emit the ONLINE status event.8c75d5d produced two completely different binaries: incremental 52921343 vs clean rebuild f28e6ff9. Spent ~30 min misdiagnosing the runtime against code I'd already edited. Force /t:Rebuild after every code change. Verify with strings driver.dll | grep <unique-token>.dpdu-saab/bench_runs/ — per-iteration archive on Mac, date/sha/outcome-tagged filenamesbench_runs/deploy_and_archive.sh — staged .ps1 deploy (avoids SSH-bash-PS quote mangling), gzips + uploads to R2 via /ingest/shim-logsaab-security-api 578acbb — INGEST_VALID_SOURCES extended to accept dpdu_passthru; Koyeb redeployedkill_all_t2w.ps1 on .80 desktop — covers named Tech2Win/emulator/T2Configurator processes + any process loading driver.dll / CSTech2Win.dll / j2534_interface.dll + Tech2Win* servicesX-Capture-Source: dpdu_passthru and X-Outcome tags.80 with MD5 378B66C5 already deployed/t:Rebuild on .59 (incremental-build gotcha) and re-deployConnect - already running ... — re-emit ONLINE log line appearsPDUSetUniqueRespIdTable (STUB, called ~40×) and adapter firmware mode (j2534_pro.bin vs kline_pro.bin vs canhacker_pro.bin)[[project-saab-dpdu-passthru-iso15765]], [[feedback-msbuild-incremental-serves-stale-obj]]
j2534_pro.bin was "the firmware blob with the magic sequence." Verified locally — entropy 7.997 bits/byte (uniform random, encrypted), update_j2534_pro.bat literally runs binupdate.exe j2534_pro.bin — it's an encrypted adapter-microcontroller firmware image, not a protocol blob the driver loads. Three swappable adapter personalities: j2534_pro.bin / canhacker_pro.bin / kline_pro.bin. Pushed back, then dug into where the real seam lives.FUN_005C3576 reread ⚠️ Pulled the existing 777-line decompile at saab_security_project/new_decompile/FUN_005C3576_decompile.txt. Function is a UI list-picker, not a UDS pre-prep orchestrator. Evidence: param_1+0x16 is item count, param_1+0x5a is a 8-byte-stride {key,value} array, lines 73-123 are bubble-sort dedup of that array, param_1+0x70 is a selection cursor index bumped by arrow events (sVar10 == 4/6), ENTER (sVar10 == 5) commits selection. The 45 EXEC dispatches push VM message IDs for rendering text/fields to the scenario-VM dispatcher at 0x1E40A0, not CAN frames. The "10 pre-UDS EXEC(op=4) steps from FUN_005C3576" framing from the prior agent thread was over-extrapolated.[[reference-shim-log-corpus-index]], the only instrumented Tech2Win capture with a complete 27 0B → 67 0B C4 DC seed pull is Chipsoft_RE/shim/cstech2win/captures/2026-05-21-bench-tech2win-copctrl.log. Walked all 49 REQ-PDU events. The actual ~17s pre-unlock sequence on the bus:$0101 FE 01 3E ×30x000D001F set to ... 01 01 FE 1A 9A, then $0101 FE 1A 9A functional probe → gateway @ $7E9 replies 5A 9A 01 01 (gateway capability ack)1A 90 VIN walk on multiple modules: $45, $42, $41, $43, $46, ... — each returns 5A 90 + ASCII VIN (bench VIN YS3FD49YX41012017)$27 01 Level-01 knock on 8 SWCAN modules + ECM functional: order $57, $42, $41, $43, $46, $48, $4A, $4B then $07E1. Each returns its L01 seed; every seed is discarded — no 27 02 key send anywhere in the log.FE 1A 9A → FE 3E 01 (standard TesterPresent on $0101)$0241 at ~440ms, 4 cycles; each returns NRC 7F AA 78 pending then a 12-byte status payload 41 01 NN 06 02 02 FD 04 FC with incrementing counter (NN: 66 → 65 → 68 → 69)AE 00 → EE 00 mode-shift ack1A 3F (fired exactly ONCE, not as a cadence) → 7F 1A 78 pending → 5A 3F <VIN ASCII>27 0B → 7F 27 78 pending → 67 0B C4 DC ✅1A 3F fires exactly once, immediately before 27 0B. No repeated 1A 3F chatter anywhere in the corpus.$27 01 purely as a presence probe with seed discarded — it never sends 27 02 to $0241. The bench observation that 27 02 00 00 returns NRC 0x35 (invalidKey) is genuine but irrelevant — Tech2Win doesn't enter that state. Memory [[project-saab-27-02-missing-step]] updated with retraction; algo-candidate research preserved for eventual level-0B (27 0C) key work.0x0C: 00 for physical sends on $0241, 01 for functional sends on $0101. pydpdu must set this correctly per send.[[project-saab-prep-sequence-before-27-0b]] with the byte-exact recipe[[project-saab-27-02-missing-step]] with retractionmemory/2026-05-25.md daily logNoMoreGlobalPy/csdirect.py: dropped SETTLE_SECONDS from 10.0 → 0.5 (the 10s wait was justified by the now-retracted "27 02 times out" theory; golden log actually shows ~360ms between last 27 01 and first AA)SWCAN_MODULE_WALK_27_01 flagging conflicting evidence: 5/21 shim log says $57 first, 5/24 USBPcap analysis in existing code says $41 first — to try alt order if cold-start still failsa100642 (EXP-ENTRY walker) — the 64-entry pExpectedResponseArray contents still aren't dumped. One fresh Tech2Win run on .59 with the current shim closes that gap. Still required for pydpdu to mirror Tech2Win at the D-PDU layer.$42 $43 $46 $48 $4A $4B $57 → physical module names via z90.pl / OpenSAAB catalogs.1A 3F still draws 7F 1A 22, flip the module-walk order to [$57, $42, $41, $43, $46, $48, $4A, $4B] and rerun. If that works, the order theory is confirmed.
7F 1A 22 (conditionsNotCorrect) on 1A 3F. Today's USBPcap-on-Tech2Win against the same bench works fine (67 0B C4 DC). The two "successful" csdirect runs on 5/24-5/25 night were Tech2Win-primed — we'd been driving Tech2Win for hours and the SAS module retained state for ~30-60 sec post-unlock. My SSH-run that got C4DC at 12:24 today was also within 60 sec of a Tech2Win pull. Same priming effect.tech2.dll.bin as the OS-layer RE target. That's a GlobalTIS security DLL, not Tech2Win's wire layer. Tech2Win's actual wire stack is Tech2Win.exe → CSTech2Win.dll → adapter firmware. If RE remains the path, the target is CSTech2Win.dll (~130 KB), not tech2.dll.bin.csdirect.py @ 91577d1 and chipsoft-android @ 9857456. The filter_refresh_sas helper exists but is no longer called.seed_pipeline.py.[[project-saab-seed-to-bojer-pipeline-goal]].FUN_005C5628 as a 5-level SAS state machine (0xB → 0xC → 0xD → 0xE → 0xF), each step pushing (level, opcode=4, mode=1) to 0x1E40A0 scenario_step_EXEC. Critically: dispatcher trampolines at 0x1E40A0 / 0x1E1DB0 / 0x1E1E10 dump as FF FF in NAO.bin — they're populated at boot by the Tech2 OS layer in saab_security_project/new_decompile/data/god_mode_dump/tech2.dll.bin (929 KB stripped x86 PE32 DLL). Implies pure-RE-without-Tech2Win was always going to require either that DLL or a wire-layer workaround. Took the wire-layer path.tech2win_SUCCESS_670BC4DC_20260524-122240.pcap (1.66 MB, full unlock) and a fresh csdirect capture. Op-count diff exposed the gaps: SET_FILTER 68 vs 996 (15.5×), TAG24 2 vs 84 (42×), READ_MSG 805 vs 4444 (5.5×). Tech2Win runs a continuous TAG24 → 1-2 SET_FILTERs cycle every ~5 USB ops throughout the unlock dance; csdirect installed filters once and never refreshed.NoMoreGlobalPy/csdirect.py ✅
07 80 00 00 (LE u32 = 0x00008007); HSCAN 06 00 00 00 (= 0x0006). Earlier TAG24 attempts failed because the code inserted it AFTER filters — wiping them. Ordering was the bug.inner_protocol arg on build_set_filter. Tech2Win uses outer=PHY (0x8007) but inner=FUNC (0x8008) on the 21-byte record's proto field. Same pattern on HSCAN. Resolved 3 weeks of ERR_0x6E on every BLOCK install attempt.filter_refresh_sas — the missing primitive. One TAG24 + one FLOW_CONTROL SET_FILTER refresh of the $0641 channel, fired right before each 1A 3F retry AND right before the 27 0B seed request. Adding BLOCK to the refresh ate the receive path (0 RX lines); pure FLOW_CONTROL is what works. Hypothesis: the SAS module's "is this Tech2" check reads filter freshness.00 00 06 41 ee 00 (gate flipped) → filter_refresh_sas → 00 00 06 41 7f 1a 78 (responsePending — first time csdirect ever saw this) → 00 00 06 41 5a 3f <VIN ASCII> (SAS module READY) → filter_refresh_sas → 00 00 06 41 7f 27 78 → 00 00 06 41 67 0b c4 dc (seed 0xC4DC). Reproducible.77d685d NoMoreGlobalPy: csdirect.py pulls SAS seed without Tech2Win on branch nomoreglobalpy-dpdu-2026-05-21 (origin synced).[[project-saab-seed-without-tech2win-achieved]], [[project-saab-nao-sas-vm-layout]] (extended with tech2.dll.bin pointer + FUN_005C5628 decoded state machine).chipsoft-android (Kotlin) to close out the Android-direct seed pull. Then end-to-end: csdirect seed → security_calc → Bojer POST → unlock binary written back to ECM with 04 27 0C 4E ED on $0241. Begin FastAPI emulator scaffold using NAO.bin FUN_005C5628 state machine + tonight's wire-side findings as the workflow spec.
1A 3F session preamble end-to-end. Found that the bytes csdirect.py was missing are AA 01 01 ×3 → AE 00 → EE 00 on $0241. Along the way, hardened the Chipsoft logging path so this kind of capture will never silently fail again.j2534_interface.dll ✅ Ghidra showed the Boost.Log sink in FUN_10011a99 is gated by a single byte at [edi+0xc] (LogLevel after boost::ptree). Built Chipsoft_RE/tools/patch_j2534_interface.py that NOPs the jae at RVA 0x11A9D (6 bytes) and zeroes the severity read at RVA 0x11B3D (4 bytes). 10 bytes total, length-preserving, idempotent. Validates pre-image bytes (refuses if Chipsoft updates the DLL). Applied to the installed DLL at C:\Program Files (x86)\CHIPSOFT_J2534_Pro_Driver\; original safely at .original.bak. Driver now physically cannot skip the sink — for J2534-side clients.CSTech2Win.dll show zero references to options.json, LogLevel, ProgramData, logs\, or j2534_interface. Tech2Win → CSTech2Win.dll never touches j2534_interface.dll. The byte patch helps TrionicCANFlasher / pyj2534, not Tech2Win. For Tech2Win sessions, the cstech2win shim is the only byte capture path.src/bootstrap_chipsoft_config.c: in DllMain(DLL_PROCESS_ATTACH), before the real DLL loads, ensures %ALLUSERSPROFILE%\CHIPSOFT_J2534\ exists; reads options.json, backs it up + rewrites with LogLevel: 0 if missing/invalid/≥5; creates + smoke-tests the logs\ dir. On DLL_PROCESS_DETACH: writes the newest CS log filename as an EXIT |newest CS log: breadcrumb. Defence-in-depth alongside the byte patch. Shim rebuilt (131 KB), deployed at the install path; backup renamed to CSTech2Win_real.dll.AA 01 01 and 1A 3F: AE 00 (DeviceControl, sub 0) → EE 00 positive. Added send_ae00_preamble() to NoMoreGlobalPy/csdirect.py, wired between keepalive_both and warmup_1a3f. csdirect.bat launcher created (matches vin.bat convention). pyserial 3.5 installed in 32-bit Python 3.13 on the EliteBook.%ALLUSERSPROFILE%\CHIPSOFT_J2534\logs — empty for Tech2Win-driven sessions, which had us thinking the shim was broken when it was actually writing to %TEMP% all along. Renamed to "Open Chipsoft driver log folder" + added "Open shim log folder (Tech2Win)" that highlights the newest cstech2win_shim_*.log. Rebuilt + deployed (153 MB self-contained); tray restarted.AA 01 01 retries returned 7F AA 78 (responsePending) without committing. csdirect failed identically, which confirms it's NOT a code regression — bench needed a power-cycle which we deferred. Resume tomorrow on a freshly-cycled car.csdirect.bat with the new preamble → seed should land. If yes, immediately pipe through to Bojer for the 714B post-auth. If still no, capture the fresh Tech2Win shim log and diff against ours byte-by-byte.
NoMoreGlobalPy/pydpdu.py from "PDUConstruct fails" to a clean end-to-end D-PDU run on the bench. Three separate blockers cracked — two by Ghidra-decompiling CSTech2Win.dll, one by re-instrumenting the shim. The transport hang that stonewalled every run is dead.FCT_FAILED was simply the Chipsoft adapter not being plugged in. With the bench hot, the full D-PDU lifecycle (Construct → GetModuleIds → ModuleConnect → GetVersion → Destruct) runs green.FUN_1000d0f0: its 3rd instruction requires pCllCreateFlag.NumFlagBytes == 4. pydpdu passed 0 → internal code 0x10020 → FCT_FAILED. The shim had never logged that argument, so the earlier "byte-identical to Tech2Win" diff missed it. Fixed — the CLL now creates.0x000D002B = 33333 baud + 7 timing params), bound PDUSetComParam + the PDU_PARAM_ITEM struct, wired them in. All accepted NOERROR.PDU_COP_CTRL_DATA. Tech2Win's $27 0B CoP passes NumReceiveCycles=0; pydpdu passed 1 — one receive cycle too many, so the transport waited forever on a frame that never arrives. Fixed (NumReceiveCycles=0, Time=500, 4-byte TxFlag). The hang is gone — pydpdu now runs the entire 7-request sequence + teardown clean, every PDUStartComPrimitive returning NOERROR.$AA 01 01. The shim log shows each SENDRECV produces only 0x1301 STATUS events, never 0x1300 RESULT events — the CoP completes but collects no response. Tech2Win on the same bench gets RESULT events with the UDS bytes.$0241), but Tech2Win's golden recipe creates six and runs a sustained fe 3e keepalive on the functional $0101 channel, which the $27 0B SAS path requires. Add the functional channel + keepalive to pydpdu.seed_watchdog.ps1 — 60 s hard timeout, kills python + frees COM5 so a hang can't black-screen the bench). Shim COP-CTRL + CreateComLogicalLink instrumentation committed + pushed to Chipsoft_RE (ddd1f03). OpenSAAB-Collector installer rebuilt — opensaab-collector-setup-0.3.0.exe, bundling the new shim. Bench car battery died mid-session — on the charger.
$27 0B SAS seed read down the last J2534 avenues to a definitive close, then pivoted to the D-PDU API and built a complete Python wrapper for it.cstech2win_shim_20260507-014723) found Tech2Win runs a sustained functional TesterPresent (FE 3E) on 0x0101 the whole session. Rebuilt seedkey_read.py + pyj2534.py to replicate it. Clean stable bench run: $1A 3F polled 26× over 25 s with the keepalive firing continuously — all 7F 1A 22. Falsified.0x8000 → ERR_PROTOCOL_NOT_SUPPORTED, 5 routes to the HS-CAN pins, 0x8007 is ISO-TP only. The protocol-5 + J1962_PINS=0x0101 test (seedkey_pin1.py) was accepted but the transceiver never switched — bus preflight drew ERR_CAN_WRITE_TIMEOUT.7F 1A 22 / 7F 27 22. The Chipsoft J2534 DLL structurally cannot reach the state $27 0B needs — only Tech2Win → CSTech2Win.dll (D-PDU) has ever produced 67 0B C4 DC.NoMoreGlobalPy/pydpdu.py — a complete ctypes wrapper over CSTech2Win.dll's D-PDU (ISO 22900-2 / MVCI) API: all 33 exports bound with argtypes, every pdu_api.h struct mirrored (PDU_COP_CTRL_DATA, PDU_EVENT_ITEM, PDU_RESULT_DATA, …), an event callback + event pump, high-level helpers, and a request_seed() replay skeleton. Transcribed from the Softing/AUTOSAR reference headers in external/dpdu-passthru/.PDUConstruct → PDU_ERR_FCT_FAILED. Tech2Win's PDUConstruct succeeds with identical args, so the failure is host-process-environment dependent. Ruled out tonight: argument values, working directory, COM init, shim-vs-real DLL.PDUConstruct — Ghidra-decompile it in CSTech2Win_real.dll to find the FCT_FAILED branch and/or Procmon a live Tech2Win construct — then wire request_seed() and replay the winning sequence.chipsoft-android app read the bench car's VIN directly off the bus — RX $07E8 5A 90 59 53 33 46… → YS3FD49YX41012017. This closes the "Android transport silence" thread that has run through every Android card since 2026-05-09.ctypes over the OEM j2534_interface.dll. Reads the bench VIN end-to-end on Windows: ISO15765 connect → flow-control filter → $1A 90 → 5A 90.j2534_interface.dll (and its _real backup) are both 126 KB logging shims that forward PassThruOpen into themselves → stack overflow. The genuine 1.1 MB OEM driver only exists in the RE corpus. Always load the OEM DLL.chipsoft-android connected in raw-CAN mode; it needed ISO15765 protocol mode. Raw CAN doesn't route the $07E0/$07E8 diagnostic addressing to the ECM — the app heard only body-bus noise. Same bug behind every historical "Android silent" failure, including the long $0241 chase.startFlowControlFilterIso15765, an ISO15765 write builder, and a numbered VIN button (button 7). Built, installed over wireless adb, bench-confirmed.commands/saab/vin_read_did_90.yaml ($1A 90 / $07E0 / ISO15765, including the "$22 F1 90 → NRC 0x11" finding). Committed + pushed (ad95ace).vin_iso_hub1.pcap):CAN_ID_BOTH (0x800); the working path uses 0.[Timeout][ChannelID][msgId][(TxFlags<<16)|DataSize][Timestamp]; the code had TxFlags at offset 0. Also: single WriteCommit, no Queue/SetConfig/ArmChannel.tradingalerts607/pdu_mdi2 — a working open-source ISO 22900-2 D-PDU shim with identical architecture (Tech2Win → PDUStartComPrimitive → PassThruWriteMsgs → adapter → ECU, just VPW not GMLAN-CAN). Contains full #define tables for the 0x1200/0x800x PDU constants, all IOCTL IDs 1–18, complete PDU_PARAM_ITEM struct layout, and Source.def listing the 29 PDU* exports Tech2Win demands. Closes the "no public CSTech2Win RE prior art" gap definitively — Chris's RE corpus is no longer alone.T_PDU_PARAM_MIN in Chipsoft_RE/shim/cstech2win/src/wrappers.c:34-39 was 4 fields / 16 bytes; canonical is 5 fields / 20 bytes with leading ItemType. Every PDUSetComParam log line ever captured was off by one slot — the repeating ComParamId=0x00001200 was actually ItemType=PDU_IT_PARAM (struct marker, not a parameter id). Fix landed in djfremen/Chipsoft_RE@dbd807d; new shim built via MSVC on EliteBook; deployed live. Next Tech2Win capture will be the first readable record of the ~60-call channel-init burst.mattiasclaesson/Trionic (shipping C# stack, thousands of SAAB users). The Bosch ME9.6 engine ECU SecurityAccess lives at $07E0/$07E8 level $FD (key level $FE), uses the MOTRONIC96 4-step VM recipe — same opcodes our SecurityCalculator.kt already implements (23/23 Bojer parity). The $0241/$27 0B path we'd been chasing for weeks is the separate SAS/IMMO bench-card system, not the engine ECU. Per-ECU SecurityAccess table (T7/T8/ME9.6/Z22SE Main/MCP) documented in memory + dashboard.J1962_PINS values 0x0606/0x0C0D/0x0109/0x0308/0x0101) selectable via IOCTL within J2534 mode. Standard NG 9-3 = Pin 1 SWCAN 33.3k → CIM gateway → all internal modules. Resolves the long-running "are we even talking HSCAN" question.saab_security_project/external/: pdu_mdi2 (working VPW shim), DiagProf/ISO22900.II (2.7 MB C# wrapper with the ISO_15765_4.cs ComParam name table — every CP_CanPhysReqId/CP_CanFillerByte/CP_StMin/CP_Bs/... typed), AUTOSAR interface.h (official PDU_ERR_EVT_* codes), witoldo7/dpdu-passthru fork (cleanest cop-queue template). Plus CaringCaribou. Survey report.$0101: FE 01 3E), opt-in DLC=8 padding, anchored hasUdsReply (no more DPID-flood false positives), extractNrc + awaitUdsResponse with ResponsePending handling up to 25 s. New buttons staged: button 3 "Engine SecAccess ME9.6 ($07E0/FD Trionic path)", button 4 "VIN via SWCAN/Pin-1 → CIM gateway (33.3k)". VIN ISO-TP FC fix landed — discovered ECM was replying with proper First Frame all along (07 E8 10 13 5A 90 YS3F); we just never sent 30 00 00 flow control to ask for the consecutive frames. Plus the VIN regex was bugged (required 19 chars, SAAB VINs are 17). All staged locally; awaiting phone re-pair to push.project_saab_27_0b_seed_captured (2026-05-07) conflated SAS bench-card SecurityAccess ($0241/$0B) with engine ECU SecurityAccess. Both use $27 but they're completely independent subsystems. Memory now corrected; new entry project_saab_engine_vs_sas_securityaccess documents the full per-ECU table.project_chipsoft_loglevel1_mothballed had wrong LogLevel value — claimed enable was 1; manufacturer's pinned forum post says enable is 0, default-disable is 10. Corrected.0x000D00xx vendor namespace against ISO22900.II ComParam names.$AA 01 01 (DPID setup) → $1A 90 → $27 01, never sends $10 02. Our defensive $10 02 step is harmless fall-through (~3s wasted on no-reply) but the real Tech2Win unlock path is via $AA 01 01 which we don't yet send./api/dashboard.json) now exposes the engine-vs-SAS distinction, the v0.2.8 release link, and the CIM-gateway topology. iOS clients can branch UI on engine-side vs body-side workflows accurately. The Trionic path ($07E0/$FD + MOTRONIC96 recipe) is end-to-end implementable in iOS without bench dependence — same algorithm we already ship.
check_ignition_key_status (DPID 0x0B), check_codes_read_dtcs (full 17-ECU $A9 81 sweep w/ masks 0x10/0x12), engine_me96_read_codes (single-ECU OBD-II $07E0 mask 0x0C → 22 DTCs), clear_codes_clear_dtcs ($04 sweep across 20 ECUs). Plus the morning's check_ignition_key_status. All with z90.pl-sourced English DTC descriptions.OpenSAAB/tools/decode_gmw3110_dtc.py. Caught that my initial DTC name decoding was wrong (e.g. C1 22 00 is U0122-00 "ESP Missing on BUS", not C1122-00). All 35 captured DTCs now correctly named.OpenSAAB/docs/saab_ecu_address_map_2026-05-13.md. 11 CAN-IDs identified by physical module via z90 attribution + Trionic constants: $0241 engine ECM (Tech2Win alias), $0242 BCM, $0243/$0248/$024A/$024B four door modules (sharing B3832 anti-pinch DTC), $0245 ACC/heated-seat, $0249 TCS/ESP, $024F UEC (front lighting), $07E0 ME9.6 OBD-II. Confidence levels noted; ambiguities (Trionic CIM-vs-ACC at $0245; door slot disambiguation) flagged as open.PASSTHRU_MSG.Timestamp. Both shims now ready for the same dogfooding round.tools/decode_chipsoft_pcap.py + tools/usbpcap_chipsoft_envelope.py. Used to fully decode the 43 MB TrionicCANFlasher Read ECM pcap → identified two missing OpenSAAB workflows: $A5 ProgrammingMode entry + $34 RequestDownload + ISO-TP CF-stream loader upload (5500+ frames). Captured for later catalog work./api/health, credits, Forums tab → GitHub Discussions, "No More Global ↗" tab → Chris's YouTube. Old VIN-decrypt tool preserved at /lookup.POST /ingest/shim-log on saab-security-api accepts gzipped multipart with X-Install-ID / X-Capture-Source / X-Consent-Version headers + optional vehicle profile. 50 MB hard cap. Persists to uploads/<install-id>/<source>_<wall_ms>.log.gz with sidecar JSON. Smoke-tested green in production..exe). Built end-to-end on EliteBook this evening: .NET 8 Service (BackgroundService + FileSystemWatcher + 30s settle + gzip + 5-attempt POST retry), .NET 8 WinForms tray, InnoSetup .iss with Chipsoft pre-check + consent task + DLL backup-to-*_real.dll + service install/uninstall that restores genuine DLLs byte-for-byte. Wired into landing-page download CTAs.djfremen/OpenSAAB. Default 6 categories. Welcome post seeded in Announcements explaining where to post what.commands/saab/<action>.yaml shape. Fixed; cleanup commit dropped the stray Chipsoft_RE subdir.GetDtcDescription uses bits 7-6 of byte 1 instead. Re-decoded everything against the canonical algorithm; all YAMLs corrected.docs/feedback_bojer_2026-05-12.md deleted from OpenSAAB entirely.app.ico; build failed first time on EliteBook. Removed the reference (runtime falls back to SystemIcons.Information); patched + re-built clean.build-installer.ps1 to look in $LOCALAPPDATA\Programs\Inno Setup 6\ too.uploads/<install-id>/ → community_captures increments.FilterIdCIM says CIM at $0245 but bench DTCs there are heated-seat. Resolve via $1A 9A tape-head signature.B3832-45 Anti Pinch Not Learned. Which CAN-ID is which physical door?PASSTHRU_MSG.Timestamp adds vs the USB-pcap view.https://openSAAB.com/ingest/shim-log resolves to the wrong place.Chipsoft_RE @ 185a2ef — j2534 shim scaffolding + USBPcap decoders + 4 fresh capture logsOpenSAAB @ 2d8305a — 5 new YAMLs + DTC decoder + ECU map; private Bojer feedback doc deletedsaab-security-api @ 49ea2de — landing page, /lookup, /forums, /ingest/shim-log; deployed to KoyebOpenSAAB-Collector @ 3a2fb49 — NEW repo, 17 files; v0.1.0-alpha release published with built installer attachedsas.mysaab.info/api/process, saves 714B post-auth, walks SKA tuples, prints the engine SAS key inline (NOTE:key=0x4EED → send '04 27 0C 4E ED' on $0241). Files land at YS3FD49YX41012017_POST_AUTH_bojer_*.bin.post_auth_1367-4MAY2026_chipsoft.bin (sha256 prefix d74f8334). Engine SAS pair: seed 0x77E5 → key 0xDA1E via algo 0x0367.Chipsoft_RE/workflows/ with parser, segmenter, validator scripts and 5 SAAB workflow definitions: module_presence_scan, vin_read, seed_sweep_l01, engine_sas_unlock, engine_ssa_writeback. Each definition.json is portable across any host that drives the bus.project_saab_714b_bojer_transformation.md (exact byte-level contract: HWKID at 0x01..0x0B, sccode at 0x26..0x2D, SAS key at 0x198..0x199); project_saab_0241_only_via_cstech2win.md; project_saab_chipsoft_usb_envelope.md; reference_bojer_api_callable.md.security_calc.get_key_from_seed bit-for-bit against Bojer's response. Combined with prior 33/33, the SAAB algorithm is unambiguously RE'd.10 02 occurrences. The real preamble is $AA 01 01 (DynamicallyDefineDID) on $0241. Our Android runSasSeedRequest had this wrong; reflected in updated engine_sas_unlock/definition.json.J2534Protocol.CAN → ISO15765 as a fix for $0241 silence. Every opcode returned 00 00 00 BD 00 00 00 — error code 0xBD = "protocol not supported in this config." Reverted. Will need additional SET_CONFIG + flow-control filter setup, deferred.offline-mutate output (button 5) or Bojer response — never from a Poll #N RX. Android-chipsoft has produced zero UDS replies all day.imposter/build_pre_auth.py generates random seeds, which means its output is structurally valid but functionally useless against a real ECM. Documented in project_saab_714b_bojer_transformation.md.$1A 90 reply that doesn't come. Reverted to manual car-select (buttons 6 and 6b) while transport is silent. The auto-detect code path stays in place, ready to switch on once transport works.emulator SAAB.exe.bin (13 MB PE32) loads a SAAB NAO.BIN as guest firmware and emulates the Tech2 CPU. The PDU encoding logic we want lives in the emulator's PDU-API call sites — already captured downstream by our CSTech2Win shim. Reframed: we don't need to RE the emulator binary; we already have its output stream in the shim logs.$0641, $07E8, or any other diag reply ID, regardless of VIN. Same picture with 2017 and 1367. Tech2Win on the same chipsoft → bench responds normally. So it's the J2534/raw-CAN path from Android specifically.0x04/0x12/0x17/0x22/0x0F/0x10/0x20). The security_calc algorithm is already RE'd in two languages. Bojer is callable from CLI. ~95% of what the server needs is on disk today. Open question: commit to the server build or run one more bench session first to fill the engine_sas_unlock + engine_ssa_writeback capture gaps.$27 0C key-send ack, $3D SSA write-back) have well-defined wire formats per GMW3110 §8.7-8.8 — we can spec them in the workflow JSON now and validate on the bench during testing. A bench capture would be nice-to-have, not blocking. (Note: "we already have Tech2Win's output stream from the existing CSTech2Win shim" — we are NOT building a new Tech2Win-binary shim. The workflow catalogue is built from those existing 2026-05-07 shim logs.)
project_saab_2026-05-09_full_story.md.YS3FD49YX41012017 (the "1367-suffix" cluster of files is a different SAAB).Chipsoft_RE/shim/cstech2win/captures/2026-05-09-elitebook-pull/. Headline file ssa_region_export-09may-processed.bin contains tuple (algo=0x0367, seed=0xC4DC, key=0x4EED).opAnd/opOr were big-endian (a2_1 + (a2_0 shl 8)); should be little-endian per Python (a2_0 + (a2_1 shl 8)). Misleading "params swapped per server contract" comment was a previous incorrect "fix."Byte: 0x98.toByte().toInt() = -104, never matches the unsigned (table[i] and 0xFF) = 152 lookup. OP_SUB + OP_SUB_LE arms silently no-op'd. Changed all opcode constants to Int. Recorded as feedback_kotlin_byte_constants_sign_extension.md.assets/pre_auth_2017.bin via the on-device VM, byte-identical to Bojer for all 9 shared SKA tuples including the bench unlock key 0x4EED.BojerApi.kt, HttpURLConnection + JSON SSA_DATA) → parse response → diff vs local → display verdict.$27 0B from the bench app. Three runs today, three null results.J1962_PINS=0x0101, PassFilter promiscuous, Arm channel.$27 0B → $0241 accepted (cleanly committed with msgID).0x110/0x120/0x130/0x150/0x180 = SAAB powertrain) but ZERO $0641 traffic and ZERO $67 0B matches.$10 02 DiagnosticSessionControl preamble + drain — didn't unstick it.extractSeed0B to require ISO-TP SF prefix 04 67 0B SS SS (caught a false positive: an earlier run reported seed=0x1700, which was a frame timestamp, not a real seed).$10 03 next?); wrong CAN ID (we use $0241 per shim capture; maybe $0240); ECM in non-receptive state; SWCAN routing wrong physically; Tech2Win uses a longer preamble than just $10 02.project_saab_2026-05-09_full_story.md): re-run the CSTech2Win shim with Tech2Win driving an unlock cycle, capture the FULL pre-$27 0B UDS preamble to $0241, then mirror it byte-for-byte in runSasSeedRequest. Currently we are guessing.
YS3FD49YX41012017), decoded locally, and the single deterministic SecurityAccess key was computed without any /api/process call.$27 $0B to engine ECM $0241; ECM returned deterministic seed 0xC4DC across two cold runs.decode_ssa_for_seed.py /tmp/bench_pre_auth.bin --seed 0xC4DC → tuple #09 @ offset 0x17A binds seed 0xC4DC ↔ algo 0x0367.security_calc.get_key_from_seed(0xC4DC, 0x0367) → Alt1 table → key 0x4EED.67 0B C4 DC, send 27 0C 4E ED.algo=0x0366 → key=0xB097, "most-frequent algo from imposter card") is stale — algo 0x0367 isn't in the imposter candidate list at all. Trying 0xB097 would burn the only attempt (free shots = 0) and trigger 10-s lockout. Single-shot answer is 0x4EED.0x4EED unlocks. First validation of the RE'd security_calc.py engine against a real cold-key bench seed. Held in reserve until either (a) Tech2Win's SAS-server path is restored or (b) Android-direct via Chipsoft Pro can fire it (blocked on STARTCOMM J2534 wire opcode — see Chipsoft card).security_calc.py matches 12/12 ground-truth tuples in ~/Desktop/tis2web_logs/ground_truth.md + 23/23 fresh Bojer responses. Engine is solved; this resolution is the first end-to-end on-device application.
wiki/sources/. Site loads only those two app files plus jQuery, so this is the complete client./api/process: {SSA_DATA: <base64-of-714-bytes>} only — no DEBUG, no HWKID, no version.0x0F / addr 0x2E0000, 714 bytes, 40-byte read chunks (opcode 0x81).buf[2]==0x55 → 2-byte prime write at SSA_LOCATION → writePrepare2(SSA_LOCATION, SSA_LOCATION+2) → 40B chunks (no all-FF skip, no retry) → restart 15s timeout (propagate, don't swallow).DEBUG: 69; web client sends only SSA_DATA. Split ProcessRequest into BojerProcessRequest (only SSA_DATA) for the Bojer path; DJFremen unchanged. Build clean. Local commit 1b85749.writePrepare2(BASE, BASE+714) covers full data range; web client uses (BASE, BASE+2). Could be benign protocol slack or load-bearing. Bench test required.buildChunkReadCommand emitted [offset_low, offset_high] (LE 16-bit offset); web client uses [addr_mid, addr_lo] (BE 24-bit address). Identical for offset < 0x100; bytes 4/5 silently swap at every offset ≥ 0x100, making the device read from 0x2E000N instead of 0x2E0N00. Every chunk past byte 256 returned shifted page-0 garbage instead of real flash — destroying the SKA tuple region at 0x132. Fixed in saab_simple, SAABSecurityAccess, and NoMoreGlobal_dotnet8_v8. Bench-confirmed on Tech2: post-Chipsoft live read is now byte-identical to Chipsoft's own dump (0 differences). Full Bojer round-trip closes the loop: HWKID S000310723 stamped, IMMO XJOUQVL7, all 9 SKA keys filled.tech2-byte-order-fix-2026-05-05 on djfremen/saab-security-access (main divergent histories — branch+PR strategy in effect). API repo dashboard commit pushed to master.WEB_CLIENT_DIVERGENCE_AUDIT_2026-05-04.md · Source artifacts: wiki/sources/sas.mysaab.info_*
YS3FD49YX41012017 to userXJOU INFO=QVL7SSA_READ_CHUNK_LEN 0x28 → 0x40 to match C#. The 40-byte JS-derived chunk size returned wrong bytes past the chunk boundary. C# uses 64. Once aligned, Android sees what C# sees.CMD_RESTART_DEVICE is forbidden. Use CMD_EXIT_DOWNLOAD_MODE only.
pre_auth_2017.bin with verified fixture (9 SKA tuples)pre_auth_1367.bin with verified fixture (9 SKA tuples)pre_auth_2996.bin was already correct (11 tuples)[key][status][algo][seed] is correct.entry[N].key = vm_get_key(entry[N-1].seed, entry[N-1].algo & 0xFF, table_1)SsaMutationProcessor that copies input, injects code, applies HWKID, iterates SKA tuples, fills missing keys via NativeVM. Must match server /api/process output byte-for-byte on fixtures.
| TX | RX | Bus (WIS 2008) | T8 bench (2017) | ME9.6 field car (1367) | Notes |
|---|---|---|---|---|---|
| $0241 | $0641 | P+I | CIM | CIM | Gateway between P-bus & I-bus; Tech2Win SAS routes here |
| $0242 | $0642 | I-bus | BCM | BCM | I-bus inter-coil gateway in 2008 |
| $0243 | $0643 | I-bus | DRIVER DOOR ECU | DRIVER DOOR ECU + ESP 430 NG | Context-aliased on ME9.6; ESP also on P-bus separately |
| $0244 | $0644 | I-bus | DSM | DSM | Driver Seat Module |
| $0245 | $0645 | I-bus | ACC | ACC | Climate; resolves Trionic CIM/ACC ambiguity |
| $0246 | $0646 | I-bus | IPC | IPC | = MIU (Main Instrument Unit) per WIS |
| $0247 | $0647 | I-bus | ICM 2 | — | T8 only; was O-bus in 2004, moved to I-bus in 2008 |
| $0248 | $0648 | I-bus | PASSENGER DOOR ECU | PASSENGER DOOR ECU | PDM |
| $0249 | $0649 | I-bus | REC | REC + AFL-Epsilon | Rear Electrical Centre; context-aliased on ME9.6 |
| $024A | $064A | I-bus | REAR LEFT DOOR ECU | REAR LEFT DOOR ECU | RLDM |
| $024B | $064B | I-bus | REAR RIGHT DOOR ECU | REAR RIGHT DOOR ECU | RRDM |
| $024D | $064D | I-bus | SRM | SRM | Sun Roof Module (steering reel / clockspring per Trionic naming) |
| $024F | $064F | P+I | UEC | UEC | Underhood Electrical Centre — bridges both buses per WIS |
| $0251 | $0651 | I-bus | EHU | — | T8 only; Entertainment Head Unit — was O-bus in 2004, on I-bus in 2008 |
| $0257 | $0657 | I-bus | SDM | SDM | = ACM (Airbag Control Module) per WIS — Sensing & Diagnostic Module |
| $025B | $065B | I-bus | PAS | PAS | Park-Assist (= SPA per WIS) |
| $025C | $065C | I-bus | SLM | — | T8 only; Shift Lever Module |
| $025D | $065D | ? | no $1A 97 (DID 81 = "1A9") | — | Bus assignment uncertain; needs forced menu context |
| $025F | $065F | I-bus | no $1A 97 in T8 capture | TPMS_WAL01 | TPMS on I-bus per WIS |
| $07E0 | $07E8 | P-bus | ECM | BOSCH_ME96 | Engine ECM (OBD-II address); P-bus only |
| $07E1 | $07E9 | P-bus | TCM | — | T8 only; Transmission Control Module |
$1A 97 in this capture.$0242) as inter-coil gateway. The 2004 architecture had a third O-bus (optical, ICM-gateway) — dropped in 2008; EHU + audio modules moved onto I-bus.U1xxx / U2xxx network-communication DTCs (e.g. U1500/U1501 = intra-module K-line, U2100 00 = CIM missing on P-bus, U2103 00 = AHM/PHM missing). SAAB does not use the SAE J2012 U0xxx range (ISO standard network codes). When parsing SAAB DTCs, lookup against the vendor range — per WIS DTC catalog (authoritative).saab_security_project/tech2_video_analysis/2026-05-15_module_id_table.md.
Walk videos: tech2_video_analysis/2026-05-15_ecm_walk_t8/video/ecm_walk_t8.mkv, tech2_video_analysis/2026-05-15_ecm_walk_me96/video/ecm_walk_me96.mkv.
$7E0 VIN already done; next is ECM VIN over $241/$641 RDBI $90). Then CIM VIN with Tester Present, then I-bus, then seed/key. Do not soak a 3-box CIM/SCL/ISM harness as the first move, and do not treat a 1 MB T8 flash as a SAS oracle.reports/firmware-re/bojer_alignment_audit_20260617.md, reports/firmware-re/bojer_conversation_summary_20260617.md, reports/firmware-re/j2534_python_pro_bringup_20260616.md.
YS3FD79Y276102996, code O6IFISUM, 11 SKA tuplesYS3FD49YX41012017, code XJOUQVL7, 9 SKA tuplesYS3FH46U681101367, code NHOHNJPL, 9 SKA tupleshttps://relevant-diann-djfremen2-c013cdc3.koyeb.app/api/ssavcilib.c 1124 1 · parrot backend ready (FremSoft engine in attic)
1A xx data identifiers only — stateless lookups. All target bytes already exist in the 27 non-low-value Tech2Win sessions decoded 2026-05-26 morning. No seed/key, no flow control state machine.
Tech2Win.exe
│ MVCI driver lookup
│ (HKLM\SOFTWARE\D-PDU API\Modules\Tech2Win-Parrot)
▼
┌──────────────────────────────────────┐
│ Tech2Win-Parrot driver.dll │
│ D-PDU API surface (29+ exports) │
│ ↓ │
│ ComLogicalLink + ComPrimitive │
│ ↓ │
│ ISO15765ComPrimitive::SendRecv() │
│ ↓ │
│ Recording lookup ───► recordings/*.json
│ ↓ │
│ PDU_IT_RESULT event back to Tech2Win│
└──────────────────────────────────────┘
Forked from dpdu-passthru iso15765. We already know Tech2Win lists this driver in its adapter selector — it did during today's 8 bench iterations. The wall is vcilib.c 1124 1, AFTER selection, before UDS flows.
_attic/dll-shim-experiment-2026-05-26/ for reuse as the parrot backend on the MVCI side.vcilib.c 1124 1 (same wall as dpdu-passthru).docs/ida_bp_vcilib_1124_runbook.md on emulator.exe to identify the failed preconditionPDUSetUniqueRespIdTable STUB or PDU_EVENT_ITEM struct shape — see suspect ranking in runbook)ISO15765ComPrimitive::SendRecv to the FremSoft engine from _attic/_attic/: 430 lines fremsoft.c + 250 lines recording.c + 186 lines scheduler.c + 85 lines unknown_log.c. Builds clean (MinGW-w64 i686 cross-compile from Mac, 135KB DLL output verified 2026-05-26 PM). The 29 PDU-export shim wrappers in _attic/shim/cstech2win/ are reference-only — not used on the MVCI side./Users/admin/.openclaw/workspace/Tech2Win-Parrot/.
CSTech2Win.dll → CSTech2Win_real.dll, drop in the shim, install Python + NoMoreGlobalPy deps, hand-verify each layer. We've burned this loop on .59, .80, and during the dpdu-passthru iterations enough times to justify a single installer. Goal: bench-ready in one double-click./S or InnoSetup /VERYSILENT). Drops CSTech2Win.dll + j2534_interface.dll at C:\Program Files (x86)\CHIPSOFT_J2534_Pro_Driver\.CSTech2Win.dll → CSTech2Win_real.dll and j2534_interface.dll → j2534_interface_real.dll. Drops in the latest shim DLLs from Chipsoft_RE in their place. Same pattern OpenSAAB Collector uses, but standalone (no .NET service required).j2534_interface.dll patch-check (10-byte Boost.Log sink-ON), launches Tech2Win once to confirm the shim loads + writes its first log line. Bails loudly with the failed step if anything is off.NoMoreGlobalPy/ at C:\openclaw\NoMoreGlobalPy\, creates Start Menu shortcuts for vin.bat, csdirect.bat, pydpdu_seed.bat, seedkey.bat. app.py tkinter UI gets a desktop shortcut.tech2win-installer-vX.Y.Z.exe built via InnoSetup or NSISChipsoft_RE commit SHA (no surprise drift)NoMoreGlobalPy/ snapshot pinned to a workspace SHAtech2win-installer.exe /verify re-runs the SHA + registry + shim-load check anytime_real.dll files byte-for-byte, removes the NoMoreGlobalPy bundle, leaves OEM Tech2Win install alonedjfremen/Tech2Win-Installer repo, or live inside Chipsoft_RE as a sibling to the shim source?.iss / .nsi.
/api/lookup/{vin}). Then buttons: "Pull DTC", "Add Key", "Get SAS seed". Each fires a cloud workflow that drives Tech2Win (in cloud) → relays PDU calls over network → bench Chipsoft adapter → car. Result streamed back to browser. Every interaction logged on the backend.
USER UX CLOUD BENCH (thin relay) CAR
┌──────────┐ ┌────────────────────┐ ┌─────────────────────────────┐ ┌─────┐
│ Browser │ → │ Web portal + │ │ shim_server.exe │ → │ CAN │
│ (buttons)│ ← │ workflow runner + │ ←→ │ - receives PDU calls │ ← │ │
│ │ │ Tech2Win/replay │ │ - invokes CSTech2Win_real │ │ │
└──────────┘ │ engine │ │ - returns response │ └─────┘
│ saab_codes.db ✅ │ │ - streams shim log up │
│ /api/process ✅ │ └─────────────────────────────┘
│ /api/lookup ✅ │
└────────────────────┘
▲
│ interaction log (every cmd + response)
▼
┌─────────────┐
│ R2 / PG │
└─────────────┘
Latency model: network relay is at the PDU primitive boundary, not USB packet level. One PDUStartComPrimitive = one network round-trip (~50ms WAN, ~5ms LAN). A full Tech2Win seed-pull session is ~150 PDU primitives → ~8s relay overhead over WAN, ~1s over Tailscale. ISO-TP framing happens inside CSTech2Win_real.dll on the bench — never crosses the network — so CAN timing is preserved.Get every Tech2Win byte the community generates safely uploaded so we can build the workflow library + train the cloud engine. Most of this already ships.
%TEMP%\cstech2win_shim_*.log, uploads to R2 with consent. v0.4.1 live on .59 and .80; repo.C:\Program Files (x86)\CHIPSOFT_J2534_Pro_Driver\; bootstrap auto-arms options.json; DETACH breadcrumb names the produced CS driver log.options.json drift.opensaab-capture.csdirect.bat / pydpdu_seed.bat writes runs\<tool>_<ts>.txt for diff analysis (2026-05-23).The full vision Chris described: Tech2Win runs in cloud, web portal controls it, bench is a passive PDU relay.
What's net-new (in dependency order):workflow_pull_vin(), workflow_pull_dtc(), workflow_get_sas_seed(), workflow_add_key(). Each is a literal replay of the PDU primitive sequence we captured in the shim logs. This is the "Tech2Win as code, not as exe" path — more controllable, crash-resilient, observable than driving Tech2Win.exe via UI automation.POST /api/sessions → assigns an available bench agent, returns session_idWS /api/sessions/{id}/stream → bidirectional event stream (progress, results, errors)POST /api/sessions/{id}/cmd/{kind} → invokes a workflowGET /api/sessions/{id}/log → full audit trail of interactions(session_id, agent_id, ts, cmd_kind, request, response, latency_ms, status). Drives the audit-log viewer and feeds back into Phase-A data collection.sas.mysaab.info/console (or new dashboard tab): Connect → VIN → car panel → action buttons. Streams progress events via WS. ~500 lines.PDUConstruct) end-to-end: cloud calls "construct" → bench shim invokes real DLL → returns. Proves the wire format + latency.PDUGetVersion as second primitive. Two-end-to-end proves serialization scales.Chipsoft_RE/workflows/<name>/definition.json schema (already 5 workflows defined) or rebuild for the cloud runner? Vote: keep + extend.j2534_interface.dll shim plus a new generic dpdu shim we cover Chipsoft + MDI + VX Nano + every other compliant adapter going forward.OpenSAAB/docs/adapter_coverage_matrix.md — per-adapter DLL ↔ shim ↔ standard-API map; shim build process template; per-adapter open work checklistsChipsoft_RE/shim/dpdu/ — scaffolded generic D-PDU shim: README, parameterised Makefile (make TARGET=mdi_api), src/dllmain.c, src/log.c, src/shim.h, scripts/gen_shim.py. Awaiting first real DLL to instrument.gen_shim.py against the real mdi_api.dll, fill in wrappers.c modeled on CSTech2Win, make TARGET=mdi_api. ~1-2 days.[Files] + [Run] entries per shim. X-Capture-Source whitelist gets the new source name (one-line server change).FastAPI BackgroundTasks so /ingest returns immediately (today's blocking parse is the only real bottleneck)nano instance ($5/mo, no cold-start) at 20+ active contributors.CSTech2Win.dll shim with a 4-field / 16-byte T_PDU_PARAM_MIN struct missing the leading ItemType field. Per cross-reference with tradingalerts607/pdu_mdi2 and AUTOSAR spec, the canonical layout is 5 fields / 20 bytes with ItemType at offset 0. Every PDUSetComParam log line in old releases is shifted one slot — the repeating ComParamId=0x00001200 we all saw was actually ItemType=PDU_IT_PARAM (a struct marker, not a parameter id). Real values were silently shifted into adjacent columns. Anyone running v0.2.5-alpha is uploading mis-aligned ComParam data to /ingest.Chipsoft_RE@dbd807d. Only the bundled DLL is different vs v0.2.7 — no .NET service/tray changes. After installing, the next Tech2Win capture will show PDUSetComParam with the proper 5-column layout: ItemType=0x1200 ComParamId=0x000D00xx DataType=0x00000105 Class=1/3/5 pData=.... The Chipsoft 0x000D00xx vendor namespace is now visible and cross-referenceable against DiagProf/ISO22900.II's ISO_15765_4.cs named ComParams.CSTech2Win_real.dll byte-for-byte), then run the new opensaab-collector-setup-0.2.8.exe. Service + tray + new shim re-installed in one pass.OpenSAAB/dtcs/<module>/<CODE>-<FT>.yaml for every DTC in the SAAB WIS catalog. 1,735 codes across 36 modules (ACC, ACM, AHL, AHM-PHM, AMP1, AMP2, BCM, CDCF, CDCR, CDF, CIM, DDM, DSM, ECM/Trionic-T8/Motronic-E9/Simtec/EDC16, ESP, EHU, ICM, MIU, OnStar, PDM, PHM, PSG16 diesel-pump, REC, RLDM, RRDM, SDM, SLM, SPA, SRM, TCM, TMC, TPMS, UEC, UHP). Crawled directly from saabwisonline.com/9-3-9440/2003/dtcs/ — covers all M03+ MYs since SAAB consolidated the DTC dictionary in MY03.dtc (e.g. B0260-06), module, wis.description (verbatim WIS title), wis.fault_tracing (multi-paragraph WIS notes preserved as YAML literal block), wis.url, wis.fetched_at. iOS / Android clients can pull a single YAML per DTC for live tooltip / diagnostic context.B0260-06 was annotated in commands/saab/check_codes_read_dtcs.yaml:122 as "possibly seat-related" (z90.pl had no entry). WIS authoritative: Vent Door Motor Left Circuit, Open/Short to Ground — it's an HVAC actuator, not seat. Fixed.U0xxx codes after all — but only on PSG16 (Bosch diesel pump ECU on EDC15) and with SAAB-specific semantics that differ from SAE J2012 (e.g. U0002-00 WIS = "PSG16 was Missing on Bus", SAE = "High Speed CAN Bus Performance"). My earlier "SAAB never uses U0xxx" blanket statement was overstated.U1500 failure-types in WIS are only 01/0F/38, not 01/02/03/0F/38 as my earlier note claimed./api/dashboard.json) doesn't yet wire DTC YAMLs in — but a future endpoint like /api/dtc/<code> can serve the WIS-sourced description + fault-trace per-call from the catalog. iOS users see "B0260-06 → Vent Door Motor Left Circuit" instead of "B0260-06" alone.operator_api/ Python module — chipsoft envelope (pcap-validated), UDS framing, Bojer client, Transport interface, workflow JSON runner, FastAPI routerChipsoft_RE/workflows/ (bundled into Docker as operator_api/workflows/): module_presence_scan, vin_read, seed_sweep_l01, engine_sas_unlock, engine_ssa_writeback/operator/v1/: /workflows, /bojer/process, /security/key, plus a WebSocket stub at /usb-relayPOST /operator/v1/security/key — local seed→key (33/33 validated)POST /operator/v1/bojer/process — proxy 714B pre-auth to BojerGET /operator/v1/workflows — catalogue listingoperator_api/transport.py already defines the interface; MockTransport covers the offline test path; WebsocketTransport is the missing piece.vehicle/ directory. The synthetic-shim sketch at Chipsoft_RE/shim/cstech2win/SYNTHETIC_MODE_SKETCH.md is the planned source of new workflow captures without bench dependence.
Stack: Kotlin 2.0.21, AGP 8.13.0, min/target SDK 24/36, Retrofit 2.9.0, Room 2.6.1, Compose BOM 2024.09.00
The work is done, open source. roffe/eep ("Saab CIM Tool") — Go GUI + Arduino UNO firmware + SOP8 clamp on the 93C66. Add Key dialog takes IDE (4-byte hex) + Sync (4-byte hex) and writes the cell. View / Edit / Hew also implemented. No CIM unlock, no UDS, no CAN — talks straight to the EEPROM chip in-circuit.
Project is now "build the fixture and use it," not RE the protocol. Earlier plan to reverse-engineer the CIM diagnostic write surface is obsolete — chip-level access bypasses the unlock entirely.
Key format already known: 8 bytes per key = 4-byte IDE + 4-byte Sync.
Full notes: wiki/projects/cim-bench-key-tool.md
Goal: understand the Chipsoft J2534 Pro adapter at the wire level so the Android client can talk to it directly over USB-CDC, no Windows driver required. This is the path-of-least-friction transport for an Android-direct SAAB unlock against a SAAB on a real OBD port.
Static RE — DONE. Five rounds of Ghidra analysis on j2534_interface.dll. Every layer mapped: USB-CDC virtual COM transport, 8-byte wire envelope (cmd, len, reserved, sum-mod-0xFFFF checksum, payload), IOCP queue / drain thread, and the FUN_1001d270 send-and-wait dispatcher with all 24 opcodes catalogued and traced back to their PassThru* exports.
Algorithm correctness — DONE. Trionic 8 + ME96 seed→key validated against 45 captured (seed, key) pairs / 3+ years of TrionicCANFlasher logs. 100% match. Kotlin port is unblocked.
options.json fully decoded — DONE. LogLevel scale: 0..4 enables Boost.Log sink (0=trace, max verbosity), ≥5 disables. Default log path: %ALLUSERSPROFILE%\CHIPSOFT_J2534\logs\<YYYYMMDD>_<HHMMSS>.log. Tier sub-objects: Lite / Mid / Pro, each with OpenPort2Mode / RemapAUXToPIN / SplitReadTimeout.
Firmware bins (canhacker_pro.bin / j2534_pro.bin / kline_pro.bin) — BLOCKED, not on critical path. Format is [4-byte LE length][encrypted payload]. Payload entropy 7.99/8 bits/byte; single-byte XOR and repeating-key XOR ruled out (IoC = uniform random across all periods 1..2048). binupdate.exe contains zero crypto constants — it's a pure passthrough; decryption happens in the on-device STM32 bootloader. Recovering would need SWD/JTAG against a physical device with RDP not engaged. Unnecessary for the Android-client objective.
DLL shim project — REVIVED. Tech2Win uses CSTech2Win.dll (D-PDU/ISO 22900-2), not j2534_interface.dll, so the j2534-shim plan was the wrong target. Built and shipped a CSTech2Win shim instead — captures every D-PDU API call and the underlying UDS PDUs. 30+ shim runs landed; Tech2Win's per-channel init pattern recovered: PDURegisterEventCallback → PDUIoCtl(0x000C0001) → PDUIoCtl(0x000C0000) → PDUStartComPrimitive(0x8001 STARTCOMM) → PDUStartComPrimitive(0x8004 SEND/RECV).
Android-direct USB-CDC — LIVE 2026-05-07. Pixel 7 + powered USB-C hub talks the Chipsoft Pro J2534 adapter directly. Firmware string returned via opcode 0x01: CHIPSOFT J2534 Pro v. 1.5.2. Empirically confirmed wire shapes: 0x0B = PassThruIoctl SET ([ChannelID 4B LE][Parameter 4B LE][Value 4B LE], empty-payload ack); 0x0C = PassThruIoctl GET (same shape, returns current value as 4B LE payload). Channel 1 round-trips DATA_RATE=33 333 + LOOP_BACK=0 + J1962_PINS=0x0101; GET reads back what SET wrote. Full session lifecycle (0x20 → 0x01 → 0x08 Open, 0x20 Close) round-trips cleanly.
Standalone bench app — github.com/djfremen/Chipsoft-Android. Single Compose screen, three sections of probe buttons (session lifecycle / SAAB SWCAN init / SecurityAccess wire). Per-session capture file at <external>/captures/chipsoft_<ts>.log for adb pull. Repo also carries a curated reference/shim-captures/ with 15 named shim logs (YYYY-MM-DD_HHMMSS_<topic>.log convention: seed-exchange-c4dc, ecu-info, vehicle-voltage, seed-nack-only, etc.) so wire ground truth lives next to the app code.
Open: STARTCOMM-equivalent J2534 wire opcode. Tech2's D-PDU layer uses PDUStartComPrimitive(0x8001) to bring a channel online before any Filter / WriteMsgs. Empirically probed every un-mapped J2534 ioctl from Android (0x07/0x11/0x12/0x18/0x1C/0x23) — only 0x1C returned a clean SET-ack, but even after firing it the bus stayed silent on subsequent Read polls (40+ polls, all BD 00 00 00). Without STARTCOMM the channel installs filters and accepts WriteMsgs but nothing actually transmits. Cleanest unblock: USBPcap of TrionicCANFlasher session on Win10 (TrionicCANFlasher uses j2534_interface.dll directly = same wire envelope Android-direct mimics).
DumpOpcodes.javaoptions.json reverse-engineered$27 0B on engine ECM $0241 wire-confirmed; seed 0xC4DC deterministic across runs0x01/0x03/0x08/0x0B/0x0C/0x0D/0x20/0x21 empirically confirmedchipsoft-android repo with curated shim-capture reference$27 0B seed exchange + $27 0C 4E ED unlock from Android-direct$23 ReadMemoryByAddress or replay of Tech2's read sequence) — byte-identical to /tmp/bench_pre_auth.bin
Repos: djfremen/Chipsoft_RE (RE notes + shim source-of-truth) · djfremen/Chipsoft-Android (bench app + curated wire reference)
Latest notes: Chipsoft_RE/notes/2026-05-07-{handoff-ssa-direct-read,bench-runbook-seed-capture-and-app-validation,sas-monitor-feature-request}.md
Day's full bench journal: memory/2026-05-07.md in workspace
What it is: a behavior mode added to the existing CSTech2Win.dll shim (and parallel J2534 shim). When the registry flag is set to playback, the shim stops forwarding to the real Chipsoft DLL and serves UDS responses from a pre-recorded JSON session. Tech2Win cannot tell the difference — that's the point. Name = Fremen + soft, with a pun on Chipsoft.
Three layers of value. (1) Replay — demo Tech2Win against a ghost car, no ECM connected; useful for dogfooding, training, repair walk-throughs. (2) Probe capture — unrecorded requests get answered 7F 11 ServiceNotSupported and logged to %TEMP%\fremsoft_unknown_*.log; Tech2Win itself walks every menu, the log becomes a self-discovered protocol map. (3) Module emulation — once every request is known, build per-module responders (fake CIM/BCM/ECM) producing internally consistent state. Tech2Win drives a complete software-only SAAB.
Registry mode switch. HKLM\SOFTWARE\OpenSAAB\Collector\Mode ∈ {passthrough, record, playback, standalone}. PlaybackRecording points at the JSON file. Helper .cmds already ship in the Collector installer at installer/fremsoft/enable-record.cmd + enable-playback.cmd.
Recording format. Indexed JSON, lookup key (can_id, uds_bytes). Converter tools/build_recording.py (Python, Mac side) ingests a CSTech2Win shim log and emits the JSON. First recording landed: recordings/2026-05-13-check-codes.json from yesterday's bench Check-Codes capture.
Live activity log → decoder pipe. Playback emits %TEMP%\fremsoft_<wall_ms>.log in the same line format as the existing CSTech2Win shim. The Collector's bundled fremsoft-decoder.exe (PyInstaller scapy dissector) tail-pipes it with no special-casing; the tray already recognises fremsoft_* as a third log family alongside cstech2win_shim_* and j2534_shim_*.
docs/design.md)fix_msvc.py before cl; path portablefremsoft_*.log spec wired so the bundled scapy decoder ingests with no special-casingMode=playback; point at 2026-05-13-check-codes.jsonB3832-45 on doors) with no ECM%TEMP%\fremsoft_unknown_*.log for any out-of-recording requests = protocol-map data points
Repo: github.com/djfremen/FremSoft — v0.1.0 tagged 2026-05-14
Split history: lived under Chipsoft_RE/fremsoft/ 2026-05-13; bundled into Collector v0.1.3-alpha; bundling reverted and FremSoft moved to its own repo 2026-05-14 to keep Collector (community data collection) and FremSoft (replay/ghost-car) goals cleanly separated
Docs: docs/design.md, docs/standalone-export-map.md (29 D-PDU), docs/standalone-export-map-j2534.md (14 J2534)
Wiki: wiki/projects/fremsoft.md
Objective: empirically pin down what makes the SAAB ECM treat written SSA bytes as fresh vs stale, so the Android detector can reliably distinguish "previously enrolled" from "session-authorized." Feeds the on-device 3-table runtime decision.
Working hypothesis: staleness is coverage-based, not time-based. ECM picks a seed from an internal pool at access; if our pre-written SKA (seed, key) tuples don't cover the asked seed, ECM rejects with 0x35 invalid key. Time delay (0x37) is a secondary lockout after failures.
Two bench rigs: ECM tabletop (CAN @ 500k via OBDLink SX) probes UDS 0x27 → SKA tuples; ISM tabletop (K-line via VAG KKL) probes IMMO codes. Different modules behind the same SSA byte layout — separates "ISM accepts vehicle-bound IMMO" from "ECM accepts session-bound SKA."
tools/bench_probe.py — pyserial + ELM327 AT, scripts UDS 0x27 handshakereports/ecm-freshness/
Resources: saab_security_project/external/Trionic/ (cloned, gitignored — full UDS 0x27 reference + SeedToKey + ELM327 driver + error code table); table_1_v13.bin + execute_step VM (77/77 captures verified); tools/ssa_inspect.py.
Full notes: wiki/projects/ecm-freshness-investigation.md