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

+82
View File
@@ -0,0 +1,82 @@
#include "deserialiser_bridge.h"
#include "deserialiser_host.h"
#include <string.h>
static int range(wasm_rt_memory_t *ram,uint64_t address,uint64_t size) {
return size<=ram->size && address<=ram->size-size;
}
static uint64_t get(const uint8_t *memory,uint64_t at) {
uint64_t value;memcpy(&value,memory+at,8);return value;
}
u32 w2c_deser__host_allocate(struct w2c_deser__host *env,u32 context) {
wasm_rt_memory_t *ram=w2c_factorio__deser_memory(env->module);
if (!range(ram,context,256) || env->heap.memory!=ram->data || env->heap.memory_size!=ram->size) return 2;
uint64_t pointer;int status=factorio_guest_heap_allocate(&env->heap,get(ram->data,context),&pointer);
if (status) return status==1 ? 5 : 2;
memcpy(ram->data+context,&pointer,8);return 0;
}
u32 w2c_deser__host_read_or_throw(struct w2c_deser__host *env,u64 source,u64 destination,u64 size) {
wasm_rt_memory_t *ram=w2c_factorio__deser_memory(env->module);
if (!range(ram,source,16) || !range(ram,destination,size)) return 2;
uint64_t stream=get(ram->data,source+8);
if (!range(ram,stream,24)) return 2;
uint64_t data=get(ram->data,stream),length=get(ram->data,stream+8),cursor=get(ram->data,stream+16);
if (!range(ram,data,length)) return 2;
if (cursor>length || size>length-cursor) return 1;
memmove(ram->data+destination,ram->data+data+cursor,(size_t)size);
cursor+=size;memcpy(ram->data+stream+16,&cursor,8);++env->reads;return 0;
}
u32 w2c_deser__host_zero_memory(struct w2c_deser__host *env,u64 destination,u64 size,u64 ignored) {
(void)ignored;wasm_rt_memory_t *ram=w2c_factorio__deser_memory(env->module);
if (!range(ram,destination,size)) return 2;
/* The selected bzero service may initialize live arena bytes only. */
if (destination<env->heap.begin || destination>env->heap.cursor || size>env->heap.cursor-destination) return 2;
memset(ram->data+destination,0,(size_t)size);++env->zero_calls;return 0;
}
u32 w2c_deser__host_unsupported(struct w2c_deser__host *env,u64 a,u64 b,u64 c) {
(void)a;(void)b;(void)c;++env->unsupported_calls;return 6;
}
u32 w2c_deser__host_unsupported_delete(struct w2c_deser__host *env,u64 a,u64 b,u64 c) {
return w2c_deser__host_unsupported(env,a,b,c);
}
u32 w2c_deser__host_unsupported_length(struct w2c_deser__host *env,u64 a,u64 b,u64 c) {
return w2c_deser__host_unsupported(env,a,b,c);
}
u32 w2c_deser__host_unsupported_array_length(struct w2c_deser__host *env,u64 a,u64 b,u64 c) {
return w2c_deser__host_unsupported(env,a,b,c);
}
u32 w2c_deser__host_version_error(struct w2c_deser__host *env,u64 a,u64 b,u64 c) {
(void)a;(void)b;(void)c;++env->version_errors;return 7;
}
int factorio_deserialiser_probe(const uint8_t *prefix,size_t length,FactorioDeserialiserProbe *out) {
if (!prefix || !out) return 2;
if (!wasm_rt_is_initialized()) wasm_rt_init();
w2c_factorio__deser module;
struct w2c_deser__host env={.module=&module};
wasm2c_factorio__deser_instantiate(&module,&env);
wasm_rt_memory_t *ram=w2c_factorio__deser_memory(&module);uint8_t *memory=ram->data;
memset(memory,0xa5,(size_t)ram->size);
uint64_t registers[32]={0};registers[0]=4096;registers[1]=512;registers[31]=ram->size-256;
uint64_t stream[3]={16384,length<9 ? length:9,0};
memcpy(memory,registers,sizeof(registers));memcpy(memory+512,stream,sizeof(stream));
memcpy(memory+16384,prefix,(size_t)stream[1]);
int status=factorio_guest_heap_init(&env.heap,memory,ram->size,131072,196608);
if (!status) status=(int)w2c_factorio__deser_run(&module,0,10000);
if (!status) {
FactorioDeserialiserProbe result={0};uint64_t version=get(memory,4096+24),target=get(memory,4096+40);
result.version=(FactorioMapVersion){version>>48,(version>>32)&65535,(version>>16)&65535,version&65535,memory[4096+32]};
result.allocations=env.heap.allocations;result.reads=env.reads;
result.heap_bytes=env.heap.cursor-env.heap.begin;result.stream_cursor=get(memory,528);
result.owns_target=memory[4096+0x4c6];
if (!range(ram,target,144) || target<env.heap.begin || target>env.heap.cursor-144 ||
result.allocations!=12 || result.reads!=5 || env.zero_calls!=1 || env.unsupported_calls ||
result.stream_cursor!=9 || result.owns_target!=1 || memory[4096+16]!=1 || get(memory,0)!=4096) status=8;
if (!status) {
result.target_capacity=(unsigned)get(memory,target+32);
if (result.target_capacity!=64) status=8;
else *out=result;
}
}
wasm2c_factorio__deser_free(&module);return status;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef FACTORIO_DESERIALISER_BRIDGE_H
#define FACTORIO_DESERIALISER_BRIDGE_H
#include "version_bridge.h"
typedef struct {
FactorioMapVersion version;
unsigned allocations, reads, target_capacity;
uint64_t heap_bytes, stream_cursor;
int owns_target;
} FactorioDeserialiserProbe;
/* Initialize a fresh original deserializer from a nine-byte version prefix.
* 0 success; 1 short input; 2 invalid; 5 heap exhaustion; 6 unsupported service;
* 7 original version-error branch. No output commits on failure. */
int factorio_deserialiser_probe(const uint8_t *prefix, size_t length, FactorioDeserialiserProbe *out);
#endif
+10
View File
@@ -0,0 +1,10 @@
#ifndef FACTORIO_DESERIALISER_HOST_H
#define FACTORIO_DESERIALISER_HOST_H
#include "factorio_deser.h"
#include "guest_heap.h"
struct w2c_deser__host {
w2c_factorio__deser *module;
FactorioGuestHeap heap;
unsigned reads, zero_calls, unsupported_calls, version_errors;
};
#endif
+26
View File
@@ -0,0 +1,26 @@
#include "guest_heap.h"
#include <limits.h>
int factorio_guest_heap_init(FactorioGuestHeap *heap, uint8_t *memory,
uint64_t memory_size, uint64_t begin, uint64_t end) {
if (!heap || !memory || !begin || begin > end || end > memory_size ||
memory_size > SIZE_MAX || begin % 16) return 2;
*heap = (FactorioGuestHeap){memory, memory_size, begin, begin, end, 0};
return 0;
}
int factorio_guest_heap_allocate(FactorioGuestHeap *heap, uint64_t size, uint64_t *out) {
if (!heap || !out || !heap->memory || !heap->begin || heap->begin % 16 ||
heap->cursor < heap->begin || heap->cursor > heap->end ||
heap->end > heap->memory_size || heap->memory_size > SIZE_MAX ||
heap->allocations == UINT_MAX) return 2;
uint64_t padding = (16 - heap->cursor % 16) % 16;
if (padding > heap->end - heap->cursor) return 1;
uint64_t address = heap->cursor + padding;
if (!size) size = 1; /* operator new(0) still returns a distinct allocation. */
if (size > heap->end - address) return 1;
heap->cursor = address + size;
++heap->allocations;
*out = address;
return 0;
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef FACTORIO_GUEST_HEAP_H
#define FACTORIO_GUEST_HEAP_H
#include <stddef.h>
#include <stdint.h>
/* A bounded, per-run arena of guest offsets. No native pointers enter guest RAM.
* Allocations are 16-byte aligned and uninitialized; reset discards the arena.
* This does not implement C++ destruction, free, or exception unwinding. */
typedef struct {
uint8_t *memory;
uint64_t memory_size, begin, cursor, end;
unsigned allocations;
} FactorioGuestHeap;
int factorio_guest_heap_init(FactorioGuestHeap *heap, uint8_t *memory,
uint64_t memory_size, uint64_t begin, uint64_t end);
/* 0 success, 1 exhausted, 2 invalid. Output and heap unchanged on failure. */
int factorio_guest_heap_allocate(FactorioGuestHeap *heap, uint64_t size, uint64_t *out);
#endif
+50
View File
@@ -0,0 +1,50 @@
#include "map_header_bridge.h"
#include "factorio_map_header.h"
#include <string.h>
struct w2c_map__host {
w2c_factorio__map__header *module;
unsigned reads;
};
u32 w2c_map__host_read_or_throw(struct w2c_map__host *env, u64 source, u64 destination, u64 size) {
wasm_rt_memory_t *memory = w2c_factorio__map__header_memory(env->module);
if (memory->size < 24 || source > memory->size - 24 ||
size > memory->size || destination > memory->size - size) return 2;
uint64_t reader[3];
memcpy(reader, memory->data + source, sizeof(reader));
uint64_t data = reader[0], length = reader[1], cursor = reader[2];
if (data > memory->size || length > memory->size - data) return 2;
if (cursor > length || size > length - cursor) return 1;
memmove(memory->data + destination, memory->data + data + cursor, (size_t)size);
cursor += size;
memcpy(memory->data + source + 16, &cursor, sizeof(cursor));
++env->reads;
return 0;
}
int factorio_map_header_parse(const uint8_t *payload, size_t length,
FactorioMapVersion version, FactorioMapHeader *out) {
if (!payload || !out) return 2;
if (!wasm_rt_is_initialized()) wasm_rt_init();
w2c_factorio__map__header module;
struct w2c_map__host env = {.module = &module};
wasm2c_factorio__map__header_instantiate(&module, &env);
uint8_t *memory = w2c_factorio__map__header_memory(&module)->data;
uint64_t registers[32] = {0};
registers[0] = 2048; registers[1] = 512; registers[31] = 65280;
uint64_t reader[5] = {4096, length < 24 ? length : 24, 0,
(uint64_t)version.major << 48 | (uint64_t)version.minor << 32 |
(uint64_t)version.patch << 16 | version.build, version.flag};
memcpy(memory, registers, sizeof(registers));
memcpy(memory + 512, reader, sizeof(reader));
memcpy(memory + 4096, payload, (size_t)reader[1]);
int result = (int)w2c_factorio__map__header_run(&module, 0, 1000);
if (!result) {
FactorioMapHeader parsed = {.version = version};
memcpy(parsed.tick_fields, memory + 2064, sizeof(parsed.tick_fields));
*out = parsed;
}
wasm2c_factorio__map__header_free(&module);
return result;
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef FACTORIO_MAP_HEADER_BRIDGE_H
#define FACTORIO_MAP_HEADER_BRIDGE_H
#include "version_bridge.h"
/* Three serialized MapTick fields; their individual meanings are not recovered. */
typedef struct {
FactorioMapVersion version;
uint64_t tick_fields[3];
} FactorioMapHeader;
/* Input starts at the constructor's stream position, NOT at the ZIP or map start.
* 0 success, 1 short stream, 2 invalid argument/address, 3 execution budget.
* Only success modifies out. This is not a complete save/world parser. */
int factorio_map_header_parse(const uint8_t *payload, size_t length,
FactorioMapVersion version, FactorioMapHeader *out);
#endif
+176
View File
@@ -0,0 +1,176 @@
#include "save_header.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
enum { PREFIX_LIMIT = 1024 * 1024, CHUNK = 4096 };
static uint16_t u16(const unsigned char *p) { return (uint16_t)(p[0] | ((uint16_t)p[1] << 8)); }
static uint32_t u32(const unsigned char *p) { return (uint32_t)u16(p) | ((uint32_t)u16(p + 2) << 16); }
static int read_at(FILE *f, uint64_t position, void *out, size_t length) {
if (position > INT64_MAX || fseeko(f, (off_t)position, SEEK_SET)) return 0;
return fread(out, 1, length, f) == length;
}
static int suffix(const char *name, size_t length, const char *tail) {
size_t count = strlen(tail);
return length >= count && !memcmp(name + length - count, tail, count) &&
(length == count || name[length - count - 1] == '/');
}
typedef struct { uint32_t local, compressed; uint16_t method, flags, name_length; char name[4097]; } Entry;
static int find_level(FILE *file, uint64_t file_size, Entry *selected) {
if (file_size < 22) return FACTORIO_SAVE_INVALID_ZIP;
size_t count = file_size < 65557 ? (size_t)file_size : 65557;
unsigned char *tail = malloc(count);
if (!tail) return FACTORIO_SAVE_IO;
if (!read_at(file, file_size - count, tail, count)) { free(tail); return FACTORIO_SAVE_IO; }
size_t end = count;
for (size_t i = count - 22 + 1; i-- > 0;) {
if (u32(tail + i) == 0x06054b50 && (size_t)u16(tail + i + 20) == count - i - 22) {
end = i; break;
}
}
if (end == count) { free(tail); return FACTORIO_SAVE_INVALID_ZIP; }
unsigned char *e = tail + end;
uint32_t central_size = u32(e + 12), central_offset = u32(e + 16);
uint16_t entries = u16(e + 10);
if (u16(e + 4) || u16(e + 6) || entries != u16(e + 8) || entries == 0xffff ||
central_size == 0xffffffff || central_offset == 0xffffffff) {
free(tail); return FACTORIO_SAVE_UNSUPPORTED;
}
uint64_t central_end = (uint64_t)central_offset + central_size;
if (central_end > file_size - count + end) { free(tail); return FACTORIO_SAVE_INVALID_ZIP; }
free(tail);
uint64_t cursor = central_offset;
int priority = 0;
for (unsigned index = 0; index < entries; ++index) {
unsigned char header[46];
if (cursor > central_end || central_end - cursor < sizeof(header) ||
!read_at(file, cursor, header, sizeof(header)) || u32(header) != 0x02014b50) return FACTORIO_SAVE_INVALID_ZIP;
uint16_t name_length = u16(header + 28);
uint64_t next = cursor + 46 + name_length + u16(header + 30) + u16(header + 32);
if (next > central_end || name_length > 4096) return FACTORIO_SAVE_UNSUPPORTED;
char name[4097];
if (!read_at(file, cursor + 46, name, name_length)) return FACTORIO_SAVE_IO;
if (memchr(name, 0, name_length)) return FACTORIO_SAVE_INVALID_ZIP;
name[name_length] = 0;
int match = suffix(name, name_length, "level.dat0") ? 2 : suffix(name, name_length, "level.dat");
if (match && match >= priority) {
if (match == priority) return FACTORIO_SAVE_INVALID_ZIP; /* ambiguous roots */
if (u16(header + 34) || u32(header + 20) == 0xffffffff || u32(header + 42) == 0xffffffff)
return FACTORIO_SAVE_UNSUPPORTED;
*selected = (Entry){.local = u32(header + 42), .compressed = u32(header + 20),
.method = u16(header + 10), .flags = u16(header + 8), .name_length = name_length};
memcpy(selected->name, name, name_length + 1);
priority = match;
}
cursor = next;
}
if (!priority) return FACTORIO_SAVE_NO_LEVEL;
if ((selected->flags & 0x41) || (selected->method != 0 && selected->method != 8)) return FACTORIO_SAVE_UNSUPPORTED;
return FACTORIO_SAVE_OK;
}
static int feed_inner(z_stream *inner, unsigned char *bytes, size_t length, unsigned char *header, size_t requested) {
inner->next_in = bytes; inner->avail_in = (uInt)length;
inner->next_out = header + inner->total_out; inner->avail_out = (uInt)(requested - inner->total_out);
int status = inflate(inner, Z_NO_FLUSH);
if (status != Z_OK && status != Z_STREAM_END && status != Z_BUF_ERROR) return FACTORIO_SAVE_COMPRESSION;
if (inner->total_out == requested) return FACTORIO_SAVE_OK;
return status == Z_STREAM_END ? FACTORIO_SAVE_SHORT_HEADER : -1;
}
static int prefix(FILE *file, uint64_t file_size, const Entry *entry, unsigned char *header, size_t requested) {
unsigned char local[30];
if (!read_at(file, entry->local, local, sizeof(local)) || u32(local) != 0x04034b50) return FACTORIO_SAVE_INVALID_ZIP;
if (u16(local + 8) != entry->method || u16(local + 6) != entry->flags ||
u16(local + 26) != entry->name_length) return FACTORIO_SAVE_INVALID_ZIP;
char name[4097];
if (!read_at(file, (uint64_t)entry->local + 30, name, entry->name_length) ||
memcmp(name, entry->name, entry->name_length)) return FACTORIO_SAVE_INVALID_ZIP;
uint64_t data_offset = (uint64_t)entry->local + 30 + u16(local + 26) + u16(local + 28);
if (data_offset > file_size || entry->compressed > file_size - data_offset) return FACTORIO_SAVE_INVALID_ZIP;
z_stream outer = {0}, inner = {0};
if (inflateInit(&inner) != Z_OK) return FACTORIO_SAVE_COMPRESSION;
if (entry->method == 8 && inflateInit2(&outer, -MAX_WBITS) != Z_OK) {
inflateEnd(&inner); return FACTORIO_SAVE_COMPRESSION;
}
int result = FACTORIO_SAVE_SHORT_HEADER;
unsigned char input[CHUNK], decoded[CHUNK];
uint64_t consumed = 0, produced = 0;
while (consumed < entry->compressed && consumed < PREFIX_LIMIT) {
size_t amount = entry->compressed - consumed < CHUNK ? (size_t)(entry->compressed - consumed) : CHUNK;
if (!read_at(file, data_offset + consumed, input, amount)) { result = FACTORIO_SAVE_IO; break; }
consumed += amount;
if (entry->method == 0) {
result = feed_inner(&inner, input, amount, header, requested);
if (result >= 0) goto finish;
} else {
outer.next_in = input; outer.avail_in = (uInt)amount;
do {
outer.next_out = decoded; outer.avail_out = sizeof(decoded);
int status = inflate(&outer, Z_NO_FLUSH);
if (status != Z_OK && status != Z_STREAM_END && status != Z_BUF_ERROR) {
result = FACTORIO_SAVE_COMPRESSION; goto finish;
}
size_t bytes = sizeof(decoded) - outer.avail_out;
produced += bytes;
if (produced > PREFIX_LIMIT) { result = FACTORIO_SAVE_PREFIX_LIMIT; goto finish; }
result = feed_inner(&inner, decoded, bytes, header, requested);
if (result >= 0) goto finish;
if (status == Z_STREAM_END) { result = FACTORIO_SAVE_SHORT_HEADER; goto finish; }
if (!bytes && !outer.avail_in) break;
if (!bytes && status == Z_BUF_ERROR) { result = FACTORIO_SAVE_COMPRESSION; goto finish; }
} while (outer.avail_in || !outer.avail_out);
}
}
result = consumed >= PREFIX_LIMIT ? FACTORIO_SAVE_PREFIX_LIMIT : FACTORIO_SAVE_SHORT_HEADER;
finish:
if (entry->method == 8) inflateEnd(&outer);
inflateEnd(&inner);
return result;
}
int factorio_save_prefix(const char *path, uint8_t *out, size_t length) {
if (!path || !out) return FACTORIO_SAVE_IO;
if (!length || length > PREFIX_LIMIT) return FACTORIO_SAVE_PREFIX_LIMIT;
FILE *file = fopen(path, "rb");
if (!file) return FACTORIO_SAVE_IO;
if (fseeko(file, 0, SEEK_END)) { fclose(file); return FACTORIO_SAVE_IO; }
off_t end = ftello(file);
if (end < 0) { fclose(file); return FACTORIO_SAVE_IO; }
Entry entry;
int status = find_level(file, (uint64_t)end, &entry);
unsigned char *temporary = NULL;
if (!status) {
temporary = malloc(length);
if (!temporary) status = FACTORIO_SAVE_IO;
else status = prefix(file, (uint64_t)end, &entry, temporary, length);
}
fclose(file);
if (!status) memcpy(out, temporary, length);
free(temporary);
return status;
}
int factorio_save_header(const char *path, FactorioMapVersion *out) {
if (!out) return FACTORIO_SAVE_IO;
unsigned char header[9];
int status = factorio_save_prefix(path, header, sizeof(header));
if (!status && factorio_version_parse(header, 9, out)) status = FACTORIO_SAVE_SHORT_HEADER;
return status;
}
const char *factorio_save_status_string(int status) {
switch (status) {
case FACTORIO_SAVE_OK: return "Version header read; full map is not loaded";
case FACTORIO_SAVE_IO: return "Cannot read the selected file";
case FACTORIO_SAVE_INVALID_ZIP: return "Invalid or ambiguous ZIP archive";
case FACTORIO_SAVE_NO_LEVEL: return "No level.dat0 or level.dat entry";
case FACTORIO_SAVE_UNSUPPORTED: return "Unsupported archive layout, compression or encryption";
case FACTORIO_SAVE_COMPRESSION: return "Invalid compressed level header";
case FACTORIO_SAVE_SHORT_HEADER: return "Incomplete map version header";
case FACTORIO_SAVE_PREFIX_LIMIT: return "Header decompression limit reached";
default: return "Unknown save-header error";
}
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef FACTORIO_SAVE_HEADER_H
#define FACTORIO_SAVE_HEADER_H
#include "version_bridge.h"
/* Header probe only: does not verify the whole archive or deserialize the map. */
enum FactorioSaveStatus { FACTORIO_SAVE_OK = 0, FACTORIO_SAVE_IO,
FACTORIO_SAVE_INVALID_ZIP, FACTORIO_SAVE_NO_LEVEL,
FACTORIO_SAVE_UNSUPPORTED, FACTORIO_SAVE_COMPRESSION,
FACTORIO_SAVE_SHORT_HEADER, FACTORIO_SAVE_PREFIX_LIMIT };
int factorio_save_header(const char *path, FactorioMapVersion *out);
/* Decode exactly length bytes of the nested map stream (1..1 MiB).
* Writes out only on success. Does not validate the complete stream/archive. */
int factorio_save_prefix(const char *path, uint8_t *out, size_t length);
const char *factorio_save_status_string(int status);
#endif
+114
View File
@@ -0,0 +1,114 @@
#include "save_layout.h"
#include <stdlib.h>
enum { LIMIT = 1024 * 1024, NODES = 4096, DEPTH = 64 };
typedef struct {
const uint8_t *bytes;
size_t length, cursor, needed;
unsigned nodes;
int status;
} Reader;
static void fail(Reader *r, int status) { if (!r->status) r->status = status; }
static const uint8_t *take(Reader *r, size_t size) {
if (r->status) return NULL;
if (size > LIMIT || r->cursor > LIMIT - size) { fail(r, FACTORIO_SAVE_PREFIX_LIMIT); return NULL; }
size_t end = r->cursor + size;
if (end > r->length) {
r->needed = end; fail(r, FACTORIO_SAVE_SHORT_HEADER); return NULL;
}
const uint8_t *result = r->bytes + r->cursor;
r->cursor = end;
return result;
}
static uint32_t number(Reader *r, unsigned size) {
const uint8_t *p = take(r,size);
uint32_t result = 0;
if (p) for (unsigned i=0;i<size;++i) result |= (uint32_t)p[i] << (8*i);
return result;
}
static uint32_t compact(Reader *r, unsigned extended) {
uint32_t value = number(r,1);
return value == 255 ? number(r,extended) : value;
}
static void string(Reader *r) { size_t count = compact(r,4); take(r,count); }
static void boolean(Reader *r) { if (number(r,1)>1) fail(r,FACTORIO_SAVE_UNSUPPORTED); }
static void version(Reader *r) { for(unsigned i=0;i<3;++i) compact(r,2); }
static void localised(Reader *r, unsigned depth) {
if (r->status) return;
if (depth>=DEPTH || ++r->nodes>NODES) { fail(r,FACTORIO_SAVE_PREFIX_LIMIT); return; }
if (number(r,1)) {
string(r);
unsigned children = number(r,1);
for(unsigned i=0;i<children && !r->status;++i) localised(r,depth+1);
}
}
static void ending(Reader *r) {
localised(r,0); localised(r,0);
uint32_t count = compact(r,4);
if (count>NODES) { fail(r,FACTORIO_SAVE_PREFIX_LIMIT); return; }
for(uint32_t i=0;i<count && !r->status;++i) localised(r,0);
localised(r,0); string(r);
}
int factorio_save_layout_bytes(const uint8_t *prefix, size_t length,
FactorioSaveLayout *out, size_t *needed) {
if (!prefix || !out || !needed) return FACTORIO_SAVE_IO;
*needed=0;
if (length<9) { *needed=9; return FACTORIO_SAVE_SHORT_HEADER; }
FactorioMapVersion v;
if (factorio_version_parse(prefix,9,&v)) return FACTORIO_SAVE_SHORT_HEADER;
if (v.major!=2 || v.minor!=0 || v.patch!=77 || v.build || v.flag)
return FACTORIO_SAVE_UNSUPPORTED;
Reader r = {.bytes=prefix,.length=length,.cursor=9};
// ScenarioLocation: three compact-length strings.
string(&r); string(&r); string(&r);
if(number(&r,1)>2) fail(&r,FACTORIO_SAVE_UNSUPPORTED); // Difficulty
boolean(&r); boolean(&r); string(&r);
for(unsigned i=0;i<4;++i) boolean(&r);
version(&r); number(&r,4); boolean(&r); number(&r,4);
uint32_t mods=compact(&r,4);
if(mods>NODES) fail(&r,FACTORIO_SAVE_PREFIX_LIMIT);
for(uint32_t i=0;i<mods && !r.status;++i) { string(&r); version(&r); number(&r,4); }
number(&r,4);
// PropertyTree type=5 (dictionary), any-type boolean, uint32 child count.
// Reject other/nonempty settings instead of guessing recursive wire layouts.
if(number(&r,1)!=5) fail(&r,FACTORIO_SAVE_UNSUPPORTED);
boolean(&r);
if(number(&r,4)) fail(&r,FACTORIO_SAVE_UNSUPPORTED);
ending(&r); ending(&r); boolean(&r);
if(r.status) { *needed=r.needed; return r.status; }
size_t position=r.cursor;
if (!take(&r,24)) { *needed=r.needed; return r.status; }
FactorioSaveLayout result = {.header_offset=position,.next_offset=r.cursor,.mod_count=mods};
if(factorio_map_header_parse(prefix+position,24,v,&result.header)) return FACTORIO_SAVE_SHORT_HEADER;
*out=result;
return FACTORIO_SAVE_OK;
}
int factorio_save_map_header(const char *path, FactorioSaveLayout *out) {
if(!path || !out) return FACTORIO_SAVE_IO;
size_t requested=9;
// Retry exactly the bytes the parser needs. Avoid assuming a fixed offset or
// requiring a large prefix when a valid short stream contains the whole header.
for(unsigned attempt=0;attempt<128;++attempt) {
uint8_t *prefix=malloc(requested);
if(!prefix) return FACTORIO_SAVE_IO;
int status=factorio_save_prefix(path,prefix,requested);
size_t needed=0;
if(!status) status=factorio_save_layout_bytes(prefix,requested,out,&needed);
free(prefix);
if(status!=FACTORIO_SAVE_SHORT_HEADER || needed<=requested) return status;
requested=needed;
}
return FACTORIO_SAVE_PREFIX_LIMIT;
}
const char *factorio_save_layout_status_string(int status) {
switch(status) {
case FACTORIO_SAVE_OK: return "Map header read; full world is not loaded";
case FACTORIO_SAVE_UNSUPPORTED: return "Unsupported save metadata or archive; currently supports 2.0.77 with empty startup settings";
case FACTORIO_SAVE_SHORT_HEADER: return "Incomplete scenario metadata or map header";
case FACTORIO_SAVE_PREFIX_LIMIT: return "Scenario prefix, recursion or read limit reached";
default: return factorio_save_status_string(status);
}
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef FACTORIO_SAVE_LAYOUT_H
#define FACTORIO_SAVE_LAYOUT_H
#include "save_header.h"
#include "map_header_bridge.h"
/* Bounded host reconstruction of the 2.0.77 base-game scenario envelope.
* Only empty startup PropertyTree settings are currently supported.
* This locates MapHeader; it does not initialize a Scenario or load a world. */
typedef struct {
FactorioMapHeader header;
size_t header_offset, next_offset;
uint32_t mod_count;
} FactorioSaveLayout;
int factorio_save_layout_bytes(const uint8_t *prefix, size_t length,
FactorioSaveLayout *out, size_t *needed);
int factorio_save_map_header(const char *path, FactorioSaveLayout *out);
const char *factorio_save_layout_status_string(int status);
#endif
+66
View File
@@ -0,0 +1,66 @@
#include "target_bridge.h"
#include "target_host.h"
#include <string.h>
u32 w2c_target__host_allocate(struct w2c_target__host *env, u32 context) {
wasm_rt_memory_t *memory=w2c_factorio__target_memory(env->module);
if (memory->size<256 || context>memory->size-256 || env->heap.memory!=memory->data ||
env->heap.memory_size!=memory->size) return 2;
uint64_t size,address;
memcpy(&size,memory->data+context,8);
int status=factorio_guest_heap_allocate(&env->heap,size,&address);
if (status) return status==1 ? 5 : 2;
memcpy(memory->data+context,&address,8);
return 0;
}
u32 w2c_target__host_unsupported_delete(struct w2c_target__host *env,u64 a,u64 b,u64 c) {
(void)env;(void)a;(void)b;(void)c;return 6;
}
u32 w2c_target__host_unsupported_length(struct w2c_target__host *env,u64 a,u64 b,u64 c) {
return w2c_target__host_unsupported_delete(env,a,b,c);
}
u32 w2c_target__host_unsupported_array_length(struct w2c_target__host *env,u64 a,u64 b,u64 c) {
return w2c_target__host_unsupported_delete(env,a,b,c);
}
static uint64_t get(const uint8_t *memory,size_t offset) {
uint64_t value;memcpy(&value,memory+offset,8);return value;
}
int factorio_target_probe(FactorioTargetProbe *out) {
if (!out) return 2;
if (!wasm_rt_is_initialized()) wasm_rt_init();
w2c_factorio__target module;
struct w2c_target__host env={.module=&module};
wasm2c_factorio__target_instantiate(&module,&env);
wasm_rt_memory_t *ram=w2c_factorio__target_memory(&module);
uint8_t *memory=ram->data;
memset(memory,0xa5,(size_t)ram->size);
int status=factorio_guest_heap_init(&env.heap,memory,ram->size,131072,196608);
uint64_t registers[32]={0};
registers[0]=4096;registers[31]=ram->size-256;
memcpy(memory,registers,sizeof(registers));
if (!status) status=(int)w2c_factorio__target_run(&module,0,10000);
if (!status) {
/* Fixed allocation trace is part of this pinned constructor's contract.
* No dereference occurs until those offsets have been checked. */
uint64_t block=131072,targets=131104,vector=133152,last=133168;
if (env.heap.allocations!=4 || env.heap.cursor!=last+8 || get(memory,0)!=4096 ||
memory[4096]!=0 || memory[4096+0x88]!=0 ||
get(memory,4104)!=vector || get(memory,4112)!=vector+8 || get(memory,4120)!=vector+8 ||
get(memory,4128)!=64 || get(memory,4136)!=0 || get(memory,4144)!=0 ||
get(memory,4096+0x38)!=last || get(memory,4096+0x40)!=last+8 ||
get(memory,4096+0x48)!=last+8 || get(memory,last)!=0 ||
get(memory,vector)!=block || get(memory,block)!=targets ||
get(memory,block+8)!=targets+2048 || get(memory,block+16)!=targets+2048) status=7;
for (unsigned i=0;!status && i<64;++i) {
if (get(memory,targets+i*32)!=65536+0xda8 ||
get(memory,targets+i*32+8) || get(memory,targets+i*32+16) || get(memory,targets+i*32+24)) status=7;
}
for (unsigned i=0x50;!status && i<0x81;++i) if (memory[4096+i]) status=7;
if (!status) *out=(FactorioTargetProbe){4,64,64,env.heap.cursor-env.heap.begin};
}
wasm2c_factorio__target_free(&module);
return status;
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef FACTORIO_TARGET_BRIDGE_H
#define FACTORIO_TARGET_BRIDGE_H
#include <stdint.h>
typedef struct {
unsigned allocations, pool_capacity, initialized_targets;
uint64_t heap_bytes;
} FactorioTargetProbe;
/* Executes the original initialization graph in an isolated 1 MiB guest.
* 0 verified; 5 heap exhausted; 6 unsupported service; other values are errors.
* Result commits only after complete object-graph validation. */
int factorio_target_probe(FactorioTargetProbe *out);
#endif
+9
View File
@@ -0,0 +1,9 @@
#ifndef FACTORIO_TARGET_HOST_H
#define FACTORIO_TARGET_HOST_H
#include "factorio_target.h"
#include "guest_heap.h"
struct w2c_target__host {
w2c_factorio__target *module;
FactorioGuestHeap heap;
};
#endif
+51
View File
@@ -0,0 +1,51 @@
#include "version_bridge.h"
#include "version_host.h"
#include <string.h>
static uint64_t read64(const void *p) {
uint64_t value;
memcpy(&value, p, sizeof(value));
return value;
}
u32 w2c_env_read_or_throw(struct w2c_env *env, u64 source, u64 destination, u64 size) {
wasm_rt_memory_t *memory = w2c_factorio__version_memory(env->module);
if (memory->size < 24 || source > memory->size - 24 ||
size > memory->size || destination > memory->size - size) return 2;
uint64_t data = read64(memory->data + source);
uint64_t length = read64(memory->data + source + 8);
uint64_t cursor = read64(memory->data + source + 16);
if (data > memory->size || length > memory->size - data) return 2;
if (cursor > length || size > length - cursor) return 1;
memmove(memory->data + destination, memory->data + data + cursor, (size_t)size);
cursor += size;
memcpy(memory->data + source + 16, &cursor, sizeof(cursor));
++env->reads;
return 0;
}
int factorio_version_parse(const uint8_t *bytes, size_t length, FactorioMapVersion *out) {
if (!bytes || !out) return 2;
if (!wasm_rt_is_initialized()) wasm_rt_init();
w2c_factorio__version module;
struct w2c_env env = {.module = &module};
wasm2c_factorio__version_instantiate(&module, &env);
uint8_t *memory = w2c_factorio__version_memory(&module)->data;
uint64_t registers[32] = {0};
registers[0] = 2048; /* guest output object */
registers[1] = 512; /* opaque stream adapter */
registers[31] = 65280;
uint64_t reader[3] = {4096, length < 9 ? length : 9, 0};
memcpy(memory, registers, sizeof(registers));
memcpy(memory + 512, reader, sizeof(reader));
memcpy(memory + 4096, bytes, (size_t)reader[1]);
int result = (int)w2c_factorio__version_run(&module, 0);
if (!result) {
uint64_t packed = read64(memory + 2048);
*out = (FactorioMapVersion){.major = (uint16_t)(packed >> 48),
.minor = (uint16_t)(packed >> 32), .patch = (uint16_t)(packed >> 16),
.build = (uint16_t)packed, .flag = memory[2056]};
}
wasm2c_factorio__version_free(&module);
return result;
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef FACTORIO_VERSION_BRIDGE_H
#define FACTORIO_VERSION_BRIDGE_H
#include <stddef.h>
#include <stdint.h>
typedef struct {
uint16_t major, minor, patch, build;
uint8_t flag; /* Stored byte; semantics have not yet been recovered. */
} FactorioMapVersion;
/* 0=success; 1=short header; 2=invalid memory. Parses only the version header. */
int factorio_version_parse(const uint8_t *bytes, size_t length, FactorioMapVersion *out);
#endif
+8
View File
@@ -0,0 +1,8 @@
#ifndef FACTORIO_VERSION_HOST_H
#define FACTORIO_VERSION_HOST_H
#include "factorio_version.h"
struct w2c_env {
w2c_factorio__version *module;
uint32_t reads;
};
#endif