review by AI:
Below is a “walk-through plus reality-check” of the script you shared, with a focus on BMW N54 (MSD80 / MSD81) flashing and the thorny topic of security bypass for custom tunes.
1. High-level architecture
Layer | What the script does | Fit for MSD80/81 |
---|
Serial-CAN transport | Opens a PC COM port at 500 kbit/s and speaks a home-grown CAN framing: 4-byte ID + 1-byte DLC + data. | MSD80/81 really do communicate on 500 kbit/s HS-CAN, but K-DCAN cables present as FTDI/CH340 and speak either Lawicel “SLCAN” or ELM327 ASCII. A raw 5-byte header is not what they understand, so you would need a microcontroller on the other end that purpose-builds this framing. |
ISO-TP (ISO 15765-2) | Manual implementation of single-frame, first-frame, flow-control & consecutive-frame logic (including timing). | Conceptually correct. Edge-cases (eg. STmin ≥ 0xF1, block-size = 0) and error handling are only partly covered. |
Diagnostic protocols | Falls back from UDS → KWP2000, then provides helpers for TesterPresent, SessionControl, SecurityAccess, Read/WriteMemory, RoutineControl, etc. | MSD80/81 use KWP2000 on CAN (not UDS). The KWP paths are present. |
Flashing logic | – Detect ECU type from ID
– Enter programming session
– Perform SecurityAccess (seed/key)
– Erase sector(s)
– RequestDownload / WriteMemory
– TransferData / RequestTransferExit
– Verify by reading back | Flow is right.
BMW’s factory tools actually use “RequestDownload / TransferData” even for KWP2000 ECUs, not KWP 0x3D WriteMemory. The script mixes both styles and may not match what the MSD80 bootloader accepts. |
CLI & GUI stubs | argparse main(), plus large PyQt imports that are currently unused. | Harmless clutter, but no functional problem. |
2. Security bypass: will the XOR/CRC trick open MSD80/81?
Short answer:
No.
- BMW seed-key is proprietary. For MSD80/81 (and all N54-era Bosch ME(MS) ECUs) the key is 16-bit but derived via a non-linear algorithm that BMW never published. Reverse-engineered public tools (e.g. bimmerlabs bootmod3 loader, DSflash, bflash) use tables or per-ECU formulas that are not the simple
python
CopyEdit
key = ((seed ^ 0x5A3C) + 0x7F1B) & 0xFFFF
that you hard-coded. Therefore _send_kwp_command(0x27, [0x02, key_hi, key_lo]) will be rejected with NRC 0x35 InvalidKey.
- MG1/MD1 & MEVD17.x really use 32-bit seeds with CRC-based keys, but the polynomial and post-processing again differ from the generic 0x4C11DB7 CRC you coded.
Bottom line: the “security_algorithm” field in ECU_MEMORY_MAPS needs a
real per-type implementation or a plug-in interface so users can drop in their own seed→key function.
3. Read/Write Memory services for MSD80/81
Service | Spec | Script implementation | Issues |
---|
ReadMemoryByAddress (0x23) | Typical “31 xx aa aa aa ll” where xx is length of address, aa… address bytes, ll length of data. | You send [addr(4B), size(1B)] (total 5 payload bytes). | Missing the addressLength and sizeLength prefix bytes – the ECU will NAK with 0x13. |
WriteMemoryByAddress (0x3D) | Same prefix rules as above, plus data. | You send [addr(4B)] + data with no lengths. | Likely rejected. |
For large transfers BMW actually expects:
- StartRoutine 0x31 FF00 (erase),
- RequestDownload (0x34) with format byte & lengths,
- TransferData (0x36) chunks,
- RequestTransferExit (0x37),
exactly how you implemented in the
UDS path, not in the KWP path. You would need to port that chunked downloader to the KWP branch.
4. Memory map accuracy
The sector layout you define (Bootloader @ 0x800000, Calibration @ 0x810000, Program @ 0x850000) is
plausible for MSD80/81, but:
- On real ECUs those addresses are logical addresses; the bootloader expects them without the 0x8 offset (e.g. starts at 0x000000).
- Sector sizes sometimes differ across software releases (77 kB cal area common).
- Program code is normally 128 kB aligned.
These mismatches do not brick the ECU—your erase/write commands would merely NAK—but verification will fail.
5. Transport layer caveats
- K-DCAN cable framing:
FTDI+Lawicel = ASCII like t6F11FF7...\r.
ELM327 = ATCRA 6F9, ATSH 6F1.
Your raw binary framing only works with a flashed STM32/Teensy that you control.
- Bus timing & watchdog:
Sending TesterPresent every 2 s is fine, but some MSD80 bootloaders require it every 1 s during erase. Also STmin handling below 0x7F should be 0–127 ms (you treat them as ms, good).
- Flow-control window:
You honour block-size but never send a second FC frame if the ECU wants one. Most Bosch bootloaders use BS=0, so you are fine.
6. What will happen if you run it?
- Detects ECU (positive).
- Fails at 0x27 SecurityAccess (negative response 0x35).
- If you code-hack the key algorithm, the next blocker will be incorrect 0x3D/0x23 framing → NRC 0x13.
- Even with framing fixed, you need KWP “RequestDownload/TransferData” not 0x3D to program flash.
- Assuming those are fixed, flashing will work, but the verify step calls _read_memory() which again uses the incorrect 0x23 framing → verification fails.
7. Recommendations to reach a working N54 flasher
- Use python-can with candleLight USB, ValueCAN, or KVASER so you avoid inventing a CAN framing.
- Swap in a proven seed-key algo. Community-known XOR table for MSD80/81 exists (search for “msd80 seed key algorithm” on github).
- Port UDS-style download routine to KWP: use 0x34/0x36/0x37 even under KWP.
- Fix Read/WriteMemory address-length prefixes if you still want the 0x23/0x3D fall-back.
- Make the memory map configurable (read 0x1A ECU ID and load a JSON) because tune files for N54 often use patched layouts.
- Remove GUI imports or wrap them in if __name__ == '__main__' and --gui to keep the CLI lightweight.
Verdict
As it stands the code is a solid educational scaffold but
will not flash an MSD80/81 safely—the security access math, KWP framing, and transport framing need real-world fixes. Once those are addressed, the general structure (session → security → erase → download → verify → reset) is correct and can absolutely power a custom N54 tuning suite.