Implement version.dll properly

This commit is contained in:
Luke Street 2025-09-26 09:49:21 -06:00
parent 82e2809b33
commit c14ad86d72
6 changed files with 407 additions and 7 deletions

View File

@ -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<size_t>(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)

View File

@ -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;

View File

@ -1,18 +1,323 @@
#include "common.h"
#include "files.h"
#include "resources.h"
#include "strutil.h"
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <string>
#include <vector>
namespace {
constexpr uint32_t RT_VERSION = 16;
static uint16_t read_u16(const uint8_t *ptr) {
return static_cast<uint16_t>(ptr[0] | (ptr[1] << 8));
}
static size_t align4(size_t offset) {
return (offset + 3u) & ~static_cast<size_t>(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<char>(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<char16_t>(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<size_t>(cursor - block), static_cast<size_t>(end - block));
return false;
}
cursor = block + align4(static_cast<size_t>(cursor - block));
uint32_t valueBytes = 0;
if (valueLength) {
valueBytes = type == 1 ? static_cast<uint32_t>(valueLength) * sizeof(uint16_t)
: static_cast<uint32_t>(valueLength);
if (cursor + valueBytes > end) {
DEBUG_LOG("value beyond block: bytes=%u remaining=%zu\n", valueBytes, static_cast<size_t>(end - cursor));
return false;
}
}
const uint8_t *children = block + align4(static_cast<size_t>((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<uint32_t>(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<char>(std::tolower(c)); });
return str;
}
static bool queryVersionBlock(const uint8_t *block, size_t available,
const std::vector<std::string> &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<size_t>(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<size_t>(child.totalLength);
cursor = childStart + align4(offset);
if (cursor <= childStart || cursor > end)
break;
}
return false;
}
static bool splitSubBlock(const std::string &subBlock, std::vector<std::string> &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<size_t>(next - cursor));
cursor = *next ? next + 1 : next;
}
return true;
}
static bool loadVersionResource(const char *fileName, std::vector<uint8_t> &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<const uint8_t *>(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<uint8_t> buffer;
if (!loadVersionResource(lptstrFilename, buffer))
return 0;
return static_cast<unsigned int>(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<uint8_t> 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<uint8_t *>(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<const uint8_t *>(pBlock);
uint16_t totalLength = read_u16(base);
if (totalLength < 6)
return 0;
std::vector<std::string> 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<char *>(const_cast<uint8_t *>(outPtr));
std::string narrow = wideStringToString(reinterpret_cast<const uint16_t *>(outPtr), static_cast<int>(outLen));
std::memcpy(dest, narrow.c_str(), narrow.size() + 1);
if (lplpBuffer)
*lplpBuffer = dest;
if (puLen)
*puLen = static_cast<unsigned int>(narrow.size());
return 1;
}
if (lplpBuffer)
*lplpBuffer = const_cast<uint8_t *>(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;
}

View File

@ -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);

View File

@ -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)

View File

@ -1,5 +1,6 @@
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
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;
}