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.
95 lines
4.1 KiB
Python
95 lines
4.1 KiB
Python
"""Verify the bounded 2.0.77 scenario envelope, offsets and unsupported inputs."""
|
|
import io
|
|
import json
|
|
import struct
|
|
import subprocess
|
|
import tempfile
|
|
import zipfile
|
|
import zlib
|
|
from pathlib import Path
|
|
|
|
ROOT=Path(__file__).resolve().parents[1]
|
|
PROBE=ROOT/'build/save-layout/probe'
|
|
TICKS=(720001,42,0xffffffffffffffff)
|
|
|
|
|
|
def compact(value,size=4):
|
|
return bytes([value]) if value<255 else b'\xff'+value.to_bytes(size,'little')
|
|
|
|
|
|
def string(value): return compact(len(value))+value
|
|
def version(values=(2,0,77)): return b''.join(compact(v,2) for v in values)
|
|
def localised(text=None,children=(),kind=1):
|
|
return b'\0' if text is None else bytes([kind])+string(text)+bytes([len(children)])+b''.join(children)
|
|
def ending(title=None,children=()):
|
|
return localised(title,children)+localised()+compact(0)+localised()+string(b'')
|
|
|
|
|
|
def fixture(long_string=b'freeplay',mod_count=1,extended_version=False,nested=False):
|
|
result=bytearray(struct.pack('<4HB',2,0,77,0,0))
|
|
positions={}
|
|
result+=string(b'')+string(long_string)+string(b'base')
|
|
positions['difficulty']=len(result);result+=b'\1'
|
|
positions['boolean']=len(result);result+=b'\0\0'+string(b'')+b'\0\0\0\1'
|
|
result+=version((65535,255,254) if extended_version else (2,0,77))
|
|
result+=struct.pack('<I',1234)+b'\1'+struct.pack('<I',0xa00000)
|
|
positions['mod_count']=len(result);result+=compact(mod_count)
|
|
for i in range(mod_count):
|
|
result+=string(b'base')+version((255,256,65535) if extended_version else (2,0,77))+struct.pack('<I',i)
|
|
result+=struct.pack('<I',0)
|
|
positions['property_type']=len(result);result+=b'\5\0'
|
|
positions['property_count']=len(result);result+=struct.pack('<I',0)
|
|
positions['ending']=len(result)
|
|
children=(localised(b'child',(localised(),)),) if nested else ()
|
|
result+=ending(b'victory',children)+ending(b'defeat')+b'\0'
|
|
offset=len(result)
|
|
result+=struct.pack('<3Q',*TICKS)
|
|
return bytes(result),offset,positions
|
|
|
|
|
|
checks=0
|
|
def probe(data,status=0,mode='bytes',offset=None,mods=1):
|
|
global checks
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
p=Path(directory)/'fixture';p.write_bytes(data)
|
|
result=subprocess.run([str(PROBE),mode,str(p)],capture_output=True,text=True)
|
|
assert result.returncode==(1 if status else 0),(result.returncode,result.stderr)
|
|
report=json.loads(result.stdout)
|
|
assert report['status']==status,report
|
|
if not status:
|
|
assert report['header_offset']==offset,report
|
|
assert report['next_offset']==offset+24,report
|
|
assert report['mod_count']==mods,report
|
|
assert report['tick_fields_hex']==[f'{v:016x}' for v in TICKS],report
|
|
elif status==6 and mode=='bytes':
|
|
assert report['needed']>len(data),report
|
|
checks+=1
|
|
|
|
|
|
for length in (0,1,254,255,256,4097):
|
|
data,offset,_=fixture(long_string=b'x'*length,extended_version=True,nested=True)
|
|
probe(data,offset=offset)
|
|
for count in (0,1,254,255):
|
|
data,offset,_=fixture(mod_count=count)
|
|
probe(data,offset=offset,mods=count)
|
|
data,offset,positions=fixture()
|
|
for length in range(len(data)):
|
|
probe(data[:length],6)
|
|
for index,value in [(4,78),(8,1),(positions['difficulty'],3),(positions['boolean'],2),
|
|
(positions['property_type'],4),(positions['property_count'],1)]:
|
|
changed=bytearray(data);changed[index]=value;probe(changed,4)
|
|
probe(data[:9]+b'\xff'+struct.pack('<I',1024*1024+1),7)
|
|
probe(data[:positions['mod_count']]+b'\xff'+struct.pack('<I',4097),7)
|
|
deep=localised()
|
|
for i in range(65): deep=localised(b'',(deep,))
|
|
probe(data[:positions['ending']]+deep+data[positions['ending']:],7)
|
|
|
|
for method in (zipfile.ZIP_STORED,zipfile.ZIP_DEFLATED):
|
|
output=io.BytesIO()
|
|
with zipfile.ZipFile(output,'w',compression=method) as z:
|
|
z.writestr('fixture/level.dat0',zlib.compress(data))
|
|
probe(output.getvalue(),mode='zip',offset=offset)
|
|
print(f'PASS: {checks} scenario-envelope cases, variable offsets, sentinel lengths, truncation, limits and failure atomicity')
|
|
(ROOT/'build/save-layout/verification.json').write_text(json.dumps({'status':'pass','cases':checks,
|
|
'scope':'2.0.77 envelope with empty startup settings; not world loading'},indent=2)+'\n')
|