From 104e9e869db17a331b094a1d9012ef8764f27aae Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 10:39:09 -0600 Subject: [PATCH] Add proper testing framework & integrate with CI --- .github/workflows/ci.yml | 16 +++++- AGENTS.md | 32 ++++++++++++ CMakeLists.txt | 91 +++++++++++++++++++++++++++++++++ README.md | 33 ++++++++++-- dll/crt.cpp | 4 ++ test/.gitignore | 3 ++ test/Makefile | 32 ------------ test/test_assert.h | 56 ++++++++++++++++++++ test/test_external_dll.c | 42 +++++++-------- test/test_resources.c | 107 ++++++++++++++++++--------------------- 10 files changed, 300 insertions(+), 116 deletions(-) create mode 100644 AGENTS.md create mode 100644 test/.gitignore delete mode 100644 test/Makefile create mode 100644 test/test_assert.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85ca5f7..f4348d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,15 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y file unzip wget + sudo apt-get install -y \ + file \ + unzip \ + wget \ + cmake \ + ninja-build \ + g++-multilib \ + gcc-mingw-w64-i686 \ + binutils-mingw-w64-i686 - name: Build debug run: docker build --build-arg build_type=Debug --target export --output build_debug . @@ -40,6 +48,12 @@ jobs: build/wibo Wii/1.7/mwcceppc.exe -nodefaults -c test/test.c -Itest -o test.o file test.o + - name: Fixture tests + run: | + cmake -S . -B build_ctest -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON -DWIBO_ENABLE_FIXTURE_TESTS=ON + cmake --build build_ctest + ctest --test-dir build_ctest --output-on-failure + - name: Upload release uses: actions/upload-artifact@v4 with: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..af8fd14 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- Core launcher logic lives in `main.cpp`, `loader.cpp`, `files.cpp`, `handles.cpp` and `module_registry.cpp`; shared interfaces in headers near them. +- Windows API shims reside in `dll/`, grouped by emulated DLL name; keep new APIs in the matching file instead of creating ad-hoc helpers. +- Reusable utilities sit in `strutil.*`, `processes.*` and `resources.*`; prefer extending these before introducing new singleton modules. +- Sample fixtures for exercising the loader live in `test/`; keep new repros small and self-contained. + +## Build, Test, and Development Commands +- `cmake -B build -DCMAKE_BUILD_TYPE=Debug` configures a 32-bit toolchain; ensure multilib packages are present. +- `cmake --build build --target wibo` compiles the shim; switch to `-DCMAKE_BUILD_TYPE=Release` for optimised binaries. +- `./build/wibo /path/to/program.exe` runs a Windows binary through the shim; use `WIBO_DEBUG=1` for verbose logging. +- `cmake -B build -DBUILD_TESTING=ON` + `ctest --test-dir build --output-on-failure` runs the self-checking WinAPI fixtures (requires `i686-w64-mingw32-gcc` and `i686-w64-mingw32-windres`). +- `clang-format -i path/to/file.cpp` and `clang-tidy path/to/file.cpp -p build` keep contributions aligned with the repo's tooling. + +## Coding Style & Naming Conventions +- Formatting follows `.clang-format` (LLVM base, tabbed indentation width 4, 120 column limit); never hand-wrap differently. +- Prefer PascalCase for emulated Win32 entry points, camelCase for internal helpers, and SCREAMING_SNAKE_CASE for constants or macros. +- Document non-obvious control flow with short comments and keep platform-specific code paths behind descriptive helper functions. + +## Testing Guidelines +- Fixture binaries live in `test/` and are compiled automatically when `BUILD_TESTING` is enabled; keep new repros small and self-contained (`test_.c`). +- All fixtures must self-assert; use `test_assert.h` helpers so `ctest` fails on mismatched WinAPI behaviour. +- Cross-compile new repros with `i686-w64-mingw32-gcc` (and `i686-w64-mingw32-windres` for resources); CMake handles this during the build, but direct invocation is useful while iterating. +- Run `ctest --test-dir build --output-on-failure` after rebuilding to verify changes; ensure failures print actionable diagnostics. + +## Debugging Workflow +- Reproduce crashes under `gdb` (or `lldb`) with `-q -batch` to capture backtraces, register state, and the faulting instruction without interactive prompts. +- Enable `WIBO_DEBUG=1` and tee output to a log when running the guest binary; loader traces often pinpoint missing imports, resource lookups, or API shims that misbehave. +- Inspect relevant source right away—most issues stem from stubbed shims in `dll/`; compare the guest stack from `gdb` with those implementations. +- When host-side behaviour is suspect (filesystem, execve, etc.), rerun under `strace -f -o `; this highlights missing files or permissions before the shim faults. +- If the `ghidra` MCP tool is available, request that the user import and analyze the guest binary; you can then use it to disassemble/decompile code around the crash site. diff --git a/CMakeLists.txt b/CMakeLists.txt index eaed480..4a9dd13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,3 +39,94 @@ add_executable(wibo ) target_link_libraries(wibo PRIVATE std::filesystem) install(TARGETS wibo DESTINATION bin) + +include(CTest) + +if(BUILD_TESTING) + find_program(WIBO_MINGW_CC i686-w64-mingw32-gcc) + find_program(WIBO_MINGW_WINDRES i686-w64-mingw32-windres) + + set(WIBO_HAVE_MINGW_TOOLCHAIN FALSE) + if(WIBO_MINGW_CC AND WIBO_MINGW_WINDRES) + set(WIBO_HAVE_MINGW_TOOLCHAIN TRUE) + endif() + + option(WIBO_ENABLE_FIXTURE_TESTS "Build and run Windows fixture binaries through wibo" ${WIBO_HAVE_MINGW_TOOLCHAIN}) + + if(WIBO_ENABLE_FIXTURE_TESTS) + if(NOT WIBO_HAVE_MINGW_TOOLCHAIN) + message(WARNING "MinGW toolchain not found; skipping fixture tests") + else() + set(WIBO_TEST_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}/test) + file(MAKE_DIRECTORY ${WIBO_TEST_BIN_DIR}) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/external_exports.dll + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 -shared + -o external_exports.dll + ${CMAKE_CURRENT_SOURCE_DIR}/test/external_exports.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/test/external_exports.c) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_external_dll.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_external_dll.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_external_dll.c + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_external_dll.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_resources_res.o + COMMAND ${WIBO_MINGW_WINDRES} + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_resources.rc + -O coff -o test_resources_res.o + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/test/test_resources.rc) + + add_custom_command( + OUTPUT ${WIBO_TEST_BIN_DIR}/test_resources.exe + COMMAND ${WIBO_MINGW_CC} -Wall -Wextra -O2 + -I${CMAKE_CURRENT_SOURCE_DIR}/test + -o test_resources.exe + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_resources.c + test_resources_res.o -lversion + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_resources.c + ${CMAKE_CURRENT_SOURCE_DIR}/test/test_assert.h + ${WIBO_TEST_BIN_DIR}/test_resources_res.o) + + add_custom_target(wibo_test_fixtures + DEPENDS + ${WIBO_TEST_BIN_DIR}/external_exports.dll + ${WIBO_TEST_BIN_DIR}/test_external_dll.exe + ${WIBO_TEST_BIN_DIR}/test_resources.exe) + + if(CMAKE_CONFIGURATION_TYPES) + set(_wibo_fixture_build_command + ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --config $ --target wibo_test_fixtures) + else() + set(_wibo_fixture_build_command + ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target wibo_test_fixtures) + endif() + + add_test(NAME wibo.build_fixtures COMMAND ${_wibo_fixture_build_command}) + + add_test(NAME wibo.test_external_dll + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_external_dll.exe) + set_tests_properties(wibo.test_external_dll PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + + add_test(NAME wibo.test_resources + COMMAND $ ${WIBO_TEST_BIN_DIR}/test_resources.exe) + set_tests_properties(wibo.test_resources PROPERTIES + WORKING_DIRECTORY ${WIBO_TEST_BIN_DIR} + DEPENDS wibo.build_fixtures) + endif() + endif() +endif() diff --git a/README.md b/README.md index b9d6c44..93857be 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,36 @@ A minimal, low-fuss wrapper that can run really simple command-line 32-bit Windo Don't run this on any untrusted executables, I implore you. (Or probably just don't run it at all... :p) - cmake -B build - cmake --build build - build/wibo +## Building + +```sh +cmake -B build -DCMAKE_BUILD_TYPE=Debug +cmake --build build --target wibo +``` + +`cmake -B build -DCMAKE_BUILD_TYPE=Release` to produce an optimized binary instead. + +## Running + +```sh +./build/wibo /path/to/program.exe +# or, with debug logging: +WIBO_DEBUG=1 ./build/wibo /path/to/program.exe +``` + +## Tests + +Self-checking Windows fixtures run through CTest. They require a 32-bit MinGW cross toolchain (`i686-w64-mingw32-gcc` and `i686-w64-mingw32-windres`). + +With the toolchain installed: + +```sh +cmake -B build -DBUILD_TESTING=ON +cmake --build build +ctest --test-dir build --output-on-failure +``` + +This will cross-compile the fixture executables, run them through `wibo`, and fail if any WinAPI expectations are not met. --- diff --git a/dll/crt.cpp b/dll/crt.cpp index 93d1e0f..f7208cd 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -117,6 +117,8 @@ int *WIN_ENTRY __p___argc() { return &wibo::argc; } size_t WIN_ENTRY strlen(const char *str) { return ::strlen(str); } +int WIN_ENTRY strcmp(const char *lhs, const char *rhs) { return ::strcmp(lhs, rhs); } + int WIN_ENTRY strncmp(const char *lhs, const char *rhs, size_t count) { return ::strncmp(lhs, rhs, count); } void *WIN_ENTRY malloc(size_t size) { return ::malloc(size); } @@ -247,6 +249,8 @@ static void *resolveByName(const char *name) { return (void *)crt::__p___argc; if (strcmp(name, "strlen") == 0) return (void *)crt::strlen; + if (strcmp(name, "strcmp") == 0) + return (void *)crt::strcmp; if (strcmp(name, "strncmp") == 0) return (void *)crt::strncmp; if (strcmp(name, "malloc") == 0) diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..d19fcf8 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,3 @@ +*.o +*.dll +*.exe diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index 1fad9e8..0000000 --- a/test/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -CC = i686-w64-mingw32-gcc -WINDRES = i686-w64-mingw32-windres -CFLAGS = -Wall -Wextra -O2 - -DLL_SRC = external_exports.c -EXE_SRC = test_external_dll.c -DLL = external_exports.dll -EXE = test_external_dll.exe - -RES_EXE_SRC = test_resources.c -RES_RC = test_resources.rc -RES_OBJ = test_resources_res.o -RES_EXE = test_resources.exe - -all: $(DLL) $(EXE) $(RES_EXE) - -$(DLL): $(DLL_SRC) - $(CC) $(CFLAGS) -shared -o $@ $< - -$(EXE): $(EXE_SRC) - $(CC) $(CFLAGS) -o $@ $< - -$(RES_OBJ): $(RES_RC) - $(WINDRES) $< -O coff -o $@ - -$(RES_EXE): $(RES_EXE_SRC) $(RES_OBJ) - $(CC) $(CFLAGS) -o $@ $^ -lversion - -clean: - rm -f $(DLL) $(EXE) $(RES_EXE) $(RES_OBJ) - -.PHONY: all clean diff --git a/test/test_assert.h b/test/test_assert.h new file mode 100644 index 0000000..ea99683 --- /dev/null +++ b/test/test_assert.h @@ -0,0 +1,56 @@ +#ifndef WIBO_TEST_ASSERT_H +#define WIBO_TEST_ASSERT_H + +#include +#include +#include + +#define TEST_FAIL(fmt, ...) \ + do { \ + fprintf(stderr, "FAIL:%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ + exit(EXIT_FAILURE); \ + } while (0) + +#define TEST_CHECK(cond) \ + do { \ + if (!(cond)) { \ + TEST_FAIL("Assertion '%s' failed", #cond); \ + } \ + } while (0) + +#define TEST_CHECK_MSG(cond, fmt, ...) \ + do { \ + if (!(cond)) { \ + TEST_FAIL(fmt, ##__VA_ARGS__); \ + } \ + } while (0) + +#define TEST_CHECK_EQ(expected, actual) \ + do { \ + long long _expected_value = (long long)(expected); \ + long long _actual_value = (long long)(actual); \ + if (_expected_value != _actual_value) { \ + TEST_FAIL("Expected %s (%lld) == %s (%lld)", \ + #expected, _expected_value, #actual, _actual_value); \ + } \ + } while (0) + +#define TEST_CHECK_U64_EQ(expected, actual) \ + do { \ + unsigned long long _expected_value = (unsigned long long)(expected); \ + unsigned long long _actual_value = (unsigned long long)(actual); \ + if (_expected_value != _actual_value) { \ + TEST_FAIL("Expected %s (%llu) == %s (%llu)", \ + #expected, _expected_value, #actual, _actual_value); \ + } \ + } while (0) + +#define TEST_CHECK_STR_EQ(expected, actual) \ + do { \ + if (strcmp((expected), (actual)) != 0) { \ + TEST_FAIL("Expected %s (\"%s\") == %s (\"%s\")", \ + #expected, (expected), #actual, (actual)); \ + } \ + } while (0) + +#endif diff --git a/test/test_external_dll.c b/test/test_external_dll.c index 65d21cb..6c44755 100644 --- a/test/test_external_dll.c +++ b/test/test_external_dll.c @@ -1,32 +1,32 @@ #include +#include #include +#include "test_assert.h" + int main(void) { -typedef int (__stdcall *add_numbers_fn)(int, int); -typedef int (__stdcall *was_attached_fn)(void); + typedef int(__stdcall *add_numbers_fn)(int, int); + typedef int(__stdcall *was_attached_fn)(void); - HMODULE mod = LoadLibraryA("external_exports.dll"); - if (!mod) { - printf("LoadLibraryA failed: %lu\n", GetLastError()); - return 1; - } + HMODULE mod = LoadLibraryA("external_exports.dll"); + TEST_CHECK_MSG(mod != NULL, "LoadLibraryA failed: %lu", (unsigned long)GetLastError()); - add_numbers_fn add_numbers = (add_numbers_fn)GetProcAddress(mod, "add_numbers@8"); - was_attached_fn was_attached = (was_attached_fn)GetProcAddress(mod, "was_attached@0"); - if (!add_numbers || !was_attached) { - printf("GetProcAddress failed: %lu\n", GetLastError()); - return 1; - } + 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()); - int sum = add_numbers(2, 40); - int attached = was_attached(); + 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; - printf("sum=%d attached=%d\n", sum, attached); + int sum = add_numbers(2, 40); + int attached = was_attached(); - if (!FreeLibrary(mod)) { - printf("FreeLibrary failed: %lu\n", GetLastError()); - return 1; - } + TEST_CHECK_EQ(42, sum); + TEST_CHECK_EQ(1, attached); - return (sum == 42 && attached == 1) ? 0 : 2; + 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 index 9401ed3..546ddd4 100644 --- a/test/test_resources.c +++ b/test/test_resources.c @@ -2,86 +2,75 @@ #include #include +#include "test_assert.h" + int main(void) { char buffer[128]; int copied = LoadStringA(GetModuleHandleA(NULL), 100, buffer, sizeof(buffer)); - if (copied <= 0) { - printf("LoadString failed: %lu\n", GetLastError()); - return 1; - } - printf("STRING[100]=%s\n", buffer); + TEST_CHECK_MSG(copied > 0, "LoadStringA failed: %lu", (unsigned long)GetLastError()); + TEST_CHECK_EQ((int)strlen("Resource string 100"), copied); + TEST_CHECK_STR_EQ("Resource string 100", buffer); HRSRC versionInfo = FindResourceA(NULL, MAKEINTRESOURCEA(1), MAKEINTRESOURCEA(RT_VERSION)); - if (!versionInfo) { - printf("FindResource version failed: %lu\n", GetLastError()); - return 1; - } + TEST_CHECK_MSG(versionInfo != NULL, "FindResourceA version failed: %lu", (unsigned long)GetLastError()); + DWORD versionSize = SizeofResource(NULL, versionInfo); - if (!versionSize) { - printf("SizeofResource failed: %lu\n", GetLastError()); - return 1; - } - printf("VERSION size=%lu\n", (unsigned long)versionSize); + TEST_CHECK_MSG(versionSize != 0, "SizeofResource failed: %lu", (unsigned long)GetLastError()); + TEST_CHECK_EQ(364, (int)versionSize); char modulePath[MAX_PATH]; DWORD moduleLen = GetModuleFileNameA(NULL, modulePath, sizeof(modulePath)); - if (moduleLen == 0 || moduleLen >= sizeof(modulePath)) { - printf("GetModuleFileNameA failed: %lu\n", GetLastError()); - return 1; - } + TEST_CHECK_MSG(moduleLen > 0 && moduleLen < sizeof(modulePath), + "GetModuleFileNameA failed: %lu", (unsigned long)GetLastError()); DWORD handle = 0; DWORD infoSize = GetFileVersionInfoSizeA(modulePath, &handle); - if (!infoSize) { - printf("GetFileVersionInfoSizeA failed: %lu\n", GetLastError()); - return 1; - } + TEST_CHECK_MSG(infoSize != 0, "GetFileVersionInfoSizeA failed: %lu", (unsigned long)GetLastError()); char *infoBuffer = (char *)malloc(infoSize); - if (!infoBuffer) { - printf("malloc failed\n"); - return 1; - } + TEST_CHECK_MSG(infoBuffer != NULL, "malloc(%lu) failed", (unsigned long)infoSize); - if (!GetFileVersionInfoA(modulePath, 0, infoSize, infoBuffer)) { - printf("GetFileVersionInfoA failed: %lu\n", GetLastError()); - free(infoBuffer); - return 1; - } + TEST_CHECK_MSG(GetFileVersionInfoA(modulePath, 0, infoSize, infoBuffer) != 0, + "GetFileVersionInfoA failed: %lu", (unsigned long)GetLastError()); VS_FIXEDFILEINFO *fixedInfo = NULL; unsigned int fixedSize = 0; - if (!VerQueryValueA(infoBuffer, "\\", (void **)&fixedInfo, &fixedSize)) { - printf("VerQueryValueA root failed\n"); - free(infoBuffer); - return 1; - } - printf("FILEVERSION=%u.%u.%u.%u\n", - fixedInfo->dwFileVersionMS >> 16, - fixedInfo->dwFileVersionMS & 0xFFFF, - fixedInfo->dwFileVersionLS >> 16, - fixedInfo->dwFileVersionLS & 0xFFFF); + TEST_CHECK_MSG(VerQueryValueA(infoBuffer, "\\", (void **)&fixedInfo, &fixedSize) != 0 && + fixedInfo != NULL, + "VerQueryValueA root failed"); + TEST_CHECK_MSG(fixedSize >= sizeof(*fixedInfo), + "Unexpected VS_FIXEDFILEINFO size: %u", fixedSize); + TEST_CHECK_EQ(1, (int)(fixedInfo->dwFileVersionMS >> 16)); + TEST_CHECK_EQ(2, (int)(fixedInfo->dwFileVersionMS & 0xFFFF)); + TEST_CHECK_EQ(3, (int)(fixedInfo->dwFileVersionLS >> 16)); + TEST_CHECK_EQ(4, (int)(fixedInfo->dwFileVersionLS & 0xFFFF)); struct { WORD wLanguage; WORD wCodePage; } *translations = NULL; unsigned int transSize = 0; - if (VerQueryValueA(infoBuffer, "\\VarFileInfo\\Translation", (void **)&translations, &transSize) && - translations && transSize >= sizeof(*translations)) { - printf("Translation=%04X %04X\n", translations[0].wLanguage, translations[0].wCodePage); - char subBlock[64]; - snprintf(subBlock, sizeof(subBlock), "\\StringFileInfo\\%04X%04X\\ProductVersion", - translations[0].wLanguage, translations[0].wCodePage); - char *productVersion = NULL; - unsigned int pvSize = 0; - printf("Querying %s\n", subBlock); - if (VerQueryValueA(infoBuffer, subBlock, (void **)&productVersion, &pvSize) && productVersion) { - printf("PRODUCTVERSION=%s\n", productVersion); - } else { - printf("ProductVersion lookup failed\n"); - } - } else { - printf("ProductVersion lookup failed\n"); - } + TEST_CHECK_MSG(VerQueryValueA(infoBuffer, "\\VarFileInfo\\Translation", + (void **)&translations, &transSize) != 0 && + translations != NULL, + "Translation lookup failed"); + TEST_CHECK_MSG(transSize >= sizeof(*translations), + "Translation block too small: %u", transSize); + TEST_CHECK_EQ(0x0409, translations[0].wLanguage); + TEST_CHECK_EQ(0x04B0, translations[0].wCodePage); + + char subBlock[64]; + int subLen = snprintf(subBlock, sizeof(subBlock), + "\\StringFileInfo\\%04X%04X\\ProductVersion", + translations[0].wLanguage, translations[0].wCodePage); + TEST_CHECK_MSG(subLen > 0 && (size_t)subLen < sizeof(subBlock), + "Failed to build ProductVersion path"); + + char *productVersion = NULL; + unsigned int pvSize = 0; + TEST_CHECK_MSG(VerQueryValueA(infoBuffer, subBlock, (void **)&productVersion, &pvSize) != 0 && + productVersion != NULL, + "ProductVersion lookup failed"); + TEST_CHECK_STR_EQ("1.2.3-test", productVersion); free(infoBuffer); - return 0; + puts("resource metadata validated"); + return EXIT_SUCCESS; }