-
Notifications
You must be signed in to change notification settings - Fork 5
XWA Decoder and Lifter
The translator is built from two layers. The bottom layer is a vendored, MIT-licensed static-recompilation toolkit originally written for X-Wing Alliance ("XWA tools", third_party/xwa), which provides PE parsing, Capstone-based x86 disassembly and a permissive x86-to-C lifter. On top of it, tools/engine_reuse subclasses and replaces most of that toolkit with a strict decoder and lifter: it never guesses an indirect target, never approximates a flag, and refuses (or traps at run time) any instruction it cannot express exactly. Static Translation Pipeline drives these classes; the C they emit targets EngineReuse Runtime.
| File | Role |
|---|---|
| third_party/xwa/LICENSE | MIT license (copyright sp00nznet) |
| third_party/xwa/tools/pe_analyze.py |
pefile wrapper: sections, imports, code/data ranges, IAT map, JSON export. Used (through load_pe) |
| third_party/xwa/tools/disasm.py |
Instruction, BasicBlock, Function dataclasses and Disassembler. Used as base class and data model |
| third_party/xwa/tools/lifter.py | Upstream Lifter. Used as base class for operand formatting and ten flag-neutral instructions |
| third_party/xwa/tools/translator.py | Upstream pipeline driver. Only load_pe is used
|
| third_party/xwa/tools/generate.py | Upstream linear-sweep generator. Not used |
| third_party/xwa/recomp_types.h | Upstream C runtime header (global registers, RECOMP_CALL). Not used |
| third_party/xwa/tools/__init__.py, __main__.py | Package marker; python -m entry that calls translator.main()
|
| tools/engine_reuse/upstream.py | Imports the pinned toolkit under a private package name; PE path and PE_SHA256
|
| tools/engine_reuse/decode.py |
EngineDisassembler and DecodeError
|
| tools/engine_reuse/lift.py |
EngineLifter: control flow, memory, stack, function skeleton |
| tools/engine_reuse/flags.py |
FlagsMixin: exact integer EFLAGS producers and condition consumers |
| tools/engine_reuse/extra.py |
ExtraMixin: string ops, one-operand MUL/IMUL, shifts/rotates, bit ops, atomics-shaped ops, CPUID/RDTSC |
| tools/engine_reuse/fpu.py |
FPUMixin: x87 instructions (see x87 Floating Point) |
| tools/engine_reuse/test_decode.py, test_flags.py, test_fpu.py | Decoder regression, flags differential, x87 differential |
The upstream toolkit is designed to get a large game running quickly: it keeps x86 registers in C globals, discards EFLAGS and instead pattern-matches the most recent cmp/test into the next jcc, models only the common x87 operations, and lets unresolved branches fall into a dispatcher. Its own sources document the limits, for example #define CMP_O(a, b) 0 /* TODO: overflow detection */ and CMP_P in recomp_types.h:255-258, the "test-after-MOV codegen bug" workaround in lifter.py:361-408, and the comments explaining that a per-function double _st[8] made __ftol return 0 (recomp_types.h:41-44).
The Halo translator keeps the parts that are mechanical and safe (PE parsing, Capstone decoding, operand text formatting) and replaces the rest. The module docstring of lift.py states the rule:
Strict reusable static x86-to-C lowering around the original instruction graph. No per-function CPU reset, flag-history heuristic, guessed game calls or no-op fallback. Every unresolved operation is reported before compilation.
| Concern | Upstream XWA toolkit | Halo engine_reuse
|
|---|---|---|
| Register state | C globals g_eax..; ebp once per-function local |
EngineCPU struct passed to every function; register names are macros over cpu->gpr[]
|
| EFLAGS | not stored; condition rebuilt from the last cmp/test/add... operands |
real eflags word updated by each producer, read by each consumer |
| Calls |
RECOMP_CALL: push dummy 0xDEAD0000, call C function, restore ebx/esi/edi/ebp
|
push the real return address, dispatch, check cpu->pc on return |
| Unknown jump targets |
goto to undefined label rewritten to RECOMP_ITAIL
|
switch over own leaders, else engine_dispatch; decode never invents targets |
| Memory |
MEM32(addr) volatile dereference at addr + g_mem_base
|
engine_read_u32(cpu, addr) through engine_address (flat or checked) |
| x87 | shifting double _st[8], partial instruction set |
rotating register file with tags, status/control words, environments (x87 Floating Point) |
| Unsupported instruction |
/* UNIMPLEMENTED: ... */ comment, execution continues |
Python exception, or engine_fail trap with --trap-unsupported
|
| Function discovery | E8 call scan plus 55 8B EC prologue scan |
explicit entry lists plus call graph closure plus optional immediate scan |
upstream.py creates a synthetic package named _halo_pinned_xwa_tools whose __path__ is third_party/xwa/tools, then imports disasm, lifter and translator from it. This avoids name clashes with any tools package on sys.path and makes the relative imports inside translator.py (from .pe_analyze import ...) resolve against the vendored copy. Its docstring reads "Pinned MIT-licensed decoding/lifting tools; never mutate the proven prototype": fixes go into tools/engine_reuse subclasses, not into third_party/xwa.
Exported names: Disassembler, Instruction, Function, BasicBlock, Lifter, load_pe, plus VISION (repository root), PE and PE_SHA256 (see Static Translation Pipeline).
Note: the vendored third_party/xwa/tools/lifter.py already contains a few Halo-specific edits from an earlier prototype (for example the fcomp/fnstsw/test ah parity conditions marked "Prototype-local Halo fix" at lines 278-287, and the capstone 5 ST register numbering fix at lines 97-102). None of those branches are reached by EngineLifter, which handles all flag consumers and x87 operations itself.
| Class | Fields used by the Halo tools |
|---|---|
Instruction |
address, size, mnemonic, op_str, bytes, operands (Capstone operand objects); properties is_call, is_ret, is_cond_jump (includes jcxz/jecxz), is_uncond_jump, is_jump, end_address; get_branch_target() returns the immediate target or None for indirect |
BasicBlock |
start, end, instructions, successors, is_exit
|
Function |
address, end, name (sub_XXXXXXXX), blocks (leader -> block), calls_to, size; num_instructions. EngineDisassembler adds indirect_edges and tail_calls
|
The Disassembler constructor builds a Capstone Cs(CS_ARCH_X86, CS_MODE_32) with detail = True and a cache of code-section ranges. is_code_address tests membership in those ranges (raw size, so trailing virtual-only space is not code); read_bytes falls back to data sections, which is how jump tables and selector bytes are read.
decode.py replaces the upstream recursive descent. Upstream disassemble_function re-disassembles up to 8 KiB linearly from every work item, guesses that conditional targets within 1 MiB are part of the same function, and does not track tail calls. The Halo version decodes one instruction at a time and records exactly what it saw.
instruction_at(address) (lines 15-25) reads 15 bytes (the maximum x86 instruction length), decodes exactly one instruction, wraps it in the upstream Instruction, and caches it in self.instructions. The cache is shared across all functions decoded by one EngineDisassembler; the generator's --discover pass scans it for immediates.
Lines 27-130. Parameters:
| Parameter | Meaning |
|---|---|
start_va |
function entry; must be in a code section or DecodeError("Entry outside code")
|
end_va |
optional exclusive upper bound; targets at or beyond it are not internal |
extra_entries |
additional block leaders (jump-table targets) |
known_functions |
other function entries; any of them except start_va is "foreign" |
A target is internal when it is in a code section, not foreign, and inside [start_va, end_va) if end_va is set.
Pass 1 walks a worklist of leaders and decodes linearly from each until a terminator:
flowchart TD
A["pop leader address"] --> B{"already decoded?"}
B -- yes --> A
B -- no --> C{"decoded >= budget (50000)?"}
C -- yes --> X1["DecodeError: exceeds budget"]
C -- no --> D{"internal(address)?"}
D -- no --> E{"foreign known entry,<br/>not an explicit leader?"}
E -- yes --> T["tail_calls.add(address)<br/>(shared epilogue fallthrough)"]
E -- no --> X2["DecodeError: fallthrough outside function"]
D -- yes --> F["decode one instruction"]
F --> G{"call?"}
G -- yes --> G1["direct: calls_to.add(target)<br/>indirect: indirect_edges += (addr,'call',ops)<br/>return site -> new leader if internal"]
G1 --> N["continue at next instruction"]
G -- no --> H{"jump?"}
H -- yes --> H1["indirect: indirect_edges += (addr,'jump',ops)<br/>internal target: new leader<br/>else tail_calls.add(target)<br/>jcc fallthrough: leader if internal else tail call"]
H1 --> A
H -- no --> I{"ret or int3?"}
I -- yes --> A
I -- no --> N
N --> C
Design points, each justified in the code comments:
- Call return sites are block leaders. "The return site is a resume point for setjmp/longjmp and SEH unwinding, so make it a basic-block leader (dispatchable)". This is what lets generated code resume at a return address after a non-local return (see EngineReuse Runtime).
- Falling into another known function is a tail call. "Adjacent CRT entries can share an epilogue by falling directly into the next function instead of using JMP." The same applies to a conditional branch's fallthrough (lines 81-88).
- Indirect edges are reported, not resolved. Upstream code tried to guess switch tables; here the generator does that separately with proven guards (Static Translation Pipeline).
-
The budget fails closed. A function over 50,000 instructions (
instruction_budget, constructor keyword) is rejected instead of being truncated.
Pass 2 validates and builds blocks:
- Sorted decoded addresses must not overlap ("Every byte must belong to exactly one decoded instruction. Reject accidental entry into the middle of another instruction."); otherwise
DecodeError("Overlapping decode at ..."). - For each leader, instructions are appended until the next leader, a call whose return site is a leader, a jump, a
retor anint3. A leader with no instructions raisesDecodeError("Empty block"). -
func.endis the maximum block end;func.size = end - start.
The iat_map argument is accepted for signature compatibility and not used; import calls are indirect memory calls and appear in indirect_edges.
EngineLifter(ExtraMixin, FlagsMixin, FPUMixin, Lifter). Constructor keywords:
| Keyword | Default | Meaning |
|---|---|---|
trap_unsupported |
False |
on a lowering error, append {address, reason} to self.unsupported and emit engine_fail(cpu, "unsupported original instruction"); instead of raising |
static_functions |
True |
emit static void sub_... (single translation unit) or void sub_... (chunked) |
iat_map, ... |
passed through to upstream Lifter.__init__
|
classDiagram
class Lifter {
<<third_party/xwa>>
_fmt_read(op)
_fmt_write(op, value)
_fmt_mem_addr(mem)
lift_instruction(insn)
}
class ExtraMixin {
lift_extra_instruction(insn)
_string_form / _string_lines / _bit_test
}
class FlagsMixin {
lift_flag_instruction(insn)
flags_condition(mnemonic)
}
class FPUMixin {
lift_fpu_instruction(insn)
_fp_read / _fp_index
}
class EngineLifter {
trap_unsupported
static_functions
unsupported
_fmt_mem_addr / _fmt_mem_read / _fmt_mem_write
_branch(target)
lift_instruction(insn)
lift_function(function)
}
ExtraMixin <|-- EngineLifter
FlagsMixin <|-- EngineLifter
FPUMixin <|-- EngineLifter
Lifter <|-- EngineLifter
Inherited from upstream _fmt_read/_fmt_write (lifter.py:140-186), with memory hooks overridden (lift.py:21-34):
| Operand | Read | Write |
|---|---|---|
| 32-bit register | eax |
eax = v |
| 16-bit register | LO16(eax) |
SET_LO16(eax, v) |
| low byte | LO8(eax) |
SET_LO8(eax, v) |
| high byte | HI8(eax) |
SET_HI8(eax, v) |
| immediate | masked to 32 bits; 0x%08Xu above 0xFFFF, 0x%Xu above 9, else decimal |
n/a |
| memory, size 1/2/4/8 | engine_read_u8/16/32/64(cpu, addr) |
engine_write_u8/16/32/64(cpu, addr, v) |
| memory, other size | ValueError("Unsupported memory load size") |
ValueError("Unsupported memory store size") |
| FS or GS segment override | address becomes (ENGINE_FS_BASE + addr)
|
same |
| segment register | read as _seg_cs etc. (constants in engine_registers.h) |
rejected (Unsupported register operand) |
The effective address is base + index * scale + disp, with negative displacements written (-0x...). All arithmetic is on uint32_t, so it wraps like the hardware. GS is folded onto the FS base because Win32 user code only uses FS (the thread information block).
_check_flag_clobber is overridden to do nothing: "EFLAGS is real shared state, not a deferred operand expression." The upstream workaround is unnecessary when every producer writes eflags immediately.
lift_instruction tries handlers in a fixed order and the first one that returns code wins:
flowchart TD
I["instruction"] --> F1{"sahf lahf stc clc cmc cld std"}
F1 -- yes --> O1["direct eflags bit operations"]
F1 -- no --> F2{"div / idiv"}
F2 -- yes --> O2["engine_divide(cpu, src, bits, signed)"]
F2 -- no --> F3["ExtraMixin.lift_extra_instruction"]
F3 -- "code" --> OUT["emit"]
F3 -- None --> F4["FlagsMixin.lift_flag_instruction"]
F4 -- "code" --> OUT
F4 -- "raises for known unsupported flag writers" --> ERR
F4 -- None --> F5["FPUMixin.lift_fpu_instruction<br/>(mnemonics starting with f, plus wait)"]
F5 -- "code" --> OUT
F5 -- "raises" --> ERR
F5 -- None --> F6["call / jmp / jecxz / jcc / setcc / cmovcc /<br/>ret / push / pop / leave / not"]
F6 -- "code" --> OUT
F6 -- no --> F7{"mov movzx movsx lea nop<br/>cdq cwde cbw bswap cwd"}
F7 -- yes --> UP["upstream Lifter.lift_instruction"]
F7 -- no --> ERR["NotImplementedError / ValueError"]
ERR --> TRAP{"trap_unsupported?"}
TRAP -- yes --> T["engine_fail(cpu, 'unsupported original instruction')"]
TRAP -- no --> RAISE["function recorded as unsupported"]
The final whitelist (mov, movzx, movsx, lea, nop, cdq, cwde, cbw, bswap, cwd) is the only place upstream lowering is reused, because "all other upstream branches include heuristic or partial approximations" (lines 123-129). The result is rejected if the upstream text contains unknown reg, seg reg or ???. (cwd is in the whitelist but ExtraMixin handles it first.)
| Instruction | Generated C |
|---|---|
call target (any operand) |
{ uint32_t target = <operand>; engine_push(cpu, <return VA>, 4); engine_dispatch(cpu, target); if (cpu->pc != <return VA>) { if (cpu->pc >= <func start> && cpu->pc < <func end>) goto L_ENTRY; else return; } } (lines 66-72); the generator later turns constant targets into ENGINE_DIRECT
|
jmp target (direct) |
goto L_target; if the target is a block of this function, else engine_dispatch(cpu, target); return; (_branch, lines 39-42) |
jmp <reg or mem> |
switch (<operand>) { case <each leader>: goto L_<leader>; ... default: engine_dispatch(cpu, <operand>); return; } (operand must be 32 bits) |
jcc |
if (<flags condition>) { <_branch(target)> } |
jecxz / jcxz
|
if (ecx == 0) / if (LO16(ecx) == 0) then _branch
|
setcc |
<dst> = (<condition> ? 1u : 0u); |
cmovcc |
{ uint32_t value = <src>; if (<condition>) { <dst> = value; } }: the source is read unconditionally, as on hardware |
ret / ret n
|
cpu->pc = engine_pop(cpu, 4); esp += n; return; (operand-size-prefixed ret rejected) |
| fallthrough at block end |
_branch(next address) unless the block ends in ret or unconditional jmp
|
The call protocol and indirect dispatch are diagrammed on EngineReuse Runtime.
| Instruction | Generated C |
|---|---|
push x (16/32-bit) |
engine_push(cpu, x, size); |
pop x (16/32-bit) |
{ uint32_t value=engine_pop(cpu,size); <x> = value; } (a memory destination is addressed with the already incremented esp, as on hardware) |
leave |
esp=ebp;ebp=engine_pop(cpu,4); |
not x |
<x> = ~(<x>); (no flags) |
div / idiv (8/16/32) |
engine_divide(cpu, <src>, bits, signed); (traps on zero divisor and quotient overflow) |
sahf |
eflags = (eflags & ~0xd5u) | (HI8(eax) & 0xd5u); |
lahf |
SET_HI8(eax, (eflags & 0xd5u) | 2u); |
stc / clc / cmc
|
eflags |= 1u; / eflags &= ~1u; / eflags ^= 1u;
|
cld / std
|
clear / set 0x400 (DF) |
mov, movzx, movsx, lea, bswap, cdq, cwde, cbw, nop
|
upstream text, e.g. eax = engine_read_u32(cpu, esp + 0x4); /* 0x...: mov ... */
|
flags.py owns add sub adc sbb and or xor cmp test, sal sar shl shr, inc dec neg and two/three-operand imul. Its contract:
- The mnemonic is the last whitespace token, so Capstone's textual prefixes (
lock add,repe cmpsb) are normalized; then alockprefix (byte0xF0orlocktext) raisesValueError("LOCK flag instruction requires atomic lowering"). - Operand widths must be 8, 16 or 32 bits and both sides must match (immediates excepted).
- Each producer becomes one helper call that both computes the result and updates
eflags:
| Instruction | Generated C |
|---|---|
add a, b / sub a, b
|
a = engine_flags_add(&eflags, a, b, 0u, W); / engine_flags_sub(...)
|
adc / sbb
|
same helpers with (eflags & ENGINE_EFLAGS_CF) as carry/borrow in |
cmp a, b |
(void)engine_flags_sub(&eflags, a, b, 0u, W); |
test a, b |
(void)engine_flags_logic(&eflags, (a) & (b), W); |
and/or/xor
|
a = engine_flags_logic(&eflags, (a) op (b), W); |
inc/dec/neg
|
a = engine_flags_inc/dec/neg(&eflags, a, W); |
shl/sal/shr/sar
|
a = engine_flags_shl/shr/sar(&eflags, a, count, W); |
imul r, a / imul r, a, imm (16/32) |
r = engine_flags_imul(&eflags, a, b, W); |
- Instructions that write flags but have no lowering in this mixin are listed in
_KNOWN_UNSUPPORTED_FLAG_WRITERS(lines 22-27) and raiseNotImplementedErrorrather than returningNone, so the heuristic upstream path can never handle them silently. Most of them are handled earlier byEngineLifterorExtraMixin(bt*,rol/ror/rcl/rcr,shld/shrd, string compares,xadd,cmpxchg,mul,div,popf,sahf,stc/clc/cmc). The ones that remain unsupported end to end are the BCD instructions (aaa aad aam aas daa das) andcmpxchg8b. -
flags_condition(mnemonic)(lines 209-239) stripsj/set/cmov, resolves aliases (c/naetob,ztoe,nbetoa,petop,ngetol, ...) and returns the architectural expression overENGINE_EFLAGS_*bits, e.g.jgbecomes(!ZF && (SF == OF)). Unknown suffixes raiseValueError(includingjecxz, which has its own lowering). - Architecturally undefined flags are made deterministic in
engine_flags.hand summarized inUNDEFINED_FLAG_NOTES; see EngineReuse Runtime.
The mixin finds operand formatters through _read_op(op, insn)/_write_op(op, value, insn) if the host class defines them, else the upstream _fmt_read(op)/_fmt_write(op, value), so it is testable without the upstream lifter (the tests use a stub host).
extra.py runs before the flags mixin. "Everything here is lowered exactly (no heuristics). String instructions honor the direction flag."
| Group | Instructions | Lowering |
|---|---|---|
| String ops |
movs stos lods scas cmps in byte/word/dword forms, with optional rep/repe/repne
|
Opcode and prefixes are decoded from the raw bytes (F3 rep/repe, F2 repne, 66 word size). Step is (eflags & 0x400) ? -width : width. scas/cmps set flags through engine_flags_sub. Repeated forms are while (ecx) { body; ecx--; [if (stop) break;] } where the stop test is !ZF for repe and ZF for repne. Address-size prefix 0x67 is rejected |
| Widening multiply | one-operand mul/imul (8/16/32) |
engine_mul1(cpu, src, bits, signed) writes AX, DX:AX or EDX:EAX and sets CF=OF |
| Double shifts |
shld, shrd (16/32) |
engine_flags_shld/shrd |
| Rotates |
rol ror rcl rcr (8/16/32) |
engine_flags_rol/ror/rcl/rcr |
| Bit tests |
bt bts btr btc (16/32) |
CF from the selected bit; for a memory base with a register offset, the signed offset selects a word outside the operand (`bitaddr = addr + (bitoff >> 5 |
| Bit scans |
bsf, bsr
|
ZF set and destination unchanged when the source is 0, else engine_bsf/bsr
|
| Exchange | xchg |
swap through temporaries |
xadd |
engine_flags_add, destination gets the sum, source gets the old destination |
|
cmpxchg |
compare with AL/AX/EAX via engine_flags_sub; ZF selects store or accumulator load |
|
| Table lookup | xlatb |
SET_LO8(eax, engine_read_u8(cpu, ebx + LO8(eax))) |
pushal/popal
|
eight pushes/pops; popal discards the saved ESP |
|
pushf(d)/popf(d)
|
push eflags | 0x202; pop the whole word |
|
cpuid |
engine_cpuid(cpu): "a plain CPU with no MMX/SSE/3DNow! so the game selects its x87 code paths" |
|
rdtsc |
engine_rdtsc(cpu): host monotonic clock |
|
emms/femms
|
comment only (no MMX state modeled) | |
int3, hlt, ud2
|
engine_fail(cpu, "...") |
|
lock (bare mnemonic) |
no code |
Because ExtraMixin matches on the last mnemonic token, lock xadd and lock cmpxchg are lowered as ordinary, non-atomic C sequences, whereas lock add/lock or etc. reach FlagsMixin and are rejected. The code does not establish whether Halo executes any lock-prefixed instruction concurrently on shared data.
Every x87 instruction is lowered to a helper call in engine_cpu.h; the mapping table and semantics are on x87 Floating Point. Unsupported x87 forms raise ValueError.
- Records the function and its range
[_flo, _fhi)(_fhiis the maximum block end) for the call-return check. - Emits
[static] void sub_XXXXXXXX(EngineCPU *cpu) {andL_ENTRY: switch (cpu->pc) { case <leader>: goto L_<leader>; ... default: engine_fail(cpu, "dispatch to non-leader address"); }. Every block leader, including call return sites and harvested jump-table targets, is an entry point. - For each block in address order: label
L_XXXXXXXX:;, then per instructionengine_step(cpu,0x...u); /* mnemonic operands */followed by the lowered statements. - A block that does not end in
retor an unconditionaljmpgets an explicit branch to its fallthrough address.
There is no per-function register reset, no local copies of registers, and no prologue/epilogue synthesis; the guest's own instructions manage the guest stack.
These are vendored for completeness; none of Master Chef's build paths call them, and their output format (recomp_*.c against recomp_types.h) is not compatible with EngineReuse.
| Tool | Invocation | Options |
|---|---|---|
translator.py |
main(); __main__.py calls it (its docstring says python -m tools.recomp <args>, the upstream project's layout) |
pe_file; --output/-o (default src/game/recomp/gen); --split (1000 functions per file); --all (full pipeline); --analyze-only; --functions-json PATH; --stubs PATH (import stub file); --pe-json PATH
|
generate.py |
python generate.py [exe] [output_dir] [split] |
defaults config/xwingalliance_decrypted.exe, src/game/recomp/gen, 500; also writes config/functions.json
|
pe_analyze.py |
python pe_analyze.py <pe_file> [--json out.json] |
prints a section/import summary |
The upstream discovery heuristics are worth knowing when reading old notes: find_call_targets scans every E8 byte as a potential relative call, find_functions adds every 55 8B EC (push ebp; mov ebp, esp) prologue, and generate.py additionally accepts 83 EC (sub esp, imm8) after CC/90/C3 padding and caps each function at 64 KiB. The Halo pipeline uses explicit lists instead (Function Address Lists).
Two regression cases against the real executable (test_decode.py):
- With
known_functions={0x4D0580, 0x4D05CA, 0x4D05D0}, function0x4D0580(a pool reset) has0x4D05CAintail_calls, not inblocks, and its lifted C containsengine_dispatch(cpu, 0x004D05CAu); return;. This is the conditional-fallthrough-into-shared-epilogue case. - Without
0x4D05CAin the known set, the same bytes keep0x4D05CAas an internal block.
0x4D05CA is not in the checked-in lists, so the production build uses the second behavior. The test requires halo.exe with the supported hash and uses package-relative imports (from .upstream import ...), so it must be run as a module from tools/.
A differential test of engine_flags.h and FlagsMixin against Unicorn executing the original x86 encodings (test_flags.py). It compiles a small C library at -O0 and -O2 (with -Wall -Wextra -Werror) and loads it with ctypes. Each test prints a receipt:
| Receipt | Assertion |
|---|---|
FLAGS-001 |
12 operations x 3 widths x 100 operand pairs (25 directed edge pairs plus 75 seeded random) x 2 builds: result and every defined status flag (AF excluded for logical ops), DF and bit 1 match Unicorn |
FLAGS-002 |
all 16 Jcc conditions over all 32 combinations of CF/PF/ZF/SF/OF match the architectural truth table |
FLAGS-003 |
mixin hook contract, width checks, alias handling, rejection of shld/div/scasd/xadd in the mixin, one-operand IMUL, 8-bit IMUL, 64-bit operands, mismatched widths, LOCK prefix and jecxz
|
FLAGS-004 |
a UBSan build sweeping all helpers over valid widths has no undefined behavior |
FLAGS-005 |
flags written by one call are read correctly by a later, separate call (no hidden per-call state) |
FLAGS-006 |
shl/shr/sar with masked counts match Unicorn |
FLAGS-007 |
two- and three-operand imul match Unicorn for CF/OF and result |
Lifts x87 snippets with FPUMixin and compares them with Unicorn and an x86_64 binary run under Rosetta. Described on x87 Floating Point.
None of the three Python tests is invoked by tools/run_source_checks.py; they need unicorn, a C compiler, and (for test_decode) the executable.
Documents master-chef at commit 9f915af (v1.0.3). Unofficial project, not affiliated with Microsoft, Bungie, Gearbox or Apple. Original code is MIT licensed; game content is not included.
Overview
- Architecture Overview
- Repository Layout
- Glossary
- Environment Variables
- Contributing Guide
- Open Questions
Translation
- Static Translation Pipeline
- XWA Decoder and Lifter
- Function Address Lists
- EngineReuse Runtime
- x87 Floating Point
Host runtime
- EngineHost Overview
- Win32 Compatibility Layer
- Threading and Synchronization
- Guest Memory and Heap
- Engine Overrides and Hooks
- Runtime Settings
Graphics
- Direct3D9 Bridge
- Metal Renderer
- Shader Translation
- Textures and Texture Packs
- Geometry Fast Paths
- Radial Fog
Panorama and presentation
- Panorama System
- Panorama Budget and LOD
- Frame Pacing
- visionOS App
- Immersive Presenter
- Layer Alignment
Audio and input
Tooling and process