On an ordinary workday, I received a 64 MB CompactFlash card — yes, megabytes, not gigabytes — from an industrial computer that controlled a concrete batching plant. The mission was simple: make a backup. What followed was a fascinating journey into an operating system that most programmers have never heard of.
<h2>First contact</h2>
<p>The CompactFlash came from an industrial controller used by a concrete batching plant. These computers don't run Windows or Linux — they are specialized machines, built to run non-stop, with no blue screen of death, no updates, no surprises. Some of them have been running for over 20 years without interruption.</p>
<p>Connecting it required a CompactFlash → IDE → USB adapter with external power — without a separate power supply, the laptop wouldn't even detect the card. A first sign that we were dealing with hardware from another era.</p>
$ lsblk
sdb 61,3M disk SanDisk SD25B-64
└─sdb1 61,1M part <p>A SanDisk SD25B-64. 64 megabytes. In this space — roughly the size of a single photo from your phone — fits a complete operating system, industrial automation software, configurations, concrete recipes, and a boot loader.</p>
<h2>Cloning</h2>
<p>The first rule when working with old hardware: <strong>make a bit-for-bit copy before doing anything else</strong>. I used <code>dd</code> to create an exact image of the card:</p>
$ sudo dd if=/dev/sdb of=cf_card.img bs=512 status=progress
125440+0 records in
125440+0 records out
64225280 bytes (64 MB, 61 MiB) copied, 21s, 3.0 MB/s <p>3 MB/s. In the age of NVMe SSDs hitting 7 GB/s, this speed seems like a joke. But every byte matters — this card contains software that doesn't exist anywhere else.</p>
<p>I verified the integrity with an MD5 checksum and immediately created a read-only copy of the original image. The golden rule: <strong>never touch the original</strong>.</p>
<h2>What operating system is this?</h2>
<p>The partition table revealed the first clue:</p>
Partition 1: ID=0x09, active — AIX bootable <p>Type <code>0x09</code> is associated with AIX, but also with another operating system: <strong>OS-9</strong>. A <code>hexdump</code> of the boot sector confirmed it:</p>
000001A0: 4f 53 2d 39 30 30 30 2f |OS-9000/|
000001A8: 4d 53 2d 44 4f 53 28 4f |MS-DOS(O|
000001B0: 2f 4d 29 |/M) | <p>And the volume identification sector:</p>
41 45 53 2c 20 4f 53 39 30 30 30 20 56 32 2e 32
|AES, OS9000 V2.2| <p><strong>OS-9000 version 2.2</strong>, developed by Microware Systems Corporation in Des Moines, Iowa. A real-time operating system (RTOS) for x86 processors, rewritten in C from the legendary OS-9 — which had originally been created in the '80s for Motorola 6809 processors.</p>
<h2>OS-9000 — an operating system you've never heard of, but have used indirectly</h2>
<p>OS-9 and its successor OS-9000 were among the most widely deployed real-time operating systems in industry. If you've ever used:</p>
<ul>
<li>A Philips CD-i (yes, that multimedia player from the '90s)</li>
<li>A cable set-top box</li>
<li>An industrial router</li>
<li>A concrete or asphalt batching plant</li>
</ul>
<p>...there's a good chance OS-9 was running under the hood.</p>
<p>What made it special? <strong>Absolute reliability.</strong> In a concrete batching plant, a software crash doesn't mean a blue screen — it means tons of concrete hardening inside the mixer, tens of thousands of euros in damage, and days of manual cleanup. OS-9000 was designed to never stop. Ever.</p>
<h2>Attempting to boot in an emulator</h2>
<p>I couldn't resist the temptation to see the system in action. I launched the image in a QEMU emulator:</p>
$ qemu-system-i386 -m 64 -drive file=cf_card.img,format=raw \
-boot c -vga std -display gtk -cpu pentium <p>And... it booted! On a blue background, a single message:</p>
<figure class="blog-image">
<img src="/blog/img/screenshot_interbus_error.png" alt="Interbus error on first boot in QEMU" loading="lazy">
<figcaption>The Interbus error on first boot in QEMU — the blue screen with the message "INITIALISATION IMPOSSIBLE. Erreur #032:032 sur interbus"</figcaption>
</figure>
<blockquote>
<p>INITIALISATION IMPOSSIBLE. Erreur #032:032 sur interbus</p>
</blockquote>
<p>The operating system booted perfectly. The OS-9000 kernel initialized, mounted the filesystem, and executed the startup scripts. But the AES application — the software that controls the concrete batching plant — halted at the initialization of the <strong>Interbus-S</strong> fieldbus, the industrial network that connects the aggregate feeders, conveyor belts, and mixer.</p>
<p>Makes sense. There's no physical Interbus card in the emulator. But the fact that it booted proves the image is intact and functional.</p>
<h2>The problem: how do you read a filesystem nobody supports anymore?</h2>
<p>This is where the real challenge began. OS-9000 uses a proprietary filesystem called <strong>RBF</strong> (Random Block File). There's no native support in Linux. There are no modern tools that can read it.</p>
<p>I tried several approaches:</p>
<p><strong>Toolshed</strong> — an open-source utility for OS-9 disks, compiled from source. But Toolshed is made for OS-9/6809 (Motorola, 8-bit). OS-9000/x86 uses a different variant of the RBF format, with 32-bit addresses and little-endian byte order. Toolshed refused to recognize the image.</p>
<p><strong>Linux kernel RBF module</strong> — developed in 2001 by Andrew Cannon and ported to kernel 2.6 by Carsten Emde. The latest version supports kernel 2.6.21 — from 2007. With a 6.19 kernel, there's no point in even trying.</p>
<p><strong>OS9MAX</strong> — commercial Windows software that can read OS-9000 disks. The manufacturer's website no longer responds.</p>
<p>No functional tools exist. I was left with a single option: <strong>manual reverse engineering of the filesystem</strong>.</p>
<h2>Reverse engineering: unraveling the RBF structure</h2>
<p>Starting from the public OS-9 documentation (which describes the 6809/68000 variant, 24-bit, big-endian) and from direct hexdump analysis, I reconstructed the OS-9000/x86 filesystem structure.</p>
<h3>The identification sector</h3>
<p>At offset 0x200 in the partition (logical sector 1), I found the volume header:</p>
<table>
<thead>
<tr>
<th>Offset</th>
<th>Value</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0x00</code></td>
<td><code>0xADB0B0AD</code></td>
<td>OS-9000 magic number</td>
</tr>
<tr>
<td><code>0x08</code></td>
<td>125,151</td>
<td>Total sectors (× 512B = 61 MB)</td>
</tr>
<tr>
<td><code>0x38</code></td>
<td><code>AES, OS9000 V2.2</code></td>
<td>Volume name</td>
</tr>
</tbody>
</table>
<h3>Directory entries</h3>
<p>Unlike classic OS-9 (32-byte entries), OS-9000/x86 uses <strong>64-byte entries</strong>:</p>
<ul>
<li>Bytes 0-27: the filename</li>
<li>Bytes 60-63: the File Descriptor address (little-endian, 32-bit)</li>
</ul>
<h3>File Descriptors</h3>
<p>Every file or directory has a File Descriptor (FD) that starts with the magic value <code>0xFDB0B0FD</code>. The FD contains the file's attributes, size, and segment list (the physical locations of data on disk).</p>
<p>I discovered a subtle detail: the FD address in the directory entry is 1 less than the actual physical sector. <code>firstboot</code> indicates FD=10, but the FD is actually located at sector 11. This +1 offset was the key that unlocked the entire structure.</p>
<h3>Automated scanning</h3>
<p>I wrote a scanner that searched for all sectors with the <code>0xFDB0B0FD</code> magic value and found <strong>657 File Descriptors</strong> across the entire disk — 657 files and directories on a 64 MB card.</p>
<h2>What I found: the anatomy of a concrete batching plant</h2>
<p>With the parser working, I reconstructed the complete directory tree:</p>
/
├── firstboot ─ initial boot sequence
├── sysboot ─ hardware configuration (438 KB)
├── CMDS/ ─ OS-9000 system commands
│ ├── shell, mshell ─ command interpreters
│ ├── dir, del, copy ─ familiar utilities
│ ├── fdisk, format ─ disk management
│ └── login, backup ─ administration
├── SYS/ ─ system configurations
├── AES/ ─ plant software (active version)
│ ├── CMDS/
│ │ ├── initial ─ main program (89 KB)
│ │ ├── interbus ─ Interbus-S driver (57 KB)
│ │ ├── sequenceur ─ production sequencer (206 KB)
│ │ ├── visual ─ graphical interface (388 KB)
│ │ ├── regul ─ PID controllers (112 KB)
│ │ ├── maint ─ preventive maintenance (169 KB)
│ │ └── ...26 modules total
│ └── CLIENT_CONFIG/ ─ client configuration
│ ├── aIBS_Config ─ Interbus configuration (231 KB!)
│ ├── ad1..ad4 ─ aggregate feeder configs
│ ├── ac1 ─ plant configuration
│ ├── am1 ─ mixer configuration
│ ├── ap1, ap2 ─ production parameters
│ ├── at1..at12 ─ multilingual texts
│ └── formule/ ─ concrete recipes
└── AES.old/ ─ backup of previous configuration <p>The <strong>AES</strong> software (Automatismes et Equipements de Statie) is a complete automation system, written for OS-9000, which includes:</p>
<ul>
<li><strong>Sequencer</strong> — orchestrates the production cycle: aggregate weighing → cement dosing → water addition → mixing → discharge</li>
<li><strong>PID controllers</strong> — closed-loop control for aggregate flow rate on conveyor belts</li>
<li><strong>Graphical interface</strong> — display on a QVT terminal, with menus in French, German, English, and Romanian</li>
<li><strong>Interbus-S driver</strong> — communication with distributed I/O modules (Phoenix Contact IBS)</li>
<li><strong>Modbus TCP server</strong> — communication with external systems</li>
<li><strong>Preventive maintenance</strong> — timers for part replacements, with access codes</li>
<li><strong>Kermit</strong> — serial transfer protocol for backups</li>
</ul>
<p>The configuration is for a client in Romania — one of the largest road construction companies in the country.</p>
<h3>The concrete recipes</h3>
<p>Among the strings extracted from the image, I found a few recipes:</p>
<ul>
<li><code>beton borduri</code> — concrete for curb stones</li>
<li><code>beton semiud</code> — semi-dry concrete (for precast elements)</li>
</ul>
<p>Each recipe defines the exact proportions of aggregates (gravel of different grain sizes), cement, water, and admixtures. These recipes are the intellectual property of the concrete producer — yet another reason why backing up this card is essential.</p>
<h2>The startup scripts: the boot sequence</h2>
<p>From the string analysis, I reconstructed the complete startup sequence:</p>
<ol style="padding-left: 1.5rem; color: var(--text-secondary); margin: 1rem 0 1.5rem;">
<li style="margin-bottom: 0.5rem; line-height: 1.7;"><strong style="color: var(--text-primary);">BIOS</strong> → boots from IDE Primary Master (the CompactFlash)</li>
<li style="margin-bottom: 0.5rem; line-height: 1.7;"><strong style="color: var(--text-primary);">sysboot</strong> → loads the OS-9000 kernel and hardware drivers</li>
<li style="margin-bottom: 0.5rem; line-height: 1.7;"><strong style="color: var(--text-primary);">firstboot</strong> → minimal initialization</li>
<li style="margin-bottom: 0.5rem; line-height: 1.7;"><strong style="color: var(--text-primary);">startup</strong> → the main script:
setenv SHELL mshell
setenv TERM pcat
profile SYS/set_os ← OS configuration
profile SYS/set_aes ← launches the AES application </li>
<li style="margin-bottom: 0.5rem; line-height: 1.7;"><strong style="color: var(--text-primary);">set_aes</strong> → configures paths, loads serial drivers, then:
initial a -iq <>>>/nil & ← starts the AES application </li>
<li style="margin-bottom: 0.5rem; line-height: 1.7;"><strong style="color: var(--text-primary);">initial</strong> → initializes Interbus-S, loads configuration, starts the sequencer</li>
</ol>
<p>The error message we saw in QEMU comes from step 6 — <code>initial</code> can't find the physical Interbus card.</p>
<h2>Extracting the scripts — how a concrete batching plant boots up</h2>
<p>With the parser working, I was able to extract and read the text files from the disk. The main startup script reveals the entire initialization sequence:</p>
* * * * * * * * * AUTOMATISME ELECTROTECHNIQUE SERVICES * * * * * * * *
* *
* 255, avenue de l'Europe - Parc d'activites Jean Monet *
* BP 32 - VERT SAINT DENIS 77241 CESSON CEDEX *
* *
* SYSTEME COMPUMAT *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
setenv TZ GMT:no
setenv SHELL mshell
setenv TERM pcat
profile SYS/set_os
profile SYS/set_aes <p><strong>AES</strong> — Automatisme Electrotechnique Services — was a French company based in Vert-Saint-Denis, near Paris. Their <strong>Compumat</strong> system was the software platform for controlling concrete and asphalt batching plants. Notice the address and phone number in the header — an artifact from an era when the manufacturer's contact information was hardcoded directly into the software.</p>
<h3>The options_appli file — installation-specific configuration</h3>
<p>This file contains the parameters specific to each installation. Here's the critical section:</p>
* VALIDATION DU CONTROLE INTERBUS *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*setenv CONTROL_IBS jjmmaa <p>The asterisk (<code>*</code>) at the beginning of a line is a comment in OS-9. The <code>CONTROL_IBS</code> variable is disabled — which means Interbus date validation is turned off. But the <code>initial</code> application still tries to communicate with the physical hardware.</p>
<p>Other configurable options: recalibration of the 8253 timer (the hardware clock), redirection of LCPC serial ports (local console) and printer, interface color (black or gray on the QVT terminal), and the ability to launch the application on a remote serial connection — for remote debugging, via modem!</p>
<h3>The boot selection mechanism</h3>
<p>The startup script contains a surprisingly sophisticated interactive menu:</p>
***************************************************
* CHOISISSEZ LA CONSOLE DE DEMARRAGE DU SYSTEME *
***************************************************
* *
* - Tapez '0' pour lancer sur le système local *
* - Tapez '1' pour lancer sur le système distant *
* *
*************************************************** <p>At startup, the operator can choose to launch the interface on the local terminal (option 0) or on a remote serial connection (option 1, via modem). If no choice is made within 30 seconds, the system automatically starts on the default console. A configured timeout, text-based UI, everything designed for maximum reliability.</p>
<h2>The Interbus bypass: how I booted a concrete batching plant without hardware</h2>
<p>I first tried the brute-force approach: mapping the Interbus card's memory region with zeros in QEMU. It didn't work — the <code>initial</code> application read the registers, got only zeros, and interpreted that as "card not responding."</p>
<p>Binary analysis of the <code>interbus</code> module (57 KB) revealed details about the communication card:</p>
<ul>
<li><strong>Controller</strong>: Phoenix Contact with the <strong>PC979K</strong> ASIC (4th generation Interbus)</li>
<li><strong>Bus modules</strong>: 6 distributed Phoenix Contact modules (bus terminals, digital I/O modules, modules with local bus)</li>
</ul>
<p>Interbus-S works as a ring shift register: data flows through each module and returns to the master. If a single module is missing, the entire bus reports an error. It's like a Christmas light string wired in series — if one bulb is missing, none of them work.</p>
<p>Fully simulating the hardware would have required emulating the PC979K ASIC, the responses of all bus modules, and implementing the scan protocol — a project that would take months.</p>
<p>But I had a better idea: <strong>if we can't simulate the hardware, why not bypass it entirely?</strong></p>
<h3>Surgery on a proprietary filesystem</h3>
<p>I already knew the RBF filesystem structure. I knew exactly where the <code>startup</code> script was located on the disk (File Descriptor 23699, data starting at sector 23700). I knew that OS-9 uses <code>*</code> for comments and <code>%</code> for variable expansion.</p>
<p>The critical line in the startup script:</p>
let go = var_rep("initial a -iq <>>>/nil &")
...
%go <p>The plan: change <strong>a single byte</strong> on the disk image. The <code>%</code> character (0x25) in <code>%go</code> becomes <code>*</code> (0x2A), turning the command into a comment. The <code>initial</code> application will no longer be launched, but everything else in the system will start normally.</p>
<p>I calculated the exact offset in the image: byte 4117 of the startup file, which translates to absolute address <code>0xB97A15</code> in the disk image. A single byte. I patched it and relaunched QEMU.</p>
<h3>And... it booted!</h3>
<p>The familiar blue screen with the AES Compumat header appeared — but this time, <strong>without any error</strong>. The company splash, the address in Vert-Saint-Denis, the software authors (Christian Be. et Christophe Bl.) — all displayed on the emulated QVT terminal.</p>
<figure class="blog-image">
<img src="/blog/img/screenshot_qvt_splash.png" alt="AES Compumat screen after Interbus bypass" loading="lazy">
<figcaption>The AES Compumat screen displayed on the emulated QVT terminal — showing the company address in Vert-Saint-Denis and the software authors</figcaption>
</figure>
<p>I then disabled the QVT terminal and the hardware timer (PIT 8253, which behaves strangely in the emulator) to get direct console access. And on the black screen, white text, appeared:</p>
(C) Microware Reproduit sous licence numéro 1313-0029 1863900 par AES
*********************************************************************
**** Chargement de l'application ****
................. <figure class="blog-image">
<img src="/blog/img/screenshot_console_boot.png" alt="OS-9000 console - full boot in QEMU" loading="lazy">
<figcaption>The OS-9000 console — full boot in QEMU with the Microware license message and AES application loading</figcaption>
</figure>
<p><strong>OS-9000 boots fully in QEMU.</strong> The Microware license number (1313-0029 1863900) confirms this is a licensed system, not a pirated copy — a fascinating detail about the professionalism of the AES developers in the '90s.</p>
<p>But the console was mute — it wouldn't respond to keystrokes. The startup script finished execution and the shell exited without leaving an interactive prompt. I had one more problem to solve.</p>
<h3>Getting an interactive shell</h3>
<p>I modified the startup (again through direct patching on the disk image) to launch a shell connected to the console terminal at the end:</p>
shell <>>>/term <p>The <code><>>>/term</code> syntax is specific to OS-9 and means "redirect stdin, stdout, and stderr to the <code>/term</code> device" — i.e., the VGA screen.</p>
<p>And, at last:</p>
<figure class="blog-image">
<img src="/blog/img/screenshot_shell_prompt.png" alt="OS-9000 shell - interactive prompt" loading="lazy">
<figcaption>The first interactive OS-9000 prompt — a live shell on a system from the '90s, running in QEMU</figcaption>
</figure>
<p><code>[1]$ _</code> — the first interactive OS-9000 prompt I had ever seen. I typed <code>dir</code>:</p>
<figure class="blog-image">
<img src="/blog/img/screenshot_dir_listing.png" alt="Root directory listing with dir" loading="lazy">
<figcaption>The root directory listing — exactly matching the structure we discovered through reverse engineering</figcaption>
</figure>
Directory of . 16:44:41
AES AES.old CMDS SPF SYS
firstboot sysboot <p>Exactly the structure we had discovered through reverse engineering! And navigating into <code>AES/CMDS</code>:</p>
<figure class="blog-image">
<img src="/blog/img/screenshot_aes_cmds.png" alt="AES application modules" loading="lazy">
<figcaption>All 40+ application modules listed live from the OS-9000 shell — the complete AES Compumat software ecosystem</figcaption>
</figure>
<p>All 40+ application modules: <code>initial</code>, <code>interbus</code>, <code>sequenceur</code>, <code>visual</code>, <code>regul</code>, <code>maint</code>, <code>modbusTCP</code>, <code>kermit_aes</code>, <code>espion</code>, <code>ClientLAN</code>, <code>modem</code>... A complete software ecosystem, confirmed live.</p>
<p>A surprising detail: the keyboard was configured as <strong>AZERTY</strong> (French layout). When I typed <code>AES</code> on our QWERTY keyboard, the screen showed <code>QES</code>. Makes sense — the software was developed in France, in Vert-Saint-Denis. I created a translation mapping so I could communicate with the system.</p>
<p>Now we had full access to a live OS-9000 system. We could explore files, run commands, and potentially launch individual application components — the graphical interface <code>visual</code>, the sequencer <code>sequenceur</code>, the maintenance module <code>maint</code>.</p>
<h2>Binary dissection: OS-9000 modules and CRC protection</h2>
<p>Trying to run <code>initial a</code> from the interactive shell failed — and the reason was revealing.</p>
<h3>The OS-9000 module format</h3>
<p>Every executable in OS-9000 is a <strong>module</strong> — a self-contained unit with a standardized header:</p>
<table>
<thead>
<tr>
<th>Offset</th>
<th>Field</th>
<th>Example (initial)</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0x00</code></td>
<td>Sync word</td>
<td><code>0x4AFC</code> (little-endian)</td>
</tr>
<tr>
<td><code>0x04</code></td>
<td>Module size</td>
<td>89,200 bytes</td>
</tr>
<tr>
<td><code>0x0C</code></td>
<td>Name offset</td>
<td><code>initial</code></td>
</tr>
<tr>
<td><code>0x12</code></td>
<td>Type/Language</td>
<td>Program / Machine code</td>
</tr>
<tr>
<td><code>0x24</code></td>
<td>Execution offset</td>
<td><code>0x60</code></td>
</tr>
<tr>
<td><code>0x2C</code></td>
<td>Data size</td>
<td>5,616 bytes</td>
</tr>
<tr>
<td>last 3 bytes</td>
<td><strong>CRC-24</strong></td>
<td>Integrity checksum</td>
</tr>
</tbody>
</table>
<p>The last 3 bytes of every module are a <strong>24-bit CRC</strong>. The algorithm:</p>
<ul>
<li>Initial value: <code>0xFFFFFF</code></li>
<li>Polynomial: <code>0x800063</code></li>
<li>When processing the <strong>entire</strong> module (including the CRC), the result must be exactly <code>0x800FE3</code></li>
</ul>
<p>This mechanism guarantees module integrity — if a single bit changes, OS-9000 refuses to load the module. I discovered this when I tried to patch a single byte (a <code>jne</code> → <code>jmp</code> to bypass a check) and the shell refused to execute the module, treating it as plain text:</p>
shell: can't execute "¶J" - Error #000:216 <p><code>¶J</code> are the first two bytes of the sync word (<code>FC 4A</code> = <code>0x4AFC</code>) — the shell was seeing random bytes, not a valid module. Only after recalculating the CRC was the module accepted again.</p>
<h3>Application architecture: a distributed system on a single processor</h3>
<p>Module analysis revealed an elegant layered architecture:</p>
initial ─── creates shared data modules (share, data_ecran,
│ data_reseau, init, cio, input, defaut, vidange, share_ana)
│
└── master ─── the orchestrator, forks all processes:
├── visual (388 KB) — operator graphical interface
├── sequenceur (206 KB) — production cycle
├── regul (112 KB) — PID controllers
├── interbus (57 KB) — fieldbus communication
├── modbusTCP — Modbus communication
├── srvmodbusTCP — Modbus server
├── input/output — I/O management
├── maint (169 KB) — preventive maintenance
├── autorise — access authorization
├── refresh — screen refresh
├── look — monitoring
├── modem — serial communication
├── ClientLAN — network client
└── acquit_AU — alarm acknowledgment <p>The module table inside <code>master</code> is structured as an array of structures at 78-byte intervals in the initialized data section (idata) — each entry containing the module name and launch parameters.</p>
<p>Inter-process communication is done through <strong>shared data modules</strong> — memory regions created by <code>initial</code> and accessible to all processes via system calls (<code>F$Link</code>, <code>F$DatMod</code>). This is a form of IPC (Inter-Process Communication) typical of OS-9, faster than pipes or messages, essential for a real-time system where latency matters.</p>
<h3>The AZERTY keyboard — a detail that changes everything</h3>
<p>OS-9000 was configured with an <strong>AZERTY</strong> (French) keyboard layout. QEMU sends QWERTY scancodes, and OS-9000 interprets them as AZERTY. This means:</p>
<ul>
<li><code>a</code> → must be sent as the <code>q</code> key</li>
<li><code>q</code> → as the <code>a</code> key</li>
<li><code>z</code> → as <code>w</code>, <code>w</code> → as <code>z</code></li>
<li><code>m</code> → as <code>;</code></li>
<li>Digits <code>0-9</code> require <strong>Shift</strong> (on AZERTY, digits are on Shift, while symbols <code>&é"'(-è_çà</code> are on the main row)</li>
<li><code>/</code> (path separator) → <code>Shift+.</code></li>
</ul>
<p>Without this mapping, navigating the system is impossible — every file path becomes a cryptographic puzzle.</p>
<h2>Complete extraction: a filesystem brought to light after 20 years</h2>
<p>With the RBF structure fully understood, I wrote a Python extractor that parses the entire image and exports all files to disk. The result:</p>
17 directories, 637 files, 11,534,829 bytes
594 OS-9000 modules with valid CRC, 5 boot modules (different CRC)
13 text files (scripts, configurations) <p>637 files on a 64 MB card. Every bit counted.</p>
<h3>The complete inventory</h3>
<p>Here's what the card contains — a complete software ecosystem:</p>
<p><strong>The operating system</strong> — 80+ commands in <code>/CMDS/</code>:</p>
shell, mshell — command interpreters
dir, del, copy — classic utilities
fdisk, format — disk management
login, backup — administration
load, unlink — module management
telnet, ftp — yes, this 64 MB card has FTP and Telnet clients <p><strong>The AES application</strong> — 40+ modules in <code>/AES/CMDS/</code>:</p>
initial (87 KB) — application bootstrap
master (36 KB) — process orchestrator
visual (380 KB) — graphical interface (the largest module!)
sequenceur (202 KB) — production sequencer
regul (110 KB) — PID controllers
interbus (56 KB) — Interbus-S driver
maint (166 KB) — preventive maintenance
modbusTCP (17 KB) — Modbus TCP communication
kermit_aes (41 KB) — serial file transfer
gzip (77 KB) — compression — yes, on 64 MB <p><strong>Multilingual resources</strong> — the interface available in 5 languages:</p>
aecran — French screens (default)
aecran_gb — English screens
aecran_d — German screens
aecran_pl — Polish screens
aecran_hu — Hungarian screens
aerreur* — error messages in all languages
ahelp* — operator help in all languages <p><strong>Client configuration</strong> — <code>/AES/CLIENT_CONFIG/</code>:</p>
aIBS_Config (227 KB) — Interbus configuration (the entire I/O wiring!)
ad1..ad4 — aggregate feeder configs (4 aggregate lines)
ac1 — concrete plant configuration
am1 — mixer configuration
at1..at12 — installation-specific texts
aformule (69 KB) — concrete recipes
amagasin (28 KB) — silo/storage configuration <h3>Operator screens — terminal UI from the '90s</h3>
<p>The <code>aecran</code> modules contain the operator screen definitions, drawn with box-drawing characters for the QVT terminal:</p>
═════════════════════════════════════════════════════════
║ Att APPELLATIONS % %H2O DEBIT 0% 100% ║
╠═════════════════════════════════════════════════════════╣
║ TC ║
║ TD1 ║
║ TOTAL ║
║ ED ║
║ TD2 ║
║ DEBIT TM ║
║ MARCHE CENTRALE ║
║ QT DEMANDEE ║
║ MARCHE MATERIAUX ║
║ QT DELIVREE ║
║ CONJUGATEUR en % % ║
║ QT CASQUE ║
╚═════════════════════════════════════════════════════════╝ <p>Each screen — <code>DONNEES DE SERVICE</code> (Service Data), <code>AJUSTEMENT DE LA FORMULE</code> (Formula Adjustment), <code>DEFAUTS</code> (Faults) — is a full terminal page with editable fields marked by QVT-specific control codes.</p>
<p>In the English version, the titles become <code>SERVICE DATA</code>, <code>FORMULA ADJUSTMENT</code>, <code>FAULTS</code>. In the German version, the operator sees <code>WATTMESSER MISCHER</code> (mixer wattmeter), <code>DOSIERWERKE</code> (batchers), <code>WASSERDURCHSATZ</code> (water flow rate). Five languages, each compiled into a separate OS-9000 module.</p>
<h3>Concrete recipes — the client's intellectual property</h3>
<p>From the <code>aformule</code> module (69 KB), I extracted the names of all stored recipes:</p>
stabilizat aeroport
spalare statie
beton semiud
beton borduri
BORDURI [ORAS]
stabilizat
stabilizat special
LIMON
limon traite test '4% cim
formula ciment
formula test <p>Each recipe defines the exact proportions for each batcher: the amount of aggregates from silo 1, silo 2, cement, water, admixtures. "Stabilizat aeroport" (Airport stabilized soil) suggests an airport construction contract. "BORDURI [ORAS]" — curb stones for a Romanian city. "Limon traité test 4% ciment" — a soil treatment test with 4% cement.</p>
<p>These recipes are calibrated through successive trials, adjusted based on aggregate moisture, outdoor temperature, and concrete strength requirements. Losing them would have meant weeks of recalibration.</p>
<h3>Error messages — what the operator sees when something goes wrong</h3>
<p>The <code>aerreur</code> modules contain hundreds of diagnostic messages, each tied to a specific situation at the plant:</p>
Arrêt d'urgence machine → Machine emergency stop
Chaîne thermique matériaux → Materials thermal chain open
Trémie pleine → Hopper full
Trémie vide → Hopper empty
Verrouillage malaxeur → Mixer locking
Défaut de position chargement → Loading position fault
Poids +12 % → Weight +12%
Sous-vitesse vis → Screw underspeed
Défaut lecture poids → Weight reading fault <p>Each message corresponds to an I/O signal on the Interbus fieldbus — a thermal contact opening, a level sensor in the hopper, an encoder on the conveyor belt. When the operator sees "Sous-vitesse vis" (Dosing screw underspeed), they know exactly that the cement auger motor isn't reaching its set speed — it could be a worn drive belt, a material blockage, or an electrical problem.</p>
<h3>Networking — FTP and Telnet on a concrete batching plant</h3>
<p>An unexpected detail: the card contains a full TCP/IP stack with FTP and Telnet servers. The <code>start_spf</code> script configures:</p>
load spf — SPF file manager (the OS-9000 network protocol)
load sptcp tcp0 — TCP driver
load spudp udp0 — UDP driver
ipstart — IP stack initialization
inetd <>>>/nil& — Internet daemon (FTP + Telnet) <p>The <code>inetd.conf</code> configuration enables FTP and Telnet — meaning an AES technician could, in theory, connect to the plant over the network, transfer configuration files via FTP, and run commands through Telnet. On a 64 MB card. In the early 2000s.</p>
<h3>AES Compumat — a company that changed its address</h3>
<p>Comparing the startup scripts, I discovered that AES had moved its headquarters:</p>
<p><strong>Old version</strong> (<code>startup_appli</code>):</p>
85, rue Eugène Delaroue - BP 165 - 77197 DAMMARIE CEDEX
Tél 01 64 37 11 02 - Télécopie 01 64 37 23 23 <p><strong>Current version</strong> (<code>startup</code>):</p>
255, avenue de l'Europe - Parc d'activités Jean Monet
BP 32 - VERT SAINT DENIS - 77241 CESSON CEDEX
Tél 01 64 64 33 70 - Télécopie 01 64 64 33 71 <p>From Dammarie-lès-Lys to Vert-Saint-Denis — both in the Seine-et-Marne department, southeast of Paris. The older address on rue Eugène Delaroue suggests a small office. The move to avenue de l'Europe, in a "Parc d'activités" (business park), indicates the company was growing. These details, hardcoded into the boot scripts, are small time capsules of a company that no longer exists.</p>
<h2>How the plant software works: dissecting a real-time application</h2>
<p>With all files extracted and analyzed, we can now fully reconstruct how this software controls a concrete batching plant. It's a lesson in industrial software architecture from the '90s — elegant, minimalist, and built for absolute reliability.</p>
<h3>Startup: from power-on to concrete</h3>
<p>The boot sequence takes a few seconds:</p>
BIOS → CompactFlash (IDE) → sysboot (OS-9000 kernel + drivers)
→ firstboot → startup
→ profile SYS/set_os ← system configuration
→ profile SYS/set_aes ← application configuration
→ set_pit8253 ← hardware timer calibration
→ [menu: local console or modem?]
→ initial a -iq <>>>/nil & ← launches the AES application <p>The boot menu detail is fascinating: the operator can choose to launch the interface on the local screen (option 0) or on a remote serial connection (option 1, via modem — for remote debugging). If no choice is made within 30 seconds, the system automatically starts on the default console.</p>
<h3>Architecture: a distributed system on a single processor</h3>
<p>The AES Compumat application is structured in three layers:</p>
<p><strong>Layer 1 — <code>initial</code></strong> (bootstrap)</p>
<p>The first process launched. Its role is to prepare the environment:</p>
<ul>
<li>Creates <strong>9 shared data modules</strong> in RAM — memory regions accessible to all processes:
<ul>
<li><code>share</code> — global plant state</li>
<li><code>share_ana</code> — analog values (weights, flow rates, speeds, temperatures)</li>
<li><code>data_ecran</code> — operator screen buffer</li>
<li><code>data_reseau</code> — network communication data</li>
<li><code>defaut</code> — active fault register</li>
<li><code>vidange</code> — batcher draining state</li>
<li><code>cio</code>, <code>init</code>, <code>input</code> — I/O control</li>
</ul>
</li>
<li>Loads configurations from disk: recipes (<code>formule</code>), silo configuration (<code>magasin</code>), Interbus mapping (<code>IBS_Config</code>)</li>
<li>Then executes <code>F$Fork</code> to launch <code>master</code></li>
</ul>
<p><strong>Layer 2 — <code>master</code></strong> (orchestrator)</p>
<p>The <code>master</code> module contains a fork table at fixed 78-byte intervals — a structure I deciphered from binary analysis. Each entry: the process name and a launch ID. <code>master</code> forks all 18 processes, then enters a watchdog loop: if a process dies, it displays the error (<code>INITIALISATION IMPOSSIBLE. Erreur #%03d:%03d sur %s</code>) and shuts down the plant.</p>
<p><strong>Layer 3 — application processes</strong></p>
<p>18 processes running simultaneously, each with a precise role:</p>
<p><strong>Control core:</strong></p>
<table>
<thead>
<tr>
<th>Process</th>
<th>Size</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sequenceur</code></td>
<td>202 KB</td>
<td>Sequencer — orchestrates the complete production cycle</td>
</tr>
<tr>
<td><code>regul</code></td>
<td>110 KB</td>
<td>PID controllers — controls flow rates on conveyor belts</td>
</tr>
<tr>
<td><code>input</code></td>
<td>14 KB</td>
<td>Reads physical inputs (sensors, buttons, safety covers)</td>
</tr>
<tr>
<td><code>output</code></td>
<td>8 KB</td>
<td>Writes physical outputs (contactors, valves, motors)</td>
</tr>
</tbody>
</table>
<p><strong>Equipment communication:</strong></p>
<table>
<thead>
<tr>
<th>Process</th>
<th>Size</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>interbus</code></td>
<td>56 KB</td>
<td>Interbus-S driver — communication with distributed I/O modules</td>
</tr>
<tr>
<td><code>modbusTCP</code></td>
<td>17 KB</td>
<td>Modbus TCP client — communication with external equipment</td>
</tr>
<tr>
<td><code>srvmodbusTCP</code></td>
<td>32 KB</td>
<td>Modbus TCP server — exposes data to SCADA</td>
</tr>
</tbody>
</table>
<p><strong>Operator interface:</strong></p>
<table>
<thead>
<tr>
<th>Process</th>
<th>Size</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>visual</code></td>
<td>380 KB</td>
<td>Graphical interface — the largest module!</td>
</tr>
<tr>
<td><code>refresh</code></td>
<td>30 KB</td>
<td>Periodic screen refresh</td>
</tr>
<tr>
<td><code>look</code></td>
<td>65 KB</td>
<td>Monitoring, logging, receipt printer</td>
</tr>
</tbody>
</table>
<p><strong>Support:</strong></p>
<table>
<thead>
<tr>
<th>Process</th>
<th>Size</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>autorise</code></td>
<td>32 KB</td>
<td>Operator access code management</td>
</tr>
<tr>
<td><code>maint</code></td>
<td>166 KB</td>
<td>Preventive maintenance — part replacement timers</td>
</tr>
<tr>
<td><code>modem</code></td>
<td>41 KB</td>
<td>Remote debugging via serial modem</td>
</tr>
<tr>
<td><code>lcpc</code></td>
<td>51 KB</td>
<td>Local console (serial port)</td>
</tr>
<tr>
<td><code>ClientLAN</code></td>
<td>15 KB</td>
<td>TCP/IP client for external systems</td>
</tr>
</tbody>
</table>
<h3>Inter-process communication: shared memory as a data bus</h3>
<p>The processes don't communicate through pipes, sockets, or message queues — they use <strong>shared data modules</strong>, a native form of IPC in OS-9000. Each module is a memory region created by <code>initial</code> and accessible to all processes via the <code>F$Link</code> and <code>F$DatMod</code> system calls.</p>
┌──────────────────────────────┐
│ SHARED MEMORY │
│ │
interbus ──────────►│ share (global state) │◄─── visual
(reads sensors, │ share_ana (analog values) │ (displays on screen)
writes actuators) │ defaut (active faults) │
│ vidange (draining state) │◄─── look
sequenceur ────────►│ data_ecran (screen buffer) │ (logging)
(production cycle) │ data_reseau (network data) │
│ formule (active recipes) │◄─── regul
input ─────────────►│ cio (I/O control) │ (PID controllers)
output ◄────────────│ init (init data) │
│ input (input data) │◄─── modbusTCP
└──────────────────────────────┘ (external SCADA) <p>Why shared memory instead of messages? <strong>Speed.</strong> In a real-time system, the PID controller needs to read the weight from a sensor and adjust the belt speed every cycle — on the order of milliseconds. Copying data through messages would introduce unacceptable latency. With shared memory, <code>regul</code> reads directly from <code>share_ana</code>, with no copying, no waiting, no overhead.</p>
<p>The downside: synchronization must be managed manually. A <code>swing_buffer</code> (double buffer) appears in the <code>master</code>, <code>visual</code>, and <code>maint</code> code — a classic double-buffering technique where a producer writes to one buffer while consumers read from the other.</p>
<h3>The production cycle: how a batch of concrete is made</h3>
<p>From the operator help (in English) and error messages, we can reconstruct every step of production:</p>
<p><strong>1. Recipe selection</strong></p>
<p>The operator navigates to the <code>SERVICE DATA</code> screen (or <code>DONNEES DE SERVICE</code> in French), selects a recipe — for example "beton borduri" (curb stone concrete) — and enters the desired quantity in tons. They can adjust the conjugator (50-100%), which controls the production pace. Aggregate moisture is set manually or read from an automatic probe.</p>
<p>Each recipe defines, for each batcher, the material percentage in the dry formula, the flow rate, and the control parameters. The <code>aformule</code> module (69 KB) stores all recipes — from "stabilizat aeroport" (airport stabilized soil) to "beton borduri" (curb stone concrete).</p>
<p><strong>2. Aggregate dosing</strong></p>
<p>The plant has 4 aggregate feeders (<code>ad1</code>..<code>ad4</code>), each with:</p>
<ul>
<li>A hopper on a weighing system (load cells)</li>
<li>A variable-speed conveyor belt</li>
<li>Vibrators for sticky materials</li>
<li>Speed sensors (encoder on the belt)</li>
</ul>
<p><code>sequenceur</code> commands the feeders to start in sequence. <code>regul</code> takes over — reads the weight from <code>share_ana</code>, calculates the error against the setpoint, and adjusts the belt speed through the PID algorithm. It monitors continuously: underspeed (<code>Underspeed</code>), overspeed (<code>Overspeed</code>), weight deviation ±12%.</p>
<p><strong>3. Binder dosing</strong></p>
<p>Cement comes from silos through dosing screws (augers) or rotary feeders (alvéolaire). Control via flow meter. Checks: compressed air pressure, screw jamming (<code>Jamming on screw</code>), silo level, correct silo selection/deselection.</p>
<p><strong>4. Water and admixture dosing</strong></p>
<p>Separate pumps for water, emulsion, and admixtures. Water flow rate is adjustable ±15% from the recipe, with introduction/stop time adjustable to ±5 seconds — to compensate for pipe inertia. The operator can adjust in real time if the concrete is too wet or too dry.</p>
<p><strong>5. Mixing</strong></p>
<p>The mixer starts. A wattmeter monitors motor consumption — a direct indicator of concrete consistency. When consumption exceeds a configurable threshold, the concrete is ready. Optional: extended mixing.</p>
<p><strong>6. Discharge</strong></p>
<p>The mixer gate opens (<code>Mixer unlocking</code>). The concrete drops into the waiting hopper (trémie). A shuttle directs the flow — left or right — to the waiting truck. Level sensors: <code>Hopper full</code> / <code>Hopper empty</code>. Mechanical safety interlock: <code>Mixer locking</code> — the mixer cannot start with the gate open.</p>
<p><strong>7. Recording and delivery</strong></p>
<p>The <code>look</code> module logs every batch: formula, actual quantities per component, date/time. <code>tagueur</code> (receipt printer) generates the delivery receipt with truck code, receipt number, and quantity. The system tracks stocks and credits per client, with the option for weekly resets.</p>
<h3>Security: who gets access to what</h3>
<p>The access system is layered:</p>
<ul>
<li><strong>Operator</strong> — can select recipes, start/stop production, acknowledge alarms</li>
<li><strong>Correction code</strong> — allows modification of existing recipes</li>
<li><strong>Binder code</strong> — access to cement dosing parameters (critical for quality)</li>
<li><strong>Calibration code</strong> — for taring and calibrating the weighing systems</li>
<li><strong>Management code</strong> — access to stocks and client credits</li>
<li><strong>Maintenance code</strong> — full access: machine parameters, preventive timers, I/O configuration</li>
<li><strong>SUPER_USER</strong> — disabled in production, unlimited access (for AES technicians)</li>
</ul>
<p>Each level has its own code, validated by <code>autorise</code> and <code>get_psw</code>. This isn't advanced encryption — they're numeric codes, sufficient to prevent accidental changes in an industrial environment where operators wear gloves and have priorities other than hacking.</p>
<h3>External communication: a connected plant</h3>
<p>Although physically isolated (no internet), the plant wasn't informationally isolated:</p>
<ul>
<li><strong>Modbus TCP</strong> — the <code>srvmodbusTCP</code> server exposes dozens of data zones to an external SCADA system (PCVue by Arc Informatique): digital I/O, cumulative production per formula, product codes, machine states. An operator in a central control room could monitor multiple plants simultaneously.</li>
<li><strong>Serial modem</strong> — <code>modem</code> enabled remote connections for Service Après-Vente (after-sales service). An AES engineer in Vert-Saint-Denis could, over the phone line, connect to a plant in Romania, check parameters, and modify the configuration. In the early 2000s, this was "the cloud."</li>
<li><strong>Kermit</strong> — the serial transfer protocol <code>kermit_aes</code> served for backup/restore: exporting the configuration to a laptop connected via serial cable, or restoring a saved configuration.</li>
<li><strong>LAN TCP/IP</strong> — <code>ClientLAN</code> communicated with OMRON FINS servers (programmable logic controllers) and Pro-face terminals (graphical HMIs), for more complex installations with additional equipment.</li>
</ul>
<h3>18 processes, 64 MB, zero crashes</h3>
<p>This architecture — 18 cooperating processes via shared memory, on an x86 processor with 32 MB RAM, booted from a 64 MB CompactFlash — is an example of software engineering under absolute constraints. Every byte of RAM is explicitly allocated. Every process has a precise role. There's no garbage collector, no memory leaks (data modules have fixed sizes), no unnecessary processes.</p>
<p>The system ran for years without rebooting, producing thousands of tons of concrete, in conditions of dust, vibration, and extreme temperatures. It's the kind of software you never see — but the bridges, roads, and buildings around you depend on it.</p>
<h2>The moment of truth: the application starts in the emulator</h2>
<p>After all the analysis — reverse engineering the filesystem, binary dissection of modules, reconstructing the application architecture — it was time for the final test: starting the complete application in an emulator, with no physical hardware at all.</p>
<p>The challenge was clear: the <code>interbus</code> module (the driver for the Phoenix Contact PC979K card) directly accesses hardware — ISA I/O ports and a shared memory region (DPRAM) at physical address 0xD0000. Without the physical card, <code>interbus</code> aborts with <code>Erreur #032:032</code> and shuts down the entire application.</p>
<h3>The solution: 3 bytes + a memory block</h3>
<p>I combined two techniques:</p>
<p><strong>1. Neutralizing I/O access</strong> — I replaced the <code>in al,dx</code> and <code>out dx,al</code> instructions in the port_read/port_write functions of the <code>interbus</code> module with <code>nop</code>. Two instructions, <strong>2 bytes</strong> modified. All I/O port reads now return 0 (the "complete reset, no error" signal), all writes are silently ignored.</p>
<p><strong>2. Pre-populated DPRAM</strong> — I created a 16 KB block with the Interbus controller's status registers set to the correct values:</p>
<ul>
<li>Diagnostic register: <code>0x00E0</code> (RUN + ACTIVE + READY)</li>
<li>Status Register 1: <code>0x8800</code> (both nodes ready)</li>
<li>Control: <code>0x4000</code> (data cycle active)</li>
</ul>
<p>This block is loaded by QEMU at physical address 0xD0000 — exactly where <code>interbus</code> expects to find the PC979K's DPRAM.</p>
<p><strong>3. The PIT 8253 timer</strong> — a single byte: <code>%set_ticker</code> → <code>*set_ticker</code> in the startup script. Hardware timer calibration doesn't work in the emulator, but it's irrelevant for the application.</p>
<p>Total: <strong>3 bytes</strong> modified on a 64 MB image, plus a memory blob. And then:</p>
<h3>The DONNEES DE SERVICE screen</h3>
<figure class="blog-image">
<img src="/blog/img/screenshot_gui_working.png" alt="AES Compumat interface - DONNEES DE SERVICE live in QEMU" loading="lazy">
<figcaption>The AES Compumat operator interface — DONNEES DE SERVICE (Service Data) screen running live in the QEMU emulator, 20+ years after the software was written</figcaption>
</figure>
<p>On the blue screen of the emulated QVT terminal, the operator interface displays:</p>
Formule 0 DONNEES DE SERVICE 15/03/26 11:15:56
< ESSAI >
Att APPELLATIONS % %H2O DEBIT 0% 100%
GRA.
GRA.
GRA.
GRA.
GRA.
GRA.
LIA.
LIA.
EMU.
ADJ.
ADJ.
EAU
EAU
TC : 0 0 TD1: 1 1 TOTAL :
ED : TD2: - DEBIT TM : 0.000
MARCHE CENTRALE : 0 0 QT DEMANDEE : 0.000 PRESELECTION : 0.000
MARCHE MATERIAUX : 0 0 QT DELIVREE : 0.000 0.000
CONJUGATEUR en % : 100 % QT CASQUE : 0.500
CURSEUR: Home SOMMAIRE: Esc GUIDE: ? <p>Every line has a purpose:</p>
<ul>
<li><strong>GRA.</strong> × 6 — six aggregate feeders (gravel and sand, various grain sizes)</li>
<li><strong>LIA.</strong> × 2 — two binder silos (cement)</li>
<li><strong>EMU.</strong> — emulsion batcher</li>
<li><strong>ADJ.</strong> × 2 — two admixtures (plasticizers, setting accelerators)</li>
<li><strong>EAU</strong> × 2 — two water circuits</li>
<li><strong>ESSAI</strong> — test mode (the plant isn't producing, only verifying parameters)</li>
<li><strong>CONJUGATEUR 100%</strong> — production pace at maximum</li>
<li><strong>QT CASQUE 0.500</strong> — 500 kg hopper capacity</li>
</ul>
<p>The date in the corner: <strong>15/03/26</strong> — March 15, 2026. The OS-9000 clock works correctly in the emulator, 20+ years after the software was written.</p>
<p>This is the screen the plant operator saw every day, in the control cabin, with cement dust on their hands and phone calls from the construction site. A screen that commanded the production of thousands of tons of concrete — from the curb stones in a small town to the stabilized soil on an airport runway.</p>
<p>And now it's running on a laptop, in an emulator, on an operating system nobody uses anymore, without a single gram of cement within hundreds of kilometers.</p>
<h2>What I learned</h2>
<div class="blog-conclusion">
<p>This 64 MB card contains a complete software ecosystem: operating system, drivers, industrial application, configurations, recipes, and a network stack. Everything — including FTP, Telnet, five languages, and a gzip compressor — in less space than a single photo from your phone.</p>
<p><strong>Simplicity is resilience.</strong> A system that has been running for over 15 years without updates, without antivirus, without an internet connection. It boots in seconds, not minutes. It has no unnecessary background processes. Every byte has a purpose.</p>
<p><strong>Documentation vanishes, hardware wears out, but data persists.</strong> I couldn't find a single modern tool that natively reads this filesystem. The OS-9000 manufacturer (Microware) was acquired by RadiSys, then by Eurotech. The AES software appears to be abandoned. But the data on the card is intact and readable — if you know where to look.</p>
<p><strong>Reverse engineering is an art of patience.</strong> I reconstructed the structure of a proprietary filesystem starting from public documentation for a different architecture (6809/68000, big-endian) and adapting it for x86 (little-endian, 32-bit addresses). The key was a +1 offset between the logical address and the physical sector — a detail that no documentation mentioned.</p>
<p><strong>Backups are critical.</strong> This CompactFlash is no longer manufactured. If it had failed, the entire plant configuration — recipes, parameters, calibrations — would have been lost. Without a backup, getting the plant back online would have meant weeks of work.</p>
<p><strong>Emulation beats simulation.</strong> I first tried to fully simulate the Interbus card — with DPRAM, status registers, initialization sequences. It wasn't enough. The solution came from combining two minimalist techniques: neutralizing two I/O instructions (2 bytes) and pre-populating shared memory (16 KB). Sometimes, the simplest approach works best.</p>
</div>
<hr>
<p class="blog-endnote">All work was performed on copies of the original image. The original remains untouched, protected as read-only — the first rule of digital forensics.</p>
<p class="blog-endnote">The scripts used in this investigation — the RBF extractor and the QEMU boot loader — are available as Python scripts, written from scratch for a filesystem and an operating system that nobody uses anymore.</p>