Reverse-engineering notes and tooling for patching PSA / Stellantis "SMEG+" infotainment firmware so the AUX audio source is selected automatically when a signal appears — the thing you want when an aftermarket CarPlay/Android-Auto piggyback (such as CarABC / AutoABC) feeds its audio into the head unit's AUX input.
Tested against: Peugeot 208 (2015), SMEG+ iV1, hardware diversity NAV,
firmware SMEG5.43.A.R2 (CD 26482, 19-09-17) — but the same approach applies to the
non-NAV (AUDIO_BT) builds.
Status: a patched, contract re-sealed package has been flashed to a real unit and
accepted — the media check passed, the application image was written, and the
IsAUXSRCAvailable() change is confirmed working (AUX no longer greys out). The
automatic switch itself has not yet been observed working; that is the open question.
See Hardware verification.
This repository contains only original reverse-engineering notes and scripts. It does not contain any Peugeot / Citroën / DS / Stellantis / Magneti Marelli firmware, upgrade packages, symbol maps, or other copyrighted binaries — those are not distributed here and
.gitignoreis set up to keep them out.To use these tools you must supply your own legally obtained firmware upgrade package. You are responsible for complying with the laws and licence terms that apply to your own device and software.
The head unit's application is delivered as AppBin/f_BigQuick.bin: a 0x801-byte
header followed by a zlib stream that inflates to a raw PowerPC image loaded
at 0x01000000. The release also ships absolute symbol maps
(Application/PKG/abs_symbols_base.txt.gz) which line up with that image, so the
firmware can be patched by symbol rather than by blind search.
The firmware already contains the auto-switch logic, in
C_HMI_MEDIA_APP_BASE::HandleAudioAuxInputStatusChnged(), reached via the event chain:
audio server --DBUS--> C_BCM_HMI_AUDIO_CLIENT::AUDIO_AUX_SIGNAL_STATUS_CHANGED()
-> internal message 0xcc -> HMI event 0x613dc
-> HandleAudioAuxInputStatusChnged() -> SetMediaDeviceState()/ActivateSource()
Two things stop that from actually switching on some units:
C_HMI_AUDIO_APP_BASE::IsAUXSRCAvailable()only reports AUX as available when the vehicle configuration already has it and a signal is present.HandleAudioAuxInputStatusChnged()returns early ifGetMediaDevice(AUX)fails, before it ever callsActivateSource().
The patch set addresses both. See docs/ANALYSIS.md for the full
write-up and docs/PATCHES.md for exact addresses and bytes.
| build | app image | IsAUXSRCAvailable |
…StatusChnged +0x10c |
|---|---|---|---|
AUDIO_BT (512) |
AUDIO_BT/AppBin/f_BigQuick.bin |
0x02247718 → li r3,1 ; blr |
0x023032e8 → nop |
AUDIO_BT_256 |
AUDIO_BT_256/AppBin/f_BigQuick.bin |
0x02247718 → li r3,1 ; blr |
0x023032e8 → nop |
NAV |
NAV/AppBin/f_BigQuick.bin |
0x02247858 → li r3,1 ; blr |
0x02303428 → nop |
Patches are data-driven — see patches/aux-autoswitch.json.
- uv — the scripts declare their own dependencies, so
uv run tools/<script>needs no setup. See Running the tools. ffmpegfor audio conversion (mp3/ogg/flac/...); WAV needs nothing.- Your own SMEG+ upgrade package, laid out as
SMEG_PLUS_UPG/… capstoneis only needed by the disassembly helper (tools/ppcdis.py).
python3 tools/unpack.py SMEG_PLUS_UPG/NAV/AppBin/f_BigQuick.bin app_nav.binuv run tools/patch_smeg.py --src SMEG_PLUS_UPG --out SMEG_PLUS_UPG_modpython3 tools/patch_smeg.py \
--src SMEG_PLUS_UPG \
--out SMEG_PLUS_UPG_modThis inflates each app image, applies the patches (verifying the original bytes first), re-deflates it, and rebuilds the whole checksum cascade:
f_BigQuick.bin -> f_BigQuick.bin.inf + smeg.inf + <module>_ctrl.bin -> ctrl.bin
Use --only NAV to patch a single variant, or --patches my.json for your own patch
set.
Put the modified package folder at the root of a FAT32 USB stick and run the normal
SMEG+ update on the car (engine running). See docs/FLASHING.md.
| tool | purpose |
|---|---|
tools/unpack.py |
inflate f_BigQuick.bin → raw PPC image (and re-pack) |
tools/patch_contract.py |
regenerate contract.dat so a modified package is accepted |
tools/patch_smeg.py |
apply a patch set and rebuild the CRC cascade |
tools/mkelf.py |
wrap a raw image + symbol map into a disassemblable PPC ELF |
tools/ppcdis.py |
PowerPC disassembler with symbol/call resolution |
tools/xref.py |
find code that references a string, address or pointer |
tools/callers.py |
find direct (bl) callers of a function |
tools/patch_media.py |
rebuild a media partition (ring tones, resources) |
tools/fix_userdata_case.py |
verify/fix the FAT long filename needed for a lowercase sqlite payload |
tools/ringtones.py |
convert custom audio to the unit's ring/wait tone formats |
tools/patch_studio.py |
Qt front-end: ringtone conversion + patch builder (optional) |
tools/ppcemu.py |
run a single firmware function on an emulated PowerPC core |
tools/elfsyms.py |
read the symbol tables the package ships in upgrade.out and friends |
tools/apply_files.sh |
overlay patched files onto a package copy |
pip install capstone
python3 tools/ppcdis.py app_nav.bin abs_symbols_base.txt 0x0230331c 0x02303460Also published as a docs site: https://smeg.kroper.uk/ (Zensical, built and deployed by GitHub Actions).
| doc | contents |
|---|---|
docs/ANALYSIS.md |
application image format, symbol maps, the AUX event chain, why the switch fails |
docs/RUNNING.md |
how to run everything with uv / uvx |
docs/PATCHES.md |
exact addresses and bytes per build |
docs/FLASHING.md |
preparing the USB stick and flashing |
docs/ARCHITECTURE.md |
how the whole firmware fits together: modules, HMI framework, messaging, subsystems, databases |
docs/MEDIA_PROTECTION.md |
the signed contract that blocks modified firmware from being flashed |
docs/FLASH_CHAIN.md |
BSP/flash layout, the updater's phases and gates, the manifest format used above |
docs/MEDIA_PARTITION.md |
media partition layout, ringtones and wait tones, and the checksum cascade |
docs/CHEATCODES.md |
cheatcode list, entry mechanism, spy/diagnostics system |
docs/RELEASING.md |
conventional commits and the automatic release/changelog flow |
docs/RINGTONES.md |
ring/wait tone formats, the converter, and the Qt studio |
docs/VERSION_STRINGS.md |
what the version screens read, and how the updater gates on them |
docs/AUX_CHAIN.md |
the AUX auto-switch gate by gate: what has to happen, what is proven, what is still unknown |
docs/EMULATION.md |
executing firmware functions without a car — and what that proved about the AUX patches |
The CLI tools need nothing but uv — each script declares its own dependencies, so
uv run tools/<script> just works. The GUI, tests and docs build want a virtualenv.
.venv/ is not in the repo; create it once (Python 3.13, because Homebrew's python3 is
3.14, where ensurepip is broken and PySide6 has no wheels):
uv venv --seed --python 3.13 .venv
uv pip install --python .venv/bin/python PySide6 pytest zensical ruff.venv/bin/python tools/patch_studio.py # the GUI
.venv/bin/python -m pytest tests -q # the test suiteOr use plain pip install -r requirements-dev.txt for the tests alone.
Install the git hooks once — pre-commit install wires all three types:
pre-commit install # pre-commit, commit-msg and pre-pushLint, hygiene and the no-firmware guard run on every commit; the tests, a strict docs build
and a bandit security scan run on push, so a push that would go red in CI fails locally
first. See CONTRIBUTING.md.
Agents should start at AGENTS.md: the hard rules (no vendor firmware,
conventional commit titles, main is PR-only), how to run the tests and GUI, and the
firmware quirks that are easy to get wrong.
Conventional commit titles (they drive the release), and no vendor firmware in the repo —
see CONTRIBUTING.md.
tests/ builds a synthetic package from scratch — no vendor firmware is
needed — and asserts the patch/repack path, the expect byte check, the CRC cascade
and the docs nav. Run them with python -m pytest tests -q; CI runs the same on every
pull request.
MIT — see LICENSE. The scripts are the author's own work. No third-party
firmware is covered by, or included under, this licence. See NOTICE.md
for the trademark and no-firmware statements.