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,130 @@
|
||||
"""Execute branch/loop translations against original ARM64 instruction words."""
|
||||
import json
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / 'tools'))
|
||||
from a64_lift import Compiler, UnsupportedInstruction
|
||||
|
||||
OUT = ROOT / 'build/cfg'
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
# Hand-authored instruction fixtures, NOT recovered Factorio functions.
|
||||
# Original relative branch offsets are preserved in the native oracle.
|
||||
FIXTURES = {
|
||||
'countdown': [0x34000060, 0x51000400, 0x17fffffe, 0xd65f03c0],
|
||||
'bit63': [0xb6f80060, 0xd2800060, 0x14000002, 0xd28000e0, 0xd65f03c0],
|
||||
'nonzero32': [0x35000060, 0xd28000a0, 0x14000002, 0xd2800160, 0xd65f03c0],
|
||||
'nonzero64': [0xb5000060, 0xd28000a0, 0x14000002, 0xd2800160, 0xd65f03c0],
|
||||
'bit0set': [0x37000060, 0xd2800060, 0x14000002, 0xd28000e0, 0xd65f03c0],
|
||||
}
|
||||
for operation, opcode in [('cmp32', 0x6b01001f), ('cmp64', 0xeb01001f),
|
||||
('cmn32', 0x2b01001f), ('cmn64', 0xab01001f)]:
|
||||
for condition in range(16):
|
||||
FIXTURES[f'{operation}c{condition}'] = [opcode, 0x54000060 | condition,
|
||||
0xd2800060, 0x14000002, 0xd28000e0, 0xd65f03c0]
|
||||
for operation, opcode in [('addimm64', 0xb100041f), ('subimm32', 0x7100041f),
|
||||
('asr32', 0x6b817c1f), ('asr64', 0xeb81fc1f)]:
|
||||
for condition in (0, 2, 6, 10):
|
||||
FIXTURES[f'{operation}c{condition}'] = [opcode, 0x54000060 | condition,
|
||||
0xd2800060, 0x14000002, 0xd28000e0, 0xd65f03c0]
|
||||
for width in (32, 64):
|
||||
for operation in range(4):
|
||||
condition = 10 if width == 32 else 8
|
||||
select = (0x1a810000 if width == 32 else 0x9a810000) | condition << 12
|
||||
select |= (operation >> 1) << 30 | (operation & 1) << 10
|
||||
FIXTURES[f'select{width}op{operation}'] = [0x14000001,
|
||||
0x6b01001f if width == 32 else 0xeb01001f, select, 0xd65f03c0]
|
||||
|
||||
|
||||
def run(*args):
|
||||
subprocess.run(list(map(str, args)), cwd=ROOT, check=True)
|
||||
|
||||
|
||||
assembly = ['.text', '.p2align 2']
|
||||
for name, words in FIXTURES.items():
|
||||
body = struct.pack('<' + 'I' * len(words), *words)
|
||||
(OUT / f'{name}.wat').write_text(Compiler().compile(body, 0x1000))
|
||||
run(ROOT / 'build/wabt/wat2wasm', OUT / f'{name}.wat', '-o', OUT / f'{name}.wasm')
|
||||
run(ROOT / 'build/wabt/wasm2c', OUT / f'{name}.wasm', '-o', OUT / f'{name}.c', '--module-name=' + name)
|
||||
assembly += [f'.globl _native_{name}', f'_native_{name}:']
|
||||
assembly += [f'.long 0x{word:08x}' for word in words]
|
||||
(OUT / 'oracle.S').write_text('\n'.join(assembly) + '\n')
|
||||
|
||||
for words in ([0x14000002, 0xd65f03c0], # target outside body
|
||||
[0x14000002, 0xd2800000, 0xd65f03c0], # unreachable code
|
||||
[0x34000040, 0x14000002, 0xd65f03c0], # one edge leaves body
|
||||
[0x54000040, 0xd2800000, 0xd65f03c0], # flags undefined
|
||||
[0x34000040, 0xf100041f, 0x54000040, 0xd2800000, 0xd65f03c0], # flags defined on only one path
|
||||
[0x14000000]): # no RET
|
||||
try:
|
||||
Compiler().compile(struct.pack('<' + 'I' * len(words), *words), 0x1000)
|
||||
except UnsupportedInstruction:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError('Invalid CFG accepted')
|
||||
|
||||
includes = '\n'.join(f'#include "{name}.h"\nextern uint64_t native_{name}(uint64_t, uint64_t);' for name in FIXTURES)
|
||||
checks = []
|
||||
for name in FIXTURES:
|
||||
inputs = 'i % 201' if name == 'countdown' else 'values[i % 8] ^ (i < 64 ? 0 : random_value(&seed))'
|
||||
checks.append(f'''
|
||||
{{
|
||||
w2c_{name} module;
|
||||
wasm2c_{name}_instantiate(&module);
|
||||
uint8_t *memory = w2c_{name}_memory(&module)->data;
|
||||
for (unsigned i = 0; i < 20000; ++i) {{
|
||||
uint64_t input = {inputs};
|
||||
uint64_t registers[32];
|
||||
for (int r = 0; r < 32; ++r) registers[r] = random_value(&seed);
|
||||
registers[0] = input;
|
||||
registers[1] = i < 64 ? values[(i / 8) % 8] : registers[1];
|
||||
memset(memory, 0xa5, 65536);
|
||||
memcpy(memory, registers, sizeof(registers));
|
||||
assert(w2c_{name}_run(&module, 0, 1000) == 0);
|
||||
uint64_t output[32]; memcpy(output, memory, sizeof(output));
|
||||
assert(output[0] == native_{name}(input, registers[1]));
|
||||
assert(memcmp(output + 1, registers + 1, 31 * sizeof(uint64_t)) == 0);
|
||||
for (unsigned j = 256; j < 65536; ++j) assert(memory[j] == 0xa5);
|
||||
}}
|
||||
wasm2c_{name}_free(&module);
|
||||
}}''')
|
||||
(OUT / 'verify.c').write_text(f'''
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
{includes}
|
||||
static uint64_t random_value(uint64_t *state) {{
|
||||
*state ^= *state << 13; *state ^= *state >> 7; *state ^= *state << 17; return *state;
|
||||
}}
|
||||
int main(void) {{
|
||||
wasm_rt_init();
|
||||
uint64_t seed = 0x123456789abcdef;
|
||||
uint64_t values[] = {{0, 1, 0xffffffff, 0x100000000, 0x8000000000000000,
|
||||
0xffffffffffffffff, 0x7fffffffffffffff, 2}};
|
||||
{''.join(checks)}
|
||||
w2c_countdown loop;
|
||||
wasm2c_countdown_instantiate(&loop);
|
||||
uint64_t start = 100;
|
||||
memcpy(w2c_countdown_memory(&loop)->data, &start, 8);
|
||||
assert(w2c_countdown_run(&loop, 0, 0) == 3);
|
||||
assert(w2c_countdown_run(&loop, 0, 1) == 3);
|
||||
assert(w2c_countdown_run(&loop, 0, 1000) == 0);
|
||||
wasm2c_countdown_free(&loop);
|
||||
wasm_rt_free();
|
||||
puts("PASS: {len(FIXTURES) * 20000} native branch/loop/NZCV comparisons, preserved registers, memory guards, budget and CFG rejection");
|
||||
return 0;
|
||||
}}
|
||||
''')
|
||||
runtime = ROOT / 'third_party/wabt/wasm2c'
|
||||
run('xcrun', 'clang', '-O2', '-DWASM_RT_USE_MMAP=0', '-DWASM_RT_MEMCHECK_BOUNDS_CHECK=1',
|
||||
'-I' + str(OUT), '-I' + str(runtime), *(OUT / f'{name}.c' for name in FIXTURES),
|
||||
OUT / 'verify.c', OUT / 'oracle.S', runtime / 'wasm-rt-impl.c', runtime / 'wasm-rt-mem-impl.c',
|
||||
'-o', OUT / 'verify')
|
||||
run(OUT / 'verify')
|
||||
(OUT / 'verification.json').write_text(json.dumps({'status': 'pass', 'native_comparisons': len(FIXTURES) * 20000,
|
||||
'fixtures': list(FIXTURES), 'source': 'hand-authored instruction fixtures; not game code',
|
||||
'cfg_invalid_cases': 6, 'budget_exhaustion_status': 3}, indent=2) + '\n')
|
||||
@@ -0,0 +1,21 @@
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).parents[1] / 'tools'))
|
||||
from a64_lift import Compiler, UnsupportedInstruction
|
||||
|
||||
for words, reason in [([0x14000000, 0xd65f03c0], 'loop with unreachable return'),
|
||||
([0x94000000, 0xd65f03c0], 'undeclared host call'),
|
||||
([0x91000400], 'missing return'),
|
||||
([0xd65f03c0, 0xd503201f], 'code after return'),
|
||||
([0x9a010000, 0xd65f03c0], 'unsupported carry arithmetic'),
|
||||
([0x9a810000, 0xd65f03c0], 'conditional select without defined flags'),
|
||||
([0x3cc18020, 0xd65f03c0], '128-bit load without adjacent store'),
|
||||
([0x3d800000, 0xd65f03c0], '128-bit store without adjacent load'),
|
||||
([0x14000002, 0x3cc18020, 0x3d800000, 0xd65f03c0], 'branch into middle of copy')]:
|
||||
try:
|
||||
Compiler().compile(b''.join(struct.pack('<I', word) for word in words), 0x1000)
|
||||
except UnsupportedInstruction:
|
||||
print('PASS: rejected', reason)
|
||||
else:
|
||||
raise AssertionError('Unexpected support for ' + reason)
|
||||
@@ -0,0 +1,100 @@
|
||||
"""Native comparisons for the instruction forms added for allocation/initialization."""
|
||||
import json
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0,str(ROOT/'tools'))
|
||||
from a64_program import ProgramCompiler
|
||||
from a64_lift import UnsupportedInstruction
|
||||
OUT=ROOT/'build/initialization'
|
||||
OUT.mkdir(parents=True,exist_ok=True)
|
||||
def pack(words): return struct.pack('<'+'I'*len(words),*words)
|
||||
def run(*args): subprocess.run(list(map(str,args)),cwd=ROOT,check=True)
|
||||
|
||||
operations=[]
|
||||
for width in (32,64):
|
||||
for shift in range(width):
|
||||
operations.append((0x13000000 if width==32 else 0x93400000) | shift<<16 | (width-1)<<10)
|
||||
for length in range(1,6 if width==32 else 7):
|
||||
for operation in range(3):
|
||||
# Rotate repeated patterns at every supported element size.
|
||||
n=int(length==6)
|
||||
s=((((~((1 << (length+1))-1))&63) | ((1 << length)//2-1)))
|
||||
rotation=(1 << length)-1
|
||||
operations.append((0x12000000 if width==32 else 0x92000000) | operation<<29 | n<<22 | rotation<<16 | s<<10)
|
||||
words=[]
|
||||
for word in operations:
|
||||
words += [0xaa0203e0,word,0xf8008420] # mov x0,x2; op; str x0,[x1],#8
|
||||
words += [0xd65f03c0]
|
||||
compiler=ProgramCompiler(memory_pages=2)
|
||||
(OUT/'initialization.wat').write_text(compiler.compile({0x1000:pack(words)},0x1000))
|
||||
run(ROOT/'build/wabt/wat2wasm',OUT/'initialization.wat','-o',OUT/'initialization.wasm')
|
||||
run(ROOT/'build/wabt/wasm2c',OUT/'initialization.wasm','-o',OUT/'initialization.c','--module-name=initialization')
|
||||
(OUT/'oracle.S').write_text('.text\n.p2align 2\n.globl _native_initialization\n_native_initialization:\n'+
|
||||
'\n'.join(f'.long 0x{w:08x}' for w in words)+'\n')
|
||||
invalid=[
|
||||
[0x3c800000,0xd65f03c0], # uninitialized vector register
|
||||
[0xb4000040,0x6f00e400,0x3c800000,0xd65f03c0], # zero is missing on one incoming path
|
||||
[0x14000002,0x6f00e400,0x3c800000,0xd65f03c0], # branch into the idiom
|
||||
[0x6f00e400,0x3c800001,0xd65f03c0], # mismatched vector register
|
||||
[0x90000000,0xd65f03c0], # undeclared static page
|
||||
[0x9240fc00,0xd65f03c0], # reserved all-ones logical mask
|
||||
[0x93400000,0xd65f03c0], # signed bitfield beyond ASR
|
||||
]
|
||||
for fixture in invalid:
|
||||
try: ProgramCompiler().compile({0x1000:pack(fixture)},0x1000)
|
||||
except UnsupportedInstruction: pass
|
||||
else: raise AssertionError('Invalid initialization fixture accepted')
|
||||
for options in [dict(memory_pages=0),dict(memory_pages=32769),dict(returning_hosts={1}),
|
||||
dict(static_pages={0:1}),dict(hosts={1:'same',2:'same'},returning_hosts={1})]:
|
||||
try: ProgramCompiler(**options)
|
||||
except ValueError: pass
|
||||
else: raise AssertionError('Invalid program configuration accepted')
|
||||
(OUT/'verify.c').write_text(f'''
|
||||
#include "initialization.h"
|
||||
#include "wasm-rt-impl.h"
|
||||
#include "wasm-rt-exceptions.h"
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
extern uint64_t native_initialization(uint64_t,uint64_t *,uint64_t);
|
||||
int main(void) {{
|
||||
wasm_rt_init();w2c_initialization module;wasm2c_initialization_instantiate(&module);
|
||||
uint8_t *memory=w2c_initialization_memory(&module)->data;
|
||||
uint64_t seed=0xdeadbeef12345678,reference[{len(operations)}];
|
||||
for(unsigned i=0;i<512;++i) {{
|
||||
seed^=seed<<13;seed^=seed>>7;seed^=seed<<17;
|
||||
uint64_t input=i==0 ? 0 : i==1 ? UINT64_MAX : seed;
|
||||
uint64_t registers[32]={{0}};registers[1]=66048;registers[2]=input;
|
||||
memset(memory,0xa5,131072);memcpy(memory,registers,sizeof(registers));
|
||||
native_initialization(0,reference,input);
|
||||
assert(w2c_initialization_run(&module,0,10000)==0);
|
||||
assert(memcmp(memory+66048,reference,sizeof(reference))==0);
|
||||
for(unsigned j=256;j<131072;++j) {{
|
||||
if(j>=66048 && j<66048+sizeof(reference)) continue;
|
||||
assert(memory[j]==0xa5);
|
||||
}}
|
||||
}}
|
||||
const uint64_t bad[]={{131072-7,131072,UINT64_C(0x100000000)+66048,UINT64_MAX-7}};
|
||||
for(unsigned i=0;i<sizeof(bad)/sizeof(bad[0]);++i) {{
|
||||
uint64_t registers[32]={{0}};registers[1]=bad[i];
|
||||
memset(memory,0xa5,131072);memcpy(memory,registers,sizeof(registers));
|
||||
wasm_rt_trap_t trap=wasm_rt_impl_try();
|
||||
if(!trap) {{w2c_initialization_run(&module,0,10000);assert(0);}}
|
||||
assert(trap==WASM_RT_TRAP_UNREACHABLE);
|
||||
for(unsigned j=256;j<131072;++j) assert(memory[j]==0xa5);
|
||||
}}
|
||||
wasm2c_initialization_free(&module);wasm_rt_free();
|
||||
puts("PASS: {len(operations)*512} native ASR/logical-mask comparisons, extended memory, 64-bit address guards, 12 rejection cases");
|
||||
}}
|
||||
''')
|
||||
runtime=ROOT/'third_party/wabt/wasm2c'
|
||||
run('xcrun','clang','-O2','-DWASM_RT_USE_MMAP=0','-DWASM_RT_MEMCHECK_BOUNDS_CHECK=1',
|
||||
'-I'+str(OUT),'-I'+str(runtime),OUT/'initialization.c',OUT/'verify.c',OUT/'oracle.S',
|
||||
runtime/'wasm-rt-impl.c',runtime/'wasm-rt-mem-impl.c',runtime/'wasm-rt-exceptions-impl.c','-o',OUT/'verify')
|
||||
run(OUT/'verify')
|
||||
(OUT/'verification.json').write_text(json.dumps({'status':'pass','native_comparisons':len(operations)*512,
|
||||
'instruction_forms':len(operations),'invalid_cases':12,'source':'hand-authored instruction fixtures'},indent=2)+'\n')
|
||||
@@ -0,0 +1,137 @@
|
||||
"""Compare direct guest calls, nested LR saves, recursion, tails and memory with ARM64."""
|
||||
import json
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0,str(ROOT/'tools'))
|
||||
from a64_program import ProgramCompiler
|
||||
from a64_lift import UnsupportedInstruction
|
||||
|
||||
OUT = ROOT/'build/program'
|
||||
OUT.mkdir(parents=True,exist_ok=True)
|
||||
|
||||
|
||||
def bl(site,target): return 0x94000000 | (((target-site)//4)&0x3ffffff)
|
||||
def body(words): return struct.pack('<'+'I'*len(words),*words)
|
||||
def run(*args): subprocess.run(list(map(str,args)),cwd=ROOT,check=True)
|
||||
|
||||
|
||||
fixtures = {
|
||||
'nested': (0x1000, {
|
||||
0x1000:[0xa9bf7bf3,0xaa0003f3,bl(0x1008,0x1100),bl(0x100c,0x1200),0x8b130000,0xa8c17bf3,0xd65f03c0],
|
||||
0x1100:[0x91000400,0xd65f03c0],
|
||||
0x1200:[0xa9bf7bf4,bl(0x1204,0x1100),0xa8c17bf4,0x91000800,0xd65f03c0]}),
|
||||
'recursive': (0x2000, {0x2000:[0xa9bf7bf3,0xb4000080,0xd1000400,
|
||||
bl(0x200c,0x2000),0x91000400,0xa8c17bf3,0xd65f03c0]}),
|
||||
'tail': (0x3000, {0x3000:[0x14000040],0x3100:[0x91001c00,0xd65f03c0]}),
|
||||
'memory': (0x4000, {
|
||||
0x4000:[0xa9bf7bf3,0xaa0003f3,bl(0x4008,0x4100),0xaa1303e0,bl(0x4010,0x4100),0xa8c17bf3,0xd65f03c0],
|
||||
0x4100:[0xf9400008,0x91000508,0xf9000008,0xaa0803e0,0xd65f03c0]})}
|
||||
|
||||
assembly = ['.text','.p2align 2']
|
||||
cursor = 0x1000
|
||||
for name,(entry,functions) in fixtures.items():
|
||||
compiler = ProgramCompiler()
|
||||
wat = compiler.compile({address:body(words) for address,words in functions.items()},entry)
|
||||
(OUT/f'{name}.wat').write_text(wat)
|
||||
run(ROOT/'build/wabt/wat2wasm',OUT/f'{name}.wat','-o',OUT/f'{name}.wasm')
|
||||
run(ROOT/'build/wabt/wasm2c',OUT/f'{name}.wasm','-o',OUT/f'{name}.c','--module-name='+name)
|
||||
for address,words in sorted(functions.items()):
|
||||
assert address>=cursor
|
||||
assembly.append(f'.space {address-cursor}')
|
||||
if address==entry: assembly += [f'.globl _native_{name}',f'_native_{name}:']
|
||||
assembly += [f'.long 0x{word:08x}' for word in words]
|
||||
cursor=address+4*len(words)
|
||||
(OUT/'oracle.S').write_text('\n'.join(assembly)+'\n')
|
||||
bad_return = ProgramCompiler().compile({0x5000:body([0xd282469e,0xd65f03c0])},0x5000)
|
||||
(OUT/'badreturn.wat').write_text(bad_return)
|
||||
run(ROOT/'build/wabt/wat2wasm',OUT/'badreturn.wat','-o',OUT/'badreturn.wasm')
|
||||
run(ROOT/'build/wabt/wasm2c',OUT/'badreturn.wasm','-o',OUT/'badreturn.c','--module-name=badreturn')
|
||||
for functions,entry in [({0x1000:body([bl(0x1000,0x1100),0xd65f03c0])},0x1000),
|
||||
({0x1000:body([0xd65f03c0]),0x1100:body([0xd65f03c0])},0x1000),
|
||||
({0x1000:body([0x14000000])},0x1000),
|
||||
({0x1000:body([0x54000040,0xd2800000,0xd65f03c0])},0x1000)]:
|
||||
try: ProgramCompiler().compile(functions,entry)
|
||||
except UnsupportedInstruction: pass
|
||||
else: raise AssertionError('Invalid guest program accepted')
|
||||
|
||||
includes = '\n'.join(f'#include "{name}.h"\nextern uint64_t native_{name}(uint64_t);' for name in fixtures)
|
||||
checks=[]
|
||||
for name in fixtures:
|
||||
argument = 'i%51' if name=='recursive' else 'next(&seed)'
|
||||
prepare = 'memcpy(memory+2048,&expected,8); registers[0]=2048;' if name=='memory' else ''
|
||||
callarg = '(uint64_t)&expected' if name=='memory' else 'input'
|
||||
memory_check = 'assert(memcmp(memory+2048,&expected,8)==0);' if name=='memory' else ''
|
||||
checks.append(f'''
|
||||
{{
|
||||
w2c_{name} module; wasm2c_{name}_instantiate(&module);
|
||||
uint8_t *memory=w2c_{name}_memory(&module)->data;
|
||||
for(unsigned i=0;i<20000;++i) {{
|
||||
uint64_t input={argument}, expected=input, registers[32], after[32];
|
||||
for(unsigned r=0;r<32;++r) registers[r]=next(&seed);
|
||||
registers[0]=input; registers[30]=0; registers[31]=65280;
|
||||
memset(memory,0xa5,65536);
|
||||
{prepare}
|
||||
memcpy(memory,registers,sizeof(registers));
|
||||
uint64_t native=native_{name}({callarg});
|
||||
assert(w2c_{name}_run(&module,0,1000)==0);
|
||||
memcpy(after,memory,sizeof(after));
|
||||
assert(after[0]==native);
|
||||
for(unsigned r=19;r<32;++r) assert(after[r]==registers[r]);
|
||||
{memory_check}
|
||||
for(unsigned a=256;a<65536;++a) {{
|
||||
if(a>=64464 && a<65280) continue;
|
||||
if(a>=2048 && a<2056) continue;
|
||||
assert(memory[a]==0xa5);
|
||||
}}
|
||||
}}
|
||||
uint64_t start[32]={{0}}; start[0]=20; start[31]=65280;
|
||||
memcpy(memory,start,sizeof(start));
|
||||
assert(w2c_{name}_run(&module,0,0)==3);
|
||||
memcpy(memory,start,sizeof(start));
|
||||
assert(w2c_{name}_run(&module,0,1)==3);
|
||||
memcpy(memory,start,sizeof(start));
|
||||
start[30]={fixtures[name][0]}; memcpy(memory,start,sizeof(start));
|
||||
assert(w2c_{name}_run(&module,0,1000)==2);
|
||||
wasm2c_{name}_free(&module);
|
||||
}}
|
||||
''')
|
||||
(OUT/'verify.c').write_text(f'''
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
{includes}
|
||||
#include "badreturn.h"
|
||||
static uint64_t next(uint64_t *s) {{ *s^=*s<<13; *s^=*s>>7; *s^=*s<<17; return *s; }}
|
||||
int main(void) {{
|
||||
wasm_rt_init(); uint64_t seed=0x123456789abcdef;
|
||||
{''.join(checks)}
|
||||
w2c_nested bounded; wasm2c_nested_instantiate(&bounded);
|
||||
uint64_t registers[32]={{0}}; registers[0]=3; registers[31]=65280;
|
||||
memcpy(w2c_nested_memory(&bounded)->data,registers,sizeof(registers));
|
||||
assert(w2c_nested_run(&bounded,0,15)==3);
|
||||
memcpy(w2c_nested_memory(&bounded)->data,registers,sizeof(registers));
|
||||
assert(w2c_nested_run(&bounded,0,16)==0);
|
||||
memcpy(registers,w2c_nested_memory(&bounded)->data,sizeof(registers));
|
||||
assert(registers[0]==10);
|
||||
wasm2c_nested_free(&bounded);
|
||||
w2c_badreturn bad; wasm2c_badreturn_instantiate(&bad);
|
||||
assert(w2c_badreturn_run(&bad,0,10)==4);
|
||||
wasm2c_badreturn_free(&bad);
|
||||
wasm_rt_free();
|
||||
puts("PASS: 80000 native guest-program comparisons, nested calls, recursion, tails, shared memory, guards and failure statuses");
|
||||
return 0;
|
||||
}}
|
||||
''')
|
||||
runtime=ROOT/'third_party/wabt/wasm2c'
|
||||
run('xcrun','clang','-O2','-DWASM_RT_USE_MMAP=0','-DWASM_RT_MEMCHECK_BOUNDS_CHECK=1',
|
||||
'-I'+str(OUT),'-I'+str(runtime),*(OUT/f'{name}.c' for name in [*fixtures,'badreturn']),
|
||||
OUT/'verify.c',OUT/'oracle.S',runtime/'wasm-rt-impl.c',runtime/'wasm-rt-mem-impl.c','-o',OUT/'verify')
|
||||
run(OUT/'verify')
|
||||
(OUT/'verification.json').write_text(json.dumps({'status':'pass','native_comparisons':80000,
|
||||
'source':'hand-authored instruction fixtures, not recovered game code','fixtures':list(fixtures),
|
||||
'scope':'bounded direct guest calls, known returns, shared registers/memory'},indent=2)+'\n')
|
||||
@@ -0,0 +1,74 @@
|
||||
"""Compare zero-vector CFG facts and store addressing with native ARM64 fixtures."""
|
||||
import json
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT/'tools'))
|
||||
from a64_program import ProgramCompiler
|
||||
from a64_lift import UnsupportedInstruction
|
||||
OUT=ROOT/'build/zero-vectors';OUT.mkdir(parents=True,exist_ok=True)
|
||||
def body(words):return struct.pack('<'+'I'*len(words),*words)
|
||||
def run(*args):subprocess.run(list(map(str,args)),cwd=ROOT,check=True)
|
||||
def zero(register):return 0x6f00e400|register
|
||||
def single(register,offset,mode=0):return 0x3c800000|((offset&511)<<12)|(mode<<10)|register
|
||||
def pair(first,second,offset,mode=2):return 0xac000000|(mode<<23)|(((offset//16)&127)<<15)|(second<<10)|first
|
||||
fixtures={
|
||||
'unscaled':[zero(31),single(31,-256),single(31,255),0xd65f03c0],
|
||||
'scaled':[zero(1),0x3d800000|(63<<10)|1,0xd65f03c0],
|
||||
'prepost':[zero(0),single(0,-32,3),single(0,64,1),0xd65f03c0],
|
||||
'pair':[zero(0),zero(31),pair(0,31,-1024),pair(31,0,1008),0xd65f03c0],
|
||||
'pairprepost':[zero(1),zero(31),pair(1,31,-32,3),pair(31,1,64,1),0xd65f03c0],
|
||||
'branch':[zero(0),0x14000001,0x91000408,single(0,0),0xd65f03c0],
|
||||
'merge':[0xb4000061,zero(0),0x14000002,zero(0),single(0,0),0xd65f03c0],
|
||||
'loop':[zero(0),single(0,0),0x91000421,0xf100103f,0x54ffffa3,0xd65f03c0]}
|
||||
assembly=['.text','.p2align 2']
|
||||
for name,words in fixtures.items():
|
||||
(OUT/f'{name}.wat').write_text(ProgramCompiler().compile({0x1000:body(words)},0x1000))
|
||||
run(ROOT/'build/wabt/wat2wasm',OUT/f'{name}.wat','-o',OUT/f'{name}.wasm')
|
||||
run(ROOT/'build/wabt/wasm2c',OUT/f'{name}.wasm','-o',OUT/f'{name}.c','--module-name='+name)
|
||||
assembly += [f'.globl _native_{name}',f'_native_{name}:',*[f'.long 0x{word:08x}' for word in words]]
|
||||
(OUT/'oracle.S').write_text('\n'.join(assembly)+'\n')
|
||||
invalid=[([zero(0),pair(0,1,0),0xd65f03c0],{}),
|
||||
([zero(0),0x9400003f,single(0,0),0xd65f03c0],{0x1100:'clobber'}),
|
||||
([zero(0),pair(0,0,0,0),0xd65f03c0],{}),
|
||||
([zero(0),single(0,0,2),0xd65f03c0],{})]
|
||||
for words,hosts in invalid:
|
||||
try:ProgramCompiler(hosts).compile({0x1000:body(words)},0x1000)
|
||||
except UnsupportedInstruction:pass
|
||||
else:raise AssertionError('Invalid vector facts/access accepted')
|
||||
includes='\n'.join(f'#include "{name}.h"\nextern uint8_t *native_{name}(uint8_t *,uint64_t);' for name in fixtures)
|
||||
checks=[]
|
||||
for name in fixtures:
|
||||
checks.append(f'''
|
||||
{{ w2c_{name} module;wasm2c_{name}_instantiate(&module);
|
||||
uint8_t *memory=w2c_{name}_memory(&module)->data;
|
||||
for(unsigned i=0;i<512;++i) {{
|
||||
uint8_t expected[4096];memset(expected,i&255,sizeof(expected));
|
||||
memset(memory,0xa5,65536);memset(memory+16384,i&255,sizeof(expected));
|
||||
uint64_t registers[32]={{0}};registers[0]=16384+1024;registers[1]=i%2;
|
||||
memcpy(memory,registers,sizeof(registers));
|
||||
uint8_t *result=native_{name}(expected+1024,i%2);
|
||||
assert(w2c_{name}_run(&module,0,1000)==0);
|
||||
uint64_t returned;memcpy(&returned,memory,8);
|
||||
assert(returned==16384+(uint64_t)(result-expected));
|
||||
assert(memcmp(memory+16384,expected,sizeof(expected))==0);
|
||||
for(unsigned j=256;j<65536;++j) {{if(j>=16384 && j<20480)continue;assert(memory[j]==0xa5);}}
|
||||
}}
|
||||
wasm2c_{name}_free(&module); }}''')
|
||||
(OUT/'verify.c').write_text(f'''
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
{includes}
|
||||
int main(void) {{wasm_rt_init();{''.join(checks)}wasm_rt_free();
|
||||
puts("PASS: 4096 native zero-vector store comparisons, pairs, writeback, negative offsets, branches, merges, loops and 4 rejection cases");}}
|
||||
''')
|
||||
runtime=ROOT/'third_party/wabt/wasm2c'
|
||||
run('xcrun','clang','-O2','-DWASM_RT_USE_MMAP=0','-DWASM_RT_MEMCHECK_BOUNDS_CHECK=1',
|
||||
'-I'+str(OUT),'-I'+str(runtime),*[OUT/f'{name}.c' for name in fixtures],OUT/'verify.c',OUT/'oracle.S',
|
||||
runtime/'wasm-rt-impl.c',runtime/'wasm-rt-mem-impl.c','-o',OUT/'verify')
|
||||
run(OUT/'verify')
|
||||
(OUT/'verification.json').write_text(json.dumps({'status':'pass','native_comparisons':4096,
|
||||
'fixtures':list(fixtures),'invalid_cases':4,'scope':'hand-authored zero-vector fixtures'},indent=2)+'\n')
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Check refusal of changed game code/constants without writing modified binaries."""
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
spec = importlib.util.spec_from_file_location("lift_rng", Path(__file__).parents[1] / "tools/lift_rng.py")
|
||||
lift = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(lift)
|
||||
source = Path(sys.argv[1])
|
||||
original = source.read_bytes()
|
||||
image, slice_offset = lift.arm64_image(original)
|
||||
ranges = [("function", lift.FUNCTION_VA, lift.BODY_SIZE),
|
||||
("seed constructor", lift.SEED_VA, lift.SEED_SIZE),
|
||||
("constants", lift.CONSTANT_VA, 32)]
|
||||
for label, address, length in ranges:
|
||||
changed = bytearray(original)
|
||||
changed[slice_offset + lift.file_range(image, address, length)] ^= 1
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
out = Path(folder) / "generated"
|
||||
with patch.object(Path, "read_bytes", return_value=bytes(changed)):
|
||||
try:
|
||||
lift.generate(source, out)
|
||||
except ValueError:
|
||||
assert not out.exists(), "Rejected input produced output artifacts"
|
||||
print(f"PASS: changed {label} rejected before output")
|
||||
else:
|
||||
raise AssertionError(f"Changed {label} accepted")
|
||||
@@ -0,0 +1,68 @@
|
||||
"""Exercise native ZIP/zlib header reading, including malformed archives."""
|
||||
import io
|
||||
import json
|
||||
import struct
|
||||
import subprocess
|
||||
import tempfile
|
||||
import zipfile
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
HEADER = struct.pack('<4HB', 2, 0, 77, 0, 0)
|
||||
|
||||
|
||||
def archive(entries, method=zipfile.ZIP_DEFLATED, stream=False):
|
||||
class NonSeeking(io.BytesIO):
|
||||
def seekable(self): return False
|
||||
def seek(self, *args): raise OSError('streaming archive')
|
||||
buffer = NonSeeking() if stream else io.BytesIO()
|
||||
with zipfile.ZipFile(buffer, 'w', compression=method) as output:
|
||||
for name, contents in entries:
|
||||
output.writestr(name, contents)
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
def probe(contents, status, label):
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
path = Path(folder) / 'save.zip'
|
||||
path.write_bytes(contents)
|
||||
result = subprocess.run([str(ROOT / 'build/version/save_probe'), str(path)], capture_output=True, text=True)
|
||||
actual = json.loads(result.stdout)
|
||||
assert actual['status'] == status, (label, actual, status, result.stderr)
|
||||
assert result.returncode == (1 if status else 0), (label, result.returncode)
|
||||
if not status:
|
||||
assert [actual[k] for k in ('major', 'minor', 'patch', 'build', 'flag')] == [2, 0, 77, 0, 0]
|
||||
print('PASS:', label)
|
||||
|
||||
|
||||
payload = zlib.compress(HEADER + bytes(10000))
|
||||
for method in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED):
|
||||
probe(archive([('world/level.dat0', payload)], method), 0, f'outer method {method}, nested zlib')
|
||||
probe(archive([('world/level.dat0', payload)], stream=True), 0, 'ZIP data descriptor')
|
||||
probe(archive([('world/level.dat', payload)]), 0, 'legacy level.dat entry name')
|
||||
probe(archive([('world/level.dat', b'invalid'), ('world/level.dat0', payload)]), 0, 'level.dat0 preference')
|
||||
probe(b'not a zip', 2, 'invalid ZIP')
|
||||
probe(archive([('description.json', b'{}')]), 3, 'no level entry')
|
||||
probe(archive([('a/level.dat0', payload), ('b/level.dat0', payload)]), 2, 'ambiguous roots')
|
||||
probe(archive([('world/level.dat0', b'not zlib')]), 5, 'bad inner compression')
|
||||
probe(archive([('world/level.dat0', zlib.compress(HEADER[:8]))]), 6, 'short decompressed header')
|
||||
valid = archive([('world/level.dat0', payload)])
|
||||
probe(valid[:-1], 2, 'truncated end record')
|
||||
changed = bytearray(valid)
|
||||
central = changed.index(b'PK\x01\x02')
|
||||
struct.pack_into('<H', changed, 6, 1)
|
||||
struct.pack_into('<H', changed, central + 8, 1)
|
||||
probe(changed, 4, 'encrypted flag refused')
|
||||
changed = bytearray(valid)
|
||||
changed[30] ^= 1
|
||||
probe(changed, 2, 'local/central name mismatch')
|
||||
changed = bytearray(valid)
|
||||
struct.pack_into('<I', changed, central + 42, 0xfffffffe)
|
||||
probe(changed, 2, 'out-of-file local offset')
|
||||
changed = bytearray(valid)
|
||||
end = changed.rindex(b'PK\x05\x06')
|
||||
struct.pack_into('<H', changed, end + 10, 0xffff)
|
||||
struct.pack_into('<H', changed, end + 8, 0xffff)
|
||||
probe(changed, 4, 'ZIP64 sentinel refused')
|
||||
print('PASS: 15 archive cases; only the bounded version prefix is validated')
|
||||
@@ -0,0 +1,94 @@
|
||||
"""Verify the bounded 2.0.77 scenario envelope, offsets and unsupported inputs."""
|
||||
import io
|
||||
import json
|
||||
import struct
|
||||
import subprocess
|
||||
import tempfile
|
||||
import zipfile
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
PROBE=ROOT/'build/save-layout/probe'
|
||||
TICKS=(720001,42,0xffffffffffffffff)
|
||||
|
||||
|
||||
def compact(value,size=4):
|
||||
return bytes([value]) if value<255 else b'\xff'+value.to_bytes(size,'little')
|
||||
|
||||
|
||||
def string(value): return compact(len(value))+value
|
||||
def version(values=(2,0,77)): return b''.join(compact(v,2) for v in values)
|
||||
def localised(text=None,children=(),kind=1):
|
||||
return b'\0' if text is None else bytes([kind])+string(text)+bytes([len(children)])+b''.join(children)
|
||||
def ending(title=None,children=()):
|
||||
return localised(title,children)+localised()+compact(0)+localised()+string(b'')
|
||||
|
||||
|
||||
def fixture(long_string=b'freeplay',mod_count=1,extended_version=False,nested=False):
|
||||
result=bytearray(struct.pack('<4HB',2,0,77,0,0))
|
||||
positions={}
|
||||
result+=string(b'')+string(long_string)+string(b'base')
|
||||
positions['difficulty']=len(result);result+=b'\1'
|
||||
positions['boolean']=len(result);result+=b'\0\0'+string(b'')+b'\0\0\0\1'
|
||||
result+=version((65535,255,254) if extended_version else (2,0,77))
|
||||
result+=struct.pack('<I',1234)+b'\1'+struct.pack('<I',0xa00000)
|
||||
positions['mod_count']=len(result);result+=compact(mod_count)
|
||||
for i in range(mod_count):
|
||||
result+=string(b'base')+version((255,256,65535) if extended_version else (2,0,77))+struct.pack('<I',i)
|
||||
result+=struct.pack('<I',0)
|
||||
positions['property_type']=len(result);result+=b'\5\0'
|
||||
positions['property_count']=len(result);result+=struct.pack('<I',0)
|
||||
positions['ending']=len(result)
|
||||
children=(localised(b'child',(localised(),)),) if nested else ()
|
||||
result+=ending(b'victory',children)+ending(b'defeat')+b'\0'
|
||||
offset=len(result)
|
||||
result+=struct.pack('<3Q',*TICKS)
|
||||
return bytes(result),offset,positions
|
||||
|
||||
|
||||
checks=0
|
||||
def probe(data,status=0,mode='bytes',offset=None,mods=1):
|
||||
global checks
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
p=Path(directory)/'fixture';p.write_bytes(data)
|
||||
result=subprocess.run([str(PROBE),mode,str(p)],capture_output=True,text=True)
|
||||
assert result.returncode==(1 if status else 0),(result.returncode,result.stderr)
|
||||
report=json.loads(result.stdout)
|
||||
assert report['status']==status,report
|
||||
if not status:
|
||||
assert report['header_offset']==offset,report
|
||||
assert report['next_offset']==offset+24,report
|
||||
assert report['mod_count']==mods,report
|
||||
assert report['tick_fields_hex']==[f'{v:016x}' for v in TICKS],report
|
||||
elif status==6 and mode=='bytes':
|
||||
assert report['needed']>len(data),report
|
||||
checks+=1
|
||||
|
||||
|
||||
for length in (0,1,254,255,256,4097):
|
||||
data,offset,_=fixture(long_string=b'x'*length,extended_version=True,nested=True)
|
||||
probe(data,offset=offset)
|
||||
for count in (0,1,254,255):
|
||||
data,offset,_=fixture(mod_count=count)
|
||||
probe(data,offset=offset,mods=count)
|
||||
data,offset,positions=fixture()
|
||||
for length in range(len(data)):
|
||||
probe(data[:length],6)
|
||||
for index,value in [(4,78),(8,1),(positions['difficulty'],3),(positions['boolean'],2),
|
||||
(positions['property_type'],4),(positions['property_count'],1)]:
|
||||
changed=bytearray(data);changed[index]=value;probe(changed,4)
|
||||
probe(data[:9]+b'\xff'+struct.pack('<I',1024*1024+1),7)
|
||||
probe(data[:positions['mod_count']]+b'\xff'+struct.pack('<I',4097),7)
|
||||
deep=localised()
|
||||
for i in range(65): deep=localised(b'',(deep,))
|
||||
probe(data[:positions['ending']]+deep+data[positions['ending']:],7)
|
||||
|
||||
for method in (zipfile.ZIP_STORED,zipfile.ZIP_DEFLATED):
|
||||
output=io.BytesIO()
|
||||
with zipfile.ZipFile(output,'w',compression=method) as z:
|
||||
z.writestr('fixture/level.dat0',zlib.compress(data))
|
||||
probe(output.getvalue(),mode='zip',offset=offset)
|
||||
print(f'PASS: {checks} scenario-envelope cases, variable offsets, sentinel lengths, truncation, limits and failure atomicity')
|
||||
(ROOT/'build/save-layout/verification.json').write_text(json.dumps({'status':'pass','cases':checks,
|
||||
'scope':'2.0.77 envelope with empty startup settings; not world loading'},indent=2)+'\n')
|
||||
@@ -0,0 +1,67 @@
|
||||
"""Exercise arbitrary bounded prefix lengths, outer/nested streaming and guards."""
|
||||
import io
|
||||
import hashlib
|
||||
import json
|
||||
import struct
|
||||
import subprocess
|
||||
import tempfile
|
||||
import zipfile
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OUT = ROOT / 'build/save-prefix'
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
runtime = ROOT / 'third_party/wabt/wasm2c'
|
||||
subprocess.run(['xcrun', 'clang', '-O2', '-DWASM_RT_USE_MMAP=0', '-DWASM_RT_MEMCHECK_BOUNDS_CHECK=1',
|
||||
'-I'+str(ROOT/'runtime'), '-I'+str(ROOT/'build/version'), '-I'+str(runtime),
|
||||
str(ROOT/'tests/save_prefix_probe.c'), str(ROOT/'runtime/save_header.c'),
|
||||
str(ROOT/'runtime/version_bridge.c'), str(ROOT/'build/version/factorio_version.c'),
|
||||
str(runtime/'wasm-rt-impl.c'), str(runtime/'wasm-rt-mem-impl.c'), '-lz', '-o', str(OUT/'probe')], check=True)
|
||||
|
||||
|
||||
def archive(payload, method, streaming=False):
|
||||
class NonSeeking(io.BytesIO):
|
||||
def seek(self, *args): raise OSError('streaming')
|
||||
output = NonSeeking() if streaming else io.BytesIO()
|
||||
with zipfile.ZipFile(output, 'w', compression=method) as z:
|
||||
z.writestr('fixture/level.dat0', zlib.compress(payload))
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
checks = 0
|
||||
def probe(data, length, expected, status=0):
|
||||
global checks
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory)/'fixture.zip'
|
||||
path.write_bytes(data)
|
||||
result = subprocess.run([str(OUT/'probe'),str(path),str(length)],capture_output=True,text=True)
|
||||
assert result.returncode == (1 if status else 0), result.stderr
|
||||
report = json.loads(result.stdout)
|
||||
assert report['status'] == status, report
|
||||
assert bytes.fromhex(report['prefix_hex']) == expected
|
||||
checks += 1
|
||||
|
||||
|
||||
payload = struct.pack('<4HB',2,0,77,0,0) + bytes(range(256))*300
|
||||
for method in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED):
|
||||
for streaming in (False, True):
|
||||
data = archive(payload,method,streaming)
|
||||
for length in (1,8,9,10,33,4095,4096,4097,65536,len(payload)):
|
||||
probe(data,length,payload[:length])
|
||||
probe(data,len(payload)+1,b'',6)
|
||||
probe(data,0,b'',7)
|
||||
probe(data,1024*1024+1,b'',7)
|
||||
probe(b'invalid',33,b'',2)
|
||||
incompressible = struct.pack('<4HB',2,0,77,0,0)
|
||||
incompressible += b''.join(hashlib.sha256(i.to_bytes(4,'little')).digest() for i in range(2048))
|
||||
for method in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED):
|
||||
for streaming in (False,True):
|
||||
data=archive(incompressible,method,streaming)
|
||||
for length in (4097,len(incompressible)):
|
||||
probe(data,length,incompressible[:length])
|
||||
over_budget=b''.join(hashlib.sha256(i.to_bytes(4,'little')).digest() for i in range(32768))
|
||||
probe(archive(over_budget,zipfile.ZIP_STORED),1024*1024,b'',7)
|
||||
print(f'PASS: {checks} bounded prefix cases, exact bytes, output guards and failure atomicity')
|
||||
(OUT/'verification.json').write_text(json.dumps({'status':'pass','cases':checks,
|
||||
'scope':'synthetic archives; bounded prefix only'},indent=2)+'\n')
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "map_header_bridge.h"
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
const uint8_t *bytes;
|
||||
size_t length, cursor;
|
||||
uint64_t version[2];
|
||||
unsigned reads;
|
||||
size_t sizes[3];
|
||||
} NativeReader;
|
||||
_Static_assert(offsetof(NativeReader, version) == 24, "MapDeserialiser version offset");
|
||||
extern void *factorio_map_header_original(void *, NativeReader *);
|
||||
void native_map_header_read(NativeReader *reader, void *out, size_t length) {
|
||||
assert(reader->reads < 3 && (length == 4 || length == 8));
|
||||
assert(reader->cursor + length <= reader->length);
|
||||
memcpy(out, reader->bytes + reader->cursor, length);
|
||||
reader->cursor += length;
|
||||
reader->sizes[reader->reads++] = length;
|
||||
}
|
||||
static uint64_t random_value(uint64_t *state) {
|
||||
*state ^= *state << 13; *state ^= *state >> 7; *state ^= *state << 17; return *state;
|
||||
}
|
||||
int main(void) {
|
||||
uint64_t seed = 0x123456789abcdef;
|
||||
const uint64_t cutoff = UINT64_C(0x1000200000175);
|
||||
for (unsigned test = 0; test < 20000; ++test) {
|
||||
uint64_t packed = test < 4 ? cutoff + test - 1 : random_value(&seed);
|
||||
if (test % 4 == 0) packed &= UINT64_C(0xffffffffffff);
|
||||
uint64_t payload[3] = {random_value(&seed), random_value(&seed), random_value(&seed)};
|
||||
if (test < 8) memset(payload, test & 1 ? 0xff : 0, sizeof(payload));
|
||||
uint64_t original_payload[3]; memcpy(original_payload, payload, sizeof(payload));
|
||||
uint8_t flag = (uint8_t)test;
|
||||
NativeReader reader = {.bytes=(const uint8_t *)payload, .length=sizeof(payload), .version={packed,flag}};
|
||||
uint8_t native[48]; memset(native, 0xa5, sizeof(native));
|
||||
assert(factorio_map_header_original(native, &reader) == native);
|
||||
assert(reader.reads == 3);
|
||||
size_t field_size = packed > cutoff ? 8 : 4;
|
||||
assert(reader.cursor == 3 * field_size);
|
||||
for (unsigned i=0;i<3;++i) assert(reader.sizes[i] == field_size);
|
||||
for (unsigned i=40;i<48;++i) assert(native[i] == 0xa5);
|
||||
FactorioMapVersion version = {.major=packed>>48, .minor=packed>>32,
|
||||
.patch=packed>>16, .build=packed, .flag=flag};
|
||||
struct { uint64_t before; FactorioMapHeader header; uint64_t after; } output;
|
||||
memset(&output, 0xa5, sizeof(output));
|
||||
assert(factorio_map_header_parse((const uint8_t *)payload,sizeof(payload),version,&output.header)==0);
|
||||
assert(output.before == UINT64_C(0xa5a5a5a5a5a5a5a5) && output.after == output.before);
|
||||
assert(memcmp(output.header.tick_fields,native+16,24)==0);
|
||||
assert(output.header.version.major==version.major && output.header.version.flag==flag);
|
||||
assert(memcmp(payload,original_payload,sizeof(payload))==0);
|
||||
if (test < 8) for (size_t length=0;length<3*field_size;++length) {
|
||||
FactorioMapHeader before; memset(&before,0xa5,sizeof(before));
|
||||
FactorioMapHeader result = before;
|
||||
assert(factorio_map_header_parse((const uint8_t *)payload,length,version,&result)==1);
|
||||
assert(memcmp(&before,&result,sizeof(result))==0);
|
||||
}
|
||||
}
|
||||
puts("PASS: 20000 original ARM64 MapHeader comparisons, version branches, legacy sentinels, guards and short-stream failures");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Decodes every PNG under a directory with pngdecode.c and with macOS ImageIO (which yields
|
||||
// exact non-premultiplied samples on macOS) and requires byte-identical results.
|
||||
// Usage: png_parity DATA_DIR
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#include "pngdecode.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
@autoreleasepool {
|
||||
NSString *root = @(argv[1]);
|
||||
long checked = 0, fallback = 0, mismatched = 0, skipped = 0;
|
||||
double tw = 0, ti = 0; long px = 0;
|
||||
for (NSString *rel in [NSFileManager.defaultManager enumeratorAtPath:root]) {
|
||||
if (![rel hasSuffix:@".png"]) continue;
|
||||
NSData *data = [NSData dataWithContentsOfFile:[root stringByAppendingPathComponent:rel]];
|
||||
uint8_t *mine; int w, h, ch;
|
||||
double t0 = CACurrentMediaTime();
|
||||
int ok = fx_png_decode(data.bytes, data.length, &mine, &w, &h, &ch);
|
||||
double t1 = CACurrentMediaTime();
|
||||
if (!ok) { fallback++; continue; }
|
||||
CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
|
||||
CGImageRef img = CGImageSourceCreateImageAtIndex(src, 0, NULL);
|
||||
CFDataRef ref = CGDataProviderCopyData(CGImageGetDataProvider(img));
|
||||
double t2 = CACurrentMediaTime();
|
||||
tw += t1 - t0; ti += t2 - t1; px += (long)w * h;
|
||||
size_t bpp = CGImageGetBitsPerPixel(img) / 8, row = CGImageGetBytesPerRow(img);
|
||||
CGImageAlphaInfo a = CGImageGetAlphaInfo(img);
|
||||
if (CGImageGetBitsPerComponent(img) != 8 || (CGImageGetBitmapInfo(img) & kCGBitmapByteOrderMask) ||
|
||||
a == kCGImageAlphaPremultipliedLast || a == kCGImageAlphaPremultipliedFirst || a == kCGImageAlphaFirst ||
|
||||
a == kCGImageAlphaNoneSkipFirst) { skipped++; goto next; }
|
||||
// Compare the channels AppKit would expose (drop ImageIO's padding byte for RGBX).
|
||||
for (int y = 0; y < h && !mismatched; y++)
|
||||
for (int x = 0; x < w; x++) {
|
||||
const uint8_t *r = CFDataGetBytePtr(ref) + y * row + x * bpp, *m = mine + ((size_t)y * w + x) * ch;
|
||||
if (memcmp(r, m, ch)) {
|
||||
NSLog(@"MISMATCH %@ at %d,%d ch=%d", rel, x, y, ch);
|
||||
mismatched++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
checked++;
|
||||
next:
|
||||
CFRelease(ref); CGImageRelease(img); CFRelease(src); free(mine);
|
||||
}
|
||||
NSLog(@"checked %ld identical-format images, %ld mismatched, %ld skipped (ImageIO layout), %ld fallback to ImageIO",
|
||||
checked, mismatched, skipped, fallback);
|
||||
NSLog(@"%.0f Mpx: wuffs %.2fs (%.0f Mpx/s), ImageIO %.2fs (%.0f Mpx/s)", px / 1e6, tw, px / 1e6 / tw, ti, px / 1e6 / ti);
|
||||
return mismatched ? 1 : 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#include "factorio_rng.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
extern uint32_t factorio_rng_original(uint32_t state[3]);
|
||||
extern void factorio_rng_seed_original(uint32_t state[3], uint32_t seed);
|
||||
|
||||
static uint32_t next_case(uint32_t *seed) {
|
||||
*seed ^= *seed << 13;
|
||||
*seed ^= *seed >> 17;
|
||||
*seed ^= *seed << 5;
|
||||
return *seed;
|
||||
}
|
||||
|
||||
// Returns zero only after comparing outputs AND mutated state with original
|
||||
// ARM64 instruction words. No generated code is mapped executable at runtime.
|
||||
int factorio_rng_verify(void) {
|
||||
const uint32_t edges[] = {0, 1, 2, 7, 8, 15, 16, 127, 128,
|
||||
0x7fffffff, 0x80000000, 0xffffffff};
|
||||
const uint32_t offsets[] = {0, 1, 15, 1024, 65524};
|
||||
uint32_t random = 0x375adc91;
|
||||
uint64_t calls = 0;
|
||||
w2c_factorio__rng instance;
|
||||
wasm_rt_init();
|
||||
wasm2c_factorio__rng_instantiate(&instance);
|
||||
wasm_rt_memory_t *memory = w2c_factorio__rng_memory(&instance);
|
||||
for (uint32_t example = 0; example < 6848; ++example) {
|
||||
uint32_t state[3];
|
||||
if (example < 1728) {
|
||||
state[0] = edges[example % 12];
|
||||
state[1] = edges[(example / 12) % 12];
|
||||
state[2] = edges[example / 144];
|
||||
} else if (example < 5824) {
|
||||
for (int i = 0; i < 3; ++i) state[i] = next_case(&random);
|
||||
}
|
||||
uint32_t offset = offsets[example % 5];
|
||||
memset(memory->data, 0xa5, 65536);
|
||||
if (example >= 5824) {
|
||||
uint32_t seed = example < 6336 ? example - 5824 : next_case(&random);
|
||||
factorio_rng_seed_original(state, seed);
|
||||
w2c_factorio__rng_seed(&instance, offset, seed);
|
||||
if (memcmp(memory->data + offset, state, 12)) {
|
||||
fprintf(stderr, "Constructor mismatch at seed %u\n", seed);
|
||||
wasm2c_factorio__rng_free(&instance);
|
||||
wasm_rt_free();
|
||||
return 3;
|
||||
}
|
||||
} else {
|
||||
memcpy(memory->data + offset, state, 12);
|
||||
}
|
||||
for (int step = 0; step < 128; ++step) {
|
||||
uint32_t expected = factorio_rng_original(state);
|
||||
uint32_t actual = w2c_factorio__rng_get_int(&instance, offset);
|
||||
++calls;
|
||||
if (actual != expected || memcmp(memory->data + offset, state, 12)) {
|
||||
fprintf(stderr, "Mismatch: case %u step %d, expected %08x actual %08x\n",
|
||||
example, step, expected, actual);
|
||||
wasm2c_factorio__rng_free(&instance);
|
||||
wasm_rt_free();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// Catch writes outside the 12-byte state, including unaligned addresses.
|
||||
for (uint32_t i = 0; i < 65536; ++i) {
|
||||
if ((i < offset || i >= offset + 12) && memory->data[i] != 0xa5) {
|
||||
fprintf(stderr, "Out-of-state write: case %u offset %u\n", example, i);
|
||||
wasm2c_factorio__rng_free(&instance);
|
||||
wasm_rt_free();
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
wasm2c_factorio__rng_free(&instance);
|
||||
wasm_rt_free();
|
||||
printf("PASS: %llu RNG calls and 1024 constructor calls, 6848 starting states, five memory alignments; outputs, state and guards match\n",
|
||||
(unsigned long long)calls);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef FACTORIO_RNG_NO_MAIN
|
||||
int main(void) { return factorio_rng_verify(); }
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
#include "save_layout.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
const uint8_t *bytes;
|
||||
size_t length,cursor;
|
||||
uint64_t version[2];
|
||||
unsigned reads;
|
||||
} NativeReader;
|
||||
extern void *factorio_map_header_original(void *,NativeReader *);
|
||||
void native_map_header_read(NativeReader *r,void *out,size_t size) {
|
||||
assert(size==8 && r->reads<3 && r->cursor+size<=r->length);
|
||||
memcpy(out,r->bytes+r->cursor,size);r->cursor+=size;++r->reads;
|
||||
}
|
||||
int main(int argc,char **argv) {
|
||||
if(argc!=2) return 2;
|
||||
FactorioSaveLayout layout;
|
||||
assert(factorio_save_map_header(argv[1],&layout)==0);
|
||||
uint8_t *prefix=malloc(layout.next_offset);assert(prefix);
|
||||
assert(factorio_save_prefix(argv[1],prefix,layout.next_offset)==0);
|
||||
FactorioMapVersion v=layout.header.version;
|
||||
NativeReader reader={.bytes=prefix,.length=layout.next_offset,.cursor=layout.header_offset,
|
||||
.version={(uint64_t)v.major<<48|(uint64_t)v.minor<<32|(uint64_t)v.patch<<16|v.build,v.flag}};
|
||||
uint8_t native[48];memset(native,0xa5,sizeof(native));
|
||||
assert(factorio_map_header_original(native,&reader)==native);
|
||||
assert(reader.reads==3 && reader.cursor==layout.next_offset);
|
||||
assert(memcmp(native+16,layout.header.tick_fields,24)==0);
|
||||
for(unsigned i=40;i<48;++i) assert(native[i]==0xa5);
|
||||
free(prefix);
|
||||
printf("{\"status\":\"pass\",\"header_offset\":%zu,\"native_reads\":%u,"
|
||||
"\"constructor_values_match\":true,\"offset_authority\":\"recovered metadata layout; not full-engine execution\"}\n",
|
||||
layout.header_offset,reader.reads);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include "save_layout.h"
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc,char **argv) {
|
||||
if(argc!=3) return 2;
|
||||
FactorioSaveLayout result;
|
||||
memset(&result,0xa5,sizeof(result));
|
||||
FactorioSaveLayout before=result;
|
||||
int status;
|
||||
size_t needed=0;
|
||||
if(!strcmp(argv[1],"zip")) status=factorio_save_map_header(argv[2],&result);
|
||||
else if(!strcmp(argv[1],"bytes")) {
|
||||
FILE *file=fopen(argv[2],"rb"); if(!file) return 2;
|
||||
uint8_t *bytes=malloc(1024*1024); if(!bytes) return 2;
|
||||
size_t count=fread(bytes,1,1024*1024,file); fclose(file);
|
||||
status=factorio_save_layout_bytes(bytes,count,&result,&needed); free(bytes);
|
||||
} else return 2;
|
||||
if(status && memcmp(&before,&result,sizeof(result))) return 3;
|
||||
printf("{\"status\":%d,\"needed\":%zu",status,needed);
|
||||
if(!status) printf(",\"header_offset\":%zu,\"next_offset\":%zu,\"mod_count\":%u,"
|
||||
"\"tick_fields_hex\":[\"%016" PRIx64 "\",\"%016" PRIx64 "\",\"%016" PRIx64 "\"]",
|
||||
result.header_offset,result.next_offset,result.mod_count,
|
||||
result.header.tick_fields[0],result.header.tick_fields[1],result.header.tick_fields[2]);
|
||||
puts("}");
|
||||
return status ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "save_header.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 3) return 2;
|
||||
char *end = NULL;
|
||||
unsigned long requested = strtoul(argv[2], &end, 10);
|
||||
if (!end || *end || requested > 1024 * 1024 + 1) return 2;
|
||||
uint8_t *guarded = malloc(requested + 16);
|
||||
if (!guarded) return 2;
|
||||
memset(guarded, 0xa5, requested + 16);
|
||||
int status = factorio_save_prefix(argv[1], guarded + 8, requested);
|
||||
for (unsigned i=0;i<8;++i) {
|
||||
if (guarded[i] != 0xa5 || guarded[requested+8+i] != 0xa5) return 3;
|
||||
}
|
||||
if (status) for (unsigned long i=0;i<requested;++i) if (guarded[i+8]!=0xa5) return 4;
|
||||
printf("{\"status\":%d,\"prefix_hex\":\"", status);
|
||||
if (!status) for (unsigned long i=0;i<requested;++i) printf("%02x",guarded[i+8]);
|
||||
puts("\"}");
|
||||
free(guarded);
|
||||
return status ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "save_header.h"
|
||||
#include <stdio.h>
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 2) return 64;
|
||||
FactorioMapVersion version;
|
||||
int status = factorio_save_header(argv[1], &version);
|
||||
if (!status) printf("{\"status\":0,\"major\":%u,\"minor\":%u,\"patch\":%u,\"build\":%u,\"flag\":%u}\n",
|
||||
version.major, version.minor, version.patch, version.build, version.flag);
|
||||
else printf("{\"status\":%d}\n", status);
|
||||
return status ? 1 : 0;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
#include "target_bridge.h"
|
||||
#include "target_host.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
extern void *factorio_target_original(void *);
|
||||
extern const uint8_t native_target_vtable[];
|
||||
static _Alignas(16) uint8_t native_heap[65536];
|
||||
static size_t native_cursor;
|
||||
static unsigned native_allocations;
|
||||
void *native_target_allocate(size_t size) {
|
||||
native_cursor=(native_cursor+15)&~(size_t)15;
|
||||
assert(size<=sizeof(native_heap)-native_cursor);
|
||||
void *result=native_heap+native_cursor;
|
||||
native_cursor+=size; ++native_allocations;
|
||||
return result;
|
||||
}
|
||||
void native_target_unsupported_delete(void *p) {(void)p;abort();}
|
||||
void native_target_unsupported_length(void *p) {(void)p;abort();}
|
||||
void native_target_unsupported_array_length(void *p) {(void)p;abort();}
|
||||
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);}
|
||||
static uint64_t relocate(uint64_t pointer,uint64_t begin) {
|
||||
assert(pointer>=(uintptr_t)native_heap && pointer<=(uintptr_t)native_heap+sizeof(native_heap));
|
||||
return pointer-(uintptr_t)native_heap+begin;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
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);
|
||||
assert(ram->size==1048576);
|
||||
uint8_t *memory=ram->data;
|
||||
for (unsigned iteration=0;iteration<512;++iteration) {
|
||||
uint64_t begin=131072+iteration*16,object=4096+(iteration%16)*256;
|
||||
memset(memory,0xa5,(size_t)ram->size);
|
||||
memset(native_heap,0xa5,sizeof(native_heap));
|
||||
uint8_t original[144];memset(original,0xa5,sizeof(original));
|
||||
native_cursor=0;native_allocations=0;
|
||||
assert(factorio_target_original(original)==original);
|
||||
assert(native_allocations==4 && native_cursor==2104);
|
||||
assert(factorio_guest_heap_init(&env.heap,memory,ram->size,begin,begin+65536)==0);
|
||||
uint64_t registers[32]={0};
|
||||
for(unsigned i=19;i<30;++i) registers[i]=0x1234567800000000ull+iteration+i;
|
||||
registers[0]=object;registers[31]=ram->size-256;
|
||||
memcpy(memory,registers,sizeof(registers));
|
||||
assert(w2c_factorio__target_run(&module,0,10000)==0);
|
||||
assert(env.heap.allocations==4 && env.heap.cursor-begin==native_cursor);
|
||||
assert(get(memory,0)==object);
|
||||
for(unsigned i=19;i<32;++i) assert(get(memory,i*8)==registers[i]);
|
||||
const unsigned fields[]={8,16,24,56,64,72};
|
||||
for(unsigned i=0;i<sizeof(fields)/sizeof(fields[0]);++i)
|
||||
put(original,fields[i],relocate(get(original,fields[i]),begin));
|
||||
assert(memcmp(memory+object,original,sizeof(original))==0);
|
||||
/* Normalize every native pointer in the allocated object graph. */
|
||||
for(unsigned i=0;i<3;++i) put(native_heap,i*8,relocate(get(native_heap,i*8),begin));
|
||||
put(native_heap,2080,relocate(get(native_heap,2080),begin));
|
||||
for(unsigned i=0;i<64;++i) {
|
||||
assert(get(native_heap,32+i*32)==(uintptr_t)native_target_vtable+16);
|
||||
put(native_heap,32+i*32,65536+0xda8);
|
||||
}
|
||||
assert(memcmp(memory+begin,native_heap,native_cursor)==0);
|
||||
for(size_t i=256;i<ram->size;++i) {
|
||||
if(i>=object && i<object+144) continue;
|
||||
if(i>=begin && i<begin+native_cursor) continue;
|
||||
if(i>=ram->size-368 && i<ram->size-256) continue;
|
||||
assert(memory[i]==0xa5);
|
||||
}
|
||||
}
|
||||
/* Each original allocation can fail without returning a forged pointer. */
|
||||
const uint64_t lengths[]={0,23,24,2079,2080,2087,2088,2103,2104};
|
||||
for(unsigned i=0;i<sizeof(lengths)/sizeof(lengths[0]);++i) {
|
||||
memset(memory,0xa5,(size_t)ram->size);
|
||||
assert(factorio_guest_heap_init(&env.heap,memory,ram->size,131072,131072+lengths[i])==0);
|
||||
uint64_t registers[32]={0};registers[0]=4096;registers[31]=ram->size-256;
|
||||
memcpy(memory,registers,sizeof(registers));
|
||||
assert(w2c_factorio__target_run(&module,0,10000)==(lengths[i]<2104 ? 5u:0u));
|
||||
for(uint64_t j=131072+lengths[i];j<196608;++j) assert(memory[j]==0xa5);
|
||||
}
|
||||
assert(w2c_factorio__target_run(&module,1048576-255,10000)==2);
|
||||
assert(w2c_factorio__target_run(&module,UINT32_MAX,10000)==2);
|
||||
uint64_t registers[32]={0};registers[0]=4096;registers[31]=ram->size-256;
|
||||
memcpy(memory,registers,sizeof(registers));
|
||||
assert(w2c_factorio__target_run(&module,0,0)==3);
|
||||
/* Heap input arithmetic is checked before any addition or narrowing. */
|
||||
FactorioGuestHeap heap;
|
||||
assert(factorio_guest_heap_init(&heap,memory,ram->size,131072,196608)==0);
|
||||
FactorioGuestHeap before=heap;uint64_t address=123;
|
||||
assert(factorio_guest_heap_allocate(&heap,UINT64_MAX,&address)==1);
|
||||
assert(address==123 && memcmp(&before,&heap,sizeof(heap))==0);
|
||||
assert(factorio_guest_heap_allocate(&heap,0,&address)==0 && address==131072);
|
||||
assert(factorio_guest_heap_allocate(&heap,0,&address)==0 && address==131088);
|
||||
assert(factorio_guest_heap_init(&heap,memory,ram->size,0,196608)==2);
|
||||
assert(factorio_guest_heap_init(&heap,memory,ram->size,131073,196608)==2);
|
||||
assert(factorio_guest_heap_init(&heap,memory,ram->size,131072,UINT64_MAX)==2);
|
||||
assert(factorio_guest_heap_init(&heap,memory,ram->size,131072,131071)==2);
|
||||
assert(factorio_guest_heap_init(&heap,memory,ram->size,131072,196608)==0);
|
||||
heap.cursor=heap.end+1;address=123;
|
||||
assert(factorio_guest_heap_allocate(&heap,8,&address)==2 && address==123);
|
||||
heap.cursor=131072;heap.allocations=UINT32_MAX;
|
||||
assert(factorio_guest_heap_allocate(&heap,8,&address)==2 && address==123);
|
||||
FactorioTargetProbe report={0};
|
||||
assert(factorio_target_probe(&report)==0);
|
||||
assert(report.allocations==4 && report.initialized_targets==64 && report.heap_bytes==2104);
|
||||
assert(factorio_target_probe(NULL)==2);
|
||||
wasm2c_factorio__target_free(&module);wasm_rt_free();
|
||||
puts("PASS: 512 original ARM64 object graphs, 64 targets each, relocated pointers, heap exhaustion, output/register/memory guards");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "version_bridge.h"
|
||||
#include "version_host.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct { uint8_t bytes[9]; size_t cursor; unsigned reads; } NativeReader;
|
||||
extern void *factorio_version_original(void *out, NativeReader *reader);
|
||||
void native_version_read(NativeReader *reader, void *out, size_t length) {
|
||||
assert(length == (reader->reads == 4 ? 1 : 2));
|
||||
assert(reader->cursor + length <= 9);
|
||||
memcpy(out, reader->bytes + reader->cursor, length);
|
||||
reader->cursor += length;
|
||||
++reader->reads;
|
||||
}
|
||||
static uint32_t random_word(uint32_t *state) {
|
||||
*state ^= *state << 13; *state ^= *state >> 17; *state ^= *state << 5;
|
||||
return *state;
|
||||
}
|
||||
int main(void) {
|
||||
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;
|
||||
uint32_t seed = 0xc46b21f3;
|
||||
for (unsigned example = 0; example < 20000; ++example) {
|
||||
NativeReader reader = {0};
|
||||
for (int i = 0; i < 9; ++i) reader.bytes[i] = (uint8_t)random_word(&seed);
|
||||
if (example < 2) memset(reader.bytes, example ? 0xff : 0, 9);
|
||||
uint8_t expected[16];
|
||||
memset(expected, 0xa5, sizeof(expected));
|
||||
assert(factorio_version_original(expected, &reader) == expected);
|
||||
assert(reader.cursor == 9 && reader.reads == 5);
|
||||
memset(memory, 0xa5, 65536);
|
||||
uint64_t registers[32];
|
||||
for (int i = 0; i < 32; ++i) registers[i] = random_word(&seed);
|
||||
registers[0] = 2048; registers[1] = 512; registers[31] = 65280;
|
||||
uint64_t stream[] = {4096, 9, 0};
|
||||
memcpy(memory, registers, sizeof(registers));
|
||||
memcpy(memory + 512, stream, sizeof(stream));
|
||||
memcpy(memory + 4096, reader.bytes, 9);
|
||||
env.reads = 0;
|
||||
assert(w2c_factorio__version_run(&module, 0) == 0);
|
||||
assert(env.reads == 5);
|
||||
assert(memcmp(memory + 2048, expected, sizeof(expected)) == 0);
|
||||
uint64_t after[32]; memcpy(after, memory, sizeof(after));
|
||||
assert(after[0] == 2048 && after[31] == registers[31]);
|
||||
for (int i = 19; i <= 30; ++i) assert(after[i] == registers[i]);
|
||||
for (int i = 256; i < 65536; ++i) {
|
||||
if ((i >= 512 && i < 536) || (i >= 2048 && i < 2057) ||
|
||||
(i >= 4096 && i < 4105) || (i >= 65200 && i < 65280)) continue;
|
||||
assert(memory[i] == 0xa5);
|
||||
}
|
||||
}
|
||||
uint8_t sample[9] = {2, 0, 0, 0, 77, 0, 0, 0, 0};
|
||||
FactorioMapVersion output;
|
||||
assert(factorio_version_parse(sample, sizeof(sample), &output) == 0);
|
||||
assert(output.major == 2 && output.minor == 0 && output.patch == 77 && output.build == 0 && output.flag == 0);
|
||||
for (size_t length = 0; length < 9; ++length) {
|
||||
memset(&output, 0xa5, sizeof(output));
|
||||
FactorioMapVersion before = output;
|
||||
assert(factorio_version_parse(sample, length, &output) == 1);
|
||||
assert(memcmp(&output, &before, sizeof(output)) == 0);
|
||||
}
|
||||
wasm2c_factorio__version_free(&module);
|
||||
wasm_rt_free();
|
||||
puts("PASS: 20000 original ARM64 constructor comparisons, five-read protocol, preserved registers, memory guards, and short-header errors");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user