use super::{arg_dword, call_guest, HostState, Registry, StubFn, Win32Error};
use crate::emulator::{Cpu, Mmu};
pub fn register(registry: &mut Registry) {
register_for_dll(registry, "msvcrt.dll");
}
pub fn register_alias(registry: &mut Registry, dll: &str) {
register_for_dll(registry, dll);
}
fn register_for_dll(registry: &mut Registry, dll: &str) {
registry.register(dll, "??2@YAPAXI@Z", stub_operator_new as StubFn, 0);
registry.register(dll, "??3@YAXPAX@Z", stub_operator_delete as StubFn, 0);
registry.register_data(dll, "_adjust_fdiv", 0);
registry.register(dll, "_except_handler3", stub_except_handler3 as StubFn, 0);
registry.register(dll, "_initterm", stub_initterm as StubFn, 0);
registry.register(dll, "_purecall", stub_purecall as StubFn, 0);
registry.register(dll, "_onexit", stub_onexit as StubFn, 0);
registry.register(dll, "__dllonexit", stub_dllonexit as StubFn, 0);
registry.register(dll, "sprintf", stub_sprintf as StubFn, 0);
registry.register(dll, "malloc", stub_malloc as StubFn, 0);
registry.register(dll, "free", stub_free as StubFn, 0);
registry.register(dll, "_endthreadex", stub_end_thread_ex as StubFn, 0);
registry.register(dll, "_strnicmp", stub_strnicmp as StubFn, 0);
registry.register(dll, "_beginthreadex", stub_begin_thread_ex as StubFn, 0);
registry.register(dll, "_ftol", stub_ftol as StubFn, 0);
registry.register(dll, "rand", stub_rand as StubFn, 0);
registry.register(dll, "srand", stub_srand as StubFn, 0);
registry.register(dll, "_CIpow", stub_ci_pow as StubFn, 0);
registry.register(dll, "__security_error_handler", stub_purecall as StubFn, 0);
registry.register(dll, "__CppXcptFilter", stub_except_handler3 as StubFn, 0);
registry.register(dll, "_XcptFilter", stub_except_handler3 as StubFn, 0);
registry.register(dll, "_amsg_exit", stub_purecall as StubFn, 0);
registry.register(dll, "_lock", stub_purecall as StubFn, 0);
registry.register(dll, "_unlock", stub_purecall as StubFn, 0);
registry.register(dll, "memcpy", stub_memcpy as StubFn, 0);
registry.register(dll, "memset", stub_memset as StubFn, 0);
registry.register(dll, "memmove", stub_memmove as StubFn, 0);
registry.register(dll, "strncpy", stub_strncpy as StubFn, 0);
registry.register(dll, "_CIsqrt", stub_ci_sqrt as StubFn, 0);
registry.register(dll, "_vsnwprintf", stub_vsnwprintf as StubFn, 0);
registry.register(dll, "fopen", stub_returns_zero as StubFn, 0);
registry.register(dll, "_wfopen", stub_returns_zero as StubFn, 0);
registry.register(dll, "fclose", stub_purecall as StubFn, 0);
registry.register(dll, "time", stub_time as StubFn, 0);
registry.register(dll, "localtime", stub_localtime as StubFn, 0);
registry.register_data(dll, "_iob", 0);
registry.register(dll, "_errno", stub_errno as StubFn, 0);
registry.register_data(dll, "__mb_cur_max", 1);
registry.register(dll, "_write", stub_returns_arg2 as StubFn, 0);
registry.register(dll, "asctime", stub_asctime as StubFn, 0);
registry.register(dll, "fflush", stub_purecall as StubFn, 0);
registry.register(dll, "fprintf", stub_purecall as StubFn, 0);
registry.register(dll, "puts", stub_purecall as StubFn, 0);
registry.register(dll, "printf", stub_purecall as StubFn, 0);
registry.register(dll, "abort", stub_purecall as StubFn, 0);
registry.register(dll, "_snprintf", stub_snprintf as StubFn, 0);
registry.register(dll, "_putenv", stub_purecall as StubFn, 0);
registry.register(dll, "_stricmp", stub_stricmp as StubFn, 0);
registry.register(dll, "strchr", stub_strchr as StubFn, 0);
registry.register(dll, "isupper", stub_isupper as StubFn, 0);
registry.register(dll, "tolower", stub_tolower as StubFn, 0);
registry.register(dll, "ceil", stub_ceil as StubFn, 0);
registry.register(dll, "_CIcos", stub_ci_cos as StubFn, 0);
registry.register(dll, "_CIsin", stub_ci_sin as StubFn, 0);
registry.register(dll, "_CIlog", stub_ci_log as StubFn, 0);
registry.register(dll, "isalnum", stub_isalnum as StubFn, 0);
registry.register(dll, "isspace", stub_isspace as StubFn, 0);
registry.register(dll, "iswctype", stub_iswctype as StubFn, 0);
registry.register(dll, "toupper", stub_toupper as StubFn, 0);
registry.register(dll, "towlower", stub_towlower as StubFn, 0);
registry.register(dll, "towupper", stub_towupper as StubFn, 0);
registry.register(dll, "memchr", stub_memchr as StubFn, 0);
registry.register(dll, "memcmp", stub_memcmp as StubFn, 0);
registry.register(dll, "strcat", stub_strcat as StubFn, 0);
registry.register(dll, "strcmp", stub_strcmp as StubFn, 0);
registry.register(dll, "strcoll", stub_strcmp as StubFn, 0);
registry.register(dll, "strlen", stub_strlen as StubFn, 0);
registry.register(dll, "strncmp", stub_strncmp as StubFn, 0);
registry.register(dll, "strrchr", stub_strrchr as StubFn, 0);
registry.register(dll, "strxfrm", stub_strxfrm as StubFn, 0);
registry.register(dll, "strerror", stub_strerror as StubFn, 0);
registry.register(dll, "strftime", stub_strftime as StubFn, 0);
registry.register(dll, "strtoul", stub_strtoul as StubFn, 0);
registry.register(dll, "atoi", stub_atoi as StubFn, 0);
registry.register(dll, "wcslen", stub_wcslen as StubFn, 0);
registry.register(dll, "wcscoll", stub_wcscoll as StubFn, 0);
registry.register(dll, "wcsxfrm", stub_wcsxfrm as StubFn, 0);
registry.register(dll, "wcsftime", stub_wcsftime as StubFn, 0);
registry.register(dll, "fputc", stub_fputc as StubFn, 0);
registry.register(dll, "fputs", stub_purecall as StubFn, 0);
registry.register(dll, "fwrite", stub_fwrite as StubFn, 0);
registry.register(dll, "vfprintf", stub_purecall as StubFn, 0);
registry.register(dll, "setvbuf", stub_purecall as StubFn, 0);
registry.register(dll, "calloc", stub_calloc as StubFn, 0);
registry.register(dll, "realloc", stub_realloc as StubFn, 0);
registry.register(dll, "getenv", stub_returns_zero as StubFn, 0);
registry.register(dll, "localeconv", stub_localeconv as StubFn, 0);
registry.register(dll, "setlocale", stub_setlocale as StubFn, 0);
registry.register(dll, "_wmkdir", stub_purecall as StubFn, 0);
registry.register(
dll,
"__clean_type_info_names_internal",
stub_returns_zero as StubFn,
0,
);
registry.register(dll, "_crt_debugger_hook", stub_returns_zero as StubFn, 0);
registry.register_data(dll, "_encoded_null", 0);
registry.register(dll, "_encode_pointer", stub_returns_arg0 as StubFn, 0);
registry.register(dll, "_decode_pointer", stub_returns_arg0 as StubFn, 0);
registry.register(
dll,
"_except_handler4_common",
stub_except_handler3 as StubFn,
0,
);
registry.register(dll, "_initterm_e", stub_initterm as StubFn, 0);
registry.register(dll, "_malloc_crt", stub_malloc as StubFn, 0);
registry.register(dll, "sprintf_s", stub_sprintf_s as StubFn, 0);
registry.register(dll, "sscanf", stub_returns_zero as StubFn, 0);
registry.register(dll, "sscanf_s", stub_returns_zero as StubFn, 0);
}
fn stub_returns_arg0(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
arg_dword(cpu, mmu, 0).map_err(|t| trap("_encode_pointer/_decode_pointer", t))
}
fn stub_sprintf_s(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let buf = arg_dword(cpu, mmu, 0).map_err(|t| trap("sprintf_s", t))?;
if buf != 0 {
mmu.store8(buf, 0).map_err(|t| trap("sprintf_s", t))?;
}
Ok(0)
}
fn stub_operator_new(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let size = arg_dword(cpu, mmu, 0).map_err(|t| trap("operator new", t))?;
if size == 0 {
return Ok(0);
}
let addr = state.arena_alloc(size)?;
let zeros = vec![0u8; size as usize];
mmu.write_initializer(addr, &zeros)
.map_err(|t| trap("operator new", t))?;
Ok(addr)
}
fn stub_operator_delete(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p = arg_dword(cpu, mmu, 0).map_err(|t| trap("operator delete", t))?;
if p == 0 {
return Ok(0);
}
let _ = state.heap.remove(&p);
Ok(0)
}
fn stub_except_handler3(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_initterm(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
registry: &Registry,
) -> Result<u32, Win32Error> {
let begin = arg_dword(cpu, mmu, 0).map_err(|t| trap("_initterm", t))?;
let end = arg_dword(cpu, mmu, 1).map_err(|t| trap("_initterm", t))?;
if begin == 0 || end == 0 || end <= begin {
return Ok(0);
}
let span = end.saturating_sub(begin);
let count = (span / 4).min(4096);
for i in 0..count {
let slot = begin.wrapping_add(i * 4);
let fnptr = match mmu.load32(slot) {
Ok(v) => v,
Err(_) => break,
};
if fnptr == 0 {
continue;
}
match call_guest(cpu, mmu, registry, state, fnptr, &[]) {
Ok(_) => {}
Err(crate::Error::Win32(e)) => return Err(e),
Err(_) => break,
}
}
Ok(0)
}
fn stub_purecall(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_malloc(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let size = arg_dword(cpu, mmu, 0).map_err(|t| trap("malloc", t))?;
if size == 0 {
return Ok(0);
}
let addr = state.arena_alloc(size)?;
let zeros = vec![0u8; size as usize];
mmu.write_initializer(addr, &zeros)
.map_err(|t| trap("malloc", t))?;
Ok(addr)
}
fn stub_free(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p = arg_dword(cpu, mmu, 0).map_err(|t| trap("free", t))?;
if p == 0 {
return Ok(0);
}
let _ = state.heap.remove(&p);
Ok(0)
}
fn stub_onexit(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let func = arg_dword(cpu, mmu, 0).map_err(|t| trap("_onexit", t))?;
Ok(func)
}
fn stub_dllonexit(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let func = arg_dword(cpu, mmu, 0).map_err(|t| trap("__dllonexit", t))?;
Ok(func)
}
fn stub_sprintf(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let buf = arg_dword(cpu, mmu, 0).map_err(|t| trap("sprintf", t))?;
let fmt = arg_dword(cpu, mmu, 1).map_err(|t| trap("sprintf", t))?;
let mut arg_idx: u32 = 2;
let mut out: Vec<u8> = Vec::with_capacity(64);
let mut p = fmt;
loop {
let b = mmu.load8(p).map_err(|t| trap("sprintf", t))?;
if b == 0 {
break;
}
p = p.wrapping_add(1);
if b != b'%' {
out.push(b);
continue;
}
let mut left_align = false;
let mut zero_pad = false;
let mut width: usize = 0;
let mut precision: Option<usize> = None;
loop {
let c = mmu.load8(p).map_err(|t| trap("sprintf", t))?;
match c {
b'-' => {
left_align = true;
p = p.wrapping_add(1);
}
b'0' => {
zero_pad = true;
p = p.wrapping_add(1);
}
b'+' | b' ' | b'#' => {
p = p.wrapping_add(1);
}
_ => break,
}
}
loop {
let c = mmu.load8(p).map_err(|t| trap("sprintf", t))?;
if c.is_ascii_digit() {
width = width.saturating_mul(10) + (c - b'0') as usize;
p = p.wrapping_add(1);
} else {
break;
}
}
let mut prec_seen = false;
if mmu.load8(p).map_err(|t| trap("sprintf", t))? == b'.' {
p = p.wrapping_add(1);
prec_seen = true;
let mut prec: usize = 0;
loop {
let c = mmu.load8(p).map_err(|t| trap("sprintf", t))?;
if c.is_ascii_digit() {
prec = prec.saturating_mul(10) + (c - b'0') as usize;
p = p.wrapping_add(1);
} else {
break;
}
}
precision = Some(prec);
}
loop {
let c = mmu.load8(p).map_err(|t| trap("sprintf", t))?;
match c {
b'l' | b'h' | b'L' | b'I' | b'j' | b'z' | b't' => p = p.wrapping_add(1),
_ => break,
}
}
let spec = mmu.load8(p).map_err(|t| trap("sprintf", t))?;
p = p.wrapping_add(1);
let _ = prec_seen;
let formatted: Vec<u8> = match spec {
b'%' => vec![b'%'],
b's' => {
let s_addr = arg_dword(cpu, mmu, arg_idx).map_err(|t| trap("sprintf", t))?;
arg_idx += 1;
let mut s = Vec::new();
let mut q = s_addr;
let limit = precision.unwrap_or(8192);
for _ in 0..limit {
let c = mmu.load8(q).map_err(|t| trap("sprintf", t))?;
if c == 0 {
break;
}
s.push(c);
q = q.wrapping_add(1);
}
s
}
b'c' => {
let v = arg_dword(cpu, mmu, arg_idx).map_err(|t| trap("sprintf", t))?;
arg_idx += 1;
vec![v as u8]
}
b'd' | b'i' => {
let v = arg_dword(cpu, mmu, arg_idx).map_err(|t| trap("sprintf", t))? as i32;
arg_idx += 1;
format!("{v}").into_bytes()
}
b'u' => {
let v = arg_dword(cpu, mmu, arg_idx).map_err(|t| trap("sprintf", t))?;
arg_idx += 1;
format!("{v}").into_bytes()
}
b'x' => {
let v = arg_dword(cpu, mmu, arg_idx).map_err(|t| trap("sprintf", t))?;
arg_idx += 1;
format!("{v:x}").into_bytes()
}
b'X' => {
let v = arg_dword(cpu, mmu, arg_idx).map_err(|t| trap("sprintf", t))?;
arg_idx += 1;
format!("{v:X}").into_bytes()
}
b'p' => {
let v = arg_dword(cpu, mmu, arg_idx).map_err(|t| trap("sprintf", t))?;
arg_idx += 1;
format!("{v:08X}").into_bytes()
}
other => {
arg_idx += 1;
vec![b'%', other]
}
};
let pad = width.saturating_sub(formatted.len());
if !left_align {
let fill = if zero_pad { b'0' } else { b' ' };
out.resize(out.len() + pad, fill);
}
out.extend_from_slice(&formatted);
if left_align {
out.resize(out.len() + pad, b' ');
}
}
out.push(0);
for (i, byte) in out.iter().enumerate() {
mmu.store8(buf.wrapping_add(i as u32), *byte)
.map_err(|t| trap("sprintf", t))?;
}
Ok((out.len() as u32).saturating_sub(1))
}
fn stub_end_thread_ex(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _retval = arg_dword(cpu, mmu, 0).map_err(|t| trap("_endthreadex", t))?;
Ok(0)
}
fn stub_strnicmp(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let s1 = arg_dword(cpu, mmu, 0).map_err(|t| trap("_strnicmp", t))?;
let s2 = arg_dword(cpu, mmu, 1).map_err(|t| trap("_strnicmp", t))?;
let count = arg_dword(cpu, mmu, 2).map_err(|t| trap("_strnicmp", t))?;
if count == 0 {
return Ok(0);
}
const MAX_COUNT: u32 = 1 << 20;
if count > MAX_COUNT {
return Ok(0);
}
fn ascii_tolower(b: u8) -> u8 {
if b.is_ascii_uppercase() {
b + 0x20
} else {
b
}
}
for i in 0..count {
let p1 = s1.wrapping_add(i);
let p2 = s2.wrapping_add(i);
let b1 = match mmu.load8(p1) {
Ok(v) => v,
Err(_) => return Ok(0),
};
let b2 = match mmu.load8(p2) {
Ok(v) => v,
Err(_) => return Ok(0),
};
if b1 == 0 || b2 == 0 {
let diff = (b1 as i32) - (b2 as i32);
return Ok(diff as u32);
}
let l1 = ascii_tolower(b1);
let l2 = ascii_tolower(b2);
if l1 != l2 {
let diff = (l1 as i32) - (l2 as i32);
return Ok(diff as u32);
}
}
Ok(0)
}
fn stub_begin_thread_ex(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _security = arg_dword(cpu, mmu, 0).map_err(|t| trap("_beginthreadex", t))?;
let _stack_size = arg_dword(cpu, mmu, 1).map_err(|t| trap("_beginthreadex", t))?;
let _start_address = arg_dword(cpu, mmu, 2).map_err(|t| trap("_beginthreadex", t))?;
let _arglist = arg_dword(cpu, mmu, 3).map_err(|t| trap("_beginthreadex", t))?;
let _initflag = arg_dword(cpu, mmu, 4).map_err(|t| trap("_beginthreadex", t))?;
let thrdaddr = arg_dword(cpu, mmu, 5).map_err(|t| trap("_beginthreadex", t))?;
if thrdaddr != 0 {
let _ = mmu.store32(thrdaddr, 0);
}
Ok(0)
}
fn stub_ftol(
cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let f = cpu.fpu.st(0);
let _ = cpu.fpu.pop();
let v: i32 = if f.is_nan() {
i32::MIN
} else if f >= 2_147_483_648.0_f64 {
i32::MAX
} else if f <= -2_147_483_649.0_f64 {
i32::MIN
} else {
f as i32
};
Ok(v as u32)
}
fn stub_rand(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
state.rand_state = state.rand_state.wrapping_mul(214013).wrapping_add(2531011);
let r = (state.rand_state >> 16) & 0x7FFF;
Ok(r)
}
fn stub_srand(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let seed = arg_dword(cpu, mmu, 0).map_err(|t| trap("srand", t))?;
state.rand_state = seed;
Ok(0)
}
fn stub_ci_pow(
cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let exp = cpu.fpu.st(0);
let _ = cpu.fpu.pop();
let base = cpu.fpu.st(0);
let _ = cpu.fpu.pop();
let result = base.powf(exp);
cpu.fpu.push(result);
Ok(0)
}
fn trap(stub: &'static str, t: crate::emulator::Trap) -> Win32Error {
Win32Error::InvalidArgument {
stub,
reason: format!("{t}"),
}
}
fn stub_errno(
_cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
if let Some(addr) = state.errno_cell {
return Ok(addr);
}
let addr = state.arena_alloc(4)?;
mmu.write_initializer(addr, &0u32.to_le_bytes())
.map_err(|t| trap("_errno", t))?;
state.errno_cell = Some(addr);
Ok(addr)
}
fn stub_returns_zero(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_memcpy(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let dest = arg_dword(cpu, mmu, 0).map_err(|t| trap("memcpy", t))?;
let src = arg_dword(cpu, mmu, 1).map_err(|t| trap("memcpy", t))?;
let n = arg_dword(cpu, mmu, 2).map_err(|t| trap("memcpy", t))?;
let data = mmu.read(src, n as usize).map_err(|t| trap("memcpy", t))?;
mmu.write(dest, &data).map_err(|t| trap("memcpy", t))?;
Ok(dest)
}
fn stub_memset(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let dest = arg_dword(cpu, mmu, 0).map_err(|t| trap("memset", t))?;
let c = arg_dword(cpu, mmu, 1).map_err(|t| trap("memset", t))? as u8;
let n = arg_dword(cpu, mmu, 2).map_err(|t| trap("memset", t))?;
let buf = vec![c; n as usize];
mmu.write(dest, &buf).map_err(|t| trap("memset", t))?;
Ok(dest)
}
fn stub_memmove(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let dest = arg_dword(cpu, mmu, 0).map_err(|t| trap("memmove", t))?;
let src = arg_dword(cpu, mmu, 1).map_err(|t| trap("memmove", t))?;
let n = arg_dword(cpu, mmu, 2).map_err(|t| trap("memmove", t))?;
let data = mmu.read(src, n as usize).map_err(|t| trap("memmove", t))?;
mmu.write(dest, &data).map_err(|t| trap("memmove", t))?;
Ok(dest)
}
fn stub_strncpy(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let dest = arg_dword(cpu, mmu, 0).map_err(|t| trap("strncpy", t))?;
let src = arg_dword(cpu, mmu, 1).map_err(|t| trap("strncpy", t))?;
let n = arg_dword(cpu, mmu, 2).map_err(|t| trap("strncpy", t))? as usize;
let mut buf = vec![0u8; n];
let mut hit_nul = false;
for i in 0..n {
if hit_nul {
buf[i] = 0;
} else {
let b = mmu
.load8(src.wrapping_add(i as u32))
.map_err(|t| trap("strncpy", t))?;
if b == 0 {
hit_nul = true;
buf[i] = 0;
} else {
buf[i] = b;
}
}
}
mmu.write(dest, &buf).map_err(|t| trap("strncpy", t))?;
Ok(dest)
}
fn stub_ci_sqrt(
cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let x = cpu.fpu.st(0);
let _ = cpu.fpu.pop();
cpu.fpu.push(x.sqrt());
Ok(0)
}
fn stub_vsnwprintf(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let s = arg_dword(cpu, mmu, 0).map_err(|t| trap("_vsnwprintf", t))?;
let n = arg_dword(cpu, mmu, 1).map_err(|t| trap("_vsnwprintf", t))?;
if s != 0 && n >= 1 {
mmu.store16(s, 0).map_err(|t| trap("_vsnwprintf", t))?;
}
Ok(0)
}
fn stub_time(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
state.tick = state.tick.wrapping_add(1);
let value = 1_704_067_200u32.wrapping_add(state.tick);
let p = arg_dword(cpu, mmu, 0).map_err(|t| trap("time", t))?;
if p != 0 {
mmu.store32(p, value).map_err(|t| trap("time", t))?;
}
Ok(value)
}
fn stub_localtime(
_cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p = state.arena_const_alloc(36)?;
mmu.write_initializer(p, &[0u8; 36])
.map_err(|t| trap("localtime", t))?;
Ok(p)
}
fn stub_returns_arg2(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let n = arg_dword(cpu, mmu, 2).map_err(|t| trap("_write", t))?;
Ok(n)
}
fn stub_asctime(
_cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p = state.arena_const_alloc(26)?;
let canned = b"Mon Jan 1 00:00:00 2024\n\0";
mmu.write_initializer(p, canned)
.map_err(|t| trap("asctime", t))?;
Ok(p)
}
fn stub_snprintf(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let buf = arg_dword(cpu, mmu, 0).map_err(|t| trap("_snprintf", t))?;
let n = arg_dword(cpu, mmu, 1).map_err(|t| trap("_snprintf", t))?;
if buf != 0 && n >= 1 {
mmu.store8(buf, 0).map_err(|t| trap("_snprintf", t))?;
}
Ok(0)
}
fn stub_stricmp(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p1 = arg_dword(cpu, mmu, 0).map_err(|t| trap("_stricmp", t))?;
let p2 = arg_dword(cpu, mmu, 1).map_err(|t| trap("_stricmp", t))?;
for i in 0..0x1_0000u32 {
let a = mmu
.load8(p1.wrapping_add(i))
.map_err(|t| trap("_stricmp", t))?
.to_ascii_lowercase();
let b = mmu
.load8(p2.wrapping_add(i))
.map_err(|t| trap("_stricmp", t))?
.to_ascii_lowercase();
if a != b {
return Ok((a as i32 - b as i32) as u32);
}
if a == 0 {
return Ok(0);
}
}
Ok(0)
}
fn stub_strchr(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p = arg_dword(cpu, mmu, 0).map_err(|t| trap("strchr", t))?;
let needle = arg_dword(cpu, mmu, 1).map_err(|t| trap("strchr", t))? as u8;
for i in 0..0x1_0000u32 {
let b = mmu
.load8(p.wrapping_add(i))
.map_err(|t| trap("strchr", t))?;
if b == needle {
return Ok(p.wrapping_add(i));
}
if b == 0 {
return Ok(0);
}
}
Ok(0)
}
fn stub_isupper(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let c = arg_dword(cpu, mmu, 0).map_err(|t| trap("isupper", t))? as u8;
Ok(u32::from(c.is_ascii_uppercase()))
}
fn stub_tolower(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let c = arg_dword(cpu, mmu, 0).map_err(|t| trap("tolower", t))? as u8;
Ok(u32::from(c.to_ascii_lowercase()))
}
fn stub_ceil(
cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let x = cpu.fpu.st(0);
let _ = cpu.fpu.pop();
cpu.fpu.push(x.ceil());
Ok(0)
}
fn stub_ci_cos(
cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let x = cpu.fpu.st(0);
let _ = cpu.fpu.pop();
cpu.fpu.push(x.cos());
Ok(0)
}
fn stub_ci_sin(
cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let x = cpu.fpu.st(0);
let _ = cpu.fpu.pop();
cpu.fpu.push(x.sin());
Ok(0)
}
fn stub_ci_log(
cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let x = cpu.fpu.st(0);
let _ = cpu.fpu.pop();
cpu.fpu.push(x.ln());
Ok(0)
}
const STR_SCAN_CAP: u32 = 0x1_0000;
fn stub_isalnum(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let c = arg_dword(cpu, mmu, 0).map_err(|t| trap("isalnum", t))? as u8;
Ok(u32::from(c.is_ascii_alphanumeric()))
}
fn stub_isspace(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let c = arg_dword(cpu, mmu, 0).map_err(|t| trap("isspace", t))? as u8;
Ok(u32::from(c.is_ascii_whitespace()))
}
fn stub_iswctype(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_toupper(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let c = arg_dword(cpu, mmu, 0).map_err(|t| trap("toupper", t))?;
Ok(u32::from((c as u8).to_ascii_uppercase()))
}
fn stub_towlower(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let c = arg_dword(cpu, mmu, 0).map_err(|t| trap("towlower", t))?;
Ok(if (b'A' as u32..=b'Z' as u32).contains(&c) {
c + 32
} else {
c
})
}
fn stub_towupper(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let c = arg_dword(cpu, mmu, 0).map_err(|t| trap("towupper", t))?;
Ok(if (b'a' as u32..=b'z' as u32).contains(&c) {
c - 32
} else {
c
})
}
fn stub_memchr(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let buf = arg_dword(cpu, mmu, 0).map_err(|t| trap("memchr", t))?;
let c = arg_dword(cpu, mmu, 1).map_err(|t| trap("memchr", t))? as u8;
let n = arg_dword(cpu, mmu, 2).map_err(|t| trap("memchr", t))?;
for i in 0..n {
if mmu
.load8(buf.wrapping_add(i))
.map_err(|t| trap("memchr", t))?
== c
{
return Ok(buf.wrapping_add(i));
}
}
Ok(0)
}
fn stub_memcmp(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p1 = arg_dword(cpu, mmu, 0).map_err(|t| trap("memcmp", t))?;
let p2 = arg_dword(cpu, mmu, 1).map_err(|t| trap("memcmp", t))?;
let n = arg_dword(cpu, mmu, 2).map_err(|t| trap("memcmp", t))?;
for i in 0..n {
let a = mmu
.load8(p1.wrapping_add(i))
.map_err(|t| trap("memcmp", t))?;
let b = mmu
.load8(p2.wrapping_add(i))
.map_err(|t| trap("memcmp", t))?;
if a != b {
return Ok(if a < b { (-1i32) as u32 } else { 1 });
}
}
Ok(0)
}
fn read_c(mmu: &Mmu, base: u32, stub: &'static str) -> Result<Vec<u8>, Win32Error> {
let mut out = Vec::new();
if base == 0 {
return Ok(out);
}
for i in 0..STR_SCAN_CAP {
let b = mmu.load8(base.wrapping_add(i)).map_err(|t| trap(stub, t))?;
if b == 0 {
break;
}
out.push(b);
}
Ok(out)
}
fn stub_strcat(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let dest = arg_dword(cpu, mmu, 0).map_err(|t| trap("strcat", t))?;
let src = arg_dword(cpu, mmu, 1).map_err(|t| trap("strcat", t))?;
let mut end = dest;
let mut scanned = 0u32;
while scanned < STR_SCAN_CAP && mmu.load8(end).map_err(|t| trap("strcat", t))? != 0 {
end = end.wrapping_add(1);
scanned += 1;
}
let mut bytes = read_c(mmu, src, "strcat")?;
bytes.push(0);
mmu.write(end, &bytes).map_err(|t| trap("strcat", t))?;
Ok(dest)
}
fn stub_strcmp(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let s1 = arg_dword(cpu, mmu, 0).map_err(|t| trap("strcmp", t))?;
let s2 = arg_dword(cpu, mmu, 1).map_err(|t| trap("strcmp", t))?;
let a = read_c(mmu, s1, "strcmp")?;
let b = read_c(mmu, s2, "strcmp")?;
Ok(match a.cmp(&b) {
std::cmp::Ordering::Less => (-1i32) as u32,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
})
}
fn stub_strlen(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let s = arg_dword(cpu, mmu, 0).map_err(|t| trap("strlen", t))?;
Ok(read_c(mmu, s, "strlen")?.len() as u32)
}
fn stub_strncmp(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let s1 = arg_dword(cpu, mmu, 0).map_err(|t| trap("strncmp", t))?;
let s2 = arg_dword(cpu, mmu, 1).map_err(|t| trap("strncmp", t))?;
let n = arg_dword(cpu, mmu, 2).map_err(|t| trap("strncmp", t))?;
for i in 0..n {
let a = mmu
.load8(s1.wrapping_add(i))
.map_err(|t| trap("strncmp", t))?;
let b = mmu
.load8(s2.wrapping_add(i))
.map_err(|t| trap("strncmp", t))?;
if a != b {
return Ok(if a < b { (-1i32) as u32 } else { 1 });
}
if a == 0 {
break;
}
}
Ok(0)
}
fn stub_strrchr(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let s = arg_dword(cpu, mmu, 0).map_err(|t| trap("strrchr", t))?;
let c = arg_dword(cpu, mmu, 1).map_err(|t| trap("strrchr", t))? as u8;
let mut hit = 0u32;
for i in 0..STR_SCAN_CAP {
let b = mmu
.load8(s.wrapping_add(i))
.map_err(|t| trap("strrchr", t))?;
if b == c {
hit = s.wrapping_add(i);
}
if b == 0 {
break;
}
}
Ok(hit)
}
fn stub_strxfrm(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let dest = arg_dword(cpu, mmu, 0).map_err(|t| trap("strxfrm", t))?;
let src = arg_dword(cpu, mmu, 1).map_err(|t| trap("strxfrm", t))?;
let n = arg_dword(cpu, mmu, 2).map_err(|t| trap("strxfrm", t))?;
let bytes = read_c(mmu, src, "strxfrm")?;
let src_len = bytes.len() as u32;
if dest != 0 && n > 0 {
let take = (n as usize - 1).min(bytes.len());
let mut buf = bytes[..take].to_vec();
buf.push(0);
mmu.write(dest, &buf).map_err(|t| trap("strxfrm", t))?;
}
Ok(src_len)
}
fn stub_strerror(
_cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let msg = b"Error\0";
let addr = state.arena_alloc(msg.len() as u32)?;
mmu.write_initializer(addr, msg)
.map_err(|t| trap("strerror", t))?;
Ok(addr)
}
fn stub_strftime(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let dest = arg_dword(cpu, mmu, 0).map_err(|t| trap("strftime", t))?;
if dest != 0 {
mmu.store8(dest, 0).map_err(|t| trap("strftime", t))?;
}
Ok(0)
}
fn skip_ws(bytes: &[u8], mut i: usize) -> usize {
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
i += 1;
}
i
}
fn stub_strtoul(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let nptr = arg_dword(cpu, mmu, 0).map_err(|t| trap("strtoul", t))?;
let endptr = arg_dword(cpu, mmu, 1).map_err(|t| trap("strtoul", t))?;
let mut base = arg_dword(cpu, mmu, 2).map_err(|t| trap("strtoul", t))?;
let bytes = read_c(mmu, nptr, "strtoul")?;
let mut i = skip_ws(&bytes, 0);
let mut negate = false;
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
negate = bytes[i] == b'-';
i += 1;
}
if (base == 0 || base == 16)
&& i + 1 < bytes.len()
&& bytes[i] == b'0'
&& (bytes[i + 1] | 0x20) == b'x'
{
i += 2;
base = 16;
} else if base == 0 && i < bytes.len() && bytes[i] == b'0' {
base = 8;
} else if base == 0 {
base = 10;
}
let mut value: u32 = 0;
while i < bytes.len() {
let d = match bytes[i] {
c @ b'0'..=b'9' => u32::from(c - b'0'),
c @ b'a'..=b'z' => u32::from(c - b'a') + 10,
c @ b'A'..=b'Z' => u32::from(c - b'A') + 10,
_ => break,
};
if d >= base {
break;
}
value = value.wrapping_mul(base).wrapping_add(d);
i += 1;
}
if endptr != 0 {
mmu.store32(endptr, nptr.wrapping_add(i as u32))
.map_err(|t| trap("strtoul", t))?;
}
Ok(if negate { value.wrapping_neg() } else { value })
}
fn stub_atoi(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let nptr = arg_dword(cpu, mmu, 0).map_err(|t| trap("atoi", t))?;
let bytes = read_c(mmu, nptr, "atoi")?;
let mut i = skip_ws(&bytes, 0);
let mut negate = false;
if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
negate = bytes[i] == b'-';
i += 1;
}
let mut value: i32 = 0;
while i < bytes.len() && bytes[i].is_ascii_digit() {
value = value
.wrapping_mul(10)
.wrapping_add(i32::from(bytes[i] - b'0'));
i += 1;
}
Ok((if negate { -value } else { value }) as u32)
}
fn read_w(mmu: &Mmu, base: u32, stub: &'static str) -> Result<Vec<u16>, Win32Error> {
let mut out = Vec::new();
if base == 0 {
return Ok(out);
}
for i in 0..STR_SCAN_CAP {
let c = mmu
.load16(base.wrapping_add(i * 2))
.map_err(|t| trap(stub, t))?;
if c == 0 {
break;
}
out.push(c);
}
Ok(out)
}
fn stub_wcslen(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let s = arg_dword(cpu, mmu, 0).map_err(|t| trap("wcslen", t))?;
Ok(read_w(mmu, s, "wcslen")?.len() as u32)
}
fn stub_wcscoll(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let s1 = arg_dword(cpu, mmu, 0).map_err(|t| trap("wcscoll", t))?;
let s2 = arg_dword(cpu, mmu, 1).map_err(|t| trap("wcscoll", t))?;
let a = read_w(mmu, s1, "wcscoll")?;
let b = read_w(mmu, s2, "wcscoll")?;
Ok(match a.cmp(&b) {
std::cmp::Ordering::Less => (-1i32) as u32,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
})
}
fn stub_wcsxfrm(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let dest = arg_dword(cpu, mmu, 0).map_err(|t| trap("wcsxfrm", t))?;
let src = arg_dword(cpu, mmu, 1).map_err(|t| trap("wcsxfrm", t))?;
let n = arg_dword(cpu, mmu, 2).map_err(|t| trap("wcsxfrm", t))?;
let chars = read_w(mmu, src, "wcsxfrm")?;
let src_len = chars.len() as u32;
if dest != 0 && n > 0 {
let take = (n as usize - 1).min(chars.len());
for (i, c) in chars[..take].iter().enumerate() {
mmu.store16(dest.wrapping_add(i as u32 * 2), *c)
.map_err(|t| trap("wcsxfrm", t))?;
}
mmu.store16(dest.wrapping_add(take as u32 * 2), 0)
.map_err(|t| trap("wcsxfrm", t))?;
}
Ok(src_len)
}
fn stub_wcsftime(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let dest = arg_dword(cpu, mmu, 0).map_err(|t| trap("wcsftime", t))?;
if dest != 0 {
mmu.store16(dest, 0).map_err(|t| trap("wcsftime", t))?;
}
Ok(0)
}
fn stub_fputc(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
arg_dword(cpu, mmu, 0).map_err(|t| trap("fputc", t))
}
fn stub_fwrite(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
arg_dword(cpu, mmu, 2).map_err(|t| trap("fwrite", t))
}
fn stub_calloc(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let num = arg_dword(cpu, mmu, 0).map_err(|t| trap("calloc", t))?;
let size = arg_dword(cpu, mmu, 1).map_err(|t| trap("calloc", t))?;
let total = match num.checked_mul(size) {
Some(0) | None => return Ok(0),
Some(t) => t,
};
let addr = state.arena_alloc(total)?;
mmu.write_initializer(addr, &vec![0u8; total as usize])
.map_err(|t| trap("calloc", t))?;
Ok(addr)
}
fn stub_realloc(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let ptr = arg_dword(cpu, mmu, 0).map_err(|t| trap("realloc", t))?;
let size = arg_dword(cpu, mmu, 1).map_err(|t| trap("realloc", t))?;
if size == 0 {
let _ = state.heap.remove(&ptr);
return Ok(0);
}
let addr = state.arena_alloc(size)?;
mmu.write_initializer(addr, &vec![0u8; size as usize])
.map_err(|t| trap("realloc", t))?;
if ptr != 0 {
for i in 0..size {
match mmu.load8(ptr.wrapping_add(i)) {
Ok(b) => mmu
.store8(addr.wrapping_add(i), b)
.map_err(|t| trap("realloc", t))?,
Err(_) => break,
}
}
}
Ok(addr)
}
fn stub_localeconv(
_cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
const STRUCT_LEN: u32 = 48;
const DOT_OFF: u32 = 48; const EMPTY_OFF: u32 = 50; let block = state.arena_alloc(STRUCT_LEN + 3)?;
let mut buf = vec![0u8; (STRUCT_LEN + 3) as usize];
let dot = block + DOT_OFF;
let empty = block + EMPTY_OFF;
buf[0..4].copy_from_slice(&dot.to_le_bytes());
for slot in 1..10 {
let off = slot * 4;
buf[off..off + 4].copy_from_slice(&empty.to_le_bytes());
}
for b in buf.iter_mut().take(STRUCT_LEN as usize).skip(40) {
*b = 0x7F;
}
buf[DOT_OFF as usize] = b'.';
mmu.write_initializer(block, &buf)
.map_err(|t| trap("localeconv", t))?;
Ok(block)
}
fn stub_setlocale(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _category = arg_dword(cpu, mmu, 0).map_err(|t| trap("setlocale", t))?;
let locale = arg_dword(cpu, mmu, 1).map_err(|t| trap("setlocale", t))?;
if locale != 0 {
return Ok(locale);
}
let addr = state.arena_alloc(2)?;
mmu.write_initializer(addr, b"C\0")
.map_err(|t| trap("setlocale", t))?;
Ok(addr)
}
mod tests {
use super::*;
#[allow(unused_imports)]
use crate::emulator::isa_int::RET_SENTINEL;
use crate::emulator::mmu::Perm;
#[allow(unused_imports)]
use crate::emulator::regs::Reg32;
#[allow(dead_code)]
fn make_env() -> (Cpu, Mmu, Registry, HostState) {
let mut mmu = Mmu::new();
mmu.map(0x4000, 0x4000, Perm::R | Perm::W);
mmu.map(0x9000, 0x1000, Perm::R | Perm::W);
let mut cpu = Cpu::new();
cpu.regs.set_esp(0x9F00);
let mut registry = Registry::new();
registry.register_all();
let state = HostState::new(0x4000, 0x8000);
(cpu, mmu, registry, state)
}
#[allow(dead_code)]
fn call_cdecl(
cpu: &mut Cpu,
mmu: &mut Mmu,
registry: &Registry,
state: &mut HostState,
name: &str,
args: &[u32],
) -> Result<(), crate::Error> {
for a in args.iter().rev() {
cpu.push32(mmu, *a)?;
}
cpu.push32(mmu, 0xDEAD_DEAD)?;
cpu.regs.eip = registry.resolve("msvcrt.dll", name).unwrap();
crate::win32::dispatch_stub(cpu, mmu, registry, state)
}
#[test]
fn operator_new_zero_size_returns_null() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call_cdecl(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"??2@YAPAXI@Z",
&[0],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0);
}
#[test]
fn operator_new_nonzero_returns_heap_addr() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call_cdecl(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"??2@YAPAXI@Z",
&[64],
)
.unwrap();
let p = cpu.regs.get32(Reg32::Eax);
assert_ne!(p, 0);
assert!(state.heap.contains_key(&p));
}
#[test]
fn operator_delete_nullptr_is_noop() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call_cdecl(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"??3@YAXPAX@Z",
&[0],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0);
}
#[test]
fn malloc_then_free_round_trip() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call_cdecl(&mut cpu, &mut mmu, ®istry, &mut state, "malloc", &[128]).unwrap();
let p = cpu.regs.get32(Reg32::Eax);
assert_ne!(p, 0);
assert!(state.heap.contains_key(&p));
call_cdecl(&mut cpu, &mut mmu, ®istry, &mut state, "free", &[p]).unwrap();
assert!(!state.heap.contains_key(&p));
}
#[test]
fn initterm_zero_args_is_noop() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call_cdecl(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"_initterm",
&[0, 0],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0);
}
#[test]
fn initterm_walks_table_and_calls_non_null_entries() {
let mut mmu = Mmu::new();
mmu.map(0x4000, 0x4000, Perm::R | Perm::W);
mmu.map(0x8000, 0x1000, Perm::R | Perm::W);
mmu.map(0xA000, 0x1000, Perm::R | Perm::X);
mmu.write_initializer(0xA000, &[0xC3]).unwrap();
mmu.write_initializer(0x6000, &0u32.to_le_bytes()).unwrap();
mmu.write_initializer(0x6004, &0xA000u32.to_le_bytes())
.unwrap();
mmu.write_initializer(0x6008, &0u32.to_le_bytes()).unwrap();
let mut cpu = Cpu::new();
cpu.regs.set_esp(0x8F00);
let mut registry = Registry::new();
registry.register_all();
let mut state = HostState::new(0x4000, 0x8000);
let _ = RET_SENTINEL; for a in [0x600Cu32, 0x6000u32].iter() {
cpu.push32(&mut mmu, *a).unwrap();
}
cpu.push32(&mut mmu, 0xDEAD_DEAD).unwrap();
cpu.regs.eip = registry.resolve("msvcrt.dll", "_initterm").unwrap();
crate::win32::dispatch_stub(&mut cpu, &mut mmu, ®istry, &mut state).unwrap();
assert_eq!(cpu.regs.eip, 0xDEAD_DEAD);
}
}