2666 lines
95 KiB
C
2666 lines
95 KiB
C
// Win32 kernel32 compatibility layer. This file maps common Win32 process, file, console, heap, and timing APIs onto native syscalls.
|
|
|
|
// 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 user-mode heap. This is fine: we don't expose
|
|
// multiple heaps, and NULL is a legal (no-op) heap argument under Win32.
|
|
// - Win32 per-thread state lives behind SYS_TEB::SubsystemData so the native
|
|
// TEB ABI stays subsystem-neutral.
|
|
// - File I/O uses the flags-based SYS_OPEN_FILE request plus the common
|
|
// read/write/seek/query/close syscalls.
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <intrin.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
extern size_t _malloc_usable_size(const void* ptr);
|
|
|
|
// Types.
|
|
typedef void* HANDLE;
|
|
typedef unsigned long DWORD;
|
|
typedef unsigned short WORD;
|
|
typedef int BOOL;
|
|
typedef unsigned char BYTE;
|
|
typedef short SHORT;
|
|
typedef long LONG;
|
|
typedef struct { LONG x; LONG y; } POINT;
|
|
typedef struct { SHORT X; SHORT Y; } COORD;
|
|
typedef struct { SHORT Left; SHORT Top; SHORT Right; SHORT Bottom; } SMALL_RECT;
|
|
|
|
typedef struct _CONSOLE_SCREEN_BUFFER_INFO {
|
|
COORD dwSize;
|
|
COORD dwCursorPosition;
|
|
WORD wAttributes;
|
|
SMALL_RECT srWindow;
|
|
COORD dwMaximumWindowSize;
|
|
} CONSOLE_SCREEN_BUFFER_INFO;
|
|
|
|
#define MAX_PATH 260
|
|
#define TRUE 1
|
|
#define FALSE 0
|
|
|
|
#define INVALID_HANDLE_VALUE ((HANDLE)(intptr_t)-1)
|
|
|
|
#define MEM_COMMIT 0x00001000UL
|
|
#define MEM_RESERVE 0x00002000UL
|
|
#define MEM_RELEASE 0x00008000UL
|
|
#define MEM_FREE 0x00010000UL
|
|
#define MEM_PRIVATE 0x00020000UL
|
|
#define MEM_MAPPED 0x00040000UL
|
|
#define PAGE_NOACCESS 0x01UL
|
|
#define PAGE_READONLY 0x02UL
|
|
#define PAGE_READWRITE 0x04UL
|
|
#define PAGE_WRITECOPY 0x08UL
|
|
#define PAGE_EXECUTE 0x10UL
|
|
#define PAGE_EXECUTE_READ 0x20UL
|
|
#define PAGE_EXECUTE_READWRITE 0x40UL
|
|
#define SEC_COMMIT 0x08000000UL
|
|
|
|
#define FILE_MAP_COPY 0x00000001UL
|
|
#define FILE_MAP_WRITE 0x00000002UL
|
|
#define FILE_MAP_READ 0x00000004UL
|
|
#define FILE_MAP_EXECUTE 0x00000020UL
|
|
#define FILE_MAP_ALL_ACCESS 0x000F001FUL
|
|
|
|
typedef struct _MEMORY_BASIC_INFORMATION {
|
|
void* BaseAddress;
|
|
void* AllocationBase;
|
|
DWORD AllocationProtect;
|
|
size_t RegionSize;
|
|
DWORD State;
|
|
DWORD Protect;
|
|
DWORD Type;
|
|
} MEMORY_BASIC_INFORMATION;
|
|
|
|
// Implements the win Protect To Sys Vm operation.
|
|
static uint32_t WinProtectToSysVm(DWORD protect) {
|
|
switch (protect) {
|
|
case PAGE_NOACCESS: return SYS_VM_NOACCESS;
|
|
case PAGE_READONLY: return SYS_VM_READ;
|
|
case PAGE_READWRITE:
|
|
case PAGE_WRITECOPY: return SYS_VM_READWRITE;
|
|
case PAGE_EXECUTE: return SYS_VM_EXECUTE;
|
|
case PAGE_EXECUTE_READ: return SYS_VM_EXECUTE_READ;
|
|
case PAGE_EXECUTE_READWRITE: return SYS_VM_EXECUTE_READWRITE;
|
|
default: return SYS_VM_READWRITE;
|
|
}
|
|
}
|
|
|
|
static uint32_t WinFileMappingProtectToSysVm(DWORD protect) {
|
|
// Strip section attributes; the low byte carries PAGE_* protection.
|
|
DWORD pageProtect = protect & 0xFFu;
|
|
if (pageProtect == PAGE_WRITECOPY)
|
|
return SYS_VM_READ; // MapViewOfFile(FILE_MAP_COPY) asks for the private writable view.
|
|
return WinProtectToSysVm(pageProtect ? pageProtect : PAGE_READONLY);
|
|
}
|
|
|
|
static uint32_t WinFileMapAccessToSys(DWORD desiredAccess) {
|
|
uint32_t access = 0;
|
|
if (desiredAccess & FILE_MAP_EXECUTE) access |= SYS_MAP_ACCESS_EXECUTE;
|
|
if (desiredAccess & FILE_MAP_COPY) access |= SYS_MAP_ACCESS_COPY;
|
|
if (desiredAccess & FILE_MAP_WRITE) access |= SYS_MAP_ACCESS_WRITE;
|
|
if (desiredAccess & FILE_MAP_READ) access |= SYS_MAP_ACCESS_READ;
|
|
if ((desiredAccess & FILE_MAP_ALL_ACCESS) == FILE_MAP_ALL_ACCESS)
|
|
access |= SYS_MAP_ACCESS_READ | SYS_MAP_ACCESS_COPY;
|
|
if (!access) access = SYS_MAP_ACCESS_READ;
|
|
return access;
|
|
}
|
|
|
|
// Implements the protect Vm To Win operation.
|
|
static DWORD SysProtectVmToWin(uint32_t protect) {
|
|
switch (protect) {
|
|
case SYS_VM_NOACCESS: return PAGE_NOACCESS;
|
|
case SYS_VM_READ: return PAGE_READONLY;
|
|
case SYS_VM_READWRITE: return PAGE_READWRITE;
|
|
case SYS_VM_EXECUTE: return PAGE_EXECUTE;
|
|
case SYS_VM_EXECUTE_READ: return PAGE_EXECUTE_READ;
|
|
case SYS_VM_EXECUTE_READWRITE: return PAGE_EXECUTE_READWRITE;
|
|
default: return PAGE_NOACCESS;
|
|
}
|
|
}
|
|
|
|
// File-find structures.
|
|
typedef struct _FILETIME {
|
|
DWORD dwLowDateTime;
|
|
DWORD dwHighDateTime;
|
|
} FILETIME;
|
|
|
|
typedef struct _SYSTEMTIME {
|
|
unsigned short wYear;
|
|
unsigned short wMonth;
|
|
unsigned short wDayOfWeek;
|
|
unsigned short wDay;
|
|
unsigned short wHour;
|
|
unsigned short wMinute;
|
|
unsigned short wSecond;
|
|
unsigned short wMilliseconds;
|
|
} SYSTEMTIME;
|
|
|
|
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
|
|
|
|
// 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; }
|
|
|
|
static inline BOOL IS_VALID_KERNEL_HANDLE(HANDLE h) {
|
|
uintptr_t v = (uintptr_t)h;
|
|
return v != 0 && v != (uintptr_t)INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
// Path translator.
|
|
static void TranslatePath(const char* input, char* output) {
|
|
if (!output) return;
|
|
if (!input) { output[0] = '\0'; return; }
|
|
|
|
if (input[0] != '\0' && input[1] == ':' && (input[2] == '\\' || input[2] == '/')) {
|
|
// Translate "C:\" -> "0:/". Native nt-lite paths such as
|
|
// "0:/system/foo.dll" are already drive-indexed and pass through.
|
|
char drive = input[0];
|
|
if (drive >= 'a' && drive <= 'z') drive -= ('a' - 'A'); // To uppercase
|
|
|
|
output[0] = (drive >= '0' && drive <= '7') ? drive : (char)('0' + (drive - 'C'));
|
|
output[1] = ':';
|
|
output[2] = '/';
|
|
|
|
// Copy the rest, swapping backslashes to forward slashes.
|
|
const char* src = input + 3;
|
|
char* dst = output + 3;
|
|
while (*src) {
|
|
*dst = (*src == '\\') ? '/' : *src;
|
|
src++; dst++;
|
|
}
|
|
*dst = '\0';
|
|
} else {
|
|
// Fallback for relative paths: just swap slashes.
|
|
while (*input) {
|
|
*output = (*input == '\\') ? '/' : *input;
|
|
input++; output++;
|
|
}
|
|
*output = '\0';
|
|
}
|
|
}
|
|
|
|
// Implements the k32 Open File Native operation.
|
|
static uint32_t K32OpenFileNative(const char* path,
|
|
uint32_t access,
|
|
uint32_t disposition,
|
|
uint32_t* handleOut) {
|
|
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path),
|
|
access,
|
|
disposition,
|
|
SYS_FILE_FLAG_NONE,
|
|
handleOut);
|
|
return SysOpenFile(&req);
|
|
}
|
|
|
|
// Implements the k32 Delete File Native operation.
|
|
static uint32_t K32DeleteFileNative(const char* path) {
|
|
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
|
|
return SysDeleteFile(&req);
|
|
}
|
|
|
|
// Implements the k32 Open Dir Native operation.
|
|
static uint32_t K32OpenDirNative(const char* path, uint32_t* handleOut) {
|
|
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut };
|
|
return SysOpenDir(&req);
|
|
}
|
|
|
|
// Implements the k32 Set Cwd Native operation.
|
|
static uint32_t K32SetCwdNative(const char* path) {
|
|
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
|
|
return SysSetCwd(&req);
|
|
}
|
|
|
|
// Implements the k32 Get Cwd Native operation.
|
|
static uint32_t K32GetCwdNative(char* buffer, size_t capacity, size_t* lengthOut) {
|
|
SYS_STRING_VIEW cwd = SysGetCurrentDirectoryView();
|
|
if ((cwd.Length && !cwd.Data) || cwd.Length > SYS_CWD_MAX) return STATUS_INVALID_PARAMETER;
|
|
if (lengthOut) *lengthOut = cwd.Length;
|
|
if (!buffer || cwd.Length + 1 > capacity) return STATUS_BUFFER_TOO_SMALL;
|
|
|
|
if (cwd.Length) memcpy(buffer, cwd.Data, cwd.Length);
|
|
buffer[cwd.Length] = '\0';
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Implements the k32 Create Process Native operation.
|
|
static uint32_t K32CreateProcessNative(const char* path, uint32_t* handleOut, const char* commandLine) {
|
|
SYS_CREATE_PROCESS_REQUEST req = { SysSvFromCstr(path), SysSvFromCstr(commandLine), handleOut };
|
|
return SysCreateProcess(&req);
|
|
}
|
|
|
|
// Implements the k32 Write Console Span operation.
|
|
static void K32WriteConsoleSpan(const char* data, size_t length) {
|
|
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(data, length) };
|
|
SysWriteConsole(&req);
|
|
}
|
|
|
|
// Implements the k32 Debug Print A operation.
|
|
static void K32DebugPrintA(const char* s) {
|
|
if (!s) return;
|
|
SYS_PRINT_DEBUG_REQUEST req = { SysSvFromCstr(s) };
|
|
SysPrintDebug(&req);
|
|
}
|
|
|
|
#define WIN32_TLS_SLOTS 64
|
|
#define FLS_MAX_SLOTS 64
|
|
#define FLS_OUT_OF_INDEXES 0xFFFFFFFFUL
|
|
|
|
typedef struct WIN32_THREAD_ENV {
|
|
DWORD LastError;
|
|
DWORD Reserved0;
|
|
void* TlsSlots[WIN32_TLS_SLOTS];
|
|
void* FlsSlots[FLS_MAX_SLOTS];
|
|
} WIN32_THREAD_ENV;
|
|
|
|
static WIN32_THREAD_ENV gFallbackThreadEnv;
|
|
|
|
// Implements the k32 Thread Env operation.
|
|
static WIN32_THREAD_ENV* K32ThreadEnv(void) {
|
|
SYS_TEB* teb = SysGetTeb();
|
|
if (!teb || teb->Magic != SYS_TEB_MAGIC) return &gFallbackThreadEnv;
|
|
|
|
WIN32_THREAD_ENV* env = (WIN32_THREAD_ENV*)teb->SubsystemData;
|
|
if (!env) {
|
|
env = (WIN32_THREAD_ENV*)malloc(sizeof(WIN32_THREAD_ENV));
|
|
if (!env) return &gFallbackThreadEnv;
|
|
memset(env, 0, sizeof(*env));
|
|
teb->SubsystemData = env;
|
|
}
|
|
return env;
|
|
}
|
|
|
|
// Implements the k32 Last Error Slot operation.
|
|
static DWORD* K32LastErrorSlot(void) {
|
|
return &K32ThreadEnv()->LastError;
|
|
}
|
|
|
|
#define gLastError (*K32LastErrorSlot())
|
|
|
|
// Implements the file Exists A operation.
|
|
static BOOL KiFileExistsA(const char* path) {
|
|
if (!path || !*path) return FALSE;
|
|
char native[MAX_PATH];
|
|
TranslatePath(path, native);
|
|
uint32_t h = 0;
|
|
if (K32OpenFileNative(native,
|
|
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
|
|
SYS_FILE_OPEN_EXISTING,
|
|
&h) != 0) return FALSE;
|
|
SysCloseHandle(h);
|
|
return TRUE;
|
|
}
|
|
|
|
// Copies search Path Result.
|
|
static DWORD KiCopySearchPathResult(char* out, DWORD outChars, const char* text, char** filePart) {
|
|
DWORD len = (DWORD)strlen(text);
|
|
if (out && outChars) {
|
|
DWORD copy = (len < outChars - 1) ? len : outChars - 1;
|
|
memcpy(out, text, copy);
|
|
out[copy] = '\0';
|
|
if (filePart) {
|
|
char* slash1 = strrchr(out, '/');
|
|
char* slash2 = strrchr(out, '\\');
|
|
char* slash = slash1 > slash2 ? slash1 : slash2;
|
|
*filePart = slash ? slash + 1 : out;
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
__declspec(dllexport) DWORD SearchPathA(const char* lpPath, const char* lpFileName,
|
|
const char* lpExtension, DWORD nBufferLength,
|
|
char* lpBuffer, char** lpFilePart) {
|
|
if (!lpFileName || !*lpFileName) { gLastError = 87; return 0; }
|
|
|
|
char candidate[MAX_PATH];
|
|
size_t nameLen = strlen(lpFileName);
|
|
bool hasExt = (strrchr(lpFileName, '.') != NULL);
|
|
bool rooted = (lpFileName[0] == '/' || lpFileName[0] == '\\' ||
|
|
(lpFileName[0] && lpFileName[1] == ':'));
|
|
|
|
if (rooted || strchr(lpFileName, '/') || strchr(lpFileName, '\\')) {
|
|
strncpy(candidate, lpFileName, sizeof(candidate) - 1);
|
|
candidate[sizeof(candidate) - 1] = '\0';
|
|
if (!hasExt && lpExtension && nameLen + strlen(lpExtension) + 1 < sizeof(candidate))
|
|
strcat(candidate, lpExtension);
|
|
if (KiFileExistsA(candidate))
|
|
return KiCopySearchPathResult(lpBuffer, nBufferLength, candidate, lpFilePart);
|
|
}
|
|
|
|
const char* paths = lpPath && *lpPath ? lpPath : ".";
|
|
const char* p = paths;
|
|
while (*p) {
|
|
char dir[MAX_PATH];
|
|
size_t di = 0;
|
|
while (*p && *p != ';' && di + 1 < sizeof(dir)) dir[di++] = *p++;
|
|
while (*p == ';') p++;
|
|
dir[di] = '\0';
|
|
|
|
size_t pos = 0;
|
|
if (strcmp(dir, ".") != 0) {
|
|
while (dir[pos] && pos + 1 < sizeof(candidate)) { candidate[pos] = dir[pos]; pos++; }
|
|
if (pos && candidate[pos - 1] != '/' && candidate[pos - 1] != '\\' && pos + 1 < sizeof(candidate))
|
|
candidate[pos++] = '/';
|
|
}
|
|
for (size_t i = 0; lpFileName[i] && pos + 1 < sizeof(candidate); i++) candidate[pos++] = lpFileName[i];
|
|
if (!hasExt && lpExtension) {
|
|
for (size_t i = 0; lpExtension[i] && pos + 1 < sizeof(candidate); i++) candidate[pos++] = lpExtension[i];
|
|
}
|
|
candidate[pos] = '\0';
|
|
|
|
if (KiFileExistsA(candidate))
|
|
return KiCopySearchPathResult(lpBuffer, nBufferLength, candidate, lpFilePart);
|
|
}
|
|
|
|
gLastError = 2; // ERROR_FILE_NOT_FOUND
|
|
if (lpBuffer && nBufferLength) lpBuffer[0] = '\0';
|
|
if (lpFilePart) *lpFilePart = NULL;
|
|
return 0;
|
|
}
|
|
|
|
// GetLastError / SetLastError (single-threaded stub).
|
|
static SYS_PEB* gProcessPeb = NULL;
|
|
|
|
// Implements the peb operation.
|
|
static SYS_PEB* Win32Peb(void) {
|
|
if (!gProcessPeb) gProcessPeb = SysGetPeb();
|
|
return gProcessPeb;
|
|
}
|
|
|
|
// Returns last Error.
|
|
__declspec(dllexport) DWORD GetLastError(void) {
|
|
return gLastError;
|
|
}
|
|
// Updates last Error.
|
|
__declspec(dllexport) void SetLastError(DWORD err) {
|
|
gLastError = err;
|
|
}
|
|
|
|
// Process termination.
|
|
__declspec(dllexport) _Noreturn void ExitProcess(unsigned int code) {
|
|
SysExitProcess((int32_t)code);
|
|
while (true) ; // unreachable
|
|
}
|
|
|
|
// Debug output.
|
|
__declspec(dllexport) void OutputDebugStringA(const char* str) {
|
|
if (str) K32DebugPrintA(str);
|
|
}
|
|
|
|
// Queries the native monotonic tick frequency.
|
|
static uint64_t K32TickFrequency(void) {
|
|
uint64_t frequency = 0;
|
|
if (SysGetTickFrequency(&frequency) != STATUS_SUCCESS) return 0;
|
|
return frequency;
|
|
}
|
|
|
|
// Returns monotonic milliseconds since boot.
|
|
static uint64_t K32TickMilliseconds(void) {
|
|
uint64_t frequency = K32TickFrequency();
|
|
if (!frequency) return 0;
|
|
return SysTicksToMilliseconds(SysGetTick(), frequency);
|
|
}
|
|
|
|
// Timer.
|
|
__declspec(dllexport) DWORD GetTickCount(void) {
|
|
return (DWORD)K32TickMilliseconds();
|
|
}
|
|
|
|
// Returns tick Count64.
|
|
__declspec(dllexport) uint64_t GetTickCount64(void) {
|
|
return K32TickMilliseconds();
|
|
}
|
|
|
|
// Memory (heap argument is ignored - single user-mode heap).
|
|
#define HEAP_ZERO_MEMORY 0x00000008UL
|
|
#define HEAP_REALLOC_IN_PLACE_ONLY 0x00000010UL
|
|
|
|
__declspec(dllexport) void* HeapAlloc(void* heap, DWORD flags, size_t size) {
|
|
(void)heap;
|
|
if (!size) return NULL;
|
|
|
|
void* ptr = malloc(size);
|
|
if (!ptr) {
|
|
gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY
|
|
return NULL;
|
|
}
|
|
|
|
// Bink and the Microsoft CRT routinely allocate internal state with
|
|
// HEAP_ZERO_MEMORY. Ignoring this flag only works accidentally while the
|
|
// heap keeps receiving fresh demand-zero pages; under low memory / swap the
|
|
// allocator reuses dirty freed blocks and stale pitch/pointer fields can
|
|
// corrupt the decoder.
|
|
if (flags & HEAP_ZERO_MEMORY)
|
|
memset(ptr, 0, size);
|
|
|
|
return ptr;
|
|
}
|
|
|
|
// Implements the heap Free operation.
|
|
__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 user-mode heap carries the block size.
|
|
if (ptr) free(ptr);
|
|
return TRUE;
|
|
}
|
|
|
|
__declspec(dllexport) void* HeapReAlloc(void* heap, DWORD flags,
|
|
void* ptr, size_t size) {
|
|
(void)heap;
|
|
|
|
if (flags & HEAP_REALLOC_IN_PLACE_ONLY) {
|
|
// The user heap does not expose an in-place grow primitive. Preserve
|
|
// Windows semantics by failing instead of silently moving the block.
|
|
size_t oldSize = _malloc_usable_size(ptr);
|
|
if (oldSize != (size_t)-1 && oldSize >= size)
|
|
return ptr;
|
|
gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY
|
|
return NULL;
|
|
}
|
|
|
|
size_t oldSize = ptr ? _malloc_usable_size(ptr) : 0;
|
|
void* newptr = realloc(ptr, size);
|
|
if (!newptr && size) {
|
|
gLastError = 8;
|
|
return NULL;
|
|
}
|
|
|
|
if (newptr && (flags & HEAP_ZERO_MEMORY) && oldSize != (size_t)-1 && size > oldSize)
|
|
memset((unsigned char*)newptr + oldSize, 0, size - oldSize);
|
|
|
|
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;
|
|
}
|
|
|
|
// Virtual memory compatibility.
|
|
__declspec(dllexport) void* VirtualAlloc(void* lpAddress, size_t dwSize,
|
|
DWORD flAllocationType, DWORD flProtect) {
|
|
(void)flAllocationType;
|
|
if (!dwSize) return NULL;
|
|
|
|
size_t pages = (dwSize + 4095u) / 4096u;
|
|
void* base = NULL;
|
|
uint32_t flags = lpAddress ? SYS_VM_FLAG_FIXED : SYS_VM_FLAG_NONE;
|
|
uint32_t s = SysAllocVm(lpAddress, pages, WinProtectToSysVm(flProtect), flags, &base);
|
|
if (s != STATUS_SUCCESS) {
|
|
gLastError = (s == STATUS_NOT_FOUND || s == STATUS_INVALID_PARAMETER) ? 87 : 8;
|
|
return NULL;
|
|
}
|
|
return base;
|
|
}
|
|
|
|
// Implements the virtual Free operation.
|
|
__declspec(dllexport) BOOL VirtualFree(void* lpAddress, size_t dwSize, DWORD dwFreeType) {
|
|
(void)dwSize; (void)dwFreeType;
|
|
if (!lpAddress) return TRUE;
|
|
uint32_t s = SysFreeVm(lpAddress);
|
|
if (s != STATUS_SUCCESS) { gLastError = 87; return FALSE; }
|
|
return TRUE;
|
|
}
|
|
|
|
__declspec(dllexport) BOOL VirtualProtect(void* lpAddress, size_t dwSize,
|
|
DWORD flNewProtect, DWORD* lpflOldProtect) {
|
|
if (!lpAddress || !dwSize) { gLastError = 87; return FALSE; }
|
|
size_t pages = (dwSize + 4095u) / 4096u;
|
|
uint32_t oldSysProtect = 0;
|
|
uint32_t s = SysProtectVm(lpAddress, pages, WinProtectToSysVm(flNewProtect),
|
|
lpflOldProtect ? &oldSysProtect : NULL);
|
|
if (s != STATUS_SUCCESS) { gLastError = 87; return FALSE; }
|
|
if (lpflOldProtect) *lpflOldProtect = SysProtectVmToWin(oldSysProtect);
|
|
return TRUE;
|
|
}
|
|
|
|
__declspec(dllexport) size_t VirtualQuery(const void* lpAddress,
|
|
MEMORY_BASIC_INFORMATION* lpBuffer,
|
|
size_t dwLength) {
|
|
if (!lpAddress || !lpBuffer || dwLength < sizeof(MEMORY_BASIC_INFORMATION)) {
|
|
gLastError = 87; // ERROR_INVALID_PARAMETER
|
|
return 0;
|
|
}
|
|
|
|
SYS_VM_REGION_INFO info;
|
|
uint32_t s = SysQueryVm((void*)lpAddress, &info);
|
|
if (s != STATUS_SUCCESS) {
|
|
gLastError = 87;
|
|
return 0;
|
|
}
|
|
|
|
DWORD protect = SysProtectVmToWin(info.Protect);
|
|
lpBuffer->BaseAddress = info.Base;
|
|
lpBuffer->AllocationBase = info.Base;
|
|
lpBuffer->AllocationProtect = protect;
|
|
lpBuffer->RegionSize = info.PageCount * 4096u;
|
|
lpBuffer->State = MEM_COMMIT;
|
|
lpBuffer->Protect = protect;
|
|
lpBuffer->Type = (info.Type == 7u) ? MEM_MAPPED : MEM_PRIVATE; // 7 == MmVadMappedFile
|
|
return sizeof(MEMORY_BASIC_INFORMATION);
|
|
}
|
|
|
|
__declspec(dllexport) HANDLE CreateFileMappingA(HANDLE hFile,
|
|
void* lpFileMappingAttributes,
|
|
DWORD flProtect,
|
|
DWORD dwMaximumSizeHigh,
|
|
DWORD dwMaximumSizeLow,
|
|
const char* lpName) {
|
|
(void)lpFileMappingAttributes;
|
|
(void)lpName;
|
|
if (!hFile || hFile == INVALID_HANDLE_VALUE) {
|
|
gLastError = 6; // ERROR_INVALID_HANDLE
|
|
return NULL;
|
|
}
|
|
|
|
uint64_t maximumSize = ((uint64_t)dwMaximumSizeHigh << 32) | (uint64_t)dwMaximumSizeLow;
|
|
uint32_t kh = 0;
|
|
uint32_t s = SysCreateFileMapping(HANDLE_TO_KH(hFile),
|
|
WinFileMappingProtectToSysVm(flProtect),
|
|
SYS_MAP_FLAG_NONE,
|
|
maximumSize,
|
|
&kh);
|
|
if (s != STATUS_SUCCESS) {
|
|
gLastError = (s == STATUS_NOT_SUPPORTED) ? 120 : ((s == STATUS_NO_MEMORY) ? 8 : 87);
|
|
return NULL;
|
|
}
|
|
return KH_TO_HANDLE(kh);
|
|
}
|
|
|
|
__declspec(dllexport) HANDLE CreateFileMappingW(HANDLE hFile,
|
|
void* lpFileMappingAttributes,
|
|
DWORD flProtect,
|
|
DWORD dwMaximumSizeHigh,
|
|
DWORD dwMaximumSizeLow,
|
|
const unsigned short* lpName) {
|
|
(void)lpName;
|
|
return CreateFileMappingA(hFile, lpFileMappingAttributes, flProtect,
|
|
dwMaximumSizeHigh, dwMaximumSizeLow, NULL);
|
|
}
|
|
|
|
__declspec(dllexport) void* MapViewOfFile(HANDLE hFileMappingObject,
|
|
DWORD dwDesiredAccess,
|
|
DWORD dwFileOffsetHigh,
|
|
DWORD dwFileOffsetLow,
|
|
size_t dwNumberOfBytesToMap) {
|
|
if (!hFileMappingObject || hFileMappingObject == INVALID_HANDLE_VALUE) {
|
|
gLastError = 6;
|
|
return NULL;
|
|
}
|
|
|
|
uint64_t offset = ((uint64_t)dwFileOffsetHigh << 32) | (uint64_t)dwFileOffsetLow;
|
|
void* base = NULL;
|
|
uint32_t s = SysMapFile(HANDLE_TO_KH(hFileMappingObject),
|
|
offset,
|
|
dwNumberOfBytesToMap,
|
|
WinFileMapAccessToSys(dwDesiredAccess),
|
|
SYS_MAP_FLAG_NONE,
|
|
&base);
|
|
if (s != STATUS_SUCCESS) {
|
|
gLastError = (s == STATUS_NOT_SUPPORTED) ? 120 : ((s == STATUS_NO_MEMORY) ? 8 : 87);
|
|
return NULL;
|
|
}
|
|
return base;
|
|
}
|
|
|
|
__declspec(dllexport) BOOL UnmapViewOfFile(const void* lpBaseAddress) {
|
|
if (!lpBaseAddress) { gLastError = 87; return FALSE; }
|
|
uint32_t s = SysFreeVm((void*)lpBaseAddress);
|
|
if (s != STATUS_SUCCESS) { gLastError = 87; return FALSE; }
|
|
return TRUE;
|
|
}
|
|
|
|
__declspec(dllexport) BOOL FlushViewOfFile(const void* lpBaseAddress, size_t dwNumberOfBytesToFlush) {
|
|
(void)dwNumberOfBytesToFlush;
|
|
if (!lpBaseAddress) { gLastError = 87; return FALSE; }
|
|
// Native flush is file-handle based for now; unmap/close also harvests dirty
|
|
// PTEs, so FlushViewOfFile remains a successful cache-coherency hint.
|
|
return TRUE;
|
|
}
|
|
|
|
// --- Win32 API ---.
|
|
|
|
// Returns whether one input key is currently held for Win32 adapters.
|
|
static bool Win32IsInputKeyDown(INPUT_KEY key) {
|
|
bool down = false;
|
|
return SysIsKeyDown(key, &down) == STATUS_SUCCESS && down;
|
|
}
|
|
|
|
// GetAsyncKeyState implementation over the canonical input key namespace.
|
|
__declspec(dllexport) SHORT GetAsyncKeyState(int vKey) {
|
|
if (vKey == 0x11) { // VK_CONTROL
|
|
return (Win32IsInputKeyDown(INPUT_KEY_LEFT_CTRL) || Win32IsInputKeyDown(INPUT_KEY_RIGHT_CTRL)) ? (SHORT)0x8000 : 0;
|
|
}
|
|
if (vKey == 0x10) { // VK_SHIFT
|
|
return (Win32IsInputKeyDown(INPUT_KEY_LEFT_SHIFT) || Win32IsInputKeyDown(INPUT_KEY_RIGHT_SHIFT)) ? (SHORT)0x8000 : 0;
|
|
}
|
|
if (vKey == 0x12) { // VK_MENU / ALT
|
|
return (Win32IsInputKeyDown(INPUT_KEY_LEFT_ALT) || Win32IsInputKeyDown(INPUT_KEY_RIGHT_ALT)) ? (SHORT)0x8000 : 0;
|
|
}
|
|
|
|
static const struct { int vk; INPUT_KEY key; } vk_to_key[] = {
|
|
// Letters.
|
|
{ 0x41, INPUT_KEY_A }, { 0x42, INPUT_KEY_B }, { 0x43, INPUT_KEY_C },
|
|
{ 0x44, INPUT_KEY_D }, { 0x45, INPUT_KEY_E }, { 0x46, INPUT_KEY_F },
|
|
{ 0x47, INPUT_KEY_G }, { 0x48, INPUT_KEY_H }, { 0x49, INPUT_KEY_I },
|
|
{ 0x4A, INPUT_KEY_J }, { 0x4B, INPUT_KEY_K }, { 0x4C, INPUT_KEY_L },
|
|
{ 0x4D, INPUT_KEY_M }, { 0x4E, INPUT_KEY_N }, { 0x4F, INPUT_KEY_O },
|
|
{ 0x50, INPUT_KEY_P }, { 0x51, INPUT_KEY_Q }, { 0x52, INPUT_KEY_R },
|
|
{ 0x53, INPUT_KEY_S }, { 0x54, INPUT_KEY_T }, { 0x55, INPUT_KEY_U },
|
|
{ 0x56, INPUT_KEY_V }, { 0x57, INPUT_KEY_W }, { 0x58, INPUT_KEY_X },
|
|
{ 0x59, INPUT_KEY_Y }, { 0x5A, INPUT_KEY_Z },
|
|
|
|
// Numbers.
|
|
{ 0x31, INPUT_KEY_1 }, { 0x32, INPUT_KEY_2 }, { 0x33, INPUT_KEY_3 },
|
|
{ 0x34, INPUT_KEY_4 }, { 0x35, INPUT_KEY_5 }, { 0x36, INPUT_KEY_6 },
|
|
{ 0x37, INPUT_KEY_7 }, { 0x38, INPUT_KEY_8 }, { 0x39, INPUT_KEY_9 },
|
|
{ 0x30, INPUT_KEY_0 },
|
|
|
|
// Arrows.
|
|
{ 0x26, INPUT_KEY_UP }, { 0x28, INPUT_KEY_DOWN },
|
|
{ 0x25, INPUT_KEY_LEFT }, { 0x27, INPUT_KEY_RIGHT },
|
|
|
|
// Controls.
|
|
{ 0x1B, INPUT_KEY_ESCAPE }, { 0x0D, INPUT_KEY_ENTER },
|
|
{ 0x20, INPUT_KEY_SPACE },
|
|
{ 0x08, INPUT_KEY_BACKSPACE }, { 0x09, INPUT_KEY_TAB },
|
|
|
|
// Function Keys.
|
|
{ 0x70, INPUT_KEY_F1 }, { 0x71, INPUT_KEY_F2 }, { 0x72, INPUT_KEY_F3 },
|
|
{ 0x73, INPUT_KEY_F4 }, { 0x74, INPUT_KEY_F5 }, { 0x75, INPUT_KEY_F6 },
|
|
{ 0x76, INPUT_KEY_F7 }, { 0x77, INPUT_KEY_F8 }, { 0x78, INPUT_KEY_F9 },
|
|
{ 0x79, INPUT_KEY_F10 }, { 0x7A, INPUT_KEY_F11 }, { 0x7B, INPUT_KEY_F12 },
|
|
|
|
// Symbols.
|
|
{ 0xBD, INPUT_KEY_MINUS }, { 0xBB, INPUT_KEY_EQUAL },
|
|
{ 0xBC, INPUT_KEY_COMMA }, { 0xBE, INPUT_KEY_PERIOD },
|
|
};
|
|
|
|
for (size_t i = 0; i < sizeof(vk_to_key) / sizeof(vk_to_key[0]); ++i) {
|
|
if (vk_to_key[i].vk == vKey) {
|
|
if (Win32IsInputKeyDown(vk_to_key[i].key))
|
|
return (SHORT)0x8000;
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// FlushConsoleInputBuffer: clear the keyboard input buffer.
|
|
__declspec(dllexport) BOOL FlushConsoleInputBuffer(HANDLE hConsoleInput) {
|
|
(void)hConsoleInput;
|
|
// Drain the keyboard buffer by reading until empty.
|
|
INPUT_KEY key;
|
|
while (SysPollKeyboard(&key) == STATUS_SUCCESS) {
|
|
char ignored;
|
|
(void)SysReadKeyboardChar(&ignored);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// !Made-up! clear console api.
|
|
__declspec(dllexport) BOOL ClearConsoleScreenBuffer(HANDLE hConsole) {
|
|
SysClearScreen(0x012456);
|
|
return TRUE;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Returns aCP.
|
|
__declspec(dllexport) DWORD GetACP(void) { return 1252; } // Windows-1252
|
|
// Returns oEMCP.
|
|
__declspec(dllexport) DWORD GetOEMCP(void) { return 437; } // DOS Latin US
|
|
|
|
// Checks whether dBCS Lead Byte Ex.
|
|
__declspec(dllexport) BOOL IsDBCSLeadByteEx(unsigned int CodePage, BYTE TestChar) {
|
|
(void)CodePage; (void)TestChar;
|
|
return FALSE;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// Translate the path from Windows format to custom 0:/ format.
|
|
char translatedName[260];
|
|
TranslatePath(name, translatedName);
|
|
|
|
uint32_t sysAccess = SYS_FILE_ACCESS_QUERY;
|
|
if (access & GENERIC_READ)
|
|
sysAccess |= SYS_FILE_ACCESS_READ;
|
|
if (access & GENERIC_WRITE)
|
|
sysAccess |= SYS_FILE_ACCESS_WRITE;
|
|
|
|
uint32_t sysDisposition;
|
|
switch (disposition) {
|
|
case OPEN_EXISTING: sysDisposition = SYS_FILE_OPEN_EXISTING; break;
|
|
case OPEN_ALWAYS: sysDisposition = SYS_FILE_OPEN_ALWAYS; break;
|
|
case CREATE_ALWAYS: sysDisposition = SYS_FILE_CREATE_ALWAYS; break;
|
|
case CREATE_NEW: sysDisposition = SYS_FILE_CREATE_NEW; break;
|
|
case TRUNCATE_EXISTING: sysDisposition = SYS_FILE_TRUNCATE_EXISTING; break;
|
|
default:
|
|
gLastError = 87;
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
uint32_t kh = 0;
|
|
uint32_t s = K32OpenFileNative(translatedName, sysAccess, sysDisposition, &kh);
|
|
if (s != 0) {
|
|
gLastError = (s == STATUS_OBJECT_NAME_COLLISION) ? 80 :
|
|
(s == STATUS_NOT_FOUND) ? 2 : 5;
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
return KH_TO_HANDLE(kh);
|
|
}
|
|
|
|
// 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 || hFile == INVALID_HANDLE_VALUE) { gLastError = 6; return 0xFFFFFFFFUL; }
|
|
uint64_t sz = 0;
|
|
uint32_t s = SysGetFileSize(HANDLE_TO_KH(hFile), &sz);
|
|
if (s != 0) { gLastError = 6; return 0xFFFFFFFFUL; }
|
|
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 (bytesRead) *bytesRead = 0;
|
|
if (!hFile || hFile == INVALID_HANDLE_VALUE || !buf) {
|
|
gLastError = 6;
|
|
return FALSE;
|
|
}
|
|
|
|
size_t got = 0;
|
|
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(HANDLE_TO_KH(hFile), buf, (size_t)toRead, &got);
|
|
uint32_t s = SysReadFile(&req);
|
|
if (bytesRead) *bytesRead = (DWORD)got;
|
|
|
|
if (s != 0) {
|
|
gLastError = 1;
|
|
return FALSE;
|
|
}
|
|
|
|
// Harden callers that accidentally look past *bytesRead. EOF/short reads
|
|
// are legal for files, but do not touch the caller's remaining buffer after
|
|
// a failed read because that can fault in megabytes under low memory.
|
|
if (got < (size_t)toRead)
|
|
memset((unsigned char*)buf + got, 0, (size_t)toRead - got);
|
|
|
|
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 || hFile == INVALID_HANDLE_VALUE || !buf) { gLastError = 6; return FALSE; }
|
|
size_t written = 0;
|
|
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(HANDLE_TO_KH(hFile), buf, (size_t)toWrite, &written);
|
|
uint32_t s = SysWriteFile(&req);
|
|
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 || hFile == INVALID_HANDLE_VALUE) {
|
|
gLastError = 6;
|
|
return 0xFFFFFFFFUL;
|
|
}
|
|
|
|
uint32_t kh = HANDLE_TO_KH(hFile);
|
|
int64_t delta;
|
|
if (distHigh) {
|
|
uint64_t combined = ((uint64_t)(uint32_t)(*distHigh) << 32) | (uint32_t)dist;
|
|
delta = (int64_t)combined;
|
|
} else {
|
|
delta = (int64_t)dist;
|
|
}
|
|
|
|
int64_t target = 0;
|
|
if (method == FILE_BEGIN) {
|
|
target = delta;
|
|
} else if (method == FILE_CURRENT) {
|
|
uint64_t cur = 0;
|
|
uint32_t s = SysGetFilePos(kh, &cur);
|
|
if (s != 0 || cur > 0x7FFFFFFFFFFFFFFFULL) { gLastError = 6; return 0xFFFFFFFFUL; }
|
|
target = (int64_t)cur + delta;
|
|
} else if (method == FILE_END) {
|
|
uint64_t sz = 0;
|
|
uint32_t s = SysGetFileSize(kh, &sz);
|
|
if (s != 0 || sz > 0x7FFFFFFFFFFFFFFFULL) { gLastError = 6; return 0xFFFFFFFFUL; }
|
|
target = (int64_t)sz + delta;
|
|
} else {
|
|
gLastError = 87; return 0xFFFFFFFFUL;
|
|
}
|
|
|
|
if (target < 0) {
|
|
gLastError = 131; // ERROR_NEGATIVE_SEEK
|
|
return 0xFFFFFFFFUL;
|
|
}
|
|
|
|
uint64_t offset = (uint64_t)target;
|
|
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 || hFile == INVALID_HANDLE_VALUE) { gLastError = 6; return FALSE; }
|
|
uint32_t kh = HANDLE_TO_KH(hFile);
|
|
uint64_t pos = 0;
|
|
uint32_t s = SysGetFilePos(kh, &pos);
|
|
if (s != 0) { gLastError = 6; return FALSE; }
|
|
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; }
|
|
|
|
char translatedPath[MAX_PATH];
|
|
TranslatePath(lpFileName, translatedPath);
|
|
|
|
uint32_t s = K32DeleteFileNative(translatedPath);
|
|
if (s != 0) { gLastError = 2; return FALSE; }
|
|
return TRUE;
|
|
}
|
|
|
|
// CloseHandle - unified close for files, threads, mutexes, semaphores, events, queues, and processes.
|
|
__declspec(dllexport) BOOL CloseHandle(HANDLE hObject) {
|
|
if (!hObject || hObject == INVALID_HANDLE_VALUE) { 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
|
|
}
|
|
|
|
// Implements the heap Destroy operation.
|
|
__declspec(dllexport) BOOL HeapDestroy(HANDLE hHeap) {
|
|
(void)hHeap;
|
|
return TRUE;
|
|
}
|
|
|
|
// Implements the heap Size operation.
|
|
__declspec(dllexport) size_t HeapSize(HANDLE heap, DWORD flags, const void* ptr) {
|
|
(void)heap; (void)flags;
|
|
size_t size = _malloc_usable_size(ptr);
|
|
if (size == (size_t)-1)
|
|
gLastError = 87; // ERROR_INVALID_PARAMETER
|
|
return size;
|
|
}
|
|
|
|
__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);
|
|
}
|
|
|
|
// Returns current Thread.
|
|
__declspec(dllexport) HANDLE GetCurrentThread(void) {
|
|
return (HANDLE)(uintptr_t)(~(uintptr_t)1);
|
|
}
|
|
|
|
// Returns current Thread Id.
|
|
__declspec(dllexport) DWORD GetCurrentThreadId(void) { return (DWORD)SysGetCurrentTid(); }
|
|
// Returns current Process Id.
|
|
__declspec(dllexport) DWORD GetCurrentProcessId(void) { return (DWORD)SysGetCurrentPid(); }
|
|
|
|
// Tiny TLS index allocator; slot values are stored per thread in WIN32_THREAD_ENV.
|
|
static uint64_t gTlsBitmap = 1; // reserve slot 0
|
|
|
|
// Implements the tls Alloc operation.
|
|
__declspec(dllexport) DWORD TlsAlloc(void) {
|
|
for (DWORD i = 1; i < WIN32_TLS_SLOTS; i++) {
|
|
uint64_t bit = 1ULL << i;
|
|
if (!(gTlsBitmap & bit)) { gTlsBitmap |= bit; return i; }
|
|
}
|
|
gLastError = 8;
|
|
return 0xFFFFFFFFUL;
|
|
}
|
|
|
|
// Implements the tls Free operation.
|
|
__declspec(dllexport) BOOL TlsFree(DWORD dwTlsIndex) {
|
|
if (dwTlsIndex >= WIN32_TLS_SLOTS || !(gTlsBitmap & (1ULL << dwTlsIndex))) { gLastError = 87; return FALSE; }
|
|
WIN32_THREAD_ENV* env = K32ThreadEnv();
|
|
if (env) env->TlsSlots[dwTlsIndex] = NULL;
|
|
gTlsBitmap &= ~(1ULL << dwTlsIndex);
|
|
return TRUE;
|
|
}
|
|
|
|
// Implements the tls Get Value operation.
|
|
__declspec(dllexport) void* TlsGetValue(DWORD dwTlsIndex) {
|
|
if (dwTlsIndex >= WIN32_TLS_SLOTS || !(gTlsBitmap & (1ULL << dwTlsIndex))) { gLastError = 87; return NULL; }
|
|
WIN32_THREAD_ENV* env = K32ThreadEnv();
|
|
gLastError = 0;
|
|
return env->TlsSlots[dwTlsIndex];
|
|
}
|
|
|
|
// Implements the tls Set Value operation.
|
|
__declspec(dllexport) BOOL TlsSetValue(DWORD dwTlsIndex, void* lpTlsValue) {
|
|
if (dwTlsIndex >= WIN32_TLS_SLOTS || !(gTlsBitmap & (1ULL << dwTlsIndex))) { gLastError = 87; return FALSE; }
|
|
WIN32_THREAD_ENV* env = K32ThreadEnv();
|
|
env->TlsSlots[dwTlsIndex] = lpTlsValue;
|
|
return TRUE;
|
|
}
|
|
|
|
// Pseudo HMODULEs for callers that probe already-linked personality DLLs.
|
|
#define WIN32_HMODULE_EXE ((HANDLE)(uintptr_t)0x00400000UL)
|
|
#define WIN32_HMODULE_WIN32 ((HANDLE)(uintptr_t)0x57333232UL) // W322
|
|
|
|
// Implements the stricmp A operation.
|
|
static int KiStricmpA(const char* a, const char* b) {
|
|
while (*a && (((unsigned char)*a | 0x20) == ((unsigned char)*b | 0x20))) { a++; b++; }
|
|
return ((unsigned char)*a | 0x20) - ((unsigned char)*b | 0x20);
|
|
}
|
|
|
|
// Checks whether win32 Dll Name A.
|
|
static BOOL KiIsWin32DllNameA(const char* name) {
|
|
return name && (
|
|
!KiStricmpA(name, "kernel32.dll") || !KiStricmpA(name, "user32.dll") ||
|
|
!KiStricmpA(name, "gdi32.dll") || !KiStricmpA(name, "ole32.dll") ||
|
|
!KiStricmpA(name, "winmm.dll") || !KiStricmpA(name, "win32.dll"));
|
|
}
|
|
|
|
// Implements the dbg Write A operation.
|
|
static void KiDbgWriteA(const char* s) {
|
|
if (s) K32WriteConsoleSpan(s, strlen(s));
|
|
}
|
|
|
|
// Returns module Handle A.
|
|
__declspec(dllexport) HANDLE GetModuleHandleA(const char* lpModuleName) {
|
|
if (!lpModuleName) return WIN32_HMODULE_EXE;
|
|
if (KiIsWin32DllNameA(lpModuleName)) return WIN32_HMODULE_WIN32;
|
|
gLastError = 126; // ERROR_MOD_NOT_FOUND
|
|
return NULL;
|
|
}
|
|
|
|
// Returns module Handle W.
|
|
__declspec(dllexport) HANDLE GetModuleHandleW(const unsigned short* lpModuleName) {
|
|
return lpModuleName ? NULL : WIN32_HMODULE_EXE;
|
|
}
|
|
|
|
// Loads a user-mode DLL through the native module loader.
|
|
__declspec(dllexport) HANDLE LoadLibraryA(const char* lpLibFileName) {
|
|
if (KiIsWin32DllNameA(lpLibFileName)) return WIN32_HMODULE_WIN32;
|
|
if (!lpLibFileName) { gLastError = 87; return NULL; }
|
|
|
|
uint32_t handle = 0;
|
|
SYS_LOAD_MODULE_REQUEST req;
|
|
memset(&req, 0, sizeof(req));
|
|
req.Size = sizeof(req);
|
|
req.Path = SysSvFromCstr(lpLibFileName);
|
|
req.ModuleHandleOut = &handle;
|
|
|
|
uint32_t status = SysLoadModule(&req);
|
|
if (status != STATUS_SUCCESS) {
|
|
gLastError = 126;
|
|
return NULL;
|
|
}
|
|
return (HANDLE)(uintptr_t)handle;
|
|
}
|
|
|
|
// GetProcAddress handles both built-in win32 exports and native module handles.
|
|
__declspec(dllexport) BOOL FreeLibrary(HANDLE hLibModule);
|
|
__declspec(dllexport) void Sleep(DWORD dwMilliseconds);
|
|
__declspec(dllexport) BOOL GetConsoleScreenBufferInfo(HANDLE hConsoleOutput,
|
|
CONSOLE_SCREEN_BUFFER_INFO* lpConsoleScreenBufferInfo);
|
|
__declspec(dllexport) BOOL FillConsoleOutputCharacterA(HANDLE hConsoleOutput,
|
|
char cCharacter,
|
|
DWORD nLength,
|
|
COORD dwWriteCoord,
|
|
DWORD* lpNumberOfCharsWritten);
|
|
__declspec(dllexport) BOOL FillConsoleOutputAttribute(HANDLE hConsoleOutput,
|
|
WORD wAttribute,
|
|
DWORD nLength,
|
|
COORD dwWriteCoord,
|
|
DWORD* lpNumberOfAttrsWritten);
|
|
__declspec(dllexport) BOOL SetConsoleCursorPosition(HANDLE hConsoleOutput,
|
|
COORD dwCursorPosition);
|
|
// Returns proc Address.
|
|
__declspec(dllexport) void* GetProcAddress(HANDLE hModule, const char* lpProcName) {
|
|
if (!hModule || !lpProcName) { gLastError = 127; return NULL; }
|
|
|
|
// Ordinal probes are represented as small integer pointers. Do not treat
|
|
// them as strings while debugging misses.
|
|
if ((uintptr_t)lpProcName < 0x10000) {
|
|
KiDbgWriteA("GetProcAddress ordinal miss\n");
|
|
gLastError = 127;
|
|
return NULL;
|
|
}
|
|
|
|
if (hModule != WIN32_HMODULE_WIN32) {
|
|
uintptr_t address = 0;
|
|
SYS_GET_MODULE_EXPORT_REQUEST req;
|
|
memset(&req, 0, sizeof(req));
|
|
req.Size = sizeof(req);
|
|
req.ModuleHandle = (uint32_t)(uintptr_t)hModule;
|
|
req.Name = SysSvFromCstr(lpProcName);
|
|
req.AddressOut = &address;
|
|
uint32_t status = SysGetModuleExport(&req);
|
|
if (status != STATUS_SUCCESS || !address) { gLastError = 127; return NULL; }
|
|
return (void*)address;
|
|
}
|
|
|
|
#define GP(name) if (strcmp(lpProcName, #name) == 0) return (void*)&name
|
|
GP(GetLastError); GP(SetLastError); GP(LoadLibraryA); GP(GetProcAddress);
|
|
GP(FreeLibrary); GP(VirtualAlloc); GP(VirtualFree); GP(VirtualProtect);
|
|
GP(VirtualQuery); GP(GetTickCount); GP(GetTickCount64); GP(Sleep);
|
|
GP(TlsAlloc); GP(TlsFree); GP(TlsGetValue); GP(TlsSetValue);
|
|
GP(GetConsoleScreenBufferInfo); GP(FillConsoleOutputCharacterA);
|
|
GP(FillConsoleOutputAttribute); GP(SetConsoleCursorPosition);
|
|
#undef GP
|
|
KiDbgWriteA("GetProcAddress miss: ");
|
|
KiDbgWriteA(lpProcName);
|
|
KiDbgWriteA("\n");
|
|
gLastError = 127; // ERROR_PROC_NOT_FOUND
|
|
return NULL;
|
|
}
|
|
|
|
// Releases one native module handle returned by LoadLibraryA.
|
|
__declspec(dllexport) BOOL FreeLibrary(HANDLE hLibModule) {
|
|
if (!hLibModule || hLibModule == WIN32_HMODULE_WIN32 || hLibModule == WIN32_HMODULE_EXE)
|
|
return TRUE;
|
|
uint32_t status = SysUnloadModule((uint32_t)(uintptr_t)hLibModule);
|
|
if (status != STATUS_SUCCESS) {
|
|
gLastError = 126;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
__declspec(dllexport) DWORD GetModuleFileNameA(HANDLE hModule,
|
|
char* lpFilename, DWORD nSize) {
|
|
(void)hModule;
|
|
if (!lpFilename || !nSize) return 0;
|
|
|
|
SYS_PEB* peb = Win32Peb();
|
|
const char* src = (peb && peb->Magic == SYS_PEB_MAGIC && peb->ImagePath.Data)
|
|
? peb->ImagePath.Data
|
|
: "";
|
|
|
|
DWORD len = (DWORD)strlen(src);
|
|
DWORD copy = (len < nSize - 1) ? len : nSize - 1;
|
|
memcpy(lpFilename, src, copy);
|
|
lpFilename[copy] = '\0';
|
|
if (copy < len) gLastError = 122; // ERROR_INSUFFICIENT_BUFFER
|
|
return copy;
|
|
}
|
|
|
|
// Implements the terminate Process operation.
|
|
__declspec(dllexport) BOOL TerminateProcess(HANDLE hProcess, unsigned int uExitCode) {
|
|
if (hProcess == GetCurrentProcess()) {
|
|
SysExitProcess((int32_t)uExitCode);
|
|
while (true) ;
|
|
}
|
|
|
|
if (!hProcess) {
|
|
gLastError = 6; // ERROR_INVALID_HANDLE
|
|
return FALSE;
|
|
}
|
|
|
|
uint32_t s = SysKillProcess(HANDLE_TO_KH(hProcess), (int32_t)uExitCode);
|
|
if (s != 0) {
|
|
gLastError = (s == STATUS_INVALID_HANDLE) ? 6 : 5;
|
|
return FALSE;
|
|
}
|
|
return 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;
|
|
|
|
// Returns startup Info A.
|
|
__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);
|
|
}
|
|
|
|
// Returns command Line A.
|
|
__declspec(dllexport) char* GetCommandLineA(void) {
|
|
SYS_PEB* peb = Win32Peb();
|
|
if (!peb || peb->Magic != SYS_PEB_MAGIC || !peb->CommandLine.Data) return "";
|
|
return (char*)peb->CommandLine.Data;
|
|
}
|
|
|
|
// 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 kh = 0;
|
|
SYS_CREATE_THREAD_REQUEST req = {
|
|
(uintptr_t)lpStartAddress,
|
|
(uintptr_t)lpParameter,
|
|
0,
|
|
0,
|
|
&kh,
|
|
&tid
|
|
};
|
|
uint32_t s = SysCreateThread(&req);
|
|
if (s != 0) { gLastError = 11; return INVALID_HANDLE_VALUE; }
|
|
if (lpThreadId) *lpThreadId = (DWORD)tid;
|
|
return KH_TO_HANDLE(kh);
|
|
}
|
|
|
|
// WaitForSingleObject - blocks on native waitable handles. A zero timeout is
|
|
// only a probe for mutexes/semaphores until native timeout waits exist.
|
|
__declspec(dllexport) DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) {
|
|
if (!IS_VALID_KERNEL_HANDLE(hHandle)) { gLastError = 6; return 0xFFFFFFFFUL; }
|
|
|
|
uint32_t kh = HANDLE_TO_KH(hHandle);
|
|
if (dwMilliseconds == 0) {
|
|
uint32_t s = SysTryAcquireMutex(kh);
|
|
if (s == 0) return WAIT_OBJECT_0;
|
|
|
|
s = SysTryWaitSem(kh);
|
|
if (s == 0) return WAIT_OBJECT_0;
|
|
|
|
return WAIT_TIMEOUT;
|
|
}
|
|
|
|
uint32_t s = SysWait(kh, SYS_WAIT_FLAG_NONE);
|
|
if (s == 0) return WAIT_OBJECT_0;
|
|
|
|
gLastError = 6;
|
|
return 0xFFFFFFFFUL;
|
|
}
|
|
|
|
// Implements the extract Application Path A operation.
|
|
static BOOL ExtractApplicationPathA(const char* commandLine, char* out, size_t outSize) {
|
|
if (!commandLine || !out || outSize == 0) return FALSE;
|
|
|
|
while (*commandLine == ' ' || *commandLine == '\t') commandLine++;
|
|
if (*commandLine == '\0') return FALSE;
|
|
|
|
size_t pos = 0;
|
|
if (*commandLine == '"') {
|
|
commandLine++;
|
|
while (*commandLine && *commandLine != '"' && pos + 1 < outSize)
|
|
out[pos++] = *commandLine++;
|
|
} else {
|
|
while (*commandLine && *commandLine != ' ' && *commandLine != '\t' && pos + 1 < outSize)
|
|
out[pos++] = *commandLine++;
|
|
}
|
|
|
|
out[pos] = '\0';
|
|
return pos != 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)lpProcessAttributes; (void)lpThreadAttributes;
|
|
(void)bInheritHandles; (void)dwCreationFlags; (void)lpEnvironment;
|
|
(void)lpStartupInfo;
|
|
|
|
// lpProcessInformation is mandatory.
|
|
if (!lpProcessInformation) {
|
|
gLastError = 87; // ERROR_INVALID_PARAMETER
|
|
return FALSE;
|
|
}
|
|
|
|
char appPath[MAX_PATH];
|
|
const char* targetPath = lpApplicationName;
|
|
if (!targetPath) {
|
|
if (!ExtractApplicationPathA(lpCommandLine, appPath, sizeof(appPath))) {
|
|
gLastError = 87;
|
|
return FALSE;
|
|
}
|
|
targetPath = appPath;
|
|
}
|
|
|
|
const char* cmdline = (lpCommandLine && lpCommandLine[0]) ? lpCommandLine : targetPath;
|
|
|
|
char translatedPath[MAX_PATH];
|
|
TranslatePath(targetPath, translatedPath);
|
|
|
|
char savedCwd[SYS_CWD_MAX];
|
|
bool changedCwd = false;
|
|
savedCwd[0] = '\0';
|
|
|
|
if (lpCurrentDirectory && lpCurrentDirectory[0]) {
|
|
char translatedCwd[MAX_PATH];
|
|
uint32_t gs = K32GetCwdNative(savedCwd, sizeof(savedCwd), NULL);
|
|
if (gs != 0) { gLastError = 3; return FALSE; }
|
|
|
|
TranslatePath(lpCurrentDirectory, translatedCwd);
|
|
gs = K32SetCwdNative(translatedCwd);
|
|
if (gs != 0) { gLastError = 3; return FALSE; }
|
|
changedCwd = true;
|
|
}
|
|
|
|
uint32_t kh = 0;
|
|
// Call the syscall.dll stub. The kernel snapshots the current CWD into
|
|
// the child process, so the temporary lpCurrentDirectory switch can be
|
|
// restored immediately after creation.
|
|
uint32_t s = K32CreateProcessNative(translatedPath, &kh, cmdline);
|
|
|
|
if (changedCwd) {
|
|
if (savedCwd[0]) {
|
|
char restoreCwd[SYS_CWD_MAX + 3];
|
|
restoreCwd[0] = '0'; restoreCwd[1] = ':'; restoreCwd[2] = '/';
|
|
size_t rlen = strlen(savedCwd);
|
|
if (rlen + 4 > sizeof(restoreCwd)) rlen = sizeof(restoreCwd) - 4;
|
|
memcpy(restoreCwd + 3, savedCwd, rlen);
|
|
restoreCwd[3 + rlen] = '\0';
|
|
K32SetCwdNative(restoreCwd);
|
|
} else {
|
|
K32SetCwdNative("0:/");
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Returns exit Code Process.
|
|
__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
|
|
int32_t code = 0;
|
|
uint32_t s = SysGetProcessExitCode(kh, &code);
|
|
if (s != 0) { gLastError = 6; return FALSE; }
|
|
*lpExitCode = (DWORD)code;
|
|
return TRUE;
|
|
}
|
|
|
|
// Implements the resume Thread operation.
|
|
__declspec(dllexport) DWORD ResumeThread(HANDLE hThread) {
|
|
(void)hThread;
|
|
return 0; // previous suspend count
|
|
}
|
|
|
|
#define THREAD_PRIORITY_NORMAL 0
|
|
|
|
// Updates thread Priority.
|
|
__declspec(dllexport) BOOL SetThreadPriority(HANDLE hThread, int nPriority) {
|
|
(void)hThread; (void)nPriority;
|
|
return TRUE;
|
|
}
|
|
|
|
// Returns thread Priority.
|
|
__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
|
|
}
|
|
|
|
// Implements the sleep operation.
|
|
__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)
|
|
|
|
// Implements the k32 Is Std Handle Value operation.
|
|
static BOOL K32IsStdHandleValue(HANDLE h, DWORD stdHandle) {
|
|
uintptr_t value = (uintptr_t)h;
|
|
return value == (uintptr_t)stdHandle || value == (uintptr_t)(intptr_t)(int32_t)stdHandle;
|
|
}
|
|
|
|
// Implements the k32 Is Console Output Handle operation.
|
|
static BOOL K32IsConsoleOutputHandle(HANDLE h) {
|
|
return h && h != INVALID_HANDLE_VALUE &&
|
|
(K32IsStdHandleValue(h, STD_OUTPUT_HANDLE) ||
|
|
K32IsStdHandleValue(h, STD_ERROR_HANDLE));
|
|
}
|
|
|
|
// Implements the k32 Set Console Last Error operation.
|
|
static void K32SetConsoleLastError(uint32_t status) {
|
|
switch (status) {
|
|
case STATUS_SUCCESS:
|
|
gLastError = 0;
|
|
break;
|
|
case STATUS_INVALID_PARAMETER:
|
|
gLastError = 87;
|
|
break;
|
|
case STATUS_NO_MEMORY:
|
|
gLastError = 8;
|
|
break;
|
|
default:
|
|
gLastError = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 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 SysReadKeyboardChar 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 = 0;
|
|
if (SysReadKeyboardChar(&c) != STATUS_SUCCESS) continue;
|
|
|
|
// 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;
|
|
}
|
|
K32WriteConsoleSpan((const char*)lpBuffer, (size_t)nNumberOfCharsToWrite);
|
|
if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = nNumberOfCharsToWrite;
|
|
return TRUE;
|
|
}
|
|
|
|
__declspec(dllexport) BOOL GetConsoleScreenBufferInfo(HANDLE hConsoleOutput,
|
|
CONSOLE_SCREEN_BUFFER_INFO* lpConsoleScreenBufferInfo) {
|
|
if (!K32IsConsoleOutputHandle(hConsoleOutput)) {
|
|
gLastError = 6;
|
|
return FALSE;
|
|
}
|
|
if (!lpConsoleScreenBufferInfo) {
|
|
gLastError = 87;
|
|
return FALSE;
|
|
}
|
|
|
|
SYS_CONSOLE_INFO info;
|
|
uint32_t s = SysGetConsoleInfo(&info);
|
|
if (s != STATUS_SUCCESS) {
|
|
K32SetConsoleLastError(s);
|
|
return FALSE;
|
|
}
|
|
|
|
memset(lpConsoleScreenBufferInfo, 0, sizeof(*lpConsoleScreenBufferInfo));
|
|
lpConsoleScreenBufferInfo->dwSize.X = (SHORT)info.Columns;
|
|
lpConsoleScreenBufferInfo->dwSize.Y = (SHORT)info.Rows;
|
|
lpConsoleScreenBufferInfo->dwCursorPosition.X = (SHORT)info.CursorX;
|
|
lpConsoleScreenBufferInfo->dwCursorPosition.Y = (SHORT)info.CursorY;
|
|
lpConsoleScreenBufferInfo->wAttributes = (WORD)info.Attribute;
|
|
lpConsoleScreenBufferInfo->srWindow.Left = 0;
|
|
lpConsoleScreenBufferInfo->srWindow.Top = 0;
|
|
lpConsoleScreenBufferInfo->srWindow.Right = info.Columns ? (SHORT)(info.Columns - 1u) : 0;
|
|
lpConsoleScreenBufferInfo->srWindow.Bottom = info.Rows ? (SHORT)(info.Rows - 1u) : 0;
|
|
lpConsoleScreenBufferInfo->dwMaximumWindowSize = lpConsoleScreenBufferInfo->dwSize;
|
|
return TRUE;
|
|
}
|
|
|
|
__declspec(dllexport) BOOL SetConsoleCursorPosition(HANDLE hConsoleOutput,
|
|
COORD dwCursorPosition) {
|
|
if (!K32IsConsoleOutputHandle(hConsoleOutput)) {
|
|
gLastError = 6;
|
|
return FALSE;
|
|
}
|
|
if (dwCursorPosition.X < 0 || dwCursorPosition.Y < 0) {
|
|
gLastError = 87;
|
|
return FALSE;
|
|
}
|
|
|
|
uint32_t s = SysSetConsoleCursor((int)dwCursorPosition.X, (int)dwCursorPosition.Y);
|
|
if (s != STATUS_SUCCESS) {
|
|
K32SetConsoleLastError(s);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
__declspec(dllexport) BOOL FillConsoleOutputCharacterA(HANDLE hConsoleOutput,
|
|
char cCharacter,
|
|
DWORD nLength,
|
|
COORD dwWriteCoord,
|
|
DWORD* lpNumberOfCharsWritten) {
|
|
if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = 0;
|
|
if (!K32IsConsoleOutputHandle(hConsoleOutput)) {
|
|
gLastError = 6;
|
|
return FALSE;
|
|
}
|
|
if (dwWriteCoord.X < 0 || dwWriteCoord.Y < 0) {
|
|
gLastError = 87;
|
|
return FALSE;
|
|
}
|
|
if (nLength == 0)
|
|
return TRUE;
|
|
|
|
size_t written = 0;
|
|
uint32_t s = SysFillConsoleCharacter(cCharacter, (int)dwWriteCoord.X,
|
|
(int)dwWriteCoord.Y, (size_t)nLength,
|
|
&written);
|
|
if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = (DWORD)written;
|
|
if (s != STATUS_SUCCESS) {
|
|
K32SetConsoleLastError(s);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
__declspec(dllexport) BOOL FillConsoleOutputAttribute(HANDLE hConsoleOutput,
|
|
WORD wAttribute,
|
|
DWORD nLength,
|
|
COORD dwWriteCoord,
|
|
DWORD* lpNumberOfAttrsWritten) {
|
|
if (lpNumberOfAttrsWritten) *lpNumberOfAttrsWritten = 0;
|
|
if (!K32IsConsoleOutputHandle(hConsoleOutput)) {
|
|
gLastError = 6;
|
|
return FALSE;
|
|
}
|
|
if (dwWriteCoord.X < 0 || dwWriteCoord.Y < 0) {
|
|
gLastError = 87;
|
|
return FALSE;
|
|
}
|
|
if (nLength == 0)
|
|
return TRUE;
|
|
|
|
size_t written = 0;
|
|
uint32_t s = SysFillConsoleAttribute(wAttribute, (int)dwWriteCoord.X,
|
|
(int)dwWriteCoord.Y, (size_t)nLength,
|
|
&written);
|
|
if (lpNumberOfAttrsWritten) *lpNumberOfAttrsWritten = (DWORD)written;
|
|
if (s != STATUS_SUCCESS) {
|
|
K32SetConsoleLastError(s);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// Directory enumeration (FindFirstFileA / FindNextFileA / FindClose).
|
|
|
|
typedef SYS_DIR_ENTRY 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
|
|
|
|
// Implements the k32 Dir Is Leap Year operation.
|
|
static int K32DirIsLeapYear(unsigned int year) {
|
|
return ((year % 4u) == 0u && (year % 100u) != 0u) || ((year % 400u) == 0u);
|
|
}
|
|
|
|
// Implements the k32 Dir Days In Month operation.
|
|
static unsigned int K32DirDaysInMonth(unsigned int year, unsigned int month) {
|
|
static const unsigned char days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
|
|
if (month < 1u || month > 12u) return 31u;
|
|
if (month == 2u && K32DirIsLeapYear(year)) return 29u;
|
|
return days[month - 1u];
|
|
}
|
|
|
|
// Implements the k32 Dir Calendar To Epoch Seconds operation.
|
|
static uint64_t K32DirCalendarToEpochSeconds(const SYS_SYSTEM_TIME* st) {
|
|
uint64_t days = 0;
|
|
for (unsigned int y = 1970u; y < st->Year; y++)
|
|
days += K32DirIsLeapYear(y) ? 366u : 365u;
|
|
for (unsigned int m = 1u; m < st->Month; m++)
|
|
days += K32DirDaysInMonth(st->Year, m);
|
|
days += (uint64_t)(st->Day - 1u);
|
|
return (((days * 24u) + st->Hour) * 60u + st->Minute) * 60u + st->Second;
|
|
}
|
|
|
|
// Implements the k32 Dir Store File Time operation.
|
|
static void K32DirStoreFileTime(FILETIME* ft, const SYS_SYSTEM_TIME* st) {
|
|
if (!ft || !st || !(st->Flags & SYS_TIME_FLAG_VALID) || st->Year < 1970u) {
|
|
if (ft) { ft->dwLowDateTime = 0; ft->dwHighDateTime = 0; }
|
|
return;
|
|
}
|
|
const uint64_t WINDOWS_EPOCH_BIAS_SECONDS = 11644473600ULL;
|
|
uint64_t epoch = st->EpochSeconds ? st->EpochSeconds : K32DirCalendarToEpochSeconds(st);
|
|
uint64_t value = (epoch + WINDOWS_EPOCH_BIAS_SECONDS) * 10000000ULL +
|
|
((uint64_t)st->Millisecond * 10000ULL) +
|
|
((uint64_t)st->Nanosecond / 100ULL);
|
|
ft->dwLowDateTime = (DWORD)(value & 0xFFFFFFFFULL);
|
|
ft->dwHighDateTime = (DWORD)(value >> 32);
|
|
}
|
|
|
|
// Implements the kdir To Find Data operation.
|
|
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;
|
|
K32DirStoreFileTime(&fd->ftCreationTime, &ke->CreateTime);
|
|
K32DirStoreFileTime(&fd->ftLastAccessTime, &ke->LastAccessTime);
|
|
K32DirStoreFileTime(&fd->ftLastWriteTime, &ke->LastWriteTime);
|
|
|
|
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;
|
|
}
|
|
|
|
// Translate the path.
|
|
char translatedInput[MAX_PATH];
|
|
TranslatePath(lpFileName, translatedInput);
|
|
|
|
// Split path into directory and wildcard pattern.
|
|
char dir[MAX_PATH];
|
|
char pat[MAX_PATH];
|
|
SplitFindPath(translatedInput, dir, sizeof(dir), pat, sizeof(pat));
|
|
|
|
// Open the kernel directory iterator.
|
|
uint32_t kh = 0;
|
|
uint32_t s = K32OpenDirNative(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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Returns close.
|
|
__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; }
|
|
|
|
char translatedPath[MAX_PATH];
|
|
TranslatePath(lpPathName, translatedPath);
|
|
|
|
uint32_t s = K32SetCwdNative(translatedPath);
|
|
if (s != 0) { gLastError = 2; return FALSE; }
|
|
return TRUE;
|
|
}
|
|
|
|
// Returns current Directory A.
|
|
__declspec(dllexport) DWORD GetCurrentDirectoryA(DWORD nBufferLength, char* lpBuffer) {
|
|
if (!lpBuffer || nBufferLength == 0) {
|
|
// Return a conservative required size including NUL.
|
|
return SYS_CWD_MAX + 1;
|
|
}
|
|
|
|
// Kernel returns native "N:/path". Present it as a Win32 drive letter.
|
|
char raw[SYS_CWD_MAX + 1];
|
|
uint32_t s = K32GetCwdNative(raw, sizeof(raw), NULL);
|
|
if (s != 0) { gLastError = 1; return 0; }
|
|
|
|
char full[SYS_CWD_MAX + 1];
|
|
char drive = 'C';
|
|
const char* rel = raw;
|
|
if (raw[0] >= '0' && raw[0] <= '7' && raw[1] == ':' &&
|
|
(raw[2] == '\\' || raw[2] == '/')) {
|
|
drive = (char)('C' + (raw[0] - '0'));
|
|
rel = raw + 3;
|
|
}
|
|
|
|
full[0] = drive;
|
|
full[1] = ':';
|
|
full[2] = '\\';
|
|
size_t rawLen = strlen(rel);
|
|
if (rawLen + 4 > sizeof(full)) rawLen = sizeof(full) - 4;
|
|
memcpy(full + 3, rel, rawLen);
|
|
full[3 + rawLen] = '\0';
|
|
for (char* p = full + 3; *p; p++) if (*p == '/') *p = '\\';
|
|
|
|
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)lpName;
|
|
uint32_t kh = 0;
|
|
uint32_t s = SysCreateMutex(&kh);
|
|
if (s != 0 || !kh) { gLastError = 8; return NULL; }
|
|
if (bInitialOwner) {
|
|
s = SysWait(kh, SYS_WAIT_FLAG_NONE);
|
|
if (s != 0) {
|
|
SysCloseHandle(kh);
|
|
gLastError = 1;
|
|
return NULL;
|
|
}
|
|
}
|
|
return KH_TO_HANDLE(kh);
|
|
}
|
|
|
|
// Releases mutex.
|
|
__declspec(dllexport) BOOL ReleaseMutex(HANDLE hMutex) {
|
|
if (!IS_VALID_KERNEL_HANDLE(hMutex)) { gLastError = 6; return FALSE; }
|
|
uint32_t s = SysReleaseMutex(HANDLE_TO_KH(hMutex));
|
|
if (s != 0) { gLastError = 6; return FALSE; }
|
|
return TRUE;
|
|
}
|
|
|
|
__declspec(dllexport) HANDLE CreateSemaphoreA(void* lpSemaphoreAttributes,
|
|
long lInitialCount, long lMaximumCount,
|
|
const char* lpName) {
|
|
(void)lpSemaphoreAttributes; (void)lpName;
|
|
uint32_t kh = 0;
|
|
uint32_t s = SysCreateSem((int32_t)lInitialCount, (int32_t)lMaximumCount, &kh);
|
|
if (s != 0 || !kh) { gLastError = 8; return NULL; }
|
|
return KH_TO_HANDLE(kh);
|
|
}
|
|
|
|
__declspec(dllexport) BOOL ReleaseSemaphore(HANDLE hSemaphore, long lReleaseCount,
|
|
long* lpPreviousCount) {
|
|
if (!IS_VALID_KERNEL_HANDLE(hSemaphore) || lReleaseCount < 0) { gLastError = 6; return FALSE; }
|
|
if (lpPreviousCount) *lpPreviousCount = 0;
|
|
for (long i = 0; i < lReleaseCount; ++i) {
|
|
uint32_t s = SysSignalSem(HANDLE_TO_KH(hSemaphore));
|
|
if (s != 0) { gLastError = 6; return FALSE; }
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// Critical Sections & Condition Variables.
|
|
// Match the size of Win64 RTL_CRITICAL_SECTION (5 pointer-sized fields) while
|
|
// using the storage as a small recursive spin lock. Bink uses these from its
|
|
// worker/copy paths, so the old no-op implementation allowed internal state
|
|
// races as soon as CreateThread started working.
|
|
typedef struct { uintptr_t _opaque[5]; } CRITICAL_SECTION;
|
|
typedef struct { void* _ptr; } CONDITION_VARIABLE;
|
|
|
|
#define K32_CS_LOCK 0
|
|
#define K32_CS_OWNER 1
|
|
#define K32_CS_RECURSION 2
|
|
|
|
static volatile long* K32CsLockWord(CRITICAL_SECTION* cs) {
|
|
return (volatile long*)&cs->_opaque[K32_CS_LOCK];
|
|
}
|
|
|
|
static DWORD K32CurrentTidOrOne(void) {
|
|
DWORD tid = (DWORD)SysGetCurrentTid();
|
|
return tid ? tid : 1;
|
|
}
|
|
|
|
// Initializes critical Section.
|
|
__declspec(dllexport) void InitializeCriticalSection(CRITICAL_SECTION* lpCS) {
|
|
if (!lpCS) return;
|
|
memset(lpCS, 0, sizeof(*lpCS));
|
|
}
|
|
__declspec(dllexport) BOOL InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION* lpCS,
|
|
DWORD dwSpinCount) {
|
|
(void)dwSpinCount;
|
|
InitializeCriticalSection(lpCS);
|
|
return TRUE;
|
|
}
|
|
// Releases critical Section.
|
|
__declspec(dllexport) void DeleteCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; }
|
|
// Implements the enter Critical Section operation.
|
|
__declspec(dllexport) void EnterCriticalSection(CRITICAL_SECTION* lpCS) {
|
|
if (!lpCS) return;
|
|
DWORD tid = K32CurrentTidOrOne();
|
|
if ((DWORD)lpCS->_opaque[K32_CS_OWNER] == tid) {
|
|
lpCS->_opaque[K32_CS_RECURSION]++;
|
|
return;
|
|
}
|
|
while (_InterlockedCompareExchange(K32CsLockWord(lpCS), 1, 0) != 0)
|
|
_mm_pause();
|
|
lpCS->_opaque[K32_CS_OWNER] = tid;
|
|
lpCS->_opaque[K32_CS_RECURSION] = 1;
|
|
}
|
|
// Implements the leave Critical Section operation.
|
|
__declspec(dllexport) void LeaveCriticalSection(CRITICAL_SECTION* lpCS) {
|
|
if (!lpCS) return;
|
|
DWORD tid = K32CurrentTidOrOne();
|
|
if ((DWORD)lpCS->_opaque[K32_CS_OWNER] != tid || lpCS->_opaque[K32_CS_RECURSION] == 0)
|
|
return;
|
|
lpCS->_opaque[K32_CS_RECURSION]--;
|
|
if (lpCS->_opaque[K32_CS_RECURSION] == 0) {
|
|
lpCS->_opaque[K32_CS_OWNER] = 0;
|
|
_ReadWriteBarrier();
|
|
_InterlockedExchange(K32CsLockWord(lpCS), 0);
|
|
}
|
|
}
|
|
// Implements the try Enter Critical Section operation.
|
|
__declspec(dllexport) BOOL TryEnterCriticalSection(CRITICAL_SECTION* lpCS) {
|
|
if (!lpCS) return FALSE;
|
|
DWORD tid = K32CurrentTidOrOne();
|
|
if ((DWORD)lpCS->_opaque[K32_CS_OWNER] == tid) {
|
|
lpCS->_opaque[K32_CS_RECURSION]++;
|
|
return TRUE;
|
|
}
|
|
if (_InterlockedCompareExchange(K32CsLockWord(lpCS), 1, 0) != 0)
|
|
return FALSE;
|
|
lpCS->_opaque[K32_CS_OWNER] = tid;
|
|
lpCS->_opaque[K32_CS_RECURSION] = 1;
|
|
return TRUE;
|
|
}
|
|
|
|
// Initializes condition Variable.
|
|
__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;
|
|
}
|
|
// Implements the wake Condition Variable operation.
|
|
__declspec(dllexport) void WakeConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; }
|
|
// Implements the wake All Condition Variable operation.
|
|
__declspec(dllexport) void WakeAllConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; }
|
|
|
|
// Timing & Performance Counters.
|
|
|
|
typedef union {
|
|
struct { DWORD LowPart; long HighPart; } u;
|
|
long long QuadPart;
|
|
} LARGE_INTEGER;
|
|
|
|
static volatile long long gTscFreq = 0;
|
|
|
|
// Attempt to read TSC frequency from CPUID leaf 0x15 (Modern Intel/AMD).
|
|
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];
|
|
unsigned numer = (unsigned)info[1];
|
|
unsigned crystal_hz = (unsigned)info[2];
|
|
|
|
if (!denom || !numer || !crystal_hz) return 0;
|
|
return (long long)crystal_hz * numer / denom;
|
|
}
|
|
|
|
// Calibrates TSC against the native monotonic tick source.
|
|
static long long TscFreqCalibrate(void) {
|
|
uint64_t frequency = K32TickFrequency();
|
|
if (!frequency) return 0;
|
|
|
|
uint64_t calibrationTicks = frequency / 10ull;
|
|
if (!calibrationTicks) calibrationTicks = 1;
|
|
|
|
uint64_t t0 = SysGetTick();
|
|
uint64_t tsc0 = __rdtsc();
|
|
|
|
while ((SysGetTick() - t0) < calibrationTicks) {
|
|
_mm_pause();
|
|
}
|
|
|
|
uint64_t t1 = SysGetTick();
|
|
uint64_t tsc1 = __rdtsc();
|
|
|
|
uint64_t delta_tsc = tsc1 - tsc0;
|
|
uint64_t delta_us = SysTicksToMicroseconds(t1 - t0, frequency);
|
|
if (!delta_us) return 0;
|
|
|
|
return (long long)(delta_tsc * 1000000ULL / delta_us);
|
|
}
|
|
|
|
// Implements the tsc Freq Get operation.
|
|
static long long TscFreqGet(void) {
|
|
if (gTscFreq) return gTscFreq;
|
|
|
|
long long f = TscFreqFromCpuid();
|
|
if (!f) f = TscFreqCalibrate();
|
|
|
|
gTscFreq = f;
|
|
return f;
|
|
}
|
|
|
|
// Returns performance Frequency.
|
|
__declspec(dllexport) BOOL QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency) {
|
|
if (lpFrequency) lpFrequency->QuadPart = TscFreqGet();
|
|
return TRUE;
|
|
}
|
|
|
|
// Returns performance Counter.
|
|
__declspec(dllexport) BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount) {
|
|
// Ensure calibration happens before the first read.
|
|
TscFreqGet();
|
|
if (lpPerformanceCount)
|
|
lpPerformanceCount->QuadPart = (long long)__rdtsc();
|
|
return TRUE;
|
|
}
|
|
|
|
// Implements the k32 Is Leap Year operation.
|
|
static BOOL K32IsLeapYear(unsigned int year) {
|
|
return ((year % 4u) == 0u && (year % 100u) != 0u) || ((year % 400u) == 0u);
|
|
}
|
|
|
|
// Implements the k32 Days In Month operation.
|
|
static unsigned int K32DaysInMonth(unsigned int year, unsigned int month) {
|
|
static const unsigned char days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
|
|
if (month == 2u && K32IsLeapYear(year)) return 29u;
|
|
if (month < 1u || month > 12u) return 0u;
|
|
return days[month - 1u];
|
|
}
|
|
|
|
// Implements the k32 Calendar To Epoch Seconds operation.
|
|
static uint64_t K32CalendarToEpochSeconds(const SYSTEMTIME* st) {
|
|
uint64_t days = 0;
|
|
for (unsigned int y = 1970u; y < st->wYear; y++)
|
|
days += K32IsLeapYear(y) ? 366u : 365u;
|
|
for (unsigned int m = 1u; m < st->wMonth; m++)
|
|
days += K32DaysInMonth(st->wYear, m);
|
|
days += (uint64_t)(st->wDay - 1u);
|
|
return (((days * 24u) + st->wHour) * 60u + st->wMinute) * 60u + st->wSecond;
|
|
}
|
|
|
|
// Implements the k32 Epoch To File Time operation.
|
|
static uint64_t K32EpochToFileTime(uint64_t epochSeconds, uint32_t nanosecond) {
|
|
const uint64_t WINDOWS_EPOCH_BIAS_SECONDS = 11644473600ULL;
|
|
return (epochSeconds + WINDOWS_EPOCH_BIAS_SECONDS) * 10000000ULL +
|
|
((uint64_t)nanosecond / 100ULL);
|
|
}
|
|
|
|
// Implements the k32 Store File Time operation.
|
|
static void K32StoreFileTime(FILETIME* ft, uint64_t value) {
|
|
ft->dwLowDateTime = (DWORD)(value & 0xFFFFFFFFULL);
|
|
ft->dwHighDateTime = (DWORD)(value >> 32);
|
|
}
|
|
|
|
// Returns system Time.
|
|
__declspec(dllexport) void GetSystemTime(SYSTEMTIME* lpSystemTime) {
|
|
if (!lpSystemTime) return;
|
|
|
|
SYS_SYSTEM_TIME st;
|
|
if (SysGetSystemTime(&st) != STATUS_SUCCESS) {
|
|
memset(lpSystemTime, 0, sizeof(*lpSystemTime));
|
|
return;
|
|
}
|
|
|
|
lpSystemTime->wYear = st.Year;
|
|
lpSystemTime->wMonth = st.Month;
|
|
lpSystemTime->wDayOfWeek = st.DayOfWeek;
|
|
lpSystemTime->wDay = st.Day;
|
|
lpSystemTime->wHour = st.Hour;
|
|
lpSystemTime->wMinute = st.Minute;
|
|
lpSystemTime->wSecond = st.Second;
|
|
lpSystemTime->wMilliseconds = st.Millisecond;
|
|
}
|
|
|
|
__declspec(dllexport) BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime,
|
|
FILETIME* lpFileTime) {
|
|
if (!lpSystemTime || !lpFileTime) return FALSE;
|
|
if (lpSystemTime->wYear < 1970 || lpSystemTime->wMonth < 1 ||
|
|
lpSystemTime->wMonth > 12 || lpSystemTime->wDay < 1 ||
|
|
lpSystemTime->wDay > K32DaysInMonth(lpSystemTime->wYear, lpSystemTime->wMonth) ||
|
|
lpSystemTime->wHour > 23 || lpSystemTime->wMinute > 59 ||
|
|
lpSystemTime->wSecond > 59 || lpSystemTime->wMilliseconds > 999) {
|
|
return FALSE;
|
|
}
|
|
|
|
uint64_t ft = K32EpochToFileTime(K32CalendarToEpochSeconds(lpSystemTime),
|
|
(uint32_t)lpSystemTime->wMilliseconds * 1000000u);
|
|
K32StoreFileTime(lpFileTime, ft);
|
|
return TRUE;
|
|
}
|
|
|
|
// Returns system Time As File Time.
|
|
__declspec(dllexport) void GetSystemTimeAsFileTime(void* lpSystemTimeAsFileTime) {
|
|
if (!lpSystemTimeAsFileTime) return;
|
|
|
|
SYS_SYSTEM_TIME st;
|
|
uint64_t ft;
|
|
if (SysGetSystemTime(&st) == STATUS_SUCCESS) {
|
|
ft = K32EpochToFileTime(st.EpochSeconds, st.Nanosecond);
|
|
} else {
|
|
uint64_t frequency = K32TickFrequency();
|
|
const uint64_t FALLBACK_2024_01_01_FT = 133485408000000000ULL;
|
|
ft = FALLBACK_2024_01_01_FT + SysTicksToMicroseconds(SysGetTick(), frequency) * 10ULL;
|
|
}
|
|
memcpy(lpSystemTimeAsFileTime, &ft, sizeof(ft));
|
|
}
|
|
|
|
// 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;
|
|
|
|
// Returns 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
|
|
|
|
// Returns system Directory A.
|
|
__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
|
|
}
|
|
|
|
// Returns windows Directory A.
|
|
__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;
|
|
}
|
|
|
|
// Returns static Env A.
|
|
static const char* LookupStaticEnvA(const char* name) {
|
|
if (!name) return NULL;
|
|
if (strcmp(name, "HOME") == 0) return ".";
|
|
if (strcmp(name, "USERPROFILE") == 0) return ".";
|
|
if (strcmp(name, "HOMEDRIVE") == 0) return "C:";
|
|
if (strcmp(name, "HOMEPATH") == 0) return "\\";
|
|
return NULL;
|
|
}
|
|
|
|
__declspec(dllexport) DWORD GetEnvironmentVariableA(const char* lpName, char* lpBuffer,
|
|
DWORD nSize) {
|
|
const char* value = LookupStaticEnvA(lpName);
|
|
if (!value) {
|
|
if (lpBuffer && nSize) lpBuffer[0] = '\0';
|
|
gLastError = 203; // ERROR_ENVVAR_NOT_FOUND
|
|
return 0;
|
|
}
|
|
|
|
DWORD len = (DWORD)strlen(value);
|
|
if (!lpBuffer || nSize == 0) return len;
|
|
if (len + 1 > nSize) {
|
|
if (nSize) lpBuffer[0] = '\0';
|
|
gLastError = 122; // ERROR_INSUFFICIENT_BUFFER
|
|
return len + 1;
|
|
}
|
|
|
|
memcpy(lpBuffer, value, len + 1);
|
|
return len;
|
|
}
|
|
|
|
// Returns environment Strings.
|
|
__declspec(dllexport) char* GetEnvironmentStrings(void) {
|
|
// Double-NUL-terminated environment block.
|
|
static char env[] = "HOME=.\0USERPROFILE=.\0HOMEDRIVE=C:\0HOMEPATH=\\\0\0";
|
|
return env;
|
|
}
|
|
|
|
// Releases environment Strings A.
|
|
__declspec(dllexport) BOOL FreeEnvironmentStringsA(char* penv) {
|
|
(void)penv; return TRUE;
|
|
}
|
|
|
|
// Returns environment Strings W.
|
|
__declspec(dllexport) unsigned short* GetEnvironmentStringsW(void) {
|
|
static unsigned short emptyEnvW[2] = { 0, 0 };
|
|
return emptyEnvW;
|
|
}
|
|
|
|
// Releases environment Strings W.
|
|
__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
|
|
}
|
|
|
|
// Updates handle Count.
|
|
__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)
|
|
|
|
// Returns file Type.
|
|
__declspec(dllexport) DWORD GetFileType(HANDLE hFile) {
|
|
if (!hFile || hFile == INVALID_HANDLE_VALUE) return FILE_TYPE_UNKNOWN;
|
|
if (K32IsStdHandleValue(hFile, STD_INPUT_HANDLE) ||
|
|
K32IsStdHandleValue(hFile, STD_OUTPUT_HANDLE) ||
|
|
K32IsStdHandleValue(hFile, STD_ERROR_HANDLE))
|
|
return FILE_TYPE_CHAR;
|
|
return FILE_TYPE_DISK;
|
|
}
|
|
|
|
// Exception Handling & Debugging.
|
|
|
|
#define EXCEPTION_CONTINUE_SEARCH 0
|
|
#define EXCEPTION_EXECUTE_HANDLER 1
|
|
|
|
// Implements the add Vectored Exception Handler operation.
|
|
__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'
|
|
}
|
|
|
|
// Releases vectored Exception Handler.
|
|
__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;
|
|
|
|
// Implements the unhandled Exception Filter operation.
|
|
__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
|
|
// Updates unhandled Exception Filter.
|
|
SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpFilter) {
|
|
(void)lpFilter;
|
|
return NULL; // previous filter: none
|
|
}
|
|
|
|
// Checks whether debugger Present.
|
|
__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;
|
|
|
|
__declspec(dllexport) BOOL RtlAddFunctionTable(RUNTIME_FUNCTION* FunctionTable,
|
|
DWORD EntryCount,
|
|
unsigned long long BaseAddress) {
|
|
(void)FunctionTable; (void)EntryCount; (void)BaseAddress;
|
|
return TRUE;
|
|
}
|
|
|
|
// Implements the rtl Delete Function Table operation.
|
|
__declspec(dllexport) BOOL RtlDeleteFunctionTable(RUNTIME_FUNCTION* FunctionTable) {
|
|
(void)FunctionTable;
|
|
return TRUE;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Implements the rtl Capture Context operation.
|
|
__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;
|
|
|
|
// Returns cP Info.
|
|
__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;
|
|
}
|
|
|
|
// Checks whether valid Code Page.
|
|
__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).
|
|
// Allocation state is process-global, but the values themselves are per-thread,
|
|
// matching the contract that Bink and the CRT expect when worker threads are
|
|
// enabled. The old single global value array let unrelated threads overwrite
|
|
// each other's decoder-local state.
|
|
|
|
static int gFlsUsed[FLS_MAX_SLOTS]; // 0 = free, 1 = allocated
|
|
|
|
// Implements the fls Alloc operation.
|
|
__declspec(dllexport) DWORD FlsAlloc(void* lpCallback) {
|
|
(void)lpCallback;
|
|
for (int i = 0; i < FLS_MAX_SLOTS; i++) {
|
|
if (!gFlsUsed[i]) {
|
|
gFlsUsed[i] = 1;
|
|
K32ThreadEnv()->FlsSlots[i] = NULL;
|
|
return (DWORD)i;
|
|
}
|
|
}
|
|
gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY
|
|
return (DWORD)FLS_OUT_OF_INDEXES;
|
|
}
|
|
|
|
// Implements the fls Get Value operation.
|
|
__declspec(dllexport) void* FlsGetValue(DWORD dwFlsIndex) {
|
|
if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) {
|
|
gLastError = 87; // ERROR_INVALID_PARAMETER
|
|
return NULL;
|
|
}
|
|
return K32ThreadEnv()->FlsSlots[dwFlsIndex];
|
|
}
|
|
|
|
// Implements the fls Set Value operation.
|
|
__declspec(dllexport) BOOL FlsSetValue(DWORD dwFlsIndex, void* lpFlsData) {
|
|
if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) {
|
|
gLastError = 87;
|
|
return FALSE;
|
|
}
|
|
K32ThreadEnv()->FlsSlots[dwFlsIndex] = lpFlsData;
|
|
return TRUE;
|
|
}
|
|
|
|
// Implements the fls Free operation.
|
|
__declspec(dllexport) BOOL FlsFree(DWORD dwFlsIndex) {
|
|
if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) {
|
|
gLastError = 87;
|
|
return FALSE;
|
|
}
|
|
gFlsUsed[dwFlsIndex] = 0;
|
|
K32ThreadEnv()->FlsSlots[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; }
|
|
// Implements the decode Pointer operation.
|
|
__declspec(dllexport) void* DecodePointer(void* Ptr) { return Ptr; }
|