mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
44 lines
2.2 KiB
Python
44 lines
2.2 KiB
Python
"""Strict {sector offset, byte size} archives with an in-file index.
|
|
|
|
Offsets are relative to the start of the archive, not the end of its table.
|
|
Payloads are consecutive after sector rounding; the complete file must be
|
|
accounted for. Recognition says nothing about executable code or RAM addresses.
|
|
"""
|
|
import struct
|
|
|
|
|
|
def extract_members(data, *, sector_size=2048, table_offset=0, count=None):
|
|
if sector_size < 8 or sector_size & (sector_size - 1):
|
|
raise ValueError('Sector size must be a power of two of at least eight')
|
|
if table_offset < 0 or table_offset % 4 or table_offset + 8 > len(data):
|
|
raise ValueError('Invalid archive table offset')
|
|
if not data or len(data) % sector_size:
|
|
raise ValueError('Archive must be sector aligned')
|
|
first_sector = struct.unpack_from('<I', data, table_offset)[0]
|
|
first_payload = first_sector * sector_size
|
|
if not table_offset + 8 <= first_payload < len(data):
|
|
raise ValueError('Archive header overlaps or exceeds payload')
|
|
if count is not None and (count < 1 or table_offset + (count + 1) * 8 > first_payload):
|
|
raise ValueError('Invalid archive member count')
|
|
members, cursor, at = [], first_payload, table_offset
|
|
while at + 8 <= first_payload:
|
|
sector, size = struct.unpack_from('<II', data, at)
|
|
at += 8
|
|
if sector == size == 0:
|
|
break
|
|
if count is not None and len(members) >= count:
|
|
raise ValueError('Archive member count changed')
|
|
offset = sector * sector_size
|
|
if not size or offset != cursor or offset + size > len(data):
|
|
raise ValueError('Archive extent has a gap, overlap, or exceeds bounds')
|
|
members.append(dict(index=len(members), sector=sector, source_offset=offset,
|
|
body=data[offset:offset + size]))
|
|
cursor = (offset + size + sector_size - 1) // sector_size * sector_size
|
|
else:
|
|
raise ValueError('Archive table has no terminator')
|
|
if not members or (count is not None and len(members) != count):
|
|
raise ValueError('Archive member count changed')
|
|
if cursor != len(data):
|
|
raise ValueError('Archive extents do not cover the file')
|
|
return members
|