1:1 source port of the engine and game code; Windows build and system layer; docs. No game files or SDK headers.

This commit is contained in:
CRTkafa 2026-10-22 16:30:40 +08:00
parent c3b032a45a
commit 0ecb47e9cb
3871 changed files with 91080 additions and 1640063 deletions

View file

@ -0,0 +1,3 @@
One file per area (e.g. `math.py`, `stdio.py`). Each function is named exactly like the imported symbol
(`_sinf`, `_vsnprintf`, `__ZdlPv`...), takes the `Game` and ends with `g._ret(value)` (or sets FP registers for
float returns). Behave like 2009 Darwin libc on i386.

View file

@ -0,0 +1,64 @@
"""libm import stubs (float functions) for the emulator, behaving like 2009 Darwin i386 libm.
i386 float functions return their result in x87 ST0. A Python hook cannot push the x87 stack directly, so the
stub stores the float in a scratch word and jumps to a tiny trampoline `flds [scratch]; ret` (the caller's return
address is still on the stack, so the trampoline's `ret` returns to the caller, which pops ST0 as usual).
sinf/cosf: Darwin's libm of that era evaluates them in double precision and rounds to float, i.e. the result is
the correctly rounded float of the double result. The double sin/cos of the host libm is used for that.
Setting `g.h_libm_host = True` makes the stubs call the host's sinf/cosf instead (the libm a port runs on), so a
differential test can separate libm rounding differences from differences in the ported code.
Helpers are prefixed h_ so the stub loader (which registers callables starting with a single '_') skips them.
"""
import ctypes
import math
import struct
from unicorn.x86_const import UC_X86_REG_ESP, UC_X86_REG_EIP
def h_f32(x):
return struct.unpack('<f', struct.pack('<f', x))[0]
def h_arg_f32(g, i):
esp = g.uc.reg_read(UC_X86_REG_ESP)
return struct.unpack('<f', bytes(g.uc.mem_read(esp + 4 + 4 * i, 4)))[0]
def h_fld_trampoline(g):
"""Returns (code, scratch): code = `flds [scratch]; ret`, placed on its own page, scratch on another page."""
t = getattr(g, 'h_fld_tramp', None)
if t is None:
block = g.malloc(0x3000)
code = (block + 0xfff) & ~0xfff
scratch = code + 0x1000
g.uc.mem_write(code, b'\xd9\x05' + struct.pack('<I', scratch) + b'\xc3')
t = g.h_fld_tramp = (code, scratch)
return t
def h_ret_f32(g, value):
code, scratch = h_fld_trampoline(g)
g.uc.mem_write(scratch, struct.pack('<f', value))
g.uc.reg_write(UC_X86_REG_EIP, code)
h_libc = ctypes.CDLL(None)
for _n in ('sinf', 'cosf'):
getattr(h_libc, _n).restype = ctypes.c_float
getattr(h_libc, _n).argtypes = [ctypes.c_float]
def h_libm(g, name, model, x):
if getattr(g, 'h_libm_host', False):
return getattr(h_libc, name)(x)
return model(x) if math.isfinite(x) else math.nan
def _sinf(g):
h_ret_f32(g, h_libm(g, 'sinf', math.sin, h_arg_f32(g, 0)))
def _cosf(g):
h_ret_f32(g, h_libm(g, 'cosf', math.cos, h_arg_f32(g, 0)))

View file

@ -0,0 +1,366 @@
"""Import stubs for the TMemoryFile / TMemDataSource differential test (tools/port/test_memfile.py).
Behaves like 2009 Darwin i386 (libSystem, libstdc++ of gcc 4.0):
- _vsnprintf: C99 semantics (returns the full length, writes at most n-1 chars plus NUL), integer/string subset.
- _pthread_mutex_lock/_unlock: return 0 and count the calls in g.mutex_log (the test checks lock/unlock pairs).
- std::_Rb_tree_* free functions of libstdc++ (tree.cc), transcribed from the gcc 4.0 source.
- Weak template instantiations that the binary imports through its jump table although it defines them itself
(e.g. std::_Rb_tree<str, pair<const str,str>>::erase(const str&)) are bound to the binary's own copy, as dyld
coalesces them to the executable's definition.
emu.py's loader skips names that start with '__' (all C++ imports), so the C++ ones are registered into
EXTRA_STUBS directly at load time (see h_register at the bottom).
"""
import re
import struct
from unicorn.x86_const import UC_X86_REG_EIP
RED, BLACK = 0, 1
# ---- libc ------------------------------------------------------------------------------------------------------
def h_format(g, fmt, ap):
out = bytearray()
i = 0
while i < len(fmt):
c = fmt[i]
if c != 0x25:
out.append(c); i += 1; continue
m = re.match(rb'%([-+ #0]*)(\d*)(?:\.(\d+))?(hh|h|ll|l|z|j|t)?([diouxXcs%])', fmt[i:])
if not m:
raise RuntimeError(f'_vsnprintf: unsupported format {fmt[i:]!r}')
flags, width, prec, length, conv = m.groups()
i += m.end()
if conv == b'%':
out += b'%'; continue
if length == b'll':
v = g.u32(ap) | (g.u32(ap + 4) << 32); ap += 8; bits = 64
else:
v = g.u32(ap); ap += 4; bits = 32
if conv in (b'd', b'i'):
if v & (1 << (bits - 1)):
v -= 1 << bits
body = str(abs(v)).encode()
sign = b'-' if v < 0 else (b'+' if b'+' in flags else (b' ' if b' ' in flags else b''))
elif conv == b'u':
body, sign = str(v).encode(), b''
elif conv == b'o':
body, sign = format(v, 'o').encode(), b''
elif conv in (b'x', b'X'):
body, sign = format(v, 'x' if conv == b'x' else 'X').encode(), b''
elif conv == b'c':
body, sign = bytes([v & 0xff]), b''
else: # s
body, sign = (g.read_cstr(v) if v else b'(null)'), b''
if prec is not None:
body = body[:int(prec)]
if conv not in (b's', b'c') and prec is not None:
body = body.rjust(int(prec), b'0')
w = int(width) if width else 0
pad = w - len(sign) - len(body)
if pad > 0:
if b'-' in flags:
body = sign + body + b' ' * pad; sign = b''
elif b'0' in flags and prec is None and conv not in (b's', b'c'):
body = sign + b'0' * pad + body; sign = b''
else:
body = b' ' * pad + sign + body; sign = b''
out += sign + body
return bytes(out)
def _vsnprintf(g):
buf, n, fmt, ap = g._args(4)
text = h_format(g, g.read_cstr(fmt), ap)
if n:
part = text[:n - 1]
g.uc.mem_write(buf, part + b'\0')
g._ret(len(text))
def _pthread_mutex_lock(g):
(m,) = g._args(1)
g.__dict__.setdefault('mutex_log', []).append(('lock', m))
g._ret(0)
def _pthread_mutex_unlock(g):
(m,) = g._args(1)
g.__dict__.setdefault('mutex_log', []).append(('unlock', m))
g._ret(0)
# ---- libstdc++ tree.cc (gcc 4.0) -----------------------------------------------------------------------------
# _Rb_tree_node_base: +0 color, +4 parent, +8 left, +0xc right.
def h_col(g, x): return g.u32(x)
def h_par(g, x): return g.u32(x + 4)
def h_left(g, x): return g.u32(x + 8)
def h_right(g, x): return g.u32(x + 12)
def h_scol(g, x, v): g.w32(x, v)
def h_spar(g, x, v): g.w32(x + 4, v)
def h_sleft(g, x, v): g.w32(x + 8, v)
def h_sright(g, x, v): g.w32(x + 12, v)
def h_rotate_left(g, x, root_slot):
y = h_right(g, x)
h_sright(g, x, h_left(g, y))
if h_left(g, y):
h_spar(g, h_left(g, y), x)
h_spar(g, y, h_par(g, x))
if x == g.u32(root_slot):
g.w32(root_slot, y)
elif x == h_left(g, h_par(g, x)):
h_sleft(g, h_par(g, x), y)
else:
h_sright(g, h_par(g, x), y)
h_sleft(g, y, x)
h_spar(g, x, y)
def h_rotate_right(g, x, root_slot):
y = h_left(g, x)
h_sleft(g, x, h_right(g, y))
if h_right(g, y):
h_spar(g, h_right(g, y), x)
h_spar(g, y, h_par(g, x))
if x == g.u32(root_slot):
g.w32(root_slot, y)
elif x == h_right(g, h_par(g, x)):
h_sright(g, h_par(g, x), y)
else:
h_sleft(g, h_par(g, x), y)
h_sright(g, y, x)
h_spar(g, x, y)
def h_increment(g, x):
if h_right(g, x):
x = h_right(g, x)
while h_left(g, x):
x = h_left(g, x)
else:
y = h_par(g, x)
while x == h_right(g, y):
x = y
y = h_par(g, y)
if h_right(g, x) != y:
x = y
return x
def h_decrement(g, x):
if h_col(g, x) == RED and h_par(g, h_par(g, x)) == x:
x = h_right(g, x)
elif h_left(g, x):
y = h_left(g, x)
while h_right(g, y):
y = h_right(g, y)
x = y
else:
y = h_par(g, x)
while x == h_left(g, y):
x = y
y = h_par(g, y)
x = y
return x
def rb_increment(g):
(x,) = g._args(1); g._ret(h_increment(g, x))
def rb_decrement(g):
(x,) = g._args(1); g._ret(h_decrement(g, x))
def rb_insert_and_rebalance(g):
insert_left, x, p, header = g._args(4)
insert_left &= 0xff
root_slot = header + 4
h_spar(g, x, p); h_sleft(g, x, 0); h_sright(g, x, 0); h_scol(g, x, RED)
if insert_left:
h_sleft(g, p, x)
if p == header:
g.w32(header + 4, x)
g.w32(header + 12, x)
elif p == g.u32(header + 8):
g.w32(header + 8, x)
else:
h_sright(g, p, x)
if p == g.u32(header + 12):
g.w32(header + 12, x)
while x != g.u32(root_slot) and h_col(g, h_par(g, x)) == RED:
xpp = h_par(g, h_par(g, x))
if h_par(g, x) == h_left(g, xpp):
y = h_right(g, xpp)
if y and h_col(g, y) == RED:
h_scol(g, h_par(g, x), BLACK); h_scol(g, y, BLACK); h_scol(g, xpp, RED)
x = xpp
else:
if x == h_right(g, h_par(g, x)):
x = h_par(g, x)
h_rotate_left(g, x, root_slot)
h_scol(g, h_par(g, x), BLACK); h_scol(g, xpp, RED)
h_rotate_right(g, xpp, root_slot)
else:
y = h_left(g, xpp)
if y and h_col(g, y) == RED:
h_scol(g, h_par(g, x), BLACK); h_scol(g, y, BLACK); h_scol(g, xpp, RED)
x = xpp
else:
if x == h_left(g, h_par(g, x)):
x = h_par(g, x)
h_rotate_right(g, x, root_slot)
h_scol(g, h_par(g, x), BLACK); h_scol(g, xpp, RED)
h_rotate_left(g, xpp, root_slot)
h_scol(g, g.u32(root_slot), BLACK)
g._ret(0)
def rb_rebalance_for_erase(g):
z, header = g._args(2)
root_slot, left_slot, right_slot = header + 4, header + 8, header + 12
y = z
x = 0
x_parent = 0
if h_left(g, y) == 0:
x = h_right(g, y)
elif h_right(g, y) == 0:
x = h_left(g, y)
else:
y = h_right(g, y)
while h_left(g, y):
y = h_left(g, y)
x = h_right(g, y)
if y != z:
h_spar(g, h_left(g, z), y)
h_sleft(g, y, h_left(g, z))
if y != h_right(g, z):
x_parent = h_par(g, y)
if x:
h_spar(g, x, h_par(g, y))
h_sleft(g, h_par(g, y), x)
h_sright(g, y, h_right(g, z))
h_spar(g, h_right(g, z), y)
else:
x_parent = y
if g.u32(root_slot) == z:
g.w32(root_slot, y)
elif h_left(g, h_par(g, z)) == z:
h_sleft(g, h_par(g, z), y)
else:
h_sright(g, h_par(g, z), y)
h_spar(g, y, h_par(g, z))
cy, cz = h_col(g, y), h_col(g, z)
h_scol(g, y, cz); h_scol(g, z, cy)
y = z
else:
x_parent = h_par(g, y)
if x:
h_spar(g, x, h_par(g, y))
if g.u32(root_slot) == z:
g.w32(root_slot, x)
elif h_left(g, h_par(g, z)) == z:
h_sleft(g, h_par(g, z), x)
else:
h_sright(g, h_par(g, z), x)
if g.u32(left_slot) == z:
if h_right(g, z) == 0:
g.w32(left_slot, h_par(g, z))
else:
m = x
while h_left(g, m):
m = h_left(g, m)
g.w32(left_slot, m)
if g.u32(right_slot) == z:
if h_left(g, z) == 0:
g.w32(right_slot, h_par(g, z))
else:
m = x
while h_right(g, m):
m = h_right(g, m)
g.w32(right_slot, m)
if h_col(g, y) != RED:
while x != g.u32(root_slot) and (x == 0 or h_col(g, x) == BLACK):
if x == h_left(g, x_parent):
w = h_right(g, x_parent)
if h_col(g, w) == RED:
h_scol(g, w, BLACK); h_scol(g, x_parent, RED)
h_rotate_left(g, x_parent, root_slot)
w = h_right(g, x_parent)
if (h_left(g, w) == 0 or h_col(g, h_left(g, w)) == BLACK) and \
(h_right(g, w) == 0 or h_col(g, h_right(g, w)) == BLACK):
h_scol(g, w, RED)
x = x_parent
x_parent = h_par(g, x_parent)
else:
if h_right(g, w) == 0 or h_col(g, h_right(g, w)) == BLACK:
h_scol(g, h_left(g, w), BLACK); h_scol(g, w, RED)
h_rotate_right(g, w, root_slot)
w = h_right(g, x_parent)
h_scol(g, w, h_col(g, x_parent)); h_scol(g, x_parent, BLACK)
if h_right(g, w):
h_scol(g, h_right(g, w), BLACK)
h_rotate_left(g, x_parent, root_slot)
break
else:
w = h_left(g, x_parent)
if h_col(g, w) == RED:
h_scol(g, w, BLACK); h_scol(g, x_parent, RED)
h_rotate_right(g, x_parent, root_slot)
w = h_left(g, x_parent)
if (h_right(g, w) == 0 or h_col(g, h_right(g, w)) == BLACK) and \
(h_left(g, w) == 0 or h_col(g, h_left(g, w)) == BLACK):
h_scol(g, w, RED)
x = x_parent
x_parent = h_par(g, x_parent)
else:
if h_left(g, w) == 0 or h_col(g, h_left(g, w)) == BLACK:
h_scol(g, h_right(g, w), BLACK); h_scol(g, w, RED)
h_rotate_left(g, w, root_slot)
w = h_left(g, x_parent)
h_scol(g, w, h_col(g, x_parent)); h_scol(g, x_parent, BLACK)
if h_left(g, w):
h_scol(g, h_left(g, w), BLACK)
h_rotate_right(g, x_parent, root_slot)
break
if x:
h_scol(g, x, BLACK)
g._ret(y)
def h_forward_to_binary(name):
"""A weak symbol the executable imports from itself: jump to its own definition (dyld coalescing)."""
def stub(g):
g.uc.reg_write(UC_X86_REG_EIP, g.symbols[name])
return stub
h_MAP = 'I3strSt4pairIKS0_S0_ESt10_Select1stIS3_ESt4lessIS0_ESaIS3_EE'
CXX_STUBS = {
'__ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base': rb_increment,
'__ZSt18_Rb_tree_incrementPKSt18_Rb_tree_node_base': rb_increment,
'__ZSt18_Rb_tree_decrementPSt18_Rb_tree_node_base': rb_decrement,
'__ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_': rb_insert_and_rebalance,
'__ZSt28_Rb_tree_rebalance_for_erasePSt18_Rb_tree_node_baseRS_': rb_rebalance_for_erase,
}
for h_n in ['__ZNSt8_Rb_tree' + h_MAP + '5eraseERS2_',
'__ZNSt8_Rb_tree' + h_MAP + '5eraseESt17_Rb_tree_iteratorIS3_ESB_',
'__ZNSt8_Rb_tree' + h_MAP + '11upper_boundERS2_',
'__ZNSt8_Rb_tree' + h_MAP + '4findERS2_']:
CXX_STUBS[h_n] = h_forward_to_binary(h_n)
def h_register():
import sys
f = sys._getframe(1)
while f is not None:
if 'EXTRA_STUBS' in f.f_globals and '_load_extra_stubs' in f.f_globals:
f.f_globals['EXTRA_STUBS'].update(CXX_STUBS)
return
f = f.f_back
h_register()

440
tools/port/emu_stubs/xml.py Normal file
View file

@ -0,0 +1,440 @@
"""Import stubs for the simplexml / stringtable differential test (tools/port/test_xml.py).
Behaviour follows 2009 Darwin libc / libstdc++ (gcc 4.0) on i386:
- ctype in the "C" locale (isspace is true only for 9..13 and 32; negative values other than EOF are not space),
- strchr also finds the terminating NUL when searching for 0, strncmp returns the unsigned byte difference,
- printf/scanf: the conversions used by the SDK (%d %s %c %u %x ...), sscanf returns EOF on an input failure before
the first conversion,
- std::list node hook/unhook and the red-black tree helpers are ports of libstdc++'s list.cc / tree.cc.
All stubs are registered into emu.EXTRA_STUBS at the bottom of this file with setdefault, so an equivalent stub that
another area's file registered first is kept (emu.py's own loader would let the last file win, and it skips names
starting with "__" such as the libstdc++ "__Z..." symbols). The functions therefore carry an "x" prefix.
"""
import re
import struct
from unicorn.x86_const import UC_X86_REG_ESP
def s32(v):
v &= 0xffffffff
return v - (1 << 32) if v & 0x80000000 else v
# ---- ctype / string ---------------------------------------------------------------------------------------
def x_isspace(g):
(c,) = g._args(1)
c = s32(c)
# _CTYPE_S = 0x4000 in Darwin's rune table; only ASCII entries are set in the C locale.
g._ret(0x4000 if c in (9, 10, 11, 12, 13, 32) else 0)
def x_strchr(g):
s, c = g._args(2)
c &= 0xff
i = 0
while True:
b = g.uc.mem_read(s + i, 1)[0]
if b == c:
g._ret(s + i)
return
if b == 0:
g._ret(0)
return
i += 1
def x_strncmp(g):
a, b, n = g._args(3)
for i in range(n):
x = g.uc.mem_read(a + i, 1)[0]
y = g.uc.mem_read(b + i, 1)[0]
if x != y:
g._ret(x - y)
return
if x == 0:
break
g._ret(0)
def x_strstr(g):
h, n = g._args(2)
hay = g.read_cstr(h)
needle = g.read_cstr(n)
i = hay.find(needle)
g._ret(h + i if i >= 0 else 0)
# ---- printf / scanf ---------------------------------------------------------------------------------------
# FreeBSD-derived vfprintf (Darwin): an unknown conversion character is printed as itself, no argument used.
_SPEC = re.compile(rb"%([-+ #0']*)(\*|\d+)?(?:\.(\*|\d*))?(hh|h|ll|l|q|L|j|z|t)?(.)", re.S)
def format_c(g, fmt, args):
"""printf-style formatting of the bytes `fmt`; `args` yields 32-bit stack words (i386 va_list)."""
out = bytearray()
pos = 0
while True:
m = _SPEC.search(fmt, pos)
if not m:
out += fmt[pos:]
return bytes(out)
out += fmt[pos:m.start()]
pos = m.end()
flags, width, prec, length, conv = m.groups()
flags = flags.decode().replace("'", '')
if conv == b'%':
out += b'%'
continue
if width == b'*':
width = s32(next(args))
if width < 0:
flags += '-'
width = -width
else:
width = int(width) if width else None
if prec == b'*':
prec = s32(next(args))
prec = prec if prec >= 0 else None
elif prec is not None:
prec = int(prec) if prec else 0
if conv in b'di':
v = next(args)
if length in (b'll', b'q', b'j'):
v |= next(args) << 32
v = v - (1 << 64) if v & (1 << 63) else v
else:
v = s32(v)
spec = '%' + flags + (str(width) if width else '') + ('.%d' % prec if prec is not None else '') + 'd'
out += (spec % v).encode()
elif conv in b'ouxX':
v = next(args)
if length in (b'll', b'q', b'j'):
v |= next(args) << 32
spec = '%' + flags + (str(width) if width else '') + ('.%d' % prec if prec is not None else '') + \
(conv.decode() if conv != b'u' else 'd')
out += (spec % v).encode()
elif conv == b'c':
s = bytes([next(args) & 0xff])
out += s.rjust(width or 0) if '-' not in flags else s.ljust(width or 0)
elif conv == b's':
p = next(args)
s = g.read_cstr(p) if p else b'(null)'
if prec is not None:
s = s[:prec]
out += s.rjust(width or 0) if '-' not in flags else s.ljust(width or 0)
elif conv == b'p':
out += b'0x%x' % next(args)
elif conv == b'n':
next(args)
else:
out += conv
def arg_words(g, ptr):
while True:
yield g.u32(ptr)
ptr += 4
def x_vsnprintf(g):
buf, size, fmt, va = g._args(4)
s = format_c(g, g.read_cstr(fmt), arg_words(g, va))
if size:
n = min(len(s), size - 1)
g.uc.mem_write(buf, s[:n] + b'\0')
g._ret(len(s))
def x_vasprintf(g):
ret, fmt, va = g._args(3)
s = format_c(g, g.read_cstr(fmt), arg_words(g, va))
p = g.malloc(len(s) + 1)
g.uc.mem_write(p, s + b'\0')
g.w32(ret, p)
g._ret(len(s))
def x_sscanf(g):
src, fmt = g._args(2)
esp_args = arg_words(g, g.uc.reg_read(UC_X86_REG_ESP) + 12)
s = g.read_cstr(src)
f = g.read_cstr(fmt)
si = 0
fi = 0
count = 0
ws = b' \t\n\v\f\r'
def result(input_failure):
return -1 if (input_failure and count == 0) else count
while fi < len(f):
fc = f[fi:fi + 1]
if fc in ws:
while si < len(s) and s[si:si + 1] in ws:
si += 1
fi += 1
continue
if fc != b'%':
if si >= len(s):
g._ret(result(True)); return
if s[si:si + 1] != fc:
g._ret(result(False)); return
si += 1
fi += 1
continue
m = re.match(rb'%(\*)?(\d+)?(hh|h|l|ll)?([dis%])', f[fi:])
if not m:
raise ValueError('unsupported scanf format %r' % f)
fi += m.end()
suppress, width, _length, conv = m.groups()
if conv == b'%':
while si < len(s) and s[si:si + 1] in ws:
si += 1
if si >= len(s):
g._ret(result(True)); return
if s[si:si + 1] != b'%':
g._ret(result(False)); return
si += 1
continue
while si < len(s) and s[si:si + 1] in ws:
si += 1
if si >= len(s):
g._ret(result(True)); return
width = int(width) if width else 1 << 30
if conv == b's':
start = si
while si < len(s) and si - start < width and s[si:si + 1] not in ws:
si += 1
if not suppress:
p = next(esp_args)
g.uc.mem_write(p, s[start:si] + b'\0')
count += 1
continue
start = si
if si < len(s) and si - start < width and s[si:si + 1] in b'+-':
si += 1
dstart = si
while si < len(s) and si - start < width and 48 <= s[si] <= 57:
si += 1
if si == dstart:
g._ret(result(False)); return
if not suppress:
p = next(esp_args)
g.uc.mem_write(p, struct.pack('<i', s32(int(s[start:si]))))
count += 1
g._ret(count)
# ---- WriteDbg's ostream output (recorded on the Game object as g.dbg_log) -----------------------------------
def __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(g):
os_, s = g._args(2)
if not hasattr(g, 'dbg_log'):
g.dbg_log = []
g.dbg_log.append(g.read_cstr(s))
g._ret(os_)
def __ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_(g):
(os_,) = g._args(1)
g._ret(os_)
# ---- libstdc++ list.cc ------------------------------------------------------------------------------------
def __ZNSt15_List_node_base4hookEPS_(g):
node, pos = g._args(2)
prev = g.u32(pos + 4)
g.w32(node, pos)
g.w32(node + 4, prev)
g.w32(prev, node)
g.w32(pos + 4, node)
g._ret(0)
def __ZNSt15_List_node_base6unhookEv(g):
(node,) = g._args(1)
nxt = g.u32(node)
prev = g.u32(node + 4)
g.w32(prev, nxt)
g.w32(nxt + 4, prev)
g._ret(0)
# ---- libstdc++ tree.cc (node: color +0 (red 0, black 1), parent +4, left +8, right +0xc) ---------------------
RED, BLACK = 0, 1
def rb_col(g, x): return g.u32(x)
def rb_par(g, x): return g.u32(x + 4)
def rb_lft(g, x): return g.u32(x + 8)
def rb_rgt(g, x): return g.u32(x + 12)
def rb_increment(g, x):
if rb_rgt(g, x):
x = rb_rgt(g, x)
while rb_lft(g, x):
x = rb_lft(g, x)
else:
y = rb_par(g, x)
while x == rb_rgt(g, y):
x = y
y = rb_par(g, y)
if rb_rgt(g, x) != y:
x = y
return x
def rb_decrement(g, x):
if rb_col(g, x) == RED and rb_par(g, rb_par(g, x)) == x:
x = rb_rgt(g, x)
elif rb_lft(g, x):
y = rb_lft(g, x)
while rb_rgt(g, y):
y = rb_rgt(g, y)
x = y
else:
y = rb_par(g, x)
while x == rb_lft(g, y):
x = y
y = rb_par(g, y)
x = y
return x
def __ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base(g):
(x,) = g._args(1); g._ret(rb_increment(g, x))
def __ZSt18_Rb_tree_incrementPKSt18_Rb_tree_node_base(g):
(x,) = g._args(1); g._ret(rb_increment(g, x))
def __ZSt18_Rb_tree_decrementPSt18_Rb_tree_node_base(g):
(x,) = g._args(1); g._ret(rb_decrement(g, x))
def rb_rotate_left(g, x, header):
y = rb_rgt(g, x)
g.w32(x + 12, rb_lft(g, y))
if rb_lft(g, y):
g.w32(rb_lft(g, y) + 4, x)
g.w32(y + 4, rb_par(g, x))
if x == g.u32(header + 4):
g.w32(header + 4, y)
elif x == rb_lft(g, rb_par(g, x)):
g.w32(rb_par(g, x) + 8, y)
else:
g.w32(rb_par(g, x) + 12, y)
g.w32(y + 8, x)
g.w32(x + 4, y)
def rb_rotate_right(g, x, header):
y = rb_lft(g, x)
g.w32(x + 8, rb_rgt(g, y))
if rb_rgt(g, y):
g.w32(rb_rgt(g, y) + 4, x)
g.w32(y + 4, rb_par(g, x))
if x == g.u32(header + 4):
g.w32(header + 4, y)
elif x == rb_rgt(g, rb_par(g, x)):
g.w32(rb_par(g, x) + 12, y)
else:
g.w32(rb_par(g, x) + 8, y)
g.w32(y + 12, x)
g.w32(x + 4, y)
def __ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_(g):
insert_left, x, p, header = g._args(4)
insert_left &= 0xff
g.w32(x + 4, p)
g.w32(x + 8, 0)
g.w32(x + 12, 0)
g.w32(x, RED)
if insert_left:
g.w32(p + 8, x)
if p == header:
g.w32(header + 4, x)
g.w32(header + 12, x)
elif p == g.u32(header + 8):
g.w32(header + 8, x)
else:
g.w32(p + 12, x)
if p == g.u32(header + 12):
g.w32(header + 12, x)
while x != g.u32(header + 4) and rb_col(g, rb_par(g, x)) == RED:
xpp = rb_par(g, rb_par(g, x))
if rb_par(g, x) == rb_lft(g, xpp):
y = rb_rgt(g, xpp)
if y and rb_col(g, y) == RED:
g.w32(rb_par(g, x), BLACK)
g.w32(y, BLACK)
g.w32(xpp, RED)
x = xpp
else:
if x == rb_rgt(g, rb_par(g, x)):
x = rb_par(g, x)
rb_rotate_left(g, x, header)
g.w32(rb_par(g, x), BLACK)
g.w32(xpp, RED)
rb_rotate_right(g, xpp, header)
else:
y = rb_lft(g, xpp)
if y and rb_col(g, y) == RED:
g.w32(rb_par(g, x), BLACK)
g.w32(y, BLACK)
g.w32(xpp, RED)
x = xpp
else:
if x == rb_lft(g, rb_par(g, x)):
x = rb_par(g, x)
rb_rotate_right(g, x, header)
g.w32(rb_par(g, x), BLACK)
g.w32(xpp, RED)
rb_rotate_left(g, xpp, header)
g.w32(g.u32(header + 4), BLACK)
g._ret(0)
# ---- coalesced (weak) template instantiations --------------------------------------------------------------
# The game calls these std::map<str,str> members through __jump_table stubs although it defines them itself; dyld
# binds a coalesced symbol to the main executable's definition, so the stub continues at the local code.
COALESCED_LOCAL = [
'__ZNSt8_Rb_treeI3strSt4pairIKS0_S0_ESt10_Select1stIS3_ESt4lessIS0_ESaIS3_EE4findERS2_',
'__ZNSt8_Rb_treeI3strSt4pairIKS0_S0_ESt10_Select1stIS3_ESt4lessIS0_ESaIS3_EE11upper_boundERS2_',
'__ZNSt8_Rb_treeI3strSt4pairIKS0_S0_ESt10_Select1stIS3_ESt4lessIS0_ESaIS3_EE5eraseESt17_Rb_tree_iteratorIS3_ESB_',
'__ZNSt8_Rb_treeI3strSt4pairIKS0_S0_ESt10_Select1stIS3_ESt4lessIS0_ESaIS3_EE5eraseERS2_',
]
def make_local_jump(name):
def stub(g):
from unicorn.x86_const import UC_X86_REG_EIP
g.uc.reg_write(UC_X86_REG_EIP, g.symbols[name])
return stub
# ---- registration of the "__Z..." names (skipped by emu.py's loader) ---------------------------------------
def register_cxx():
import sys
names = {k[1:]: v for k, v in globals().items() if k.startswith('x_') and callable(v)}
names.update({k: v for k, v in globals().items() if k.startswith('__Z') and callable(v)})
for n in COALESCED_LOCAL:
names[n] = make_local_jump(n)
for mod in list(sys.modules.values()):
stubs = getattr(mod, 'EXTRA_STUBS', None)
if isinstance(stubs, dict):
for k, v in names.items():
stubs.setdefault(k, v)
register_cxx()