use super::{arg_dword, HostState, Registry, StubFn, Win32Error};
use crate::emulator::{Cpu, Mmu};
pub fn register(registry: &mut Registry) {
registry.register("user32.dll", "BeginPaint", stub_begin_paint as StubFn, 2);
registry.register(
"user32.dll",
"DialogBoxParamA",
stub_dialog_box_param_a as StubFn,
5,
);
registry.register("user32.dll", "EndDialog", stub_end_dialog as StubFn, 2);
registry.register("user32.dll", "EndPaint", stub_end_paint as StubFn, 2);
registry.register("user32.dll", "GetDC", stub_get_dc as StubFn, 1);
registry.register(
"user32.dll",
"GetDlgItemInt",
stub_get_dlg_item_int as StubFn,
4,
);
registry.register(
"user32.dll",
"GetWindowLongA",
stub_get_window_long_a as StubFn,
2,
);
registry.register(
"user32.dll",
"GetWindowRect",
stub_get_window_rect as StubFn,
2,
);
registry.register("user32.dll", "LoadBitmapA", stub_load_bitmap_a as StubFn, 2);
registry.register("user32.dll", "LoadStringA", stub_load_string_a as StubFn, 4);
registry.register("user32.dll", "MessageBeep", stub_message_beep as StubFn, 1);
registry.register("user32.dll", "MessageBoxA", stub_message_box_a as StubFn, 4);
registry.register(
"user32.dll",
"PostMessageA",
stub_post_message_a as StubFn,
4,
);
registry.register("user32.dll", "ReleaseDC", stub_release_dc as StubFn, 2);
registry.register(
"user32.dll",
"SetDlgItemTextA",
stub_set_dlg_item_text_a as StubFn,
3,
);
registry.register("user32.dll", "wsprintfA", stub_wsprintf_a as StubFn, 0);
registry.register("user32.dll", "CheckDlgButton", stub_zero3 as StubFn, 3);
registry.register("user32.dll", "CheckRadioButton", stub_zero4 as StubFn, 4);
registry.register("user32.dll", "CreateDialogParamA", stub_zero5 as StubFn, 5);
registry.register("user32.dll", "DefWindowProcA", stub_zero4 as StubFn, 4);
registry.register(
"user32.dll",
"DestroyWindow",
stub_destroy_window as StubFn,
1,
);
registry.register("user32.dll", "EnableWindow", stub_zero2 as StubFn, 2);
registry.register(
"user32.dll",
"GetClientRect",
stub_get_client_rect as StubFn,
2,
);
registry.register("user32.dll", "GetDesktopWindow", stub_zero0 as StubFn, 0);
registry.register("user32.dll", "GetDlgCtrlID", stub_zero1 as StubFn, 1);
registry.register("user32.dll", "GetDlgItem", stub_zero2 as StubFn, 2);
registry.register("user32.dll", "GetDlgItemTextA", stub_zero4 as StubFn, 4);
registry.register("user32.dll", "GetFocus", stub_zero0 as StubFn, 0);
registry.register("user32.dll", "InvalidateRect", stub_zero3 as StubFn, 3);
registry.register("user32.dll", "IsDlgButtonChecked", stub_zero2 as StubFn, 2);
registry.register("user32.dll", "IsRectEmpty", stub_is_rect_empty as StubFn, 1);
registry.register("user32.dll", "LoadStringW", stub_zero4 as StubFn, 4);
registry.register("user32.dll", "MapWindowPoints", stub_zero4 as StubFn, 4);
registry.register("user32.dll", "MoveWindow", stub_one6 as StubFn, 6);
registry.register("user32.dll", "OffsetRect", stub_offset_rect as StubFn, 3);
registry.register("user32.dll", "SendMessageA", stub_zero4 as StubFn, 4);
registry.register("user32.dll", "SetDlgItemInt", stub_zero4 as StubFn, 4);
registry.register("user32.dll", "SetFocus", stub_zero1 as StubFn, 1);
registry.register("user32.dll", "SetWindowLongA", stub_zero3 as StubFn, 3);
registry.register("user32.dll", "SetWindowPos", stub_zero7 as StubFn, 7);
registry.register("user32.dll", "SetWindowTextA", stub_zero2 as StubFn, 2);
registry.register("user32.dll", "ShowWindow", stub_zero2 as StubFn, 2);
registry.register("user32.dll", "WinHelpA", stub_zero4 as StubFn, 4);
registry.register("user32.dll", "wvsprintfA", stub_zero0 as StubFn, 0);
registry.register("user32.dll", "ScreenToClient", stub_one2 as StubFn, 2);
registry.register("user32.dll", "SendDlgItemMessageA", stub_zero5 as StubFn, 5);
registry.register("user32.dll", "LoadIconA", stub_one2 as StubFn, 2);
registry.register("user32.dll", "PostThreadMessageA", stub_one4 as StubFn, 4);
registry.register("user32.dll", "wsprintfW", stub_wsprintf_w as StubFn, 0);
registry.register("user32.dll", "GetScrollPos", stub_zero2 as StubFn, 2);
registry.register("user32.dll", "SetScrollPos", stub_zero4 as StubFn, 4);
registry.register("user32.dll", "SetScrollRange", stub_zero5 as StubFn, 5);
registry.register(
"user32.dll",
"RegisterClassExA",
stub_register_class_ex_a as StubFn,
1,
);
registry.register(
"user32.dll",
"UnregisterClassA",
stub_unregister_class_a as StubFn,
2,
);
registry.register(
"user32.dll",
"MapDialogRect",
stub_map_dialog_rect as StubFn,
2,
);
registry.register("user32.dll", "SetTimer", stub_set_timer as StubFn, 4);
registry.register("user32.dll", "KillTimer", stub_kill_timer as StubFn, 2);
registry.register(
"user32.dll",
"CreateWindowExA",
stub_create_window_ex_a as StubFn,
12,
);
registry.register("user32.dll", "UpdateWindow", stub_one1 as StubFn, 1);
registry.register("user32.dll", "IsWindow", stub_is_window as StubFn, 1);
registry.register("user32.dll", "GetMessageA", stub_get_message_a as StubFn, 4);
registry.register("user32.dll", "DispatchMessageA", stub_zero1 as StubFn, 1);
registry.register("user32.dll", "TranslateMessage", stub_zero1 as StubFn, 1);
registry.register("user32.dll", "PeekMessageA", stub_zero5 as StubFn, 5);
registry.register("user32.dll", "PostQuitMessage", stub_zero1 as StubFn, 1);
}
fn stub_register_class_ex_a(
_: &mut Cpu,
_: &mut Mmu,
_: &mut HostState,
_: &Registry,
) -> Result<u32, Win32Error> {
Ok(0xC001)
}
fn stub_unregister_class_a(
_: &mut Cpu,
_: &mut Mmu,
_: &mut HostState,
_: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_map_dialog_rect(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hdlg = arg_dword(cpu, mmu, 0)
.map_err(|t| crate::win32::trap_to_win32_local("MapDialogRect", t))?;
let _lprect = arg_dword(cpu, mmu, 1)
.map_err(|t| crate::win32::trap_to_win32_local("MapDialogRect", t))?;
Ok(1)
}
fn stub_set_timer(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hwnd =
arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("SetTimer", t))?;
let nid_event =
arg_dword(cpu, mmu, 1).map_err(|t| crate::win32::trap_to_win32_local("SetTimer", t))?;
let _uelapse =
arg_dword(cpu, mmu, 2).map_err(|t| crate::win32::trap_to_win32_local("SetTimer", t))?;
let _timer_proc =
arg_dword(cpu, mmu, 3).map_err(|t| crate::win32::trap_to_win32_local("SetTimer", t))?;
if nid_event != 0 {
Ok(nid_event)
} else {
Ok(1)
}
}
fn stub_kill_timer(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hwnd =
arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("KillTimer", t))?;
let _uid_event =
arg_dword(cpu, mmu, 1).map_err(|t| crate::win32::trap_to_win32_local("KillTimer", t))?;
Ok(1)
}
fn stub_zero0(
_: &mut Cpu,
_: &mut Mmu,
_: &mut HostState,
_: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_zero1(
_: &mut Cpu,
_: &mut Mmu,
_: &mut HostState,
_: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_zero2(
_: &mut Cpu,
_: &mut Mmu,
_: &mut HostState,
_: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_zero3(
_: &mut Cpu,
_: &mut Mmu,
_: &mut HostState,
_: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_zero4(
_: &mut Cpu,
_: &mut Mmu,
_: &mut HostState,
_: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_zero5(
_: &mut Cpu,
_: &mut Mmu,
_: &mut HostState,
_: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_zero7(
_: &mut Cpu,
_: &mut Mmu,
_: &mut HostState,
_: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_one1(_: &mut Cpu, _: &mut Mmu, _: &mut HostState, _: &Registry) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_one2(_: &mut Cpu, _: &mut Mmu, _: &mut HostState, _: &Registry) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_one4(_: &mut Cpu, _: &mut Mmu, _: &mut HostState, _: &Registry) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_one6(_: &mut Cpu, _: &mut Mmu, _: &mut HostState, _: &Registry) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_wsprintf_w(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let out =
arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("wsprintfW", t))?;
if out != 0 {
mmu.store16(out, 0)
.map_err(|t| crate::win32::trap_to_win32_local("wsprintfW", t))?;
}
Ok(0)
}
pub const HWND_BASE: u32 = 0xCAFE_0000;
fn stub_create_window_ex_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let hwnd = HWND_BASE.wrapping_add(state.next_hwnd_index);
state.next_hwnd_index = state.next_hwnd_index.wrapping_add(1);
state.hwnd_registry.insert(hwnd);
Ok(hwnd)
}
fn stub_destroy_window(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let hwnd = arg_dword(cpu, mmu, 0)
.map_err(|t| crate::win32::trap_to_win32_local("DestroyWindow", t))?;
state.hwnd_registry.remove(&hwnd);
Ok(1)
}
fn stub_is_window(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let hwnd =
arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("IsWindow", t))?;
Ok(if hwnd != 0 && state.hwnd_registry.contains(&hwnd) {
1
} else {
0
})
}
fn stub_get_message_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let lp =
arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("GetMessageA", t))?;
if lp != 0 {
for i in 0..28u32 {
mmu.store8(lp + i, 0)
.map_err(|t| crate::win32::trap_to_win32_local("GetMessageA", t))?;
}
}
Ok(0)
}
fn stub_get_client_rect(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hwnd = arg_dword(cpu, mmu, 0)
.map_err(|t| crate::win32::trap_to_win32_local("GetClientRect", t))?;
let prect = arg_dword(cpu, mmu, 1)
.map_err(|t| crate::win32::trap_to_win32_local("GetClientRect", t))?;
if prect != 0 {
for i in 0..16u32 {
let _ = mmu.store8(prect + i, 0);
}
}
Ok(1)
}
fn stub_is_rect_empty(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p =
arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("IsRectEmpty", t))?;
if p == 0 {
return Ok(1);
}
let l = mmu
.load32(p)
.map_err(|t| crate::win32::trap_to_win32_local("IsRectEmpty", t))? as i32;
let t = mmu
.load32(p + 4)
.map_err(|t| crate::win32::trap_to_win32_local("IsRectEmpty", t))? as i32;
let r = mmu
.load32(p + 8)
.map_err(|t| crate::win32::trap_to_win32_local("IsRectEmpty", t))? as i32;
let b = mmu
.load32(p + 12)
.map_err(|t| crate::win32::trap_to_win32_local("IsRectEmpty", t))? as i32;
Ok(if r <= l || b <= t { 1 } else { 0 })
}
fn stub_offset_rect(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p =
arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("OffsetRect", t))?;
let dx = arg_dword(cpu, mmu, 1)
.map_err(|t| crate::win32::trap_to_win32_local("OffsetRect", t))? as i32;
let dy = arg_dword(cpu, mmu, 2)
.map_err(|t| crate::win32::trap_to_win32_local("OffsetRect", t))? as i32;
if p == 0 {
return Ok(0);
}
let mut bump = |off: u32, delta: i32| -> Result<(), Win32Error> {
let v = mmu
.load32(p + off)
.map_err(|t| crate::win32::trap_to_win32_local("OffsetRect", t))?
as i32;
mmu.store32(p + off, v.wrapping_add(delta) as u32)
.map_err(|t| crate::win32::trap_to_win32_local("OffsetRect", t))
};
bump(0, dx)?;
bump(4, dy)?;
bump(8, dx)?;
bump(12, dy)?;
Ok(1)
}
const PAINTSTRUCT_SIZE: u32 = 64;
fn stub_begin_paint(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hwnd =
arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("BeginPaint", t))?;
let p =
arg_dword(cpu, mmu, 1).map_err(|t| crate::win32::trap_to_win32_local("BeginPaint", t))?;
if p != 0 {
for i in 0..PAINTSTRUCT_SIZE {
mmu.store8(p + i, 0)
.map_err(|t| crate::win32::trap_to_win32_local("BeginPaint", t))?;
}
}
Ok(super::gdi32::SENTINEL_HDC)
}
fn stub_end_paint(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
const IDOK: u32 = 1;
const IDCANCEL: u32 = 2;
fn stub_dialog_box_param_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(IDCANCEL)
}
fn stub_end_dialog(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_get_dc(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
state
.gdi_hdcs
.get_or_insert_with(std::collections::BTreeSet::new)
.insert(super::gdi32::SENTINEL_HDC);
Ok(super::gdi32::SENTINEL_HDC)
}
fn stub_release_dc(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_get_dlg_item_int(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hdlg = arg_dword(cpu, mmu, 0)
.map_err(|t| crate::win32::trap_to_win32_local("GetDlgItemInt", t))?;
let _id = arg_dword(cpu, mmu, 1)
.map_err(|t| crate::win32::trap_to_win32_local("GetDlgItemInt", t))?;
let p_translated = arg_dword(cpu, mmu, 2)
.map_err(|t| crate::win32::trap_to_win32_local("GetDlgItemInt", t))?;
let _signed = arg_dword(cpu, mmu, 3)
.map_err(|t| crate::win32::trap_to_win32_local("GetDlgItemInt", t))?;
if p_translated != 0 {
mmu.store32(p_translated, 0)
.map_err(|t| crate::win32::trap_to_win32_local("GetDlgItemInt", t))?;
}
Ok(0)
}
fn stub_get_window_long_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_get_window_rect(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hwnd = arg_dword(cpu, mmu, 0)
.map_err(|t| crate::win32::trap_to_win32_local("GetWindowRect", t))?;
let p = arg_dword(cpu, mmu, 1)
.map_err(|t| crate::win32::trap_to_win32_local("GetWindowRect", t))?;
if p != 0 {
mmu.store32(p, 0)
.map_err(|t| crate::win32::trap_to_win32_local("GetWindowRect", t))?;
mmu.store32(p + 4, 0)
.map_err(|t| crate::win32::trap_to_win32_local("GetWindowRect", t))?;
mmu.store32(p + 8, 640)
.map_err(|t| crate::win32::trap_to_win32_local("GetWindowRect", t))?;
mmu.store32(p + 12, 480)
.map_err(|t| crate::win32::trap_to_win32_local("GetWindowRect", t))?;
}
Ok(1)
}
fn stub_load_bitmap_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_load_string_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_message_beep(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_message_box_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _hwnd =
arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("MessageBoxA", t))?;
let p_text =
arg_dword(cpu, mmu, 1).map_err(|t| crate::win32::trap_to_win32_local("MessageBoxA", t))?;
let p_caption =
arg_dword(cpu, mmu, 2).map_err(|t| crate::win32::trap_to_win32_local("MessageBoxA", t))?;
let _utype =
arg_dword(cpu, mmu, 3).map_err(|t| crate::win32::trap_to_win32_local("MessageBoxA", t))?;
let text = if p_text != 0 {
super::read_cstr_local(mmu, p_text, 4096)?
} else {
String::new()
};
let caption = if p_caption != 0 {
super::read_cstr_local(mmu, p_caption, 4096)?
} else {
String::new()
};
eprintln!("[oxideav-vfw MessageBoxA] {caption}: {text}");
state.message_box_log.push(format!("{caption}: {text}"));
Ok(IDOK)
}
fn stub_post_message_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_set_dlg_item_text_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_wsprintf_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let lp_out =
arg_dword(cpu, mmu, 0).map_err(|t| crate::win32::trap_to_win32_local("wsprintfA", t))?;
let lp_fmt =
arg_dword(cpu, mmu, 1).map_err(|t| crate::win32::trap_to_win32_local("wsprintfA", t))?;
if lp_out == 0 || lp_fmt == 0 {
return Ok(0);
}
let fmt = super::read_cstr_local(mmu, lp_fmt, 4096)?;
let mut out = Vec::<u8>::with_capacity(fmt.len() + 32);
let mut iter = fmt.chars().peekable();
let mut next_arg: u32 = 2; let pop_dword = |cpu: &Cpu, mmu: &Mmu, n: u32| -> Result<u32, Win32Error> {
arg_dword(cpu, mmu, n).map_err(|t| crate::win32::trap_to_win32_local("wsprintfA", t))
};
while let Some(c) = iter.next() {
if c != '%' {
let mut buf = [0u8; 4];
let s = c.encode_utf8(&mut buf);
out.extend_from_slice(s.as_bytes());
continue;
}
let mut spec = '\0';
loop {
match iter.next() {
None => break,
Some(ch) => {
if matches!(ch, '#' | '-' | '+' | ' ' | '0' | '.') || ch.is_ascii_digit() {
continue;
}
if ch == 'l' || ch == 'h' || ch == 'I' {
continue;
}
spec = ch;
break;
}
}
}
match spec {
'%' => out.push(b'%'),
'c' => {
let v = pop_dword(cpu, mmu, next_arg)?;
next_arg += 1;
out.push(v as u8);
}
's' => {
let p = pop_dword(cpu, mmu, next_arg)?;
next_arg += 1;
if p == 0 {
out.extend_from_slice(b"(null)");
} else {
let s = super::read_cstr_local(mmu, p, 4096)?;
out.extend_from_slice(s.as_bytes());
}
}
'd' | 'i' => {
let v = pop_dword(cpu, mmu, next_arg)?;
next_arg += 1;
out.extend_from_slice((v as i32).to_string().as_bytes());
}
'u' => {
let v = pop_dword(cpu, mmu, next_arg)?;
next_arg += 1;
out.extend_from_slice(v.to_string().as_bytes());
}
'x' => {
let v = pop_dword(cpu, mmu, next_arg)?;
next_arg += 1;
out.extend_from_slice(format!("{v:x}").as_bytes());
}
'X' => {
let v = pop_dword(cpu, mmu, next_arg)?;
next_arg += 1;
out.extend_from_slice(format!("{v:X}").as_bytes());
}
'p' => {
let v = pop_dword(cpu, mmu, next_arg)?;
next_arg += 1;
out.extend_from_slice(format!("{v:08X}").as_bytes());
}
'\0' => break, _ => {
out.push(b'%');
let mut buf = [0u8; 4];
let s = spec.encode_utf8(&mut buf);
out.extend_from_slice(s.as_bytes());
}
}
}
for (i, b) in out.iter().enumerate() {
mmu.store8(lp_out + i as u32, *b)
.map_err(|t| crate::win32::trap_to_win32_local("wsprintfA", t))?;
}
mmu.store8(lp_out + out.len() as u32, 0)
.map_err(|t| crate::win32::trap_to_win32_local("wsprintfA", t))?;
Ok(out.len() as u32)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::emulator::mmu::Perm;
use crate::emulator::regs::Reg32;
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)
}
fn call(
cpu: &mut Cpu,
mmu: &mut Mmu,
registry: &Registry,
state: &mut HostState,
dll: &str,
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(dll, name).expect("registered");
crate::win32::dispatch_stub(cpu, mmu, registry, state)
}
#[test]
fn dialog_box_param_a_returns_idcancel() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"user32.dll",
"DialogBoxParamA",
&[0, 0, 0, 0, 0],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), IDCANCEL);
}
#[test]
fn message_box_a_logs_and_returns_idok() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
mmu.write(0x4000, b"hello\0").unwrap();
mmu.write(0x4010, b"title\0").unwrap();
state.heap_cursor = 0x4020;
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"user32.dll",
"MessageBoxA",
&[0, 0x4000, 0x4010, 0],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), IDOK);
assert!(state.message_box_log.last().unwrap().contains("hello"));
assert!(state.message_box_log.last().unwrap().contains("title"));
}
#[test]
fn get_window_rect_returns_640x480() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
let p = 0x4040;
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"user32.dll",
"GetWindowRect",
&[0, p],
)
.unwrap();
assert_eq!(mmu.load32(p).unwrap(), 0);
assert_eq!(mmu.load32(p + 4).unwrap(), 0);
assert_eq!(mmu.load32(p + 8).unwrap(), 640);
assert_eq!(mmu.load32(p + 12).unwrap(), 480);
}
#[test]
fn wsprintf_a_renders_int_and_str() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
mmu.write(0x4000, b"n=%d s=%s\0").unwrap();
mmu.write(0x4020, b"abc\0").unwrap();
let args = [0x4080u32, 0x4000, 42, 0x4020];
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"user32.dll",
"wsprintfA",
&args,
)
.unwrap();
let mut buf = Vec::new();
for i in 0..32u32 {
let b = mmu.load8(0x4080 + i).unwrap();
if b == 0 {
break;
}
buf.push(b);
}
let s = String::from_utf8(buf).unwrap();
assert_eq!(s, "n=42 s=abc");
}
#[test]
fn wsprintf_a_handles_hex_and_percent() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
mmu.write(0x4000, b"v=%X %% %x\0").unwrap();
let args = [0x4080u32, 0x4000, 0xCAFEu32, 0xDEADu32];
call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"user32.dll",
"wsprintfA",
&args,
)
.unwrap();
let mut buf = Vec::new();
for i in 0..32u32 {
let b = mmu.load8(0x4080 + i).unwrap();
if b == 0 {
break;
}
buf.push(b);
}
let s = String::from_utf8(buf).unwrap();
assert_eq!(s, "v=CAFE % dead");
}
}