Taking apart the firmware of the Olympus OM-D E-M10 — the original Mark I, on version 1.4 — and putting things back into it.
This is notes, tools, a system emulator that boots the real image, a loader that runs code off the SD card without reflashing, and a FreeRTOS port to the camera's own CPU. The Mark I has had much less attention than the Pen-F and the newer bodies, so most of what is written down here was not documented anywhere when I started on it.
Everything is specific to one image: OLY_E_113_1400_0000_0000.BIN, model
signature 0x1130, version 0x1400. Addresses in the notes are addresses in
that build and nothing else. Other OM-D bodies of the same generation are close
enough that the shapes transfer and the offsets do not.
A TruePic VII: three Panasonic MN103S cores (AM32, disassembled as am33-2)
running a three-core µITRON 4.0 kernel, with a UART shell, a USF shell and a
Wi-Fi telnet shell left in the shipping firmware, along with most of its debug
strings and a good number of its function names.
The update file holds three blocks — main code, strings and fonts, and one more
whose content is still unidentified. Block 00 is loaded at 0x7D991020, which
is how the strings line up; c_start then copies it into RAM section by
section from a table at 0x7D9988E8, and skips the padding between
sections. That last detail cost three non-booting builds before I understood
it: bytes written into the 0xFF padding are in the image and are simply not in
RAM at run time. tools/patch.py load-padding extends the section descriptor so
the padding is copied too, which turns the whole tail into usable space for a
two-byte edit.
The other trap of the same kind: the MN103 faults on a 32-bit load from an address that is not a multiple of four. The emulator used to execute those quite happily, so a relocation pass doing unaligned word writes looked fine offline and would not boot. It now logs every misaligned access and can stop the run on one.
notes/— the writeup, and the reason the rest of this exists. Memory map and boot, the RTOS and its objects, USB and PTP, Wi-Fi and the OI.Share API, the movie recorder, stills/AF/lens, the GPU and GUI, the menu tables, what a flash actually does, and a ranked list of what can and cannot be changed. Start atnotes/README.md.tools/— the analysis and patching side.mkdb.pybuilds a SQLite database of 67.6k functions with their xrefs and strings;show.py ADDRdisassembles one with names filled in;patch.pyapplies the verified byte patches;theme.py,font.py,icons.py,guides.pyand friends rewrite the GUI's palettes, bitmap fonts and images;ghidra.shsets up a Ghidra project on the MN103 with a compiler spec for this firmware's calling convention.emu/—em10emu, a system emulator for this generation. It runs the stock image on all three cores with the kernel, the display controller and an SD host, boots to the GUI, and will open a window on the camera's rear monitor. It does not model the image processor or the sensor, so it cannot make a picture, but it catches the whole class of mistake that otherwise costs a flash cycle.plugins/— the SD-card plugin loader, its SDK, and nine plugins. The loader is patched into block 00 once; after that, code goes on the card underC:\PLUGINS\and is loaded at startup, with a page of its own under Setup.openkernel/— FreeRTOS V11.3.2 ported to the MN103S cores, with a µITRON service layer offering the calls the camera's own code makes, and thunks at the addresses it calls them at.web/— replacement pages for the card browser the camera serves over its own Wi-Fi.tools/em10ctl/— a terminal menu that drives the camera over the documented OI.Share HTTP API. It never touches firmware, so it is the one thing in here that cannot brick a body.
./setup.shThat fetches the firmware from the
xdel.ru mirror and checks its
hash, unpacks the three blocks with that mirror's oly_fw_manip (vendored in
tools/), builds mn10300-elf binutils, and writes the annotated listing. It
needs wine — the unpacker is a Windows binary — plus gcc, curl, xz and
python3. None of its output is in git; the repo carries only what was written
here.
Then, as you need them:
python3 tools/mkdb.py # disasm/fw.db — functions, xrefs, strings
tools/ghidra.sh setup # a Ghidra project, about 8 minutes
make -C emu # emu/em10emu
make -C plugins # the loader and the plugins, into plugins/build
make -C openkernel # openkernel/build/kernel.bintools/build.sh NAME PATCH… applies patches, runs the image tools, packs the
result to patched/OLY_E_113_1400_NAME.BIN, then unpacks that again and
compares every block, which is the check that matters. The ones worth naming:
| patch | what it does |
|---|---|
load-padding |
copies the section padding into RAM; everything below needs it |
video-29min |
the movie clip limit, mov 1740,d0 → mov 32767,d0 |
plugins |
the SD-card plugin loader and its hooks |
homebrew-menu |
a Homebrew entry in the Setup menu, and the page it opens |
menu-bg-paint |
paints the menu background instead of leaving live view under it |
picture-modes |
extra entries in the Picture Mode list, named everywhere |
web-ui |
replaces the card browser's pages with web/ |
guide-pictures |
redrawn SCN and ART guide artwork |
Unpack → pack with no changes reproduces the original BIN byte for byte, which is worth confirming before trusting anything else.
cd emu
./em10emu -N ../emu-state/nand.img ../unpacked/OLY_E_113_1400.dissected ../orig/OLY_E_113_1400_0000_0000.BIN
./em10emu -w … # a window on the rear monitor, keys on the debug shellWith a card directory it will boot a patched image with plugins on it and drive
the menus, which is how most of the plugin work was done. plugins/test/ runs
each capture plugin on a test picture inside the emulator and again as a host
build of the same sources, and requires the two to match byte for byte.
Honest status, because the difference matters a great deal here.
| Plugin loader and the Homebrew page | works |
| Capture plugins (partial colour, raw monochrome, film looks) | work; slow — 3.2 s for raw monochrome across all three cores, up to 16 s for the arithmetic-heavy ones |
| Hardware colour key | works, in live view as well as on the captured frame |
| GUI theme, fonts, icons, guide artwork | works |
| Replacement card-browser pages | works |
| Movie clip limit | the counter reads past the 29-minute cap — but see below |
| Focus bracketing | the plugin runs and the lens does not move |
| Immich uploader | the card walk, TLS and HTTP all work off the camera; on the camera the radio associates and DHCP never completes |
| The open kernel | boots and schedules on three cores in the emulator only |
The movie patch needs its caveat spelled out. At FHD Fine, around 24 Mb/s, 4
GiB of FAT32 is about 24 minutes, so the file-size limit arrives long before
the duration does, and what that counter now reports is card space rather than
when recording will actually stop. There is an AVSCA_INF_DIV_FILESIZE in the
recorder and I have not established whether this generation splits files.
Two reports on the mu-43 thread have the same patch stopping at about 30
minutes with a card error. I would not call it solved on the Mark I.
In-camera focus stacking, the feature I most wanted, is not reachable. Compared
against E-M5 II, E-M1 and E-M10 II images it is four interlocking layers — a
menu and settings component, a shooting sequencer, a per-frame REC-API lens
wrapper, and the focus task primitive underneath — and only the fourth exists
here, as fcs_lens_mfts_move_lens_ofset at 0x7DF016A9. The wrapper above it
is absent on the Mark I and a stub on the Mark II. Grafting the upper layers
across means rebasing non-relocatable code and inserting entries into four
build-specific tables, which is a porting project rather than a patch.
notes/focus.md has the comparison.
A card update writes only the three main blocks. The boot chain underneath is not in the update file and an ordinary flash cannot touch it:
- a 2 KiB mask ROM at
0x40000000,0x4F5bytes of code mirrored every0x800, which reads a strap word, sets up the NAND controller, reads the first 4096 bytes of flash into on-chip RAM and jumps there; - a first stage at
0x40000000,0x7800long, which loads the rest of itself and programs the clocks; - a second stage at
0x40020000,0x31800long, with its own FAT and flash code, the sub-CPU code, and the[firmup]card updater.
That was dumped off a running camera rather than inferred, and it is why
holding OK at power-on with a good BIN in DCOLYMP/ on the card recovers a
body that will not boot. I have done it once, after flashing a build that did
not come up. One data point on one body, but a hardware one.
None of which makes this safe. A wrong image can leave a camera that does not start, the sequence above is the only way back, and nobody is obliged to help you if it fails. The firmware here was written for a body I bought to take apart. Do not put it on a camera you need.
The firmware images and most of the tooling came out of the
modified firmware thread on mu-43
and the xdel.ru mirror behind it, and the groundwork on Olympus's firmware
format — the block headers, the scrambling, the checksums, the model
signatures — is mx3, Altsoph, wictor, Virus, tester13 and foxo's, in
tools/oly_fw_tools/. tools/credits.txt is the list that came with the
mirror. The Ghidra language for the MN103 is txt231's GhidraMn10300, with two
local changes in tools/ghidra/.
GPL-3.0, except where a subdirectory says otherwise. openkernel/third_party/
is FreeRTOS (MIT), plugins/third_party/ is BearSSL (MIT), the on-screen
keyboard in plugins/loader/ctrosk/ is ctr-osk-rt
(GPL-3.0), and the contents of tools/oly_fw_tools/ and the reference PDFs in
tools/ are other people's, mirrored here as they were distributed.
Olympus and OM Digital Solutions have nothing to do with any of this.