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:
commit
51d085e865
89 files changed
+7999
No files matched your search
@@ -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()
|
||||
Reference in new issue
Block a user