Код:
#!/usr/bin/env python3
# Copyright 2025 TIsland Crew
# SPDX-License-Identifier: Apache-2.0
# ZXS archive unpacker, see
# https://zx-pk.ru/threads/11422-versii-igr-i-softa-najdennye-na-kassetakh.html?p=1210270&viewfull=1#post1210270
from sys import argv
from struct import pack, unpack
DEBUG = False
def chksum(*args):
chksum = 0
for data in args:
for b in data: chksum ^= b
return chksum
def zxs_unpack(n: str):
print(n, 'to TAP')
with open(f'{n}.tap', 'wb') as t:
with open(n, 'rb') as f:
zxs_block_read(f, t)
print(n, 'TAP ready')
def zxs_block_read(f, t):
# phase 1: read "container" blocks
blkn = 0
while buf := f.read(2):
blkn += 1
size = unpack('<H', buf)[0]
if 0 == size: break
print('#{: 2d} @0x{:06x} length {:d}'.format(blkn, f.tell()-2, size))
# phase 2: unpack RLE encoded data for each block
# NOTE: so far in all files we've seen the RLE sequence ends with 00 00
# hence there was no need to track RLE block size
zxs_rle_unpack(f, t)
def zxs_rle_unpack(f, t):
print(' ', end='')
data = bytearray(2) # placeholder for unpacked size (2 bytes)
while buf := f.read(2):
( count, flag ) = unpack('<BB', buf)
if 0 == count and 0 == flag:
break # end of RLE encoded block
count += (flag&0x7f)<<8
if 0x80 == flag&0x80: # repeating byte
b = f.read(1)
if DEBUG: print('0x{}X{:d};'.format(b.hex(), count), end='')
data.extend(b * count)
else: # arbitrary sequence
if DEBUG: print('b{:d};'.format(count), end='')
data.extend(f.read(count))
cs = chksum(data[2:-1]) # unpacked data checksum
csa = unpack('<B', data[-1:])[0] # recorded checksum
data[0:2] = pack('<H', len(data)-2) # unpacked block length
if DEBUG: print(' checksum {:d} vs {:d}'.format(cs, csa))
if csa != cs:
print(' WARNING: checksum mismatch, fixed')
data[-1] = cs
print(' Unpacked size: {:d}'.format(len(data)-2))
t.write(data)
if __name__ == '__main__':
for arg in argv[1:]:
if '-d' == arg:
DEBUG = not DEBUG
else:
zxs_unpack(arg)
# EOF vim: et:ai:ts=4:sw=4: