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/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85ca5f7..fb9a056 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,51 +11,96 @@ 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 - - - name: Build debug - run: docker build --build-arg build_type=Debug --target export --output build_debug . + - name: Build + run: >- + docker build + -f ${{ matrix.dockerfile }} + --build-arg build_type=${{ matrix.build_type }} + --target build + -t ${{ matrix.image }} + . - - name: Build release - run: docker build --build-arg build_type=Release --target export --output build . + - name: Tests + run: docker run --rm ${{ matrix.image }} ctest --test-dir /wibo/build --output-on-failure - - name: Test - shell: bash + - name: Export binary 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: Upload release + rm -rf dist + docker build \ + -f ${{ matrix.dockerfile }} \ + --build-arg build_type=${{ matrix.build_type }} \ + --target export \ + --output dist \ + . + + - 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/') + uses: softprops/action-gh-release@v2 with: files: | - build/wibo - build/wibo_debug + artifacts/out/wibo + artifacts/out/wibo_debug + draft: true + generate_release_notes: true 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/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ad50cfa --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,39 @@ +# 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 -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. 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. + +## 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. +- 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 6c89538..965e7f5 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 @@ -24,6 +37,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 @@ -31,9 +45,121 @@ add_executable(wibo files.cpp handles.cpp loader.cpp + resources.cpp + module_registry.cpp main.cpp 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) + +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_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} + ${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_bcrypt.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_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 + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + endif() + endif() +endif() diff --git a/Dockerfile b/Dockerfile index 2fa409c..67ba719 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,16 +2,30 @@ 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 \ + git \ + 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 new file mode 100644 index 0000000..8e1efc8 --- /dev/null +++ b/Dockerfile.ubuntu @@ -0,0 +1,43 @@ +# 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 \ + binutils \ + binutils-mingw-w64-i686 \ + ca-certificates \ + cmake \ + file \ + g++-multilib \ + gcc-mingw-w64-i686 \ + gdb \ + git \ + ninja-build \ + unzip \ + wget \ + && rm -rf /var/lib/apt/lists/* + +# Copy source files +WORKDIR /wibo +COPY . /wibo + +# 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 . + +# Runnable container +FROM ubuntu:24.04 +COPY --from=build /wibo/build/wibo /usr/local/sbin/wibo +CMD ["/usr/local/sbin/wibo"] diff --git a/README.md b/README.md index 586adec..e980d20 100644 --- a/README.md +++ b/README.md @@ -4,19 +4,47 @@ 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 [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`). + +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. --- 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 -- Make the PE loader work for DLLs as well in case we ever want to load some --- diff --git a/common.h b/common.h index 0ac0157..b132019 100644 --- a/common.h +++ b/common.h @@ -1,13 +1,19 @@ +#pragma once + #include #include #include #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. @@ -20,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; @@ -57,8 +64,16 @@ 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 +#define ERROR_RESOURCE_LANG_NOT_FOUND 1815 +#define ERROR_MOD_NOT_FOUND 126 #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) @@ -66,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) @@ -84,6 +101,7 @@ namespace wibo { extern std::vector commandLineW; extern bool debugEnabled; extern unsigned int debugIndent; + extern uint16_t tibSelector; void debug_log(const char *fmt, ...); @@ -94,12 +112,58 @@ 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); 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) {} + 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(); @@ -110,21 +174,48 @@ namespace wibo { size_t imageSize; void *entryPoint; void *rsrcBase; + uint32_t rsrcSize; + uintptr_t preferredImageBase; + intptr_t relocationDelta; + uint32_t exportDirectoryRVA; + uint32_t exportDirectorySize; + 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); } }; 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/advapi32.cpp b/dll/advapi32.cpp index f3fc490..0263be0 100644 --- a/dll/advapi32.cpp +++ b/dll/advapi32.cpp @@ -1,5 +1,288 @@ #include "common.h" +#include "handles.h" +#include "strutil.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) { @@ -7,34 +290,294 @@ 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 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 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; + } + + 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); + } - return true; + 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 auto 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 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; + return FALSE; + } + auto *sid = reinterpret_cast(sidPointer); + if (!isLocalSystemSid(sid)) { + wibo::lastError = ERROR_NONE_MAPPED; + return FALSE; + } + 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::copy_n(accountName, requiredAccount, Name); + std::copy_n(domainName, requiredDomain, ReferencedDomainName); + *peUse = SidTypeWellKnownGroup; + *cchName = requiredAccount - 1; + *cchReferencedDomainName = requiredDomain - 1; + wibo::lastError = ERROR_SUCCESS; + return TRUE; } } @@ -43,6 +586,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/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 28822f0..ec5dd16 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -1,9 +1,23 @@ #include "common.h" +#include +#include +#include +#include +#include +#include #include typedef void (*_PVFV)(); typedef int (*_PIFV)(); +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); +} typedef enum _crt_app_type { _crt_unknown_app, @@ -20,8 +34,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 +61,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 +81,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 +110,57 @@ 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 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); } + +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); } + +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; +} + +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 +170,55 @@ 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); +} + +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) { @@ -112,10 +232,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 +250,60 @@ 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, "strcmp") == 0) + return (void *)crt::strcmp; + 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, "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) + 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, "__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) + 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 +319,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..73d2042 100644 --- a/dll/kernel32.cpp +++ b/dll/kernel32.cpp @@ -2,26 +2,117 @@ #include "files.h" #include "processes.h" #include "handles.h" +#include "resources.h" #include #include #include #include +#include #include +#include #include #include +#include #include +#include #include "strutil.h" -#include +#include #include #include #include #include +#include #include #include #include #include +#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; + 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; @@ -48,9 +139,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; } @@ -58,15 +149,53 @@ 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); } 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()) { @@ -133,6 +262,36 @@ 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) { + *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; @@ -176,6 +335,62 @@ 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)); + + lpSystemInfo->wProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL; + lpSystemInfo->wReserved = 0; + lpSystemInfo->dwOemId = lpSystemInfo->wProcessorArchitecture; + lpSystemInfo->dwProcessorType = PROCESSOR_INTEL_PENTIUM; + lpSystemInfo->wProcessorLevel = 6; // Pentium + 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; @@ -184,115 +399,175 @@ 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; - } + } + + 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() { @@ -355,9 +630,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); } @@ -487,7 +770,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; @@ -517,7 +800,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; @@ -575,12 +858,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; } @@ -612,21 +901,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; } @@ -654,6 +959,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) { @@ -721,6 +1041,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; @@ -1122,62 +1465,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) { @@ -1565,8 +2065,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 +2093,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 +2137,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,65 +2174,74 @@ 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); - } - else { - name = wideStringToString(lpName); + static wibo::Executable *module_executable_for_resource(void *hModule) { + if (!hModule) { + hModule = GetModuleHandleA(nullptr); } + return wibo::executableFromModule((HMODULE) hModule); + } - if((uintptr_t)lpType >> 16 == 0){ - type = std::to_string((unsigned int)(uintptr_t)lpType); + 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; } - else { - type = wideStringToString(lpType); + wibo::ResourceLocation loc; + if (!exe->findResource(type, name, language, loc)) { + return nullptr; } - - 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; + return const_cast(loc.dataEntry); } - 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; + void *WIN_FUNC FindResourceA(void *hModule, const char *lpName, const char *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); + } - 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); + 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); + } - if(size <= 0) return nullptr; + 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); + } - void* buffer = malloc(size); - if(!buffer) return nullptr; + 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); + } - if(fread(buffer, 1, size, hRes) != (size_t)size){ - free(buffer); + void* WIN_FUNC LoadResource(void* hModule, void* res) { + DEBUG_LOG("LoadResource %p %p\n", hModule, res); + 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, @@ -1736,12 +2270,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) { @@ -1749,6 +2297,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); @@ -1813,19 +2370,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) { @@ -1843,25 +2415,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) { @@ -1869,6 +2422,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. @@ -1956,6 +2554,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 @@ -1966,6 +2690,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 @@ -2111,7 +2840,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() { @@ -2161,10 +2890,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; } @@ -2177,7 +2941,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; } @@ -2307,13 +3085,96 @@ 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; + 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) { @@ -2325,35 +3186,88 @@ 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 */); + 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) { + 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(); + 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) { - 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; @@ -2362,6 +3276,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; @@ -2524,6 +3448,10 @@ 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; if (strcmp(name, "AddVectoredExceptionHandler") == 0) return (void *) kernel32::AddVectoredExceptionHandler; // processthreadsapi.h @@ -2533,6 +3461,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; @@ -2542,6 +3471,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 @@ -2560,6 +3490,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; @@ -2569,12 +3500,15 @@ 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; 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; @@ -2584,13 +3518,19 @@ 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; + 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; + 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; @@ -2601,6 +3541,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 @@ -2618,6 +3559,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; @@ -2630,9 +3572,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; @@ -2650,6 +3594,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; @@ -2673,7 +3618,9 @@ 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; if (strcmp(name, "GetProcAddress") == 0) return (void *) kernel32::GetProcAddress; diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 9b0cede..2aa2bdb 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -1,26 +1,347 @@ #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 #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; + 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; + } + + IOBProxy *WIN_ENTRY __iob_func() { + return standardIobEntries(); + } + + 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; + } + + 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); + } + + 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(); + } + + 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 = stringToLower(result); + std::string loweredExe = stringToLower(exeDir); + 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; + 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) { + std::string normalized = normalizeEnvStringForWindows(src); + size_t len = normalized.size(); + std::vector result(len + 1); + if (len > 0) { + std::memcpy(result.data(), normalized.data(), len); + } + result[len] = '\0'; + return result; + } + + std::vector copyWideString(const char *src) { + std::string normalized = normalizeEnvStringForWindows(src); + return stringToWideString(normalized.c_str()); + } + + 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,161 +384,768 @@ 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; + + // 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"); + } std::setlocale(LC_CTYPE, ""); + return getMainArgsCommon(wargc, wargv, wenv, copyWideString); + } - 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]; + // 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 getMainArgsCommon(argc, argv, env, copyNarrowString); + } - 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; + char* WIN_ENTRY getenv(const char *varname){ + return std::getenv(varname); + } + +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) { + *buffer = nullptr; + } + if (numberOfElements) { + *numberOfElements = 0; } - 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); + if (!buffer || !varname) { + DEBUG_LOG("_wdupenv_s: invalid parameter\n"); + errno = EINVAL; + return EINVAL; + } - std::vector wStr = stringToWideString(cur_env); + std::string var_str = wideStringToString(varname); + DEBUG_LOG("_wdupenv_s: var name %s\n", var_str.c_str()); - // 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; - } + 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; + } - (*wenv)[count] = nullptr; + 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; + } - __winitenv = *wenv; + wstrncpy(copy, match->value, value_len); + copy[value_len] = 0; + *buffer = copy; + if (numberOfElements) { + *numberOfElements = value_len + 1; } return 0; } - char* WIN_ENTRY getenv(const char *varname){ - return std::getenv(varname); - } + 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; + } - char* WIN_ENTRY setlocale(int category, const char *locale){ - return std::setlocale(category, locale); - } + bool bufferRequired = numberOfElements != 0; + if (!pReturnValue || !varname || (bufferRequired && !buffer)) { + DEBUG_LOG("_wgetenv_s: invalid parameter\n"); + errno = EINVAL; + return EINVAL; + } - 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; + DEBUG_LOG("_wgetenv_s: var name %s\n", var_str.c_str()); - size_t varnamelen = wstrlen(varname); + auto env = ensureWideEnvironment(); + auto match = findEnvironmentValue(env, varname); + if (!match) { + return 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); + size_t required = match->length + 1; + *pReturnValue = required; + if (!bufferRequired || !buffer) { + return 0; + } - uint16_t* copy = (uint16_t*)malloc((value_len + 1) * sizeof(uint16_t)); - if(!copy) return 12; + if (required > numberOfElements) { + errno = ERANGE; + return ERANGE; + } - wstrncpy(copy, value, value_len + 1); - *buffer = copy; + wstrncpy(buffer, match->value, match->length); + buffer[match->length] = 0; + return 0; + } - if(numberOfElements) *numberOfElements = value_len + 1; - 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 _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; } - DEBUG_LOG("Could not find env var %s\n", var_str.c_str()); + std::memcpy(dest, src, src_len + 1); return 0; } - int WIN_ENTRY _wgetenv_s(size_t* pReturnValue, uint16_t* buffer, size_t numberOfElements, const uint16_t* varname){ - std::string var_str = wideStringToString(varname); - DEBUG_LOG("_wgetenv_s: var name %s\n", var_str.c_str()); - if(!buffer || !varname) return 22; + int WIN_ENTRY strcat_s(char *dest, size_t numberOfElements, const char *src) { + if (!dest || !src || numberOfElements == 0) { + return 22; + } - size_t varnamelen = wstrlen(varname); + size_t dest_len = ::strlen(dest); + size_t src_len = ::strlen(src); + if (dest_len + src_len + 1 > numberOfElements) { + dest[0] = 0; + return 34; + } - 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); + std::memcpy(dest + dest_len, src, src_len + 1); + return 0; + } - size_t copy_len = (value_len < numberOfElements - 1) ? value_len : numberOfElements - 1; - wstrncpy(buffer, value, copy_len); - buffer[copy_len] = 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(pReturnValue) *pReturnValue = value_len + 1; - return 0; + 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; } - buffer[0] = 0; - if(pReturnValue) *pReturnValue = 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); } + void* WIN_ENTRY calloc(size_t count, size_t size){ + 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); + } + + 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); } + 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 memcmp(const void *lhs, const void *rhs, size_t count) { + 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) { + 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) { + 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); + } + + 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; + } + 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; + } + + 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 = wcharToLower(*lhs++); + uint16_t b = wcharToLower(*rhs++); + if (a != b) { + return static_cast(a) - static_cast(b); + } + } + + uint16_t a = wcharToLower(*lhs); + uint16_t b = wcharToLower(*rhs); + 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; + } + + 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"); + 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 _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; + } + + 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; @@ -318,7 +1246,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++){ @@ -331,9 +1259,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){ @@ -410,6 +1346,42 @@ namespace msvcrt { 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()); @@ -426,6 +1398,54 @@ namespace msvcrt { 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; @@ -471,7 +1491,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 } @@ -499,27 +1519,26 @@ 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)"; + } + 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){ @@ -530,9 +1549,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; } @@ -541,10 +1560,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){ @@ -593,7 +1664,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; @@ -610,27 +1681,84 @@ 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, "__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; 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, "__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; + 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; + 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, "_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, "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; + 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, "_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; + 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, "_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; @@ -649,12 +1777,26 @@ 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; 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/dll/psapi.cpp b/dll/psapi.cpp new file mode 100644 index 0000000..70657f3 --- /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; +} +} // namespace psapi + +static void *resolveByName(const char *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) { + 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..9ee967f --- /dev/null +++ b/dll/rpcrt4.cpp @@ -0,0 +1,282 @@ +#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/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/dll/version.cpp b/dll/version.cpp index e558b2f..2bbefcf 100644 --- a/dll/version.cpp +++ b/dll/version.cpp @@ -1,18 +1,316 @@ #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 + +namespace { + +constexpr uint32_t RT_VERSION = 16; + +static uint16_t readU16(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 = 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; + } + + 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 = readU16(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 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 = stringToLower(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 = stringToLower(narrowKey(child.key)); + if (childKeyLower == targetLower) { + if (queryVersionBlock(childStart, child.totalLength, segments, depth + 1, outPtr, outLen, outType)) + return true; + } + const auto 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 = readU16(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/files.cpp b/files.cpp index 2701d30..034ca98 100644 --- a/files.cpp +++ b/files.cpp @@ -1,11 +1,59 @@ #include "common.h" #include "files.h" #include "handles.h" +#include "strutil.h" #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; + toLowerInPlace(needle); + for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) { + if (ec) { + break; + } + std::string candidate = entry.path().filename().string(); + toLowerInPlace(candidate); + 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 d86e927..329a4d4 100644 --- a/files.h +++ b/files.h @@ -1,4 +1,7 @@ +#pragma once + #include +#include #include namespace files { @@ -9,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/handles.h b/handles.h index f751845..cfe0f38 100644 --- a/handles.h +++ b/handles.h @@ -1,11 +1,15 @@ -#include +#pragma once + +#include 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 bd194fe..ec1a531 100644 --- a/loader.cpp +++ b/loader.cpp @@ -92,6 +92,25 @@ 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; +}; + +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 +128,13 @@ wibo::Executable::Executable() { imageSize = 0; entryPoint = nullptr; rsrcBase = 0; + rsrcSize = 0; + preferredImageBase = 0; + relocationDelta = 0; + exportDirectoryRVA = 0; + exportDirectorySize = 0; + relocationDirectoryRVA = 0; + relocationDirectorySize = 0; } wibo::Executable::~Executable() { @@ -150,20 +176,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); @@ -188,6 +223,49 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { if (strcmp(name, ".rsrc") == 0) { rsrcBase = sectionBase; + rsrcSize = std::max(section.virtualSize, section.sizeOfRawData); + } + } + + 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; } } @@ -212,22 +290,63 @@ 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 = 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\n", hintName->name); - *addressTable = reinterpret_cast(resolveFuncByName(module, hintName->name)); + DEBUG_LOG(" Name: %s (IAT=%p)\n", hintName->name, addressTable); + void *func = module ? resolveFuncByName(module, hintName->name) + : resolveMissingImportByName(dllName, hintName->name); + DEBUG_LOG(" -> %p\n", func); + *addressTable = reinterpret_cast(func); } ++lookupTable; ++addressTable; } - freeModule(module); - ++dir; } - entryPoint = fromRVA(header32.addressOfEntryPoint); + 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); + 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); + void *func = module ? resolveFuncByName(module, hintName->name) + : resolveMissingImportByName(dllName, hintName->name); + *addressTable = reinterpret_cast(func); + } + ++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/main.cpp b/main.cpp index 9955493..c50b286 100644 --- a/main.cpp +++ b/main.cpp @@ -1,15 +1,17 @@ #include "common.h" #include "files.h" +#include "strutil.h" #include +#include +#include +#include #include #include -#include "strutil.h" +#include #include #include -#include +#include #include -#include -#include uint32_t wibo::lastError = 0; char** wibo::argv; @@ -20,6 +22,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; @@ -34,145 +37,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; @@ -222,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. * @@ -324,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(); @@ -362,15 +294,20 @@ int main(int argc, char **argv) { return 1; } + 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) @@ -407,13 +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; @@ -424,14 +363,14 @@ 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(); return 1; } diff --git a/module_registry.cpp b/module_registry.cpp new file mode 100644 index 0000000..645c309 --- /dev/null +++ b/module_registry.cpp @@ -0,0 +1,890 @@ +#include "common.h" +#include "files.h" +#include "strutil.h" + +#include +#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_rpcrt4; +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; +}; + +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); + toLowerInPlace(key); + } + key.push_back(':'); + if (funcName) { + std::string func(funcName); + toLowerInPlace(func); + key += func; + } + return key; +} + +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); + fflush(stderr); + abort(); +} + +template void stubThunk() { stubBase(Index); } + +template +constexpr std::array makeStubTable(std::index_sequence) { + return {{stubThunk...}}; +} + +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 >= 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 : ""; + StubFuncType stub = stubFuncs[stubIndex]; + stubCache.emplace(std::move(key), stub); + stubIndex++; + return stub; +} + +StubFuncType resolveMissingFuncOrdinal(const char *dllName, uint16_t ordinal) { + char buf[16]; + sprintf(buf, "%d", ordinal); + return resolveMissingFuncName(dllName, buf); +} + +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; + std::unordered_map> builtinAliasLists; + std::unordered_map builtinAliasMap; + std::unordered_set pinnedAliases; + std::unordered_set pinnedModules; +}; + +struct LockedRegistry { + ModuleRegistry *reg; + std::unique_lock lock; + + 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(), '/', '\\'); + toLowerInPlace(out); + 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 base = parsed.base; + if (!parsed.hasExtension && !parsed.endsWithDot) { + base += ".dll"; + } + return normalizeAlias(base); +} + +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 files::findCaseInsensitiveFile(directory, filename); +} + +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); + 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 (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); + } + + if (!alteredSearchPath) { + addDirectory(std::filesystem::current_path()); + } + + if (const char *envPath = std::getenv("WIBO_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(ModuleRegistry ®, 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 = files::findCaseInsensitiveFile(std::filesystem::path(posixPath).parent_path(), + std::filesystem::path(posixPath).filename().string()); + if (resolved) { + return files::canonicalPath(*resolved); + } + } + } + return std::nullopt; + } + + auto dirs = collectSearchDirectories(reg, alteredSearchPath); + for (const auto &dir : dirs) { + for (const auto &candidate : names) { + auto resolved = combineAndFind(dir, candidate); + if (resolved) { + return files::canonicalPath(*resolved); + } + } + } + + return std::nullopt; +} + +std::string storageKeyForPath(const std::filesystem::path &path) { + return normalizeAlias(files::pathToWindows(files::canonicalPath(path))); +} + +std::string storageKeyForBuiltin(const std::string &normalizedName) { return normalizedName; } + +wibo::ModuleInfo *findByAlias(ModuleRegistry ®, const std::string &alias) { + auto it = reg.modulesByAlias.find(alias); + if (it != reg.modulesByAlias.end()) { + return it->second; + } + return nullptr; +} + +void registerAlias(ModuleRegistry ®, const std::string &alias, wibo::ModuleInfo *info) { + if (alias.empty() || !info) { + return; + } + auto it = reg.modulesByAlias.find(alias); + if (it == reg.modulesByAlias.end()) { + 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; + } +} + +void registerBuiltinModule(ModuleRegistry ®, 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(); + reg.modulesByKey[storageKey] = std::move(entry); + + 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(reg, 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(reg, baseAlias, raw); + reg.builtinAliasMap[baseAlias] = 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; + } + + 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 = invokeWithGuestTIB(reason); + info.processAttachSucceeded = result != 0; + } else if (reason == DLL_PROCESS_DETACH) { + if (info.processAttachCalled && info.processAttachSucceeded) { + invokeWithGuestTIB(reason); + } + } +} + +void registerExternalModuleAliases(ModuleRegistry ®, const std::string &requestedName, + const std::filesystem::path &resolvedPath, wibo::ModuleInfo *info) { + ParsedModuleName parsed = parseModuleName(requestedName); + registerAlias(reg, normalizedBaseKey(parsed), info); + registerAlias(reg, normalizeAlias(requestedName), info); + registerAlias(reg, storageKeyForPath(resolvedPath), info); +} + +wibo::ModuleInfo *moduleFromAddress(ModuleRegistry ®, void *addr) { + if (!addr) + return nullptr; + 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; + auto *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] = + reinterpret_cast(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]; + 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; + } + } + } + info.exportsInitialized = true; +} + +} // namespace + +namespace wibo { + +void initializeModuleRegistry() { registry(); } + +void shutdownModuleRegistry() { + auto reg = registry(); + for (auto &pair : reg->modulesByKey) { + ModuleInfo *info = pair.second.get(); + if (!info || info->module) { + continue; + } + runPendingOnExit(*info); + if (info->processAttachCalled && info->processAttachSucceeded) { + callDllMain(*info, DLL_PROCESS_DETACH); + } + } + 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); + auto reg = registry(); + reg->dllDirectory = canonical; +} + +void clearDllDirectoryOverride() { + auto reg = registry(); + reg->dllDirectory.reset(); +} + +std::optional dllDirectoryOverride() { + auto reg = registry(); + return reg->dllDirectory; +} + +void registerOnExitTable(void *table) { + if (!table) + return; + auto reg = registry(); + if (reg->onExitTables.find(table) == reg->onExitTables.end()) { + if (auto *info = moduleFromAddress(*reg, table)) { + reg->onExitTables[table] = info; + } + } +} + +void addOnExitFunction(void *table, void (*func)()) { + if (!func) + return; + auto reg = registry(); + ModuleInfo *info = nullptr; + auto it = reg->onExitTables.find(table); + if (it != reg->onExitTables.end()) { + info = it->second; + } else if (table) { + info = moduleFromAddress(*reg, 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) { + auto reg = 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(*reg, table); + } + } + if (info) { + runPendingOnExit(*info); + } +} + +HMODULE findLoadedModule(const char *name) { + if (!name) { + return nullptr; + } + auto reg = registry(); + ParsedModuleName parsed = parseModuleName(name); + std::string alias = normalizedBaseKey(parsed); + ModuleInfo *info = findByAlias(*reg, alias); + if (!info) { + info = findByAlias(*reg, 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()); + + auto reg = registry(); + + ParsedModuleName parsed = parseModuleName(requested); + + 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(*reg, 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(*reg, requested, raw->resolvedPath, raw); + ensureExportsInitialized(*raw); + callDllMain(*raw, DLL_PROCESS_ATTACH); + return raw; + }; + + auto resolveAndLoadExternal = [&]() -> ModuleInfo * { + auto resolvedPath = resolveModuleOnDisk(*reg, 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(*reg, alias); + if (!existing) { + existing = findByAlias(*reg, normalizeAlias(requested)); + } + if (existing) { + 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; + } + 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()); + return existing; + } + + if (ModuleInfo *external = resolveAndLoadExternal()) { + DEBUG_LOG(" loaded external module %s\n", requested.c_str()); + lastError = ERROR_SUCCESS; + return external; + } + + auto fallbackAlias = normalizedBaseKey(parsed); + ModuleInfo *builtin = nullptr; + 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()) { + builtin = builtinIt->second; + } + } + 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) { + if (!module) { + return; + } + auto reg = registry(); + ModuleInfo *info = moduleInfoFromHandle(module); + if (!info || info->refCount == UINT_MAX) { + return; + } + if (info->refCount == 0) { + return; + } + info->refCount--; + if (info->refCount == 0) { + 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 reinterpret_cast(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) { + auto index = static_cast(ordinal - info->exportOrdinalBase); + if (index < info->exportsByOrdinal.size()) { + void *addr = info->exportsByOrdinal[index]; + if (addr) { + return addr; + } + } + } + } + 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 : ""; + [[maybe_unused]] auto reg = registry(); + return reinterpret_cast(resolveMissingFuncName(safeDll, safeFunc)); +} + +void *resolveMissingImportByOrdinal(const char *dllName, uint16_t ordinal) { + const char *safeDll = dllName ? dllName : ""; + [[maybe_unused]] auto reg = registry(); + return reinterpret_cast(resolveMissingFuncOrdinal(safeDll, 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/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 4bcad09..d4519e8 100644 --- a/processes.h +++ b/processes.h @@ -1,5 +1,11 @@ +#pragma once + #include +#include +#include #include +#include +#include namespace processes { struct Process { @@ -9,4 +15,8 @@ namespace processes { void *allocProcessHandle(pid_t pid); Process* processFromHandle(void* hHandle, bool pop); -} \ No newline at end of file + + 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); +} diff --git a/resources.cpp b/resources.cpp new file mode 100644 index 0000000..19bd093 --- /dev/null +++ b/resources.cpp @@ -0,0 +1,241 @@ +#include "resources.h" +#include "common.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..3296e78 --- /dev/null +++ b/resources.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace wibo { + +struct Executable; +struct ImageResourceDataEntry; +struct ResourceIdentifier; + +bool resourceEntryBelongsToExecutable(const Executable &exe, const ImageResourceDataEntry *entry); +ResourceIdentifier resourceIdentifierFromAnsi(const char *id); +ResourceIdentifier resourceIdentifierFromWide(const uint16_t *id); + +} // namespace wibo diff --git a/strutil.cpp b/strutil.cpp index 24b00ff..b6c8e9d 100644 --- a/strutil.cpp +++ b/strutil.cpp @@ -1,208 +1,264 @@ +#include "strutil.h" #include "common.h" -#include "strings.h" +#include +#include +#include #include -#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; - + 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]; +#ifndef NDEBUG + if (i > 0) + hexDump << ' '; + hexDump << "0x" << value; + if (value > 0xFF) + sawWide = true; +#endif + 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..69289f4 100644 --- a/strutil.h +++ b/strutil.h @@ -1,17 +1,26 @@ +#pragma once + +#include #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); +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); 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/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_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_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; +} diff --git a/test/test_external_dll.c b/test/test_external_dll.c new file mode 100644 index 0000000..6c44755 --- /dev/null +++ b/test/test_external_dll.c @@ -0,0 +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); + + HMODULE mod = LoadLibraryA("external_exports.dll"); + TEST_CHECK_MSG(mod != NULL, "LoadLibraryA failed: %lu", (unsigned long)GetLastError()); + + 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)(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(); + + TEST_CHECK_EQ(42, sum); + TEST_CHECK_EQ(1, attached); + + TEST_CHECK_MSG(FreeLibrary(mod) != 0, "FreeLibrary failed: %lu", (unsigned long)GetLastError()); + + 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 new file mode 100644 index 0000000..546ddd4 --- /dev/null +++ b/test/test_resources.c @@ -0,0 +1,76 @@ +#include +#include +#include + +#include "test_assert.h" + +int main(void) { + char buffer[128]; + int copied = LoadStringA(GetModuleHandleA(NULL), 100, buffer, sizeof(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)); + TEST_CHECK_MSG(versionInfo != NULL, "FindResourceA version failed: %lu", (unsigned long)GetLastError()); + + DWORD versionSize = SizeofResource(NULL, versionInfo); + 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)); + TEST_CHECK_MSG(moduleLen > 0 && moduleLen < sizeof(modulePath), + "GetModuleFileNameA failed: %lu", (unsigned long)GetLastError()); + + DWORD handle = 0; + DWORD infoSize = GetFileVersionInfoSizeA(modulePath, &handle); + TEST_CHECK_MSG(infoSize != 0, "GetFileVersionInfoSizeA failed: %lu", (unsigned long)GetLastError()); + + char *infoBuffer = (char *)malloc(infoSize); + TEST_CHECK_MSG(infoBuffer != NULL, "malloc(%lu) failed", (unsigned long)infoSize); + + TEST_CHECK_MSG(GetFileVersionInfoA(modulePath, 0, infoSize, infoBuffer) != 0, + "GetFileVersionInfoA failed: %lu", (unsigned long)GetLastError()); + + VS_FIXEDFILEINFO *fixedInfo = NULL; + unsigned int fixedSize = 0; + 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; + 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); + puts("resource metadata validated"); + return EXIT_SUCCESS; +} 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