81 lines
2.5 KiB
Python
Executable file
81 lines
2.5 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
# See LICENSE.md for license information.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
"""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.
|
|
|
|
Usage:
|
|
tools/szddExpand.py IN OUT
|
|
"""
|
|
|
|
import sys
|
|
|
|
MAGIC = b"SZDD\x88\xf0\x27\x33"
|
|
HEADER_BYTES = 14
|
|
WINDOW_BYTES = 4096
|
|
WINDOW_MASK = WINDOW_BYTES - 1
|
|
WINDOW_START = WINDOW_BYTES - 16
|
|
WINDOW_FILL = 0x20
|
|
MATCH_MIN = 3
|
|
|
|
|
|
def expand(data):
|
|
if data[:len(MAGIC)] != MAGIC:
|
|
raise ValueError("not an SZDD file")
|
|
length = int.from_bytes(data[10:HEADER_BYTES], "little")
|
|
window = bytearray([WINDOW_FILL]) * WINDOW_BYTES
|
|
pos = WINDOW_START
|
|
out = bytearray()
|
|
i = HEADER_BYTES
|
|
while i < len(data) and len(out) < length:
|
|
control = data[i]
|
|
i += 1
|
|
for bit in range(8):
|
|
if i >= len(data) or len(out) >= length:
|
|
break
|
|
if control & (1 << bit):
|
|
byte = data[i]
|
|
i += 1
|
|
out.append(byte)
|
|
window[pos] = byte
|
|
pos = (pos + 1) & WINDOW_MASK
|
|
else:
|
|
if i + 1 >= len(data):
|
|
break
|
|
b0 = data[i]
|
|
b1 = data[i + 1]
|
|
i += 2
|
|
offset = b0 | ((b1 & 0xF0) << 4)
|
|
for k in range((b1 & 0x0F) + MATCH_MIN):
|
|
byte = window[(offset + k) & WINDOW_MASK]
|
|
out.append(byte)
|
|
window[pos] = byte
|
|
pos = (pos + 1) & WINDOW_MASK
|
|
if len(out) != length:
|
|
raise ValueError("expanded %d bytes, the header says %d" % (len(out), length))
|
|
return bytes(out)
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) != 3:
|
|
sys.stderr.write(__doc__)
|
|
return 2
|
|
with open(sys.argv[1], "rb") as f:
|
|
data = f.read()
|
|
with open(sys.argv[2], "wb") as f:
|
|
f.write(expand(data))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|