Files
factorio-ios/tests/check_save_headers.py
T
SashaGoncharov19 51d085e865 Native iPhone port of Factorio 2.0.77
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.
2026-10-23 15:27:58 +04:00

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')