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