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",
"TryEnterCriticalSection",
stub_try_enter_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", "ExitThread", stub_exit_thread as StubFn, 1);
registry.register(
"kernel32.dll",
"SwitchToThread",
stub_switch_to_thread as StubFn,
0,
);
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", "CreatePipe", stub_create_pipe as StubFn, 4);
registry.register(
"kernel32.dll",
"CreateNamedPipeA",
stub_create_named_pipe_a as StubFn,
8,
);
registry.register(
"kernel32.dll",
"ConnectNamedPipe",
stub_connect_named_pipe as StubFn,
2,
);
registry.register(
"kernel32.dll",
"DisconnectNamedPipe",
stub_disconnect_named_pipe as StubFn,
1,
);
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,
);
registry.register(
"kernel32.dll",
"GetTempPathA",
stub_get_temp_path_a as StubFn,
2,
);
registry.register(
"kernel32.dll",
"GetTempFileNameA",
stub_get_temp_file_name_a as StubFn,
4,
);
registry.register(
"kernel32.dll",
"CreateFileA",
stub_create_file_a as StubFn,
7,
);
registry.register(
"kernel32.dll",
"CreateDirectoryA",
stub_create_directory_a as StubFn,
2,
);
registry.register(
"kernel32.dll",
"DeleteFileA",
stub_delete_file_a as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetFileAttributesA",
stub_get_file_attributes_a as StubFn,
1,
);
registry.register(
"kernel32.dll",
"GetFileSize",
stub_get_file_size as StubFn,
2,
);
registry.register(
"kernel32.dll",
"DosDateTimeToFileTime",
stub_dos_date_time_to_file_time as StubFn,
3,
);
registry.register(
"kernel32.dll",
"CreateMutexA",
stub_create_mutex_a as StubFn,
3,
);
registry.register(
"kernel32.dll",
"ReleaseMutex",
stub_release_mutex as StubFn,
1,
);
registry.register(
"kernel32.dll",
"CreateProcessA",
stub_create_process_a as StubFn,
10,
);
registry.register(
"kernel32.dll",
"GetExitCodeProcess",
stub_get_exit_code_process as StubFn,
2,
);
registry.register(
"kernel32.dll",
"GetConsoleCP",
stub_get_console_cp as StubFn,
0,
);
registry.register(
"kernel32.dll",
"GetConsoleOutputCP",
stub_get_console_cp as StubFn,
0,
);
registry.register(
"kernel32.dll",
"GetConsoleMode",
stub_get_console_mode as StubFn,
2,
);
registry.register(
"kernel32.dll",
"LocalFileTimeToFileTime",
stub_local_file_time_to_file_time as StubFn,
2,
);
registry.register(
"kernel32.dll",
"RemoveDirectoryA",
stub_remove_directory_a as StubFn,
1,
);
registry.register(
"kernel32.dll",
"SetEndOfFile",
stub_returns_true as StubFn,
1,
);
registry.register(
"kernel32.dll",
"SetFileAttributesA",
stub_returns_true as StubFn,
2,
);
registry.register(
"kernel32.dll",
"SetFileTime",
stub_returns_true as StubFn,
4,
);
registry.register(
"kernel32.dll",
"SetProcessWorkingSetSize",
stub_returns_true as StubFn,
3,
);
registry.register(
"kernel32.dll",
"WriteConsoleW",
stub_write_console_w as StubFn,
5,
);
registry.register(
"kernel32.dll",
"DuplicateHandle",
stub_duplicate_handle as StubFn,
7,
);
registry.register("kernel32.dll", "OpenEventA", stub_open_event_a as StubFn, 3);
registry.register("kernel32.dll", "OpenEventW", stub_open_event_w as StubFn, 3);
registry.register(
"kernel32.dll",
"OpenProcess",
stub_open_process 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);
state.cur_process_mut().exit_code = 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> {
let h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetFileType", t))?;
if matches!(h, 0xFFFF_FFF6 | 0xFFFF_FFF5 | 0xFFFF_FFF4) {
return Ok(2); }
if let Some(vfs) = state.context.vfs.as_ref() {
if vfs.owns(h) {
return Ok(1); }
}
Ok(0)
}
fn stub_get_last_error(
_cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let tib = state.cur_thread().tib_addr;
if tib != 0 {
if let Ok(v) = mmu.load32(tib + 0x34) {
return Ok(v);
}
}
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;
let tib = state.cur_thread().tib_addr;
if tib != 0 {
let _ = mmu.store32(tib + 0x34, 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 let Some(crate::sched::WaitObject::Pipe {
pipe_id,
is_read_end: false,
}) = state.scheduler.objects.get(&h).cloned()
{
let mut buf = vec![0u8; n as usize];
if lp_buf != 0 {
for (i, b) in buf.iter_mut().enumerate() {
*b = mmu
.load8(lp_buf + i as u32)
.map_err(|t| trap_to_win32("WriteFile", t))?;
}
}
if let Some(p) = state.scheduler.pipes.get_mut(&pipe_id) {
p.bytes.extend(buf.iter());
}
let read_handles: Vec<u32> = state
.scheduler
.objects
.iter()
.filter_map(|(handle, obj)| match obj {
crate::sched::WaitObject::Pipe {
pipe_id: pid,
is_read_end: true,
} if *pid == pipe_id => Some(*handle),
_ => None,
})
.collect();
for rh in read_handles {
for tid in crate::sched::waiters_on(&state.threads, rh) {
if let Some(t) = state.threads.get_mut(&tid) {
t.status = crate::sched::ThreadStatus::Ready;
t.wait = None;
}
}
}
if lp_written != 0 {
mmu.store32(lp_written, n)
.map_err(|t| trap_to_win32("WriteFile", t))?;
}
return Ok(1);
}
if let Some(vfs) = state.context.vfs.as_mut() {
if vfs.owns(h) && lp_buf != 0 {
let mut buf = vec![0u8; n as usize];
for (i, b) in buf.iter_mut().enumerate() {
*b = mmu
.load8(lp_buf + i as u32)
.map_err(|t| trap_to_win32("WriteFile", t))?;
}
let written = vfs.write_handle(h, &buf).unwrap_or(0) as u32;
if lp_written != 0 {
mmu.store32(lp_written, written)
.map_err(|t| trap_to_win32("WriteFile", t))?;
}
return Ok(1);
}
}
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> {
let h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("CloseHandle", t))?;
if let Some(vfs) = state.context.vfs.as_mut() {
if vfs.owns(h) {
vfs.close(h);
return Ok(1);
}
}
if let Some(reg) = state.context.registry.as_mut() {
if reg.owns(h) {
reg.close_key(h);
return Ok(1);
}
}
if let Some(crate::sched::WaitObject::Pipe {
pipe_id,
is_read_end,
}) = state.scheduler.objects.get(&h).cloned()
{
if let Some(p) = state.scheduler.pipes.get_mut(&pipe_id) {
p.closed_ends = p.closed_ends.saturating_add(1);
}
if !is_read_end {
let read_handles: Vec<u32> = state
.scheduler
.objects
.iter()
.filter_map(|(handle, obj)| match obj {
crate::sched::WaitObject::Pipe {
pipe_id: pid,
is_read_end: true,
} if *pid == pipe_id => Some(*handle),
_ => None,
})
.collect();
for rh in read_handles {
for tid in crate::sched::waiters_on(&state.threads, rh) {
if let Some(t) = state.threads.get_mut(&tid) {
t.status = crate::sched::ThreadStatus::Ready;
t.wait = None;
}
}
}
}
state.scheduler.objects.remove(&h);
return Ok(1);
}
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> {
let _attrs = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("CreateSemaphoreA", t))?;
let init = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreateSemaphoreA", t))?;
let max = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CreateSemaphoreA", t))?;
let _name = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("CreateSemaphoreA", t))?;
Ok(state
.scheduler
.insert_object(crate::sched::WaitObject::Semaphore { count: init, max }))
}
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> {
let p = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("EnterCriticalSection", t))?;
if p == 0 {
return Ok(0);
}
let cur_tid = state.active_tid;
let h = state.scheduler.critical_section_handle(p);
let take_immediately = matches!(
state.scheduler.objects.get(&h),
Some(crate::sched::WaitObject::CriticalSection { owner: None, .. })
) || matches!(
state.scheduler.objects.get(&h),
Some(crate::sched::WaitObject::CriticalSection { owner: Some(o), .. }) if *o == cur_tid
);
if take_immediately {
if let Some(crate::sched::WaitObject::CriticalSection {
owner, recursion, ..
}) = state.scheduler.objects.get_mut(&h)
{
if owner.is_some() {
*recursion += 1;
} else {
*owner = Some(cur_tid);
*recursion = 1;
}
}
return Ok(0);
}
state.yield_requested = Some(crate::sched::YieldRequest::Wait(
crate::sched::WaitCondition::Object {
handle: h,
timeout_after: None,
},
));
Ok(0)
}
fn stub_leave_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("LeaveCriticalSection", t))?;
if p == 0 {
return Ok(0);
}
let Some(&h) = state.scheduler.critical_sections.get(&p) else {
return Ok(0);
};
let cur_tid = state.active_tid;
let release_now =
if let Some(crate::sched::WaitObject::CriticalSection {
owner, recursion, ..
}) = state.scheduler.objects.get_mut(&h)
{
if *owner != Some(cur_tid) {
return Ok(0);
}
*recursion = recursion.saturating_sub(1);
if *recursion == 0 {
*owner = None;
true
} else {
false
}
} else {
false
};
if release_now {
let waiters = crate::sched::waiters_on(&state.threads, h);
if let Some(&tid) = waiters.first() {
wake_thread(state, tid, h);
}
}
Ok(0)
}
fn stub_try_enter_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("TryEnterCriticalSection", t))?;
if p == 0 {
return Ok(0);
}
let cur_tid = state.active_tid;
let h = state.scheduler.critical_section_handle(p);
if let Some(crate::sched::WaitObject::CriticalSection {
owner, recursion, ..
}) = state.scheduler.objects.get_mut(&h)
{
match *owner {
None => {
*owner = Some(cur_tid);
*recursion = 1;
return Ok(1);
}
Some(o) if o == cur_tid => {
*recursion += 1;
return Ok(1);
}
_ => return Ok(0),
}
}
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
}
fn rsrc_dir_lookup_name(
mmu: &Mmu,
rsrc_base: u32,
dir_va: u32,
target: &str,
) -> Option<(u32, bool)> {
let n_named = mmu.load16(dir_va + 12).ok()? as u32;
let entries_va = dir_va + 16;
let target_upper: Vec<u16> = target.to_ascii_uppercase().encode_utf16().collect();
for i in 0..n_named {
let e_va = entries_va + i * 8;
let name = mmu.load32(e_va).ok()?;
if (name & 0x8000_0000) == 0 {
continue;
}
let str_va = rsrc_base + (name & 0x7FFF_FFFF);
let len = mmu.load16(str_va).ok()? as u32;
if len as usize != target_upper.len() {
continue;
}
let mut matches = true;
for j in 0..len {
let w = mmu.load16(str_va + 2 + j * 2).ok()?;
let upper = (w as u8).to_ascii_uppercase() as u16; if upper != target_upper[j as usize] {
matches = false;
break;
}
}
if matches {
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)?;
let (off_type, is_dir) = if lp_type & 0xFFFF_0000 != 0 {
let name = read_cstr(mmu, lp_type, 260).ok()?;
rsrc_dir_lookup_name(mmu, rsrc_base, rsrc_base, &name)?
} else {
rsrc_dir_lookup_id(mmu, rsrc_base, rsrc_base, lp_type)?
};
if !is_dir {
return None;
}
let (off_name, is_dir) = if lp_name & 0xFFFF_0000 != 0 {
let name = read_cstr(mmu, lp_name, 260).ok()?;
rsrc_dir_lookup_name(mmu, rsrc_base, rsrc_base + off_type, &name)?
} else {
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(state.active_tid)
}
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> {
let h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("ReleaseSemaphore", t))?;
let release = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("ReleaseSemaphore", t))?;
let lp_prev = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("ReleaseSemaphore", t))?;
let Some(crate::sched::WaitObject::Semaphore { count, max }) =
state.scheduler.objects.get_mut(&h)
else {
return Ok(0);
};
let prev = *count;
*count = count.saturating_add(release).min(*max);
if lp_prev != 0 {
mmu.store32(lp_prev, prev)
.map_err(|t| trap_to_win32("ReleaseSemaphore", t))?;
}
let waiters = crate::sched::waiters_on(&state.threads, h);
for tid in waiters.into_iter().take(release as usize) {
wake_thread(state, tid, h);
}
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> {
let ms = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("Sleep", t))?;
let now = state.scheduler.instructions_global;
let resume_after_instructions =
now.saturating_add(u64::from(ms).saturating_mul(crate::sched::INSTRUCTIONS_PER_MS));
state.yield_requested = Some(crate::sched::YieldRequest::Wait(
crate::sched::WaitCondition::Sleep {
resume_after_instructions,
},
));
state.tick = state.tick.wrapping_add(ms);
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))?;
if code == 0xC000_0409 {
state.debug_log.push(format!(
"TerminateProcess(STATUS_STACK_BUFFER_OVERRUN) — \
treating as spurious GS check, continuing"
));
return Ok(1);
}
let target_pid = if h == 0xFFFF_FFFF {
state.active_pid
} else if let Some(crate::sched::WaitObject::Process { pid }) = state.scheduler.objects.get(&h)
{
*pid
} else {
state.active_pid
};
if let Some(p) = state.processes.get_mut(&target_pid) {
p.exit_code = Some(code);
}
let process_handles: Vec<u32> = state
.scheduler
.objects
.iter()
.filter_map(|(handle, obj)| match obj {
crate::sched::WaitObject::Process { pid } if *pid == target_pid => Some(*handle),
_ => None,
})
.collect();
for h in process_handles {
for tid in crate::sched::waiters_on(&state.threads, h) {
if let Some(t) = state.threads.get_mut(&tid) {
t.status = crate::sched::ThreadStatus::Ready;
t.wait = None;
}
}
}
if target_pid == state.active_pid {
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> {
let proc_ = state.cur_process_mut();
let slot = proc_.next_tls_slot;
proc_.next_tls_slot = proc_.next_tls_slot.wrapping_add(1);
Ok(slot)
}
fn stub_tls_free(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let idx = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("TlsFree", t))?;
for t in state.threads.values_mut() {
t.tls_slots.remove(&idx);
}
Ok(1)
}
fn stub_tls_get_value(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let idx = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("TlsGetValue", t))?;
Ok(state.cur_thread().tls_slots.get(&idx).copied().unwrap_or(0))
}
fn stub_tls_set_value(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let idx = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("TlsSetValue", t))?;
let value = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("TlsSetValue", t))?;
state.cur_thread_mut().tls_slots.insert(idx, value);
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> {
let h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("WaitForSingleObject", t))?;
let ms = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("WaitForSingleObject", t))?;
if !state.scheduler.objects.contains_key(&h) {
return Ok(0);
}
let cur_tid = state.active_tid;
let signaled = match state.scheduler.objects.get(&h) {
Some(crate::sched::WaitObject::Thread { tid }) => state
.threads
.get(tid)
.map(|t| matches!(t.status, crate::sched::ThreadStatus::Terminated))
.unwrap_or(true),
Some(crate::sched::WaitObject::Process { pid }) => state
.processes
.get(pid)
.map(|p| p.exit_code.is_some())
.unwrap_or(true),
Some(obj) => crate::sched::object_is_signaled(obj),
None => false,
};
if signaled {
if let Some(obj) = state.scheduler.objects.get_mut(&h) {
crate::sched::consume_signal_if_auto_reset(obj, cur_tid);
}
return Ok(0);
}
let timeout_after = if ms == 0xFFFF_FFFF {
None
} else {
Some(
state
.scheduler
.instructions_global
.saturating_add(u64::from(ms).saturating_mul(crate::sched::INSTRUCTIONS_PER_MS)),
)
};
state.yield_requested = Some(crate::sched::YieldRequest::Wait(
crate::sched::WaitCondition::Object {
handle: h,
timeout_after,
},
));
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 p_name = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("CreateEventA", t))?;
let name = if p_name != 0 {
read_cstr(mmu, p_name, 260).ok()
} else {
None
};
if let Some(n) = &name {
if let Some(h) = state.scheduler.lookup_named(n) {
state.last_error = 183;
return Ok(h);
}
}
let h = state
.scheduler
.insert_object(crate::sched::WaitObject::Event {
signaled: init != 0,
manual_reset: manual != 0,
});
if let Some(n) = name {
state.scheduler.register_named(&n, h);
}
Ok(h)
}
fn stub_create_thread(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
use crate::emulator::isa_int::RET_SENTINEL;
use crate::sched::{ThreadStatus, WaitObject};
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))?;
if start == 0 {
return Ok(0);
}
let arena_top = state.cur_process().next_thread_stack_top;
let arena_bottom = state.cur_process().thread_stack_pool_bottom;
if arena_top == 0 || arena_top.saturating_sub(arena_bottom) < crate::win32::THREAD_STACK_SIZE {
return Err(Win32Error::InvalidArgument {
stub: "CreateThread",
reason:
"thread-stack pool exhausted (configure with HostState::with_thread_stack_pool)"
.into(),
});
}
let stack_top = arena_top;
state.cur_process_mut().next_thread_stack_top = stack_top - crate::win32::THREAD_STACK_SIZE;
let tib_addr = if state.cur_process().tib_pool_bottom != 0 {
let addr = state.cur_process().next_tib_addr;
state.cur_process_mut().next_tib_addr = addr.wrapping_add(crate::win32::THREAD_TIB_SIZE);
let _ = mmu.store32(addr, 0xFFFF_FFFF);
let _ = mmu.store32(addr + 0x18, addr);
addr
} else {
0
};
let mut new_cpu = Cpu::new();
new_cpu.regs.set_esp(stack_top - 0x10); new_cpu.regs.eip = start;
if tib_addr != 0 {
new_cpu.set_fs_base(tib_addr);
}
new_cpu
.push32(mmu, param)
.map_err(|t| trap_to_win32("CreateThread", t))?;
new_cpu
.push32(mmu, RET_SENTINEL)
.map_err(|t| trap_to_win32("CreateThread", t))?;
let tid = state.next_tid;
state.next_tid = state.next_tid.wrapping_add(1);
let pid = state.cur_thread().pid;
let mut t = crate::win32::ThreadState::new(tid, pid);
t.parked_cpu = Some(new_cpu);
t.tib_addr = tib_addr;
t.status = if (flags & CREATE_SUSPENDED) != 0 {
ThreadStatus::Suspended
} else {
ThreadStatus::Ready
};
state.threads.insert(tid, t);
let handle = state.scheduler.insert_object(WaitObject::Thread { tid });
if p_tid != 0 {
mmu.store32(p_tid, tid)
.map_err(|t| trap_to_win32("CreateThread", t))?;
}
Ok(handle)
}
fn stub_exit_thread(
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("ExitThread", t))?;
state.yield_requested = Some(crate::sched::YieldRequest::Exit { code });
Ok(0)
}
fn stub_set_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("SetEvent", t))?;
let Some(crate::sched::WaitObject::Event {
signaled,
manual_reset,
}) = state.scheduler.objects.get_mut(&h)
else {
return Ok(0);
};
*signaled = true;
let manual = *manual_reset;
let waiters = crate::sched::waiters_on(&state.threads, h);
if manual {
for tid in waiters {
wake_thread(state, tid, h);
}
} else if let Some(&tid) = waiters.first() {
wake_thread(state, tid, h);
}
Ok(1)
}
fn wake_thread(state: &mut HostState, tid: u32, handle: u32) {
if let Some(obj) = state.scheduler.objects.get_mut(&handle) {
crate::sched::consume_signal_if_auto_reset(obj, tid);
}
if let Some(t) = state.threads.get_mut(&tid) {
t.status = crate::sched::ThreadStatus::Ready;
t.wait = None;
}
}
fn stub_set_thread_priority(
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("SetThreadPriority", t))?;
let prio = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("SetThreadPriority", t))? as i32;
if let Some(crate::sched::WaitObject::Thread { tid }) = state.scheduler.objects.get(&h) {
let tid = *tid;
if let Some(t) = state.threads.get_mut(&tid) {
t.priority = prio;
}
}
Ok(1)
}
fn stub_switch_to_thread(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let cur_tid = state.active_tid;
let has_peer = state
.threads
.iter()
.any(|(tid, t)| *tid != cur_tid && matches!(t.status, crate::sched::ThreadStatus::Ready));
if has_peer {
state.yield_requested = Some(crate::sched::YieldRequest::Yield);
Ok(1)
} else {
Ok(0)
}
}
fn stub_resume_thread(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
use crate::sched::{ThreadStatus, WaitObject};
let h = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("ResumeThread", t))?;
let tid = match state.scheduler.objects.get(&h) {
Some(WaitObject::Thread { tid }) => *tid,
_ => return Ok(0xFFFF_FFFF),
};
let Some(t) = state.threads.get_mut(&tid) else {
return Ok(0xFFFF_FFFF);
};
if matches!(t.status, ThreadStatus::Suspended) {
t.status = ThreadStatus::Ready;
return Ok(1);
}
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(state.cur_thread().pid)
}
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))?;
if let Some(crate::sched::WaitObject::Event { signaled, .. }) =
state.scheduler.objects.get_mut(&h)
{
*signaled = false;
}
Ok(1)
}
fn stub_wait_for_multiple_objects(
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("WaitForMultipleObjects", t))?;
let p_handles =
arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("WaitForMultipleObjects", t))?;
let wait_all =
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))?;
if n == 0 || p_handles == 0 {
return Ok(0);
}
let mut handles = Vec::with_capacity(n as usize);
for i in 0..n {
handles.push(
mmu.load32(p_handles + i * 4)
.map_err(|t| trap_to_win32("WaitForMultipleObjects", t))?,
);
}
let signaled_now: Vec<bool> = handles
.iter()
.map(|h| match state.scheduler.objects.get(h) {
Some(obj) => crate::sched::object_is_signaled(obj),
None => true,
})
.collect();
let cur_tid = state.active_tid;
if wait_all != 0 {
if signaled_now.iter().all(|x| *x) {
for h in &handles {
if let Some(obj) = state.scheduler.objects.get_mut(h) {
crate::sched::consume_signal_if_auto_reset(obj, cur_tid);
}
}
return Ok(0);
}
} else if let Some(idx) = signaled_now.iter().position(|x| *x) {
if let Some(obj) = state.scheduler.objects.get_mut(&handles[idx]) {
crate::sched::consume_signal_if_auto_reset(obj, cur_tid);
}
return Ok(idx as u32);
}
let timeout_after = if ms == 0xFFFF_FFFF {
None
} else {
Some(
state
.scheduler
.instructions_global
.saturating_add(u64::from(ms).saturating_mul(crate::sched::INSTRUCTIONS_PER_MS)),
)
};
state.yield_requested = Some(crate::sched::YieldRequest::Wait(
crate::sched::WaitCondition::Multiple {
handles,
wait_all: wait_all != 0,
timeout_after,
},
));
Ok(0)
}
fn stub_create_event_w(
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("CreateEventW", t))?;
let manual = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreateEventW", t))?;
let init = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CreateEventW", t))?;
let p_name = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("CreateEventW", t))?;
let name = if p_name != 0 {
Some(super::read_wide_cstr_local(mmu, p_name, 260))
} else {
None
};
if let Some(n) = &name {
if let Some(h) = state.scheduler.lookup_named(n) {
state.last_error = 183;
return Ok(h);
}
}
let h = state
.scheduler
.insert_object(crate::sched::WaitObject::Event {
signaled: init != 0,
manual_reset: manual != 0,
});
if let Some(n) = &name {
state.scheduler.register_named(n, h);
}
Ok(h)
}
fn stub_create_semaphore_w(
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("CreateSemaphoreW", t))?;
let init = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreateSemaphoreW", t))?;
let max = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CreateSemaphoreW", t))?;
let _name = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("CreateSemaphoreW", t))?;
Ok(state
.scheduler
.insert_object(crate::sched::WaitObject::Semaphore { count: init, max }))
}
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 let Some(crate::sched::WaitObject::Pipe {
pipe_id,
is_read_end: true,
}) = state.scheduler.objects.get(&h).cloned()
{
let (have_bytes, writer_open) = state
.scheduler
.pipes
.get(&pipe_id)
.map(|p| (!p.bytes.is_empty(), p.closed_ends < 2))
.unwrap_or((false, false));
if !have_bytes && writer_open {
state.yield_requested = Some(crate::sched::YieldRequest::Wait(
crate::sched::WaitCondition::Object {
handle: h,
timeout_after: None,
},
));
if out != 0 {
mmu.store32(out, 0)
.map_err(|t| trap_to_win32("ReadFile", t))?;
}
return Ok(1);
}
let want = n as usize;
let mut tmp = Vec::with_capacity(want);
if let Some(p) = state.scheduler.pipes.get_mut(&pipe_id) {
for _ in 0..want {
if let Some(b) = p.bytes.pop_front() {
tmp.push(b);
} else {
break;
}
}
}
if !tmp.is_empty() && buf != 0 {
mmu.write(buf, &tmp)
.map_err(|t| trap_to_win32("ReadFile", t))?;
}
if out != 0 {
mmu.store32(out, tmp.len() as u32)
.map_err(|t| trap_to_win32("ReadFile", t))?;
}
return Ok(1);
}
if let Some(vfs) = state.context.vfs.as_mut() {
if vfs.owns(h) && buf != 0 {
let mut tmp = vec![0u8; n as usize];
let got = vfs.read_handle(h, &mut tmp).unwrap_or(0);
if got > 0 {
mmu.write(buf, &tmp[..got])
.map_err(|t| trap_to_win32("ReadFile", t))?;
}
if out != 0 {
mmu.store32(out, got as u32)
.map_err(|t| trap_to_win32("ReadFile", t))?;
}
return Ok(1);
}
}
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)
}
const TEMP_PATH_A: &[u8] = b"C:\\Temp\\\0";
fn write_cstr(
mmu: &mut Mmu,
dst: u32,
cch: u32,
s: &[u8],
stub: &'static str,
) -> Result<u32, Win32Error> {
if dst == 0 || cch == 0 {
return Ok(0);
}
let cap = cch.saturating_sub(1) as usize;
let n = s.len().min(cap);
if n > 0 {
mmu.write(dst, &s[..n])
.map_err(|t| trap_to_win32(stub, t))?;
}
mmu.store8(dst + n as u32, 0)
.map_err(|t| trap_to_win32(stub, t))?;
Ok(n as u32)
}
fn stub_get_temp_path_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let n_buf = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetTempPathA", t))?;
let lp_buf = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetTempPathA", t))?;
let path = &TEMP_PATH_A[..TEMP_PATH_A.len() - 1]; let need = path.len() as u32 + 1; if lp_buf == 0 || n_buf < need {
return Ok(need);
}
write_cstr(mmu, lp_buf, n_buf, path, "GetTempPathA")?;
Ok(path.len() as u32)
}
fn stub_get_temp_file_name_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p_path = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetTempFileNameA", t))?;
let p_prefix = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetTempFileNameA", t))?;
let mut unique = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("GetTempFileNameA", t))?;
let dst = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("GetTempFileNameA", t))?;
let path = if p_path != 0 {
read_cstr(mmu, p_path, 260)?
} else {
String::new()
};
let prefix = if p_prefix != 0 {
read_cstr(mmu, p_prefix, 3)?
} else {
String::new()
};
if unique == 0 {
state.tick = state.tick.wrapping_add(1);
unique = state.tick;
}
let trimmed = path.trim_end_matches(['\\', '/']);
let full = if trimmed.is_empty() {
format!("{prefix}{unique:04X}.tmp")
} else {
format!("{trimmed}\\{prefix}{unique:04X}.tmp")
};
if let Some(vfs) = state.context.vfs.as_mut() {
vfs.insert(&full, Vec::new());
}
write_cstr(mmu, dst, 260, full.as_bytes(), "GetTempFileNameA")?;
Ok(unique)
}
const CREATE_NEW: u32 = 1;
const CREATE_ALWAYS: u32 = 2;
const OPEN_EXISTING: u32 = 3;
const TRUNCATE_EXISTING: u32 = 5;
const INVALID_HANDLE_VALUE: u32 = 0xFFFF_FFFF;
const ERROR_FILE_NOT_FOUND: u32 = 2;
const ERROR_FILE_EXISTS: u32 = 80;
fn stub_create_file_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p_name = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("CreateFileA", t))?;
let access = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreateFileA", t))?;
let _share = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CreateFileA", t))?;
let _sa = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("CreateFileA", t))?;
let disp = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("CreateFileA", t))?;
let _attrs = arg_dword(cpu, mmu, 5).map_err(|t| trap_to_win32("CreateFileA", t))?;
let _tmpl = arg_dword(cpu, mmu, 6).map_err(|t| trap_to_win32("CreateFileA", t))?;
if p_name == 0 {
state.last_error = ERROR_FILE_NOT_FOUND;
return Ok(INVALID_HANDLE_VALUE);
}
let name = read_cstr(mmu, p_name, 260)?;
if let Some(pipe_name) = parse_pipe_client_path(&name) {
if let Some(server_handle) = state.scheduler.lookup_named(&pipe_name) {
let pipe_id = match state.scheduler.objects.get(&server_handle) {
Some(crate::sched::WaitObject::Pipe { pipe_id, .. }) => *pipe_id,
_ => {
state.last_error = ERROR_FILE_NOT_FOUND;
return Ok(INVALID_HANDLE_VALUE);
}
};
let is_read_end = !matches!(
state.scheduler.objects.get(&server_handle),
Some(crate::sched::WaitObject::Pipe {
is_read_end: true,
..
})
);
return Ok(state
.scheduler
.insert_object(crate::sched::WaitObject::Pipe {
pipe_id,
is_read_end,
}));
}
state.last_error = ERROR_FILE_NOT_FOUND;
return Ok(INVALID_HANDLE_VALUE);
}
let Some(vfs) = state.context.vfs.as_mut() else {
state.last_error = ERROR_FILE_NOT_FOUND;
return Ok(INVALID_HANDLE_VALUE);
};
let exists = vfs.contains(&name);
match disp {
OPEN_EXISTING => {
if !exists {
state.last_error = ERROR_FILE_NOT_FOUND;
return Ok(INVALID_HANDLE_VALUE);
}
}
CREATE_NEW => {
if exists {
state.last_error = ERROR_FILE_EXISTS;
return Ok(INVALID_HANDLE_VALUE);
}
vfs.insert(&name, Vec::new());
}
CREATE_ALWAYS | TRUNCATE_EXISTING => {
vfs.write_path(&name, Vec::new());
}
_ => {
if !exists {
vfs.insert(&name, Vec::new());
}
}
}
let fa = crate::context::FileAccess::from_win32_desired_access(access);
match vfs.open(&name, fa) {
Some(h) => Ok(h),
None => {
state.last_error = ERROR_FILE_NOT_FOUND;
Ok(INVALID_HANDLE_VALUE)
}
}
}
fn stub_create_directory_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p_name = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("CreateDirectoryA", t))?;
let _sa = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreateDirectoryA", t))?;
if p_name == 0 {
state.last_error = ERROR_FILE_NOT_FOUND;
return Ok(0);
}
let name = read_cstr(mmu, p_name, 260)?;
let marker = format!("{}\\.dir", name.trim_end_matches(['\\', '/']));
if let Some(vfs) = state.context.vfs.as_mut() {
if vfs.contains(&marker) {
state.last_error = ERROR_FILE_EXISTS;
return Ok(0);
}
vfs.insert(&marker, Vec::new());
}
Ok(1)
}
fn stub_delete_file_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p_name = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("DeleteFileA", t))?;
if p_name == 0 {
state.last_error = ERROR_FILE_NOT_FOUND;
return Ok(0);
}
let name = read_cstr(mmu, p_name, 260)?;
if let Some(vfs) = state.context.vfs.as_mut() {
vfs.remove(&name);
}
Ok(1)
}
fn stub_get_file_attributes_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p_name = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("GetFileAttributesA", t))?;
if p_name == 0 {
state.last_error = ERROR_FILE_NOT_FOUND;
return Ok(0xFFFF_FFFF);
}
let name = read_cstr(mmu, p_name, 260)?;
let Some(vfs) = state.context.vfs.as_ref() else {
state.last_error = ERROR_FILE_NOT_FOUND;
return Ok(0xFFFF_FFFF);
};
if vfs.contains(&name) {
return Ok(0x80); }
let dir_marker = format!("{}\\.dir", name.trim_end_matches(['\\', '/']));
if vfs.contains(&dir_marker) {
return Ok(0x10); }
state.last_error = ERROR_FILE_NOT_FOUND;
Ok(0xFFFF_FFFF)
}
fn stub_get_file_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("GetFileSize", t))?;
let p_hi = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetFileSize", t))?;
if let Some(vfs) = state.context.vfs.as_ref() {
if let Some(sz) = vfs.size(h) {
if p_hi != 0 {
mmu.store32(p_hi, (sz >> 32) as u32)
.map_err(|t| trap_to_win32("GetFileSize", t))?;
}
return Ok(sz as u32);
}
}
state.last_error = ERROR_INVALID_HANDLE;
Ok(0xFFFF_FFFF)
}
fn stub_dos_date_time_to_file_time(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let fat_date =
arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("DosDateTimeToFileTime", t))? & 0xFFFF;
let fat_time =
arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("DosDateTimeToFileTime", t))? & 0xFFFF;
let p_out = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("DosDateTimeToFileTime", t))?;
if p_out == 0 {
return Ok(0);
}
let year = 1980 + ((fat_date >> 9) & 0x7F) as i32;
let month = ((fat_date >> 5) & 0x0F).max(1) as i32;
let day = (fat_date & 0x1F).max(1) as i32;
let hour = ((fat_time >> 11) & 0x1F) as i32;
let minute = ((fat_time >> 5) & 0x3F) as i32;
let second = ((fat_time & 0x1F) * 2) as i32;
let days_from_1601 = (year - 1601) as i64 * 365 + ((year - 1601) / 4) as i64
- ((year - 1601) / 100) as i64
+ ((year - 1601) / 400) as i64;
let month_days = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
let mut day_of_year = month_days[(month - 1).clamp(0, 11) as usize] as i64;
if month > 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) {
day_of_year += 1;
}
day_of_year += day as i64 - 1;
let total_days = days_from_1601 + day_of_year;
let total_seconds =
total_days * 86_400 + hour as i64 * 3600 + minute as i64 * 60 + second as i64;
let ticks = (total_seconds * 10_000_000) as u64;
mmu.store32(p_out, ticks as u32)
.map_err(|t| trap_to_win32("DosDateTimeToFileTime", t))?;
mmu.store32(p_out + 4, (ticks >> 32) as u32)
.map_err(|t| trap_to_win32("DosDateTimeToFileTime", t))?;
Ok(1)
}
fn stub_create_mutex_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("CreateMutexA", t))?;
let init_owner = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreateMutexA", t))?;
let _name = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CreateMutexA", t))?;
let owner = if init_owner != 0 {
Some(state.active_tid)
} else {
None
};
let recursion = if init_owner != 0 { 1 } else { 0 };
Ok(state
.scheduler
.insert_object(crate::sched::WaitObject::Mutex { owner, recursion }))
}
fn stub_release_mutex(
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("ReleaseMutex", t))?;
let cur_tid = state.active_tid;
let Some(crate::sched::WaitObject::Mutex { owner, recursion }) =
state.scheduler.objects.get_mut(&h)
else {
return Ok(0);
};
if *owner != Some(cur_tid) {
return Ok(0);
}
*recursion = recursion.saturating_sub(1);
if *recursion == 0 {
*owner = None;
let waiters = crate::sched::waiters_on(&state.threads, h);
if let Some(&tid) = waiters.first() {
wake_thread(state, tid, h);
}
}
Ok(1)
}
fn try_spawn_child_pe(
state: &mut HostState,
mmu: &mut Mmu,
registry: &Registry,
target_path: &str,
cmd: &str,
) -> Option<(u32, u32)> {
use crate::emulator::isa_int::RET_SENTINEL;
use crate::emulator::mmu::Perm;
use crate::pe;
use crate::sched::ThreadStatus;
let vfs = state.context.vfs.as_ref()?;
let bytes = vfs.read(target_path)?.to_vec();
let parsed = pe::header::parse(&bytes).ok()?;
if (parsed.file.characteristics & 0x0001) != 0
&& parsed.optional.image_base == state.processes[&1].image_base
{
return None;
}
let child_image_base = state.next_child_image_base;
if child_image_base == 0 {
return None;
}
state.next_child_image_base = child_image_base.wrapping_add(crate::win32::CHILD_IMAGE_STRIDE);
let heap_start = state.next_child_heap_base;
let heap_end = heap_start.wrapping_add(crate::win32::CHILD_HEAP_SIZE);
if heap_start == 0 || heap_end > state.child_heap_arena_end {
return None;
}
state.next_child_heap_base = heap_end;
let secs = pe::sections::map_sections_at(mmu, &parsed, &bytes, child_image_base).ok()?;
let delta = child_image_base.wrapping_sub(parsed.optional.image_base);
if delta != 0 {
pe::reloc::apply(mmu, &parsed, child_image_base, delta).ok()?;
}
pe::imports::resolve_strict(mmu, &parsed, child_image_base, registry).ok()?;
for s in &secs {
pe::sections::apply_section_permissions(mmu, s);
}
let stack_top = state.cur_process().next_thread_stack_top;
let stack_bottom = state.cur_process().thread_stack_pool_bottom;
if stack_top == 0 || stack_top.saturating_sub(stack_bottom) < crate::win32::THREAD_STACK_SIZE {
return None;
}
state.cur_process_mut().next_thread_stack_top = stack_top - crate::win32::THREAD_STACK_SIZE;
let tib_addr = if state.cur_process().tib_pool_bottom != 0 {
let addr = state.cur_process().next_tib_addr;
state.cur_process_mut().next_tib_addr = addr.wrapping_add(crate::win32::THREAD_TIB_SIZE);
let _ = mmu.store32(addr, 0xFFFF_FFFF);
let _ = mmu.store32(addr + 0x18, addr);
addr
} else {
0
};
let parent_pid = state.cur_thread().pid;
let child_pid = state.next_pid;
state.next_pid = state.next_pid.wrapping_add(1);
let child_tid = state.next_tid;
state.next_tid = state.next_tid.wrapping_add(1);
let entry_point = child_image_base.wrapping_add(parsed.optional.address_of_entry_point);
let _ = Perm::R; let mut child_proc = crate::win32::ProcessState {
pid: child_pid,
parent_pid,
image_base: child_image_base,
primary_module_base: child_image_base,
process_heap_handle: 0xDEAD_BEEF,
next_hic: 1,
rand_state: 1,
heap_cursor: heap_start,
heap_arena_end: heap_end,
exit_code: None,
..crate::win32::ProcessState::default()
};
child_proc.thread_stack_pool_bottom = stack_bottom;
child_proc.next_thread_stack_top = state.cur_process().next_thread_stack_top;
child_proc.tib_pool_bottom = state.cur_process().tib_pool_bottom;
child_proc.next_tib_addr = state.cur_process().next_tib_addr;
child_proc
.modules
.insert(target_path.to_ascii_lowercase(), child_image_base);
if let Some(rsrc_dir) = parsed.optional.data_directories.get(2) {
if rsrc_dir.virtual_address != 0 && rsrc_dir.size != 0 {
child_proc.module_resource_dirs.insert(
child_image_base,
child_image_base.wrapping_add(rsrc_dir.virtual_address),
);
}
}
state.processes.insert(child_pid, child_proc);
let mut new_cpu = Cpu::new();
new_cpu.regs.set_esp(stack_top - 0x10);
new_cpu.regs.eip = entry_point;
if tib_addr != 0 {
new_cpu.set_fs_base(tib_addr);
}
new_cpu.push32(mmu, RET_SENTINEL).ok()?;
let mut child_thread = crate::win32::ThreadState::new(child_tid, child_pid);
child_thread.parked_cpu = Some(new_cpu);
child_thread.tib_addr = tib_addr;
child_thread.status = ThreadStatus::Ready;
state.threads.insert(child_tid, child_thread);
state
.debug_log
.push(format!("spawned child PE {target_path:?} (cmd={cmd:?}) pid={child_pid} base={child_image_base:#010x}"));
Some((child_pid, child_tid))
}
fn stub_create_process_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
registry: &Registry,
) -> Result<u32, Win32Error> {
use crate::sched::{ThreadStatus, WaitObject};
let p_app = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("CreateProcessA", t))?;
let p_cmd = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreateProcessA", t))?;
let _attrs_p = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CreateProcessA", t))?;
let _attrs_t = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("CreateProcessA", t))?;
let _inh = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("CreateProcessA", t))?;
let _flags = arg_dword(cpu, mmu, 5).map_err(|t| trap_to_win32("CreateProcessA", t))?;
let _env = arg_dword(cpu, mmu, 6).map_err(|t| trap_to_win32("CreateProcessA", t))?;
let _cwd = arg_dword(cpu, mmu, 7).map_err(|t| trap_to_win32("CreateProcessA", t))?;
let _si = arg_dword(cpu, mmu, 8).map_err(|t| trap_to_win32("CreateProcessA", t))?;
let pi = arg_dword(cpu, mmu, 9).map_err(|t| trap_to_win32("CreateProcessA", t))?;
let app = if p_app != 0 {
read_cstr(mmu, p_app, 260).unwrap_or_default()
} else {
String::new()
};
let cmd = if p_cmd != 0 {
read_cstr(mmu, p_cmd, 4096).unwrap_or_default()
} else {
String::new()
};
state
.debug_log
.push(format!("CreateProcessA(app={app:?}, cmd={cmd:?})"));
let target = if !app.is_empty() {
app.clone()
} else if !cmd.is_empty() {
let first = cmd.split_whitespace().next().unwrap_or("");
first.trim_matches('"').to_string()
} else {
String::new()
};
if super::msiexec::is_msiexec_target(&target) {
super::msiexec::dispatch_msiexec_install(state, mmu, &target, &cmd);
}
let (child_pid, child_tid) = match (!target.is_empty())
.then(|| try_spawn_child_pe(state, mmu, registry, &target, &cmd))
.flatten()
{
Some(pair) => pair,
None => {
state.debug_log.push(format!(
"CreateProcessA: child {target:?} not loadable from VFS — \
falling back to synthetic immediate-exit"
));
let parent_pid = state.cur_thread().pid;
let pid = state.next_pid;
state.next_pid = state.next_pid.wrapping_add(1);
let tid = state.next_tid;
state.next_tid = state.next_tid.wrapping_add(1);
let child_proc = crate::win32::ProcessState {
pid,
parent_pid,
exit_code: Some(0),
..crate::win32::ProcessState::default()
};
state.processes.insert(pid, child_proc);
let mut child_thread = crate::win32::ThreadState::new(tid, pid);
child_thread.status = ThreadStatus::Terminated;
state.threads.insert(tid, child_thread);
(pid, tid)
}
};
let h_process = state
.scheduler
.insert_object(WaitObject::Process { pid: child_pid });
let h_thread = state
.scheduler
.insert_object(WaitObject::Thread { tid: child_tid });
if pi != 0 {
mmu.store32(pi, h_process)
.map_err(|t| trap_to_win32("CreateProcessA", t))?;
mmu.store32(pi + 4, h_thread)
.map_err(|t| trap_to_win32("CreateProcessA", t))?;
mmu.store32(pi + 8, child_pid)
.map_err(|t| trap_to_win32("CreateProcessA", t))?;
mmu.store32(pi + 12, child_tid)
.map_err(|t| trap_to_win32("CreateProcessA", t))?;
}
Ok(1)
}
fn stub_get_exit_code_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("GetExitCodeProcess", t))?;
let p = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetExitCodeProcess", t))?;
let code = match state.scheduler.objects.get(&h) {
Some(crate::sched::WaitObject::Process { pid }) => state
.processes
.get(pid)
.and_then(|proc| proc.exit_code)
.unwrap_or(259),
_ => 259,
};
if p != 0 {
mmu.store32(p, code)
.map_err(|t| trap_to_win32("GetExitCodeProcess", t))?;
}
Ok(1)
}
fn stub_get_console_cp(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(437)
}
fn stub_get_console_mode(
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("GetConsoleMode", t))?;
let p = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("GetConsoleMode", t))?;
if p != 0 {
mmu.store32(p, 0)
.map_err(|t| trap_to_win32("GetConsoleMode", t))?;
}
state.last_error = ERROR_INVALID_HANDLE;
Ok(0)
}
fn stub_local_file_time_to_file_time(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p_src = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("LocalFileTimeToFileTime", t))?;
let p_dst = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("LocalFileTimeToFileTime", t))?;
if p_src == 0 || p_dst == 0 {
return Ok(0);
}
let lo = mmu
.load32(p_src)
.map_err(|t| trap_to_win32("LocalFileTimeToFileTime", t))?;
let hi = mmu
.load32(p_src + 4)
.map_err(|t| trap_to_win32("LocalFileTimeToFileTime", t))?;
mmu.store32(p_dst, lo)
.map_err(|t| trap_to_win32("LocalFileTimeToFileTime", t))?;
mmu.store32(p_dst + 4, hi)
.map_err(|t| trap_to_win32("LocalFileTimeToFileTime", t))?;
Ok(1)
}
fn stub_remove_directory_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p_name = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("RemoveDirectoryA", t))?;
if p_name == 0 {
state.last_error = ERROR_FILE_NOT_FOUND;
return Ok(0);
}
let name = read_cstr(mmu, p_name, 260)?;
let marker = format!("{}\\.dir", name.trim_end_matches(['\\', '/']));
if let Some(vfs) = state.context.vfs.as_mut() {
vfs.remove(&marker);
}
Ok(1)
}
fn stub_write_console_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("WriteConsoleW", t))?;
let p_buf = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("WriteConsoleW", t))?;
let n_chars = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("WriteConsoleW", t))?;
let lp_written = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("WriteConsoleW", t))?;
let _resv = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("WriteConsoleW", t))?;
if p_buf != 0 && n_chars > 0 {
let cap = (n_chars as usize).min(4096);
let mut units = Vec::with_capacity(cap);
for i in 0..cap {
units.push(
mmu.load16(p_buf + (i as u32) * 2)
.map_err(|t| trap_to_win32("WriteConsoleW", t))?,
);
}
let s = String::from_utf16_lossy(&units);
state.debug_log.push(s);
}
if lp_written != 0 {
mmu.store32(lp_written, n_chars)
.map_err(|t| trap_to_win32("WriteConsoleW", t))?;
}
Ok(1)
}
fn stub_duplicate_handle(
cpu: &mut Cpu,
mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _sp = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("DuplicateHandle", t))?;
let h_src = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("DuplicateHandle", t))?;
let _tp = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("DuplicateHandle", t))?;
let p_tgt = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("DuplicateHandle", t))?;
if p_tgt != 0 {
mmu.store32(p_tgt, h_src)
.map_err(|t| trap_to_win32("DuplicateHandle", t))?;
}
Ok(1)
}
fn stub_open_event_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _access = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("OpenEventA", t))?;
let _inherit = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("OpenEventA", t))?;
let p_name = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("OpenEventA", t))?;
let name = if p_name != 0 {
read_cstr(mmu, p_name, 260).unwrap_or_default()
} else {
String::new()
};
open_event_by_name(state, &name)
}
fn parse_pipe_client_path(s: &str) -> Option<String> {
let lower = s.to_ascii_lowercase().replace('\\', "/");
let stripped = lower.strip_prefix("//./pipe/")?;
Some(stripped.to_string())
}
fn stub_create_named_pipe_a(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p_name = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("CreateNamedPipeA", t))?;
let open_mode = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreateNamedPipeA", t))?;
let _pipe_mode = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CreateNamedPipeA", t))?;
let _max = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("CreateNamedPipeA", t))?;
let _out_size = arg_dword(cpu, mmu, 4).map_err(|t| trap_to_win32("CreateNamedPipeA", t))?;
let _in_size = arg_dword(cpu, mmu, 5).map_err(|t| trap_to_win32("CreateNamedPipeA", t))?;
let _timeout = arg_dword(cpu, mmu, 6).map_err(|t| trap_to_win32("CreateNamedPipeA", t))?;
let _sa = arg_dword(cpu, mmu, 7).map_err(|t| trap_to_win32("CreateNamedPipeA", t))?;
if p_name == 0 {
state.last_error = 87; return Ok(INVALID_HANDLE_VALUE);
}
let raw = read_cstr(mmu, p_name, 260)?;
let Some(key) = parse_pipe_client_path(&raw) else {
state.last_error = 87;
return Ok(INVALID_HANDLE_VALUE);
};
const PIPE_ACCESS_OUTBOUND: u32 = 2;
let server_is_read = (open_mode & PIPE_ACCESS_OUTBOUND) == 0;
let pipe_id = state.scheduler.insert_pipe();
let h = state
.scheduler
.insert_object(crate::sched::WaitObject::Pipe {
pipe_id,
is_read_end: server_is_read,
});
state.scheduler.register_named(&key, h);
state
.debug_log
.push(format!("CreateNamedPipeA(name={raw:?})"));
Ok(h)
}
fn stub_connect_named_pipe(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
state.last_error = 535; Ok(1)
}
fn stub_disconnect_named_pipe(
_cpu: &mut Cpu,
_mmu: &mut Mmu,
_state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
Ok(1)
}
fn stub_create_pipe(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let p_read = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("CreatePipe", t))?;
let p_write = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("CreatePipe", t))?;
let _attrs = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("CreatePipe", t))?;
let _size = arg_dword(cpu, mmu, 3).map_err(|t| trap_to_win32("CreatePipe", t))?;
if p_read == 0 || p_write == 0 {
state.last_error = 87; return Ok(0);
}
let pipe_id = state.scheduler.insert_pipe();
let h_read = state
.scheduler
.insert_object(crate::sched::WaitObject::Pipe {
pipe_id,
is_read_end: true,
});
let h_write = state
.scheduler
.insert_object(crate::sched::WaitObject::Pipe {
pipe_id,
is_read_end: false,
});
mmu.store32(p_read, h_read)
.map_err(|t| trap_to_win32("CreatePipe", t))?;
mmu.store32(p_write, h_write)
.map_err(|t| trap_to_win32("CreatePipe", t))?;
Ok(1)
}
fn stub_open_event_w(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _access = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("OpenEventW", t))?;
let _inherit = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("OpenEventW", t))?;
let p_name = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("OpenEventW", t))?;
let name = if p_name != 0 {
super::read_wide_cstr_local(mmu, p_name, 260)
} else {
String::new()
};
open_event_by_name(state, &name)
}
fn open_event_by_name(state: &mut HostState, name: &str) -> Result<u32, Win32Error> {
state.debug_log.push(format!("OpenEvent(name={name:?})"));
if !name.is_empty() {
if let Some(h) = state.scheduler.lookup_named(name) {
return Ok(h);
}
}
let h = state
.scheduler
.insert_object(crate::sched::WaitObject::Event {
signaled: true,
manual_reset: false,
});
if !name.is_empty() {
state.scheduler.register_named(name, h);
}
Ok(h)
}
fn stub_open_process(
cpu: &mut Cpu,
mmu: &mut Mmu,
state: &mut HostState,
_registry: &Registry,
) -> Result<u32, Win32Error> {
let _access = arg_dword(cpu, mmu, 0).map_err(|t| trap_to_win32("OpenProcess", t))?;
let _inherit = arg_dword(cpu, mmu, 1).map_err(|t| trap_to_win32("OpenProcess", t))?;
let pid = arg_dword(cpu, mmu, 2).map_err(|t| trap_to_win32("OpenProcess", t))?;
if !state.processes.contains_key(&pid) {
state.last_error = 87; return Ok(0);
}
for (h, obj) in &state.scheduler.objects {
if let crate::sched::WaitObject::Process { pid: p } = obj {
if *p == pid {
return Ok(*h);
}
}
}
Ok(state
.scheduler
.insert_object(crate::sched::WaitObject::Process { pid }))
}
#[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");
}
#[test]
fn tls_alloc_set_get_value_round_trip() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"TlsAlloc",
&[],
)
.unwrap();
let slot = cpu.regs.get32(Reg32::Eax);
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"TlsAlloc",
&[],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), slot + 1);
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"TlsGetValue",
&[slot],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0);
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"TlsSetValue",
&[slot, 0xDEAD_F00D],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 1);
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"TlsGetValue",
&[slot],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0xDEAD_F00D);
}
#[test]
fn sleep_records_a_wait_on_the_current_thread() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
let before = state.scheduler.instructions_global;
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"Sleep",
&[100],
)
.unwrap();
let yr = state.yield_requested.clone().expect("yield was requested");
match yr {
crate::sched::YieldRequest::Wait(crate::sched::WaitCondition::Sleep {
resume_after_instructions,
}) => {
assert_eq!(
resume_after_instructions,
before.saturating_add(100 * crate::sched::INSTRUCTIONS_PER_MS),
);
}
other => panic!("expected Sleep wait, got {other:?}"),
}
assert_eq!(state.tick, 100);
}
#[test]
fn tls_value_is_isolated_per_thread() {
let (mut cpu, mut mmu, registry, mut state) = make_env();
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"TlsAlloc",
&[],
)
.unwrap();
let slot = cpu.regs.get32(Reg32::Eax);
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"TlsSetValue",
&[slot, 0x1234],
)
.unwrap();
state
.threads
.insert(2, crate::win32::ThreadState::new(2, 1));
state.active_tid = 2;
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"TlsGetValue",
&[slot],
)
.unwrap();
assert_eq!(
cpu.regs.get32(Reg32::Eax),
0,
"the second thread must see a fresh (zero) slot"
);
state.active_tid = 1;
push_args_and_call(
&mut cpu,
&mut mmu,
®istry,
&mut state,
"kernel32.dll",
"TlsGetValue",
&[slot],
)
.unwrap();
assert_eq!(cpu.regs.get32(Reg32::Eax), 0x1234);
}
}