Microsoft EXEPACK

From ModdingWiki
Jump to navigation Jump to search
Microsoft EXEPACK
Format typeCompression algorithm
TypeStream
I/O unit size1-bit
Games

Microsoft EXEPACK is a an executable file compressor used by several classic games.

EXEPACK compression could be added to a program by LINK.EXE /EXEPACK switch or by invoking EXEPACK.EXE utility, that was distributed with MASM.

Tools

  • exepack
    • https://www.bamsoftware.com/software/exepack/
    • Open Source (Public Domain), updated 2022.
    • Rust-based, can unpack or pack executables with the exepack algorithm, very deeply tested with many versions of the Microsoft Linker and standalone exepack
    • The most mature and superior option for handling EXEPACK files.
  • UNP (aka unp411)
    • https://bencastricum.nl/unp/
    • Source available (license unknown), updated 1995.
    • Supports many compression formats besides EXEPACK.
    • DOS assembly which works by setting breakpoints, running the target executable's own decompression algorithm, then dumping the contents of the uncompressed EXE from memory. Only works in DOS environment.

Games using EXEPACK

Some games utilize EXEPACK as an inner layer of their compression:

Game list extracted from Total DOS Collection

Games packed with EXEPACK

Very large list of games that use EXEPACK: https://w4kfu.github.io/unEXEPACK/files/exepack_list.html

File Format

offset     length    purpose
0          0x1C      DOS exe header
???        ???       packed exe
           0x12      unpacker vars (EXEPACK variables, see below)
           0x105     unpacker code
           0x16      string "Packed file is corrupt"
           ???       packed reloc table (see below for more information)

offset to packed exe = header * 16 (from exe header)
length of packed exe = CS:IP (from exe header)
length of packed reloc table = exepack_size - dest_len (from exepack variables)

EXEPACK variables

Variables used by the exepack unpacker (all except mem_start are pre-initialized):

Data type Name Description
UINT16LE real_IP real start address (offset)
UINT16LE real_CS real start address (offset)
UINT16LE mem_start start of the exe in memory (segment)
UINT16LE exepack_size size of unpacker vars + unpacker code + error string + packed reloc table in bytes
UINT16LE real_SP real stack (offset)
UINT16LE real_SS real stack (segment)
UINT16LE dest_len destination of the unpacker code (in paragraphs, relative to start of exe in memory)
UINT16LE skip_len number of paragraphs between packed exe and unpacker variables + 1
UINT16LE signature "RB" (magic number of exepacked files)

Relocation Table

packed relocation table = section_0, section_1, ..., section_0xf
section = number_of_entries [can be zero], set of entry [can be empty]
number_of_entries = unsigned word (16 bits)
entry = unsigned word (16 bits)

An entry in section n patches the segment value at:
0x1000*n + entry (relative to the start of the exe in memory)

Decompression algorithm

The exepack unpacker first copies itself to the location stored in dest_len. (the value in dest_len also equals the unpacked exe's size in paragraphs). It then executes a retf to the new location and starts unpacking. The unpacking algorithm works like this:

  int srcPos; /* start at the end of the packed exe, because the unpacker works downwards */
  int dstPos;
  int commandByte, lengthWord, fillByte;

  /* skip all 0xff bytes (they're just padding to make the packed exe's size a multiple of 16 */
  while (*srcPos == 0xff) {
    srcPos--;
  }

  /* unpack */
  do {
    commandByte = *(srcPos--);
  
    switch (commandByte & 0xFE) {
      /* (byte)value (word)length (byte)0xb0 */
      /* writes a run of <length> bytes with a value of <value> */
      case 0xb0:
        lengthWord = (*(srcPos--))*0x100;
        lengthWord += *(srcPos--);
        fillByte = *(srcPos--);
        for (i = 0; i < lengthWord; i++) {
          *(dstPos--) = fillByte;
        }
        break;
      /* (word)length (byte)0xb2 */
      /* copies the next <length> bytes */
      case 0xb2:
        lengthWord = (*(srcPos--))*0x100;
        lengthWord += *(srcPos--);
        for (i = 0; i < lengthWord; i++) {
          *(dstPos--) = *(srcPos--);
        }
        break;
      /* unknown command */
      default:
        printf("Unknown command %x at position %x\n", commandByte, srcPos);
        exit(1);
        break;
    }
  } while ((commandByte & 1) != 1); /* lowest bit set => last block */

Notes:

  • The sizes of both the packed exe and the unpacked exe are multiples of 16
  • The unpacker code unpacks the exe onto itself, i.e. the unpacked exe has the same starting address (in memory) as the packed exe (in memory).

Credits

This information was adopted from http://cvs.z88dk.org/cgi-bin/viewvc.cgi/xu4/doc/avatarExepacked.txt?revision=1.1&root=zxu4&view=markup by aowen. The source page said:

Please send additions, corrections and feedback to this e-mail address: Remove space + vowels from "marc winterrowd" and append "at yahoo dot com"