mirror of
https://github.com/decompals/wibo.git
synced 2025-10-15 14:45:12 +00:00
Implement version.dll properly
This commit is contained in:
parent
82e2809b33
commit
c14ad86d72
19
dll/crt.cpp
19
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<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)
|
||||
|
@ -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;
|
||||
|
317
dll/version.cpp
317
dll/version.cpp
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user