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

+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python3
"""Resolve every import of the macOS ARM64 slice against the iOS SDK's text stubs.
Usage: python3 tools/native_audit.py build/factorio.arm64 [--json out.json]
For each LC_LOAD_DYLIB the import is looked up in the iOS library with the same
framework name, following re-exported libraries. Imports that do not resolve
must be supplied by a shim. Nothing on the device is consulted, so this is a
link-level audit only; Objective-C selectors are not checked.
"""
import json
import re
import subprocess
import sys
from pathlib import Path
SDK = Path(subprocess.check_output(["xcrun", "--sdk", "iphoneos", "--show-sdk-path"], text=True).strip())
def tbd_documents():
"""Yield (install_name, symbols, reexports) for every tbd document in the SDK."""
files = list((SDK / "usr/lib").rglob("*.tbd")) + list((SDK / "System/Library").rglob("*.tbd"))
for path in files:
text = path.read_text(errors="ignore")
for doc in re.split(r"^--- !tapi-tbd", text, flags=re.M):
name = re.search(r"^install-name:\s*'?([^'\n]+)'?", doc, re.M)
if not name:
continue
exports = doc.split("\nreexports:")[0].split("\nundefineds:")[0]
symbols = set()
for kind, prefix in (("symbols", ""), ("weak-symbols", ""), ("thread-local-symbols", ""),
("objc-classes", "class"), ("objc-eh-types", "eh"), ("objc-ivars", "ivar")):
for block in re.findall(rf"\b{kind}:\s*\[([^\]]*)\]", exports):
for item in block.split(","):
item = item.strip().strip("'\"")
if not item:
continue
if prefix == "class":
symbols.update({f"_OBJC_CLASS_$_{item}", f"_OBJC_METACLASS_$_{item}"})
elif prefix == "eh":
symbols.add(f"_OBJC_EHTYPE_$_{item}")
elif prefix == "ivar":
symbols.add(f"_OBJC_IVAR_$_{item}")
else:
symbols.add(item)
reexports = set()
for block in re.findall(r"reexported-libraries:.*?libraries:\s*\[([^\]]*)\]", doc, re.S):
reexports.update(x.strip().strip("'\"") for x in block.split(",") if x.strip())
yield name.group(1), symbols, reexports
def main():
binary = sys.argv[1]
libs = {}
for name, symbols, reexports in tbd_documents():
entry = libs.setdefault(name, [set(), set()])
entry[0] |= symbols
entry[1] |= reexports
def resolves(install, symbol, seen=()):
if install not in libs or install in seen:
return False
symbols, reexports = libs[install]
return symbol in symbols or any(resolves(r, symbol, seen + (install,)) for r in reexports)
def ios_path(mac_path):
if mac_path in libs: # e.g. IOKit keeps its Versions/A install name on iOS
return mac_path
m = re.match(r"/System/Library/Frameworks/(\w+)\.framework/", mac_path)
if m:
return f"/System/Library/Frameworks/{m.group(1)}.framework/{m.group(1)}"
return mac_path
loads = re.findall(r"cmd LC_LOAD(?:_WEAK)?_DYLIB\n\s+cmdsize \d+\n\s+name (\S+)",
subprocess.check_output(["otool", "-l", binary], text=True))
short = {Path(p).name.replace(".dylib", "").split(".")[0]: p for p in loads}
undef = subprocess.check_output(["nm", "-m", "-u", binary], text=True)
report = {}
for line in undef.splitlines():
m = re.search(r"external (\S+) \(from (\S+)\)", line)
if not m:
continue
symbol, lib = m.groups()
mac = short.get(lib)
target = ios_path(mac) if mac else None
status = "resolved" if target and resolves(target, symbol) else "missing"
lib_report = report.setdefault(lib, {"mac_path": mac, "ios_path": target,
"ios_library_exists": target in libs,
"resolved": 0, "missing": []})
if status == "resolved":
lib_report["resolved"] += 1
else:
lib_report["missing"].append(symbol)
for lib, r in sorted(report.items(), key=lambda kv: -len(kv[1]["missing"])):
print(f"{lib:20} exists={r['ios_library_exists']!s:5} resolved={r['resolved']:4} missing={len(r['missing']):3} "
+ " ".join(r["missing"][:12]) + (" ..." if len(r["missing"]) > 12 else ""))
if "--json" in sys.argv:
Path(sys.argv[sys.argv.index("--json") + 1]).write_text(json.dumps(report, indent=2) + "\n")
if __name__ == "__main__":
main()