use super::{arg_dword, HostState, Registry, StubFn, Win32Error};
use crate::emulator::mmu::{Perm, PAGE_SIZE};
use crate::emulator::{Cpu, Mmu};
pub fn register(registry: &mut Registry) {
registry.register(
"kernel32.dll",
"GetProcessHeap",
stub_get_process_heap as StubFn,
0,
);
registry.register("kernel32.dll", "HeapAlloc", stub_heap_alloc as StubFn, 3);
registry.register("kernel32.dll", "HeapFree", stub_heap_free as StubFn, 3);
registry.register(
"kernel32.dll",
"HeapReAlloc",
stub_heap_realloc as StubFn,
4,
);
registry.register("kernel32.dll", "HeapSize", stub_heap_size as StubFn, 3);
registry.register("kernel32.dll", "LocalAlloc", stub_local_alloc as StubFn, 2);
registry.register("kernel32.dll", "LocalFree", stub_local_free as StubFn, 1);
registry.register(
"kernel32.dll",
"OutputDebugStringA",
stub_output_debug_string_a as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetTickCount",
stub_get_tick_count as StubFn,
0,
);
registry.register(
"kernel32.dll",
"InterlockedIncrement",
stub_interlocked_increment as StubFn,
1,
);
registry.register(
"kernel32.dll",
"InterlockedDecrement",
stub_interlocked_decrement as StubFn,
1,
);
registry.register(
"kernel32.dll",
"LoadLibraryA",
stub_load_library_a as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetProcAddress",
stub_get_proc_address as StubFn,
2,
);
registry.register(
"kernel32.dll",
"ExitProcess",
stub_exit_process as StubFn,
1,
);
registry.register("kernel32.dll", "GetACP", stub_get_acp as StubFn, 0);
registry.register("kernel32.dll", "GetOEMCP", stub_get_oem_cp as StubFn, 0);
registry.register("kernel32.dll", "GetCPInfo", stub_get_cp_info as StubFn, 2);
registry.register(
"kernel32.dll",
"GetCommandLineA",
stub_get_command_line_a as StubFn,
0,
);
registry.register(
"kernel32.dll",
"GetEnvironmentStrings",
stub_get_environment_strings as StubFn,
0,
);
registry.register(
"kernel32.dll",
"GetFileType",
stub_get_file_type as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetLastError",
stub_get_last_error as StubFn,
0,
);
registry.register(
"kernel32.dll",
"SetLastError",
stub_set_last_error as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetModuleFileNameA",
stub_get_module_file_name_a as StubFn,
3,
);
registry.register(
"kernel32.dll",
"GetModuleHandleA",
stub_get_module_handle_a as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetStartupInfoA",
stub_get_startup_info_a as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetStdHandle",
stub_get_std_handle as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetSystemInfo",
stub_get_system_info as StubFn,
1,
);
registry.register("kernel32.dll", "GetVersion", stub_get_version as StubFn, 0);
registry.register(
"kernel32.dll",
"GlobalAlloc",
stub_global_alloc as StubFn,
2,
);
registry.register("kernel32.dll", "GlobalFree", stub_global_free as StubFn, 1);
registry.register("kernel32.dll", "GlobalLock", stub_global_lock as StubFn, 1);
registry.register(
"kernel32.dll",
"GlobalUnlock",
stub_global_unlock as StubFn,
1,
);
registry.register(
"kernel32.dll",
"MultiByteToWideChar",
stub_multi_byte_to_wide_char as StubFn,
6,
);
registry.register(
"kernel32.dll",
"WideCharToMultiByte",
stub_wide_char_to_multi_byte as StubFn,
8,
);
registry.register("kernel32.dll", "RtlUnwind", stub_rtl_unwind as StubFn, 4);
registry.register(
"kernel32.dll",
"VirtualAlloc",
stub_virtual_alloc as StubFn,
4,
);
registry.register(
"kernel32.dll",
"VirtualFree",
stub_virtual_free as StubFn,
3,
);
registry.register("kernel32.dll", "WriteFile", stub_write_file as StubFn, 5);
registry.register(
"kernel32.dll",
"CloseHandle",
stub_close_handle as StubFn,
1,
);
registry.register(
"kernel32.dll",
"CreateFileMappingA",
stub_create_file_mapping_a as StubFn,
6,
);
registry.register(
"kernel32.dll",
"CreateSemaphoreA",
stub_create_semaphore_a as StubFn,
4,
);
registry.register(
"kernel32.dll",
"DeleteCriticalSection",
stub_delete_critical_section as StubFn,
1,
);
registry.register(
"kernel32.dll",
"DisableThreadLibraryCalls",
stub_disable_thread_library_calls as StubFn,
1,
);
registry.register(
"kernel32.dll",
"EnterCriticalSection",
stub_enter_critical_section as StubFn,
1,
);
registry.register(
"kernel32.dll",
"LeaveCriticalSection",
stub_leave_critical_section as StubFn,
1,
);
registry.register(
"kernel32.dll",
"InitializeCriticalSection",
stub_initialize_critical_section as StubFn,
1,
);
registry.register(
"kernel32.dll",
"FindResourceA",
stub_find_resource_a as StubFn,
3,
);
registry.register(
"kernel32.dll",
"FlushFileBuffers",
stub_flush_file_buffers as StubFn,
1,
);
registry.register(
"kernel32.dll",
"FreeEnvironmentStringsA",
stub_free_environment_strings as StubFn,
1,
);
registry.register(
"kernel32.dll",
"FreeEnvironmentStringsW",
stub_free_environment_strings as StubFn,
1,
);
registry.register(
"kernel32.dll",
"FreeLibrary",
stub_free_library as StubFn,
1,
);
registry.register(
"kernel32.dll",
"FreeResource",
stub_free_resource as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetCurrentProcess",
stub_get_current_process as StubFn,
0,
);
registry.register(
"kernel32.dll",
"GetCurrentThreadId",
stub_get_current_thread_id as StubFn,
0,
);
registry.register(
"kernel32.dll",
"GetEnvironmentStringsW",
stub_get_environment_strings_w as StubFn,
0,
);
registry.register(
"kernel32.dll",
"GetLocaleInfoA",
stub_get_locale_info_a as StubFn,
4,
);
registry.register(
"kernel32.dll",
"GetLocaleInfoW",
stub_get_locale_info_a as StubFn,
4,
);
registry.register(
"kernel32.dll",
"GetShortPathNameA",
stub_get_short_path_name_a as StubFn,
3,
);
registry.register(
"kernel32.dll",
"GetStringTypeA",
stub_get_string_type as StubFn,
5,
);
registry.register(
"kernel32.dll",
"GetStringTypeW",
stub_get_string_type as StubFn,
4,
);
registry.register(
"kernel32.dll",
"GetSystemDirectoryA",
stub_get_system_directory_a as StubFn,
2,
);
registry.register(
"kernel32.dll",
"GetVersionExA",
stub_get_version_ex_a as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GlobalHandle",
stub_global_handle as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GlobalReAlloc",
stub_global_realloc as StubFn,
3,
);
registry.register("kernel32.dll", "HeapCreate", stub_heap_create as StubFn, 3);
registry.register(
"kernel32.dll",
"HeapDestroy",
stub_heap_destroy as StubFn,
1,
);
registry.register("kernel32.dll", "IsBadCodePtr", stub_is_bad_ptr as StubFn, 1);
registry.register("kernel32.dll", "IsBadReadPtr", stub_is_bad_ptr as StubFn, 2);
registry.register(
"kernel32.dll",
"IsBadWritePtr",
stub_is_bad_ptr as StubFn,
2,
);
registry.register(
"kernel32.dll",
"LCMapStringA",
stub_lc_map_string as StubFn,
6,
);
registry.register(
"kernel32.dll",
"LCMapStringW",
stub_lc_map_string as StubFn,
6,
);
registry.register(
"kernel32.dll",
"LoadResource",
stub_load_resource as StubFn,
2,
);
registry.register(
"kernel32.dll",
"LocalHandle",
stub_local_handle as StubFn,
1,
);
registry.register("kernel32.dll", "LocalLock", stub_local_lock as StubFn, 1);
registry.register(
"kernel32.dll",
"LocalUnlock",
stub_local_unlock as StubFn,
1,
);
registry.register(
"kernel32.dll",
"LockResource",
stub_lock_resource as StubFn,
1,
);
registry.register(
"kernel32.dll",
"SizeofResource",
stub_sizeof_resource as StubFn,
2,
);
registry.register(
"kernel32.dll",
"MapViewOfFile",
stub_map_view_of_file as StubFn,
5,
);
registry.register(
"kernel32.dll",
"OpenFileMappingA",
stub_open_file_mapping_a as StubFn,
3,
);
registry.register(
"kernel32.dll",
"QueryPerformanceCounter",
stub_query_performance_counter as StubFn,
1,
);
registry.register(
"kernel32.dll",
"QueryPerformanceFrequency",
stub_query_performance_frequency as StubFn,
1,
);
registry.register(
"kernel32.dll",
"RaiseException",
stub_raise_exception as StubFn,
4,
);
registry.register(
"kernel32.dll",
"ReleaseSemaphore",
stub_release_semaphore as StubFn,
3,
);
registry.register(
"kernel32.dll",
"SetFilePointer",
stub_set_file_pointer as StubFn,
4,
);
registry.register(
"kernel32.dll",
"SetHandleCount",
stub_set_handle_count as StubFn,
1,
);
registry.register(
"kernel32.dll",
"SetStdHandle",
stub_set_std_handle as StubFn,
2,
);
registry.register(
"kernel32.dll",
"SetUnhandledExceptionFilter",
stub_set_unhandled_exception_filter as StubFn,
1,
);
registry.register("kernel32.dll", "Sleep", stub_sleep as StubFn, 1);
registry.register(
"kernel32.dll",
"TerminateProcess",
stub_terminate_process as StubFn,
2,
);
registry.register("kernel32.dll", "TlsAlloc", stub_tls_alloc as StubFn, 0);
registry.register("kernel32.dll", "TlsFree", stub_tls_free as StubFn, 1);
registry.register(
"kernel32.dll",
"TlsGetValue",
stub_tls_get_value as StubFn,
1,
);
registry.register(
"kernel32.dll",
"TlsSetValue",
stub_tls_set_value as StubFn,
2,
);
registry.register(
"kernel32.dll",
"UnmapViewOfFile",
stub_unmap_view_of_file as StubFn,
1,
);
registry.register(
"kernel32.dll",
"WaitForSingleObject",
stub_wait_for_single_object as StubFn,
2,
);
registry.register(
"kernel32.dll",
"WritePrivateProfileStringA",
stub_write_private_profile_string_a as StubFn,
4,
);
registry.register("kernel32.dll", "lstrlenA", stub_lstrlen_a as StubFn, 1);
registry.register(
"kernel32.dll",
"CreateEventA",
stub_create_event_a as StubFn,
4,
);
registry.register(
"kernel32.dll",
"CreateThread",
stub_create_thread as StubFn,
6,
);
registry.register("kernel32.dll", "SetEvent", stub_set_event as StubFn, 1);
registry.register(
"kernel32.dll",
"SetThreadPriority",
stub_set_thread_priority as StubFn,
2,
);
registry.register(
"kernel32.dll",
"ResumeThread",
stub_resume_thread as StubFn,
1,
);
registry.register("kernel32.dll", "MulDiv", stub_muldiv as StubFn, 3);
registry.register(
"kernel32.dll",
"GetProfileIntA",
stub_get_profile_int_a as StubFn,
3,
);
registry.register(
"kernel32.dll",
"GetCurrentProcessId",
stub_get_current_process_id as StubFn,
0,
);
registry.register(
"kernel32.dll",
"GetSystemTimeAsFileTime",
stub_get_system_time_as_file_time as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetCurrentThread",
stub_get_current_thread as StubFn,
0,
);
registry.register(
"kernel32.dll",
"InterlockedExchange",
stub_interlocked_exchange as StubFn,
2,
);
registry.register(
"kernel32.dll",
"InterlockedCompareExchange",
stub_interlocked_compare_exchange as StubFn,
3,
);
registry.register(
"kernel32.dll",
"UnhandledExceptionFilter",
stub_unhandled_exception_filter as StubFn,
1,
);
registry.register(
"kernel32.dll",
"SetErrorMode",
stub_set_error_mode as StubFn,
1,
);
registry.register("kernel32.dll", "ResetEvent", stub_reset_event as StubFn, 1);
registry.register(
"kernel32.dll",
"WaitForMultipleObjects",
stub_wait_for_multiple_objects as StubFn,
4,
);
registry.register(
"kernel32.dll",
"CreateEventW",
stub_create_event_w as StubFn,
4,
);
registry.register(
"kernel32.dll",
"CreateSemaphoreW",
stub_create_semaphore_w as StubFn,
4,
);
registry.register(
"kernel32.dll",
"GetLocalTime",
stub_get_local_time as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetModuleHandleW",
stub_get_module_handle_w as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetPrivateProfileIntA",
stub_get_private_profile_int_a as StubFn,
4,
);
registry.register(
"kernel32.dll",
"DelayLoadFailureHook",
stub_delay_load_failure_hook as StubFn,
2,
);
registry.register(
"kernel32.dll",
"GetVersionExW",
stub_get_version_ex_w as StubFn,
1,
);
registry.register(
"kernel32.dll",
"SignalObjectAndWait",
stub_signal_object_and_wait as StubFn,
4,
);
registry.register(
"kernel32.dll",
"InitializeCriticalSectionAndSpinCount",
stub_init_cs_spin as StubFn,
2,
);
registry.register(
"kernel32.dll",
"IsDebuggerPresent",
stub_is_debugger_present as StubFn,
0,
);
registry.register(
"kernel32.dll",
"VirtualProtect",
stub_virtual_protect as StubFn,
4,
);
registry.register(
"kernel32.dll",
"InterlockedExchangeAdd",
stub_interlocked_exchange_add as StubFn,
2,
);
registry.register(
"kernel32.dll",
"GetComputerNameA",
stub_get_computer_name_a as StubFn,
2,
);
registry.register(
"kernel32.dll",
"GetEnvironmentVariableW",
stub_get_environment_variable_w as StubFn,
3,
);
registry.register(
"kernel32.dll",
"GetProcessAffinityMask",
stub_get_process_affinity_mask as StubFn,
3,
);
registry.register(
"kernel32.dll",
"GetThreadPriority",
stub_get_thread_priority as StubFn,
1,
);
registry.register(
"kernel32.dll",
"SetThreadAffinityMask",
stub_set_thread_affinity_mask as StubFn,
2,
);
registry.register(
"kernel32.dll",
"LoadLibraryW",
stub_load_library_w as StubFn,
1,
);
registry.register("kernel32.dll", "ReadFile", stub_read_file as StubFn, 5);
registry.register("kernel32.dll", "lstrcatA", stub_lstrcat_a as StubFn, 2);
registry.register("kernel32.dll", "lstrcpyA", stub_lstrcpy_a as StubFn, 2);
registry.register("kernel32.dll", "lstrcmpiA", stub_lstrcmpi_a as StubFn, 2);
registry.register(
"kernel32.dll",
"CompareStringA",
stub_compare_string_a as StubFn,
6,
);
registry.register(
"kernel32.dll",
"CompareStringW",
stub_compare_string_w as StubFn,
6,
);
registry.register(
"kernel32.dll",
"FatalAppExitA",
stub_fatal_app_exit_a as StubFn,
2,
);
registry.register(
"kernel32.dll",
"GetSystemTime",
stub_get_system_time as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetTimeZoneInformation",
stub_get_time_zone_information as StubFn,
1,
);
registry.register(
"kernel32.dll",
"LoadLibraryExA",
stub_load_library_ex_a as StubFn,
3,
);
registry.register(
"kernel32.dll",
"SetEnvironmentVariableA",
stub_returns_true as StubFn,
2,
);
registry.register(
"kernel32.dll",
"AllocConsole",
stub_returns_true as StubFn,
0,
);
registry.register(
"kernel32.dll",
"SetConsoleScreenBufferSize",
stub_returns_true as StubFn,
2,
);
registry.register(
"kernel32.dll",
"SetConsoleCtrlHandler",
stub_returns_true as StubFn,
2,
);
registry.register(
"kernel32.dll",
"WriteConsoleA",
stub_write_console_a as StubFn,
5,
);
registry.register(
"kernel32.dll",
"EncodePointer",
stub_identity_pointer as StubFn,
1,
);
registry.register(
"kernel32.dll",
"DecodePointer",
stub_identity_pointer as StubFn,
1,
);
registry.register(
"kernel32.dll",
"EnumSystemLocalesA",
stub_returns_true as StubFn,
2,
);
registry.register(
"kernel32.dll",
"GetModuleFileNameW",
stub_get_module_file_name_w as StubFn,
3,
);
registry.register(
"kernel32.dll",
"GetStartupInfoW",
stub_get_startup_info_w as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetUserDefaultLCID",
stub_get_user_default_lcid as StubFn,
0,
);
registry.register(
"kernel32.dll",
"HeapQueryInformation",
stub_returns_zero as StubFn,
5,
);
registry.register(
"kernel32.dll",
"IsProcessorFeaturePresent",
stub_returns_zero as StubFn,
1,
);
registry.register(
"kernel32.dll",
"IsValidCodePage",
stub_returns_true as StubFn,
1,
);
registry.register(
"kernel32.dll",
"IsValidLocale",
stub_returns_true as StubFn,
2,
);
registry.register(
"kernel32.dll",
"WaitForMultipleObjectsEx",
stub_wait_for_multiple_objects as StubFn,
5,
);
registry.register(
"kernel32.dll",
"FreeLibraryAndExitThread",
stub_returns_zero as StubFn,
2,
);
registry.register(
"kernel32.dll",
"GetLongPathNameA",
stub_get_long_path_name_a as StubFn,
3,
);
registry.register(
"kernel32.dll",
"GetModuleHandleExA",
stub_get_module_handle_ex_a as StubFn,
3,
);
registry.register(
"kernel32.dll",
"IsDBCSLeadByteEx",
stub_returns_zero as StubFn,
2,
);
registry.register(
"kernel32.dll",
"VirtualQuery",
stub_returns_zero as StubFn,
3,
);
}
fn stub_get_process_heap(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(state.process_heap_handle)
}
const HEAP_ZERO_MEMORY: u32 = 0x0000_0008;
fn stub_heap_alloc(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h_heap = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("HeapAlloc", t))?;
let flags = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("HeapAlloc", t))?;
let n = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("HeapAlloc", t))?;
let addr = bump_alloc(state, n)?;
let buf = state.heap.entry(addr).or_default();
buf.resize(n as usize, 0);
if (flags & HEAP_ZERO_MEMORY) != 0 {
for b in buf.iter_mut() {
*b = 0;
}
}
let bytes = buf.clone();
mmu.write_initializer(addr, &bytes)
.map_err(|t| trap_to_win32("HeapAlloc", t))?;
Ok(addr)
}
fn stub_heap_free(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("HeapFree", t))?;
let _flags = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("HeapFree", t))?;
let addr = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("HeapFree", t))?;
if addr == 0 {
return Ok(1); }
state
.heap
.remove(&addr)
.ok_or(Win32Error::InvalidHeapBlock {
stub: "HeapFree",
addr,
})?;
Ok(1)
}
fn stub_heap_realloc(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("HeapReAlloc", t))?;
let flags = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("HeapReAlloc", t))?;
let addr = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("HeapReAlloc", t))?;
let n = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("HeapReAlloc", t))?;
if addr == 0 {
return stub_heap_alloc(cpu, mmu, state, _registry);
}
let old = state
.heap
.remove(&addr)
.ok_or(Win32Error::InvalidHeapBlock {
stub: "HeapReAlloc",
addr,
})?;
let new_addr = bump_alloc(state, n)?;
let mut buf = vec![0u8; n as usize];
let copy_n = old.len().min(n as usize);
buf[..copy_n].copy_from_slice(&old[..copy_n]);
if (flags & HEAP_ZERO_MEMORY) != 0 {
for b in buf.iter_mut().skip(copy_n) {
*b = 0;
}
}
mmu.write_initializer(new_addr, &buf)
.map_err(|t| trap_to_win32("HeapReAlloc", t))?;
state.heap.insert(new_addr, buf);
Ok(new_addr)
}
fn stub_heap_size(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("HeapSize", t))?;
let _flags = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("HeapSize", t))?;
let addr = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("HeapSize", t))?;
if addr == 0 {
return Ok(0xFFFF_FFFF);
}
Ok(state
.heap
.get(&addr)
.map(|v| v.len() as u32)
.unwrap_or(0xFFFF_FFFF))
}
fn bump_alloc(state: &mut HostState, n: u32) -> Result<u32, Win32Error> {
let aligned = n
.checked_add(15)
.map(|v| v & !15u32)
.ok_or(Win32Error::InvalidArgument {
stub: "HeapAlloc",
reason: "size overflow".into(),
})?;
let addr = state.heap_cursor;
let next = addr
.checked_add(aligned)
.ok_or(Win32Error::InvalidArgument {
stub: "HeapAlloc",
reason: "heap address-space overflow".into(),
})?;
if next > state.heap_arena_end {
return Err(Win32Error::InvalidArgument {
stub: "HeapAlloc",
reason: format!(
"arena exhausted (need {n}, have {})",
state.heap_arena_end - addr
),
});
}
state.heap_cursor = next;
Ok(addr)
}
const LMEM_ZEROINIT: u32 = 0x0040;
fn stub_local_alloc(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let flags = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("LocalAlloc", t))?;
let n = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("LocalAlloc", t))?;
let addr = bump_alloc(state, n)?;
let mut buf = vec![0u8; n as usize];
if (flags & LMEM_ZEROINIT) != 0 {
for b in buf.iter_mut() {
*b = 0;
}
}
mmu.write_initializer(addr, &buf)
.map_err(|t| trap_to_win32("LocalAlloc", t))?;
state.heap.insert(addr, buf);
if state.trace_stubs {
state
.stub_trace
.push(format!(" LocalAlloc(flags={flags:#x}, n={n}) → {addr:#x}"));
}
Ok(addr)
}
fn stub_local_free(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let addr = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("LocalFree", t))?;
if addr == 0 {
return Ok(0);
}
state
.heap
.remove(&addr)
.ok_or(Win32Error::InvalidHeapBlock {
stub: "LocalFree",
addr,
})?;
Ok(0) }
fn stub_output_debug_string_a(
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_to_win32("OutputDebugStringA", t))?;
let s = read_cstr(mmu, p, 4096)?;
state.debug_log.push(s);
Ok(0)
}
fn stub_get_tick_count(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
state.tick = state.tick.wrapping_add(1);
Ok(state.tick)
}
fn stub_interlocked_increment(
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_to_win32("InterlockedIncrement", t))?;
let v = mmu
.load32(p)
.map_err(|t| trap_to_win32("InterlockedIncrement", t))?;
let new = v.wrapping_add(1);
mmu.store32(p, new)
.map_err(|t| trap_to_win32("InterlockedIncrement", t))?;
Ok(new)
}
fn stub_interlocked_decrement(
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_to_win32("InterlockedDecrement", t))?;
let v = mmu
.load32(p)
.map_err(|t| trap_to_win32("InterlockedDecrement", t))?;
let new = v.wrapping_sub(1);
mmu.store32(p, new)
.map_err(|t| trap_to_win32("InterlockedDecrement", t))?;
Ok(new)
}
fn stub_load_library_a(
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_to_win32("LoadLibraryA", t))?;
let name = read_cstr(mmu, p, 260)?.to_ascii_lowercase();
if let Some(base) = state.modules.get(&name) {
return Ok(*base);
}
Ok(0)
}
fn stub_get_proc_address(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
registry: &Registry,
) -> Result<u32, Win32Error> {
let h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetProcAddress", t))?;
let name_p = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetProcAddress", t))?;
if name_p < 0x10000 {
return Ok(0);
}
let name = read_cstr(mmu, name_p, 260)?;
let dll = state
.modules
.iter()
.find(|(_, &base)| base == h)
.map(|(n, _)| n.clone());
let Some(dll) = dll else {
return Ok(0);
};
Ok(registry.resolve(&dll, &name).unwrap_or(0))
}
fn stub_exit_process(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let code = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("ExitProcess", t))?;
state.exit_requested = Some(code);
Ok(0)
}
fn stub_get_acp(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1252)
}
fn stub_get_oem_cp(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(437)
}
fn stub_get_cp_info(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _cp = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetCPInfo", t))?;
let p = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetCPInfo", t))?;
if p == 0 {
return Ok(0);
}
mmu.store32(p, 1)
.map_err(|t| trap_to_win32("GetCPInfo", t))?;
mmu.store8(p + 4, b'?')
.map_err(|t| trap_to_win32("GetCPInfo", t))?;
mmu.store8(p + 5, 0)
.map_err(|t| trap_to_win32("GetCPInfo", t))?;
for i in 0..12 {
mmu.store8(p + 6 + i, 0)
.map_err(|t| trap_to_win32("GetCPInfo", t))?;
}
Ok(1)
}
fn stub_get_command_line_a(
_cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
if state.command_line_ptr == 0 {
let s = b"oxideav-vfw\0";
let addr = state.arena_const_alloc(s.len() as u32)?;
mmu.write_initializer(addr, s)
.map_err(|t| trap_to_win32("GetCommandLineA", t))?;
state.command_line_ptr = addr;
}
Ok(state.command_line_ptr)
}
fn stub_get_environment_strings(
_cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
if state.environment_strings_ptr == 0 {
let s = b"\0\0";
let addr = state.arena_const_alloc(s.len() as u32)?;
mmu.write_initializer(addr, s)
.map_err(|t| trap_to_win32("GetEnvironmentStrings", t))?;
state.environment_strings_ptr = addr;
}
Ok(state.environment_strings_ptr)
}
fn stub_get_file_type(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_get_last_error(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(state.last_error)
}
fn stub_set_last_error(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let code = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("SetLastError", t))?;
state.last_error = code;
Ok(0)
}
fn stub_get_module_file_name_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetModuleFileNameA", t))?;
let dst = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetModuleFileNameA", t))?;
let n_size = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("GetModuleFileNameA", t))?;
if dst == 0 || n_size == 0 {
return Ok(0);
}
let s = b"oxideav-vfw";
let mut written = 0u32;
for (i, b) in s.iter().enumerate() {
if (i as u32) >= n_size.saturating_sub(1) {
break;
}
mmu.store8(dst + i as u32, *b)
.map_err(|t| trap_to_win32("GetModuleFileNameA", t))?;
written = written.saturating_add(1);
}
if n_size > 0 {
let nul_off = written.min(n_size - 1);
mmu.store8(dst + nul_off, 0)
.map_err(|t| trap_to_win32("GetModuleFileNameA", t))?;
}
Ok(written)
}
fn stub_get_module_handle_a(
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_to_win32("GetModuleHandleA", t))?;
if p == 0 {
return Ok(state.primary_module_base);
}
let name = read_cstr(mmu, p, 260)?.to_ascii_lowercase();
Ok(state.modules.get(&name).copied().unwrap_or(0))
}
fn stub_get_startup_info_a(
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_to_win32("GetStartupInfoA", t))?;
if p == 0 {
return Ok(0);
}
for i in 0..68u32 {
mmu.store8(p + i, 0)
.map_err(|t| trap_to_win32("GetStartupInfoA", t))?;
}
mmu.store32(p, 68)
.map_err(|t| trap_to_win32("GetStartupInfoA", t))?;
Ok(0)
}
fn stub_get_std_handle(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0xFFFF_FFFF)
}
fn stub_get_system_info(
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_to_win32("GetSystemInfo", t))?;
if p == 0 {
return Ok(0);
}
let trap = |t: crate::emulator::Trap| trap_to_win32("GetSystemInfo", t);
mmu.store32(p, 0).map_err(trap)?; mmu.store32(p + 4, PAGE_SIZE as u32).map_err(trap)?;
mmu.store32(p + 8, 0x10000).map_err(trap)?;
mmu.store32(p + 12, 0x7FFF_FFFF).map_err(trap)?;
mmu.store32(p + 16, 1).map_err(trap)?; mmu.store32(p + 20, 1).map_err(trap)?; mmu.store32(p + 24, 586).map_err(trap)?; mmu.store32(p + 28, 0x10000).map_err(trap)?; mmu.store16(p + 32, 0).map_err(trap)?; mmu.store16(p + 34, 0).map_err(trap)?; Ok(0)
}
fn stub_get_version(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0x0000_0A04)
}
const GMEM_ZEROINIT: u32 = 0x0040;
fn stub_global_alloc(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let flags = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GlobalAlloc", t))?;
let n = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GlobalAlloc", t))?;
let addr = bump_alloc(state, n)?;
let mut buf = vec![0u8; n as usize];
if (flags & GMEM_ZEROINIT) != 0 {
for b in buf.iter_mut() {
*b = 0;
}
}
mmu.write_initializer(addr, &buf)
.map_err(|t| trap_to_win32("GlobalAlloc", t))?;
state.heap.insert(addr, buf);
Ok(addr)
}
fn stub_global_free(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let addr = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GlobalFree", t))?;
if addr == 0 {
return Ok(0);
}
state
.heap
.remove(&addr)
.ok_or(Win32Error::InvalidHeapBlock {
stub: "GlobalFree",
addr,
})?;
Ok(0)
}
fn stub_global_lock(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let addr = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GlobalLock", t))?;
Ok(addr)
}
fn stub_global_unlock(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _addr = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GlobalUnlock", t))?;
state.last_error = 0; Ok(0)
}
fn stub_multi_byte_to_wide_char(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _cp = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("MultiByteToWideChar", t))?;
let _flags = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("MultiByteToWideChar", t))?;
let src = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("MultiByteToWideChar", t))?;
let cb = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("MultiByteToWideChar", t))?;
let dst = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("MultiByteToWideChar", t))?;
let cch = arg_dword(cpu, mmu, 5).map_err(|t| trap_to_win32("MultiByteToWideChar", t))?;
if src == 0 {
return Ok(0);
}
let n = if cb == 0xFFFF_FFFF {
let mut p = src;
let mut k: u32 = 0;
loop {
let b = mmu
.load8(p)
.map_err(|t| trap_to_win32("MultiByteToWideChar", t))?;
k = k.saturating_add(1);
if b == 0 {
break;
}
p = p.wrapping_add(1);
if k > 0x0010_0000 {
break; }
}
k
} else {
cb
};
if cch == 0 {
return Ok(n);
}
if dst == 0 {
return Ok(0);
}
let to_write = core::cmp::min(n, cch);
for i in 0..to_write {
let b = mmu
.load8(src + i)
.map_err(|t| trap_to_win32("MultiByteToWideChar", t))?;
mmu.store16(dst + i * 2, u16::from(b))
.map_err(|t| trap_to_win32("MultiByteToWideChar", t))?;
}
Ok(to_write)
}
#[allow(clippy::too_many_arguments)]
fn stub_wide_char_to_multi_byte(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _cp = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
let _flags = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
let src = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
let cch = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
let dst = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
let cb = arg_dword(cpu, mmu, 5).map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
let p_default = arg_dword(cpu, mmu, 6).map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
let p_used = arg_dword(cpu, mmu, 7).map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
if src == 0 {
return Ok(0);
}
let n = if cch == 0xFFFF_FFFF {
let mut p = src;
let mut k: u32 = 0;
loop {
let u = mmu
.load16(p)
.map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
k = k.saturating_add(1);
if u == 0 {
break;
}
p = p.wrapping_add(2);
if k > 0x0010_0000 {
break;
}
}
k
} else {
cch
};
let default_char: u8 = if p_default != 0 {
mmu.load8(p_default)
.map_err(|t| trap_to_win32("WideCharToMultiByte", t))?
} else {
b'?'
};
if cb == 0 {
return Ok(n);
}
if dst == 0 {
return Ok(0);
}
let to_write = core::cmp::min(n, cb);
let mut used_default = false;
for i in 0..to_write {
let u = mmu
.load16(src + i * 2)
.map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
let b = if u <= 0xFF {
u as u8
} else {
used_default = true;
default_char
};
mmu.store8(dst + i, b)
.map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
}
if p_used != 0 {
mmu.store32(p_used, if used_default { 1 } else { 0 })
.map_err(|t| trap_to_win32("WideCharToMultiByte", t))?;
}
Ok(to_write)
}
fn stub_rtl_unwind(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
const MEM_COMMIT: u32 = 0x0000_1000;
const MEM_RESERVE: u32 = 0x0000_2000;
#[allow(dead_code)]
const MEM_RELEASE: u32 = 0x0000_8000;
#[allow(dead_code)]
const MEM_DECOMMIT: u32 = 0x0000_4000;
const PAGE_NOACCESS: u32 = 0x01;
const PAGE_READONLY: u32 = 0x02;
const PAGE_READWRITE: u32 = 0x04;
#[allow(dead_code)]
const PAGE_EXECUTE: u32 = 0x10;
const PAGE_EXECUTE_READ: u32 = 0x20;
const PAGE_EXECUTE_READWRITE: u32 = 0x40;
fn page_protect_to_perm(flprot: u32) -> Perm {
let base = flprot & 0xFF;
match base {
PAGE_NOACCESS => Perm::from_bits(0),
PAGE_READONLY => Perm::R,
PAGE_READWRITE => Perm::R | Perm::W,
PAGE_EXECUTE_READ => Perm::R | Perm::X,
PAGE_EXECUTE_READWRITE => Perm::R | Perm::W | Perm::X,
PAGE_EXECUTE => Perm::R | Perm::X,
_ => Perm::R | Perm::W,
}
}
fn perm_to_page_protect(perm: Perm) -> u32 {
let r = perm.contains(Perm::R);
let w = perm.contains(Perm::W);
let x = perm.contains(Perm::X);
match (r, w, x) {
(false, false, false) => PAGE_NOACCESS,
(true, false, false) => PAGE_READONLY,
(true, true, false) => PAGE_READWRITE,
(true, false, true) => PAGE_EXECUTE_READ,
(true, true, true) => PAGE_EXECUTE_READWRITE,
(false, _, true) => PAGE_EXECUTE,
(false, true, false) => PAGE_READWRITE,
}
}
const VIRTUAL_ALLOC_LO: u32 = 0xA000_0000;
const VIRTUAL_ALLOC_HI: u32 = 0xC000_0000;
fn stub_virtual_alloc(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let lp_addr = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("VirtualAlloc", t))?;
let size = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("VirtualAlloc", t))?;
let alloc_type = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("VirtualAlloc", t))?;
let prot = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("VirtualAlloc", t))?;
if size == 0 {
return Ok(0);
}
let perm = page_protect_to_perm(prot);
let aligned_size = ((size + (PAGE_SIZE as u32 - 1)) & !(PAGE_SIZE as u32 - 1)).max(size);
let base = if lp_addr == 0 {
match mmu.find_free_range(VIRTUAL_ALLOC_LO, VIRTUAL_ALLOC_HI, aligned_size) {
Some(b) => b,
None => return Ok(0),
}
} else {
lp_addr & !(PAGE_SIZE as u32 - 1)
};
if (alloc_type & (MEM_COMMIT | MEM_RESERVE)) != 0 || alloc_type == 0 {
mmu.map(base, aligned_size, perm);
}
Ok(base)
}
fn stub_virtual_free(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let lp_addr = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("VirtualFree", t))?;
let size = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("VirtualFree", t))?;
let _free_type = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("VirtualFree", t))?;
if lp_addr == 0 {
return Ok(0);
}
if size > 0 {
let aligned_size = (size + (PAGE_SIZE as u32 - 1)) & !(PAGE_SIZE as u32 - 1);
mmu.unmap(lp_addr & !(PAGE_SIZE as u32 - 1), aligned_size);
}
Ok(1)
}
const ERROR_INVALID_HANDLE: u32 = 6;
fn stub_write_file(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("WriteFile", t))?;
let _lp_buf = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("WriteFile", t))?;
let _n = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("WriteFile", t))?;
let lp_written = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("WriteFile", t))?;
let _lp_ovl = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("WriteFile", t))?;
if lp_written != 0 {
mmu.store32(lp_written, 0)
.map_err(|t| trap_to_win32("WriteFile", t))?;
}
state.last_error = ERROR_INVALID_HANDLE;
Ok(0)
}
fn read_cstr(mmu: &Mmu, mut addr: u32, max: u32) -> Result<String, Win32Error> {
let mut bytes = Vec::new();
for _ in 0..max {
let b = mmu.load8(addr).map_err(|t| trap_to_win32("read_cstr", t))?;
if b == 0 {
break;
}
bytes.push(b);
addr = addr.wrapping_add(1);
}
Ok(String::from_utf8_lossy(&bytes).into_owned())
}
fn trap_to_win32(stub: &'static str, t: crate::emulator::Trap) -> Win32Error {
Win32Error::InvalidArgument {
stub,
reason: format!("{t}"),
}
}
fn stub_close_handle(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_create_file_mapping_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h_file = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("CreateFileMappingA", t))?;
let _attrs = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreateFileMappingA", t))?;
let _protect = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CreateFileMappingA", t))?;
let _size_hi = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("CreateFileMappingA", t))?;
let size_lo = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("CreateFileMappingA", t))?;
let _name = arg_dword(cpu, mmu, 5).map_err(|t| trap_to_win32("CreateFileMappingA", t))?;
if size_lo == 0 {
return Ok(0);
}
let addr = bump_alloc(state, size_lo)?;
let buf = vec![0u8; size_lo as usize];
mmu.write_initializer(addr, &buf)
.map_err(|t| trap_to_win32("CreateFileMappingA", t))?;
state.heap.insert(addr, buf);
Ok(addr)
}
fn stub_create_semaphore_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0xC0DE_5E3A) }
fn stub_delete_critical_section(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_disable_thread_library_calls(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_enter_critical_section(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_leave_critical_section(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_initialize_critical_section(
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_to_win32("InitializeCriticalSection", t))?;
if p != 0 {
for i in 0..24u32 {
let _ = mmu.store8(p + i, 0);
}
}
Ok(0)
}
fn rsrc_dir_lookup_id(
mmu: &Mmu,
_rsrc_base: u32,
dir_va: u32,
target_id: u32,
) -> Option<(u32, bool)> {
let n_named = mmu.load16(dir_va + 12).ok()? as u32;
let n_id = mmu.load16(dir_va + 14).ok()? as u32;
let entries_va = dir_va + 16;
for i in 0..n_id {
let e_va = entries_va + (n_named + i) * 8;
let name = mmu.load32(e_va).ok()?;
if (name & 0x8000_0000) != 0 {
continue;
}
if name == target_id {
let off = mmu.load32(e_va + 4).ok()?;
let is_dir = (off & 0x8000_0000) != 0;
return Some((off & 0x7FFF_FFFF, is_dir));
}
}
None
}
pub(crate) fn find_resource_data_entry(
state: &HostState,
mmu: &Mmu,
h_module: u32,
lp_name: u32,
lp_type: u32,
) -> Option<u32> {
let h = if h_module == 0 {
state.primary_module_base
} else {
h_module
};
let rsrc_base = *state.module_resource_dirs.get(&h)?;
if lp_name & 0xFFFF_0000 != 0 || lp_type & 0xFFFF_0000 != 0 {
return None;
}
let (off_type, is_dir) = rsrc_dir_lookup_id(mmu, rsrc_base, rsrc_base, lp_type)?;
if !is_dir {
return None;
}
let (off_name, is_dir) = rsrc_dir_lookup_id(mmu, rsrc_base, rsrc_base + off_type, lp_name)?;
if !is_dir {
return None;
}
let lang_dir_va = rsrc_base + off_name;
let n_named = mmu.load16(lang_dir_va + 12).ok()? as u32;
let n_id = mmu.load16(lang_dir_va + 14).ok()? as u32;
if n_named + n_id == 0 {
return None;
}
let first_entry = lang_dir_va + 16;
let off = mmu.load32(first_entry + 4).ok()?;
if (off & 0x8000_0000) != 0 {
return None;
}
Some(rsrc_base + (off & 0x7FFF_FFFF))
}
fn stub_find_resource_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let h_module = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("FindResourceA", t))?;
let lp_name = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("FindResourceA", t))?;
let lp_type = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("FindResourceA", t))?;
Ok(find_resource_data_entry(state, mmu, h_module, lp_name, lp_type).unwrap_or(0))
}
fn stub_flush_file_buffers(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_free_environment_strings(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_free_library(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_free_resource(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_get_current_process(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0xFFFF_FFFF)
}
fn stub_get_current_thread_id(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_get_environment_strings_w(
_cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
if state.environment_strings_ptr != 0 {
return Ok(state.environment_strings_ptr);
}
let p = state.arena_const_alloc(4)?;
mmu.write_initializer(p, &[0, 0, 0, 0])
.map_err(|t| trap_to_win32("GetEnvironmentStringsW", t))?;
state.environment_strings_ptr = p;
Ok(p)
}
fn stub_get_locale_info_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_get_short_path_name_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_get_string_type(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_get_system_directory_a(
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_to_win32("GetSystemDirectoryA", t))?;
let size = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetSystemDirectoryA", t))?;
let s = b"C:\\WINDOWS\\System32";
if buf == 0 || size == 0 {
return Ok(s.len() as u32 + 1);
}
let n = (size as usize).saturating_sub(1).min(s.len());
for (i, &b) in s.iter().take(n).enumerate() {
mmu.store8(buf + i as u32, b)
.map_err(|t| trap_to_win32("GetSystemDirectoryA", t))?;
}
mmu.store8(buf + n as u32, 0)
.map_err(|t| trap_to_win32("GetSystemDirectoryA", t))?;
Ok(n as u32)
}
fn stub_get_version_ex_a(
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_to_win32("GetVersionExA", t))?;
if p == 0 {
return Ok(0);
}
mmu.store32(p + 4, 4)
.map_err(|t| trap_to_win32("GetVersionExA", t))?; mmu.store32(p + 8, 0)
.map_err(|t| trap_to_win32("GetVersionExA", t))?; mmu.store32(p + 12, 950)
.map_err(|t| trap_to_win32("GetVersionExA", t))?; mmu.store32(p + 16, 1)
.map_err(|t| trap_to_win32("GetVersionExA", t))?; mmu.store8(p + 20, 0)
.map_err(|t| trap_to_win32("GetVersionExA", t))?;
Ok(1)
}
fn stub_global_handle(
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_to_win32("GlobalHandle", t))?;
Ok(p)
}
fn stub_global_realloc(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let addr = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GlobalReAlloc", t))?;
let n = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GlobalReAlloc", t))?;
let _flags = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("GlobalReAlloc", t))?;
if addr == 0 {
let new_addr = bump_alloc(state, n)?;
let buf = vec![0u8; n as usize];
mmu.write_initializer(new_addr, &buf)
.map_err(|t| trap_to_win32("GlobalReAlloc", t))?;
state.heap.insert(new_addr, buf);
return Ok(new_addr);
}
let old = state
.heap
.remove(&addr)
.ok_or(Win32Error::InvalidHeapBlock {
stub: "GlobalReAlloc",
addr,
})?;
let new_addr = bump_alloc(state, n)?;
let mut buf = vec![0u8; n as usize];
let copy_n = old.len().min(n as usize);
buf[..copy_n].copy_from_slice(&old[..copy_n]);
mmu.write_initializer(new_addr, &buf)
.map_err(|t| trap_to_win32("GlobalReAlloc", t))?;
state.heap.insert(new_addr, buf);
Ok(new_addr)
}
fn stub_heap_create(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(state.process_heap_handle)
}
fn stub_heap_destroy(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_is_bad_ptr(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_lc_map_string(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_load_resource(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h_module = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("LoadResource", t))?;
let h_res_info = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("LoadResource", t))?;
Ok(h_res_info)
}
fn stub_local_handle(
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_to_win32("LocalHandle", t))?;
Ok(p)
}
fn stub_local_lock(
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_to_win32("LocalLock", t))?;
Ok(p)
}
fn stub_local_unlock(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_lock_resource(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("LockResource", t))?;
if h == 0 {
return Ok(0);
}
let rva = match mmu.load32(h) {
Ok(v) => v,
Err(_) => return Ok(0),
};
if state.primary_module_base == 0 {
return Ok(0);
}
Ok(state.primary_module_base.wrapping_add(rva))
}
fn stub_sizeof_resource(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h_module = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("SizeofResource", t))?;
let h_res_info = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("SizeofResource", t))?;
if h_res_info == 0 {
return Ok(0);
}
match mmu.load32(h_res_info + 4) {
Ok(v) => Ok(v),
Err(_) => Ok(0),
}
}
fn stub_map_view_of_file(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("MapViewOfFile", t))?;
let _access = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("MapViewOfFile", t))?;
let _off_hi = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("MapViewOfFile", t))?;
let off_lo = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("MapViewOfFile", t))?;
let _num = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("MapViewOfFile", t))?;
if h == 0 {
return Ok(0);
}
Ok(h.wrapping_add(off_lo))
}
fn stub_open_file_mapping_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_query_performance_counter(
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_to_win32("QueryPerformanceCounter", t))?;
state.tick = state.tick.wrapping_add(1);
if p != 0 {
mmu.store32(p, state.tick)
.map_err(|t| trap_to_win32("QueryPerformanceCounter", t))?;
mmu.store32(p + 4, 0)
.map_err(|t| trap_to_win32("QueryPerformanceCounter", t))?;
}
Ok(1)
}
fn stub_query_performance_frequency(
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_to_win32("QueryPerformanceFrequency", t))?;
if p != 0 {
mmu.store32(p, 1_000_000)
.map_err(|t| trap_to_win32("QueryPerformanceFrequency", t))?;
mmu.store32(p + 4, 0)
.map_err(|t| trap_to_win32("QueryPerformanceFrequency", t))?;
}
Ok(1)
}
fn stub_raise_exception(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let code = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("RaiseException", t))?;
state
.debug_log
.push(format!("RaiseException code={code:#010x}"));
Ok(0)
}
fn stub_release_semaphore(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_set_file_pointer(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0xFFFF_FFFF)
}
fn stub_set_handle_count(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let n = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("SetHandleCount", t))?;
Ok(n)
}
fn stub_set_std_handle(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_set_unhandled_exception_filter(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_sleep(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_terminate_process(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("TerminateProcess", t))?;
let code = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("TerminateProcess", t))?;
state.exit_requested = Some(code);
Ok(1)
}
fn stub_tls_alloc(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
state.tick = state.tick.wrapping_add(1);
Ok(state.tick)
}
fn stub_tls_free(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_tls_get_value(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_tls_set_value(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_unmap_view_of_file(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_wait_for_single_object(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_write_private_profile_string_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_lstrlen_a(
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_to_win32("lstrlenA", t))?;
if p == 0 {
return Ok(0);
}
let mut n: u32 = 0;
while n < 0x10000 {
match mmu.load8(p + n) {
Ok(0) => break,
Ok(_) => n = n.wrapping_add(1),
Err(_) => break,
}
}
Ok(n)
}
fn stub_create_event_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _attrs = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("CreateEventA", t))?;
let _manual = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreateEventA", t))?;
let _init = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CreateEventA", t))?;
let _name = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("CreateEventA", t))?;
state.tick = state.tick.wrapping_add(1);
Ok(0xCAFE_E000u32.wrapping_add(state.tick))
}
fn stub_create_thread(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
registry: &Registry,
) -> Result<u32, Win32Error> {
const CREATE_SUSPENDED: u32 = 0x0000_0004;
let _attrs = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("CreateThread", t))?;
let _stack = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreateThread", t))?;
let start = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CreateThread", t))?;
let param = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("CreateThread", t))?;
let flags = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("CreateThread", t))?;
let p_tid = arg_dword(cpu, mmu, 5).map_err(|t| trap_to_win32("CreateThread", t))?;
state.tick = state.tick.wrapping_add(1);
let handle = 0xCAFE_C000u32.wrapping_add(state.tick);
if p_tid != 0 {
mmu.store32(p_tid, handle)
.map_err(|t| trap_to_win32("CreateThread", t))?;
}
if start != 0 && (flags & CREATE_SUSPENDED) == 0 {
let _ = crate::win32::call_guest(cpu, mmu, registry, state, start, &[param]);
}
Ok(handle)
}
fn stub_set_event(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_set_thread_priority(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_resume_thread(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_muldiv(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let a = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("MulDiv", t))? as i32;
let b = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("MulDiv", t))? as i32;
let c = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("MulDiv", t))? as i32;
if c == 0 {
return Ok((-1i32) as u32);
}
let prod = (a as i64).wrapping_mul(b as i64);
let cb = c as i64;
let sign_match = (prod < 0) == (cb < 0);
let half = cb.wrapping_abs() / 2;
let adj = if sign_match { half } else { -half };
let result = prod.wrapping_add(adj) / cb;
if result > i32::MAX as i64 || result < i32::MIN as i64 {
return Ok((-1i32) as u32);
}
Ok((result as i32) as u32)
}
fn stub_get_profile_int_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _app = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetProfileIntA", t))?;
let _key = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetProfileIntA", t))?;
let default = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("GetProfileIntA", t))?;
Ok(default)
}
fn stub_get_current_process_id(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_get_system_time_as_file_time(
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_to_win32("GetSystemTimeAsFileTime", t))?;
if p == 0 {
return Ok(0);
}
state.tick = state.tick.wrapping_add(1);
let base: u64 = 133_482_240_000_000_000; let ft = base.wrapping_add(u64::from(state.tick).wrapping_mul(10_000));
let low = ft as u32;
let high = (ft >> 32) as u32;
mmu.store32(p, low)
.map_err(|t| trap_to_win32("GetSystemTimeAsFileTime", t))?;
mmu.store32(p.wrapping_add(4), high)
.map_err(|t| trap_to_win32("GetSystemTimeAsFileTime", t))?;
Ok(0)
}
fn stub_get_current_thread(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0xFFFF_FFFE)
}
fn stub_interlocked_exchange(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let target = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("InterlockedExchange", t))?;
let value = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("InterlockedExchange", t))?;
let prev = mmu
.load32(target)
.map_err(|t| trap_to_win32("InterlockedExchange", t))?;
mmu.store32(target, value)
.map_err(|t| trap_to_win32("InterlockedExchange", t))?;
Ok(prev)
}
fn stub_interlocked_compare_exchange(
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_to_win32("InterlockedCompareExchange", t))?;
let exchange =
arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("InterlockedCompareExchange", t))?;
let comparand =
arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("InterlockedCompareExchange", t))?;
let prev = mmu
.load32(dest)
.map_err(|t| trap_to_win32("InterlockedCompareExchange", t))?;
if prev == comparand {
mmu.store32(dest, exchange)
.map_err(|t| trap_to_win32("InterlockedCompareExchange", t))?;
}
Ok(prev)
}
fn stub_unhandled_exception_filter(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_set_error_mode(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _new_mode = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("SetErrorMode", t))?;
Ok(0)
}
fn stub_reset_event(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("ResetEvent", t))?;
Ok(1)
}
fn stub_wait_for_multiple_objects(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _ncount = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("WaitForMultipleObjects", t))?;
let _phandles =
arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("WaitForMultipleObjects", t))?;
let _waitall =
arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("WaitForMultipleObjects", t))?;
let _ms = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("WaitForMultipleObjects", t))?;
Ok(0)
}
fn stub_create_event_w(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
state.tick = state.tick.wrapping_add(1);
Ok(0xE000_0000u32.wrapping_add(state.tick))
}
fn stub_create_semaphore_w(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
state.tick = state.tick.wrapping_add(1);
Ok(0xE100_0000u32.wrapping_add(state.tick))
}
fn stub_get_local_time(
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_to_win32("GetLocalTime", t))?;
if p == 0 {
return Ok(0);
}
let fields: [u16; 8] = [2024, 1, 1, 1, 0, 0, 0, 0];
for (i, w) in fields.iter().enumerate() {
mmu.store16(p.wrapping_add(i as u32 * 2), *w)
.map_err(|t| trap_to_win32("GetLocalTime", t))?;
}
Ok(0)
}
fn stub_get_module_handle_w(
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_to_win32("GetModuleHandleW", t))?;
if p == 0 {
return Ok(state.primary_module_base);
}
let name = read_wcstr(mmu, p, 260)?.to_ascii_lowercase();
Ok(state.modules.get(&name).copied().unwrap_or(0))
}
fn read_wcstr(mmu: &Mmu, mut addr: u32, max: u32) -> Result<String, Win32Error> {
let mut bytes = Vec::with_capacity(64);
for _ in 0..max {
let c = mmu
.load16(addr)
.map_err(|t| trap_to_win32("read_wcstr", t))?;
if c == 0 {
break;
}
bytes.push(c);
addr = addr.wrapping_add(2);
}
Ok(String::from_utf16_lossy(&bytes))
}
fn stub_get_private_profile_int_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _app = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetPrivateProfileIntA", t))?;
let _key = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetPrivateProfileIntA", t))?;
let default = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("GetPrivateProfileIntA", t))?;
let _file = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("GetPrivateProfileIntA", t))?;
Ok(default)
}
fn stub_delay_load_failure_hook(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _dll = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("DelayLoadFailureHook", t))?;
let _proc = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("DelayLoadFailureHook", t))?;
Ok(0)
}
fn stub_get_version_ex_w(
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_to_win32("GetVersionExW", t))?;
if p == 0 {
return Ok(0);
}
mmu.store32(p.wrapping_add(4), 6)
.map_err(|t| trap_to_win32("GetVersionExW", t))?;
mmu.store32(p.wrapping_add(8), 1)
.map_err(|t| trap_to_win32("GetVersionExW", t))?;
mmu.store32(p.wrapping_add(12), 7600)
.map_err(|t| trap_to_win32("GetVersionExW", t))?;
mmu.store32(p.wrapping_add(16), 2)
.map_err(|t| trap_to_win32("GetVersionExW", t))?;
let zeros = [0u8; 256];
mmu.write(p.wrapping_add(20), &zeros)
.map_err(|t| trap_to_win32("GetVersionExW", t))?;
Ok(1)
}
fn stub_signal_object_and_wait(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_init_cs_spin(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_is_debugger_present(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_virtual_protect(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let addr = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("VirtualProtect", t))?;
let size = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("VirtualProtect", t))?;
let new = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("VirtualProtect", t))?;
let out = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("VirtualProtect", t))?;
if size == 0 || !mmu.is_mapped(addr) {
return Ok(0);
}
let old_perm = mmu.perm_at(addr).unwrap_or(Perm::from_bits(0));
if out != 0 {
mmu.store32(out, perm_to_page_protect(old_perm))
.map_err(|t| trap_to_win32("VirtualProtect", t))?;
}
let new_perm = page_protect_to_perm(new);
mmu.map(addr, size, new_perm);
Ok(1)
}
fn stub_interlocked_exchange_add(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let addend = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("InterlockedExchangeAdd", t))?;
let value = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("InterlockedExchangeAdd", t))?;
let prev = mmu
.load32(addend)
.map_err(|t| trap_to_win32("InterlockedExchangeAdd", t))?;
let new = prev.wrapping_add(value);
mmu.store32(addend, new)
.map_err(|t| trap_to_win32("InterlockedExchangeAdd", t))?;
Ok(prev)
}
fn stub_get_computer_name_a(
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_to_win32("GetComputerNameA", t))?;
let n_ptr = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetComputerNameA", t))?;
let name = b"UDEMULATOR\0";
let mut cap = 0u32;
if n_ptr != 0 {
cap = mmu
.load32(n_ptr)
.map_err(|t| trap_to_win32("GetComputerNameA", t))?;
mmu.store32(n_ptr, name.len() as u32 - 1)
.map_err(|t| trap_to_win32("GetComputerNameA", t))?;
}
if buf != 0 && cap as usize >= name.len() {
mmu.write(buf, name)
.map_err(|t| trap_to_win32("GetComputerNameA", t))?;
}
Ok(1)
}
fn stub_get_environment_variable_w(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_get_process_affinity_mask(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetProcessAffinityMask", t))?;
let proc_mask =
arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetProcessAffinityMask", t))?;
let sys_mask =
arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("GetProcessAffinityMask", t))?;
if proc_mask != 0 {
mmu.store32(proc_mask, 1)
.map_err(|t| trap_to_win32("GetProcessAffinityMask", t))?;
}
if sys_mask != 0 {
mmu.store32(sys_mask, 1)
.map_err(|t| trap_to_win32("GetProcessAffinityMask", t))?;
}
Ok(1)
}
fn stub_get_thread_priority(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_set_thread_affinity_mask(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_load_library_w(
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_to_win32("LoadLibraryW", t))?;
if p == 0 {
return Ok(0);
}
let name = read_wcstr(mmu, p, 260)?.to_ascii_lowercase();
Ok(state.modules.get(&name).copied().unwrap_or(0))
}
fn stub_read_file(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("ReadFile", t))?;
let _buf = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("ReadFile", t))?;
let _n = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("ReadFile", t))?;
let out = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("ReadFile", t))?;
if out != 0 {
mmu.store32(out, 0)
.map_err(|t| trap_to_win32("ReadFile", t))?;
}
Ok(1)
}
fn stub_returns_zero(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_returns_true(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
const CSTR_SCAN_CAP: u32 = 0x1_0000;
fn stub_lstrcat_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let dst = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("lstrcatA", t))?;
let src = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("lstrcatA", t))?;
if dst == 0 {
return Ok(0);
}
let mut end = dst;
let mut scanned = 0u32;
while scanned < CSTR_SCAN_CAP && mmu.load8(end).map_err(|t| trap_to_win32("lstrcatA", t))? != 0
{
end = end.wrapping_add(1);
scanned += 1;
}
if src != 0 {
let mut i = 0u32;
loop {
let b = mmu
.load8(src.wrapping_add(i))
.map_err(|t| trap_to_win32("lstrcatA", t))?;
mmu.store8(end.wrapping_add(i), b)
.map_err(|t| trap_to_win32("lstrcatA", t))?;
if b == 0 || i >= CSTR_SCAN_CAP {
break;
}
i += 1;
}
} else {
mmu.store8(end, 0)
.map_err(|t| trap_to_win32("lstrcatA", t))?;
}
Ok(dst)
}
fn stub_lstrcpy_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let dst = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("lstrcpyA", t))?;
let src = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("lstrcpyA", t))?;
if dst == 0 {
return Ok(0);
}
if src == 0 {
mmu.store8(dst, 0)
.map_err(|t| trap_to_win32("lstrcpyA", t))?;
return Ok(dst);
}
let mut i = 0u32;
loop {
let b = mmu
.load8(src.wrapping_add(i))
.map_err(|t| trap_to_win32("lstrcpyA", t))?;
mmu.store8(dst.wrapping_add(i), b)
.map_err(|t| trap_to_win32("lstrcpyA", t))?;
if b == 0 || i >= CSTR_SCAN_CAP {
break;
}
i += 1;
}
Ok(dst)
}
fn read_cstr_lower(mmu: &Mmu, base: u32) -> Vec<u8> {
let mut out = Vec::new();
if base == 0 {
return out;
}
for i in 0..CSTR_SCAN_CAP {
match mmu.load8(base.wrapping_add(i)) {
Ok(0) | Err(_) => break,
Ok(b) => out.push(b.to_ascii_lowercase()),
}
}
out
}
fn stub_lstrcmpi_a(
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_to_win32("lstrcmpiA", t))?;
let s2 = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("lstrcmpiA", t))?;
let a = read_cstr_lower(mmu, s1);
let b = read_cstr_lower(mmu, s2);
Ok(match a.cmp(&b) {
std::cmp::Ordering::Less => (-1i32) as u32,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
})
}
const CSTR_LESS_THAN: u32 = 1;
const CSTR_EQUAL: u32 = 2;
const CSTR_GREATER_THAN: u32 = 3;
fn cmp_to_cstr(ord: std::cmp::Ordering) -> u32 {
match ord {
std::cmp::Ordering::Less => CSTR_LESS_THAN,
std::cmp::Ordering::Equal => CSTR_EQUAL,
std::cmp::Ordering::Greater => CSTR_GREATER_THAN,
}
}
fn stub_compare_string_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let s1 = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CompareStringA", t))?;
let s2 = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("CompareStringA", t))?;
let a = read_cstr_lower(mmu, s1);
let b = read_cstr_lower(mmu, s2);
Ok(cmp_to_cstr(a.cmp(&b)))
}
fn stub_compare_string_w(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let s1 = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CompareStringW", t))?;
let s2 = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("CompareStringW", t))?;
let read_w = |base: u32| -> Vec<u16> {
let mut out = Vec::new();
if base == 0 {
return out;
}
for i in 0..CSTR_SCAN_CAP {
match mmu.load16(base.wrapping_add(i * 2)) {
Ok(0) | Err(_) => break,
Ok(c) => out.push(c),
}
}
out
};
let a = read_w(s1);
let b = read_w(s2);
Ok(cmp_to_cstr(a.cmp(&b)))
}
fn stub_fatal_app_exit_a(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0)
}
fn stub_get_system_time(
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_to_win32("GetSystemTime", t))?;
if p == 0 {
return Ok(0);
}
let fields: [u16; 8] = [2024, 1, 1, 1, 0, 0, 0, 0];
for (i, v) in fields.iter().enumerate() {
mmu.store16(p.wrapping_add(i as u32 * 2), *v)
.map_err(|t| trap_to_win32("GetSystemTime", t))?;
}
Ok(0)
}
fn stub_get_time_zone_information(
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_to_win32("GetTimeZoneInformation", t))?;
if p != 0 {
mmu.write(p, &[0u8; 172])
.map_err(|t| trap_to_win32("GetTimeZoneInformation", t))?;
}
Ok(0)
}
fn stub_load_library_ex_a(
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_to_win32("LoadLibraryExA", t))?;
let name = read_cstr(mmu, p, 260)?.to_ascii_lowercase();
Ok(state.modules.get(&name).copied().unwrap_or(0))
}
fn stub_write_console_a(
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_to_win32("WriteConsoleA", t))?;
let written = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("WriteConsoleA", t))?;
if written != 0 {
mmu.store32(written, n)
.map_err(|t| trap_to_win32("WriteConsoleA", t))?;
}
Ok(1)
}
fn stub_identity_pointer(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("EncodePointer/DecodePointer", t))
}
fn stub_get_module_file_name_w(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetModuleFileNameW", t))?;
let dst = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetModuleFileNameW", t))?;
let n_size = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("GetModuleFileNameW", t))?;
if dst == 0 || n_size == 0 {
return Ok(0);
}
let s = "oxideav-vfw";
let mut written = 0u32;
for (i, c) in s.chars().enumerate() {
if (i as u32) >= n_size.saturating_sub(1) {
break;
}
mmu.store16(dst + i as u32 * 2, c as u16)
.map_err(|t| trap_to_win32("GetModuleFileNameW", t))?;
written += 1;
}
let nul_off = written.min(n_size - 1);
mmu.store16(dst + nul_off * 2, 0)
.map_err(|t| trap_to_win32("GetModuleFileNameW", t))?;
Ok(written)
}
fn stub_get_startup_info_w(
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_to_win32("GetStartupInfoW", t))?;
if p == 0 {
return Ok(0);
}
mmu.write(p, &[0u8; 68])
.map_err(|t| trap_to_win32("GetStartupInfoW", t))?;
mmu.store32(p, 68)
.map_err(|t| trap_to_win32("GetStartupInfoW", t))?;
Ok(0)
}
fn stub_get_user_default_lcid(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(0x0409)
}
fn stub_get_long_path_name_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let short = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetLongPathNameA", t))?;
let long = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetLongPathNameA", t))?;
let cch = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("GetLongPathNameA", t))?;
let path = read_cstr(mmu, short, CSTR_SCAN_CAP)?;
let len = path.len() as u32;
if long == 0 || cch < len + 1 {
return Ok(len + 1);
}
let mut bytes = path.into_bytes();
bytes.push(0);
mmu.write(long, &bytes)
.map_err(|t| trap_to_win32("GetLongPathNameA", t))?;
Ok(len)
}
fn stub_get_module_handle_ex_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let name_p = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetModuleHandleExA", t))?;
let out = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("GetModuleHandleExA", t))?;
let handle = if name_p == 0 {
state.primary_module_base
} else {
let name = read_cstr(mmu, name_p, 260)?.to_ascii_lowercase();
state.modules.get(&name).copied().unwrap_or(0)
};
if out != 0 {
mmu.store32(out, handle)
.map_err(|t| trap_to_win32("GetModuleHandleExA", t))?;
}
Ok(1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::emulator::mmu::Perm;
use crate::emulator::regs::Reg32;
use crate::win32::Registry;
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_kernel32();
let state = HostState::new(0x4000, 0x8000);
(cpu, mmu, registry, state)
}
fn push_args_and_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 registers_at_least_twelve_kernel32_stubs() {
let mut r = Registry::new();
let n = r.register_kernel32();
assert!(n >= 12, "expected ≥ 12 round-1 stubs, got {n}");
}
#[test]
fn get_process_heap_returns_canned_handle() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"GetProcessHeap",
&[],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0xDEAD_BEEF);
}
#[test]
fn heap_alloc_then_heap_free_roundtrip() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"HeapAlloc",
&[0xDEAD_BEEF, 0, 64],
)
.unwrap();
let addr = cpu.regs.get32(Reg32::Eax);
assert_ne!(addr, 0);
assert!(state.heap.contains_key(&addr));
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"HeapFree",
&[0xDEAD_BEEF, 0, addr],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 1);
assert!(!state.heap.contains_key(&addr));
}
#[test]
fn heap_alloc_zero_fills_when_flag_set() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"HeapAlloc",
&[0xDEAD_BEEF, HEAP_ZERO_MEMORY, 16],
)
.unwrap();
let addr = cpu.regs.get32(Reg32::Eax);
for i in 0..16 {
assert_eq!(mmu.load8(addr + i).unwrap(), 0);
}
}
#[test]
fn heap_free_invalid_pointer_errors() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
let bad = 0xBAD_ADD00u32;
let r = push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"HeapFree",
&[0xDEAD_BEEF, 0, bad],
);
match r {
Err(crate::Error::Win32(Win32Error::InvalidHeapBlock { addr, .. })) if addr == bad => {}
other => panic!("expected InvalidHeapBlock, got {other:?}"),
}
}
#[test]
fn local_alloc_local_free() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"LocalAlloc",
&[LMEM_ZEROINIT, 32],
)
.unwrap();
let addr = cpu.regs.get32(Reg32::Eax);
assert_ne!(addr, 0);
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"LocalFree",
&[addr],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0);
}
#[test]
fn output_debug_string_a_logs() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
mmu.write(0x4000, b"hi\0").unwrap();
state.heap_cursor = 0x4010;
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"OutputDebugStringA",
&[0x4000],
)
.unwrap();
assert_eq!(state.debug_log.last().unwrap(), "hi");
}
#[test]
fn get_tick_count_monotonic() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"GetTickCount",
&[],
)
.unwrap();
let t1 = cpu.regs.get32(Reg32::Eax);
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"GetTickCount",
&[],
)
.unwrap();
let t2 = cpu.regs.get32(Reg32::Eax);
assert!(t2 > t1);
}
#[test]
fn interlocked_increment_decrement_roundtrip() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
mmu.store32(0x4000, 5).unwrap();
state.heap_cursor = 0x4010;
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"InterlockedIncrement",
&[0x4000],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 6);
assert_eq!(mmu.load32(0x4000).unwrap(), 6);
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"InterlockedDecrement",
&[0x4000],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 5);
}
#[test]
fn load_library_a_returns_known_module_or_null() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
state.modules.insert("kernel32.dll".into(), 0x10000);
let s = b"kernel32.dll\0";
mmu.write(0x4000, s).unwrap();
state.heap_cursor = 0x4020;
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"LoadLibraryA",
&[0x4000],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0x10000);
let s = b"unknown.dll\0";
mmu.write(0x4040, s).unwrap();
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"LoadLibraryA",
&[0x4040],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0);
}
#[test]
fn heap_realloc_preserves_old_bytes() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"HeapAlloc",
&[0xDEAD_BEEF, 0, 8],
)
.unwrap();
let addr = cpu.regs.get32(Reg32::Eax);
for i in 0..8u32 {
mmu.store8(addr + i, (i + 1) as u8).unwrap();
state.heap.get_mut(&addr).unwrap()[i as usize] = (i + 1) as u8;
}
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"HeapReAlloc",
&[0xDEAD_BEEF, 0, addr, 16],
)
.unwrap();
let new_addr = cpu.regs.get32(Reg32::Eax);
for i in 0..8u32 {
assert_eq!(mmu.load8(new_addr + i).unwrap(), (i + 1) as u8);
}
}
#[test]
fn find_resource_a_walks_synthetic_resource_directory() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
mmu.map(0x10000, 0x1000, Perm::R | Perm::W);
let rsrc_base = 0x10000u32;
mmu.store32(rsrc_base, 0).unwrap(); mmu.store32(rsrc_base + 4, 0).unwrap(); mmu.store32(rsrc_base + 8, 0).unwrap(); mmu.store16(rsrc_base + 12, 0).unwrap(); mmu.store16(rsrc_base + 14, 1).unwrap(); mmu.store32(rsrc_base + 16, 2).unwrap(); mmu.store32(rsrc_base + 20, 0x8000_0020).unwrap();
mmu.store16(rsrc_base + 0x20 + 12, 0).unwrap();
mmu.store16(rsrc_base + 0x20 + 14, 1).unwrap();
mmu.store32(rsrc_base + 0x20 + 16, 112).unwrap(); mmu.store32(rsrc_base + 0x20 + 20, 0x8000_0040).unwrap();
mmu.store16(rsrc_base + 0x40 + 12, 0).unwrap();
mmu.store16(rsrc_base + 0x40 + 14, 1).unwrap();
mmu.store32(rsrc_base + 0x40 + 16, 1033).unwrap(); mmu.store32(rsrc_base + 0x40 + 20, 0x60).unwrap();
mmu.store32(rsrc_base + 0x60, 0xC000).unwrap();
mmu.store32(rsrc_base + 0x60 + 4, 0x1234).unwrap();
let h_module = 0x10000000u32;
state.modules.insert("synth.dll".into(), h_module);
state.module_resource_dirs.insert(h_module, rsrc_base);
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"FindResourceA",
&[h_module, 112, 2],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), rsrc_base + 0x60);
}
#[test]
fn virtual_protect_flips_text_page_to_writable_and_back() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
let code = 0x20000u32;
mmu.map(code, 0x1000, Perm::R | Perm::X);
assert_eq!(mmu.perm_at(code), Some(Perm::R | Perm::X));
mmu.map(0x30000, 0x1000, Perm::R | Perm::W);
let old_out = 0x30000u32;
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"VirtualProtect",
&[code, 0x100, 0x04, old_out],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 1, "should return TRUE");
assert_eq!(mmu.perm_at(code), Some(Perm::R | Perm::W));
assert_eq!(mmu.load32(old_out).unwrap(), 0x20);
mmu.store32(code, 0xDEAD_BEEF).expect("writable after flip");
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"VirtualProtect",
&[code, 0x100, 0x20, old_out],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 1);
assert_eq!(mmu.perm_at(code), Some(Perm::R | Perm::X));
assert_eq!(mmu.load32(old_out).unwrap(), 0x04);
assert_eq!(mmu.load32(code).unwrap(), 0xDEAD_BEEF);
}
#[test]
fn virtual_protect_rejects_unmapped_address() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
mmu.map(0x30000, 0x1000, Perm::R | Perm::W);
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"VirtualProtect",
&[0x5000_0000, 0x100, 0x04, 0x30000],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0, "should return FALSE");
}
}