joeylib2/tools/szddExpand/szddExpand.c

115 lines
3.7 KiB
C

// 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}