This is an old revision of the document!
Table of Contents
June work
2025-06-29
lDOS boot
- Update attribution year of testboot.asm
- doc: List FreeDOS protocol file size limits (128 KiB up to 134 KiB)
- Add mak script to build testpl.com and testboot.bin
- doc: Mention the advantage of segment 200h load, mention lkernpl
lDebug
- doc: Add references/links to each section in the quick start
insref
Add the section iref-prefix listing all prefixes (8086, 286, 386). Additionally, add the REPZ and REPNZ synonyms for REPE and REPNE.
wwwecm.scr
Add lzexe to automatic current release builds, and to documentation builds, and add toolchain for FreePascal i8086 DOS target. (The FreePascal build of lzexe isn't used by the current builds yet.)
webecm
List LZEXE in a section on the page, and add a news item about it.
LZEXE
Robert pointed out the recent source release of Fabrice Bellard's LZEXE executable packer to me (on 2025-05-24). Initially it seems the release wasn't accompanied by a free software license so I didn't want to look into it. However, a while later someone else blogged about it and noted the MIT license is applicable to the sources. That's when I picked up the sources and started porting and extending them. (It seems that Robert did initially load an archive without the license, but it was silently added later.)
Building the initial source release proved difficult. The A86 source for the depacker stub was missing a segmentation directive and a label with a public directive. It's a riddle how this was supposed to build. I tried out several A86 versions and none of them worked with the file as is.
Next, the stub size was hardcoded in lzexe.pas which is a bizarre choice. I did fix this later also.
It also became clear that the provided sources do not exactly match any of the three binaries of LZEXE. This may be in part due to the choice of compiler and its version. (I use TurboPascal 5.0 to build.) But the v0.91 lzexe.exe file also contains a "chemin" keyword in its help display that doesn't match the provided sources.
The documentation does exactly match v0.91 though. This revision of the documentation was all in french, much like most comments and messages in the sources.
The new stub format
I iterated on the stub format several times, producing the LZE0 to LZE6 formats and finally LZX0. In the final format:
- Instead of placing 7 word variables at the start of the buffer, they're inlined into instruction immediates.
- The variables are found by unlzexe using search patterns to match the code snippets.
- There are 8 different variants of the stub:
- With/without relocation table. If the relocation table would be empty, it can be omitted from the stub.
- With/without the segment change handling. If the decompressed image is smaller than 40 KiB (A000h bytes) this handling can be omitted from the stub.
- With getbit inlined or not. This is purely a speed vs size tradeoff.
- The choice of the variants can be influenced somewhat by six switches to LZEXE:
- /S (stop optimisations) vs /O (optimise reloc table and seg change)
- The default is /2 /O /J
The /1 option won't recreate an exactly identical output file to v0.91 because of some assembly encoding choices, and several or reg, reg to test reg, reg changes I did to the original stub.
As unlzexe 0.9G2 (via) uses part of the stub to detect the format, it very likely will fail to depack files written by our LZEXE with the /1 switch.
Relocation table format
The compressed data format for the LZX0 image did not change, much like between LZEXE v0.90 and v0.91. The relocation table format changed slightly from v0.91: Instead of a first byte equal to 0 being the encoding for a longer table entry, now the first byte equal to 255 is used for this purpose.
This allows to encode zero-difference entries. This includes duplicate relocation entries (more than one pointing at the same address) as well as a relocation entry at the very beginning of the executable image. The former is arguably invalid, albeit DOS supports it. But the latter could very well occur in a valid file.
LZEXE v0.91 would corrupt its relocation table upon encountering a zero-difference relocation. In our fork, selecting the v0.91 format with the /1 switch will instead detect and reject files with such zero-difference relocation entries.
Porting
The A86 to NASM port was trivial with fixmem and ident86. (exepack.nas, lzss.nas fixmem and identicalising.)
The Pascal files originally built with TurboPascal 5.0 and I ported them to also build with FreePascal 3.2.2 using its i8086 msdos target. Needed changes:
- Repeating function parameter and return types in the definition of functions, instead of relying only on the declaration. (There seems to be a FreePascal option to accept the old style but it doesn't harm us to change these.)
- Marking variables for use by the assembly language object files using
;export(This is not accepted by TurboPascal.) As advised on the FreePascal forum. - Selecting the large memory model to make some far pointers work. Notably the copy from PSP:80h in cmdline.pas (as I queried on the FreePascal forum) and the function pointers in lzutil.pas. This also allows getmem to allocate more than 64 KiB in total (across multiple getmem calls).
- Changing the section directives in lzss.nas to accommodate FreePascal's linker. This involved the repeated warning message
Warning: Encountered an OMF reference to a section, that has been removed by smartlinking: DATA||. I also asked about this on the FreePascal forum but ultimately figured it out by my self. This also turned out to be incompatible to TurboPascal's linker so we now create two object files, lzss.obj for TurboPascal, and lzss.o for FreePascal.
The added export keywords as well as some compiler options that generate warnings in FreePascal are gated using {{$IFDEF FPC}} constructs that fortunately are supported by both compilers.
Other changes
- UPACKEXE won't create an invalid relocation table entry if the offset in the packed relocation table is equal to FFFFh.
- COMTOEXE will add a stub to push a zero word on top of the stack. It will also limit the input file size.
- Everything is translated to english.
- unlzexe is added from v0.5 shipped with LZEXE v0.91e, with one change picked from v0.7 (extracted from v0.9G2). It can detect and unpack any LZX0 variant created by our LZEXE.
- unlzexe has more optional outputs, selected using the DEBUG variable. It can also be run in dot mode with the second parameter equal to a lone dot, in which case it doesn't write its output anywhere.
- LZX0 stub is optimised for size in several more ways than just embedding the variables. Its maximum size changed from 330 bytes to 305 bytes.
- /L switch to LZEXE allows to keep output file even if it isn't smaller than the input file, for testing purposes.
- /# switch to LZEXE allows to hack the two signature bytes after the "LZ" signature. Only printable ASCII allowed.
- The stub objects include a preparation function each. This is called by the host Pascal code, and it sets some variables exported by the Pascal code. The prepare function also returns a far pointer to its associated depacker stub, avoiding the need to dispatch for two statements.
- In several places either a ZM or MZ signature for the EXE header is allowed.
- The lzexedat tool depicts how to use lzss.nas to build a plain compression format. No depacker exists for this format yet.
