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_data(dll, "_errno", 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);
}
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_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)
}
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);
}
}