Files
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

5.9 KiB
Raw Permalink Blame History

World-loading translation: first stage

The iOS prototype now runs an automatically translated MapVersion::MapVersion(Deserialiser&) constructor. It reads the save's version header. It does not load entities, surfaces, mods, prototypes, or a playable world.

Translation and authority

tools/a64_lift.py decodes ARM64 instruction words and emits WAT for a bounded straight-line integer subset. tools/build_version.py compiles the original constructor at 0x10150bf28, 192 bytes / 48 instructions, with body SHA-256 b9254262cde89c9e79a9fdf6d700860ff9fd623d53c576efaf431600096c8a9b. A changed body, unknown call, unsupported instruction, missing return, or extra bytes after return fails compilation.

Registers remain 64-bit. Guest addresses are checked before conversion to the prototype's 64 KiB WASM memory. The compiler supports only the declared execution model; it does not yet implement arbitrary branches, SIMD, native imports, global relocation, indirect calls, or exceptions. ARM encoding reference: Arm A64 ISA.

REA confirms the exact constructor bytes and instructions in evidence/world-loading-final.json:

  • Constructor dossier: ev_5b212f1169e5fff89ef9e6675af58a9df4616c5260c451a6d403a822935e36d0.
  • Complete 192-byte read: ev_e17c26cc53fcb5bc8e04b1342129594def2f2646cc186f462e281e394b436d45.
  • Deserialiser::readOrThrow dossier: ev_31eb669fe011e10190a6f9c1aa32ba33c2ad5b5a38cb37c46db03066d36f33b2.

The constructor makes five reads of 2, 2, 2, 2 and 1 bytes. It packs four little-endian 16-bit components into a 64-bit field and stores the final byte separately. The final byte's meaning is not established. The original readOrThrow dispatches through a stream virtual method and throws a C++ exception on a short read. This bounded prototype replaces that host boundary with a memory stream and an explicit error status. It does not claim to preserve C++ exception unwinding for arbitrary game code.

Archive adapter

runtime/save_header.c reads the ZIP central directory, locates level.dat0 (or level.dat), handles stored or deflated ZIP entries, and decodes the nested zlib stream's first nine bytes. It streams bounded prefixes instead of extracting the save or loading the whole world into memory. It rejects ambiguous roots, encryption, unsupported central-directory layouts, invalid offsets and malformed compression. Each compressed/decoded prefix is limited to 1 MiB.

This is a header probe, not whole-archive validation: it does not verify the entire member CRC or the rest of the zlib/map stream. ZIP64 central directories and encrypted archives are unsupported. The generated test archive contains a header and synthetic text, not a real map.

Checks completed

  • 20,000 native comparisons against original constructor instruction words, with the same stream adapter on both sides; output bytes, five-read sequence, callee-saved registers, SP and memory guards match.
  • Short-header failures leave the public result unchanged.
  • Fifteen ZIP/zlib fixtures cover success, streaming descriptors, malformed/truncated inputs, unsupported flags, ambiguous roots and offset validation.
  • The local macOS probe reads version 2.0.77 from the installed _autosave1.zip without modifying it.
  • The updated iOS application builds with no Steam imports or dependency. It was signed, installed and launched on the connected iPhone 15 Pro. Its native constructor self-test passed, and it read version 2.0.77 from a generated header-only ZIP. Results are recorded under build/version/iphone-*.json.
  • After explicit user approval, _autosave1.zip was copied into the iPhone application’s Documents/ProbeSave.zip. The application was relaunched and returned status 0 and version 2.0.77. The actual-save result is retained in build/version/iphone-real-save-probe.json; device-verification.json records this test, and generated-device-verification.json retains the earlier synthetic test. This verifies the version header only; the world has not been loaded.

Run:

python3 tools/build_version.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
python3 tests/check_a64_guards.py
python3 tests/check_save_headers.py
python3 tools/check_rea_rng.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio' evidence/world-loading-final.json --include-version
python3 tools/startup_inventory.py
python3 tools/package_ios.py

Remaining startup and world work

The retained REA GlobalContext::init dossier observes 151 direct callees and 643 basic blocks, plus indirect calls with unresolved targets. build/startup/frontier.json lists this observed frontier; it is not a complete dependency graph or runnable startup implementation.

LLVM symbol inventory identifies the next candidate entry points (names/addresses, not recovered behavior): Scenario::loadFactory at 0x1000fa1d0, Map::load at 0x1014c93f8, and Map::loadData at 0x1014c9618. Follow-up live REA callee queries became unavailable after the Hopper connection was lost; no graph completeness is claimed. The completed evidence was exported and a new snapshot retained in build/world-loading-rea.snapshot.json.

Next: recover those loader dependencies, add the required control-flow and memory/import support to the lifter, and replace graphical startup's inlined Steam block with a local optional-services context. Graphics and simulation remain unimplemented.

A subsequent REA session recovered direct callee frontiers for all three loader roots and translated the branching MapHeader constructor. See translation-progress.md for evidence, checks, and remaining scope.

The next batch adds a restricted scenario-envelope adapter and locates MapHeader at decoded offset 333 in the approved autosave. The original native constructor agrees with its translated output there. This is header decoding, not world loading; see save-layout-progress.md for profile restrictions and evidence.