Files
factorio-ios/tests/deserialiser_equivalence.c
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

146 lines
7.7 KiB
C

#include "deserialiser_bridge.h"
#include "deserialiser_host.h"
#include <assert.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern void *factorio_deser_original(void *,void *,void *,unsigned);
extern const uint8_t native_deser_vtable[];
static _Alignas(16) uint8_t native_heap[65536],native_object[1224],native_external[144];
static size_t native_cursor,native_limit;
static unsigned native_allocations,native_reads,native_zero_calls,native_version_errors;
static jmp_buf abort_target;
typedef struct {const uint8_t *data;size_t length,cursor;} NativeReader;
static NativeReader native_reader;
void *native_deser_allocate(size_t size) {
size_t address=(native_cursor+15)&~(size_t)15;
if(address>native_limit || size>native_limit-address) longjmp(abort_target,5);
native_cursor=address+size;++native_allocations;return native_heap+address;
}
static uint64_t get(const uint8_t *data,size_t at) {uint64_t x;memcpy(&x,data+at,8);return x;}
static void put(uint8_t *data,size_t at,uint64_t x) {memcpy(data+at,&x,8);}
void native_deser_read_or_throw(const uint8_t *source,void *destination,size_t size) {
NativeReader *reader=(NativeReader *)(uintptr_t)get(source,8);
assert(reader==&native_reader);
if(reader->cursor>reader->length || size>reader->length-reader->cursor) longjmp(abort_target,1);
memcpy(destination,reader->data+reader->cursor,size);reader->cursor+=size;++native_reads;
}
void native_deser_zero_memory(void *destination,size_t size) {memset(destination,0,size);++native_zero_calls;}
void native_deser_version_error(void) {++native_version_errors;longjmp(abort_target,7);}
void native_deser_unsupported(void) {abort();}
void native_deser_unsupported_delete(void) {abort();}
void native_deser_unsupported_length(void) {abort();}
void native_deser_unsupported_array_length(void) {abort();}
static uint64_t canonical(uint64_t value,uint64_t heap,uint64_t object) {
if(value>=(uintptr_t)native_heap && value<=(uintptr_t)native_heap+native_cursor)
return heap+value-(uintptr_t)native_heap;
if(value>=(uintptr_t)native_object && value<=(uintptr_t)native_object+sizeof(native_object))
return object+value-(uintptr_t)native_object;
if(value>=(uintptr_t)native_external && value<=(uintptr_t)native_external+sizeof(native_external))
return 8192+value-(uintptr_t)native_external;
if(value==(uintptr_t)&native_reader) return 512;
if(value==(uintptr_t)native_deser_vtable+16) return 65536+0xda8;
return value;
}
static void normalize(uint8_t *data,size_t length,uint64_t heap,uint64_t object) {
for(size_t i=0;i+8<=length;i+=8) put(data,i,canonical(get(data,i),heap,object));
}
static uint8_t payload[9];
static void version(unsigned major,unsigned minor,unsigned patch,unsigned build,unsigned flag) {
const uint16_t fields[]={(uint16_t)major,(uint16_t)minor,(uint16_t)patch,(uint16_t)build};
memcpy(payload,fields,8);payload[8]=(uint8_t)flag;
}
static void compare(w2c_factorio__deser *module,struct w2c_deser__host *env,unsigned iteration,
size_t length,size_t limit,int external,unsigned owns,int expected) {
uint8_t *memory=w2c_factorio__deser_memory(module)->data;
uint64_t begin=131072+(iteration%32)*16,object=4096+(iteration%8)*256;
memset(memory,0xa5,1048576);memset(native_heap,0xa5,sizeof(native_heap));
memset(native_object,0xa5,sizeof(native_object));memset(native_external,0xa5,sizeof(native_external));
native_cursor=0;native_allocations=0;native_reads=0;native_zero_calls=0;native_version_errors=0;
native_limit=limit;native_reader=(NativeReader){payload,length,0};
int native_status=setjmp(abort_target);
if(!native_status) assert(factorio_deser_original(native_object,&native_reader,external?native_external:NULL,owns)==native_object);
assert(native_status==expected);
*env=(struct w2c_deser__host){.module=module};
assert(factorio_guest_heap_init(&env->heap,memory,1048576,begin,begin+limit)==0);
uint64_t registers[32]={0};registers[0]=object;registers[1]=512;
registers[2]=external?8192:0;registers[3]=owns;registers[31]=1048576-256;
for(unsigned i=19;i<30;++i) registers[i]=UINT64_C(0x123456789abc0000)+iteration+i;
uint64_t stream[]={16384,length,0};
memcpy(memory,registers,sizeof(registers));memcpy(memory+512,stream,sizeof(stream));
memcpy(memory+16384,payload,length);
unsigned status=w2c_factorio__deser_run(module,0,10000);
assert(status==(unsigned)native_status);
assert(env->heap.allocations==native_allocations && env->heap.cursor-begin==native_cursor);
assert(env->reads==native_reads && env->zero_calls==native_zero_calls && env->version_errors==native_version_errors);
assert(get(memory,528)==native_reader.cursor && !env->unsupported_calls);
if(!status) {
normalize(native_object,sizeof(native_object),begin,object);
normalize(native_heap,native_cursor,begin,object);
assert(memcmp(memory+object,native_object,sizeof(native_object))==0);
assert(memcmp(memory+begin,native_heap,native_cursor)==0);
assert(get(memory,0)==object);
for(unsigned i=19;i<32;++i) assert(get(memory,i*8)==registers[i]);
}
for(size_t i=256;i<1048576;++i) {
if(i>=object && i<object+sizeof(native_object)) continue;
if(i>=begin && i<begin+native_cursor) continue;
if(i>=512 && i<536) continue;
if(i>=16384 && i<16384+length) continue;
if(i>=1048576-256-368 && i<1048576-256) continue;
assert(memory[i]==0xa5);
}
}
int main(void) {
wasm_rt_init();w2c_factorio__deser module;
struct w2c_deser__host env={.module=&module};wasm2c_factorio__deser_instantiate(&module,&env);
for(unsigned i=0;i<512;++i) {
if(i%2) version(2,0,i%78,i%78==77?0:i,0);
else version(1,i%65500,i*13,i*29,0);
compare(&module,&env,i,9,65536,i%3==0,i%2,0);
}
for(unsigned length=0;length<9;++length) {
version(2,0,77,0,0);compare(&module,&env,length,length,65536,0,0,1);
}
const unsigned invalid[][5]={{0,0,0,0,0},{2,0,77,1,0},{2,0,78,0,0},{2,1,0,0,0},
{3,0,0,0,0},{2,0,77,0,1},{1,2,0,0,0},{1,2,0,27,0}};
for(unsigned i=0;i<sizeof(invalid)/sizeof(invalid[0]);++i) {
version(invalid[i][0],invalid[i][1],invalid[i][2],invalid[i][3],invalid[i][4]);
compare(&module,&env,i,9,65536,0,0,7);
}
version(1,2,0,28,0);compare(&module,&env,0,9,65536,0,0,0);
version(2,0,77,0,0);compare(&module,&env,0,9,65536,0,0,0);
uint64_t required=env.heap.cursor-env.heap.begin;
/* Find all allocation boundaries from the original constructor's trace. */
const size_t sizes[]={48,8,56,8192,24,48,56,144,24,2048,8,8};
size_t cursor=0;
for(unsigned i=0;i<sizeof(sizes)/sizeof(sizes[0]);++i) {
cursor=(cursor+15)&~(size_t)15;
compare(&module,&env,i,9,cursor+sizes[i]-1,0,0,5);
cursor+=sizes[i];
}
assert(cursor==required);
compare(&module,&env,0,9,required,0,0,0);
FactorioDeserialiserProbe report;
assert(factorio_deserialiser_probe(payload,9,&report)==0);
assert(report.version.major==2 && report.version.patch==77 && report.allocations==12 &&
report.reads==5 && report.stream_cursor==9 && report.target_capacity==64 && report.owns_target==1 && report.heap_bytes==required);
FactorioDeserialiserProbe untouched;memset(&untouched,0xa5,sizeof(untouched));
for(unsigned n=0;n<9;++n) {
report=untouched;assert(factorio_deserialiser_probe(payload,n,&report)==1);
assert(memcmp(&report,&untouched,sizeof(report))==0);
}
version(2,0,78,0,0);report=untouched;
assert(factorio_deserialiser_probe(payload,9,&report)==7 && memcmp(&report,&untouched,sizeof(report))==0);
assert(factorio_deserialiser_probe(NULL,9,&report)==2);
assert(factorio_deserialiser_probe(payload,9,NULL)==2);
wasm2c_factorio__deser_free(&module);wasm_rt_free();
printf("PASS: 512 original MapDeserialiser graphs, supplied/owned targets, 9 truncations, 8 version errors, 12 allocation failures; heap %llu bytes\n",(unsigned long long)required);
return 0;
}