From c80b7cb3d01705badadc367031229fd413ece1e2 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 15 Aug 2025 10:03:13 -0600 Subject: [PATCH 01/28] Integrate mimalloc (#88) --- CMakeLists.txt | 19 ++++++++++++++++--- Dockerfile | 2 +- dll/kernel32.cpp | 27 ++++++++++++++++++--------- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c89538..72b0c2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,14 +5,27 @@ if(NOT CMAKE_BUILD_TYPE) "Build type options: Debug Release RelWithDebInfo MinSizeRel" FORCE) endif() +set(CMAKE_C_FLAGS_INIT "-m32") +set(CMAKE_CXX_FLAGS_INIT "-m32") +set(CMAKE_EXE_LINKER_FLAGS_INIT "-m32") +set(CMAKE_SHARED_LINKER_FLAGS_INIT "-m32") + project(wibo LANGUAGES CXX) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fno-pie -no-pie -D_LARGEFILE64_SOURCE") + find_package(Filesystem REQUIRED) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32 -Wall -fno-pie -no-pie -D_LARGEFILE64_SOURCE") +include(FetchContent) +FetchContent_Declare( + mimalloc + GIT_REPOSITORY https://github.com/microsoft/mimalloc.git + GIT_TAG dfa50c37d951128b1e77167dd9291081aa88eea4 # v3.1.5 +) +FetchContent_MakeAvailable(mimalloc) include_directories(.) add_executable(wibo @@ -35,5 +48,5 @@ add_executable(wibo processes.cpp strutil.cpp ) -target_link_libraries(wibo PRIVATE std::filesystem) +target_link_libraries(wibo PRIVATE std::filesystem mimalloc-static) install(TARGETS wibo DESTINATION bin) diff --git a/Dockerfile b/Dockerfile index 2fa409c..d618896 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM --platform=linux/i386 alpine:latest AS build # Install dependencies -RUN apk add --no-cache cmake ninja g++ linux-headers binutils +RUN apk add --no-cache cmake ninja g++ linux-headers binutils git # Copy source files COPY . /wibo diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index e030bdd..e7068c9 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -10,7 +10,7 @@ #include #include #include "strutil.h" -#include +#include #include #include #include @@ -46,9 +46,9 @@ namespace kernel32 { static void *doAlloc(unsigned int dwBytes, bool zero) { if (dwBytes == 0) dwBytes = 1; - void *ret = malloc(dwBytes); + void *ret = mi_malloc_aligned(dwBytes, 8); if (ret && zero) { - memset(ret, 0, malloc_usable_size(ret)); + memset(ret, 0, mi_usable_size(ret)); } return ret; } @@ -56,9 +56,9 @@ namespace kernel32 { static void *doRealloc(void *mem, unsigned int dwBytes, bool zero) { if (dwBytes == 0) dwBytes = 1; - size_t oldSize = malloc_usable_size(mem); - void *ret = realloc(mem, dwBytes); - size_t newSize = malloc_usable_size(ret); + size_t oldSize = mi_usable_size(mem); + void *ret = mi_realloc_aligned(mem, dwBytes, 8); + size_t newSize = mi_usable_size(ret); if (ret && zero && newSize > oldSize) { memset((char*)ret + oldSize, 0, newSize - oldSize); } @@ -353,9 +353,17 @@ namespace kernel32 { int WIN_FUNC InitOnceBeginInitialize(LPINIT_ONCE lpInitOnce, DWORD dwFlags, PBOOL fPending, LPVOID* lpContext) { DEBUG_LOG("STUB: InitOnceBeginInitialize\n"); + if (fPending != nullptr) { + *fPending = TRUE; + } return 1; } + BOOL WIN_FUNC InitOnceComplete(LPINIT_ONCE lpInitOnce, DWORD dwFlags, LPVOID lpContext) { + DEBUG_LOG("STUB: InitOnceComplete\n"); + return TRUE; + } + void WIN_FUNC AcquireSRWLockShared(void *SRWLock) { DEBUG_LOG("STUB: AcquireSRWLockShared(%p)\n", SRWLock); } void WIN_FUNC ReleaseSRWLockShared(void *SRWLock) { DEBUG_LOG("STUB: ReleaseSRWLockShared(%p)\n", SRWLock); } @@ -485,7 +493,7 @@ namespace kernel32 { bufSize++; // Step 2, actually build that buffer - char *buffer = (char *) malloc(bufSize); + char *buffer = (char *) mi_malloc(bufSize); char *ptr = buffer; work = environ; @@ -515,7 +523,7 @@ namespace kernel32 { bufSizeW++; // Step 2, actually build that buffer - uint16_t *buffer = (uint16_t *) malloc(bufSizeW * 2); + uint16_t *buffer = (uint16_t *) mi_malloc(bufSizeW * 2); uint16_t *ptr = buffer; work = environ; @@ -1896,7 +1904,7 @@ namespace kernel32 { unsigned int WIN_FUNC HeapSize(void *hHeap, unsigned int dwFlags, void *lpMem) { DEBUG_LOG("HeapSize(heap=%p, flags=%x, mem=%p)\n", hHeap, dwFlags, lpMem); - return malloc_usable_size(lpMem); + return mi_usable_size(lpMem); } void *WIN_FUNC GetProcessHeap() { @@ -2348,6 +2356,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "EnterCriticalSection") == 0) return (void *) kernel32::EnterCriticalSection; if (strcmp(name, "LeaveCriticalSection") == 0) return (void *) kernel32::LeaveCriticalSection; if (strcmp(name, "InitOnceBeginInitialize") == 0) return (void *) kernel32::InitOnceBeginInitialize; + if (strcmp(name, "InitOnceComplete") == 0) return (void *) kernel32::InitOnceComplete; if (strcmp(name, "AcquireSRWLockShared") == 0) return (void *) kernel32::AcquireSRWLockShared; if (strcmp(name, "ReleaseSRWLockShared") == 0) return (void *) kernel32::ReleaseSRWLockShared; if (strcmp(name, "AcquireSRWLockExclusive") == 0) return (void *) kernel32::AcquireSRWLockExclusive; From 6d0bff464ac998a9c68964e3bab13f9bd53ef329 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 15 Aug 2025 10:05:04 -0600 Subject: [PATCH 02/28] Update CI release action --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85ca5f7..e02c8b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,9 +53,11 @@ jobs: path: build/wibo_debug - name: Publish release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: files: | build/wibo build/wibo_debug + draft: true + generate_release_notes: true From 836f485d660e6a5dd8db470973392b518df2b21e Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 00:55:35 -0600 Subject: [PATCH 03/28] Initial external DLL support --- CMakeLists.txt | 1 + README.md | 2 +- common.h | 45 ++- dll/crt.cpp | 140 ++++++- dll/kernel32.cpp | 209 ++++++++-- loader.cpp | 81 +++- main.cpp | 150 +------ module_registry.cpp | 815 +++++++++++++++++++++++++++++++++++++++ test/Makefile | 20 + test/external_exports.c | 22 ++ test/test_external_dll.c | 32 ++ 11 files changed, 1318 insertions(+), 199 deletions(-) create mode 100644 module_registry.cpp create mode 100644 test/Makefile create mode 100644 test/external_exports.c create mode 100644 test/test_external_dll.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c89538..755a4cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable(wibo files.cpp handles.cpp loader.cpp + module_registry.cpp main.cpp processes.cpp strutil.cpp diff --git a/README.md b/README.md index 586adec..c98ff23 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Rough to-do list: - Do something intelligent with Windows `HANDLE`s - Convert paths in environment variables (and the structure of `PATH` itself, maybe) to Windows format - Implement PE relocations rather than just failing unceremoniously -- Make the PE loader work for DLLs as well in case we ever want to load some +- Land external DLL loading support (module registry + search order + export resolution) --- diff --git a/common.h b/common.h index 0ac0157..8d56669 100644 --- a/common.h +++ b/common.h @@ -3,11 +3,14 @@ #include #include #include +#include +#include +#include #include +#include #include #include #include -#include // On Windows, the incoming stack is aligned to a 4 byte boundary. // force_align_arg_pointer will realign the stack to match GCC's 16 byte alignment. @@ -57,7 +60,9 @@ typedef unsigned char BYTE; #define ERROR_INVALID_PARAMETER 87 #define ERROR_BUFFER_OVERFLOW 111 #define ERROR_INSUFFICIENT_BUFFER 122 +#define ERROR_MOD_NOT_FOUND 126 #define ERROR_NEGATIVE_SEEK 131 +#define ERROR_BAD_EXE_FORMAT 193 #define ERROR_ALREADY_EXISTS 183 #define INVALID_SET_FILE_POINTER ((DWORD)-1) @@ -94,7 +99,18 @@ namespace wibo { ResolveByName byName; ResolveByOrdinal byOrdinal; }; - extern const Module *modules[]; + struct ModuleInfo; + void initializeModuleRegistry(); + void shutdownModuleRegistry(); + ModuleInfo *moduleInfoFromHandle(HMODULE module); + void setDllDirectoryOverride(const std::filesystem::path &path); + void clearDllDirectoryOverride(); + std::optional dllDirectoryOverride(); + HMODULE findLoadedModule(const char *name); + void registerOnExitTable(void *table); + void addOnExitFunction(void *table, void (*func)()); + void executeOnExitTable(void *table); + void runPendingOnExit(ModuleInfo &info); HMODULE loadModule(const char *name); void freeModule(HMODULE module); @@ -110,6 +126,12 @@ namespace wibo { size_t imageSize; void *entryPoint; void *rsrcBase; + uintptr_t preferredImageBase; + intptr_t relocationDelta; + uint32_t exportDirectoryRVA; + uint32_t exportDirectorySize; + uint32_t relocationDirectoryRVA; + uint32_t relocationDirectorySize; template T *fromRVA(uint32_t rva) { @@ -122,9 +144,24 @@ namespace wibo { } }; struct ModuleInfo { - std::string name; - const wibo::Module* module = nullptr; + std::string originalName; + std::string normalizedName; + std::filesystem::path resolvedPath; + const wibo::Module *module = nullptr; std::unique_ptr executable; + void *entryPoint = nullptr; + void *imageBase = nullptr; + size_t imageSize = 0; + unsigned int refCount = 0; + bool dataFile = false; + bool processAttachCalled = false; + bool processAttachSucceeded = false; + bool dontResolveReferences = false; + uint32_t exportOrdinalBase = 0; + std::vector exportsByOrdinal; + std::unordered_map exportNameToOrdinal; + bool exportsInitialized = false; + std::vector onExitFunctions; }; extern Executable *mainModule; diff --git a/dll/crt.cpp b/dll/crt.cpp index 28822f0..99d6259 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -1,9 +1,18 @@ #include "common.h" +#include +#include +#include +#include +#include #include +#include typedef void (*_PVFV)(); typedef int (*_PIFV)(); +typedef void (*_invalid_parameter_handler)(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, uintptr_t); + +extern char **environ; typedef enum _crt_app_type { _crt_unknown_app, @@ -20,8 +29,10 @@ typedef enum _crt_argv_mode { namespace crt { int _commode = 0; +int _fmode = 0; std::vector<_PVFV> atexitFuncs; +_invalid_parameter_handler invalidParameterHandler = nullptr; void WIN_ENTRY _initterm(const _PVFV *ppfn, const _PVFV *end) { do { @@ -45,12 +56,15 @@ int WIN_ENTRY _initterm_e(const _PIFV *ppfn, const _PIFV *end) { void WIN_ENTRY _set_app_type(_crt_app_type type) { DEBUG_LOG("STUB: _set_app_type(%i)\n", type); } int WIN_ENTRY _set_fmode(int mode) { - DEBUG_LOG("STUB: _set_fmode(%i)\n", mode); + DEBUG_LOG("_set_fmode(%i)\n", mode); + _fmode = mode; return 0; } int *WIN_ENTRY __p__commode() { return &_commode; } +int *WIN_ENTRY __p__fmode() { return &_fmode; } + int WIN_ENTRY _crt_atexit(void (*func)()) { DEBUG_LOG("_crt_atexit(%p)\n", func); atexitFuncs.push_back(func); @@ -62,6 +76,13 @@ int WIN_ENTRY _configure_narrow_argv(_crt_argv_mode mode) { return 0; } +_invalid_parameter_handler WIN_ENTRY _set_invalid_parameter_handler(_invalid_parameter_handler newHandler) { + DEBUG_LOG("STUB: _set_invalid_parameter_handler(%p)\n", newHandler); + _invalid_parameter_handler oldHandler = invalidParameterHandler; + invalidParameterHandler = newHandler; + return oldHandler; +} + int WIN_ENTRY _controlfp_s(unsigned int *currentControl, unsigned int newControl, unsigned int mask) { DEBUG_LOG("STUB: _controlfp_s(%p, %u, %u)\n", currentControl, newControl, mask); return 0; @@ -84,12 +105,49 @@ int WIN_ENTRY _set_new_mode(int newhandlermode) { char **WIN_ENTRY _get_initial_narrow_environment() { return environ; } +char ***WIN_ENTRY __p__environ() { return &environ; } + char ***WIN_ENTRY __p___argv() { return &wibo::argv; } int *WIN_ENTRY __p___argc() { return &wibo::argc; } size_t WIN_ENTRY strlen(const char *str) { return ::strlen(str); } +int WIN_ENTRY strncmp(const char *lhs, const char *rhs, size_t count) { return ::strncmp(lhs, rhs, count); } + +void *WIN_ENTRY malloc(size_t size) { return ::malloc(size); } + +void *WIN_ENTRY calloc(size_t count, size_t size) { return ::calloc(count, size); } + +void *WIN_ENTRY realloc(void *ptr, size_t newSize) { return ::realloc(ptr, newSize); } + +void WIN_ENTRY free(void *ptr) { ::free(ptr); } + +void *WIN_ENTRY memcpy(void *dest, const void *src, size_t count) { return std::memcpy(dest, src, count); } + +int WIN_ENTRY __setusermatherr(void *handler) { + DEBUG_LOG("STUB: __setusermatherr(%p)\n", handler); + return 0; +} + +int WIN_ENTRY _initialize_onexit_table(void *table) { + DEBUG_LOG("STUB: _initialize_onexit_table(%p)\n", table); + wibo::registerOnExitTable(table); + return 0; +} + +int WIN_ENTRY _register_onexit_function(void *table, void (*func)()) { + DEBUG_LOG("STUB: _register_onexit_function(%p, %p)\n", table, func); + wibo::addOnExitFunction(table, func); + return 0; +} + +int WIN_ENTRY _execute_onexit_table(void *table) { + DEBUG_LOG("STUB: _execute_onexit_table(%p)\n", table); + wibo::executeOnExitTable(table); + return 0; +} + void WIN_ENTRY exit(int status) { DEBUG_LOG("exit(%i)\n", status); for (auto it = atexitFuncs.rbegin(); it != atexitFuncs.rend(); ++it) { @@ -99,6 +157,42 @@ void WIN_ENTRY exit(int status) { ::exit(status); } +void WIN_ENTRY _cexit(void) { + DEBUG_LOG("_cexit()\n"); + for (auto it = atexitFuncs.rbegin(); it != atexitFuncs.rend(); ++it) { + DEBUG_LOG("Calling atexit function %p\n", *it); + (*it)(); + } +} + +void WIN_ENTRY _exit(int status) { + DEBUG_LOG("_exit(%i)\n", status); + ::_exit(status); +} + +void WIN_ENTRY abort(void) { + DEBUG_LOG("abort()\n"); + std::abort(); +} + +using signal_handler = void (*)(int); + +signal_handler WIN_ENTRY signal(int signum, signal_handler handler) { return std::signal(signum, handler); } + +void *WIN_ENTRY __acrt_iob_func(unsigned int index) { + if (index == 0) + return stdin; + if (index == 1) + return stdout; + if (index == 2) + return stderr; + return nullptr; +} + +int WIN_ENTRY __stdio_common_vfprintf(unsigned long long /*options*/, FILE *stream, const char *format, void * /*locale*/, va_list args) { + return vfprintf(stream, format, args); +} + } // namespace crt static void *resolveByName(const char *name) { @@ -112,10 +206,14 @@ static void *resolveByName(const char *name) { return (void *)crt::_set_fmode; if (strcmp(name, "__p__commode") == 0) return (void *)crt::__p__commode; + if (strcmp(name, "__p__fmode") == 0) + return (void *)crt::__p__fmode; if (strcmp(name, "_crt_atexit") == 0) return (void *)crt::_crt_atexit; if (strcmp(name, "_configure_narrow_argv") == 0) return (void *)crt::_configure_narrow_argv; + if (strcmp(name, "_set_invalid_parameter_handler") == 0) + return (void *)crt::_set_invalid_parameter_handler; if (strcmp(name, "_controlfp_s") == 0) return (void *)crt::_controlfp_s; if (strcmp(name, "_configthreadlocale") == 0) @@ -126,14 +224,48 @@ static void *resolveByName(const char *name) { return (void *)crt::_set_new_mode; if (strcmp(name, "_get_initial_narrow_environment") == 0) return (void *)crt::_get_initial_narrow_environment; + if (strcmp(name, "__p__environ") == 0) + return (void *)crt::__p__environ; if (strcmp(name, "__p___argv") == 0) return (void *)crt::__p___argv; if (strcmp(name, "__p___argc") == 0) return (void *)crt::__p___argc; if (strcmp(name, "strlen") == 0) return (void *)crt::strlen; + if (strcmp(name, "strncmp") == 0) + return (void *)crt::strncmp; + if (strcmp(name, "malloc") == 0) + return (void *)crt::malloc; + if (strcmp(name, "calloc") == 0) + return (void *)crt::calloc; + if (strcmp(name, "realloc") == 0) + return (void *)crt::realloc; + if (strcmp(name, "free") == 0) + return (void *)crt::free; + if (strcmp(name, "memcpy") == 0) + return (void *)crt::memcpy; if (strcmp(name, "exit") == 0) return (void *)crt::exit; + if (strcmp(name, "_cexit") == 0) + return (void *)crt::_cexit; + if (strcmp(name, "_exit") == 0) + return (void *)crt::_exit; + if (strcmp(name, "abort") == 0) + return (void *)crt::abort; + if (strcmp(name, "signal") == 0) + return (void *)crt::signal; + if (strcmp(name, "__acrt_iob_func") == 0) + return (void *)crt::__acrt_iob_func; + if (strcmp(name, "__stdio_common_vfprintf") == 0) + return (void *)crt::__stdio_common_vfprintf; + if (strcmp(name, "__setusermatherr") == 0) + return (void *)crt::__setusermatherr; + if (strcmp(name, "_initialize_onexit_table") == 0) + return (void *)crt::_initialize_onexit_table; + if (strcmp(name, "_register_onexit_function") == 0) + return (void *)crt::_register_onexit_function; + if (strcmp(name, "_execute_onexit_table") == 0) + return (void *)crt::_execute_onexit_table; return nullptr; } @@ -149,6 +281,12 @@ wibo::Module lib_crt = { "api-ms-win-crt-stdio-l1-1-0.dll", "api-ms-win-crt-string-l1-1-0", "api-ms-win-crt-string-l1-1-0.dll", + "api-ms-win-crt-environment-l1-1-0", + "api-ms-win-crt-environment-l1-1-0.dll", + "api-ms-win-crt-math-l1-1-0", + "api-ms-win-crt-math-l1-1-0.dll", + "api-ms-win-crt-private-l1-1-0", + "api-ms-win-crt-private-l1-1-0.dll", nullptr, }, resolveByName, diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index b39d018..40a5ad3 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -1565,8 +1565,9 @@ namespace kernel32 { return wibo::mainModule->imageBuffer; } - // wibo::lastError = 0; - return wibo::loadModule(lpModuleName); + HMODULE module = wibo::findLoadedModule(lpModuleName); + wibo::lastError = module ? ERROR_SUCCESS : ERROR_MOD_NOT_FOUND; + return module; } HMODULE WIN_FUNC GetModuleHandleW(LPCWSTR lpModuleName) { @@ -1592,7 +1593,16 @@ namespace kernel32 { const auto absPath = std::filesystem::absolute(exePath); path = files::pathToWindows(absPath); } else { - path = static_cast(hModule)->name; + auto *info = wibo::moduleInfoFromHandle(hModule); + if (!info) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + if (!info->resolvedPath.empty()) { + path = files::pathToWindows(info->resolvedPath); + } else { + path = info->originalName; + } } const size_t len = path.size(); if (nSize == 0) { @@ -1627,17 +1637,32 @@ namespace kernel32 { const auto absPath = std::filesystem::absolute(exePath); path = files::pathToWindows(absPath); } else { - path = static_cast(hModule)->name; + auto *info = wibo::moduleInfoFromHandle(hModule); + if (!info) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + if (!info->resolvedPath.empty()) { + path = files::pathToWindows(info->resolvedPath); + } else { + path = info->originalName; + } } - const size_t len = path.size(); if (nSize == 0) { wibo::lastError = ERROR_INSUFFICIENT_BUFFER; return 0; } - const size_t copyLen = std::min(len, nSize - 1); - memcpy(lpFilename, stringToWideString(path.c_str()).data(), copyLen * 2); - if (copyLen < nSize) { + auto wide = stringToWideString(path.c_str()); + if (wide.empty()) { + wide.push_back(0); + } + const size_t len = wide.size() - 1; + const size_t copyLen = std::min(len, static_cast(nSize - 1)); + for (size_t i = 0; i < copyLen; i++) { + lpFilename[i] = wide[i]; + } + if (copyLen < static_cast(nSize)) { lpFilename[copyLen] = 0; } if (copyLen < len) { @@ -1649,38 +1674,58 @@ namespace kernel32 { return copyLen; } - void* WIN_FUNC FindResourceA(void* hModule, const char* lpName, const char* lpType) { - DEBUG_LOG("FindResourceA %p %s %s\n", hModule, lpName, lpType); - return (void*)0x100002; - } - - // https://github.com/reactos/reactos/blob/master/dll/win32/kernelbase/wine/loader.c#L1090 - // https://github.com/wine-mirror/wine/blob/master/dlls/kernelbase/loader.c#L1097 - void* WIN_FUNC FindResourceW(void* hModule, const uint16_t* lpName, const uint16_t* lpType) { - DEBUG_LOG("FindResourceW %p\n", hModule); - std::string name, type; - - if(!hModule) hModule = GetModuleHandleW(0); - - if((uintptr_t)lpName >> 16 == 0){ - name = std::to_string((unsigned int)(uintptr_t)lpName); + static std::string resource_identifier_to_string(const char *id) { + if (!id) { + return ""; } - else { - name = wideStringToString(lpName); + if ((uintptr_t)id >> 16 == 0) { + return std::to_string(static_cast((uintptr_t)id)); } + return id; + } - if((uintptr_t)lpType >> 16 == 0){ - type = std::to_string((unsigned int)(uintptr_t)lpType); + static std::string resource_identifier_to_string(const uint16_t *id) { + if (!id) { + return ""; } - else { - type = wideStringToString(lpType); + if ((uintptr_t)id >> 16 == 0) { + return std::to_string(static_cast((uintptr_t)id)); } + return wideStringToString(id); + } + static FILE *open_resource_stream(const std::string &type, const std::string &name) { char path[512]; snprintf(path, sizeof(path), "resources/%s/%s.res", type.c_str(), name.c_str()); DEBUG_LOG("Created path %s\n", path); return fopen(path, "rb"); - // return (void*)0x100002; + } + + void *WIN_FUNC FindResourceA(void *hModule, const char *lpName, const char *lpType) { + DEBUG_LOG("FindResourceA %p %s %s\n", hModule, lpName, lpType); + + if (!hModule) { + hModule = GetModuleHandleA(nullptr); + } + + const std::string name = resource_identifier_to_string(lpName); + const std::string type = resource_identifier_to_string(lpType); + + return open_resource_stream(type, name); + } + + // https://github.com/reactos/reactos/blob/master/dll/win32/kernelbase/wine/loader.c#L1090 + // https://github.com/wine-mirror/wine/blob/master/dlls/kernelbase/loader.c#L1097 + void *WIN_FUNC FindResourceW(void *hModule, const uint16_t *lpName, const uint16_t *lpType) { + DEBUG_LOG("FindResourceW %p\n", hModule); + + if (!hModule) + hModule = GetModuleHandleW(0); + + const std::string name = resource_identifier_to_string(lpName); + const std::string type = resource_identifier_to_string(lpType); + + return open_resource_stream(type, name); } void* WIN_FUNC LoadResource(void* hModule, void* res) { @@ -1813,19 +1858,34 @@ namespace kernel32 { return (void *) 0x100006; } + static int translateProtect(DWORD flProtect) { + switch (flProtect) { + case 0x01: /* PAGE_NOACCESS */ + return PROT_NONE; + case 0x02: /* PAGE_READONLY */ + return PROT_READ; + case 0x04: /* PAGE_READWRITE */ + return PROT_READ | PROT_WRITE; + case 0x08: /* PAGE_WRITECOPY */ + return PROT_READ | PROT_WRITE; + case 0x10: /* PAGE_EXECUTE */ + return PROT_EXEC; + case 0x20: /* PAGE_EXECUTE_READ */ + return PROT_READ | PROT_EXEC; + case 0x40: /* PAGE_EXECUTE_READWRITE */ + return PROT_READ | PROT_WRITE | PROT_EXEC; + case 0x80: /* PAGE_EXECUTE_WRITECOPY */ + return PROT_READ | PROT_WRITE | PROT_EXEC; + default: + DEBUG_LOG("Unhandled flProtect: %u, defaulting to RW\n", flProtect); + return PROT_READ | PROT_WRITE; + } + } + void *WIN_FUNC VirtualAlloc(void *lpAddress, unsigned int dwSize, unsigned int flAllocationType, unsigned int flProtect) { DEBUG_LOG("VirtualAlloc %p %u %u %u\n", lpAddress, dwSize, flAllocationType, flProtect); - int prot = PROT_READ | PROT_WRITE; - if (flProtect == 0x04 /* PAGE_READWRITE */) { - prot = PROT_READ | PROT_WRITE; - } else if (flProtect == 0x02 /* PAGE_READONLY */) { - prot = PROT_READ; - } else if (flProtect == 0x40 /* PAGE_EXECUTE_READWRITE */) { - prot = PROT_READ | PROT_WRITE | PROT_EXEC; - } else { - DEBUG_LOG("Unhandled flProtect: %u, defaulting to RW\n", flProtect); - } + int prot = translateProtect(flProtect); int flags = MAP_PRIVATE | MAP_ANONYMOUS; // MAP_ANONYMOUS ensures the memory is zeroed out if (lpAddress != NULL) { @@ -1869,6 +1929,51 @@ namespace kernel32 { return 1; } + BOOL WIN_FUNC VirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) { + DEBUG_LOG("VirtualProtect %p %zu %u\n", lpAddress, dwSize, flNewProtect); + if (!lpAddress || dwSize == 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + if (lpflOldProtect) + *lpflOldProtect = flNewProtect; + size_t pageSize = static_cast(sysconf(_SC_PAGESIZE)); + uintptr_t base = reinterpret_cast(lpAddress) & ~(pageSize - 1); + size_t length = ((reinterpret_cast(lpAddress) + dwSize) - base + pageSize - 1) & ~(pageSize - 1); + int prot = translateProtect(flNewProtect); + if (mprotect(reinterpret_cast(base), length, prot) != 0) { + perror("VirtualProtect/mprotect"); + return FALSE; + } + return TRUE; + } + + typedef struct _MEMORY_BASIC_INFORMATION { + void *BaseAddress; + void *AllocationBase; + DWORD AllocationProtect; + size_t RegionSize; + DWORD State; + DWORD Protect; + DWORD Type; + } MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION; + + SIZE_T WIN_FUNC VirtualQuery(const void *lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer, SIZE_T dwLength) { + DEBUG_LOG("VirtualQuery %p %zu\n", lpAddress, dwLength); + if (!lpBuffer || dwLength < sizeof(MEMORY_BASIC_INFORMATION)) { + return 0; + } + memset(lpBuffer, 0, sizeof(MEMORY_BASIC_INFORMATION)); + lpBuffer->BaseAddress = const_cast(lpAddress); + lpBuffer->AllocationBase = lpBuffer->BaseAddress; + lpBuffer->AllocationProtect = 0x04; // PAGE_READWRITE + lpBuffer->RegionSize = static_cast(sysconf(_SC_PAGESIZE)); + lpBuffer->State = 0x1000; // MEM_COMMIT + lpBuffer->Protect = 0x04; // PAGE_READWRITE + lpBuffer->Type = 0x20000; // MEM_PRIVATE + return sizeof(MEMORY_BASIC_INFORMATION); + } + unsigned int WIN_FUNC GetProcessWorkingSetSize(void *hProcess, unsigned int *lpMinimumWorkingSetSize, unsigned int *lpMaximumWorkingSetSize) { DEBUG_LOG("GetProcessWorkingSetSize\n"); // A pointer to a variable that receives the minimum working set size of the specified process, in bytes. @@ -1966,6 +2071,11 @@ namespace kernel32 { return uNumber + 10; } + void WIN_FUNC Sleep(DWORD dwMilliseconds) { + DEBUG_LOG("Sleep(%u)\n", dwMilliseconds); + usleep(static_cast(dwMilliseconds) * 1000); + } + unsigned int WIN_FUNC GetACP() { DEBUG_LOG("GetACP\n"); // return 65001; // UTF-8 @@ -2177,7 +2287,21 @@ namespace kernel32 { } BOOL WIN_FUNC SetDllDirectoryA(LPCSTR lpPathName) { - DEBUG_LOG("STUB: SetDllDirectoryA(%s)\n", lpPathName); + DEBUG_LOG("SetDllDirectoryA(%s)\n", lpPathName); + if (!lpPathName || lpPathName[0] == '\0') { + wibo::clearDllDirectoryOverride(); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + auto hostPath = files::pathFromWindows(lpPathName); + if (hostPath.empty() || !std::filesystem::exists(hostPath)) { + wibo::lastError = ERROR_PATH_NOT_FOUND; + return FALSE; + } + + wibo::setDllDirectoryOverride(std::filesystem::absolute(hostPath)); + wibo::lastError = ERROR_SUCCESS; return TRUE; } @@ -2591,6 +2715,9 @@ static void *resolveByName(const char *name) { if (strcmp(name, "EncodePointer") == 0) return (void *) kernel32::EncodePointer; if (strcmp(name, "DecodePointer") == 0) return (void *) kernel32::DecodePointer; if (strcmp(name, "SetDllDirectoryA") == 0) return (void *) kernel32::SetDllDirectoryA; + if (strcmp(name, "Sleep") == 0) return (void *) kernel32::Sleep; + if (strcmp(name, "VirtualProtect") == 0) return (void *) kernel32::VirtualProtect; + if (strcmp(name, "VirtualQuery") == 0) return (void *) kernel32::VirtualQuery; // processenv.h if (strcmp(name, "GetCommandLineA") == 0) return (void *) kernel32::GetCommandLineA; diff --git a/loader.cpp b/loader.cpp index bd194fe..5d5b80d 100644 --- a/loader.cpp +++ b/loader.cpp @@ -92,6 +92,14 @@ struct PEHintNameTableEntry { char name[1]; // variable length }; +struct PEBaseRelocationBlock { + uint32_t virtualAddress; + uint32_t sizeOfBlock; +}; + +constexpr uint16_t IMAGE_REL_BASED_ABSOLUTE = 0; +constexpr uint16_t IMAGE_REL_BASED_HIGHLOW = 3; + uint16_t read16(FILE *file) { uint16_t v = 0; fread(&v, 2, 1, file); @@ -109,6 +117,12 @@ wibo::Executable::Executable() { imageSize = 0; entryPoint = nullptr; rsrcBase = 0; + preferredImageBase = 0; + relocationDelta = 0; + exportDirectoryRVA = 0; + exportDirectorySize = 0; + relocationDirectoryRVA = 0; + relocationDirectorySize = 0; } wibo::Executable::~Executable() { @@ -150,20 +164,29 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { long pageSize = sysconf(_SC_PAGE_SIZE); DEBUG_LOG("Page size: %x\n", (unsigned int)pageSize); + preferredImageBase = header32.imageBase; + exportDirectoryRVA = header32.exportTable.virtualAddress; + exportDirectorySize = header32.exportTable.size; + relocationDirectoryRVA = header32.baseRelocationTable.virtualAddress; + relocationDirectorySize = header32.baseRelocationTable.size; + // Build buffer imageSize = header32.sizeOfImage; - if (exec) { - imageBuffer = mmap((void *)header32.imageBase, header32.sizeOfImage, PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, -1, 0); - } else { - imageBuffer = mmap(nullptr, header32.sizeOfImage, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + int prot = PROT_READ | PROT_WRITE; + if (exec) + prot |= PROT_EXEC; + void *preferredBase = (void *)(uintptr_t)header32.imageBase; + imageBuffer = mmap(preferredBase, header32.sizeOfImage, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (imageBuffer == MAP_FAILED) { + imageBuffer = mmap(nullptr, header32.sizeOfImage, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); } - memset(imageBuffer, 0, header32.sizeOfImage); if (imageBuffer == MAP_FAILED) { perror("Image mapping failed!"); - imageBuffer = 0; + imageBuffer = nullptr; return false; } + relocationDelta = (intptr_t)((uintptr_t)imageBuffer - (uintptr_t)header32.imageBase); + memset(imageBuffer, 0, header32.sizeOfImage); // Read the sections fseek(file, offsetToPE + sizeof header + header.sizeOfOptionalHeader, SEEK_SET); @@ -191,6 +214,48 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { } } + if (exec && relocationDelta != 0) { + if (relocationDirectoryRVA == 0 || relocationDirectorySize == 0) { + DEBUG_LOG("Relocation required but no relocation directory present\n"); + munmap(imageBuffer, imageSize); + imageBuffer = nullptr; + return false; + } + + uint8_t *relocCursor = fromRVA(relocationDirectoryRVA); + uint8_t *relocEnd = relocCursor + relocationDirectorySize; + while (relocCursor < relocEnd) { + auto *block = reinterpret_cast(relocCursor); + if (block->sizeOfBlock < sizeof(PEBaseRelocationBlock) || block->sizeOfBlock > static_cast(relocEnd - relocCursor)) { + break; + } + if (block->sizeOfBlock == sizeof(PEBaseRelocationBlock)) { + break; + } + size_t entryCount = (block->sizeOfBlock - sizeof(PEBaseRelocationBlock)) / sizeof(uint16_t); + auto *entries = reinterpret_cast(relocCursor + sizeof(PEBaseRelocationBlock)); + for (size_t i = 0; i < entryCount; ++i) { + uint16_t entry = entries[i]; + uint16_t type = entry >> 12; + uint16_t offset = entry & 0x0FFF; + if (type == IMAGE_REL_BASED_ABSOLUTE) + continue; + uintptr_t target = reinterpret_cast(imageBuffer) + block->virtualAddress + offset; + switch (type) { + case IMAGE_REL_BASED_HIGHLOW: { + auto *addr = reinterpret_cast(target); + *addr += static_cast(relocationDelta); + break; + } + default: + DEBUG_LOG("Unhandled relocation type %u at %08x\n", type, block->virtualAddress + offset); + break; + } + } + relocCursor += block->sizeOfBlock; + } + } + if (!exec) { // No need to resolve imports return true; @@ -222,8 +287,6 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { ++lookupTable; ++addressTable; } - freeModule(module); - ++dir; } diff --git a/main.cpp b/main.cpp index 9955493..93c4f53 100644 --- a/main.cpp +++ b/main.cpp @@ -1,15 +1,15 @@ #include "common.h" #include "files.h" +#include "strutil.h" #include +#include +#include #include #include -#include "strutil.h" +#include #include #include -#include #include -#include -#include uint32_t wibo::lastError = 0; char** wibo::argv; @@ -34,145 +34,6 @@ void wibo::debug_log(const char *fmt, ...) { va_end(args); } -#define FOR_256_3(a, b, c, d) FOR_ITER((a << 6 | b << 4 | c << 2 | d)) -#define FOR_256_2(a, b) \ - FOR_256_3(a, b, 0, 0) FOR_256_3(a, b, 0, 1) FOR_256_3(a, b, 0, 2) FOR_256_3(a, b, 0, 3) \ - FOR_256_3(a, b, 1, 0) FOR_256_3(a, b, 1, 1) FOR_256_3(a, b, 1, 2) FOR_256_3(a, b, 1, 3) \ - FOR_256_3(a, b, 2, 0) FOR_256_3(a, b, 2, 1) FOR_256_3(a, b, 2, 2) FOR_256_3(a, b, 2, 3) \ - FOR_256_3(a, b, 3, 0) FOR_256_3(a, b, 3, 1) FOR_256_3(a, b, 3, 2) FOR_256_3(a, b, 3, 3) -#define FOR_256 \ - FOR_256_2(0, 0) FOR_256_2(0, 1) FOR_256_2(0, 2) FOR_256_2(0, 3) \ - FOR_256_2(1, 0) FOR_256_2(1, 1) FOR_256_2(1, 2) FOR_256_2(1, 3) \ - FOR_256_2(2, 0) FOR_256_2(2, 1) FOR_256_2(2, 2) FOR_256_2(2, 3) \ - FOR_256_2(3, 0) FOR_256_2(3, 1) FOR_256_2(3, 2) FOR_256_2(3, 3) \ - -static int stubIndex = 0; -static char stubDlls[0x100][0x100]; -static char stubFuncNames[0x100][0x100]; - -static void stubBase(int index) { - printf("Unhandled function %s (%s)\n", stubFuncNames[index], stubDlls[index]); - exit(1); -} - -void (*stubFuncs[0x100])(void) = { -#define FOR_ITER(i) []() { stubBase(i); }, -FOR_256 -#undef FOR_ITER -}; - -#undef FOR_256_3 -#undef FOR_256_2 -#undef FOR_256 - -static void *resolveMissingFuncName(const char *dllName, const char *funcName) { - DEBUG_LOG("Missing function: %s (%s)\n", dllName, funcName); - assert(stubIndex < 0x100); - assert(strlen(dllName) < 0x100); - assert(strlen(funcName) < 0x100); - strcpy(stubFuncNames[stubIndex], funcName); - strcpy(stubDlls[stubIndex], dllName); - return (void *)stubFuncs[stubIndex++]; -} - -static void *resolveMissingFuncOrdinal(const char *dllName, uint16_t ordinal) { - char buf[16]; - sprintf(buf, "%d", ordinal); - return resolveMissingFuncName(dllName, buf); -} - -extern const wibo::Module lib_advapi32; -extern const wibo::Module lib_bcrypt; -extern const wibo::Module lib_crt; -extern const wibo::Module lib_kernel32; -extern const wibo::Module lib_lmgr; -extern const wibo::Module lib_mscoree; -extern const wibo::Module lib_msvcrt; -extern const wibo::Module lib_ntdll; -extern const wibo::Module lib_ole32; -extern const wibo::Module lib_user32; -extern const wibo::Module lib_vcruntime; -extern const wibo::Module lib_version; -const wibo::Module * wibo::modules[] = { - &lib_advapi32, - &lib_bcrypt, - &lib_crt, - &lib_kernel32, - &lib_lmgr, - &lib_mscoree, - &lib_msvcrt, - &lib_ntdll, - &lib_ole32, - &lib_user32, - &lib_vcruntime, - &lib_version, - nullptr, -}; - -HMODULE wibo::loadModule(const char *dllName) { - auto *result = new ModuleInfo; - result->name = dllName; - for (int i = 0; modules[i]; i++) { - for (int j = 0; modules[i]->names[j]; j++) { - if (strcasecmp(dllName, modules[i]->names[j]) == 0) { - result->module = modules[i]; - return result; - } - } - } - return result; -} - -void wibo::freeModule(HMODULE module) { delete static_cast(module); } - -void *wibo::resolveFuncByName(HMODULE module, const char *funcName) { - auto *info = static_cast(module); - assert(info); - if (info->module && info->module->byName) { - void *func = info->module->byName(funcName); - if (func) - return func; - } - return resolveMissingFuncName(info->name.c_str(), funcName); -} - -void *wibo::resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) { - auto *info = static_cast(module); - assert(info); - if (info->module && info->module->byOrdinal) { - void *func = info->module->byOrdinal(ordinal); - if (func) - return func; - } - return resolveMissingFuncOrdinal(info->name.c_str(), ordinal); -} - -wibo::Executable *wibo::executableFromModule(HMODULE module) { - if (wibo::isMainModule(module)) { - return wibo::mainModule; - } - - auto info = static_cast(module); - if (!info->executable) { - DEBUG_LOG("wibo::executableFromModule: loading %s\n", info->name.c_str()); - auto executable = std::make_unique(); - const auto path = files::pathFromWindows(info->name.c_str()); - FILE *f = fopen(path.c_str(), "rb"); - if (!f) { - perror("wibo::executableFromModule"); - return nullptr; - } - bool result = executable->loadPE(f, false); - fclose(f); - if (!result) { - DEBUG_LOG("wibo::executableFromModule: failed to load %s\n", path.c_str()); - return nullptr; - } - info->executable = std::move(executable); - } - return info->executable.get(); -} - struct UNICODE_STRING { unsigned short Length; unsigned short MaximumLength; @@ -410,6 +271,8 @@ int main(int argc, char **argv) { wibo::argv = argv + 1; wibo::argc = argc - 1; + wibo::initializeModuleRegistry(); + wibo::Executable exec; wibo::mainModule = &exec; @@ -432,6 +295,7 @@ int main(int argc, char **argv) { : "r"(tibSegment), "r"(exec.entryPoint) ); DEBUG_LOG("We came back\n"); + wibo::shutdownModuleRegistry(); return 1; } diff --git a/module_registry.cpp b/module_registry.cpp new file mode 100644 index 0000000..f835eed --- /dev/null +++ b/module_registry.cpp @@ -0,0 +1,815 @@ +#include "common.h" +#include "files.h" +#include "strutil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern const wibo::Module lib_advapi32; +extern const wibo::Module lib_bcrypt; +extern const wibo::Module lib_crt; +extern const wibo::Module lib_kernel32; +extern const wibo::Module lib_lmgr; +extern const wibo::Module lib_mscoree; +extern const wibo::Module lib_msvcrt; +extern const wibo::Module lib_ntdll; +extern const wibo::Module lib_ole32; +extern const wibo::Module lib_user32; +extern const wibo::Module lib_vcruntime; +extern const wibo::Module lib_version; + +namespace { + +constexpr DWORD DLL_PROCESS_DETACH = 0; +constexpr DWORD DLL_PROCESS_ATTACH = 1; + +struct PEExportDirectory { + uint32_t characteristics; + uint32_t timeDateStamp; + uint16_t majorVersion; + uint16_t minorVersion; + uint32_t name; + uint32_t base; + uint32_t numberOfFunctions; + uint32_t numberOfNames; + uint32_t addressOfFunctions; + uint32_t addressOfNames; + uint32_t addressOfNameOrdinals; +}; + +#define FOR_256_3(a, b, c, d) FOR_ITER((a << 6 | b << 4 | c << 2 | d)) +#define FOR_256_2(a, b) \ + FOR_256_3(a, b, 0, 0) \ + FOR_256_3(a, b, 0, 1) \ + FOR_256_3(a, b, 0, 2) FOR_256_3(a, b, 0, 3) FOR_256_3(a, b, 1, 0) FOR_256_3(a, b, 1, 1) FOR_256_3(a, b, 1, 2) \ + FOR_256_3(a, b, 1, 3) FOR_256_3(a, b, 2, 0) FOR_256_3(a, b, 2, 1) FOR_256_3(a, b, 2, 2) FOR_256_3(a, b, 2, 3) \ + FOR_256_3(a, b, 3, 0) FOR_256_3(a, b, 3, 1) FOR_256_3(a, b, 3, 2) FOR_256_3(a, b, 3, 3) +#define FOR_256 \ + FOR_256_2(0, 0) \ + FOR_256_2(0, 1) \ + FOR_256_2(0, 2) FOR_256_2(0, 3) FOR_256_2(1, 0) FOR_256_2(1, 1) FOR_256_2(1, 2) FOR_256_2(1, 3) FOR_256_2(2, 0) \ + FOR_256_2(2, 1) FOR_256_2(2, 2) FOR_256_2(2, 3) FOR_256_2(3, 0) FOR_256_2(3, 1) FOR_256_2(3, 2) \ + FOR_256_2(3, 3) + +static int stubIndex = 0; +static char stubDlls[0x100][0x100]; +static char stubFuncNames[0x100][0x100]; + +static void stubBase(int index) { + printf("Unhandled function %s (%s)\n", stubFuncNames[index], stubDlls[index]); + exit(1); +} + +void (*stubFuncs[0x100])(void) = { +#define FOR_ITER(i) []() { stubBase(i); }, + FOR_256 +#undef FOR_ITER +}; + +#undef FOR_256_3 +#undef FOR_256_2 +#undef FOR_256 + +void *resolveMissingFuncName(const char *dllName, const char *funcName) { + DEBUG_LOG("Missing function: %s (%s)\n", dllName, funcName); + assert(stubIndex < 0x100); + assert(strlen(dllName) < 0x100); + assert(strlen(funcName) < 0x100); + strcpy(stubFuncNames[stubIndex], funcName); + strcpy(stubDlls[stubIndex], dllName); + return (void *)stubFuncs[stubIndex++]; +} + +void *resolveMissingFuncOrdinal(const char *dllName, uint16_t ordinal) { + char buf[16]; + sprintf(buf, "%d", ordinal); + return resolveMissingFuncName(dllName, buf); +} + +} // namespace + +namespace { + +using ModulePtr = std::unique_ptr; + +struct ModuleRegistry { + std::recursive_mutex mutex; + std::unordered_map modulesByKey; + std::unordered_map modulesByAlias; + std::optional dllDirectory; + bool initialized = false; + std::unordered_map onExitTables; +}; + +ModuleRegistry ®istry() { + static ModuleRegistry reg; + return reg; +} + +std::string toLowerCopy(const std::string &value) { + std::string out = value; + std::transform(out.begin(), out.end(), out.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return out; +} + +std::string normalizeAlias(const std::string &value) { + std::string out = value; + std::replace(out.begin(), out.end(), '/', '\\'); + std::transform(out.begin(), out.end(), out.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return out; +} + +struct ParsedModuleName { + std::string original; + std::string directory; // Windows-style directory component (may be empty) + std::string base; + bool hasExtension = false; + bool endsWithDot = false; +}; + +ParsedModuleName parseModuleName(const std::string &name) { + ParsedModuleName parsed; + parsed.original = name; + parsed.base = name; + std::string sanitized = name; + std::replace(sanitized.begin(), sanitized.end(), '/', '\\'); + auto sep = sanitized.find_last_of('\\'); + if (sep != std::string::npos) { + parsed.directory = sanitized.substr(0, sep); + parsed.base = sanitized.substr(sep + 1); + } else { + parsed.base = sanitized; + } + parsed.endsWithDot = !parsed.base.empty() && parsed.base.back() == '.'; + parsed.hasExtension = (!parsed.endsWithDot) && parsed.base.find('.') != std::string::npos; + return parsed; +} + +std::vector candidateModuleNames(const ParsedModuleName &parsed) { + std::vector names; + if (!parsed.base.empty()) { + names.push_back(parsed.base); + if (!parsed.hasExtension && !parsed.endsWithDot) { + names.push_back(parsed.base + ".dll"); + } + } + return names; +} + +std::string normalizedBaseKey(const ParsedModuleName &parsed) { + if (parsed.base.empty()) { + return std::string(); + } + std::string base = parsed.base; + if (!parsed.hasExtension && !parsed.endsWithDot) { + base += ".dll"; + } + return normalizeAlias(base); +} + +std::optional findCaseInsensitiveFile(const std::filesystem::path &directory, + const std::string &filename) { + std::error_code ec; + if (directory.empty()) { + return std::nullopt; + } + if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec)) { + return std::nullopt; + } + const std::string lower = toLowerCopy(filename); + for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) { + if (ec) { + break; + } + const std::string candidate = toLowerCopy(entry.path().filename().string()); + if (candidate == lower) { + return std::filesystem::canonical(entry.path(), ec); + } + } + auto direct = directory / filename; + if (std::filesystem::exists(direct, ec)) { + return std::filesystem::canonical(direct, ec); + } + return std::nullopt; +} + +std::optional combineAndFind(const std::filesystem::path &directory, + const std::string &filename) { + if (filename.empty()) { + return std::nullopt; + } + if (directory.empty()) { + return std::nullopt; + } + return findCaseInsensitiveFile(directory, filename); +} + +std::filesystem::path canonicalPath(const std::filesystem::path &path) { + std::error_code ec; + auto canonical = std::filesystem::weakly_canonical(path, ec); + if (!ec) { + return canonical; + } + return std::filesystem::absolute(path); +} + +std::vector collectSearchDirectories(bool alteredSearchPath) { + std::vector dirs; + std::unordered_set seen; + auto addDirectory = [&](const std::filesystem::path &dir) { + if (dir.empty()) + return; + std::error_code ec; + auto canonical = std::filesystem::weakly_canonical(dir, ec); + if (ec) { + canonical = std::filesystem::absolute(dir, ec); + } + if (ec) + return; + if (!std::filesystem::exists(canonical, ec) || ec) + return; + std::string key = toLowerCopy(canonical.string()); + if (seen.insert(key).second) { + dirs.push_back(canonical); + } + }; + + auto ® = registry(); + + if (wibo::argv && wibo::argc > 0 && wibo::argv[0]) { + std::filesystem::path mainBinary = std::filesystem::absolute(wibo::argv[0]); + if (mainBinary.has_parent_path()) { + addDirectory(mainBinary.parent_path()); + } + } + + if (reg.dllDirectory.has_value()) { + addDirectory(*reg.dllDirectory); + } + + addDirectory(files::pathFromWindows("Z:/Windows/System32")); + addDirectory(files::pathFromWindows("Z:/Windows")); + + if (!alteredSearchPath) { + addDirectory(std::filesystem::current_path()); + } + + if (const char *envPath = std::getenv("PATH")) { + std::string pathList = envPath; + size_t start = 0; + while (start <= pathList.size()) { + size_t end = pathList.find_first_of(":;", start); + if (end == std::string::npos) { + end = pathList.size(); + } + if (end > start) { + auto piece = pathList.substr(start, end - start); + if (!piece.empty()) { + std::filesystem::path candidate(piece); + if (piece.find(':') != std::string::npos || piece.find('\\') != std::string::npos) { + auto converted = files::pathFromWindows(piece.c_str()); + if (!converted.empty()) { + candidate = converted; + } + } + addDirectory(candidate); + } + } + if (end == pathList.size()) { + break; + } + start = end + 1; + } + } + + return dirs; +} +std::optional resolveModuleOnDisk(const std::string &requestedName, bool alteredSearchPath) { + ParsedModuleName parsed = parseModuleName(requestedName); + auto names = candidateModuleNames(parsed); + + if (!parsed.directory.empty()) { + for (const auto &candidate : names) { + auto combined = parsed.directory + "\\" + candidate; + auto posixPath = files::pathFromWindows(combined.c_str()); + if (!posixPath.empty()) { + auto resolved = findCaseInsensitiveFile(std::filesystem::path(posixPath).parent_path(), + std::filesystem::path(posixPath).filename().string()); + if (resolved) { + return canonicalPath(*resolved); + } + } + } + return std::nullopt; + } + + auto dirs = collectSearchDirectories(alteredSearchPath); + for (const auto &dir : dirs) { + for (const auto &candidate : names) { + auto resolved = combineAndFind(dir, candidate); + if (resolved) { + return canonicalPath(*resolved); + } + } + } + + return std::nullopt; +} + +std::string storageKeyForPath(const std::filesystem::path &path) { + return normalizeAlias(files::pathToWindows(canonicalPath(path))); +} + +std::string storageKeyForBuiltin(const std::string &normalizedName) { return normalizedName; } + +wibo::ModuleInfo *findByAlias(const std::string &alias) { + auto ® = registry(); + auto it = reg.modulesByAlias.find(alias); + if (it != reg.modulesByAlias.end()) { + return it->second; + } + return nullptr; +} + +void registerAlias(const std::string &alias, wibo::ModuleInfo *info) { + if (alias.empty() || !info) { + return; + } + auto ® = registry(); + if (reg.modulesByAlias.find(alias) == reg.modulesByAlias.end()) { + reg.modulesByAlias[alias] = info; + } +} + +void registerBuiltinModule(const wibo::Module *module) { + if (!module) { + return; + } + ModulePtr entry = std::make_unique(); + entry->module = module; + entry->refCount = UINT_MAX; + entry->originalName = module->names[0] ? module->names[0] : ""; + entry->normalizedName = normalizedBaseKey(parseModuleName(entry->originalName)); + entry->exportsInitialized = true; + auto storageKey = storageKeyForBuiltin(entry->normalizedName); + auto raw = entry.get(); + auto ® = registry(); + reg.modulesByKey[storageKey] = std::move(entry); + + for (size_t i = 0; module->names[i]; ++i) { + registerAlias(normalizeAlias(module->names[i]), raw); + ParsedModuleName parsed = parseModuleName(module->names[i]); + registerAlias(normalizedBaseKey(parsed), raw); + } +} + +void callDllMain(wibo::ModuleInfo &info, DWORD reason) { + if (!info.entryPoint || info.module) { + return; + } + using DllMainFunc = BOOL(WIN_FUNC *)(HMODULE, DWORD, LPVOID); + auto dllMain = reinterpret_cast(info.entryPoint); + if (!dllMain) { + return; + } + if (reason == 1) { + if (info.processAttachCalled) { + return; + } + info.processAttachCalled = true; + BOOL result = dllMain(reinterpret_cast(info.imageBase), reason, nullptr); + info.processAttachSucceeded = result != 0; + } else if (reason == 0) { + if (info.processAttachCalled && info.processAttachSucceeded) { + dllMain(reinterpret_cast(info.imageBase), reason, nullptr); + } + } +} + +void ensureInitialized() { + auto ® = registry(); + if (reg.initialized) { + return; + } + reg.initialized = true; + + const wibo::Module *builtins[] = { + &lib_advapi32, &lib_bcrypt, &lib_crt, &lib_kernel32, &lib_lmgr, &lib_mscoree, &lib_msvcrt, + &lib_ntdll, &lib_ole32, &lib_user32, &lib_vcruntime, &lib_version, nullptr, + }; + + for (const wibo::Module **module = builtins; *module; ++module) { + registerBuiltinModule(*module); + } +} + +void registerExternalModuleAliases(const std::string &requestedName, const std::filesystem::path &resolvedPath, + wibo::ModuleInfo *info) { + ParsedModuleName parsed = parseModuleName(requestedName); + registerAlias(normalizedBaseKey(parsed), info); + registerAlias(normalizeAlias(requestedName), info); + registerAlias(storageKeyForPath(resolvedPath), info); +} + +wibo::ModuleInfo *moduleFromAddress(void *addr) { + if (!addr) + return nullptr; + auto ® = registry(); + for (auto &pair : reg.modulesByKey) { + wibo::ModuleInfo *info = pair.second.get(); + if (!info) + continue; + uint8_t *base = nullptr; + size_t size = 0; + if (info->imageBase && info->imageSize) { + base = static_cast(info->imageBase); + size = info->imageSize; + } else if (info->executable) { + base = static_cast(info->executable->imageBuffer); + size = info->executable->imageSize; + } + if (!base || size == 0) + continue; + uint8_t *ptr = static_cast(addr); + if (ptr >= base && ptr < base + size) { + return info; + } + } + return nullptr; +} + +void ensureExportsInitialized(wibo::ModuleInfo &info) { + if (info.module || info.exportsInitialized) + return; + if (!info.executable) + return; + auto *exe = info.executable.get(); + if (!exe->exportDirectoryRVA || !exe->exportDirectorySize) { + info.exportsInitialized = true; + return; + } + + auto *dir = exe->fromRVA(exe->exportDirectoryRVA); + info.exportOrdinalBase = dir->base; + uint32_t functionCount = dir->numberOfFunctions; + info.exportsByOrdinal.assign(functionCount, nullptr); + if (functionCount) { + auto *functions = exe->fromRVA(dir->addressOfFunctions); + for (uint32_t i = 0; i < functionCount; ++i) { + uint32_t rva = functions[i]; + if (!rva) { + continue; + } + if (rva >= exe->exportDirectoryRVA && rva < exe->exportDirectoryRVA + exe->exportDirectorySize) { + const char *forward = exe->fromRVA(rva); + info.exportsByOrdinal[i] = resolveMissingFuncName(info.originalName.c_str(), forward); + } else { + info.exportsByOrdinal[i] = exe->fromRVA(rva); + } + } + } + + uint32_t nameCount = dir->numberOfNames; + if (nameCount) { + auto *names = exe->fromRVA(dir->addressOfNames); + auto *ordinals = exe->fromRVA(dir->addressOfNameOrdinals); + for (uint32_t i = 0; i < nameCount; ++i) { + uint16_t index = ordinals[i]; + uint16_t ordinal = static_cast(dir->base + index); + if (index < info.exportsByOrdinal.size()) { + const char *namePtr = exe->fromRVA(names[i]); + info.exportNameToOrdinal[std::string(namePtr)] = ordinal; + } + } + } + info.exportsInitialized = true; +} + +} // namespace + +namespace wibo { + +void initializeModuleRegistry() { + std::lock_guard lock(registry().mutex); + ensureInitialized(); +} + +void shutdownModuleRegistry() { + std::lock_guard lock(registry().mutex); + for (auto &pair : registry().modulesByKey) { + ModuleInfo *info = pair.second.get(); + if (!info || info->module) { + continue; + } + runPendingOnExit(*info); + if (info->processAttachCalled && info->processAttachSucceeded) { + callDllMain(*info, DLL_PROCESS_DETACH); + } + } + registry().modulesByKey.clear(); + registry().modulesByAlias.clear(); + registry().dllDirectory.reset(); + registry().initialized = false; + registry().onExitTables.clear(); +} + +ModuleInfo *moduleInfoFromHandle(HMODULE module) { return static_cast(module); } + +void setDllDirectoryOverride(const std::filesystem::path &path) { + auto canonical = canonicalPath(path); + std::lock_guard lock(registry().mutex); + registry().dllDirectory = canonical; +} + +void clearDllDirectoryOverride() { + std::lock_guard lock(registry().mutex); + registry().dllDirectory.reset(); +} + +std::optional dllDirectoryOverride() { + std::lock_guard lock(registry().mutex); + return registry().dllDirectory; +} + +void registerOnExitTable(void *table) { + if (!table) + return; + std::lock_guard lock(registry().mutex); + ensureInitialized(); + auto ® = registry(); + if (reg.onExitTables.find(table) == reg.onExitTables.end()) { + if (auto *info = moduleFromAddress(table)) { + reg.onExitTables[table] = info; + } + } +} + +void addOnExitFunction(void *table, void (*func)()) { + if (!func) + return; + std::lock_guard lock(registry().mutex); + auto ® = registry(); + ModuleInfo *info = nullptr; + auto it = reg.onExitTables.find(table); + if (it != reg.onExitTables.end()) { + info = it->second; + } else if (table) { + info = moduleFromAddress(table); + if (info) + reg.onExitTables[table] = info; + } + if (info) { + info->onExitFunctions.push_back(reinterpret_cast(func)); + } +} + +void runPendingOnExit(ModuleInfo &info) { + for (auto it = info.onExitFunctions.rbegin(); it != info.onExitFunctions.rend(); ++it) { + auto fn = reinterpret_cast(*it); + if (fn) { + fn(); + } + } + info.onExitFunctions.clear(); +} + +void executeOnExitTable(void *table) { + std::lock_guard lock(registry().mutex); + auto ® = registry(); + ModuleInfo *info = nullptr; + if (table) { + auto it = reg.onExitTables.find(table); + if (it != reg.onExitTables.end()) { + info = it->second; + reg.onExitTables.erase(it); + } else { + info = moduleFromAddress(table); + } + } + if (info) { + runPendingOnExit(*info); + } +} + +HMODULE findLoadedModule(const char *name) { + if (!name) { + return nullptr; + } + std::lock_guard lock(registry().mutex); + ensureInitialized(); + ParsedModuleName parsed = parseModuleName(name); + std::string alias = normalizedBaseKey(parsed); + ModuleInfo *info = findByAlias(alias); + if (!info) { + info = findByAlias(normalizeAlias(name)); + } + return info; +} + +HMODULE loadModule(const char *dllName) { + if (!dllName) { + lastError = ERROR_INVALID_PARAMETER; + return nullptr; + } + std::string requested(dllName); +DEBUG_LOG("loadModule(%s)\n", requested.c_str()); + + std::lock_guard lock(registry().mutex); + ensureInitialized(); + + ParsedModuleName parsed = parseModuleName(requested); + std::string alias = normalizedBaseKey(parsed); + ModuleInfo *existing = findByAlias(alias); + if (!existing) { + existing = findByAlias(normalizeAlias(requested)); + } + if (existing) { + DEBUG_LOG(" found existing module alias %s\n", alias.c_str()); + if (existing->refCount != UINT_MAX) { + existing->refCount++; + } + lastError = ERROR_SUCCESS; + return existing; + } + + auto resolvedPath = resolveModuleOnDisk(requested, false); + if (!resolvedPath) { + DEBUG_LOG(" module not found on disk\n"); + lastError = ERROR_MOD_NOT_FOUND; + return nullptr; + } + + std::string key = storageKeyForPath(*resolvedPath); + auto ® = registry(); + auto it = reg.modulesByKey.find(key); + if (it != reg.modulesByKey.end()) { + ModuleInfo *info = it->second.get(); + info->refCount++; + registerExternalModuleAliases(requested, *resolvedPath, info); + lastError = ERROR_SUCCESS; + return info; + } + + FILE *file = fopen(resolvedPath->c_str(), "rb"); + if (!file) { + perror("loadModule"); + lastError = ERROR_MOD_NOT_FOUND; + return nullptr; + } + + auto executable = std::make_unique(); + if (!executable->loadPE(file, true)) { + DEBUG_LOG(" loadPE failed for %s\n", resolvedPath->c_str()); + fclose(file); + lastError = ERROR_BAD_EXE_FORMAT; + return nullptr; + } + fclose(file); + + ModulePtr info = std::make_unique(); + info->module = nullptr; + info->originalName = requested; + info->normalizedName = alias; + info->resolvedPath = *resolvedPath; + info->executable = std::move(executable); + info->entryPoint = info->executable->entryPoint; + info->imageBase = info->executable->imageBuffer; + info->imageSize = info->executable->imageSize; + info->refCount = 1; + info->dataFile = false; + info->dontResolveReferences = false; + + ModuleInfo *raw = info.get(); + reg.modulesByKey[key] = std::move(info); + registerExternalModuleAliases(requested, *resolvedPath, raw); + ensureExportsInitialized(*raw); + + callDllMain(*raw, DLL_PROCESS_ATTACH); + lastError = ERROR_SUCCESS; + + return raw; +} + +void freeModule(HMODULE module) { + if (!module) { + return; + } + std::lock_guard lock(registry().mutex); + ModuleInfo *info = moduleInfoFromHandle(module); + if (!info || info->refCount == UINT_MAX) { + return; + } + if (info->refCount == 0) { + return; + } + info->refCount--; + if (info->refCount == 0) { + auto ® = registry(); + for (auto it = reg.onExitTables.begin(); it != reg.onExitTables.end();) { + if (it->second == info) { + it = reg.onExitTables.erase(it); + } else { + ++it; + } + } + runPendingOnExit(*info); + callDllMain(*info, DLL_PROCESS_DETACH); + std::string key = info->resolvedPath.empty() ? storageKeyForBuiltin(info->normalizedName) + : storageKeyForPath(info->resolvedPath); + reg.modulesByKey.erase(key); + for (auto it = reg.modulesByAlias.begin(); it != reg.modulesByAlias.end();) { + if (it->second == info) { + it = reg.modulesByAlias.erase(it); + } else { + ++it; + } + } + } +} + +void *resolveFuncByName(HMODULE module, const char *funcName) { + ModuleInfo *info = moduleInfoFromHandle(module); + if (!info) { + return nullptr; + } + if (info->module && info->module->byName) { + void *func = info->module->byName(funcName); + if (func) { + return func; + } + } + ensureExportsInitialized(*info); + if (!info->module) { + auto it = info->exportNameToOrdinal.find(funcName); + if (it != info->exportNameToOrdinal.end()) { + return resolveFuncByOrdinal(module, it->second); + } + } + return resolveMissingFuncName(info->originalName.c_str(), funcName); +} + +void *resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) { + ModuleInfo *info = moduleInfoFromHandle(module); + if (!info) { + return nullptr; + } + if (info->module && info->module->byOrdinal) { + void *func = info->module->byOrdinal(ordinal); + if (func) { + return func; + } + } + if (!info->module) { + ensureExportsInitialized(*info); + if (!info->exportsByOrdinal.empty() && ordinal >= info->exportOrdinalBase) { + size_t index = static_cast(ordinal - info->exportOrdinalBase); + if (index < info->exportsByOrdinal.size()) { + void *addr = info->exportsByOrdinal[index]; + if (addr) { + return addr; + } + } + } + } + return resolveMissingFuncOrdinal(info->originalName.c_str(), ordinal); +} + +Executable *executableFromModule(HMODULE module) { + if (isMainModule(module)) { + return mainModule; + } + ModuleInfo *info = moduleInfoFromHandle(module); + if (!info) { + return nullptr; + } + if (!info->executable && !info->resolvedPath.empty()) { + FILE *file = fopen(info->resolvedPath.c_str(), "rb"); + if (!file) { + perror("executableFromModule"); + return nullptr; + } + auto executable = std::make_unique(); + if (!executable->loadPE(file, false)) { + DEBUG_LOG("executableFromModule: failed to load %s\n", info->resolvedPath.c_str()); + fclose(file); + return nullptr; + } + fclose(file); + info->executable = std::move(executable); + } + return info->executable.get(); +} + +} // namespace wibo diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..2b4d545 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,20 @@ +CC = i686-w64-mingw32-gcc +CFLAGS = -Wall -Wextra -O2 + +DLL_SRC = external_exports.c +EXE_SRC = test_external_dll.c +DLL = external_exports.dll +EXE = test_external_dll.exe + +all: $(DLL) $(EXE) + +$(DLL): $(DLL_SRC) + $(CC) $(CFLAGS) -shared -o $@ $< + +$(EXE): $(EXE_SRC) + $(CC) $(CFLAGS) -o $@ $< + +clean: + rm -f $(DLL) $(EXE) + +.PHONY: all clean diff --git a/test/external_exports.c b/test/external_exports.c new file mode 100644 index 0000000..8057edf --- /dev/null +++ b/test/external_exports.c @@ -0,0 +1,22 @@ +#include + +static int attached = 0; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { + (void) hinstDLL; + (void) lpReserved; + if (fdwReason == DLL_PROCESS_ATTACH) { + attached = 1; + } else if (fdwReason == DLL_PROCESS_DETACH) { + attached = 2; + } + return TRUE; +} + +__declspec(dllexport) int __stdcall add_numbers(int a, int b) { + return a + b; +} + +__declspec(dllexport) int __stdcall was_attached(void) { + return attached; +} diff --git a/test/test_external_dll.c b/test/test_external_dll.c new file mode 100644 index 0000000..65d21cb --- /dev/null +++ b/test/test_external_dll.c @@ -0,0 +1,32 @@ +#include +#include + +int main(void) { +typedef int (__stdcall *add_numbers_fn)(int, int); +typedef int (__stdcall *was_attached_fn)(void); + + HMODULE mod = LoadLibraryA("external_exports.dll"); + if (!mod) { + printf("LoadLibraryA failed: %lu\n", GetLastError()); + return 1; + } + + add_numbers_fn add_numbers = (add_numbers_fn)GetProcAddress(mod, "add_numbers@8"); + was_attached_fn was_attached = (was_attached_fn)GetProcAddress(mod, "was_attached@0"); + if (!add_numbers || !was_attached) { + printf("GetProcAddress failed: %lu\n", GetLastError()); + return 1; + } + + int sum = add_numbers(2, 40); + int attached = was_attached(); + + printf("sum=%d attached=%d\n", sum, attached); + + if (!FreeLibrary(mod)) { + printf("FreeLibrary failed: %lu\n", GetLastError()); + return 1; + } + + return (sum == 42 && attached == 1) ? 0 : 2; +} From 720e6639a99e44cbcfa447f241c0a9d0a74601ac Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 01:16:52 -0600 Subject: [PATCH 04/28] Return ERROR_RESOURCE_DATA_NOT_FOUND if open_resource_stream fails --- common.h | 1 + dll/kernel32.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/common.h b/common.h index 8d56669..5f4a8e9 100644 --- a/common.h +++ b/common.h @@ -60,6 +60,7 @@ typedef unsigned char BYTE; #define ERROR_INVALID_PARAMETER 87 #define ERROR_BUFFER_OVERFLOW 111 #define ERROR_INSUFFICIENT_BUFFER 122 +#define ERROR_RESOURCE_DATA_NOT_FOUND 1812 #define ERROR_MOD_NOT_FOUND 126 #define ERROR_NEGATIVE_SEEK 131 #define ERROR_BAD_EXE_FORMAT 193 diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 40a5ad3..ceaf447 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -1711,7 +1711,11 @@ namespace kernel32 { const std::string name = resource_identifier_to_string(lpName); const std::string type = resource_identifier_to_string(lpType); - return open_resource_stream(type, name); + FILE *res = open_resource_stream(type, name); + if (!res) { + wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; + } + return res; } // https://github.com/reactos/reactos/blob/master/dll/win32/kernelbase/wine/loader.c#L1090 From 01ddf95d36802898411ae211ffbba96d4ead93e3 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 01:51:25 -0600 Subject: [PATCH 05/28] Add proper resource implementation --- CMakeLists.txt | 1 + common.h | 47 +++++++- dll/kernel32.cpp | 134 +++++++++++----------- dll/user32.cpp | 118 ++++++-------------- loader.cpp | 2 + resources.cpp | 248 +++++++++++++++++++++++++++++++++++++++++ resources.h | 13 +++ test/Makefile | 20 +++- test/test_resources.c | 25 +++++ test/test_resources.rc | 31 ++++++ 10 files changed, 483 insertions(+), 156 deletions(-) create mode 100644 resources.cpp create mode 100644 resources.h create mode 100644 test/test_resources.c create mode 100644 test/test_resources.rc diff --git a/CMakeLists.txt b/CMakeLists.txt index 755a4cb..eaed480 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable(wibo files.cpp handles.cpp loader.cpp + resources.cpp module_registry.cpp main.cpp processes.cpp diff --git a/common.h b/common.h index 5f4a8e9..4201116 100644 --- a/common.h +++ b/common.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +62,9 @@ typedef unsigned char BYTE; #define ERROR_BUFFER_OVERFLOW 111 #define ERROR_INSUFFICIENT_BUFFER 122 #define ERROR_RESOURCE_DATA_NOT_FOUND 1812 +#define ERROR_RESOURCE_TYPE_NOT_FOUND 1813 +#define ERROR_RESOURCE_NAME_NOT_FOUND 1814 +#define ERROR_RESOURCE_LANG_NOT_FOUND 1815 #define ERROR_MOD_NOT_FOUND 126 #define ERROR_NEGATIVE_SEEK 131 #define ERROR_BAD_EXE_FORMAT 193 @@ -118,6 +122,39 @@ namespace wibo { void *resolveFuncByName(HMODULE module, const char *funcName); void *resolveFuncByOrdinal(HMODULE module, uint16_t ordinal); + struct ResourceIdentifier { + ResourceIdentifier() : isString(false), id(0) {} + static ResourceIdentifier fromID(uint32_t value) { + ResourceIdentifier ident; + ident.isString = false; + ident.id = value; + return ident; + } + static ResourceIdentifier fromString(std::u16string value) { + ResourceIdentifier ident; + ident.isString = true; + ident.name = std::move(value); + return ident; + } + bool isString; + uint32_t id; + std::u16string name; + }; + + struct ResourceLocation { + const void *dataEntry = nullptr; + const void *data = nullptr; + uint32_t size = 0; + uint16_t language = 0; + }; + + struct ImageResourceDataEntry { + uint32_t offsetToData; + uint32_t size; + uint32_t codePage; + uint32_t reserved; + }; + struct Executable { Executable(); ~Executable(); @@ -127,6 +164,7 @@ namespace wibo { size_t imageSize; void *entryPoint; void *rsrcBase; + uint32_t rsrcSize; uintptr_t preferredImageBase; intptr_t relocationDelta; uint32_t exportDirectoryRVA; @@ -134,13 +172,18 @@ namespace wibo { uint32_t relocationDirectoryRVA; uint32_t relocationDirectorySize; + bool findResource(const ResourceIdentifier &type, + const ResourceIdentifier &name, + std::optional language, + ResourceLocation &out) const; + template - T *fromRVA(uint32_t rva) { + T *fromRVA(uint32_t rva) const { return (T *) (rva + (uint8_t *) imageBuffer); } template - T *fromRVA(T *rva) { + T *fromRVA(T *rva) const { return fromRVA((uint32_t) rva); } }; diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index ceaf447..97f05c3 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -2,6 +2,7 @@ #include "files.h" #include "processes.h" #include "handles.h" +#include "resources.h" #include #include #include @@ -1674,89 +1675,74 @@ namespace kernel32 { return copyLen; } - static std::string resource_identifier_to_string(const char *id) { - if (!id) { - return ""; - } - if ((uintptr_t)id >> 16 == 0) { - return std::to_string(static_cast((uintptr_t)id)); + static wibo::Executable *module_executable_for_resource(void *hModule) { + if (!hModule) { + hModule = GetModuleHandleA(nullptr); } - return id; - } + return wibo::executableFromModule((HMODULE) hModule); + } - static std::string resource_identifier_to_string(const uint16_t *id) { - if (!id) { - return ""; + static void *find_resource_internal(void *hModule, + const wibo::ResourceIdentifier &type, + const wibo::ResourceIdentifier &name, + std::optional language) { + auto *exe = module_executable_for_resource(hModule); + if (!exe) { + wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; + return nullptr; } - if ((uintptr_t)id >> 16 == 0) { - return std::to_string(static_cast((uintptr_t)id)); + wibo::ResourceLocation loc; + if (!exe->findResource(type, name, language, loc)) { + return nullptr; } - return wideStringToString(id); - } - - static FILE *open_resource_stream(const std::string &type, const std::string &name) { - char path[512]; - snprintf(path, sizeof(path), "resources/%s/%s.res", type.c_str(), name.c_str()); - DEBUG_LOG("Created path %s\n", path); - return fopen(path, "rb"); + return const_cast(loc.dataEntry); } void *WIN_FUNC FindResourceA(void *hModule, const char *lpName, const char *lpType) { - DEBUG_LOG("FindResourceA %p %s %s\n", hModule, lpName, lpType); - - if (!hModule) { - hModule = GetModuleHandleA(nullptr); - } - - const std::string name = resource_identifier_to_string(lpName); - const std::string type = resource_identifier_to_string(lpType); + DEBUG_LOG("FindResourceA %p %p %p\n", hModule, lpName, lpType); + auto type = wibo::resourceIdentifierFromAnsi(lpType); + auto name = wibo::resourceIdentifierFromAnsi(lpName); + return find_resource_internal(hModule, type, name, std::nullopt); + } - FILE *res = open_resource_stream(type, name); - if (!res) { - wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; - } - return res; + void *WIN_FUNC FindResourceExA(void *hModule, const char *lpType, const char *lpName, uint16_t wLanguage) { + DEBUG_LOG("FindResourceExA %p %p %p %u\n", hModule, lpName, lpType, wLanguage); + auto type = wibo::resourceIdentifierFromAnsi(lpType); + auto name = wibo::resourceIdentifierFromAnsi(lpName); + return find_resource_internal(hModule, type, name, wLanguage); } - // https://github.com/reactos/reactos/blob/master/dll/win32/kernelbase/wine/loader.c#L1090 - // https://github.com/wine-mirror/wine/blob/master/dlls/kernelbase/loader.c#L1097 void *WIN_FUNC FindResourceW(void *hModule, const uint16_t *lpName, const uint16_t *lpType) { DEBUG_LOG("FindResourceW %p\n", hModule); + auto type = wibo::resourceIdentifierFromWide(lpType); + auto name = wibo::resourceIdentifierFromWide(lpName); + return find_resource_internal(hModule, type, name, std::nullopt); + } - if (!hModule) - hModule = GetModuleHandleW(0); - - const std::string name = resource_identifier_to_string(lpName); - const std::string type = resource_identifier_to_string(lpType); - - return open_resource_stream(type, name); + void *WIN_FUNC FindResourceExW(void *hModule, const uint16_t *lpType, const uint16_t *lpName, uint16_t wLanguage) { + DEBUG_LOG("FindResourceExW %p %u\n", hModule, wLanguage); + auto type = wibo::resourceIdentifierFromWide(lpType); + auto name = wibo::resourceIdentifierFromWide(lpName); + return find_resource_internal(hModule, type, name, wLanguage); } void* WIN_FUNC LoadResource(void* hModule, void* res) { DEBUG_LOG("LoadResource %p %p\n", hModule, res); - - if(!hModule || !res) return nullptr; - FILE* hRes = (FILE*)res; - - long pos = ftell(hRes); - DEBUG_LOG("Pos: %d\n", pos); - fseek(hRes, 0, SEEK_END); - long size = ftell(hRes); - fseek(hRes, pos, SEEK_SET); - DEBUG_LOG("Size: %d\n", size); - - if(size <= 0) return nullptr; - - void* buffer = malloc(size); - if(!buffer) return nullptr; - - if(fread(buffer, 1, size, hRes) != (size_t)size){ - free(buffer); + if (!res) { + wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; return nullptr; } - return buffer; - - // return (void*)0x100003; + auto *exe = module_executable_for_resource(hModule); + if (!exe || !exe->rsrcBase) { + wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; + return nullptr; + } + const auto *entry = reinterpret_cast(res); + if (!wibo::resourceEntryBelongsToExecutable(*exe, entry)) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return nullptr; + } + return const_cast(exe->fromRVA(entry->offsetToData)); } BOOL WIN_FUNC GetDiskFreeSpaceExW(const uint16_t* lpDirectoryName, @@ -1785,12 +1771,26 @@ namespace kernel32 { void* WIN_FUNC LockResource(void* res) { DEBUG_LOG("LockResource %p\n", res); - return (void*)0x100004; + return res; } unsigned int WIN_FUNC SizeofResource(void* hModule, void* res) { DEBUG_LOG("SizeofResource %p %p\n", hModule, res); - return 0; + if (!res) { + wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; + return 0; + } + auto *exe = module_executable_for_resource(hModule); + if (!exe || !exe->rsrcBase) { + wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; + return 0; + } + const auto *entry = reinterpret_cast(res); + if (!wibo::resourceEntryBelongsToExecutable(*exe, entry)) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + return entry->size; } HMODULE WIN_FUNC LoadLibraryA(LPCSTR lpLibFileName) { @@ -2712,7 +2712,9 @@ static void *resolveByName(const char *name) { if (strcmp(name, "GetCurrentDirectoryA") == 0) return (void *) kernel32::GetCurrentDirectoryA; if (strcmp(name, "GetCurrentDirectoryW") == 0) return (void *) kernel32::GetCurrentDirectoryW; if (strcmp(name, "FindResourceA") == 0) return (void *) kernel32::FindResourceA; + if (strcmp(name, "FindResourceExA") == 0) return (void *) kernel32::FindResourceExA; if (strcmp(name, "FindResourceW") == 0) return (void *) kernel32::FindResourceW; + if (strcmp(name, "FindResourceExW") == 0) return (void *) kernel32::FindResourceExW; if (strcmp(name, "SetHandleCount") == 0) return (void *) kernel32::SetHandleCount; if (strcmp(name, "FormatMessageA") == 0) return (void *) kernel32::FormatMessageA; if (strcmp(name, "GetComputerNameA") == 0) return (void *) kernel32::GetComputerNameA; diff --git a/dll/user32.cpp b/dll/user32.cpp index eda8744..b9cbaea 100644 --- a/dll/user32.cpp +++ b/dll/user32.cpp @@ -1,104 +1,54 @@ #include "common.h" +#include "strutil.h" namespace user32 { - struct Resource { - uint32_t id; - uint32_t value; - }; + constexpr uint32_t RT_STRING_ID = 6; - struct ResourceTable { - char pad[12]; - uint16_t nameEntryCount; - uint16_t idEntryCount; - Resource resources[]; - }; - - static unsigned int searchResourceTableByID(const char *tableAddr, unsigned int id) { - ResourceTable* table = (ResourceTable*)tableAddr; - for (int i = 0; i < table->idEntryCount; i++) { - const Resource& r = table->resources[table->nameEntryCount + i]; - if (r.id == id) { - return r.value; - } - } - return 0; - } - - static unsigned int* getResourceByID(wibo::Executable *mod, unsigned int typeID, unsigned int nameID, unsigned int languageID) { - const char *rsrcBase = (const char *)mod->rsrcBase; - - if (rsrcBase == 0) { - DEBUG_LOG("getResourceByID: no .rsrc section\n"); - wibo::lastError = 1812; // ERROR_RESOURCE_DATA_NOT_FOUND + int WIN_FUNC LoadStringA(void* hInstance, unsigned int uID, char* lpBuffer, int cchBufferMax) { + DEBUG_LOG("LoadStringA %p %u %d\n", hInstance, uID, cchBufferMax); + if (!lpBuffer || cchBufferMax <= 0) { return 0; } - - unsigned int typeTable = searchResourceTableByID(rsrcBase, typeID) & 0x7FFFFFFFu; - if (typeTable == 0) { - DEBUG_LOG("getResourceByID: no type table with id = %s\n", typeID); - wibo::lastError = 1813; // ERROR_RESOURCE_TYPE_NOT_FOUND + wibo::Executable *mod = wibo::executableFromModule((HMODULE) hInstance); + if (!mod) { return 0; } - - unsigned int nameTable = searchResourceTableByID(rsrcBase + typeTable, nameID) & 0x7FFFFFFFu; - if (nameTable == 0) { - DEBUG_LOG("getResourceByID: no name table with id = %s\n", nameID); - wibo::lastError = 1814; // ERROR_RESOURCE_NAME_NOT_FOUND + wibo::ResourceIdentifier type = wibo::ResourceIdentifier::fromID(RT_STRING_ID); + wibo::ResourceIdentifier table = wibo::ResourceIdentifier::fromID((uID >> 4) + 1); + wibo::ResourceLocation loc; + if (!mod->findResource(type, table, std::nullopt, loc)) { return 0; } - - unsigned int langEntry = searchResourceTableByID(rsrcBase + nameTable, languageID); - if (langEntry == 0) { - DEBUG_LOG("getResourceByID: no lang entry with id = %s\n", languageID); - wibo::lastError = 1814; // ERROR_RESOURCE_NAME_NOT_FOUND - return 0; + const uint16_t *cursor = reinterpret_cast(loc.data); + const uint16_t *end = cursor + (loc.size / sizeof(uint16_t)); + unsigned int entryIndex = uID & 0x0Fu; + for (unsigned int i = 0; i < entryIndex; ++i) { + if (cursor >= end) { + return 0; + } + uint16_t length = *cursor++; + if (cursor + length > end) { + return 0; + } + cursor += length; } - - return (unsigned int*)(rsrcBase + langEntry); - } - - static const char *getStringFromTable(wibo::Executable *mod, unsigned int uID) { - unsigned int tableID = (uID >> 4) + 1; - unsigned int entryID = uID & 15; - unsigned int* stringTable = getResourceByID(mod, 6, tableID, 1033); - if (stringTable == 0) + if (cursor >= end) { return 0; - - // what's in here? - const char *str = mod->fromRVA(stringTable[0]); - unsigned int size = stringTable[1]; - assert(entryID < size); - - // skip over strings to get to the one we want - for (unsigned int i = 0; i < entryID; i++) { - int stringSize = *(uint16_t*)str; - str += 2; - str += stringSize * 2; } - - return str; - } - - int WIN_FUNC LoadStringA(void* hInstance, unsigned int uID, char* lpBuffer, int cchBufferMax) { - DEBUG_LOG("LoadStringA %p %d %d\n", hInstance, uID, cchBufferMax); - wibo::Executable *mod = wibo::executableFromModule(hInstance); - if (!mod) { + uint16_t length = *cursor++; + if (cursor + length > end) { return 0; } - const char* s = getStringFromTable(mod, uID); - if (!s) { - return 0; + int copyLength = length; + if (copyLength > cchBufferMax - 1) { + copyLength = cchBufferMax - 1; } - int len = *(int16_t*)s; - s += 2; - assert(cchBufferMax != 0); - len = (len < cchBufferMax - 1 ? len : cchBufferMax - 1); - for (int i = 0; i < len; i++) { - lpBuffer[i] = s[i * 2]; + for (int i = 0; i < copyLength; ++i) { + lpBuffer[i] = static_cast(cursor[i] & 0xFF); } - lpBuffer[len] = 0; - DEBUG_LOG("returning: %s\n", lpBuffer); - return len; + lpBuffer[copyLength] = 0; + DEBUG_LOG("LoadStringA -> %.*s\n", copyLength, lpBuffer); + return copyLength; } int WIN_FUNC MessageBoxA(void *hwnd, const char *lpText, const char *lpCaption, unsigned int uType) { diff --git a/loader.cpp b/loader.cpp index 5d5b80d..b074cf7 100644 --- a/loader.cpp +++ b/loader.cpp @@ -117,6 +117,7 @@ wibo::Executable::Executable() { imageSize = 0; entryPoint = nullptr; rsrcBase = 0; + rsrcSize = 0; preferredImageBase = 0; relocationDelta = 0; exportDirectoryRVA = 0; @@ -211,6 +212,7 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { if (strcmp(name, ".rsrc") == 0) { rsrcBase = sectionBase; + rsrcSize = std::max(section.virtualSize, section.sizeOfRawData); } } diff --git a/resources.cpp b/resources.cpp new file mode 100644 index 0000000..380bbbb --- /dev/null +++ b/resources.cpp @@ -0,0 +1,248 @@ +#include "common.h" +#include "resources.h" + +namespace { + +struct ImageResourceDirectory { + uint32_t characteristics; + uint32_t timeDateStamp; + uint16_t majorVersion; + uint16_t minorVersion; + uint16_t numberOfNamedEntries; + uint16_t numberOfIdEntries; +}; + +struct ImageResourceDirectoryEntry { + uint32_t name; + uint32_t offsetToData; +}; + +constexpr uint32_t RESOURCE_NAME_IS_STRING = 0x80000000u; +constexpr uint32_t RESOURCE_DATA_IS_DIRECTORY = 0x80000000u; + +const ImageResourceDirectoryEntry *resourceEntries(const ImageResourceDirectory *dir) { + return reinterpret_cast(dir + 1); +} + +bool resourceOffsetInRange(uint32_t offset, size_t needed, uint32_t available) { + if (available == 0) + return true; + if (offset > available) + return false; + if (available - offset < needed) + return false; + return true; +} + +bool resourceNameEquals(const uint8_t *base, uint32_t nameField, const std::u16string &value, uint32_t rsrcSize) { + if (!(nameField & RESOURCE_NAME_IS_STRING)) + return false; + uint32_t offset = nameField & ~RESOURCE_NAME_IS_STRING; + if (!resourceOffsetInRange(offset, sizeof(uint16_t), rsrcSize)) + return false; + const auto *lengthPtr = reinterpret_cast(base + offset); + uint16_t length = *lengthPtr; + size_t bytesNeeded = sizeof(uint16_t) + static_cast(length) * sizeof(uint16_t); + if (!resourceOffsetInRange(offset, bytesNeeded, rsrcSize)) + return false; + if (length != value.size()) + return false; + const uint16_t *str = lengthPtr + 1; + for (uint16_t i = 0; i < length; ++i) { + if (str[i] != value[i]) + return false; + } + return true; +} + +const ImageResourceDirectoryEntry *findEntry(const uint8_t *base, const ImageResourceDirectory *dir, + const wibo::ResourceIdentifier &ident, + uint32_t rsrcSize) { + const auto *entries = resourceEntries(dir); + if (ident.isString) { + for (uint16_t i = 0; i < dir->numberOfNamedEntries; ++i) { + const auto &entry = entries[i]; + if (resourceNameEquals(base, entry.name, ident.name, rsrcSize)) + return &entry; + } + return nullptr; + } + for (uint16_t i = 0; i < dir->numberOfIdEntries; ++i) { + const auto &entry = entries[dir->numberOfNamedEntries + i]; + if (!(entry.name & RESOURCE_NAME_IS_STRING) && (entry.name & 0xFFFFu) == (ident.id & 0xFFFFu)) + return &entry; + } + return nullptr; +} + +const ImageResourceDirectory *entryAsDirectory(const uint8_t *base, const ImageResourceDirectoryEntry *entry, uint32_t rsrcSize) { + if (!(entry->offsetToData & RESOURCE_DATA_IS_DIRECTORY)) + return nullptr; + uint32_t offset = entry->offsetToData & ~RESOURCE_DATA_IS_DIRECTORY; + if (!resourceOffsetInRange(offset, sizeof(ImageResourceDirectory), rsrcSize)) + return nullptr; + return reinterpret_cast(base + offset); +} + +const wibo::ImageResourceDataEntry *entryAsData(const uint8_t *base, const ImageResourceDirectoryEntry *entry, uint32_t rsrcSize) { + if (entry->offsetToData & RESOURCE_DATA_IS_DIRECTORY) + return nullptr; + uint32_t offset = entry->offsetToData; + if (!resourceOffsetInRange(offset, sizeof(wibo::ImageResourceDataEntry), rsrcSize)) + return nullptr; + return reinterpret_cast(base + offset); +} + +uint16_t primaryLang(uint16_t lang) { + return lang & 0x3FFu; +} + +const ImageResourceDirectoryEntry *selectLanguageEntry(const ImageResourceDirectory *dir, + const uint8_t *base, + uint32_t rsrcSize, + std::optional desired, + uint16_t &chosenLang) { + const auto *entries = resourceEntries(dir); + uint16_t total = dir->numberOfNamedEntries + dir->numberOfIdEntries; + const ImageResourceDirectoryEntry *primaryMatch = nullptr; + const ImageResourceDirectoryEntry *neutralMatch = nullptr; + const ImageResourceDirectoryEntry *first = nullptr; + for (uint16_t i = 0; i < total; ++i) { + const auto &entry = entries[i]; + if (entry.name & RESOURCE_NAME_IS_STRING) + continue; + uint16_t lang = static_cast(entry.name & 0xFFFFu); + if (!first) + first = &entry; + if (desired && lang == desired.value()) { + chosenLang = lang; + return &entry; + } + if (!primaryMatch && desired && primaryLang(lang) == primaryLang(desired.value())) { + primaryMatch = &entry; + } + if (!neutralMatch && lang == 0) + neutralMatch = &entry; + } + if (primaryMatch) { + chosenLang = static_cast(primaryMatch->name & 0xFFFFu); + return primaryMatch; + } + if (neutralMatch) { + chosenLang = 0; + return neutralMatch; + } + if (first) { + chosenLang = static_cast(first->name & 0xFFFFu); + return first; + } + return nullptr; +} + +} // namespace + +namespace wibo { + +bool Executable::findResource(const ResourceIdentifier &type, + const ResourceIdentifier &name, + std::optional language, + ResourceLocation &out) const { + const uint8_t *base = reinterpret_cast(rsrcBase); + if (!base) { + wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; + return false; + } + const auto *root = reinterpret_cast(base); + const auto *typeEntry = findEntry(base, root, type, rsrcSize); + if (!typeEntry) { + wibo::lastError = ERROR_RESOURCE_TYPE_NOT_FOUND; + return false; + } + const auto *nameDir = entryAsDirectory(base, typeEntry, rsrcSize); + if (!nameDir) { + wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; + return false; + } + const auto *nameEntry = findEntry(base, nameDir, name, rsrcSize); + if (!nameEntry) { + wibo::lastError = ERROR_RESOURCE_NAME_NOT_FOUND; + return false; + } + const auto *langDir = entryAsDirectory(base, nameEntry, rsrcSize); + if (!langDir) { + wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; + return false; + } + uint16_t chosenLang = language.value_or(0); + const auto *langEntry = selectLanguageEntry(langDir, base, rsrcSize, language, chosenLang); + if (!langEntry) { + wibo::lastError = ERROR_RESOURCE_LANG_NOT_FOUND; + return false; + } + const auto *dataEntry = entryAsData(base, langEntry, rsrcSize); + if (!dataEntry) { + wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; + return false; + } + out.dataEntry = dataEntry; + out.data = fromRVA(dataEntry->offsetToData); + out.size = dataEntry->size; + out.language = chosenLang; + return true; +} + +bool resourceEntryBelongsToExecutable(const Executable &exe, const ImageResourceDataEntry *entry) { + if (!entry || !exe.rsrcBase) + return false; + const auto *base = reinterpret_cast(exe.rsrcBase); + const auto *ptr = reinterpret_cast(entry); + if (exe.rsrcSize == 0) + return true; + return ptr >= base && (ptr + sizeof(*entry)) <= (base + exe.rsrcSize); +} + +static bool isIntegerIdentifier(const void *ptr) { + return ((uintptr_t)ptr >> 16) == 0; +} + +static std::u16string ansiToU16String(const char *str) { + std::u16string result; + if (!str) + return result; + while (*str) { + result.push_back(static_cast(*str++)); + } + return result; +} + +static std::u16string wideToU16String(const uint16_t *str) { + std::u16string result; + if (!str) + return result; + while (*str) { + result.push_back(*str++); + } + return result; +} + +ResourceIdentifier resourceIdentifierFromAnsi(const char *id) { + if (!id) { + return ResourceIdentifier::fromID(0); + } + if (isIntegerIdentifier(id)) { + return ResourceIdentifier::fromID(static_cast(reinterpret_cast(id))); + } + return ResourceIdentifier::fromString(ansiToU16String(id)); +} + +ResourceIdentifier resourceIdentifierFromWide(const uint16_t *id) { + if (!id) { + return ResourceIdentifier::fromID(0); + } + if (isIntegerIdentifier(id)) { + return ResourceIdentifier::fromID(static_cast(reinterpret_cast(id))); + } + return ResourceIdentifier::fromString(wideToU16String(id)); +} + +} // namespace wibo diff --git a/resources.h b/resources.h new file mode 100644 index 0000000..28e80d6 --- /dev/null +++ b/resources.h @@ -0,0 +1,13 @@ +#pragma once + + +namespace wibo { + +struct Executable; +struct ImageResourceDataEntry; + +bool resourceEntryBelongsToExecutable(const Executable &exe, const ImageResourceDataEntry *entry); +ResourceIdentifier resourceIdentifierFromAnsi(const char *id); +ResourceIdentifier resourceIdentifierFromWide(const uint16_t *id); + +} diff --git a/test/Makefile b/test/Makefile index 2b4d545..4b4eebb 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,4 +1,5 @@ CC = i686-w64-mingw32-gcc +WINDRES = i686-w64-mingw32-windres CFLAGS = -Wall -Wextra -O2 DLL_SRC = external_exports.c @@ -6,15 +7,26 @@ EXE_SRC = test_external_dll.c DLL = external_exports.dll EXE = test_external_dll.exe -all: $(DLL) $(EXE) +RES_EXE_SRC = test_resources.c +RES_RC = test_resources.rc +RES_OBJ = test_resources_res.o +RES_EXE = test_resources.exe + +all: $(DLL) $(EXE) $(RES_EXE) $(DLL): $(DLL_SRC) - $(CC) $(CFLAGS) -shared -o $@ $< + $(CC) $(CFLAGS) -shared -o $@ $< $(EXE): $(EXE_SRC) - $(CC) $(CFLAGS) -o $@ $< + $(CC) $(CFLAGS) -o $@ $< + +$(RES_OBJ): $(RES_RC) + $(WINDRES) $< -O coff -o $@ + +$(RES_EXE): $(RES_EXE_SRC) $(RES_OBJ) + $(CC) $(CFLAGS) -o $@ $^ clean: - rm -f $(DLL) $(EXE) + rm -f $(DLL) $(EXE) $(RES_EXE) $(RES_OBJ) .PHONY: all clean diff --git a/test/test_resources.c b/test/test_resources.c new file mode 100644 index 0000000..04c40a1 --- /dev/null +++ b/test/test_resources.c @@ -0,0 +1,25 @@ +#include +#include + +int main(void) { + char buffer[128]; + int copied = LoadStringA(GetModuleHandleA(NULL), 100, buffer, sizeof(buffer)); + if (copied <= 0) { + printf("LoadString failed: %lu\n", GetLastError()); + return 1; + } + printf("STRING[100]=%s\n", buffer); + + HRSRC versionInfo = FindResourceA(NULL, MAKEINTRESOURCEA(1), MAKEINTRESOURCEA(RT_VERSION)); + if (!versionInfo) { + printf("FindResource version failed: %lu\n", GetLastError()); + return 1; + } + DWORD versionSize = SizeofResource(NULL, versionInfo); + if (!versionSize) { + printf("SizeofResource failed: %lu\n", GetLastError()); + return 1; + } + printf("VERSION size=%lu\n", (unsigned long)versionSize); + return 0; +} diff --git a/test/test_resources.rc b/test/test_resources.rc new file mode 100644 index 0000000..bd9c30a --- /dev/null +++ b/test/test_resources.rc @@ -0,0 +1,31 @@ +#include + +#define IDS_SAMPLE 100 + +STRINGTABLE +BEGIN + IDS_SAMPLE "Resource string 100" +END + +1 VERSIONINFO +FILEVERSION 1,2,3,4 +PRODUCTVERSION 1,2,3,4 +FILEFLAGSMASK 0x3fL +FILEFLAGS 0x0L +FILEOS 0x40004L +FILETYPE 0x1L +FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "FileDescription", "Test Resource Binary\0" + VALUE "ProductVersion", "1.2.3-test\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 + END +END From 82e2809b33abaa241206fde78b36c1a7b633eef7 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 01:52:57 -0600 Subject: [PATCH 06/28] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c98ff23..b9d6c44 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,6 @@ Rough to-do list: - Implement more APIs - Do something intelligent with Windows `HANDLE`s - Convert paths in environment variables (and the structure of `PATH` itself, maybe) to Windows format -- Implement PE relocations rather than just failing unceremoniously -- Land external DLL loading support (module registry + search order + export resolution) --- From c14ad86d72bc5631bb1765df0a1e7ff33c88120a Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 09:49:21 -0600 Subject: [PATCH 07/28] Implement version.dll properly --- dll/crt.cpp | 19 +++ dll/msvcrt.cpp | 13 ++ dll/version.cpp | 317 +++++++++++++++++++++++++++++++++++++++++- resources.h | 1 + test/Makefile | 2 +- test/test_resources.c | 62 +++++++++ 6 files changed, 407 insertions(+), 7 deletions(-) diff --git a/dll/crt.cpp b/dll/crt.cpp index 99d6259..93d1e0f 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -14,6 +14,10 @@ typedef void (*_invalid_parameter_handler)(const wchar_t *, const wchar_t *, con extern char **environ; +namespace msvcrt { + int WIN_ENTRY puts(const char *str); +} + typedef enum _crt_app_type { _crt_unknown_app, _crt_console_app, @@ -193,6 +197,17 @@ int WIN_ENTRY __stdio_common_vfprintf(unsigned long long /*options*/, FILE *stre return vfprintf(stream, format, args); } +int WIN_ENTRY __stdio_common_vsprintf(unsigned long long /*options*/, char *buffer, size_t len, const char *format, void * /*locale*/, va_list args) { + if (!buffer || !format) + return -1; + int result = vsnprintf(buffer, len, format, args); + if (result < 0) + return -1; + if (len > 0 && static_cast(result) >= len) + return -1; + return result; +} + } // namespace crt static void *resolveByName(const char *name) { @@ -258,6 +273,10 @@ static void *resolveByName(const char *name) { return (void *)crt::__acrt_iob_func; if (strcmp(name, "__stdio_common_vfprintf") == 0) return (void *)crt::__stdio_common_vfprintf; + if (strcmp(name, "__stdio_common_vsprintf") == 0) + return (void *)crt::__stdio_common_vsprintf; + if (strcmp(name, "puts") == 0) + return (void *)msvcrt::puts; if (strcmp(name, "__setusermatherr") == 0) return (void *)crt::__setusermatherr; if (strcmp(name, "_initialize_onexit_table") == 0) diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 9b0cede..e83f80c 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -522,6 +522,18 @@ namespace msvcrt { else return 0; } + int WIN_ENTRY puts(const char *str) { + if (!str) { + str = "(null)"; + } + DEBUG_LOG("puts %s\n", str); + if (std::fputs(str, stdout) < 0) + return EOF; + if (std::fputc('\n', stdout) == EOF) + return EOF; + return 0; + } + int WIN_ENTRY fclose(FILE* stream){ return ::fclose(stream); } @@ -649,6 +661,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "_dup2") == 0) return (void*)msvcrt::_dup2; if (strcmp(name, "_wfsopen") == 0) return (void*)msvcrt::_wfsopen; if (strcmp(name, "fputws") == 0) return (void*)msvcrt::fputws; + if (strcmp(name, "puts") == 0) return (void*)msvcrt::puts; if (strcmp(name, "fclose") == 0) return (void*)msvcrt::fclose; if (strcmp(name, "_flushall") == 0) return (void*)msvcrt::_flushall; if (strcmp(name, "_errno") == 0) return (void*)msvcrt::_errno; diff --git a/dll/version.cpp b/dll/version.cpp index e558b2f..06521c7 100644 --- a/dll/version.cpp +++ b/dll/version.cpp @@ -1,18 +1,323 @@ #include "common.h" +#include "files.h" +#include "resources.h" +#include "strutil.h" -namespace version { - unsigned int WIN_FUNC GetFileVersionInfoSizeA(const char* lptstrFilename, unsigned int* outZero) { - DEBUG_LOG("GetFileVersionInfoSizeA %s\n", lptstrFilename); - if (outZero != NULL) { - *outZero = 0; +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr uint32_t RT_VERSION = 16; + +static uint16_t read_u16(const uint8_t *ptr) { + return static_cast(ptr[0] | (ptr[1] << 8)); +} + +static size_t align4(size_t offset) { + return (offset + 3u) & ~static_cast(3u); +} + +static std::string narrowKey(const std::u16string &key) { + std::string result; + result.reserve(key.size()); + for (char16_t ch : key) { + result.push_back(static_cast(ch & 0xFF)); + } + return result; +} + +struct VersionBlockView { + uint16_t totalLength = 0; + uint16_t valueLength = 0; + uint16_t type = 0; + std::u16string key; + const uint8_t *valuePtr = nullptr; + uint32_t valueBytes = 0; + const uint8_t *childrenPtr = nullptr; + uint32_t childrenBytes = 0; +}; + +static bool parseVersionBlock(const uint8_t *block, size_t available, VersionBlockView &out) { + if (available < sizeof(uint16_t) * 3) { + DEBUG_LOG("header too small: available=%zu\n", available); + return false; + } + + uint16_t totalLength = read_u16(block); + uint16_t valueLength = read_u16(block + sizeof(uint16_t)); + uint16_t type = read_u16(block + sizeof(uint16_t) * 2); + if (totalLength == 0 || totalLength > available) { + DEBUG_LOG("invalid totalLength=%u available=%zu\n", totalLength, available); + return false; + } + + const uint8_t *end = block + totalLength; + const uint8_t *cursor = block + sizeof(uint16_t) * 3; + out.key.clear(); + while (cursor + sizeof(uint16_t) <= end) { + uint16_t ch = read_u16(cursor); + cursor += sizeof(uint16_t); + if (!ch) + break; + out.key.push_back(static_cast(ch)); + } + DEBUG_LOG("parsed key fragment=%s\n", narrowKey(out.key).c_str()); + + cursor = block + sizeof(uint16_t) * 3 + (out.key.size() + 1) * sizeof(uint16_t); + if (cursor > end) { + DEBUG_LOG("key cursor beyond block: cursor=%zu end=%zu\n", static_cast(cursor - block), static_cast(end - block)); + return false; + } + + cursor = block + align4(static_cast(cursor - block)); + + uint32_t valueBytes = 0; + if (valueLength) { + valueBytes = type == 1 ? static_cast(valueLength) * sizeof(uint16_t) + : static_cast(valueLength); + if (cursor + valueBytes > end) { + DEBUG_LOG("value beyond block: bytes=%u remaining=%zu\n", valueBytes, static_cast(end - cursor)); + return false; } - wibo::lastError = 0; + } + + const uint8_t *children = block + align4(static_cast((cursor + valueBytes) - block)); + if (children > end) + children = end; + + out.totalLength = totalLength; + out.valueLength = valueLength; + out.type = type; + out.valuePtr = valueLength ? cursor : nullptr; + out.valueBytes = valueBytes; + out.childrenPtr = children; + out.childrenBytes = static_cast(end - children); + return true; +} + +static std::string toLowerCopy(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); + return str; +} + +static bool queryVersionBlock(const uint8_t *block, size_t available, + const std::vector &segments, + size_t depth, + const uint8_t **outPtr, + uint32_t *outLen, + uint16_t *outType) { + VersionBlockView view; + if (!parseVersionBlock(block, available, view)) + return false; + + if (depth == segments.size()) { + if (outPtr) + *outPtr = view.valueBytes ? view.valuePtr : nullptr; + if (outLen) + *outLen = view.type == 1 ? view.valueLength : view.valueBytes; + if (outType) + *outType = view.type; + return true; + } + + const std::string targetLower = toLowerCopy(segments[depth]); + const uint8_t *cursor = view.childrenPtr; + const uint8_t *end = view.childrenPtr + view.childrenBytes; + + while (cursor + 6 <= end) { + const uint8_t *childStart = cursor; + VersionBlockView child; + if (!parseVersionBlock(cursor, static_cast(end - cursor), child)) + break; + if (child.totalLength == 0) + break; + std::string childKeyLower = toLowerCopy(narrowKey(child.key)); + if (childKeyLower == targetLower) { + if (queryVersionBlock(childStart, child.totalLength, segments, depth + 1, outPtr, outLen, outType)) + return true; + } + size_t offset = static_cast(child.totalLength); + cursor = childStart + align4(offset); + if (cursor <= childStart || cursor > end) + break; + } + return false; +} + +static bool splitSubBlock(const std::string &subBlock, std::vector &segments) { + segments.clear(); + if (subBlock.empty() || subBlock == "\\") + return true; + + const char *cursor = subBlock.c_str(); + if (*cursor == '\\') + ++cursor; + + while (*cursor) { + const char *next = std::strchr(cursor, '\\'); + if (!next) + next = cursor + std::strlen(cursor); + segments.emplace_back(cursor, static_cast(next - cursor)); + cursor = *next ? next + 1 : next; + } + return true; +} + +static bool loadVersionResource(const char *fileName, std::vector &buffer) { + if (!fileName) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return false; + } + + auto hostPath = files::pathFromWindows(fileName); + std::string hostPathStr = hostPath.string(); + FILE *fp = std::fopen(hostPathStr.c_str(), "rb"); + if (!fp) { + wibo::lastError = ERROR_FILE_NOT_FOUND; + return false; + } + + wibo::Executable executable; + if (!executable.loadPE(fp, false)) { + std::fclose(fp); + wibo::lastError = ERROR_BAD_EXE_FORMAT; + return false; + } + + std::fclose(fp); + + wibo::ResourceIdentifier type = wibo::ResourceIdentifier::fromID(RT_VERSION); + wibo::ResourceIdentifier name = wibo::ResourceIdentifier::fromID(1); + wibo::ResourceLocation loc; + if (!executable.findResource(type, name, std::nullopt, loc)) { + auto nameString = wibo::ResourceIdentifier::fromString(u"VS_VERSION_INFO"); + if (!executable.findResource(type, nameString, std::nullopt, loc)) + return false; + } + + const uint8_t *start = static_cast(loc.data); + buffer.assign(start, start + loc.size); + wibo::lastError = ERROR_SUCCESS; + return true; +} + +} // namespace + +namespace version { + +unsigned int WIN_FUNC GetFileVersionInfoSizeA(const char *lptstrFilename, unsigned int *lpdwHandle) { + DEBUG_LOG("GetFileVersionInfoSizeA %s\n", lptstrFilename); + if (lpdwHandle) + *lpdwHandle = 0; + + std::vector buffer; + if (!loadVersionResource(lptstrFilename, buffer)) + return 0; + return static_cast(buffer.size()); +} + +unsigned int WIN_FUNC GetFileVersionInfoA(const char *lptstrFilename, unsigned int dwHandle, unsigned int dwLen, void *lpData) { + (void) dwHandle; + DEBUG_LOG("GetFileVersionInfoA %s len=%u\n", lptstrFilename, dwLen); + if (!lpData || dwLen == 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; return 0; } + + std::vector buffer; + if (!loadVersionResource(lptstrFilename, buffer)) + return 0; + + if (buffer.size() > dwLen) { + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + + std::memcpy(lpData, buffer.data(), buffer.size()); + if (buffer.size() < dwLen) { + std::memset(static_cast(lpData) + buffer.size(), 0, dwLen - buffer.size()); + } + wibo::lastError = ERROR_SUCCESS; + return 1; } +static unsigned int VerQueryValueImpl(const void *pBlock, const std::string &subBlock, void **lplpBuffer, unsigned int *puLen) { + if (!pBlock) + return 0; + + const uint8_t *base = static_cast(pBlock); + uint16_t totalLength = read_u16(base); + if (totalLength < 6) + return 0; + + std::vector segments; + if (!splitSubBlock(subBlock, segments)) + return 0; + + const uint8_t *outPtr = nullptr; + uint32_t outLen = 0; + uint16_t outType = 0; + if (!queryVersionBlock(base, totalLength, segments, 0, &outPtr, &outLen, &outType)) + return 0; + + if (outType == 1 && outPtr) { + char *dest = reinterpret_cast(const_cast(outPtr)); + std::string narrow = wideStringToString(reinterpret_cast(outPtr), static_cast(outLen)); + std::memcpy(dest, narrow.c_str(), narrow.size() + 1); + if (lplpBuffer) + *lplpBuffer = dest; + if (puLen) + *puLen = static_cast(narrow.size()); + return 1; + } + + if (lplpBuffer) + *lplpBuffer = const_cast(outPtr); + if (puLen) + *puLen = outLen; + return 1; +} + +unsigned int WIN_FUNC VerQueryValueA(const void *pBlock, const char *lpSubBlock, void **lplpBuffer, unsigned int *puLen) { + DEBUG_LOG("VerQueryValueA %p %s\n", pBlock, lpSubBlock ? lpSubBlock : "(null)"); + if (!lpSubBlock) + return 0; + return VerQueryValueImpl(pBlock, lpSubBlock, lplpBuffer, puLen); +} + +unsigned int WIN_FUNC GetFileVersionInfoSizeW(const uint16_t *lptstrFilename, unsigned int *lpdwHandle) { + auto narrow = wideStringToString(lptstrFilename); + return GetFileVersionInfoSizeA(narrow.c_str(), lpdwHandle); +} + +unsigned int WIN_FUNC GetFileVersionInfoW(const uint16_t *lptstrFilename, unsigned int dwHandle, unsigned int dwLen, void *lpData) { + auto narrow = wideStringToString(lptstrFilename); + return GetFileVersionInfoA(narrow.c_str(), dwHandle, dwLen, lpData); +} + +unsigned int WIN_FUNC VerQueryValueW(const void *pBlock, const uint16_t *lpSubBlock, void **lplpBuffer, unsigned int *puLen) { + if (!lpSubBlock) + return 0; + auto narrow = wideStringToString(lpSubBlock); + DEBUG_LOG("VerQueryValueW %p %s\n", pBlock, narrow.c_str()); + return VerQueryValueImpl(pBlock, narrow, lplpBuffer, puLen); +} + +} // namespace version + static void *resolveByName(const char *name) { if (strcmp(name, "GetFileVersionInfoSizeA") == 0) return (void *) version::GetFileVersionInfoSizeA; + if (strcmp(name, "GetFileVersionInfoA") == 0) return (void *) version::GetFileVersionInfoA; + if (strcmp(name, "VerQueryValueA") == 0) return (void *) version::VerQueryValueA; + if (strcmp(name, "GetFileVersionInfoSizeW") == 0) return (void *) version::GetFileVersionInfoSizeW; + if (strcmp(name, "GetFileVersionInfoW") == 0) return (void *) version::GetFileVersionInfoW; + if (strcmp(name, "VerQueryValueW") == 0) return (void *) version::VerQueryValueW; return nullptr; } diff --git a/resources.h b/resources.h index 28e80d6..5612743 100644 --- a/resources.h +++ b/resources.h @@ -5,6 +5,7 @@ namespace wibo { struct Executable; struct ImageResourceDataEntry; +struct ResourceIdentifier; bool resourceEntryBelongsToExecutable(const Executable &exe, const ImageResourceDataEntry *entry); ResourceIdentifier resourceIdentifierFromAnsi(const char *id); diff --git a/test/Makefile b/test/Makefile index 4b4eebb..1fad9e8 100644 --- a/test/Makefile +++ b/test/Makefile @@ -24,7 +24,7 @@ $(RES_OBJ): $(RES_RC) $(WINDRES) $< -O coff -o $@ $(RES_EXE): $(RES_EXE_SRC) $(RES_OBJ) - $(CC) $(CFLAGS) -o $@ $^ + $(CC) $(CFLAGS) -o $@ $^ -lversion clean: rm -f $(DLL) $(EXE) $(RES_EXE) $(RES_OBJ) diff --git a/test/test_resources.c b/test/test_resources.c index 04c40a1..9401ed3 100644 --- a/test/test_resources.c +++ b/test/test_resources.c @@ -1,5 +1,6 @@ #include #include +#include int main(void) { char buffer[128]; @@ -21,5 +22,66 @@ int main(void) { return 1; } printf("VERSION size=%lu\n", (unsigned long)versionSize); + + char modulePath[MAX_PATH]; + DWORD moduleLen = GetModuleFileNameA(NULL, modulePath, sizeof(modulePath)); + if (moduleLen == 0 || moduleLen >= sizeof(modulePath)) { + printf("GetModuleFileNameA failed: %lu\n", GetLastError()); + return 1; + } + + DWORD handle = 0; + DWORD infoSize = GetFileVersionInfoSizeA(modulePath, &handle); + if (!infoSize) { + printf("GetFileVersionInfoSizeA failed: %lu\n", GetLastError()); + return 1; + } + + char *infoBuffer = (char *)malloc(infoSize); + if (!infoBuffer) { + printf("malloc failed\n"); + return 1; + } + + if (!GetFileVersionInfoA(modulePath, 0, infoSize, infoBuffer)) { + printf("GetFileVersionInfoA failed: %lu\n", GetLastError()); + free(infoBuffer); + return 1; + } + + VS_FIXEDFILEINFO *fixedInfo = NULL; + unsigned int fixedSize = 0; + if (!VerQueryValueA(infoBuffer, "\\", (void **)&fixedInfo, &fixedSize)) { + printf("VerQueryValueA root failed\n"); + free(infoBuffer); + return 1; + } + printf("FILEVERSION=%u.%u.%u.%u\n", + fixedInfo->dwFileVersionMS >> 16, + fixedInfo->dwFileVersionMS & 0xFFFF, + fixedInfo->dwFileVersionLS >> 16, + fixedInfo->dwFileVersionLS & 0xFFFF); + + struct { WORD wLanguage; WORD wCodePage; } *translations = NULL; + unsigned int transSize = 0; + if (VerQueryValueA(infoBuffer, "\\VarFileInfo\\Translation", (void **)&translations, &transSize) && + translations && transSize >= sizeof(*translations)) { + printf("Translation=%04X %04X\n", translations[0].wLanguage, translations[0].wCodePage); + char subBlock[64]; + snprintf(subBlock, sizeof(subBlock), "\\StringFileInfo\\%04X%04X\\ProductVersion", + translations[0].wLanguage, translations[0].wCodePage); + char *productVersion = NULL; + unsigned int pvSize = 0; + printf("Querying %s\n", subBlock); + if (VerQueryValueA(infoBuffer, subBlock, (void **)&productVersion, &pvSize) && productVersion) { + printf("PRODUCTVERSION=%s\n", productVersion); + } else { + printf("ProductVersion lookup failed\n"); + } + } else { + printf("ProductVersion lookup failed\n"); + } + + free(infoBuffer); return 0; } From 104e9e869db17a331b094a1d9012ef8764f27aae Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 10:39:09 -0600 Subject: [PATCH 08/28] Add proper testing framework & integrate with CI --- .github/workflows/ci.yml | 16 +++++- AGENTS.md | 32 ++++++++++++ CMakeLists.txt | 91 +++++++++++++++++++++++++++++++++ README.md | 33 ++++++++++-- dll/crt.cpp | 4 ++ test/.gitignore | 3 ++ test/Makefile | 32 ------------ test/test_assert.h | 56 ++++++++++++++++++++ test/test_external_dll.c | 42 +++++++-------- test/test_resources.c | 107 ++++++++++++++++++--------------------- 10 files changed, 300 insertions(+), 116 deletions(-) create mode 100644 AGENTS.md create mode 100644 test/.gitignore delete mode 100644 test/Makefile create mode 100644 test/test_assert.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85ca5f7..f4348d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,15 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y file unzip wget + sudo apt-get install -y \ + file \ + unzip \ + wget \ + cmake \ + ninja-build \ + g++-multilib \ + gcc-mingw-w64-i686 \ + binutils-mingw-w64-i686 - name: Build debug run: docker build --build-arg build_type=Debug --target export --output build_debug . @@ -40,6 +48,12 @@ jobs: build/wibo Wii/1.7/mwcceppc.exe -nodefaults -c test/test.c -Itest -o test.o file test.o + - name: Fixture tests + run: | + cmake -S . -B build_ctest -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON -DWIBO_ENABLE_FIXTURE_TESTS=ON + cmake --build build_ctest + ctest --test-dir build_ctest --output-on-failure + - name: Upload release uses: actions/upload-artifact@v4 with: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..af8fd14 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- Core launcher logic lives in `main.cpp`, `loader.cpp`, `files.cpp`, `handles.cpp` and `module_registry.cpp`; shared interfaces in headers near them. +- Windows API shims reside in `dll/`, grouped by emulated DLL name; keep new APIs in the matching file instead of creating ad-hoc helpers. +- Reusable utilities sit in `strutil.*`, `processes.*` and `resources.*`; prefer extending these before introducing new singleton modules. +- Sample fixtures for exercising the loader live in `test/`; keep new repros small and self-contained. + +## Build, Test, and Development Commands +- `cmake -B build -DCMAKE_BUILD_TYPE=Debug` configures a 32-bit toolchain; ensure multilib packages are present. +- `cmake --build build --target wibo` compiles the shim; switch to `-DCMAKE_BUILD_TYPE=Release` for optimised binaries. +- `./build/wibo /path/to/program.exe` runs a Windows binary through the shim; use `WIBO_DEBUG=1` for verbose logging. +- `cmake -B build -DBUILD_TESTING=ON` + `ctest --test-dir build --output-on-failure` runs the self-checking WinAPI fixtures (requires `i686-w64-mingw32-gcc` and `i686-w64-mingw32-windres`). +- `clang-format -i path/to/file.cpp` and `clang-tidy path/to/file.cpp -p build` keep contributions aligned with the repo's tooling. + +## Coding Style & Naming Conventions +- Formatting follows `.clang-format` (LLVM base, tabbed indentation width 4, 120 column limit); never hand-wrap differently. +- Prefer PascalCase for emulated Win32 entry points, camelCase for internal helpers, and SCREAMING_SNAKE_CASE for constants or macros. +- Document non-obvious control flow with short comments and keep platform-specific code paths behind descriptive helper functions. + +## Testing Guidelines +- Fixture binaries live in `test/` and are compiled automatically when `BUILD_TESTING` is enabled; keep new repros small and self-contained (`test_.c`). +- All fixtures must self-assert; use `test_assert.h` helpers so `ctest` fails on mismatched WinAPI behaviour. +- Cross-compile new repros with `i686-w64-mingw32-gcc` (and `i686-w64-mingw32-windres` for resources); CMake handles this during the build, but direct invocation is useful while iterating. +- Run `ctest --test-dir build --output-on-failure` after rebuilding to verify changes; ensure failures print actionable diagnostics. + +## Debugging Workflow +- Reproduce crashes under `gdb` (or `lldb`) with `-q -batch` to capture backtraces, register state, and the faulting instruction without interactive prompts. +- Enable `WIBO_DEBUG=1` and tee output to a log when running the guest binary; loader traces often pinpoint missing imports, resource lookups, or API shims that misbehave. +- Inspect relevant source right away—most issues stem from stubbed shims in `dll/`; compare the guest stack from `gdb` with those implementations. +- When host-side behaviour is suspect (filesystem, execve, etc.), rerun under `strace -f -o `; this highlights missing files or permissions before the shim faults. +- If the `ghidra` MCP tool is available, request that the user import and analyze the guest binary; you can then use it to disassemble/decompile code around the crash site. diff --git a/CMakeLists.txt b/CMakeLists.txt index eaed480..4a9dd13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,3 +39,94 @@ add_executable(wibo ) target_link_libraries(wibo PRIVATE std::filesystem) install(TARGETS wibo DESTINATION bin) + +include(CTest) + +if(BUILD_TESTING) + find_program(WIBO_MINGW_CC i686-w64-mingw32-gcc) + find_program(WIBO_MINGW_WINDRES i686-w64-mingw32-windres) + + set(WIBO_HAVE_MINGW_TOOLCHAIN FALSE) + if(WIBO_MINGW_CC AND WIBO_MINGW_WINDRES) + set(WIBO_HAVE_MINGW_TOOLCHAIN TRUE) + endif() + + option(WIBO_ENABLE_FIXTURE_TESTS "Build and run Windows fixture binaries through wibo" ${WIBO_HAVE_MINGW_TOOLCHAIN}) + + if(WIBO_ENABLE_FIXTURE_TESTS) + if(NOT WIBO_HAVE_MINGW_TOOLCHAIN) + message(WARNING "MinGW toolchain not found; skipping fixture tests") + else() + set(WIBO_TEST_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}/test) + file(MAKE_DIRECTORY ${WIBO_TEST_BIN_DIR}) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/external_exports.dll + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 -shared + -o external_exports.dll + ${CMAKE_CURRENT_SOURCE_DIR}/test/external_exports.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/test/external_exports.c) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_external_dll.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_external_dll.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_external_dll.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_external_dll.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_resources_res.o + COMMAND ${WIBO_MINGW_WINDRES} + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_resources.rc + -O coff -o test_resources_res.o + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/test/test_resources.rc) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_resources.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_resources.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_resources.c + test_resources_res.o -lversion + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_resources.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h + ${WIBO_TEST_BIN_DIR}/test_resources_res.o) + + add_custom_target(wibo_test_fixtures + DEPENDS + ${WIBO_TEST_BIN_DIR}/external_exports.dll + ${WIBO_TEST_BIN_DIR}/test_external_dll.exe + ${WIBO_TEST_BIN_DIR}/test_resources.exe) + + if(CMAKE_CONFIGURATION_TYPES) + set(_wibo_fixture_build_command + ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --config $ --target wibo_test_fixtures) + else() + set(_wibo_fixture_build_command + ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target wibo_test_fixtures) + endif() + + add_test(NAME wibo.build_fixtures COMMAND ${_wibo_fixture_build_command}) + + add_test(NAME wibo.test_external_dll + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_external_dll.exe) + set_tests_properties(wibo.test_external_dll PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + + add_test(NAME wibo.test_resources + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_resources.exe) + set_tests_properties(wibo.test_resources PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + endif() + endif() +endif() diff --git a/README.md b/README.md index b9d6c44..93857be 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,36 @@ A minimal, low-fuss wrapper that can run really simple command-line 32-bit Windo Don't run this on any untrusted executables, I implore you. (Or probably just don't run it at all... :p) - cmake -B build - cmake --build build - build/wibo +## Building + +```sh +cmake -B build -DCMAKE_BUILD_TYPE=Debug +cmake --build build --target wibo +``` + +`cmake -B build -DCMAKE_BUILD_TYPE=Release` to produce an optimized binary instead. + +## Running + +```sh +./build/wibo /path/to/program.exe +# or, with debug logging: +WIBO_DEBUG=1 ./build/wibo /path/to/program.exe +``` + +## Tests + +Self-checking Windows fixtures run through CTest. They require a 32-bit MinGW cross toolchain (`i686-w64-mingw32-gcc` and `i686-w64-mingw32-windres`). + +With the toolchain installed: + +```sh +cmake -B build -DBUILD_TESTING=ON +cmake --build build +ctest --test-dir build --output-on-failure +``` + +This will cross-compile the fixture executables, run them through `wibo`, and fail if any WinAPI expectations are not met. --- diff --git a/dll/crt.cpp b/dll/crt.cpp index 93d1e0f..f7208cd 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -117,6 +117,8 @@ int *WIN_ENTRY __p___argc() { return &wibo::argc; } size_t WIN_ENTRY strlen(const char *str) { return ::strlen(str); } +int WIN_ENTRY strcmp(const char *lhs, const char *rhs) { return ::strcmp(lhs, rhs); } + int WIN_ENTRY strncmp(const char *lhs, const char *rhs, size_t count) { return ::strncmp(lhs, rhs, count); } void *WIN_ENTRY malloc(size_t size) { return ::malloc(size); } @@ -247,6 +249,8 @@ static void *resolveByName(const char *name) { return (void *)crt::__p___argc; if (strcmp(name, "strlen") == 0) return (void *)crt::strlen; + if (strcmp(name, "strcmp") == 0) + return (void *)crt::strcmp; if (strcmp(name, "strncmp") == 0) return (void *)crt::strncmp; if (strcmp(name, "malloc") == 0) diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..d19fcf8 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,3 @@ +*.o +*.dll +*.exe diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index 1fad9e8..0000000 --- a/test/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -CC = i686-w64-mingw32-gcc -WINDRES = i686-w64-mingw32-windres -CFLAGS = -Wall -Wextra -O2 - -DLL_SRC = external_exports.c -EXE_SRC = test_external_dll.c -DLL = external_exports.dll -EXE = test_external_dll.exe - -RES_EXE_SRC = test_resources.c -RES_RC = test_resources.rc -RES_OBJ = test_resources_res.o -RES_EXE = test_resources.exe - -all: $(DLL) $(EXE) $(RES_EXE) - -$(DLL): $(DLL_SRC) - $(CC) $(CFLAGS) -shared -o $@ $< - -$(EXE): $(EXE_SRC) - $(CC) $(CFLAGS) -o $@ $< - -$(RES_OBJ): $(RES_RC) - $(WINDRES) $< -O coff -o $@ - -$(RES_EXE): $(RES_EXE_SRC) $(RES_OBJ) - $(CC) $(CFLAGS) -o $@ $^ -lversion - -clean: - rm -f $(DLL) $(EXE) $(RES_EXE) $(RES_OBJ) - -.PHONY: all clean diff --git a/test/test_assert.h b/test/test_assert.h new file mode 100644 index 0000000..ea99683 --- /dev/null +++ b/test/test_assert.h @@ -0,0 +1,56 @@ +#ifndef WIBO_TEST_ASSERT_H +#define WIBO_TEST_ASSERT_H + +#include +#include +#include + +#define TEST_FAIL(fmt, ...) \ + do { \ + fprintf(stderr, "FAIL:%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ + exit(EXIT_FAILURE); \ + } while (0) + +#define TEST_CHECK(cond) \ + do { \ + if (!(cond)) { \ + TEST_FAIL("Assertion '%s' failed", #cond); \ + } \ + } while (0) + +#define TEST_CHECK_MSG(cond, fmt, ...) \ + do { \ + if (!(cond)) { \ + TEST_FAIL(fmt, ##__VA_ARGS__); \ + } \ + } while (0) + +#define TEST_CHECK_EQ(expected, actual) \ + do { \ + long long _expected_value = (long long)(expected); \ + long long _actual_value = (long long)(actual); \ + if (_expected_value != _actual_value) { \ + TEST_FAIL("Expected %s (%lld) == %s (%lld)", \ + #expected, _expected_value, #actual, _actual_value); \ + } \ + } while (0) + +#define TEST_CHECK_U64_EQ(expected, actual) \ + do { \ + unsigned long long _expected_value = (unsigned long long)(expected); \ + unsigned long long _actual_value = (unsigned long long)(actual); \ + if (_expected_value != _actual_value) { \ + TEST_FAIL("Expected %s (%llu) == %s (%llu)", \ + #expected, _expected_value, #actual, _actual_value); \ + } \ + } while (0) + +#define TEST_CHECK_STR_EQ(expected, actual) \ + do { \ + if (strcmp((expected), (actual)) != 0) { \ + TEST_FAIL("Expected %s (\"%s\") == %s (\"%s\")", \ + #expected, (expected), #actual, (actual)); \ + } \ + } while (0) + +#endif diff --git a/test/test_external_dll.c b/test/test_external_dll.c index 65d21cb..6c44755 100644 --- a/test/test_external_dll.c +++ b/test/test_external_dll.c @@ -1,32 +1,32 @@ #include +#include #include +#include "test_assert.h" + int main(void) { -typedef int (__stdcall *add_numbers_fn)(int, int); -typedef int (__stdcall *was_attached_fn)(void); + typedef int(__stdcall *add_numbers_fn)(int, int); + typedef int(__stdcall *was_attached_fn)(void); + + HMODULE mod = LoadLibraryA("external_exports.dll"); + TEST_CHECK_MSG(mod != NULL, "LoadLibraryA failed: %lu", (unsigned long)GetLastError()); - HMODULE mod = LoadLibraryA("external_exports.dll"); - if (!mod) { - printf("LoadLibraryA failed: %lu\n", GetLastError()); - return 1; - } + FARPROC raw_add_numbers = GetProcAddress(mod, "add_numbers@8"); + FARPROC raw_was_attached = GetProcAddress(mod, "was_attached@0"); + TEST_CHECK_MSG(raw_add_numbers != NULL, "GetProcAddress(add_numbers@8) failed: %lu", (unsigned long)GetLastError()); + TEST_CHECK_MSG(raw_was_attached != NULL, "GetProcAddress(was_attached@0) failed: %lu", (unsigned long)GetLastError()); - add_numbers_fn add_numbers = (add_numbers_fn)GetProcAddress(mod, "add_numbers@8"); - was_attached_fn was_attached = (was_attached_fn)GetProcAddress(mod, "was_attached@0"); - if (!add_numbers || !was_attached) { - printf("GetProcAddress failed: %lu\n", GetLastError()); - return 1; - } + add_numbers_fn add_numbers = (add_numbers_fn)(uintptr_t)raw_add_numbers; + was_attached_fn was_attached = (was_attached_fn)(uintptr_t)raw_was_attached; - int sum = add_numbers(2, 40); - int attached = was_attached(); + int sum = add_numbers(2, 40); + int attached = was_attached(); - printf("sum=%d attached=%d\n", sum, attached); + TEST_CHECK_EQ(42, sum); + TEST_CHECK_EQ(1, attached); - if (!FreeLibrary(mod)) { - printf("FreeLibrary failed: %lu\n", GetLastError()); - return 1; - } + TEST_CHECK_MSG(FreeLibrary(mod) != 0, "FreeLibrary failed: %lu", (unsigned long)GetLastError()); - return (sum == 42 && attached == 1) ? 0 : 2; + printf("external_exports: sum=%d attached=%d\n", sum, attached); + return EXIT_SUCCESS; } diff --git a/test/test_resources.c b/test/test_resources.c index 9401ed3..546ddd4 100644 --- a/test/test_resources.c +++ b/test/test_resources.c @@ -2,86 +2,75 @@ #include #include +#include "test_assert.h" + int main(void) { char buffer[128]; int copied = LoadStringA(GetModuleHandleA(NULL), 100, buffer, sizeof(buffer)); - if (copied <= 0) { - printf("LoadString failed: %lu\n", GetLastError()); - return 1; - } - printf("STRING[100]=%s\n", buffer); + TEST_CHECK_MSG(copied > 0, "LoadStringA failed: %lu", (unsigned long)GetLastError()); + TEST_CHECK_EQ((int)strlen("Resource string 100"), copied); + TEST_CHECK_STR_EQ("Resource string 100", buffer); HRSRC versionInfo = FindResourceA(NULL, MAKEINTRESOURCEA(1), MAKEINTRESOURCEA(RT_VERSION)); - if (!versionInfo) { - printf("FindResource version failed: %lu\n", GetLastError()); - return 1; - } + TEST_CHECK_MSG(versionInfo != NULL, "FindResourceA version failed: %lu", (unsigned long)GetLastError()); + DWORD versionSize = SizeofResource(NULL, versionInfo); - if (!versionSize) { - printf("SizeofResource failed: %lu\n", GetLastError()); - return 1; - } - printf("VERSION size=%lu\n", (unsigned long)versionSize); + TEST_CHECK_MSG(versionSize != 0, "SizeofResource failed: %lu", (unsigned long)GetLastError()); + TEST_CHECK_EQ(364, (int)versionSize); char modulePath[MAX_PATH]; DWORD moduleLen = GetModuleFileNameA(NULL, modulePath, sizeof(modulePath)); - if (moduleLen == 0 || moduleLen >= sizeof(modulePath)) { - printf("GetModuleFileNameA failed: %lu\n", GetLastError()); - return 1; - } + TEST_CHECK_MSG(moduleLen > 0 && moduleLen < sizeof(modulePath), + "GetModuleFileNameA failed: %lu", (unsigned long)GetLastError()); DWORD handle = 0; DWORD infoSize = GetFileVersionInfoSizeA(modulePath, &handle); - if (!infoSize) { - printf("GetFileVersionInfoSizeA failed: %lu\n", GetLastError()); - return 1; - } + TEST_CHECK_MSG(infoSize != 0, "GetFileVersionInfoSizeA failed: %lu", (unsigned long)GetLastError()); char *infoBuffer = (char *)malloc(infoSize); - if (!infoBuffer) { - printf("malloc failed\n"); - return 1; - } + TEST_CHECK_MSG(infoBuffer != NULL, "malloc(%lu) failed", (unsigned long)infoSize); - if (!GetFileVersionInfoA(modulePath, 0, infoSize, infoBuffer)) { - printf("GetFileVersionInfoA failed: %lu\n", GetLastError()); - free(infoBuffer); - return 1; - } + TEST_CHECK_MSG(GetFileVersionInfoA(modulePath, 0, infoSize, infoBuffer) != 0, + "GetFileVersionInfoA failed: %lu", (unsigned long)GetLastError()); VS_FIXEDFILEINFO *fixedInfo = NULL; unsigned int fixedSize = 0; - if (!VerQueryValueA(infoBuffer, "\\", (void **)&fixedInfo, &fixedSize)) { - printf("VerQueryValueA root failed\n"); - free(infoBuffer); - return 1; - } - printf("FILEVERSION=%u.%u.%u.%u\n", - fixedInfo->dwFileVersionMS >> 16, - fixedInfo->dwFileVersionMS & 0xFFFF, - fixedInfo->dwFileVersionLS >> 16, - fixedInfo->dwFileVersionLS & 0xFFFF); + TEST_CHECK_MSG(VerQueryValueA(infoBuffer, "\\", (void **)&fixedInfo, &fixedSize) != 0 && + fixedInfo != NULL, + "VerQueryValueA root failed"); + TEST_CHECK_MSG(fixedSize >= sizeof(*fixedInfo), + "Unexpected VS_FIXEDFILEINFO size: %u", fixedSize); + TEST_CHECK_EQ(1, (int)(fixedInfo->dwFileVersionMS >> 16)); + TEST_CHECK_EQ(2, (int)(fixedInfo->dwFileVersionMS & 0xFFFF)); + TEST_CHECK_EQ(3, (int)(fixedInfo->dwFileVersionLS >> 16)); + TEST_CHECK_EQ(4, (int)(fixedInfo->dwFileVersionLS & 0xFFFF)); struct { WORD wLanguage; WORD wCodePage; } *translations = NULL; unsigned int transSize = 0; - if (VerQueryValueA(infoBuffer, "\\VarFileInfo\\Translation", (void **)&translations, &transSize) && - translations && transSize >= sizeof(*translations)) { - printf("Translation=%04X %04X\n", translations[0].wLanguage, translations[0].wCodePage); - char subBlock[64]; - snprintf(subBlock, sizeof(subBlock), "\\StringFileInfo\\%04X%04X\\ProductVersion", - translations[0].wLanguage, translations[0].wCodePage); - char *productVersion = NULL; - unsigned int pvSize = 0; - printf("Querying %s\n", subBlock); - if (VerQueryValueA(infoBuffer, subBlock, (void **)&productVersion, &pvSize) && productVersion) { - printf("PRODUCTVERSION=%s\n", productVersion); - } else { - printf("ProductVersion lookup failed\n"); - } - } else { - printf("ProductVersion lookup failed\n"); - } + TEST_CHECK_MSG(VerQueryValueA(infoBuffer, "\\VarFileInfo\\Translation", + (void **)&translations, &transSize) != 0 && + translations != NULL, + "Translation lookup failed"); + TEST_CHECK_MSG(transSize >= sizeof(*translations), + "Translation block too small: %u", transSize); + TEST_CHECK_EQ(0x0409, translations[0].wLanguage); + TEST_CHECK_EQ(0x04B0, translations[0].wCodePage); + + char subBlock[64]; + int subLen = snprintf(subBlock, sizeof(subBlock), + "\\StringFileInfo\\%04X%04X\\ProductVersion", + translations[0].wLanguage, translations[0].wCodePage); + TEST_CHECK_MSG(subLen > 0 && (size_t)subLen < sizeof(subBlock), + "Failed to build ProductVersion path"); + + char *productVersion = NULL; + unsigned int pvSize = 0; + TEST_CHECK_MSG(VerQueryValueA(infoBuffer, subBlock, (void **)&productVersion, &pvSize) != 0 && + productVersion != NULL, + "ProductVersion lookup failed"); + TEST_CHECK_STR_EQ("1.2.3-test", productVersion); free(infoBuffer); - return 0; + puts("resource metadata validated"); + return EXIT_SUCCESS; } From b5da26aa48a302728268c1871b2b9951c8787ec0 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 11:40:36 -0600 Subject: [PATCH 09/28] msvcrt: Clean up __wgetmainargs, _wdupenv_s, _wgetenv_s; add __getmainargs --- AGENTS.md | 9 +- dll/kernel32.cpp | 19 --- dll/msvcrt.cpp | 350 +++++++++++++++++++++++++++++++---------------- 3 files changed, 242 insertions(+), 136 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index af8fd14..c2cbcf3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,17 +7,24 @@ - Sample fixtures for exercising the loader live in `test/`; keep new repros small and self-contained. ## Build, Test, and Development Commands -- `cmake -B build -DCMAKE_BUILD_TYPE=Debug` configures a 32-bit toolchain; ensure multilib packages are present. +- `cmake -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON` configures a 32-bit toolchain; ensure multilib packages are present. - `cmake --build build --target wibo` compiles the shim; switch to `-DCMAKE_BUILD_TYPE=Release` for optimised binaries. - `./build/wibo /path/to/program.exe` runs a Windows binary through the shim; use `WIBO_DEBUG=1` for verbose logging. - `cmake -B build -DBUILD_TESTING=ON` + `ctest --test-dir build --output-on-failure` runs the self-checking WinAPI fixtures (requires `i686-w64-mingw32-gcc` and `i686-w64-mingw32-windres`). - `clang-format -i path/to/file.cpp` and `clang-tidy path/to/file.cpp -p build` keep contributions aligned with the repo's tooling. +- DON'T use `clang-format` on existing files, only new or heavily modified ones; the repo hasn't been fully formatted yet. ## Coding Style & Naming Conventions - Formatting follows `.clang-format` (LLVM base, tabbed indentation width 4, 120 column limit); never hand-wrap differently. - Prefer PascalCase for emulated Win32 entry points, camelCase for internal helpers, and SCREAMING_SNAKE_CASE for constants or macros. - Document non-obvious control flow with short comments and keep platform-specific code paths behind descriptive helper functions. +## Shim Implementation Guidelines +- Target pre-XP behavior; our binaries are old and don't expect modern WinAPI behavior. +- Use the `microsoft_docs` tools to fetch WinAPI signatures and documentation; always fetch the documentation when working on an API function. +- Create minimal, self-contained repros in `test/` when implementing or debugging APIs; this aids both development and future testing. +- Stub unimplemented APIs with `DEBUG_LOG` calls to track usage; prioritize based on the needs of real-world binaries. + ## Testing Guidelines - Fixture binaries live in `test/` and are compiled automatically when `BUILD_TESTING` is enabled; keep new repros small and self-contained (`test_.c`). - All fixtures must self-assert; use `test_assert.h` helpers so `ctest` fails on mismatched WinAPI behaviour. diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 97f05c3..624d243 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -1907,25 +1907,6 @@ namespace kernel32 { DEBUG_LOG("-> %p\n", result); return result; } - - - // DEBUG_LOG("VirtualAlloc %p %u %u %u\n",lpAddress, dwSize, flAllocationType, flProtect); - // if (flAllocationType & 0x2000 || lpAddress == NULL) { // MEM_RESERVE - // // do this for now... - // assert(lpAddress == NULL); - // void *mem = 0; - // posix_memalign(&mem, 0x1000, dwSize); - // memset(mem, 0, dwSize); - - // // Windows only fences off the lower 2GB of the 32-bit address space for the private use of processes. - // assert(mem < (void*)0x80000000); - - // DEBUG_LOG("-> %p\n", mem); - // return mem; - // } else { - // assert(lpAddress != NULL); - // return lpAddress; - // } } unsigned int WIN_FUNC VirtualFree(void *lpAddress, unsigned int dwSize, int dwFreeType) { diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index e83f80c..e687372 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -1,16 +1,21 @@ #include "common.h" +#include +#include +#include #include #include #include +#include #include +#include #include #include -#include -#include -#include -#include +#include +#include #include +#include #include +#include #include "strutil.h" typedef void (*_PVFV)(); @@ -19,9 +24,152 @@ typedef int (*_PIFV)(); namespace msvcrt { int _commode; int _fmode; + char** __initenv; uint16_t** __winitenv; uint16_t* _wpgmptr; + namespace { + template + struct StringListStorage { + std::vector> strings; + std::unique_ptr pointers; + + template + CharT **assign(char **source, Converter convert) { + if (!source) { + strings.clear(); + pointers.reset(); + return nullptr; + } + + size_t count = 0; + while (source[count]) { + ++count; + } + + strings.clear(); + strings.reserve(count); + pointers = std::make_unique(count + 1); + + for (size_t i = 0; i < count; ++i) { + auto data = convert(source[i]); + auto buffer = std::make_unique(data.size()); + std::copy(data.begin(), data.end(), buffer.get()); + CharT *raw = buffer.get(); + strings.emplace_back(std::move(buffer)); + pointers[i] = raw; + } + + pointers[count] = nullptr; + return pointers.get(); + } + }; + + std::vector copyNarrowString(const char *src) { + if (!src) { + src = ""; + } + size_t len = std::strlen(src); + std::vector result(len + 1); + if (len > 0) { + std::memcpy(result.data(), src, len); + } + result[len] = '\0'; + return result; + } + + std::vector copyWideString(const char *src) { + if (!src) { + src = ""; + } + return stringToWideString(src); + } + + template + // NOLINTNEXTLINE(readability-non-const-parameter) + int getMainArgsCommon(int *argcOut, CharT ***argvOut, CharT ***envOut, Converter convert) { + if (argcOut) { + *argcOut = wibo::argc; + } + + static StringListStorage argvStorage; + static StringListStorage envStorage; + + if (argvOut) { + *argvOut = argvStorage.assign(wibo::argv, convert); + } + + CharT **envData = envStorage.assign(environ, convert); + if (envOut) { + *envOut = envData; + } + + if constexpr (std::is_same_v) { + __winitenv = envData; + } else if constexpr (std::is_same_v) { + __initenv = envData; + } + + return 0; + } + + template + size_t envStringLength(const CharT *str) { + if (!str) { + return 0; + } + if constexpr (std::is_same_v) { + return std::strlen(str); + } else { + return wstrlen(str); + } + } + + template + int envStringCompare(const CharT *lhs, const CharT *rhs, size_t count) { + if constexpr (std::is_same_v) { + return std::strncmp(lhs, rhs, count); + } else { + return wstrncmp(lhs, rhs, count); + } + } + + template + struct EnvLookupResult { + const CharT *value; + size_t length; + }; + + template + std::optional> findEnvironmentValue(CharT **env, const CharT *varname) { + if (!env || !varname) { + return std::nullopt; + } + + size_t nameLength = envStringLength(varname); + if (nameLength == 0) { + return std::nullopt; + } + + for (CharT **cursor = env; *cursor; ++cursor) { + CharT *entry = *cursor; + if (envStringCompare(entry, varname, nameLength) == 0 && entry[nameLength] == static_cast('=')) { + const CharT *value = entry + nameLength + 1; + return EnvLookupResult{value, envStringLength(value)}; + } + } + + return std::nullopt; + } + + uint16_t **ensureWideEnvironment() { + if (!__winitenv) { + getMainArgsCommon(nullptr, nullptr, nullptr, copyWideString); + } + return __winitenv; + } + } // namespace + // Stub because we're only ever a console application void WIN_ENTRY __set_app_type(int at) { } @@ -63,80 +211,30 @@ namespace msvcrt { _PIFV WIN_ENTRY _onexit(_PIFV func) { DEBUG_LOG("_onexit(%p)\n", func); if(!func) return nullptr; - if (atexit((void(*)(void))func) != 0) return nullptr; + if (atexit(reinterpret_cast(func)) != 0) return nullptr; return func; } - - // wgetmainargs references: - // https://github.com/reactos/reactos/blob/fade0c3b8977d43f3a9e0b8887d18afcabd8e145/sdk/lib/crt/misc/getargs.c#L328 - // https://learn.microsoft.com/en-us/cpp/c-runtime-library/getmainargs-wgetmainargs?view=msvc-170 - - int WIN_ENTRY __wgetmainargs(int* wargc, uint16_t*** wargv, uint16_t*** wenv, int doWildcard, int* startInfo){ - DEBUG_LOG("__wgetmainargs\n"); - // get the regular, non-wide versions of argc/argv/env - // argc: the number of args in argv. always >= 1 - // argv: array of null-terminated strings for command-line args. - // argv[0] = the command to invoke the program - // argv[1] = the first command-line arg - // argv[argc - 1] = the last command-line arg - // argv[argc] = NULL - // env: array of strings for user's environment variables. always terminated by NULL entry. - int* regular_argc = &wibo::argc; - char*** regular_argv = &wibo::argv; - char** regular_env = environ; - - int argc = *regular_argc; - char** argv = *regular_argv; - char** env = regular_env; - - // DEBUG_LOG("Wildcard: %d\n", doWildcard); - // if(startInfo){ - // DEBUG_LOG("Start info: %d\n", *startInfo); - // } - - if(wargc) *wargc = argc; - - std::setlocale(LC_CTYPE, ""); - if(wargv){ - *wargv = new uint16_t*[argc + 1]; // allocate array of our future wstrings - for(int i = 0; i < argc; i++){ - const char* cur_arg = argv[i]; - - std::vector wStr = stringToWideString(cur_arg); - - // allocate a copy on the heap, - // since wStr will go out of scope - (*wargv)[i] = new uint16_t[wStr.size() + 1]; - std::copy(wStr.begin(), wStr.end(), (*wargv)[i]); - (*wargv)[i][wStr.size()] = 0; - } - (*wargv)[argc] = nullptr; + // NOLINTNEXTLINE(readability-non-const-parameter) + int WIN_ENTRY __wgetmainargs(int *wargc, uint16_t ***wargv, uint16_t ***wenv, int doWildcard, int *startInfo) { + DEBUG_LOG("__wgetmainargs(doWildcard=%d)\n", doWildcard); + (void)startInfo; + if (doWildcard) { + DEBUG_LOG("\tWildcard expansion is not implemented\n"); } - if(wenv){ - int count = 0; - for(; env[count] != nullptr; count++); - // DEBUG_LOG("Found env count %d\n", count); - *wenv = new uint16_t*[count + 1]; // allocate array of our future wstrings - for (int i = 0; i < count; i++) { - const char* cur_env = env[i]; - // DEBUG_LOG("Adding env %s\n", cur_env); - - std::vector wStr = stringToWideString(cur_env); - - // allocate a copy on the heap, - // since wStr will go out of scope - (*wenv)[i] = new uint16_t[wStr.size() + 1]; - std::copy(wStr.begin(), wStr.end(), (*wenv)[i]); - (*wenv)[i][wStr.size()] = 0; - } - - (*wenv)[count] = nullptr; + std::setlocale(LC_CTYPE, ""); + return getMainArgsCommon(wargc, wargv, wenv, copyWideString); + } - __winitenv = *wenv; + // NOLINTNEXTLINE(readability-non-const-parameter) + int WIN_ENTRY __getmainargs(int *argc, char ***argv, char ***env, int doWildcard, int *startInfo) { + DEBUG_LOG("__getmainargs(doWildcard=%d)\n", doWildcard); + (void)startInfo; + if (doWildcard) { + DEBUG_LOG("\tWildcard expansion is not implemented\n"); } - return 0; + return getMainArgsCommon(argc, argv, env, copyNarrowString); } char* WIN_ENTRY getenv(const char *varname){ @@ -148,65 +246,83 @@ namespace msvcrt { } int WIN_ENTRY _wdupenv_s(uint16_t **buffer, size_t *numberOfElements, const uint16_t *varname){ - std::string var_str = wideStringToString(varname); - DEBUG_LOG("_wdupenv_s: var name %s\n", var_str.c_str()); - if(!buffer || !varname) return 22; - *buffer = nullptr; - if(numberOfElements) *numberOfElements = 0; - - size_t varnamelen = wstrlen(varname); + if (buffer) { + *buffer = nullptr; + } + if (numberOfElements) { + *numberOfElements = 0; + } - // DEBUG_LOG("\tSearching env vars...\n"); - for(uint16_t** env = __winitenv; env && *env; ++env){ - uint16_t* cur = *env; - std::string cur_str = wideStringToString(cur); - // DEBUG_LOG("\tCur env var: %s\n", cur_str.c_str()); - if(wstrncmp(cur, varname, varnamelen) == 0 && cur[varnamelen] == L'='){ - DEBUG_LOG("Found the env var %s!\n", var_str.c_str()); - uint16_t* value = cur + varnamelen + 1; - size_t value_len = wstrlen(value); + if (!buffer || !varname) { + DEBUG_LOG("_wdupenv_s: invalid parameter\n"); + errno = EINVAL; + return EINVAL; + } - uint16_t* copy = (uint16_t*)malloc((value_len + 1) * sizeof(uint16_t)); - if(!copy) return 12; + std::string var_str = wideStringToString(varname); + DEBUG_LOG("_wdupenv_s: var name %s\n", var_str.c_str()); - wstrncpy(copy, value, value_len + 1); - *buffer = copy; + auto env = ensureWideEnvironment(); + auto match = findEnvironmentValue(env, varname); + if (!match) { + DEBUG_LOG("Could not find env var %s\n", var_str.c_str()); + return 0; + } - if(numberOfElements) *numberOfElements = value_len + 1; - return 0; - } + size_t value_len = match->length; + auto *copy = static_cast(malloc((value_len + 1) * sizeof(uint16_t))); + if (!copy) { + DEBUG_LOG("_wdupenv_s: allocation failed\n"); + errno = ENOMEM; + return ENOMEM; } - DEBUG_LOG("Could not find env var %s\n", var_str.c_str()); + wstrncpy(copy, match->value, value_len); + copy[value_len] = 0; + *buffer = copy; + if (numberOfElements) { + *numberOfElements = value_len + 1; + } return 0; } int WIN_ENTRY _wgetenv_s(size_t* pReturnValue, uint16_t* buffer, size_t numberOfElements, const uint16_t* varname){ + if (pReturnValue) { + *pReturnValue = 0; + } + if (numberOfElements > 0 && buffer) { + buffer[0] = 0; + } + + bool bufferRequired = numberOfElements != 0; + if (!pReturnValue || !varname || (bufferRequired && !buffer)) { + DEBUG_LOG("_wgetenv_s: invalid parameter\n"); + errno = EINVAL; + return EINVAL; + } + std::string var_str = wideStringToString(varname); DEBUG_LOG("_wgetenv_s: var name %s\n", var_str.c_str()); - if(!buffer || !varname) return 22; - - size_t varnamelen = wstrlen(varname); - for(uint16_t** env = __winitenv; env && *env; ++env){ - uint16_t* cur = *env; - // std::string cur_str = wideStringToString(cur); - // DEBUG_LOG("\tCur env var: %s\n", cur_str.c_str()); - if(wstrncmp(cur, varname, varnamelen) == 0 && cur[varnamelen] == L'='){ - uint16_t* value = cur + varnamelen + 1; - size_t value_len = wstrlen(value); + auto env = ensureWideEnvironment(); + auto match = findEnvironmentValue(env, varname); + if (!match) { + return 0; + } - size_t copy_len = (value_len < numberOfElements - 1) ? value_len : numberOfElements - 1; - wstrncpy(buffer, value, copy_len); - buffer[copy_len] = 0; + size_t required = match->length + 1; + *pReturnValue = required; + if (!bufferRequired || !buffer) { + return 0; + } - if(pReturnValue) *pReturnValue = value_len + 1; - return 0; - } + if (required > numberOfElements) { + errno = ERANGE; + return ERANGE; } - buffer[0] = 0; - if(pReturnValue) *pReturnValue = 0; + wstrncpy(buffer, match->value, match->length); + buffer[match->length] = 0; return 0; } @@ -318,7 +434,7 @@ namespace msvcrt { if(!strSource) return nullptr; size_t strLen = wstrlen(strSource); - uint16_t* dup = (uint16_t*)malloc((strLen + 1) * sizeof(uint16_t)); + auto *dup = static_cast(malloc((strLen + 1) * sizeof(uint16_t))); if(!dup) return nullptr; for(size_t i = 0; i <= strLen; i++){ @@ -471,7 +587,7 @@ namespace msvcrt { std::memcpy(buffer, wide.data(), copy_len * sizeof(uint16_t)); buffer[copy_len] = 0; - return copy_len; + return static_cast(copy_len); // return vswprintf(buffer, size, format, args); this doesn't work because on this architecture, wchar_t is size 4, instead of size 2 } @@ -605,7 +721,7 @@ namespace msvcrt { return absPath; } else { // Windows behavior: if absPath == NULL, allocate new - uint16_t* newBuf = new uint16_t[wResolved.size() + 1]; + auto *newBuf = new uint16_t[wResolved.size() + 1]; std::copy(wResolved.begin(), wResolved.end(), newBuf); newBuf[wResolved.size()] = 0; @@ -622,6 +738,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "__set_app_type") == 0) return (void *) msvcrt::__set_app_type; if (strcmp(name, "_fmode") == 0) return (void *)&msvcrt::_fmode; if (strcmp(name, "_commode") == 0) return (void *)&msvcrt::_commode; + if (strcmp(name, "__initenv") == 0) return (void *)&msvcrt::__initenv; if (strcmp(name, "__winitenv") == 0) return (void *)&msvcrt::__winitenv; if (strcmp(name, "__p__fmode") == 0) return (void *) msvcrt::__p__fmode; if (strcmp(name, "__p__commode") == 0) return (void *) msvcrt::__p__commode; @@ -629,6 +746,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "_initterm_e") == 0) return (void *)msvcrt::_initterm_e; if (strcmp(name, "_controlfp_s") == 0) return (void *)msvcrt::_controlfp_s; if (strcmp(name, "_onexit") == 0) return (void*)msvcrt::_onexit; + if (strcmp(name, "__getmainargs") == 0) return (void*)msvcrt::__getmainargs; if (strcmp(name, "__wgetmainargs") == 0) return (void*)msvcrt::__wgetmainargs; if (strcmp(name, "setlocale") == 0) return (void*)msvcrt::setlocale; if (strcmp(name, "_wdupenv_s") == 0) return (void*)msvcrt::_wdupenv_s; From b27ca28c2074e027fbe247c689f912f5815fb02e Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 11:41:02 -0600 Subject: [PATCH 10/28] Ignore .cache from clangd --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1cb8d6d..9030c66 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ build/ .vscode/ # CLion .idea/ -cmake-build-*/ \ No newline at end of file +cmake-build-*/ +# clangd +.cache/ From f83d228cc1192c3a4ea724bc11a5626d13066188 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 12:01:15 -0600 Subject: [PATCH 11/28] strutil cleanup & fixes --- common.h | 2 + files.h | 2 + handles.h | 4 +- processes.h | 4 +- resources.h | 3 +- strutil.cpp | 263 ++++++++++++++++++++++++++++------------------------ strutil.h | 21 +++-- 7 files changed, 164 insertions(+), 135 deletions(-) diff --git a/common.h b/common.h index 4201116..74e0d70 100644 --- a/common.h +++ b/common.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include diff --git a/files.h b/files.h index d86e927..74e6d30 100644 --- a/files.h +++ b/files.h @@ -1,3 +1,5 @@ +#pragma once + #include #include diff --git a/handles.h b/handles.h index f751845..af96c3f 100644 --- a/handles.h +++ b/handles.h @@ -1,4 +1,6 @@ -#include +#pragma once + +#include namespace handles { enum Type { diff --git a/processes.h b/processes.h index 4bcad09..0e6ecca 100644 --- a/processes.h +++ b/processes.h @@ -1,3 +1,5 @@ +#pragma once + #include #include @@ -9,4 +11,4 @@ namespace processes { void *allocProcessHandle(pid_t pid); Process* processFromHandle(void* hHandle, bool pop); -} \ No newline at end of file +} diff --git a/resources.h b/resources.h index 5612743..3296e78 100644 --- a/resources.h +++ b/resources.h @@ -1,5 +1,6 @@ #pragma once +#include namespace wibo { @@ -11,4 +12,4 @@ bool resourceEntryBelongsToExecutable(const Executable &exe, const ImageResource ResourceIdentifier resourceIdentifierFromAnsi(const char *id); ResourceIdentifier resourceIdentifierFromWide(const uint16_t *id); -} +} // namespace wibo diff --git a/strutil.cpp b/strutil.cpp index 24b00ff..16f1738 100644 --- a/strutil.cpp +++ b/strutil.cpp @@ -1,208 +1,225 @@ +#include "strutil.h" #include "common.h" -#include "strings.h" #include -#include -#include -#include -#include #include +#include +#include size_t wstrlen(const uint16_t *str) { - if(!str) return 0; - + if (!str) + return 0; + size_t len = 0; while (str[len] != 0) ++len; return len; } -size_t wstrnlen(const uint16_t* str, size_t numberOfElements){ +size_t wstrnlen(const uint16_t *str, size_t numberOfElements) { + if (!str) + return 0; size_t len = 0; - while (str[len] != 0 && len < numberOfElements) + while (len < numberOfElements && str[len] != 0) ++len; - return len; + return len; } -int wstrncmp(const uint16_t *string1, const uint16_t *string2, size_t count){ - const uint16_t* ptr1 = string1; - const uint16_t* ptr2 = string2; - for(size_t i = 0; i < count; i++){ +int wstrncmp(const uint16_t *string1, const uint16_t *string2, size_t count) { + const uint16_t *ptr1 = string1; + const uint16_t *ptr2 = string2; + for (size_t i = 0; i < count; i++) { uint16_t c1 = *ptr1++; uint16_t c2 = *ptr2++; if (c1 != c2) { - return (c1 > c2) ? 1 : -1; - } + return (c1 > c2) ? 1 : -1; + } } return 0; } -const uint16_t* wstrstr(const uint16_t *dest, const uint16_t *src){ - if (!*src) return dest; +const uint16_t *wstrstr(const uint16_t *dest, const uint16_t *src) { + if (!*src) + return dest; - for (; *dest != 0; dest++) { - const uint16_t* d = dest; - const uint16_t* s = src; + for (; *dest != 0; dest++) { + const uint16_t *d = dest; + const uint16_t *s = src; - while (*d != 0 && *s != 0 && *d == *s) { - d++; - s++; - } + while (*d != 0 && *s != 0 && *d == *s) { + d++; + s++; + } - if (*s == 0) { - return dest; - } - } + if (*s == 0) { + return dest; + } + } - return nullptr; + return nullptr; } -uint16_t* wstrchr(const uint16_t* str, uint16_t c) { - for (; *str != 0; str++) { - if (*str == c) { - return (uint16_t*)str; - } - } - // If searching for '\0', return pointer to terminator - if (c == 0) { - return (uint16_t*)str; - } - return nullptr; +uint16_t *wstrchr(const uint16_t *str, uint16_t c) { + if (!str) + return nullptr; + for (; *str != 0; str++) { + if (*str == c) { + return (uint16_t *)str; + } + } + // If searching for '\0', return pointer to terminator + if (c == 0) { + return (uint16_t *)str; + } + return nullptr; } -uint16_t* wstrrchr(const uint16_t* str, uint16_t c){ - const uint16_t* last = nullptr; - for (; *str != 0; str++) { - if (*str == c) { - last = str; - } - } - return (uint16_t*)last; +uint16_t *wstrrchr(const uint16_t *str, uint16_t c) { + if (!str) + return nullptr; + const uint16_t *last = nullptr; + const uint16_t *it = str; + for (; *it != 0; ++it) { + if (*it == c) { + last = it; + } + } + if (c == 0) + return (uint16_t *)it; + return (uint16_t *)last; } -uint16_t* wstrcat(uint16_t* dest, const uint16_t* src){ - uint16_t* d = dest; - while (*d) d++; - while ((*d++ = *src++) != 0); +uint16_t *wstrcat(uint16_t *dest, const uint16_t *src) { + uint16_t *d = dest; + while (*d) + d++; + while ((*d++ = *src++) != 0) + ; return dest; } -uint16_t* wstrncat(uint16_t* dest, const uint16_t* src, size_t count){ - uint16_t* d = dest; - while (*d) d++; - for(size_t i = 0; i < count && src[i] != 0; i++){ +uint16_t *wstrncat(uint16_t *dest, const uint16_t *src, size_t count) { + uint16_t *d = dest; + while (*d) + d++; + for (size_t i = 0; i < count && src[i] != 0; i++) { *d++ = src[i]; } *d = 0; - return dest; + return dest; } -uint16_t* wstrcpy(uint16_t* dest, const uint16_t* src){ - uint16_t* d = dest; - while ((*d++ = *src++) != 0); - return dest; +uint16_t *wstrcpy(uint16_t *dest, const uint16_t *src) { + uint16_t *d = dest; + while ((*d++ = *src++) != 0) + ; + return dest; } size_t wstrncpy(uint16_t *dst, const uint16_t *src, size_t n) { + if (!dst || !src || n == 0) + return 0; size_t i = 0; - while (i < n && src[i] != 0) { + for (; i < n && src[i] != 0; ++i) { dst[i] = src[i]; - ++i; } - if (i < n) - dst[i] = 0; + for (size_t j = i; j < n; ++j) { + dst[j] = 0; + } return i; } -std::string wideStringToString(const uint16_t *src, int len = -1) { - if(!src) return std::string(); - if (len < 0) { - len = src ? wstrlen(src) : 0; - } - - // std::u16string u16str; - // for(const uint16_t* p = src; *p != 0; p++){ - // u16str.push_back(*p); - // } - - // std::wstring_convert, char16_t> convert; - // return convert.to_bytes(reinterpret_cast(u16str.data())); - - // the old implementation - std::string res(len, '\0'); - std::string debug_wstr; - std::stringstream ss; - bool is_wide = false; - for (int i = 0; i < len; i++) { - ss << "0x" << std::hex << src[i] << " "; - // debug_wstr += std::format("0x%X ", src[i]); - if(src[i] > 255){ - // DEBUG_LOG("Encountered wide char with value 0x%X!\n", src[i]); - // assert(src[i] <= 255); - is_wide = true; - } - res[i] = src[i] & 0xFF; +std::string wideStringToString(const uint16_t *src, int len) { + if (!src) + return {}; + + size_t count; + if (len >= 0) { + count = static_cast(len); + } else { + count = wstrlen(src); } - if(is_wide){ - debug_wstr += ss.str(); - DEBUG_LOG("wideString (%d): %s\n", wstrlen(src), debug_wstr.c_str()); + +#ifndef NDEBUG + std::stringstream hexDump; + hexDump << std::hex; + bool sawWide = false; +#endif + + std::string result(count, '\0'); + for (size_t i = 0; i < count; ++i) { + uint16_t value = src[i]; + if (i > 0) + hexDump << ' '; + hexDump << "0x" << value; + if (value > 0xFF) + sawWide = true; + result[i] = static_cast(value & 0xFF); } - return res; + +#ifndef NDEBUG + if (sawWide) { + size_t loggedLength = (len >= 0) ? count : wstrlen(src); + DEBUG_LOG("wideString (%zu): %s\n", loggedLength, hexDump.str().c_str()); + } +#endif + + return result; } std::vector stringToWideString(const char *src) { - int len = strlen(src); + if (!src) + return std::vector{0}; + size_t len = strlen(src); std::vector res(len + 1); - for (size_t i = 0; i < res.size(); i++) { - res[i] = src[i] & 0xFF; + res[i] = static_cast(src[i] & 0xFF); } res[len] = 0; // NUL terminate - return res; } -long wstrtol(const uint16_t* string, uint16_t** end_ptr, int base){ - if(!string){ - if(end_ptr) *end_ptr = nullptr; +long wstrtol(const uint16_t *string, uint16_t **end_ptr, int base) { + if (!string) { + if (end_ptr) + *end_ptr = nullptr; return 0; } std::string normal_str = wideStringToString(string); - char* normal_end = nullptr; + char *normal_end = nullptr; long res = std::strtol(normal_str.c_str(), &normal_end, base); - if(end_ptr){ - if(normal_end && *normal_end){ + if (end_ptr) { + if (normal_end && *normal_end) { size_t offset = normal_end - normal_str.c_str(); - *end_ptr = (uint16_t*)(string + offset); - } - else { - *end_ptr = (uint16_t*)(string + normal_str.size()); + *end_ptr = (uint16_t *)(string + offset); + } else { + *end_ptr = (uint16_t *)(string + normal_str.size()); } } return res; } -unsigned long wstrtoul(const uint16_t* string, uint16_t** end_ptr, int base){ - if(!string){ - if(end_ptr) *end_ptr = nullptr; +unsigned long wstrtoul(const uint16_t *string, uint16_t **end_ptr, int base) { + if (!string) { + if (end_ptr) + *end_ptr = nullptr; return 0; } std::string normal_str = wideStringToString(string); - char* normal_end = nullptr; + char *normal_end = nullptr; unsigned long res = std::strtoul(normal_str.c_str(), &normal_end, base); - if(end_ptr){ - if(normal_end && *normal_end){ + if (end_ptr) { + if (normal_end && *normal_end) { size_t offset = normal_end - normal_str.c_str(); - *end_ptr = (uint16_t*)(string + offset); - } - else { - *end_ptr = (uint16_t*)(string + normal_str.size()); + *end_ptr = (uint16_t *)(string + offset); + } else { + *end_ptr = (uint16_t *)(string + normal_str.size()); } } return res; -} \ No newline at end of file +} diff --git a/strutil.h b/strutil.h index 4aa4728..897ae4a 100644 --- a/strutil.h +++ b/strutil.h @@ -1,17 +1,20 @@ +#pragma once + +#include #include #include size_t wstrlen(const uint16_t *str); -size_t wstrnlen(const uint16_t* str, size_t numberOfElements); +size_t wstrnlen(const uint16_t *str, size_t numberOfElements); int wstrncmp(const uint16_t *string1, const uint16_t *string2, size_t count); -const uint16_t* wstrstr(const uint16_t *dest, const uint16_t *src); -uint16_t* wstrchr(const uint16_t* str, uint16_t c); -uint16_t* wstrrchr(const uint16_t* str, uint16_t c); -uint16_t* wstrcat(uint16_t* dest, const uint16_t* src); -uint16_t* wstrncat(uint16_t* dest, const uint16_t* src, size_t count); -uint16_t* wstrcpy(uint16_t* dest, const uint16_t* src); +const uint16_t *wstrstr(const uint16_t *dest, const uint16_t *src); +uint16_t *wstrchr(const uint16_t *str, uint16_t c); +uint16_t *wstrrchr(const uint16_t *str, uint16_t c); +uint16_t *wstrcat(uint16_t *dest, const uint16_t *src); +uint16_t *wstrncat(uint16_t *dest, const uint16_t *src, size_t count); +uint16_t *wstrcpy(uint16_t *dest, const uint16_t *src); size_t wstrncpy(uint16_t *dst, const uint16_t *src, size_t n); std::string wideStringToString(const uint16_t *src, int len = -1); std::vector stringToWideString(const char *src); -long wstrtol(const uint16_t* string, uint16_t** end_ptr, int base); -unsigned long wstrtoul(const uint16_t* strSource, uint16_t** end_ptr, int base); \ No newline at end of file +long wstrtol(const uint16_t *string, uint16_t **end_ptr, int base); +unsigned long wstrtoul(const uint16_t *string, uint16_t **end_ptr, int base); From 8b29e1bcbbe85d4266b3e43183d842e4b496e194 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 12:10:56 -0600 Subject: [PATCH 12/28] clang-format and fix release build --- module_registry.cpp | 53 +++++++++++++++++++++++---------------------- resources.cpp | 33 +++++++++++----------------- strutil.cpp | 2 ++ 3 files changed, 42 insertions(+), 46 deletions(-) diff --git a/module_registry.cpp b/module_registry.cpp index f835eed..e0b3514 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -51,15 +51,16 @@ struct PEExportDirectory { #define FOR_256_2(a, b) \ FOR_256_3(a, b, 0, 0) \ FOR_256_3(a, b, 0, 1) \ - FOR_256_3(a, b, 0, 2) FOR_256_3(a, b, 0, 3) FOR_256_3(a, b, 1, 0) FOR_256_3(a, b, 1, 1) FOR_256_3(a, b, 1, 2) \ - FOR_256_3(a, b, 1, 3) FOR_256_3(a, b, 2, 0) FOR_256_3(a, b, 2, 1) FOR_256_3(a, b, 2, 2) FOR_256_3(a, b, 2, 3) \ - FOR_256_3(a, b, 3, 0) FOR_256_3(a, b, 3, 1) FOR_256_3(a, b, 3, 2) FOR_256_3(a, b, 3, 3) + FOR_256_3(a, b, 0, 2) \ + FOR_256_3(a, b, 0, 3) FOR_256_3(a, b, 1, 0) FOR_256_3(a, b, 1, 1) FOR_256_3(a, b, 1, 2) FOR_256_3(a, b, 1, 3) \ + FOR_256_3(a, b, 2, 0) FOR_256_3(a, b, 2, 1) FOR_256_3(a, b, 2, 2) FOR_256_3(a, b, 2, 3) FOR_256_3(a, b, 3, 0) \ + FOR_256_3(a, b, 3, 1) FOR_256_3(a, b, 3, 2) FOR_256_3(a, b, 3, 3) #define FOR_256 \ FOR_256_2(0, 0) \ FOR_256_2(0, 1) \ - FOR_256_2(0, 2) FOR_256_2(0, 3) FOR_256_2(1, 0) FOR_256_2(1, 1) FOR_256_2(1, 2) FOR_256_2(1, 3) FOR_256_2(2, 0) \ - FOR_256_2(2, 1) FOR_256_2(2, 2) FOR_256_2(2, 3) FOR_256_2(3, 0) FOR_256_2(3, 1) FOR_256_2(3, 2) \ - FOR_256_2(3, 3) + FOR_256_2(0, 2) \ + FOR_256_2(0, 3) FOR_256_2(1, 0) FOR_256_2(1, 1) FOR_256_2(1, 2) FOR_256_2(1, 3) FOR_256_2(2, 0) FOR_256_2(2, 1) \ + FOR_256_2(2, 2) FOR_256_2(2, 3) FOR_256_2(3, 0) FOR_256_2(3, 1) FOR_256_2(3, 2) FOR_256_2(3, 3) static int stubIndex = 0; static char stubDlls[0x100][0x100]; @@ -416,7 +417,7 @@ void ensureInitialized() { } void registerExternalModuleAliases(const std::string &requestedName, const std::filesystem::path &resolvedPath, - wibo::ModuleInfo *info) { + wibo::ModuleInfo *info) { ParsedModuleName parsed = parseModuleName(requestedName); registerAlias(normalizedBaseKey(parsed), info); registerAlias(normalizeAlias(requestedName), info); @@ -428,24 +429,24 @@ wibo::ModuleInfo *moduleFromAddress(void *addr) { return nullptr; auto ® = registry(); for (auto &pair : reg.modulesByKey) { - wibo::ModuleInfo *info = pair.second.get(); - if (!info) - continue; - uint8_t *base = nullptr; - size_t size = 0; - if (info->imageBase && info->imageSize) { - base = static_cast(info->imageBase); - size = info->imageSize; - } else if (info->executable) { - base = static_cast(info->executable->imageBuffer); - size = info->executable->imageSize; - } - if (!base || size == 0) - continue; - uint8_t *ptr = static_cast(addr); - if (ptr >= base && ptr < base + size) { - return info; - } + wibo::ModuleInfo *info = pair.second.get(); + if (!info) + continue; + uint8_t *base = nullptr; + size_t size = 0; + if (info->imageBase && info->imageSize) { + base = static_cast(info->imageBase); + size = info->imageSize; + } else if (info->executable) { + base = static_cast(info->executable->imageBuffer); + size = info->executable->imageSize; + } + if (!base || size == 0) + continue; + uint8_t *ptr = static_cast(addr); + if (ptr >= base && ptr < base + size) { + return info; + } } return nullptr; } @@ -624,7 +625,7 @@ HMODULE loadModule(const char *dllName) { return nullptr; } std::string requested(dllName); -DEBUG_LOG("loadModule(%s)\n", requested.c_str()); + DEBUG_LOG("loadModule(%s)\n", requested.c_str()); std::lock_guard lock(registry().mutex); ensureInitialized(); diff --git a/resources.cpp b/resources.cpp index 380bbbb..19bd093 100644 --- a/resources.cpp +++ b/resources.cpp @@ -1,5 +1,5 @@ -#include "common.h" #include "resources.h" +#include "common.h" namespace { @@ -56,8 +56,7 @@ bool resourceNameEquals(const uint8_t *base, uint32_t nameField, const std::u16s } const ImageResourceDirectoryEntry *findEntry(const uint8_t *base, const ImageResourceDirectory *dir, - const wibo::ResourceIdentifier &ident, - uint32_t rsrcSize) { + const wibo::ResourceIdentifier &ident, uint32_t rsrcSize) { const auto *entries = resourceEntries(dir); if (ident.isString) { for (uint16_t i = 0; i < dir->numberOfNamedEntries; ++i) { @@ -75,7 +74,8 @@ const ImageResourceDirectoryEntry *findEntry(const uint8_t *base, const ImageRes return nullptr; } -const ImageResourceDirectory *entryAsDirectory(const uint8_t *base, const ImageResourceDirectoryEntry *entry, uint32_t rsrcSize) { +const ImageResourceDirectory *entryAsDirectory(const uint8_t *base, const ImageResourceDirectoryEntry *entry, + uint32_t rsrcSize) { if (!(entry->offsetToData & RESOURCE_DATA_IS_DIRECTORY)) return nullptr; uint32_t offset = entry->offsetToData & ~RESOURCE_DATA_IS_DIRECTORY; @@ -84,7 +84,8 @@ const ImageResourceDirectory *entryAsDirectory(const uint8_t *base, const ImageR return reinterpret_cast(base + offset); } -const wibo::ImageResourceDataEntry *entryAsData(const uint8_t *base, const ImageResourceDirectoryEntry *entry, uint32_t rsrcSize) { +const wibo::ImageResourceDataEntry *entryAsData(const uint8_t *base, const ImageResourceDirectoryEntry *entry, + uint32_t rsrcSize) { if (entry->offsetToData & RESOURCE_DATA_IS_DIRECTORY) return nullptr; uint32_t offset = entry->offsetToData; @@ -93,15 +94,11 @@ const wibo::ImageResourceDataEntry *entryAsData(const uint8_t *base, const Image return reinterpret_cast(base + offset); } -uint16_t primaryLang(uint16_t lang) { - return lang & 0x3FFu; -} +uint16_t primaryLang(uint16_t lang) { return lang & 0x3FFu; } -const ImageResourceDirectoryEntry *selectLanguageEntry(const ImageResourceDirectory *dir, - const uint8_t *base, - uint32_t rsrcSize, - std::optional desired, - uint16_t &chosenLang) { +const ImageResourceDirectoryEntry *selectLanguageEntry(const ImageResourceDirectory *dir, const uint8_t *base, + uint32_t rsrcSize, std::optional desired, + uint16_t &chosenLang) { const auto *entries = resourceEntries(dir); uint16_t total = dir->numberOfNamedEntries + dir->numberOfIdEntries; const ImageResourceDirectoryEntry *primaryMatch = nullptr; @@ -143,10 +140,8 @@ const ImageResourceDirectoryEntry *selectLanguageEntry(const ImageResourceDirect namespace wibo { -bool Executable::findResource(const ResourceIdentifier &type, - const ResourceIdentifier &name, - std::optional language, - ResourceLocation &out) const { +bool Executable::findResource(const ResourceIdentifier &type, const ResourceIdentifier &name, + std::optional language, ResourceLocation &out) const { const uint8_t *base = reinterpret_cast(rsrcBase); if (!base) { wibo::lastError = ERROR_RESOURCE_DATA_NOT_FOUND; @@ -201,9 +196,7 @@ bool resourceEntryBelongsToExecutable(const Executable &exe, const ImageResource return ptr >= base && (ptr + sizeof(*entry)) <= (base + exe.rsrcSize); } -static bool isIntegerIdentifier(const void *ptr) { - return ((uintptr_t)ptr >> 16) == 0; -} +static bool isIntegerIdentifier(const void *ptr) { return ((uintptr_t)ptr >> 16) == 0; } static std::u16string ansiToU16String(const char *str) { std::u16string result; diff --git a/strutil.cpp b/strutil.cpp index 16f1738..1c0708b 100644 --- a/strutil.cpp +++ b/strutil.cpp @@ -148,11 +148,13 @@ std::string wideStringToString(const uint16_t *src, int len) { std::string result(count, '\0'); for (size_t i = 0; i < count; ++i) { uint16_t value = src[i]; +#ifndef NDEBUG if (i > 0) hexDump << ' '; hexDump << "0x" << value; if (value > 0xFF) sawWide = true; +#endif result[i] = static_cast(value & 0xFF); } From a27d5c2078a4cf9b7432c04fbac27835d21d860e Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 12:52:02 -0600 Subject: [PATCH 13/28] msvcrt: Implement strlen, strcmp, strncmp --- dll/msvcrt.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index e687372..854bf01 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -326,6 +326,12 @@ namespace msvcrt { return 0; } + size_t WIN_ENTRY strlen(const char *str) { return ::strlen(str); } + + int WIN_ENTRY strcmp(const char *lhs, const char *rhs) { return ::strcmp(lhs, rhs); } + + int WIN_ENTRY strncmp(const char *lhs, const char *rhs, size_t count) { return ::strncmp(lhs, rhs, count); } + void* WIN_ENTRY malloc(size_t size){ return std::malloc(size); } @@ -750,6 +756,9 @@ static void *resolveByName(const char *name) { if (strcmp(name, "__wgetmainargs") == 0) return (void*)msvcrt::__wgetmainargs; if (strcmp(name, "setlocale") == 0) return (void*)msvcrt::setlocale; if (strcmp(name, "_wdupenv_s") == 0) return (void*)msvcrt::_wdupenv_s; + if (strcmp(name, "strlen") == 0) return (void *)msvcrt::strlen; + if (strcmp(name, "strcmp") == 0) return (void *)msvcrt::strcmp; + if (strcmp(name, "strncmp") == 0) return (void *)msvcrt::strncmp; if (strcmp(name, "malloc") == 0) return (void*)msvcrt::malloc; if (strcmp(name, "free") == 0) return (void*)msvcrt::free; if (strcmp(name, "_get_wpgmptr") == 0) return (void*)msvcrt::_get_wpgmptr; From a17a3c5413ebab71f79098ac02719f2215616eac Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 13:09:27 -0600 Subject: [PATCH 14/28] Invoke dllMain with proper TIB selector --- common.h | 1 + dll/msvcrt.cpp | 1 + main.cpp | 6 ++++-- module_registry.cpp | 22 ++++++++++++++++++---- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/common.h b/common.h index 74e0d70..e69563f 100644 --- a/common.h +++ b/common.h @@ -96,6 +96,7 @@ namespace wibo { extern std::vector commandLineW; extern bool debugEnabled; extern unsigned int debugIndent; + extern uint16_t tibSelector; void debug_log(const char *fmt, ...); diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 854bf01..b9752af 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -760,6 +760,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "strcmp") == 0) return (void *)msvcrt::strcmp; if (strcmp(name, "strncmp") == 0) return (void *)msvcrt::strncmp; if (strcmp(name, "malloc") == 0) return (void*)msvcrt::malloc; + if (strcmp(name, "_malloc_crt") == 0) return (void*)msvcrt::malloc; if (strcmp(name, "free") == 0) return (void*)msvcrt::free; if (strcmp(name, "_get_wpgmptr") == 0) return (void*)msvcrt::_get_wpgmptr; if (strcmp(name, "_wsplitpath_s") == 0) return (void*)msvcrt::_wsplitpath_s; diff --git a/main.cpp b/main.cpp index 93c4f53..3fcd2e5 100644 --- a/main.cpp +++ b/main.cpp @@ -20,6 +20,7 @@ std::vector wibo::commandLineW; wibo::Executable *wibo::mainModule = 0; bool wibo::debugEnabled = false; unsigned int wibo::debugIndent = 0; +uint16_t wibo::tibSelector = 0; void wibo::debug_log(const char *fmt, ...) { va_list args; @@ -223,6 +224,8 @@ int main(int argc, char **argv) { return 1; } + wibo::tibSelector = static_cast((tibDesc.entry_number << 3) | 7); + // Build a command line std::string cmdLine; for (int i = 1; i < argc; i++) { @@ -287,12 +290,11 @@ int main(int argc, char **argv) { exec.loadPE(f, true); fclose(f); - uint16_t tibSegment = (tibDesc.entry_number << 3) | 7; // Invoke the damn thing asm( "movw %0, %%fs; call *%1" : - : "r"(tibSegment), "r"(exec.entryPoint) + : "r"(wibo::tibSelector), "r"(exec.entryPoint) ); DEBUG_LOG("We came back\n"); wibo::shutdownModuleRegistry(); diff --git a/module_registry.cpp b/module_registry.cpp index e0b3514..a040328 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -385,16 +385,30 @@ void callDllMain(wibo::ModuleInfo &info, DWORD reason) { if (!dllMain) { return; } - if (reason == 1) { + + auto invokeWithGuestTIB = [&](DWORD callReason) -> BOOL { + if (!wibo::tibSelector) { + return dllMain(reinterpret_cast(info.imageBase), callReason, nullptr); + } + + uint16_t previousSegment = 0; + asm volatile("mov %%fs, %0" : "=r"(previousSegment)); + asm volatile("movw %0, %%fs" : : "r"(wibo::tibSelector) : "memory"); + BOOL result = dllMain(reinterpret_cast(info.imageBase), callReason, nullptr); + asm volatile("movw %0, %%fs" : : "r"(previousSegment) : "memory"); + return result; + }; + + if (reason == DLL_PROCESS_ATTACH) { if (info.processAttachCalled) { return; } info.processAttachCalled = true; - BOOL result = dllMain(reinterpret_cast(info.imageBase), reason, nullptr); + BOOL result = invokeWithGuestTIB(reason); info.processAttachSucceeded = result != 0; - } else if (reason == 0) { + } else if (reason == DLL_PROCESS_DETACH) { if (info.processAttachCalled && info.processAttachSucceeded) { - dllMain(reinterpret_cast(info.imageBase), reason, nullptr); + invokeWithGuestTIB(reason); } } } From 042a43ced13147d1aa446e693d170e683131c835 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 13:30:39 -0600 Subject: [PATCH 15/28] DLL fixes; impl GetSystemInfo, __dllonexit, and more --- common.h | 1 + dll/kernel32.cpp | 122 ++++++++++++++++++++++++++++++++++++++++++++ dll/msvcrt.cpp | 129 ++++++++++++++++++++++++++++++++++++++++++++++- loader.cpp | 2 +- 4 files changed, 252 insertions(+), 2 deletions(-) diff --git a/common.h b/common.h index e69563f..d1eec02 100644 --- a/common.h +++ b/common.h @@ -26,6 +26,7 @@ typedef void *HMODULE; typedef void *PVOID; typedef void *LPVOID; typedef void *FARPROC; +typedef uint16_t WORD; typedef uint32_t DWORD; typedef DWORD *PDWORD; typedef DWORD *LPDWORD; diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 624d243..91f38a5 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -21,9 +22,46 @@ #include #include #include +#include #include #include +namespace { + using DWORD_PTR = uintptr_t; + + constexpr WORD PROCESSOR_ARCHITECTURE_INTEL = 0; + constexpr WORD PROCESSOR_ARCHITECTURE_ARM = 5; + constexpr WORD PROCESSOR_ARCHITECTURE_IA64 = 6; + constexpr WORD PROCESSOR_ARCHITECTURE_AMD64 = 9; + constexpr WORD PROCESSOR_ARCHITECTURE_ARM64 = 12; + constexpr WORD PROCESSOR_ARCHITECTURE_UNKNOWN = 0xFFFF; + + constexpr DWORD PROCESSOR_INTEL_386 = 386; + constexpr DWORD PROCESSOR_INTEL_486 = 486; + constexpr DWORD PROCESSOR_INTEL_PENTIUM = 586; + constexpr DWORD PROCESSOR_INTEL_IA64 = 2200; + constexpr DWORD PROCESSOR_AMD_X8664 = 8664; + + struct SYSTEM_INFO { + union { + DWORD dwOemId; + struct { + WORD wProcessorArchitecture; + WORD wReserved; + }; + }; + DWORD dwPageSize; + LPVOID lpMinimumApplicationAddress; + LPVOID lpMaximumApplicationAddress; + DWORD_PTR dwActiveProcessorMask; + DWORD dwNumberOfProcessors; + DWORD dwProcessorType; + DWORD dwAllocationGranularity; + WORD wProcessorLevel; + WORD wProcessorRevision; + }; +} + typedef union _RTL_RUN_ONCE { PVOID Ptr; } RTL_RUN_ONCE, *PRTL_RUN_ONCE; @@ -177,6 +215,88 @@ namespace kernel32 { return 1; // success in retrieval } + BOOL WIN_FUNC DisableThreadLibraryCalls(HMODULE hLibModule) { + DEBUG_LOG("DisableThreadLibraryCalls(%p)\n", hLibModule); + (void)hLibModule; + return TRUE; + } + + void WIN_FUNC GetSystemInfo(SYSTEM_INFO *lpSystemInfo) { + DEBUG_LOG("GetSystemInfo\n"); + if (!lpSystemInfo) { + return; + } + + std::memset(lpSystemInfo, 0, sizeof(*lpSystemInfo)); + + WORD architecture = PROCESSOR_ARCHITECTURE_UNKNOWN; + DWORD processorType = 0; + WORD processorLevel = 0; + +#if defined(__x86_64__) || defined(_M_X64) + architecture = PROCESSOR_ARCHITECTURE_AMD64; + processorType = PROCESSOR_AMD_X8664; + processorLevel = 6; +#elif defined(__i386__) || defined(_M_IX86) + architecture = PROCESSOR_ARCHITECTURE_INTEL; + processorType = PROCESSOR_INTEL_PENTIUM; + processorLevel = 6; +#elif defined(__aarch64__) + architecture = PROCESSOR_ARCHITECTURE_ARM64; + processorType = 0; + processorLevel = 8; +#elif defined(__arm__) + architecture = PROCESSOR_ARCHITECTURE_ARM; + processorType = 0; + processorLevel = 7; +#else + architecture = PROCESSOR_ARCHITECTURE_UNKNOWN; + processorType = 0; + processorLevel = 0; +#endif + + lpSystemInfo->wProcessorArchitecture = architecture; + lpSystemInfo->wReserved = 0; + lpSystemInfo->dwOemId = lpSystemInfo->wProcessorArchitecture; + lpSystemInfo->dwProcessorType = processorType; + lpSystemInfo->wProcessorLevel = processorLevel; + lpSystemInfo->wProcessorRevision = 0; + + long pageSize = sysconf(_SC_PAGESIZE); + if (pageSize <= 0) { + pageSize = 4096; + } + lpSystemInfo->dwPageSize = static_cast(pageSize); + + lpSystemInfo->lpMinimumApplicationAddress = reinterpret_cast(0x00010000); + if (sizeof(void *) == 4) { + lpSystemInfo->lpMaximumApplicationAddress = reinterpret_cast(0x7FFEFFFF); + } else { + lpSystemInfo->lpMaximumApplicationAddress = reinterpret_cast(0x00007FFFFFFEFFFFull); + } + + unsigned int cpuCount = 1; + long reported = sysconf(_SC_NPROCESSORS_ONLN); + if (reported > 0) { + cpuCount = static_cast(reported); + } + lpSystemInfo->dwNumberOfProcessors = cpuCount; + + unsigned int maskWidth = static_cast(sizeof(DWORD_PTR) * 8); + DWORD_PTR mask; + if (cpuCount >= maskWidth) { + mask = static_cast(~static_cast(0)); + } else { + mask = (static_cast(1) << cpuCount) - 1; + } + if (mask == 0) { + mask = 1; + } + lpSystemInfo->dwActiveProcessorMask = mask; + + lpSystemInfo->dwAllocationGranularity = 0x10000; + } + struct PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; @@ -2764,6 +2884,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "GetDiskFreeSpaceExW") == 0) return (void*) kernel32::GetDiskFreeSpaceExW; // sysinfoapi.h + if (strcmp(name, "GetSystemInfo") == 0) return (void *) kernel32::GetSystemInfo; if (strcmp(name, "GetSystemTime") == 0) return (void *) kernel32::GetSystemTime; if (strcmp(name, "GetLocalTime") == 0) return (void *) kernel32::GetLocalTime; if (strcmp(name, "GetSystemTimeAsFileTime") == 0) return (void *) kernel32::GetSystemTimeAsFileTime; @@ -2788,6 +2909,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "SizeofResource") == 0) return (void *) kernel32::SizeofResource; if (strcmp(name, "LoadLibraryA") == 0) return (void *) kernel32::LoadLibraryA; if (strcmp(name, "LoadLibraryExW") == 0) return (void *) kernel32::LoadLibraryExW; + if (strcmp(name, "DisableThreadLibraryCalls") == 0) return (void *) kernel32::DisableThreadLibraryCalls; if (strcmp(name, "FreeLibrary") == 0) return (void *) kernel32::FreeLibrary; if (strcmp(name, "GetProcAddress") == 0) return (void *) kernel32::GetProcAddress; diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index b9752af..57c8498 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -1,5 +1,6 @@ #include "common.h" #include +#include #include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +22,7 @@ typedef void (*_PVFV)(); typedef int (*_PIFV)(); +using _onexit_t = _PIFV; namespace msvcrt { int _commode; @@ -29,6 +32,40 @@ namespace msvcrt { uint16_t* _wpgmptr; namespace { + struct DllOnExitTable { + _PVFV **pbegin; + _PVFV **pend; + std::vector<_PVFV> callbacks; + bool registered; + }; + + constexpr size_t LOCK_TABLE_SIZE = 64; + std::array &lockTable() { + static std::array table; + return table; + } + + std::vector &dllOnExitTables() { + static std::vector tables; + return tables; + } + + std::mutex &dllOnExitMutex() { + static std::mutex mutex; + return mutex; + } + + DllOnExitTable &ensureDllOnExitTable(_PVFV **pbegin, _PVFV **pend) { + auto &tables = dllOnExitTables(); + for (auto &table : tables) { + if (table.pbegin == pbegin && table.pend == pend) { + return table; + } + } + tables.push_back(DllOnExitTable{pbegin, pend, {}, false}); + return tables.back(); + } + template struct StringListStorage { std::vector> strings; @@ -336,10 +373,96 @@ namespace msvcrt { return std::malloc(size); } + void* WIN_ENTRY _malloc_crt(size_t size) { + return std::malloc(size); + } + + void WIN_ENTRY _lock(int locknum) { + if (locknum < 0 || static_cast(locknum) >= LOCK_TABLE_SIZE) { + DEBUG_LOG("_lock: unsupported lock %d\n", locknum); + return; + } + lockTable()[static_cast(locknum)].lock(); + } + + void WIN_ENTRY _unlock(int locknum) { + if (locknum < 0 || static_cast(locknum) >= LOCK_TABLE_SIZE) { + DEBUG_LOG("_unlock: unsupported lock %d\n", locknum); + return; + } + lockTable()[static_cast(locknum)].unlock(); + } + + _onexit_t WIN_ENTRY __dllonexit(_onexit_t func, _PVFV **pbegin, _PVFV **pend) { + if (!pbegin || !pend) { + return nullptr; + } + + std::lock_guard guard(dllOnExitMutex()); + auto &table = ensureDllOnExitTable(pbegin, pend); + if (!table.registered) { + wibo::registerOnExitTable(reinterpret_cast(pbegin)); + table.registered = true; + } + + if (func) { + auto callback = reinterpret_cast<_PVFV>(func); + table.callbacks.push_back(callback); + wibo::addOnExitFunction(reinterpret_cast(pbegin), reinterpret_cast(callback)); + } + + if (table.callbacks.empty()) { + *pbegin = nullptr; + *pend = nullptr; + } else { + _PVFV *dataPtr = table.callbacks.data(); + *pbegin = dataPtr; + *pend = dataPtr + table.callbacks.size(); + } + + return reinterpret_cast<_onexit_t>(func); + } + void WIN_ENTRY free(void* ptr){ std::free(ptr); } + static uint16_t toLower(uint16_t ch) { + if (ch >= 'A' && ch <= 'Z') { + return static_cast(ch + ('a' - 'A')); + } + wchar_t wide = static_cast(ch); + wchar_t lowered = std::towlower(wide); + if (lowered < 0 || lowered > 0xFFFF) { + return ch; + } + return static_cast(lowered); + } + + int WIN_ENTRY _wcsicmp(const uint16_t *lhs, const uint16_t *rhs) { + if (lhs == rhs) { + return 0; + } + if (!lhs) { + return -1; + } + if (!rhs) { + return 1; + } + + while (*lhs && *rhs) { + uint16_t a = toLower(*lhs++); + uint16_t b = toLower(*rhs++); + if (a != b) { + return static_cast(a) - static_cast(b); + } + } + + uint16_t a = toLower(*lhs); + uint16_t b = toLower(*rhs); + return static_cast(a) - static_cast(b); + } + int WIN_ENTRY _get_wpgmptr(uint16_t** pValue){ DEBUG_LOG("_get_wpgmptr(%p)\n", pValue); if(!pValue) return 22; @@ -760,8 +883,12 @@ static void *resolveByName(const char *name) { if (strcmp(name, "strcmp") == 0) return (void *)msvcrt::strcmp; if (strcmp(name, "strncmp") == 0) return (void *)msvcrt::strncmp; if (strcmp(name, "malloc") == 0) return (void*)msvcrt::malloc; - if (strcmp(name, "_malloc_crt") == 0) return (void*)msvcrt::malloc; + if (strcmp(name, "_malloc_crt") == 0) return (void*)msvcrt::_malloc_crt; + if (strcmp(name, "_lock") == 0) return (void*)msvcrt::_lock; + if (strcmp(name, "_unlock") == 0) return (void*)msvcrt::_unlock; + if (strcmp(name, "__dllonexit") == 0) return (void*)msvcrt::__dllonexit; if (strcmp(name, "free") == 0) return (void*)msvcrt::free; + if (strcmp(name, "_wcsicmp") == 0) return (void*)msvcrt::_wcsicmp; if (strcmp(name, "_get_wpgmptr") == 0) return (void*)msvcrt::_get_wpgmptr; if (strcmp(name, "_wsplitpath_s") == 0) return (void*)msvcrt::_wsplitpath_s; if (strcmp(name, "wcscat_s") == 0) return (void*)msvcrt::wcscat_s; diff --git a/loader.cpp b/loader.cpp index b074cf7..a8e1389 100644 --- a/loader.cpp +++ b/loader.cpp @@ -292,7 +292,7 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { ++dir; } - entryPoint = fromRVA(header32.addressOfEntryPoint); + entryPoint = header32.addressOfEntryPoint ? fromRVA(header32.addressOfEntryPoint) : nullptr; return true; } From b4ea1da959b9bfa9d93d5c918d331d64c9077beb Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 15:04:55 -0600 Subject: [PATCH 16/28] Improve DLL loading and process launch handling --- dll/advapi32.cpp | 18 +- dll/kernel32.cpp | 183 +++++++++------- dll/msvcrt.cpp | 511 +++++++++++++++++++++++++++++++++++++++++--- files.cpp | 120 +++++++++++ files.h | 5 + module_registry.cpp | 224 ++++++++++--------- processes.cpp | 245 ++++++++++++++++++++- processes.h | 8 + 8 files changed, 1097 insertions(+), 217 deletions(-) diff --git a/dll/advapi32.cpp b/dll/advapi32.cpp index f3fc490..b3052c2 100644 --- a/dll/advapi32.cpp +++ b/dll/advapi32.cpp @@ -7,34 +7,34 @@ namespace advapi32 { return 1; // screw them for now } - bool WIN_FUNC CryptReleaseContext(void* hProv, unsigned int dwFlags) { + BOOL WIN_FUNC CryptReleaseContext(void* hProv, unsigned int dwFlags) { DEBUG_LOG("STUB: CryptReleaseContext %p %u\n", hProv, dwFlags); - return true; + return TRUE; } - bool WIN_FUNC CryptAcquireContextW(void** phProv, const wchar_t* pszContainer, const wchar_t* pszProvider, unsigned int dwProvType, unsigned int dwFlags){ + BOOL WIN_FUNC CryptAcquireContextW(void** phProv, const wchar_t* pszContainer, const wchar_t* pszProvider, unsigned int dwProvType, unsigned int dwFlags){ DEBUG_LOG("STUB: CryptAcquireContextW(%p)\n", phProv); // to quote the guy above me: screw them for now static int lmao = 42; if (phProv) { *phProv = &lmao; - return true; + return TRUE; } - return false; + return FALSE; } - bool WIN_FUNC CryptGenRandom(void* hProv, unsigned int dwLen, unsigned char* pbBuffer){ + BOOL WIN_FUNC CryptGenRandom(void* hProv, unsigned int dwLen, unsigned char* pbBuffer){ DEBUG_LOG("STUB: CryptGenRandom(%p)\n", hProv); - if (!pbBuffer || dwLen == 0) return false; + if (!pbBuffer || dwLen == 0) return FALSE; ssize_t ret = getrandom(pbBuffer, dwLen, 0); if (ret < 0 || (size_t)ret != dwLen) { - return false; + return FALSE; } - return true; + return TRUE; } } diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 91f38a5..ba3409d 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "strutil.h" #include #include @@ -305,90 +306,73 @@ namespace kernel32 { }; - BOOL WIN_FUNC CreateProcessA( + BOOL WIN_FUNC CreateProcessA( LPCSTR lpApplicationName, LPSTR lpCommandLine, - void *lpProcessAttributes, - void *lpThreadAttributes, + void *lpProcessAttributes, + void *lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, - void *lpStartupInfo, + void *lpStartupInfo, PROCESS_INFORMATION *lpProcessInformation - ) { + ) { DEBUG_LOG("CreateProcessA %s \"%s\" %p %p %d 0x%x %p %s %p %p\n", - lpApplicationName, - lpCommandLine, - lpProcessAttributes, + lpApplicationName ? lpApplicationName : "", + lpCommandLine ? lpCommandLine : "", + lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory ? lpCurrentDirectory : "", - lpStartupInfo, - lpProcessInformation - ); - - // Argument parsing - // First: how many arguments do we have? - size_t argc = 2; - - for (size_t i = 1; i < strlen(lpCommandLine); i++) { - if (isspace(lpCommandLine[i]) && !isspace(lpCommandLine[i - 1])) - argc++; - } - - char **argv = (char **) calloc(argc + 1, sizeof(char*)); - argv[0] = wibo::executableName; - std::string pathStr = files::pathFromWindows(lpApplicationName).string(); - argv[1] = (char *) pathStr.c_str(); - - char* arg = strtok(lpCommandLine, " "); - size_t current_arg_index = 2; - - while (arg != NULL) { - // We're deliberately discarding the first token here - // to prevent from doubling up on the target executable name - // (it appears as lpApplicationName, and as the first token in lpCommandLine) - arg = strtok(NULL, " "); - - if (arg) { - // Trim all quotation marks from the start and the end of the string - while(*arg == '\"') { - arg++; - } - - char* end = arg + strlen(arg) - 1; - while(end > arg && *end == '\"') { - *end = '\0'; - end--; - } + lpStartupInfo, + lpProcessInformation + ); + + std::string application = lpApplicationName ? lpApplicationName : ""; + std::vector arguments = processes::splitCommandLine(lpCommandLine); + if (application.empty()) { + if (arguments.empty()) { + wibo::lastError = ERROR_FILE_NOT_FOUND; + return 0; } - - argv[current_arg_index++] = arg; + application = arguments.front(); + } + if (arguments.empty()) { + arguments.push_back(application); } - argv[argc] = NULL; // Last element in argv should be a null pointer - - // YET TODO: take into account process / thread attributes, environment variables - // working directory, etc. - setenv("WIBO_DEBUG_INDENT", std::to_string(wibo::debugIndent + 1).c_str(), true); - - pid_t pid; - if (posix_spawn(&pid, wibo::executableName, NULL, NULL, argv, environ)) { + auto resolved = processes::resolveExecutable(application, true); + if (!resolved) { + wibo::lastError = ERROR_FILE_NOT_FOUND; return 0; - }; + } - *lpProcessInformation = { - .hProcess = processes::allocProcessHandle(pid), - .hThread = nullptr, - .dwProcessId = (DWORD) pid, - .dwThreadId = 42 - }; + pid_t pid = -1; + int spawnResult = processes::spawnViaWibo(*resolved, arguments, &pid); + if (spawnResult != 0) { + wibo::lastError = (spawnResult == ENOENT) ? ERROR_FILE_NOT_FOUND : ERROR_ACCESS_DENIED; + return 0; + } + if (lpProcessInformation) { + lpProcessInformation->hProcess = processes::allocProcessHandle(pid); + lpProcessInformation->hThread = nullptr; + lpProcessInformation->dwProcessId = static_cast(pid); + lpProcessInformation->dwThreadId = 0; + } + wibo::lastError = ERROR_SUCCESS; + (void)lpProcessAttributes; + (void)lpThreadAttributes; + (void)bInheritHandles; + (void)dwCreationFlags; + (void)lpEnvironment; + (void)lpCurrentDirectory; + (void)lpStartupInfo; return 1; - } + } unsigned int WIN_FUNC WaitForSingleObject(void *hHandle, unsigned int dwMilliseconds) { DEBUG_LOG("WaitForSingleObject (%u)\n", dwMilliseconds); @@ -1918,6 +1902,15 @@ namespace kernel32 { return wibo::loadModule(lpLibFileName); } + HMODULE WIN_FUNC LoadLibraryW(LPCWSTR lpLibFileName) { + DEBUG_LOG("LoadLibraryW\n"); + if (!lpLibFileName) { + return nullptr; + } + auto filename = wideStringToString(lpLibFileName); + return LoadLibraryA(filename.c_str()); + } + HMODULE WIN_FUNC LoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags) { assert(!hFile); DEBUG_LOG("LoadLibraryExW(%x) -> ", dwFlags); @@ -2554,35 +2547,74 @@ namespace kernel32 { return 0; // fail } + + static std::string convertEnvValueForWindows(const std::string &name, const char *rawValue) { + if (!rawValue) { + return std::string(); + } + if (strcasecmp(name.c_str(), "PATH") != 0) { + return rawValue; + } + std::string converted = files::hostPathListToWindows(rawValue); + return converted.empty() ? std::string(rawValue) : converted; + } + + static std::string convertEnvValueToHost(const std::string &name, const char *rawValue) { + if (!rawValue) { + return std::string(); + } + if (strcasecmp(name.c_str(), "PATH") != 0) { + return rawValue; + } + std::string converted = files::windowsPathListToHost(rawValue); + return converted.empty() ? std::string(rawValue) : converted; + } + DWORD WIN_FUNC GetEnvironmentVariableA(LPCSTR lpName, LPSTR lpBuffer, DWORD nSize) { DEBUG_LOG("GetEnvironmentVariableA: %s\n", lpName); - const char *value = getenv(lpName); - if (!value) { + if (!lpName) { + return 0; + } + const char *rawValue = getenv(lpName); + if (!rawValue) { return 0; } - unsigned int len = strlen(value); + std::string converted = convertEnvValueForWindows(lpName, rawValue); + const std::string &finalValue = converted.empty() ? std::string(rawValue) : converted; + unsigned int len = finalValue.size(); if (nSize == 0) { return len + 1; } - if (nSize < len) { + if (nSize <= len) { return len; } - memcpy(lpBuffer, value, len + 1); + memcpy(lpBuffer, finalValue.c_str(), len + 1); return len; } unsigned int WIN_FUNC SetEnvironmentVariableA(const char *lpName, const char *lpValue) { - DEBUG_LOG("SetEnvironmentVariableA: %s=%s\n", lpName, lpValue); - return setenv(lpName, lpValue, 1 /* OVERWRITE */); + DEBUG_LOG("SetEnvironmentVariableA: %s=%s\n", lpName, lpValue ? lpValue : ""); + if (!lpName) { + return 0; + } + if (!lpValue) { + return unsetenv(lpName); + } + std::string hostValue = convertEnvValueToHost(lpName, lpValue); + const char *valuePtr = hostValue.empty() ? lpValue : hostValue.c_str(); + return setenv(lpName, valuePtr, 1 /* OVERWRITE */); } DWORD WIN_FUNC GetEnvironmentVariableW(LPCWSTR lpName, LPWSTR lpBuffer, DWORD nSize) { - DEBUG_LOG("GetEnvironmentVariableW: %s\n", wideStringToString(lpName).c_str()); - const char *value = getenv(wideStringToString(lpName).c_str()); - if (!value) { + std::string name = wideStringToString(lpName); + DEBUG_LOG("GetEnvironmentVariableW: %s\n", name.c_str()); + const char *rawValue = getenv(name.c_str()); + if (!rawValue) { return 0; } - auto wideValue = stringToWideString(value); + std::string converted = convertEnvValueForWindows(name, rawValue); + const std::string &finalValue = converted.empty() ? std::string(rawValue) : converted; + auto wideValue = stringToWideString(finalValue.c_str()); const auto len = wideValue.size(); if (nSize < len) { return len; @@ -2908,6 +2940,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "LockResource") == 0) return (void *) kernel32::LockResource; if (strcmp(name, "SizeofResource") == 0) return (void *) kernel32::SizeofResource; if (strcmp(name, "LoadLibraryA") == 0) return (void *) kernel32::LoadLibraryA; + if (strcmp(name, "LoadLibraryW") == 0) return (void *) kernel32::LoadLibraryW; if (strcmp(name, "LoadLibraryExW") == 0) return (void *) kernel32::LoadLibraryExW; if (strcmp(name, "DisableThreadLibraryCalls") == 0) return (void *) kernel32::DisableThreadLibraryCalls; if (strcmp(name, "FreeLibrary") == 0) return (void *) kernel32::FreeLibrary; diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 57c8498..7dbd1ec 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -11,19 +11,27 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include +#include +#include +#include "files.h" +#include "processes.h" #include "strutil.h" typedef void (*_PVFV)(); typedef int (*_PIFV)(); using _onexit_t = _PIFV; +extern "C" char **environ; + namespace msvcrt { int _commode; int _fmode; @@ -66,6 +74,60 @@ namespace msvcrt { return tables.back(); } + std::string normalizeEnvStringForWindows(const char *src) { + if (!src) { + return std::string(); + } + std::string entry(src); + auto pos = entry.find('='); + if (pos == std::string::npos) { + return entry; + } + std::string name = entry.substr(0, pos); + std::string value = entry.substr(pos + 1); + if (strcasecmp(name.c_str(), "PATH") == 0) { + std::string converted = files::hostPathListToWindows(value); + std::string result = converted.empty() ? value : converted; + std::string exeDir; + if (wibo::argv && wibo::argv[0]) { + std::filesystem::path exePath = std::filesystem::absolute(std::filesystem::path(wibo::argv[0])).parent_path(); + if (!exePath.empty()) { + exeDir = files::pathToWindows(exePath); + } + } + if (!exeDir.empty()) { + std::string loweredResult = result; + std::string loweredExe = exeDir; + std::transform(loweredResult.begin(), loweredResult.end(), loweredResult.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); + std::transform(loweredExe.begin(), loweredExe.end(), loweredExe.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); + bool present = false; + size_t start = 0; + while (start <= loweredResult.size()) { + size_t end = loweredResult.find(';', start); + if (end == std::string::npos) { + end = loweredResult.size(); + } + if (loweredResult.substr(start, end - start) == loweredExe) { + present = true; + break; + } + if (end == loweredResult.size()) { + break; + } + start = end + 1; + } + if (!present) { + if (!result.empty() && result.back() != ';') { + result.push_back(';'); + } + result += exeDir; + } + } + entry = name + "=" + result; + } + return entry; + } + template struct StringListStorage { std::vector> strings; @@ -103,23 +165,19 @@ namespace msvcrt { }; std::vector copyNarrowString(const char *src) { - if (!src) { - src = ""; - } - size_t len = std::strlen(src); + std::string normalized = normalizeEnvStringForWindows(src); + size_t len = normalized.size(); std::vector result(len + 1); if (len > 0) { - std::memcpy(result.data(), src, len); + std::memcpy(result.data(), normalized.data(), len); } result[len] = '\0'; return result; } std::vector copyWideString(const char *src) { - if (!src) { - src = ""; - } - return stringToWideString(src); + std::string normalized = normalizeEnvStringForWindows(src); + return stringToWideString(normalized.c_str()); } template @@ -427,6 +485,90 @@ namespace msvcrt { std::free(ptr); } + void* WIN_ENTRY memcpy(void *dest, const void *src, size_t count) { + return std::memcpy(dest, src, count); + } + + void* WIN_ENTRY memmove(void *dest, const void *src, size_t count) { + return std::memmove(dest, src, count); + } + + int WIN_ENTRY fflush(FILE *stream) { + return std::fflush(stream); + } + + FILE *WIN_ENTRY fopen(const char *filename, const char *mode) { + return std::fopen(filename, mode); + } + + int WIN_ENTRY _dup2(int fd1, int fd2) { + return dup2(fd1, fd2); + } + + int WIN_ENTRY _isatty(int fd) { + return isatty(fd); + } + + int WIN_ENTRY fseek(FILE *stream, long offset, int origin) { + return std::fseek(stream, offset, origin); + } + + long WIN_ENTRY ftell(FILE *stream) { + return std::ftell(stream); + } + + int WIN_ENTRY feof(FILE *stream) { + return std::feof(stream); + } + + int WIN_ENTRY fputws(const uint16_t *str, FILE *stream) { + std::wstring temp; + if (str) { + for (const uint16_t *cursor = str; *cursor; ++cursor) { + temp.push_back(static_cast(*cursor)); + } + } + return std::fputws(temp.c_str(), stream); + } + + uint16_t* WIN_ENTRY fgetws(uint16_t *buffer, int size, FILE *stream) { + if (!buffer || size <= 0) { + return nullptr; + } + std::vector temp(static_cast(size)); + wchar_t *res = std::fgetws(temp.data(), size, stream); + if (!res) { + return nullptr; + } + for (int i = 0; i < size; ++i) { + buffer[i] = static_cast(temp[i]); + if (temp[i] == L'\0') { + break; + } + } + return buffer; + } + + wint_t WIN_ENTRY fgetwc(FILE *stream) { + return std::fgetwc(stream); + } + + int WIN_ENTRY _wfopen_s(FILE **stream, const uint16_t *filename, const uint16_t *mode) { + if (!stream || !filename || !mode) { + errno = EINVAL; + return EINVAL; + } + std::string narrowName = wideStringToString(filename); + std::string narrowMode = wideStringToString(mode); + FILE *handle = std::fopen(narrowName.c_str(), narrowMode.c_str()); + if (!handle) { + *stream = nullptr; + return errno ? errno : EINVAL; + } + *stream = handle; + return 0; + } + static uint16_t toLower(uint16_t ch) { if (ch >= 'A' && ch <= 'Z') { return static_cast(ch + ('a' - 'A')); @@ -463,6 +605,234 @@ namespace msvcrt { return static_cast(a) - static_cast(b); } + int WIN_ENTRY _wmakepath_s(uint16_t *path, size_t sizeInWords, const uint16_t *drive, const uint16_t *dir, + const uint16_t *fname, const uint16_t *ext) { + if (!path || sizeInWords == 0) { + return EINVAL; + } + + path[0] = 0; + std::u16string result; + + auto append = [&](const uint16_t *src) { + if (!src || !*src) { + return; + } + for (const uint16_t *cursor = src; *cursor; ++cursor) { + result.push_back(static_cast(*cursor)); + } + }; + + if (drive && *drive) { + result.push_back(static_cast(drive[0])); + if (drive[1] == u':') { + result.push_back(u':'); + append(drive + 2); + } else { + result.push_back(u':'); + append(drive + 1); + } + } + + auto appendDir = [&](const uint16_t *directory) { + if (!directory || !*directory) { + return; + } + append(directory); + if (result.empty()) { + return; + } + char16_t last = result.back(); + if (last != u'/' && last != u'\\') { + result.push_back(u'\\'); + } + }; + + appendDir(dir); + append(fname); + + if (ext && *ext) { + if (*ext != u'.') { + result.push_back(u'.'); + append(ext); + } else { + append(ext); + } + } + + size_t required = result.size() + 1; + if (required > sizeInWords) { + path[0] = 0; + return ERANGE; + } + + for (size_t i = 0; i < result.size(); ++i) { + path[i] = static_cast(result[i]); + } + path[result.size()] = 0; + return 0; + } + + int WIN_ENTRY _wputenv_s(const uint16_t *varname, const uint16_t *value) { + if (!varname || !value) { + errno = EINVAL; + return EINVAL; + } + + if (!*varname) { + errno = EINVAL; + return EINVAL; + } + + for (const uint16_t *cursor = varname; *cursor; ++cursor) { + if (*cursor == static_cast('=')) { + errno = EINVAL; + return EINVAL; + } + } + + std::string name = wideStringToString(varname); + if (name.empty()) { + errno = EINVAL; + return EINVAL; + } + + int resultCode = 0; + if (!*value) { + if (unsetenv(name.c_str()) != 0) { + resultCode = errno != 0 ? errno : EINVAL; + } + } else { + std::string narrowValue = wideStringToString(value); + if (setenv(name.c_str(), narrowValue.c_str(), 1) != 0) { + resultCode = errno != 0 ? errno : EINVAL; + } + } + + if (resultCode != 0) { + errno = resultCode; + return resultCode; + } + + getMainArgsCommon(nullptr, nullptr, nullptr, copyNarrowString); + getMainArgsCommon(nullptr, nullptr, nullptr, copyWideString); + return 0; + } + + unsigned long WIN_ENTRY wcsspn(const uint16_t *str1, const uint16_t *str2) { + if (!str1 || !str2) { + return 0; + } + unsigned long count = 0; + for (const uint16_t *p = str1; *p; ++p) { + bool match = false; + for (const uint16_t *q = str2; *q; ++q) { + if (*p == *q) { + match = true; + break; + } + } + if (!match) { + break; + } + ++count; + } + return count; + } + + long WIN_ENTRY _wtol(const uint16_t *str) { + return wstrtol(str, nullptr, 10); + } + + int WIN_ENTRY _wcsupr_s(uint16_t *str, size_t size) { + if (!str || size == 0) { + return EINVAL; + } + size_t len = wstrnlen(str, size); + if (len >= size) { + return ERANGE; + } + for (size_t i = 0; i < len; ++i) { + wchar_t ch = static_cast(str[i]); + str[i] = static_cast(std::towupper(ch)); + } + return 0; + } + + int WIN_ENTRY _wcslwr_s(uint16_t *str, size_t size) { + if (!str || size == 0) { + return EINVAL; + } + size_t len = wstrnlen(str, size); + if (len >= size) { + return ERANGE; + } + for (size_t i = 0; i < len; ++i) { + wchar_t ch = static_cast(str[i]); + str[i] = static_cast(std::towlower(ch)); + } + return 0; + } + + wint_t WIN_ENTRY towlower(wint_t ch) { + return static_cast(std::towlower(static_cast(ch))); + } + + int WIN_ENTRY _ftime64_s(void *timeb) { + DEBUG_LOG("STUB: _ftime64_s(%p)\n", timeb); + return 0; + } + + int WIN_ENTRY _crt_debugger_hook(int value) { + DEBUG_LOG("_crt_debugger_hook(%d)\n", value); + (void)value; + return 0; + } + + int WIN_ENTRY _configthreadlocale(int mode) { + static int currentMode = 0; + int previous = currentMode; + if (mode == -1) { + return previous; + } + if (mode == 0 || mode == 1 || mode == 2) { + currentMode = mode; + return previous; + } + errno = EINVAL; + return -1; + } + + static void abort_and_log(const char *reason) { + DEBUG_LOG("Runtime abort: %s\n", reason ? reason : ""); + std::abort(); + } + + int WIN_ENTRY _amsg_exit(int reason) { + DEBUG_LOG("_amsg_exit(%d)\n", reason); + abort_and_log("_amsg_exit"); + return reason; + } + + void WIN_ENTRY _invoke_watson(const uint16_t *, const uint16_t *, const uint16_t *, unsigned int, uintptr_t) { + DEBUG_LOG("_invoke_watson\n"); + abort_and_log("_invoke_watson"); + } + + void WIN_ENTRY terminateShim() { + abort_and_log("terminate"); + } + + int WIN_ENTRY _except_handler4_common(void *, void *, void *, void *) { + DEBUG_LOG("_except_handler4_common\n"); + return 0; + } + + long WIN_ENTRY _XcptFilter(unsigned long code, void *) { + DEBUG_LOG("_XcptFilter(%lu)\n", code); + return 0; + } + int WIN_ENTRY _get_wpgmptr(uint16_t** pValue){ DEBUG_LOG("_get_wpgmptr(%p)\n", pValue); if(!pValue) return 22; @@ -576,9 +946,17 @@ namespace msvcrt { } int WIN_ENTRY _waccess_s(const uint16_t* path, int mode){ - std::string str = wideStringToString(path); - DEBUG_LOG("_waccess_s %s\n", str.c_str()); - return access(str.c_str(), mode); + std::string original = wideStringToString(path); + DEBUG_LOG("_waccess_s %s\n", original.c_str()); + std::filesystem::path host = files::pathFromWindows(original.c_str()); + std::string candidate; + if (!host.empty()) { + candidate = host.string(); + } else { + candidate = original; + std::replace(candidate.begin(), candidate.end(), '\\', '/'); + } + return access(candidate.c_str(), mode); } void* WIN_ENTRY memset(void *s, int c, size_t n){ @@ -744,29 +1122,16 @@ namespace msvcrt { return wstrtoul(strSource, endptr, base); } - int WIN_ENTRY _dup2(int fd1, int fd2){ - return dup2(fd1, fd2); - } - FILE* WIN_ENTRY _wfsopen(const uint16_t* filename, const uint16_t* mode, int shflag){ if (!filename || !mode) return nullptr; std::string fname_str = wideStringToString(filename); std::string mode_str = wideStringToString(mode); DEBUG_LOG("_wfsopen file %s, mode %s\n", fname_str.c_str(), mode_str.c_str()); + (void)shflag; return fopen(fname_str.c_str(), mode_str.c_str()); } - int WIN_ENTRY fputws(const uint16_t* str, FILE* stream){ - if(!str || !stream) return EOF; - - std::string fname_str = wideStringToString(str); - DEBUG_LOG("fputws %s\n", fname_str.c_str()); - - if(fputs(fname_str.c_str(), stream) < 0) return EOF; - else return 0; - } - int WIN_ENTRY puts(const char *str) { if (!str) { str = "(null)"; @@ -787,9 +1152,9 @@ namespace msvcrt { DEBUG_LOG("flushall\n"); int count = 0; - if (fflush(stdin) == 0) count++; - if (fflush(stdout) == 0) count++; - if (fflush(stderr) == 0) count++; + if (msvcrt::fflush(stdin) == 0) count++; + if (msvcrt::fflush(stdout) == 0) count++; + if (msvcrt::fflush(stderr) == 0) count++; return count; } @@ -798,10 +1163,62 @@ namespace msvcrt { return &errno; } - intptr_t WIN_ENTRY _wspawnvp(int mode, const uint16_t* cmdname, const uint16_t* const * argv){ - std::string str_cmd = wideStringToString(cmdname); - DEBUG_LOG("STUB: _wspawnvp %s\n", str_cmd.c_str()); - return -1; + intptr_t WIN_ENTRY _wspawnvp(int mode, const uint16_t* cmdname, const uint16_t* const * argv) { + if (!cmdname || !argv) { + errno = EINVAL; + return -1; + } + + std::string command = wideStringToString(cmdname); + DEBUG_LOG("_wspawnvp(mode=%d, cmd=%s)\n", mode, command.c_str()); + + std::vector argStorage; + for (const uint16_t *const *cursor = argv; *cursor; ++cursor) { + argStorage.emplace_back(wideStringToString(*cursor)); + } + if (argStorage.empty()) { + argStorage.emplace_back(command); + } + + auto resolved = processes::resolveExecutable(command, true); + if (!resolved) { + errno = ENOENT; + DEBUG_LOG("\tfailed to resolve executable for %s\n", command.c_str()); + return -1; + } + + pid_t pid = -1; + int spawnResult = processes::spawnViaWibo(*resolved, argStorage, &pid); + if (spawnResult != 0) { + errno = spawnResult; + DEBUG_LOG("\tspawnViaWibo failed: %d\n", spawnResult); + return -1; + } + + constexpr int P_WAIT = 0; + constexpr int P_DETACH = 2; + + if (mode == P_WAIT) { + int status = 0; + if (waitpid(pid, &status, 0) == -1) { + DEBUG_LOG("\twaitpid failed: %d\n", errno); + return -1; + } + if (WIFEXITED(status)) { + return static_cast(WEXITSTATUS(status)); + } + if (WIFSIGNALED(status)) { + errno = EINTR; + } + return -1; + } + + if (mode == P_DETACH) { + return 0; + } + + // _P_NOWAIT and unknown flags: return process id + return static_cast(pid); } int WIN_ENTRY _wunlink(const uint16_t *filename){ @@ -889,11 +1306,39 @@ static void *resolveByName(const char *name) { if (strcmp(name, "__dllonexit") == 0) return (void*)msvcrt::__dllonexit; if (strcmp(name, "free") == 0) return (void*)msvcrt::free; if (strcmp(name, "_wcsicmp") == 0) return (void*)msvcrt::_wcsicmp; + if (strcmp(name, "_wmakepath_s") == 0) return (void*)msvcrt::_wmakepath_s; + if (strcmp(name, "_wputenv_s") == 0) return (void*)msvcrt::_wputenv_s; if (strcmp(name, "_get_wpgmptr") == 0) return (void*)msvcrt::_get_wpgmptr; if (strcmp(name, "_wsplitpath_s") == 0) return (void*)msvcrt::_wsplitpath_s; if (strcmp(name, "wcscat_s") == 0) return (void*)msvcrt::wcscat_s; if (strcmp(name, "_wcsdup") == 0) return (void*)msvcrt::_wcsdup; if (strcmp(name, "memset") == 0) return (void*)msvcrt::memset; + if (strcmp(name, "memcpy") == 0) return (void*)msvcrt::memcpy; + if (strcmp(name, "memmove") == 0) return (void*)msvcrt::memmove; + if (strcmp(name, "fflush") == 0) return (void*)msvcrt::fflush; + if (strcmp(name, "fopen") == 0) return (void*)msvcrt::fopen; + if (strcmp(name, "fseek") == 0) return (void*)msvcrt::fseek; + if (strcmp(name, "ftell") == 0) return (void*)msvcrt::ftell; + if (strcmp(name, "feof") == 0) return (void*)msvcrt::feof; + if (strcmp(name, "fgetws") == 0) return (void*)msvcrt::fgetws; + if (strcmp(name, "fgetwc") == 0) return (void*)msvcrt::fgetwc; + if (strcmp(name, "fputws") == 0) return (void*)msvcrt::fputws; + if (strcmp(name, "_wfopen_s") == 0) return (void*)msvcrt::_wfopen_s; + if (strcmp(name, "wcsspn") == 0) return (void*)msvcrt::wcsspn; + if (strcmp(name, "_wtol") == 0) return (void*)msvcrt::_wtol; + if (strcmp(name, "_wcsupr_s") == 0) return (void*)msvcrt::_wcsupr_s; + if (strcmp(name, "_wcslwr_s") == 0) return (void*)msvcrt::_wcslwr_s; + if (strcmp(name, "_dup2") == 0) return (void*)msvcrt::_dup2; + if (strcmp(name, "_isatty") == 0) return (void*)msvcrt::_isatty; + if (strcmp(name, "towlower") == 0) return (void*)msvcrt::towlower; + if (strcmp(name, "_ftime64_s") == 0) return (void*)msvcrt::_ftime64_s; + if (strcmp(name, "_crt_debugger_hook") == 0) return (void*)msvcrt::_crt_debugger_hook; + if (strcmp(name, "_configthreadlocale") == 0) return (void*)msvcrt::_configthreadlocale; + if (strcmp(name, "_amsg_exit") == 0) return (void*)msvcrt::_amsg_exit; + if (strcmp(name, "_invoke_watson") == 0) return (void*)msvcrt::_invoke_watson; + if (strcmp(name, "_except_handler4_common") == 0) return (void*)msvcrt::_except_handler4_common; + if (strcmp(name, "_XcptFilter") == 0) return (void*)msvcrt::_XcptFilter; + if (strcmp(name, "?terminate@@YAXXZ") == 0) return (void*)msvcrt::terminateShim; if (strcmp(name, "wcsncpy_s") == 0) return (void*)msvcrt::wcsncpy_s; if (strcmp(name, "wcsncat_s") == 0) return (void*)msvcrt::wcsncat_s; if (strcmp(name, "_itow_s") == 0) return (void*)msvcrt::_itow_s; diff --git a/files.cpp b/files.cpp index 2701d30..579538b 100644 --- a/files.cpp +++ b/files.cpp @@ -2,10 +2,58 @@ #include "files.h" #include "handles.h" #include +#include #include +#include +#include +#include namespace files { + static std::vector splitList(const std::string &value, char delimiter) { + std::vector entries; + size_t start = 0; + while (start <= value.size()) { + size_t end = value.find(delimiter, start); + if (end == std::string::npos) { + end = value.size(); + } + entries.emplace_back(value.substr(start, end - start)); + if (end == value.size()) { + break; + } + start = end + 1; + } + return entries; + } + + static std::string toWindowsPathEntry(const std::string &entry) { + if (entry.empty()) { + return std::string(); + } + bool looksWindows = entry.find('\\') != std::string::npos || + (entry.size() >= 2 && entry[1] == ':' && entry[0] != '/'); + if (looksWindows) { + std::string normalized = entry; + std::replace(normalized.begin(), normalized.end(), '/', '\\'); + return normalized; + } + return pathToWindows(std::filesystem::path(entry)); + } + + static std::string toHostPathEntry(const std::string &entry) { + if (entry.empty()) { + return std::string(); + } + auto converted = pathFromWindows(entry.c_str()); + if (!converted.empty()) { + return converted.string(); + } + std::string normalized = entry; + std::replace(normalized.begin(), normalized.end(), '\\', '/'); + return normalized; + } + static void *stdinHandle; static void *stdoutHandle; static void *stderrHandle; @@ -123,4 +171,76 @@ namespace files { stdoutHandle = allocFpHandle(stdout); stderrHandle = allocFpHandle(stderr); } + + std::optional findCaseInsensitiveFile(const std::filesystem::path &directory, + const std::string &filename) { + std::error_code ec; + if (directory.empty()) { + return std::nullopt; + } + if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec)) { + return std::nullopt; + } + std::string needle = filename; + std::transform(needle.begin(), needle.end(), needle.begin(), [](unsigned char ch) { return std::tolower(ch); }); + for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) { + if (ec) { + break; + } + std::string candidate = entry.path().filename().string(); + std::transform(candidate.begin(), candidate.end(), candidate.begin(), [](unsigned char ch) { return std::tolower(ch); }); + if (candidate == needle) { + return canonicalPath(entry.path()); + } + } + auto direct = directory / filename; + if (std::filesystem::exists(direct, ec)) { + return canonicalPath(direct); + } + return std::nullopt; + } + + std::filesystem::path canonicalPath(const std::filesystem::path &path) { + std::error_code ec; + auto canonical = std::filesystem::weakly_canonical(path, ec); + if (!ec) { + return canonical; + } + return std::filesystem::absolute(path); + } + + std::string hostPathListToWindows(const std::string &value) { + if (value.empty()) { + return value; + } + char delimiter = value.find(';') != std::string::npos ? ';' : ':'; + auto entries = splitList(value, delimiter); + std::string result; + for (size_t i = 0; i < entries.size(); ++i) { + if (i != 0) { + result.push_back(';'); + } + if (!entries[i].empty()) { + result += toWindowsPathEntry(entries[i]); + } + } + return result; + } + + std::string windowsPathListToHost(const std::string &value) { + if (value.empty()) { + return value; + } + auto entries = splitList(value, ';'); + std::string result; + for (size_t i = 0; i < entries.size(); ++i) { + if (i != 0) { + result.push_back(':'); + } + if (!entries[i].empty()) { + result += toHostPathEntry(entries[i]); + } + } + return result; + } } diff --git a/files.h b/files.h index 74e6d30..329a4d4 100644 --- a/files.h +++ b/files.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace files { @@ -11,6 +12,10 @@ namespace files { void *getStdHandle(uint32_t nStdHandle); unsigned int setStdHandle(uint32_t nStdHandle, void *hHandle); void init(); + std::optional findCaseInsensitiveFile(const std::filesystem::path &directory, const std::string &filename); + std::filesystem::path canonicalPath(const std::filesystem::path &path); + std::string hostPathListToWindows(const std::string &value); + std::string windowsPathListToHost(const std::string &value); } inline bool endsWith(const std::string &str, const std::string &suffix) { diff --git a/module_registry.cpp b/module_registry.cpp index a040328..481f9cf 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -110,6 +110,8 @@ struct ModuleRegistry { std::optional dllDirectory; bool initialized = false; std::unordered_map onExitTables; + std::unordered_map> builtinAliasLists; + std::unordered_map builtinAliasMap; }; ModuleRegistry ®istry() { @@ -180,32 +182,6 @@ std::string normalizedBaseKey(const ParsedModuleName &parsed) { return normalizeAlias(base); } -std::optional findCaseInsensitiveFile(const std::filesystem::path &directory, - const std::string &filename) { - std::error_code ec; - if (directory.empty()) { - return std::nullopt; - } - if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec)) { - return std::nullopt; - } - const std::string lower = toLowerCopy(filename); - for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) { - if (ec) { - break; - } - const std::string candidate = toLowerCopy(entry.path().filename().string()); - if (candidate == lower) { - return std::filesystem::canonical(entry.path(), ec); - } - } - auto direct = directory / filename; - if (std::filesystem::exists(direct, ec)) { - return std::filesystem::canonical(direct, ec); - } - return std::nullopt; -} - std::optional combineAndFind(const std::filesystem::path &directory, const std::string &filename) { if (filename.empty()) { @@ -214,16 +190,7 @@ std::optional combineAndFind(const std::filesystem::path if (directory.empty()) { return std::nullopt; } - return findCaseInsensitiveFile(directory, filename); -} - -std::filesystem::path canonicalPath(const std::filesystem::path &path) { - std::error_code ec; - auto canonical = std::filesystem::weakly_canonical(path, ec); - if (!ec) { - return canonical; - } - return std::filesystem::absolute(path); + return files::findCaseInsensitiveFile(directory, filename); } std::vector collectSearchDirectories(bool alteredSearchPath) { @@ -305,11 +272,11 @@ std::optional resolveModuleOnDisk(const std::string &requ for (const auto &candidate : names) { auto combined = parsed.directory + "\\" + candidate; auto posixPath = files::pathFromWindows(combined.c_str()); - if (!posixPath.empty()) { - auto resolved = findCaseInsensitiveFile(std::filesystem::path(posixPath).parent_path(), + if (!posixPath.empty()) { + auto resolved = files::findCaseInsensitiveFile(std::filesystem::path(posixPath).parent_path(), std::filesystem::path(posixPath).filename().string()); if (resolved) { - return canonicalPath(*resolved); + return files::canonicalPath(*resolved); } } } @@ -321,7 +288,7 @@ std::optional resolveModuleOnDisk(const std::string &requ for (const auto &candidate : names) { auto resolved = combineAndFind(dir, candidate); if (resolved) { - return canonicalPath(*resolved); + return files::canonicalPath(*resolved); } } } @@ -330,7 +297,7 @@ std::optional resolveModuleOnDisk(const std::string &requ } std::string storageKeyForPath(const std::filesystem::path &path) { - return normalizeAlias(files::pathToWindows(canonicalPath(path))); + return normalizeAlias(files::pathToWindows(files::canonicalPath(path))); } std::string storageKeyForBuiltin(const std::string &normalizedName) { return normalizedName; } @@ -349,7 +316,13 @@ void registerAlias(const std::string &alias, wibo::ModuleInfo *info) { return; } auto ® = registry(); - if (reg.modulesByAlias.find(alias) == reg.modulesByAlias.end()) { + auto it = reg.modulesByAlias.find(alias); + if (it == reg.modulesByAlias.end()) { + reg.modulesByAlias[alias] = info; + return; + } + // Prefer externally loaded modules over built-ins when both are present. + if (it->second && it->second->module != nullptr && info->module == nullptr) { reg.modulesByAlias[alias] = info; } } @@ -369,10 +342,20 @@ void registerBuiltinModule(const wibo::Module *module) { auto ® = registry(); reg.modulesByKey[storageKey] = std::move(entry); + reg.builtinAliasLists[module] = {}; + auto &aliasList = reg.builtinAliasLists[module]; for (size_t i = 0; module->names[i]; ++i) { - registerAlias(normalizeAlias(module->names[i]), raw); + std::string alias = normalizeAlias(module->names[i]); + aliasList.push_back(alias); + registerAlias(alias, raw); + reg.builtinAliasMap[alias] = raw; ParsedModuleName parsed = parseModuleName(module->names[i]); - registerAlias(normalizedBaseKey(parsed), raw); + std::string baseAlias = normalizedBaseKey(parsed); + if (baseAlias != alias) { + aliasList.push_back(baseAlias); + registerAlias(baseAlias, raw); + reg.builtinAliasMap[baseAlias] = raw; + } } } @@ -543,7 +526,7 @@ void shutdownModuleRegistry() { ModuleInfo *moduleInfoFromHandle(HMODULE module) { return static_cast(module); } void setDllDirectoryOverride(const std::filesystem::path &path) { - auto canonical = canonicalPath(path); + auto canonical = files::canonicalPath(path); std::lock_guard lock(registry().mutex); registry().dllDirectory = canonical; } @@ -645,76 +628,119 @@ HMODULE loadModule(const char *dllName) { ensureInitialized(); ParsedModuleName parsed = parseModuleName(requested); + + auto ® = registry(); + DWORD diskError = ERROR_SUCCESS; + + auto tryLoadExternal = [&](const std::filesystem::path &path) -> ModuleInfo * { + std::string key = storageKeyForPath(path); + auto existingIt = reg.modulesByKey.find(key); + if (existingIt != reg.modulesByKey.end()) { + ModuleInfo *info = existingIt->second.get(); + if (info->refCount != UINT_MAX) { + info->refCount++; + } + registerExternalModuleAliases(requested, files::canonicalPath(path), info); + return info; + } + + FILE *file = fopen(path.c_str(), "rb"); + if (!file) { + perror("loadModule"); + diskError = ERROR_MOD_NOT_FOUND; + return nullptr; + } + + auto executable = std::make_unique(); + if (!executable->loadPE(file, true)) { + DEBUG_LOG(" loadPE failed for %s\n", path.c_str()); + fclose(file); + diskError = ERROR_BAD_EXE_FORMAT; + return nullptr; + } + fclose(file); + + ModulePtr info = std::make_unique(); + info->module = nullptr; + info->originalName = requested; + info->normalizedName = normalizedBaseKey(parsed); + info->resolvedPath = files::canonicalPath(path); + info->executable = std::move(executable); + info->entryPoint = info->executable->entryPoint; + info->imageBase = info->executable->imageBuffer; + info->imageSize = info->executable->imageSize; + info->refCount = 1; + info->dataFile = false; + info->dontResolveReferences = false; + + ModuleInfo *raw = info.get(); + reg.modulesByKey[key] = std::move(info); + registerExternalModuleAliases(requested, raw->resolvedPath, raw); + ensureExportsInitialized(*raw); + callDllMain(*raw, DLL_PROCESS_ATTACH); + return raw; + }; + + auto resolveAndLoadExternal = [&]() -> ModuleInfo * { + auto resolvedPath = resolveModuleOnDisk(requested, false); + if (!resolvedPath) { + DEBUG_LOG(" module not found on disk\n"); + return nullptr; + } + return tryLoadExternal(*resolvedPath); + }; + std::string alias = normalizedBaseKey(parsed); ModuleInfo *existing = findByAlias(alias); if (!existing) { existing = findByAlias(normalizeAlias(requested)); } if (existing) { - DEBUG_LOG(" found existing module alias %s\n", alias.c_str()); - if (existing->refCount != UINT_MAX) { - existing->refCount++; + DEBUG_LOG(" found existing module alias %s (builtin=%d)\n", alias.c_str(), existing->module != nullptr); + if (existing->module == nullptr) { + if (existing->refCount != UINT_MAX) { + existing->refCount++; + } + DEBUG_LOG(" returning existing external module %s\n", existing->originalName.c_str()); + lastError = ERROR_SUCCESS; + return existing; + } + if (ModuleInfo *external = resolveAndLoadExternal()) { + DEBUG_LOG(" replaced builtin module %s with external copy\n", requested.c_str()); + lastError = ERROR_SUCCESS; + return external; } lastError = ERROR_SUCCESS; + DEBUG_LOG(" returning builtin module %s\n", existing->originalName.c_str()); return existing; } - auto resolvedPath = resolveModuleOnDisk(requested, false); - if (!resolvedPath) { - DEBUG_LOG(" module not found on disk\n"); - lastError = ERROR_MOD_NOT_FOUND; - return nullptr; - } - - std::string key = storageKeyForPath(*resolvedPath); - auto ® = registry(); - auto it = reg.modulesByKey.find(key); - if (it != reg.modulesByKey.end()) { - ModuleInfo *info = it->second.get(); - info->refCount++; - registerExternalModuleAliases(requested, *resolvedPath, info); + if (ModuleInfo *external = resolveAndLoadExternal()) { + DEBUG_LOG(" loaded external module %s\n", requested.c_str()); lastError = ERROR_SUCCESS; - return info; + return external; } - FILE *file = fopen(resolvedPath->c_str(), "rb"); - if (!file) { - perror("loadModule"); - lastError = ERROR_MOD_NOT_FOUND; - return nullptr; + auto fallbackAlias = normalizedBaseKey(parsed); + ModuleInfo *builtin = nullptr; + auto builtinIt = reg.builtinAliasMap.find(fallbackAlias); + if (builtinIt != reg.builtinAliasMap.end()) { + builtin = builtinIt->second; } - - auto executable = std::make_unique(); - if (!executable->loadPE(file, true)) { - DEBUG_LOG(" loadPE failed for %s\n", resolvedPath->c_str()); - fclose(file); - lastError = ERROR_BAD_EXE_FORMAT; - return nullptr; + if (!builtin) { + builtinIt = reg.builtinAliasMap.find(normalizeAlias(requested)); + if (builtinIt != reg.builtinAliasMap.end()) { + builtin = builtinIt->second; + } } - fclose(file); - - ModulePtr info = std::make_unique(); - info->module = nullptr; - info->originalName = requested; - info->normalizedName = alias; - info->resolvedPath = *resolvedPath; - info->executable = std::move(executable); - info->entryPoint = info->executable->entryPoint; - info->imageBase = info->executable->imageBuffer; - info->imageSize = info->executable->imageSize; - info->refCount = 1; - info->dataFile = false; - info->dontResolveReferences = false; - - ModuleInfo *raw = info.get(); - reg.modulesByKey[key] = std::move(info); - registerExternalModuleAliases(requested, *resolvedPath, raw); - ensureExportsInitialized(*raw); - - callDllMain(*raw, DLL_PROCESS_ATTACH); - lastError = ERROR_SUCCESS; - - return raw; + if (builtin && builtin->module != nullptr) { + DEBUG_LOG(" falling back to builtin module %s\n", builtin->originalName.c_str()); + lastError = (diskError != ERROR_SUCCESS) ? diskError : ERROR_SUCCESS; + return builtin; + } + + lastError = (diskError != ERROR_SUCCESS) ? diskError : ERROR_MOD_NOT_FOUND; + return nullptr; } void freeModule(HMODULE module) { diff --git a/processes.cpp b/processes.cpp index 902acaa..838534e 100644 --- a/processes.cpp +++ b/processes.cpp @@ -1,7 +1,21 @@ #include "processes.h" +#include "common.h" +#include "files.h" #include "handles.h" +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" char **environ; namespace processes { void *allocProcessHandle(pid_t pid) { @@ -21,4 +35,233 @@ namespace processes { assert(0); } } -} \ No newline at end of file + + static bool hasDirectoryComponent(const std::string &command) { + return command.find('/') != std::string::npos || command.find('\\') != std::string::npos || + command.find(':') != std::string::npos; + } + + static bool hasExtension(const std::string &command) { + auto pos = command.find_last_of('.'); + auto slash = command.find_last_of("/\\"); + return pos != std::string::npos && (slash == std::string::npos || pos > slash + 1); + } + + static std::vector pathextValues() { + const char *envValue = std::getenv("PATHEXT"); + std::string raw = envValue ? envValue : ".COM;.EXE;.BAT;.CMD"; + std::vector exts; + size_t start = 0; + while (start <= raw.size()) { + size_t end = raw.find(';', start); + if (end == std::string::npos) { + end = raw.size(); + } + std::string part = raw.substr(start, end - start); + if (!part.empty()) { + if (part[0] != '.') { + part.insert(part.begin(), '.'); + } + exts.push_back(part); + } + if (end == raw.size()) { + break; + } + start = end + 1; + } + if (exts.empty()) { + exts = {".COM", ".EXE", ".BAT", ".CMD"}; + } + return exts; + } + + static std::vector parseHostPath(const std::string &value) { + std::vector paths; + const char *delims = strchr(value.c_str(), ';') ? ";" : ":"; + size_t start = 0; + while (start <= value.size()) { + size_t end = value.find_first_of(delims, start); + if (end == std::string::npos) { + end = value.size(); + } + std::string entry = value.substr(start, end - start); + if (!entry.empty()) { + bool looksWindows = entry.find('\\') != std::string::npos || + (entry.size() >= 2 && entry[1] == ':' && entry[0] != '/'); + std::filesystem::path candidate; + if (looksWindows) { + auto converted = files::pathFromWindows(entry.c_str()); + if (!converted.empty()) { + candidate = converted; + } + } + if (candidate.empty()) { + candidate = std::filesystem::path(entry); + } + paths.push_back(std::move(candidate)); + } + if (end == value.size()) { + break; + } + start = end + 1; + } + return paths; + } + + static std::vector buildSearchDirectories() { + std::vector dirs; + dirs.push_back(std::filesystem::current_path()); + if (const char *envPath = std::getenv("PATH")) { + auto parsed = parseHostPath(envPath); + dirs.insert(dirs.end(), parsed.begin(), parsed.end()); + } + return dirs; + } + + std::optional resolveExecutable(const std::string &command, bool searchPath) { + if (command.empty()) { + return std::nullopt; + } + + std::vector candidates; + candidates.push_back(command); + if (!hasExtension(command)) { + for (const auto &ext : pathextValues()) { + candidates.push_back(command + ext); + } + } + + auto tryResolveDirect = [&](const std::string &name) -> std::optional { + auto host = files::pathFromWindows(name.c_str()); + if (host.empty()) { + std::string normalized = name; + std::replace(normalized.begin(), normalized.end(), '\\', '/'); + host = std::filesystem::path(normalized); + } + std::filesystem::path parent = host.parent_path().empty() ? std::filesystem::current_path() : host.parent_path(); + std::string filename = host.filename().string(); + auto resolved = files::findCaseInsensitiveFile(parent, filename); + if (resolved) { + return files::canonicalPath(*resolved); + } + std::error_code ec; + if (!filename.empty() && std::filesystem::exists(host, ec)) { + return files::canonicalPath(host); + } + return std::nullopt; + }; + + if (hasDirectoryComponent(command)) { + for (const auto &name : candidates) { + auto resolved = tryResolveDirect(name); + if (resolved) { + return resolved; + } + } + return std::nullopt; + } + + if (searchPath) { + auto dirs = buildSearchDirectories(); + for (const auto &dir : dirs) { + for (const auto &name : candidates) { + auto resolved = files::findCaseInsensitiveFile(dir, name); + if (resolved) { + return files::canonicalPath(*resolved); + } + } + } + } + + return std::nullopt; + } + + int spawnViaWibo(const std::filesystem::path &hostExecutable, const std::vector &arguments, pid_t *pidOut) { + if (hostExecutable.empty()) { + return ENOENT; + } + + std::vector storage; + storage.reserve(arguments.size() + 1); + storage.push_back(hostExecutable.string()); + for (const auto &arg : arguments) { + storage.push_back(arg); + } + + std::vector nativeArgs; + nativeArgs.reserve(storage.size() + 2); + nativeArgs.push_back(wibo::executableName); + for (auto &entry : storage) { + nativeArgs.push_back(entry.data()); + } + nativeArgs.push_back(nullptr); + + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + + std::string indent = std::to_string(wibo::debugIndent + 1); + setenv("WIBO_DEBUG_INDENT", indent.c_str(), 1); + + pid_t pid = -1; + int spawnResult = posix_spawn(&pid, wibo::executableName, &actions, nullptr, nativeArgs.data(), environ); + posix_spawn_file_actions_destroy(&actions); + if (spawnResult != 0) { + return spawnResult; + } + if (pidOut) { + *pidOut = pid; + } + return 0; + } + + std::vector splitCommandLine(const char *commandLine) { + std::vector result; + if (!commandLine) { + return result; + } + std::string input(commandLine); + size_t i = 0; + size_t len = input.size(); + while (i < len) { + while (i < len && (input[i] == ' ' || input[i] == '\t')) { + ++i; + } + if (i >= len) { + break; + } + std::string arg; + bool inQuotes = false; + int backslashes = 0; + for (; i < len; ++i) { + char c = input[i]; + if (c == '\\') { + ++backslashes; + continue; + } + if (c == '"') { + if ((backslashes % 2) == 0) { + arg.append(backslashes / 2, '\\'); + inQuotes = !inQuotes; + } else { + arg.append(backslashes / 2, '\\'); + arg.push_back('"'); + } + backslashes = 0; + continue; + } + arg.append(backslashes, '\\'); + backslashes = 0; + if (!inQuotes && (c == ' ' || c == '\t')) { + break; + } + arg.push_back(c); + } + arg.append(backslashes, '\\'); + result.push_back(std::move(arg)); + while (i < len && (input[i] == ' ' || input[i] == '\t')) { + ++i; + } + } + return result; + } +} diff --git a/processes.h b/processes.h index 0e6ecca..d4519e8 100644 --- a/processes.h +++ b/processes.h @@ -1,7 +1,11 @@ #pragma once #include +#include +#include #include +#include +#include namespace processes { struct Process { @@ -11,4 +15,8 @@ namespace processes { void *allocProcessHandle(pid_t pid); Process* processFromHandle(void* hHandle, bool pop); + + std::optional resolveExecutable(const std::string &command, bool searchPath); + int spawnViaWibo(const std::filesystem::path &hostExecutable, const std::vector &arguments, pid_t *pidOut); + std::vector splitCommandLine(const char *commandLine); } From f23224bbcc45768efcbf2777bd2b953c0d821322 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 17:38:24 -0600 Subject: [PATCH 17/28] cl.exe works! but I didn't review most of this code --- CMakeLists.txt | 1 + common.h | 2 + dll/advapi32.cpp | 545 ++++++++++++++++++++++++++++++++ dll/kernel32.cpp | 755 +++++++++++++++++++++++++++++++++++++++----- dll/psapi.cpp | 67 ++++ dll/rpcrt4.cpp | 295 +++++++++++++++++ handles.h | 6 +- loader.cpp | 54 +++- module_registry.cpp | 3 +- 9 files changed, 1651 insertions(+), 77 deletions(-) create mode 100644 dll/psapi.cpp create mode 100644 dll/rpcrt4.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a9dd13..f377820 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(wibo dll/mscoree.cpp dll/msvcrt.cpp dll/ntdll.cpp + dll/rpcrt4.cpp dll/ole32.cpp dll/user32.cpp dll/vcruntime.cpp diff --git a/common.h b/common.h index d1eec02..fc7bffc 100644 --- a/common.h +++ b/common.h @@ -64,6 +64,7 @@ typedef unsigned char BYTE; #define ERROR_INVALID_PARAMETER 87 #define ERROR_BUFFER_OVERFLOW 111 #define ERROR_INSUFFICIENT_BUFFER 122 +#define ERROR_NONE_MAPPED 1332 #define ERROR_RESOURCE_DATA_NOT_FOUND 1812 #define ERROR_RESOURCE_TYPE_NOT_FOUND 1813 #define ERROR_RESOURCE_NAME_NOT_FOUND 1814 @@ -72,6 +73,7 @@ typedef unsigned char BYTE; #define ERROR_NEGATIVE_SEEK 131 #define ERROR_BAD_EXE_FORMAT 193 #define ERROR_ALREADY_EXISTS 183 +#define ERROR_NOT_OWNER 288 #define INVALID_SET_FILE_POINTER ((DWORD)-1) #define INVALID_HANDLE_VALUE ((HANDLE)-1) diff --git a/dll/advapi32.cpp b/dll/advapi32.cpp index b3052c2..d74bbee 100644 --- a/dll/advapi32.cpp +++ b/dll/advapi32.cpp @@ -1,5 +1,287 @@ #include "common.h" +#include "handles.h" #include +#include +#include + +namespace { + using ALG_ID = unsigned int; + + constexpr ALG_ID CALG_MD5 = 0x00008003; + constexpr ALG_ID CALG_SHA1 = 0x00008004; + + constexpr DWORD HP_ALGID = 0x00000001; + constexpr DWORD HP_HASHVAL = 0x00000002; + constexpr DWORD HP_HASHSIZE = 0x00000004; + + struct HashObject { + ALG_ID algid = 0; + std::vector data; + std::vector digest; + bool digestComputed = false; + }; + + struct TokenObject { + HANDLE processHandle = nullptr; + DWORD desiredAccess = 0; + }; + + struct SidIdentifierAuthority { + uint8_t Value[6] = {0}; + }; + + struct Sid { + uint8_t Revision = 1; + uint8_t SubAuthorityCount = 0; + SidIdentifierAuthority IdentifierAuthority = {}; + uint32_t SubAuthority[1] = {0}; + }; + + struct SidAndAttributes { + Sid *SidPtr = nullptr; + DWORD Attributes = 0; + }; + + struct TokenUserData { + SidAndAttributes User; + }; + + enum SID_NAME_USE { + SidTypeUser = 1, + SidTypeGroup, + SidTypeDomain, + SidTypeAlias, + SidTypeWellKnownGroup, + SidTypeDeletedAccount, + SidTypeInvalid, + SidTypeUnknown, + SidTypeComputer, + SidTypeLabel + }; + + bool isLocalSystemSid(const Sid *sid) { + if (!sid) { + return false; + } + static const uint8_t ntAuthority[6] = {0, 0, 0, 0, 0, 5}; + if (sid->Revision != 1 || sid->SubAuthorityCount != 1) { + return false; + } + for (size_t i = 0; i < 6; ++i) { + if (sid->IdentifierAuthority.Value[i] != ntAuthority[i]) { + return false; + } + } + return sid->SubAuthority[0] == 18; // SECURITY_LOCAL_SYSTEM_RID + } + + struct Luid { + uint32_t LowPart = 0; + int32_t HighPart = 0; + }; + + struct TokenStatisticsData { + Luid tokenId; + Luid authenticationId; + int64_t expirationTime = 0; + uint32_t tokenType = 0; + uint32_t impersonationLevel = 0; + uint32_t dynamicCharged = 0; + uint32_t dynamicAvailable = 0; + uint32_t groupCount = 0; + uint32_t privilegeCount = 0; + Luid modifiedId; + }; + + static inline uint32_t leftRotate(uint32_t value, uint32_t bits) { + return (value << bits) | (value >> (32 - bits)); + } + + static std::vector computeMD5(const std::vector &input) { + static const uint32_t s[64] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + }; + static const uint32_t K[64] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + std::vector data = input; + uint64_t bitLen = static_cast(data.size()) * 8ULL; + data.push_back(0x80); + while ((data.size() % 64) != 56) { + data.push_back(0); + } + for (int i = 0; i < 8; ++i) { + data.push_back(static_cast((bitLen >> (8 * i)) & 0xFF)); + } + + uint32_t A = 0x67452301; + uint32_t B = 0xEFCDAB89; + uint32_t C = 0x98BADCFE; + uint32_t D = 0x10325476; + + for (size_t offset = 0; offset < data.size(); offset += 64) { + uint32_t M[16]; + for (int i = 0; i < 16; ++i) { + M[i] = static_cast(data[offset + i * 4]) | + (static_cast(data[offset + i * 4 + 1]) << 8) | + (static_cast(data[offset + i * 4 + 2]) << 16) | + (static_cast(data[offset + i * 4 + 3]) << 24); + } + uint32_t a = A; + uint32_t b = B; + uint32_t c = C; + uint32_t d = D; + for (int i = 0; i < 64; ++i) { + uint32_t F; + int g; + if (i < 16) { + F = (b & c) | ((~b) & d); + g = i; + } else if (i < 32) { + F = (d & b) | ((~d) & c); + g = (5 * i + 1) % 16; + } else if (i < 48) { + F = b ^ c ^ d; + g = (3 * i + 5) % 16; + } else { + F = c ^ (b | (~d)); + g = (7 * i) % 16; + } + uint32_t temp = d; + d = c; + c = b; + uint32_t rotateVal = a + F + K[i] + M[g]; + b = b + leftRotate(rotateVal, s[i]); + a = temp; + } + A += a; + B += b; + C += c; + D += d; + } + + std::vector digest(16); + uint32_t output[4] = {A, B, C, D}; + for (int i = 0; i < 4; ++i) { + digest[i * 4] = static_cast(output[i] & 0xFF); + digest[i * 4 + 1] = static_cast((output[i] >> 8) & 0xFF); + digest[i * 4 + 2] = static_cast((output[i] >> 16) & 0xFF); + digest[i * 4 + 3] = static_cast((output[i] >> 24) & 0xFF); + } + return digest; + } + + static std::vector computeSHA1(const std::vector &input) { + std::vector data = input; + uint64_t bitLen = static_cast(data.size()) * 8ULL; + data.push_back(0x80); + while ((data.size() % 64) != 56) { + data.push_back(0); + } + for (int i = 7; i >= 0; --i) { + data.push_back(static_cast((bitLen >> (8 * i)) & 0xFF)); + } + + uint32_t h0 = 0x67452301; + uint32_t h1 = 0xEFCDAB89; + uint32_t h2 = 0x98BADCFE; + uint32_t h3 = 0x10325476; + uint32_t h4 = 0xC3D2E1F0; + + for (size_t offset = 0; offset < data.size(); offset += 64) { + uint32_t w[80]; + for (int i = 0; i < 16; ++i) { + w[i] = (static_cast(data[offset + i * 4]) << 24) | + (static_cast(data[offset + i * 4 + 1]) << 16) | + (static_cast(data[offset + i * 4 + 2]) << 8) | + static_cast(data[offset + i * 4 + 3]); + } + for (int i = 16; i < 80; ++i) { + w[i] = leftRotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + } + uint32_t a = h0; + uint32_t b = h1; + uint32_t c = h2; + uint32_t d = h3; + uint32_t e = h4; + for (int i = 0; i < 80; ++i) { + uint32_t f; + uint32_t k; + if (i < 20) { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } else if (i < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } else if (i < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + uint32_t temp = leftRotate(a, 5) + f + e + k + w[i]; + e = d; + d = c; + c = leftRotate(b, 30); + b = a; + a = temp; + } + h0 += a; + h1 += b; + h2 += c; + h3 += d; + h4 += e; + } + + std::vector digest(20); + uint32_t output[5] = {h0, h1, h2, h3, h4}; + for (int i = 0; i < 5; ++i) { + digest[i * 4] = static_cast((output[i] >> 24) & 0xFF); + digest[i * 4 + 1] = static_cast((output[i] >> 16) & 0xFF); + digest[i * 4 + 2] = static_cast((output[i] >> 8) & 0xFF); + digest[i * 4 + 3] = static_cast(output[i] & 0xFF); + } + return digest; + } + + static bool computeDigest(HashObject &hash) { + if (hash.digestComputed) { + return true; + } + switch (hash.algid) { + case CALG_MD5: + hash.digest = computeMD5(hash.data); + hash.digestComputed = true; + return true; + case CALG_SHA1: + hash.digest = computeSHA1(hash.data); + hash.digestComputed = true; + return true; + default: + return false; + } + } +} namespace advapi32 { unsigned int WIN_FUNC RegOpenKeyExA(void *hKey, const char *lpSubKey, unsigned int ulOptions, void *samDesired, void **phkResult) { @@ -36,6 +318,262 @@ namespace advapi32 { return TRUE; } + + BOOL WIN_FUNC CryptCreateHash(void* hProv, unsigned int Algid, void* hKey, unsigned int dwFlags, void** phHash) { + DEBUG_LOG("CryptCreateHash(Algid=0x%x)\n", Algid); + (void)hProv; + if (!phHash) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + if (dwFlags != 0) { + wibo::lastError = ERROR_NOT_SUPPORTED; + return FALSE; + } + if (hKey != nullptr) { + wibo::lastError = ERROR_NOT_SUPPORTED; + return FALSE; + } + if (Algid != CALG_MD5 && Algid != CALG_SHA1) { + wibo::lastError = ERROR_NOT_SUPPORTED; + return FALSE; + } + auto *hash = new HashObject; + hash->algid = Algid; + hash->digestComputed = false; + hash->data.clear(); + hash->digest.clear(); + *phHash = hash; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC CryptHashData(void* hHash, const unsigned char* pbData, unsigned int dwDataLen, unsigned int dwFlags) { + DEBUG_LOG("CryptHashData(%p, %u bytes)\n", hHash, dwDataLen); + if (!hHash || (dwDataLen && !pbData) || dwFlags != 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + auto *hash = reinterpret_cast(hHash); + if (pbData && dwDataLen) { + hash->data.insert(hash->data.end(), pbData, pbData + dwDataLen); + hash->digestComputed = false; + hash->digest.clear(); + } + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC CryptGetHashParam(void* hHash, unsigned int dwParam, unsigned char* pbData, unsigned int* pdwDataLen, unsigned int dwFlags) { + DEBUG_LOG("CryptGetHashParam(%p, param=0x%x)\n", hHash, dwParam); + if (!hHash || !pdwDataLen || dwFlags != 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + auto *hash = reinterpret_cast(hHash); + switch (dwParam) { + case HP_ALGID: { + unsigned int required = sizeof(ALG_ID); + if (!pbData) { + *pdwDataLen = required; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + if (*pdwDataLen < required) { + *pdwDataLen = required; + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + memcpy(pbData, &hash->algid, required); + *pdwDataLen = required; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + case HP_HASHSIZE: { + unsigned int size = 0; + switch (hash->algid) { + case CALG_MD5: + size = 16; + break; + case CALG_SHA1: + size = 20; + break; + default: + wibo::lastError = ERROR_NOT_SUPPORTED; + return FALSE; + } + if (!pbData) { + *pdwDataLen = sizeof(unsigned int); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + if (*pdwDataLen < sizeof(unsigned int)) { + *pdwDataLen = sizeof(unsigned int); + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + memcpy(pbData, &size, sizeof(unsigned int)); + *pdwDataLen = sizeof(unsigned int); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + case HP_HASHVAL: { + if (!computeDigest(*hash)) { + wibo::lastError = ERROR_NOT_SUPPORTED; + return FALSE; + } + unsigned int required = hash->digest.size(); + if (!pbData) { + *pdwDataLen = required; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + if (*pdwDataLen < required) { + *pdwDataLen = required; + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + memcpy(pbData, hash->digest.data(), required); + *pdwDataLen = required; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + default: + wibo::lastError = ERROR_NOT_SUPPORTED; + return FALSE; + } + } + + BOOL WIN_FUNC CryptDestroyHash(void* hHash) { + DEBUG_LOG("CryptDestroyHash(%p)\n", hHash); + if (!hHash) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + delete reinterpret_cast(hHash); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC OpenProcessToken(HANDLE ProcessHandle, DWORD DesiredAccess, HANDLE *TokenHandle) { + DEBUG_LOG("OpenProcessToken(process=%p, access=0x%x)\n", ProcessHandle, DesiredAccess); + if (!TokenHandle) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + auto *token = new TokenObject; + token->processHandle = ProcessHandle; + token->desiredAccess = DesiredAccess; + handles::Data data; + data.type = handles::TYPE_TOKEN; + data.ptr = token; + data.size = 0; + *TokenHandle = handles::allocDataHandle(data); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + void releaseToken(void *tokenPtr) { + delete reinterpret_cast(tokenPtr); + } + + BOOL WIN_FUNC GetTokenInformation(HANDLE TokenHandle, unsigned int TokenInformationClass, void *TokenInformation, unsigned int TokenInformationLength, unsigned int *ReturnLength) { + DEBUG_LOG("GetTokenInformation(%p, class=%u, len=%u)\n", TokenHandle, TokenInformationClass, TokenInformationLength); + if (!ReturnLength) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + auto data = handles::dataFromHandle(TokenHandle, false); + if (data.type != handles::TYPE_TOKEN) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + constexpr unsigned int TokenUserClass = 1; // TokenUser + constexpr unsigned int TokenStatisticsClass = 10; // TokenStatistics + constexpr unsigned int TokenElevationClass = 20; // TokenElevation + if (TokenInformationClass == TokenUserClass) { + constexpr size_t sidSize = sizeof(Sid); + constexpr size_t tokenUserSize = sizeof(TokenUserData); + const unsigned int required = static_cast(tokenUserSize + sidSize); + *ReturnLength = required; + if (!TokenInformation || TokenInformationLength < required) { + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + auto *tokenUser = reinterpret_cast(TokenInformation); + auto *sid = reinterpret_cast(reinterpret_cast(TokenInformation) + tokenUserSize); + SidIdentifierAuthority ntAuthority = {{0, 0, 0, 0, 0, 5}}; + sid->Revision = 1; + sid->SubAuthorityCount = 1; + sid->IdentifierAuthority = ntAuthority; + sid->SubAuthority[0] = 18; // SECURITY_LOCAL_SYSTEM_RID + tokenUser->User.SidPtr = sid; + tokenUser->User.Attributes = 0; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + if (TokenInformationClass == TokenStatisticsClass) { + const unsigned int required = sizeof(TokenStatisticsData); + *ReturnLength = required; + if (!TokenInformation || TokenInformationLength < required) { + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + auto *stats = reinterpret_cast(TokenInformation); + memset(stats, 0, required); + stats->tokenType = 1; // TokenPrimary + stats->impersonationLevel = 0; // SecurityAnonymous + stats->tokenId.LowPart = 1; + stats->authenticationId.LowPart = 1; + stats->modifiedId.LowPart = 1; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + if (TokenInformationClass == TokenElevationClass) { + const unsigned int required = sizeof(DWORD); + *ReturnLength = required; + if (!TokenInformation || TokenInformationLength < required) { + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + *reinterpret_cast(TokenInformation) = 0; // not elevated + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + wibo::lastError = ERROR_NOT_SUPPORTED; + return FALSE; + } + + BOOL WIN_FUNC LookupAccountSidW(const wchar_t *lpSystemName, const void *sidPointer, wchar_t *Name, unsigned long *cchName, wchar_t *ReferencedDomainName, unsigned long *cchReferencedDomainName, SID_NAME_USE *peUse) { + DEBUG_LOG("LookupAccountSidW(system=%ls, sid=%p)\n", lpSystemName ? lpSystemName : L"(null)", sidPointer); + (void) lpSystemName; // Only local lookup supported + if (!sidPointer || !cchName || !cchReferencedDomainName || !peUse) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + auto *sid = reinterpret_cast(sidPointer); + if (!isLocalSystemSid(sid)) { + wibo::lastError = ERROR_NONE_MAPPED; + return FALSE; + } + const wchar_t *accountName = L"SYSTEM"; + const wchar_t *domainName = L"NT AUTHORITY"; + unsigned long requiredAccount = static_cast(std::wcslen(accountName) + 1); + unsigned long requiredDomain = static_cast(std::wcslen(domainName) + 1); + if (!Name || *cchName < requiredAccount || !ReferencedDomainName || *cchReferencedDomainName < requiredDomain) { + *cchName = requiredAccount; + *cchReferencedDomainName = requiredDomain; + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + std::wmemcpy(Name, accountName, requiredAccount); + std::wmemcpy(ReferencedDomainName, domainName, requiredDomain); + *peUse = SidTypeWellKnownGroup; + *cchName = requiredAccount - 1; + *cchReferencedDomainName = requiredDomain - 1; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } } static void *resolveByName(const char *name) { @@ -43,6 +581,13 @@ static void *resolveByName(const char *name) { if (strcmp(name, "CryptReleaseContext") == 0) return (void*) advapi32::CryptReleaseContext; if (strcmp(name, "CryptAcquireContextW") == 0) return (void*) advapi32::CryptAcquireContextW; if (strcmp(name, "CryptGenRandom") == 0) return (void*) advapi32::CryptGenRandom; + if (strcmp(name, "CryptCreateHash") == 0) return (void*) advapi32::CryptCreateHash; + if (strcmp(name, "CryptHashData") == 0) return (void*) advapi32::CryptHashData; + if (strcmp(name, "CryptGetHashParam") == 0) return (void*) advapi32::CryptGetHashParam; + if (strcmp(name, "CryptDestroyHash") == 0) return (void*) advapi32::CryptDestroyHash; + if (strcmp(name, "OpenProcessToken") == 0) return (void*) advapi32::OpenProcessToken; + if (strcmp(name, "GetTokenInformation") == 0) return (void*) advapi32::GetTokenInformation; + if (strcmp(name, "LookupAccountSidW") == 0) return (void*) advapi32::LookupAccountSidW; return nullptr; } diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index ba3409d..a7434a5 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -26,8 +28,56 @@ #include #include #include +#include +#include +#include +#include + +namespace advapi32 { + void releaseToken(void *tokenPtr); +} namespace { + struct MappingObject; + struct ViewInfo { + void *mapBase = nullptr; + size_t mapLength = 0; + MappingObject *owner = nullptr; + }; + + struct MappingObject { + int fd = -1; + size_t maxSize = 0; + unsigned int protect = 0; + bool anonymous = false; + bool closed = false; + size_t refCount = 0; + }; + + void closeMappingIfPossible(MappingObject *mapping); + void tryReleaseMapping(MappingObject *mapping); + std::unordered_map g_viewInfo; + + void closeMappingIfPossible(MappingObject *mapping) { + if (!mapping) { + return; + } + if (mapping->fd != -1) { + close(mapping->fd); + mapping->fd = -1; + } + delete mapping; + } + + void tryReleaseMapping(MappingObject *mapping) { + if (!mapping) { + return; + } + if (mapping->closed && mapping->refCount == 0) { + closeMappingIfPossible(mapping); + } + } + using DWORD_PTR = uintptr_t; constexpr WORD PROCESSOR_ARCHITECTURE_INTEL = 0; @@ -107,6 +157,44 @@ namespace kernel32 { return ret; } + struct MutexObject { + pthread_mutex_t mutex; + bool ownerValid = false; + pthread_t owner = 0; + unsigned int recursionCount = 0; + std::u16string name; + int refCount = 1; + }; + + static std::mutex mutexRegistryLock; + static std::unordered_map namedMutexes; + + static std::u16string makeMutexName(LPCWSTR name) { + if (!name) { + return std::u16string(); + } + size_t len = wstrlen(reinterpret_cast(name)); + return std::u16string(reinterpret_cast(name), len); + } + + static void releaseMutexObject(MutexObject *obj) { + if (!obj) { + return; + } + std::lock_guard lock(mutexRegistryLock); + obj->refCount--; + if (obj->refCount == 0) { + if (!obj->name.empty()) { + auto it = namedMutexes.find(obj->name); + if (it != namedMutexes.end() && it->second == obj) { + namedMutexes.erase(it); + } + } + pthread_mutex_destroy(&obj->mutex); + delete obj; + } + } + static int doCompareString(const std::string &a, const std::string &b, unsigned int dwCmpFlags) { for (size_t i = 0; ; i++) { if (i == a.size()) { @@ -173,6 +261,28 @@ namespace kernel32 { wibo::lastError = dwErrCode; } + BOOL WIN_FUNC Wow64DisableWow64FsRedirection(void **OldValue) { + DEBUG_LOG("Wow64DisableWow64FsRedirection\n"); + if (OldValue) { + *OldValue = nullptr; + } + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC Wow64RevertWow64FsRedirection(void *OldValue) { + DEBUG_LOG("Wow64RevertWow64FsRedirection\n"); + (void) OldValue; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + void WIN_FUNC RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR *lpArguments) { + DEBUG_LOG("RaiseException(code=0x%x, flags=0x%x, args=%u)\n", dwExceptionCode, dwExceptionFlags, nNumberOfArguments); + (void)lpArguments; + exit(static_cast(dwExceptionCode)); + } + PVOID WIN_FUNC AddVectoredExceptionHandler(ULONG first, PVECTORED_EXCEPTION_HANDLER handler) { DEBUG_LOG("STUB: AddVectoredExceptionHandler(%u, %p)\n", first, handler); return (PVOID)handler; @@ -374,30 +484,107 @@ namespace kernel32 { return 1; } + BOOL WIN_FUNC CreateProcessW( + LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + void *lpProcessAttributes, + void *lpThreadAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + void *lpStartupInfo, + PROCESS_INFORMATION *lpProcessInformation + ) { + std::string applicationUtf8; + if (lpApplicationName) { + applicationUtf8 = wideStringToString(lpApplicationName); + } + std::string commandUtf8; + if (lpCommandLine) { + commandUtf8 = wideStringToString(lpCommandLine); + } + std::string directoryUtf8; + if (lpCurrentDirectory) { + directoryUtf8 = wideStringToString(lpCurrentDirectory); + } + DEBUG_LOG("CreateProcessW %s \"%s\" %p %p %d 0x%x %p %s %p %p\n", + applicationUtf8.empty() ? "" : applicationUtf8.c_str(), + commandUtf8.empty() ? "" : commandUtf8.c_str(), + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + directoryUtf8.empty() ? "" : directoryUtf8.c_str(), + lpStartupInfo, + lpProcessInformation + ); + std::vector commandBuffer; + if (!commandUtf8.empty()) { + commandBuffer.assign(commandUtf8.begin(), commandUtf8.end()); + commandBuffer.push_back('\0'); + } + LPSTR commandPtr = commandBuffer.empty() ? nullptr : commandBuffer.data(); + LPCSTR applicationPtr = applicationUtf8.empty() ? nullptr : applicationUtf8.c_str(); + LPCSTR directoryPtr = directoryUtf8.empty() ? nullptr : directoryUtf8.c_str(); + return CreateProcessA( + applicationPtr, + commandPtr, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + directoryPtr, + lpStartupInfo, + lpProcessInformation + ); + } + unsigned int WIN_FUNC WaitForSingleObject(void *hHandle, unsigned int dwMilliseconds) { DEBUG_LOG("WaitForSingleObject (%u)\n", dwMilliseconds); - - // TODO - wait on other objects? - - // TODO: wait for less than forever - assert(dwMilliseconds == 0xffffffff); - - processes::Process* process = processes::processFromHandle(hHandle, false); - - int status; - waitpid(process->pid, &status, 0); - - if (WIFEXITED(status)) { - process->exitCode = WEXITSTATUS(status); - } else { - // If we're here, *something* has caused our child process to exit abnormally - // Specific exit codes don't really map onto any of these situations - we just know it's bad. - // Specify a non-zero exit code to alert our parent process something's gone wrong. - DEBUG_LOG("WaitForSingleObject: Child process exited abnormally - returning exit code 1."); - process->exitCode = 1; + handles::Data data = handles::dataFromHandle(hHandle, false); + switch (data.type) { + case handles::TYPE_PROCESS: { + // TODO: wait for less than forever + assert(dwMilliseconds == 0xffffffff); + processes::Process *process = reinterpret_cast(data.ptr); + int status; + waitpid(process->pid, &status, 0); + if (WIFEXITED(status)) { + process->exitCode = WEXITSTATUS(status); + } else { + DEBUG_LOG("WaitForSingleObject: Child process exited abnormally - returning exit code 1.\n"); + process->exitCode = 1; + } + wibo::lastError = ERROR_SUCCESS; + return 0; + } + case handles::TYPE_MUTEX: { + MutexObject *obj = reinterpret_cast(data.ptr); + if (dwMilliseconds != 0xffffffff) { + DEBUG_LOG("WaitForSingleObject: timeout for mutex not supported\n"); + wibo::lastError = ERROR_NOT_SUPPORTED; + return 0xFFFFFFFF; + } + pthread_mutex_lock(&obj->mutex); + pthread_t self = pthread_self(); + if (obj->ownerValid && pthread_equal(obj->owner, self)) { + obj->recursionCount++; + } else { + obj->owner = self; + obj->ownerValid = true; + obj->recursionCount = 1; + } + wibo::lastError = ERROR_SUCCESS; + return 0; + } + default: + DEBUG_LOG("WaitForSingleObject: unsupported handle type %d\n", data.type); + wibo::lastError = ERROR_INVALID_HANDLE; + return 0xFFFFFFFF; } - - return 0; } int WIN_FUNC GetSystemDefaultLangID() { @@ -680,12 +867,18 @@ namespace kernel32 { if (!(fp == stdin || fp == stdout || fp == stderr)) { fclose(fp); } - } else if (data.type == handles::TYPE_MAPPED) { - if (data.ptr != (void *) 0x1) { - munmap(data.ptr, data.size); - } - } else if (data.type == handles::TYPE_PROCESS) { + } else if (data.type == handles::TYPE_MAPPED) { + auto *mapping = reinterpret_cast(data.ptr); + if (mapping) { + mapping->closed = true; + tryReleaseMapping(mapping); + } + } else if (data.type == handles::TYPE_PROCESS) { delete (processes::Process*) data.ptr; + } else if (data.type == handles::TYPE_TOKEN) { + advapi32::releaseToken(data.ptr); + } else if (data.type == handles::TYPE_MUTEX) { + releaseMutexObject(reinterpret_cast(data.ptr)); } return TRUE; } @@ -717,21 +910,37 @@ namespace kernel32 { } DWORD WIN_FUNC GetFullPathNameW(LPCWSTR lpFileName, DWORD nBufferLength, LPWSTR lpBuffer, LPWSTR *lpFilePart) { - const auto fileName = wideStringToString(lpFileName); - DEBUG_LOG("GetFullPathNameW(%s) ", fileName.c_str()); + std::string narrowName = wideStringToString(lpFileName); + DEBUG_LOG("GetFullPathNameW(%s) ", narrowName.c_str()); - const auto lpFileNameA = wideStringToString(lpFileName); - std::filesystem::path absPath = std::filesystem::absolute(files::pathFromWindows(lpFileNameA.c_str())); + std::filesystem::path absPath = std::filesystem::absolute(files::pathFromWindows(narrowName.c_str())); std::string absStr = files::pathToWindows(absPath); - const auto absStrW = stringToWideString(absStr.c_str()); + auto absStrW = stringToWideString(absStr.c_str()); DEBUG_LOG("-> %s\n", absStr.c_str()); - const auto len = wstrlen(absStrW.data()); - if (nBufferLength < len + 1) { + size_t len = wstrlen(absStrW.data()); + if (nBufferLength == 0 || nBufferLength <= len) { + if (lpFilePart) { + *lpFilePart = nullptr; + } return len + 1; } + wstrncpy(lpBuffer, absStrW.data(), len + 1); - assert(!lpFilePart); + if (lpFilePart) { + *lpFilePart = nullptr; + std::error_code ec; + bool pathIsDir = std::filesystem::is_directory(absPath, ec) && !ec; + if (!pathIsDir) { + uint16_t *lastSlash = wstrrchr(lpBuffer, '\\'); + if (lastSlash && *(lastSlash + 1) != 0) { + *lpFilePart = lastSlash + 1; + } else if (!lastSlash && len > 0) { + *lpFilePart = lpBuffer; + } + } + } + wibo::lastError = ERROR_SUCCESS; return len; } @@ -759,6 +968,21 @@ namespace kernel32 { } } + DWORD WIN_FUNC GetShortPathNameW(LPCWSTR lpszLongPath, LPWSTR lpszShortPath, DWORD cchBuffer) { + std::string longPath = wideStringToString(lpszLongPath); + DEBUG_LOG("GetShortPathNameW(%s)\n", longPath.c_str()); + std::filesystem::path absPath = std::filesystem::absolute(files::pathFromWindows(longPath.c_str())); + std::string absStr = files::pathToWindows(absPath); + auto absStrW = stringToWideString(absStr.c_str()); + size_t len = wstrlen(absStrW.data()); + if (cchBuffer == 0 || cchBuffer <= len) { + return len + 1; + } + wstrncpy(lpszShortPath, absStrW.data(), len + 1); + wibo::lastError = ERROR_SUCCESS; + return len; + } + using random_shorts_engine = std::independent_bits_engine; unsigned int WIN_FUNC GetTempFileNameA(LPSTR lpPathName, LPSTR lpPrefixString, unsigned int uUnique, LPSTR lpTempFileName) { @@ -826,6 +1050,29 @@ namespace kernel32 { (unsigned int)(UNIX_TIME_ZERO >> 32) }; + static FILETIME fileTimeFromDuration(uint64_t ticks100ns) { + FILETIME result; + result.dwLowDateTime = (unsigned int)(ticks100ns & 0xFFFFFFFF); + result.dwHighDateTime = (unsigned int)(ticks100ns >> 32); + return result; + } + + static FILETIME fileTimeFromTimeval(const struct timeval &value) { + uint64_t total = 0; + if (value.tv_sec > 0 || value.tv_usec > 0) { + total = (uint64_t)value.tv_sec * 10000000ULL + (uint64_t)value.tv_usec * 10ULL; + } + return fileTimeFromDuration(total); + } + + static FILETIME fileTimeFromTimespec(const struct timespec &value) { + uint64_t total = 0; + if (value.tv_sec > 0 || value.tv_nsec > 0) { + total = (uint64_t)value.tv_sec * 10000000ULL + (uint64_t)value.tv_nsec / 100ULL; + } + return fileTimeFromDuration(total); + } + template struct WIN32_FIND_DATA { uint32_t dwFileAttributes; @@ -1227,62 +1474,219 @@ namespace kernel32 { unsigned int dwMaximumSizeHigh, unsigned int dwMaximumSizeLow, const char *lpName) { - DEBUG_LOG("CreateFileMappingA(%p, %p, %u, %u, %u, %s)\n", hFile, lpFileMappingAttributes, flProtect, dwMaximumSizeHigh, dwMaximumSizeLow, lpName); + DEBUG_LOG("CreateFileMappingA(%p, %p, %u, %u, %u, %s)\n", hFile, lpFileMappingAttributes, flProtect, dwMaximumSizeHigh, dwMaximumSizeLow, lpName ? lpName : "(null)"); + (void) lpFileMappingAttributes; + (void) lpName; - int64_t size = (int64_t) dwMaximumSizeHigh << 32 | dwMaximumSizeLow; + auto mapping = new MappingObject(); + mapping->protect = flProtect; - void *mmapped; + uint64_t size = ((uint64_t) dwMaximumSizeHigh << 32) | dwMaximumSizeLow; + if (flProtect != 0x02 /* PAGE_READONLY */ && flProtect != 0x04 /* PAGE_READWRITE */ && flProtect != 0x08 /* PAGE_WRITECOPY */) { + DEBUG_LOG("CreateFileMappingA: unsupported protection 0x%x\n", flProtect); + wibo::lastError = ERROR_INVALID_PARAMETER; + closeMappingIfPossible(mapping); + return nullptr; + } - if (hFile == (void*) -1) { // INVALID_HANDLE_VALUE + if (hFile == (void *) -1) { + mapping->anonymous = true; + mapping->fd = -1; if (size == 0) { - mmapped = (void *) 0x1; - } else { - mmapped = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + wibo::lastError = ERROR_INVALID_PARAMETER; + closeMappingIfPossible(mapping); + return nullptr; } + mapping->maxSize = size; } else { - int fd = fileno(files::fpFromHandle(hFile)); - - if (size == 0) { - size = getFileSize(hFile); - if (size == -1) { - return (void*) -1; - } + FILE *fp = files::fpFromHandle(hFile); + if (!fp) { + wibo::lastError = ERROR_INVALID_HANDLE; + closeMappingIfPossible(mapping); + return nullptr; } - + int originalFd = fileno(fp); + if (originalFd == -1) { + setLastErrorFromErrno(); + closeMappingIfPossible(mapping); + return nullptr; + } + int dupFd = fcntl(originalFd, F_DUPFD_CLOEXEC, 0); + if (dupFd == -1) { + setLastErrorFromErrno(); + closeMappingIfPossible(mapping); + return nullptr; + } + mapping->fd = dupFd; if (size == 0) { - mmapped = (void *) 0x1; - } else { - mmapped = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + int64_t fileSize = getFileSize(hFile); + if (fileSize < 0) { + closeMappingIfPossible(mapping); + return nullptr; + } + size = static_cast(fileSize); } + mapping->maxSize = size; } - assert(mmapped != MAP_FAILED); - return handles::allocDataHandle({handles::TYPE_MAPPED, mmapped, (unsigned int) size}); + wibo::lastError = ERROR_SUCCESS; + return handles::allocDataHandle({handles::TYPE_MAPPED, mapping, static_cast(mapping->maxSize)}); + } + + void *WIN_FUNC CreateFileMappingW( + void *hFile, + void *lpFileMappingAttributes, + unsigned int flProtect, + unsigned int dwMaximumSizeHigh, + unsigned int dwMaximumSizeLow, + const uint16_t *lpName) { + std::string name = wideStringToString(lpName); + return CreateFileMappingA(hFile, lpFileMappingAttributes, flProtect, dwMaximumSizeHigh, dwMaximumSizeLow, lpName ? name.c_str() : nullptr); } + constexpr unsigned int FILE_MAP_COPY = 0x00000001; + constexpr unsigned int FILE_MAP_WRITE = 0x00000002; + constexpr unsigned int FILE_MAP_READ = 0x00000004; + constexpr unsigned int FILE_MAP_EXECUTE = 0x00000020; + void *WIN_FUNC MapViewOfFile( void *hFileMappingObject, unsigned int dwDesiredAccess, unsigned int dwFileOffsetHigh, unsigned int dwFileOffsetLow, unsigned int dwNumberOfBytesToMap) { - DEBUG_LOG("MapViewOfFile(%p, %u, %u, %u, %u)\n", hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh, dwFileOffsetLow, dwNumberOfBytesToMap); + DEBUG_LOG("MapViewOfFile(%p, 0x%x, %u, %u, %u)\n", hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh, dwFileOffsetLow, dwNumberOfBytesToMap); handles::Data data = handles::dataFromHandle(hFileMappingObject, false); - assert(data.type == handles::TYPE_MAPPED); - return (void*)((unsigned int) data.ptr + dwFileOffsetLow); + if (data.type != handles::TYPE_MAPPED) { + wibo::lastError = ERROR_INVALID_HANDLE; + return nullptr; + } + auto *mapping = reinterpret_cast(data.ptr); + if (!mapping) { + wibo::lastError = ERROR_INVALID_HANDLE; + return nullptr; + } + if (mapping->closed) { + wibo::lastError = ERROR_INVALID_HANDLE; + return nullptr; + } + + uint64_t offset = ((uint64_t) dwFileOffsetHigh << 32) | dwFileOffsetLow; + if (mapping->anonymous && offset != 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return nullptr; + } + size_t maxSize = mapping->maxSize; + uint64_t length = dwNumberOfBytesToMap; + if (length == 0) { + if (maxSize == 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return nullptr; + } + if (offset > maxSize) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return nullptr; + } + length = maxSize - offset; + } + if (length == 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return nullptr; + } + if (maxSize && offset + length > maxSize) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return nullptr; + } + + int prot = PROT_READ; + bool wantWrite = (dwDesiredAccess & FILE_MAP_WRITE) != 0; + bool wantExecute = (dwDesiredAccess & FILE_MAP_EXECUTE) != 0; + + if (mapping->protect == 0x04 /* PAGE_READWRITE */) { + if (wantWrite) { + prot |= PROT_WRITE; + } + } else { // read-only or write copy + if (wantWrite && !(dwDesiredAccess & FILE_MAP_COPY)) { + wibo::lastError = ERROR_ACCESS_DENIED; + return nullptr; + } + } + if (wantExecute) { + prot |= PROT_EXEC; + } + + int flags = 0; + if (mapping->anonymous) { + flags |= MAP_ANONYMOUS; + } + flags |= (dwDesiredAccess & FILE_MAP_COPY) ? MAP_PRIVATE : MAP_SHARED; + + size_t pageSize = static_cast(sysconf(_SC_PAGESIZE)); + off_t alignedOffset = mapping->anonymous ? 0 : static_cast(offset & ~static_cast(pageSize - 1)); + size_t offsetDelta = static_cast(offset - alignedOffset); + size_t mapLength = static_cast(length + offsetDelta); + if (mapLength < length) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return nullptr; + } + + int mmapFd = mapping->anonymous ? -1 : mapping->fd; + void *mapBase = mmap(nullptr, mapLength, prot, flags, mmapFd, alignedOffset); + if (mapBase == MAP_FAILED) { + setLastErrorFromErrno(); + return nullptr; + } + void *viewPtr = static_cast(mapBase) + offsetDelta; + g_viewInfo[viewPtr] = ViewInfo{mapBase, mapLength, mapping}; + mapping->refCount++; + wibo::lastError = ERROR_SUCCESS; + return viewPtr; } int WIN_FUNC UnmapViewOfFile(void *lpBaseAddress) { DEBUG_LOG("UnmapViewOfFile(%p)\n", lpBaseAddress); + auto it = g_viewInfo.find(lpBaseAddress); + if (it == g_viewInfo.end()) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + ViewInfo info = it->second; + g_viewInfo.erase(it); + if (info.mapBase && info.mapLength) { + munmap(info.mapBase, info.mapLength); + } + if (info.owner && info.owner->refCount > 0) { + info.owner->refCount--; + tryReleaseMapping(info.owner); + } + wibo::lastError = ERROR_SUCCESS; return 1; } - int WIN_FUNC DeleteFileA(const char* lpFileName) { + BOOL WIN_FUNC DeleteFileA(const char* lpFileName) { + if (!lpFileName) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } std::string path = files::pathFromWindows(lpFileName); DEBUG_LOG("DeleteFileA %s (%s)\n", lpFileName, path.c_str()); - unlink(path.c_str()); - return 1; + if (unlink(path.c_str()) == 0) { + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + setLastErrorFromErrno(); + return FALSE; + } + + BOOL WIN_FUNC DeleteFileW(const uint16_t *lpFileName) { + if (!lpFileName) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + std::string name = wideStringToString(lpFileName); + return DeleteFileA(name.c_str()); } DWORD WIN_FUNC SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod) { @@ -2159,6 +2563,132 @@ namespace kernel32 { return S_OK; } + HANDLE WIN_FUNC CreateMutexW(void *lpMutexAttributes, BOOL bInitialOwner, LPCWSTR lpName) { + std::string nameLog; + if (lpName) { + nameLog = wideStringToString(reinterpret_cast(lpName)); + } else { + nameLog = ""; + } + DEBUG_LOG("CreateMutexW(name=%s, initialOwner=%d)\n", nameLog.c_str(), bInitialOwner); + (void)lpMutexAttributes; + + std::u16string name = makeMutexName(lpName); + MutexObject *obj = nullptr; + bool alreadyExists = false; + { + std::lock_guard lock(mutexRegistryLock); + if (!name.empty()) { + auto it = namedMutexes.find(name); + if (it != namedMutexes.end()) { + obj = it->second; + obj->refCount++; + alreadyExists = true; + } + } + if (!obj) { + obj = new MutexObject(); + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&obj->mutex, &attr); + pthread_mutexattr_destroy(&attr); + obj->ownerValid = false; + obj->recursionCount = 0; + obj->name = name; + obj->refCount = 1; + if (!name.empty()) { + namedMutexes[name] = obj; + } + } + } + + if (!alreadyExists && bInitialOwner) { + pthread_mutex_lock(&obj->mutex); + obj->owner = pthread_self(); + obj->ownerValid = true; + obj->recursionCount = 1; + } + + HANDLE handle = handles::allocDataHandle({handles::TYPE_MUTEX, obj, 0}); + wibo::lastError = alreadyExists ? ERROR_ALREADY_EXISTS : ERROR_SUCCESS; + return handle; + } + + BOOL WIN_FUNC ReleaseMutex(HANDLE hMutex) { + DEBUG_LOG("ReleaseMutex(%p)\n", hMutex); + auto data = handles::dataFromHandle(hMutex, false); + if (data.type != handles::TYPE_MUTEX) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + auto *obj = reinterpret_cast(data.ptr); + pthread_t self = pthread_self(); + if (!obj->ownerValid || !pthread_equal(obj->owner, self)) { + wibo::lastError = ERROR_NOT_OWNER; + return FALSE; + } + if (obj->recursionCount > 0) { + obj->recursionCount--; + } + if (obj->recursionCount == 0) { + obj->ownerValid = false; + } + pthread_mutex_unlock(&obj->mutex); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + BOOL WIN_FUNC GetThreadTimes(HANDLE hThread, + FILETIME *lpCreationTime, + FILETIME *lpExitTime, + FILETIME *lpKernelTime, + FILETIME *lpUserTime) { + DEBUG_LOG("GetThreadTimes(%p, %p, %p, %p, %p)\n", + hThread, lpCreationTime, lpExitTime, lpKernelTime, lpUserTime); + + if (!lpKernelTime || !lpUserTime) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + + bool isPseudoCurrentThread = hThread == (HANDLE)0x100007 || hThread == (HANDLE)0xFFFFFFFE || hThread == (HANDLE)0 || hThread == (HANDLE)0xFFFFFFFF; + if (!isPseudoCurrentThread) { + DEBUG_LOG("GetThreadTimes: unsupported handle %p\n", hThread); + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + + if (lpCreationTime) { + *lpCreationTime = defaultFiletime; + } + if (lpExitTime) { + lpExitTime->dwLowDateTime = 0; + lpExitTime->dwHighDateTime = 0; + } + + struct rusage usage; + if (getrusage(RUSAGE_THREAD, &usage) == 0) { + *lpKernelTime = fileTimeFromTimeval(usage.ru_stime); + *lpUserTime = fileTimeFromTimeval(usage.ru_utime); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + struct timespec cpuTime; + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &cpuTime) == 0) { + *lpKernelTime = fileTimeFromDuration(0); + *lpUserTime = fileTimeFromTimespec(cpuTime); + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } + + setLastErrorFromErrno(); + *lpKernelTime = fileTimeFromDuration(0); + *lpUserTime = fileTimeFromDuration(0); + return FALSE; + } + unsigned short WIN_FUNC GetFileType(void *hFile) { DEBUG_LOG("GetFileType %p\n", hFile); return 1; // FILE_TYPE_DISK @@ -2529,13 +3059,61 @@ namespace kernel32 { return FALSE; // We're not multibyte (yet?) } + constexpr unsigned int LCMAP_LOWERCASE = 0x00000100; + constexpr unsigned int LCMAP_UPPERCASE = 0x00000200; + constexpr unsigned int LCMAP_SORTKEY = 0x00000400; + constexpr unsigned int LCMAP_BYTEREV = 0x00000800; + constexpr unsigned int LCMAP_LINGUISTIC_CASING = 0x01000000; + int WIN_FUNC LCMapStringW(int Locale, unsigned int dwMapFlags, const uint16_t* lpSrcStr, int cchSrc, uint16_t* lpDestStr, int cchDest) { - DEBUG_LOG("LCMapStringW: (locale=%i, flags=%u, src=%p, dest=%p)\n", Locale, dwMapFlags, cchSrc, cchDest); - if (cchSrc < 0) { - cchSrc = wstrlen(lpSrcStr) + 1; + DEBUG_LOG("LCMapStringW(locale=%i, flags=0x%x, src=%p, dest=%p, cchSrc=%d, cchDest=%d)\n", Locale, dwMapFlags, lpSrcStr, lpDestStr, cchSrc, cchDest); + (void) Locale; + if (!lpSrcStr || cchSrc == 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; } - // DEBUG_LOG("lpSrcStr: %s\n", lpSrcStr); - return 1; // success + + bool nullTerminated = cchSrc < 0; + size_t srcLen = nullTerminated ? (wstrlen(lpSrcStr) + 1) : static_cast(cchSrc); + if (srcLen == 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + + if (!lpDestStr || cchDest == 0) { + // Caller is asking for the required length. + wibo::lastError = ERROR_SUCCESS; + return static_cast(srcLen); + } + if (cchDest < static_cast(srcLen)) { + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + + unsigned int casingFlags = dwMapFlags & (LCMAP_UPPERCASE | LCMAP_LOWERCASE); + unsigned int ignoredFlags = dwMapFlags & (LCMAP_LINGUISTIC_CASING); + (void) ignoredFlags; + if (dwMapFlags & (LCMAP_SORTKEY | LCMAP_BYTEREV)) { + DEBUG_LOG("LCMapStringW: unsupported mapping flags 0x%x\n", dwMapFlags); + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + + std::vector buffer(srcLen, 0); + for (size_t i = 0; i < srcLen; ++i) { + uint16_t ch = lpSrcStr[i]; + if (casingFlags == LCMAP_UPPERCASE) { + buffer[i] = static_cast(std::towupper(static_cast(ch))); + } else if (casingFlags == LCMAP_LOWERCASE) { + buffer[i] = static_cast(std::towlower(static_cast(ch))); + } else { + buffer[i] = ch; + } + } + + std::memcpy(lpDestStr, buffer.data(), srcLen * sizeof(uint16_t)); + wibo::lastError = ERROR_SUCCESS; + return static_cast(srcLen); } int WIN_FUNC LCMapStringA(int Locale, unsigned int dwMapFlags, const char* lpSrcStr, int cchSrc, char* lpDestStr, int cchDest) { @@ -2592,17 +3170,31 @@ namespace kernel32 { return len; } - unsigned int WIN_FUNC SetEnvironmentVariableA(const char *lpName, const char *lpValue) { - DEBUG_LOG("SetEnvironmentVariableA: %s=%s\n", lpName, lpValue ? lpValue : ""); - if (!lpName) { - return 0; + BOOL WIN_FUNC SetEnvironmentVariableA(const char *lpName, const char *lpValue) { + DEBUG_LOG("SetEnvironmentVariableA: %s=%s\n", lpName ? lpName : "(null)", lpValue ? lpValue : "(null)"); + if (!lpName || std::strchr(lpName, '=')) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; } + int rc = 0; if (!lpValue) { - return unsetenv(lpName); + rc = unsetenv(lpName); + if (rc != 0) { + setLastErrorFromErrno(); + return FALSE; + } + wibo::lastError = ERROR_SUCCESS; + return TRUE; } std::string hostValue = convertEnvValueToHost(lpName, lpValue); const char *valuePtr = hostValue.empty() ? lpValue : hostValue.c_str(); - return setenv(lpName, valuePtr, 1 /* OVERWRITE */); + rc = setenv(lpName, valuePtr, 1 /* overwrite */); + if (rc != 0) { + setLastErrorFromErrno(); + return FALSE; + } + wibo::lastError = ERROR_SUCCESS; + return TRUE; } DWORD WIN_FUNC GetEnvironmentVariableW(LPCWSTR lpName, LPWSTR lpBuffer, DWORD nSize) { @@ -2623,6 +3215,16 @@ namespace kernel32 { return len - 1; } + BOOL WIN_FUNC SetEnvironmentVariableW(const uint16_t *lpName, const uint16_t *lpValue) { + if (!lpName) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + std::string name = wideStringToString(lpName); + std::string value = lpValue ? wideStringToString(lpValue) : std::string(); + return SetEnvironmentVariableA(name.c_str(), lpValue ? value.c_str() : nullptr); + } + unsigned int WIN_FUNC QueryPerformanceCounter(unsigned long int *lpPerformanceCount) { DEBUG_LOG("QueryPerformanceCounter\n"); *lpPerformanceCount = 0; @@ -2785,6 +3387,9 @@ static void *resolveByName(const char *name) { // errhandlingapi.h if (strcmp(name, "GetLastError") == 0) return (void *) kernel32::GetLastError; if (strcmp(name, "SetLastError") == 0) return (void *) kernel32::SetLastError; + if (strcmp(name, "Wow64DisableWow64FsRedirection") == 0) return (void *) kernel32::Wow64DisableWow64FsRedirection; + if (strcmp(name, "Wow64RevertWow64FsRedirection") == 0) return (void *) kernel32::Wow64RevertWow64FsRedirection; + if (strcmp(name, "RaiseException") == 0) return (void *) kernel32::RaiseException; if (strcmp(name, "AddVectoredExceptionHandler") == 0) return (void *) kernel32::AddVectoredExceptionHandler; // processthreadsapi.h @@ -2794,6 +3399,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "GetCurrentThreadId") == 0) return (void *) kernel32::GetCurrentThreadId; if (strcmp(name, "ExitProcess") == 0) return (void *) kernel32::ExitProcess; if (strcmp(name, "GetExitCodeProcess") == 0) return (void *) kernel32::GetExitCodeProcess; + if (strcmp(name, "CreateProcessW") == 0) return (void *) kernel32::CreateProcessW; if (strcmp(name, "CreateProcessA") == 0) return (void *) kernel32::CreateProcessA; if (strcmp(name, "TlsAlloc") == 0) return (void *) kernel32::TlsAlloc; if (strcmp(name, "TlsFree") == 0) return (void *) kernel32::TlsFree; @@ -2803,6 +3409,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "GetStartupInfoW") == 0) return (void *) kernel32::GetStartupInfoW; if (strcmp(name, "SetThreadStackGuarantee") == 0) return (void *) kernel32::SetThreadStackGuarantee; if (strcmp(name, "GetCurrentThread") == 0) return (void *) kernel32::GetCurrentThread; + if (strcmp(name, "GetThreadTimes") == 0) return (void *) kernel32::GetThreadTimes; if (strcmp(name, "SetThreadDescription") == 0) return (void *) kernel32::SetThreadDescription; // winnls.h @@ -2836,6 +3443,8 @@ static void *resolveByName(const char *name) { if (strcmp(name, "ReleaseSRWLockExclusive") == 0) return (void *) kernel32::ReleaseSRWLockExclusive; if (strcmp(name, "TryAcquireSRWLockExclusive") == 0) return (void *) kernel32::TryAcquireSRWLockExclusive; if (strcmp(name, "WaitForSingleObject") == 0) return (void *) kernel32::WaitForSingleObject; + if (strcmp(name, "CreateMutexW") == 0) return (void *) kernel32::CreateMutexW; + if (strcmp(name, "ReleaseMutex") == 0) return (void *) kernel32::ReleaseMutex; // winbase.h if (strcmp(name, "GlobalAlloc") == 0) return (void *) kernel32::GlobalAlloc; @@ -2867,6 +3476,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "FreeEnvironmentStringsW") == 0) return (void *) kernel32::FreeEnvironmentStringsW; if (strcmp(name, "GetEnvironmentVariableA") == 0) return (void *) kernel32::GetEnvironmentVariableA; if (strcmp(name, "SetEnvironmentVariableA") == 0) return (void *) kernel32::SetEnvironmentVariableA; + if (strcmp(name, "SetEnvironmentVariableW") == 0) return (void *) kernel32::SetEnvironmentVariableW; if (strcmp(name, "GetEnvironmentVariableW") == 0) return (void *) kernel32::GetEnvironmentVariableW; // console api @@ -2884,6 +3494,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "GetFullPathNameA") == 0) return (void *) kernel32::GetFullPathNameA; if (strcmp(name, "GetFullPathNameW") == 0) return (void *) kernel32::GetFullPathNameW; if (strcmp(name, "GetShortPathNameA") == 0) return (void *) kernel32::GetShortPathNameA; + if (strcmp(name, "GetShortPathNameW") == 0) return (void *) kernel32::GetShortPathNameW; if (strcmp(name, "FindFirstFileA") == 0) return (void *) kernel32::FindFirstFileA; if (strcmp(name, "FindFirstFileW") == 0) return (void *) kernel32::FindFirstFileW; if (strcmp(name, "FindFirstFileExA") == 0) return (void *) kernel32::FindFirstFileExA; @@ -2896,9 +3507,11 @@ static void *resolveByName(const char *name) { if (strcmp(name, "CreateFileA") == 0) return (void *) kernel32::CreateFileA; if (strcmp(name, "CreateFileW") == 0) return (void *) kernel32::CreateFileW; if (strcmp(name, "CreateFileMappingA") == 0) return (void *) kernel32::CreateFileMappingA; + if (strcmp(name, "CreateFileMappingW") == 0) return (void *) kernel32::CreateFileMappingW; if (strcmp(name, "MapViewOfFile") == 0) return (void *) kernel32::MapViewOfFile; if (strcmp(name, "UnmapViewOfFile") == 0) return (void *) kernel32::UnmapViewOfFile; if (strcmp(name, "DeleteFileA") == 0) return (void *) kernel32::DeleteFileA; + if (strcmp(name, "DeleteFileW") == 0) return (void *) kernel32::DeleteFileW; if (strcmp(name, "SetFilePointer") == 0) return (void *) kernel32::SetFilePointer; if (strcmp(name, "SetFilePointerEx") == 0) return (void *) kernel32::SetFilePointerEx; if (strcmp(name, "SetEndOfFile") == 0) return (void *) kernel32::SetEndOfFile; diff --git a/dll/psapi.cpp b/dll/psapi.cpp new file mode 100644 index 0000000..bd7e4ff --- /dev/null +++ b/dll/psapi.cpp @@ -0,0 +1,67 @@ +#include "common.h" +#include "handles.h" + +namespace psapi { + BOOL WIN_FUNC EnumProcessModules(HANDLE hProcess, HMODULE *lphModule, DWORD cb, DWORD *lpcbNeeded) { + DEBUG_LOG("EnumProcessModules(hProcess=%p, cb=%u)\n", hProcess, cb); + + bool recognizedHandle = false; + if (hProcess == (HANDLE)0xFFFFFFFF) { + recognizedHandle = true; + } else { + auto data = handles::dataFromHandle(hProcess, false); + recognizedHandle = (data.type == handles::TYPE_PROCESS); + } + if (!recognizedHandle) { + wibo::lastError = ERROR_ACCESS_DENIED; + return FALSE; + } + + HMODULE currentModule = wibo::mainModule ? reinterpret_cast(wibo::mainModule->imageBuffer) : nullptr; + DWORD required = currentModule ? sizeof(HMODULE) : 0; + if (lpcbNeeded) { + *lpcbNeeded = required; + } + + if (required == 0) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } + + if (!lphModule || cb < required) { + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + + lphModule[0] = currentModule; + wibo::lastError = ERROR_SUCCESS; + return TRUE; + } +} + +static void *resolveByName(const char *name) { + DEBUG_LOG("psapi resolveByName(%s)\n", name); + if (strcmp(name, "EnumProcessModules") == 0) return (void *) psapi::EnumProcessModules; + if (strcmp(name, "K32EnumProcessModules") == 0) return (void *) psapi::EnumProcessModules; + return nullptr; +} + +static void *resolveByOrdinal(uint16_t ordinal) { + DEBUG_LOG("psapi resolveByOrdinal(%u)\n", ordinal); + switch (ordinal) { + case 4: // EnumProcessModules + return (void *) psapi::EnumProcessModules; + default: + return nullptr; + } +} + +wibo::Module lib_psapi = { + (const char *[]){ + "psapi", + "psapi.dll", + nullptr, + }, + resolveByName, + resolveByOrdinal, +}; diff --git a/dll/rpcrt4.cpp b/dll/rpcrt4.cpp new file mode 100644 index 0000000..4a77962 --- /dev/null +++ b/dll/rpcrt4.cpp @@ -0,0 +1,295 @@ +#include "common.h" + +#include +#include +#include +#include +#include +#include + +namespace { + +using RPC_STATUS = unsigned long; +using RPC_WSTR = uint16_t *; +using RPC_BINDING_HANDLE = void *; +using RPC_AUTH_IDENTITY_HANDLE = void *; +using LONG_PTR = intptr_t; +using PMIDL_STUB_DESC = void *; +using PFORMAT_STRING = unsigned char *; +using PRPC_MESSAGE = void *; + +constexpr RPC_STATUS RPC_S_OK = 0; +constexpr RPC_STATUS RPC_S_INVALID_STRING_BINDING = 1700; +constexpr RPC_STATUS RPC_S_INVALID_BINDING = 1702; +constexpr RPC_STATUS RPC_S_SERVER_UNAVAILABLE = 1722; +constexpr RPC_STATUS RPC_S_INVALID_ARG = 87; +constexpr RPC_STATUS RPC_S_OUT_OF_MEMORY = 14; + +struct RPC_SECURITY_QOS { + unsigned long Version = 0; + unsigned long Capabilities = 0; + unsigned long IdentityTracking = 0; + unsigned long ImpersonationType = 0; + void *AdditionalSecurityInfo = nullptr; +}; + +struct BindingComponents { + std::u16string objectUuid; + std::u16string protocolSequence; + std::u16string networkAddress; + std::u16string endpoint; + std::u16string options; +}; + +struct BindingHandleData { + BindingComponents components; + std::u16string bindingString; + std::u16string serverPrincipalName; + unsigned long authnLevel = 0; + unsigned long authnService = 0; + RPC_AUTH_IDENTITY_HANDLE authIdentity = nullptr; + unsigned long authzService = 0; + bool hasAuthInfo = false; + bool hasSecurityQos = false; + RPC_SECURITY_QOS securityQos = {}; + bool serverReachable = false; +}; + +union CLIENT_CALL_RETURN { + void *Pointer; + LONG_PTR Simple; +}; + +std::unordered_map g_stringBindings; +std::unordered_map> g_bindingHandles; + +std::u16string toU16(RPC_WSTR str) { + if (!str) { + return {}; + } + auto *ptr = reinterpret_cast(str); + size_t length = 0; + while (ptr[length] != 0) { + ++length; + } + return std::u16string(ptr, ptr + length); +} + +std::string narrow(const std::u16string &value) { + std::string out; + out.reserve(value.size()); + for (char16_t ch : value) { + if (ch <= 0x7F) { + out.push_back(static_cast(ch)); + } else { + out.push_back('?'); + } + } + return out; +} + +std::u16string composeString(const BindingComponents &components) { + std::u16string result; + if (!components.objectUuid.empty()) { + result += components.objectUuid; + result += u"@"; + } + if (!components.protocolSequence.empty()) { + result += components.protocolSequence; + } + if (!components.networkAddress.empty()) { + if (!components.protocolSequence.empty()) { + result += u":"; + } + result += components.networkAddress; + } + if (!components.endpoint.empty()) { + result += u"["; + result += components.endpoint; + result += u"]"; + } + if (!components.options.empty()) { + result += u"{"; + result += components.options; + result += u"}"; + } + return result; +} + +BindingHandleData *getBinding(RPC_BINDING_HANDLE handle) { + auto it = g_bindingHandles.find(handle); + if (it == g_bindingHandles.end()) { + return nullptr; + } + return it->second.get(); +} + +} // namespace + +extern "C" { + +RPC_STATUS WIN_FUNC RpcStringBindingComposeW( + RPC_WSTR objUuid, + RPC_WSTR protSeq, + RPC_WSTR networkAddr, + RPC_WSTR endpoint, + RPC_WSTR options, + RPC_WSTR *stringBinding +) { + BindingComponents components; + components.objectUuid = toU16(objUuid); + components.protocolSequence = toU16(protSeq); + components.networkAddress = toU16(networkAddr); + components.endpoint = toU16(endpoint); + components.options = toU16(options); + + std::u16string encoded = composeString(components); + DEBUG_LOG("RpcStringBindingComposeW -> %s\n", narrow(encoded).c_str()); + + if (stringBinding) { + size_t length = encoded.size(); + auto *buffer = static_cast(std::malloc((length + 1) * sizeof(char16_t))); + if (!buffer) { + return RPC_S_OUT_OF_MEMORY; + } + if (length > 0) { + std::memcpy(buffer, encoded.data(), length * sizeof(char16_t)); + } + buffer[length] = 0; + RPC_WSTR result = reinterpret_cast(buffer); + g_stringBindings[result] = components; + *stringBinding = result; + } + + return RPC_S_OK; +} + +RPC_STATUS WIN_FUNC RpcBindingFromStringBindingW(RPC_WSTR stringBinding, RPC_BINDING_HANDLE *binding) { + if (!binding) { + return RPC_S_INVALID_ARG; + } + *binding = nullptr; + if (!stringBinding) { + return RPC_S_INVALID_STRING_BINDING; + } + auto it = g_stringBindings.find(stringBinding); + if (it == g_stringBindings.end()) { + return RPC_S_INVALID_STRING_BINDING; + } + auto handleData = std::make_unique(); + handleData->components = it->second; + handleData->bindingString = composeString(handleData->components); + handleData->serverReachable = false; + RPC_BINDING_HANDLE handle = reinterpret_cast(handleData.get()); + g_bindingHandles.emplace(handle, std::move(handleData)); + *binding = handle; + DEBUG_LOG("RpcBindingFromStringBindingW(handle=%p)\n", handle); + return RPC_S_OK; +} + +RPC_STATUS WIN_FUNC RpcBindingSetAuthInfoExW( + RPC_BINDING_HANDLE binding, + RPC_WSTR serverPrincName, + unsigned long authnLevel, + unsigned long authnSvc, + RPC_AUTH_IDENTITY_HANDLE authIdentity, + unsigned long authzSvc, + RPC_SECURITY_QOS *securityQos +) { + BindingHandleData *data = getBinding(binding); + if (!data) { + return RPC_S_INVALID_BINDING; + } + data->serverPrincipalName = toU16(serverPrincName); + data->authnLevel = authnLevel; + data->authnService = authnSvc; + data->authIdentity = authIdentity; + data->authzService = authzSvc; + data->hasAuthInfo = true; + if (securityQos) { + data->securityQos = *securityQos; + data->hasSecurityQos = true; + } else { + data->hasSecurityQos = false; + } + DEBUG_LOG("RpcBindingSetAuthInfoExW(handle=%p, authnSvc=%lu, authnLevel=%lu)\n", binding, authnSvc, authnLevel); + return RPC_S_OK; +} + +RPC_STATUS WIN_FUNC RpcBindingFree(RPC_BINDING_HANDLE *binding) { + if (!binding) { + return RPC_S_INVALID_ARG; + } + RPC_BINDING_HANDLE handle = *binding; + if (!handle) { + return RPC_S_INVALID_BINDING; + } + auto it = g_bindingHandles.find(handle); + if (it == g_bindingHandles.end()) { + return RPC_S_INVALID_BINDING; + } + g_bindingHandles.erase(it); + *binding = nullptr; + DEBUG_LOG("RpcBindingFree\n"); + return RPC_S_OK; +} + +RPC_STATUS WIN_FUNC RpcStringFreeW(RPC_WSTR *string) { + if (!string) { + return RPC_S_INVALID_ARG; + } + RPC_WSTR value = *string; + if (!value) { + return RPC_S_OK; + } + auto it = g_stringBindings.find(value); + if (it != g_stringBindings.end()) { + g_stringBindings.erase(it); + } + std::free(reinterpret_cast(value)); + *string = nullptr; + return RPC_S_OK; +} + +CLIENT_CALL_RETURN __attribute__((force_align_arg_pointer, callee_pop_aggregate_return(0), cdecl)) +NdrClientCall2(PMIDL_STUB_DESC stubDescriptor, PFORMAT_STRING format, ...) { + DEBUG_LOG("STUB: NdrClientCall2 stubDescriptor=%p format=%p\n", stubDescriptor, format); + CLIENT_CALL_RETURN result = {}; + result.Simple = RPC_S_SERVER_UNAVAILABLE; + DEBUG_LOG("NdrClientCall2 returning RPC_S_SERVER_UNAVAILABLE\n"); + return result; +} + +void WIN_FUNC NdrServerCall2(PRPC_MESSAGE message) { + DEBUG_LOG("STUB: NdrServerCall2 message=%p\n", message); +} + +} // extern "C" + +namespace { + +void *resolveByName(const char *name) { + if (std::strcmp(name, "RpcStringBindingComposeW") == 0) + return (void *) RpcStringBindingComposeW; + if (std::strcmp(name, "RpcBindingFromStringBindingW") == 0) + return (void *) RpcBindingFromStringBindingW; + if (std::strcmp(name, "RpcStringFreeW") == 0) + return (void *) RpcStringFreeW; + if (std::strcmp(name, "RpcBindingFree") == 0) + return (void *) RpcBindingFree; + if (std::strcmp(name, "RpcBindingSetAuthInfoExW") == 0) + return (void *) RpcBindingSetAuthInfoExW; + if (std::strcmp(name, "NdrClientCall2") == 0) + return (void *) NdrClientCall2; + if (std::strcmp(name, "NdrServerCall2") == 0) + return (void *) NdrServerCall2; + return nullptr; +} + +} // namespace + +wibo::Module lib_rpcrt4 = { + (const char *[]){"rpcrt4", "rpcrt4.dll", nullptr}, + resolveByName, + nullptr, +}; diff --git a/handles.h b/handles.h index af96c3f..cfe0f38 100644 --- a/handles.h +++ b/handles.h @@ -4,10 +4,12 @@ namespace handles { enum Type { - TYPE_UNUSED, + TYPE_UNUSED, TYPE_FILE, TYPE_MAPPED, - TYPE_PROCESS + TYPE_PROCESS, + TYPE_TOKEN, + TYPE_MUTEX }; struct Data { diff --git a/loader.cpp b/loader.cpp index a8e1389..fc09f61 100644 --- a/loader.cpp +++ b/loader.cpp @@ -92,6 +92,17 @@ struct PEHintNameTableEntry { char name[1]; // variable length }; +struct PEDelayImportDescriptor { + uint32_t attributes; + uint32_t name; + uint32_t moduleHandle; + uint32_t importAddressTable; + uint32_t importNameTable; + uint32_t boundImportAddressTable; + uint32_t unloadInformationTable; + uint32_t timeStamp; +}; + struct PEBaseRelocationBlock { uint32_t virtualAddress; uint32_t sizeOfBlock; @@ -279,12 +290,16 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { // Import by ordinal uint16_t ordinal = lookup & 0xFFFF; DEBUG_LOG(" Ordinal: %d\n", ordinal); - *addressTable = reinterpret_cast(resolveFuncByOrdinal(module, ordinal)); + void *func = resolveFuncByOrdinal(module, ordinal); + DEBUG_LOG(" -> %p\n", func); + *addressTable = reinterpret_cast(func); } else { // Import by name PEHintNameTableEntry *hintName = fromRVA(lookup); - DEBUG_LOG(" Name: %s\n", hintName->name); - *addressTable = reinterpret_cast(resolveFuncByName(module, hintName->name)); + DEBUG_LOG(" Name: %s (IAT=%p)\n", hintName->name, addressTable); + void *func = resolveFuncByName(module, hintName->name); + DEBUG_LOG(" -> %p\n", func); + *addressTable = reinterpret_cast(func); } ++lookupTable; ++addressTable; @@ -292,6 +307,39 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { ++dir; } + if (header32.delayImportDescriptor.virtualAddress) { + DEBUG_LOG("Processing delay import table at RVA %x\n", header32.delayImportDescriptor.virtualAddress); + PEDelayImportDescriptor *delay = fromRVA(header32.delayImportDescriptor.virtualAddress); + while (delay->name) { + char *dllName = fromRVA(delay->name); + DEBUG_LOG("Delay DLL Name: %s\n", dllName); + uint32_t *lookupTable = fromRVA(delay->importNameTable); + uint32_t *addressTable = fromRVA(delay->importAddressTable); + HMODULE module = loadModule(dllName); + while (*lookupTable) { + uint32_t lookup = *lookupTable; + if (lookup & 0x80000000) { + uint16_t ordinal = lookup & 0xFFFF; + DEBUG_LOG(" Ordinal: %d (IAT=%p)\n", ordinal, addressTable); + *addressTable = reinterpret_cast(resolveFuncByOrdinal(module, ordinal)); + } else { + PEHintNameTableEntry *hintName = fromRVA(lookup); + DEBUG_LOG(" Name: %s\n", hintName->name); + *addressTable = reinterpret_cast(resolveFuncByName(module, hintName->name)); + } + ++lookupTable; + ++addressTable; + } + if (delay->moduleHandle) { + HMODULE *moduleSlot = fromRVA(delay->moduleHandle); + if (moduleSlot) { + *moduleSlot = module; + } + } + ++delay; + } + } + entryPoint = header32.addressOfEntryPoint ? fromRVA(header32.addressOfEntryPoint) : nullptr; return true; diff --git a/module_registry.cpp b/module_registry.cpp index 481f9cf..9e90af3 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -23,6 +23,7 @@ extern const wibo::Module lib_lmgr; extern const wibo::Module lib_mscoree; extern const wibo::Module lib_msvcrt; extern const wibo::Module lib_ntdll; +extern const wibo::Module lib_rpcrt4; extern const wibo::Module lib_ole32; extern const wibo::Module lib_user32; extern const wibo::Module lib_vcruntime; @@ -405,7 +406,7 @@ void ensureInitialized() { const wibo::Module *builtins[] = { &lib_advapi32, &lib_bcrypt, &lib_crt, &lib_kernel32, &lib_lmgr, &lib_mscoree, &lib_msvcrt, - &lib_ntdll, &lib_ole32, &lib_user32, &lib_vcruntime, &lib_version, nullptr, + &lib_ntdll, &lib_ole32, &lib_rpcrt4, &lib_user32, &lib_vcruntime, &lib_version, nullptr, }; for (const wibo::Module **module = builtins; *module; ++module) { From b53ae15c82ea6b14c51ca411c627c27180e9b79f Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 19:59:58 -0600 Subject: [PATCH 18/28] Force builtin lmgr stub and stub missing imports --- common.h | 2 + dll/kernel32.cpp | 9 +++++ loader.cpp | 14 +++++-- module_registry.cpp | 92 ++++++++++++++++++++++++++++++++++++++------- 4 files changed, 99 insertions(+), 18 deletions(-) diff --git a/common.h b/common.h index fc7bffc..44a7d3a 100644 --- a/common.h +++ b/common.h @@ -127,6 +127,8 @@ namespace wibo { void freeModule(HMODULE module); void *resolveFuncByName(HMODULE module, const char *funcName); void *resolveFuncByOrdinal(HMODULE module, uint16_t ordinal); + void *resolveMissingImportByName(const char *dllName, const char *funcName); + void *resolveMissingImportByOrdinal(const char *dllName, uint16_t ordinal); struct ResourceIdentifier { ResourceIdentifier() : isString(false), id(0) {} diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index a7434a5..a8ba573 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -261,6 +261,14 @@ namespace kernel32 { wibo::lastError = dwErrCode; } + BOOL WIN_FUNC IsBadReadPtr(const void *lp, uintptr_t ucb) { + DEBUG_LOG("STUB: IsBadReadPtr(ptr=%p, size=%zu)\n", lp, static_cast(ucb)); + if (!lp) { + return TRUE; + } + return FALSE; + } + BOOL WIN_FUNC Wow64DisableWow64FsRedirection(void **OldValue) { DEBUG_LOG("Wow64DisableWow64FsRedirection\n"); if (OldValue) { @@ -3387,6 +3395,7 @@ static void *resolveByName(const char *name) { // errhandlingapi.h if (strcmp(name, "GetLastError") == 0) return (void *) kernel32::GetLastError; if (strcmp(name, "SetLastError") == 0) return (void *) kernel32::SetLastError; + if (strcmp(name, "IsBadReadPtr") == 0) return (void *) kernel32::IsBadReadPtr; if (strcmp(name, "Wow64DisableWow64FsRedirection") == 0) return (void *) kernel32::Wow64DisableWow64FsRedirection; if (strcmp(name, "Wow64RevertWow64FsRedirection") == 0) return (void *) kernel32::Wow64RevertWow64FsRedirection; if (strcmp(name, "RaiseException") == 0) return (void *) kernel32::RaiseException; diff --git a/loader.cpp b/loader.cpp index fc09f61..ec1a531 100644 --- a/loader.cpp +++ b/loader.cpp @@ -290,14 +290,16 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { // Import by ordinal uint16_t ordinal = lookup & 0xFFFF; DEBUG_LOG(" Ordinal: %d\n", ordinal); - void *func = resolveFuncByOrdinal(module, ordinal); + void *func = module ? resolveFuncByOrdinal(module, ordinal) + : resolveMissingImportByOrdinal(dllName, ordinal); DEBUG_LOG(" -> %p\n", func); *addressTable = reinterpret_cast(func); } else { // Import by name PEHintNameTableEntry *hintName = fromRVA(lookup); DEBUG_LOG(" Name: %s (IAT=%p)\n", hintName->name, addressTable); - void *func = resolveFuncByName(module, hintName->name); + void *func = module ? resolveFuncByName(module, hintName->name) + : resolveMissingImportByName(dllName, hintName->name); DEBUG_LOG(" -> %p\n", func); *addressTable = reinterpret_cast(func); } @@ -321,11 +323,15 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { if (lookup & 0x80000000) { uint16_t ordinal = lookup & 0xFFFF; DEBUG_LOG(" Ordinal: %d (IAT=%p)\n", ordinal, addressTable); - *addressTable = reinterpret_cast(resolveFuncByOrdinal(module, ordinal)); + void *func = module ? resolveFuncByOrdinal(module, ordinal) + : resolveMissingImportByOrdinal(dllName, ordinal); + *addressTable = reinterpret_cast(func); } else { PEHintNameTableEntry *hintName = fromRVA(lookup); DEBUG_LOG(" Name: %s\n", hintName->name); - *addressTable = reinterpret_cast(resolveFuncByName(module, hintName->name)); + void *func = module ? resolveFuncByName(module, hintName->name) + : resolveMissingImportByName(dllName, hintName->name); + *addressTable = reinterpret_cast(func); } ++lookupTable; ++addressTable; diff --git a/module_registry.cpp b/module_registry.cpp index 9e90af3..2b8e9c5 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -3,6 +3,7 @@ #include "strutil.h" #include +#include #include #include #include @@ -63,16 +64,35 @@ struct PEExportDirectory { FOR_256_2(0, 3) FOR_256_2(1, 0) FOR_256_2(1, 1) FOR_256_2(1, 2) FOR_256_2(1, 3) FOR_256_2(2, 0) FOR_256_2(2, 1) \ FOR_256_2(2, 2) FOR_256_2(2, 3) FOR_256_2(3, 0) FOR_256_2(3, 1) FOR_256_2(3, 2) FOR_256_2(3, 3) +static constexpr size_t MAX_STUBS = 0x100; static int stubIndex = 0; -static char stubDlls[0x100][0x100]; -static char stubFuncNames[0x100][0x100]; +static std::array stubDlls; +static std::array stubFuncNames; +static std::unordered_map stubCache; + +static std::string makeStubKey(const char *dllName, const char *funcName) { + std::string key; + if (dllName) { + key.assign(dllName); + std::transform(key.begin(), key.end(), key.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + } + key.push_back(':'); + if (funcName) { + std::string func(funcName); + std::transform(func.begin(), func.end(), func.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + key += func; + } + return key; +} static void stubBase(int index) { - printf("Unhandled function %s (%s)\n", stubFuncNames[index], stubDlls[index]); + printf("Unhandled function %s (%s)\n", stubFuncNames[index].c_str(), stubDlls[index].c_str()); exit(1); } -void (*stubFuncs[0x100])(void) = { +void (*stubFuncs[MAX_STUBS])(void) = { #define FOR_ITER(i) []() { stubBase(i); }, FOR_256 #undef FOR_ITER @@ -84,12 +104,23 @@ void (*stubFuncs[0x100])(void) = { void *resolveMissingFuncName(const char *dllName, const char *funcName) { DEBUG_LOG("Missing function: %s (%s)\n", dllName, funcName); - assert(stubIndex < 0x100); - assert(strlen(dllName) < 0x100); - assert(strlen(funcName) < 0x100); - strcpy(stubFuncNames[stubIndex], funcName); - strcpy(stubDlls[stubIndex], dllName); - return (void *)stubFuncs[stubIndex++]; + std::string key = makeStubKey(dllName, funcName); + auto existing = stubCache.find(key); + if (existing != stubCache.end()) { + return existing->second; + } + if (stubIndex >= static_cast(MAX_STUBS)) { + fprintf(stderr, + "Too many missing functions encountered (>%zu). Last failure: %s (%s)\n", + MAX_STUBS, funcName, dllName); + exit(1); + } + stubFuncNames[stubIndex] = funcName ? funcName : ""; + stubDlls[stubIndex] = dllName ? dllName : ""; + void *stub = (void *)stubFuncs[stubIndex]; + stubCache.emplace(std::move(key), stub); + stubIndex++; + return stub; } void *resolveMissingFuncOrdinal(const char *dllName, uint16_t ordinal) { @@ -113,6 +144,8 @@ struct ModuleRegistry { std::unordered_map onExitTables; std::unordered_map> builtinAliasLists; std::unordered_map builtinAliasMap; + std::unordered_set pinnedAliases; + std::unordered_set pinnedModules; }; ModuleRegistry ®istry() { @@ -322,6 +355,9 @@ void registerAlias(const std::string &alias, wibo::ModuleInfo *info) { reg.modulesByAlias[alias] = info; return; } + if (reg.pinnedAliases.count(alias)) { + return; + } // Prefer externally loaded modules over built-ins when both are present. if (it->second && it->second->module != nullptr && info->module == nullptr) { reg.modulesByAlias[alias] = info; @@ -345,15 +381,25 @@ void registerBuiltinModule(const wibo::Module *module) { reg.builtinAliasLists[module] = {}; auto &aliasList = reg.builtinAliasLists[module]; + const bool pinModule = (module == &lib_lmgr); + if (pinModule) { + reg.pinnedModules.insert(raw); + } for (size_t i = 0; module->names[i]; ++i) { std::string alias = normalizeAlias(module->names[i]); aliasList.push_back(alias); + if (pinModule) { + reg.pinnedAliases.insert(alias); + } registerAlias(alias, raw); reg.builtinAliasMap[alias] = raw; ParsedModuleName parsed = parseModuleName(module->names[i]); std::string baseAlias = normalizedBaseKey(parsed); if (baseAlias != alias) { aliasList.push_back(baseAlias); + if (pinModule) { + reg.pinnedAliases.insert(baseAlias); + } registerAlias(baseAlias, raw); reg.builtinAliasMap[baseAlias] = raw; } @@ -706,10 +752,13 @@ HMODULE loadModule(const char *dllName) { lastError = ERROR_SUCCESS; return existing; } - if (ModuleInfo *external = resolveAndLoadExternal()) { - DEBUG_LOG(" replaced builtin module %s with external copy\n", requested.c_str()); - lastError = ERROR_SUCCESS; - return external; + bool pinned = reg.pinnedModules.count(existing) != 0; + if (!pinned) { + if (ModuleInfo *external = resolveAndLoadExternal()) { + DEBUG_LOG(" replaced builtin module %s with external copy\n", requested.c_str()); + lastError = ERROR_SUCCESS; + return external; + } } lastError = ERROR_SUCCESS; DEBUG_LOG(" returning builtin module %s\n", existing->originalName.c_str()); @@ -828,6 +877,21 @@ void *resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) { return resolveMissingFuncOrdinal(info->originalName.c_str(), ordinal); } +void *resolveMissingImportByName(const char *dllName, const char *funcName) { + const char *safeDll = dllName ? dllName : ""; + const char *safeFunc = funcName ? funcName : ""; + std::lock_guard lock(registry().mutex); + ensureInitialized(); + return resolveMissingFuncName(safeDll, safeFunc); +} + +void *resolveMissingImportByOrdinal(const char *dllName, uint16_t ordinal) { + const char *safeDll = dllName ? dllName : ""; + std::lock_guard lock(registry().mutex); + ensureInitialized(); + return resolveMissingFuncOrdinal(safeDll, ordinal); +} + Executable *executableFromModule(HMODULE module) { if (isMainModule(module)) { return mainModule; From 2c5fdd6c727f80daf0bb164e7c21d86e0246b5c7 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 28 Sep 2025 13:17:43 -0600 Subject: [PATCH 19/28] msvcrt: export calloc --- dll/msvcrt.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 7dbd1ec..b2db453 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -431,6 +431,10 @@ namespace msvcrt { return std::malloc(size); } + void* WIN_ENTRY calloc(size_t count, size_t size){ + return std::calloc(count, size); + } + void* WIN_ENTRY _malloc_crt(size_t size) { return std::malloc(size); } @@ -1300,6 +1304,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "strcmp") == 0) return (void *)msvcrt::strcmp; if (strcmp(name, "strncmp") == 0) return (void *)msvcrt::strncmp; if (strcmp(name, "malloc") == 0) return (void*)msvcrt::malloc; + if (strcmp(name, "calloc") == 0) return (void*)msvcrt::calloc; if (strcmp(name, "_malloc_crt") == 0) return (void*)msvcrt::_malloc_crt; if (strcmp(name, "_lock") == 0) return (void*)msvcrt::_lock; if (strcmp(name, "_unlock") == 0) return (void*)msvcrt::_unlock; From 2732bd584a45887260b5b423650a4f053ea5f02e Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 28 Sep 2025 15:09:13 -0600 Subject: [PATCH 20/28] More msvcrt impls for Ubuntu mingw --- .dockerignore | 4 - Dockerfile.ubuntu | 28 +++++++ dll/kernel32.cpp | 37 +++++++++ dll/msvcrt.cpp | 196 +++++++++++++++++++++++++++++++++++++++++++- module_registry.cpp | 8 +- 5 files changed, 265 insertions(+), 8 deletions(-) create mode 100644 Dockerfile.ubuntu diff --git a/.dockerignore b/.dockerignore index b9ce231..5d22764 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,3 @@ .git/ .vscode/ build/ -test/ - -Dockerfile -README.md diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu new file mode 100644 index 0000000..05076ff --- /dev/null +++ b/Dockerfile.ubuntu @@ -0,0 +1,28 @@ +# Ubuntu 24.04 environment that matches CI toolchain and fixture tests. +FROM ubuntu:24.04 AS deps +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + binutils \ + binutils-mingw-w64-i686 \ + cmake \ + file \ + g++-multilib \ + gcc-mingw-w64-i686 \ + gdb \ + ninja-build \ + unzip \ + wget \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /wibo + +FROM deps AS dev +COPY . /wibo + +ARG BUILD_TYPE=Debug +# Configure default build folders so docker build can cache compilation layers if desired. +RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILD_TESTING=ON -DWIBO_ENABLE_FIXTURE_TESTS=ON \ + && cmake --build build + +ENTRYPOINT ["/bin/bash"] diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index a8ba573..27e422a 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include "strutil.h" @@ -3067,6 +3068,41 @@ namespace kernel32 { return FALSE; // We're not multibyte (yet?) } + BOOL WIN_FUNC IsDBCSLeadByteEx(unsigned int CodePage, BYTE TestChar) { + DEBUG_LOG("IsDBCSLeadByteEx(cp=%u, ch=%u)\n", CodePage, TestChar); + + const auto inRanges = [TestChar](std::initializer_list> ranges) -> BOOL { + for (const auto &range : ranges) { + if (TestChar >= range.first && TestChar <= range.second) { + return TRUE; + } + } + return FALSE; + }; + + constexpr unsigned int CP_ACP = 0; + constexpr unsigned int CP_OEMCP = 1; + constexpr unsigned int CP_MACCP = 2; + constexpr unsigned int CP_THREAD_ACP = 3; + + if (CodePage == CP_ACP || CodePage == CP_OEMCP || CodePage == CP_MACCP || CodePage == CP_THREAD_ACP) { + return FALSE; + } + + switch (CodePage) { + case 932: // Japanese Shift-JIS + return inRanges({{0x81, 0x9F}, {0xE0, 0xFC}}); + case 936: // Simplified Chinese (GBK) + case 949: // Korean + case 950: // Traditional Chinese (Big5) + case 1361: // Johab + return inRanges({{0x81, 0xFE}}); + default: + wibo::lastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + } + constexpr unsigned int LCMAP_LOWERCASE = 0x00000100; constexpr unsigned int LCMAP_UPPERCASE = 0x00000200; constexpr unsigned int LCMAP_SORTKEY = 0x00000400; @@ -3437,6 +3473,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "EnumSystemLocalesA") == 0) return (void *) kernel32::EnumSystemLocalesA; if (strcmp(name, "GetUserDefaultLCID") == 0) return (void *) kernel32::GetUserDefaultLCID; if (strcmp(name, "IsDBCSLeadByte") == 0) return (void *) kernel32::IsDBCSLeadByte; + if (strcmp(name, "IsDBCSLeadByteEx") == 0) return (void *) kernel32::IsDBCSLeadByteEx; // synchapi.h if (strcmp(name, "InitializeCriticalSection") == 0) return (void *) kernel32::InitializeCriticalSection; diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index b2db453..8617ca2 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -4,10 +4,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -18,10 +20,12 @@ #include #include #include +#include #include #include #include #include +#include #include "files.h" #include "processes.h" #include "strutil.h" @@ -38,6 +42,69 @@ namespace msvcrt { char** __initenv; uint16_t** __winitenv; uint16_t* _wpgmptr; + static unsigned int mbCurMaxValue = 1; + + struct IOBProxy { + char *_ptr; + int _cnt; + char *_base; + int _flag; + int _file; + int _charbuf; + int _bufsiz; + char *_tmpfname; + }; + + using UserMathErrHandler = int (*)(struct _exception *); + + UserMathErrHandler &mathErrHandler() { + static UserMathErrHandler handler = nullptr; + return handler; + } + + std::mutex &mathErrMutex() { + static std::mutex mutex; + return mutex; + } + + IOBProxy *standardIobEntries() { + static IOBProxy entries[3] = {}; + return entries; + } + + std::unordered_map &iobMapping() { + static std::unordered_map mapping; + return mapping; + } + + std::once_flag &iobInitFlag() { + static std::once_flag flag; + return flag; + } + + void initializeIobMapping() { + std::call_once(iobInitFlag(), []() { + auto &mapping = iobMapping(); + IOBProxy *entries = standardIobEntries(); + mapping.emplace(static_cast(&entries[0]), stdin); + mapping.emplace(static_cast(&entries[1]), stdout); + mapping.emplace(static_cast(&entries[2]), stderr); + }); + } + + FILE *mapToHostFile(FILE *stream) { + initializeIobMapping(); + auto &mapping = iobMapping(); + auto it = mapping.find(stream); + if (it != mapping.end()) { + return it->second; + } + return stream; + } + + void refreshMbCurMax() { + mbCurMaxValue = static_cast(MB_CUR_MAX); + } namespace { struct DllOnExitTable { @@ -336,9 +403,13 @@ namespace msvcrt { return std::getenv(varname); } - char* WIN_ENTRY setlocale(int category, const char *locale){ - return std::setlocale(category, locale); +char* WIN_ENTRY setlocale(int category, const char *locale){ + char *result = std::setlocale(category, locale); + if (result) { + refreshMbCurMax(); } + return result; +} int WIN_ENTRY _wdupenv_s(uint16_t **buffer, size_t *numberOfElements, const uint16_t *varname){ if (buffer) { @@ -435,6 +506,10 @@ namespace msvcrt { return std::calloc(count, size); } + void* WIN_ENTRY realloc(void *ptr, size_t size) { + return std::realloc(ptr, size); + } + void* WIN_ENTRY _malloc_crt(size_t size) { return std::malloc(size); } @@ -807,11 +882,113 @@ namespace msvcrt { return -1; } + void WIN_ENTRY __setusermatherr(UserMathErrHandler handler) { + std::lock_guard lock(mathErrMutex()); + mathErrHandler() = handler; + } + + void WIN_ENTRY _cexit() { + DEBUG_LOG("_cexit()\n"); + std::fflush(nullptr); + } + + static FILE *resolveFileStream(FILE *stream) { + if (!stream) { + return nullptr; + } + return mapToHostFile(stream); + } + + int WIN_ENTRY vfprintf(FILE *stream, const char *format, va_list args) { + if (!format || !stream) { + errno = EINVAL; + return -1; + } + FILE *native = resolveFileStream(stream); + if (!native) { + errno = EINVAL; + return -1; + } + va_list argsCopy; + va_copy(argsCopy, args); + int result = std::vfprintf(native, format, argsCopy); + va_end(argsCopy); + return result; + } + + int WIN_ENTRY fprintf(FILE *stream, const char *format, ...) { + va_list args; + va_start(args, format); + int result = msvcrt::vfprintf(stream, format, args); + va_end(args); + return result; + } + + int WIN_ENTRY fputc(int ch, FILE *stream) { + if (!stream) { + errno = EINVAL; + return EOF; + } + FILE *native = resolveFileStream(stream); + if (!native) { + errno = EINVAL; + return EOF; + } + return std::fputc(ch, native); + } + + size_t WIN_ENTRY fwrite(const void *buffer, size_t size, size_t count, FILE *stream) { + if (!buffer || !stream) { + errno = EINVAL; + return 0; + } + FILE *native = resolveFileStream(stream); + if (!native) { + errno = EINVAL; + return 0; + } + return std::fwrite(buffer, size, count, native); + } + + char *WIN_ENTRY strerror(int errnum) { + return std::strerror(errnum); + } + + char *WIN_ENTRY strchr(const char *str, int character) { + return const_cast(std::strchr(str, character)); + } + + struct lconv *WIN_ENTRY localeconv() { + return std::localeconv(); + } + + using SignalHandler = void (*)(int); + + SignalHandler WIN_ENTRY signal(int sig, SignalHandler handler) { + return std::signal(sig, handler); + } + + size_t WIN_ENTRY wcslen(const uint16_t *str) { + return wstrlen(str); + } + static void abort_and_log(const char *reason) { DEBUG_LOG("Runtime abort: %s\n", reason ? reason : ""); std::abort(); } + void WIN_ENTRY abort() { + abort_and_log("abort"); + } + + int WIN_ENTRY atoi(const char *str) { + if (!str) { + errno = EINVAL; + return 0; + } + return std::atoi(str); + } + int WIN_ENTRY _amsg_exit(int reason) { DEBUG_LOG("_amsg_exit(%d)\n", reason); abort_and_log("_amsg_exit"); @@ -1299,6 +1476,8 @@ static void *resolveByName(const char *name) { if (strcmp(name, "__getmainargs") == 0) return (void*)msvcrt::__getmainargs; if (strcmp(name, "__wgetmainargs") == 0) return (void*)msvcrt::__wgetmainargs; if (strcmp(name, "setlocale") == 0) return (void*)msvcrt::setlocale; + if (strcmp(name, "__mb_cur_max") == 0) return (void *)&msvcrt::mbCurMaxValue; + if (strcmp(name, "__setusermatherr") == 0) return (void *)msvcrt::__setusermatherr; if (strcmp(name, "_wdupenv_s") == 0) return (void*)msvcrt::_wdupenv_s; if (strcmp(name, "strlen") == 0) return (void *)msvcrt::strlen; if (strcmp(name, "strcmp") == 0) return (void *)msvcrt::strcmp; @@ -1373,6 +1552,19 @@ static void *resolveByName(const char *name) { if (strcmp(name, "_wspawnvp") == 0) return (void*)msvcrt::_wspawnvp; if (strcmp(name, "_wunlink") == 0) return (void*)msvcrt::_wunlink; if (strcmp(name, "_wfullpath") == 0) return (void*)msvcrt::_wfullpath; + if (strcmp(name, "_cexit") == 0) return (void*)msvcrt::_cexit; + if (strcmp(name, "_iob") == 0) return (void*)msvcrt::standardIobEntries(); + if (strcmp(name, "abort") == 0) return (void*)msvcrt::abort; + if (strcmp(name, "atoi") == 0) return (void*)msvcrt::atoi; + if (strcmp(name, "fprintf") == 0) return (void*)msvcrt::fprintf; + if (strcmp(name, "vfprintf") == 0) return (void*)msvcrt::vfprintf; + if (strcmp(name, "fputc") == 0) return (void*)msvcrt::fputc; + if (strcmp(name, "fwrite") == 0) return (void*)msvcrt::fwrite; + if (strcmp(name, "localeconv") == 0) return (void*)msvcrt::localeconv; + if (strcmp(name, "signal") == 0) return (void*)msvcrt::signal; + if (strcmp(name, "strchr") == 0) return (void*)msvcrt::strchr; + if (strcmp(name, "strerror") == 0) return (void*)msvcrt::strerror; + if (strcmp(name, "wcslen") == 0) return (void*)msvcrt::wcslen; return nullptr; } diff --git a/module_registry.cpp b/module_registry.cpp index 2b8e9c5..f0f6730 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -88,8 +89,11 @@ static std::string makeStubKey(const char *dllName, const char *funcName) { } static void stubBase(int index) { - printf("Unhandled function %s (%s)\n", stubFuncNames[index].c_str(), stubDlls[index].c_str()); - exit(1); + const char *func = stubFuncNames[index].empty() ? "" : stubFuncNames[index].c_str(); + const char *dll = stubDlls[index].empty() ? "" : stubDlls[index].c_str(); + fprintf(stderr, "wibo: call reached missing import %s from %s\n", func, dll); + fflush(stderr); + abort(); } void (*stubFuncs[MAX_STUBS])(void) = { From bc33bae65943c339f7a11ed482436b3f32bf3c06 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 28 Sep 2025 17:00:38 -0600 Subject: [PATCH 21/28] Formatting, fixes, deduplication --- dll/advapi32.cpp | 27 ++-- dll/crt.cpp | 13 +- dll/kernel32.cpp | 32 +--- dll/msvcrt.cpp | 26 +--- dll/psapi.cpp | 70 ++++----- dll/rpcrt4.cpp | 41 ++---- dll/version.cpp | 25 ++-- files.cpp | 6 +- module_registry.cpp | 351 +++++++++++++++++++------------------------- strutil.cpp | 37 +++++ strutil.h | 6 + 11 files changed, 291 insertions(+), 343 deletions(-) diff --git a/dll/advapi32.cpp b/dll/advapi32.cpp index d74bbee..0263be0 100644 --- a/dll/advapi32.cpp +++ b/dll/advapi32.cpp @@ -1,7 +1,8 @@ #include "common.h" #include "handles.h" +#include "strutil.h" +#include #include -#include #include namespace { @@ -294,7 +295,8 @@ namespace advapi32 { return TRUE; } - BOOL WIN_FUNC CryptAcquireContextW(void** phProv, const wchar_t* pszContainer, const wchar_t* pszProvider, unsigned int dwProvType, unsigned int dwFlags){ + BOOL WIN_FUNC CryptAcquireContextW(void **phProv, const uint16_t *pszContainer, const uint16_t *pszProvider, + unsigned int dwProvType, unsigned int dwFlags) { DEBUG_LOG("STUB: CryptAcquireContextW(%p)\n", phProv); // to quote the guy above me: screw them for now @@ -494,7 +496,7 @@ namespace advapi32 { if (TokenInformationClass == TokenUserClass) { constexpr size_t sidSize = sizeof(Sid); constexpr size_t tokenUserSize = sizeof(TokenUserData); - const unsigned int required = static_cast(tokenUserSize + sidSize); + const auto required = static_cast(tokenUserSize + sidSize); *ReturnLength = required; if (!TokenInformation || TokenInformationLength < required) { wibo::lastError = ERROR_INSUFFICIENT_BUFFER; @@ -544,8 +546,11 @@ namespace advapi32 { return FALSE; } - BOOL WIN_FUNC LookupAccountSidW(const wchar_t *lpSystemName, const void *sidPointer, wchar_t *Name, unsigned long *cchName, wchar_t *ReferencedDomainName, unsigned long *cchReferencedDomainName, SID_NAME_USE *peUse) { - DEBUG_LOG("LookupAccountSidW(system=%ls, sid=%p)\n", lpSystemName ? lpSystemName : L"(null)", sidPointer); + BOOL WIN_FUNC LookupAccountSidW(const uint16_t *lpSystemName, const void *sidPointer, uint16_t *Name, + unsigned long *cchName, uint16_t *ReferencedDomainName, + unsigned long *cchReferencedDomainName, SID_NAME_USE *peUse) { + std::string systemName = lpSystemName ? wideStringToString(lpSystemName) : std::string("(null)"); + DEBUG_LOG("LookupAccountSidW(system=%s, sid=%p)\n", systemName.c_str(), sidPointer); (void) lpSystemName; // Only local lookup supported if (!sidPointer || !cchName || !cchReferencedDomainName || !peUse) { wibo::lastError = ERROR_INVALID_PARAMETER; @@ -556,18 +561,18 @@ namespace advapi32 { wibo::lastError = ERROR_NONE_MAPPED; return FALSE; } - const wchar_t *accountName = L"SYSTEM"; - const wchar_t *domainName = L"NT AUTHORITY"; - unsigned long requiredAccount = static_cast(std::wcslen(accountName) + 1); - unsigned long requiredDomain = static_cast(std::wcslen(domainName) + 1); + static constexpr uint16_t accountName[] = {u'S', u'Y', u'S', u'T', u'E', u'M', u'\0'}; + static constexpr uint16_t domainName[] = {u'N', u'T', u' ', u'A', u'U', u'T', u'H', u'O', u'R', u'I', u'T', u'Y', u'\0'}; + unsigned long requiredAccount = wstrlen(accountName) + 1; + unsigned long requiredDomain = wstrlen(domainName) + 1; if (!Name || *cchName < requiredAccount || !ReferencedDomainName || *cchReferencedDomainName < requiredDomain) { *cchName = requiredAccount; *cchReferencedDomainName = requiredDomain; wibo::lastError = ERROR_INSUFFICIENT_BUFFER; return FALSE; } - std::wmemcpy(Name, accountName, requiredAccount); - std::wmemcpy(ReferencedDomainName, domainName, requiredDomain); + std::copy_n(accountName, requiredAccount, Name); + std::copy_n(domainName, requiredDomain, ReferencedDomainName); *peUse = SidTypeWellKnownGroup; *cchName = requiredAccount - 1; *cchReferencedDomainName = requiredDomain - 1; diff --git a/dll/crt.cpp b/dll/crt.cpp index f7208cd..99b8b3a 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -5,17 +5,18 @@ #include #include #include -#include #include +#include typedef void (*_PVFV)(); typedef int (*_PIFV)(); -typedef void (*_invalid_parameter_handler)(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, uintptr_t); +typedef void (*_invalid_parameter_handler)(const uint16_t *, const uint16_t *, const uint16_t *, unsigned int, + uintptr_t); extern char **environ; namespace msvcrt { - int WIN_ENTRY puts(const char *str); +int WIN_ENTRY puts(const char *str); } typedef enum _crt_app_type { @@ -195,11 +196,13 @@ void *WIN_ENTRY __acrt_iob_func(unsigned int index) { return nullptr; } -int WIN_ENTRY __stdio_common_vfprintf(unsigned long long /*options*/, FILE *stream, const char *format, void * /*locale*/, va_list args) { +int WIN_ENTRY __stdio_common_vfprintf(unsigned long long /*options*/, FILE *stream, const char *format, + void * /*locale*/, va_list args) { return vfprintf(stream, format, args); } -int WIN_ENTRY __stdio_common_vsprintf(unsigned long long /*options*/, char *buffer, size_t len, const char *format, void * /*locale*/, va_list args) { +int WIN_ENTRY __stdio_common_vsprintf(unsigned long long /*options*/, char *buffer, size_t len, const char *format, + void * /*locale*/, va_list args) { if (!buffer || !format) return -1; int result = vsnprintf(buffer, len, format, args); diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 27e422a..574cafa 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -349,37 +349,11 @@ namespace kernel32 { std::memset(lpSystemInfo, 0, sizeof(*lpSystemInfo)); - WORD architecture = PROCESSOR_ARCHITECTURE_UNKNOWN; - DWORD processorType = 0; - WORD processorLevel = 0; - -#if defined(__x86_64__) || defined(_M_X64) - architecture = PROCESSOR_ARCHITECTURE_AMD64; - processorType = PROCESSOR_AMD_X8664; - processorLevel = 6; -#elif defined(__i386__) || defined(_M_IX86) - architecture = PROCESSOR_ARCHITECTURE_INTEL; - processorType = PROCESSOR_INTEL_PENTIUM; - processorLevel = 6; -#elif defined(__aarch64__) - architecture = PROCESSOR_ARCHITECTURE_ARM64; - processorType = 0; - processorLevel = 8; -#elif defined(__arm__) - architecture = PROCESSOR_ARCHITECTURE_ARM; - processorType = 0; - processorLevel = 7; -#else - architecture = PROCESSOR_ARCHITECTURE_UNKNOWN; - processorType = 0; - processorLevel = 0; -#endif - - lpSystemInfo->wProcessorArchitecture = architecture; + lpSystemInfo->wProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL; lpSystemInfo->wReserved = 0; lpSystemInfo->dwOemId = lpSystemInfo->wProcessorArchitecture; - lpSystemInfo->dwProcessorType = processorType; - lpSystemInfo->wProcessorLevel = processorLevel; + lpSystemInfo->dwProcessorType = PROCESSOR_INTEL_PENTIUM; + lpSystemInfo->wProcessorLevel = 6; // Pentium lpSystemInfo->wProcessorRevision = 0; long pageSize = sysconf(_SC_PAGESIZE); diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 8617ca2..b8f17d0 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -163,10 +163,8 @@ namespace msvcrt { } } if (!exeDir.empty()) { - std::string loweredResult = result; - std::string loweredExe = exeDir; - std::transform(loweredResult.begin(), loweredResult.end(), loweredResult.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); - std::transform(loweredExe.begin(), loweredExe.end(), loweredExe.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); + std::string loweredResult = stringToLower(result); + std::string loweredExe = stringToLower(exeDir); bool present = false; size_t start = 0; while (start <= loweredResult.size()) { @@ -648,18 +646,6 @@ char* WIN_ENTRY setlocale(int category, const char *locale){ return 0; } - static uint16_t toLower(uint16_t ch) { - if (ch >= 'A' && ch <= 'Z') { - return static_cast(ch + ('a' - 'A')); - } - wchar_t wide = static_cast(ch); - wchar_t lowered = std::towlower(wide); - if (lowered < 0 || lowered > 0xFFFF) { - return ch; - } - return static_cast(lowered); - } - int WIN_ENTRY _wcsicmp(const uint16_t *lhs, const uint16_t *rhs) { if (lhs == rhs) { return 0; @@ -672,15 +658,15 @@ char* WIN_ENTRY setlocale(int category, const char *locale){ } while (*lhs && *rhs) { - uint16_t a = toLower(*lhs++); - uint16_t b = toLower(*rhs++); + uint16_t a = wcharToLower(*lhs++); + uint16_t b = wcharToLower(*rhs++); if (a != b) { return static_cast(a) - static_cast(b); } } - uint16_t a = toLower(*lhs); - uint16_t b = toLower(*rhs); + uint16_t a = wcharToLower(*lhs); + uint16_t b = wcharToLower(*rhs); return static_cast(a) - static_cast(b); } diff --git a/dll/psapi.cpp b/dll/psapi.cpp index bd7e4ff..70657f3 100644 --- a/dll/psapi.cpp +++ b/dll/psapi.cpp @@ -2,55 +2,55 @@ #include "handles.h" namespace psapi { - BOOL WIN_FUNC EnumProcessModules(HANDLE hProcess, HMODULE *lphModule, DWORD cb, DWORD *lpcbNeeded) { - DEBUG_LOG("EnumProcessModules(hProcess=%p, cb=%u)\n", hProcess, cb); +BOOL WIN_FUNC EnumProcessModules(HANDLE hProcess, HMODULE *lphModule, DWORD cb, DWORD *lpcbNeeded) { + DEBUG_LOG("EnumProcessModules(hProcess=%p, cb=%u)\n", hProcess, cb); - bool recognizedHandle = false; - if (hProcess == (HANDLE)0xFFFFFFFF) { - recognizedHandle = true; - } else { - auto data = handles::dataFromHandle(hProcess, false); - recognizedHandle = (data.type == handles::TYPE_PROCESS); - } - if (!recognizedHandle) { - wibo::lastError = ERROR_ACCESS_DENIED; - return FALSE; - } - - HMODULE currentModule = wibo::mainModule ? reinterpret_cast(wibo::mainModule->imageBuffer) : nullptr; - DWORD required = currentModule ? sizeof(HMODULE) : 0; - if (lpcbNeeded) { - *lpcbNeeded = required; - } + bool recognizedHandle = false; + if (hProcess == (HANDLE)0xFFFFFFFF) { + recognizedHandle = true; + } else { + auto data = handles::dataFromHandle(hProcess, false); + recognizedHandle = (data.type == handles::TYPE_PROCESS); + } + if (!recognizedHandle) { + wibo::lastError = ERROR_ACCESS_DENIED; + return FALSE; + } - if (required == 0) { - wibo::lastError = ERROR_INVALID_HANDLE; - return FALSE; - } + HMODULE currentModule = wibo::mainModule ? reinterpret_cast(wibo::mainModule->imageBuffer) : nullptr; + DWORD required = currentModule ? sizeof(HMODULE) : 0; + if (lpcbNeeded) { + *lpcbNeeded = required; + } - if (!lphModule || cb < required) { - wibo::lastError = ERROR_INSUFFICIENT_BUFFER; - return FALSE; - } + if (required == 0) { + wibo::lastError = ERROR_INVALID_HANDLE; + return FALSE; + } - lphModule[0] = currentModule; - wibo::lastError = ERROR_SUCCESS; - return TRUE; + if (!lphModule || cb < required) { + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return FALSE; } + + lphModule[0] = currentModule; + wibo::lastError = ERROR_SUCCESS; + return TRUE; } +} // namespace psapi static void *resolveByName(const char *name) { - DEBUG_LOG("psapi resolveByName(%s)\n", name); - if (strcmp(name, "EnumProcessModules") == 0) return (void *) psapi::EnumProcessModules; - if (strcmp(name, "K32EnumProcessModules") == 0) return (void *) psapi::EnumProcessModules; + if (strcmp(name, "EnumProcessModules") == 0) + return (void *)psapi::EnumProcessModules; + if (strcmp(name, "K32EnumProcessModules") == 0) + return (void *)psapi::EnumProcessModules; return nullptr; } static void *resolveByOrdinal(uint16_t ordinal) { - DEBUG_LOG("psapi resolveByOrdinal(%u)\n", ordinal); switch (ordinal) { case 4: // EnumProcessModules - return (void *) psapi::EnumProcessModules; + return (void *)psapi::EnumProcessModules; default: return nullptr; } diff --git a/dll/rpcrt4.cpp b/dll/rpcrt4.cpp index 4a77962..9ee967f 100644 --- a/dll/rpcrt4.cpp +++ b/dll/rpcrt4.cpp @@ -128,14 +128,8 @@ BindingHandleData *getBinding(RPC_BINDING_HANDLE handle) { extern "C" { -RPC_STATUS WIN_FUNC RpcStringBindingComposeW( - RPC_WSTR objUuid, - RPC_WSTR protSeq, - RPC_WSTR networkAddr, - RPC_WSTR endpoint, - RPC_WSTR options, - RPC_WSTR *stringBinding -) { +RPC_STATUS WIN_FUNC RpcStringBindingComposeW(RPC_WSTR objUuid, RPC_WSTR protSeq, RPC_WSTR networkAddr, + RPC_WSTR endpoint, RPC_WSTR options, RPC_WSTR *stringBinding) { BindingComponents components; components.objectUuid = toU16(objUuid); components.protocolSequence = toU16(protSeq); @@ -187,15 +181,10 @@ RPC_STATUS WIN_FUNC RpcBindingFromStringBindingW(RPC_WSTR stringBinding, RPC_BIN return RPC_S_OK; } -RPC_STATUS WIN_FUNC RpcBindingSetAuthInfoExW( - RPC_BINDING_HANDLE binding, - RPC_WSTR serverPrincName, - unsigned long authnLevel, - unsigned long authnSvc, - RPC_AUTH_IDENTITY_HANDLE authIdentity, - unsigned long authzSvc, - RPC_SECURITY_QOS *securityQos -) { +RPC_STATUS WIN_FUNC RpcBindingSetAuthInfoExW(RPC_BINDING_HANDLE binding, RPC_WSTR serverPrincName, + unsigned long authnLevel, unsigned long authnSvc, + RPC_AUTH_IDENTITY_HANDLE authIdentity, unsigned long authzSvc, + RPC_SECURITY_QOS *securityQos) { BindingHandleData *data = getBinding(binding); if (!data) { return RPC_S_INVALID_BINDING; @@ -260,9 +249,7 @@ NdrClientCall2(PMIDL_STUB_DESC stubDescriptor, PFORMAT_STRING format, ...) { return result; } -void WIN_FUNC NdrServerCall2(PRPC_MESSAGE message) { - DEBUG_LOG("STUB: NdrServerCall2 message=%p\n", message); -} +void WIN_FUNC NdrServerCall2(PRPC_MESSAGE message) { DEBUG_LOG("STUB: NdrServerCall2 message=%p\n", message); } } // extern "C" @@ -270,19 +257,19 @@ namespace { void *resolveByName(const char *name) { if (std::strcmp(name, "RpcStringBindingComposeW") == 0) - return (void *) RpcStringBindingComposeW; + return (void *)RpcStringBindingComposeW; if (std::strcmp(name, "RpcBindingFromStringBindingW") == 0) - return (void *) RpcBindingFromStringBindingW; + return (void *)RpcBindingFromStringBindingW; if (std::strcmp(name, "RpcStringFreeW") == 0) - return (void *) RpcStringFreeW; + return (void *)RpcStringFreeW; if (std::strcmp(name, "RpcBindingFree") == 0) - return (void *) RpcBindingFree; + return (void *)RpcBindingFree; if (std::strcmp(name, "RpcBindingSetAuthInfoExW") == 0) - return (void *) RpcBindingSetAuthInfoExW; + return (void *)RpcBindingSetAuthInfoExW; if (std::strcmp(name, "NdrClientCall2") == 0) - return (void *) NdrClientCall2; + return (void *)NdrClientCall2; if (std::strcmp(name, "NdrServerCall2") == 0) - return (void *) NdrServerCall2; + return (void *)NdrServerCall2; return nullptr; } diff --git a/dll/version.cpp b/dll/version.cpp index 06521c7..2bbefcf 100644 --- a/dll/version.cpp +++ b/dll/version.cpp @@ -3,8 +3,6 @@ #include "resources.h" #include "strutil.h" -#include -#include #include #include #include @@ -15,7 +13,7 @@ namespace { constexpr uint32_t RT_VERSION = 16; -static uint16_t read_u16(const uint8_t *ptr) { +static uint16_t readU16(const uint8_t *ptr) { return static_cast(ptr[0] | (ptr[1] << 8)); } @@ -49,9 +47,9 @@ static bool parseVersionBlock(const uint8_t *block, size_t available, VersionBlo return false; } - uint16_t totalLength = read_u16(block); - uint16_t valueLength = read_u16(block + sizeof(uint16_t)); - uint16_t type = read_u16(block + sizeof(uint16_t) * 2); + uint16_t totalLength = readU16(block); + uint16_t valueLength = readU16(block + sizeof(uint16_t)); + uint16_t type = readU16(block + sizeof(uint16_t) * 2); if (totalLength == 0 || totalLength > available) { DEBUG_LOG("invalid totalLength=%u available=%zu\n", totalLength, available); return false; @@ -61,7 +59,7 @@ static bool parseVersionBlock(const uint8_t *block, size_t available, VersionBlo const uint8_t *cursor = block + sizeof(uint16_t) * 3; out.key.clear(); while (cursor + sizeof(uint16_t) <= end) { - uint16_t ch = read_u16(cursor); + uint16_t ch = readU16(cursor); cursor += sizeof(uint16_t); if (!ch) break; @@ -101,11 +99,6 @@ static bool parseVersionBlock(const uint8_t *block, size_t available, VersionBlo return true; } -static std::string toLowerCopy(std::string str) { - std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); - return str; -} - static bool queryVersionBlock(const uint8_t *block, size_t available, const std::vector &segments, size_t depth, @@ -126,7 +119,7 @@ static bool queryVersionBlock(const uint8_t *block, size_t available, return true; } - const std::string targetLower = toLowerCopy(segments[depth]); + const std::string targetLower = stringToLower(segments[depth]); const uint8_t *cursor = view.childrenPtr; const uint8_t *end = view.childrenPtr + view.childrenBytes; @@ -137,12 +130,12 @@ static bool queryVersionBlock(const uint8_t *block, size_t available, break; if (child.totalLength == 0) break; - std::string childKeyLower = toLowerCopy(narrowKey(child.key)); + std::string childKeyLower = stringToLower(narrowKey(child.key)); if (childKeyLower == targetLower) { if (queryVersionBlock(childStart, child.totalLength, segments, depth + 1, outPtr, outLen, outType)) return true; } - size_t offset = static_cast(child.totalLength); + const auto offset = static_cast(child.totalLength); cursor = childStart + align4(offset); if (cursor <= childStart || cursor > end) break; @@ -252,7 +245,7 @@ static unsigned int VerQueryValueImpl(const void *pBlock, const std::string &sub return 0; const uint8_t *base = static_cast(pBlock); - uint16_t totalLength = read_u16(base); + uint16_t totalLength = readU16(base); if (totalLength < 6) return 0; diff --git a/files.cpp b/files.cpp index 579538b..034ca98 100644 --- a/files.cpp +++ b/files.cpp @@ -1,8 +1,8 @@ #include "common.h" #include "files.h" #include "handles.h" +#include "strutil.h" #include -#include #include #include #include @@ -182,13 +182,13 @@ namespace files { return std::nullopt; } std::string needle = filename; - std::transform(needle.begin(), needle.end(), needle.begin(), [](unsigned char ch) { return std::tolower(ch); }); + toLowerInPlace(needle); for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) { if (ec) { break; } std::string candidate = entry.path().filename().string(); - std::transform(candidate.begin(), candidate.end(), candidate.begin(), [](unsigned char ch) { return std::tolower(ch); }); + toLowerInPlace(candidate); if (candidate == needle) { return canonicalPath(entry.path()); } diff --git a/module_registry.cpp b/module_registry.cpp index f0f6730..926f647 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -50,45 +49,29 @@ struct PEExportDirectory { uint32_t addressOfNameOrdinals; }; -#define FOR_256_3(a, b, c, d) FOR_ITER((a << 6 | b << 4 | c << 2 | d)) -#define FOR_256_2(a, b) \ - FOR_256_3(a, b, 0, 0) \ - FOR_256_3(a, b, 0, 1) \ - FOR_256_3(a, b, 0, 2) \ - FOR_256_3(a, b, 0, 3) FOR_256_3(a, b, 1, 0) FOR_256_3(a, b, 1, 1) FOR_256_3(a, b, 1, 2) FOR_256_3(a, b, 1, 3) \ - FOR_256_3(a, b, 2, 0) FOR_256_3(a, b, 2, 1) FOR_256_3(a, b, 2, 2) FOR_256_3(a, b, 2, 3) FOR_256_3(a, b, 3, 0) \ - FOR_256_3(a, b, 3, 1) FOR_256_3(a, b, 3, 2) FOR_256_3(a, b, 3, 3) -#define FOR_256 \ - FOR_256_2(0, 0) \ - FOR_256_2(0, 1) \ - FOR_256_2(0, 2) \ - FOR_256_2(0, 3) FOR_256_2(1, 0) FOR_256_2(1, 1) FOR_256_2(1, 2) FOR_256_2(1, 3) FOR_256_2(2, 0) FOR_256_2(2, 1) \ - FOR_256_2(2, 2) FOR_256_2(2, 3) FOR_256_2(3, 0) FOR_256_2(3, 1) FOR_256_2(3, 2) FOR_256_2(3, 3) - -static constexpr size_t MAX_STUBS = 0x100; -static int stubIndex = 0; -static std::array stubDlls; -static std::array stubFuncNames; -static std::unordered_map stubCache; - -static std::string makeStubKey(const char *dllName, const char *funcName) { +using StubFuncType = void (*)(); +constexpr size_t MAX_STUBS = 0x100; +size_t stubIndex = 0; +std::array stubDlls; +std::array stubFuncNames; +std::unordered_map stubCache; + +std::string makeStubKey(const char *dllName, const char *funcName) { std::string key; if (dllName) { key.assign(dllName); - std::transform(key.begin(), key.end(), key.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); + toLowerInPlace(key); } key.push_back(':'); if (funcName) { std::string func(funcName); - std::transform(func.begin(), func.end(), func.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); + toLowerInPlace(func); key += func; } return key; } -static void stubBase(int index) { +void stubBase(size_t index) { const char *func = stubFuncNames[index].empty() ? "" : stubFuncNames[index].c_str(); const char *dll = stubDlls[index].empty() ? "" : stubDlls[index].c_str(); fprintf(stderr, "wibo: call reached missing import %s from %s\n", func, dll); @@ -96,47 +79,42 @@ static void stubBase(int index) { abort(); } -void (*stubFuncs[MAX_STUBS])(void) = { -#define FOR_ITER(i) []() { stubBase(i); }, - FOR_256 -#undef FOR_ITER -}; +template void stubThunk() { stubBase(Index); } -#undef FOR_256_3 -#undef FOR_256_2 -#undef FOR_256 +template +constexpr std::array makeStubTable(std::index_sequence) { + return {{stubThunk...}}; +} -void *resolveMissingFuncName(const char *dllName, const char *funcName) { +constexpr auto stubFuncs = makeStubTable(std::make_index_sequence{}); + +StubFuncType resolveMissingFuncName(const char *dllName, const char *funcName) { DEBUG_LOG("Missing function: %s (%s)\n", dllName, funcName); std::string key = makeStubKey(dllName, funcName); auto existing = stubCache.find(key); if (existing != stubCache.end()) { return existing->second; } - if (stubIndex >= static_cast(MAX_STUBS)) { - fprintf(stderr, - "Too many missing functions encountered (>%zu). Last failure: %s (%s)\n", - MAX_STUBS, funcName, dllName); - exit(1); + if (stubIndex >= MAX_STUBS) { + fprintf(stderr, "wibo: too many missing functions encountered (>%zu). Last failure: %s (%s)\n", MAX_STUBS, + funcName, dllName); + fflush(stderr); + abort(); } stubFuncNames[stubIndex] = funcName ? funcName : ""; stubDlls[stubIndex] = dllName ? dllName : ""; - void *stub = (void *)stubFuncs[stubIndex]; + StubFuncType stub = stubFuncs[stubIndex]; stubCache.emplace(std::move(key), stub); stubIndex++; return stub; } -void *resolveMissingFuncOrdinal(const char *dllName, uint16_t ordinal) { +StubFuncType resolveMissingFuncOrdinal(const char *dllName, uint16_t ordinal) { char buf[16]; sprintf(buf, "%d", ordinal); return resolveMissingFuncName(dllName, buf); } -} // namespace - -namespace { - using ModulePtr = std::unique_ptr; struct ModuleRegistry { @@ -152,23 +130,45 @@ struct ModuleRegistry { std::unordered_set pinnedModules; }; -ModuleRegistry ®istry() { - static ModuleRegistry reg; - return reg; -} +struct LockedRegistry { + ModuleRegistry *reg; + std::unique_lock lock; -std::string toLowerCopy(const std::string &value) { - std::string out = value; - std::transform(out.begin(), out.end(), out.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return out; + LockedRegistry(ModuleRegistry ®istryRef, std::unique_lock &&guard) + : reg(®istryRef), lock(std::move(guard)) {} + + LockedRegistry(const LockedRegistry &) = delete; + LockedRegistry &operator=(const LockedRegistry &) = delete; + LockedRegistry(LockedRegistry &&) = default; + LockedRegistry &operator=(LockedRegistry &&) = default; + + [[nodiscard]] ModuleRegistry &get() const { return *reg; } + ModuleRegistry *operator->() const { return reg; } + ModuleRegistry &operator*() const { return *reg; } +}; + +void registerBuiltinModule(ModuleRegistry ®, const wibo::Module *module); + +LockedRegistry registry() { + static ModuleRegistry reg; + std::unique_lock guard(reg.mutex); + if (!reg.initialized) { + reg.initialized = true; + const wibo::Module *builtins[] = { + &lib_advapi32, &lib_bcrypt, &lib_crt, &lib_kernel32, &lib_lmgr, &lib_mscoree, &lib_msvcrt, + &lib_ntdll, &lib_ole32, &lib_rpcrt4, &lib_user32, &lib_vcruntime, &lib_version, nullptr, + }; + for (const wibo::Module **module = builtins; *module; ++module) { + registerBuiltinModule(reg, *module); + } + } + return {reg, std::move(guard)}; } std::string normalizeAlias(const std::string &value) { std::string out = value; std::replace(out.begin(), out.end(), '/', '\\'); - std::transform(out.begin(), out.end(), out.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); + toLowerInPlace(out); return out; } @@ -211,7 +211,7 @@ std::vector candidateModuleNames(const ParsedModuleName &parsed) { std::string normalizedBaseKey(const ParsedModuleName &parsed) { if (parsed.base.empty()) { - return std::string(); + return {}; } std::string base = parsed.base; if (!parsed.hasExtension && !parsed.endsWithDot) { @@ -231,48 +231,36 @@ std::optional combineAndFind(const std::filesystem::path return files::findCaseInsensitiveFile(directory, filename); } -std::vector collectSearchDirectories(bool alteredSearchPath) { +std::vector collectSearchDirectories(ModuleRegistry ®, bool alteredSearchPath) { std::vector dirs; std::unordered_set seen; - auto addDirectory = [&](const std::filesystem::path &dir) { - if (dir.empty()) - return; - std::error_code ec; - auto canonical = std::filesystem::weakly_canonical(dir, ec); + auto addDirectory = [&](const std::filesystem::path &dir) { + if (dir.empty()) + return; + std::error_code ec; + auto canonical = std::filesystem::weakly_canonical(dir, ec); if (ec) { canonical = std::filesystem::absolute(dir, ec); } if (ec) return; - if (!std::filesystem::exists(canonical, ec) || ec) - return; - std::string key = toLowerCopy(canonical.string()); - if (seen.insert(key).second) { - dirs.push_back(canonical); - } - }; - - auto ® = registry(); - - if (wibo::argv && wibo::argc > 0 && wibo::argv[0]) { - std::filesystem::path mainBinary = std::filesystem::absolute(wibo::argv[0]); - if (mainBinary.has_parent_path()) { - addDirectory(mainBinary.parent_path()); - } - } + if (!std::filesystem::exists(canonical, ec) || ec) + return; + std::string key = stringToLower(canonical.string()); + if (seen.insert(key).second) { + dirs.push_back(canonical); + } + }; if (reg.dllDirectory.has_value()) { addDirectory(*reg.dllDirectory); } - addDirectory(files::pathFromWindows("Z:/Windows/System32")); - addDirectory(files::pathFromWindows("Z:/Windows")); - if (!alteredSearchPath) { addDirectory(std::filesystem::current_path()); } - if (const char *envPath = std::getenv("PATH")) { + if (const char *envPath = std::getenv("WIBO_PATH")) { std::string pathList = envPath; size_t start = 0; while (start <= pathList.size()) { @@ -302,7 +290,9 @@ std::vector collectSearchDirectories(bool alteredSearchPa return dirs; } -std::optional resolveModuleOnDisk(const std::string &requestedName, bool alteredSearchPath) { + +std::optional resolveModuleOnDisk(ModuleRegistry ®, const std::string &requestedName, + bool alteredSearchPath) { ParsedModuleName parsed = parseModuleName(requestedName); auto names = candidateModuleNames(parsed); @@ -310,9 +300,9 @@ std::optional resolveModuleOnDisk(const std::string &requ for (const auto &candidate : names) { auto combined = parsed.directory + "\\" + candidate; auto posixPath = files::pathFromWindows(combined.c_str()); - if (!posixPath.empty()) { - auto resolved = files::findCaseInsensitiveFile(std::filesystem::path(posixPath).parent_path(), - std::filesystem::path(posixPath).filename().string()); + if (!posixPath.empty()) { + auto resolved = files::findCaseInsensitiveFile(std::filesystem::path(posixPath).parent_path(), + std::filesystem::path(posixPath).filename().string()); if (resolved) { return files::canonicalPath(*resolved); } @@ -321,7 +311,7 @@ std::optional resolveModuleOnDisk(const std::string &requ return std::nullopt; } - auto dirs = collectSearchDirectories(alteredSearchPath); + auto dirs = collectSearchDirectories(reg, alteredSearchPath); for (const auto &dir : dirs) { for (const auto &candidate : names) { auto resolved = combineAndFind(dir, candidate); @@ -340,8 +330,7 @@ std::string storageKeyForPath(const std::filesystem::path &path) { std::string storageKeyForBuiltin(const std::string &normalizedName) { return normalizedName; } -wibo::ModuleInfo *findByAlias(const std::string &alias) { - auto ® = registry(); +wibo::ModuleInfo *findByAlias(ModuleRegistry ®, const std::string &alias) { auto it = reg.modulesByAlias.find(alias); if (it != reg.modulesByAlias.end()) { return it->second; @@ -349,11 +338,10 @@ wibo::ModuleInfo *findByAlias(const std::string &alias) { return nullptr; } -void registerAlias(const std::string &alias, wibo::ModuleInfo *info) { +void registerAlias(ModuleRegistry ®, const std::string &alias, wibo::ModuleInfo *info) { if (alias.empty() || !info) { return; } - auto ® = registry(); auto it = reg.modulesByAlias.find(alias); if (it == reg.modulesByAlias.end()) { reg.modulesByAlias[alias] = info; @@ -368,7 +356,7 @@ void registerAlias(const std::string &alias, wibo::ModuleInfo *info) { } } -void registerBuiltinModule(const wibo::Module *module) { +void registerBuiltinModule(ModuleRegistry ®, const wibo::Module *module) { if (!module) { return; } @@ -380,7 +368,6 @@ void registerBuiltinModule(const wibo::Module *module) { entry->exportsInitialized = true; auto storageKey = storageKeyForBuiltin(entry->normalizedName); auto raw = entry.get(); - auto ® = registry(); reg.modulesByKey[storageKey] = std::move(entry); reg.builtinAliasLists[module] = {}; @@ -395,7 +382,7 @@ void registerBuiltinModule(const wibo::Module *module) { if (pinModule) { reg.pinnedAliases.insert(alias); } - registerAlias(alias, raw); + registerAlias(reg, alias, raw); reg.builtinAliasMap[alias] = raw; ParsedModuleName parsed = parseModuleName(module->names[i]); std::string baseAlias = normalizedBaseKey(parsed); @@ -404,7 +391,7 @@ void registerBuiltinModule(const wibo::Module *module) { if (pinModule) { reg.pinnedAliases.insert(baseAlias); } - registerAlias(baseAlias, raw); + registerAlias(reg, baseAlias, raw); reg.builtinAliasMap[baseAlias] = raw; } } @@ -447,35 +434,17 @@ void callDllMain(wibo::ModuleInfo &info, DWORD reason) { } } -void ensureInitialized() { - auto ® = registry(); - if (reg.initialized) { - return; - } - reg.initialized = true; - - const wibo::Module *builtins[] = { - &lib_advapi32, &lib_bcrypt, &lib_crt, &lib_kernel32, &lib_lmgr, &lib_mscoree, &lib_msvcrt, - &lib_ntdll, &lib_ole32, &lib_rpcrt4, &lib_user32, &lib_vcruntime, &lib_version, nullptr, - }; - - for (const wibo::Module **module = builtins; *module; ++module) { - registerBuiltinModule(*module); - } -} - -void registerExternalModuleAliases(const std::string &requestedName, const std::filesystem::path &resolvedPath, - wibo::ModuleInfo *info) { +void registerExternalModuleAliases(ModuleRegistry ®, const std::string &requestedName, + const std::filesystem::path &resolvedPath, wibo::ModuleInfo *info) { ParsedModuleName parsed = parseModuleName(requestedName); - registerAlias(normalizedBaseKey(parsed), info); - registerAlias(normalizeAlias(requestedName), info); - registerAlias(storageKeyForPath(resolvedPath), info); + registerAlias(reg, normalizedBaseKey(parsed), info); + registerAlias(reg, normalizeAlias(requestedName), info); + registerAlias(reg, storageKeyForPath(resolvedPath), info); } -wibo::ModuleInfo *moduleFromAddress(void *addr) { +wibo::ModuleInfo *moduleFromAddress(ModuleRegistry ®, void *addr) { if (!addr) return nullptr; - auto ® = registry(); for (auto &pair : reg.modulesByKey) { wibo::ModuleInfo *info = pair.second.get(); if (!info) @@ -491,7 +460,7 @@ wibo::ModuleInfo *moduleFromAddress(void *addr) { } if (!base || size == 0) continue; - uint8_t *ptr = static_cast(addr); + auto *ptr = static_cast(addr); if (ptr >= base && ptr < base + size) { return info; } @@ -523,7 +492,8 @@ void ensureExportsInitialized(wibo::ModuleInfo &info) { } if (rva >= exe->exportDirectoryRVA && rva < exe->exportDirectoryRVA + exe->exportDirectorySize) { const char *forward = exe->fromRVA(rva); - info.exportsByOrdinal[i] = resolveMissingFuncName(info.originalName.c_str(), forward); + info.exportsByOrdinal[i] = + reinterpret_cast(resolveMissingFuncName(info.originalName.c_str(), forward)); } else { info.exportsByOrdinal[i] = exe->fromRVA(rva); } @@ -536,7 +506,7 @@ void ensureExportsInitialized(wibo::ModuleInfo &info) { auto *ordinals = exe->fromRVA(dir->addressOfNameOrdinals); for (uint32_t i = 0; i < nameCount; ++i) { uint16_t index = ordinals[i]; - uint16_t ordinal = static_cast(dir->base + index); + auto ordinal = static_cast(dir->base + index); if (index < info.exportsByOrdinal.size()) { const char *namePtr = exe->fromRVA(names[i]); info.exportNameToOrdinal[std::string(namePtr)] = ordinal; @@ -550,14 +520,11 @@ void ensureExportsInitialized(wibo::ModuleInfo &info) { namespace wibo { -void initializeModuleRegistry() { - std::lock_guard lock(registry().mutex); - ensureInitialized(); -} +void initializeModuleRegistry() { registry(); } void shutdownModuleRegistry() { - std::lock_guard lock(registry().mutex); - for (auto &pair : registry().modulesByKey) { + auto reg = registry(); + for (auto &pair : reg->modulesByKey) { ModuleInfo *info = pair.second.get(); if (!info || info->module) { continue; @@ -567,40 +534,38 @@ void shutdownModuleRegistry() { callDllMain(*info, DLL_PROCESS_DETACH); } } - registry().modulesByKey.clear(); - registry().modulesByAlias.clear(); - registry().dllDirectory.reset(); - registry().initialized = false; - registry().onExitTables.clear(); + reg->modulesByKey.clear(); + reg->modulesByAlias.clear(); + reg->dllDirectory.reset(); + reg->initialized = false; + reg->onExitTables.clear(); } ModuleInfo *moduleInfoFromHandle(HMODULE module) { return static_cast(module); } void setDllDirectoryOverride(const std::filesystem::path &path) { auto canonical = files::canonicalPath(path); - std::lock_guard lock(registry().mutex); - registry().dllDirectory = canonical; + auto reg = registry(); + reg->dllDirectory = canonical; } void clearDllDirectoryOverride() { - std::lock_guard lock(registry().mutex); - registry().dllDirectory.reset(); + auto reg = registry(); + reg->dllDirectory.reset(); } std::optional dllDirectoryOverride() { - std::lock_guard lock(registry().mutex); - return registry().dllDirectory; + auto reg = registry(); + return reg->dllDirectory; } void registerOnExitTable(void *table) { if (!table) return; - std::lock_guard lock(registry().mutex); - ensureInitialized(); - auto ® = registry(); - if (reg.onExitTables.find(table) == reg.onExitTables.end()) { - if (auto *info = moduleFromAddress(table)) { - reg.onExitTables[table] = info; + auto reg = registry(); + if (reg->onExitTables.find(table) == reg->onExitTables.end()) { + if (auto *info = moduleFromAddress(*reg, table)) { + reg->onExitTables[table] = info; } } } @@ -608,16 +573,15 @@ void registerOnExitTable(void *table) { void addOnExitFunction(void *table, void (*func)()) { if (!func) return; - std::lock_guard lock(registry().mutex); - auto ® = registry(); + auto reg = registry(); ModuleInfo *info = nullptr; - auto it = reg.onExitTables.find(table); - if (it != reg.onExitTables.end()) { + auto it = reg->onExitTables.find(table); + if (it != reg->onExitTables.end()) { info = it->second; } else if (table) { - info = moduleFromAddress(table); + info = moduleFromAddress(*reg, table); if (info) - reg.onExitTables[table] = info; + reg->onExitTables[table] = info; } if (info) { info->onExitFunctions.push_back(reinterpret_cast(func)); @@ -635,16 +599,15 @@ void runPendingOnExit(ModuleInfo &info) { } void executeOnExitTable(void *table) { - std::lock_guard lock(registry().mutex); - auto ® = registry(); + auto reg = registry(); ModuleInfo *info = nullptr; if (table) { - auto it = reg.onExitTables.find(table); - if (it != reg.onExitTables.end()) { + auto it = reg->onExitTables.find(table); + if (it != reg->onExitTables.end()) { info = it->second; - reg.onExitTables.erase(it); + reg->onExitTables.erase(it); } else { - info = moduleFromAddress(table); + info = moduleFromAddress(*reg, table); } } if (info) { @@ -656,13 +619,12 @@ HMODULE findLoadedModule(const char *name) { if (!name) { return nullptr; } - std::lock_guard lock(registry().mutex); - ensureInitialized(); + auto reg = registry(); ParsedModuleName parsed = parseModuleName(name); std::string alias = normalizedBaseKey(parsed); - ModuleInfo *info = findByAlias(alias); + ModuleInfo *info = findByAlias(*reg, alias); if (!info) { - info = findByAlias(normalizeAlias(name)); + info = findByAlias(*reg, normalizeAlias(name)); } return info; } @@ -675,23 +637,21 @@ HMODULE loadModule(const char *dllName) { std::string requested(dllName); DEBUG_LOG("loadModule(%s)\n", requested.c_str()); - std::lock_guard lock(registry().mutex); - ensureInitialized(); + auto reg = registry(); ParsedModuleName parsed = parseModuleName(requested); - auto ® = registry(); DWORD diskError = ERROR_SUCCESS; auto tryLoadExternal = [&](const std::filesystem::path &path) -> ModuleInfo * { std::string key = storageKeyForPath(path); - auto existingIt = reg.modulesByKey.find(key); - if (existingIt != reg.modulesByKey.end()) { + auto existingIt = reg->modulesByKey.find(key); + if (existingIt != reg->modulesByKey.end()) { ModuleInfo *info = existingIt->second.get(); if (info->refCount != UINT_MAX) { info->refCount++; } - registerExternalModuleAliases(requested, files::canonicalPath(path), info); + registerExternalModuleAliases(*reg, requested, files::canonicalPath(path), info); return info; } @@ -725,15 +685,15 @@ HMODULE loadModule(const char *dllName) { info->dontResolveReferences = false; ModuleInfo *raw = info.get(); - reg.modulesByKey[key] = std::move(info); - registerExternalModuleAliases(requested, raw->resolvedPath, raw); + reg->modulesByKey[key] = std::move(info); + registerExternalModuleAliases(*reg, requested, raw->resolvedPath, raw); ensureExportsInitialized(*raw); callDllMain(*raw, DLL_PROCESS_ATTACH); return raw; }; auto resolveAndLoadExternal = [&]() -> ModuleInfo * { - auto resolvedPath = resolveModuleOnDisk(requested, false); + auto resolvedPath = resolveModuleOnDisk(*reg, requested, false); if (!resolvedPath) { DEBUG_LOG(" module not found on disk\n"); return nullptr; @@ -742,9 +702,9 @@ HMODULE loadModule(const char *dllName) { }; std::string alias = normalizedBaseKey(parsed); - ModuleInfo *existing = findByAlias(alias); + ModuleInfo *existing = findByAlias(*reg, alias); if (!existing) { - existing = findByAlias(normalizeAlias(requested)); + existing = findByAlias(*reg, normalizeAlias(requested)); } if (existing) { DEBUG_LOG(" found existing module alias %s (builtin=%d)\n", alias.c_str(), existing->module != nullptr); @@ -756,7 +716,7 @@ HMODULE loadModule(const char *dllName) { lastError = ERROR_SUCCESS; return existing; } - bool pinned = reg.pinnedModules.count(existing) != 0; + bool pinned = reg->pinnedModules.count(existing) != 0; if (!pinned) { if (ModuleInfo *external = resolveAndLoadExternal()) { DEBUG_LOG(" replaced builtin module %s with external copy\n", requested.c_str()); @@ -777,13 +737,13 @@ HMODULE loadModule(const char *dllName) { auto fallbackAlias = normalizedBaseKey(parsed); ModuleInfo *builtin = nullptr; - auto builtinIt = reg.builtinAliasMap.find(fallbackAlias); - if (builtinIt != reg.builtinAliasMap.end()) { + auto builtinIt = reg->builtinAliasMap.find(fallbackAlias); + if (builtinIt != reg->builtinAliasMap.end()) { builtin = builtinIt->second; } if (!builtin) { - builtinIt = reg.builtinAliasMap.find(normalizeAlias(requested)); - if (builtinIt != reg.builtinAliasMap.end()) { + builtinIt = reg->builtinAliasMap.find(normalizeAlias(requested)); + if (builtinIt != reg->builtinAliasMap.end()) { builtin = builtinIt->second; } } @@ -801,7 +761,7 @@ void freeModule(HMODULE module) { if (!module) { return; } - std::lock_guard lock(registry().mutex); + auto reg = registry(); ModuleInfo *info = moduleInfoFromHandle(module); if (!info || info->refCount == UINT_MAX) { return; @@ -811,10 +771,9 @@ void freeModule(HMODULE module) { } info->refCount--; if (info->refCount == 0) { - auto ® = registry(); - for (auto it = reg.onExitTables.begin(); it != reg.onExitTables.end();) { + for (auto it = reg->onExitTables.begin(); it != reg->onExitTables.end();) { if (it->second == info) { - it = reg.onExitTables.erase(it); + it = reg->onExitTables.erase(it); } else { ++it; } @@ -823,10 +782,10 @@ void freeModule(HMODULE module) { callDllMain(*info, DLL_PROCESS_DETACH); std::string key = info->resolvedPath.empty() ? storageKeyForBuiltin(info->normalizedName) : storageKeyForPath(info->resolvedPath); - reg.modulesByKey.erase(key); - for (auto it = reg.modulesByAlias.begin(); it != reg.modulesByAlias.end();) { + reg->modulesByKey.erase(key); + for (auto it = reg->modulesByAlias.begin(); it != reg->modulesByAlias.end();) { if (it->second == info) { - it = reg.modulesByAlias.erase(it); + it = reg->modulesByAlias.erase(it); } else { ++it; } @@ -852,7 +811,7 @@ void *resolveFuncByName(HMODULE module, const char *funcName) { return resolveFuncByOrdinal(module, it->second); } } - return resolveMissingFuncName(info->originalName.c_str(), funcName); + return reinterpret_cast(resolveMissingFuncName(info->originalName.c_str(), funcName)); } void *resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) { @@ -869,7 +828,7 @@ void *resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) { if (!info->module) { ensureExportsInitialized(*info); if (!info->exportsByOrdinal.empty() && ordinal >= info->exportOrdinalBase) { - size_t index = static_cast(ordinal - info->exportOrdinalBase); + auto index = static_cast(ordinal - info->exportOrdinalBase); if (index < info->exportsByOrdinal.size()) { void *addr = info->exportsByOrdinal[index]; if (addr) { @@ -878,22 +837,20 @@ void *resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) { } } } - return resolveMissingFuncOrdinal(info->originalName.c_str(), ordinal); + return reinterpret_cast(resolveMissingFuncOrdinal(info->originalName.c_str(), ordinal)); } void *resolveMissingImportByName(const char *dllName, const char *funcName) { const char *safeDll = dllName ? dllName : ""; const char *safeFunc = funcName ? funcName : ""; - std::lock_guard lock(registry().mutex); - ensureInitialized(); - return resolveMissingFuncName(safeDll, safeFunc); + [[maybe_unused]] auto reg = registry(); + return reinterpret_cast(resolveMissingFuncName(safeDll, safeFunc)); } void *resolveMissingImportByOrdinal(const char *dllName, uint16_t ordinal) { const char *safeDll = dllName ? dllName : ""; - std::lock_guard lock(registry().mutex); - ensureInitialized(); - return resolveMissingFuncOrdinal(safeDll, ordinal); + [[maybe_unused]] auto reg = registry(); + return reinterpret_cast(resolveMissingFuncOrdinal(safeDll, ordinal)); } Executable *executableFromModule(HMODULE module) { diff --git a/strutil.cpp b/strutil.cpp index 1c0708b..b6c8e9d 100644 --- a/strutil.cpp +++ b/strutil.cpp @@ -1,10 +1,47 @@ #include "strutil.h" #include "common.h" +#include +#include +#include #include #include #include #include +void toLowerInPlace(std::string &str) { + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); +} + +void toUpperInPlace(std::string &str) { + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) { return static_cast(std::toupper(c)); }); +} + +std::string stringToLower(std::string_view str) { + std::string result(str); + toLowerInPlace(result); + return result; +} + +std::string stringToUpper(std::string_view str) { + std::string result(str); + toUpperInPlace(result); + return result; +} + +uint16_t wcharToLower(uint16_t ch) { + if (ch >= 'A' && ch <= 'Z') { + return static_cast(ch + ('a' - 'A')); + } + wchar_t wide = static_cast(ch); + wchar_t lowered = std::towlower(wide); + if (lowered < 0 || lowered > 0xFFFF) { + return ch; + } + return static_cast(lowered); +} + size_t wstrlen(const uint16_t *str) { if (!str) return 0; diff --git a/strutil.h b/strutil.h index 897ae4a..69289f4 100644 --- a/strutil.h +++ b/strutil.h @@ -2,6 +2,7 @@ #include #include +#include #include size_t wstrlen(const uint16_t *str); @@ -18,3 +19,8 @@ std::string wideStringToString(const uint16_t *src, int len = -1); std::vector stringToWideString(const char *src); long wstrtol(const uint16_t *string, uint16_t **end_ptr, int base); unsigned long wstrtoul(const uint16_t *string, uint16_t **end_ptr, int base); +void toLowerInPlace(std::string &str); +void toUpperInPlace(std::string &str); +std::string stringToLower(std::string_view str); +std::string stringToUpper(std::string_view str); +uint16_t wcharToLower(uint16_t ch); From 8cac50e50e1642aff7721380d2322192b78de2f2 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 28 Sep 2025 17:20:43 -0600 Subject: [PATCH 22/28] Rewrite BCryptGenRandom and add tests --- CMakeLists.txt | 19 +++++++++++++ common.h | 2 ++ dll/bcrypt.cpp | 61 ++++++++++++++++++++++++++++++++++------- dll/crt.cpp | 12 +++++++++ dll/msvcrt.cpp | 5 ++++ test/test_bcrypt.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 157 insertions(+), 9 deletions(-) create mode 100644 test/test_bcrypt.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f377820..de66aeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,18 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/test/test_external_dll.c ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_bcrypt.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_bcrypt.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_bcrypt.c + -lbcrypt + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_bcrypt.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + add_custom_command( OUTPUT ${WIBO_TEST_BIN_DIR}/test_resources_res.o COMMAND ${WIBO_MINGW_WINDRES} @@ -105,6 +117,7 @@ if(BUILD_TESTING) DEPENDS ${WIBO_TEST_BIN_DIR}/external_exports.dll ${WIBO_TEST_BIN_DIR}/test_external_dll.exe + ${WIBO_TEST_BIN_DIR}/test_bcrypt.exe ${WIBO_TEST_BIN_DIR}/test_resources.exe) if(CMAKE_CONFIGURATION_TYPES) @@ -123,6 +136,12 @@ if(BUILD_TESTING) WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} DEPENDS wibo.build_fixtures) + add_test(NAME wibo.test_bcrypt + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_bcrypt.exe) + set_tests_properties(wibo.test_bcrypt PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + add_test(NAME wibo.test_resources COMMAND $ ${WIBO_TEST_BIN_DIR}/test_resources.exe) set_tests_properties(wibo.test_resources PROPERTIES diff --git a/common.h b/common.h index 44a7d3a..b132019 100644 --- a/common.h +++ b/common.h @@ -81,6 +81,8 @@ typedef unsigned char BYTE; typedef int NTSTATUS; #define STATUS_SUCCESS ((NTSTATUS)0x00000000) #define STATUS_INVALID_HANDLE ((NTSTATUS)0xC0000008) +#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000D) +#define STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002) #define STATUS_END_OF_FILE ((NTSTATUS)0xC0000011) #define STATUS_NOT_SUPPORTED ((NTSTATUS)0xC00000BB) #define STATUS_UNEXPECTED_IO_ERROR ((NTSTATUS)0xC00000E9) diff --git a/dll/bcrypt.cpp b/dll/bcrypt.cpp index a2dc27b..054e30b 100644 --- a/dll/bcrypt.cpp +++ b/dll/bcrypt.cpp @@ -1,21 +1,64 @@ #include "common.h" -#include -#include -#include +#include +#include + +namespace { typedef PVOID BCRYPT_ALG_HANDLE; -namespace bcrypt { +constexpr ULONG BCRYPT_RNG_USE_ENTROPY_IN_BUFFER = 0x00000001; +constexpr ULONG BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002; + +bool fillWithSystemRandom(PUCHAR buffer, size_t length) { + while (length > 0) { + ssize_t written = getrandom(buffer, length, 0); + if (written < 0) { + if (errno == EINTR) + continue; + return false; + } + if (written == 0) + continue; + buffer += written; + length -= static_cast(written); + } + return true; +} -using random_bytes_engine = std::independent_bits_engine; +} // namespace + +namespace bcrypt { NTSTATUS WIN_FUNC BCryptGenRandom(BCRYPT_ALG_HANDLE hAlgorithm, PUCHAR pbBuffer, ULONG cbBuffer, ULONG dwFlags) { DEBUG_LOG("BCryptGenRandom(%p, %p, %lu, %lu)\n", hAlgorithm, pbBuffer, cbBuffer, dwFlags); - assert(hAlgorithm == nullptr); - assert(dwFlags == 0 || dwFlags == 2 /* BCRYPT_USE_SYSTEM_PREFERRED_RNG */); - random_bytes_engine rbe; - std::generate(pbBuffer, pbBuffer + cbBuffer, std::ref(rbe)); + if (pbBuffer == nullptr && cbBuffer != 0) + return STATUS_INVALID_HANDLE; + + if (hAlgorithm != nullptr) + return STATUS_NOT_IMPLEMENTED; + + if ((dwFlags & BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0) + return STATUS_INVALID_HANDLE; + + ULONG allowedFlags = BCRYPT_RNG_USE_ENTROPY_IN_BUFFER | BCRYPT_USE_SYSTEM_PREFERRED_RNG; + if ((dwFlags & ~allowedFlags) != 0) + return STATUS_INVALID_PARAMETER; + + if (cbBuffer == 0) + return STATUS_SUCCESS; + + std::vector entropy; + if ((dwFlags & BCRYPT_RNG_USE_ENTROPY_IN_BUFFER) && pbBuffer != nullptr) + entropy.assign(pbBuffer, pbBuffer + cbBuffer); + + if (!fillWithSystemRandom(pbBuffer, cbBuffer)) + return STATUS_UNEXPECTED_IO_ERROR; + + if (!entropy.empty()) { + for (size_t i = 0; i < entropy.size(); ++i) + pbBuffer[i] ^= entropy[i]; + } return STATUS_SUCCESS; } diff --git a/dll/crt.cpp b/dll/crt.cpp index 99b8b3a..ec5dd16 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -132,6 +132,12 @@ void WIN_ENTRY free(void *ptr) { ::free(ptr); } void *WIN_ENTRY memcpy(void *dest, const void *src, size_t count) { return std::memcpy(dest, src, count); } +void *WIN_ENTRY memmove(void *dest, const void *src, size_t count) { return std::memmove(dest, src, count); } + +void *WIN_ENTRY memset(void *dest, int ch, size_t count) { return std::memset(dest, ch, count); } + +int WIN_ENTRY memcmp(const void *lhs, const void *rhs, size_t count) { return std::memcmp(lhs, rhs, count); } + int WIN_ENTRY __setusermatherr(void *handler) { DEBUG_LOG("STUB: __setusermatherr(%p)\n", handler); return 0; @@ -266,6 +272,12 @@ static void *resolveByName(const char *name) { return (void *)crt::free; if (strcmp(name, "memcpy") == 0) return (void *)crt::memcpy; + if (strcmp(name, "memmove") == 0) + return (void *)crt::memmove; + if (strcmp(name, "memset") == 0) + return (void *)crt::memset; + if (strcmp(name, "memcmp") == 0) + return (void *)crt::memcmp; if (strcmp(name, "exit") == 0) return (void *)crt::exit; if (strcmp(name, "_cexit") == 0) diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index b8f17d0..6a42000 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -570,6 +570,10 @@ char* WIN_ENTRY setlocale(int category, const char *locale){ return std::memmove(dest, src, count); } + int WIN_ENTRY memcmp(const void *lhs, const void *rhs, size_t count) { + return std::memcmp(lhs, rhs, count); + } + int WIN_ENTRY fflush(FILE *stream) { return std::fflush(stream); } @@ -1485,6 +1489,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "memset") == 0) return (void*)msvcrt::memset; if (strcmp(name, "memcpy") == 0) return (void*)msvcrt::memcpy; if (strcmp(name, "memmove") == 0) return (void*)msvcrt::memmove; + if (strcmp(name, "memcmp") == 0) return (void*)msvcrt::memcmp; if (strcmp(name, "fflush") == 0) return (void*)msvcrt::fflush; if (strcmp(name, "fopen") == 0) return (void*)msvcrt::fopen; if (strcmp(name, "fseek") == 0) return (void*)msvcrt::fseek; diff --git a/test/test_bcrypt.c b/test/test_bcrypt.c new file mode 100644 index 0000000..5e1146a --- /dev/null +++ b/test/test_bcrypt.c @@ -0,0 +1,67 @@ +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include +#include + +#include "test_assert.h" +#include +#include +#include + +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif + +static void expect_success(NTSTATUS status) { + TEST_CHECK_MSG(NT_SUCCESS(status), "Expected success NTSTATUS, got 0x%08lx", (unsigned long)status); +} + +int main(void) { + UCHAR temp[32] = {0}; + const UCHAR zero_block[32] = {0}; + NTSTATUS status = BCryptGenRandom(NULL, temp, sizeof(temp), 0); + TEST_CHECK_EQ(STATUS_INVALID_HANDLE, status); + + UCHAR first[32] = {0}; + status = BCryptGenRandom(NULL, first, sizeof(first), BCRYPT_USE_SYSTEM_PREFERRED_RNG); + expect_success(status); + TEST_CHECK_MSG(memcmp(first, zero_block, sizeof(first)) != 0, + "BCryptGenRandom with system RNG flag left buffer zeroed"); + + UCHAR second[32] = {0}; + status = BCryptGenRandom(NULL, second, sizeof(second), BCRYPT_USE_SYSTEM_PREFERRED_RNG); + expect_success(status); + TEST_CHECK_MSG(memcmp(second, zero_block, sizeof(second)) != 0, + "BCryptGenRandom produced zeroed buffer on repeat call"); + TEST_CHECK_MSG(memcmp(first, second, sizeof(first)) != 0, + "BCryptGenRandom produced identical buffers across calls"); + + UCHAR entropy_buffer[32]; + UCHAR entropy_original[32]; + memset(entropy_buffer, 0x5a, sizeof(entropy_buffer)); + memcpy(entropy_original, entropy_buffer, sizeof(entropy_buffer)); + status = BCryptGenRandom(NULL, entropy_buffer, sizeof(entropy_buffer), + BCRYPT_RNG_USE_ENTROPY_IN_BUFFER | BCRYPT_USE_SYSTEM_PREFERRED_RNG); + expect_success(status); + TEST_CHECK_MSG(memcmp(entropy_buffer, entropy_original, sizeof(entropy_buffer)) != 0, + "Entropy flag did not modify buffer"); + + status = BCryptGenRandom((BCRYPT_ALG_HANDLE)0x1, first, sizeof(first), 0); + TEST_CHECK_EQ(STATUS_NOT_IMPLEMENTED, status); + + status = BCryptGenRandom(NULL, first, sizeof(first), 0x4); + TEST_CHECK_EQ(STATUS_INVALID_HANDLE, status); + + status = BCryptGenRandom((BCRYPT_ALG_HANDLE)0x1, first, sizeof(first), BCRYPT_USE_SYSTEM_PREFERRED_RNG); + TEST_CHECK_EQ(STATUS_NOT_IMPLEMENTED, status); + + status = BCryptGenRandom(NULL, NULL, sizeof(first), 0); + TEST_CHECK_EQ(STATUS_INVALID_HANDLE, status); + + status = BCryptGenRandom(NULL, NULL, 0, 0); + TEST_CHECK_EQ(STATUS_INVALID_HANDLE, status); + + printf("bcrypt_gen_random: passed\n"); + return EXIT_SUCCESS; +} From fd47411fff48038c864a24bed757424ecb04055a Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 28 Sep 2025 18:00:48 -0600 Subject: [PATCH 23/28] Rework CI into a build matrix; update Dockerfiles --- .github/workflows/ci.yml | 121 ++++++++++++++++++++++++--------------- Dockerfile | 17 +++++- Dockerfile.ubuntu | 31 +++++++--- 3 files changed, 112 insertions(+), 57 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4348d7..bacb783 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,65 +11,94 @@ env: DOCKER_BUILDKIT: 1 jobs: - build_and_test: - name: Build and test + build: + name: Build (${{ matrix.display }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - id: ubuntu-debug + display: Debug + dockerfile: Dockerfile.ubuntu + build_type: Debug + image: wibo-host-debug + - id: ubuntu-release + display: Release + dockerfile: Dockerfile.ubuntu + build_type: Release + image: wibo-host-release + - id: static-debug + display: Static, Debug + dockerfile: Dockerfile + build_type: Debug + image: wibo-static-debug + - id: static-release + display: Static, Release + dockerfile: Dockerfile + build_type: Release + image: wibo-static-release steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - file \ - unzip \ - wget \ - cmake \ - ninja-build \ - g++-multilib \ - gcc-mingw-w64-i686 \ - binutils-mingw-w64-i686 - - - name: Build debug - run: docker build --build-arg build_type=Debug --target export --output build_debug . - - - name: Build release - run: docker build --build-arg build_type=Release --target export --output build . + - name: Build + run: >- + docker build + -f ${{ matrix.dockerfile }} + --build-arg build_type=${{ matrix.build_type }} + --target build + -t ${{ matrix.image }} + . - - name: Test - shell: bash - run: | - mv build_debug/wibo build/wibo_debug - wget -q https://files.decomp.dev/compilers_latest.zip - unzip -q compilers_latest.zip - set -x - build/wibo_debug Wii/1.7/mwcceppc.exe -nodefaults -c test/test.c -Itest -o test_debug.o - file test_debug.o - build/wibo Wii/1.7/mwcceppc.exe -nodefaults -c test/test.c -Itest -o test.o - file test.o + - name: Tests + run: docker run --rm ${{ matrix.image }} ctest --test-dir /wibo/build --output-on-failure - - name: Fixture tests + - name: Export binary run: | - cmake -S . -B build_ctest -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON -DWIBO_ENABLE_FIXTURE_TESTS=ON - cmake --build build_ctest - ctest --test-dir build_ctest --output-on-failure + rm -rf dist + docker build \ + -f ${{ matrix.dockerfile }} \ + --build-arg build_type=${{ matrix.build_type }} \ + --target export \ + --output dist \ + . - - name: Upload release + - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: wibo - path: build/wibo + name: ${{ matrix.id }} + path: dist/wibo - - name: Upload debug - uses: actions/upload-artifact@v4 + release: + name: Publish Release + if: startsWith(github.ref, 'refs/tags/') + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download static debug + uses: actions/download-artifact@v4 + with: + name: static-debug + path: artifacts/static-debug + + - name: Download static release + uses: actions/download-artifact@v4 with: - name: wibo_debug - path: build/wibo_debug + name: static-release + path: artifacts/static-release + + - name: Prepare assets + run: | + mkdir -p artifacts/out + cp artifacts/static-debug/wibo artifacts/out/wibo_debug + cp artifacts/static-release/wibo artifacts/out/wibo - name: Publish release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') with: files: | - build/wibo - build/wibo_debug + artifacts/out/wibo + artifacts/out/wibo_debug diff --git a/Dockerfile b/Dockerfile index 2fa409c..a759e86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,16 +2,29 @@ FROM --platform=linux/i386 alpine:latest AS build # Install dependencies -RUN apk add --no-cache cmake ninja g++ linux-headers binutils +RUN apk add --no-cache \ + bash \ + cmake \ + ninja \ + g++ \ + linux-headers \ + binutils \ + mingw-w64-binutils \ + mingw-w64-gcc # Copy source files +WORKDIR /wibo COPY . /wibo # Build type (Release, Debug, RelWithDebInfo, MinSizeRel) ARG build_type=Release # Build static binary -RUN cmake -S /wibo -B /wibo/build -G Ninja -DCMAKE_BUILD_TYPE="$build_type" -DCMAKE_CXX_FLAGS="-static" \ +RUN cmake -S /wibo -B /wibo/build -G Ninja \ + -DCMAKE_BUILD_TYPE="$build_type" \ + -DCMAKE_CXX_FLAGS="-static" \ + -DBUILD_TESTING=ON \ + -DWIBO_ENABLE_FIXTURE_TESTS=ON \ && cmake --build /wibo/build \ && ( [ "$build_type" != "Release" ] || strip -g /wibo/build/wibo ) diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index 05076ff..550b606 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -1,5 +1,7 @@ -# Ubuntu 24.04 environment that matches CI toolchain and fixture tests. -FROM ubuntu:24.04 AS deps +# Build stage +FROM ubuntu:24.04 AS build + +# Install dependencies ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && apt-get install -y --no-install-recommends \ @@ -15,14 +17,25 @@ RUN apt-get update \ wget \ && rm -rf /var/lib/apt/lists/* +# Copy source files WORKDIR /wibo - -FROM deps AS dev COPY . /wibo -ARG BUILD_TYPE=Debug -# Configure default build folders so docker build can cache compilation layers if desired. -RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DBUILD_TESTING=ON -DWIBO_ENABLE_FIXTURE_TESTS=ON \ - && cmake --build build +# Build type (Release, Debug, RelWithDebInfo, MinSizeRel) +ARG build_type=Release + +RUN cmake -S /wibo -B /wibo/build -G Ninja \ + -DCMAKE_BUILD_TYPE="$build_type" \ + -DBUILD_TESTING=ON \ + -DWIBO_ENABLE_FIXTURE_TESTS=ON \ + && cmake --build /wibo/build \ + && ( [ "$build_type" != "Release" ] || strip -g /wibo/build/wibo ) + +# Export binary (usage: docker build -f Dockerfile.ubuntu --target export --output dist .) +FROM scratch AS export +COPY --from=build /wibo/build/wibo . -ENTRYPOINT ["/bin/bash"] +# Runnable container +FROM ubuntu:24.04 +COPY --from=build /wibo/build/wibo /usr/local/sbin/wibo +CMD ["/usr/local/sbin/wibo"] From 3d225385903e35e36263c7e88cb0a5c0d98495df Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 28 Sep 2025 18:43:09 -0600 Subject: [PATCH 24/28] Add command line arguments (--chdir/--debug, ...) --- AGENTS.md | 2 +- README.md | 13 +++++--- main.cpp | 99 +++++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 95 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c2cbcf3..ad50cfa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ ## Build, Test, and Development Commands - `cmake -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON` configures a 32-bit toolchain; ensure multilib packages are present. - `cmake --build build --target wibo` compiles the shim; switch to `-DCMAKE_BUILD_TYPE=Release` for optimised binaries. -- `./build/wibo /path/to/program.exe` runs a Windows binary through the shim; use `WIBO_DEBUG=1` for verbose logging. +- `./build/wibo /path/to/program.exe` runs a Windows binary. Use `WIBO_DEBUG=1` (or `--debug`/`-D`) for verbose logging. Use `--chdir`/`-C` to set the working directory. - `cmake -B build -DBUILD_TESTING=ON` + `ctest --test-dir build --output-on-failure` runs the self-checking WinAPI fixtures (requires `i686-w64-mingw32-gcc` and `i686-w64-mingw32-windres`). - `clang-format -i path/to/file.cpp` and `clang-tidy path/to/file.cpp -p build` keep contributions aligned with the repo's tooling. - DON'T use `clang-format` on existing files, only new or heavily modified ones; the repo hasn't been fully formatted yet. diff --git a/README.md b/README.md index 93857be..e980d20 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,16 @@ cmake --build build --target wibo ## Running ```sh -./build/wibo /path/to/program.exe -# or, with debug logging: -WIBO_DEBUG=1 ./build/wibo /path/to/program.exe +./build/wibo /path/to/program.exe [arguments...] ``` +Supported command line options: + +- `--help`: Print usage information. +- `-D`, `--debug`: Enable shim debug logging (equivalent to `WIBO_DEBUG=1`). +- `-C DIR`, `--chdir DIR`, `--chdir=DIR`: Change to `DIR` before running the program. +- `--`: Stop option parsing; following arguments are interpreted as the program command line. + ## Tests Self-checking Windows fixtures run through CTest. They require a 32-bit MinGW cross toolchain (`i686-w64-mingw32-gcc` and `i686-w64-mingw32-windres`). @@ -40,8 +45,6 @@ This will cross-compile the fixture executables, run them through `wibo`, and fa Rough to-do list: - Implement more APIs -- Do something intelligent with Windows `HANDLE`s -- Convert paths in environment variables (and the structure of `PATH` itself, maybe) to Windows format --- diff --git a/main.cpp b/main.cpp index 3fcd2e5..c50b286 100644 --- a/main.cpp +++ b/main.cpp @@ -3,12 +3,14 @@ #include "strutil.h" #include #include +#include #include #include #include #include #include #include +#include #include uint32_t wibo::lastError = 0; @@ -84,6 +86,18 @@ TIB tib; const size_t MAPS_BUFFER_SIZE = 0x10000; +static void printHelp(const char *argv0) { + std::filesystem::path exePath(argv0 ? argv0 : "wibo"); + std::string exeName = exePath.filename().string(); + fprintf(stdout, "Usage: %s [options] [arguments...]\n", exeName.c_str()); + fprintf(stdout, "\n"); + fprintf(stdout, "Options:\n"); + fprintf(stdout, " --help\t\tShow this help message and exit\n"); + fprintf(stdout, " -C, --chdir DIR\tChange working directory before launching the program\n"); + fprintf(stdout, " -D, --debug\tEnable shim debug logging (same as WIBO_DEBUG=1)\n"); + fprintf(stdout, " --\t\tStop option parsing; following arguments are interpreted as the program command line\n"); +} + /** * Read /proc/self/maps into a buffer. * @@ -186,17 +200,73 @@ static void blockUpper2GB() { } int main(int argc, char **argv) { - if (argc <= 1) { - printf("Usage: ./wibo program.exe ...\n"); - return 1; + std::string chdirPath; + bool optionDebug = false; + bool parsingOptions = true; + int programIndex = -1; + + for (int i = 1; i < argc; ++i) { + const char *arg = argv[i]; + if (parsingOptions) { + if (strcmp(arg, "--") == 0) { + parsingOptions = false; + continue; + } + if (strcmp(arg, "--help") == 0) { + printHelp(argv[0]); + return 0; + } + if (strcmp(arg, "-D") == 0 || strcmp(arg, "--debug") == 0) { + optionDebug = true; + continue; + } + if (strncmp(arg, "--chdir=", 8) == 0) { + chdirPath = arg + 8; + continue; + } + if (strcmp(arg, "-C") == 0 || strcmp(arg, "--chdir") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "Option %s requires a directory argument\n", arg); + return 1; + } + chdirPath = argv[++i]; + continue; + } + if (strncmp(arg, "-C", 2) == 0 && arg[2] != '\0') { + chdirPath = arg + 2; + continue; + } + if (arg[0] == '-' && arg[1] != '\0') { + fprintf(stderr, "Unknown option: %s\n", arg); + fprintf(stderr, "\n"); + printHelp(argv[0]); + return 1; + } + } + + programIndex = i; + break; } - if (getenv("WIBO_DEBUG")) { + if (programIndex == -1) { + printHelp(argv[0]); + return argc <= 1 ? 0 : 1; + } + + if (!chdirPath.empty()) { + if (chdir(chdirPath.c_str()) != 0) { + std::string message = std::string("Failed to chdir to ") + chdirPath; + perror(message.c_str()); + return 1; + } + } + + if (optionDebug || getenv("WIBO_DEBUG")) { wibo::debugEnabled = true; } - if (getenv("WIBO_DEBUG_INDENT")) { - wibo::debugIndent = std::stoul(getenv("WIBO_DEBUG_INDENT")); + if (const char *debugIndentEnv = getenv("WIBO_DEBUG_INDENT")) { + wibo::debugIndent = std::stoul(debugIndentEnv); } blockUpper2GB(); @@ -226,15 +296,18 @@ int main(int argc, char **argv) { wibo::tibSelector = static_cast((tibDesc.entry_number << 3) | 7); + char **guestArgv = argv + programIndex; + int guestArgc = argc - programIndex; + // Build a command line std::string cmdLine; - for (int i = 1; i < argc; i++) { + for (int i = 0; i < guestArgc; ++i) { std::string arg; - if (i == 1) { - arg = files::pathToWindows(std::filesystem::absolute(argv[1])); + if (i == 0) { + arg = files::pathToWindows(std::filesystem::absolute(guestArgv[0])); } else { cmdLine += ' '; - arg = argv[i]; + arg = guestArgv[i]; } bool needQuotes = arg.find_first_of("\\\" \t\n") != std::string::npos; if (needQuotes) @@ -271,15 +344,15 @@ int main(int argc, char **argv) { DEBUG_LOG("Command line: %s\n", wibo::commandLine); wibo::executableName = argv[0]; - wibo::argv = argv + 1; - wibo::argc = argc - 1; + wibo::argv = guestArgv; + wibo::argc = guestArgc; wibo::initializeModuleRegistry(); wibo::Executable exec; wibo::mainModule = &exec; - char* pe_path = argv[1]; + char* pe_path = guestArgv[0]; FILE *f = fopen(pe_path, "rb"); if (!f) { std::string mesg = std::string("Failed to open file ") + pe_path; From 1a4944e619d3c671e4f90c1d83dd051c021307ce Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 28 Sep 2025 19:33:58 -0600 Subject: [PATCH 25/28] More msvcrt, fix loading DLLs in program dir regression --- dll/msvcrt.cpp | 243 +++++++++++++++++++++++++++++++++++++++++++- module_registry.cpp | 32 +++--- 2 files changed, 262 insertions(+), 13 deletions(-) diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 6a42000..2aa2bdb 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -72,6 +72,10 @@ namespace msvcrt { return entries; } + IOBProxy *WIN_ENTRY __iob_func() { + return standardIobEntries(); + } + std::unordered_map &iobMapping() { static std::unordered_map mapping; return mapping; @@ -102,6 +106,15 @@ namespace msvcrt { return stream; } + int WIN_ENTRY _fileno(FILE *stream) { + if (!stream) { + errno = EINVAL; + return -1; + } + FILE *host = mapToHostFile(stream); + return ::fileno(host); + } + void refreshMbCurMax() { mbCurMaxValue = static_cast(MB_CUR_MAX); } @@ -496,6 +509,106 @@ char* WIN_ENTRY setlocale(int category, const char *locale){ int WIN_ENTRY strncmp(const char *lhs, const char *rhs, size_t count) { return ::strncmp(lhs, rhs, count); } + void WIN_ENTRY _exit(int status) { + _Exit(status); + } + + int WIN_ENTRY strcpy_s(char *dest, size_t dest_size, const char *src) { + if (!dest || !src || dest_size == 0) { + return 22; + } + + size_t src_len = ::strlen(src); + if (src_len + 1 > dest_size) { + dest[0] = 0; + return 34; + } + + std::memcpy(dest, src, src_len + 1); + return 0; + } + + int WIN_ENTRY strcat_s(char *dest, size_t numberOfElements, const char *src) { + if (!dest || !src || numberOfElements == 0) { + return 22; + } + + size_t dest_len = ::strlen(dest); + size_t src_len = ::strlen(src); + if (dest_len + src_len + 1 > numberOfElements) { + dest[0] = 0; + return 34; + } + + std::memcpy(dest + dest_len, src, src_len + 1); + return 0; + } + + int WIN_ENTRY strncpy_s(char *dest, size_t dest_size, const char *src, size_t count) { + constexpr size_t TRUNCATE = static_cast(-1); + constexpr int STRUNCATE = 80; + + if (!dest || dest_size == 0) { + return 22; + } + + if (!src) { + dest[0] = 0; + return count == 0 ? 0 : 22; + } + + if (count == 0) { + dest[0] = 0; + return 0; + } + + if (count == TRUNCATE) { + size_t src_len = ::strlen(src); + if (src_len + 1 > dest_size) { + size_t copy_len = dest_size > 0 ? dest_size - 1 : 0; + if (copy_len > 0) { + std::memcpy(dest, src, copy_len); + } + dest[copy_len] = '\0'; + return STRUNCATE; + } + std::memcpy(dest, src, src_len + 1); + return 0; + } + + size_t src_len = ::strlen(src); + size_t copy_len = count < src_len ? count : src_len; + if (copy_len >= dest_size) { + dest[0] = 0; + return 34; + } + + if (copy_len > 0) { + std::memcpy(dest, src, copy_len); + } + dest[copy_len] = '\0'; + return 0; + } + + char *WIN_ENTRY _strdup(const char *strSource) { + if (!strSource) { + return nullptr; + } + + size_t length = ::strlen(strSource); + auto *copy = static_cast(std::malloc(length + 1)); + if (!copy) { + return nullptr; + } + + std::memcpy(copy, strSource, length + 1); + return copy; + } + + unsigned long WIN_ENTRY strtoul(const char *str, char **endptr, int base) { + return ::strtoul(str, endptr, base); + } + void* WIN_ENTRY malloc(size_t size){ return std::malloc(size); } @@ -574,8 +687,28 @@ char* WIN_ENTRY setlocale(int category, const char *locale){ return std::memcmp(lhs, rhs, count); } + void WIN_ENTRY qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *)) { + std::qsort(base, num, size, compar); + } + int WIN_ENTRY fflush(FILE *stream) { - return std::fflush(stream); + if (!stream) { + return std::fflush(nullptr); + } + FILE *host = mapToHostFile(stream); + return std::fflush(host); + } + + int WIN_ENTRY vfwprintf(FILE *stream, const uint16_t *format, va_list args) { + FILE *host = mapToHostFile(stream ? stream : stdout); + std::wstring fmt; + if (format) { + for (const uint16_t *ptr = format; *ptr; ++ptr) { + fmt.push_back(static_cast(*ptr)); + } + } + fmt.push_back(L'\0'); + return std::vfwprintf(host, fmt.c_str(), args); } FILE *WIN_ENTRY fopen(const char *filename, const char *mode) { @@ -612,6 +745,10 @@ char* WIN_ENTRY setlocale(int category, const char *locale){ return std::fputws(temp.c_str(), stream); } + int WIN_ENTRY _cputws(const uint16_t *string) { + return fputws(string, stdout); + } + uint16_t* WIN_ENTRY fgetws(uint16_t *buffer, int size, FILE *stream) { if (!buffer || size <= 0) { return nullptr; @@ -994,6 +1131,11 @@ char* WIN_ENTRY setlocale(int category, const char *locale){ abort_and_log("terminate"); } + int WIN_ENTRY _purecall() { + abort_and_log("_purecall"); + return 0; + } + int WIN_ENTRY _except_handler4_common(void *, void *, void *, void *) { DEBUG_LOG("_except_handler4_common\n"); return 0; @@ -1204,6 +1346,42 @@ char* WIN_ENTRY setlocale(int category, const char *locale){ return wstrtol(str, nullptr, 10); } + int WIN_ENTRY _ltoa_s(long value, char *buffer, size_t sizeInChars, int radix) { + if (!buffer || sizeInChars == 0) { + return 22; + } + if (radix < 2 || radix > 36) { + buffer[0] = 0; + return 22; + } + + bool isNegative = (value < 0) && (radix == 10); + uint64_t magnitude = isNegative ? static_cast(-(int64_t)value) : static_cast(static_cast(value)); + char temp[65]; + size_t index = 0; + do { + uint64_t digit = magnitude % static_cast(radix); + temp[index++] = static_cast((digit < 10) ? ('0' + digit) : ('a' + (digit - 10))); + magnitude /= static_cast(radix); + } while (magnitude != 0 && index < sizeof(temp)); + + if (isNegative) { + temp[index++] = '-'; + } + + size_t required = index + 1; // include null terminator + if (required > sizeInChars) { + buffer[0] = 0; + return 34; + } + + for (size_t i = 0; i < index; ++i) { + buffer[i] = temp[index - i - 1]; + } + buffer[index] = '\0'; + return 0; + } + int WIN_ENTRY wcscpy_s(uint16_t *dest, size_t dest_size, const uint16_t *src){ std::string src_str = wideStringToString(src); DEBUG_LOG("wcscpy_s %s\n", src_str.c_str()); @@ -1220,6 +1398,54 @@ char* WIN_ENTRY setlocale(int category, const char *locale){ return 0; } + int WIN_ENTRY swprintf_s(uint16_t *buffer, size_t sizeOfBuffer, const uint16_t *format, ...) { + if (!buffer || sizeOfBuffer == 0 || !format) { + errno = EINVAL; + return EINVAL; + } + std::wstring fmt; + for (const uint16_t *ptr = format; *ptr; ++ptr) { + fmt.push_back(static_cast(*ptr)); + } + fmt.push_back(L'\0'); + std::vector temp(sizeOfBuffer); + va_list args; + va_start(args, format); + int written = std::vswprintf(temp.data(), temp.size(), fmt.c_str(), args); + va_end(args); + if (written < 0 || static_cast(written) >= sizeOfBuffer) { + buffer[0] = 0; + errno = ERANGE; + return ERANGE; + } + for (int i = 0; i <= written; ++i) { + buffer[i] = static_cast(temp[static_cast(i)]); + } + return written; + } + + int WIN_ENTRY swscanf_s(const uint16_t *buffer, const uint16_t *format, ...) { + if (!buffer || !format) { + errno = EINVAL; + return EOF; + } + std::wstring bufW; + for (const uint16_t *ptr = buffer; *ptr; ++ptr) { + bufW.push_back(static_cast(*ptr)); + } + bufW.push_back(L'\0'); + std::wstring fmt; + for (const uint16_t *ptr = format; *ptr; ++ptr) { + fmt.push_back(static_cast(*ptr)); + } + fmt.push_back(L'\0'); + va_list args; + va_start(args, format); + int result = std::vswscanf(bufW.c_str(), fmt.c_str(), args); + va_end(args); + return result; + } + int* WIN_ENTRY _get_osfhandle(int fd){ DEBUG_LOG("STUB: _get_osfhandle %d\n", fd); return (int*)fd; @@ -1457,6 +1683,8 @@ static void *resolveByName(const char *name) { if (strcmp(name, "_commode") == 0) return (void *)&msvcrt::_commode; if (strcmp(name, "__initenv") == 0) return (void *)&msvcrt::__initenv; if (strcmp(name, "__winitenv") == 0) return (void *)&msvcrt::__winitenv; + if (strcmp(name, "__iob_func") == 0) return (void *) msvcrt::__iob_func; + if (strcmp(name, "_exit") == 0) return (void *) msvcrt::_exit; if (strcmp(name, "__p__fmode") == 0) return (void *) msvcrt::__p__fmode; if (strcmp(name, "__p__commode") == 0) return (void *) msvcrt::__p__commode; if (strcmp(name, "_initterm") == 0) return (void *)msvcrt::_initterm; @@ -1472,6 +1700,11 @@ static void *resolveByName(const char *name) { if (strcmp(name, "strlen") == 0) return (void *)msvcrt::strlen; if (strcmp(name, "strcmp") == 0) return (void *)msvcrt::strcmp; if (strcmp(name, "strncmp") == 0) return (void *)msvcrt::strncmp; + if (strcmp(name, "strcpy_s") == 0) return (void *)msvcrt::strcpy_s; + if (strcmp(name, "strcat_s") == 0) return (void *)msvcrt::strcat_s; + if (strcmp(name, "strncpy_s") == 0) return (void *)msvcrt::strncpy_s; + if (strcmp(name, "_strdup") == 0) return (void *)msvcrt::_strdup; + if (strcmp(name, "strtoul") == 0) return (void *)msvcrt::strtoul; if (strcmp(name, "malloc") == 0) return (void*)msvcrt::malloc; if (strcmp(name, "calloc") == 0) return (void*)msvcrt::calloc; if (strcmp(name, "_malloc_crt") == 0) return (void*)msvcrt::_malloc_crt; @@ -1490,6 +1723,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "memcpy") == 0) return (void*)msvcrt::memcpy; if (strcmp(name, "memmove") == 0) return (void*)msvcrt::memmove; if (strcmp(name, "memcmp") == 0) return (void*)msvcrt::memcmp; + if (strcmp(name, "qsort") == 0) return (void*)msvcrt::qsort; if (strcmp(name, "fflush") == 0) return (void*)msvcrt::fflush; if (strcmp(name, "fopen") == 0) return (void*)msvcrt::fopen; if (strcmp(name, "fseek") == 0) return (void*)msvcrt::fseek; @@ -1498,13 +1732,18 @@ static void *resolveByName(const char *name) { if (strcmp(name, "fgetws") == 0) return (void*)msvcrt::fgetws; if (strcmp(name, "fgetwc") == 0) return (void*)msvcrt::fgetwc; if (strcmp(name, "fputws") == 0) return (void*)msvcrt::fputws; + if (strcmp(name, "_cputws") == 0) return (void*)msvcrt::_cputws; + if (strcmp(name, "vfwprintf") == 0) return (void*)msvcrt::vfwprintf; if (strcmp(name, "_wfopen_s") == 0) return (void*)msvcrt::_wfopen_s; if (strcmp(name, "wcsspn") == 0) return (void*)msvcrt::wcsspn; + if (strcmp(name, "_fileno") == 0) return (void*)msvcrt::_fileno; if (strcmp(name, "_wtol") == 0) return (void*)msvcrt::_wtol; if (strcmp(name, "_wcsupr_s") == 0) return (void*)msvcrt::_wcsupr_s; if (strcmp(name, "_wcslwr_s") == 0) return (void*)msvcrt::_wcslwr_s; if (strcmp(name, "_dup2") == 0) return (void*)msvcrt::_dup2; if (strcmp(name, "_isatty") == 0) return (void*)msvcrt::_isatty; + if (strcmp(name, "swprintf_s") == 0) return (void*)msvcrt::swprintf_s; + if (strcmp(name, "swscanf_s") == 0) return (void*)msvcrt::swscanf_s; if (strcmp(name, "towlower") == 0) return (void*)msvcrt::towlower; if (strcmp(name, "_ftime64_s") == 0) return (void*)msvcrt::_ftime64_s; if (strcmp(name, "_crt_debugger_hook") == 0) return (void*)msvcrt::_crt_debugger_hook; @@ -1514,10 +1753,12 @@ static void *resolveByName(const char *name) { if (strcmp(name, "_except_handler4_common") == 0) return (void*)msvcrt::_except_handler4_common; if (strcmp(name, "_XcptFilter") == 0) return (void*)msvcrt::_XcptFilter; if (strcmp(name, "?terminate@@YAXXZ") == 0) return (void*)msvcrt::terminateShim; + if (strcmp(name, "_purecall") == 0) return (void*)msvcrt::_purecall; if (strcmp(name, "wcsncpy_s") == 0) return (void*)msvcrt::wcsncpy_s; if (strcmp(name, "wcsncat_s") == 0) return (void*)msvcrt::wcsncat_s; if (strcmp(name, "_itow_s") == 0) return (void*)msvcrt::_itow_s; if (strcmp(name, "_wtoi") == 0) return (void*)msvcrt::_wtoi; + if (strcmp(name, "_ltoa_s") == 0) return (void*)msvcrt::_ltoa_s; if (strcmp(name, "wcscpy_s") == 0) return (void*)msvcrt::wcscpy_s; if (strcmp(name, "_get_osfhandle") == 0) return (void*)msvcrt::_get_osfhandle; if (strcmp(name, "_write") == 0) return (void*)msvcrt::_write; diff --git a/module_registry.cpp b/module_registry.cpp index 926f647..645c309 100644 --- a/module_registry.cpp +++ b/module_registry.cpp @@ -234,23 +234,31 @@ std::optional combineAndFind(const std::filesystem::path std::vector collectSearchDirectories(ModuleRegistry ®, bool alteredSearchPath) { std::vector dirs; std::unordered_set seen; - auto addDirectory = [&](const std::filesystem::path &dir) { - if (dir.empty()) - return; - std::error_code ec; - auto canonical = std::filesystem::weakly_canonical(dir, ec); + + auto addDirectory = [&](const std::filesystem::path &dir) { + if (dir.empty()) + return; + std::error_code ec; + auto canonical = std::filesystem::weakly_canonical(dir, ec); if (ec) { canonical = std::filesystem::absolute(dir, ec); } if (ec) return; - if (!std::filesystem::exists(canonical, ec) || ec) - return; - std::string key = stringToLower(canonical.string()); - if (seen.insert(key).second) { - dirs.push_back(canonical); - } - }; + if (!std::filesystem::exists(canonical, ec) || ec) + return; + std::string key = stringToLower(canonical.string()); + if (seen.insert(key).second) { + dirs.push_back(canonical); + } + }; + + if (wibo::argv && wibo::argc > 0 && wibo::argv[0]) { + std::filesystem::path mainBinary = std::filesystem::absolute(wibo::argv[0]); + if (mainBinary.has_parent_path()) { + addDirectory(mainBinary.parent_path()); + } + } if (reg.dllDirectory.has_value()) { addDirectory(*reg.dllDirectory); From 1c35da4801af7a1cce3bc58f1b6752be3544d2de Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 28 Sep 2025 19:52:15 -0600 Subject: [PATCH 26/28] Implement GetComputerNameW --- dll/kernel32.cpp | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/dll/kernel32.cpp b/dll/kernel32.cpp index 574cafa..3ba1ee0 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -2882,10 +2882,45 @@ namespace kernel32 { int WIN_FUNC GetComputerNameA(char *lpBuffer, unsigned int *nSize) { DEBUG_LOG("GetComputerNameA\n"); - if (*nSize < 9) + if (!nSize || !lpBuffer) { + if (nSize) { + *nSize = 0; + } + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + constexpr unsigned int required = 9; // "COMPNAME" + null terminator + if (*nSize < required) { + *nSize = required; + wibo::lastError = ERROR_BUFFER_OVERFLOW; return 0; + } strcpy(lpBuffer, "COMPNAME"); - *nSize = 8; + *nSize = required - 1; + wibo::lastError = ERROR_SUCCESS; + return 1; + } + + int WIN_FUNC GetComputerNameW(uint16_t *lpBuffer, unsigned int *nSize) { + DEBUG_LOG("GetComputerNameW\n"); + if (!nSize || !lpBuffer) { + if (nSize) { + *nSize = 0; + } + wibo::lastError = ERROR_INVALID_PARAMETER; + return 0; + } + constexpr uint16_t computerName[] = {'C', 'O', 'M', 'P', 'N', 'A', 'M', 'E', 0}; + constexpr unsigned int nameLength = 8; + constexpr unsigned int required = nameLength + 1; + if (*nSize < required) { + *nSize = required; + wibo::lastError = ERROR_BUFFER_OVERFLOW; + return 0; + } + wstrncpy(lpBuffer, computerName, required); + *nSize = nameLength; + wibo::lastError = ERROR_SUCCESS; return 1; } @@ -3480,6 +3515,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "SetHandleCount") == 0) return (void *) kernel32::SetHandleCount; if (strcmp(name, "FormatMessageA") == 0) return (void *) kernel32::FormatMessageA; if (strcmp(name, "GetComputerNameA") == 0) return (void *) kernel32::GetComputerNameA; + if (strcmp(name, "GetComputerNameW") == 0) return (void *) kernel32::GetComputerNameW; if (strcmp(name, "EncodePointer") == 0) return (void *) kernel32::EncodePointer; if (strcmp(name, "DecodePointer") == 0) return (void *) kernel32::DecodePointer; if (strcmp(name, "SetDllDirectoryA") == 0) return (void *) kernel32::SetDllDirectoryA; From d54ff9b9c6a2b24fb006db5c75c8a4c1a0baca15 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 28 Sep 2025 20:41:40 -0600 Subject: [PATCH 27/28] Add git to Dockerfile.ubuntu --- Dockerfile.ubuntu | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index 550b606..286e721 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -12,6 +12,7 @@ RUN apt-get update \ g++-multilib \ gcc-mingw-w64-i686 \ gdb \ + git \ ninja-build \ unzip \ wget \ From d1ed0662d1ecd7bf8c41d325fbd966730e37f0f3 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 28 Sep 2025 21:08:29 -0600 Subject: [PATCH 28/28] Also add ca-certificates --- Dockerfile.ubuntu | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index 286e721..8e1efc8 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -7,6 +7,7 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends \ binutils \ binutils-mingw-w64-i686 \ + ca-certificates \ cmake \ file \ g++-multilib \