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.
This commit is contained in:
SashaGoncharov19 committed 2026-10-23 15:27:58 +04:00
commit 51d085e865
89 files changed
+7999

No files matched your search

+94
View File
@@ -0,0 +1,94 @@
"""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')