Files
LibretroAdmin 1ff51405c6 archive: rar archives
The archive layer reads a third kind of archive besides zip and 7z,
through libretro-common's rrar_archive (the last patch): it is handed
the archive's bytes, the mapping or the file read into memory; a stored
member of a mapped archive is the mapping itself, any other is unpacked
when it is asked for and kept for the archive's lifetime like a deflated
one.

What a rar does not get is a place in the "archive#member" paths the
file layer opens: libretro-common's path_get_archive_delim() knows zip,
7z and apk. So a disc image inside a rar is not offered
(archive_resolve_disc() says no); the members are for those who open
the archive themselves, which the next patch does for NAOMI images.

tools/archive: the fixtures gain stored.rar, the GD-ROM fixture's files
stored in the RAR 2.9 format, written out by hand in make_fixture.py,
which archive_test checks like the others in the mapped and the
unmapped build; and run.sh builds and runs rar_test.
2026-10-08 05:00:52 +02:00

104 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""Package the GD-ROM fixture of tools/chd/make_fixture.py as archives.
make_fixture.py <workdir>
Writes <workdir>/src/{disc.gdi,track01.bin,track02.raw,track03.bin} and:
stored.zip every member stored (method 0): a mapped archive hands
these out in place, nothing is copied or decoded
deflate.zip every member deflated
subdir.zip deflated, members under GAME/, plus a decoy .cue so the
disc pick has to prefer the .gdi
solid.7z one LZMA2 folder holding all members
single.7z track03.bin alone, so the member is its folder's whole
output and is handed over without a copy
suffix stored.zip again under a name with no extension, to be
opened as "suffix" + ".zip"
stored.rar every member stored, in the RAR 2.9 format, written out
by hand here (no free tool packs one; packed ones, made
by RAR itself, are in tools/archive/rar)
"""
import os
import shutil
import subprocess
import sys
import zipfile
import py7zr
import struct
import zlib
MEMBERS = ['disc.gdi', 'track01.bin', 'track02.raw', 'track03.bin']
def main():
work = sys.argv[1]
src = os.path.join(work, 'src')
os.makedirs(src, exist_ok=True)
here = os.path.dirname(os.path.abspath(__file__))
subprocess.check_call([sys.executable,
os.path.join(here, '..', 'chd', 'make_fixture.py'),
os.path.join(src, 'disc'), '--gdi'])
def zip_of(name, method, prefix='', decoy=False):
with zipfile.ZipFile(os.path.join(work, name), 'w', method) as z:
if decoy:
z.writestr(prefix + 'other.cue', 'FILE "x.bin" BINARY\n')
for i, m in enumerate(MEMBERS):
# Extra fields of different lengths on every member, as
# Info-ZIP and 7-Zip write them (timestamps, unix
# attributes): the directory walk has to step over them.
info = zipfile.ZipInfo.from_file(os.path.join(src, m),
prefix + m)
info.compress_type = method
ut = b'\x55\x54\x05\x00\x03' + bytes(4)
ux = b'\x75\x78\x0b\x00\x01' + bytes(10)
info.extra = (ut, ut + ux, ux, b'')[i % 4]
with open(os.path.join(src, m), 'rb') as f:
z.writestr(info, f.read())
zip_of('stored.zip', zipfile.ZIP_STORED)
zip_of('deflate.zip', zipfile.ZIP_DEFLATED)
zip_of('subdir.zip', zipfile.ZIP_DEFLATED, 'GAME/', True)
shutil.copyfile(os.path.join(work, 'stored.zip'),
os.path.join(work, 'suffix.zip'))
with py7zr.SevenZipFile(os.path.join(work, 'solid.7z'), 'w') as z:
for m in MEMBERS:
z.write(os.path.join(src, m), m)
with py7zr.SevenZipFile(os.path.join(work, 'single.7z'), 'w') as z:
z.write(os.path.join(src, 'track03.bin'), 'track03.bin')
write_stored_rar(os.path.join(work, 'stored.rar'), src, MEMBERS)
def rar_block(kind, flags, body, data_size=None):
"""A RAR 2.9 block: checksum, kind, flags, size, then the body; the
checksum is the low half of the CRC-32 of everything after it."""
size = 7 + len(body)
head = struct.pack('<BHH', kind, flags, size) + body
return struct.pack('<H', zlib.crc32(head) & 0xffff) + head
def write_stored_rar(path, src, members):
with open(path, 'wb') as out:
out.write(b'Rar!\x1a\x07\x00')
out.write(rar_block(0x73, 0, bytes(6))) # archive header
for m in members:
with open(os.path.join(src, m), 'rb') as f:
data = f.read()
name = m.encode()
# packed size, size, host (Unix), CRC-32, time, version needed
# (2.9), method (0x30: stored), name length, attributes
body = struct.pack('<IIBIIBBHI', len(data), len(data), 3,
zlib.crc32(data), 0x3d2a8000, 29, 0x30,
len(name), 0o100644) + name
out.write(rar_block(0x74, 0x8000, body)) # file header
out.write(data)
out.write(rar_block(0x7b, 0x4000, b'')) # end of archive
if __name__ == '__main__':
main()