Run the original macOS ARM64 game code on iOS: - tools/retarget.py: header-only Mach-O retargeting to an iOS dylib - platform/native: UIKit host and macOS API shims (AppKit for SDL's Cocoa backend, CoreGraphics displays, CoreAudio device, Metal device properties, Wuffs PNG decoding, Steam and peripheral stubs) - touch, overlay, iOS keyboard and hardware keyboard input - tools/build_native.py: build, sign, install, data copy, launch - docs: build guide, controls, architecture Also includes the earlier static-translation research (ARM64 -> WASM -> native) under docs/research, runtime/, tests/ and tools/. No game files are included; the build reads them from a local install.
69 lines
3.0 KiB
Python
69 lines
3.0 KiB
Python
"""Exercise native ZIP/zlib header reading, including malformed archives."""
|
|
import io
|
|
import json
|
|
import struct
|
|
import subprocess
|
|
import tempfile
|
|
import zipfile
|
|
import zlib
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
HEADER = struct.pack('<4HB', 2, 0, 77, 0, 0)
|
|
|
|
|
|
def archive(entries, method=zipfile.ZIP_DEFLATED, stream=False):
|
|
class NonSeeking(io.BytesIO):
|
|
def seekable(self): return False
|
|
def seek(self, *args): raise OSError('streaming archive')
|
|
buffer = NonSeeking() if stream else io.BytesIO()
|
|
with zipfile.ZipFile(buffer, 'w', compression=method) as output:
|
|
for name, contents in entries:
|
|
output.writestr(name, contents)
|
|
return buffer.getvalue()
|
|
|
|
|
|
def probe(contents, status, label):
|
|
with tempfile.TemporaryDirectory() as folder:
|
|
path = Path(folder) / 'save.zip'
|
|
path.write_bytes(contents)
|
|
result = subprocess.run([str(ROOT / 'build/version/save_probe'), str(path)], capture_output=True, text=True)
|
|
actual = json.loads(result.stdout)
|
|
assert actual['status'] == status, (label, actual, status, result.stderr)
|
|
assert result.returncode == (1 if status else 0), (label, result.returncode)
|
|
if not status:
|
|
assert [actual[k] for k in ('major', 'minor', 'patch', 'build', 'flag')] == [2, 0, 77, 0, 0]
|
|
print('PASS:', label)
|
|
|
|
|
|
payload = zlib.compress(HEADER + bytes(10000))
|
|
for method in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED):
|
|
probe(archive([('world/level.dat0', payload)], method), 0, f'outer method {method}, nested zlib')
|
|
probe(archive([('world/level.dat0', payload)], stream=True), 0, 'ZIP data descriptor')
|
|
probe(archive([('world/level.dat', payload)]), 0, 'legacy level.dat entry name')
|
|
probe(archive([('world/level.dat', b'invalid'), ('world/level.dat0', payload)]), 0, 'level.dat0 preference')
|
|
probe(b'not a zip', 2, 'invalid ZIP')
|
|
probe(archive([('description.json', b'{}')]), 3, 'no level entry')
|
|
probe(archive([('a/level.dat0', payload), ('b/level.dat0', payload)]), 2, 'ambiguous roots')
|
|
probe(archive([('world/level.dat0', b'not zlib')]), 5, 'bad inner compression')
|
|
probe(archive([('world/level.dat0', zlib.compress(HEADER[:8]))]), 6, 'short decompressed header')
|
|
valid = archive([('world/level.dat0', payload)])
|
|
probe(valid[:-1], 2, 'truncated end record')
|
|
changed = bytearray(valid)
|
|
central = changed.index(b'PK\x01\x02')
|
|
struct.pack_into('<H', changed, 6, 1)
|
|
struct.pack_into('<H', changed, central + 8, 1)
|
|
probe(changed, 4, 'encrypted flag refused')
|
|
changed = bytearray(valid)
|
|
changed[30] ^= 1
|
|
probe(changed, 2, 'local/central name mismatch')
|
|
changed = bytearray(valid)
|
|
struct.pack_into('<I', changed, central + 42, 0xfffffffe)
|
|
probe(changed, 2, 'out-of-file local offset')
|
|
changed = bytearray(valid)
|
|
end = changed.rindex(b'PK\x05\x06')
|
|
struct.pack_into('<H', changed, end + 10, 0xffff)
|
|
struct.pack_into('<H', changed, end + 8, 0xffff)
|
|
probe(changed, 4, 'ZIP64 sentinel refused')
|
|
print('PASS: 15 archive cases; only the bounded version prefix is validated')
|