1499 lines
58 KiB
C
1499 lines
58 KiB
C
// kernel32.c - Win32 personality layer
|
||
//
|
||
// Thin wrappers around syscall.dll stubs that expose a familiar Win32 API
|
||
// surface to user-mode code originally written for Windows.
|
||
//
|
||
// Design notes:
|
||
// - HANDLE is void*; we store the uint32_t kernel handle as a pointer value
|
||
// so Win32 code can pass it around opaquely without knowing its size.
|
||
// HANDLE_TO_KH / KH_TO_HANDLE do that conversion in one place.
|
||
// - INVALID_HANDLE_VALUE maps to kernel handle 0 (the always-invalid slot).
|
||
// - HeapAlloc / HeapFree / HeapReAlloc ignore the HANDLE heap argument and
|
||
// always use the single kernel user-heap. This is fine: we don't expose
|
||
// multiple heaps, and NULL is a legal (no-op) heap argument under Win32.
|
||
// - GetLastError / SetLastError are per-thread on Windows. We keep a single
|
||
// global for now; enough for Bink and similar single-threaded guests.
|
||
// - File I/O uses SYS_OPEN_FILE / SYS_READ_FILE / SYS_SEEK_FILE /
|
||
// SYS_GET_FILE_SIZE / SYS_CLOSE_HANDLE (unified close path - no separate
|
||
// SysCloseFile).
|
||
// - WriteFile / CreateFileA in write-mode / SetEndOfFile are stubs because
|
||
// the VFS has no write path yet.
|
||
|
||
#include <stdint.h>
|
||
#include <stddef.h>
|
||
#include <stdbool.h>
|
||
#include <string.h>
|
||
#include <intrin.h>
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Syscall stubs (from syscall.dll, assembled from syscall_stubs.asm)
|
||
// ---------------------------------------------------------------------------
|
||
extern void* SysAlloc(size_t size);
|
||
extern void SysFree(void* ptr, size_t size);
|
||
extern void* SysRealloc(void* ptr, size_t newSize);
|
||
|
||
extern uint32_t SysOpenFile(const char* path, uint32_t* handleOut);
|
||
extern uint32_t SysReadFile(uint32_t handle, void* buf, size_t size, size_t* bytesReadOut);
|
||
extern uint32_t SysWriteFile(uint32_t handle, const void* buf,
|
||
size_t size, size_t* bytesWrittenOut);
|
||
extern uint32_t SysCreateFile(const char* path, uint32_t* handleOut);
|
||
extern uint32_t SysDeleteFile(const char* path);
|
||
extern uint32_t SysTruncateFile(uint32_t handle, uint64_t newSize);
|
||
extern uint64_t SysGetFilePos(uint32_t handle);
|
||
extern uint32_t SysSeekFile(uint32_t handle, uint64_t offset);
|
||
extern uint64_t SysGetFileSize(uint32_t handle);
|
||
extern void SysCloseHandle(uint32_t handle);
|
||
|
||
extern void SysExitProcess(int exitCode);
|
||
extern void SysExitThread(int exitCode);
|
||
extern uint32_t SysCreateThread(uint64_t entry, uint64_t arg,
|
||
uint32_t priority, uint32_t* tidOut);
|
||
extern uint32_t SysGetCurrentTid(void);
|
||
extern uint32_t SysGetCurrentPid(void);
|
||
extern uint32_t SysCreateProcess(const char* path, uint32_t* handleOut, const char* cmdline);
|
||
extern uint32_t SysWaitForProcess(uint32_t handle);
|
||
extern uint32_t SysGetProcessExitCode(uint32_t handle, int* exitCodeOut);
|
||
extern char SysKbReadChar(void);
|
||
extern uint32_t SysKbPoll(void);
|
||
extern void SysYield(void);
|
||
extern void SysSleep(uint32_t ms);
|
||
|
||
extern uint64_t SysGetTick(void);
|
||
extern void SysDebugPrint(const char* str);
|
||
|
||
// Console & directory & CWD stubs (syscall.dll)
|
||
extern void SysWriteConsole(const char* buf, size_t len);
|
||
extern uint32_t SysOpenDir(const char* dirPath, uint32_t* handleOut);
|
||
extern uint32_t SysReadDir(uint32_t handle, void* entryOut);
|
||
extern uint32_t SysSetCwd(const char* path);
|
||
extern uint32_t SysGetCwd(char* buf, size_t size);
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Types
|
||
// ---------------------------------------------------------------------------
|
||
typedef void* HANDLE;
|
||
typedef unsigned long DWORD;
|
||
typedef int BOOL;
|
||
|
||
#define MAX_PATH 260
|
||
#define TRUE 1
|
||
#define FALSE 0
|
||
|
||
#define INVALID_HANDLE_VALUE ((HANDLE)(intptr_t)-1)
|
||
|
||
// File-find structures
|
||
typedef struct _FILETIME {
|
||
DWORD dwLowDateTime;
|
||
DWORD dwHighDateTime;
|
||
} FILETIME;
|
||
|
||
typedef struct _WIN32_FIND_DATAA {
|
||
DWORD dwFileAttributes;
|
||
FILETIME ftCreationTime;
|
||
FILETIME ftLastAccessTime;
|
||
FILETIME ftLastWriteTime;
|
||
DWORD nFileSizeHigh;
|
||
DWORD nFileSizeLow;
|
||
DWORD dwReserved0;
|
||
DWORD dwReserved1;
|
||
char cFileName[260]; // MAX_PATH
|
||
char cAlternateFileName[14];
|
||
} WIN32_FIND_DATAA;
|
||
|
||
// CreateFileA disposition / access flags (subset used by Bink and similar)
|
||
#define GENERIC_READ 0x80000000UL
|
||
#define GENERIC_WRITE 0x40000000UL
|
||
#define GENERIC_READ 0x80000000UL
|
||
#define GENERIC_WRITE 0x40000000UL
|
||
#define OPEN_EXISTING 3
|
||
#define OPEN_ALWAYS 4
|
||
#define CREATE_ALWAYS 2
|
||
#define CREATE_NEW 1
|
||
#define TRUNCATE_EXISTING 5
|
||
#define FILE_BEGIN 0
|
||
#define FILE_CURRENT 1
|
||
#define FILE_END 2
|
||
|
||
// HeapAlloc flags
|
||
#define HEAP_ZERO_MEMORY 0x00000008UL
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Handle conversion helpers
|
||
// uint32_t kernel handle <-> opaque Win32 HANDLE
|
||
// Kernel handle 0 == invalid; we map that to INVALID_HANDLE_VALUE (NULL).
|
||
// ---------------------------------------------------------------------------
|
||
static inline HANDLE KH_TO_HANDLE(uint32_t kh) { return (HANDLE)(uintptr_t)kh; }
|
||
static inline uint32_t HANDLE_TO_KH(HANDLE h) { return (uint32_t)(uintptr_t)h; }
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// GetLastError / SetLastError (single-threaded stub)
|
||
// ---------------------------------------------------------------------------
|
||
static DWORD gLastError = 0;
|
||
|
||
__declspec(dllexport) DWORD GetLastError(void) {
|
||
return gLastError;
|
||
}
|
||
__declspec(dllexport) void SetLastError(DWORD err) {
|
||
gLastError = err;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Process termination
|
||
// ---------------------------------------------------------------------------
|
||
__declspec(dllexport) _Noreturn void ExitProcess(unsigned int code) {
|
||
SysExitProcess((int)code);
|
||
while (true) ; // unreachable
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Debug output
|
||
// ---------------------------------------------------------------------------
|
||
__declspec(dllexport) void OutputDebugStringA(const char* str) {
|
||
if (str) SysDebugPrint(str);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Timer
|
||
// ---------------------------------------------------------------------------
|
||
__declspec(dllexport) DWORD GetTickCount(void) {
|
||
return (DWORD)SysGetTick() * 10;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Memory (heap argument is ignored - single kernel user-heap)
|
||
// ---------------------------------------------------------------------------
|
||
__declspec(dllexport) void* HeapAlloc(void* heap, DWORD flags, size_t size) {
|
||
(void)heap;
|
||
if (!size) return NULL;
|
||
void* ptr = SysAlloc(size);
|
||
if (ptr && (flags & HEAP_ZERO_MEMORY))
|
||
memset(ptr, 0, size);
|
||
if (!ptr) gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY
|
||
return ptr;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL HeapFree(void* heap, DWORD flags, void* ptr) {
|
||
(void)heap; (void)flags;
|
||
// Win32's HeapFree doesn't need the size; we pass 0 and the kernel's
|
||
// allocator handles it (the block header carries the size).
|
||
if (ptr) SysFree(ptr, 0);
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) void* HeapReAlloc(void* heap, DWORD flags,
|
||
void* ptr, size_t size) {
|
||
(void)heap; (void)flags;
|
||
void* newptr = SysRealloc(ptr, size);
|
||
if (!newptr && size) gLastError = 8;
|
||
return newptr;
|
||
}
|
||
|
||
// GetProcessHeap - return a sentinel non-NULL value so callers can pass it
|
||
// back into HeapAlloc/HeapFree without crashing (we ignore it anyway).
|
||
__declspec(dllexport) void* GetProcessHeap(void) {
|
||
return (void*)1;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Locale / code-page stubs
|
||
// ---------------------------------------------------------------------------
|
||
__declspec(dllexport) int MultiByteToWideChar(unsigned int cp, DWORD flags,
|
||
const char* src, int srcLen,
|
||
unsigned short* dst, int dstLen) {
|
||
(void)cp; (void)flags;
|
||
// Trivial ASCII -> UTF-16LE expansion, good enough for English strings.
|
||
if (!src || !dstLen) return 0;
|
||
if (srcLen < 0) srcLen = (int)strlen(src) + 1;
|
||
int out = 0;
|
||
while (out < dstLen && out < srcLen) {
|
||
if (dst) dst[out] = (unsigned short)(unsigned char)src[out];
|
||
out++;
|
||
}
|
||
return out;
|
||
}
|
||
|
||
__declspec(dllexport) int WideCharToMultiByte(unsigned int cp, DWORD flags,
|
||
const unsigned short* src, int srcLen,
|
||
char* dst, int dstLen,
|
||
const char* defChar, int* usedDefault) {
|
||
(void)cp; (void)flags; (void)defChar;
|
||
if (usedDefault) *usedDefault = FALSE;
|
||
if (!src || !dstLen) return 0;
|
||
if (srcLen < 0) {
|
||
srcLen = 0;
|
||
while (src[srcLen]) srcLen++;
|
||
srcLen++;
|
||
}
|
||
int out = 0;
|
||
while (out < dstLen && out < srcLen) {
|
||
if (dst) dst[out] = (char)(src[out] < 128 ? src[out] : '?');
|
||
out++;
|
||
}
|
||
return out;
|
||
}
|
||
|
||
__declspec(dllexport) DWORD GetACP(void) { return 1252; } // Windows-1252
|
||
__declspec(dllexport) DWORD GetOEMCP(void) { return 437; } // DOS Latin US
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// File I/O
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// CreateFileA - opens or creates a file.
|
||
// Supported modes:
|
||
// OPEN_EXISTING + GENERIC_READ -> open existing, read-only
|
||
// OPEN_EXISTING + GENERIC_WRITE -> open existing, read-write
|
||
// OPEN_EXISTING + GENERIC_READ|GENERIC_WRITE -> open existing, read-write
|
||
// CREATE_ALWAYS + GENERIC_WRITE -> create/truncate, write
|
||
// OPEN_ALWAYS + GENERIC_WRITE -> create if absent, write
|
||
// TRUNCATE_EXISTING + GENERIC_WRITE -> open & truncate to zero
|
||
__declspec(dllexport) HANDLE CreateFileA(const char* name,
|
||
DWORD access, DWORD share,
|
||
void* sa, DWORD disposition,
|
||
DWORD flags, void* templ) {
|
||
(void)share; (void)sa; (void)flags; (void)templ;
|
||
|
||
int wantWrite = (access & GENERIC_WRITE) != 0;
|
||
|
||
if (disposition == OPEN_EXISTING || disposition == TRUNCATE_EXISTING) {
|
||
// Open the file via the read path (it must already exist).
|
||
uint32_t kh = 0;
|
||
uint32_t s = SysOpenFile(name, &kh);
|
||
if (s != 0) { gLastError = 2; return INVALID_HANDLE_VALUE; }
|
||
|
||
if (disposition == TRUNCATE_EXISTING && wantWrite) {
|
||
// Immediately truncate to zero.
|
||
uint32_t ts = SysTruncateFile(kh, 0);
|
||
if (ts != 0) {
|
||
SysCloseHandle(kh);
|
||
gLastError = 5;
|
||
return INVALID_HANDLE_VALUE;
|
||
}
|
||
}
|
||
return KH_TO_HANDLE(kh);
|
||
}
|
||
|
||
if (disposition == CREATE_ALWAYS && wantWrite) {
|
||
// Create-or-replace: try to create; if the file already exists,
|
||
// open it and truncate it to zero instead.
|
||
uint32_t kh = 0;
|
||
uint32_t s = SysCreateFile(name, &kh);
|
||
if (s != 0) {
|
||
// File may already exist - open and truncate.
|
||
s = SysOpenFile(name, &kh);
|
||
if (s != 0) { gLastError = 5; return INVALID_HANDLE_VALUE; }
|
||
SysTruncateFile(kh, 0);
|
||
}
|
||
return KH_TO_HANDLE(kh);
|
||
}
|
||
|
||
if (disposition == OPEN_ALWAYS && wantWrite) {
|
||
// Open if exists, create if not.
|
||
uint32_t kh = 0;
|
||
if (SysOpenFile(name, &kh) != 0) {
|
||
if (SysCreateFile(name, &kh) != 0) {
|
||
gLastError = 5;
|
||
return INVALID_HANDLE_VALUE;
|
||
}
|
||
}
|
||
return KH_TO_HANDLE(kh);
|
||
}
|
||
|
||
if (disposition == CREATE_NEW && wantWrite) {
|
||
// Fail if the file already exists (we can't check atomically,
|
||
// but SysCreateFile returns NAME_COLLISION for existing files).
|
||
uint32_t kh = 0;
|
||
uint32_t s = SysCreateFile(name, &kh);
|
||
if (s != 0) { gLastError = 80; return INVALID_HANDLE_VALUE; } // ERROR_FILE_EXISTS
|
||
return KH_TO_HANDLE(kh);
|
||
}
|
||
|
||
// Any other combination (e.g. read-only CREATE_ALWAYS) is not supported.
|
||
gLastError = 87; // ERROR_INVALID_PARAMETER
|
||
return INVALID_HANDLE_VALUE;
|
||
}
|
||
|
||
// GetFileSize - fills optional high-DWORD pointer, returns low DWORD.
|
||
// Returns 0xFFFFFFFF on error, matching Win32 behaviour.
|
||
__declspec(dllexport) DWORD GetFileSize(HANDLE hFile, DWORD* fileSizeHigh) {
|
||
if (!hFile) { gLastError = 6; return 0xFFFFFFFFUL; }
|
||
uint64_t sz = SysGetFileSize(HANDLE_TO_KH(hFile));
|
||
if (fileSizeHigh) *fileSizeHigh = (DWORD)(sz >> 32);
|
||
return (DWORD)(sz & 0xFFFFFFFFULL);
|
||
}
|
||
|
||
// ReadFile - sequential read at the file's current position.
|
||
__declspec(dllexport) BOOL ReadFile(HANDLE hFile, void* buf, DWORD toRead,
|
||
DWORD* bytesRead, void* overlapped) {
|
||
(void)overlapped;
|
||
if (!hFile || !buf) { gLastError = 6; return FALSE; }
|
||
size_t got = 0;
|
||
uint32_t s = SysReadFile(HANDLE_TO_KH(hFile), buf, (size_t)toRead, &got);
|
||
if (bytesRead) *bytesRead = (DWORD)got;
|
||
if (s != 0) { gLastError = 1; return FALSE; }
|
||
return TRUE;
|
||
}
|
||
|
||
// WriteFile - sequential write at the file's current position.
|
||
// Extends the file if writing past EOF. Overlapped I/O is not supported.
|
||
__declspec(dllexport) BOOL WriteFile(HANDLE hFile, const void* buf, DWORD toWrite,
|
||
DWORD* bytesWritten, void* overlapped) {
|
||
(void)overlapped;
|
||
if (!hFile || !buf) { gLastError = 6; return FALSE; }
|
||
size_t written = 0;
|
||
uint32_t s = SysWriteFile(HANDLE_TO_KH(hFile), buf, (size_t)toWrite, &written);
|
||
if (bytesWritten) *bytesWritten = (DWORD)written;
|
||
if (s != 0) { gLastError = 29; return FALSE; } // ERROR_WRITE_FAULT
|
||
return TRUE;
|
||
}
|
||
|
||
// SetFilePointer - seek to an absolute, relative, or end-relative position.
|
||
// Returns new position low DWORD, or 0xFFFFFFFF on error.
|
||
__declspec(dllexport) DWORD SetFilePointer(HANDLE hFile, long dist,
|
||
long* distHigh, DWORD method) {
|
||
if (!hFile) { gLastError = 6; return 0xFFFFFFFFUL; }
|
||
|
||
uint32_t kh = HANDLE_TO_KH(hFile);
|
||
uint64_t offset = 0;
|
||
int64_t delta = distHigh
|
||
? ((int64_t)(uint64_t)(uint32_t)*distHigh << 32) | (uint32_t)dist
|
||
: (int64_t)dist;
|
||
|
||
if (method == FILE_BEGIN) {
|
||
offset = (uint64_t)delta;
|
||
} else if (method == FILE_CURRENT) {
|
||
uint64_t cur = SysGetFilePos(kh);
|
||
if (cur == (uint64_t)-1) { gLastError = 6; return 0xFFFFFFFFUL; }
|
||
offset = (uint64_t)((int64_t)cur + delta);
|
||
} else if (method == FILE_END) {
|
||
uint64_t sz = SysGetFileSize(kh);
|
||
offset = (uint64_t)((int64_t)sz + delta);
|
||
} else {
|
||
gLastError = 87; return 0xFFFFFFFFUL;
|
||
}
|
||
|
||
uint32_t s = SysSeekFile(kh, offset);
|
||
if (s != 0) { gLastError = 1; return 0xFFFFFFFFUL; }
|
||
if (distHigh) *distHigh = (long)(int32_t)(offset >> 32);
|
||
return (DWORD)(offset & 0xFFFFFFFFULL);
|
||
}
|
||
|
||
// SetEndOfFile - truncate or extend the file to the current seek position.
|
||
__declspec(dllexport) BOOL SetEndOfFile(HANDLE hFile) {
|
||
if (!hFile) { gLastError = 6; return FALSE; }
|
||
uint32_t kh = HANDLE_TO_KH(hFile);
|
||
uint64_t pos = SysGetFilePos(kh);
|
||
if (pos == (uint64_t)-1) { gLastError = 6; return FALSE; }
|
||
uint32_t s = SysTruncateFile(kh, pos);
|
||
if (s != 0) { gLastError = 1; return FALSE; }
|
||
return TRUE;
|
||
}
|
||
|
||
// DeleteFileA - delete a file by name; no open handle required.
|
||
__declspec(dllexport) BOOL DeleteFileA(const char* lpFileName) {
|
||
if (!lpFileName) { gLastError = 87; return FALSE; }
|
||
uint32_t s = SysDeleteFile(lpFileName);
|
||
if (s != 0) { gLastError = 2; return FALSE; } // ERROR_FILE_NOT_FOUND
|
||
return TRUE;
|
||
}
|
||
|
||
// CloseHandle - unified close for files, mutexes, semaphores, events, queues.
|
||
__declspec(dllexport) BOOL CloseHandle(HANDLE hObject) {
|
||
if (!hObject) { gLastError = 6; return FALSE; }
|
||
SysCloseHandle(HANDLE_TO_KH(hObject));
|
||
return TRUE;
|
||
}
|
||
|
||
// ===========================================================================
|
||
// Extended shims - funcs.txt additions
|
||
// All functions here return plausible values; no real implementation exists
|
||
// unless a matching syscall stub is available.
|
||
// ===========================================================================
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Additional Heap APIs
|
||
// We have one kernel heap; the HANDLE is always ignored.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
typedef int HEAP_INFORMATION_CLASS; // opaque
|
||
|
||
__declspec(dllexport) HANDLE HeapCreate(DWORD flOptions,
|
||
size_t dwInitialSize,
|
||
size_t dwMaximumSize) {
|
||
(void)flOptions; (void)dwInitialSize; (void)dwMaximumSize;
|
||
return (HANDLE)(uintptr_t)1; // non-NULL sentinel
|
||
}
|
||
|
||
__declspec(dllexport) BOOL HeapDestroy(HANDLE hHeap) {
|
||
(void)hHeap;
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) size_t HeapSize(HANDLE heap, DWORD flags, const void* ptr) {
|
||
(void)heap; (void)flags; (void)ptr;
|
||
return (size_t)-1; // Win32 returns (SIZE_T)-1 on error / unsupported
|
||
}
|
||
|
||
__declspec(dllexport) BOOL HeapSetInformation(HANDLE HeapHandle,
|
||
HEAP_INFORMATION_CLASS cls,
|
||
void* info, size_t infoLen) {
|
||
(void)HeapHandle; (void)cls; (void)info; (void)infoLen;
|
||
return TRUE;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Process & Module pseudo-handles / queries
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// Pseudo-handles matching Windows convention: -1 = process, -2 = thread.
|
||
__declspec(dllexport) HANDLE GetCurrentProcess(void) {
|
||
return (HANDLE)(uintptr_t)(~(uintptr_t)0);
|
||
}
|
||
|
||
__declspec(dllexport) HANDLE GetCurrentThread(void) {
|
||
return (HANDLE)(uintptr_t)(~(uintptr_t)1);
|
||
}
|
||
|
||
__declspec(dllexport) DWORD GetCurrentThreadId(void) { return (DWORD)SysGetCurrentTid(); }
|
||
__declspec(dllexport) DWORD GetCurrentProcessId(void) { return (DWORD)SysGetCurrentPid(); }
|
||
|
||
// GetModuleHandleA/W - no PE map; return NULL so callers that check can fail
|
||
// gracefully. GetModuleHandleA(NULL) on Windows returns the exe base; we
|
||
// return a recognisable non-NULL sentinel so code that just passes it to
|
||
// GetProcAddress doesn't immediately crash.
|
||
__declspec(dllexport) HANDLE GetModuleHandleA(const char* lpModuleName) {
|
||
return lpModuleName ? NULL : (HANDLE)(uintptr_t)0x00400000UL;
|
||
}
|
||
|
||
__declspec(dllexport) HANDLE GetModuleHandleW(const unsigned short* lpModuleName) {
|
||
return lpModuleName ? NULL : (HANDLE)(uintptr_t)0x00400000UL;
|
||
}
|
||
|
||
// GetProcAddress - no dynamic linker; always fail.
|
||
__declspec(dllexport) void* GetProcAddress(HANDLE hModule, const char* lpProcName) {
|
||
(void)hModule; (void)lpProcName;
|
||
gLastError = 127; // ERROR_PROC_NOT_FOUND
|
||
return NULL;
|
||
}
|
||
|
||
// LoadLibraryA - no dynamic loader; always fail.
|
||
__declspec(dllexport) HANDLE LoadLibraryA(const char* lpLibFileName) {
|
||
(void)lpLibFileName;
|
||
gLastError = 2; // ERROR_FILE_NOT_FOUND
|
||
return NULL;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL FreeLibrary(HANDLE hLibModule) {
|
||
(void)hLibModule;
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) DWORD GetModuleFileNameA(HANDLE hModule,
|
||
char* lpFilename, DWORD nSize) {
|
||
(void)hModule;
|
||
if (lpFilename && nSize) lpFilename[0] = '\0';
|
||
return 0;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL TerminateProcess(HANDLE hProcess, unsigned int uExitCode) {
|
||
(void)hProcess;
|
||
SysExitProcess((int)uExitCode);
|
||
while (true) ;
|
||
}
|
||
|
||
// STARTUPINFOA - only cb matters for our stub.
|
||
typedef struct {
|
||
DWORD cb;
|
||
char* lpReserved;
|
||
char* lpDesktop;
|
||
char* lpTitle;
|
||
DWORD dwX, dwY, dwXSize, dwYSize;
|
||
DWORD dwXCountChars, dwYCountChars;
|
||
DWORD dwFillAttribute, dwFlags;
|
||
unsigned short wShowWindow, cbReserved2;
|
||
unsigned char* lpReserved2;
|
||
HANDLE hStdInput, hStdOutput, hStdError;
|
||
} STARTUPINFOA;
|
||
|
||
__declspec(dllexport) void GetStartupInfoA(STARTUPINFOA* lpStartupInfo) {
|
||
if (!lpStartupInfo) return;
|
||
// Zero every field, then set the mandatory cb.
|
||
unsigned char* p = (unsigned char*)lpStartupInfo;
|
||
for (size_t i = 0; i < sizeof(*lpStartupInfo); i++) p[i] = 0;
|
||
lpStartupInfo->cb = (DWORD)sizeof(*lpStartupInfo);
|
||
}
|
||
|
||
__declspec(dllexport) char* GetCommandLineA(void) {
|
||
// Return a valid but empty-ish command line.
|
||
static char cmdline[4] = "";
|
||
return cmdline;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Threading
|
||
// ---------------------------------------------------------------------------
|
||
|
||
#define WAIT_OBJECT_0 0x00000000UL
|
||
#define WAIT_TIMEOUT 0x00000102UL
|
||
#define INFINITE 0xFFFFFFFFUL
|
||
|
||
__declspec(dllexport) HANDLE CreateThread(void* lpThreadAttributes,
|
||
size_t dwStackSize,
|
||
void* lpStartAddress,
|
||
void* lpParameter,
|
||
DWORD dwCreationFlags,
|
||
DWORD* lpThreadId) {
|
||
(void)lpThreadAttributes; (void)dwStackSize; (void)dwCreationFlags;
|
||
uint32_t tid = 0;
|
||
uint32_t s = SysCreateThread((uint64_t)(uintptr_t)lpStartAddress,
|
||
(uint64_t)(uintptr_t)lpParameter,
|
||
0 /* normal priority */, &tid);
|
||
if (s != 0) { gLastError = 11; return INVALID_HANDLE_VALUE; }
|
||
if (lpThreadId) *lpThreadId = (DWORD)tid;
|
||
return KH_TO_HANDLE(tid);
|
||
}
|
||
|
||
// WaitForSingleObject - blocks on process handles via SysWaitForProcess;
|
||
// thread and other handle types are not yet waitable so return immediately.
|
||
__declspec(dllexport) DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) {
|
||
(void)dwMilliseconds;
|
||
uint32_t kh = HANDLE_TO_KH(hHandle);
|
||
if (kh) SysWaitForProcess(kh); // no-op KSTATUS if not a process handle
|
||
return WAIT_OBJECT_0;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Process creation
|
||
// ---------------------------------------------------------------------------
|
||
|
||
typedef struct {
|
||
HANDLE hProcess;
|
||
HANDLE hThread;
|
||
DWORD dwProcessId;
|
||
DWORD dwThreadId;
|
||
} PROCESS_INFORMATION;
|
||
|
||
#define STILL_ACTIVE 259UL
|
||
|
||
__declspec(dllexport) BOOL CreateProcessA(
|
||
const char* lpApplicationName,
|
||
char* lpCommandLine,
|
||
void* lpProcessAttributes,
|
||
void* lpThreadAttributes,
|
||
BOOL bInheritHandles,
|
||
DWORD dwCreationFlags,
|
||
void* lpEnvironment,
|
||
const char* lpCurrentDirectory,
|
||
STARTUPINFOA* lpStartupInfo,
|
||
PROCESS_INFORMATION* lpProcessInformation) {
|
||
(void)lpCommandLine; (void)lpProcessAttributes; (void)lpThreadAttributes;
|
||
(void)bInheritHandles; (void)dwCreationFlags; (void)lpEnvironment;
|
||
(void)lpCurrentDirectory; (void)lpStartupInfo;
|
||
|
||
// lpProcessInformation is mandatory
|
||
if (!lpProcessInformation) {
|
||
gLastError = 87; // ERROR_INVALID_PARAMETER
|
||
return FALSE;
|
||
}
|
||
|
||
// Standard Win32 behavior: use lpCommandLine if lpApplicationName is NULL
|
||
const char* targetPath = lpApplicationName;
|
||
if (!targetPath) {
|
||
targetPath = lpCommandLine;
|
||
}
|
||
|
||
// If both are NULL, then it's truly an invalid parameter
|
||
if (!targetPath) {
|
||
gLastError = 87;
|
||
return FALSE;
|
||
}
|
||
|
||
// Build the cmdline: prefer lpCommandLine if given, otherwise fall back to path
|
||
const char* cmdline = lpCommandLine ? lpCommandLine : targetPath;
|
||
|
||
uint32_t kh = 0;
|
||
// Call the syscall.dll stub
|
||
uint32_t s = SysCreateProcess(targetPath, &kh, cmdline);
|
||
if (s != 0 || kh == 0) {
|
||
gLastError = 2; // ERROR_FILE_NOT_FOUND
|
||
return FALSE;
|
||
}
|
||
|
||
lpProcessInformation->hProcess = KH_TO_HANDLE(kh);
|
||
lpProcessInformation->hThread = INVALID_HANDLE_VALUE;
|
||
lpProcessInformation->dwProcessId = 0;
|
||
lpProcessInformation->dwThreadId = 0;
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL GetExitCodeProcess(HANDLE hProcess, DWORD* lpExitCode) {
|
||
if (!lpExitCode) { gLastError = 87; return FALSE; }
|
||
uint32_t kh = HANDLE_TO_KH(hProcess);
|
||
if (!kh) { gLastError = 6; return FALSE; } // ERROR_INVALID_HANDLE
|
||
int code = 0;
|
||
uint32_t s = SysGetProcessExitCode(kh, &code);
|
||
if (s != 0) { gLastError = 6; return FALSE; }
|
||
*lpExitCode = (DWORD)code;
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) DWORD ResumeThread(HANDLE hThread) {
|
||
(void)hThread;
|
||
return 0; // previous suspend count
|
||
}
|
||
|
||
#define THREAD_PRIORITY_NORMAL 0
|
||
|
||
__declspec(dllexport) BOOL SetThreadPriority(HANDLE hThread, int nPriority) {
|
||
(void)hThread; (void)nPriority;
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) int GetThreadPriority(HANDLE hThread) {
|
||
(void)hThread;
|
||
return THREAD_PRIORITY_NORMAL;
|
||
}
|
||
|
||
__declspec(dllexport) uintptr_t SetThreadAffinityMask(HANDLE hThread,
|
||
uintptr_t dwThreadAffinityMask) {
|
||
(void)hThread; (void)dwThreadAffinityMask;
|
||
return 1; // previous mask: CPU 0 only
|
||
}
|
||
|
||
__declspec(dllexport) void Sleep(DWORD dwMilliseconds) {
|
||
SysSleep(dwMilliseconds);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Console I/O
|
||
// ---------------------------------------------------------------------------
|
||
|
||
#define STD_INPUT_HANDLE ((DWORD)-10)
|
||
#define STD_OUTPUT_HANDLE ((DWORD)-11)
|
||
#define STD_ERROR_HANDLE ((DWORD)-12)
|
||
|
||
// We have no real console handles; return a sentinel the other APIs recognise.
|
||
__declspec(dllexport) HANDLE GetStdHandle(DWORD nStdHandle) {
|
||
return (HANDLE)(uintptr_t)nStdHandle;
|
||
}
|
||
|
||
// ReadConsoleA: blocking read of up to nNumberOfCharsToRead characters.
|
||
// Each character is read individually via SysKbReadChar so the caller can
|
||
// do its own line-editing (echo, backspace, etc.) one char at a time.
|
||
__declspec(dllexport) BOOL ReadConsoleA(HANDLE hConsoleInput,
|
||
void* lpBuffer,
|
||
DWORD nNumberOfCharsToRead,
|
||
DWORD* lpNumberOfCharsRead,
|
||
void* pInputControl) {
|
||
(void)hConsoleInput; (void)pInputControl;
|
||
if (!lpBuffer || nNumberOfCharsToRead == 0) return FALSE;
|
||
|
||
char* buf = (char*)lpBuffer;
|
||
DWORD i = 0; // Notice we switched to a while loop
|
||
|
||
while (i < nNumberOfCharsToRead) {
|
||
char c = SysKbReadChar();
|
||
|
||
// Ignore unmapped keys (Shift, Ctrl, etc.)
|
||
if (c == 0) continue;
|
||
|
||
// Deliver the raw character; the caller handles echo and line editing.
|
||
buf[i] = c;
|
||
i++;
|
||
|
||
// Stop reading when the user presses Enter
|
||
if (c == '\n' || c == '\r') {
|
||
// If your shell expects strings to be \n terminated, leave this.
|
||
// If your shell expects \0 termination without a newline,
|
||
// you might want to replace buf[i-1] with \0 here!
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (lpNumberOfCharsRead) *lpNumberOfCharsRead = i;
|
||
return TRUE;
|
||
}
|
||
|
||
// WriteConsoleA: write nNumberOfCharsToWrite characters from lpBuffer to the
|
||
// console as white-on-black text via SYS_WRITE_CONSOLE.
|
||
__declspec(dllexport) BOOL WriteConsoleA(HANDLE hConsoleOutput,
|
||
const void* lpBuffer,
|
||
DWORD nNumberOfCharsToWrite,
|
||
DWORD* lpNumberOfCharsWritten,
|
||
void* lpReserved) {
|
||
(void)hConsoleOutput; (void)lpReserved;
|
||
if (!lpBuffer || nNumberOfCharsToWrite == 0) {
|
||
if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = 0;
|
||
return TRUE;
|
||
}
|
||
SysWriteConsole((const char*)lpBuffer, (size_t)nNumberOfCharsToWrite);
|
||
if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = nNumberOfCharsToWrite;
|
||
return TRUE;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Directory enumeration (FindFirstFileA / FindNextFileA / FindClose)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// KDIR_ENTRY layout must exactly match the definition in io_file.h.
|
||
#define KDIR_NAME_MAX 260
|
||
|
||
typedef struct {
|
||
char Name[KDIR_NAME_MAX];
|
||
uint8_t Attr;
|
||
uint32_t FileSize;
|
||
} KDIR_ENTRY;
|
||
|
||
// FIND_HANDLE is a user-side wrapper: it stores the kernel dir handle and
|
||
// the wildcard pattern so FindNextFileA can filter subsequent entries.
|
||
// We return a pointer to this struct cast to HANDLE; it is NOT a kernel
|
||
// handle, so FindClose must NOT go through CloseHandle/SysCloseHandle.
|
||
typedef struct {
|
||
uint32_t kh; // kernel DIR_OBJECT handle (raw pointer)
|
||
char pattern[MAX_PATH]; // wildcard pattern, e.g. "*" or "*.txt"
|
||
} FIND_HANDLE;
|
||
|
||
// Minimal wildcard match: supports '*' (any sequence) and '?' (any char).
|
||
// Case-insensitive to match FAT32/Windows behaviour.
|
||
static int WildMatch(const char* pat, const char* name) {
|
||
while (*pat && *name) {
|
||
if (*pat == '*') {
|
||
// Skip consecutive stars
|
||
while (*pat == '*') pat++;
|
||
if (!*pat) return 1; // trailing * matches everything
|
||
// Try matching the rest of the pattern at every position
|
||
while (*name) {
|
||
if (WildMatch(pat, name)) return 1;
|
||
name++;
|
||
}
|
||
return 0;
|
||
}
|
||
char p = (*pat >= 'A' && *pat <= 'Z') ? *pat + 32 : *pat;
|
||
char n = (*name >= 'A' && *name <= 'Z') ? *name + 32 : *name;
|
||
if (*pat != '?' && p != n) return 0;
|
||
pat++; name++;
|
||
}
|
||
// Consume trailing stars in the pattern
|
||
while (*pat == '*') pat++;
|
||
return (*pat == '\0' && *name == '\0');
|
||
}
|
||
|
||
// Split a FindFirstFileA-style search path into directory and pattern parts.
|
||
// "apps\\*" -> dir="apps" pat="*"
|
||
// "*.txt" -> dir="" pat="*.txt"
|
||
// "*" -> dir="" pat="*"
|
||
// "apps\\" -> dir="apps" pat="*"
|
||
static void SplitFindPath(const char* searchPath,
|
||
char* dirOut, size_t dirSize,
|
||
char* patOut, size_t patSize) {
|
||
// Find last backslash or forward-slash
|
||
const char* last = NULL;
|
||
for (const char* p = searchPath; *p; p++)
|
||
if (*p == '\\' || *p == '/') last = p;
|
||
|
||
if (!last) {
|
||
// No separator: whole string is the pattern, dir is CWD
|
||
if (dirOut && dirSize) dirOut[0] = '\0';
|
||
if (patOut && patSize) {
|
||
size_t len = strlen(searchPath);
|
||
if (len + 1 > patSize) len = patSize - 1;
|
||
memcpy(patOut, searchPath, len);
|
||
patOut[len] = '\0';
|
||
}
|
||
} else {
|
||
// Everything before the last separator is the directory
|
||
if (dirOut) {
|
||
size_t len = (size_t)(last - searchPath);
|
||
if (len + 1 > dirSize) len = dirSize - 1;
|
||
memcpy(dirOut, searchPath, len);
|
||
dirOut[len] = '\0';
|
||
}
|
||
// Everything after the last separator is the pattern
|
||
if (patOut) {
|
||
const char* p = last + 1;
|
||
size_t len = strlen(p);
|
||
if (len + 1 > patSize) len = patSize - 1;
|
||
memcpy(patOut, p, len);
|
||
patOut[len] = '\0';
|
||
}
|
||
}
|
||
|
||
// Empty pattern defaults to "*"
|
||
if (patOut && patOut[0] == '\0') {
|
||
patOut[0] = '*'; patOut[1] = '\0';
|
||
}
|
||
}
|
||
|
||
// Fill a WIN32_FIND_DATAA from a KDIR_ENTRY.
|
||
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010UL
|
||
#define FILE_ATTRIBUTE_ARCHIVE 0x00000020UL
|
||
#define FILE_ATTRIBUTE_NORMAL 0x00000080UL
|
||
|
||
static void KdirToFindData(const KDIR_ENTRY* ke, WIN32_FIND_DATAA* fd) {
|
||
// Zero the entire struct (covers all FILETIME and reserved fields)
|
||
unsigned char* p = (unsigned char*)fd;
|
||
for (size_t i = 0; i < sizeof(*fd); i++) p[i] = 0;
|
||
|
||
fd->dwFileAttributes = ke->Attr;
|
||
if (!(fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
|
||
!(fd->dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE))
|
||
fd->dwFileAttributes |= FILE_ATTRIBUTE_NORMAL;
|
||
|
||
fd->nFileSizeLow = ke->FileSize;
|
||
fd->nFileSizeHigh = 0;
|
||
|
||
size_t nameLen = strlen(ke->Name);
|
||
if (nameLen >= MAX_PATH) nameLen = MAX_PATH - 1;
|
||
memcpy(fd->cFileName, ke->Name, nameLen);
|
||
fd->cFileName[nameLen] = '\0';
|
||
}
|
||
|
||
__declspec(dllexport) HANDLE FindFirstFileA(const char* lpFileName,
|
||
WIN32_FIND_DATAA* lpFindFileData) {
|
||
if (!lpFileName || !lpFindFileData) {
|
||
gLastError = 87; // ERROR_INVALID_PARAMETER
|
||
return INVALID_HANDLE_VALUE;
|
||
}
|
||
|
||
// Split path into directory and wildcard pattern
|
||
char dir[MAX_PATH];
|
||
char pat[MAX_PATH];
|
||
SplitFindPath(lpFileName, dir, sizeof(dir), pat, sizeof(pat));
|
||
|
||
// Open the kernel directory iterator
|
||
uint32_t kh = 0;
|
||
uint32_t s = SysOpenDir(dir[0] ? dir : NULL, &kh);
|
||
if (s != 0 || kh == 0) {
|
||
gLastError = 2; // ERROR_FILE_NOT_FOUND
|
||
return INVALID_HANDLE_VALUE;
|
||
}
|
||
|
||
// Allocate our user-side FIND_HANDLE wrapper
|
||
FIND_HANDLE* fh = (FIND_HANDLE*)HeapAlloc(NULL, 0, sizeof(FIND_HANDLE));
|
||
if (!fh) {
|
||
SysCloseHandle(kh);
|
||
gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY
|
||
return INVALID_HANDLE_VALUE;
|
||
}
|
||
fh->kh = kh;
|
||
size_t patLen = strlen(pat);
|
||
if (patLen >= MAX_PATH) patLen = MAX_PATH - 1;
|
||
memcpy(fh->pattern, pat, patLen);
|
||
fh->pattern[patLen] = '\0';
|
||
|
||
// Read the first entry that matches the pattern
|
||
KDIR_ENTRY ke;
|
||
for (;;) {
|
||
uint32_t rs = SysReadDir(kh, &ke);
|
||
if (rs != 0) {
|
||
// Directory exhausted or error
|
||
SysCloseHandle(kh);
|
||
HeapFree(NULL, 0, fh);
|
||
gLastError = 18; // ERROR_NO_MORE_FILES
|
||
return INVALID_HANDLE_VALUE;
|
||
}
|
||
if (WildMatch(fh->pattern, ke.Name)) {
|
||
KdirToFindData(&ke, lpFindFileData);
|
||
return (HANDLE)fh;
|
||
}
|
||
}
|
||
}
|
||
|
||
__declspec(dllexport) BOOL FindNextFileA(HANDLE hFindFile,
|
||
WIN32_FIND_DATAA* lpFindFileData) {
|
||
if (hFindFile == INVALID_HANDLE_VALUE || !lpFindFileData) {
|
||
gLastError = 6; // ERROR_INVALID_HANDLE
|
||
return FALSE;
|
||
}
|
||
|
||
FIND_HANDLE* fh = (FIND_HANDLE*)hFindFile;
|
||
KDIR_ENTRY ke;
|
||
|
||
for (;;) {
|
||
uint32_t rs = SysReadDir(fh->kh, &ke);
|
||
if (rs != 0) {
|
||
gLastError = 18; // ERROR_NO_MORE_FILES
|
||
return FALSE;
|
||
}
|
||
if (WildMatch(fh->pattern, ke.Name)) {
|
||
KdirToFindData(&ke, lpFindFileData);
|
||
return TRUE;
|
||
}
|
||
}
|
||
}
|
||
|
||
__declspec(dllexport) BOOL FindClose(HANDLE hFindFile) {
|
||
if (hFindFile == INVALID_HANDLE_VALUE) {
|
||
gLastError = 6;
|
||
return FALSE;
|
||
}
|
||
FIND_HANDLE* fh = (FIND_HANDLE*)hFindFile;
|
||
SysCloseHandle(fh->kh);
|
||
HeapFree(NULL, 0, fh);
|
||
return TRUE;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Current Working Directory
|
||
// ---------------------------------------------------------------------------
|
||
|
||
__declspec(dllexport) BOOL SetCurrentDirectoryA(const char* lpPathName) {
|
||
if (!lpPathName) { gLastError = 87; return FALSE; }
|
||
uint32_t s = SysSetCwd(lpPathName);
|
||
if (s != 0) { gLastError = 2; return FALSE; }
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) DWORD GetCurrentDirectoryA(DWORD nBufferLength, char* lpBuffer) {
|
||
if (!lpBuffer || nBufferLength == 0) {
|
||
// Return required size (including NUL). Max CWD is 512 bytes + "C:\".
|
||
return 515;
|
||
}
|
||
|
||
// Retrieve the raw relative CWD from the kernel (e.g. "" or "apps\\data")
|
||
char raw[512];
|
||
uint32_t s = SysGetCwd(raw, sizeof(raw));
|
||
if (s != 0) { gLastError = 1; return 0; }
|
||
|
||
// Prepend the Windows-style drive letter so callers see "C:\apps\data"
|
||
// rather than just "apps\data".
|
||
char full[515];
|
||
if (raw[0] == '\0') {
|
||
// Root
|
||
full[0] = 'C'; full[1] = ':'; full[2] = '\\'; full[3] = '\0';
|
||
} else {
|
||
// "C:\<raw>"
|
||
full[0] = 'C'; full[1] = ':'; full[2] = '\\';
|
||
size_t rawLen = strlen(raw);
|
||
if (rawLen + 4 > sizeof(full)) rawLen = sizeof(full) - 4;
|
||
memcpy(full + 3, raw, rawLen);
|
||
full[3 + rawLen] = '\0';
|
||
}
|
||
|
||
size_t needed = strlen(full) + 1;
|
||
if ((DWORD)needed > nBufferLength) {
|
||
gLastError = 122; // ERROR_INSUFFICIENT_BUFFER
|
||
return (DWORD)needed;
|
||
}
|
||
memcpy(lpBuffer, full, needed);
|
||
return (DWORD)(needed - 1); // length without NUL, matching Win32
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
|
||
__declspec(dllexport) HANDLE CreateMutexA(void* lpMutexAttributes, BOOL bInitialOwner,
|
||
const char* lpName) {
|
||
(void)lpMutexAttributes; (void)bInitialOwner; (void)lpName;
|
||
return (HANDLE)(uintptr_t)0x4D555458UL; // 'MUTX' sentinel
|
||
}
|
||
|
||
__declspec(dllexport) BOOL ReleaseMutex(HANDLE hMutex) {
|
||
(void)hMutex;
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) HANDLE CreateSemaphoreA(void* lpSemaphoreAttributes,
|
||
long lInitialCount, long lMaximumCount,
|
||
const char* lpName) {
|
||
(void)lpSemaphoreAttributes; (void)lInitialCount;
|
||
(void)lMaximumCount; (void)lpName;
|
||
return (HANDLE)(uintptr_t)0x53454D41UL; // 'SEMA' sentinel
|
||
}
|
||
|
||
__declspec(dllexport) BOOL ReleaseSemaphore(HANDLE hSemaphore, long lReleaseCount,
|
||
long* lpPreviousCount) {
|
||
(void)hSemaphore; (void)lReleaseCount;
|
||
if (lpPreviousCount) *lpPreviousCount = 0;
|
||
return TRUE;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Critical Sections & Condition Variables
|
||
// Single-threaded: all locks are no-ops.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// Minimal opaque types; callers allocate these from their Windows headers -
|
||
// we just need the pointer to be accepted by our stubs.
|
||
typedef struct { uintptr_t _opaque[5]; } CRITICAL_SECTION;
|
||
typedef struct { void* _ptr; } CONDITION_VARIABLE;
|
||
|
||
__declspec(dllexport) void InitializeCriticalSection(CRITICAL_SECTION* lpCS) {
|
||
(void)lpCS;
|
||
}
|
||
__declspec(dllexport) BOOL InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION* lpCS,
|
||
DWORD dwSpinCount) {
|
||
(void)lpCS; (void)dwSpinCount;
|
||
return TRUE;
|
||
}
|
||
__declspec(dllexport) void DeleteCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; }
|
||
__declspec(dllexport) void EnterCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; }
|
||
__declspec(dllexport) void LeaveCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; }
|
||
__declspec(dllexport) BOOL TryEnterCriticalSection(CRITICAL_SECTION* lpCS) {
|
||
(void)lpCS; return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) void InitializeConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; }
|
||
|
||
__declspec(dllexport) BOOL SleepConditionVariableCS(CONDITION_VARIABLE* cv,
|
||
CRITICAL_SECTION* cs,
|
||
DWORD dwMilliseconds) {
|
||
(void)cv; (void)cs; (void)dwMilliseconds;
|
||
return TRUE;
|
||
}
|
||
__declspec(dllexport) void WakeConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; }
|
||
__declspec(dllexport) void WakeAllConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; }
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Timing & Performance Counters
|
||
//
|
||
// We use the CPU's Time Stamp Counter (TSC) for QueryPerformanceCounter so
|
||
// that callers like Bink get a truly high-resolution, continuously-advancing
|
||
// clock. The TSC frequency is measured once on first use.
|
||
//
|
||
// Calibration strategy:
|
||
// 1. Try CPUID leaf 0x15 (TSC / Crystal-clock ratio). If the crystal
|
||
// frequency (ECX) is non-zero, compute TSC Hz = crystal_hz * EBX / EAX
|
||
// with no blocking at all.
|
||
// 2. Otherwise fall back to measuring TSC ticks over a known wall-clock
|
||
// interval supplied by SysGetTick(). SysGetTick() returns raw LAPIC
|
||
// interrupt counts; each unit represents QUANTUM_MS = 10 ms. We wait
|
||
// for two consecutive tick-boundary crossings and divide.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
#define QUANTUM_MS 10 // must match hal_timer.c QUANTUM_MS
|
||
|
||
typedef union {
|
||
struct { DWORD LowPart; long HighPart; } u;
|
||
long long QuadPart;
|
||
} LARGE_INTEGER;
|
||
|
||
static volatile long long gTscFreq = 0; // TSC ticks per second; 0 = uninitialised
|
||
|
||
// Attempt to read TSC frequency from CPUID leaf 0x15.
|
||
// Returns 0 if the leaf is absent or the crystal frequency is not enumerated.
|
||
static long long TscFreqFromCpuid(void) {
|
||
int info[4];
|
||
__cpuid(info, 0);
|
||
if ((unsigned)info[0] < 0x15) return 0;
|
||
|
||
__cpuid(info, 0x15);
|
||
unsigned denom = (unsigned)info[0]; // EAX – TSC/crystal denominator
|
||
unsigned numer = (unsigned)info[1]; // EBX – TSC/crystal numerator
|
||
unsigned crystal_hz = (unsigned)info[2]; // ECX – crystal clock in Hz
|
||
|
||
if (!denom || !numer || !crystal_hz) return 0;
|
||
return (long long)crystal_hz * numer / denom;
|
||
}
|
||
|
||
// Calibrate TSC frequency by timing over two LAPIC-tick boundaries.
|
||
// Each SysGetTick() unit = QUANTUM_MS ms; we measure across N_TICKS units
|
||
// for a total gate time of N_TICKS * QUANTUM_MS ms.
|
||
static long long TscFreqCalibrate(void) {
|
||
const uint64_t N_TICKS = 10; // 10 * 10 ms = 100 ms gate time
|
||
|
||
// Synchronise to the leading edge of a tick.
|
||
uint64_t t = SysGetTick();
|
||
while (SysGetTick() == t) { _mm_pause(); }
|
||
|
||
uint64_t tsc0 = __rdtsc();
|
||
uint64_t t0 = SysGetTick();
|
||
|
||
// Wait for N_TICKS more ticks.
|
||
while ((SysGetTick() - t0) < N_TICKS) { _mm_pause(); }
|
||
|
||
uint64_t tsc1 = __rdtsc();
|
||
uint64_t ticks_got = SysGetTick() - t0; // actual ticks elapsed (>= N_TICKS)
|
||
|
||
// ticks_got LAPIC ticks = ticks_got * QUANTUM_MS ms = ticks_got * QUANTUM_MS / 1000 s
|
||
// TSC Hz = (tsc1-tsc0) / (ticks_got * QUANTUM_MS / 1000)
|
||
// = (tsc1-tsc0) * 1000 / (ticks_got * QUANTUM_MS)
|
||
return (long long)((tsc1 - tsc0) * 1000ULL / (ticks_got * (uint64_t)QUANTUM_MS));
|
||
}
|
||
|
||
static long long TscFreqGet(void) {
|
||
if (gTscFreq) return gTscFreq;
|
||
|
||
long long f = TscFreqFromCpuid();
|
||
if (!f) f = TscFreqCalibrate();
|
||
|
||
gTscFreq = f;
|
||
return f;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency) {
|
||
if (lpFrequency) lpFrequency->QuadPart = TscFreqGet();
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount) {
|
||
// Ensure frequency is measured before the first counter read so that the
|
||
// baseline TSC and the frequency come from the same calibration window.
|
||
TscFreqGet();
|
||
if (lpPerformanceCount)
|
||
lpPerformanceCount->QuadPart = (long long)__rdtsc();
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) void GetSystemTimeAsFileTime(void* lpSystemTimeAsFileTime) {
|
||
// Return a fixed FILETIME representing 2024-01-01 00:00:00 UTC.
|
||
// FILETIME = 100-nanosecond intervals since 1601-01-01.
|
||
// 2024-01-01 offset: 133186080000000000 * 100 ns
|
||
if (!lpSystemTimeAsFileTime) return;
|
||
// SysGetTick() units = QUANTUM_MS ms = QUANTUM_MS * 10000 * 100-ns intervals.
|
||
unsigned long long ft = 133186080000000000ULL
|
||
+ SysGetTick() * ((unsigned long long)QUANTUM_MS * 10000ULL);
|
||
unsigned char* p = (unsigned char*)lpSystemTimeAsFileTime;
|
||
for (int i = 0; i < 8; i++) p[i] = (unsigned char)(ft >> (i * 8));
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// System Information
|
||
// ---------------------------------------------------------------------------
|
||
|
||
typedef struct {
|
||
union {
|
||
DWORD dwOemId;
|
||
struct { unsigned short wProcessorArchitecture;
|
||
unsigned short wReserved; };
|
||
};
|
||
DWORD dwPageSize;
|
||
void* lpMinimumApplicationAddress;
|
||
void* lpMaximumApplicationAddress;
|
||
uintptr_t dwActiveProcessorMask;
|
||
DWORD dwNumberOfProcessors;
|
||
DWORD dwProcessorType;
|
||
DWORD dwAllocationGranularity;
|
||
unsigned short wProcessorLevel;
|
||
unsigned short wProcessorRevision;
|
||
} SYSTEM_INFO;
|
||
|
||
__declspec(dllexport) void GetSystemInfo(SYSTEM_INFO* lpSystemInfo) {
|
||
if (!lpSystemInfo) return;
|
||
unsigned char* p = (unsigned char*)lpSystemInfo;
|
||
for (size_t i = 0; i < sizeof(*lpSystemInfo); i++) p[i] = 0;
|
||
lpSystemInfo->wProcessorArchitecture = 9; // PROCESSOR_ARCHITECTURE_AMD64
|
||
lpSystemInfo->dwPageSize = 4096;
|
||
lpSystemInfo->lpMinimumApplicationAddress = (void*)0x10000;
|
||
lpSystemInfo->lpMaximumApplicationAddress = (void*)0x7FFFFFFEFFFFull;
|
||
lpSystemInfo->dwActiveProcessorMask = 1;
|
||
lpSystemInfo->dwNumberOfProcessors = 1;
|
||
lpSystemInfo->dwProcessorType = 8664; // PROCESSOR_AMD_X8664
|
||
lpSystemInfo->dwAllocationGranularity= 65536;
|
||
lpSystemInfo->wProcessorLevel = 6;
|
||
lpSystemInfo->wProcessorRevision = 0;
|
||
}
|
||
|
||
typedef struct {
|
||
DWORD dwLength;
|
||
DWORD dwMemoryLoad;
|
||
unsigned long long ullTotalPhys;
|
||
unsigned long long ullAvailPhys;
|
||
unsigned long long ullTotalPageFile;
|
||
unsigned long long ullAvailPageFile;
|
||
unsigned long long ullTotalVirtual;
|
||
unsigned long long ullAvailVirtual;
|
||
unsigned long long ullAvailExtendedVirtual;
|
||
} MEMORYSTATUSEX;
|
||
|
||
__declspec(dllexport) BOOL GetLogicalProcessorInformation(void* Buffer,
|
||
DWORD* ReturnedLength) {
|
||
(void)Buffer;
|
||
// Signal that the buffer is too small so callers know to handle failure.
|
||
if (ReturnedLength) *ReturnedLength = 0;
|
||
gLastError = 122; // ERROR_INSUFFICIENT_BUFFER
|
||
return FALSE;
|
||
}
|
||
|
||
#define MAX_PATH 260
|
||
|
||
__declspec(dllexport) unsigned int GetSystemDirectoryA(char* lpBuffer, unsigned int uSize) {
|
||
static const char sysdir[] = "C:\\Windows\\System32";
|
||
unsigned int needed = sizeof(sysdir); // includes NUL
|
||
if (lpBuffer && uSize >= needed) {
|
||
for (unsigned int i = 0; i < needed; i++) lpBuffer[i] = sysdir[i];
|
||
}
|
||
return needed - 1; // return length without NUL, matching Win32
|
||
}
|
||
|
||
__declspec(dllexport) unsigned int GetWindowsDirectoryA(char* lpBuffer, unsigned int uSize) {
|
||
static const char windir[] = "C:\\Windows";
|
||
unsigned int needed = sizeof(windir);
|
||
if (lpBuffer && uSize >= needed) {
|
||
for (unsigned int i = 0; i < needed; i++) lpBuffer[i] = windir[i];
|
||
}
|
||
return needed - 1;
|
||
}
|
||
|
||
__declspec(dllexport) DWORD GetEnvironmentVariableA(const char* lpName, char* lpBuffer,
|
||
DWORD nSize) {
|
||
(void)lpName; (void)lpBuffer; (void)nSize;
|
||
gLastError = 203; // ERROR_ENVVAR_NOT_FOUND
|
||
return 0;
|
||
}
|
||
|
||
__declspec(dllexport) char* GetEnvironmentStrings(void) {
|
||
// Return a double-NUL-terminated empty environment block.
|
||
static char emptyEnv[2] = { '\0', '\0' };
|
||
return emptyEnv;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL FreeEnvironmentStringsA(char* penv) {
|
||
(void)penv; return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) unsigned short* GetEnvironmentStringsW(void) {
|
||
static unsigned short emptyEnvW[2] = { 0, 0 };
|
||
return emptyEnvW;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL FreeEnvironmentStringsW(unsigned short* penv) {
|
||
(void)penv; return TRUE;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Handle / Error helpers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
__declspec(dllexport) unsigned int SetErrorMode(unsigned int uMode) {
|
||
(void)uMode;
|
||
return 0; // previous mode
|
||
}
|
||
|
||
__declspec(dllexport) unsigned int SetHandleCount(unsigned int uNumber) {
|
||
return uNumber < 256 ? 256 : uNumber;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// File helpers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
#define FILE_TYPE_UNKNOWN 0x0000
|
||
#define FILE_TYPE_DISK 0x0001
|
||
#define FILE_TYPE_CHAR 0x0002
|
||
#define FILE_TYPE_PIPE 0x0003
|
||
|
||
#define STD_INPUT_HANDLE ((DWORD)-10)
|
||
#define STD_OUTPUT_HANDLE ((DWORD)-11)
|
||
#define STD_ERROR_HANDLE ((DWORD)-12)
|
||
|
||
__declspec(dllexport) DWORD GetFileType(HANDLE hFile) {
|
||
if (!hFile || hFile == INVALID_HANDLE_VALUE) return FILE_TYPE_UNKNOWN;
|
||
return FILE_TYPE_DISK;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Exception Handling & Debugging
|
||
// ---------------------------------------------------------------------------
|
||
|
||
#define EXCEPTION_CONTINUE_SEARCH 0
|
||
#define EXCEPTION_EXECUTE_HANDLER 1
|
||
|
||
__declspec(dllexport) void* AddVectoredExceptionHandler(unsigned long First, void* Handler) {
|
||
(void)First; (void)Handler;
|
||
// Return a non-NULL sentinel so callers can pass it to RemoveVectoredExceptionHandler.
|
||
return (void*)(uintptr_t)0x56455843UL; // 'VEXC'
|
||
}
|
||
|
||
__declspec(dllexport) unsigned long RemoveVectoredExceptionHandler(void* Handle) {
|
||
(void)Handle;
|
||
return 1; // success
|
||
}
|
||
|
||
__declspec(dllexport) void RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags,
|
||
DWORD nNumberOfArguments,
|
||
const uintptr_t* lpArguments) {
|
||
(void)dwExceptionCode; (void)dwExceptionFlags;
|
||
(void)nNumberOfArguments; (void)lpArguments;
|
||
// Best-effort: nothing to unwind; just halt.
|
||
while (true) ;
|
||
}
|
||
|
||
typedef struct _EXCEPTION_POINTERS { void* ExceptionRecord; void* ContextRecord; } EXCEPTION_POINTERS;
|
||
|
||
__declspec(dllexport) long UnhandledExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) {
|
||
(void)ExceptionInfo;
|
||
return EXCEPTION_CONTINUE_SEARCH;
|
||
}
|
||
|
||
typedef long (*LPTOP_LEVEL_EXCEPTION_FILTER)(EXCEPTION_POINTERS*);
|
||
|
||
__declspec(dllexport) LPTOP_LEVEL_EXCEPTION_FILTER
|
||
SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpFilter) {
|
||
(void)lpFilter;
|
||
return NULL; // previous filter: none
|
||
}
|
||
|
||
__declspec(dllexport) BOOL IsDebuggerPresent(void) {
|
||
return FALSE;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// RTL Exception / Unwind (SEH infrastructure - all stubs)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
typedef struct _RUNTIME_FUNCTION {
|
||
unsigned int BeginAddress;
|
||
unsigned int EndAddress;
|
||
unsigned int UnwindInfoAddress;
|
||
} RUNTIME_FUNCTION;
|
||
|
||
// We have no runtime function table, so RtlLookupFunctionEntry always fails.
|
||
__declspec(dllexport) RUNTIME_FUNCTION*
|
||
RtlLookupFunctionEntry(unsigned long long ControlPc,
|
||
unsigned long long* ImageBase,
|
||
void* HistoryTable) {
|
||
(void)ControlPc; (void)HistoryTable;
|
||
if (ImageBase) *ImageBase = 0;
|
||
return NULL;
|
||
}
|
||
|
||
__declspec(dllexport) void* RtlVirtualUnwind(unsigned long HandlerType,
|
||
unsigned long long ImageBase,
|
||
unsigned long long ControlPc,
|
||
RUNTIME_FUNCTION* FunctionEntry,
|
||
void* ContextRecord,
|
||
void** HandlerData,
|
||
unsigned long long* EstablisherFrame,
|
||
void* ContextPointers) {
|
||
(void)HandlerType; (void)ImageBase; (void)ControlPc; (void)FunctionEntry;
|
||
(void)ContextRecord; (void)ContextPointers;
|
||
if (HandlerData) *HandlerData = NULL;
|
||
if (EstablisherFrame)*EstablisherFrame = 0;
|
||
return NULL;
|
||
}
|
||
|
||
__declspec(dllexport) void RtlCaptureContext(void* ContextRecord) {
|
||
// Zero out the context record so callers don't read garbage.
|
||
if (!ContextRecord) return;
|
||
// CONTEXT is ~1232 bytes on x64; zero a conservative upper bound.
|
||
unsigned char* p = (unsigned char*)ContextRecord;
|
||
for (int i = 0; i < 1232; i++) p[i] = 0;
|
||
}
|
||
|
||
__declspec(dllexport) void RtlUnwindEx(void* TargetFrame, void* TargetIp,
|
||
void* ExceptionRecord, void* ReturnValue,
|
||
void* ContextRecord, void* HistoryTable) {
|
||
(void)TargetFrame; (void)TargetIp; (void)ExceptionRecord;
|
||
(void)ReturnValue; (void)ContextRecord; (void)HistoryTable;
|
||
// No real unwind possible; halt rather than corrupt the stack silently.
|
||
while (true) ;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Code Pages & Locale helpers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
typedef struct { unsigned int MaxCharSize; unsigned char DefaultChar[2]; unsigned char LeadByte[12]; } CPINFO;
|
||
|
||
__declspec(dllexport) BOOL GetCPInfo(unsigned int CodePage, CPINFO* lpCPInfo) {
|
||
(void)CodePage;
|
||
if (lpCPInfo) {
|
||
lpCPInfo->MaxCharSize = 1;
|
||
lpCPInfo->DefaultChar[0] = '?';
|
||
lpCPInfo->DefaultChar[1] = 0;
|
||
for (int i = 0; i < 12; i++) lpCPInfo->LeadByte[i] = 0;
|
||
}
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL IsValidCodePage(unsigned int CodePage) {
|
||
// Acknowledge the two code pages we claim to support.
|
||
return (CodePage == 1252 || CodePage == 437) ? TRUE : FALSE;
|
||
}
|
||
|
||
__declspec(dllexport) int LCMapStringA(unsigned int Locale, DWORD dwMapFlags,
|
||
const char* lpSrcStr, int cchSrc,
|
||
char* lpDestStr, int cchDest) {
|
||
(void)Locale; (void)dwMapFlags; (void)lpSrcStr; (void)cchSrc;
|
||
(void)lpDestStr; (void)cchDest;
|
||
return 0;
|
||
}
|
||
|
||
__declspec(dllexport) int LCMapStringW(unsigned int Locale, DWORD dwMapFlags,
|
||
const unsigned short* lpSrcStr, int cchSrc,
|
||
unsigned short* lpDestStr, int cchDest) {
|
||
(void)Locale; (void)dwMapFlags; (void)lpSrcStr; (void)cchSrc;
|
||
(void)lpDestStr; (void)cchDest;
|
||
return 0;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL GetStringTypeA(unsigned int Locale, DWORD dwInfoType,
|
||
const char* lpSrcStr, int cchSrc,
|
||
unsigned short* lpCharType) {
|
||
(void)Locale; (void)dwInfoType; (void)lpSrcStr; (void)cchSrc; (void)lpCharType;
|
||
return FALSE;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL GetStringTypeW(DWORD dwInfoType,
|
||
const unsigned short* lpSrcStr, int cchSrc,
|
||
unsigned short* lpCharType) {
|
||
(void)dwInfoType; (void)lpSrcStr; (void)cchSrc; (void)lpCharType;
|
||
return FALSE;
|
||
}
|
||
|
||
__declspec(dllexport) int GetLocaleInfoA(unsigned int Locale, DWORD LCType,
|
||
char* lpLCData, int cchData) {
|
||
(void)Locale; (void)LCType; (void)lpLCData; (void)cchData;
|
||
return 0;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Fiber Local Storage (FLS)
|
||
// We implement a minimal 64-slot static table so callers that use FLS for
|
||
// thread-local-ish state don't immediately fail.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
#define FLS_MAX_SLOTS 64
|
||
#define FLS_OUT_OF_INDEXES 0xFFFFFFFFUL
|
||
|
||
static void* gFlsSlots[FLS_MAX_SLOTS];
|
||
static int gFlsUsed[FLS_MAX_SLOTS]; // 0 = free, 1 = allocated
|
||
|
||
__declspec(dllexport) DWORD FlsAlloc(void* lpCallback) {
|
||
(void)lpCallback;
|
||
for (int i = 0; i < FLS_MAX_SLOTS; i++) {
|
||
if (!gFlsUsed[i]) {
|
||
gFlsUsed[i] = 1;
|
||
gFlsSlots[i] = NULL;
|
||
return (DWORD)i;
|
||
}
|
||
}
|
||
gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY
|
||
return (DWORD)FLS_OUT_OF_INDEXES;
|
||
}
|
||
|
||
__declspec(dllexport) void* FlsGetValue(DWORD dwFlsIndex) {
|
||
if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) {
|
||
gLastError = 87; // ERROR_INVALID_PARAMETER
|
||
return NULL;
|
||
}
|
||
return gFlsSlots[dwFlsIndex];
|
||
}
|
||
|
||
__declspec(dllexport) BOOL FlsSetValue(DWORD dwFlsIndex, void* lpFlsData) {
|
||
if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) {
|
||
gLastError = 87;
|
||
return FALSE;
|
||
}
|
||
gFlsSlots[dwFlsIndex] = lpFlsData;
|
||
return TRUE;
|
||
}
|
||
|
||
__declspec(dllexport) BOOL FlsFree(DWORD dwFlsIndex) {
|
||
if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) {
|
||
gLastError = 87;
|
||
return FALSE;
|
||
}
|
||
gFlsUsed[dwFlsIndex] = 0;
|
||
gFlsSlots[dwFlsIndex] = NULL;
|
||
return TRUE;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Pointer encoding (security cookie obfuscation)
|
||
// We have no security cookie; return the pointer unchanged.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
__declspec(dllexport) void* EncodePointer(void* Ptr) { return Ptr; }
|
||
__declspec(dllexport) void* DecodePointer(void* Ptr) { return Ptr; }
|