LC-Executable | DOS/32a Extender
Linear Compressed Executable | DOS/32a executable format
The internet remembers everything, and this infamous format is not an exception. Unfortunately, people, who want to find some of information about - couldn’t find it. The Linear compressed executables bound to DOS/32a extender very hard. And if you don’t know about this extender - you can’t find anymore about this details.
In another words: if we will trust in some internet segment, the LC executable format
is a DOS/32a feature.
Little about DOS32/a | Technical details review
The DOS/32a extender is a freeware for now, and sources of it are store in GitHub repository. All what we need – find anything about supporting programs and games by our target.
DOS/32a supports
-> LC (extender's) executables
-> LE (Windows-OS/2) executables;
-> LX (OS/2-ArcaOS) executables;
-> MZ real-mode DOS executables;
The real-time DOS executables and other programs we may convert or compress and run with other options for extender’s toolkit.
That’s all we need to run it.
The DOS/32a extender has some parts
DOS32A.EXE #Main real-mode DOS executable
+--> SVER.EXE
+--> SC.EXE #Compressor
+--> SB.EXE #Builder
+--> SS.EXE #Stub configuration
+--> STUB32A #Advanded (default) stub <--+ Those files just code objects
+--> STUB32C #Configurable stab <--+ but marked with .EXE
This extender may bind with Watcom toolkit, and proceed projects in OS/2-DOS modules
we can process again with DOS/32a. Or we may configure DOS/32a for DOS32a stub insertions.
It has 3 types of stub by itself, and help page represents us the choise:
I’ve run under eComStation (OS/2 Warp 4.5) because I need a support of OS/2 standard modules and DOS emulation.
For test a file format we don’t need all package, just compressor SC.EXE.
Who wants to be compressed? | Victim details review
Before we run 1.44MiB virtual diskette with prepared DOS/32a package and testable file for target compressor.
Let’s define all what I want.
My tasks for this run:
- Mount diskette
- Run under DOS session
- Compress the target executabe;
My target was a DOOM II "Hell on the Earth" for OS/2. So, then we have a well linked
LE executable program.
I want to see just two things:
- Objects Table;
- Fixup Records;
The first table here at is:
| # | Name:s | VirtualSize:4 | RelBase:4 | FlagsMask:4 | PageMapIndex:4 | PageMapEntries:4 | Unknown:4 | Flags:s |
|---|---|---|---|---|---|---|---|---|
| 1 | .CODE | 0x0001BEA0 | 0x00010000 | 0x00002045 | 0x00000001 | 0x0000001C | 00000000 | SINGLE PRELOAD_PAGES USE_32 |
| 2 | .DATA | 0x0003EEC0 | 0x00030000 | 0x00002043 | 0x0000001D | 0x00000005 | 00000000 | SINGLE PRELOAD_PAGES USE_32 |
Remember: This is an LE compiled and linked program module without version and
module directives. We can’t find more details about this file. (Verification records are missing, and external fixups too)
The EntryTable of DOOM.EXE are empty.
But 2 objects with all sources are there. And we need it magic number (number of Objects) later.
Here is a Fixup records common data:
| Source | TargetFlags | SourceOffset | HasSourceList | HasAdditive | Is32BitTarget | Is32BitAdditive | Is16BitObjectModule | Is8BitOrdinal | TargetData | AdditiveValue | SourceOffsetList | # |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0x07 | 0x10 | 0x081A | False | False | True | False | False | False | Internal Target | 0x00000000 | 0 | |
| 0x07 | 0x10 | 0x0800 | False | False | True | False | False | False | Internal Target | 0x00000000 | 1 |
I’ve truncated it. For real the count of relocations here equals 5951. This document makes a boom if I insert it.
This is a FLAT32 pointers, just because objects of code and data are 32-bit. (look at the Objects Table).
Finally we don’t need segments and FAR pointers!
I’ll not show you details of internal targets. They are pointers to functions in statically linked modules bound with this EXE.
I’ve decided my DOOM.EXE is legal and troubles with rebuild/compression couldn’t happened.
Tests
Finally, I’ve moved file into diskette, ejected it and run OS/2 with inserted virtual disk with letter A.
The help page of SC.EXE don’t desribes syntax rules a little.
And I’ve tell it here:
Firstly we need iterate all flags for target program.
Secondary tell output file path in filesys.
A:\> SC.EXE </flag /flag /flag...> <target.exe>
Our special flags will be
- Insert default stub (
DOS32a); - Don’t apply optimizations;
- Verbose (2’nd level);
And my target is ready to be compressed into new format. Let’s do it!

I suppose, flag /D (Disable optimizations) depends on compression very hard. (Obvious :D).
My target DOOM engine for OS/2 has size about 230K. After SC call it was reformatted and differs with linear bro with 30 kilobytes.
Do you remember what I’ve told about objects? It’s really found 2 objects with data and code. And following fixup records by fixup pages are follows next after objects header.
Our victim ready to be explored and explained for now.
We have produced LC-executable what can runs only
through the DOS32A.EXE. If we look up at the sky and down at the
backend of DOS32A compressor, we’ll see next part of tree:
...
| +dos32a
| |-+text
| | +-> changes.txt
| | +-> kernel.asm
| | +-> loadpe.asm
| | +-> loadlc.asm
| | +-> make.bat
| | +-> makebeta.bat
| + +-> notes.txt
| +sb
| | ...
...
This is not disassembled project, you’re right. This is an actually backend part of extender.
Linear Compressed | Format review
Only three sources in all project what describes this format little better are the files
loadlc.asm (.../dos32a/text/loadlc.asm);sccomp.asm (.../sc/sccomp.asm);sload.asm (.../sc/sload.asm);
All what I want to write here – are belongs to those three files.
The compressed file’s layout looks like this:
┌───────────────────────┐
│ IMAGE_DOS_HEADER <-- MZ Header
├───────────────────────┤
│ This could be a stub <-- Data here depends on configured
│ but this is a DOS32a │ compressor and builder.
│ or DOS32c stub │ STUB32A.EXE or STUB32C placed here.
├───────────────────────┤
│ IMAGE_DOS32_HEADER <-- LC Header
├───────────────────────┤
│ Object #1 Header │ <-- 16 Bytes
├───────────────────────┤
│ 00 01 FF 4D D6 90 20 <-- Deompressed or raw code/data
│ 33 00 00 03 00 04 FF │ contains here.
│ 56 09 DA F0 0B CC 3C │ Strictly by the header.
├───────────────────────┤
│ Object #2 Header │
├───────────────────────┤
│ 03 00 05 DD 0A 5F 4D <-- Same idea.
├───────────────────────┤
│ *Fixups Header* <-- Fixup records header
├───────────────────────┤
│ ??? │ ← Decompressed/raw Fixups
└───────────────────────┘
There’s no tables like in linear executables (LE/LX binaries). There flat layout of all file. All data follows strictly by headers about those data.
The loader header equals the 1 Intel paragraph (or 16 BYTEs).
And each header of following objects equals 1 Intel paragraph too.
Linear Compressed Header
Let’s name it not only IMAGE_DOS32_HEADER. It seems
the same with infamous Windows API named MZ header struct (a.k.a. IMAGE_DOS_HEADER).
#[repr(C, packed)]
struct LcHeader {
e32_magic: [u8; 4], // "LC\0\0"
e32_objcnt: u8, // Number of Objects
e32_ver: u8, // LC_SPECVER = 0x04
e32_startobj: u8, // Number of object with set-EIP
e32_stackobj: u8, // Number of object with set-ESP
e32_eip: u32, // Offset in EIP-set object
e32_esp: u32, // Offset ESP-set object
}
I suppose, first object will be CODE object with loaded
instructions. And second object will be a stack.
As we runs under DOS, as DOS32A wants, We need back
to the segmend addresses world.
The offsets
The Linear Compressed header really reminds about Linear Executables header. Let’s compare them right now
┌────────────────────────────────────┐
│ Explicit Offsets │
│ Field LC LE LX │
│e32_ver 0x05 0x0E 0x0E│
│e32_startobj 0x06 0x24 0x24│
│e32_stackobj 0x07 0x32 0x32│
│e32_eip 0x08 0x28 0x28│
│e32_esp 0x0C 0x34 0x34│
│e32_objcnt 0x04 0x4C 0x4C│
└────────────────────────────────────┘
Object Header
After the LC header the object header structure follows.
If you are compress the Linear executable: all object flags what have taken
from ObjectTable are moved to compressed format into Object headers.
#[repr(C, packed)]
struct LcObjectHeader {
virtual_size: u32, // Bitmask: 31=encoded(0)/not_encoded(1)
compressed_size: u32, // Size of compressed data
flags: u16, // Flags of Objest (takes from LE/LX linked module)
extended_flags: u16, // Extended (always 0)
page_table_index: u16, // Index in PageMap/Table
num_page_entries: u16, // Number of page entries
// The raw/compressed data strictly follows next
}
Fixup Header
After all headers follows big section named “Fixup Data” and header of Fixup data having 12-bytes size.
#[repr(C, packed)]
struct LcFixupsHeader {
uncompressed_size: u32, // Bits: 31=encoded(0)/not_encoded(1)
compressed_size: u32, // Size of compressed data
fixup_table_offset: u32, // FixupRecords Table Offset
// LE/LX Fixup data goes next
}
Fixup information inherits from Linear executable format and
layout of FixupPagesTable and FixupRecordsTable are described very good
in IBM Linear eXecutable module format manual. See any revision for more details.
Little more
- If you look at the,
loadlc.asmand jump todecompress_datalabel, You’ll may see that LC data compression bases on LZ algorithm or something same with it; - Rewrite already LC executable back to LE/LX not possible; (see FAQ of DOS/32a)