115 lines
3.7 KiB
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;
|
|
}
|