From c14ad86d72bc5631bb1765df0a1e7ff33c88120a Mon Sep 17 00:00:00 2001 From: Luke Street Date: Fri, 26 Sep 2025 09:49:21 -0600 Subject: [PATCH] Implement version.dll properly --- dll/crt.cpp | 19 +++ dll/msvcrt.cpp | 13 ++ dll/version.cpp | 317 +++++++++++++++++++++++++++++++++++++++++- resources.h | 1 + test/Makefile | 2 +- test/test_resources.c | 62 +++++++++ 6 files changed, 407 insertions(+), 7 deletions(-) diff --git a/dll/crt.cpp b/dll/crt.cpp index 99d6259..93d1e0f 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -14,6 +14,10 @@ typedef void (*_invalid_parameter_handler)(const wchar_t *, const wchar_t *, con extern char **environ; +namespace msvcrt { + int WIN_ENTRY puts(const char *str); +} + typedef enum _crt_app_type { _crt_unknown_app, _crt_console_app, @@ -193,6 +197,17 @@ int WIN_ENTRY __stdio_common_vfprintf(unsigned long long /*options*/, FILE *stre return vfprintf(stream, format, args); } +int WIN_ENTRY __stdio_common_vsprintf(unsigned long long /*options*/, char *buffer, size_t len, const char *format, void * /*locale*/, va_list args) { + if (!buffer || !format) + return -1; + int result = vsnprintf(buffer, len, format, args); + if (result < 0) + return -1; + if (len > 0 && static_cast(result) >= len) + return -1; + return result; +} + } // namespace crt static void *resolveByName(const char *name) { @@ -258,6 +273,10 @@ static void *resolveByName(const char *name) { return (void *)crt::__acrt_iob_func; if (strcmp(name, "__stdio_common_vfprintf") == 0) return (void *)crt::__stdio_common_vfprintf; + if (strcmp(name, "__stdio_common_vsprintf") == 0) + return (void *)crt::__stdio_common_vsprintf; + if (strcmp(name, "puts") == 0) + return (void *)msvcrt::puts; if (strcmp(name, "__setusermatherr") == 0) return (void *)crt::__setusermatherr; if (strcmp(name, "_initialize_onexit_table") == 0) diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 9b0cede..e83f80c 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -522,6 +522,18 @@ namespace msvcrt { else return 0; } + int WIN_ENTRY puts(const char *str) { + if (!str) { + str = "(null)"; + } + DEBUG_LOG("puts %s\n", str); + if (std::fputs(str, stdout) < 0) + return EOF; + if (std::fputc('\n', stdout) == EOF) + return EOF; + return 0; + } + int WIN_ENTRY fclose(FILE* stream){ return ::fclose(stream); } @@ -649,6 +661,7 @@ static void *resolveByName(const char *name) { if (strcmp(name, "_dup2") == 0) return (void*)msvcrt::_dup2; if (strcmp(name, "_wfsopen") == 0) return (void*)msvcrt::_wfsopen; if (strcmp(name, "fputws") == 0) return (void*)msvcrt::fputws; + if (strcmp(name, "puts") == 0) return (void*)msvcrt::puts; if (strcmp(name, "fclose") == 0) return (void*)msvcrt::fclose; if (strcmp(name, "_flushall") == 0) return (void*)msvcrt::_flushall; if (strcmp(name, "_errno") == 0) return (void*)msvcrt::_errno; diff --git a/dll/version.cpp b/dll/version.cpp index e558b2f..06521c7 100644 --- a/dll/version.cpp +++ b/dll/version.cpp @@ -1,18 +1,323 @@ #include "common.h" +#include "files.h" +#include "resources.h" +#include "strutil.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr uint32_t RT_VERSION = 16; + +static uint16_t read_u16(const uint8_t *ptr) { + return static_cast(ptr[0] | (ptr[1] << 8)); +} + +static size_t align4(size_t offset) { + return (offset + 3u) & ~static_cast(3u); +} + +static std::string narrowKey(const std::u16string &key) { + std::string result; + result.reserve(key.size()); + for (char16_t ch : key) { + result.push_back(static_cast(ch & 0xFF)); + } + return result; +} + +struct VersionBlockView { + uint16_t totalLength = 0; + uint16_t valueLength = 0; + uint16_t type = 0; + std::u16string key; + const uint8_t *valuePtr = nullptr; + uint32_t valueBytes = 0; + const uint8_t *childrenPtr = nullptr; + uint32_t childrenBytes = 0; +}; + +static bool parseVersionBlock(const uint8_t *block, size_t available, VersionBlockView &out) { + if (available < sizeof(uint16_t) * 3) { + DEBUG_LOG("header too small: available=%zu\n", available); + return false; + } + + uint16_t totalLength = read_u16(block); + uint16_t valueLength = read_u16(block + sizeof(uint16_t)); + uint16_t type = read_u16(block + sizeof(uint16_t) * 2); + if (totalLength == 0 || totalLength > available) { + DEBUG_LOG("invalid totalLength=%u available=%zu\n", totalLength, available); + return false; + } + + const uint8_t *end = block + totalLength; + const uint8_t *cursor = block + sizeof(uint16_t) * 3; + out.key.clear(); + while (cursor + sizeof(uint16_t) <= end) { + uint16_t ch = read_u16(cursor); + cursor += sizeof(uint16_t); + if (!ch) + break; + out.key.push_back(static_cast(ch)); + } + DEBUG_LOG("parsed key fragment=%s\n", narrowKey(out.key).c_str()); + + cursor = block + sizeof(uint16_t) * 3 + (out.key.size() + 1) * sizeof(uint16_t); + if (cursor > end) { + DEBUG_LOG("key cursor beyond block: cursor=%zu end=%zu\n", static_cast(cursor - block), static_cast(end - block)); + return false; + } + + cursor = block + align4(static_cast(cursor - block)); + + uint32_t valueBytes = 0; + if (valueLength) { + valueBytes = type == 1 ? static_cast(valueLength) * sizeof(uint16_t) + : static_cast(valueLength); + if (cursor + valueBytes > end) { + DEBUG_LOG("value beyond block: bytes=%u remaining=%zu\n", valueBytes, static_cast(end - cursor)); + return false; + } + } + + const uint8_t *children = block + align4(static_cast((cursor + valueBytes) - block)); + if (children > end) + children = end; + + out.totalLength = totalLength; + out.valueLength = valueLength; + out.type = type; + out.valuePtr = valueLength ? cursor : nullptr; + out.valueBytes = valueBytes; + out.childrenPtr = children; + out.childrenBytes = static_cast(end - children); + return true; +} + +static std::string toLowerCopy(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); + return str; +} + +static bool queryVersionBlock(const uint8_t *block, size_t available, + const std::vector &segments, + size_t depth, + const uint8_t **outPtr, + uint32_t *outLen, + uint16_t *outType) { + VersionBlockView view; + if (!parseVersionBlock(block, available, view)) + return false; + + if (depth == segments.size()) { + if (outPtr) + *outPtr = view.valueBytes ? view.valuePtr : nullptr; + if (outLen) + *outLen = view.type == 1 ? view.valueLength : view.valueBytes; + if (outType) + *outType = view.type; + return true; + } + + const std::string targetLower = toLowerCopy(segments[depth]); + const uint8_t *cursor = view.childrenPtr; + const uint8_t *end = view.childrenPtr + view.childrenBytes; + + while (cursor + 6 <= end) { + const uint8_t *childStart = cursor; + VersionBlockView child; + if (!parseVersionBlock(cursor, static_cast(end - cursor), child)) + break; + if (child.totalLength == 0) + break; + std::string childKeyLower = toLowerCopy(narrowKey(child.key)); + if (childKeyLower == targetLower) { + if (queryVersionBlock(childStart, child.totalLength, segments, depth + 1, outPtr, outLen, outType)) + return true; + } + size_t offset = static_cast(child.totalLength); + cursor = childStart + align4(offset); + if (cursor <= childStart || cursor > end) + break; + } + return false; +} + +static bool splitSubBlock(const std::string &subBlock, std::vector &segments) { + segments.clear(); + if (subBlock.empty() || subBlock == "\\") + return true; + + const char *cursor = subBlock.c_str(); + if (*cursor == '\\') + ++cursor; + + while (*cursor) { + const char *next = std::strchr(cursor, '\\'); + if (!next) + next = cursor + std::strlen(cursor); + segments.emplace_back(cursor, static_cast(next - cursor)); + cursor = *next ? next + 1 : next; + } + return true; +} + +static bool loadVersionResource(const char *fileName, std::vector &buffer) { + if (!fileName) { + wibo::lastError = ERROR_INVALID_PARAMETER; + return false; + } + + auto hostPath = files::pathFromWindows(fileName); + std::string hostPathStr = hostPath.string(); + FILE *fp = std::fopen(hostPathStr.c_str(), "rb"); + if (!fp) { + wibo::lastError = ERROR_FILE_NOT_FOUND; + return false; + } + + wibo::Executable executable; + if (!executable.loadPE(fp, false)) { + std::fclose(fp); + wibo::lastError = ERROR_BAD_EXE_FORMAT; + return false; + } + + std::fclose(fp); + + wibo::ResourceIdentifier type = wibo::ResourceIdentifier::fromID(RT_VERSION); + wibo::ResourceIdentifier name = wibo::ResourceIdentifier::fromID(1); + wibo::ResourceLocation loc; + if (!executable.findResource(type, name, std::nullopt, loc)) { + auto nameString = wibo::ResourceIdentifier::fromString(u"VS_VERSION_INFO"); + if (!executable.findResource(type, nameString, std::nullopt, loc)) + return false; + } + + const uint8_t *start = static_cast(loc.data); + buffer.assign(start, start + loc.size); + wibo::lastError = ERROR_SUCCESS; + return true; +} + +} // namespace namespace version { - unsigned int WIN_FUNC GetFileVersionInfoSizeA(const char* lptstrFilename, unsigned int* outZero) { - DEBUG_LOG("GetFileVersionInfoSizeA %s\n", lptstrFilename); - if (outZero != NULL) { - *outZero = 0; - } - wibo::lastError = 0; + +unsigned int WIN_FUNC GetFileVersionInfoSizeA(const char *lptstrFilename, unsigned int *lpdwHandle) { + DEBUG_LOG("GetFileVersionInfoSizeA %s\n", lptstrFilename); + if (lpdwHandle) + *lpdwHandle = 0; + + std::vector buffer; + if (!loadVersionResource(lptstrFilename, buffer)) + return 0; + return static_cast(buffer.size()); +} + +unsigned int WIN_FUNC GetFileVersionInfoA(const char *lptstrFilename, unsigned int dwHandle, unsigned int dwLen, void *lpData) { + (void) dwHandle; + DEBUG_LOG("GetFileVersionInfoA %s len=%u\n", lptstrFilename, dwLen); + if (!lpData || dwLen == 0) { + wibo::lastError = ERROR_INVALID_PARAMETER; return 0; } + + std::vector buffer; + if (!loadVersionResource(lptstrFilename, buffer)) + return 0; + + if (buffer.size() > dwLen) { + wibo::lastError = ERROR_INSUFFICIENT_BUFFER; + return 0; + } + + std::memcpy(lpData, buffer.data(), buffer.size()); + if (buffer.size() < dwLen) { + std::memset(static_cast(lpData) + buffer.size(), 0, dwLen - buffer.size()); + } + wibo::lastError = ERROR_SUCCESS; + return 1; } +static unsigned int VerQueryValueImpl(const void *pBlock, const std::string &subBlock, void **lplpBuffer, unsigned int *puLen) { + if (!pBlock) + return 0; + + const uint8_t *base = static_cast(pBlock); + uint16_t totalLength = read_u16(base); + if (totalLength < 6) + return 0; + + std::vector segments; + if (!splitSubBlock(subBlock, segments)) + return 0; + + const uint8_t *outPtr = nullptr; + uint32_t outLen = 0; + uint16_t outType = 0; + if (!queryVersionBlock(base, totalLength, segments, 0, &outPtr, &outLen, &outType)) + return 0; + + if (outType == 1 && outPtr) { + char *dest = reinterpret_cast(const_cast(outPtr)); + std::string narrow = wideStringToString(reinterpret_cast(outPtr), static_cast(outLen)); + std::memcpy(dest, narrow.c_str(), narrow.size() + 1); + if (lplpBuffer) + *lplpBuffer = dest; + if (puLen) + *puLen = static_cast(narrow.size()); + return 1; + } + + if (lplpBuffer) + *lplpBuffer = const_cast(outPtr); + if (puLen) + *puLen = outLen; + return 1; +} + +unsigned int WIN_FUNC VerQueryValueA(const void *pBlock, const char *lpSubBlock, void **lplpBuffer, unsigned int *puLen) { + DEBUG_LOG("VerQueryValueA %p %s\n", pBlock, lpSubBlock ? lpSubBlock : "(null)"); + if (!lpSubBlock) + return 0; + return VerQueryValueImpl(pBlock, lpSubBlock, lplpBuffer, puLen); +} + +unsigned int WIN_FUNC GetFileVersionInfoSizeW(const uint16_t *lptstrFilename, unsigned int *lpdwHandle) { + auto narrow = wideStringToString(lptstrFilename); + return GetFileVersionInfoSizeA(narrow.c_str(), lpdwHandle); +} + +unsigned int WIN_FUNC GetFileVersionInfoW(const uint16_t *lptstrFilename, unsigned int dwHandle, unsigned int dwLen, void *lpData) { + auto narrow = wideStringToString(lptstrFilename); + return GetFileVersionInfoA(narrow.c_str(), dwHandle, dwLen, lpData); +} + +unsigned int WIN_FUNC VerQueryValueW(const void *pBlock, const uint16_t *lpSubBlock, void **lplpBuffer, unsigned int *puLen) { + if (!lpSubBlock) + return 0; + auto narrow = wideStringToString(lpSubBlock); + DEBUG_LOG("VerQueryValueW %p %s\n", pBlock, narrow.c_str()); + return VerQueryValueImpl(pBlock, narrow, lplpBuffer, puLen); +} + +} // namespace version + static void *resolveByName(const char *name) { if (strcmp(name, "GetFileVersionInfoSizeA") == 0) return (void *) version::GetFileVersionInfoSizeA; + if (strcmp(name, "GetFileVersionInfoA") == 0) return (void *) version::GetFileVersionInfoA; + if (strcmp(name, "VerQueryValueA") == 0) return (void *) version::VerQueryValueA; + if (strcmp(name, "GetFileVersionInfoSizeW") == 0) return (void *) version::GetFileVersionInfoSizeW; + if (strcmp(name, "GetFileVersionInfoW") == 0) return (void *) version::GetFileVersionInfoW; + if (strcmp(name, "VerQueryValueW") == 0) return (void *) version::VerQueryValueW; return nullptr; } diff --git a/resources.h b/resources.h index 28e80d6..5612743 100644 --- a/resources.h +++ b/resources.h @@ -5,6 +5,7 @@ namespace wibo { struct Executable; struct ImageResourceDataEntry; +struct ResourceIdentifier; bool resourceEntryBelongsToExecutable(const Executable &exe, const ImageResourceDataEntry *entry); ResourceIdentifier resourceIdentifierFromAnsi(const char *id); diff --git a/test/Makefile b/test/Makefile index 4b4eebb..1fad9e8 100644 --- a/test/Makefile +++ b/test/Makefile @@ -24,7 +24,7 @@ $(RES_OBJ): $(RES_RC) $(WINDRES) $< -O coff -o $@ $(RES_EXE): $(RES_EXE_SRC) $(RES_OBJ) - $(CC) $(CFLAGS) -o $@ $^ + $(CC) $(CFLAGS) -o $@ $^ -lversion clean: rm -f $(DLL) $(EXE) $(RES_EXE) $(RES_OBJ) diff --git a/test/test_resources.c b/test/test_resources.c index 04c40a1..9401ed3 100644 --- a/test/test_resources.c +++ b/test/test_resources.c @@ -1,5 +1,6 @@ #include #include +#include int main(void) { char buffer[128]; @@ -21,5 +22,66 @@ int main(void) { return 1; } printf("VERSION size=%lu\n", (unsigned long)versionSize); + + char modulePath[MAX_PATH]; + DWORD moduleLen = GetModuleFileNameA(NULL, modulePath, sizeof(modulePath)); + if (moduleLen == 0 || moduleLen >= sizeof(modulePath)) { + printf("GetModuleFileNameA failed: %lu\n", GetLastError()); + return 1; + } + + DWORD handle = 0; + DWORD infoSize = GetFileVersionInfoSizeA(modulePath, &handle); + if (!infoSize) { + printf("GetFileVersionInfoSizeA failed: %lu\n", GetLastError()); + return 1; + } + + char *infoBuffer = (char *)malloc(infoSize); + if (!infoBuffer) { + printf("malloc failed\n"); + return 1; + } + + if (!GetFileVersionInfoA(modulePath, 0, infoSize, infoBuffer)) { + printf("GetFileVersionInfoA failed: %lu\n", GetLastError()); + free(infoBuffer); + return 1; + } + + VS_FIXEDFILEINFO *fixedInfo = NULL; + unsigned int fixedSize = 0; + if (!VerQueryValueA(infoBuffer, "\\", (void **)&fixedInfo, &fixedSize)) { + printf("VerQueryValueA root failed\n"); + free(infoBuffer); + return 1; + } + printf("FILEVERSION=%u.%u.%u.%u\n", + fixedInfo->dwFileVersionMS >> 16, + fixedInfo->dwFileVersionMS & 0xFFFF, + fixedInfo->dwFileVersionLS >> 16, + fixedInfo->dwFileVersionLS & 0xFFFF); + + struct { WORD wLanguage; WORD wCodePage; } *translations = NULL; + unsigned int transSize = 0; + if (VerQueryValueA(infoBuffer, "\\VarFileInfo\\Translation", (void **)&translations, &transSize) && + translations && transSize >= sizeof(*translations)) { + printf("Translation=%04X %04X\n", translations[0].wLanguage, translations[0].wCodePage); + char subBlock[64]; + snprintf(subBlock, sizeof(subBlock), "\\StringFileInfo\\%04X%04X\\ProductVersion", + translations[0].wLanguage, translations[0].wCodePage); + char *productVersion = NULL; + unsigned int pvSize = 0; + printf("Querying %s\n", subBlock); + if (VerQueryValueA(infoBuffer, subBlock, (void **)&productVersion, &pvSize) && productVersion) { + printf("PRODUCTVERSION=%s\n", productVersion); + } else { + printf("ProductVersion lookup failed\n"); + } + } else { + printf("ProductVersion lookup failed\n"); + } + + free(infoBuffer); return 0; }