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 @@
|
||||
#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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user