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,82 @@
|
||||
# Save stream positioning and guest calls
|
||||
|
||||
2026-10-20. This batch connects the translated map-header constructor to a bounded real-save probe. It does not load a playable world.
|
||||
|
||||
## Results and authority
|
||||
|
||||
The approved `_autosave1.zip` has version 2.0.77.0, flag 0. The recovered metadata layout places MapHeader at decoded map-stream offset **333** and the next byte after its three fields at **357**. The three raw fields are **720001 / 720001 / 720001**. Their individual meanings are not yet established.
|
||||
|
||||
The offset is derived from the instruction-level serialized-field order described below. It is not a recorded full-game execution trace. At that derived position, the original ARM64 MapHeader constructor and the translated constructor agree on all three values, with exactly three eight-byte reads. `build/save-layout/desktop-real-save.json` records the source-save hash, offsets, values and native comparison. Signed build 3 ran on the iPhone 15 Pro and returned the same offsets and values from the same approved save. Its input SHA-256, launch ID and probe ID match the companion reports. Results and the signed binary hash are retained in `build/save-layout/device-verification.json`.
|
||||
|
||||
The scenario envelope is a **host-side reconstruction** in `runtime/save_layout.c`; it is not translated original Scenario code. MapVersion and MapHeader execute original byte-guarded translated functions. The envelope adapter deliberately supports only 2.0.77.0/flag 0 and an empty dictionary for startup PropertyTree settings. Other versions, nonempty/other settings, malformed booleans, excessive lengths, recursion and read budgets fail explicitly.
|
||||
|
||||
## Recovered stream order
|
||||
|
||||
1. MapDeserialiser reads four little-endian uint16 version components and one flag byte. Its version object starts at object offset `0x18`. The selected valid-version path initializes helpers without consuming additional serialized fields.
|
||||
2. The normal save path calls ScenarioExecutionContext's deserializing constructor before Map::load. ScenarioLocation consumes three compact-length strings; Difficulty consumes one byte.
|
||||
3. ScenarioExecutionContext reads booleans, a string, additional booleans, a compact three-component Version, fixed-width metadata fields, a vector of ModIDs, and startup PropertyTree settings. ModIDs contain a string, three compact uint16 version components and a four-byte field.
|
||||
4. The modern path reads two EndingInfo values and a final boolean. EndingInfo includes LocalisedStrings, a vector of LocalisedStrings and an ordinary string. LocalisedString starts with a type byte: zero consumes no further bytes; other values read a string, a one-byte child count, and recursive child strings.
|
||||
5. The selected path then calls Map::load and Map::loadData. Map::loadData begins by constructing MapHeader. The retained Map constructor assembly does not preserve/use its incoming reader before its first call; its observed constructor dependencies initialize engine state rather than this serialized envelope.
|
||||
|
||||
Compact string/vector lengths use a byte unless it is `0xff`, which introduces a little-endian uint32. Compact Version/ModVersion components use the same sentinel with a uint16 extension. These two encodings must not be conflated. This batch does not implement recursive PropertyTree settings; rejecting them avoids assuming their complete wire representation.
|
||||
|
||||
The envelope adapter reports the next required prefix size when input is short. The ZIP adapter retries bounded prefixes, up to 128 reads and a 1 MiB prefix budget. No full extraction is required. Whole-member CRCs, the remainder of the map stream, object references, mods, entities, or world integrity are not validated.
|
||||
|
||||
## Compiler/runtime changes
|
||||
|
||||
`tools/a64_program.py` adds a multi-function dispatcher over declared original instruction addresses. It shares the guest's registers, stack, memory and one instruction budget across direct BL calls, tail branches and RET. Original stack save/restore instructions implement nested return-address preservation. No native recursion, runtime compilation or JIT is used.
|
||||
|
||||
RET accepts the external return sentinel or known guest-call continuations. Unknown computed returns produce status 4; external sentinels aliasing declared code are rejected. Indirect calls and arbitrary computed returns remain unsupported. The call graph/flag analysis is conservative; a rejected program is not automatically evidence of invalid original game code. Host stream errors still return an explicit status rather than implementing C++ unwinding.
|
||||
|
||||
MapHeader now enters through the original four-byte public C1 tail branch at `0x10010582c`, then reaches its 332-byte C2 body at `0x1014c6aac`. Both inputs are byte guarded. The desktop oracle relocates the alias's B to its copied body and redirects only declared stream reads. The iOS library excludes the oracle.
|
||||
|
||||
## Verification
|
||||
|
||||
- **80,000 native comparisons** for hand-authored multi-function instruction fixtures: nested calls, recursive LR saves, tail calls, shared-memory mutation, preserved callee-saved registers, memory guards, exact shared-budget exhaustion and invalid return/entry statuses. These are compiler tests, not recovered game functions.
|
||||
- **20,000 original MapHeader comparisons** now run through its original public entry and body in the multi-function dispatcher. Modern/legacy paths and failure behavior still pass.
|
||||
- **62 bounded ZIP prefix cases** compare exact decoded bytes and check output guards/failure atomicity across stored/deflated outer members, data descriptors, buffer boundaries, empty/oversized requests and truncated streams.
|
||||
- **140 scenario-envelope cases** cover variable string lengths, sentinel encodings, mod counts, nested LocalisedStrings, every truncation of a controlled fixture, malformed values, unsupported settings and recursion/size limits.
|
||||
- Existing MapVersion equivalence and fifteen archive-version fixtures pass.
|
||||
- All six translated code/constant/entry ranges match complete exported REA byte evidence.
|
||||
- The real-save constructor comparison passes at offset 333 on macOS. The iPhone real-save probe, modern/legacy constructor self-tests and version regression pass in one launch. The signed artifact passes a Steam import/dependency audit and contains none of the desktop native-oracle symbols.
|
||||
|
||||
Reproduce after the RNG build:
|
||||
|
||||
```sh
|
||||
python3 tools/build_version.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
|
||||
python3 tests/check_a64_program.py
|
||||
python3 tools/build_map_header.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
|
||||
python3 tests/check_save_prefixes.py
|
||||
python3 tools/build_save_layout.py
|
||||
python3 tests/check_save_layout.py
|
||||
python3 tools/check_rea_rng.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio' evidence/save-layout-final.json --include-version --include-map-header --include-map-header-entry
|
||||
build/save-layout/verify_native '/absolute/path/to/save.zip'
|
||||
python3 tools/package_ios.py
|
||||
```
|
||||
|
||||
## REA evidence
|
||||
|
||||
Complete canonical bundle: `evidence/save-layout-final.json` (64 records). The first session lost its Hopper connection after the main constructor graph was captured; its evidence was preserved in `save-layout-pass1.json`. The restarted session completed the remaining readers and closed successfully into `build/save-layout-rea.snapshot.json`. Matching LLVM symbol/instruction checks were used while the second analysis prepared; they are supplementary, not REA records.
|
||||
|
||||
| Observation | REA evidence ID |
|
||||
| --- | --- |
|
||||
| MapDeserialiser constructor | `ev_3143ceb6dee72f0a56235e02cd4a5ed942c3826860ea21c315847bb186053650` |
|
||||
| ScenarioExecutionContext serialized order | `ev_a4333ddb98e9e43cd6eacf68a19936091fb88eb905df0037617599b1f64952c9` |
|
||||
| ScenarioLocation strings | `ev_17cb5c99e94825134f5ec68367c0dc43d89c520bffd31830815fd785006d3cf4` |
|
||||
| String loader | `ev_36139f8e3f048ef8ad9744727fa699868cea0cb6c45785a552284adf3b3091a9` |
|
||||
| Boolean validation | `ev_8654783b6211640b058d9dd0904eef6b49fcd73230410956eb6b4aff686835cf` |
|
||||
| Version component widths | `ev_705e76d3df68f5080b2401c46ae2a8c9f0eff826727469271c101c8d73be0d68` |
|
||||
| ModVersion component widths | `ev_eedbdacc998513468e3f7550844f2421f16ae817f129f10863cc7810aa002b93` |
|
||||
| EndingInfo order | `ev_8eb93855422407b11b683ef67324e000b41916c072b18543e9b94bcffba8d1dc` |
|
||||
| Recursive LocalisedString | `ev_8c14f94ef7ba1133a664ab94262e1d527f6e3cd687a80d0fc61a8f7f3a1cf9df` |
|
||||
| LocalisedString vector count | `ev_b723c046c47b29ec10d022700ed74861aad1449048c57233526c6083c83f105f` |
|
||||
| Public MapHeader entry bytes | `ev_525aeb761646ca31af2fbacfc51ec786e6036b6259fd09505063144d0a5feba1` |
|
||||
|
||||
Hopper merged Difficulty's entry with a preceding procedure. Its assembly includes the relevant block at `0x1001bf6ac`; procedure names and reported lengths alone are not reliable function-boundary authority. Source bytes, decoded call targets, matching symbols and the specific observed blocks were used together.
|
||||
|
||||
## Next engine work
|
||||
|
||||
1. Translate the compact Version/ModVersion readers and other metadata utilities as composable guest functions, reducing reconstructed host behavior where useful.
|
||||
2. Implement a scalable guest memory image and explicit allocator/global/import contracts needed by the actual MapDeserialiser and Map constructors.
|
||||
3. Expand supported PropertyTree settings using recovered wire cases and validated guest code.
|
||||
4. Recover and translate MapGenSettings and the earliest Map::loadData state allocations. Entity/world reconstruction, simulation, graphics, audio and controls remain ahead.
|
||||
Reference in new issue
Block a user