// Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. // See LICENSE.md for license information. // SPDX-License-Identifier: MIT // // szddExpand.c - expand a file compressed by Microsoft's COMPRESS.EXE // (SZDD, the WINGDE.DL_ kind on Windows 3.x setup disks), what EXPAND.EXE // or msexpand do, so the installer needs neither. // // The format: the 8-byte magic, a mode byte ('A'), the name's last // character (the one the _ replaced), the expanded length (32-bit // little-endian), then LZSS: a control byte, its bits low first, a 1 // meaning a literal byte and a 0 a two-byte match -- offset // b0 | (b1 & 0xF0) << 4 into a 4096-byte window that starts filled with // spaces and written from 4096 - 16, length (b1 & 0x0F) + 3. // // szddExpand IN OUT #include #include #include #include "toolFile.h" #define TOOL_NAME "szddExpand" #define USAGE_EXIT 2 #define MAGIC "SZDD\x88\xF0\x27\x33" #define MAGIC_BYTES 8u #define LENGTH_AT 10u #define HEADER_BYTES 14u #define WINDOW_BYTES 4096u #define WINDOW_MASK (WINDOW_BYTES - 1u) #define WINDOW_START (WINDOW_BYTES - 16u) #define WINDOW_FILL 0x20u #define MATCH_MIN 3u #define CONTROL_BITS 8u #define OFFSET_HIGH 0xF0u #define LENGTH_MASK 0x0Fu #define OFFSET_SHIFT 4u int main(int argc, char **argv) { uint8_t window[WINDOW_BYTES]; uint8_t *data; uint8_t *out; size_t len; size_t n; size_t i; uint32_t length; uint32_t pos; uint32_t bit; uint32_t control; uint32_t offset; uint32_t count; uint32_t k; uint8_t byte; toolFileName(TOOL_NAME); if (argc != 3) { fprintf(stderr, "usage: %s IN OUT\n", TOOL_NAME); return USAGE_EXIT; } if (!toolReadFile(argv[1], &data, &len)) { toolFail("cannot read %s", argv[1]); } if (len < HEADER_BYTES || memcmp(data, MAGIC, MAGIC_BYTES) != 0) { toolFail("%s is not an SZDD file", argv[1]); } length = (uint32_t)data[LENGTH_AT] | ((uint32_t)data[LENGTH_AT + 1u] << 8) | ((uint32_t)data[LENGTH_AT + 2u] << 16) | ((uint32_t)data[LENGTH_AT + 3u] << 24); // A match may run past the length by its last few bytes before the // count is checked: room for the longest one more. out = (uint8_t *)malloc((size_t)length + LENGTH_MASK + MATCH_MIN); if (out == NULL) { toolFail("out of memory"); } memset(window, WINDOW_FILL, sizeof(window)); pos = WINDOW_START; n = 0u; i = HEADER_BYTES; while (i < len && n < length) { control = data[i++]; for (bit = 0; bit < CONTROL_BITS; bit++) { if (i >= len || n >= length) { break; } if ((control & (1u << bit)) != 0u) { byte = data[i++]; out[n++] = byte; window[pos] = byte; pos = (pos + 1u) & WINDOW_MASK; continue; } if (i + 1u >= len) { break; } offset = (uint32_t)data[i] | (((uint32_t)data[i + 1u] & OFFSET_HIGH) << OFFSET_SHIFT); count = (data[i + 1u] & LENGTH_MASK) + MATCH_MIN; i += 2u; for (k = 0; k < count; k++) { byte = window[(offset + k) & WINDOW_MASK]; out[n++] = byte; window[pos] = byte; pos = (pos + 1u) & WINDOW_MASK; } } } if (n != length) { toolFail("expanded %zu bytes, the header says %u", n, length); } if (!toolWriteFile(argv[2], out, n)) { toolFail("cannot write %s", argv[2]); } free(out); free(data); return 0; }