mirror of
https://github.com/decompals/wibo.git
synced 2025-10-16 15:15:10 +00:00
Add proper testing framework & integrate with CI
This commit is contained in:
parent
c14ad86d72
commit
104e9e869d
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@ -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:
|
||||
|
32
AGENTS.md
Normal file
32
AGENTS.md
Normal file
@ -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_<feature>.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 <log>`; 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.
|
@ -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 $<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 $<TARGET_FILE:wibo> ${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 $<TARGET_FILE:wibo> ${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()
|
||||
|
33
README.md
33
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.
|
||||
|
||||
---
|
||||
|
||||
|
@ -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)
|
||||
|
3
test/.gitignore
vendored
Normal file
3
test/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.o
|
||||
*.dll
|
||||
*.exe
|
@ -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
|
56
test/test_assert.h
Normal file
56
test/test_assert.h
Normal file
@ -0,0 +1,56 @@
|
||||
#ifndef WIBO_TEST_ASSERT_H
|
||||
#define WIBO_TEST_ASSERT_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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
|
@ -1,32 +1,32 @@
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
@ -2,86 +2,75 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user