{"_id":"@emdzej/nfsx-directmode","_rev":"3-39fca9ef0ae3d206ca9cc4244c855412","name":"@emdzej/nfsx-directmode","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@emdzej/nfsx-directmode","version":"0.1.0","_id":"@emdzej/nfsx-directmode@0.1.0","maintainers":[{"name":"emdzej","email":"michal@jaskolski.pro"}],"homepage":"https://github.com/emdzej/nfsx#readme","bugs":{"url":"https://github.com/emdzej/nfsx/issues"},"dist":{"shasum":"0d36256509e5113c149fdb3e22e9e40a8c700e03","tarball":"https://registry.npmjs.org/@emdzej/nfsx-directmode/-/nfsx-directmode-0.1.0.tgz","fileCount":31,"integrity":"sha512-RTrCEIMyZPT57k37lzN9afM4Q5/31nVJ/UwG30scXs/5XQpnZ+b0xb0m5fzB2XjixH6rChG0itGKPm3339KaWw==","signatures":[{"sig":"MEUCIEsZfwJUc4PfIf7wkqts7JUlg1i18lEM1uyUwMR7Pw+YAiEAyH9nQhhR85vInVyaKMPBvojlvzFc+bAiF1ES27wMnB8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":192868},"main":"dist/index.js","type":"module","_from":"file:emdzej-nfsx-directmode-0.1.0.tgz","types":"dist/index.d.ts","scripts":{"test":"vitest run --passWithNoTests","build":"tsc -p tsconfig.json","clean":"rm -rf dist .turbo *.tsbuildinfo","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"emdzej","email":"michal@jaskolski.pro"},"_resolved":"/private/var/folders/7h/x_w_580x4s9dq3sq11tpvzkwy3nbj8/T/447ef02db3debb29d482196a14ae9560/emdzej-nfsx-directmode-0.1.0.tgz","_integrity":"sha512-RTrCEIMyZPT57k37lzN9afM4Q5/31nVJ/UwG30scXs/5XQpnZ+b0xb0m5fzB2XjixH6rChG0itGKPm3339KaWw==","repository":{"url":"git+https://github.com/emdzej/nfsx.git","type":"git","directory":"packages/nfsx-directmode"},"_npmVersion":"11.14.1","description":"Direct DS2 flashing for BMW MS42/MS43/GS20 ECUs via K-line, without going through the BMW IPO/SGBD path. IDENT, SEED/KEY, erase, write, verify — at 9600 8E1.","directories":{},"_nodeVersion":"22.22.1","dependencies":{"@emdzej/nfsx-flash-data":"0.1.0","@emdzej/ediabasx-interface-serial":"^0.5.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.8","@types/node":"^22.13.1"},"_npmOperationalInternal":{"tmp":"tmp/nfsx-directmode_0.1.0_1780306022772_0.613922670470521","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@emdzej/nfsx-directmode","version":"0.2.0","_id":"@emdzej/nfsx-directmode@0.2.0","maintainers":[{"name":"emdzej","email":"michal@jaskolski.pro"}],"homepage":"https://github.com/emdzej/nfsx#readme","bugs":{"url":"https://github.com/emdzej/nfsx/issues"},"dist":{"shasum":"b64d41aa62f0aa325d20f9a53aab1a09d04fe026","tarball":"https://registry.npmjs.org/@emdzej/nfsx-directmode/-/nfsx-directmode-0.2.0.tgz","fileCount":39,"integrity":"sha512-+na2tgPLVhzTh8aRsnvU6KZXqB8pYbl9m7gwzK6lSfQVSSOXa+2+gRv5cNz+xaFhdLfbHh4Gmrcds8ETIHIjpQ==","signatures":[{"sig":"MEUCIE/3q/L4nMsX8ah5cZTEZ3iSqNemZbjTFT29hOZA4IwIAiEAoINd0qbxudzzBr71J4R+JrZY9pG7REcSs9nZx1SE1WA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":211971},"main":"dist/index.js","type":"module","_from":"file:emdzej-nfsx-directmode-0.2.0.tgz","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./node":{"types":"./dist/node-transport.d.ts","default":"./dist/node-transport.js"}},"scripts":{"test":"vitest run --passWithNoTests","build":"tsc -p tsconfig.json","clean":"rm -rf dist .turbo *.tsbuildinfo","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"emdzej","email":"michal@jaskolski.pro"},"_resolved":"/private/var/folders/7h/x_w_580x4s9dq3sq11tpvzkwy3nbj8/T/f57620c1a8e147223e06da34438e61d6/emdzej-nfsx-directmode-0.2.0.tgz","_integrity":"sha512-+na2tgPLVhzTh8aRsnvU6KZXqB8pYbl9m7gwzK6lSfQVSSOXa+2+gRv5cNz+xaFhdLfbHh4Gmrcds8ETIHIjpQ==","repository":{"url":"git+https://github.com/emdzej/nfsx.git","type":"git","directory":"packages/nfsx-directmode"},"_npmVersion":"11.14.1","description":"Direct DS2 flashing for BMW MS42/MS43/GS20 ECUs via K-line, without going through the BMW IPO/SGBD path. IDENT, SEED/KEY, erase, write, verify — at 9600 8E1.","directories":{},"_nodeVersion":"22.22.1","dependencies":{"@emdzej/nfsx-flash-data":"0.2.0","@emdzej/ediabasx-interface-serial":"^0.5.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.8","@types/node":"^22.13.1"},"_npmOperationalInternal":{"tmp":"tmp/nfsx-directmode_0.2.0_1780342475194_0.604793193363395","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@emdzej/nfsx-directmode","version":"0.4.0","description":"Direct DS2 flashing for BMW MS42/MS43/GS20 ECUs via K-line, without going through the BMW IPO/SGBD path. IDENT, SEED/KEY, erase, write, verify — at 9600 8E1.","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./node":{"types":"./dist/node-transport.d.ts","default":"./dist/node-transport.js"}},"type":"module","dependencies":{"@emdzej/ediabasx-interface-serial":"^0.8.0","@emdzej/nfsx-flash-data":"0.4.0"},"devDependencies":{"@types/node":"^22.13.1","vitest":"^2.1.8"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/emdzej/nfsx.git","directory":"packages/nfsx-directmode"},"scripts":{"build":"tsc -p tsconfig.json","test":"vitest run --passWithNoTests","test:watch":"vitest","typecheck":"tsc --noEmit","clean":"rm -rf dist .turbo *.tsbuildinfo"},"_id":"@emdzej/nfsx-directmode@0.4.0","bugs":{"url":"https://github.com/emdzej/nfsx/issues"},"homepage":"https://github.com/emdzej/nfsx#readme","_integrity":"sha512-kdWl/R888Gij5sUDu30IotBOvKAB1ySEfkRJQMG+UFLNPqCrdN5wy/0JxMlKWys3JgjWvu1QSruG4T1f8t9nOA==","_resolved":"/private/var/folders/7h/x_w_580x4s9dq3sq11tpvzkwy3nbj8/T/25d500ae960fb3b5923b4e56687bff1d/emdzej-nfsx-directmode-0.4.0.tgz","_from":"file:emdzej-nfsx-directmode-0.4.0.tgz","_nodeVersion":"22.22.1","_npmVersion":"11.16.0","dist":{"integrity":"sha512-kdWl/R888Gij5sUDu30IotBOvKAB1ySEfkRJQMG+UFLNPqCrdN5wy/0JxMlKWys3JgjWvu1QSruG4T1f8t9nOA==","shasum":"c57908131087abb1dd725fbef0141e7a1d213038","tarball":"https://registry.npmjs.org/@emdzej/nfsx-directmode/-/nfsx-directmode-0.4.0.tgz","fileCount":39,"unpackedSize":211971,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDcTpkSUsWG7lSBdsLUk9D6rMy0bPRZnpxJSH5GWfZPbgIhAKAgEpWv0jVn0TEkH80uK+8c0+VdYXO3mX0F+rBUThqO"}]},"_npmUser":{"name":"emdzej","email":"michal@jaskolski.pro"},"directories":{},"maintainers":[{"name":"emdzej","email":"michal@jaskolski.pro"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nfsx-directmode_0.4.0_1783345239549_0.958290585407205"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-01T09:27:02.539Z","modified":"2026-07-06T13:40:39.777Z","0.1.0":"2026-06-01T09:27:02.946Z","0.2.0":"2026-06-01T19:34:35.339Z","0.4.0":"2026-07-06T13:40:39.673Z"},"bugs":{"url":"https://github.com/emdzej/nfsx/issues"},"homepage":"https://github.com/emdzej/nfsx#readme","repository":{"type":"git","url":"git+https://github.com/emdzej/nfsx.git","directory":"packages/nfsx-directmode"},"description":"Direct DS2 flashing for BMW MS42/MS43/GS20 ECUs via K-line, without going through the BMW IPO/SGBD path. IDENT, SEED/KEY, erase, write, verify — at 9600 8E1.","maintainers":[{"name":"emdzej","email":"michal@jaskolski.pro"}],"readme":"# @emdzej/nfsx-directmode\n\nDirect DS2 flashing for BMW MS42 / MS43 engine ECUs and the GS20\ntransmission control unit over the K-line. Drives the ECU through\nits **normal diagnostic session** (no IPO bytecode, no SGBD dispatch,\nno boot-pin hardware setup): IDENT → SEED/KEY → erase → write →\nverify, all from the host.\n\n> ℹ️ This is the raw-DS2 path, not the BMW WinKFP path. For the\n> IPO-driven flow that BMW's own tool uses (and that mirrors the BEST/2\n> VM execution of the SG_PROGRAMMIEREN job), use `@emdzej/nfsx-flash`.\n> For BSL bootmode flashing of bench-pulled ECUs, use\n> `@emdzej/nfsx-bootmode`.\n\n## What it gives you\n\n- **DS2 framing primitives** (`encodeFrame`, `decodeFrame`, `calcXor`)\n  — cross-checked against the EdiabasLib K+DCAN transport. `LEN` is\n  the total frame length including `ADDR`; `XOR` covers offsets 0\n  through the last data byte, inclusive of `ADDR`.\n- **SEED/KEY auth** (`buildSeedRequestPayload`, `deriveKey`,\n  `buildKeySubmitPayload`) — the BMW key derivation\n  `key[i] = (seed[(nonce+i) mod seed[1]] + seed[18+i] + seed[41+i]) mod 256`\n  for i=0..3, nonce in 1..23\n- **Per-ECU region tables** for the DS2 write loop:\n  - **MS42** — 1:1 BIN→ECU mapping; FULL = 3 regions; CALIBRATION = 32 KB data block at ECU `0x48000-0x4FFEF`\n  - **MS43** — has a `+0x80000` BIN→ECU shift on the program region; FULL = 2 regions; CALIBRATION = 64 KB data block at ECU `0x70000-0x7FFEE`\n  - **GS20** (TCU) — FULL = 320 KB across `0x90000-0x9FFFF` + `0xA0000-0xDFFFF`; CALIBRATION = 64 KB program block at `0x90000`\n- **ECU detection** (`identifyEcu`) — heuristic match against IDENT\n  signature substrings; the session probes both `0x12` (MS-class) and\n  `0x32` (TCU) addresses when `--variant` isn't forced\n- **Serial transport** (`NodeDirectModeTransport`) — **9600 8E1** (DS2\n  default), no flow control, TX-with-echo-verify primitive\n- **Session orchestrators** — `probe`, `readFlash`, `writeFlash` with\n  status-byte polling (the `0xA1` pending-retry pattern) and `0xFF`\n  skip optimisation in the write loop\n\n## Install\n\n```bash\npnpm add @emdzej/nfsx-directmode\n```\n\nCLI front-end lives in `@emdzej/nfsx-cli` (`nfsx directmode …`); see\nthe parent [README](../../README.md) and `nfsx directmode --help`.\n\n## Programmatic usage\n\n```ts\nimport {\n  probe,\n  readFlash,\n  writeFlash,\n} from '@emdzej/nfsx-directmode';\n\n// 1. Probe — IDENT + ECU type detection.\nconst id = await probe(\n  { device: '/dev/cu.usbserial-XXXX', baud: 9600, defaultTimeoutMs: 3000 },\n  (p) => console.error(`[${p.stage}] ${p.message}`),\n);\nconsole.log(id);  // { variant: 'MS43', identAscii: '...' }\n\n// 2. Read flash in FULL mode (all writable regions; ~256-360 KB\n// depending on variant).\nconst { variant, image } = await readFlash(\n  { device: '/dev/cu.usbserial-XXXX', baud: 9600, defaultTimeoutMs: 5000 },\n  { mode: 'full' },\n);\nawait fs.writeFile(`${variant}-dump.bin`, image);\n\n// 3. Read calibration-only — much faster, just the data block.\nconst cal = await readFlash(\n  { device: '/dev/cu.usbserial-XXXX', baud: 9600, defaultTimeoutMs: 5000 },\n  { mode: 'calibration' },\n);\n\n// 4. Write — DESTRUCTIVE. nonce defaults to 7 (any value 1..23 works).\nconst bin = await fs.readFile('modified.bin');\nconst result = await writeFlash(\n  bin,\n  { device: '/dev/cu.usbserial-XXXX', baud: 9600, defaultTimeoutMs: 5000 },\n  { mode: 'calibration', skipVerify: false, nonce: 7 },\n);\nconsole.log(result);\n// { variant: 'MS43', mode: 'calibration', bytesWritten, bytesSkipped, verified }\n```\n\n## Full vs calibration mode\n\nThe mode flag picks which subset of the ECU's flash gets rewritten:\n\n| Variant | FULL | CALIBRATION |\n|---|---|---|\n| MS42 | 3 regions covering `0x11000-0x3FFFF`, `0x48000-0x4FFEF`, `0x5002C-0x7FFFF` | upper region `0x5002C-0x7FFFF` only |\n| MS43 | 2 regions (ECU `0x90000-0xEFFEF` from BIN `0x10000` with `+0x80000` shift; ECU `0x70000-0x7FFEF` 1:1) | upper region only |\n| GS20 | 2 regions (ECU `0x90000-0x9FFFF` + `0xA0000-0xDFFFF` from BIN `0x10000-0x5FFFF`) | program block `0x90000-0x9FFFF` only |\n\nFor any variant, `--mode calibration` is significantly faster (skips\nthe unchanged program region), but only useful if you've authored your\nBIN with the program region unchanged from the original.\n\nIf a third-party-authored BIN has modified checksums or CRC tables\nthat the ECU validates on boot, pair this with the\n`--calculate-checksum` flag on the CLI (or call `verifyMs4xChecksums`\n+ `rewriteMs4xChecksums` from `@emdzej/nfsx-flash-data`) to recompute\nthem before flashing.\n\n## Status\n\nProtocol implementation and per-ECU region tables are complete. **Not\nyet validated against real hardware from this codebase.** The regions\nand DS2 protocol are well-understood for these ECUs; this is a\nTypeScript implementation.\n\nFor real-hardware first runs, take a backup with `nfsx directmode read\n--mode full` first, store it somewhere safe, and only attempt\ncalibration-only writes until you trust the flow against your specific\nECU sub-variant.\n","readmeFilename":"README.md"}