mirror of https://github.com/decompals/wibo.git
Fix `TlsGetValue` & more (#48)
`TlsGetValue` disambiguates 0 and an error by relying on `GetLastError`. Depending on the program state, `GetLastError` could be non-0, even though `TlsGetValue` succeeded. Resolve this by always setting `wibo::lastError`. This matches the behavior described by the documentation. Additionally, when reading resources, later versions of mwcc and mwld call `GetModuleHandleA` with the program path, and then call `LoadStringA` on that handle. Support this behavior by _actually_ loading the PE at the path passed in to `GetModuleHandleA`, instead of assuming it's the current program. (This is especially useful because sjiswrap relies on overriding `GetModuleFileNameA`, so the wrapped program reads its own resources, rather than sjiswrap's.) Other small changes: - Add ms-win-crt `exit` & run atexit funcs - Implements vcruntime `memmove` - Implements kernel32 `GetModuleFileNameA`
This commit is contained in:
parent
8a6aacb82d
commit
c4de05946d
18
common.h
18
common.h
|
@ -7,6 +7,7 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
// On Windows, the incoming stack is aligned to a 4 byte boundary.
|
// On Windows, the incoming stack is aligned to a 4 byte boundary.
|
||||||
// force_align_arg_pointer will realign the stack to match GCC's 16 byte alignment.
|
// force_align_arg_pointer will realign the stack to match GCC's 16 byte alignment.
|
||||||
|
@ -53,6 +54,7 @@ typedef unsigned char BYTE;
|
||||||
#define ERROR_HANDLE_EOF 38
|
#define ERROR_HANDLE_EOF 38
|
||||||
#define ERROR_NOT_SUPPORTED 50
|
#define ERROR_NOT_SUPPORTED 50
|
||||||
#define ERROR_INVALID_PARAMETER 87
|
#define ERROR_INVALID_PARAMETER 87
|
||||||
|
#define ERROR_INSUFFICIENT_BUFFER 122
|
||||||
#define ERROR_NEGATIVE_SEEK 131
|
#define ERROR_NEGATIVE_SEEK 131
|
||||||
#define ERROR_ALREADY_EXISTS 183
|
#define ERROR_ALREADY_EXISTS 183
|
||||||
|
|
||||||
|
@ -98,7 +100,7 @@ namespace wibo {
|
||||||
struct Executable {
|
struct Executable {
|
||||||
Executable();
|
Executable();
|
||||||
~Executable();
|
~Executable();
|
||||||
bool loadPE(FILE *file);
|
bool loadPE(FILE *file, bool exec);
|
||||||
|
|
||||||
void *imageBuffer;
|
void *imageBuffer;
|
||||||
size_t imageSize;
|
size_t imageSize;
|
||||||
|
@ -115,6 +117,20 @@ namespace wibo {
|
||||||
return fromRVA<T>((uint32_t) rva);
|
return fromRVA<T>((uint32_t) rva);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
struct ModuleInfo {
|
||||||
|
std::string name;
|
||||||
|
const wibo::Module* module = nullptr;
|
||||||
|
std::unique_ptr<wibo::Executable> executable;
|
||||||
|
};
|
||||||
|
|
||||||
extern Executable *mainModule;
|
extern Executable *mainModule;
|
||||||
|
Executable *executableFromModule(HMODULE module);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HMODULE will be `nullptr` or `mainModule->imageBuffer` if it's the main module,
|
||||||
|
* otherwise it will be a pointer to a `wibo::ModuleInfo`.
|
||||||
|
*/
|
||||||
|
inline bool isMainModule(HMODULE hModule) {
|
||||||
|
return hModule == nullptr || hModule == mainModule->imageBuffer;
|
||||||
}
|
}
|
||||||
|
} // namespace wibo
|
||||||
|
|
18
dll/crt.cpp
18
dll/crt.cpp
|
@ -1,5 +1,7 @@
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
typedef void (*_PVFV)();
|
typedef void (*_PVFV)();
|
||||||
typedef int (*_PIFV)();
|
typedef int (*_PIFV)();
|
||||||
|
|
||||||
|
@ -19,6 +21,8 @@ namespace crt {
|
||||||
|
|
||||||
int _commode = 0;
|
int _commode = 0;
|
||||||
|
|
||||||
|
std::vector<_PVFV> atexitFuncs;
|
||||||
|
|
||||||
void WIN_ENTRY _initterm(const _PVFV *ppfn, const _PVFV *end) {
|
void WIN_ENTRY _initterm(const _PVFV *ppfn, const _PVFV *end) {
|
||||||
do {
|
do {
|
||||||
if (_PVFV pfn = *++ppfn) {
|
if (_PVFV pfn = *++ppfn) {
|
||||||
|
@ -48,7 +52,8 @@ int WIN_ENTRY _set_fmode(int mode) {
|
||||||
int *WIN_ENTRY __p__commode() { return &_commode; }
|
int *WIN_ENTRY __p__commode() { return &_commode; }
|
||||||
|
|
||||||
int WIN_ENTRY _crt_atexit(void (*func)()) {
|
int WIN_ENTRY _crt_atexit(void (*func)()) {
|
||||||
DEBUG_LOG("STUB: _crt_atexit(%p)\n", func);
|
DEBUG_LOG("_crt_atexit(%p)\n", func);
|
||||||
|
atexitFuncs.push_back(func);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +90,15 @@ int *WIN_ENTRY __p___argc() { return &wibo::argc; }
|
||||||
|
|
||||||
size_t WIN_ENTRY strlen(const char *str) { return ::strlen(str); }
|
size_t WIN_ENTRY strlen(const char *str) { return ::strlen(str); }
|
||||||
|
|
||||||
|
void WIN_ENTRY exit(int status) {
|
||||||
|
DEBUG_LOG("exit(%i)\n", status);
|
||||||
|
for (auto it = atexitFuncs.rbegin(); it != atexitFuncs.rend(); ++it) {
|
||||||
|
DEBUG_LOG("Calling atexit function %p\n", *it);
|
||||||
|
(*it)();
|
||||||
|
}
|
||||||
|
::exit(status);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace crt
|
} // namespace crt
|
||||||
|
|
||||||
static void *resolveByName(const char *name) {
|
static void *resolveByName(const char *name) {
|
||||||
|
@ -118,6 +132,8 @@ static void *resolveByName(const char *name) {
|
||||||
return (void *)crt::__p___argc;
|
return (void *)crt::__p___argc;
|
||||||
if (strcmp(name, "strlen") == 0)
|
if (strcmp(name, "strlen") == 0)
|
||||||
return (void *)crt::strlen;
|
return (void *)crt::strlen;
|
||||||
|
if (strcmp(name, "exit") == 0)
|
||||||
|
return (void *)crt::exit;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -366,30 +366,42 @@ namespace kernel32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DEBUG_LOG("...returning nothing\n");
|
DEBUG_LOG("...returning nothing\n");
|
||||||
return 0xFFFFFFFF;
|
wibo::lastError = 1;
|
||||||
|
return 0xFFFFFFFF; // TLS_OUT_OF_INDEXES
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int WIN_FUNC TlsFree(unsigned int dwTlsIndex) {
|
unsigned int WIN_FUNC TlsFree(unsigned int dwTlsIndex) {
|
||||||
DEBUG_LOG("TlsFree(%u)\n", dwTlsIndex);
|
DEBUG_LOG("TlsFree(%u)\n", dwTlsIndex);
|
||||||
if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) {
|
if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) {
|
||||||
tlsValuesUsed[dwTlsIndex] = false;
|
tlsValuesUsed[dwTlsIndex] = false;
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
|
wibo::lastError = 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void *WIN_FUNC TlsGetValue(unsigned int dwTlsIndex) {
|
void *WIN_FUNC TlsGetValue(unsigned int dwTlsIndex) {
|
||||||
// DEBUG_LOG("TlsGetValue(%u)\n", dwTlsIndex);
|
// DEBUG_LOG("TlsGetValue(%u)", dwTlsIndex);
|
||||||
if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex])
|
void *result = nullptr;
|
||||||
return tlsValues[dwTlsIndex];
|
if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) {
|
||||||
else
|
result = tlsValues[dwTlsIndex];
|
||||||
return 0;
|
// See https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-TlsGetValue#return-value
|
||||||
|
wibo::lastError = ERROR_SUCCESS;
|
||||||
|
} else {
|
||||||
|
wibo::lastError = 1;
|
||||||
}
|
}
|
||||||
|
// DEBUG_LOG(" -> %p\n", result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int WIN_FUNC TlsSetValue(unsigned int dwTlsIndex, void *lpTlsValue) {
|
unsigned int WIN_FUNC TlsSetValue(unsigned int dwTlsIndex, void *lpTlsValue) {
|
||||||
// DEBUG_LOG("TlsSetValue(%u, %p)\n", dwTlsIndex, lpTlsValue);
|
// DEBUG_LOG("TlsSetValue(%u, %p)\n", dwTlsIndex, lpTlsValue);
|
||||||
if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) {
|
if (dwTlsIndex >= 0 && dwTlsIndex < MAX_TLS_VALUES && tlsValuesUsed[dwTlsIndex]) {
|
||||||
tlsValues[dwTlsIndex] = lpTlsValue;
|
tlsValues[dwTlsIndex] = lpTlsValue;
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
|
wibo::lastError = 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1303,16 +1315,42 @@ namespace kernel32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int WIN_FUNC GetModuleFileNameA(void* hModule, char* lpFilename, unsigned int nSize) {
|
DWORD WIN_FUNC GetModuleFileNameA(HMODULE hModule, LPSTR lpFilename, DWORD nSize) {
|
||||||
DEBUG_LOG("GetModuleFileNameA (hModule=%p, nSize=%i)\n", hModule, nSize);
|
DEBUG_LOG("GetModuleFileNameA (hModule=%p, nSize=%i)\n", hModule, nSize);
|
||||||
|
if (lpFilename == nullptr) {
|
||||||
*lpFilename = 0; // just NUL terminate
|
wibo::lastError = ERROR_INVALID_PARAMETER;
|
||||||
|
|
||||||
wibo::lastError = 0;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int WIN_FUNC GetModuleFileNameW(void* hModule, uint16_t* lpFilename, unsigned int nSize) {
|
std::string path;
|
||||||
|
if (wibo::isMainModule(hModule)) {
|
||||||
|
const auto exePath = files::pathFromWindows(wibo::argv[0]);
|
||||||
|
const auto absPath = std::filesystem::absolute(exePath);
|
||||||
|
path = files::pathToWindows(absPath);
|
||||||
|
} else {
|
||||||
|
path = static_cast<wibo::ModuleInfo *>(hModule)->name;
|
||||||
|
}
|
||||||
|
const size_t len = path.size();
|
||||||
|
if (nSize == 0) {
|
||||||
|
wibo::lastError = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t copyLen = std::min(len, nSize - 1);
|
||||||
|
memcpy(lpFilename, path.c_str(), copyLen);
|
||||||
|
if (copyLen < nSize) {
|
||||||
|
lpFilename[copyLen] = 0;
|
||||||
|
}
|
||||||
|
if (copyLen < len) {
|
||||||
|
wibo::lastError = ERROR_INSUFFICIENT_BUFFER;
|
||||||
|
return nSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
wibo::lastError = ERROR_SUCCESS;
|
||||||
|
return copyLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WIN_FUNC GetModuleFileNameW(HMODULE hModule, LPWSTR lpFilename, DWORD nSize) {
|
||||||
DEBUG_LOG("GetModuleFileNameW (hModule=%p, nSize=%i)\n", hModule, nSize);
|
DEBUG_LOG("GetModuleFileNameW (hModule=%p, nSize=%i)\n", hModule, nSize);
|
||||||
|
|
||||||
*lpFilename = 0; // just NUL terminate
|
*lpFilename = 0; // just NUL terminate
|
||||||
|
|
|
@ -57,8 +57,7 @@ namespace user32 {
|
||||||
return (unsigned int*)(rsrcBase + langEntry);
|
return (unsigned int*)(rsrcBase + langEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *getStringFromTable(unsigned int uID) {
|
static const char *getStringFromTable(wibo::Executable *mod, unsigned int uID) {
|
||||||
wibo::Executable *mod = wibo::mainModule;
|
|
||||||
unsigned int tableID = (uID >> 4) + 1;
|
unsigned int tableID = (uID >> 4) + 1;
|
||||||
unsigned int entryID = uID & 15;
|
unsigned int entryID = uID & 15;
|
||||||
unsigned int* stringTable = getResourceByID(mod, 6, tableID, 1033);
|
unsigned int* stringTable = getResourceByID(mod, 6, tableID, 1033);
|
||||||
|
@ -82,7 +81,11 @@ namespace user32 {
|
||||||
|
|
||||||
int WIN_FUNC LoadStringA(void* hInstance, unsigned int uID, char* lpBuffer, int cchBufferMax) {
|
int WIN_FUNC LoadStringA(void* hInstance, unsigned int uID, char* lpBuffer, int cchBufferMax) {
|
||||||
DEBUG_LOG("LoadStringA %p %d %d\n", hInstance, uID, cchBufferMax);
|
DEBUG_LOG("LoadStringA %p %d %d\n", hInstance, uID, cchBufferMax);
|
||||||
const char* s = getStringFromTable(uID);
|
wibo::Executable *mod = wibo::executableFromModule(hInstance);
|
||||||
|
if (!mod) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const char* s = getStringFromTable(mod, uID);
|
||||||
if (!s) {
|
if (!s) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ void *WIN_ENTRY memset(void *dest, int ch, size_t count) { return ::memset(dest,
|
||||||
|
|
||||||
int WIN_ENTRY memcmp(const void *buf1, const void *buf2, size_t count) { return ::memcmp(buf1, buf2, count); }
|
int WIN_ENTRY memcmp(const void *buf1, const void *buf2, size_t count) { return ::memcmp(buf1, buf2, count); }
|
||||||
|
|
||||||
|
void *WIN_ENTRY memmove(void *dest, const void *src, size_t count) { return ::memmove(dest, src, count); }
|
||||||
|
|
||||||
} // namespace vcruntime
|
} // namespace vcruntime
|
||||||
|
|
||||||
static void *resolveByName(const char *name) {
|
static void *resolveByName(const char *name) {
|
||||||
|
@ -17,6 +19,8 @@ static void *resolveByName(const char *name) {
|
||||||
return (void *)vcruntime::memset;
|
return (void *)vcruntime::memset;
|
||||||
if (strcmp(name, "memcmp") == 0)
|
if (strcmp(name, "memcmp") == 0)
|
||||||
return (void *)vcruntime::memcmp;
|
return (void *)vcruntime::memcmp;
|
||||||
|
if (strcmp(name, "memmove") == 0)
|
||||||
|
return (void *)vcruntime::memmove;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
22
loader.cpp
22
loader.cpp
|
@ -116,7 +116,13 @@ wibo::Executable::~Executable() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wibo::Executable::loadPE(FILE *file) {
|
/**
|
||||||
|
* Load a PE file into memory.
|
||||||
|
*
|
||||||
|
* @param file The file to load.
|
||||||
|
* @param exec Whether to make the loaded image executable.
|
||||||
|
*/
|
||||||
|
bool wibo::Executable::loadPE(FILE *file, bool exec) {
|
||||||
// Skip to PE header
|
// Skip to PE header
|
||||||
fseek(file, 0x3C, SEEK_SET);
|
fseek(file, 0x3C, SEEK_SET);
|
||||||
uint32_t offsetToPE = read32(file);
|
uint32_t offsetToPE = read32(file);
|
||||||
|
@ -145,7 +151,12 @@ bool wibo::Executable::loadPE(FILE *file) {
|
||||||
|
|
||||||
// Build buffer
|
// Build buffer
|
||||||
imageSize = header32.sizeOfImage;
|
imageSize = header32.sizeOfImage;
|
||||||
imageBuffer = mmap((void *) header32.imageBase, header32.sizeOfImage, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS|MAP_FIXED|MAP_PRIVATE, -1, 0);
|
if (exec) {
|
||||||
|
imageBuffer = mmap((void *)header32.imageBase, header32.sizeOfImage, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||||
|
MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, -1, 0);
|
||||||
|
} else {
|
||||||
|
imageBuffer = mmap(nullptr, header32.sizeOfImage, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||||
|
}
|
||||||
memset(imageBuffer, 0, header32.sizeOfImage);
|
memset(imageBuffer, 0, header32.sizeOfImage);
|
||||||
if (imageBuffer == MAP_FAILED) {
|
if (imageBuffer == MAP_FAILED) {
|
||||||
perror("Image mapping failed!");
|
perror("Image mapping failed!");
|
||||||
|
@ -165,7 +176,7 @@ bool wibo::Executable::loadPE(FILE *file) {
|
||||||
name[8] = 0;
|
name[8] = 0;
|
||||||
DEBUG_LOG("Section %d: name=%s addr=%x size=%x (raw=%x) ptr=%x\n", i, name, section.virtualAddress, section.virtualSize, section.sizeOfRawData, section.pointerToRawData);
|
DEBUG_LOG("Section %d: name=%s addr=%x size=%x (raw=%x) ptr=%x\n", i, name, section.virtualAddress, section.virtualSize, section.sizeOfRawData, section.pointerToRawData);
|
||||||
|
|
||||||
void *sectionBase = (void *) (header32.imageBase + section.virtualAddress);
|
void *sectionBase = (void *) ((uintptr_t) imageBuffer + section.virtualAddress);
|
||||||
if (section.pointerToRawData > 0 && section.sizeOfRawData > 0) {
|
if (section.pointerToRawData > 0 && section.sizeOfRawData > 0) {
|
||||||
// Grab this data
|
// Grab this data
|
||||||
long savePos = ftell(file);
|
long savePos = ftell(file);
|
||||||
|
@ -179,6 +190,11 @@ bool wibo::Executable::loadPE(FILE *file) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!exec) {
|
||||||
|
// No need to resolve imports
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle imports
|
// Handle imports
|
||||||
PEImportDirectoryEntry *dir = fromRVA<PEImportDirectoryEntry>(header32.importTable.virtualAddress);
|
PEImportDirectoryEntry *dir = fromRVA<PEImportDirectoryEntry>(header32.importTable.virtualAddress);
|
||||||
|
|
||||||
|
|
33
main.cpp
33
main.cpp
|
@ -110,11 +110,6 @@ const wibo::Module * wibo::modules[] = {
|
||||||
nullptr,
|
nullptr,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ModuleInfo {
|
|
||||||
std::string name;
|
|
||||||
const wibo::Module* module = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
HMODULE wibo::loadModule(const char *dllName) {
|
HMODULE wibo::loadModule(const char *dllName) {
|
||||||
auto *result = new ModuleInfo;
|
auto *result = new ModuleInfo;
|
||||||
result->name = dllName;
|
result->name = dllName;
|
||||||
|
@ -151,6 +146,32 @@ void *wibo::resolveFuncByOrdinal(HMODULE module, uint16_t ordinal) {
|
||||||
return resolveMissingFuncOrdinal(info->name.c_str(), ordinal);
|
return resolveMissingFuncOrdinal(info->name.c_str(), ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wibo::Executable *wibo::executableFromModule(HMODULE module) {
|
||||||
|
if (wibo::isMainModule(module)) {
|
||||||
|
return wibo::mainModule;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto info = static_cast<wibo::ModuleInfo *>(module);
|
||||||
|
if (!info->executable) {
|
||||||
|
DEBUG_LOG("wibo::executableFromModule: loading %s\n", info->name.c_str());
|
||||||
|
auto executable = std::make_unique<wibo::Executable>();
|
||||||
|
const auto path = files::pathFromWindows(info->name.c_str());
|
||||||
|
FILE *f = fopen(path.c_str(), "rb");
|
||||||
|
if (!f) {
|
||||||
|
perror("wibo::executableFromModule");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
bool result = executable->loadPE(f, false);
|
||||||
|
fclose(f);
|
||||||
|
if (!result) {
|
||||||
|
DEBUG_LOG("wibo::executableFromModule: failed to load %s\n", path.c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
info->executable = std::move(executable);
|
||||||
|
}
|
||||||
|
return info->executable.get();
|
||||||
|
}
|
||||||
|
|
||||||
struct UNICODE_STRING {
|
struct UNICODE_STRING {
|
||||||
unsigned short Length;
|
unsigned short Length;
|
||||||
unsigned short MaximumLength;
|
unsigned short MaximumLength;
|
||||||
|
@ -295,7 +316,7 @@ int main(int argc, char **argv) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
exec.loadPE(f);
|
exec.loadPE(f, true);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
// 32-bit windows only reserves the lowest 2GB of memory for use by a process (https://www.tenouk.com/WinVirtualAddressSpace.html)
|
// 32-bit windows only reserves the lowest 2GB of memory for use by a process (https://www.tenouk.com/WinVirtualAddressSpace.html)
|
||||||
|
|
Loading…
Reference in New Issue