From f5fd7bdd750a548b45893f70195e97e8dab42577 Mon Sep 17 00:00:00 2001 From: Maksymilian Wilczynski Date: Mon, 18 May 2026 21:30:07 +0200 Subject: [PATCH] doom is now using the native api, add a PEB + TEB to replace the dummy and the awkward argument passing to processes, all allocations are zeroed because i had enough, small readme changes and dedication. --- OVMF/OVMF_VARS_4M_d.fd | Bin 540672 -> 540672 bytes build.bat | 2 +- code/doom/doom.c | 338 +-- code/drivers/fat32/fat32.c | 2395 +++++++++++----------- code/init/init.c | 5 +- code/kernel/io_file.c | 867 ++++---- code/kernel/ke_debug.c | 4 +- code/kernel/ke_debug.h | 2 +- code/kernel/ke_process.c | 609 +++--- code/kernel/ke_process.h | 169 +- code/kernel/ke_switch.asm | 278 +-- code/kernel/ke_syscall.c | 2662 ++++++++++++------------ code/kernel/ke_thread.c | 537 ++--- code/kernel/ke_thread.h | 203 +- code/kernel/ke_userenv.h | 35 + code/kernel/kmain.c | 43 +- code/kernel/mm_virt.c | 2 + code/posix/posix.h | 2 + code/posix/posix_io.c | 36 +- code/posix/posix_proc.c | 14 + code/posix/posix_thread.c | 24 +- code/shell_native/shell_native.c | 35 +- code/shell_posix/shell_posix.c | 54 +- code/shell_win32/shell_win32.c | 327 +-- code/syscall/syscall.h | 50 + code/video/video.c | 44 +- code/win32/kernel32.c | 3300 ++++++++++++++++-------------- readme.md | 19 +- 28 files changed, 6334 insertions(+), 5722 deletions(-) create mode 100644 code/kernel/ke_userenv.h diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 0c3327597853165fe94dcc5afc6bddd237f76ca6..2bf364d429384b3e57f2ed4d34ec7d9449621fdc 100644 GIT binary patch delta 195 zcmZo@P;6*W+)y`VvR{zX<^xkMaczE=yNi|a_vVQ~-2#k%Ak<$7^$$Y*2UAQ8ESu$$ zy9AgRp)?beW`@!%P?{A=vq5QgD9r(-IiWNcl;#G}9s-l=rZ_S2fY=wfnkUU~pERFw b`=t3y)7;twSlR?w+XUF!1lX4ea2NmpiF7`j delta 59 zcmZo@P;6*W+)y`Vde}Zjr_BeZT;giaI>`vcOxv?gGEZ}B|8S0R`-gK(H#FM>SlR?w P+XUF!1lX4ea2NmpCgT_| diff --git a/build.bat b/build.bat index e0e0e4c..3d58cc2 100644 --- a/build.bat +++ b/build.bat @@ -167,7 +167,7 @@ for %%f in (%SRC%\doom\*.c) do ( :: ---- Link doom.exe ---- echo Linking doom.exe... link %LFLAGS% /SUBSYSTEM:NATIVE /ENTRY:main ^ - /OUT:"bin\doom.exe" /PDB:"bin\doom.pdb" bin\win32.lib obj\libc.obj obj\doom\*.obj || exit /b 1 + /OUT:"bin\doom.exe" /PDB:"bin\doom.pdb" bin\syscall.lib obj\libc.obj obj\doom\*.obj || exit /b 1 :: ---- Compile video player ---- echo Compiling video... diff --git a/code/doom/doom.c b/code/doom/doom.c index c6bce2e..69f4388 100644 --- a/code/doom/doom.c +++ b/code/doom/doom.c @@ -1,43 +1,51 @@ -// doom.c — NT-Lite port of PureDOOM -// Win32 personality carrying all the weight, just like the real ntoskrnl does. +// doom.c — PureDOOM port +// Native personality carrying all the weight. // Drop doom1.wad (or doom.wad) next to this binary on your FAT32 volume. -#include -#define DOOM_IMPLEMENTATION -#include "PureDOOM.h" #include #include +#include #include #include -int _fltused; +#include -// --------------------------------------------------------------------------- -// Globals -// --------------------------------------------------------------------------- -static HANDLE g_stdout; +#define DOOM_IMPLEMENTATION +#include "PureDOOM.h" + +int _fltused; // --------------------------------------------------------------------------- // doom_print_fn // --------------------------------------------------------------------------- static void doom_print_wrapper(const char* msg) { - UINT len = (UINT)strlen(msg); - WriteConsoleA(g_stdout, msg, len, 0, 0); + SysWriteConsole(msg, strlen(msg)); } // --------------------------------------------------------------------------- // doom_malloc_fn / doom_free_fn +// Prepend size tracking metadata to satisfy SysFree's size requirement. // --------------------------------------------------------------------------- static void* doom_malloc_wrapper(int size) { - void* p = HeapAlloc(0, HEAP_ZERO_MEMORY, size); - if (!p) ExitProcess(GetLastError()); - return p; + size_t total_size = size + sizeof(size_t); + void* p = SysAlloc(total_size); + if (!p) SysExitProcess(1); + + // Store total allocated size at the start of the block + *(size_t*)p = total_size; + + // Return pointer past the size prefix + return (void*)((size_t*)p + 1); } static void doom_free_wrapper(void* ptr) { - HeapFree(0, 0, ptr); + if (!ptr) return; + + size_t* real_p = (size_t*)ptr - 1; + size_t total_size = *real_p; + SysFree(real_p, total_size); } // --------------------------------------------------------------------------- @@ -45,61 +53,71 @@ static void doom_free_wrapper(void* ptr) // --------------------------------------------------------------------------- static void* doom_open_wrapper(const char* path, const char* mode) { - DWORD access = GENERIC_READ; - DWORD disp = OPEN_EXISTING; + uint32_t handle = 0; + uint32_t result; if (mode[0] == 'w') { - access = GENERIC_WRITE; - disp = CREATE_ALWAYS; - } else if (mode[0] == 'r' && mode[1] == '+') { - access = GENERIC_READ | GENERIC_WRITE; + result = SysCreateFile(path, &handle); + } else { + result = SysOpenFile(path, &handle); } - HANDLE h = CreateFileA(path, access, FILE_SHARE_READ, 0, disp, 0, 0); - return (h == INVALID_HANDLE_VALUE) ? 0 : h; + return (result == 0) ? (void*)(uintptr_t)handle : 0; } static void doom_close_wrapper(void* handle) { - CloseHandle((HANDLE)handle); + SysCloseHandle((uint32_t)(uintptr_t)handle); } static int doom_read_wrapper(void* handle, void* buf, int count) { - DWORD read = 0; - ReadFile((HANDLE)handle, buf, (DWORD)count, &read, 0); + size_t read = 0; + SysReadFile((uint32_t)(uintptr_t)handle, buf, (size_t)count, &read); return (int)read; } static int doom_write_wrapper(void* handle, const void* buf, int count) { - DWORD written = 0; - WriteFile((HANDLE)handle, buf, (DWORD)count, &written, 0); + size_t written = 0; + SysWriteFile((uint32_t)(uintptr_t)handle, buf, (size_t)count, &written); return (int)written; } static int doom_seek_wrapper(void* handle, int offset, doom_seek_t origin) { - DWORD method; + uint32_t h = (uint32_t)(uintptr_t)handle; + uint64_t absolute_offset = 0; + switch (origin) { - case DOOM_SEEK_SET: method = FILE_BEGIN; break; - case DOOM_SEEK_CUR: method = FILE_CURRENT; break; - case DOOM_SEEK_END: method = FILE_END; break; - default: method = FILE_BEGIN; break; + case DOOM_SEEK_SET: + absolute_offset = (uint64_t)offset; + break; + case DOOM_SEEK_CUR: + absolute_offset = SysGetFilePos(h) + offset; + break; + case DOOM_SEEK_END: + absolute_offset = SysGetFileSize(h) + offset; + break; + default: + absolute_offset = (uint64_t)offset; + break; } - SetFilePointer((HANDLE)handle, (LONG)offset, 0, method); + + SysSeekFile(h, absolute_offset); return 0; } static int doom_tell_wrapper(void* handle) { - return (int)SetFilePointer((HANDLE)handle, 0, 0, FILE_CURRENT); + return (int)SysGetFilePos((uint32_t)(uintptr_t)handle); } static int doom_eof_wrapper(void* handle) { - DWORD size = GetFileSize((HANDLE)handle, 0); - DWORD pos = (DWORD)doom_tell_wrapper(handle); + uint32_t h = (uint32_t)(uintptr_t)handle; + uint64_t size = SysGetFileSize(h); + uint64_t pos = SysGetFilePos(h); return (pos >= size) ? 1 : 0; } @@ -108,9 +126,9 @@ static int doom_eof_wrapper(void* handle) // --------------------------------------------------------------------------- static void doom_gettime_wrapper(int* sec, int* usec) { - DWORD t = GetTickCount(); // milliseconds - *sec = (int)(t / 1000); - *usec = (int)((t % 1000) * 1000); // ms remainder → us + uint64_t t = SysGetTick(); + *sec = (int)(t / 1000000); // 1,000,000 us = 1 second + *usec = (int)(t % 1000000); // Remainder in microseconds } // --------------------------------------------------------------------------- @@ -118,16 +136,16 @@ static void doom_gettime_wrapper(int* sec, int* usec) // --------------------------------------------------------------------------- static void doom_exit_wrapper(int code) { - ExitProcess((UINT)code); + SysExitProcess(code); } // --------------------------------------------------------------------------- -// doom_getenv_fn — we don't have environment variables +// doom_getenv_fn // --------------------------------------------------------------------------- static char* doom_getenv_wrapper(const char* name) { if (strcmp(name, "HOME") == 0) { - static char home[] = "c:"; + static char home[] = "."; doom_print_wrapper("getenv: Returning HOME="); doom_print_wrapper(home); doom_print_wrapper("\n"); return home; } @@ -136,102 +154,88 @@ static char* doom_getenv_wrapper(const char* name) } // --------------------------------------------------------------------------- -// Input — poll GetAsyncKeyState every frame -// Generates key_down / key_up edge events, not held-key spam. +// Input — Maps Scancode Set 1 values to PureDOOM // --------------------------------------------------------------------------- -typedef struct { int vk; doom_key_t key; } KeyMap; +typedef struct { uint8_t scancode; bool extended; doom_key_t key; } KeyMap; -// NOTE: Win32 VK codes for letters are uppercase ASCII (e.g. 'W' = 0x57). -// DOOM_KEY_* for letters are lowercase ASCII (e.g. DOOM_KEY_W = 'w' = 0x77). -// These are different values — the VK column is what GetAsyncKeyState takes, -// the doom_key column is what PureDOOM wants. Don't collapse them. static const KeyMap g_keymap[] = { - // Letters — VK is uppercase, DOOM key is lowercase - { 'W', DOOM_KEY_W }, - { 'A', DOOM_KEY_A }, - { 'S', DOOM_KEY_S }, - { 'D', DOOM_KEY_D }, - { 'E', DOOM_KEY_E }, - { 'Q', DOOM_KEY_Q }, - { 'Y', DOOM_KEY_Y }, // yes/no menus - { 'N', DOOM_KEY_N }, + // Letters + { 0x11, false, DOOM_KEY_W }, + { 0x1E, false, DOOM_KEY_A }, + { 0x1F, false, DOOM_KEY_S }, + { 0x20, false, DOOM_KEY_D }, + { 0x12, false, DOOM_KEY_E }, + { 0x10, false, DOOM_KEY_Q }, + { 0x15, false, DOOM_KEY_Y }, + { 0x31, false, DOOM_KEY_N }, // Arrow keys - { VK_UP, DOOM_KEY_UP_ARROW }, - { VK_DOWN, DOOM_KEY_DOWN_ARROW }, - { VK_LEFT, DOOM_KEY_LEFT_ARROW }, - { VK_RIGHT, DOOM_KEY_RIGHT_ARROW }, + { 0x48, true, DOOM_KEY_UP_ARROW }, + { 0x50, true, DOOM_KEY_DOWN_ARROW }, + { 0x4B, true, DOOM_KEY_LEFT_ARROW }, + { 0x4D, true, DOOM_KEY_RIGHT_ARROW }, - // Action keys — Ctrl=fire, Shift=run, Alt=strafe - { VK_CONTROL, DOOM_KEY_CTRL }, - { VK_SPACE, DOOM_KEY_SPACE }, - { VK_SHIFT, DOOM_KEY_SHIFT }, - { VK_MENU, DOOM_KEY_ALT }, + // Action keys + { 0x1D, false, DOOM_KEY_CTRL }, + { 0x39, false, DOOM_KEY_SPACE }, + { 0x2A, false, DOOM_KEY_SHIFT }, + { 0x38, false, DOOM_KEY_ALT }, // Navigation - { VK_ESCAPE, DOOM_KEY_ESCAPE }, - { VK_RETURN, DOOM_KEY_ENTER }, - { VK_TAB, DOOM_KEY_TAB }, - { VK_BACK, DOOM_KEY_BACKSPACE }, - { VK_PAUSE, DOOM_KEY_PAUSE }, + { 0x01, false, DOOM_KEY_ESCAPE }, + { 0x1C, false, DOOM_KEY_ENTER }, + { 0x0F, false, DOOM_KEY_TAB }, + { 0x0E, false, DOOM_KEY_BACKSPACE }, // Weapon slots - { '1', DOOM_KEY_1 }, - { '2', DOOM_KEY_2 }, - { '3', DOOM_KEY_3 }, - { '4', DOOM_KEY_4 }, - { '5', DOOM_KEY_5 }, - { '6', DOOM_KEY_6 }, - { '7', DOOM_KEY_7 }, + { 0x02, false, DOOM_KEY_1 }, + { 0x03, false, DOOM_KEY_2 }, + { 0x04, false, DOOM_KEY_3 }, + { 0x05, false, DOOM_KEY_4 }, + { 0x06, false, DOOM_KEY_5 }, + { 0x07, false, DOOM_KEY_6 }, + { 0x08, false, DOOM_KEY_7 }, // Misc - { '-', DOOM_KEY_MINUS }, - { '=', DOOM_KEY_EQUALS }, - { ',', DOOM_KEY_COMMA }, // default strafe left - { '.', DOOM_KEY_PERIOD }, // default strafe right + { 0x0C, false, DOOM_KEY_MINUS }, + { 0x0D, false, DOOM_KEY_EQUALS }, + { 0x33, false, DOOM_KEY_COMMA }, + { 0x34, false, DOOM_KEY_PERIOD }, - // Function keys — save/load/volume/etc - { VK_F1, DOOM_KEY_F1 }, - { VK_F2, DOOM_KEY_F2 }, - { VK_F3, DOOM_KEY_F3 }, - { VK_F4, DOOM_KEY_F4 }, - { VK_F5, DOOM_KEY_F5 }, - { VK_F6, DOOM_KEY_F6 }, - { VK_F7, DOOM_KEY_F7 }, - { VK_F8, DOOM_KEY_F8 }, - { VK_F9, DOOM_KEY_F9 }, - { VK_F10, DOOM_KEY_F10 }, - { VK_F11, DOOM_KEY_F11 }, - { VK_F12, DOOM_KEY_F12 }, + // Function keys + { 0x3B, false, DOOM_KEY_F1 }, + { 0x3C, false, DOOM_KEY_F2 }, + { 0x3D, false, DOOM_KEY_F3 }, + { 0x3E, false, DOOM_KEY_F4 }, + { 0x3F, false, DOOM_KEY_F5 }, + { 0x40, false, DOOM_KEY_F6 }, + { 0x41, false, DOOM_KEY_F7 }, + { 0x42, false, DOOM_KEY_F8 }, + { 0x43, false, DOOM_KEY_F9 }, + { 0x44, false, DOOM_KEY_F10 }, + { 0x57, false, DOOM_KEY_F11 }, + { 0x58, false, DOOM_KEY_F12 }, }; #define KEYMAP_COUNT (sizeof(g_keymap) / sizeof(g_keymap[0])) -static short g_prev_key[256]; +static bool g_prev_key[KEYMAP_COUNT]; static void poll_input(void) { for (int i = 0; i < (int)KEYMAP_COUNT; i++) { - int vk = g_keymap[i].vk; - short cur = GetAsyncKeyState(vk); - int cur_down = (cur & 0x8000) != 0; - int prev_down = (g_prev_key[vk] & 0x8000) != 0; + bool cur_down = (SysKbIsKeyDown(g_keymap[i].scancode, g_keymap[i].extended) != 0); + bool prev_down = g_prev_key[i]; if (cur_down && !prev_down) doom_key_down(g_keymap[i].key); if (!cur_down && prev_down) doom_key_up (g_keymap[i].key); - g_prev_key[vk] = cur; + g_prev_key[i] = cur_down; } } // --------------------------------------------------------------------------- -// Render — StretchDIBits to screen, same pipeline as video.c. -// -// NOTE: PureDOOM's framebuffer is RGBA; Win32 StretchDIBits with BI_RGB/32bpp -// expects BGRA. Channels will be swapped (red ↔ blue) until you either: -// a) swap in the blit loop below, or -// b) tell PureDOOM to give you BGRA if a future version supports it. -// For a first boot, blue-tinted demons are still demons. +// Render — Performs lightweight software upscale and blits to the screen // --------------------------------------------------------------------------- #define DOOM_W 320 #define DOOM_H 200 @@ -239,55 +243,82 @@ static void poll_input(void) #define DISP_W (DOOM_W * SCALE) #define DISP_H (DOOM_H * SCALE) -static BITMAPINFO g_bmi; -static uint32_t g_blit_buf[DOOM_W * DOOM_H]; // BGRA-swapped copy +static uint32_t g_blit_buf[DISP_W * DISP_H]; static void render_frame(const uint8_t* rgba) { - // Swap R <-> B so Win32 GDI sees correct colours const uint32_t* src = (const uint32_t*)rgba; - for (int i = 0; i < DOOM_W * DOOM_H; i++) { - uint32_t p = src[i]; - // RGBA -> BGRA: swap byte 0 (R) and byte 2 (B) - g_blit_buf[i] = (p & 0xFF00FF00u) - | ((p & 0x00FF0000u) >> 16) - | ((p & 0x000000FFu) << 16); + + // Software upscale loop mapping 320x200 buffer directly to scaled window buffer + for (int y = 0; y < DOOM_H; y++) { + for (int x = 0; x < DOOM_W; x++) { + uint32_t p = src[y * DOOM_W + x]; + + // RGBA -> BGRA conversion (standard for linear x86/64 framebuffers) + uint32_t bgra = (p & 0xFF00FF00u) + | ((p & 0x00FF0000u) >> 16) + | ((p & 0x000000FFu) << 16); + + // Populate SCALE * SCALE pixel block + for (int sy = 0; sy < SCALE; sy++) { + for (int sx = 0; sx < SCALE; sx++) { + g_blit_buf[(y * SCALE + sy) * DISP_W + (x * SCALE + sx)] = bgra; + } + } + } } - PAINTSTRUCT ps; - HDC hdc = BeginPaint((HWND)0, &ps); - StretchDIBits( - hdc, - 0, 0, DISP_W, DISP_H, // destination — scaled up - 0, 0, DOOM_W, DOOM_H, // source — native 320×200 - g_blit_buf, - &g_bmi, - DIB_RGB_COLORS, - SRCCOPY - ); - EndPaint((HWND)0, &ps); + SysScreenBlit(0, 0, DISP_W, DISP_H, g_blit_buf); +} + +static int build_argv_from_command_line(const char* cmdline, char* storage, size_t storageSize, + const char** argv, int maxArgv) { + if (!storage || storageSize == 0 || !argv || maxArgv <= 1) return 0; + + if (!cmdline || cmdline[0] == '\0') cmdline = "doom"; + + size_t len = strlen(cmdline); + if (len >= storageSize) len = storageSize - 1; + memcpy(storage, cmdline, len); + storage[len] = '\0'; + + int argc = 0; + char* p = storage; + while (*p && argc < maxArgv - 1) { + while (*p == ' ' || *p == '\t') p++; + if (!*p) break; + + if (*p == '"') { + p++; + argv[argc++] = p; + while (*p && *p != '"') p++; + if (*p) *p++ = '\0'; + } else { + argv[argc++] = p; + while (*p && *p != ' ' && *p != '\t') p++; + if (*p) *p++ = '\0'; + } + } + + argv[argc] = 0; + if (argc == 0) { + argv[0] = "doom"; + argv[1] = 0; + argc = 1; + } + return argc; } // --------------------------------------------------------------------------- // main // --------------------------------------------------------------------------- -int main(const char* cmdline) +int main(void) { - (void)cmdline; + const char* cmdline = SysGetCommandLine(); - g_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - WriteConsoleA(g_stdout, "doom: starting\n", 15, 0, 0); + SysWriteConsole("doom: starting\n", 15); - // Prepare the BMI header once (top-down, like video.c) - memset(&g_bmi, 0, sizeof(g_bmi)); - g_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - g_bmi.bmiHeader.biWidth = DOOM_W; - g_bmi.bmiHeader.biHeight = -DOOM_H; // negative = top-down - g_bmi.bmiHeader.biPlanes = 1; - g_bmi.bmiHeader.biBitCount = 32; - g_bmi.bmiHeader.biCompression = BI_RGB; - - // Wire up PureDOOM + // Wire up PureDOOM core mechanics doom_set_print(doom_print_wrapper); doom_set_malloc(doom_malloc_wrapper, doom_free_wrapper); doom_set_file_io( @@ -312,19 +343,20 @@ int main(const char* cmdline) doom_set_default_int("key_use", DOOM_KEY_E); doom_set_default_int("mouse_move", 0); - // No extra args — PureDOOM will scan for doom1.wad / doom.wad - const char* args[] = { "doom" }; - doom_init(1, (char**)args, 0); + char argvStorage[512]; + const char* argv[32]; + int argc = build_argv_from_command_line(cmdline, argvStorage, sizeof(argvStorage), argv, 32); + doom_init(argc, argv, 0); - WriteConsoleA(g_stdout, "doom: entering game loop\n", 25, 0, 0); + SysWriteConsole("doom: entering game loop\n", 25); - // Game loop — identical shape to video.c's Bink loop + // Core execution loop while (1) { poll_input(); doom_update(); render_frame((const uint8_t*)doom_get_framebuffer(4 /* RGBA */)); } - // Unreachable: doom_exit() -> ExitProcess() handles clean shutdown - ExitProcess(0); + SysExitProcess(0); + return 0; } diff --git a/code/drivers/fat32/fat32.c b/code/drivers/fat32/fat32.c index 8e920eb..ca8aee6 100644 --- a/code/drivers/fat32/fat32.c +++ b/code/drivers/fat32/fat32.c @@ -1,1189 +1,1206 @@ -// fat32.c - FAT32 filesystem driver -// Read + write support: file data, cluster allocation, directory entries. -// -// Performance improvements over the original: -// -// 1. FAT sector cache (FAT_CACHE_SLOTS write-back entries). -// FatEntry() and WriteFatEntry() go through the cache; the hot FAT -// region stays in RAM instead of being read from disk on every chain -// traversal step. A 32-slot cache covers 32 x (512/4) = 4096 -// consecutive cluster entries — enough to keep the entire FAT of a -// small partition in memory, and to avoid repeated reads even on large -// ones. -// -// 2. Fat32ReadDir() cluster buffer cached in FAT32_DIR_CTX. -// The original re-read the same cluster from disk on every call -// (allocate -> read -> return one entry -> free). Now the buffer is -// allocated once in Fat32OpenDir, freed in Fat32CloseDir, and only -// re-read when the iteration moves to a new cluster. -// -// 3. AllocCluster() next-free hint. -// The search now starts from the cluster after the last allocation -// (gVol.NextFreeHint) and wraps around once, so repeated allocations -// on a fragmented volume don't rescan from sector 0 every time. -// -// 4. Contiguous-cluster run coalescing in Fat32Read() and Fat32Write(). -// After fetching a cluster's FAT entry the code peeks ahead: if the -// next cluster is physically adjacent (cluster+1) it extends the run -// rather than issuing a separate ReadSectors/WriteSectors call. A -// freshly-written unfragmented file now transfers in one or a handful -// of large DMA bursts instead of SectorsPerCluster-sized slices. -// -// 5. FAT write batching via a dirty-sector cache. -// WriteFatEntry() marks modified FAT cache slots dirty instead of -// flushing immediately. FlushFatCache() (called from Fat32Close, -// Fat32Truncate, and Fat32Delete) writes each dirty slot once for -// every FAT copy, collapsing the 2x(read+write) per-entry cost of the -// original into a single write per dirty sector per FAT copy. - -#include -#include -#include -#include -#include -#include -#include -#include - -// --------------------------------------------------------------------------- -// On-disk structures -// --------------------------------------------------------------------------- -#pragma pack(push, 1) - -typedef struct { - uint8_t Jump[3]; - char OemId[8]; - uint16_t BytesPerSector; - uint8_t SectorsPerCluster; - uint16_t ReservedSectors; - uint8_t NumFats; - uint16_t RootEntryCount; - uint16_t TotalSectors16; - uint8_t MediaType; - uint16_t FatSize16; - uint16_t SectorsPerTrack; - uint16_t NumHeads; - uint32_t HiddenSectors; - uint32_t TotalSectors32; - uint32_t FatSize32; - uint16_t ExtFlags; - uint16_t FsVersion; - uint32_t RootCluster; - uint16_t FsInfoSector; - uint16_t BackupBootSector; - uint8_t Reserved[12]; - uint8_t DriveNumber; - uint8_t Reserved1; - uint8_t BootSignature; - uint32_t VolumeId; - char VolumeLabel[11]; - char FsType[8]; -} BPB; - -typedef struct { - char Name[8]; - char Ext[3]; - uint8_t Attr; - uint8_t Reserved; - uint8_t CrtTimeTenth; - uint16_t CrtTime; - uint16_t CrtDate; - uint16_t LastAccDate; - uint16_t FirstClusterHi; - uint16_t WrtTime; - uint16_t WrtDate; - uint16_t FirstClusterLo; - uint32_t FileSize; -} DIRENT; - -typedef struct { - uint8_t Order; - uint16_t Name1[5]; - uint8_t Attr; - uint8_t Type; - uint8_t Checksum; - uint16_t Name2[6]; - uint16_t FirstCluster; - uint16_t Name3[2]; -} LFN_ENTRY; - -typedef struct { - uint32_t Data1; - uint16_t Data2; - uint16_t Data3; - uint8_t Data4[8]; -} EFI_GUID; - -typedef struct { - char Signature[8]; - uint32_t Revision; - uint32_t HeaderSize; - uint32_t HeaderCRC32; - uint32_t Reserved; - uint64_t MyLBA; - uint64_t AlternateLBA; - uint64_t FirstUsableLBA; - uint64_t LastUsableLBA; - EFI_GUID DiskGUID; - uint64_t PartitionEntryLBA; - uint32_t NumberOfPartitionEntries; - uint32_t SizeOfPartitionEntry; - uint32_t PartitionEntryArrayCRC32; -} GPT_HEADER; - -typedef struct { - EFI_GUID PartitionTypeGUID; - EFI_GUID UniquePartitionGUID; - uint64_t StartingLBA; - uint64_t EndingLBA; - uint64_t Attributes; - uint16_t PartitionName[36]; -} GPT_ENTRY; - -#pragma pack(pop) - -#define ATTR_DIRECTORY 0x10 -#define ATTR_ARCHIVE 0x20 -#define ATTR_LFN 0x0F -#define FAT32_EOC 0x0FFFFFF8u -#define FAT32_EOC_MARK 0x0FFFFFFFu -#define FAT32_FREE 0x00000000u - -// --------------------------------------------------------------------------- -// FAT sector cache (fixes #1 and #5) -// --------------------------------------------------------------------------- -// 32 direct-mapped slots, indexed by (lba % FAT_CACHE_SLOTS). -// Write-back: WriteFatEntry modifies the in-cache bytes and marks the slot -// dirty. FlushFatCache() propagates dirty slots to every FAT copy at once, -// replacing the original per-entry 2x(read+write) pattern with a single -// write per dirty sector per FAT copy. - -#define FAT_CACHE_SLOTS 32 - -typedef struct { - uint64_t lba; - uint8_t data[512]; - bool valid; - bool dirty; -} FAT_CACHE_ENTRY; - -static FAT_CACHE_ENTRY gFatCache[FAT_CACHE_SLOTS]; - -// FAT32_VOL forward declaration (needed by cache helpers). -typedef struct FAT32_VOL_S FAT32_VOL; - -// Raw (non-cached) sector I/O — forward declarations. -static KSTATUS ReadSectorsRaw (FAT32_VOL *v, uint64_t lba, uint32_t count, void *buf); -static KSTATUS WriteSectorsRaw(FAT32_VOL *v, uint64_t lba, uint32_t count, const void *buf); - -// Read one FAT sector through the cache. -// Returns a pointer into the cache buffer (valid until the next eviction). -static KSTATUS FatCacheRead(FAT32_VOL *v, uint64_t lba, uint8_t **out) { - uint32_t slot = (uint32_t)(lba % FAT_CACHE_SLOTS); - FAT_CACHE_ENTRY *e = &gFatCache[slot]; - - if (e->valid && e->lba == lba) { - *out = e->data; - return STATUS_SUCCESS; - } - - // Evict current occupant: single-copy writeback on collision. - // The full multi-copy flush happens in FlushFatCache(). - if (e->valid && e->dirty) { - WriteSectorsRaw(v, e->lba, 1, e->data); - e->dirty = false; - } - - KSTATUS s = ReadSectorsRaw(v, lba, 1, e->data); - if (!KSUCCESS(s)) { e->valid = false; return s; } - - e->lba = lba; - e->valid = true; - e->dirty = false; - *out = e->data; - return STATUS_SUCCESS; -} - -static void FatCacheMarkDirty(uint64_t lba) { - uint32_t slot = (uint32_t)(lba % FAT_CACHE_SLOTS); - if (gFatCache[slot].valid && gFatCache[slot].lba == lba) - gFatCache[slot].dirty = true; -} - -static void FatCacheInvalidate(void) { - for (int i = 0; i < FAT_CACHE_SLOTS; i++) { - gFatCache[i].valid = false; - gFatCache[i].dirty = false; - } -} - -// FlushFatCache — write every dirty slot to all FAT copies, clear dirty flag. -// Defined after FAT32_VOL is fully declared (below). -static void FlushFatCache(FAT32_VOL *v); - -// --------------------------------------------------------------------------- -// Driver state -// --------------------------------------------------------------------------- -struct FAT32_VOL_S { - uint8_t DriveIndex; - uint32_t PartitionLba; - uint32_t BytesPerSector; - uint32_t SectorsPerCluster; - uint32_t BytesPerCluster; - uint32_t ReservedSectors; - uint32_t NumFats; - uint32_t FatSize; // sectors per FAT copy - uint32_t RootCluster; - uint32_t DataStart; // absolute LBA of cluster 2 - uint32_t TotalClusters; // usable data clusters - uint32_t NextFreeHint; // fix #3: start cluster for next free-scan - const HAL_API *Hal; -}; - -typedef struct { - uint32_t StartCluster; - uint32_t FileSize; - FAT32_VOL *Vol; - uint64_t DirentLba; - uint32_t DirentOffset; -} FAT32_FILE; - -// --------------------------------------------------------------------------- -// Raw sector I/O (bypasses FAT cache; used for file data and cache eviction) -// --------------------------------------------------------------------------- -static KSTATUS ReadSectorsRaw(FAT32_VOL *v, uint64_t lba, - uint32_t count, void *buf) { - if (!v || !v->Hal || !v->Hal->Disk.ReadSectors) - return STATUS_INVALID_PARAMETER; - return v->Hal->Disk.ReadSectors(v->DriveIndex, lba, count, buf); -} - -static KSTATUS WriteSectorsRaw(FAT32_VOL *v, uint64_t lba, - uint32_t count, const void *buf) { - if (!v || !v->Hal || !v->Hal->Disk.WriteSectors) - return STATUS_INVALID_PARAMETER; - return v->Hal->Disk.WriteSectors(v->DriveIndex, lba, count, buf); -} - -// Convenience aliases for file-data I/O. -static KSTATUS ReadSectors (FAT32_VOL *v, uint64_t lba, uint32_t n, void *b) { return ReadSectorsRaw (v, lba, n, b); } -static KSTATUS WriteSectors(FAT32_VOL *v, uint64_t lba, uint32_t n, const void *b) { return WriteSectorsRaw(v, lba, n, b); } - -static uint64_t ClusterToLba(FAT32_VOL *v, uint32_t cluster) { - return v->DataStart + (uint64_t)(cluster - 2) * v->SectorsPerCluster; -} - -// --------------------------------------------------------------------------- -// FlushFatCache — needs full FAT32_VOL definition -// --------------------------------------------------------------------------- -static void FlushFatCache(FAT32_VOL *v) { - uint64_t fat0Start = (uint64_t)v->PartitionLba + v->ReservedSectors; - - for (int i = 0; i < FAT_CACHE_SLOTS; i++) { - FAT_CACHE_ENTRY *e = &gFatCache[i]; - if (!e->valid || !e->dirty) continue; - if (e->lba < fat0Start) continue; - - uint64_t sectorIdx = e->lba - fat0Start; - if (sectorIdx >= v->FatSize) continue; - - for (uint32_t f = 0; f < v->NumFats; f++) { - uint64_t lba = fat0Start + (uint64_t)f * v->FatSize + sectorIdx; - WriteSectorsRaw(v, lba, 1, e->data); - } - e->dirty = false; - } -} - -// --------------------------------------------------------------------------- -// FAT read / write (through cache) -// --------------------------------------------------------------------------- - -static uint32_t FatEntry(FAT32_VOL *v, uint32_t cluster) { - uint32_t fatOffset = cluster * 4; - uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors - + fatOffset / v->BytesPerSector; - uint32_t offInSec = fatOffset % v->BytesPerSector; - - uint8_t *sec; - if (!KSUCCESS(FatCacheRead(v, sector, &sec))) return 0; - return *(uint32_t *)(sec + offInSec) & 0x0FFFFFFF; -} - -static KSTATUS WriteFatEntry(FAT32_VOL *v, uint32_t cluster, uint32_t value) { - uint32_t fatOffset = cluster * 4; - uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors - + fatOffset / v->BytesPerSector; - uint32_t offInSec = fatOffset % v->BytesPerSector; - - uint8_t *sec; - KSTATUS s = FatCacheRead(v, sector, &sec); - if (!KSUCCESS(s)) return s; - - uint32_t existing = *(uint32_t *)(sec + offInSec); - *(uint32_t *)(sec + offInSec) = (existing & 0xF0000000u) - | (value & 0x0FFFFFFFu); - FatCacheMarkDirty(sector); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// Cluster allocation (fix #3: next-free hint + two-phase wrap-around scan) -// --------------------------------------------------------------------------- -static uint32_t AllocCluster(FAT32_VOL *v) { - if (v->TotalClusters == 0) return 0; - - uint32_t total = v->TotalClusters; - uint32_t start = v->NextFreeHint; - if (start < 2 || start >= total + 2) start = 2; - - for (uint32_t pass = 0; pass < 2; pass++) { - uint32_t lo = (pass == 0) ? start : 2; - uint32_t hi = (pass == 0) ? total+2 : start; - for (uint32_t c = lo; c < hi; c++) { - if (FatEntry(v, c) == FAT32_FREE) { - if (!KSUCCESS(WriteFatEntry(v, c, FAT32_EOC_MARK))) return 0; - v->NextFreeHint = c + 1; - - // Zero the cluster (required for directory correctness). - uint8_t *zero = (uint8_t *)MmAllocatePool(v->BytesPerCluster); - if (zero) { - memset(zero, 0, v->BytesPerCluster); - WriteSectors(v, ClusterToLba(v, c), v->SectorsPerCluster, zero); - MmFreePool(zero); - } - return c; - } - } - } - return 0; -} - -static uint32_t AppendCluster(FAT32_VOL *v, uint32_t lastCluster) { - uint32_t newCluster = AllocCluster(v); - if (!newCluster) return 0; - if (!KSUCCESS(WriteFatEntry(v, lastCluster, newCluster))) return 0; - return newCluster; -} - -static uint32_t GetChainEnd(FAT32_VOL *v, uint32_t start, uint32_t *outCount) { - uint32_t cluster = start, prev = start, count = 0; - while (cluster >= 2 && cluster < FAT32_EOC) { - prev = cluster; count++; - cluster = FatEntry(v, cluster); - } - if (outCount) *outCount = count; - return prev; -} - -static void FreeClusterChain(FAT32_VOL *v, uint32_t start) { - uint32_t cluster = start; - while (cluster >= 2 && cluster < FAT32_EOC) { - uint32_t next = FatEntry(v, cluster); - WriteFatEntry(v, cluster, FAT32_FREE); - if (cluster < v->NextFreeHint) v->NextFreeHint = cluster; - cluster = next; - } -} - -// --------------------------------------------------------------------------- -// DIRENT update helpers -// --------------------------------------------------------------------------- -static KSTATUS DirentUpdateSize(FAT32_VOL *v, uint64_t lba, - uint32_t offset, uint32_t size) { - uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); - if (!sec) return STATUS_NO_MEMORY; - KSTATUS s = ReadSectors(v, lba, 1, sec); - if (KSUCCESS(s)) { - ((DIRENT *)(sec + offset))->FileSize = size; - s = WriteSectors(v, lba, 1, sec); - } - MmFreePool(sec); - return s; -} - -static KSTATUS DirentUpdateFirstCluster(FAT32_VOL *v, uint64_t lba, - uint32_t offset, uint32_t cluster) { - uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); - if (!sec) return STATUS_NO_MEMORY; - KSTATUS s = ReadSectors(v, lba, 1, sec); - if (KSUCCESS(s)) { - DIRENT *d = (DIRENT *)(sec + offset); - d->FirstClusterHi = (uint16_t)(cluster >> 16); - d->FirstClusterLo = (uint16_t)(cluster & 0xFFFF); - s = WriteSectors(v, lba, 1, sec); - } - MmFreePool(sec); - return s; -} - -// --------------------------------------------------------------------------- -// 8.3 name matching / assembly -// --------------------------------------------------------------------------- -static int MatchName83(const char *dirName, const char *dirExt, - const char *want) { - char buf[13]; int n = 0; - for (int i = 0; i < 8 && dirName[i] != ' '; i++) - buf[n++] = (char)(dirName[i] >= 'A' && dirName[i] <= 'Z' ? dirName[i]+32 : dirName[i]); - if (dirExt[0] != ' ') { - buf[n++] = '.'; - for (int i = 0; i < 3 && dirExt[i] != ' '; i++) - buf[n++] = (char)(dirExt[i] >= 'A' && dirExt[i] <= 'Z' ? dirExt[i]+32 : dirExt[i]); - } - buf[n] = '\0'; - const char *p = buf; - while (*want && *p) { - char a = *p++, b = *want++; - if (a >= 'A' && a <= 'Z') a += 32; - if (b >= 'A' && b <= 'Z') b += 32; - if (a != b) return 0; - } - return *want == '\0' && *p == '\0'; -} - -static void Make83Name(const char *src, char name8[8], char ext3[3]) { - memset(name8, ' ', 8); memset(ext3, ' ', 3); - const char *dot = NULL; - for (const char *p = src; *p; p++) if (*p == '.') dot = p; - int ni = 0; - const char *end = dot ? dot : src + strlen(src); - for (const char *p = src; p < end && ni < 8; p++) { - char c = *p; if (c >= 'a' && c <= 'z') c -= 32; name8[ni++] = c; - } - if (dot) { - int ei = 0; - for (const char *p = dot+1; *p && ei < 3; p++) { - char c = *p; if (c >= 'a' && c <= 'z') c -= 32; ext3[ei++] = c; - } - } -} - -// --------------------------------------------------------------------------- -// LFN assembly / matching -// --------------------------------------------------------------------------- -#define MAX_LFN 256 - -static void LfnAppend(char *lfn, const LFN_ENTRY *e) { - int seq = (e->Order & 0x3F) - 1; - if (seq < 0 || seq >= (MAX_LFN / 13)) return; - int pos = seq * 13; - uint16_t chars[13]; int idx = 0; - for (int i = 0; i < 5; i++) chars[idx++] = e->Name1[i]; - for (int i = 0; i < 6; i++) chars[idx++] = e->Name2[i]; - for (int i = 0; i < 2; i++) chars[idx++] = e->Name3[i]; - for (int i = 0; i < 13 && pos < MAX_LFN-1; i++, pos++) { - uint16_t c = chars[i]; - if (c == 0x0000 || c == 0xFFFF) { lfn[pos] = '\0'; break; } - lfn[pos] = (char)(c & 0x7F); - } -} - -static int LfnMatch(const char *lfn, const char *want) { - while (*lfn && *want) { - char a = *lfn++, b = *want++; - if (a >= 'A' && a <= 'Z') a += 32; - if (b >= 'A' && b <= 'Z') b += 32; - if (a != b) return 0; - } - return *lfn == '\0' && *want == '\0'; -} - -// --------------------------------------------------------------------------- -// Directory search -// --------------------------------------------------------------------------- -static int SearchDir(FAT32_VOL *v, uint32_t dirCluster, const char *name, - uint32_t *outCluster, uint32_t *outSize, uint8_t *outAttr, - uint64_t *outDirentLba, uint32_t *outDirentOffset) { - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); - if (!clusterBuf) return 0; - - char lfn[MAX_LFN]; int hasLfn = 0; memset(lfn, 0, sizeof(lfn)); - uint32_t cluster = dirCluster; - - while (cluster >= 2 && cluster < FAT32_EOC) { - uint64_t clusterLba = ClusterToLba(v, cluster); - if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) break; - - uint32_t entries = v->BytesPerCluster / sizeof(DIRENT); - DIRENT *dir = (DIRENT *)clusterBuf; - - for (uint32_t i = 0; i < entries; i++) { - if (dir[i].Name[0] == 0x00) goto done; - if ((uint8_t)dir[i].Name[0] == 0xE5) { hasLfn = 0; memset(lfn, 0, sizeof(lfn)); continue; } - if (dir[i].Attr == ATTR_LFN) { LfnAppend(lfn, (LFN_ENTRY *)&dir[i]); hasLfn = 1; continue; } - - int match = 0; - if (hasLfn && lfn[0]) match = LfnMatch(lfn, name); - if (!match) match = MatchName83(dir[i].Name, dir[i].Ext, name); - hasLfn = 0; memset(lfn, 0, sizeof(lfn)); - - if (match) { - *outCluster = ((uint32_t)dir[i].FirstClusterHi << 16) | dir[i].FirstClusterLo; - *outSize = dir[i].FileSize; - *outAttr = dir[i].Attr; - if (outDirentLba || outDirentOffset) { - uint64_t eb = (uint64_t)i * sizeof(DIRENT); - if (outDirentLba) *outDirentLba = clusterLba + eb / v->BytesPerSector; - if (outDirentOffset) *outDirentOffset = (uint32_t)(eb % v->BytesPerSector); - } - MmFreePool(clusterBuf); - return 1; - } - } - cluster = FatEntry(v, cluster); - } -done: - MmFreePool(clusterBuf); - return 0; -} - -// --------------------------------------------------------------------------- -// Path traversal -// --------------------------------------------------------------------------- -static KSTATUS TraverseEx(FAT32_VOL *v, const char *path, - uint32_t *outCluster, uint32_t *outSize, - uint64_t *outDirentLba, uint32_t *outDirentOffset) { - uint32_t cluster = v->RootCluster, size = 0; - uint8_t attr = ATTR_DIRECTORY; - if (outDirentLba) *outDirentLba = 0; - if (outDirentOffset) *outDirentOffset = 0; - - char component[256]; const char *p = path; - while (*p) { - while (*p == '\\' || *p == '/') p++; - if (!*p) break; - int len = 0; - while (*p && *p != '\\' && *p != '/' && len < 255) component[len++] = *p++; - component[len] = '\0'; - if (!len) break; - if (!(attr & ATTR_DIRECTORY)) return STATUS_NOT_FOUND; - - uint32_t childCluster, childSize; uint8_t childAttr; - uint64_t childLba = 0; uint32_t childOffset = 0; - if (!SearchDir(v, cluster, component, &childCluster, &childSize, - &childAttr, &childLba, &childOffset)) - return STATUS_NOT_FOUND; - - cluster = childCluster; size = childSize; attr = childAttr; - if (outDirentLba) *outDirentLba = childLba; - if (outDirentOffset) *outDirentOffset = childOffset; - } - *outCluster = cluster; *outSize = size; - return STATUS_SUCCESS; -} - -static KSTATUS Traverse(FAT32_VOL *v, const char *path, - uint32_t *outCluster, uint32_t *outSize) { - return TraverseEx(v, path, outCluster, outSize, NULL, NULL); -} - -// --------------------------------------------------------------------------- -// Path helpers for Fat32Create -// --------------------------------------------------------------------------- -static void SplitPath(const char *path, char parent[256], char name[256]) { - const char *last = NULL; - for (const char *p = path; *p; p++) if (*p == '\\' || *p == '/') last = p; - if (!last) { parent[0] = '\0'; strncpy(name, path, 255); name[255] = '\0'; } - else { - size_t len = (size_t)(last - path); if (len > 255) len = 255; - memcpy(parent, path, len); parent[len] = '\0'; - strncpy(name, last+1, 255); name[255] = '\0'; - } -} - -// --------------------------------------------------------------------------- -// Find a free DIRENT slot -// --------------------------------------------------------------------------- -static int FindFreeDirent(FAT32_VOL *v, uint32_t dirCluster, - uint64_t *outLba, uint32_t *outOffset) { - uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); - if (!clusterBuf) return 0; - - uint32_t cluster = dirCluster, prevCluster = dirCluster; - while (cluster >= 2 && cluster < FAT32_EOC) { - uint64_t clusterLba = ClusterToLba(v, cluster); - if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) { - MmFreePool(clusterBuf); return 0; - } - uint32_t entries = v->BytesPerCluster / sizeof(DIRENT); - DIRENT *dir = (DIRENT *)clusterBuf; - for (uint32_t i = 0; i < entries; i++) { - uint8_t first = (uint8_t)dir[i].Name[0]; - if (first == 0xE5 || first == 0x00) { - uint64_t eb = (uint64_t)i * sizeof(DIRENT); - *outLba = clusterLba + eb / v->BytesPerSector; - *outOffset = (uint32_t)(eb % v->BytesPerSector); - MmFreePool(clusterBuf); return 1; - } - } - prevCluster = cluster; - cluster = FatEntry(v, cluster); - } - - uint32_t newCluster = AppendCluster(v, prevCluster); - if (newCluster) { - *outLba = ClusterToLba(v, newCluster); *outOffset = 0; - MmFreePool(clusterBuf); return 1; - } - MmFreePool(clusterBuf); - return 0; -} - -// --------------------------------------------------------------------------- -// Open / Close -// --------------------------------------------------------------------------- -static KSTATUS Fat32Open(IO_FILESYSTEM *fs, const char *path, void **fileCtx) { - FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; - uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; - KSTATUS s = TraverseEx(v, path, &cluster, &size, &direntLba, &direntOffset); - if (!KSUCCESS(s)) return s; - FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); - if (!f) return STATUS_NO_MEMORY; - f->StartCluster = cluster; f->FileSize = size; f->Vol = v; - f->DirentLba = direntLba; f->DirentOffset = direntOffset; - *fileCtx = f; - return STATUS_SUCCESS; -} - -static void Fat32Close(IO_FILESYSTEM *fs, void *fileCtx) { - (void)fs; - FAT32_FILE *f = (FAT32_FILE *)fileCtx; - FlushFatCache(f->Vol); - MmFreePool(fileCtx); -} - -// --------------------------------------------------------------------------- -// Read (fix #4: contiguous-run coalescing) -// --------------------------------------------------------------------------- -// We peek ahead in the FAT chain: if cluster N+1 == cluster N physically, -// extend the current run instead of issuing a new ReadSectors call. -// Run length is capped at MAX_RUN_SECTORS to stay within the AHCI bounce -// buffer (128 sectors = 64 KB). -// -// FatEntry() calls in the peek loop are nearly free because they hit the -// FAT sector cache (fix #1). - -#define MAX_RUN_SECTORS 128u - -static KSTATUS Fat32Read(IO_FILESYSTEM *fs, void *fileCtx, - uint64_t offset, void *buf, - size_t size, size_t *bytesRead) { - (void)fs; - FAT32_FILE *f = (FAT32_FILE *)fileCtx; - FAT32_VOL *v = f->Vol; - - if (offset >= f->FileSize) { *bytesRead = 0; return STATUS_SUCCESS; } - if (offset + size > f->FileSize) size = (size_t)(f->FileSize - offset); - - // Single allocation for the largest possible coalesced run. - uint8_t *runBuf = (uint8_t *)MmAllocatePool(MAX_RUN_SECTORS * 512u); - if (!runBuf) return STATUS_NO_MEMORY; - - size_t totalRead = 0; - uint8_t *out = (uint8_t *)buf; - uint32_t skipClusters = (uint32_t)(offset / v->BytesPerCluster); - uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); - uint32_t cluster = f->StartCluster; - - for (uint32_t i = 0; i < skipClusters && cluster < FAT32_EOC; i++) - cluster = FatEntry(v, cluster); - - while (size > 0 && cluster >= 2 && cluster < FAT32_EOC) { - // Build the longest physically-contiguous run we can. - uint32_t runStart = cluster; - uint32_t runSectors = v->SectorsPerCluster; - uint32_t next = FatEntry(v, cluster); - - while (next == cluster + 1 && next < FAT32_EOC - && runSectors + v->SectorsPerCluster <= MAX_RUN_SECTORS) { - cluster = next; - next = FatEntry(v, cluster); - runSectors += v->SectorsPerCluster; - } - - if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, runStart), runSectors, runBuf))) - break; - - uint32_t runBytes = runSectors * 512u; - size_t avail = runBytes - offsetInCluster; - size_t copy = avail < size ? avail : size; - - memcpy(out, runBuf + offsetInCluster, copy); - out += copy; - totalRead += copy; - size -= copy; - offsetInCluster = 0; - cluster = next; - } - - MmFreePool(runBuf); - *bytesRead = totalRead; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// Write (fix #4: contiguous-run coalescing) -// --------------------------------------------------------------------------- -// Same strategy as read. Whole-cluster-aligned runs skip the read-modify- -// write; partial head/tail clusters still require it. - -static KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx, - uint64_t offset, const void *buf, - size_t size, size_t *bytesWritten) { - (void)fs; - if (!size) { *bytesWritten = 0; return STATUS_SUCCESS; } - - FAT32_FILE *f = (FAT32_FILE *)fileCtx; - FAT32_VOL *v = f->Vol; - const uint8_t *src = (const uint8_t *)buf; - - // Ensure at least one cluster exists. - if (f->StartCluster < 2) { - uint32_t first = AllocCluster(v); - if (!first) { *bytesWritten = 0; return STATUS_NO_MEMORY; } - f->StartCluster = first; - DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); - } - - // Walk to the starting cluster, allocating as needed. - uint32_t clusterIndex = (uint32_t)(offset / v->BytesPerCluster); - uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); - uint32_t cluster = f->StartCluster; - - for (uint32_t i = 0; i < clusterIndex; i++) { - uint32_t next = FatEntry(v, cluster); - if (next < 2 || next >= FAT32_EOC) { - next = AppendCluster(v, cluster); - if (!next) { *bytesWritten = 0; return STATUS_NO_MEMORY; } - } - cluster = next; - } - - uint8_t *runBuf = (uint8_t *)MmAllocatePool(MAX_RUN_SECTORS * 512u); - if (!runBuf) { *bytesWritten = 0; return STATUS_NO_MEMORY; } - - size_t written = 0, remaining = size; - - while (remaining > 0 && cluster >= 2) { - // Build a contiguous run (only when cluster-aligned to avoid a - // partial read-modify-write spanning multiple clusters). - uint32_t runStart = cluster; - uint32_t runSectors = v->SectorsPerCluster; - uint32_t next = FatEntry(v, cluster); - - if (offsetInCluster == 0) { - while (next == cluster + 1 && next < FAT32_EOC - && runSectors + v->SectorsPerCluster <= MAX_RUN_SECTORS) { - cluster = next; - next = FatEntry(v, cluster); - runSectors += v->SectorsPerCluster; - } - } - - uint32_t runBytes = runSectors * 512u; - size_t avail = runBytes - offsetInCluster; - size_t copy = avail < remaining ? avail : remaining; - - // Read existing data only for partial-cluster writes. - bool partial = (offsetInCluster > 0) || (copy < (size_t)runBytes); - if (partial) { - if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, runStart), runSectors, runBuf))) - break; - } - - memcpy(runBuf + offsetInCluster, src, copy); - - if (!KSUCCESS(WriteSectors(v, ClusterToLba(v, runStart), runSectors, runBuf))) - break; - - src += copy; - written += copy; - remaining -= copy; - offsetInCluster = 0; - - if (remaining > 0) { - // Advance: if end-of-chain, append a new cluster. - cluster = next; - if (cluster < 2 || cluster >= FAT32_EOC) { - // Find the actual last cluster in the run we just wrote. - uint32_t last = runStart; - for (uint32_t tmp = FatEntry(v, last); - tmp >= 2 && tmp < FAT32_EOC; - tmp = FatEntry(v, last)) - last = tmp; - cluster = AppendCluster(v, last); - if (!cluster) break; - } - } - } - - MmFreePool(runBuf); - - uint64_t newEnd = offset + written; - if (newEnd > f->FileSize) { - f->FileSize = (uint32_t)newEnd; - DirentUpdateSize(v, f->DirentLba, f->DirentOffset, f->FileSize); - } - - *bytesWritten = written; - return (written == size) ? STATUS_SUCCESS : STATUS_DEVICE_ERROR; -} - -// --------------------------------------------------------------------------- -// Create -// --------------------------------------------------------------------------- -static KSTATUS Fat32Create(IO_FILESYSTEM *fs, const char *path, void **fileCtx) { - FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; - char parentPath[256], filename[256]; - SplitPath(path, parentPath, filename); - if (!filename[0]) return STATUS_INVALID_PARAMETER; - - uint32_t parentCluster, parentSize; - KSTATUS s = Traverse(v, parentPath, &parentCluster, &parentSize); - if (!KSUCCESS(s)) return s; - - { uint32_t ec, es; uint8_t ea; - if (SearchDir(v, parentCluster, filename, &ec, &es, &ea, NULL, NULL)) - return STATUS_OBJECT_NAME_COLLISION; } - - uint64_t direntLba; uint32_t direntOffset; - if (!FindFreeDirent(v, parentCluster, &direntLba, &direntOffset)) - return STATUS_NO_MEMORY; - - uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); - if (!sec) return STATUS_NO_MEMORY; - s = ReadSectors(v, direntLba, 1, sec); - if (!KSUCCESS(s)) { MmFreePool(sec); return s; } - - DIRENT *d = (DIRENT *)(sec + direntOffset); - memset(d, 0, sizeof(DIRENT)); - Make83Name(filename, d->Name, d->Ext); - d->Attr = ATTR_ARCHIVE; - - s = WriteSectors(v, direntLba, 1, sec); - MmFreePool(sec); - if (!KSUCCESS(s)) return s; - - FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); - if (!f) return STATUS_NO_MEMORY; - f->StartCluster = 0; f->FileSize = 0; f->Vol = v; - f->DirentLba = direntLba; f->DirentOffset = direntOffset; - *fileCtx = f; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// Truncate -// --------------------------------------------------------------------------- -static KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize) { - (void)fs; - FAT32_FILE *f = (FAT32_FILE *)fileCtx; - FAT32_VOL *v = f->Vol; - if ((uint32_t)newSize == f->FileSize) return STATUS_SUCCESS; - - if (newSize == 0) { - if (f->StartCluster >= 2) { - FreeClusterChain(v, f->StartCluster); - f->StartCluster = 0; - DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, 0); - } - f->FileSize = 0; - KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, 0); - FlushFatCache(v); - return s; - } - - uint32_t newClusters = (uint32_t) - ((newSize + v->BytesPerCluster - 1) / v->BytesPerCluster); - - if (newSize < f->FileSize) { - if (f->StartCluster < 2) goto update_size; - uint32_t cluster = f->StartCluster, lastGood = cluster; - for (uint32_t i = 1; i < newClusters && cluster < FAT32_EOC; i++) { - lastGood = cluster; cluster = FatEntry(v, cluster); - } - uint32_t tail = FatEntry(v, lastGood); - WriteFatEntry(v, lastGood, FAT32_EOC_MARK); - FreeClusterChain(v, tail); - } else { - uint32_t curClusters = f->FileSize - ? (f->FileSize + v->BytesPerCluster - 1) / v->BytesPerCluster : 0; - uint32_t lastCluster = 0; - if (f->StartCluster < 2) { - uint32_t first = AllocCluster(v); - if (!first) return STATUS_NO_MEMORY; - f->StartCluster = first; - DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); - lastCluster = first; curClusters = 1; - } else { - lastCluster = GetChainEnd(v, f->StartCluster, NULL); - } - for (uint32_t i = curClusters; i < newClusters; i++) { - uint32_t next = AppendCluster(v, lastCluster); - if (!next) return STATUS_NO_MEMORY; - lastCluster = next; - } - } - -update_size: - f->FileSize = (uint32_t)newSize; - { KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, (uint32_t)newSize); - FlushFatCache(v); - return s; } -} - -// --------------------------------------------------------------------------- -// Delete -// --------------------------------------------------------------------------- -static KSTATUS Fat32Delete(IO_FILESYSTEM *fs, const char *path) { - FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; - uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; - KSTATUS s = TraverseEx(v, path, &cluster, &size, &direntLba, &direntOffset); - if (!KSUCCESS(s)) return s; - if (!direntLba) return STATUS_ACCESS_DENIED; - - uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); - if (!sec) return STATUS_NO_MEMORY; - s = ReadSectors(v, direntLba, 1, sec); - if (KSUCCESS(s)) { sec[direntOffset] = 0xE5; s = WriteSectors(v, direntLba, 1, sec); } - MmFreePool(sec); - if (!KSUCCESS(s)) return s; - - if (cluster >= 2) FreeClusterChain(v, cluster); - FlushFatCache(v); - return STATUS_SUCCESS; -} - -static uint64_t Fat32GetSize(IO_FILESYSTEM *fs, void *fileCtx) { - (void)fs; return ((FAT32_FILE *)fileCtx)->FileSize; -} - -// --------------------------------------------------------------------------- -// Directory enumeration (fix #2: cluster buffer cached in context) -// --------------------------------------------------------------------------- -// Previously: allocate + ReadSectors + return 1 entry + free, every call. -// Now: buffer lives in FAT32_DIR_CTX; ReadSectors fires only on cluster -// boundaries. - -typedef struct { - FAT32_VOL *Vol; - uint32_t DirCluster; - uint32_t CurCluster; - uint32_t LoadedCluster; // which cluster is currently in ClusterBuf - uint32_t EntryIndex; - char LfnBuf[MAX_LFN]; - int HasLfn; - uint8_t *ClusterBuf; // allocated once in OpenDir, freed in CloseDir -} FAT32_DIR_CTX; - -static KSTATUS Fat32OpenDir(IO_FILESYSTEM *fs, const char *dirPath, void **dirCtx) { - FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; - uint32_t cluster; - if (!dirPath || dirPath[0] == '\0') { - cluster = v->RootCluster; - } else { - uint32_t size; KSTATUS s = Traverse(v, dirPath, &cluster, &size); - if (!KSUCCESS(s)) return s; - } - - FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)MmAllocatePool(sizeof(FAT32_DIR_CTX)); - if (!ctx) return STATUS_NO_MEMORY; - - ctx->ClusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); - if (!ctx->ClusterBuf) { MmFreePool(ctx); return STATUS_NO_MEMORY; } - - ctx->Vol = v; - ctx->DirCluster = cluster; - ctx->CurCluster = cluster; - ctx->LoadedCluster = (uint32_t)-1; - ctx->EntryIndex = 0; - ctx->HasLfn = 0; - memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); - *dirCtx = ctx; - return STATUS_SUCCESS; -} - -static KSTATUS Fat32ReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out) { - (void)fs; - FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)dirCtx; - FAT32_VOL *v = ctx->Vol; - uint32_t epc = v->BytesPerCluster / sizeof(DIRENT); - - while (ctx->CurCluster >= 2 && ctx->CurCluster < FAT32_EOC) { - // Load the cluster only when crossing a cluster boundary. - if (ctx->CurCluster != ctx->LoadedCluster) { - if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, ctx->CurCluster), - v->SectorsPerCluster, ctx->ClusterBuf))) - break; - ctx->LoadedCluster = ctx->CurCluster; - } - - DIRENT *dir = (DIRENT *)ctx->ClusterBuf; - while (ctx->EntryIndex < epc) { - DIRENT *e = &dir[ctx->EntryIndex++]; - - if (e->Name[0] == 0x00) { ctx->CurCluster = FAT32_EOC; return STATUS_NO_MORE_FILES; } - if ((uint8_t)e->Name[0] == 0xE5) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } - if (e->Attr == ATTR_LFN) { LfnAppend(ctx->LfnBuf, (LFN_ENTRY *)e); ctx->HasLfn = 1; continue; } - if (e->Attr & 0x08 && !(e->Attr & ATTR_DIRECTORY)) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } - - if (ctx->HasLfn && ctx->LfnBuf[0]) { - size_t len = strlen(ctx->LfnBuf); - if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; - memcpy(out->Name, ctx->LfnBuf, len); out->Name[len] = '\0'; - } else { - char name83[13]; int n = 0; - for (int i = 0; i < 8 && e->Name[i] != ' '; i++) - name83[n++] = (char)(e->Name[i] >= 'A' && e->Name[i] <= 'Z' ? e->Name[i]+32 : e->Name[i]); - if (e->Ext[0] != ' ') { - name83[n++] = '.'; - for (int i = 0; i < 3 && e->Ext[i] != ' '; i++) - name83[n++] = (char)(e->Ext[i] >= 'A' && e->Ext[i] <= 'Z' ? e->Ext[i]+32 : e->Ext[i]); - } - name83[n] = '\0'; - size_t len = (size_t)n; if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; - memcpy(out->Name, name83, len); out->Name[len] = '\0'; - } - - out->Attr = e->Attr; out->FileSize = e->FileSize; - ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); - return STATUS_SUCCESS; - } - - ctx->CurCluster = FatEntry(v, ctx->CurCluster); - ctx->EntryIndex = 0; ctx->HasLfn = 0; - memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); - } - - return STATUS_NO_MORE_FILES; -} - -static void Fat32CloseDir(IO_FILESYSTEM *fs, void *dirCtx) { - (void)fs; - FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)dirCtx; - MmFreePool(ctx->ClusterBuf); - MmFreePool(ctx); -} - -// --------------------------------------------------------------------------- -// Partition scan (MBR + GPT) — unchanged -// --------------------------------------------------------------------------- -static const EFI_GUID GUID_EFI_SYSTEM = { 0xC12A7328, 0xF81F, 0x11D2, {0xBA,0x4B,0x00,0xA0,0xC9,0x3E,0xC9,0x3B} }; -static const EFI_GUID GUID_MS_BASIC_DATA = { 0xEBD0A0A2, 0xB9E5, 0x4433, {0x87,0xC0,0x68,0xB6,0xB7,0x26,0x99,0xC7} }; -static const EFI_GUID GUID_LINUX_BASIC_DATA = { 0x0FC63DAF, 0x8483, 0x4772, {0x8E,0x79,0x3D,0x69,0xD8,0x47,0x7D,0xE4} }; - -static int IsFat32Vbr(FAT32_VOL *v, uint32_t lba) { - uint8_t sector[512]; - if (!KSUCCESS(ReadSectors(v, lba, 1, sector))) return 0; - if (sector[510] != 0x55 || sector[511] != 0xAA) return 0; - BPB *bpb = (BPB *)sector; - return bpb->FatSize16 == 0 && bpb->RootEntryCount == 0 && bpb->FatSize32 != 0; -} - -static uint32_t FindFat32Partition(FAT32_VOL *v) { - uint8_t mbr[512]; - if (!KSUCCESS(ReadSectors(v, 0, 1, mbr))) return 0; - if (mbr[510] != 0x55 || mbr[511] != 0xAA) return 0; - - int isGpt = 0; - for (int i = 0; i < 4; i++) if (mbr[0x1BE + i*16 + 4] == 0xEE) { isGpt = 1; break; } - - if (isGpt) { - uint8_t gptSector[512]; - if (!KSUCCESS(ReadSectors(v, 1, 1, gptSector))) return 0; - GPT_HEADER *gpt = (GPT_HEADER *)gptSector; - if (memcmp(gpt->Signature, "EFI PART", 8) != 0) return 0; - - uint64_t entryLBA = gpt->PartitionEntryLBA; - uint32_t numEntries = gpt->NumberOfPartitionEntries; - uint32_t entrySize = gpt->SizeOfPartitionEntry; - if (entrySize < sizeof(GPT_ENTRY)) return 0; - - uint32_t eps = 512 / entrySize; uint8_t entrySector[512]; - for (uint32_t i = 0; i < numEntries; i++) { - if (i % eps == 0) - if (!KSUCCESS(ReadSectors(v, entryLBA + i/eps, 1, entrySector))) return 0; - GPT_ENTRY *entry = (GPT_ENTRY *)(entrySector + (i % eps) * entrySize); - static const EFI_GUID emptyGuid = {0}; - if (memcmp(&entry->PartitionTypeGUID, &emptyGuid, sizeof(EFI_GUID)) == 0) continue; - if (memcmp(&entry->PartitionTypeGUID, &GUID_EFI_SYSTEM, sizeof(EFI_GUID)) == 0 || - memcmp(&entry->PartitionTypeGUID, &GUID_MS_BASIC_DATA, sizeof(EFI_GUID)) == 0 || - memcmp(&entry->PartitionTypeGUID, &GUID_LINUX_BASIC_DATA, sizeof(EFI_GUID)) == 0) { - uint32_t lba = (uint32_t)entry->StartingLBA; - if (IsFat32Vbr(v, lba)) return lba; - } - } - return 0; - } - - for (int i = 0; i < 4; i++) { - uint8_t *entry = mbr + 0x1BE + i*16; - if (entry[4] == 0x0B || entry[4] == 0x0C) { - uint32_t lba = *(uint32_t *)(entry + 8); - if (IsFat32Vbr(v, lba)) return lba; - } - } - return 0; -} - -// --------------------------------------------------------------------------- -// DriverEntry -// --------------------------------------------------------------------------- -static FAT32_VOL gVol; -static IO_FILESYSTEM gFs; - -KSTATUS DriverEntry(const HAL_API *hal) { - DbgPrint("FAT32: Init\n"); - if (!hal) return STATUS_INVALID_PARAMETER; - - FatCacheInvalidate(); - - gVol.Hal = hal; - gVol.DriveIndex = 0; - - uint32_t partLba = FindFat32Partition(&gVol); - if (!partLba) { DbgPrint("FAT32: No FAT32 volume found.\n"); return STATUS_NOT_SUPPORTED; } - gVol.PartitionLba = partLba; - - uint8_t sector[512]; - if (!KSUCCESS(ReadSectors(&gVol, partLba, 1, sector))) return STATUS_DEVICE_ERROR; - - BPB *bpb = (BPB *)sector; - if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) return STATUS_NOT_SUPPORTED; - - gVol.BytesPerSector = bpb->BytesPerSector; - gVol.SectorsPerCluster = bpb->SectorsPerCluster; - gVol.BytesPerCluster = bpb->BytesPerSector * bpb->SectorsPerCluster; - gVol.ReservedSectors = bpb->ReservedSectors; - gVol.NumFats = bpb->NumFats; - gVol.FatSize = bpb->FatSize32; - gVol.RootCluster = bpb->RootCluster; - gVol.DataStart = partLba + bpb->ReservedSectors + bpb->NumFats * bpb->FatSize32; - gVol.NextFreeHint = 2; - - uint32_t totalSectors = bpb->TotalSectors16 ? bpb->TotalSectors16 : bpb->TotalSectors32; - uint32_t dataSectors = totalSectors - bpb->ReservedSectors - bpb->NumFats * bpb->FatSize32; - gVol.TotalClusters = dataSectors / bpb->SectorsPerCluster; - - gFs.Open = Fat32Open; - gFs.Read = Fat32Read; - gFs.Write = Fat32Write; - gFs.Create = Fat32Create; - gFs.Delete = Fat32Delete; - gFs.Truncate = Fat32Truncate; - gFs.GetSize = Fat32GetSize; - gFs.Close = Fat32Close; - gFs.DriverCtx = &gVol; - gFs.OpenDir = Fat32OpenDir; - gFs.ReadDir = Fat32ReadDir; - gFs.CloseDir = Fat32CloseDir; - - IO_VOLUME_PARAMS volParams; - volParams.DiskIndex = gVol.DriveIndex; - volParams.PartitionLba = partLba; - volParams.PartitionSize = totalSectors; - - KSTATUS status = IoRegisterFilesystem(gVol.DriveIndex, &gFs, &volParams); - DbgPrint("FAT32: Init complete (status=%d, clusters=%u).\n", status, gVol.TotalClusters); - return STATUS_SUCCESS; -} +// fat32.c - FAT32 filesystem driver +// Read + write support: file data, cluster allocation, directory entries. +// +// Performance improvements over the original: +// +// 1. FAT sector cache (FAT_CACHE_SLOTS write-back entries). +// FatEntry() and WriteFatEntry() go through the cache; the hot FAT +// region stays in RAM instead of being read from disk on every chain +// traversal step. A 32-slot cache covers 32 x (512/4) = 4096 +// consecutive cluster entries — enough to keep the entire FAT of a +// small partition in memory, and to avoid repeated reads even on large +// ones. +// +// 2. Fat32ReadDir() cluster buffer cached in FAT32_DIR_CTX. +// The original re-read the same cluster from disk on every call +// (allocate -> read -> return one entry -> free). Now the buffer is +// allocated once in Fat32OpenDir, freed in Fat32CloseDir, and only +// re-read when the iteration moves to a new cluster. +// +// 3. AllocCluster() next-free hint. +// The search now starts from the cluster after the last allocation +// (gVol.NextFreeHint) and wraps around once, so repeated allocations +// on a fragmented volume don't rescan from sector 0 every time. +// +// 4. Contiguous-cluster run coalescing in Fat32Read() and Fat32Write(). +// After fetching a cluster's FAT entry the code peeks ahead: if the +// next cluster is physically adjacent (cluster+1) it extends the run +// rather than issuing a separate ReadSectors/WriteSectors call. A +// freshly-written unfragmented file now transfers in one or a handful +// of large DMA bursts instead of SectorsPerCluster-sized slices. +// +// 5. FAT write batching via a dirty-sector cache. +// WriteFatEntry() marks modified FAT cache slots dirty instead of +// flushing immediately. FlushFatCache() (called from Fat32Close, +// Fat32Truncate, and Fat32Delete) writes each dirty slot once for +// every FAT copy, collapsing the 2x(read+write) per-entry cost of the +// original into a single write per dirty sector per FAT copy. + +#include +#include +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// On-disk structures +// --------------------------------------------------------------------------- +#pragma pack(push, 1) + +typedef struct { + uint8_t Jump[3]; + char OemId[8]; + uint16_t BytesPerSector; + uint8_t SectorsPerCluster; + uint16_t ReservedSectors; + uint8_t NumFats; + uint16_t RootEntryCount; + uint16_t TotalSectors16; + uint8_t MediaType; + uint16_t FatSize16; + uint16_t SectorsPerTrack; + uint16_t NumHeads; + uint32_t HiddenSectors; + uint32_t TotalSectors32; + uint32_t FatSize32; + uint16_t ExtFlags; + uint16_t FsVersion; + uint32_t RootCluster; + uint16_t FsInfoSector; + uint16_t BackupBootSector; + uint8_t Reserved[12]; + uint8_t DriveNumber; + uint8_t Reserved1; + uint8_t BootSignature; + uint32_t VolumeId; + char VolumeLabel[11]; + char FsType[8]; +} BPB; + +typedef struct { + char Name[8]; + char Ext[3]; + uint8_t Attr; + uint8_t Reserved; + uint8_t CrtTimeTenth; + uint16_t CrtTime; + uint16_t CrtDate; + uint16_t LastAccDate; + uint16_t FirstClusterHi; + uint16_t WrtTime; + uint16_t WrtDate; + uint16_t FirstClusterLo; + uint32_t FileSize; +} DIRENT; + +typedef struct { + uint8_t Order; + uint16_t Name1[5]; + uint8_t Attr; + uint8_t Type; + uint8_t Checksum; + uint16_t Name2[6]; + uint16_t FirstCluster; + uint16_t Name3[2]; +} LFN_ENTRY; + +typedef struct { + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; +} EFI_GUID; + +typedef struct { + char Signature[8]; + uint32_t Revision; + uint32_t HeaderSize; + uint32_t HeaderCRC32; + uint32_t Reserved; + uint64_t MyLBA; + uint64_t AlternateLBA; + uint64_t FirstUsableLBA; + uint64_t LastUsableLBA; + EFI_GUID DiskGUID; + uint64_t PartitionEntryLBA; + uint32_t NumberOfPartitionEntries; + uint32_t SizeOfPartitionEntry; + uint32_t PartitionEntryArrayCRC32; +} GPT_HEADER; + +typedef struct { + EFI_GUID PartitionTypeGUID; + EFI_GUID UniquePartitionGUID; + uint64_t StartingLBA; + uint64_t EndingLBA; + uint64_t Attributes; + uint16_t PartitionName[36]; +} GPT_ENTRY; + +#pragma pack(pop) + +#define ATTR_DIRECTORY 0x10 +#define ATTR_ARCHIVE 0x20 +#define ATTR_LFN 0x0F +#define FAT32_EOC 0x0FFFFFF8u +#define FAT32_EOC_MARK 0x0FFFFFFFu +#define FAT32_FREE 0x00000000u + +// --------------------------------------------------------------------------- +// FAT sector cache (fixes #1 and #5) +// --------------------------------------------------------------------------- +// 32 direct-mapped slots, indexed by (lba % FAT_CACHE_SLOTS). +// Write-back: WriteFatEntry modifies the in-cache bytes and marks the slot +// dirty. FlushFatCache() propagates dirty slots to every FAT copy at once, +// replacing the original per-entry 2x(read+write) pattern with a single +// write per dirty sector per FAT copy. + +#define FAT_CACHE_SLOTS 32 + +typedef struct { + uint64_t lba; + uint8_t data[512]; + bool valid; + bool dirty; +} FAT_CACHE_ENTRY; + +static FAT_CACHE_ENTRY gFatCache[FAT_CACHE_SLOTS]; + +// FAT32_VOL forward declaration (needed by cache helpers). +typedef struct FAT32_VOL_S FAT32_VOL; + +// Raw (non-cached) sector I/O — forward declarations. +static KSTATUS ReadSectorsRaw (FAT32_VOL *v, uint64_t lba, uint32_t count, void *buf); +static KSTATUS WriteSectorsRaw(FAT32_VOL *v, uint64_t lba, uint32_t count, const void *buf); + +// Read one FAT sector through the cache. +// Returns a pointer into the cache buffer (valid until the next eviction). +static KSTATUS FatCacheRead(FAT32_VOL *v, uint64_t lba, uint8_t **out) { + uint32_t slot = (uint32_t)(lba % FAT_CACHE_SLOTS); + FAT_CACHE_ENTRY *e = &gFatCache[slot]; + + if (e->valid && e->lba == lba) { + *out = e->data; + return STATUS_SUCCESS; + } + + // Evict current occupant: single-copy writeback on collision. + // The full multi-copy flush happens in FlushFatCache(). + if (e->valid && e->dirty) { + WriteSectorsRaw(v, e->lba, 1, e->data); + e->dirty = false; + } + + KSTATUS s = ReadSectorsRaw(v, lba, 1, e->data); + if (!KSUCCESS(s)) { e->valid = false; return s; } + + e->lba = lba; + e->valid = true; + e->dirty = false; + *out = e->data; + return STATUS_SUCCESS; +} + +static void FatCacheMarkDirty(uint64_t lba) { + uint32_t slot = (uint32_t)(lba % FAT_CACHE_SLOTS); + if (gFatCache[slot].valid && gFatCache[slot].lba == lba) + gFatCache[slot].dirty = true; +} + +static void FatCacheInvalidate(void) { + for (int i = 0; i < FAT_CACHE_SLOTS; i++) { + gFatCache[i].valid = false; + gFatCache[i].dirty = false; + } +} + +// FlushFatCache — write every dirty slot to all FAT copies, clear dirty flag. +// Defined after FAT32_VOL is fully declared (below). +static void FlushFatCache(FAT32_VOL *v); + +// --------------------------------------------------------------------------- +// Driver state +// --------------------------------------------------------------------------- +struct FAT32_VOL_S { + uint8_t DriveIndex; + uint32_t PartitionLba; + uint32_t BytesPerSector; + uint32_t SectorsPerCluster; + uint32_t BytesPerCluster; + uint32_t ReservedSectors; + uint32_t NumFats; + uint32_t FatSize; // sectors per FAT copy + uint32_t RootCluster; + uint32_t DataStart; // absolute LBA of cluster 2 + uint32_t TotalClusters; // usable data clusters + uint32_t NextFreeHint; // fix #3: start cluster for next free-scan + const HAL_API *Hal; +}; + +typedef struct { + uint32_t StartCluster; + uint32_t FileSize; + FAT32_VOL *Vol; + uint64_t DirentLba; + uint32_t DirentOffset; +} FAT32_FILE; + +// --------------------------------------------------------------------------- +// Raw sector I/O (bypasses FAT cache; used for file data and cache eviction) +// --------------------------------------------------------------------------- +static KSTATUS ReadSectorsRaw(FAT32_VOL *v, uint64_t lba, + uint32_t count, void *buf) { + if (!v || !v->Hal || !v->Hal->Disk.ReadSectors) + return STATUS_INVALID_PARAMETER; + return v->Hal->Disk.ReadSectors(v->DriveIndex, lba, count, buf); +} + +static KSTATUS WriteSectorsRaw(FAT32_VOL *v, uint64_t lba, + uint32_t count, const void *buf) { + if (!v || !v->Hal || !v->Hal->Disk.WriteSectors) + return STATUS_INVALID_PARAMETER; + return v->Hal->Disk.WriteSectors(v->DriveIndex, lba, count, buf); +} + +// Convenience aliases for file-data I/O. +static KSTATUS ReadSectors (FAT32_VOL *v, uint64_t lba, uint32_t n, void *b) { return ReadSectorsRaw (v, lba, n, b); } +static KSTATUS WriteSectors(FAT32_VOL *v, uint64_t lba, uint32_t n, const void *b) { return WriteSectorsRaw(v, lba, n, b); } + +static uint64_t ClusterToLba(FAT32_VOL *v, uint32_t cluster) { + return v->DataStart + (uint64_t)(cluster - 2) * v->SectorsPerCluster; +} + +// --------------------------------------------------------------------------- +// FlushFatCache — needs full FAT32_VOL definition +// --------------------------------------------------------------------------- +static void FlushFatCache(FAT32_VOL *v) { + uint64_t fat0Start = (uint64_t)v->PartitionLba + v->ReservedSectors; + + for (int i = 0; i < FAT_CACHE_SLOTS; i++) { + FAT_CACHE_ENTRY *e = &gFatCache[i]; + if (!e->valid || !e->dirty) continue; + if (e->lba < fat0Start) continue; + + uint64_t sectorIdx = e->lba - fat0Start; + if (sectorIdx >= v->FatSize) continue; + + for (uint32_t f = 0; f < v->NumFats; f++) { + uint64_t lba = fat0Start + (uint64_t)f * v->FatSize + sectorIdx; + WriteSectorsRaw(v, lba, 1, e->data); + } + e->dirty = false; + } +} + +// --------------------------------------------------------------------------- +// FAT read / write (through cache) +// --------------------------------------------------------------------------- + +static uint32_t FatEntry(FAT32_VOL *v, uint32_t cluster) { + uint32_t fatOffset = cluster * 4; + uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors + + fatOffset / v->BytesPerSector; + uint32_t offInSec = fatOffset % v->BytesPerSector; + + uint8_t *sec; + if (!KSUCCESS(FatCacheRead(v, sector, &sec))) return 0; + return *(uint32_t *)(sec + offInSec) & 0x0FFFFFFF; +} + +static KSTATUS WriteFatEntry(FAT32_VOL *v, uint32_t cluster, uint32_t value) { + uint32_t fatOffset = cluster * 4; + uint64_t sector = (uint64_t)v->PartitionLba + v->ReservedSectors + + fatOffset / v->BytesPerSector; + uint32_t offInSec = fatOffset % v->BytesPerSector; + + uint8_t *sec; + KSTATUS s = FatCacheRead(v, sector, &sec); + if (!KSUCCESS(s)) return s; + + uint32_t existing = *(uint32_t *)(sec + offInSec); + *(uint32_t *)(sec + offInSec) = (existing & 0xF0000000u) + | (value & 0x0FFFFFFFu); + FatCacheMarkDirty(sector); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// Cluster allocation (fix #3: next-free hint + two-phase wrap-around scan) +// --------------------------------------------------------------------------- +static uint32_t AllocCluster(FAT32_VOL *v) { + if (v->TotalClusters == 0) return 0; + + uint32_t total = v->TotalClusters; + uint32_t start = v->NextFreeHint; + if (start < 2 || start >= total + 2) start = 2; + + for (uint32_t pass = 0; pass < 2; pass++) { + uint32_t lo = (pass == 0) ? start : 2; + uint32_t hi = (pass == 0) ? total+2 : start; + for (uint32_t c = lo; c < hi; c++) { + if (FatEntry(v, c) == FAT32_FREE) { + if (!KSUCCESS(WriteFatEntry(v, c, FAT32_EOC_MARK))) return 0; + v->NextFreeHint = c + 1; + + // Zero the cluster (required for directory correctness). + uint8_t *zero = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + if (zero) { + memset(zero, 0, v->BytesPerCluster); + WriteSectors(v, ClusterToLba(v, c), v->SectorsPerCluster, zero); + MmFreePool(zero); + } + return c; + } + } + } + return 0; +} + +static uint32_t AppendCluster(FAT32_VOL *v, uint32_t lastCluster) { + uint32_t newCluster = AllocCluster(v); + if (!newCluster) return 0; + if (!KSUCCESS(WriteFatEntry(v, lastCluster, newCluster))) return 0; + return newCluster; +} + +static uint32_t GetChainEnd(FAT32_VOL *v, uint32_t start, uint32_t *outCount) { + uint32_t cluster = start, prev = start, count = 0; + while (cluster >= 2 && cluster < FAT32_EOC) { + prev = cluster; count++; + cluster = FatEntry(v, cluster); + } + if (outCount) *outCount = count; + return prev; +} + +static void FreeClusterChain(FAT32_VOL *v, uint32_t start) { + uint32_t cluster = start; + while (cluster >= 2 && cluster < FAT32_EOC) { + uint32_t next = FatEntry(v, cluster); + WriteFatEntry(v, cluster, FAT32_FREE); + if (cluster < v->NextFreeHint) v->NextFreeHint = cluster; + cluster = next; + } +} + +// --------------------------------------------------------------------------- +// DIRENT update helpers +// --------------------------------------------------------------------------- +static KSTATUS DirentUpdateSize(FAT32_VOL *v, uint64_t lba, + uint32_t offset, uint32_t size) { + uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); + if (!sec) return STATUS_NO_MEMORY; + KSTATUS s = ReadSectors(v, lba, 1, sec); + if (KSUCCESS(s)) { + ((DIRENT *)(sec + offset))->FileSize = size; + s = WriteSectors(v, lba, 1, sec); + } + MmFreePool(sec); + return s; +} + +static KSTATUS DirentUpdateFirstCluster(FAT32_VOL *v, uint64_t lba, + uint32_t offset, uint32_t cluster) { + uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); + if (!sec) return STATUS_NO_MEMORY; + KSTATUS s = ReadSectors(v, lba, 1, sec); + if (KSUCCESS(s)) { + DIRENT *d = (DIRENT *)(sec + offset); + d->FirstClusterHi = (uint16_t)(cluster >> 16); + d->FirstClusterLo = (uint16_t)(cluster & 0xFFFF); + s = WriteSectors(v, lba, 1, sec); + } + MmFreePool(sec); + return s; +} + +// --------------------------------------------------------------------------- +// 8.3 name matching / assembly +// --------------------------------------------------------------------------- +static int MatchName83(const char *dirName, const char *dirExt, + const char *want) { + char buf[13]; int n = 0; + for (int i = 0; i < 8 && dirName[i] != ' '; i++) + buf[n++] = (char)(dirName[i] >= 'A' && dirName[i] <= 'Z' ? dirName[i]+32 : dirName[i]); + if (dirExt[0] != ' ') { + buf[n++] = '.'; + for (int i = 0; i < 3 && dirExt[i] != ' '; i++) + buf[n++] = (char)(dirExt[i] >= 'A' && dirExt[i] <= 'Z' ? dirExt[i]+32 : dirExt[i]); + } + buf[n] = '\0'; + const char *p = buf; + while (*want && *p) { + char a = *p++, b = *want++; + if (a >= 'A' && a <= 'Z') a += 32; + if (b >= 'A' && b <= 'Z') b += 32; + if (a != b) return 0; + } + return *want == '\0' && *p == '\0'; +} + +static void Make83Name(const char *src, char name8[8], char ext3[3]) { + memset(name8, ' ', 8); memset(ext3, ' ', 3); + const char *dot = NULL; + for (const char *p = src; *p; p++) if (*p == '.') dot = p; + int ni = 0; + const char *end = dot ? dot : src + strlen(src); + for (const char *p = src; p < end && ni < 8; p++) { + char c = *p; if (c >= 'a' && c <= 'z') c -= 32; name8[ni++] = c; + } + if (dot) { + int ei = 0; + for (const char *p = dot+1; *p && ei < 3; p++) { + char c = *p; if (c >= 'a' && c <= 'z') c -= 32; ext3[ei++] = c; + } + } +} + +// --------------------------------------------------------------------------- +// LFN assembly / matching +// --------------------------------------------------------------------------- +#define MAX_LFN 256 + +static void LfnAppend(char *lfn, const LFN_ENTRY *e) { + int seq = (e->Order & 0x3F) - 1; + if (seq < 0 || seq >= (MAX_LFN / 13)) return; + int pos = seq * 13; + uint16_t chars[13]; int idx = 0; + for (int i = 0; i < 5; i++) chars[idx++] = e->Name1[i]; + for (int i = 0; i < 6; i++) chars[idx++] = e->Name2[i]; + for (int i = 0; i < 2; i++) chars[idx++] = e->Name3[i]; + for (int i = 0; i < 13 && pos < MAX_LFN-1; i++, pos++) { + uint16_t c = chars[i]; + if (c == 0x0000 || c == 0xFFFF) { lfn[pos] = '\0'; break; } + lfn[pos] = (char)(c & 0x7F); + } +} + +static int LfnMatch(const char *lfn, const char *want) { + while (*lfn && *want) { + char a = *lfn++, b = *want++; + if (a >= 'A' && a <= 'Z') a += 32; + if (b >= 'A' && b <= 'Z') b += 32; + if (a != b) return 0; + } + return *lfn == '\0' && *want == '\0'; +} + +// --------------------------------------------------------------------------- +// Directory search +// --------------------------------------------------------------------------- +static int SearchDir(FAT32_VOL *v, uint32_t dirCluster, const char *name, + uint32_t *outCluster, uint32_t *outSize, uint8_t *outAttr, + uint64_t *outDirentLba, uint32_t *outDirentOffset) { + uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + if (!clusterBuf) return 0; + + char lfn[MAX_LFN]; int hasLfn = 0; memset(lfn, 0, sizeof(lfn)); + uint32_t cluster = dirCluster; + + while (cluster >= 2 && cluster < FAT32_EOC) { + uint64_t clusterLba = ClusterToLba(v, cluster); + if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) break; + + uint32_t entries = v->BytesPerCluster / sizeof(DIRENT); + DIRENT *dir = (DIRENT *)clusterBuf; + + for (uint32_t i = 0; i < entries; i++) { + if (dir[i].Name[0] == 0x00) goto done; + if ((uint8_t)dir[i].Name[0] == 0xE5) { hasLfn = 0; memset(lfn, 0, sizeof(lfn)); continue; } + if (dir[i].Attr == ATTR_LFN) { LfnAppend(lfn, (LFN_ENTRY *)&dir[i]); hasLfn = 1; continue; } + + int match = 0; + if (hasLfn && lfn[0]) match = LfnMatch(lfn, name); + if (!match) match = MatchName83(dir[i].Name, dir[i].Ext, name); + hasLfn = 0; memset(lfn, 0, sizeof(lfn)); + + if (match) { + *outCluster = ((uint32_t)dir[i].FirstClusterHi << 16) | dir[i].FirstClusterLo; + *outSize = dir[i].FileSize; + *outAttr = dir[i].Attr; + if (outDirentLba || outDirentOffset) { + uint64_t eb = (uint64_t)i * sizeof(DIRENT); + if (outDirentLba) *outDirentLba = clusterLba + eb / v->BytesPerSector; + if (outDirentOffset) *outDirentOffset = (uint32_t)(eb % v->BytesPerSector); + } + MmFreePool(clusterBuf); + return 1; + } + } + cluster = FatEntry(v, cluster); + } +done: + MmFreePool(clusterBuf); + return 0; +} + +// --------------------------------------------------------------------------- +// Path traversal +// --------------------------------------------------------------------------- +static KSTATUS TraverseEx(FAT32_VOL *v, const char *path, + uint32_t *outCluster, uint32_t *outSize, + uint64_t *outDirentLba, uint32_t *outDirentOffset) { + uint32_t cluster = v->RootCluster, size = 0; + uint8_t attr = ATTR_DIRECTORY; + if (outDirentLba) *outDirentLba = 0; + if (outDirentOffset) *outDirentOffset = 0; + + char component[256]; const char *p = path; + while (*p) { + while (*p == '\\' || *p == '/') p++; + if (!*p) break; + int len = 0; + while (*p && *p != '\\' && *p != '/' && len < 255) component[len++] = *p++; + component[len] = '\0'; + if (!len) break; + if (!(attr & ATTR_DIRECTORY)) return STATUS_NOT_FOUND; + + uint32_t childCluster, childSize; uint8_t childAttr; + uint64_t childLba = 0; uint32_t childOffset = 0; + if (!SearchDir(v, cluster, component, &childCluster, &childSize, + &childAttr, &childLba, &childOffset)) + return STATUS_NOT_FOUND; + + cluster = childCluster; size = childSize; attr = childAttr; + if (outDirentLba) *outDirentLba = childLba; + if (outDirentOffset) *outDirentOffset = childOffset; + } + *outCluster = cluster; *outSize = size; + return STATUS_SUCCESS; +} + +static KSTATUS Traverse(FAT32_VOL *v, const char *path, + uint32_t *outCluster, uint32_t *outSize) { + return TraverseEx(v, path, outCluster, outSize, NULL, NULL); +} + +// --------------------------------------------------------------------------- +// Path helpers for Fat32Create +// --------------------------------------------------------------------------- +static void SplitPath(const char *path, char parent[256], char name[256]) { + const char *last = NULL; + for (const char *p = path; *p; p++) if (*p == '\\' || *p == '/') last = p; + if (!last) { parent[0] = '\0'; strncpy(name, path, 255); name[255] = '\0'; } + else { + size_t len = (size_t)(last - path); if (len > 255) len = 255; + memcpy(parent, path, len); parent[len] = '\0'; + strncpy(name, last+1, 255); name[255] = '\0'; + } +} + +// --------------------------------------------------------------------------- +// Find a free DIRENT slot +// --------------------------------------------------------------------------- +static int FindFreeDirent(FAT32_VOL *v, uint32_t dirCluster, + uint64_t *outLba, uint32_t *outOffset) { + uint8_t *clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + if (!clusterBuf) return 0; + + uint32_t cluster = dirCluster, prevCluster = dirCluster; + while (cluster >= 2 && cluster < FAT32_EOC) { + uint64_t clusterLba = ClusterToLba(v, cluster); + if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) { + MmFreePool(clusterBuf); return 0; + } + uint32_t entries = v->BytesPerCluster / sizeof(DIRENT); + DIRENT *dir = (DIRENT *)clusterBuf; + for (uint32_t i = 0; i < entries; i++) { + uint8_t first = (uint8_t)dir[i].Name[0]; + if (first == 0xE5 || first == 0x00) { + uint64_t eb = (uint64_t)i * sizeof(DIRENT); + *outLba = clusterLba + eb / v->BytesPerSector; + *outOffset = (uint32_t)(eb % v->BytesPerSector); + MmFreePool(clusterBuf); return 1; + } + } + prevCluster = cluster; + cluster = FatEntry(v, cluster); + } + + uint32_t newCluster = AppendCluster(v, prevCluster); + if (newCluster) { + *outLba = ClusterToLba(v, newCluster); *outOffset = 0; + MmFreePool(clusterBuf); return 1; + } + MmFreePool(clusterBuf); + return 0; +} + +// --------------------------------------------------------------------------- +// Open / Close +// --------------------------------------------------------------------------- +static KSTATUS Fat32Open(IO_FILESYSTEM *fs, const char *path, void **fileCtx) { + FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; + uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; + KSTATUS s = TraverseEx(v, path, &cluster, &size, &direntLba, &direntOffset); + if (!KSUCCESS(s)) return s; + FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); + if (!f) return STATUS_NO_MEMORY; + f->StartCluster = cluster; f->FileSize = size; f->Vol = v; + f->DirentLba = direntLba; f->DirentOffset = direntOffset; + *fileCtx = f; + return STATUS_SUCCESS; +} + +static void Fat32Close(IO_FILESYSTEM *fs, void *fileCtx) { + (void)fs; + FAT32_FILE *f = (FAT32_FILE *)fileCtx; + FlushFatCache(f->Vol); + MmFreePool(fileCtx); +} + +// --------------------------------------------------------------------------- +// Read (fix #4: contiguous-run coalescing) +// --------------------------------------------------------------------------- +// We peek ahead in the FAT chain: if cluster N+1 == cluster N physically, +// extend the current run instead of issuing a new ReadSectors call. +// Run length is capped at MAX_RUN_SECTORS to stay within the AHCI bounce +// buffer (128 sectors = 64 KB). +// +// FatEntry() calls in the peek loop are nearly free because they hit the +// FAT sector cache (fix #1). + +#define MAX_RUN_SECTORS 128u + +static KSTATUS Fat32Read(IO_FILESYSTEM *fs, void *fileCtx, + uint64_t offset, void *buf, + size_t size, size_t *bytesRead) { + (void)fs; + if (!bytesRead) return STATUS_INVALID_PARAMETER; + *bytesRead = 0; + if (!fileCtx || !buf) return STATUS_INVALID_PARAMETER; + + FAT32_FILE *f = (FAT32_FILE *)fileCtx; + FAT32_VOL *v = f->Vol; + if (offset >= f->FileSize || size == 0) return STATUS_SUCCESS; + if (offset + size > f->FileSize) size = (size_t)(f->FileSize - offset); + size_t wanted = size; + + // Single allocation for the largest possible coalesced run. + uint8_t *runBuf = (uint8_t *)MmAllocatePool(MAX_RUN_SECTORS * 512u); + if (!runBuf) return STATUS_NO_MEMORY; + + size_t totalRead = 0; + uint8_t *out = (uint8_t *)buf; + KSTATUS readStatus = STATUS_SUCCESS; + uint32_t skipClusters = (uint32_t)(offset / v->BytesPerCluster); + uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); + uint32_t cluster = f->StartCluster; + + for (uint32_t i = 0; i < skipClusters && cluster < FAT32_EOC; i++) + cluster = FatEntry(v, cluster); + + while (size > 0 && cluster >= 2 && cluster < FAT32_EOC) { + // Build the longest physically-contiguous run we can. + uint32_t runStart = cluster; + uint32_t runSectors = v->SectorsPerCluster; + uint32_t next = FatEntry(v, cluster); + + while (next == cluster + 1 && next < FAT32_EOC + && runSectors + v->SectorsPerCluster <= MAX_RUN_SECTORS) { + cluster = next; + next = FatEntry(v, cluster); + runSectors += v->SectorsPerCluster; + } + + KSTATUS rs = ReadSectors(v, ClusterToLba(v, runStart), runSectors, runBuf); + if (!KSUCCESS(rs)) { + readStatus = rs; + break; + } + + uint32_t runBytes = runSectors * 512u; + size_t avail = runBytes - offsetInCluster; + size_t copy = avail < size ? avail : size; + + memcpy(out, runBuf + offsetInCluster, copy); + out += copy; + totalRead += copy; + size -= copy; + offsetInCluster = 0; + cluster = next; + } + + if (totalRead != wanted) { + // The directory entry says the file has more data than the cluster chain + // or disk read actually supplied. Clear the caller's remainder so stale + // memory cannot be interpreted as file contents. + memset(out, 0, wanted - totalRead); + } + + MmFreePool(runBuf); + *bytesRead = totalRead; + if (totalRead != wanted) + return KSUCCESS(readStatus) ? STATUS_DEVICE_ERROR : readStatus; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// Write (fix #4: contiguous-run coalescing) +// --------------------------------------------------------------------------- +// Same strategy as read. Whole-cluster-aligned runs skip the read-modify- +// write; partial head/tail clusters still require it. + +static KSTATUS Fat32Write(IO_FILESYSTEM *fs, void *fileCtx, + uint64_t offset, const void *buf, + size_t size, size_t *bytesWritten) { + (void)fs; + if (!size) { *bytesWritten = 0; return STATUS_SUCCESS; } + + FAT32_FILE *f = (FAT32_FILE *)fileCtx; + FAT32_VOL *v = f->Vol; + const uint8_t *src = (const uint8_t *)buf; + + // Ensure at least one cluster exists. + if (f->StartCluster < 2) { + uint32_t first = AllocCluster(v); + if (!first) { *bytesWritten = 0; return STATUS_NO_MEMORY; } + f->StartCluster = first; + DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); + } + + // Walk to the starting cluster, allocating as needed. + uint32_t clusterIndex = (uint32_t)(offset / v->BytesPerCluster); + uint32_t offsetInCluster = (uint32_t)(offset % v->BytesPerCluster); + uint32_t cluster = f->StartCluster; + + for (uint32_t i = 0; i < clusterIndex; i++) { + uint32_t next = FatEntry(v, cluster); + if (next < 2 || next >= FAT32_EOC) { + next = AppendCluster(v, cluster); + if (!next) { *bytesWritten = 0; return STATUS_NO_MEMORY; } + } + cluster = next; + } + + uint8_t *runBuf = (uint8_t *)MmAllocatePool(MAX_RUN_SECTORS * 512u); + if (!runBuf) { *bytesWritten = 0; return STATUS_NO_MEMORY; } + + size_t written = 0, remaining = size; + + while (remaining > 0 && cluster >= 2) { + // Build a contiguous run (only when cluster-aligned to avoid a + // partial read-modify-write spanning multiple clusters). + uint32_t runStart = cluster; + uint32_t runSectors = v->SectorsPerCluster; + uint32_t next = FatEntry(v, cluster); + + if (offsetInCluster == 0) { + while (next == cluster + 1 && next < FAT32_EOC + && runSectors + v->SectorsPerCluster <= MAX_RUN_SECTORS) { + cluster = next; + next = FatEntry(v, cluster); + runSectors += v->SectorsPerCluster; + } + } + + uint32_t runBytes = runSectors * 512u; + size_t avail = runBytes - offsetInCluster; + size_t copy = avail < remaining ? avail : remaining; + + // Read existing data only for partial-cluster writes. + bool partial = (offsetInCluster > 0) || (copy < (size_t)runBytes); + if (partial) { + if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, runStart), runSectors, runBuf))) + break; + } + + memcpy(runBuf + offsetInCluster, src, copy); + + if (!KSUCCESS(WriteSectors(v, ClusterToLba(v, runStart), runSectors, runBuf))) + break; + + src += copy; + written += copy; + remaining -= copy; + offsetInCluster = 0; + + if (remaining > 0) { + // Advance: if end-of-chain, append a new cluster. + cluster = next; + if (cluster < 2 || cluster >= FAT32_EOC) { + // Find the actual last cluster in the run we just wrote. + uint32_t last = runStart; + for (uint32_t tmp = FatEntry(v, last); + tmp >= 2 && tmp < FAT32_EOC; + tmp = FatEntry(v, last)) + last = tmp; + cluster = AppendCluster(v, last); + if (!cluster) break; + } + } + } + + MmFreePool(runBuf); + + uint64_t newEnd = offset + written; + if (newEnd > f->FileSize) { + f->FileSize = (uint32_t)newEnd; + DirentUpdateSize(v, f->DirentLba, f->DirentOffset, f->FileSize); + } + + *bytesWritten = written; + return (written == size) ? STATUS_SUCCESS : STATUS_DEVICE_ERROR; +} + +// --------------------------------------------------------------------------- +// Create +// --------------------------------------------------------------------------- +static KSTATUS Fat32Create(IO_FILESYSTEM *fs, const char *path, void **fileCtx) { + FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; + char parentPath[256], filename[256]; + SplitPath(path, parentPath, filename); + if (!filename[0]) return STATUS_INVALID_PARAMETER; + + uint32_t parentCluster, parentSize; + KSTATUS s = Traverse(v, parentPath, &parentCluster, &parentSize); + if (!KSUCCESS(s)) return s; + + { uint32_t ec, es; uint8_t ea; + if (SearchDir(v, parentCluster, filename, &ec, &es, &ea, NULL, NULL)) + return STATUS_OBJECT_NAME_COLLISION; } + + uint64_t direntLba; uint32_t direntOffset; + if (!FindFreeDirent(v, parentCluster, &direntLba, &direntOffset)) + return STATUS_NO_MEMORY; + + uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); + if (!sec) return STATUS_NO_MEMORY; + s = ReadSectors(v, direntLba, 1, sec); + if (!KSUCCESS(s)) { MmFreePool(sec); return s; } + + DIRENT *d = (DIRENT *)(sec + direntOffset); + memset(d, 0, sizeof(DIRENT)); + Make83Name(filename, d->Name, d->Ext); + d->Attr = ATTR_ARCHIVE; + + s = WriteSectors(v, direntLba, 1, sec); + MmFreePool(sec); + if (!KSUCCESS(s)) return s; + + FAT32_FILE *f = (FAT32_FILE *)MmAllocatePool(sizeof(FAT32_FILE)); + if (!f) return STATUS_NO_MEMORY; + f->StartCluster = 0; f->FileSize = 0; f->Vol = v; + f->DirentLba = direntLba; f->DirentOffset = direntOffset; + *fileCtx = f; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// Truncate +// --------------------------------------------------------------------------- +static KSTATUS Fat32Truncate(IO_FILESYSTEM *fs, void *fileCtx, uint64_t newSize) { + (void)fs; + FAT32_FILE *f = (FAT32_FILE *)fileCtx; + FAT32_VOL *v = f->Vol; + if ((uint32_t)newSize == f->FileSize) return STATUS_SUCCESS; + + if (newSize == 0) { + if (f->StartCluster >= 2) { + FreeClusterChain(v, f->StartCluster); + f->StartCluster = 0; + DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, 0); + } + f->FileSize = 0; + KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, 0); + FlushFatCache(v); + return s; + } + + uint32_t newClusters = (uint32_t) + ((newSize + v->BytesPerCluster - 1) / v->BytesPerCluster); + + if (newSize < f->FileSize) { + if (f->StartCluster < 2) goto update_size; + uint32_t cluster = f->StartCluster, lastGood = cluster; + for (uint32_t i = 1; i < newClusters && cluster < FAT32_EOC; i++) { + lastGood = cluster; cluster = FatEntry(v, cluster); + } + uint32_t tail = FatEntry(v, lastGood); + WriteFatEntry(v, lastGood, FAT32_EOC_MARK); + FreeClusterChain(v, tail); + } else { + uint32_t curClusters = f->FileSize + ? (f->FileSize + v->BytesPerCluster - 1) / v->BytesPerCluster : 0; + uint32_t lastCluster = 0; + if (f->StartCluster < 2) { + uint32_t first = AllocCluster(v); + if (!first) return STATUS_NO_MEMORY; + f->StartCluster = first; + DirentUpdateFirstCluster(v, f->DirentLba, f->DirentOffset, first); + lastCluster = first; curClusters = 1; + } else { + lastCluster = GetChainEnd(v, f->StartCluster, NULL); + } + for (uint32_t i = curClusters; i < newClusters; i++) { + uint32_t next = AppendCluster(v, lastCluster); + if (!next) return STATUS_NO_MEMORY; + lastCluster = next; + } + } + +update_size: + f->FileSize = (uint32_t)newSize; + { KSTATUS s = DirentUpdateSize(v, f->DirentLba, f->DirentOffset, (uint32_t)newSize); + FlushFatCache(v); + return s; } +} + +// --------------------------------------------------------------------------- +// Delete +// --------------------------------------------------------------------------- +static KSTATUS Fat32Delete(IO_FILESYSTEM *fs, const char *path) { + FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; + uint32_t cluster, size; uint64_t direntLba; uint32_t direntOffset; + KSTATUS s = TraverseEx(v, path, &cluster, &size, &direntLba, &direntOffset); + if (!KSUCCESS(s)) return s; + if (!direntLba) return STATUS_ACCESS_DENIED; + + uint8_t *sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); + if (!sec) return STATUS_NO_MEMORY; + s = ReadSectors(v, direntLba, 1, sec); + if (KSUCCESS(s)) { sec[direntOffset] = 0xE5; s = WriteSectors(v, direntLba, 1, sec); } + MmFreePool(sec); + if (!KSUCCESS(s)) return s; + + if (cluster >= 2) FreeClusterChain(v, cluster); + FlushFatCache(v); + return STATUS_SUCCESS; +} + +static uint64_t Fat32GetSize(IO_FILESYSTEM *fs, void *fileCtx) { + (void)fs; return ((FAT32_FILE *)fileCtx)->FileSize; +} + +// --------------------------------------------------------------------------- +// Directory enumeration (fix #2: cluster buffer cached in context) +// --------------------------------------------------------------------------- +// Previously: allocate + ReadSectors + return 1 entry + free, every call. +// Now: buffer lives in FAT32_DIR_CTX; ReadSectors fires only on cluster +// boundaries. + +typedef struct { + FAT32_VOL *Vol; + uint32_t DirCluster; + uint32_t CurCluster; + uint32_t LoadedCluster; // which cluster is currently in ClusterBuf + uint32_t EntryIndex; + char LfnBuf[MAX_LFN]; + int HasLfn; + uint8_t *ClusterBuf; // allocated once in OpenDir, freed in CloseDir +} FAT32_DIR_CTX; + +static KSTATUS Fat32OpenDir(IO_FILESYSTEM *fs, const char *dirPath, void **dirCtx) { + FAT32_VOL *v = (FAT32_VOL *)fs->DriverCtx; + uint32_t cluster; + if (!dirPath || dirPath[0] == '\0') { + cluster = v->RootCluster; + } else { + uint32_t size; KSTATUS s = Traverse(v, dirPath, &cluster, &size); + if (!KSUCCESS(s)) return s; + } + + FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)MmAllocatePool(sizeof(FAT32_DIR_CTX)); + if (!ctx) return STATUS_NO_MEMORY; + + ctx->ClusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + if (!ctx->ClusterBuf) { MmFreePool(ctx); return STATUS_NO_MEMORY; } + + ctx->Vol = v; + ctx->DirCluster = cluster; + ctx->CurCluster = cluster; + ctx->LoadedCluster = (uint32_t)-1; + ctx->EntryIndex = 0; + ctx->HasLfn = 0; + memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); + *dirCtx = ctx; + return STATUS_SUCCESS; +} + +static KSTATUS Fat32ReadDir(IO_FILESYSTEM *fs, void *dirCtx, KDIR_ENTRY *out) { + (void)fs; + FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)dirCtx; + FAT32_VOL *v = ctx->Vol; + uint32_t epc = v->BytesPerCluster / sizeof(DIRENT); + + while (ctx->CurCluster >= 2 && ctx->CurCluster < FAT32_EOC) { + // Load the cluster only when crossing a cluster boundary. + if (ctx->CurCluster != ctx->LoadedCluster) { + if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, ctx->CurCluster), + v->SectorsPerCluster, ctx->ClusterBuf))) + break; + ctx->LoadedCluster = ctx->CurCluster; + } + + DIRENT *dir = (DIRENT *)ctx->ClusterBuf; + while (ctx->EntryIndex < epc) { + DIRENT *e = &dir[ctx->EntryIndex++]; + + if (e->Name[0] == 0x00) { ctx->CurCluster = FAT32_EOC; return STATUS_NO_MORE_FILES; } + if ((uint8_t)e->Name[0] == 0xE5) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } + if (e->Attr == ATTR_LFN) { LfnAppend(ctx->LfnBuf, (LFN_ENTRY *)e); ctx->HasLfn = 1; continue; } + if (e->Attr & 0x08 && !(e->Attr & ATTR_DIRECTORY)) { ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); continue; } + + if (ctx->HasLfn && ctx->LfnBuf[0]) { + size_t len = strlen(ctx->LfnBuf); + if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; + memcpy(out->Name, ctx->LfnBuf, len); out->Name[len] = '\0'; + } else { + char name83[13]; int n = 0; + for (int i = 0; i < 8 && e->Name[i] != ' '; i++) + name83[n++] = (char)(e->Name[i] >= 'A' && e->Name[i] <= 'Z' ? e->Name[i]+32 : e->Name[i]); + if (e->Ext[0] != ' ') { + name83[n++] = '.'; + for (int i = 0; i < 3 && e->Ext[i] != ' '; i++) + name83[n++] = (char)(e->Ext[i] >= 'A' && e->Ext[i] <= 'Z' ? e->Ext[i]+32 : e->Ext[i]); + } + name83[n] = '\0'; + size_t len = (size_t)n; if (len >= KDIR_NAME_MAX) len = KDIR_NAME_MAX - 1; + memcpy(out->Name, name83, len); out->Name[len] = '\0'; + } + + out->Attr = e->Attr; out->FileSize = e->FileSize; + ctx->HasLfn = 0; memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); + return STATUS_SUCCESS; + } + + ctx->CurCluster = FatEntry(v, ctx->CurCluster); + ctx->EntryIndex = 0; ctx->HasLfn = 0; + memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); + } + + return STATUS_NO_MORE_FILES; +} + +static void Fat32CloseDir(IO_FILESYSTEM *fs, void *dirCtx) { + (void)fs; + FAT32_DIR_CTX *ctx = (FAT32_DIR_CTX *)dirCtx; + MmFreePool(ctx->ClusterBuf); + MmFreePool(ctx); +} + +// --------------------------------------------------------------------------- +// Partition scan (MBR + GPT) — unchanged +// --------------------------------------------------------------------------- +static const EFI_GUID GUID_EFI_SYSTEM = { 0xC12A7328, 0xF81F, 0x11D2, {0xBA,0x4B,0x00,0xA0,0xC9,0x3E,0xC9,0x3B} }; +static const EFI_GUID GUID_MS_BASIC_DATA = { 0xEBD0A0A2, 0xB9E5, 0x4433, {0x87,0xC0,0x68,0xB6,0xB7,0x26,0x99,0xC7} }; +static const EFI_GUID GUID_LINUX_BASIC_DATA = { 0x0FC63DAF, 0x8483, 0x4772, {0x8E,0x79,0x3D,0x69,0xD8,0x47,0x7D,0xE4} }; + +static int IsFat32Vbr(FAT32_VOL *v, uint32_t lba) { + uint8_t sector[512]; + if (!KSUCCESS(ReadSectors(v, lba, 1, sector))) return 0; + if (sector[510] != 0x55 || sector[511] != 0xAA) return 0; + BPB *bpb = (BPB *)sector; + return bpb->FatSize16 == 0 && bpb->RootEntryCount == 0 && bpb->FatSize32 != 0; +} + +static uint32_t FindFat32Partition(FAT32_VOL *v) { + uint8_t mbr[512]; + if (!KSUCCESS(ReadSectors(v, 0, 1, mbr))) return 0; + if (mbr[510] != 0x55 || mbr[511] != 0xAA) return 0; + + int isGpt = 0; + for (int i = 0; i < 4; i++) if (mbr[0x1BE + i*16 + 4] == 0xEE) { isGpt = 1; break; } + + if (isGpt) { + uint8_t gptSector[512]; + if (!KSUCCESS(ReadSectors(v, 1, 1, gptSector))) return 0; + GPT_HEADER *gpt = (GPT_HEADER *)gptSector; + if (memcmp(gpt->Signature, "EFI PART", 8) != 0) return 0; + + uint64_t entryLBA = gpt->PartitionEntryLBA; + uint32_t numEntries = gpt->NumberOfPartitionEntries; + uint32_t entrySize = gpt->SizeOfPartitionEntry; + if (entrySize < sizeof(GPT_ENTRY)) return 0; + + uint32_t eps = 512 / entrySize; uint8_t entrySector[512]; + for (uint32_t i = 0; i < numEntries; i++) { + if (i % eps == 0) + if (!KSUCCESS(ReadSectors(v, entryLBA + i/eps, 1, entrySector))) return 0; + GPT_ENTRY *entry = (GPT_ENTRY *)(entrySector + (i % eps) * entrySize); + static const EFI_GUID emptyGuid = {0}; + if (memcmp(&entry->PartitionTypeGUID, &emptyGuid, sizeof(EFI_GUID)) == 0) continue; + if (memcmp(&entry->PartitionTypeGUID, &GUID_EFI_SYSTEM, sizeof(EFI_GUID)) == 0 || + memcmp(&entry->PartitionTypeGUID, &GUID_MS_BASIC_DATA, sizeof(EFI_GUID)) == 0 || + memcmp(&entry->PartitionTypeGUID, &GUID_LINUX_BASIC_DATA, sizeof(EFI_GUID)) == 0) { + uint32_t lba = (uint32_t)entry->StartingLBA; + if (IsFat32Vbr(v, lba)) return lba; + } + } + return 0; + } + + for (int i = 0; i < 4; i++) { + uint8_t *entry = mbr + 0x1BE + i*16; + if (entry[4] == 0x0B || entry[4] == 0x0C) { + uint32_t lba = *(uint32_t *)(entry + 8); + if (IsFat32Vbr(v, lba)) return lba; + } + } + return 0; +} + +// --------------------------------------------------------------------------- +// DriverEntry +// --------------------------------------------------------------------------- +static FAT32_VOL gVol; +static IO_FILESYSTEM gFs; + +KSTATUS DriverEntry(const HAL_API *hal) { + DbgPrint("FAT32: Init\n"); + if (!hal) return STATUS_INVALID_PARAMETER; + + FatCacheInvalidate(); + + gVol.Hal = hal; + gVol.DriveIndex = 0; + + uint32_t partLba = FindFat32Partition(&gVol); + if (!partLba) { DbgPrint("FAT32: No FAT32 volume found.\n"); return STATUS_NOT_SUPPORTED; } + gVol.PartitionLba = partLba; + + uint8_t sector[512]; + if (!KSUCCESS(ReadSectors(&gVol, partLba, 1, sector))) return STATUS_DEVICE_ERROR; + + BPB *bpb = (BPB *)sector; + if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) return STATUS_NOT_SUPPORTED; + + gVol.BytesPerSector = bpb->BytesPerSector; + gVol.SectorsPerCluster = bpb->SectorsPerCluster; + gVol.BytesPerCluster = bpb->BytesPerSector * bpb->SectorsPerCluster; + gVol.ReservedSectors = bpb->ReservedSectors; + gVol.NumFats = bpb->NumFats; + gVol.FatSize = bpb->FatSize32; + gVol.RootCluster = bpb->RootCluster; + gVol.DataStart = partLba + bpb->ReservedSectors + bpb->NumFats * bpb->FatSize32; + gVol.NextFreeHint = 2; + + uint32_t totalSectors = bpb->TotalSectors16 ? bpb->TotalSectors16 : bpb->TotalSectors32; + uint32_t dataSectors = totalSectors - bpb->ReservedSectors - bpb->NumFats * bpb->FatSize32; + gVol.TotalClusters = dataSectors / bpb->SectorsPerCluster; + + gFs.Open = Fat32Open; + gFs.Read = Fat32Read; + gFs.Write = Fat32Write; + gFs.Create = Fat32Create; + gFs.Delete = Fat32Delete; + gFs.Truncate = Fat32Truncate; + gFs.GetSize = Fat32GetSize; + gFs.Close = Fat32Close; + gFs.DriverCtx = &gVol; + gFs.OpenDir = Fat32OpenDir; + gFs.ReadDir = Fat32ReadDir; + gFs.CloseDir = Fat32CloseDir; + + IO_VOLUME_PARAMS volParams; + volParams.DiskIndex = gVol.DriveIndex; + volParams.PartitionLba = partLba; + volParams.PartitionSize = totalSectors; + + KSTATUS status = IoRegisterFilesystem(gVol.DriveIndex, &gFs, &volParams); + DbgPrint("FAT32: Init complete (status=%d, clusters=%u).\n", status, gVol.TotalClusters); + return STATUS_SUCCESS; +} diff --git a/code/init/init.c b/code/init/init.c index 7d576d7..519f669 100644 --- a/code/init/init.c +++ b/code/init/init.c @@ -48,12 +48,13 @@ void itoa(int num, char* str) { } } -void _start(const char* args) +void _start(void) { + const char* args = SysGetCommandLine(); uint32_t h = 0; uint32_t s = 0; - for (;;) { + while (true) { SysDebugPrint("[init] starting\n"); s = SysCreateProcess(args, &h, NULL); if (s != STATUS_SUCCESS) { diff --git a/code/kernel/io_file.c b/code/kernel/io_file.c index 84e245b..7af6948 100644 --- a/code/kernel/io_file.c +++ b/code/kernel/io_file.c @@ -1,424 +1,443 @@ -// io_file.c - VFS dispatch layer -#include -#include -#include -#include -#include -#include - -#define MAX_DRIVES 8 - -typedef struct { - IO_FILESYSTEM *Fs; - IO_VOLUME_PARAMS Vol; -} VOLUME_ENTRY; - -static VOLUME_ENTRY gVolumes[MAX_DRIVES]; - -// --------------------------------------------------------------------------- -// Current Working Directory (drive 0 relative, no trailing backslash) -// e.g. "" = root, "apps" = root\apps, "apps\\data" = nested -// --------------------------------------------------------------------------- -#define CWD_MAX 512 - -__declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size) { - if (!buf || size == 0) return STATUS_INVALID_PARAMETER; - - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return STATUS_ACCESS_DENIED; - - uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - size_t cwdLen = strlen(proc->CurrentDirectory); - if (cwdLen + 1 > size) { - KeReleaseSpinlock(&proc->CwdLock, flags); - return STATUS_BUFFER_TOO_SMALL; - } - memcpy(buf, proc->CurrentDirectory, cwdLen + 1); - KeReleaseSpinlock(&proc->CwdLock, flags); - - return STATUS_SUCCESS; -} - -// Resolve a possibly-relative path component against the CWD. -// Writes the fully-resolved, drive-stripped relative path into 'resolved' -// (caller supplies buf of at least CWD_MAX bytes). -// Returns the drive index. -static uint8_t ResolvePath(const char* path, char* resolved, size_t resolvedSize) { - // Strip explicit drive prefix "N:\" - uint8_t drive = 0; - bool fromDriveRoot = false; - if (path[0] >= '0' && path[0] <= '7' && - path[1] == ':' && (path[2] == '\\' || path[2] == '/')) { - drive = (uint8_t)(path[0] - '0'); - path = path + 3; - fromDriveRoot = true; - } - - char work[CWD_MAX * 2]; - work[0] = '\0'; - - // Initialize our working base - if (path[0] == '\\' || path[0] == '/' || fromDriveRoot) { - // Absolute path: start from the root (empty work buffer) - // The segment loop below will automatically skip the leading separators. - } else { - // Relative path: start from the current process's CWD - PROCESS* proc = KeGetCurrentProcess(); - if (proc) { - uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - size_t wlen = strlen(proc->CurrentDirectory); - if (wlen >= sizeof(work)) wlen = sizeof(work) - 1; - memcpy(work, proc->CurrentDirectory, wlen); - work[wlen] = '\0'; - KeReleaseSpinlock(&proc->CwdLock, flags); - } - } - - // Segment-by-segment canonicalization - const char* p = path; - while (*p) { - // Skip separators - while (*p == '\\' || *p == '/') p++; - if (!*p) break; - - // Read next component - char comp[256]; - int clen = 0; - while (*p && *p != '\\' && *p != '/' && clen < 255) - comp[clen++] = *p++; - comp[clen] = '\0'; - - if (clen == 0) continue; - - if (strcmp(comp, ".") == 0) { - // Stay in place - } else if (strcmp(comp, "..") == 0) { - // Remove last component from work - int i = (int)strlen(work) - 1; - while (i >= 0 && work[i] != '\\' && work[i] != '/') i--; - if (i < 0) i = 0; - work[i] = '\0'; - } else { - // Append separator + component - size_t wl = strlen(work); - if (wl > 0 && work[wl-1] != '\\' && work[wl-1] != '/') { - if (wl + 1 < sizeof(work)) { work[wl] = '\\'; work[wl+1] = '\0'; } - } - size_t wl2 = strlen(work); - if (wl2 + clen + 1 < sizeof(work)) { - memcpy(work + wl2, comp, clen); - work[wl2 + clen] = '\0'; - } - } - } - - // Output the final canonicalized, drive-stripped path - size_t finalLen = strlen(work); - if (finalLen + 1 > resolvedSize) finalLen = resolvedSize - 1; - memcpy(resolved, work, finalLen); - resolved[finalLen] = '\0'; - - return drive; -} - -__declspec(dllexport) KSTATUS IoSetCwd(const char* path) { - if (!path) return STATUS_INVALID_PARAMETER; - - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return STATUS_ACCESS_DENIED; - - // Resolve the new path (this also validates ".." navigation). - // Note: ResolvePath already uses proc->CurrentDirectory internally! - char resolved[CWD_MAX]; - uint8_t drive = ResolvePath(path, resolved, sizeof(resolved)); - - // Validate: try to open the directory to confirm it actually exists on disk - IO_FILESYSTEM* fs = gVolumes[drive].Fs; - if (!fs) return STATUS_NOT_FOUND; - if (!fs->OpenDir) return STATUS_NOT_SUPPORTED; - - void* dirCtx = NULL; - KSTATUS s = fs->OpenDir(fs, resolved, &dirCtx); - if (!KSUCCESS(s)) return s; - fs->CloseDir(fs, dirCtx); - - // Commit the new CWD to the process block - size_t len = strlen(resolved); - if (len + 1 > sizeof(proc->CurrentDirectory)) return STATUS_BUFFER_TOO_SMALL; - - uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - memcpy(proc->CurrentDirectory, resolved, len + 1); - KeReleaseSpinlock(&proc->CwdLock, flags); - - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// Registration -// --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t volumeIndex, - IO_FILESYSTEM *fs, - const IO_VOLUME_PARAMS *vol) { - if (volumeIndex >= MAX_DRIVES || !fs || !vol) - return STATUS_INVALID_PARAMETER; - if (gVolumes[volumeIndex].Fs) - return STATUS_ALREADY_EXISTS; - gVolumes[volumeIndex].Fs = fs; - gVolumes[volumeIndex].Vol = *vol; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// Destructor -// --------------------------------------------------------------------------- -static void FileDestroy(OBJECT* obj) { - FILE_OBJECT* f = CONTAINING_RECORD(obj, FILE_OBJECT, Header); - if (f->Fs && f->FileCtx) f->Fs->Close(f->Fs, f->FileCtx); -} - -// --------------------------------------------------------------------------- -// IoOpenFile -// --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) { - if (!path || !out) return STATUS_INVALID_PARAMETER; - - char relPath[CWD_MAX]; - uint8_t drive = ResolvePath(path, relPath, sizeof(relPath)); - - IO_FILESYSTEM* fs = gVolumes[drive].Fs; - if (!fs) return STATUS_NOT_FOUND; - - void* fileCtx; - KSTATUS s = fs->Open(fs, relPath, &fileCtx); - if (!KSUCCESS(s)) return s; - - FILE_OBJECT* f = (FILE_OBJECT *)ObAllocateObject(ObjectTypeFile /* reuse slot */, - sizeof(FILE_OBJECT)); - if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; } - f->Header.Destroy = FileDestroy; - f->Fs = fs; - f->FileCtx = fileCtx; - f->Size = fs->GetSize(fs, fileCtx); - f->Offset = 0; - - *out = f; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// IoReadFile - sequential read at current offset -// --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t size, size_t* bytesRead) { - if (!file || !buf) return STATUS_INVALID_PARAMETER; - if (file->Offset >= file->Size) { *bytesRead = 0; return STATUS_SUCCESS; } - - // Clamp to remaining bytes. - size_t remaining = (size_t)(file->Size - file->Offset); - if (size > remaining) size = remaining; - - KSTATUS s = file->Fs->Read(file->Fs, file->FileCtx, - file->Offset, buf, size, bytesRead); - if (KSUCCESS(s)) file->Offset += *bytesRead; - return s; -} - -// --------------------------------------------------------------------------- -// IoWriteFile - sequential write at current offset -// --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf, - size_t size, size_t* bytesWritten) { - if (!file || !buf || !bytesWritten) return STATUS_INVALID_PARAMETER; - if (!file->Fs->Write) return STATUS_NOT_SUPPORTED; - - size_t written = 0; - KSTATUS s = file->Fs->Write(file->Fs, file->FileCtx, - file->Offset, buf, size, &written); - if (KSUCCESS(s)) { - file->Offset += written; - // Keep the cached size in sync if the file grew. - if (file->Offset > file->Size) - file->Size = file->Offset; - } - *bytesWritten = written; - return s; -} - -// --------------------------------------------------------------------------- -// IoCreateFile - create a new file and return it open for writing -// --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoCreateFile(const char* path, FILE_OBJECT** out) { - if (!path || !out) return STATUS_INVALID_PARAMETER; - - char relPath[CWD_MAX]; - uint8_t drive = ResolvePath(path, relPath, sizeof(relPath)); - - IO_FILESYSTEM* fs = gVolumes[drive].Fs; - if (!fs) return STATUS_NOT_FOUND; - if (!fs->Create) return STATUS_NOT_SUPPORTED; - - void* fileCtx; - KSTATUS s = fs->Create(fs, relPath, &fileCtx); - if (!KSUCCESS(s)) return s; - - FILE_OBJECT* f = (FILE_OBJECT*)ObAllocateObject(ObjectTypeFile, - sizeof(FILE_OBJECT)); - if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; } - f->Header.Destroy = FileDestroy; - f->Fs = fs; - f->FileCtx = fileCtx; - f->Size = 0; - f->Offset = 0; - - *out = f; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// IoDeleteFile - delete a file by path (does not require an open handle) -// --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoDeleteFile(const char* path) { - if (!path) return STATUS_INVALID_PARAMETER; - - char relPath[CWD_MAX]; - uint8_t drive = ResolvePath(path, relPath, sizeof(relPath)); - - IO_FILESYSTEM* fs = gVolumes[drive].Fs; - if (!fs) return STATUS_NOT_FOUND; - if (!fs->Delete) return STATUS_NOT_SUPPORTED; - - return fs->Delete(fs, relPath); -} - -__declspec(dllexport) KSTATUS IoRenameFile(const char* oldPath, const char* newPath) { - if (!oldPath || !newPath) return STATUS_INVALID_PARAMETER; - - char oldRel[CWD_MAX]; - char newRel[CWD_MAX]; - uint8_t oldDrive = ResolvePath(oldPath, oldRel, sizeof(oldRel)); - uint8_t newDrive = ResolvePath(newPath, newRel, sizeof(newRel)); - if (oldDrive != newDrive) return STATUS_NOT_SUPPORTED; - - IO_FILESYSTEM* fs = gVolumes[oldDrive].Fs; - if (!fs) return STATUS_NOT_FOUND; - if (!fs->Rename) return STATUS_NOT_SUPPORTED; - - return fs->Rename(fs, oldRel, newRel); -} - -__declspec(dllexport) KSTATUS IoCreateDir(const char* path) { - if (!path) return STATUS_INVALID_PARAMETER; - - char relPath[CWD_MAX]; - uint8_t drive = ResolvePath(path, relPath, sizeof(relPath)); - - IO_FILESYSTEM* fs = gVolumes[drive].Fs; - if (!fs) return STATUS_NOT_FOUND; - if (!fs->CreateDir) return STATUS_NOT_SUPPORTED; - - return fs->CreateDir(fs, relPath); -} - -__declspec(dllexport) KSTATUS IoRemoveDir(const char* path) { - if (!path) return STATUS_INVALID_PARAMETER; - - char relPath[CWD_MAX]; - uint8_t drive = ResolvePath(path, relPath, sizeof(relPath)); - - IO_FILESYSTEM* fs = gVolumes[drive].Fs; - if (!fs) return STATUS_NOT_FOUND; - if (!fs->RemoveDir) return STATUS_NOT_SUPPORTED; - - return fs->RemoveDir(fs, relPath); -} - -// --------------------------------------------------------------------------- -// IoTruncateFile - resize the file; updates the cached Size on success -// --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize) { - if (!file) return STATUS_INVALID_PARAMETER; - if (!file->Fs->Truncate) return STATUS_NOT_SUPPORTED; - - KSTATUS s = file->Fs->Truncate(file->Fs, file->FileCtx, newSize); - if (KSUCCESS(s)) { - file->Size = newSize; - // Pull the file pointer back if it now sits past EOF. - if (file->Offset > file->Size) - file->Offset = file->Size; - } - return s; -} - -// --------------------------------------------------------------------------- -__declspec(dllexport) KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset) { - if (!file) return STATUS_INVALID_PARAMETER; - file->Offset = offset; - return STATUS_SUCCESS; -} - -__declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file) { - return file ? file->Size : 0; -} - -__declspec(dllexport) void IoCloseFile(FILE_OBJECT* file) { - if (file) ObDereferenceObject(&file->Header); -} - -// --------------------------------------------------------------------------- -// Directory enumeration -// --------------------------------------------------------------------------- - -static void DirDestroy(OBJECT* obj) { - DIR_OBJECT* d = (DIR_OBJECT*)obj; // Header is first member - if (d->Fs && d->DirCtx) d->Fs->CloseDir(d->Fs, d->DirCtx); -} - -__declspec(dllexport) KSTATUS IoOpenDir(const char* dirPath, DIR_OBJECT** out) { - if (!out) return STATUS_INVALID_PARAMETER; - - // NULL or empty path means the CWD itself - char relPath[CWD_MAX]; - uint8_t drive; - - if (!dirPath || dirPath[0] == '\0') { - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return STATUS_ACCESS_DENIED; - - uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); - memcpy(relPath, proc->CurrentDirectory, strlen(proc->CurrentDirectory) + 1); - KeReleaseSpinlock(&proc->CwdLock, flags); - - // Note: For now, this assumes drive 0. If you add multi-drive support - // later, you should add a CurrentDrive field to the PROCESS struct. - drive = 0; - } else { - drive = ResolvePath(dirPath, relPath, sizeof(relPath)); - } - - IO_FILESYSTEM* fs = gVolumes[drive].Fs; - if (!fs) return STATUS_NOT_FOUND; - if (!fs->OpenDir) return STATUS_NOT_SUPPORTED; - - void* dirCtx = NULL; - KSTATUS s = fs->OpenDir(fs, relPath, &dirCtx); - if (!KSUCCESS(s)) return s; - - DIR_OBJECT* d = (DIR_OBJECT*)ObAllocateObject(ObjectTypeDir, sizeof(DIR_OBJECT)); - if (!d) { fs->CloseDir(fs, dirCtx); return STATUS_NO_MEMORY; } - - d->Header.Destroy = DirDestroy; - d->Fs = fs; - d->DirCtx = dirCtx; - - *out = d; - return STATUS_SUCCESS; -} - -__declspec(dllexport) KSTATUS IoReadDir(DIR_OBJECT* dir, KDIR_ENTRY* entryOut) { - if (!dir || !entryOut) return STATUS_INVALID_PARAMETER; - if (!dir->Fs->ReadDir) return STATUS_NOT_SUPPORTED; - return dir->Fs->ReadDir(dir->Fs, dir->DirCtx, entryOut); -} - -__declspec(dllexport) void IoCloseDir(DIR_OBJECT* dir) { - if (dir) ObDereferenceObject(&dir->Header); -} +// io_file.c - VFS dispatch layer +#include +#include +#include +#include +#include +#include +#include + +#define MAX_DRIVES 8 + +typedef struct { + IO_FILESYSTEM *Fs; + IO_VOLUME_PARAMS Vol; +} VOLUME_ENTRY; + +static VOLUME_ENTRY gVolumes[MAX_DRIVES]; + +// --------------------------------------------------------------------------- +// Current Working Directory (drive 0 relative, no trailing backslash) +// e.g. "" = root, "apps" = root\apps, "apps\\data" = nested +// --------------------------------------------------------------------------- +#define CWD_MAX 512 + +__declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size) { + if (!buf || size == 0) return STATUS_INVALID_PARAMETER; + + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_ACCESS_DENIED; + + uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); + size_t cwdLen = strlen(proc->CurrentDirectory); + if (cwdLen + 1 > size) { + KeReleaseSpinlock(&proc->CwdLock, flags); + return STATUS_BUFFER_TOO_SMALL; + } + memcpy(buf, proc->CurrentDirectory, cwdLen + 1); + KeReleaseSpinlock(&proc->CwdLock, flags); + + return STATUS_SUCCESS; +} + +// Resolve a possibly-relative path component against the CWD. +// Writes the fully-resolved, drive-stripped relative path into 'resolved' +// (caller supplies buf of at least CWD_MAX bytes). +// Returns the drive index. +static uint8_t ResolvePath(const char* path, char* resolved, size_t resolvedSize) { + // Strip explicit drive prefix "N:\" + uint8_t drive = 0; + bool fromDriveRoot = false; + if (path[0] >= '0' && path[0] <= '7' && + path[1] == ':' && (path[2] == '\\' || path[2] == '/')) { + drive = (uint8_t)(path[0] - '0'); + path = path + 3; + fromDriveRoot = true; + } + + char work[CWD_MAX * 2]; + work[0] = '\0'; + + // Initialize our working base + if (path[0] == '\\' || path[0] == '/' || fromDriveRoot) { + // Absolute path: start from the root (empty work buffer) + // The segment loop below will automatically skip the leading separators. + } else { + // Relative path: start from the current process's CWD + PROCESS* proc = KeGetCurrentProcess(); + if (proc) { + uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); + size_t wlen = strlen(proc->CurrentDirectory); + if (wlen >= sizeof(work)) wlen = sizeof(work) - 1; + memcpy(work, proc->CurrentDirectory, wlen); + work[wlen] = '\0'; + KeReleaseSpinlock(&proc->CwdLock, flags); + } + } + + // Segment-by-segment canonicalization + const char* p = path; + while (*p) { + // Skip separators + while (*p == '\\' || *p == '/') p++; + if (!*p) break; + + // Read next component + char comp[256]; + int clen = 0; + while (*p && *p != '\\' && *p != '/' && clen < 255) + comp[clen++] = *p++; + comp[clen] = '\0'; + + if (clen == 0) continue; + + if (strcmp(comp, ".") == 0) { + // Stay in place + } else if (strcmp(comp, "..") == 0) { + // Remove last component from work + int i = (int)strlen(work) - 1; + while (i >= 0 && work[i] != '\\' && work[i] != '/') i--; + if (i < 0) i = 0; + work[i] = '\0'; + } else { + // Append separator + component + size_t wl = strlen(work); + if (wl > 0 && work[wl-1] != '\\' && work[wl-1] != '/') { + if (wl + 1 < sizeof(work)) { work[wl] = '\\'; work[wl+1] = '\0'; } + } + size_t wl2 = strlen(work); + if (wl2 + clen + 1 < sizeof(work)) { + memcpy(work + wl2, comp, clen); + work[wl2 + clen] = '\0'; + } + } + } + + // Output the final canonicalized, drive-stripped path + size_t finalLen = strlen(work); + if (finalLen + 1 > resolvedSize) finalLen = resolvedSize - 1; + memcpy(resolved, work, finalLen); + resolved[finalLen] = '\0'; + + return drive; +} + +__declspec(dllexport) KSTATUS IoSetCwd(const char* path) { + if (!path) return STATUS_INVALID_PARAMETER; + + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_ACCESS_DENIED; + + // Resolve the new path (this also validates ".." navigation). + // Note: ResolvePath already uses proc->CurrentDirectory internally! + char resolved[CWD_MAX]; + uint8_t drive = ResolvePath(path, resolved, sizeof(resolved)); + + // Validate: try to open the directory to confirm it actually exists on disk + IO_FILESYSTEM* fs = gVolumes[drive].Fs; + if (!fs) return STATUS_NOT_FOUND; + if (!fs->OpenDir) return STATUS_NOT_SUPPORTED; + + void* dirCtx = NULL; + KSTATUS s = fs->OpenDir(fs, resolved, &dirCtx); + if (!KSUCCESS(s)) return s; + fs->CloseDir(fs, dirCtx); + + // Commit the new CWD to the process block + size_t len = strlen(resolved); + if (len + 1 > sizeof(proc->CurrentDirectory)) return STATUS_BUFFER_TOO_SMALL; + + uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); + memcpy(proc->CurrentDirectory, resolved, len + 1); + + // Keep the user-visible PEB mirror in sync. The PEB's CWD buffer is a + // fixed SYS_CWD_MAX allocation made at process creation, so the pointer + // remains stable for personalities that cache SYS_PEB*. + if (proc->UserCwdBase && proc->UserCwdSize) { + size_t copyLen = len; + if (copyLen + 1 > proc->UserCwdSize) copyLen = proc->UserCwdSize - 1; + memcpy((void*)proc->UserCwdBase, resolved, copyLen); + ((char*)proc->UserCwdBase)[copyLen] = '\0'; + + SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase; + if (peb && peb->Magic == SYS_PEB_MAGIC) { + peb->CurrentDirectory = (const char*)proc->UserCwdBase; + peb->CurrentDirectoryLength = copyLen; + } + } + + KeReleaseSpinlock(&proc->CwdLock, flags); + + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// Registration +// --------------------------------------------------------------------------- +__declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t volumeIndex, + IO_FILESYSTEM *fs, + const IO_VOLUME_PARAMS *vol) { + if (volumeIndex >= MAX_DRIVES || !fs || !vol) + return STATUS_INVALID_PARAMETER; + if (gVolumes[volumeIndex].Fs) + return STATUS_ALREADY_EXISTS; + gVolumes[volumeIndex].Fs = fs; + gVolumes[volumeIndex].Vol = *vol; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// Destructor +// --------------------------------------------------------------------------- +static void FileDestroy(OBJECT* obj) { + FILE_OBJECT* f = CONTAINING_RECORD(obj, FILE_OBJECT, Header); + if (f->Fs && f->FileCtx) f->Fs->Close(f->Fs, f->FileCtx); +} + +// --------------------------------------------------------------------------- +// IoOpenFile +// --------------------------------------------------------------------------- +__declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) { + if (!path || !out) return STATUS_INVALID_PARAMETER; + + char relPath[CWD_MAX]; + uint8_t drive = ResolvePath(path, relPath, sizeof(relPath)); + + IO_FILESYSTEM* fs = gVolumes[drive].Fs; + if (!fs) return STATUS_NOT_FOUND; + + void* fileCtx; + KSTATUS s = fs->Open(fs, relPath, &fileCtx); + if (!KSUCCESS(s)) return s; + + FILE_OBJECT* f = (FILE_OBJECT *)ObAllocateObject(ObjectTypeFile /* reuse slot */, + sizeof(FILE_OBJECT)); + if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; } + f->Header.Destroy = FileDestroy; + f->Fs = fs; + f->FileCtx = fileCtx; + f->Size = fs->GetSize(fs, fileCtx); + f->Offset = 0; + + *out = f; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// IoReadFile - sequential read at current offset +// --------------------------------------------------------------------------- +__declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t size, size_t* bytesRead) { + if (bytesRead) *bytesRead = 0; + if (!file || !buf || !bytesRead) return STATUS_INVALID_PARAMETER; + if (file->Offset >= file->Size || size == 0) return STATUS_SUCCESS; + + // Clamp to remaining bytes. + size_t remaining = (size_t)(file->Size - file->Offset); + if (size > remaining) size = remaining; + + KSTATUS s = file->Fs->Read(file->Fs, file->FileCtx, + file->Offset, buf, size, bytesRead); + if (KSUCCESS(s)) file->Offset += *bytesRead; + return s; +} + +// --------------------------------------------------------------------------- +// IoWriteFile - sequential write at current offset +// --------------------------------------------------------------------------- +__declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf, + size_t size, size_t* bytesWritten) { + if (!file || !buf || !bytesWritten) return STATUS_INVALID_PARAMETER; + if (!file->Fs->Write) return STATUS_NOT_SUPPORTED; + + size_t written = 0; + KSTATUS s = file->Fs->Write(file->Fs, file->FileCtx, + file->Offset, buf, size, &written); + if (KSUCCESS(s)) { + file->Offset += written; + // Keep the cached size in sync if the file grew. + if (file->Offset > file->Size) + file->Size = file->Offset; + } + *bytesWritten = written; + return s; +} + +// --------------------------------------------------------------------------- +// IoCreateFile - create a new file and return it open for writing +// --------------------------------------------------------------------------- +__declspec(dllexport) KSTATUS IoCreateFile(const char* path, FILE_OBJECT** out) { + if (!path || !out) return STATUS_INVALID_PARAMETER; + + char relPath[CWD_MAX]; + uint8_t drive = ResolvePath(path, relPath, sizeof(relPath)); + + IO_FILESYSTEM* fs = gVolumes[drive].Fs; + if (!fs) return STATUS_NOT_FOUND; + if (!fs->Create) return STATUS_NOT_SUPPORTED; + + void* fileCtx; + KSTATUS s = fs->Create(fs, relPath, &fileCtx); + if (!KSUCCESS(s)) return s; + + FILE_OBJECT* f = (FILE_OBJECT*)ObAllocateObject(ObjectTypeFile, + sizeof(FILE_OBJECT)); + if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; } + f->Header.Destroy = FileDestroy; + f->Fs = fs; + f->FileCtx = fileCtx; + f->Size = 0; + f->Offset = 0; + + *out = f; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// IoDeleteFile - delete a file by path (does not require an open handle) +// --------------------------------------------------------------------------- +__declspec(dllexport) KSTATUS IoDeleteFile(const char* path) { + if (!path) return STATUS_INVALID_PARAMETER; + + char relPath[CWD_MAX]; + uint8_t drive = ResolvePath(path, relPath, sizeof(relPath)); + + IO_FILESYSTEM* fs = gVolumes[drive].Fs; + if (!fs) return STATUS_NOT_FOUND; + if (!fs->Delete) return STATUS_NOT_SUPPORTED; + + return fs->Delete(fs, relPath); +} + +__declspec(dllexport) KSTATUS IoRenameFile(const char* oldPath, const char* newPath) { + if (!oldPath || !newPath) return STATUS_INVALID_PARAMETER; + + char oldRel[CWD_MAX]; + char newRel[CWD_MAX]; + uint8_t oldDrive = ResolvePath(oldPath, oldRel, sizeof(oldRel)); + uint8_t newDrive = ResolvePath(newPath, newRel, sizeof(newRel)); + if (oldDrive != newDrive) return STATUS_NOT_SUPPORTED; + + IO_FILESYSTEM* fs = gVolumes[oldDrive].Fs; + if (!fs) return STATUS_NOT_FOUND; + if (!fs->Rename) return STATUS_NOT_SUPPORTED; + + return fs->Rename(fs, oldRel, newRel); +} + +__declspec(dllexport) KSTATUS IoCreateDir(const char* path) { + if (!path) return STATUS_INVALID_PARAMETER; + + char relPath[CWD_MAX]; + uint8_t drive = ResolvePath(path, relPath, sizeof(relPath)); + + IO_FILESYSTEM* fs = gVolumes[drive].Fs; + if (!fs) return STATUS_NOT_FOUND; + if (!fs->CreateDir) return STATUS_NOT_SUPPORTED; + + return fs->CreateDir(fs, relPath); +} + +__declspec(dllexport) KSTATUS IoRemoveDir(const char* path) { + if (!path) return STATUS_INVALID_PARAMETER; + + char relPath[CWD_MAX]; + uint8_t drive = ResolvePath(path, relPath, sizeof(relPath)); + + IO_FILESYSTEM* fs = gVolumes[drive].Fs; + if (!fs) return STATUS_NOT_FOUND; + if (!fs->RemoveDir) return STATUS_NOT_SUPPORTED; + + return fs->RemoveDir(fs, relPath); +} + +// --------------------------------------------------------------------------- +// IoTruncateFile - resize the file; updates the cached Size on success +// --------------------------------------------------------------------------- +__declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize) { + if (!file) return STATUS_INVALID_PARAMETER; + if (!file->Fs->Truncate) return STATUS_NOT_SUPPORTED; + + KSTATUS s = file->Fs->Truncate(file->Fs, file->FileCtx, newSize); + if (KSUCCESS(s)) { + file->Size = newSize; + // Pull the file pointer back if it now sits past EOF. + if (file->Offset > file->Size) + file->Offset = file->Size; + } + return s; +} + +// --------------------------------------------------------------------------- +__declspec(dllexport) KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset) { + if (!file) return STATUS_INVALID_PARAMETER; + file->Offset = offset; + return STATUS_SUCCESS; +} + +__declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file) { + return file ? file->Size : 0; +} + +__declspec(dllexport) void IoCloseFile(FILE_OBJECT* file) { + if (file) ObDereferenceObject(&file->Header); +} + +// --------------------------------------------------------------------------- +// Directory enumeration +// --------------------------------------------------------------------------- + +static void DirDestroy(OBJECT* obj) { + DIR_OBJECT* d = (DIR_OBJECT*)obj; // Header is first member + if (d->Fs && d->DirCtx) d->Fs->CloseDir(d->Fs, d->DirCtx); +} + +__declspec(dllexport) KSTATUS IoOpenDir(const char* dirPath, DIR_OBJECT** out) { + if (!out) return STATUS_INVALID_PARAMETER; + + // NULL or empty path means the CWD itself + char relPath[CWD_MAX]; + uint8_t drive; + + if (!dirPath || dirPath[0] == '\0') { + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_ACCESS_DENIED; + + uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock); + memcpy(relPath, proc->CurrentDirectory, strlen(proc->CurrentDirectory) + 1); + KeReleaseSpinlock(&proc->CwdLock, flags); + + // Note: For now, this assumes drive 0. If you add multi-drive support + // later, you should add a CurrentDrive field to the PROCESS struct. + drive = 0; + } else { + drive = ResolvePath(dirPath, relPath, sizeof(relPath)); + } + + IO_FILESYSTEM* fs = gVolumes[drive].Fs; + if (!fs) return STATUS_NOT_FOUND; + if (!fs->OpenDir) return STATUS_NOT_SUPPORTED; + + void* dirCtx = NULL; + KSTATUS s = fs->OpenDir(fs, relPath, &dirCtx); + if (!KSUCCESS(s)) return s; + + DIR_OBJECT* d = (DIR_OBJECT*)ObAllocateObject(ObjectTypeDir, sizeof(DIR_OBJECT)); + if (!d) { fs->CloseDir(fs, dirCtx); return STATUS_NO_MEMORY; } + + d->Header.Destroy = DirDestroy; + d->Fs = fs; + d->DirCtx = dirCtx; + + *out = d; + return STATUS_SUCCESS; +} + +__declspec(dllexport) KSTATUS IoReadDir(DIR_OBJECT* dir, KDIR_ENTRY* entryOut) { + if (!dir || !entryOut) return STATUS_INVALID_PARAMETER; + if (!dir->Fs->ReadDir) return STATUS_NOT_SUPPORTED; + return dir->Fs->ReadDir(dir->Fs, dir->DirCtx, entryOut); +} + +__declspec(dllexport) void IoCloseDir(DIR_OBJECT* dir) { + if (dir) ObDereferenceObject(&dir->Header); +} diff --git a/code/kernel/ke_debug.c b/code/kernel/ke_debug.c index f42efd4..60550e1 100644 --- a/code/kernel/ke_debug.c +++ b/code/kernel/ke_debug.c @@ -36,7 +36,7 @@ __declspec(dllexport) void DbgPrint(const char* fmt, ...) { } // KePanic -__declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...) { +__declspec(dllexport) _Noreturn void KePanic(const char* fmt, ...) { // Silence! Stop the world! _disable(); @@ -44,7 +44,7 @@ __declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...) { // We call PrintChar directly so we can set a red foreground. // (DbgPrint uses green; we want this visually distinct.) static const char banner[] = "\n*** KERNEL PANIC ***\n"; - for (const char *p = banner; *p; p++) + for (const char* p = banner; *p; p++) PrintChar(*p, 0xFF2222, 0x000000); // Now print the caller's message through the normal vprintf path. diff --git a/code/kernel/ke_debug.h b/code/kernel/ke_debug.h index f85e2f2..9de1e3f 100644 --- a/code/kernel/ke_debug.h +++ b/code/kernel/ke_debug.h @@ -6,7 +6,7 @@ void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), int v); __declspec(dllexport) void DbgPrint(const char* fmt, ...); __declspec(dllexport) void KePrintConsole(const char* buf, size_t len); -__declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...); +__declspec(dllexport) _Noreturn void KePanic(const char* fmt, ...); // --------------------------------------------------------------------------- // Fault dispatch - called from the three IDT fault stubs (ke_syscall.asm). diff --git a/code/kernel/ke_process.c b/code/kernel/ke_process.c index 103f715..724aa13 100644 --- a/code/kernel/ke_process.c +++ b/code/kernel/ke_process.c @@ -1,240 +1,369 @@ -// ke_process.c - process model -#include -#include -#include -#include -#include -#include -#include -#include - -// --------------------------------------------------------------------------- -// Global process list -// --------------------------------------------------------------------------- -static LIST_ENTRY gProcessList; -static SPINLOCK gProcessListLock; -static uint32_t gNextProcessId = 1; - -static void ProcessInit(void) { - static int inited = 0; - if (!inited) { - LIST_INIT(&gProcessList); - gProcessListLock = (SPINLOCK)SPINLOCK_INIT; - inited = 1; - } -} - -LIST_ENTRY* KeGetProcessList(void) { - ProcessInit(); - return &gProcessList; -} - -PROCESS* KeGetCurrentProcess(void) { - THREAD* currentThread = KeGetCurrentThread(); - if (currentThread) { - return currentThread->Owner; - } - return NULL; -} - -size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount) { - ProcessInit(); - - uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); - size_t count = 0; - LIST_ENTRY* head = &gProcessList; - for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { - PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink); - if (pidsOut && count < maxCount) pidsOut[count] = p->ProcessId; - count++; - } - KeReleaseSpinlock(&gProcessListLock, flags); - return count; -} - -// --------------------------------------------------------------------------- -// Destructor (called by ObDereferenceObject when RefCount hits zero) -// --------------------------------------------------------------------------- -static void ProcessDestroy(OBJECT* obj) { - PROCESS* proc = CONTAINING_RECORD(obj, PROCESS, Header); - - // Release all remaining handles. - for (int i = 1; i < HANDLE_TABLE_SIZE; i++) { - if (proc->Handles[i]) { - ObDereferenceObject(proc->Handles[i]); - proc->Handles[i] = (OBJECT *)0; - } - } - - // Release the main executable image. - if (proc->ImageBase && proc->ImageSize) { - MmFreeUserMemory((void *)proc->ImageBase, proc->ImageSize); - proc->ImageBase = 0; - proc->ImageSize = 0; - } - - // Release per-process modules. - for (uint32_t i = 0; i < proc->ModuleCount; i++) { - if (proc->Modules[i].Base && proc->Modules[i].Size) { - MmFreeUserMemory((void *)proc->Modules[i].Base, proc->Modules[i].Size); - } - proc->Modules[i].Base = 0; - proc->Modules[i].Size = 0; - proc->Modules[i].Name[0] = '\0'; - } - proc->ModuleCount = 0; - - // Release the user command line buffer. - if (proc->UserCmdlineBase && proc->UserCmdlineSize) { - MmFreeUserMemory((void *)proc->UserCmdlineBase, proc->UserCmdlineSize); - proc->UserCmdlineBase = 0; - proc->UserCmdlineSize = 0; - } - - // Release the exit event. - if (proc->ExitEvent) { - ObDereferenceObject(&proc->ExitEvent->Header); - proc->ExitEvent = (EVENT *)0; - } - - // Remove from global list. - uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); - ListRemove(&proc->GlobalLink); - KeReleaseSpinlock(&gProcessListLock, flags); - - // Memory for the struct itself is freed by ObDereferenceObject after - // Destroy returns. -} - -// --------------------------------------------------------------------------- -// KeCreateProcess -// --------------------------------------------------------------------------- -KSTATUS KeCreateProcess(const char* name, PROCESS **out) { - ProcessInit(); - - if (!name || !out) return STATUS_INVALID_PARAMETER; - - PROCESS* proc = (PROCESS *)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS)); - if (!proc) return STATUS_NO_MEMORY; - - proc->Header.Destroy = ProcessDestroy; - - proc->ProcessId = _InterlockedExchangeAdd((volatile long *)&gNextProcessId, 1); - strncpy(proc->Name, name, PROCESS_NAME_MAX - 1); - proc->Name[PROCESS_NAME_MAX - 1] = '\0'; - - LIST_INIT(&proc->ThreadList); - proc->ThreadCount = 0; - proc->HandleLock = (SPINLOCK)SPINLOCK_INIT; - proc->ExitCode = 0; - proc->ImageBase = 0; - proc->ImageSize = 0; - proc->UserCmdlineBase = 0; - proc->UserCmdlineSize = 0; - proc->ModuleCount = 0; - - // Slot 0 is the invalid handle - leave it NULL. - memset(proc->Handles, 0, sizeof(proc->Handles)); - - LIST_INIT(&proc->GlobalLink); - - // Create the manual-reset exit event (initially unsignaled). - if (!KSUCCESS(KeEventCreate(EventManualReset, 0, &proc->ExitEvent))) { - ObDereferenceObject(&proc->Header); - return STATUS_NO_MEMORY; - } - - uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); - ListInsertTail(&gProcessList, &proc->GlobalLink); - KeReleaseSpinlock(&gProcessListLock, flags); - - *out = proc; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// KeExitProcess -// --------------------------------------------------------------------------- -void KeExitProcess(PROCESS* process, int exitCode) { - if (!process) return; - process->ExitCode = exitCode; - // Wake any threads waiting for this process to finish. - if (process->ExitEvent) KeSignalEvent(process->ExitEvent); - // The last thread exiting via KeExitThread will drop the process refcount. - // This function is a policy hook; actual cleanup is in ProcessDestroy. - ObDereferenceObject(&process->Header); -} - -// --------------------------------------------------------------------------- -// KeGetProcessExitCode -// --------------------------------------------------------------------------- -KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut) { - if (!process || !exitCodeOut) return STATUS_INVALID_PARAMETER; - *exitCodeOut = process->ExitCode; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// Handle table -// --------------------------------------------------------------------------- -KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) { - if (!process || !object || !handleOut) return STATUS_INVALID_PARAMETER; - - uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); - - for (int i = 1; i < HANDLE_TABLE_SIZE; i++) { - if (!process->Handles[i]) { - ObReferenceObject(object); - process->Handles[i] = object; - *handleOut = (uint32_t)i; - KeReleaseSpinlock(&process->HandleLock, flags); - return STATUS_SUCCESS; - } - } - - KeReleaseSpinlock(&process->HandleLock, flags); - return STATUS_NO_MEMORY; -} - -void KeCloseHandle(PROCESS* process, uint32_t handle) { - if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return; - - uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); - OBJECT* obj = process->Handles[handle]; - if (obj) { - process->Handles[handle] = (OBJECT *)0; - KeReleaseSpinlock(&process->HandleLock, flags); - ObDereferenceObject(obj); - return; - } - KeReleaseSpinlock(&process->HandleLock, flags); -} - -OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle) { - if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) - return (OBJECT *)0; - - uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); - OBJECT* obj = process->Handles[handle]; - if (obj) ObReferenceObject(obj); - KeReleaseSpinlock(&process->HandleLock, flags); - return obj; -} - -PROCESS* KeGetProcessById(uint32_t pid) { - ProcessInit(); - uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); - LIST_ENTRY* head = &gProcessList; - LIST_ENTRY* node = head->Fwd; - while (node != head) { - PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink); - if (p->ProcessId == pid) { - ObReferenceObject(&p->Header); - KeReleaseSpinlock(&gProcessListLock, flags); - return p; - } - node = node->Fwd; - } - KeReleaseSpinlock(&gProcessListLock, flags); - return (PROCESS*)0; -} +// ke_process.c - process model +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Global process list +// --------------------------------------------------------------------------- +static LIST_ENTRY gProcessList; +static SPINLOCK gProcessListLock; +static uint32_t gNextProcessId = 1; + +static void ProcessInit(void) { + static int inited = 0; + if (!inited) { + LIST_INIT(&gProcessList); + gProcessListLock = (SPINLOCK)SPINLOCK_INIT; + inited = 1; + } +} + +LIST_ENTRY* KeGetProcessList(void) { + ProcessInit(); + return &gProcessList; +} + +PROCESS* KeGetCurrentProcess(void) { + THREAD* currentThread = KeGetCurrentThread(); + if (currentThread) { + return currentThread->Owner; + } + return NULL; +} + +size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount) { + ProcessInit(); + + uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); + size_t count = 0; + LIST_ENTRY* head = &gProcessList; + for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) { + PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink); + if (pidsOut && count < maxCount) pidsOut[count] = p->ProcessId; + count++; + } + KeReleaseSpinlock(&gProcessListLock, flags); + return count; +} + +// --------------------------------------------------------------------------- +// Destructor (called by ObDereferenceObject when RefCount hits zero) +// --------------------------------------------------------------------------- +static void ProcessDestroy(OBJECT* obj) { + PROCESS* proc = CONTAINING_RECORD(obj, PROCESS, Header); + + // Release all remaining handles. + for (int i = 1; i < HANDLE_TABLE_SIZE; i++) { + if (proc->Handles[i]) { + ObDereferenceObject(proc->Handles[i]); + proc->Handles[i] = (OBJECT *)0; + } + } + + // Release the main executable image. + if (proc->ImageBase && proc->ImageSize) { + MmFreeUserMemory((void *)proc->ImageBase, proc->ImageSize); + proc->ImageBase = 0; + proc->ImageSize = 0; + } + + // Release per-process modules. + for (uint32_t i = 0; i < proc->ModuleCount; i++) { + if (proc->Modules[i].Base && proc->Modules[i].Size) { + MmFreeUserMemory((void *)proc->Modules[i].Base, proc->Modules[i].Size); + } + proc->Modules[i].Base = 0; + proc->Modules[i].Size = 0; + proc->Modules[i].Name[0] = '\0'; + } + proc->ModuleCount = 0; + + // Release the user-mode process environment. + if (proc->UserPebBase && proc->UserPebSize) { + MmFreeUserMemory((void *)proc->UserPebBase, proc->UserPebSize); + proc->UserPebBase = 0; + proc->UserPebSize = 0; + } + if (proc->UserImagePathBase && proc->UserImagePathSize) { + MmFreeUserMemory((void *)proc->UserImagePathBase, proc->UserImagePathSize); + proc->UserImagePathBase = 0; + proc->UserImagePathSize = 0; + } + if (proc->UserCmdlineBase && proc->UserCmdlineSize) { + MmFreeUserMemory((void *)proc->UserCmdlineBase, proc->UserCmdlineSize); + proc->UserCmdlineBase = 0; + proc->UserCmdlineSize = 0; + } + if (proc->UserCwdBase && proc->UserCwdSize) { + MmFreeUserMemory((void *)proc->UserCwdBase, proc->UserCwdSize); + proc->UserCwdBase = 0; + proc->UserCwdSize = 0; + } + + // Release the exit event. + if (proc->ExitEvent) { + ObDereferenceObject(&proc->ExitEvent->Header); + proc->ExitEvent = (EVENT *)0; + } + + // Remove from global list. + uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); + ListRemove(&proc->GlobalLink); + KeReleaseSpinlock(&gProcessListLock, flags); + + // Memory for the struct itself is freed by ObDereferenceObject after + // Destroy returns. +} + +// --------------------------------------------------------------------------- +// KeCreateProcess +// --------------------------------------------------------------------------- +KSTATUS KeCreateProcess(const char* name, PROCESS **out) { + ProcessInit(); + + if (!name || !out) return STATUS_INVALID_PARAMETER; + + PROCESS* proc = (PROCESS *)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS)); + if (!proc) return STATUS_NO_MEMORY; + + proc->Header.Destroy = ProcessDestroy; + + proc->ProcessId = _InterlockedExchangeAdd((volatile long *)&gNextProcessId, 1); + strncpy(proc->Name, name, PROCESS_NAME_MAX - 1); + proc->Name[PROCESS_NAME_MAX - 1] = '\0'; + + LIST_INIT(&proc->ThreadList); + proc->ThreadCount = 0; + proc->HandleLock = (SPINLOCK)SPINLOCK_INIT; + proc->ExitCode = 0; + proc->ImageBase = 0; + proc->ImageSize = 0; + proc->UserPebBase = 0; + proc->UserPebSize = 0; + proc->UserImagePathBase = 0; + proc->UserImagePathSize = 0; + proc->UserCmdlineBase = 0; + proc->UserCmdlineSize = 0; + proc->UserCwdBase = 0; + proc->UserCwdSize = 0; + proc->ModuleCount = 0; + proc->CurrentDirectory[0] = '\0'; + proc->CwdLock = (SPINLOCK)SPINLOCK_INIT; + + // Slot 0 is the invalid handle - leave it NULL. + memset(proc->Handles, 0, sizeof(proc->Handles)); + + LIST_INIT(&proc->GlobalLink); + + // Create the manual-reset exit event (initially unsignaled). + if (!KSUCCESS(KeEventCreate(EventManualReset, 0, &proc->ExitEvent))) { + ObDereferenceObject(&proc->Header); + return STATUS_NO_MEMORY; + } + + uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); + ListInsertTail(&gProcessList, &proc->GlobalLink); + KeReleaseSpinlock(&gProcessListLock, flags); + + *out = proc; + return STATUS_SUCCESS; +} + + +// --------------------------------------------------------------------------- +// KeSetupUserProcessEnvironment +// --------------------------------------------------------------------------- +static KSTATUS CopyEnvironmentString(const char* src, uintptr_t* baseOut, size_t* sizeOut) { + if (!baseOut || !sizeOut) return STATUS_INVALID_PARAMETER; + if (!src) src = ""; + + size_t len = strlen(src) + 1; + char* dst = (char*)MmAllocateUserMemory(len); + if (!dst) return STATUS_NO_MEMORY; + + memcpy(dst, src, len); + *baseOut = (uintptr_t)dst; + *sizeOut = len; + return STATUS_SUCCESS; +} + +static KSTATUS CopyEnvironmentStringFixed(const char* src, size_t capacity, + uintptr_t* baseOut, size_t* sizeOut) { + if (!baseOut || !sizeOut || capacity == 0) return STATUS_INVALID_PARAMETER; + if (!src) src = ""; + + char* dst = (char*)MmAllocateUserMemory(capacity); + if (!dst) return STATUS_NO_MEMORY; + memset(dst, 0, capacity); + + size_t len = strlen(src); + if (len >= capacity) len = capacity - 1; + memcpy(dst, src, len); + dst[len] = '\0'; + + *baseOut = (uintptr_t)dst; + *sizeOut = capacity; + return STATUS_SUCCESS; +} + +static void FreePartialProcessEnvironment(PROCESS* proc) { + if (!proc) return; + + if (proc->UserPebBase && proc->UserPebSize) { + MmFreeUserMemory((void*)proc->UserPebBase, proc->UserPebSize); + proc->UserPebBase = 0; + proc->UserPebSize = 0; + } + if (proc->UserImagePathBase && proc->UserImagePathSize) { + MmFreeUserMemory((void*)proc->UserImagePathBase, proc->UserImagePathSize); + proc->UserImagePathBase = 0; + proc->UserImagePathSize = 0; + } + if (proc->UserCmdlineBase && proc->UserCmdlineSize) { + MmFreeUserMemory((void*)proc->UserCmdlineBase, proc->UserCmdlineSize); + proc->UserCmdlineBase = 0; + proc->UserCmdlineSize = 0; + } + if (proc->UserCwdBase && proc->UserCwdSize) { + MmFreeUserMemory((void*)proc->UserCwdBase, proc->UserCwdSize); + proc->UserCwdBase = 0; + proc->UserCwdSize = 0; + } +} + +KSTATUS KeSetupUserProcessEnvironment(PROCESS* proc, + const char* imagePath, + const char* commandLine, + const char* currentDirectory) { + if (!proc || !imagePath) return STATUS_INVALID_PARAMETER; + + const char* finalCommandLine = (commandLine && commandLine[0]) ? commandLine : imagePath; + const char* finalCwd = currentDirectory ? currentDirectory : proc->CurrentDirectory; + + FreePartialProcessEnvironment(proc); + + KSTATUS s = CopyEnvironmentString(imagePath, &proc->UserImagePathBase, &proc->UserImagePathSize); + if (!KSUCCESS(s)) goto fail; + + s = CopyEnvironmentString(finalCommandLine, &proc->UserCmdlineBase, &proc->UserCmdlineSize); + if (!KSUCCESS(s)) goto fail; + + s = CopyEnvironmentStringFixed(finalCwd, SYS_CWD_MAX, &proc->UserCwdBase, &proc->UserCwdSize); + if (!KSUCCESS(s)) goto fail; + + SYS_PEB* peb = (SYS_PEB*)MmAllocateUserMemory(sizeof(SYS_PEB)); + if (!peb) { s = STATUS_NO_MEMORY; goto fail; } + memset(peb, 0, sizeof(SYS_PEB)); + + peb->Magic = SYS_PEB_MAGIC; + peb->Version = SYS_ENV_VERSION; + peb->ProcessId = proc->ProcessId; + peb->ImagePath = (const char*)proc->UserImagePathBase; + peb->ImagePathLength = proc->UserImagePathSize ? proc->UserImagePathSize - 1 : 0; + peb->CommandLine = (const char*)proc->UserCmdlineBase; + peb->CommandLineLength = proc->UserCmdlineSize ? proc->UserCmdlineSize - 1 : 0; + peb->CurrentDirectory = (const char*)proc->UserCwdBase; + peb->CurrentDirectoryLength = strlen(finalCwd); + + proc->UserPebBase = (uintptr_t)peb; + proc->UserPebSize = sizeof(SYS_PEB); + return STATUS_SUCCESS; + +fail: + FreePartialProcessEnvironment(proc); + return s; +} + +// --------------------------------------------------------------------------- +// KeExitProcess +// --------------------------------------------------------------------------- +void KeExitProcess(PROCESS* process, int exitCode) { + if (!process) return; + process->ExitCode = exitCode; + // Wake any threads waiting for this process to finish. + if (process->ExitEvent) KeSignalEvent(process->ExitEvent); + // The last thread exiting via KeExitThread will drop the process refcount. + // This function is a policy hook; actual cleanup is in ProcessDestroy. + ObDereferenceObject(&process->Header); +} + +// --------------------------------------------------------------------------- +// KeGetProcessExitCode +// --------------------------------------------------------------------------- +KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut) { + if (!process || !exitCodeOut) return STATUS_INVALID_PARAMETER; + *exitCodeOut = process->ExitCode; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// Handle table +// --------------------------------------------------------------------------- +KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) { + if (!process || !object || !handleOut) return STATUS_INVALID_PARAMETER; + + uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); + + for (int i = 1; i < HANDLE_TABLE_SIZE; i++) { + if (!process->Handles[i]) { + ObReferenceObject(object); + process->Handles[i] = object; + *handleOut = (uint32_t)i; + KeReleaseSpinlock(&process->HandleLock, flags); + return STATUS_SUCCESS; + } + } + + KeReleaseSpinlock(&process->HandleLock, flags); + return STATUS_NO_MEMORY; +} + +void KeCloseHandle(PROCESS* process, uint32_t handle) { + if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return; + + uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); + OBJECT* obj = process->Handles[handle]; + if (obj) { + process->Handles[handle] = (OBJECT *)0; + KeReleaseSpinlock(&process->HandleLock, flags); + ObDereferenceObject(obj); + return; + } + KeReleaseSpinlock(&process->HandleLock, flags); +} + +OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle) { + if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) + return (OBJECT *)0; + + uintptr_t flags = KeAcquireSpinlock(&process->HandleLock); + OBJECT* obj = process->Handles[handle]; + if (obj) ObReferenceObject(obj); + KeReleaseSpinlock(&process->HandleLock, flags); + return obj; +} + +PROCESS* KeGetProcessById(uint32_t pid) { + ProcessInit(); + uintptr_t flags = KeAcquireSpinlock(&gProcessListLock); + LIST_ENTRY* head = &gProcessList; + LIST_ENTRY* node = head->Fwd; + while (node != head) { + PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink); + if (p->ProcessId == pid) { + ObReferenceObject(&p->Header); + KeReleaseSpinlock(&gProcessListLock, flags); + return p; + } + node = node->Fwd; + } + KeReleaseSpinlock(&gProcessListLock, flags); + return (PROCESS*)0; +} diff --git a/code/kernel/ke_process.h b/code/kernel/ke_process.h index 21ad23d..ab2d188 100644 --- a/code/kernel/ke_process.h +++ b/code/kernel/ke_process.h @@ -1,83 +1,86 @@ -#pragma once -// ke_process.h - process model -// A process owns a handle table and a list of threads. -// There is one shared address space - processes do not own one. -#include -#include -#include -#include -#include - -#define PROCESS_NAME_MAX 64 -#define HANDLE_TABLE_SIZE 256 - - -#define PROCESS_MODULE_MAX 16 - -typedef struct PROCESS_MODULE { - char Name[32]; - uintptr_t Base; - size_t Size; -} PROCESS_MODULE; - -typedef struct PROCESS { - OBJECT Header; /* must be first */ - - uint32_t ProcessId; - char Name[PROCESS_NAME_MAX]; /* UTF-8 */ - - // Windows-style flat command line, stored at launch by SysCreateProcess. - // The initial thread receives this pointer as its UserArg so _start can - // parse it into argc/argv without an extra syscall. - char Cmdline[512]; - - char CurrentDirectory[512]; - SPINLOCK CwdLock; - - /* Thread list. */ - LIST_ENTRY ThreadList; /* links THREAD.ProcessLink */ - uint32_t ThreadCount; - - /* Handle table. - Index 0 is always NULL (the invalid handle). - Entries are OBJECT pointers, each holding a reference. */ - OBJECT* Handles[HANDLE_TABLE_SIZE]; - SPINLOCK HandleLock; - - int ExitCode; - - /* Main executable image. Used for cleanup. */ - uintptr_t ImageBase; - size_t ImageSize; - - /* User command line buffer passed to the initial thread. */ - uintptr_t UserCmdlineBase; - size_t UserCmdlineSize; - - /* Per-process loaded modules (user processes only). */ - PROCESS_MODULE Modules[PROCESS_MODULE_MAX]; - uint32_t ModuleCount; - - /* Signaled (manual-reset) when the process exits. - Waiters block on this via KeWaitForEvent. */ - EVENT* ExitEvent; - - LIST_ENTRY GlobalLink; /* node in the global process list */ -} PROCESS; - -KSTATUS KeCreateProcess(const char* name, PROCESS **out); -void KeExitProcess(PROCESS* process, int exitCode); -KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut); - -/* Handle table operations. */ -KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut); -void KeCloseHandle(PROCESS* process, uint32_t handle); -OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle); - -/* Return the global process list head (kernel-internal). */ -LIST_ENTRY* KeGetProcessList(void); -PROCESS* KeGetCurrentProcess(void); -/* Copy a snapshot of process IDs into caller-provided storage. */ -size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount); -/* Find a live process by its ProcessId. Bumps refcount; caller must ObDereferenceObject. */ -PROCESS* KeGetProcessById(uint32_t pid); +#pragma once +// ke_process.h - process model +// A process owns a handle table and a list of threads. +// There is one shared address space - processes do not own one. +#include +#include +#include +#include +#include +#include + +#define PROCESS_NAME_MAX 64 +#define HANDLE_TABLE_SIZE 256 + + +#define PROCESS_MODULE_MAX 16 + +typedef struct PROCESS_MODULE { + char Name[32]; + uintptr_t Base; + size_t Size; +} PROCESS_MODULE; + +typedef struct PROCESS { + OBJECT Header; /* must be first */ + + uint32_t ProcessId; + char Name[PROCESS_NAME_MAX]; /* UTF-8 */ + + char CurrentDirectory[512]; + SPINLOCK CwdLock; + + /* Thread list. */ + LIST_ENTRY ThreadList; /* links THREAD.ProcessLink */ + uint32_t ThreadCount; + + /* Handle table. + Index 0 is always NULL (the invalid handle). + Entries are OBJECT pointers, each holding a reference. */ + OBJECT* Handles[HANDLE_TABLE_SIZE]; + SPINLOCK HandleLock; + + int ExitCode; + + /* Main executable image. Used for cleanup. */ + uintptr_t ImageBase; + size_t ImageSize; + + /* User-mode process environment. The initial thread sees this through GS->TEB->PEB. */ + uintptr_t UserPebBase; + size_t UserPebSize; + uintptr_t UserImagePathBase; + size_t UserImagePathSize; + uintptr_t UserCmdlineBase; + size_t UserCmdlineSize; + uintptr_t UserCwdBase; + size_t UserCwdSize; + + /* Per-process loaded modules (user processes only). */ + PROCESS_MODULE Modules[PROCESS_MODULE_MAX]; + uint32_t ModuleCount; + + /* Signaled (manual-reset) when the process exits. + Waiters block on this via KeWaitForEvent. */ + EVENT* ExitEvent; + + LIST_ENTRY GlobalLink; /* node in the global process list */ +} PROCESS; + +KSTATUS KeCreateProcess(const char* name, PROCESS **out); +KSTATUS KeSetupUserProcessEnvironment(PROCESS* process, const char* imagePath, const char* commandLine, const char* currentDirectory); +void KeExitProcess(PROCESS* process, int exitCode); +KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut); + +/* Handle table operations. */ +KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut); +void KeCloseHandle(PROCESS* process, uint32_t handle); +OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle); + +/* Return the global process list head (kernel-internal). */ +LIST_ENTRY* KeGetProcessList(void); +PROCESS* KeGetCurrentProcess(void); +/* Copy a snapshot of process IDs into caller-provided storage. */ +size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount); +/* Find a live process by its ProcessId. Bumps refcount; caller must ObDereferenceObject. */ +PROCESS* KeGetProcessById(uint32_t pid); diff --git a/code/kernel/ke_switch.asm b/code/kernel/ke_switch.asm index eebfc02..2b2e4cc 100644 --- a/code/kernel/ke_switch.asm +++ b/code/kernel/ke_switch.asm @@ -1,138 +1,140 @@ -; ke_switch.asm - context switch and thread entry stubs (MASM64 / ML64) -; -; Struct offsets: -; sizeof(OBJECT) = 16 (type:4, refcount:4, destroy:8) -; THREAD.SavedContext offset = 88 -; THREAD.TssRsp0 offset = 104 -; TSS64.RSP0 offset = 4 -; -THREAD_SavedContext EQU 88 -THREAD_TssRsp0 EQU 104 -THREAD_Teb EQU 112 -THREAD_FpuContext EQU 160 -TSS_RSP0_OFFSET EQU 4 - -EXTERN gTss:QWORD ; address of the single TSS, exported from ke_idt.c - -.CODE - -; ------------------------------------------------------------------------- -; void KeSwitchContext(THREAD* from, THREAD* to) -; RCX = from, RDX = to (Microsoft x64) -; ------------------------------------------------------------------------- -KeSwitchContext PROC - mov rax, [rcx + THREAD_FpuContext] - fxsave64 [rax] - - ; Save non-volatile registers onto the current (outgoing) kernel stack. - push rbx - push rbp - push r12 - push r13 - push r14 - push r15 - - ; from->SavedContext = rsp - mov [rcx + THREAD_SavedContext], rsp - - ; rsp = to->SavedContext - mov rsp, [rdx + THREAD_SavedContext] - - ; Update MSR_GS_BASE (0xC0000101) with to->Teb - mov rax, [rdx + THREAD_Teb] ; Fetch the incoming TEB pointer - mov ecx, 0C0000101h ; IA32_GS_BASE MSR - mov r8, rdx ; Save RDX ('to' thread) as WRMSR clobbers EDX - mov rdx, rax ; Copy TEB pointer to RDX - shr rdx, 32 ; EDX = High 32 bits of TEB - ; EAX = Low 32 bits of TEB (upper 32 bits ignored by CPU) - wrmsr ; Write to MSR - mov rdx, r8 ; Restore RDX - - ; Update TSS.RSP0 so that the next interrupt/syscall uses the - ; incoming thread's kernel stack top. - mov rax, [rdx + THREAD_TssRsp0] - mov r8, gTss - mov qword ptr [r8 + TSS_RSP0_OFFSET], rax - - mov rax, [rdx + THREAD_FpuContext] - fxrstor64 [rax] - - ; Restore non-volatile registers of the incoming thread. - pop r15 - pop r14 - pop r13 - pop r12 - pop rbp - pop rbx - ret -KeSwitchContext ENDP - -; ------------------------------------------------------------------------- -; KeThreadEntry - landing pad for brand-new threads. -; -; Stack layout when we arrive here (set up by KeCreateThread): -; [rsp+00] = KCONTEXT (already popped by KeSwitchContext ret) -; After ret from KeSwitchContext RSP points here: -; [rsp+00] = user RCX arg -; [rsp+08] = user RIP -; [rsp+16] = user CS (0x1B) -; [rsp+24] = RFLAGS (0x202) -; [rsp+32] = user RSP -; [rsp+40] = user SS (0x23) -; -; We load RCX from the stack (first arg per MS x64), then IRETQ. -; ------------------------------------------------------------------------- -EXTERN KeSchedulerPostSwitch : PROC -EXTERN KeGetCurrentThread : PROC -EXTERN KeExitThread : PROC - -KeThreadEntry PROC - sub rsp, 32 ; Reserve shadow space - call KeSchedulerPostSwitch - add rsp, 32 - pop rcx ; load user arg - iretq ; -> ring 3 -KeThreadEntry ENDP - -; ------------------------------------------------------------------------- -; KeKernelThreadEntry - landing pad for kernel-mode threads created by -; KeCreateKernelThread. Never touches ring 3; no IRETQ. -; -; Stack layout when we arrive here (above the KCONTEXT already popped by -; KeSwitchContext's ret): -; [rsp+00] fn ptr (kernel function: void fn(void*)) -; [rsp+08] arg ptr -; -; We pop both into non-volatile registers so KeSchedulerPostSwitch can -; clobber volatile ones freely, then call fn(arg). On return we call -; KeExitThread so the thread is cleanly destroyed. -; ------------------------------------------------------------------------- -KeKernelThreadEntry PROC - pop rbx ; rbx = fn ptr (non-volatile, survives the call below) - pop r12 ; r12 = arg (non-volatile) - - sub rsp, 32 - call KeSchedulerPostSwitch ; release scheduler spinlock, reap dead threads - add rsp, 32 - - mov rcx, r12 ; first arg = arg - sub rsp, 32 - call rbx ; fn(arg) -- still ring 0 - add rsp, 32 - - ; Thread function returned -- exit cleanly. - sub rsp, 32 - call KeGetCurrentThread ; rax = current THREAD* - add rsp, 32 - mov rcx, rax ; arg1 = thread* - xor edx, edx ; arg2 = exitCode 0 - sub rsp, 32 - call KeExitThread - add rsp, 32 - - ; KeExitThread -> KeYield -> KeSwitchContext never returns here, but - ; the assembler needs a defined end. -@@: jmp @B -KeKernelThreadEntry ENDP - -END +; ke_switch.asm - context switch and thread entry stubs (MASM64 / ML64) +; +; Struct offsets: +; sizeof(OBJECT) = 16 (type:4, refcount:4, destroy:8) +; THREAD.SavedContext offset = 88 +; THREAD.TssRsp0 offset = 104 +; THREAD.Teb offset = 112 +; THREAD.FpuContext offset = 168 +; TSS64.RSP0 offset = 4 +; +THREAD_SavedContext EQU 88 +THREAD_TssRsp0 EQU 104 +THREAD_Teb EQU 112 +THREAD_FpuContext EQU 168 +TSS_RSP0_OFFSET EQU 4 + +EXTERN gTss:QWORD ; address of the single TSS, exported from ke_idt.c + +.CODE + +; ------------------------------------------------------------------------- +; void KeSwitchContext(THREAD* from, THREAD* to) +; RCX = from, RDX = to (Microsoft x64) +; ------------------------------------------------------------------------- +KeSwitchContext PROC + mov rax, [rcx + THREAD_FpuContext] + fxsave64 [rax] + + ; Save non-volatile registers onto the current (outgoing) kernel stack. + push rbx + push rbp + push r12 + push r13 + push r14 + push r15 + + ; from->SavedContext = rsp + mov [rcx + THREAD_SavedContext], rsp + + ; rsp = to->SavedContext + mov rsp, [rdx + THREAD_SavedContext] + + ; Update MSR_GS_BASE (0xC0000101) with to->Teb + mov rax, [rdx + THREAD_Teb] ; Fetch the incoming TEB pointer + mov ecx, 0C0000101h ; IA32_GS_BASE MSR + mov r8, rdx ; Save RDX ('to' thread) as WRMSR clobbers EDX + mov rdx, rax ; Copy TEB pointer to RDX + shr rdx, 32 ; EDX = High 32 bits of TEB + ; EAX = Low 32 bits of TEB (upper 32 bits ignored by CPU) + wrmsr ; Write to MSR + mov rdx, r8 ; Restore RDX + + ; Update TSS.RSP0 so that the next interrupt/syscall uses the + ; incoming thread's kernel stack top. + mov rax, [rdx + THREAD_TssRsp0] + mov r8, gTss + mov qword ptr [r8 + TSS_RSP0_OFFSET], rax + + mov rax, [rdx + THREAD_FpuContext] + fxrstor64 [rax] + + ; Restore non-volatile registers of the incoming thread. + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + ret +KeSwitchContext ENDP + +; ------------------------------------------------------------------------- +; KeThreadEntry - landing pad for brand-new threads. +; +; Stack layout when we arrive here (set up by KeCreateThread): +; [rsp+00] = KCONTEXT (already popped by KeSwitchContext ret) +; After ret from KeSwitchContext RSP points here: +; [rsp+00] = user RCX arg +; [rsp+08] = user RIP +; [rsp+16] = user CS (0x1B) +; [rsp+24] = RFLAGS (0x202) +; [rsp+32] = user RSP +; [rsp+40] = user SS (0x23) +; +; We load RCX from the stack (first arg per MS x64), then IRETQ. +; ------------------------------------------------------------------------- +EXTERN KeSchedulerPostSwitch : PROC +EXTERN KeGetCurrentThread : PROC +EXTERN KeExitThread : PROC + +KeThreadEntry PROC + sub rsp, 32 ; Reserve shadow space + call KeSchedulerPostSwitch + add rsp, 32 + pop rcx ; load user arg + iretq ; -> ring 3 +KeThreadEntry ENDP + +; ------------------------------------------------------------------------- +; KeKernelThreadEntry - landing pad for kernel-mode threads created by +; KeCreateKernelThread. Never touches ring 3; no IRETQ. +; +; Stack layout when we arrive here (above the KCONTEXT already popped by +; KeSwitchContext's ret): +; [rsp+00] fn ptr (kernel function: void fn(void*)) +; [rsp+08] arg ptr +; +; We pop both into non-volatile registers so KeSchedulerPostSwitch can +; clobber volatile ones freely, then call fn(arg). On return we call +; KeExitThread so the thread is cleanly destroyed. +; ------------------------------------------------------------------------- +KeKernelThreadEntry PROC + pop rbx ; rbx = fn ptr (non-volatile, survives the call below) + pop r12 ; r12 = arg (non-volatile) + + sub rsp, 32 + call KeSchedulerPostSwitch ; release scheduler spinlock, reap dead threads + add rsp, 32 + + mov rcx, r12 ; first arg = arg + sub rsp, 32 + call rbx ; fn(arg) -- still ring 0 + add rsp, 32 + + ; Thread function returned -- exit cleanly. + sub rsp, 32 + call KeGetCurrentThread ; rax = current THREAD* + add rsp, 32 + mov rcx, rax ; arg1 = thread* + xor edx, edx ; arg2 = exitCode 0 + sub rsp, 32 + call KeExitThread + add rsp, 32 + + ; KeExitThread -> KeYield -> KeSwitchContext never returns here, but + ; the assembler needs a defined end. +@@: jmp @B +KeKernelThreadEntry ENDP + +END diff --git a/code/kernel/ke_syscall.c b/code/kernel/ke_syscall.c index 22204c3..83d4993 100644 --- a/code/kernel/ke_syscall.c +++ b/code/kernel/ke_syscall.c @@ -1,1331 +1,1331 @@ -// ke_syscall.c - system call dispatcher -// -// KeSyscallEntry (ke_syscall.asm) handles the INT 0x40 ring-3 -> ring-0 -// transition, re-orders the arguments to match MS x64, and calls -// KeSyscallDispatch with: -// -// nr = original RAX (syscall number) -// a0 .. a5 = RCX, RDX, R8, R9, R10, R11 (user arguments) -// -// Every handler receives the same (a0..a5) signature even if it uses fewer -// arguments; unused parameters are silently ignored. -// -// Return value (RAX) is a KSTATUS code *or* an arbitrary 64-bit datum -// (pointer, size, tick count, etc.) depending on the call; conventions are -// documented in syscall_nr.h. -// -// --------------------------------------------------------------------------- -// Unified handle design -// --------------------------------------------------------------------------- -// All kernel objects (FILE_OBJECT, MUTEX, SEMAPHORE, EVENT, QUEUE) are -// reference-counted OBJECTs stored in the per-process handle table. -// SYS_CLOSE_HANDLE is the single close path for every type: -// -// KeCloseHandle(proc, handle) -// -> ObDereferenceObject(obj) -// -> obj->Destroy(obj) [type-specific cleanup, e.g. FileDestroy] -// -> MmFreePool(obj) -// -// There is no separate SyscallCloseFile. Removing that stub was the right -// call: the object model already handled it; the dedicated syscall was only -// adding a footgun where callers could bypass the unified refcount path. - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// --------------------------------------------------------------------------- -// HAL pointer - set once at boot by KeSyscallSetHal() -// --------------------------------------------------------------------------- -static const HAL_API* gHal; - -void KeSyscallSetHal(const HAL_API* hal) { gHal = hal; } - -// --------------------------------------------------------------------------- -// Helper: look up a handle and type-check it in one call. -// Bumps the refcount on success; caller must ObDereferenceObject when done. -// Returns NULL if the handle is invalid or the type does not match. -// --------------------------------------------------------------------------- -static OBJECT* HandleToObject(uint32_t handle, OBJECT_TYPE expected) { - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return NULL; - OBJECT* obj = KeLookupHandle(proc, handle); // bumps refcount - if (!obj) return NULL; - if (obj->Type != expected) { - ObDereferenceObject(obj); // undo the bump - return NULL; - } - return obj; -} - -// --------------------------------------------------------------------------- -// Helper: allocate a handle in the current process's table for 'obj'. -// Takes one reference on behalf of the handle slot. -// On failure, drops the reference it would have stored. -// --------------------------------------------------------------------------- -static KSTATUS AllocHandleForCurrent(OBJECT* obj, uint32_t* handleOut) { - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return STATUS_FAILURE; - KSTATUS s = KeAllocateHandle(proc, obj, handleOut); - if (!KSUCCESS(s)) ObDereferenceObject(obj); - return s; -} - -// =========================================================================== -// Individual syscall handlers -// =========================================================================== - -// --------------------------------------------------------------------------- -// SYS_EXIT_PROCESS (0) a0 = exitCode -// --------------------------------------------------------------------------- -static uint64_t SyscallExitProcess(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - THREAD* t = KeGetCurrentThread(); - PROCESS* proc = t ? t->Owner : NULL; - if (proc) KeExitProcess(proc, (int)a0); - if (t) KeExitThread(t, (int)a0); - while (true) ; // unreachable - KeExitThread does not return -} - -// --------------------------------------------------------------------------- -// SYS_EXIT_THREAD (1) a0 = exitCode -// --------------------------------------------------------------------------- -static uint64_t SyscallExitThread(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - THREAD* t = KeGetCurrentThread(); - if (t) KeExitThread(t, (int)a0); - while (true); -} - -// --------------------------------------------------------------------------- -// SYS_CREATE_THREAD (2) a0=entry a1=arg a2=priority a3=*tidOut -// --------------------------------------------------------------------------- -static uint64_t SyscallCreateThread(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a4; (void)a5; - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return STATUS_FAILURE; - if (a2 >= PriorityCount) return STATUS_INVALID_PARAMETER; - - THREAD* newt = NULL; - KSTATUS s = KeCreateThread(proc, (uintptr_t)a0, (uintptr_t)a1, - (THREAD_PRIORITY)a2, &newt); - if (!KSUCCESS(s)) return s; - if (a3) *(uint32_t*)(uintptr_t)a3 = newt->ThreadId; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_GET_CURRENT_TID (3) -// --------------------------------------------------------------------------- -static uint64_t SyscallGetCurrentTid(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - THREAD* t = KeGetCurrentThread(); - return t ? t->ThreadId : 0; -} - -// --------------------------------------------------------------------------- -// SYS_GET_CURRENT_PID (4) -// --------------------------------------------------------------------------- -static uint64_t SyscallGetCurrentPid(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - PROCESS* p = KeGetCurrentProcess(); - return p ? p->ProcessId : 0; -} - -// --------------------------------------------------------------------------- -// SYS_YIELD (5) -// --------------------------------------------------------------------------- -static uint64_t SyscallYield(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - KeYield(); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_SLEEP (6) a0 = milliseconds -// --------------------------------------------------------------------------- -static uint64_t SyscallSleep(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!gHal) return STATUS_FAILURE; - uint64_t deadline = gHal->Timer.QueryTicks() + (uint64_t)a0; - while (gHal->Timer.QueryTicks() < deadline) - KeYield(); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_SET_THREAD_PRIORITY (7) a0 = priority -// --------------------------------------------------------------------------- -static uint64_t SyscallSetThreadPriority(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (a0 >= PriorityCount) return STATUS_INVALID_PARAMETER; - THREAD* t = KeGetCurrentThread(); - if (!t) return STATUS_FAILURE; - t->Priority = (THREAD_PRIORITY)a0; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_ALLOC (8) a0 = size -> void* -// --------------------------------------------------------------------------- -static uint64_t SyscallAlloc(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0) return 0; - return (uint64_t)(uintptr_t)MmAllocateUserMemory((size_t)a0); -} - -// --------------------------------------------------------------------------- -// SYS_FREE (9) a0 = ptr, a1 = size -// --------------------------------------------------------------------------- -static uint64_t SyscallFree(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - if (a0) MmFreeUserMemory((void*)(uintptr_t)a0, (size_t)a1); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_REALLOC (10) a0 = ptr, a1 = newSize -> void* -// --------------------------------------------------------------------------- -static uint64_t SyscallRealloc(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - return (uint64_t)(uintptr_t)MmReallocateUserMemory( - (void*)(uintptr_t)a0, (size_t)a1); -} - -// --------------------------------------------------------------------------- -// SYS_MUTEX_CREATE (11) a0 = *handleOut -// --------------------------------------------------------------------------- -static uint64_t SyscallMutexCreate(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0) return STATUS_INVALID_PARAMETER; - MUTEX* mx = NULL; - KSTATUS s = MutexCreate(&mx); - if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = AllocHandleForCurrent(&mx->Header, &h); // takes ownership of the ref - if (!KSUCCESS(s)) return s; - *(uint32_t*)(uintptr_t)a0 = h; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_MUTEX_ACQUIRE (12) a0 = handle -// --------------------------------------------------------------------------- -static uint64_t SyscallMutexAcquire(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - MUTEX* mx = (MUTEX*)HandleToObject((uint32_t)a0, ObjectTypeMutex); - if (!mx) return STATUS_NOT_FOUND; - KSTATUS s = KeAcquireMutex(mx); - ObDereferenceObject(&mx->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_MUTEX_TRY_ACQUIRE (13) a0 = handle -// --------------------------------------------------------------------------- -static uint64_t SyscallMutexTryAcquire(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - MUTEX* mx = (MUTEX*)HandleToObject((uint32_t)a0, ObjectTypeMutex); - if (!mx) return STATUS_NOT_FOUND; - KSTATUS s = KeTryAcquireMutex(mx); - ObDereferenceObject(&mx->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_MUTEX_RELEASE (14) a0 = handle -// --------------------------------------------------------------------------- -static uint64_t SyscallMutexRelease(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - MUTEX* mx = (MUTEX*)HandleToObject((uint32_t)a0, ObjectTypeMutex); - if (!mx) return STATUS_NOT_FOUND; - KeReleaseMutex(mx); - ObDereferenceObject(&mx->Header); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_SEM_CREATE (15) a0=initial a1=max a2=*handleOut -// --------------------------------------------------------------------------- -static uint64_t SyscallSemCreate(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a2) return STATUS_INVALID_PARAMETER; - SEMAPHORE* sem = NULL; - KSTATUS s = KeSemaphoreCreate((int32_t)a0, (int32_t)a1, &sem); - if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = AllocHandleForCurrent(&sem->Header, &h); - if (!KSUCCESS(s)) return s; - *(uint32_t*)(uintptr_t)a2 = h; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_SEM_WAIT (16) a0 = handle -// --------------------------------------------------------------------------- -static uint64_t SyscallSemWait(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - SEMAPHORE* sem = (SEMAPHORE*)HandleToObject((uint32_t)a0, ObjectTypeSemaphore); - if (!sem) return STATUS_NOT_FOUND; - KSTATUS s = KeWaitSemaphore(sem); - ObDereferenceObject(&sem->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_SEM_TRY_WAIT (17) a0 = handle -// --------------------------------------------------------------------------- -static uint64_t SyscallSemTryWait(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - SEMAPHORE* sem = (SEMAPHORE*)HandleToObject((uint32_t)a0, ObjectTypeSemaphore); - if (!sem) return STATUS_NOT_FOUND; - KSTATUS s = KeTrySemaphore(sem); - ObDereferenceObject(&sem->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_SEM_SIGNAL (18) a0 = handle -// --------------------------------------------------------------------------- -static uint64_t SyscallSemSignal(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - SEMAPHORE* sem = (SEMAPHORE*)HandleToObject((uint32_t)a0, ObjectTypeSemaphore); - if (!sem) return STATUS_NOT_FOUND; - KeSignalSemaphore(sem); - ObDereferenceObject(&sem->Header); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_EVENT_CREATE (19) a0=type a1=initialState a2=*handleOut -// --------------------------------------------------------------------------- -static uint64_t SyscallEventCreate(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a2 || a0 > EventManualReset) return STATUS_INVALID_PARAMETER; - EVENT* ev = NULL; - KSTATUS s = KeEventCreate((EVENT_TYPE)a0, (uint32_t)a1, &ev); - if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = AllocHandleForCurrent(&ev->Header, &h); - if (!KSUCCESS(s)) return s; - *(uint32_t*)(uintptr_t)a2 = h; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_EVENT_SIGNAL (20) a0 = handle -// --------------------------------------------------------------------------- -static uint64_t SyscallEventSignal(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - EVENT* ev = (EVENT*)HandleToObject((uint32_t)a0, ObjectTypeEvent); - if (!ev) return STATUS_NOT_FOUND; - KeSignalEvent(ev); - ObDereferenceObject(&ev->Header); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_EVENT_CLEAR (21) a0 = handle -// --------------------------------------------------------------------------- -static uint64_t SyscallEventClear(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - EVENT* ev = (EVENT*)HandleToObject((uint32_t)a0, ObjectTypeEvent); - if (!ev) return STATUS_NOT_FOUND; - KeClearEvent(ev); - ObDereferenceObject(&ev->Header); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_EVENT_WAIT (22) a0 = handle -// --------------------------------------------------------------------------- -static uint64_t SyscallEventWait(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - EVENT* ev = (EVENT*)HandleToObject((uint32_t)a0, ObjectTypeEvent); - if (!ev) return STATUS_NOT_FOUND; - KSTATUS s = KeWaitForEvent(ev); - ObDereferenceObject(&ev->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_QUEUE_CREATE (23) a0 = *handleOut -// --------------------------------------------------------------------------- -static uint64_t SyscallQueueCreate(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0) return STATUS_INVALID_PARAMETER; - QUEUE* q = NULL; - KSTATUS s = QueueCreate(&q); - if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = AllocHandleForCurrent(&q->Header, &h); - if (!KSUCCESS(s)) return s; - *(uint32_t*)(uintptr_t)a0 = h; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_QUEUE_SEND (24) a0=handle a1=*msg a2=blocking -// --------------------------------------------------------------------------- -static uint64_t SyscallQueueSend(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a1) return STATUS_INVALID_PARAMETER; - QUEUE* q = (QUEUE*)HandleToObject((uint32_t)a0, ObjectTypeQueue); - if (!q) return STATUS_NOT_FOUND; - KSTATUS s = KeSendMessage(q, (const QUEUE_MESSAGE*)(uintptr_t)a1, (bool)a2); - ObDereferenceObject(&q->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_QUEUE_RECV (25) a0=handle a1=*msg a2=blocking -// --------------------------------------------------------------------------- -static uint64_t SyscallQueueRecv(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a1) return STATUS_INVALID_PARAMETER; - QUEUE* q = (QUEUE*)HandleToObject((uint32_t)a0, ObjectTypeQueue); - if (!q) return STATUS_NOT_FOUND; - KSTATUS s = KeReceiveMessage(q, (QUEUE_MESSAGE*)(uintptr_t)a1, (bool)a2); - ObDereferenceObject(&q->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_CLOSE_HANDLE (26) a0 = handle -// -// The one and only close path. KeCloseHandle drops the handle-table reference -// on 'obj'. If that was the last reference, ObDereferenceObject runs the -// type-specific Destroy callback (FileDestroy, etc.) and frees the pool memory. -// No per-type close syscall needed or wanted. -// --------------------------------------------------------------------------- -static uint64_t SyscallCloseHandle(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return STATUS_FAILURE; - KeCloseHandle(proc, (uint32_t)a0); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_OPEN_FILE (27) a0=*path a1=*handleOut -// --------------------------------------------------------------------------- -static uint64_t SyscallOpenFile(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0 || !a1) return STATUS_INVALID_PARAMETER; - FILE_OBJECT* file = NULL; - KSTATUS s = IoOpenFile((const char*)(uintptr_t)a0, &file); - if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = AllocHandleForCurrent(&file->Header, &h); - if (!KSUCCESS(s)) { - // AllocHandleForCurrent already dropped the ref on failure; - // the refcount is now 0, so ObDereferenceObject will run FileDestroy. - ObDereferenceObject(&file->Header); - return s; - } - *(uint32_t*)(uintptr_t)a1 = h; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_READ_FILE (28) a0=handle a1=buf a2=size a3=*bytesReadOut -// --------------------------------------------------------------------------- -static uint64_t SyscallReadFile(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a4; (void)a5; - if (!a1) return STATUS_INVALID_PARAMETER; - FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); - if (!file) return STATUS_NOT_FOUND; - size_t got = 0; - KSTATUS s = IoReadFile(file, (void*)(uintptr_t)a1, (size_t)a2, &got); - if (a3) *(size_t*)(uintptr_t)a3 = got; - ObDereferenceObject(&file->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_WRITE_FILE (29) a0=handle a1=buf a2=size a3=*bytesWrittenOut -// --------------------------------------------------------------------------- -static uint64_t SyscallWriteFile(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a4; (void)a5; - if (!a1) return STATUS_INVALID_PARAMETER; - FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); - if (!file) return STATUS_NOT_FOUND; - size_t written = 0; - KSTATUS s = IoWriteFile(file, (const void*)(uintptr_t)a1, (size_t)a2, &written); - if (a3) *(size_t*)(uintptr_t)a3 = written; - ObDereferenceObject(&file->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_CREATE_FILE (30) a0=*path a1=*handleOut -// --------------------------------------------------------------------------- -static uint64_t SyscallCreateFile(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0 || !a1) return STATUS_INVALID_PARAMETER; - FILE_OBJECT* file = NULL; - KSTATUS s = IoCreateFile((const char*)(uintptr_t)a0, &file); - if (!KSUCCESS(s)) return s; - uint32_t h = 0; - s = AllocHandleForCurrent(&file->Header, &h); - if (!KSUCCESS(s)) { - ObDereferenceObject(&file->Header); - return s; - } - *(uint32_t*)(uintptr_t)a1 = h; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_DELETE_FILE (31) a0=*path -// --------------------------------------------------------------------------- -static uint64_t SyscallDeleteFile(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!a0) return STATUS_INVALID_PARAMETER; - return IoDeleteFile((const char*)(uintptr_t)a0); -} - -// --------------------------------------------------------------------------- -// SYS_TRUNCATE_FILE (32) a0=handle a1=newSize -// --------------------------------------------------------------------------- -static uint64_t SyscallTruncateFile(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); - if (!file) return STATUS_NOT_FOUND; - KSTATUS s = IoTruncateFile(file, (uint64_t)a1); - ObDereferenceObject(&file->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_GET_FILE_POS (33) a0=handle -> current seek offset -// --------------------------------------------------------------------------- -static uint64_t SyscallGetFilePos(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); - if (!file) return (uint64_t)-1; - uint64_t pos = file->Offset; - ObDereferenceObject(&file->Header); - return pos; -} - -// --------------------------------------------------------------------------- -// SYS_SEEK_FILE (34) a0=handle a1=offset -// --------------------------------------------------------------------------- -static uint64_t SyscallSeekFile(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); - if (!file) return STATUS_NOT_FOUND; - KSTATUS s = IoSeekFile(file, (uint64_t)a1); - ObDereferenceObject(&file->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_GET_FILE_SIZE (35) a0=handle -> uint64_t -// --------------------------------------------------------------------------- -static uint64_t SyscallGetFileSize(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); - if (!file) return 0; - uint64_t sz = IoGetFileSize(file); - ObDereferenceObject(&file->Header); - return sz; -} - -// --------------------------------------------------------------------------- -// SYS_SCREEN_BLIT (36) a0=x a1=y a2=w a3=h a4=*pixels -// --------------------------------------------------------------------------- -static uint64_t SyscallScreenBlit(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a5; - if (!gHal || !a4) return STATUS_FAILURE; - gHal->Screen.Blit((int)a0, (int)a1, (int)a2, (int)a3, - (const uint32_t*)(uintptr_t)a4); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_SCREEN_CLEAR (37) a0=color -// --------------------------------------------------------------------------- -static uint64_t SyscallScreenClear(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!gHal) return STATUS_FAILURE; - gHal->Screen.Clear((uint32_t)a0); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_SCREEN_GET_SIZE (38) a0=*widthOut a1=*heightOut -// --------------------------------------------------------------------------- -static uint64_t SyscallScreenGetSize(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - if (!gHal) return STATUS_FAILURE; - int w = 0, h = 0; - gHal->Screen.GetSize(&w, &h); - if (a0) *(int*)(uintptr_t)a0 = w; - if (a1) *(int*)(uintptr_t)a1 = h; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_SCREEN_PUT_CHAR (39) a0=c a1=x a2=y a3=fg a4=bg -// --------------------------------------------------------------------------- -static uint64_t SyscallScreenPutChar(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a5; - if (!gHal) return STATUS_FAILURE; - gHal->Screen.PutChar((char)a0, (int)a1, (int)a2, - (uint32_t)a3, (uint32_t)a4); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_KB_POLL (40) -> 1 or 0 -// --------------------------------------------------------------------------- -static uint64_t SyscallKbPoll(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!gHal) return 0; - return gHal->Keyboard.Poll(); -} - -// --------------------------------------------------------------------------- -// SYS_KB_IS_KEY_DOWN (41) a0=scancode a1=extended -> 1 or 0 -// --------------------------------------------------------------------------- -static uint64_t SyscallKbIsKeyDown(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - - // Check gHal and ensure the HAL struct actually has the new function pointer - if (!gHal || !gHal->Keyboard.IsKeyDown) return 0; - - return gHal->Keyboard.IsKeyDown((uint8_t)a0, (bool)a1) ? 1u : 0u; -} - -// --------------------------------------------------------------------------- -// SYS_KB_READ_CHAR (42) -> char (blocks, yields while waiting) -// --------------------------------------------------------------------------- -static uint64_t SyscallKbReadChar(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!gHal) return 0; - while (!gHal->Keyboard.Poll()) KeYield(); - return (uint64_t)(uint8_t)gHal->Keyboard.ReadChar(); -} - -// --------------------------------------------------------------------------- -// SYS_KB_READ_SCANCODE (43) -> uint8_t (blocks, yields while waiting) -// --------------------------------------------------------------------------- -static uint64_t SyscallKbReadScancode(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!gHal) return 0; - while (!gHal->Keyboard.Poll()) KeYield(); - return (uint64_t)gHal->Keyboard.ReadScancode(); -} - -// --------------------------------------------------------------------------- -// SYS_KB_GET_MODIFIERS (44) -> uint32_t -// --------------------------------------------------------------------------- -static uint64_t SyscallKbGetModifiers(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!gHal) return 0; - return (uint64_t)gHal->Keyboard.GetModifiers(); -} - -// --------------------------------------------------------------------------- -// SYS_MOUSE_POLL (45) -> 1 or 0 -// --------------------------------------------------------------------------- -static uint64_t SyscallMousePoll(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (!gHal) return 0; - return gHal->Mouse.Poll() ? 1u : 0u; -} - -// --------------------------------------------------------------------------- -// SYS_MOUSE_READ (46) a0=*dx a1=*dy a2=*buttons -> 1 or 0 -// --------------------------------------------------------------------------- -static uint64_t SyscallMouseRead(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!gHal) return 0; - return gHal->Mouse.Read((int*)(uintptr_t)a0, - (int*)(uintptr_t)a1, - (uint8_t*)(uintptr_t)a2) ? 1u : 0u; -} - -// --------------------------------------------------------------------------- -// SYS_MOUSE_GET_POS (47) a0=*x a1=*y -// --------------------------------------------------------------------------- -static uint64_t SyscallMouseGetPos(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - if (!gHal) return 0; - gHal->Mouse.GetPosition((int*)(uintptr_t)a0, (int*)(uintptr_t)a1); - return 0; -} - -// --------------------------------------------------------------------------- -// SYS_GET_TICK (48) -> uint64_t milliseconds -// --------------------------------------------------------------------------- -static uint64_t SyscallGetTick(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - return gHal ? gHal->Timer.QueryTicks() : 0; -} - -// --------------------------------------------------------------------------- -// SYS_DEBUG_PRINT (49) a0=*str -// --------------------------------------------------------------------------- -static uint64_t SyscallDebugPrint(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - if (a0) DbgPrint("%s", (const char*)(uintptr_t)a0); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_CREATE_PROCESS (50) a0=*path a1=*handleOut a2=*cmdline (optional) -// --------------------------------------------------------------------------- -static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - - const char* path = (const char*)(uintptr_t)a0; - uint32_t* handleOut = (uint32_t*)(uintptr_t)a1; - const char* cmdline = (const char*)(uintptr_t)a2; // NULL = no args - if (!path || !handleOut) return STATUS_INVALID_PARAMETER; - - // Read the executable from disk. - FILE_OBJECT* f; - KSTATUS s = IoOpenFile(path, &f); - if (!KSUCCESS(s)) return s; - - uint64_t size = IoGetFileSize(f); - void* mem = MmAllocateUserMemory(size); - if (!mem) { IoCloseFile(f); return STATUS_NO_MEMORY; } - - size_t got; - s = IoReadFile(f, mem, size, &got); - IoCloseFile(f); - if (!KSUCCESS(s)) { MmFreeUserMemory(mem, size); return s; } - - // Create the process object. - PROCESS* proc; - s = KeCreateProcess(path, &proc); - if (!KSUCCESS(s)) { MmFreeUserMemory(mem, size); return s; } - - // Inherit CWD from parent - PROCESS* parent = KeGetCurrentProcess(); - if (parent) { - uintptr_t flags = KeAcquireSpinlock(&parent->CwdLock); - strncpy(proc->CurrentDirectory, parent->CurrentDirectory, sizeof(proc->CurrentDirectory) - 1); - proc->CurrentDirectory[sizeof(proc->CurrentDirectory) - 1] = '\0'; - KeReleaseSpinlock(&parent->CwdLock, flags); - } - - // Store the command line in the process so _start can pick it up. - // If no cmdline was supplied default to just the image path. - if (cmdline && cmdline[0]) - strncpy(proc->Cmdline, cmdline, sizeof(proc->Cmdline) - 1); - else - strncpy(proc->Cmdline, path, sizeof(proc->Cmdline) - 1); - proc->Cmdline[sizeof(proc->Cmdline) - 1] = '\0'; - - // proc->Cmdline is kernel pool - CPL=3 code can't read it. - // Copy into user memory so the initial thread can access it directly. - size_t cmdlineLen = strlen(proc->Cmdline) + 1; - char* userCmdline = (char*)MmAllocateUserMemory(cmdlineLen); - if (!userCmdline) { MmFreeUserMemory(mem, size); ObDereferenceObject(&proc->Header); return STATUS_NO_MEMORY; } - memcpy(userCmdline, proc->Cmdline, cmdlineLen); - proc->UserCmdlineBase = (uintptr_t)userCmdline; - proc->UserCmdlineSize = cmdlineLen; - - // Map the PE image into memory. - uintptr_t imageBase = 0; - s = LdrLoadImage(mem, size, &imageBase, true); - MmFreeUserMemory(mem, size); - if (!KSUCCESS(s)) { ObDereferenceObject(&proc->Header); return s; } - - s = LdrResolveImportsForProcess(proc, imageBase); - if (!KSUCCESS(s)) { - MmFreeUserMemory((void*)imageBase, LdrGetImageSize(imageBase)); - ObDereferenceObject(&proc->Header); - return s; - } - - // Spin up the initial thread. Pass userCmdline as UserArg so _start - // receives it directly in RCX without an extra syscall. - THREAD* thread; - uintptr_t entryPoint = LdrGetEntryPoint(imageBase); - s = KeCreateThread(proc, entryPoint, (uintptr_t)userCmdline, - PriorityNormal, &thread); - if (!KSUCCESS(s)) { - MmFreeUserMemory((void*)imageBase, LdrGetImageSize(imageBase)); - ObDereferenceObject(&proc->Header); - return s; - } - proc->ImageBase = imageBase; - proc->ImageSize = LdrGetImageSize(imageBase); - - // Give the caller a handle to the new process so they can wait on it. - PROCESS* caller = KeGetCurrentProcess(); - if (!caller) { ObDereferenceObject(&proc->Header); return STATUS_FAILURE; } - - uint32_t handle; - s = KeAllocateHandle(caller, &proc->Header, &handle); - if (!KSUCCESS(s)) { ObDereferenceObject(&proc->Header); return s; } - - *handleOut = handle; - return STATUS_SUCCESS; -} -// --------------------------------------------------------------------------- -// SYS_WAIT_FOR_PROCESS (51) a0=handle -// Blocks until the target process signals its ExitEvent. -// --------------------------------------------------------------------------- -static uint64_t SyscallWaitForProcess(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - - PROCESS* proc = (PROCESS*)HandleToObject((uint32_t)a0, ObjectTypeProcess); - if (!proc) return STATUS_INVALID_HANDLE; - - KSTATUS s = KeWaitForEvent(proc->ExitEvent); - ObDereferenceObject(&proc->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_GET_PROCESS_EXIT_CODE (52) a0=handle a1=*exitCodeOut -// --------------------------------------------------------------------------- -static uint64_t SyscallGetProcessExitCode(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - - PROCESS* proc = (PROCESS*)HandleToObject((uint32_t)a0, ObjectTypeProcess); - if (!proc) return STATUS_INVALID_HANDLE; - - int* exitCodeOut = (int*)(uintptr_t)a1; - KSTATUS s = KeGetProcessExitCode(proc, exitCodeOut); - ObDereferenceObject(&proc->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_WRITE_CONSOLE (53) a0=*buf a1=len -// Writes user console text in white; distinct from green SYS_DEBUG_PRINT. -// --------------------------------------------------------------------------- -static uint64_t SyscallWriteConsole(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - const char* buf = (const char*)(uintptr_t)a0; - size_t len = (size_t)a1; - if (buf && len) KePrintConsole(buf, len); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_OPEN_DIR (54) a0=*dirPath a1=*handleOut -// --------------------------------------------------------------------------- -static uint64_t SyscallOpenDir(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - const char* dirPath = (const char*)(uintptr_t)a0; - uint32_t* handleOut = (uint32_t*)(uintptr_t)a1; - - if (!handleOut) return STATUS_INVALID_PARAMETER; - - DIR_OBJECT* dir = NULL; - KSTATUS s = IoOpenDir(dirPath, &dir); - if (!KSUCCESS(s)) return s; - - // Put the object in the handle table and get a valid index (1-255) - s = KeAllocateHandle(KeGetCurrentProcess(), &dir->Header, handleOut); - - // Drop the reference KeAllocateHandle took, so that when user space - // calls CloseHandle, the refcount drops to 0 and the object is destroyed. - ObDereferenceObject(&dir->Header); - - return s; -} - -// --------------------------------------------------------------------------- -// SYS_READ_DIR (55) a0=handle a1=*KDIR_ENTRY -// Returns STATUS_NO_MORE_FILES when the directory is exhausted. -// --------------------------------------------------------------------------- -static uint64_t SyscallReadDir(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - - // a0 = handle from user mode - // a1 = KDIR_ENTRY* buffer - - // Look up a DIR_OBJECT using ObjectTypeDir (not ObjectTypeFile) - DIR_OBJECT* dir = (DIR_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeDir); - if (!dir) return STATUS_INVALID_PARAMETER; - - KSTATUS s = IoReadDir(dir, (void*)(uintptr_t)a1); - - ObDereferenceObject(&dir->Header); // Drop the ref taken by HandleToObject - return s; -} - -// --------------------------------------------------------------------------- -// SYS_SET_CWD (56) a0=*path -// --------------------------------------------------------------------------- -static uint64_t SyscallSetCwd(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - const char* path = (const char*)(uintptr_t)a0; - if (!path) return STATUS_INVALID_PARAMETER; - return IoSetCwd(path); -} - -// --------------------------------------------------------------------------- -// SYS_GET_CWD (57) a0=*buf a1=size -// --------------------------------------------------------------------------- -static uint64_t SyscallGetCwd(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - char* buf = (char*)(uintptr_t)a0; - size_t size = (size_t)a1; - if (!buf || size == 0) return STATUS_INVALID_PARAMETER; - return IoGetCwd(buf, size); -} - -// --------------------------------------------------------------------------- -// SYS_WAIT_THREAD (58) a0=tid -// Blocks the caller until the thread with the given TID exits. -// Searches the current process's thread list; cross-process join is not -// supported in v1 (POSIX pthread_join is always intra-process). -// Returns STATUS_NOT_FOUND if the TID is unknown (already reaped or never -// existed) - treat the same as a successful join at the POSIX layer. -// --------------------------------------------------------------------------- -static uint64_t SyscallWaitThread(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - uint32_t targetTid = (uint32_t)a0; - - PROCESS* proc = KeGetCurrentProcess(); - if (!proc) return STATUS_FAILURE; - - // Walk the thread list under the spinlock to find the target, then bump - // its ExitEvent refcount so the event survives even if the thread exits - // and is destroyed while we wait. - EVENT* exitEvent = (EVENT*)0; - uintptr_t flags = KeAcquireSpinlock(&proc->HandleLock); - LIST_ENTRY* entry = proc->ThreadList.Fwd; - while (entry != &proc->ThreadList) { - THREAD* t = CONTAINING_RECORD(entry, THREAD, ProcessLink); - if (t->ThreadId == targetTid && t->ExitEvent) { - exitEvent = t->ExitEvent; - ObReferenceObject(&exitEvent->Header); // keep event alive - break; - } - entry = entry->Fwd; - } - KeReleaseSpinlock(&proc->HandleLock, flags); - - if (!exitEvent) { - // TID not found - either already exited and reaped, or invalid. - // Treat as success at the POSIX layer (join on an already-dead thread). - return STATUS_NOT_FOUND; - } - - KSTATUS s = KeWaitForEvent(exitEvent); - ObDereferenceObject(&exitEvent->Header); - return s; -} - -// --------------------------------------------------------------------------- -// SYS_GET_PROCESS_PID (59) a0=procHandle a1=*pidOut -// Returns the numeric PID of a process identified by handle. -// Used by posix_spawn to map the kernel handle back to a pid_t for waitpid. -// --------------------------------------------------------------------------- -static uint64_t SyscallGetProcessPid(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - - PROCESS* proc = (PROCESS*)HandleToObject((uint32_t)a0, ObjectTypeProcess); - uint32_t* pidOut = (uint32_t*)(uintptr_t)a1; - if (!proc) return STATUS_INVALID_HANDLE; - if (!pidOut) { ObDereferenceObject(&proc->Header); return STATUS_INVALID_PARAMETER; } - - *pidOut = proc->ProcessId; - ObDereferenceObject(&proc->Header); - return STATUS_SUCCESS; -} - -// =========================================================================== -// Named Queue Registry -// -// A flat, fixed-size table of { name -> QUEUE* } pairs protected by a -// spinlock. The registry holds one reference to each published queue for -// the lifetime of the entry, so the queue object cannot be destroyed while -// it is registered even if every process closes its own handle to it. -// -// Limits: NAMED_QUEUE_MAX names, each up to NAMED_QUEUE_NAME_MAX bytes. -// These are generous for the intended usage (a handful of system servers). -// =========================================================================== - -#define NAMED_QUEUE_MAX 16 -#define NAMED_QUEUE_NAME_MAX 64 - -typedef struct { - char Name[NAMED_QUEUE_NAME_MAX]; - QUEUE* Queue; // NULL = slot free; non-NULL = occupied - uint32_t PublisherPid; // ProcessId of whoever called SysPublishQueue -} NAMED_QUEUE_ENTRY; - -static NAMED_QUEUE_ENTRY gNamedQueues[NAMED_QUEUE_MAX]; -static SPINLOCK gNamedQueueLock; - -// --------------------------------------------------------------------------- -// SYS_PUBLISH_QUEUE (60) a0=*name a1=queueHandle -// -// Registers the queue identified by 'queueHandle' under 'name'. -// Fails with STATUS_ALREADY_EXISTS if the name is already taken. -// The registry bumps the queue's refcount so it outlives the handle. -// --------------------------------------------------------------------------- -static uint64_t SyscallPublishQueue(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - - const char* name = (const char*)(uintptr_t)a0; - if (!name || !name[0]) return STATUS_INVALID_PARAMETER; - - // Resolve the handle to a QUEUE object. HandleToObject bumps the - // refcount by one; we keep that ref in the registry slot. - QUEUE* q = (QUEUE*)HandleToObject((uint32_t)a1, ObjectTypeQueue); - if (!q) return STATUS_INVALID_HANDLE; - - uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); - - // Reject duplicate names. - for (int i = 0; i < NAMED_QUEUE_MAX; i++) { - if (gNamedQueues[i].Queue && - strncmp(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX) == 0) { - KeReleaseSpinlock(&gNamedQueueLock, flags); - ObDereferenceObject(&q->Header); // drop the ref we just took - return STATUS_ALREADY_EXISTS; - } - } - - // Find a free slot. - for (int i = 0; i < NAMED_QUEUE_MAX; i++) { - if (!gNamedQueues[i].Queue) { - strncpy(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX - 1); - gNamedQueues[i].Name[NAMED_QUEUE_NAME_MAX - 1] = '\0'; - gNamedQueues[i].Queue = q; // ref transferred to registry - gNamedQueues[i].PublisherPid = KeGetCurrentProcess()->ProcessId; - KeReleaseSpinlock(&gNamedQueueLock, flags); - return STATUS_SUCCESS; - } - } - - KeReleaseSpinlock(&gNamedQueueLock, flags); - ObDereferenceObject(&q->Header); // no room - drop the ref - return STATUS_NO_MEMORY; -} - -// --------------------------------------------------------------------------- -// SYS_OPEN_NAMED_QUEUE (61) a0=*name a1=*handleOut -// -// Looks up 'name' in the registry and allocates a handle to the queue in the -// calling process's handle table. Returns STATUS_NOT_FOUND if no match. -// Callers that race against the publisher should retry on NOT_FOUND. -// --------------------------------------------------------------------------- -static uint64_t SyscallOpenNamedQueue(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - - const char* name = (const char*)(uintptr_t)a0; - uint32_t* handleOut = (uint32_t*)(uintptr_t)a1; - if (!name || !name[0] || !handleOut) return STATUS_INVALID_PARAMETER; - - uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); - - QUEUE* q = NULL; - for (int i = 0; i < NAMED_QUEUE_MAX; i++) { - if (gNamedQueues[i].Queue && - strncmp(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX) == 0) { - q = gNamedQueues[i].Queue; - ObReferenceObject(&q->Header); // one ref for the new handle - break; - } - } - - KeReleaseSpinlock(&gNamedQueueLock, flags); - - if (!q) return STATUS_NOT_FOUND; - - // AllocHandleForCurrent consumes the ref we just bumped. - // On failure it calls ObDereferenceObject internally. - return AllocHandleForCurrent(&q->Header, handleOut); -} - -// --------------------------------------------------------------------------- -// SYS_QUEUE_DUPLICATE (62) a0=handle a1=targetPid a2=*handleOut -// -// Looks up the QUEUE behind 'handle' in the CALLER's table, then allocates -// a new handle for the same object in the TARGET process's table. -// Both processes share the same underlying QUEUE (refcount bumped). -// The caller's own handle is unchanged. -// --------------------------------------------------------------------------- -static uint64_t SyscallQueueDuplicate(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - if (!a2) return STATUS_INVALID_PARAMETER; - - QUEUE* q = (QUEUE*)HandleToObject((uint32_t)a0, ObjectTypeQueue); - if (!q) return STATUS_NOT_FOUND; - - PROCESS* target = KeGetProcessById((uint32_t)a1); - if (!target) { - ObDereferenceObject(&q->Header); - return STATUS_NOT_FOUND; - } - - uint32_t newHandle = 0; - KSTATUS s = KeAllocateHandle(target, &q->Header, &newHandle); - ObDereferenceObject(&target->Header); - ObDereferenceObject(&q->Header); - if (!KSUCCESS(s)) return s; - - *(uint32_t*)(uintptr_t)a2 = newHandle; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_NAMED_QUEUE_OWNER_PID (63) a0=*name a1=*pidOut -// -// Returns the ProcessId of whoever published the named queue. -// Lets a client get the publisher's PID so it can duplicate one of its -// own handles into that process via SYS_QUEUE_DUPLICATE. -// --------------------------------------------------------------------------- -static uint64_t SyscallNamedQueueOwnerPid(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - const char* name = (const char*)(uintptr_t)a0; - uint32_t* pidOut = (uint32_t*)(uintptr_t)a1; - if (!name || !name[0] || !pidOut) return STATUS_INVALID_PARAMETER; - - uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); - for (int i = 0; i < NAMED_QUEUE_MAX; i++) { - if (gNamedQueues[i].Queue && - strncmp(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX) == 0) { - *pidOut = gNamedQueues[i].PublisherPid; - KeReleaseSpinlock(&gNamedQueueLock, flags); - return STATUS_SUCCESS; - } - } - KeReleaseSpinlock(&gNamedQueueLock, flags); - return STATUS_NOT_FOUND; -} - -static uint64_t SyscallKillProcess(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - PROCESS* proc = KeGetProcessById((uint32_t)a0); - if (!proc) return STATUS_NOT_FOUND; - THREAD* self = KeGetCurrentThread(); - int exitCode = (int)a1; - if (self && self->Owner == proc) { - KeExitProcess(proc, exitCode); - ObDereferenceObject(&proc->Header); - KeExitThread(self, exitCode); - while (true) ; - } - KeExitProcess(proc, exitCode); - ObDereferenceObject(&proc->Header); - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_GET_PROCESS_LIST (65) a0=*pidsOut a1=maxCount a2=*countOut -// --------------------------------------------------------------------------- -static uint64_t SyscallGetProcessList(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a3; (void)a4; (void)a5; - uint32_t* pidsOut = (uint32_t*)(uintptr_t)a0; - size_t maxCount = (size_t)a1; - size_t* countOut = (size_t*)(uintptr_t)a2; - size_t count = KeCopyProcessIds(pidsOut, maxCount); - if (countOut) *countOut = count; - return STATUS_SUCCESS; -} - -// --------------------------------------------------------------------------- -// SYS_RENAME_FILE (66) a0=*oldPath a1=*newPath -// --------------------------------------------------------------------------- -static uint64_t SyscallRenameFile(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; - const char* oldPath = (const char*)(uintptr_t)a0; - const char* newPath = (const char*)(uintptr_t)a1; - if (!oldPath || !newPath) return STATUS_INVALID_PARAMETER; - return IoRenameFile(oldPath, newPath); -} - -// --------------------------------------------------------------------------- -// SYS_CREATE_DIR (67) a0=*path -// --------------------------------------------------------------------------- -static uint64_t SyscallCreateDir(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - const char* path = (const char*)(uintptr_t)a0; - if (!path) return STATUS_INVALID_PARAMETER; - return IoCreateDir(path); -} - -// --------------------------------------------------------------------------- -// SYS_REMOVE_DIR (68) a0=*path -// --------------------------------------------------------------------------- -static uint64_t SyscallRemoveDir(uint64_t a0, uint64_t a1, uint64_t a2, - uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; - const char* path = (const char*)(uintptr_t)a0; - if (!path) return STATUS_INVALID_PARAMETER; - return IoRemoveDir(path); -} - -// =========================================================================== -// Dispatch table - index must match SYS_* in syscall_nr.h exactly. -// =========================================================================== -typedef uint64_t (*SYSCALL_HANDLER)(uint64_t, uint64_t, uint64_t, - uint64_t, uint64_t, uint64_t); - -static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = { - /* 00 SYS_EXIT_PROCESS */ SyscallExitProcess, - /* 01 SYS_EXIT_THREAD */ SyscallExitThread, - /* 02 SYS_CREATE_THREAD */ SyscallCreateThread, - /* 03 SYS_GET_CURRENT_TID */ SyscallGetCurrentTid, - /* 04 SYS_GET_CURRENT_PID */ SyscallGetCurrentPid, - /* 05 SYS_YIELD */ SyscallYield, - /* 06 SYS_SLEEP */ SyscallSleep, - /* 07 SYS_SET_THREAD_PRIORITY */ SyscallSetThreadPriority, - /* 08 SYS_ALLOC */ SyscallAlloc, - /* 09 SYS_FREE */ SyscallFree, - /* 10 SYS_REALLOC */ SyscallRealloc, - /* 11 SYS_MUTEX_CREATE */ SyscallMutexCreate, - /* 12 SYS_MUTEX_ACQUIRE */ SyscallMutexAcquire, - /* 13 SYS_MUTEX_TRY_ACQUIRE */ SyscallMutexTryAcquire, - /* 14 SYS_MUTEX_RELEASE */ SyscallMutexRelease, - /* 15 SYS_SEM_CREATE */ SyscallSemCreate, - /* 16 SYS_SEM_WAIT */ SyscallSemWait, - /* 17 SYS_SEM_TRY_WAIT */ SyscallSemTryWait, - /* 18 SYS_SEM_SIGNAL */ SyscallSemSignal, - /* 19 SYS_EVENT_CREATE */ SyscallEventCreate, - /* 20 SYS_EVENT_SIGNAL */ SyscallEventSignal, - /* 21 SYS_EVENT_CLEAR */ SyscallEventClear, - /* 22 SYS_EVENT_WAIT */ SyscallEventWait, - /* 23 SYS_QUEUE_CREATE */ SyscallQueueCreate, - /* 24 SYS_QUEUE_SEND */ SyscallQueueSend, - /* 25 SYS_QUEUE_RECV */ SyscallQueueRecv, - /* 26 SYS_CLOSE_HANDLE */ SyscallCloseHandle, - /* 27 SYS_OPEN_FILE */ SyscallOpenFile, - /* 28 SYS_READ_FILE */ SyscallReadFile, - /* 29 SYS_WRITE_FILE */ SyscallWriteFile, - /* 30 SYS_CREATE_FILE */ SyscallCreateFile, - /* 31 SYS_DELETE_FILE */ SyscallDeleteFile, - /* 32 SYS_TRUNCATE_FILE */ SyscallTruncateFile, - /* 33 SYS_GET_FILE_POS */ SyscallGetFilePos, - /* 34 SYS_SEEK_FILE */ SyscallSeekFile, - /* 35 SYS_GET_FILE_SIZE */ SyscallGetFileSize, - /* 36 SYS_SCREEN_BLIT */ SyscallScreenBlit, - /* 37 SYS_SCREEN_CLEAR */ SyscallScreenClear, - /* 38 SYS_SCREEN_GET_SIZE */ SyscallScreenGetSize, - /* 39 SYS_SCREEN_PUT_CHAR */ SyscallScreenPutChar, - /* 40 SYS_KB_POLL */ SyscallKbPoll, - /* 41 SYS_KB_IS_KEY_DOWN */ SyscallKbIsKeyDown, - /* 42 SYS_KB_READ_CHAR */ SyscallKbReadChar, - /* 43 SYS_KB_READ_SCANCODE */ SyscallKbReadScancode, - /* 44 SYS_KB_GET_MODIFIERS */ SyscallKbGetModifiers, - /* 45 SYS_MOUSE_POLL */ SyscallMousePoll, - /* 46 SYS_MOUSE_READ */ SyscallMouseRead, - /* 47 SYS_MOUSE_GET_POS */ SyscallMouseGetPos, - /* 48 SYS_GET_TICK */ SyscallGetTick, - /* 49 SYS_DEBUG_PRINT */ SyscallDebugPrint, - /* 50 SYS_CREATE_PROCESS */ SyscallCreateProcess, - /* 51 SYS_WAIT_FOR_PROCESS */ SyscallWaitForProcess, - /* 52 SYS_GET_PROCESS_EXIT_CODE */ SyscallGetProcessExitCode, - /* 53 SYS_WRITE_CONSOLE */ SyscallWriteConsole, - /* 54 SYS_OPEN_DIR */ SyscallOpenDir, - /* 55 SYS_READ_DIR */ SyscallReadDir, - /* 56 SYS_SET_CWD */ SyscallSetCwd, - /* 57 SYS_GET_CWD */ SyscallGetCwd, - /* 58 SYS_WAIT_THREAD */ SyscallWaitThread, - /* 59 SYS_GET_PROCESS_PID */ SyscallGetProcessPid, - /* 60 SYS_PUBLISH_QUEUE */ SyscallPublishQueue, - /* 61 SYS_OPEN_NAMED_QUEUE */ SyscallOpenNamedQueue, - /* 62 SYS_QUEUE_DUPLICATE */ SyscallQueueDuplicate, - /* 63 SYS_NAMED_QUEUE_OWNER_PID */ SyscallNamedQueueOwnerPid, - /* 64 SYS_KILL_PROCESS */ SyscallKillProcess, - /* 65 SYS_GET_PROCESS_LIST */ SyscallGetProcessList, - /* 66 SYS_RENAME_FILE */ SyscallRenameFile, - /* 67 SYS_CREATE_DIR */ SyscallCreateDir, - /* 68 SYS_REMOVE_DIR */ SyscallRemoveDir, -}; - -// --------------------------------------------------------------------------- -// Entry point - called from ke_syscall.asm after argument re-ordering -// --------------------------------------------------------------------------- -KSTATUS KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1, - uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - if (nr >= SYS_MAX || !gSyscallTable[nr]) return STATUS_NOT_SUPPORTED; - return (KSTATUS)gSyscallTable[nr](a0, a1, a2, a3, a4, a5); -} +// ke_syscall.c - system call dispatcher +// +// KeSyscallEntry (ke_syscall.asm) handles the INT 0x40 ring-3 -> ring-0 +// transition, re-orders the arguments to match MS x64, and calls +// KeSyscallDispatch with: +// +// nr = original RAX (syscall number) +// a0 .. a5 = RCX, RDX, R8, R9, R10, R11 (user arguments) +// +// Every handler receives the same (a0..a5) signature even if it uses fewer +// arguments; unused parameters are silently ignored. +// +// Return value (RAX) is a KSTATUS code *or* an arbitrary 64-bit datum +// (pointer, size, tick count, etc.) depending on the call; conventions are +// documented in syscall_nr.h. +// +// --------------------------------------------------------------------------- +// Unified handle design +// --------------------------------------------------------------------------- +// All kernel objects (FILE_OBJECT, MUTEX, SEMAPHORE, EVENT, QUEUE) are +// reference-counted OBJECTs stored in the per-process handle table. +// SYS_CLOSE_HANDLE is the single close path for every type: +// +// KeCloseHandle(proc, handle) +// -> ObDereferenceObject(obj) +// -> obj->Destroy(obj) [type-specific cleanup, e.g. FileDestroy] +// -> MmFreePool(obj) +// +// There is no separate SyscallCloseFile. Removing that stub was the right +// call: the object model already handled it; the dedicated syscall was only +// adding a footgun where callers could bypass the unified refcount path. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// HAL pointer - set once at boot by KeSyscallSetHal() +// --------------------------------------------------------------------------- +static const HAL_API* gHal; + +void KeSyscallSetHal(const HAL_API* hal) { gHal = hal; } + +// --------------------------------------------------------------------------- +// Helper: look up a handle and type-check it in one call. +// Bumps the refcount on success; caller must ObDereferenceObject when done. +// Returns NULL if the handle is invalid or the type does not match. +// --------------------------------------------------------------------------- +static OBJECT* HandleToObject(uint32_t handle, OBJECT_TYPE expected) { + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return NULL; + OBJECT* obj = KeLookupHandle(proc, handle); // bumps refcount + if (!obj) return NULL; + if (obj->Type != expected) { + ObDereferenceObject(obj); // undo the bump + return NULL; + } + return obj; +} + +// --------------------------------------------------------------------------- +// Helper: allocate a handle in the current process's table for 'obj'. +// Takes one reference on behalf of the handle slot. +// On failure, drops the reference it would have stored. +// --------------------------------------------------------------------------- +static KSTATUS AllocHandleForCurrent(OBJECT* obj, uint32_t* handleOut) { + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_FAILURE; + KSTATUS s = KeAllocateHandle(proc, obj, handleOut); + if (!KSUCCESS(s)) ObDereferenceObject(obj); + return s; +} + +// =========================================================================== +// Individual syscall handlers +// =========================================================================== + +// --------------------------------------------------------------------------- +// SYS_EXIT_PROCESS (0) a0 = exitCode +// --------------------------------------------------------------------------- +static uint64_t SyscallExitProcess(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + THREAD* t = KeGetCurrentThread(); + PROCESS* proc = t ? t->Owner : NULL; + if (proc) KeExitProcess(proc, (int)a0); + if (t) KeExitThread(t, (int)a0); + while (true) ; // unreachable - KeExitThread does not return +} + +// --------------------------------------------------------------------------- +// SYS_EXIT_THREAD (1) a0 = exitCode +// --------------------------------------------------------------------------- +static uint64_t SyscallExitThread(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + THREAD* t = KeGetCurrentThread(); + if (t) KeExitThread(t, (int)a0); + while (true); +} + +// --------------------------------------------------------------------------- +// SYS_CREATE_THREAD (2) a0=entry a1=arg a2=priority a3=*tidOut +// --------------------------------------------------------------------------- +static uint64_t SyscallCreateThread(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a4; (void)a5; + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_FAILURE; + if (a2 >= PriorityCount) return STATUS_INVALID_PARAMETER; + + THREAD* newt = NULL; + KSTATUS s = KeCreateThread(proc, (uintptr_t)a0, (uintptr_t)a1, + (THREAD_PRIORITY)a2, &newt); + if (!KSUCCESS(s)) return s; + if (a3) *(uint32_t*)(uintptr_t)a3 = newt->ThreadId; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_GET_CURRENT_TID (3) +// --------------------------------------------------------------------------- +static uint64_t SyscallGetCurrentTid(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + THREAD* t = KeGetCurrentThread(); + return t ? t->ThreadId : 0; +} + +// --------------------------------------------------------------------------- +// SYS_GET_CURRENT_PID (4) +// --------------------------------------------------------------------------- +static uint64_t SyscallGetCurrentPid(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + PROCESS* p = KeGetCurrentProcess(); + return p ? p->ProcessId : 0; +} + +// --------------------------------------------------------------------------- +// SYS_YIELD (5) +// --------------------------------------------------------------------------- +static uint64_t SyscallYield(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + KeYield(); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_SLEEP (6) a0 = milliseconds +// --------------------------------------------------------------------------- +static uint64_t SyscallSleep(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!gHal) return STATUS_FAILURE; + uint64_t deadline = gHal->Timer.QueryTicks() + (uint64_t)a0; + while (gHal->Timer.QueryTicks() < deadline) + KeYield(); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_SET_THREAD_PRIORITY (7) a0 = priority +// --------------------------------------------------------------------------- +static uint64_t SyscallSetThreadPriority(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (a0 >= PriorityCount) return STATUS_INVALID_PARAMETER; + THREAD* t = KeGetCurrentThread(); + if (!t) return STATUS_FAILURE; + t->Priority = (THREAD_PRIORITY)a0; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_ALLOC (8) a0 = size -> void* +// --------------------------------------------------------------------------- +static uint64_t SyscallAlloc(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0) return 0; + return (uint64_t)(uintptr_t)MmAllocateUserMemory((size_t)a0); +} + +// --------------------------------------------------------------------------- +// SYS_FREE (9) a0 = ptr, a1 = size +// --------------------------------------------------------------------------- +static uint64_t SyscallFree(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + if (a0) MmFreeUserMemory((void*)(uintptr_t)a0, (size_t)a1); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_REALLOC (10) a0 = ptr, a1 = newSize -> void* +// --------------------------------------------------------------------------- +static uint64_t SyscallRealloc(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + return (uint64_t)(uintptr_t)MmReallocateUserMemory( + (void*)(uintptr_t)a0, (size_t)a1); +} + +// --------------------------------------------------------------------------- +// SYS_MUTEX_CREATE (11) a0 = *handleOut +// --------------------------------------------------------------------------- +static uint64_t SyscallMutexCreate(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0) return STATUS_INVALID_PARAMETER; + MUTEX* mx = NULL; + KSTATUS s = MutexCreate(&mx); + if (!KSUCCESS(s)) return s; + uint32_t h = 0; + s = AllocHandleForCurrent(&mx->Header, &h); // takes ownership of the ref + if (!KSUCCESS(s)) return s; + *(uint32_t*)(uintptr_t)a0 = h; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_MUTEX_ACQUIRE (12) a0 = handle +// --------------------------------------------------------------------------- +static uint64_t SyscallMutexAcquire(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + MUTEX* mx = (MUTEX*)HandleToObject((uint32_t)a0, ObjectTypeMutex); + if (!mx) return STATUS_NOT_FOUND; + KSTATUS s = KeAcquireMutex(mx); + ObDereferenceObject(&mx->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_MUTEX_TRY_ACQUIRE (13) a0 = handle +// --------------------------------------------------------------------------- +static uint64_t SyscallMutexTryAcquire(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + MUTEX* mx = (MUTEX*)HandleToObject((uint32_t)a0, ObjectTypeMutex); + if (!mx) return STATUS_NOT_FOUND; + KSTATUS s = KeTryAcquireMutex(mx); + ObDereferenceObject(&mx->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_MUTEX_RELEASE (14) a0 = handle +// --------------------------------------------------------------------------- +static uint64_t SyscallMutexRelease(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + MUTEX* mx = (MUTEX*)HandleToObject((uint32_t)a0, ObjectTypeMutex); + if (!mx) return STATUS_NOT_FOUND; + KeReleaseMutex(mx); + ObDereferenceObject(&mx->Header); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_SEM_CREATE (15) a0=initial a1=max a2=*handleOut +// --------------------------------------------------------------------------- +static uint64_t SyscallSemCreate(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a3; (void)a4; (void)a5; + if (!a2) return STATUS_INVALID_PARAMETER; + SEMAPHORE* sem = NULL; + KSTATUS s = KeSemaphoreCreate((int32_t)a0, (int32_t)a1, &sem); + if (!KSUCCESS(s)) return s; + uint32_t h = 0; + s = AllocHandleForCurrent(&sem->Header, &h); + if (!KSUCCESS(s)) return s; + *(uint32_t*)(uintptr_t)a2 = h; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_SEM_WAIT (16) a0 = handle +// --------------------------------------------------------------------------- +static uint64_t SyscallSemWait(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + SEMAPHORE* sem = (SEMAPHORE*)HandleToObject((uint32_t)a0, ObjectTypeSemaphore); + if (!sem) return STATUS_NOT_FOUND; + KSTATUS s = KeWaitSemaphore(sem); + ObDereferenceObject(&sem->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_SEM_TRY_WAIT (17) a0 = handle +// --------------------------------------------------------------------------- +static uint64_t SyscallSemTryWait(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + SEMAPHORE* sem = (SEMAPHORE*)HandleToObject((uint32_t)a0, ObjectTypeSemaphore); + if (!sem) return STATUS_NOT_FOUND; + KSTATUS s = KeTrySemaphore(sem); + ObDereferenceObject(&sem->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_SEM_SIGNAL (18) a0 = handle +// --------------------------------------------------------------------------- +static uint64_t SyscallSemSignal(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + SEMAPHORE* sem = (SEMAPHORE*)HandleToObject((uint32_t)a0, ObjectTypeSemaphore); + if (!sem) return STATUS_NOT_FOUND; + KeSignalSemaphore(sem); + ObDereferenceObject(&sem->Header); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_EVENT_CREATE (19) a0=type a1=initialState a2=*handleOut +// --------------------------------------------------------------------------- +static uint64_t SyscallEventCreate(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a3; (void)a4; (void)a5; + if (!a2 || a0 > EventManualReset) return STATUS_INVALID_PARAMETER; + EVENT* ev = NULL; + KSTATUS s = KeEventCreate((EVENT_TYPE)a0, (uint32_t)a1, &ev); + if (!KSUCCESS(s)) return s; + uint32_t h = 0; + s = AllocHandleForCurrent(&ev->Header, &h); + if (!KSUCCESS(s)) return s; + *(uint32_t*)(uintptr_t)a2 = h; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_EVENT_SIGNAL (20) a0 = handle +// --------------------------------------------------------------------------- +static uint64_t SyscallEventSignal(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + EVENT* ev = (EVENT*)HandleToObject((uint32_t)a0, ObjectTypeEvent); + if (!ev) return STATUS_NOT_FOUND; + KeSignalEvent(ev); + ObDereferenceObject(&ev->Header); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_EVENT_CLEAR (21) a0 = handle +// --------------------------------------------------------------------------- +static uint64_t SyscallEventClear(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + EVENT* ev = (EVENT*)HandleToObject((uint32_t)a0, ObjectTypeEvent); + if (!ev) return STATUS_NOT_FOUND; + KeClearEvent(ev); + ObDereferenceObject(&ev->Header); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_EVENT_WAIT (22) a0 = handle +// --------------------------------------------------------------------------- +static uint64_t SyscallEventWait(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + EVENT* ev = (EVENT*)HandleToObject((uint32_t)a0, ObjectTypeEvent); + if (!ev) return STATUS_NOT_FOUND; + KSTATUS s = KeWaitForEvent(ev); + ObDereferenceObject(&ev->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_QUEUE_CREATE (23) a0 = *handleOut +// --------------------------------------------------------------------------- +static uint64_t SyscallQueueCreate(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0) return STATUS_INVALID_PARAMETER; + QUEUE* q = NULL; + KSTATUS s = QueueCreate(&q); + if (!KSUCCESS(s)) return s; + uint32_t h = 0; + s = AllocHandleForCurrent(&q->Header, &h); + if (!KSUCCESS(s)) return s; + *(uint32_t*)(uintptr_t)a0 = h; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_QUEUE_SEND (24) a0=handle a1=*msg a2=blocking +// --------------------------------------------------------------------------- +static uint64_t SyscallQueueSend(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a3; (void)a4; (void)a5; + if (!a1) return STATUS_INVALID_PARAMETER; + QUEUE* q = (QUEUE*)HandleToObject((uint32_t)a0, ObjectTypeQueue); + if (!q) return STATUS_NOT_FOUND; + KSTATUS s = KeSendMessage(q, (const QUEUE_MESSAGE*)(uintptr_t)a1, (bool)a2); + ObDereferenceObject(&q->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_QUEUE_RECV (25) a0=handle a1=*msg a2=blocking +// --------------------------------------------------------------------------- +static uint64_t SyscallQueueRecv(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a3; (void)a4; (void)a5; + if (!a1) return STATUS_INVALID_PARAMETER; + QUEUE* q = (QUEUE*)HandleToObject((uint32_t)a0, ObjectTypeQueue); + if (!q) return STATUS_NOT_FOUND; + KSTATUS s = KeReceiveMessage(q, (QUEUE_MESSAGE*)(uintptr_t)a1, (bool)a2); + ObDereferenceObject(&q->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_CLOSE_HANDLE (26) a0 = handle +// +// The one and only close path. KeCloseHandle drops the handle-table reference +// on 'obj'. If that was the last reference, ObDereferenceObject runs the +// type-specific Destroy callback (FileDestroy, etc.) and frees the pool memory. +// No per-type close syscall needed or wanted. +// --------------------------------------------------------------------------- +static uint64_t SyscallCloseHandle(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_FAILURE; + KeCloseHandle(proc, (uint32_t)a0); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_OPEN_FILE (27) a0=*path a1=*handleOut +// --------------------------------------------------------------------------- +static uint64_t SyscallOpenFile(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0 || !a1) return STATUS_INVALID_PARAMETER; + FILE_OBJECT* file = NULL; + KSTATUS s = IoOpenFile((const char*)(uintptr_t)a0, &file); + if (!KSUCCESS(s)) return s; + uint32_t h = 0; + s = AllocHandleForCurrent(&file->Header, &h); + if (!KSUCCESS(s)) { + // AllocHandleForCurrent already dropped the ref on failure; + // the refcount is now 0, so ObDereferenceObject will run FileDestroy. + ObDereferenceObject(&file->Header); + return s; + } + *(uint32_t*)(uintptr_t)a1 = h; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_READ_FILE (28) a0=handle a1=buf a2=size a3=*bytesReadOut +// --------------------------------------------------------------------------- +static uint64_t SyscallReadFile(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a4; (void)a5; + if (!a1) return STATUS_INVALID_PARAMETER; + FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); + if (!file) return STATUS_NOT_FOUND; + size_t got = 0; + KSTATUS s = IoReadFile(file, (void*)(uintptr_t)a1, (size_t)a2, &got); + if (a3) *(size_t*)(uintptr_t)a3 = got; + ObDereferenceObject(&file->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_WRITE_FILE (29) a0=handle a1=buf a2=size a3=*bytesWrittenOut +// --------------------------------------------------------------------------- +static uint64_t SyscallWriteFile(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a4; (void)a5; + if (!a1) return STATUS_INVALID_PARAMETER; + FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); + if (!file) return STATUS_NOT_FOUND; + size_t written = 0; + KSTATUS s = IoWriteFile(file, (const void*)(uintptr_t)a1, (size_t)a2, &written); + if (a3) *(size_t*)(uintptr_t)a3 = written; + ObDereferenceObject(&file->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_CREATE_FILE (30) a0=*path a1=*handleOut +// --------------------------------------------------------------------------- +static uint64_t SyscallCreateFile(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0 || !a1) return STATUS_INVALID_PARAMETER; + FILE_OBJECT* file = NULL; + KSTATUS s = IoCreateFile((const char*)(uintptr_t)a0, &file); + if (!KSUCCESS(s)) return s; + uint32_t h = 0; + s = AllocHandleForCurrent(&file->Header, &h); + if (!KSUCCESS(s)) { + ObDereferenceObject(&file->Header); + return s; + } + *(uint32_t*)(uintptr_t)a1 = h; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_DELETE_FILE (31) a0=*path +// --------------------------------------------------------------------------- +static uint64_t SyscallDeleteFile(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!a0) return STATUS_INVALID_PARAMETER; + return IoDeleteFile((const char*)(uintptr_t)a0); +} + +// --------------------------------------------------------------------------- +// SYS_TRUNCATE_FILE (32) a0=handle a1=newSize +// --------------------------------------------------------------------------- +static uint64_t SyscallTruncateFile(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); + if (!file) return STATUS_NOT_FOUND; + KSTATUS s = IoTruncateFile(file, (uint64_t)a1); + ObDereferenceObject(&file->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_GET_FILE_POS (33) a0=handle -> current seek offset +// --------------------------------------------------------------------------- +static uint64_t SyscallGetFilePos(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); + if (!file) return (uint64_t)-1; + uint64_t pos = file->Offset; + ObDereferenceObject(&file->Header); + return pos; +} + +// --------------------------------------------------------------------------- +// SYS_SEEK_FILE (34) a0=handle a1=offset +// --------------------------------------------------------------------------- +static uint64_t SyscallSeekFile(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); + if (!file) return STATUS_NOT_FOUND; + KSTATUS s = IoSeekFile(file, (uint64_t)a1); + ObDereferenceObject(&file->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_GET_FILE_SIZE (35) a0=handle -> uint64_t +// --------------------------------------------------------------------------- +static uint64_t SyscallGetFileSize(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + FILE_OBJECT* file = (FILE_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeFile); + if (!file) return 0; + uint64_t sz = IoGetFileSize(file); + ObDereferenceObject(&file->Header); + return sz; +} + +// --------------------------------------------------------------------------- +// SYS_SCREEN_BLIT (36) a0=x a1=y a2=w a3=h a4=*pixels +// --------------------------------------------------------------------------- +static uint64_t SyscallScreenBlit(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a5; + if (!gHal || !a4) return STATUS_FAILURE; + gHal->Screen.Blit((int)a0, (int)a1, (int)a2, (int)a3, + (const uint32_t*)(uintptr_t)a4); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_SCREEN_CLEAR (37) a0=color +// --------------------------------------------------------------------------- +static uint64_t SyscallScreenClear(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!gHal) return STATUS_FAILURE; + gHal->Screen.Clear((uint32_t)a0); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_SCREEN_GET_SIZE (38) a0=*widthOut a1=*heightOut +// --------------------------------------------------------------------------- +static uint64_t SyscallScreenGetSize(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + if (!gHal) return STATUS_FAILURE; + int w = 0, h = 0; + gHal->Screen.GetSize(&w, &h); + if (a0) *(int*)(uintptr_t)a0 = w; + if (a1) *(int*)(uintptr_t)a1 = h; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_SCREEN_PUT_CHAR (39) a0=c a1=x a2=y a3=fg a4=bg +// --------------------------------------------------------------------------- +static uint64_t SyscallScreenPutChar(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a5; + if (!gHal) return STATUS_FAILURE; + gHal->Screen.PutChar((char)a0, (int)a1, (int)a2, + (uint32_t)a3, (uint32_t)a4); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_KB_POLL (40) -> 1 or 0 +// --------------------------------------------------------------------------- +static uint64_t SyscallKbPoll(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!gHal) return 0; + return gHal->Keyboard.Poll(); +} + +// --------------------------------------------------------------------------- +// SYS_KB_IS_KEY_DOWN (41) a0=scancode a1=extended -> 1 or 0 +// --------------------------------------------------------------------------- +static uint64_t SyscallKbIsKeyDown(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + + // Check gHal and ensure the HAL struct actually has the new function pointer + if (!gHal || !gHal->Keyboard.IsKeyDown) return 0; + + return gHal->Keyboard.IsKeyDown((uint8_t)a0, (bool)a1) ? 1u : 0u; +} + +// --------------------------------------------------------------------------- +// SYS_KB_READ_CHAR (42) -> char (blocks, yields while waiting) +// --------------------------------------------------------------------------- +static uint64_t SyscallKbReadChar(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!gHal) return 0; + while (!gHal->Keyboard.Poll()) KeYield(); + return (uint64_t)(uint8_t)gHal->Keyboard.ReadChar(); +} + +// --------------------------------------------------------------------------- +// SYS_KB_READ_SCANCODE (43) -> uint8_t (blocks, yields while waiting) +// --------------------------------------------------------------------------- +static uint64_t SyscallKbReadScancode(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!gHal) return 0; + while (!gHal->Keyboard.Poll()) KeYield(); + return (uint64_t)gHal->Keyboard.ReadScancode(); +} + +// --------------------------------------------------------------------------- +// SYS_KB_GET_MODIFIERS (44) -> uint32_t +// --------------------------------------------------------------------------- +static uint64_t SyscallKbGetModifiers(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!gHal) return 0; + return (uint64_t)gHal->Keyboard.GetModifiers(); +} + +// --------------------------------------------------------------------------- +// SYS_MOUSE_POLL (45) -> 1 or 0 +// --------------------------------------------------------------------------- +static uint64_t SyscallMousePoll(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (!gHal) return 0; + return gHal->Mouse.Poll() ? 1u : 0u; +} + +// --------------------------------------------------------------------------- +// SYS_MOUSE_READ (46) a0=*dx a1=*dy a2=*buttons -> 1 or 0 +// --------------------------------------------------------------------------- +static uint64_t SyscallMouseRead(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a3; (void)a4; (void)a5; + if (!gHal) return 0; + return gHal->Mouse.Read((int*)(uintptr_t)a0, + (int*)(uintptr_t)a1, + (uint8_t*)(uintptr_t)a2) ? 1u : 0u; +} + +// --------------------------------------------------------------------------- +// SYS_MOUSE_GET_POS (47) a0=*x a1=*y +// --------------------------------------------------------------------------- +static uint64_t SyscallMouseGetPos(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + if (!gHal) return 0; + gHal->Mouse.GetPosition((int*)(uintptr_t)a0, (int*)(uintptr_t)a1); + return 0; +} + +// --------------------------------------------------------------------------- +// SYS_GET_TICK (48) -> uint64_t milliseconds +// --------------------------------------------------------------------------- +static uint64_t SyscallGetTick(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a0; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + return gHal ? gHal->Timer.QueryTicks() : 0; +} + +// --------------------------------------------------------------------------- +// SYS_DEBUG_PRINT (49) a0=*str +// --------------------------------------------------------------------------- +static uint64_t SyscallDebugPrint(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + if (a0) DbgPrint("%s", (const char*)(uintptr_t)a0); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_CREATE_PROCESS (50) a0=*path a1=*handleOut a2=*cmdline (optional) +// --------------------------------------------------------------------------- +static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a3; (void)a4; (void)a5; + + const char* path = (const char*)(uintptr_t)a0; + uint32_t* handleOut = (uint32_t*)(uintptr_t)a1; + const char* cmdline = (const char*)(uintptr_t)a2; // NULL = no args + if (!path || !handleOut) return STATUS_INVALID_PARAMETER; + + // Read the executable from disk. + FILE_OBJECT* f; + KSTATUS s = IoOpenFile(path, &f); + if (!KSUCCESS(s)) return s; + + uint64_t size = IoGetFileSize(f); + void* mem = MmAllocatePool(size); + if (!mem) { IoCloseFile(f); return STATUS_NO_MEMORY; } + + size_t got; + s = IoReadFile(f, mem, size, &got); + IoCloseFile(f); + if (!KSUCCESS(s)) { MmFreePool(mem); return s; } + + // Create the process object. + PROCESS* proc; + s = KeCreateProcess(path, &proc); + if (!KSUCCESS(s)) { MmFreePool(mem); return s; } + + // Inherit CWD from parent + PROCESS* parent = KeGetCurrentProcess(); + if (parent) { + uintptr_t flags = KeAcquireSpinlock(&parent->CwdLock); + strncpy(proc->CurrentDirectory, parent->CurrentDirectory, sizeof(proc->CurrentDirectory) - 1); + proc->CurrentDirectory[sizeof(proc->CurrentDirectory) - 1] = '\0'; + KeReleaseSpinlock(&parent->CwdLock, flags); + } + + s = KeSetupUserProcessEnvironment(proc, path, cmdline, proc->CurrentDirectory); + if (!KSUCCESS(s)) { MmFreePool(mem); ObDereferenceObject(&proc->Header); return s; } + + // Map the PE image into memory. + uintptr_t imageBase = 0; + s = LdrLoadImage(mem, size, &imageBase, true); + MmFreePool(mem); + if (!KSUCCESS(s)) { ObDereferenceObject(&proc->Header); return s; } + + s = LdrResolveImportsForProcess(proc, imageBase); + if (!KSUCCESS(s)) { + MmFreeUserMemory((void*)imageBase, LdrGetImageSize(imageBase)); + ObDereferenceObject(&proc->Header); + return s; + } + + proc->ImageBase = imageBase; + proc->ImageSize = LdrGetImageSize(imageBase); + + // Give the caller a handle before making the child runnable. Otherwise a + // fast crash/exit can destroy the PROCESS while the syscall is still trying + // to finish wiring it up. + PROCESS* caller = KeGetCurrentProcess(); + if (!caller) { + MmFreeUserMemory((void*)imageBase, proc->ImageSize); + ObDereferenceObject(&proc->Header); + return STATUS_FAILURE; + } + + uint32_t handle; + s = KeAllocateHandle(caller, &proc->Header, &handle); + if (!KSUCCESS(s)) { + KeCloseHandle(caller, handle); + proc->ImageBase = 0; + proc->ImageSize = 0; + MmFreeUserMemory((void*)imageBase, proc->ImageSize); + ObDereferenceObject(&proc->Header); + return s; + } + + // Spin up the initial thread last. KeCreateThread queues the thread before + // returning, so everything visible from exit/fault/wait paths must already + // be valid at this point. + THREAD* thread; + uintptr_t entryPoint = LdrGetEntryPoint(imageBase); + s = KeCreateThread(proc, entryPoint, 0, PriorityNormal, &thread); + if (!KSUCCESS(s)) { + MmFreeUserMemory((void*)imageBase, LdrGetImageSize(imageBase)); + ObDereferenceObject(&proc->Header); + return s; + } + + *handleOut = handle; + return STATUS_SUCCESS; +} +// --------------------------------------------------------------------------- +// SYS_WAIT_FOR_PROCESS (51) a0=handle +// Blocks until the target process signals its ExitEvent. +// --------------------------------------------------------------------------- +static uint64_t SyscallWaitForProcess(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + + PROCESS* proc = (PROCESS*)HandleToObject((uint32_t)a0, ObjectTypeProcess); + if (!proc) return STATUS_INVALID_HANDLE; + + KSTATUS s = KeWaitForEvent(proc->ExitEvent); + ObDereferenceObject(&proc->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_GET_PROCESS_EXIT_CODE (52) a0=handle a1=*exitCodeOut +// --------------------------------------------------------------------------- +static uint64_t SyscallGetProcessExitCode(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + + PROCESS* proc = (PROCESS*)HandleToObject((uint32_t)a0, ObjectTypeProcess); + if (!proc) return STATUS_INVALID_HANDLE; + + int* exitCodeOut = (int*)(uintptr_t)a1; + KSTATUS s = KeGetProcessExitCode(proc, exitCodeOut); + ObDereferenceObject(&proc->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_WRITE_CONSOLE (53) a0=*buf a1=len +// Writes user console text in white; distinct from green SYS_DEBUG_PRINT. +// --------------------------------------------------------------------------- +static uint64_t SyscallWriteConsole(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + const char* buf = (const char*)(uintptr_t)a0; + size_t len = (size_t)a1; + if (buf && len) KePrintConsole(buf, len); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_OPEN_DIR (54) a0=*dirPath a1=*handleOut +// --------------------------------------------------------------------------- +static uint64_t SyscallOpenDir(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + const char* dirPath = (const char*)(uintptr_t)a0; + uint32_t* handleOut = (uint32_t*)(uintptr_t)a1; + + if (!handleOut) return STATUS_INVALID_PARAMETER; + + DIR_OBJECT* dir = NULL; + KSTATUS s = IoOpenDir(dirPath, &dir); + if (!KSUCCESS(s)) return s; + + // Put the object in the handle table and get a valid index (1-255) + s = KeAllocateHandle(KeGetCurrentProcess(), &dir->Header, handleOut); + + // Drop the reference KeAllocateHandle took, so that when user space + // calls CloseHandle, the refcount drops to 0 and the object is destroyed. + ObDereferenceObject(&dir->Header); + + return s; +} + +// --------------------------------------------------------------------------- +// SYS_READ_DIR (55) a0=handle a1=*KDIR_ENTRY +// Returns STATUS_NO_MORE_FILES when the directory is exhausted. +// --------------------------------------------------------------------------- +static uint64_t SyscallReadDir(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + + // a0 = handle from user mode + // a1 = KDIR_ENTRY* buffer + + // Look up a DIR_OBJECT using ObjectTypeDir (not ObjectTypeFile) + DIR_OBJECT* dir = (DIR_OBJECT*)HandleToObject((uint32_t)a0, ObjectTypeDir); + if (!dir) return STATUS_INVALID_PARAMETER; + + KSTATUS s = IoReadDir(dir, (void*)(uintptr_t)a1); + + ObDereferenceObject(&dir->Header); // Drop the ref taken by HandleToObject + return s; +} + +// --------------------------------------------------------------------------- +// SYS_SET_CWD (56) a0=*path +// --------------------------------------------------------------------------- +static uint64_t SyscallSetCwd(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + const char* path = (const char*)(uintptr_t)a0; + if (!path) return STATUS_INVALID_PARAMETER; + return IoSetCwd(path); +} + +// --------------------------------------------------------------------------- +// SYS_GET_CWD (57) a0=*buf a1=size +// --------------------------------------------------------------------------- +static uint64_t SyscallGetCwd(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + char* buf = (char*)(uintptr_t)a0; + size_t size = (size_t)a1; + if (!buf || size == 0) return STATUS_INVALID_PARAMETER; + return IoGetCwd(buf, size); +} + +// --------------------------------------------------------------------------- +// SYS_WAIT_THREAD (58) a0=tid +// Blocks the caller until the thread with the given TID exits. +// Searches the current process's thread list; cross-process join is not +// supported in v1 (POSIX pthread_join is always intra-process). +// Returns STATUS_NOT_FOUND if the TID is unknown (already reaped or never +// existed) - treat the same as a successful join at the POSIX layer. +// --------------------------------------------------------------------------- +static uint64_t SyscallWaitThread(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + uint32_t targetTid = (uint32_t)a0; + + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_FAILURE; + + // Walk the thread list under the spinlock to find the target, then bump + // its ExitEvent refcount so the event survives even if the thread exits + // and is destroyed while we wait. + EVENT* exitEvent = (EVENT*)0; + uintptr_t flags = KeAcquireSpinlock(&proc->HandleLock); + LIST_ENTRY* entry = proc->ThreadList.Fwd; + while (entry != &proc->ThreadList) { + THREAD* t = CONTAINING_RECORD(entry, THREAD, ProcessLink); + if (t->ThreadId == targetTid && t->ExitEvent) { + exitEvent = t->ExitEvent; + ObReferenceObject(&exitEvent->Header); // keep event alive + break; + } + entry = entry->Fwd; + } + KeReleaseSpinlock(&proc->HandleLock, flags); + + if (!exitEvent) { + // TID not found - either already exited and reaped, or invalid. + // Treat as success at the POSIX layer (join on an already-dead thread). + return STATUS_NOT_FOUND; + } + + KSTATUS s = KeWaitForEvent(exitEvent); + ObDereferenceObject(&exitEvent->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_GET_PROCESS_PID (59) a0=procHandle a1=*pidOut +// Returns the numeric PID of a process identified by handle. +// Used by posix_spawn to map the kernel handle back to a pid_t for waitpid. +// --------------------------------------------------------------------------- +static uint64_t SyscallGetProcessPid(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + + PROCESS* proc = (PROCESS*)HandleToObject((uint32_t)a0, ObjectTypeProcess); + uint32_t* pidOut = (uint32_t*)(uintptr_t)a1; + if (!proc) return STATUS_INVALID_HANDLE; + if (!pidOut) { ObDereferenceObject(&proc->Header); return STATUS_INVALID_PARAMETER; } + + *pidOut = proc->ProcessId; + ObDereferenceObject(&proc->Header); + return STATUS_SUCCESS; +} + +// =========================================================================== +// Named Queue Registry +// +// A flat, fixed-size table of { name -> QUEUE* } pairs protected by a +// spinlock. The registry holds one reference to each published queue for +// the lifetime of the entry, so the queue object cannot be destroyed while +// it is registered even if every process closes its own handle to it. +// +// Limits: NAMED_QUEUE_MAX names, each up to NAMED_QUEUE_NAME_MAX bytes. +// These are generous for the intended usage (a handful of system servers). +// =========================================================================== + +#define NAMED_QUEUE_MAX 16 +#define NAMED_QUEUE_NAME_MAX 64 + +typedef struct { + char Name[NAMED_QUEUE_NAME_MAX]; + QUEUE* Queue; // NULL = slot free; non-NULL = occupied + uint32_t PublisherPid; // ProcessId of whoever called SysPublishQueue +} NAMED_QUEUE_ENTRY; + +static NAMED_QUEUE_ENTRY gNamedQueues[NAMED_QUEUE_MAX]; +static SPINLOCK gNamedQueueLock; + +// --------------------------------------------------------------------------- +// SYS_PUBLISH_QUEUE (60) a0=*name a1=queueHandle +// +// Registers the queue identified by 'queueHandle' under 'name'. +// Fails with STATUS_ALREADY_EXISTS if the name is already taken. +// The registry bumps the queue's refcount so it outlives the handle. +// --------------------------------------------------------------------------- +static uint64_t SyscallPublishQueue(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + + const char* name = (const char*)(uintptr_t)a0; + if (!name || !name[0]) return STATUS_INVALID_PARAMETER; + + // Resolve the handle to a QUEUE object. HandleToObject bumps the + // refcount by one; we keep that ref in the registry slot. + QUEUE* q = (QUEUE*)HandleToObject((uint32_t)a1, ObjectTypeQueue); + if (!q) return STATUS_INVALID_HANDLE; + + uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); + + // Reject duplicate names. + for (int i = 0; i < NAMED_QUEUE_MAX; i++) { + if (gNamedQueues[i].Queue && + strncmp(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX) == 0) { + KeReleaseSpinlock(&gNamedQueueLock, flags); + ObDereferenceObject(&q->Header); // drop the ref we just took + return STATUS_ALREADY_EXISTS; + } + } + + // Find a free slot. + for (int i = 0; i < NAMED_QUEUE_MAX; i++) { + if (!gNamedQueues[i].Queue) { + strncpy(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX - 1); + gNamedQueues[i].Name[NAMED_QUEUE_NAME_MAX - 1] = '\0'; + gNamedQueues[i].Queue = q; // ref transferred to registry + gNamedQueues[i].PublisherPid = KeGetCurrentProcess()->ProcessId; + KeReleaseSpinlock(&gNamedQueueLock, flags); + return STATUS_SUCCESS; + } + } + + KeReleaseSpinlock(&gNamedQueueLock, flags); + ObDereferenceObject(&q->Header); // no room - drop the ref + return STATUS_NO_MEMORY; +} + +// --------------------------------------------------------------------------- +// SYS_OPEN_NAMED_QUEUE (61) a0=*name a1=*handleOut +// +// Looks up 'name' in the registry and allocates a handle to the queue in the +// calling process's handle table. Returns STATUS_NOT_FOUND if no match. +// Callers that race against the publisher should retry on NOT_FOUND. +// --------------------------------------------------------------------------- +static uint64_t SyscallOpenNamedQueue(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + + const char* name = (const char*)(uintptr_t)a0; + uint32_t* handleOut = (uint32_t*)(uintptr_t)a1; + if (!name || !name[0] || !handleOut) return STATUS_INVALID_PARAMETER; + + uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); + + QUEUE* q = NULL; + for (int i = 0; i < NAMED_QUEUE_MAX; i++) { + if (gNamedQueues[i].Queue && + strncmp(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX) == 0) { + q = gNamedQueues[i].Queue; + ObReferenceObject(&q->Header); // one ref for the new handle + break; + } + } + + KeReleaseSpinlock(&gNamedQueueLock, flags); + + if (!q) return STATUS_NOT_FOUND; + + // AllocHandleForCurrent consumes the ref we just bumped. + // On failure it calls ObDereferenceObject internally. + return AllocHandleForCurrent(&q->Header, handleOut); +} + +// --------------------------------------------------------------------------- +// SYS_QUEUE_DUPLICATE (62) a0=handle a1=targetPid a2=*handleOut +// +// Looks up the QUEUE behind 'handle' in the CALLER's table, then allocates +// a new handle for the same object in the TARGET process's table. +// Both processes share the same underlying QUEUE (refcount bumped). +// The caller's own handle is unchanged. +// --------------------------------------------------------------------------- +static uint64_t SyscallQueueDuplicate(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a3; (void)a4; (void)a5; + if (!a2) return STATUS_INVALID_PARAMETER; + + QUEUE* q = (QUEUE*)HandleToObject((uint32_t)a0, ObjectTypeQueue); + if (!q) return STATUS_NOT_FOUND; + + PROCESS* target = KeGetProcessById((uint32_t)a1); + if (!target) { + ObDereferenceObject(&q->Header); + return STATUS_NOT_FOUND; + } + + uint32_t newHandle = 0; + KSTATUS s = KeAllocateHandle(target, &q->Header, &newHandle); + ObDereferenceObject(&target->Header); + ObDereferenceObject(&q->Header); + if (!KSUCCESS(s)) return s; + + *(uint32_t*)(uintptr_t)a2 = newHandle; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_NAMED_QUEUE_OWNER_PID (63) a0=*name a1=*pidOut +// +// Returns the ProcessId of whoever published the named queue. +// Lets a client get the publisher's PID so it can duplicate one of its +// own handles into that process via SYS_QUEUE_DUPLICATE. +// --------------------------------------------------------------------------- +static uint64_t SyscallNamedQueueOwnerPid(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + const char* name = (const char*)(uintptr_t)a0; + uint32_t* pidOut = (uint32_t*)(uintptr_t)a1; + if (!name || !name[0] || !pidOut) return STATUS_INVALID_PARAMETER; + + uintptr_t flags = KeAcquireSpinlock(&gNamedQueueLock); + for (int i = 0; i < NAMED_QUEUE_MAX; i++) { + if (gNamedQueues[i].Queue && + strncmp(gNamedQueues[i].Name, name, NAMED_QUEUE_NAME_MAX) == 0) { + *pidOut = gNamedQueues[i].PublisherPid; + KeReleaseSpinlock(&gNamedQueueLock, flags); + return STATUS_SUCCESS; + } + } + KeReleaseSpinlock(&gNamedQueueLock, flags); + return STATUS_NOT_FOUND; +} + +static uint64_t SyscallKillProcess(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + PROCESS* proc = KeGetProcessById((uint32_t)a0); + if (!proc) return STATUS_NOT_FOUND; + THREAD* self = KeGetCurrentThread(); + int exitCode = (int)a1; + if (self && self->Owner == proc) { + KeExitProcess(proc, exitCode); + ObDereferenceObject(&proc->Header); + KeExitThread(self, exitCode); + while (true) ; + } + KeExitProcess(proc, exitCode); + ObDereferenceObject(&proc->Header); + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_GET_PROCESS_LIST (65) a0=*pidsOut a1=maxCount a2=*countOut +// --------------------------------------------------------------------------- +static uint64_t SyscallGetProcessList(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a3; (void)a4; (void)a5; + uint32_t* pidsOut = (uint32_t*)(uintptr_t)a0; + size_t maxCount = (size_t)a1; + size_t* countOut = (size_t*)(uintptr_t)a2; + size_t count = KeCopyProcessIds(pidsOut, maxCount); + if (countOut) *countOut = count; + return STATUS_SUCCESS; +} + +// --------------------------------------------------------------------------- +// SYS_RENAME_FILE (66) a0=*oldPath a1=*newPath +// --------------------------------------------------------------------------- +static uint64_t SyscallRenameFile(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + const char* oldPath = (const char*)(uintptr_t)a0; + const char* newPath = (const char*)(uintptr_t)a1; + if (!oldPath || !newPath) return STATUS_INVALID_PARAMETER; + return IoRenameFile(oldPath, newPath); +} + +// --------------------------------------------------------------------------- +// SYS_CREATE_DIR (67) a0=*path +// --------------------------------------------------------------------------- +static uint64_t SyscallCreateDir(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + const char* path = (const char*)(uintptr_t)a0; + if (!path) return STATUS_INVALID_PARAMETER; + return IoCreateDir(path); +} + +// --------------------------------------------------------------------------- +// SYS_REMOVE_DIR (68) a0=*path +// --------------------------------------------------------------------------- +static uint64_t SyscallRemoveDir(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + const char* path = (const char*)(uintptr_t)a0; + if (!path) return STATUS_INVALID_PARAMETER; + return IoRemoveDir(path); +} + +// =========================================================================== +// Dispatch table - index must match SYS_* in syscall_nr.h exactly. +// =========================================================================== +typedef uint64_t (*SYSCALL_HANDLER)(uint64_t, uint64_t, uint64_t, + uint64_t, uint64_t, uint64_t); + +static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = { + /* 00 SYS_EXIT_PROCESS */ SyscallExitProcess, + /* 01 SYS_EXIT_THREAD */ SyscallExitThread, + /* 02 SYS_CREATE_THREAD */ SyscallCreateThread, + /* 03 SYS_GET_CURRENT_TID */ SyscallGetCurrentTid, + /* 04 SYS_GET_CURRENT_PID */ SyscallGetCurrentPid, + /* 05 SYS_YIELD */ SyscallYield, + /* 06 SYS_SLEEP */ SyscallSleep, + /* 07 SYS_SET_THREAD_PRIORITY */ SyscallSetThreadPriority, + /* 08 SYS_ALLOC */ SyscallAlloc, + /* 09 SYS_FREE */ SyscallFree, + /* 10 SYS_REALLOC */ SyscallRealloc, + /* 11 SYS_MUTEX_CREATE */ SyscallMutexCreate, + /* 12 SYS_MUTEX_ACQUIRE */ SyscallMutexAcquire, + /* 13 SYS_MUTEX_TRY_ACQUIRE */ SyscallMutexTryAcquire, + /* 14 SYS_MUTEX_RELEASE */ SyscallMutexRelease, + /* 15 SYS_SEM_CREATE */ SyscallSemCreate, + /* 16 SYS_SEM_WAIT */ SyscallSemWait, + /* 17 SYS_SEM_TRY_WAIT */ SyscallSemTryWait, + /* 18 SYS_SEM_SIGNAL */ SyscallSemSignal, + /* 19 SYS_EVENT_CREATE */ SyscallEventCreate, + /* 20 SYS_EVENT_SIGNAL */ SyscallEventSignal, + /* 21 SYS_EVENT_CLEAR */ SyscallEventClear, + /* 22 SYS_EVENT_WAIT */ SyscallEventWait, + /* 23 SYS_QUEUE_CREATE */ SyscallQueueCreate, + /* 24 SYS_QUEUE_SEND */ SyscallQueueSend, + /* 25 SYS_QUEUE_RECV */ SyscallQueueRecv, + /* 26 SYS_CLOSE_HANDLE */ SyscallCloseHandle, + /* 27 SYS_OPEN_FILE */ SyscallOpenFile, + /* 28 SYS_READ_FILE */ SyscallReadFile, + /* 29 SYS_WRITE_FILE */ SyscallWriteFile, + /* 30 SYS_CREATE_FILE */ SyscallCreateFile, + /* 31 SYS_DELETE_FILE */ SyscallDeleteFile, + /* 32 SYS_TRUNCATE_FILE */ SyscallTruncateFile, + /* 33 SYS_GET_FILE_POS */ SyscallGetFilePos, + /* 34 SYS_SEEK_FILE */ SyscallSeekFile, + /* 35 SYS_GET_FILE_SIZE */ SyscallGetFileSize, + /* 36 SYS_SCREEN_BLIT */ SyscallScreenBlit, + /* 37 SYS_SCREEN_CLEAR */ SyscallScreenClear, + /* 38 SYS_SCREEN_GET_SIZE */ SyscallScreenGetSize, + /* 39 SYS_SCREEN_PUT_CHAR */ SyscallScreenPutChar, + /* 40 SYS_KB_POLL */ SyscallKbPoll, + /* 41 SYS_KB_IS_KEY_DOWN */ SyscallKbIsKeyDown, + /* 42 SYS_KB_READ_CHAR */ SyscallKbReadChar, + /* 43 SYS_KB_READ_SCANCODE */ SyscallKbReadScancode, + /* 44 SYS_KB_GET_MODIFIERS */ SyscallKbGetModifiers, + /* 45 SYS_MOUSE_POLL */ SyscallMousePoll, + /* 46 SYS_MOUSE_READ */ SyscallMouseRead, + /* 47 SYS_MOUSE_GET_POS */ SyscallMouseGetPos, + /* 48 SYS_GET_TICK */ SyscallGetTick, + /* 49 SYS_DEBUG_PRINT */ SyscallDebugPrint, + /* 50 SYS_CREATE_PROCESS */ SyscallCreateProcess, + /* 51 SYS_WAIT_FOR_PROCESS */ SyscallWaitForProcess, + /* 52 SYS_GET_PROCESS_EXIT_CODE */ SyscallGetProcessExitCode, + /* 53 SYS_WRITE_CONSOLE */ SyscallWriteConsole, + /* 54 SYS_OPEN_DIR */ SyscallOpenDir, + /* 55 SYS_READ_DIR */ SyscallReadDir, + /* 56 SYS_SET_CWD */ SyscallSetCwd, + /* 57 SYS_GET_CWD */ SyscallGetCwd, + /* 58 SYS_WAIT_THREAD */ SyscallWaitThread, + /* 59 SYS_GET_PROCESS_PID */ SyscallGetProcessPid, + /* 60 SYS_PUBLISH_QUEUE */ SyscallPublishQueue, + /* 61 SYS_OPEN_NAMED_QUEUE */ SyscallOpenNamedQueue, + /* 62 SYS_QUEUE_DUPLICATE */ SyscallQueueDuplicate, + /* 63 SYS_NAMED_QUEUE_OWNER_PID */ SyscallNamedQueueOwnerPid, + /* 64 SYS_KILL_PROCESS */ SyscallKillProcess, + /* 65 SYS_GET_PROCESS_LIST */ SyscallGetProcessList, + /* 66 SYS_RENAME_FILE */ SyscallRenameFile, + /* 67 SYS_CREATE_DIR */ SyscallCreateDir, + /* 68 SYS_REMOVE_DIR */ SyscallRemoveDir, +}; + +// --------------------------------------------------------------------------- +// Entry point - called from ke_syscall.asm after argument re-ordering +// --------------------------------------------------------------------------- +KSTATUS KeSyscallDispatch(uint64_t nr, uint64_t a0, uint64_t a1, + uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { + if (nr >= SYS_MAX || !gSyscallTable[nr]) return STATUS_NOT_SUPPORTED; + return (KSTATUS)gSyscallTable[nr](a0, a1, a2, a3, a4, a5); +} diff --git a/code/kernel/ke_thread.c b/code/kernel/ke_thread.c index 19d212e..1fe2feb 100644 --- a/code/kernel/ke_thread.c +++ b/code/kernel/ke_thread.c @@ -1,256 +1,281 @@ -// ke_thread.c - thread model -#include -#include -#include -#include -#include -#include -#include - -#define FPU_FCW_DEFAULT 0x037Fu -#define FPU_MXCSR_DEFAULT 0x1F80u - -static uint32_t gNextThreadId = 1; -extern void KeThreadEntry(void); - -static void ThreadDestroy(OBJECT* obj) { - THREAD* thread = CONTAINING_RECORD(obj, THREAD, Header); - - if (thread->ExitEvent) { - ObDereferenceObject(&thread->ExitEvent->Header); - thread->ExitEvent = (EVENT*)0; - } - - if (thread->Owner) { - uintptr_t flags = KeAcquireSpinlock(&thread->Owner->HandleLock); - ListRemove(&thread->ProcessLink); - thread->Owner->ThreadCount--; - KeReleaseSpinlock(&thread->Owner->HandleLock, flags); - ObDereferenceObject(&thread->Owner->Header); - thread->Owner = (PROCESS *)0; - } - - if (thread->KernelStackBase) { - MmFreePool(thread->KernelStackBase); - thread->KernelStackBase = (void *)0; - } - - // Assumes UserStackBase was added to THREAD struct in ke_thread.h. - if (thread->UserStackBase) { - MmFreeUserMemory(thread->UserStackBase, USTACK_SIZE); - thread->UserStackBase = (void *)0; - } -} - -__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg, - THREAD_PRIORITY priority, THREAD **out) { - if (!owner || !out) return STATUS_INVALID_PARAMETER; - if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER; - - THREAD* thread = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD)); - if (!thread) return STATUS_NO_MEMORY; - - thread->Header.Destroy = ThreadDestroy; - thread->ThreadId = _InterlockedExchangeAdd((volatile long *)&gNextThreadId, 1); - thread->State = ThreadReady; - thread->Priority = priority; - thread->QuantaConsumed = 0; - thread->WaitObject = (OBJECT *)0; - thread->SchedLockFlags = 0x202ULL; - - void* kstack = MmAllocatePool(KSTACK_SIZE); - if (!kstack) { - ObDereferenceObject(&thread->Header); - return STATUS_NO_MEMORY; - } - memset(kstack, 0, KSTACK_SIZE); - thread->KernelStackBase = kstack; - thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE; - thread->TssRsp0 = thread->KernelStackTop; - - void* ustack = MmAllocateUserMemory(USTACK_SIZE); - if (!ustack) { - MmFreePool(kstack); - ObDereferenceObject(&thread->Header); - return STATUS_NO_MEMORY; - } - thread->UserStackBase = ustack; - uintptr_t userStackTop = (uintptr_t)ustack + USTACK_SIZE - 8; // i don't know why, i don't want to know why - // i shouldn't have to wonder why but the stack is off by 8 bytes unless we do this terribleness - // this magic number will hunt me in my dreams - - // Initialize the dummy TEB - // offset 0x10 is StackLimit, which __chkstk checks. - uint64_t* teb = (uint64_t*)ustack; - teb[0] = 0; // ExceptionList (GS:0x00) - teb[1] = userStackTop; // StackBase - teb[2] = (uintptr_t)ustack; // StackLimit - - thread->Teb = teb; - - thread->UserEntry = entry; - thread->UserArg = arg; - - uintptr_t* ksp = (uintptr_t *)thread->KernelStackTop; - *(--ksp) = userStackTop; - *(--ksp) = 0x23ULL; - *(--ksp) = userStackTop; - *(--ksp) = 0x202ULL; - *(--ksp) = 0x1BULL; - *(--ksp) = entry; - *(--ksp) = arg; - - ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t); - KCONTEXT* ctx = (KCONTEXT *)ksp; - memset(ctx, 0, sizeof(KCONTEXT)); - ctx->Rip = (uint64_t)KeThreadEntry; - thread->SavedContext = ctx; - - ObReferenceObject(&owner->Header); - thread->Owner = owner; - - // Allocate the exit event before the thread becomes visible. Manual-reset - // so multiple joiners (if any) all wake up when the thread exits. - EVENT* exitEvent; - KSTATUS s = KeEventCreate(EventManualReset, 0, &exitEvent); - if (!KSUCCESS(s)) { - MmFreeUserMemory(ustack, USTACK_SIZE); - MmFreePool(kstack); - ObDereferenceObject(&owner->Header); - ObDereferenceObject(&thread->Header); - return s; - } - thread->ExitEvent = exitEvent; - - uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); - ListInsertTail(&owner->ThreadList, &thread->ProcessLink); - owner->ThreadCount++; - KeReleaseSpinlock(&owner->HandleLock, flags); - - KeInitThreadFpuContext(thread); - KeReadyThread(thread); - *out = thread; - return STATUS_SUCCESS; -} - -__declspec(dllexport) void KeExitThread(THREAD* thread, int code) { - (void)code; - if (!thread) return; - - // Wake any pthread_join waiters before the thread is marked dead and - // potentially freed. Manual-reset means all concurrent waiters unblock. - if (thread->ExitEvent) - KeSignalEvent(thread->ExitEvent); - - thread->State = ThreadDead; - - KeYield(); -} - -// --------------------------------------------------------------------------- -// KeCreateKernelThread -// --------------------------------------------------------------------------- -// Kernel threads never go to ring 3: they have no user stack, no TEB, and -// their entry stub (KeKernelThreadEntry) calls the function directly then -// calls KeExitThread instead of IRETQ-ing. -// -// Stack layout built here (from KernelStackTop downward): -// [ksp - 8 ] arg <-- popped by KeKernelThreadEntry as r12 -// [ksp - 16] fn <-- popped by KeKernelThreadEntry as rbx -// [ksp - 16 - sizeof(KCONTEXT)] KCONTEXT { .Rip = KeKernelThreadEntry } -// --------------------------------------------------------------------------- -extern void KeKernelThreadEntry(void); - -__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg, - THREAD_PRIORITY priority, THREAD **out) { - // Kernel threads are owned by the idle process. We need a PROCESS* for - // the owner field; grab it from the idle/init thread that already exists. - THREAD* idleThread = KeGetCurrentThread(); // safe to call before scheduler starts - PROCESS* owner = idleThread ? idleThread->Owner : (PROCESS*)0; - if (!owner) return STATUS_INVALID_PARAMETER; - if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER; - - THREAD* thread = (THREAD*)ObAllocateObject(ObjectTypeThread, sizeof(THREAD)); - if (!thread) return STATUS_NO_MEMORY; - - thread->Header.Destroy = ThreadDestroy; - thread->ThreadId = _InterlockedExchangeAdd((volatile long*)&gNextThreadId, 1); - thread->State = ThreadReady; - thread->Priority = priority; - thread->QuantaConsumed = 0; - thread->WaitObject = (OBJECT*)0; - thread->SchedLockFlags = 0x202ULL; - thread->UserStackBase = (void*)0; // no user stack - thread->Teb = (void*)0; // no TEB; GS_BASE will be 0 in kernel ctx - - void* kstack = MmAllocatePool(KSTACK_SIZE); - if (!kstack) { - ObDereferenceObject(&thread->Header); - return STATUS_NO_MEMORY; - } - memset(kstack, 0, KSTACK_SIZE); - thread->KernelStackBase = kstack; - thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE; - thread->TssRsp0 = thread->KernelStackTop; - - // Build the initial stack: arg, fn, then KCONTEXT. - uintptr_t* ksp = (uintptr_t*)thread->KernelStackTop; - *(--ksp) = arg; // popped second (-> r12) - *(--ksp) = fn; // popped first (-> rbx) - - ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t); - KCONTEXT* ctx = (KCONTEXT*)ksp; - memset(ctx, 0, sizeof(KCONTEXT)); - ctx->Rip = (uint64_t)KeKernelThreadEntry; - thread->SavedContext = ctx; - - // Register with the owning process. - ObReferenceObject(&owner->Header); - thread->Owner = owner; - - uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); - ListInsertTail(&owner->ThreadList, &thread->ProcessLink); - owner->ThreadCount++; - KeReleaseSpinlock(&owner->HandleLock, flags); - - if (out) *out = thread; - - KeInitThreadFpuContext(thread); - KeReadyThread(thread); - return STATUS_SUCCESS; -} - -void KeBlockThread(THREAD* thread, OBJECT* waitObject) { - thread->State = ThreadWaiting; - thread->WaitObject = waitObject; - - KeYield(); -} - -void KeUnblockThread(THREAD* thread) { - thread->WaitObject = (OBJECT *)0; - thread->State = ThreadReady; - - if (thread->Priority < PriorityHigh) { - thread->Priority = (THREAD_PRIORITY)(thread->Priority + 1); - thread->QuantaConsumed = 0; - } - - KeReadyThread(thread); -} - -void KeInitThreadFpuContext(THREAD* thread) -{ - uintptr_t raw = - (uintptr_t)thread->FpuStorage; - - uintptr_t aligned = - (raw + 15) & ~(uintptr_t)15; - - thread->FpuContext = (void*)aligned; - - memset(thread->FpuContext, 0, 512); - - *(uint16_t*)((uint8_t*)thread->FpuContext + 0x00) = 0x037F; - *(uint32_t*)((uint8_t*)thread->FpuContext + 0x18) = 0x1F80; -} +// ke_thread.c - thread model +#include +#include +#include +#include +#include +#include +#include +#include + +#define FPU_FCW_DEFAULT 0x037Fu +#define FPU_MXCSR_DEFAULT 0x1F80u + +static uint32_t gNextThreadId = 1; +extern void KeThreadEntry(void); + +static void ThreadDestroy(OBJECT* obj) { + THREAD* thread = CONTAINING_RECORD(obj, THREAD, Header); + + if (thread->ExitEvent) { + ObDereferenceObject(&thread->ExitEvent->Header); + thread->ExitEvent = (EVENT*)0; + } + + if (thread->Owner) { + uintptr_t flags = KeAcquireSpinlock(&thread->Owner->HandleLock); + ListRemove(&thread->ProcessLink); + thread->Owner->ThreadCount--; + KeReleaseSpinlock(&thread->Owner->HandleLock, flags); + ObDereferenceObject(&thread->Owner->Header); + thread->Owner = (PROCESS *)0; + } + + if (thread->KernelStackBase) { + MmFreePool(thread->KernelStackBase); + thread->KernelStackBase = (void *)0; + } + + if (thread->Teb && thread->TebSize) { + MmFreeUserMemory(thread->Teb, thread->TebSize); + thread->Teb = (void*)0; + thread->TebSize = 0; + } + + if (thread->UserStackBase) { + MmFreeUserMemory(thread->UserStackBase, USTACK_SIZE); + thread->UserStackBase = (void *)0; + } +} + +__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg, + THREAD_PRIORITY priority, THREAD** out) { + if (!owner || !out) return STATUS_INVALID_PARAMETER; + if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER; + + THREAD* thread = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD)); + if (!thread) return STATUS_NO_MEMORY; + + thread->Header.Destroy = ThreadDestroy; + thread->ThreadId = _InterlockedExchangeAdd((volatile long *)&gNextThreadId, 1); + thread->State = ThreadReady; + thread->Priority = priority; + thread->QuantaConsumed = 0; + thread->WaitObject = (OBJECT *)0; + thread->SchedLockFlags = 0x202ULL; + + void* kstack = MmAllocatePool(KSTACK_SIZE); + if (!kstack) { + ObDereferenceObject(&thread->Header); + return STATUS_NO_MEMORY; + } + memset(kstack, 0, KSTACK_SIZE); + thread->KernelStackBase = kstack; + thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE; + thread->TssRsp0 = thread->KernelStackTop; + + void* ustack = MmAllocateUserMemory(USTACK_SIZE); + if (!ustack) { + MmFreePool(kstack); + thread->KernelStackBase = (void*)0; + ObDereferenceObject(&thread->Header); + return STATUS_NO_MEMORY; + } + thread->UserStackBase = ustack; + uintptr_t userStackTop = (uintptr_t)ustack + USTACK_SIZE - 8; // i don't know why, i don't want to know why + // i shouldn't have to wonder why but the stack is off by 8 bytes unless we do this terribleness + // this magic number will hunt me in my dreams + + SYS_TEB* teb = (SYS_TEB*)MmAllocateUserMemory(sizeof(SYS_TEB)); + if (!teb) { + MmFreeUserMemory(ustack, USTACK_SIZE); + thread->UserStackBase = (void*)0; + MmFreePool(kstack); + thread->KernelStackBase = (void*)0; + ObDereferenceObject(&thread->Header); + return STATUS_NO_MEMORY; + } + memset(teb, 0, sizeof(SYS_TEB)); + teb->Peb = (SYS_PEB*)owner->UserPebBase; + teb->StackBase = (void*)userStackTop; + teb->StackLimit = ustack; + teb->Self = teb; + teb->ThreadId = thread->ThreadId; + teb->Magic = SYS_TEB_MAGIC; + + thread->Teb = teb; + thread->TebSize = sizeof(SYS_TEB); + + thread->UserEntry = entry; + thread->UserArg = arg; + + uintptr_t* ksp = (uintptr_t *)thread->KernelStackTop; + *(--ksp) = userStackTop; + *(--ksp) = 0x23ULL; + *(--ksp) = userStackTop; + *(--ksp) = 0x202ULL; + *(--ksp) = 0x1BULL; + *(--ksp) = entry; + *(--ksp) = arg; + + ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t); + KCONTEXT* ctx = (KCONTEXT *)ksp; + memset(ctx, 0, sizeof(KCONTEXT)); + ctx->Rip = (uint64_t)KeThreadEntry; + thread->SavedContext = ctx; + + ObReferenceObject(&owner->Header); + thread->Owner = owner; + + // Allocate the exit event before the thread becomes visible. Manual-reset + // so multiple joiners (if any) all wake up when the thread exits. + EVENT* exitEvent; + KSTATUS s = KeEventCreate(EventManualReset, 0, &exitEvent); + if (!KSUCCESS(s)) { + if (thread->Teb && thread->TebSize) MmFreeUserMemory(thread->Teb, thread->TebSize); + thread->Teb = (void*)0; + thread->TebSize = 0; + MmFreeUserMemory(ustack, USTACK_SIZE); + thread->UserStackBase = (void*)0; + MmFreePool(kstack); + thread->KernelStackBase = (void*)0; + ObDereferenceObject(&owner->Header); + thread->Owner = (PROCESS*)0; + ObDereferenceObject(&thread->Header); + return s; + } + thread->ExitEvent = exitEvent; + + uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); + ListInsertTail(&owner->ThreadList, &thread->ProcessLink); + owner->ThreadCount++; + KeReleaseSpinlock(&owner->HandleLock, flags); + + KeInitThreadFpuContext(thread); + KeReadyThread(thread); + *out = thread; + return STATUS_SUCCESS; +} + +__declspec(dllexport) void KeExitThread(THREAD* thread, int code) { + (void)code; + if (!thread) return; + + // Wake any pthread_join waiters before the thread is marked dead and + // potentially freed. Manual-reset means all concurrent waiters unblock. + if (thread->ExitEvent) + KeSignalEvent(thread->ExitEvent); + + thread->State = ThreadDead; + + KeYield(); +} + +// --------------------------------------------------------------------------- +// KeCreateKernelThread +// --------------------------------------------------------------------------- +// Kernel threads never go to ring 3: they have no user stack, no TEB, and +// their entry stub (KeKernelThreadEntry) calls the function directly then +// calls KeExitThread instead of IRETQ-ing. +// +// Stack layout built here (from KernelStackTop downward): +// [ksp - 8 ] arg <-- popped by KeKernelThreadEntry as r12 +// [ksp - 16] fn <-- popped by KeKernelThreadEntry as rbx +// [ksp - 16 - sizeof(KCONTEXT)] KCONTEXT { .Rip = KeKernelThreadEntry } +// --------------------------------------------------------------------------- +extern void KeKernelThreadEntry(void); + +__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg, + THREAD_PRIORITY priority, THREAD** out) { + // Kernel threads are owned by the idle process. We need a PROCESS* for + // the owner field; grab it from the idle/init thread that already exists. + THREAD* idleThread = KeGetCurrentThread(); // safe to call before scheduler starts + PROCESS* owner = idleThread ? idleThread->Owner : (PROCESS*)0; + if (!owner) return STATUS_INVALID_PARAMETER; + if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER; + + THREAD* thread = (THREAD*)ObAllocateObject(ObjectTypeThread, sizeof(THREAD)); + if (!thread) return STATUS_NO_MEMORY; + + thread->Header.Destroy = ThreadDestroy; + thread->ThreadId = _InterlockedExchangeAdd((volatile long*)&gNextThreadId, 1); + thread->State = ThreadReady; + thread->Priority = priority; + thread->QuantaConsumed = 0; + thread->WaitObject = (OBJECT*)0; + thread->SchedLockFlags = 0x202ULL; + thread->UserStackBase = (void*)0; // no user stack + thread->Teb = (void*)0; // no TEB; GS_BASE will be 0 in kernel ctx + thread->TebSize = 0; + + void* kstack = MmAllocatePool(KSTACK_SIZE); + if (!kstack) { + ObDereferenceObject(&thread->Header); + return STATUS_NO_MEMORY; + } + memset(kstack, 0, KSTACK_SIZE); + thread->KernelStackBase = kstack; + thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE; + thread->TssRsp0 = thread->KernelStackTop; + + // Build the initial stack: arg, fn, then KCONTEXT. + uintptr_t* ksp = (uintptr_t*)thread->KernelStackTop; + *(--ksp) = arg; // popped second (-> r12) + *(--ksp) = fn; // popped first (-> rbx) + + ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t); + KCONTEXT* ctx = (KCONTEXT*)ksp; + memset(ctx, 0, sizeof(KCONTEXT)); + ctx->Rip = (uint64_t)KeKernelThreadEntry; + thread->SavedContext = ctx; + + // Register with the owning process. + ObReferenceObject(&owner->Header); + thread->Owner = owner; + + uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); + ListInsertTail(&owner->ThreadList, &thread->ProcessLink); + owner->ThreadCount++; + KeReleaseSpinlock(&owner->HandleLock, flags); + + if (out) *out = thread; + + KeInitThreadFpuContext(thread); + KeReadyThread(thread); + return STATUS_SUCCESS; +} + +void KeBlockThread(THREAD* thread, OBJECT* waitObject) { + thread->State = ThreadWaiting; + thread->WaitObject = waitObject; + + KeYield(); +} + +void KeUnblockThread(THREAD* thread) { + thread->WaitObject = (OBJECT *)0; + thread->State = ThreadReady; + + if (thread->Priority < PriorityHigh) { + thread->Priority = (THREAD_PRIORITY)(thread->Priority + 1); + thread->QuantaConsumed = 0; + } + + KeReadyThread(thread); +} + +void KeInitThreadFpuContext(THREAD* thread) +{ + uintptr_t raw = + (uintptr_t)thread->FpuStorage; + + uintptr_t aligned = + (raw + 15) & ~(uintptr_t)15; + + thread->FpuContext = (void*)aligned; + + memset(thread->FpuContext, 0, 512); + + *(uint16_t*)((uint8_t*)thread->FpuContext + 0x00) = 0x037F; + *(uint32_t*)((uint8_t*)thread->FpuContext + 0x18) = 0x1F80; +} diff --git a/code/kernel/ke_thread.h b/code/kernel/ke_thread.h index 8500bf3..73463cd 100644 --- a/code/kernel/ke_thread.h +++ b/code/kernel/ke_thread.h @@ -1,101 +1,102 @@ -#pragma once -// ke_thread.h - thread model -// A THREAD is the unit of scheduling. Every thread belongs to exactly one process. -#include -#include -#include -#include -#include - -#define KSTACK_SIZE (64 * 1024) /* 64 KB kernel stack */ -#define USTACK_SIZE (512 * 1024) /* 512 KB user stack */ - -typedef enum THREAD_PRIORITY { - PriorityLow = 0, - PriorityNormal = 1, - PriorityHigh = 2, - PriorityCount = 3, -} THREAD_PRIORITY; - -typedef enum THREAD_STATE { - ThreadReady = 0, - ThreadRunning = 1, - ThreadWaiting = 2, - ThreadDead = 3, -} THREAD_STATE; - -// Saved register context - matches the layout pushed by KeSwitchContext. -// The saved RSP points to this structure on the kernel stack. -typedef struct KCONTEXT { - uint64_t R15, R14, R13, R12; - uint64_t Rbp, Rbx; - uint64_t Rip; /* return address back into KeSwitchContext */ -} KCONTEXT; - -typedef struct THREAD { - OBJECT Header; /* must be first */ - - uint32_t ThreadId; - THREAD_STATE State; - THREAD_PRIORITY Priority; - - // Quanta consumed without blocking - used by the MLFQ for demotion. - uint32_t QuantaConsumed; - - PROCESS* Owner; - LIST_ENTRY ProcessLink; /* node in Owner->ThreadList */ - LIST_ENTRY SchedulerLink; /* node in run queue or wait list */ - - // Kernel stack - never PAGE_USER, pointed to by TssRsp0. - void* KernelStackBase; - uintptr_t KernelStackTop; - - // Saved context pointer - set by KeSwitchContext. - KCONTEXT* SavedContext; - - // Non-NULL while State == ThreadWaiting. - OBJECT* WaitObject; - - // Updated on every context switch so the CPU loads the right kernel stack. - uintptr_t TssRsp0; - - // Thread Environment Block - void* Teb; - - // Ring-3 entry point and argument for newly created threads. - uintptr_t UserEntry; - uintptr_t UserArg; - - uintptr_t SchedLockFlags; // Stores RFLAGS while thread is context switched out - void* UserStackBase; // Tracks user stack allocation to prevent memory leaks - - // Signaled (manual-reset) when the thread exits. - // Allocated in KeCreateThread, signaled in KeExitThread before teardown. - // Waiters block on this via SYS_WAIT_THREAD -> KeWaitForEvent. - EVENT* ExitEvent; - - void* FpuContext; - uint8_t FpuStorage[528]; -} THREAD; - -__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg, - THREAD_PRIORITY priority, THREAD **out); - -// Create a kernel-mode-only thread. fn is called directly in ring 0 with -// arg; when fn returns the thread exits automatically. No user stack or TEB -// is allocated. The thread is owned by the idle process and runs at the -// given priority. -__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg, - THREAD_PRIORITY priority, THREAD **out); - -// Mark a thread dead and deschedule it permanently. -__declspec(dllexport) void KeExitThread(THREAD* thread, int code); - -// Block / unblock primitives used by sync objects. -void KeBlockThread(THREAD* thread, OBJECT* waitObject); -void KeUnblockThread(THREAD* thread); - -// Assembly stub - implemented in ke_switch.asm. -void KeSwitchContext(THREAD* from, THREAD* to); - -void KeInitThreadFpuContext(THREAD* thread); +#pragma once +// ke_thread.h - thread model +// A THREAD is the unit of scheduling. Every thread belongs to exactly one process. +#include +#include +#include +#include +#include + +#define KSTACK_SIZE (64 * 1024) /* 64 KB kernel stack */ +#define USTACK_SIZE (512 * 1024) /* 512 KB user stack */ + +typedef enum THREAD_PRIORITY { + PriorityLow = 0, + PriorityNormal = 1, + PriorityHigh = 2, + PriorityCount = 3, +} THREAD_PRIORITY; + +typedef enum THREAD_STATE { + ThreadReady = 0, + ThreadRunning = 1, + ThreadWaiting = 2, + ThreadDead = 3, +} THREAD_STATE; + +// Saved register context - matches the layout pushed by KeSwitchContext. +// The saved RSP points to this structure on the kernel stack. +typedef struct KCONTEXT { + uint64_t R15, R14, R13, R12; + uint64_t Rbp, Rbx; + uint64_t Rip; /* return address back into KeSwitchContext */ +} KCONTEXT; + +typedef struct THREAD { + OBJECT Header; /* must be first */ + + uint32_t ThreadId; + THREAD_STATE State; + THREAD_PRIORITY Priority; + + // Quanta consumed without blocking - used by the MLFQ for demotion. + uint32_t QuantaConsumed; + + PROCESS* Owner; + LIST_ENTRY ProcessLink; /* node in Owner->ThreadList */ + LIST_ENTRY SchedulerLink; /* node in run queue or wait list */ + + // Kernel stack - never PAGE_USER, pointed to by TssRsp0. + void* KernelStackBase; + uintptr_t KernelStackTop; + + // Saved context pointer - set by KeSwitchContext. + KCONTEXT* SavedContext; + + // Non-NULL while State == ThreadWaiting. + OBJECT* WaitObject; + + // Updated on every context switch so the CPU loads the right kernel stack. + uintptr_t TssRsp0; + + // Thread Environment Block. GS base is loaded from this pointer. + void* Teb; + size_t TebSize; + + // Ring-3 entry point and argument for newly created threads. + uintptr_t UserEntry; + uintptr_t UserArg; + + uintptr_t SchedLockFlags; // Stores RFLAGS while thread is context switched out + void* UserStackBase; // Tracks user stack allocation to prevent memory leaks + + // Signaled (manual-reset) when the thread exits. + // Allocated in KeCreateThread, signaled in KeExitThread before teardown. + // Waiters block on this via SYS_WAIT_THREAD -> KeWaitForEvent. + EVENT* ExitEvent; + + void* FpuContext; + uint8_t FpuStorage[528]; +} THREAD; + +__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg, + THREAD_PRIORITY priority, THREAD** out); + +// Create a kernel-mode-only thread. fn is called directly in ring 0 with +// arg; when fn returns the thread exits automatically. No user stack or TEB +// is allocated. The thread is owned by the idle process and runs at the +// given priority. +__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg, + THREAD_PRIORITY priority, THREAD** out); + +// Mark a thread dead and deschedule it permanently. +__declspec(dllexport) void KeExitThread(THREAD* thread, int code); + +// Block / unblock primitives used by sync objects. +void KeBlockThread(THREAD* thread, OBJECT* waitObject); +void KeUnblockThread(THREAD* thread); + +// Assembly stub - implemented in ke_switch.asm. +void KeSwitchContext(THREAD* from, THREAD* to); + +void KeInitThreadFpuContext(THREAD* thread); diff --git a/code/kernel/ke_userenv.h b/code/kernel/ke_userenv.h new file mode 100644 index 0000000..e6d71bd --- /dev/null +++ b/code/kernel/ke_userenv.h @@ -0,0 +1,35 @@ +#pragma once +// ke_userenv.h - kernel view of the user-mode process/thread environment. +// This is this OS' ABI, not a Windows compatibility structure. + +#include +#include + +#define SYS_PEB_MAGIC 0x30424550u /* "PEB0" */ +#define SYS_TEB_MAGIC 0x30424554u /* "TEB0" */ +#define SYS_ENV_VERSION 1u +#define SYS_CWD_MAX 512u + +typedef struct SYS_PEB { + uint32_t Magic; + uint32_t Version; + uint32_t ProcessId; + + const char* ImagePath; + size_t ImagePathLength; + + const char* CommandLine; + size_t CommandLineLength; + + const char* CurrentDirectory; + size_t CurrentDirectoryLength; +} SYS_PEB; + +typedef struct SYS_TEB { + SYS_PEB* Peb; + void* StackBase; + void* StackLimit; + struct SYS_TEB* Self; + uint32_t ThreadId; + uint32_t Magic; +} SYS_TEB; diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index 11838cb..5c9defd 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -250,8 +250,8 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) { uint64_t size = IoGetFileSize(f); DbgPrint("LaunchUserProcess: IoGetFileSize: %llu\n", size); - void* mem = MmAllocateUserMemory(size); - DbgPrint("LaunchUserProcess: MmAllocateUserMemory: 0x%llX\n", mem); + void* mem = MmAllocatePool(size); + DbgPrint("LaunchUserProcess: MmAllocatePool: 0x%llX\n", mem); if (!mem) { IoCloseFile(f); return 0; @@ -262,7 +262,7 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) { DbgPrint("LaunchUserProcess: IoReadFile: %d\n", s); IoCloseFile(f); if (!KSUCCESS(s)) { - MmFreeUserMemory(mem, size); + MmFreePool(mem); return 0; } @@ -273,35 +273,23 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) { s = KeCreateProcess(filePath, &proc); if (!KSUCCESS(s)) { DbgPrint("Failed to create process. System halted.\n"); - MmFreeUserMemory(mem, size); + MmFreePool(mem); return 0; } - DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, filePath); + DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, (args && args[0]) ? args : filePath); - if (args && args[0]) - strncpy(proc->Cmdline, args, sizeof(proc->Cmdline) - 1); - else - strncpy(proc->Cmdline, filePath, sizeof(proc->Cmdline) - 1); - proc->Cmdline[sizeof(proc->Cmdline) - 1] = '\0'; - - // proc->Cmdline is kernel pool (ffffc000...) - not readable at CPL=3. - // Copy into user memory before handing the pointer to the initial thread. - size_t cmdlineLen = strlen(proc->Cmdline) + 1; - char* userCmdline = (char*)MmAllocateUserMemory(cmdlineLen); - if (!userCmdline) { - DbgPrint("LaunchUserProcess: Failed to allocate user cmdline buffer.\n"); - MmFreeUserMemory(mem, size); + s = KeSetupUserProcessEnvironment(proc, filePath, args, proc->CurrentDirectory); + if (!KSUCCESS(s)) { + DbgPrint("LaunchUserProcess: Failed to build user process environment. Status: %d\n", s); + MmFreePool(mem); ObDereferenceObject(&proc->Header); return 0; } - memcpy(userCmdline, proc->Cmdline, cmdlineLen); - proc->UserCmdlineBase = (uintptr_t)userCmdline; - proc->UserCmdlineSize = cmdlineLen; // Parse the raw PE file and map it into executable memory. uintptr_t imageBase = 0; s = LdrLoadImage(mem, size, &imageBase, true); - MmFreeUserMemory(mem, size); + MmFreePool(mem); if (!KSUCCESS(s)) { DbgPrint("LaunchUserProcess: LdrLoadImage failed with status: %d\n", s); ObDereferenceObject(&proc->Header); @@ -316,19 +304,22 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) { return 0; } + proc->ImageBase = imageBase; + proc->ImageSize = LdrGetImageSize(imageBase); + uintptr_t entryPoint = LdrGetEntryPoint(imageBase); DbgPrint("LaunchUserProcess: PE Image mapped! Base 0x%llx, Entry 0x%llX\n", imageBase, entryPoint); - // Create the main thread, pointing it to the PE Entry Point. - s = KeCreateThread(proc, (uintptr_t)entryPoint, (uintptr_t)userCmdline, PriorityNormal, &thread); + // Create the main thread last. KeCreateThread queues it before returning. + s = KeCreateThread(proc, (uintptr_t)entryPoint, 0, PriorityNormal, &thread); if (!KSUCCESS(s)) { DbgPrint("LaunchUserProcess: Failed to create thread. Status: %d\n", s); + proc->ImageBase = 0; + proc->ImageSize = 0; MmFreeUserMemory((void*)imageBase, LdrGetImageSize(imageBase)); ObDereferenceObject(&proc->Header); return 0; } - proc->ImageBase = imageBase; - proc->ImageSize = LdrGetImageSize(imageBase); DbgPrint("LaunchUserProcess: Thread created and queued!\n"); // Caller owns the reference returned by KeCreateProcess. diff --git a/code/kernel/mm_virt.c b/code/kernel/mm_virt.c index ee42212..b17d3bf 100644 --- a/code/kernel/mm_virt.c +++ b/code/kernel/mm_virt.c @@ -323,6 +323,7 @@ static void* HeapAlloc(HEAP* h, size_t size) { HdrToFtr(best)->Magic = BLOCK_MAGIC; HdrToFtr(best)->Size = best->Size; + memset((char *)best + HDR_SIZE, 0, size); KeReleaseSpinlock(&h->Lock, flags); return (char *)best + HDR_SIZE; } @@ -409,6 +410,7 @@ static void* HeapRealloc(HEAP* h, void* ptr, size_t new_size) { if (!new_ptr) { return (void*)0; // Out of memory, original block is left untouched } + memset(new_ptr, 0, aligned_size); // Copy the old data to the new block. // We only copy up to the old block's usable size. diff --git a/code/posix/posix.h b/code/posix/posix.h index 7ffebc1..ef3323c 100644 --- a/code/posix/posix.h +++ b/code/posix/posix.h @@ -78,6 +78,8 @@ typedef void* pthread_mutexattr_t; typedef void* posix_spawn_file_actions_t; typedef void* posix_spawnattr_t; +__declspec(dllexport) const char* posix_get_command_line(void); + // --------------------------------------------------------------------------- // dirent (used by posix_dir.c) // --------------------------------------------------------------------------- diff --git a/code/posix/posix_io.c b/code/posix/posix_io.c index a91049e..af62cba 100644 --- a/code/posix/posix_io.c +++ b/code/posix/posix_io.c @@ -22,9 +22,12 @@ static int KsToErrno(uint32_t s) { } } -__declspec(dllexport) int ClearConsoleScreenBuffer(void* hConsole) { +__declspec(dllexport) void clearscreen(void) { SysScreenClear(0x00000000); - return 1; +} + +__declspec(dllexport) void clearkeyboard(void) { + while (SysKbPoll()) (void)SysKbReadChar(); } __declspec(dllexport) int open(const char* path, int flags, ...) { @@ -154,14 +157,31 @@ __declspec(dllexport) off_t lseek(int fd, off_t offset, int whence) { FD_ENTRY* e = FdGet(pid, fd); if (!e || e->Type != FD_FILE) { errno = (e ? ESPIPE : EBADF); return -1; } - uint64_t target; + int64_t target = 0; switch (whence) { - case SEEK_SET: target = (uint64_t)offset; break; - case SEEK_CUR: target = SysGetFilePos(e->KHandle) + (uint64_t)offset; break; - case SEEK_END: target = SysGetFileSize(e->KHandle) + (uint64_t)offset; break; - default: errno = EINVAL; return -1; + case SEEK_SET: + target = (int64_t)offset; + break; + case SEEK_CUR: { + uint64_t cur = SysGetFilePos(e->KHandle); + if (cur > 0x7FFFFFFFULL) { errno = EINVAL; return -1; } + target = (int64_t)cur + (int64_t)offset; + break; + } + case SEEK_END: { + uint64_t size = SysGetFileSize(e->KHandle); + if (size > 0x7FFFFFFFULL) { errno = EINVAL; return -1; } + target = (int64_t)size + (int64_t)offset; + break; + } + default: + errno = EINVAL; + return -1; } - uint32_t s = SysSeekFile(e->KHandle, target); + + if (target < 0 || target > 0x7FFFFFFFLL) { errno = EINVAL; return -1; } + + uint32_t s = SysSeekFile(e->KHandle, (uint64_t)target); if (s != 0) { errno = KsToErrno(s); return -1; } return (off_t)target; } diff --git a/code/posix/posix_proc.c b/code/posix/posix_proc.c index c3df33d..64323ef 100644 --- a/code/posix/posix_proc.c +++ b/code/posix/posix_proc.c @@ -5,6 +5,20 @@ #include "posix.h" #include "posix_path.h" + +static SYS_PEB* gProcessPeb = NULL; + +static SYS_PEB* PosixPeb(void) { + if (!gProcessPeb) gProcessPeb = SysGetPeb(); + return gProcessPeb; +} + +__declspec(dllexport) const char* posix_get_command_line(void) { + SYS_PEB* peb = PosixPeb(); + if (!peb || peb->Magic != SYS_PEB_MAGIC || !peb->CommandLine) return ""; + return peb->CommandLine; +} + // pid -> proc handle table (user-mode side, for waitpid) #define PROC_TABLE_SIZE 32 typedef struct { diff --git a/code/posix/posix_thread.c b/code/posix/posix_thread.c index 61a4b1a..c2b8444 100644 --- a/code/posix/posix_thread.c +++ b/code/posix/posix_thread.c @@ -41,7 +41,6 @@ typedef struct { void* (*Fn)(void*); void* Arg; uint32_t Pid; // owner pid, for the retval table - uint32_t Tid; // filled in by pthread_create after KeCreateThread returns } PTHREAD_TRAMP; static void PthreadTrampoline(uintptr_t raw) { @@ -49,14 +48,19 @@ static void PthreadTrampoline(uintptr_t raw) { void* (*fn)(void*) = tr->Fn; void* arg = tr->Arg; uint32_t pid = tr->Pid; - uint32_t tid = tr->Tid; + uint32_t tid = SysGetCurrentTid(); + + // Create the join slot from inside the new thread. KeCreateThread makes the + // thread runnable before the creator regains control, so the old design + // raced on tr->Tid and could observe zero/stale memory. + PTHREAD_SLOT* slot = PthreadSlotFind(pid, tid); + if (!slot) slot = PthreadSlotAlloc(pid, tid); + SysFree(tr, sizeof(PTHREAD_TRAMP)); void* retval = fn(arg); - // Store retval for pthread_join. If the slot isn't there (join was called - // before the thread was even created, which shouldn't happen) just drop it. - PTHREAD_SLOT* slot = PthreadSlotFind(pid, tid); + // Store retval for pthread_join. if (slot) slot->RetVal = retval; SysExitThread(0); @@ -77,7 +81,6 @@ __declspec(dllexport) int pthread_create(pthread_t* t, const pthread_attr_t* att tr->Fn = fn; tr->Arg = arg; tr->Pid = pid; - tr->Tid = 0; // filled below uint32_t tid = 0; uint32_t s = SysCreateThread((uint64_t)PthreadTrampoline, @@ -86,12 +89,9 @@ __declspec(dllexport) int pthread_create(pthread_t* t, const pthread_attr_t* att SysFree(tr, sizeof(PTHREAD_TRAMP)); return EAGAIN; } - tr->Tid = tid; // safe: trampoline reads Tid after SysCreateThread returns - - // Reserve a retval slot before the trampoline can exit - PthreadSlotAlloc(pid, tid); - - t->Tid = tid; + // The trampoline allocates the retval slot itself after it knows its real + // TID, avoiding the creator/new-thread race. + if (t) t->Tid = tid; return 0; } diff --git a/code/shell_native/shell_native.c b/code/shell_native/shell_native.c index e5efbab..1154d5c 100644 --- a/code/shell_native/shell_native.c +++ b/code/shell_native/shell_native.c @@ -123,6 +123,27 @@ static void cmd_type(const char* filename) { SysCloseHandle(hFile); print("\n"); } + +static bool extractProgramPath(const char* cmdline, char* out, size_t outSize) { + if (!cmdline || !out || outSize == 0) return false; + + while (*cmdline == ' ' || *cmdline == '\t') cmdline++; + if (*cmdline == '\0') return false; + + size_t pos = 0; + if (*cmdline == '"') { + cmdline++; + while (*cmdline && *cmdline != '"' && pos + 1 < outSize) + out[pos++] = *cmdline++; + } else { + while (*cmdline && *cmdline != ' ' && *cmdline != '\t' && pos + 1 < outSize) + out[pos++] = *cmdline++; + } + + out[pos] = '\0'; + return pos != 0; +} + // --------------------------------------------------------------------------- // Run one command // --------------------------------------------------------------------------- @@ -134,13 +155,16 @@ static int runCommand(const char* cmd) { if (strcmp(cmd, "crash") == 0) { *(int*)0 = 42; return 0; } if (strcmp(cmd, "dir") == 0) { cmd_dir("."); return 0; } if (strncmp(cmd, "dir ", 4) == 0) { cmd_dir(cmd + 4); return 0; } - if (strncmp(cmd, "cd ", 3) == 0) { SysSetCwd(cmd + 3); return 0; } + if (strncmp(cmd, "cd ", 3) == 0) { if (SysSetCwd(cmd + 3) != 0) print("cd: failed\n"); return 0; } if (strncmp(cmd, "del ", 4) == 0) { SysDeleteFile(cmd + 4); return 0; } if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; } if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; } + char program[MAX_CMD]; + if (!extractProgramPath(cmd, program, sizeof(program))) return 0; + uint32_t hProc; - if (SysCreateProcess(cmd, &hProc, cmd) == 0) { + if (SysCreateProcess(program, &hProc, cmd) == 0) { SysWaitForProcess(hProc); SysCloseHandle(hProc); while (SysKbPoll()) (void)SysKbReadChar(); @@ -148,8 +172,13 @@ static int runCommand(const char* cmd) { return 0; } -int main(int argc, char** argv) { +int main(void) { + const char* cmdline = SysGetCommandLine(); char lineBuf[MAX_CMD]; + + print("shell: command line: "); + print(cmdline); + print("\n"); print("shell: ready\n"); while (true) { print("> "); diff --git a/code/shell_posix/shell_posix.c b/code/shell_posix/shell_posix.c index 15d31c8..b8be491 100644 --- a/code/shell_posix/shell_posix.c +++ b/code/shell_posix/shell_posix.c @@ -2,6 +2,7 @@ #include #include #include +#include #define MAX_CMD 256 #define NAME_MAX 259 @@ -21,7 +22,8 @@ extern int unlink(const char *pathname); extern int posix_spawn(int *pid, const char *path, const void *act, const void *attr, char *const argv[], char *const envp[]); extern int waitpid(int pid, int *status, int options); extern void exit(int status); -extern int ClearConsoleScreenBuffer(void* h); +extern void clearscreen(void); +extern void clearkeyboard(void); extern void PosixFdInit(void); static int hIn = 0; static int hOut = 1; @@ -52,6 +54,31 @@ static void readLine(char* buf, int len) { buf[pos] = '\0'; } + +static int splitCommandLine(char* line, char** argv, int maxArgv) { + int argc = 0; + char* p = line; + + while (*p && argc < maxArgv - 1) { + while (*p == ' ' || *p == '\t') p++; + if (!*p) break; + + if (*p == '"') { + p++; + argv[argc++] = p; + while (*p && *p != '"') p++; + if (*p) *p++ = '\0'; + } else { + argv[argc++] = p; + while (*p && *p != ' ' && *p != '\t') p++; + if (*p) *p++ = '\0'; + } + } + + argv[argc] = NULL; + return argc; +} + static char* trim(char* s) { while (*s == ' ') s++; int len = (int)strlen(s); @@ -111,25 +138,40 @@ static int runCommand(const char* cmd) { if (!cmd || *cmd == '\0') return 0; if (strcmp(cmd, "exit") == 0) return 1; if (strcmp(cmd, "help") == 0) { cmd_help(); return 0; } - if (strcmp(cmd, "clear") == 0) { ClearConsoleScreenBuffer(0); return 0; } + if (strcmp(cmd, "clear") == 0) { clearscreen(); return 0; } if (strcmp(cmd, "crash") == 0) { *(int*)0 = 42; return 0; } if (strcmp(cmd, "dir") == 0) { cmd_dir("."); return 0; } if (strncmp(cmd, "dir ", 4) == 0) { cmd_dir(cmd + 4); return 0; } - if (strncmp(cmd, "cd ", 3) == 0) { chdir(cmd + 3); return 0; } + if (strncmp(cmd, "cd ", 3) == 0) { if (chdir(cmd + 3) != 0) print("cd: failed\n"); return 0; } if (strncmp(cmd, "del ", 4) == 0) { unlink(cmd + 4); return 0; } if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; } if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; } - int pid; char *argv[] = {(char*)cmd, NULL}; - if (posix_spawn(&pid, cmd, NULL, NULL, argv, NULL) == 0) { + char cmdBuf[MAX_CMD]; + strncpy(cmdBuf, cmd, MAX_CMD - 1); + cmdBuf[MAX_CMD - 1] = '\0'; + + char* argv[32]; + int argc = splitCommandLine(cmdBuf, argv, 32); + if (argc == 0) return 0; + + int pid; + if (posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL) == 0) { int status; waitpid(pid, &status, 0); + clearkeyboard(); } else { print("shell: launch failed\n"); } return 0; } -int main() { +int main(void) { PosixFdInit(); + + const char* cmdline = SysGetCommandLine(); char lineBuf[MAX_CMD]; + + print("shell: command line: "); + print(cmdline); + print("\n"); print("shell: ready\n"); while (true) { print("> "); diff --git a/code/shell_win32/shell_win32.c b/code/shell_win32/shell_win32.c index b3ddc09..c930b9a 100644 --- a/code/shell_win32/shell_win32.c +++ b/code/shell_win32/shell_win32.c @@ -1,159 +1,168 @@ -#include -#include -#include -#include -#include - -#define MAX_CMD 256 -static HANDLE hIn = INVALID_HANDLE_VALUE; -static HANDLE hOut = INVALID_HANDLE_VALUE; - -// External/Custom API -BOOL ClearConsoleScreenBuffer(HANDLE hConsole); - -// --- Output Helpers --- -static void print(const char* s) { - DWORD written; - if (hOut != INVALID_HANDLE_VALUE) WriteConsoleA(hOut, s, (DWORD)strlen(s), &written, 0); - else OutputDebugStringA(s); -} - -static void printUInt(uint32_t n) { - char buf[12]; int i = 0; - if (n == 0) { print("0"); return; } - while (n > 0) { buf[i++] = (char)('0' + n % 10); n /= 10; } - while (i > 0) { char c[2] = { buf[--i], '\0' }; print(c); } -} - -static void printInt(int32_t n) { - if (n < 0) { print("-"); n = -n; } - printUInt((uint32_t)n); -} - -// --- Line Editor --- -static void readLine(char* buf, int len) { - int pos = 0; - while (true) { - char c; DWORD nRead; - ReadConsoleA(hIn, &c, 1, &nRead, 0); - if (!nRead) continue; - if (c == '\r' || c == '\n') { print("\n"); break; } - if ((c == '\b' || c == 127) && pos > 0) { pos--; print("\b \b"); continue; } - if (c < 0x20 || c == 127) continue; - if (pos < len - 1) { - buf[pos++] = c; - char echo[2] = { c, '\0' }; - print(echo); - } - } - buf[pos] = '\0'; -} - -static char* trim(char* s) { - while (*s == ' ') s++; - int len = (int)strlen(s); - while (len > 0 && s[len - 1] == ' ') len--; - s[len] = '\0'; - return s; -} - -// --- Built-in Commands --- -static void cmd_help() { - print("Available commands:\n" - " help - Show this list\n" - " dir [path] - List directory contents\n" - " cd - Change directory\n" - " type - Display file contents\n" - " del - Delete a file\n" - " write - Create a file with text\n" - " clear - Clear the screen\n" - " exit - Close the shell\n"); -} - -static void cmd_write(const char* args) { - char filename[MAX_PATH]; int i = 0; - while (args[i] != ' ' && args[i] != '\0' && i < MAX_PATH - 1) { - filename[i] = args[i]; i++; - } - filename[i] = '\0'; - if (args[i] == '\0' || args[i+1] == '\0') { - print("write: usage: write \n"); - return; - } - HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); - if (hFile == INVALID_HANDLE_VALUE) { print("write: could not create file\n"); return; } - DWORD written; - WriteFile(hFile, args + i + 1, (DWORD)strlen(args + i + 1), &written, 0); - CloseHandle(hFile); - print("File written successfully.\n"); -} - -static void cmd_dir(const char* path) { - WIN32_FIND_DATAA fd; - char search_path[MAX_PATH]; - const char* target = (path && *path != '\0') ? path : "."; - - size_t len = strlen(target); - if (len + 3 > MAX_PATH) { print("dir: path too long\n"); return; } - strcpy(search_path, target); - strcat(search_path, "\\*"); - - HANDLE hFind = FindFirstFileA(search_path, &fd); - if (hFind == INVALID_HANDLE_VALUE) { print("dir: failed to read directory\n"); return; } - do { - if (fd.cFileName[0] == '.') continue; // Skip . and .. - print(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? " " : " "); - print(fd.cFileName); print("\n"); - } while (FindNextFileA(hFind, &fd)); - FindClose(hFind); -} - -static void cmd_type(const char* filename) { - HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); - if (hFile == INVALID_HANDLE_VALUE) { print("type: cannot open file\n"); return; } - char buffer[512]; DWORD bytesRead; - while (ReadFile(hFile, buffer, sizeof(buffer) - 1, &bytesRead, 0) && bytesRead > 0) { - buffer[bytesRead] = '\0'; print(buffer); - } - CloseHandle(hFile); print("\n"); -} - -static int runCommand(const char* cmd) { - if (!cmd || *cmd == '\0') return 0; - if (strcmp(cmd, "exit") == 0) return 1; - if (strcmp(cmd, "help") == 0) { cmd_help(); return 0; } - if (strcmp(cmd, "clear") == 0) { ClearConsoleScreenBuffer(hOut); return 0; } - if (strcmp(cmd, "crash") == 0) { *(int*)0 = 42; return 0; } - if (strcmp(cmd, "dir") == 0) { cmd_dir("."); return 0; } - if (strncmp(cmd, "dir ", 4) == 0) { cmd_dir(cmd + 4); return 0; } - if (strncmp(cmd, "cd ", 3) == 0) { SetCurrentDirectoryA(cmd + 3); return 0; } - if (strncmp(cmd, "del ", 4) == 0) { DeleteFileA(cmd + 4); return 0; } - if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; } - if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; } - - PROCESS_INFORMATION pi; - char cmdBuf[MAX_CMD]; - strncpy(cmdBuf, cmd, MAX_CMD - 1); - cmdBuf[MAX_CMD - 1] = '\0'; - if (CreateProcessA(NULL, cmdBuf, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi)) { - WaitForSingleObject(pi.hProcess, INFINITE); - CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - } else { - print("shell: launch failed\n"); - } - return 0; -} - -int main() { - hIn = GetStdHandle(STD_INPUT_HANDLE); - hOut = GetStdHandle(STD_OUTPUT_HANDLE); - char lineBuf[MAX_CMD]; - print("shell: ready\n"); - while (true) { - print("> "); - readLine(lineBuf, MAX_CMD); - if (runCommand(trim(lineBuf))) break; - } - print("shell: goodbye\n"); - ExitProcess(0); -} +#include +#include +#include +#include +#include +#include + +#define MAX_CMD 256 +static HANDLE hIn = INVALID_HANDLE_VALUE; +static HANDLE hOut = INVALID_HANDLE_VALUE; + +// External/Custom API +BOOL ClearConsoleScreenBuffer(HANDLE hConsole); + +// --- Output Helpers --- +static void print(const char* s) { + DWORD written; + if (hOut != INVALID_HANDLE_VALUE) WriteConsoleA(hOut, s, (DWORD)strlen(s), &written, 0); + else OutputDebugStringA(s); +} + +static void printUInt(uint32_t n) { + char buf[12]; int i = 0; + if (n == 0) { print("0"); return; } + while (n > 0) { buf[i++] = (char)('0' + n % 10); n /= 10; } + while (i > 0) { char c[2] = { buf[--i], '\0' }; print(c); } +} + +static void printInt(int32_t n) { + if (n < 0) { print("-"); n = -n; } + printUInt((uint32_t)n); +} + +// --- Line Editor --- +static void readLine(char* buf, int len) { + int pos = 0; + while (true) { + char c; DWORD nRead; + ReadConsoleA(hIn, &c, 1, &nRead, 0); + if (!nRead) continue; + if (c == '\r' || c == '\n') { print("\n"); break; } + if ((c == '\b' || c == 127) && pos > 0) { pos--; print("\b \b"); continue; } + if (c < 0x20 || c == 127) continue; + if (pos < len - 1) { + buf[pos++] = c; + char echo[2] = { c, '\0' }; + print(echo); + } + } + buf[pos] = '\0'; +} + +static char* trim(char* s) { + while (*s == ' ') s++; + int len = (int)strlen(s); + while (len > 0 && s[len - 1] == ' ') len--; + s[len] = '\0'; + return s; +} + +// --- Built-in Commands --- +static void cmd_help() { + print("Available commands:\n" + " help - Show this list\n" + " dir [path] - List directory contents\n" + " cd - Change directory\n" + " type - Display file contents\n" + " del - Delete a file\n" + " write - Create a file with text\n" + " clear - Clear the screen\n" + " exit - Close the shell\n"); +} + +static void cmd_write(const char* args) { + char filename[MAX_PATH]; int i = 0; + while (args[i] != ' ' && args[i] != '\0' && i < MAX_PATH - 1) { + filename[i] = args[i]; i++; + } + filename[i] = '\0'; + if (args[i] == '\0' || args[i+1] == '\0') { + print("write: usage: write \n"); + return; + } + HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) { print("write: could not create file\n"); return; } + DWORD written; + WriteFile(hFile, args + i + 1, (DWORD)strlen(args + i + 1), &written, 0); + CloseHandle(hFile); + print("File written successfully.\n"); +} + +static void cmd_dir(const char* path) { + WIN32_FIND_DATAA fd; + char search_path[MAX_PATH]; + const char* target = (path && *path != '\0') ? path : "."; + + size_t len = strlen(target); + if (len + 3 > MAX_PATH) { print("dir: path too long\n"); return; } + strcpy(search_path, target); + strcat(search_path, "\\*"); + + HANDLE hFind = FindFirstFileA(search_path, &fd); + if (hFind == INVALID_HANDLE_VALUE) { print("dir: failed to read directory\n"); return; } + do { + if (fd.cFileName[0] == '.') continue; // Skip . and .. + print(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? " " : " "); + print(fd.cFileName); print("\n"); + } while (FindNextFileA(hFind, &fd)); + FindClose(hFind); +} + +static void cmd_type(const char* filename) { + HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) { print("type: cannot open file\n"); return; } + char buffer[512]; DWORD bytesRead; + while (ReadFile(hFile, buffer, sizeof(buffer) - 1, &bytesRead, 0) && bytesRead > 0) { + buffer[bytesRead] = '\0'; print(buffer); + } + CloseHandle(hFile); print("\n"); +} + +static int runCommand(const char* cmd) { + if (!cmd || *cmd == '\0') return 0; + if (strcmp(cmd, "exit") == 0) return 1; + if (strcmp(cmd, "help") == 0) { cmd_help(); return 0; } + if (strcmp(cmd, "clear") == 0) { ClearConsoleScreenBuffer(hOut); return 0; } + if (strcmp(cmd, "crash") == 0) { *(int*)0 = 42; return 0; } + if (strcmp(cmd, "dir") == 0) { cmd_dir("."); return 0; } + if (strncmp(cmd, "dir ", 4) == 0) { cmd_dir(cmd + 4); return 0; } + if (strncmp(cmd, "cd ", 3) == 0) { if (!SetCurrentDirectoryA(cmd + 3)) print("cd: failed\n"); return 0; } + if (strncmp(cmd, "del ", 4) == 0) { DeleteFileA(cmd + 4); return 0; } + if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; } + if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; } + + PROCESS_INFORMATION pi; + char cmdBuf[MAX_CMD]; + strncpy(cmdBuf, cmd, MAX_CMD - 1); + cmdBuf[MAX_CMD - 1] = '\0'; + if (CreateProcessA(NULL, cmdBuf, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi)) { + WaitForSingleObject(pi.hProcess, INFINITE); + if (pi.hThread && pi.hThread != INVALID_HANDLE_VALUE) + CloseHandle(pi.hThread); + FlushConsoleInputBuffer(hIn); + } else { + print("shell: launch failed\n"); + } + return 0; +} + +int main(void) { + hIn = GetStdHandle(STD_INPUT_HANDLE); + hOut = GetStdHandle(STD_OUTPUT_HANDLE); + + const char* cmdline = SysGetCommandLine(); + char lineBuf[MAX_CMD]; + + print("shell: command line: "); + print(cmdline); + print("\n"); + print("shell: ready\n"); + while (true) { + print("> "); + readLine(lineBuf, MAX_CMD); + if (runCommand(trim(lineBuf))) break; + } + print("shell: goodbye\n"); + ExitProcess(0); +} diff --git a/code/syscall/syscall.h b/code/syscall/syscall.h index dc8c85b..29e0353 100644 --- a/code/syscall/syscall.h +++ b/code/syscall/syscall.h @@ -4,6 +4,56 @@ #include #include #include +#include + + +// --------------------------------------------------------------------------- +// User process/thread environment +// --------------------------------------------------------------------------- +// GS base points at SYS_TEB. GS:[0] is the process PEB pointer. +// This is this OS' ABI, not a Windows compatibility layout. +#define SYS_PEB_MAGIC 0x30424550u /* "PEB0" */ +#define SYS_TEB_MAGIC 0x30424554u /* "TEB0" */ +#define SYS_ENV_VERSION 1u +#define SYS_CWD_MAX 512u + +typedef struct SYS_PEB { + uint32_t Magic; + uint32_t Version; + uint32_t ProcessId; + + const char* ImagePath; + size_t ImagePathLength; + + const char* CommandLine; + size_t CommandLineLength; + + const char* CurrentDirectory; + size_t CurrentDirectoryLength; +} SYS_PEB; + +typedef struct SYS_TEB { + SYS_PEB* Peb; + void* StackBase; + void* StackLimit; + struct SYS_TEB* Self; + uint32_t ThreadId; + uint32_t Magic; +} SYS_TEB; + +static inline SYS_PEB* SysGetPeb(void) { + return (SYS_PEB*)(uintptr_t)__readgsqword(0); +} + +static inline SYS_TEB* SysGetTeb(void) { + return (SYS_TEB*)(uintptr_t)__readgsqword(offsetof(SYS_TEB, Self)); +} + +static inline const char* SysGetCommandLine(void) { + SYS_PEB* peb = SysGetPeb(); + if (!peb || peb->Magic != SYS_PEB_MAGIC) return ""; + return peb->CommandLine ? peb->CommandLine : ""; +} // --------------------------------------------------------------------------- // Thread & Process Management (0-7) diff --git a/code/video/video.c b/code/video/video.c index 1ee2907..cf16403 100644 --- a/code/video/video.c +++ b/code/video/video.c @@ -5,16 +5,52 @@ #include #include "bink.h" -int main(const char* cmdline) { +static const char* commandLineSecondToken(const char* cmdline, char* out, size_t outSize) { + if (!out || outSize == 0) return "video.bk2"; + strcpy(out, "video.bk2"); + if (!cmdline) return out; + + while (*cmdline == ' ' || *cmdline == '\t') cmdline++; + if (*cmdline == '\0') return out; + + if (*cmdline == '"') { + cmdline++; + while (*cmdline && *cmdline != '"') cmdline++; + if (*cmdline == '"') cmdline++; + } else { + while (*cmdline && *cmdline != ' ' && *cmdline != '\t') cmdline++; + } + + while (*cmdline == ' ' || *cmdline == '\t') cmdline++; + if (*cmdline == '\0') return out; + + size_t pos = 0; + if (*cmdline == '"') { + cmdline++; + while (*cmdline && *cmdline != '"' && pos + 1 < outSize) + out[pos++] = *cmdline++; + } else { + while (*cmdline && *cmdline != ' ' && *cmdline != '\t' && pos + 1 < outSize) + out[pos++] = *cmdline++; + } + out[pos] = '\0'; + return out; +} + +int main(void) { + const char* cmdline = GetCommandLineA(); + char videoPath[260]; + const char* path = commandLineSecondToken(cmdline, videoPath, sizeof(videoPath)); + void* h = GetStdHandle(STD_OUTPUT_HANDLE); WriteConsoleA(h, "video: starting\n", 16, 0, 0); WriteConsoleA(h, cmdline, (unsigned int)strlen(cmdline), 0, 0); WriteConsoleA(h, "\n", 1, 0, 0); - void* f = CreateFileA("video.bk2", 0, 0, 0, 3, 0, 0); + void* f = CreateFileA(path, 0, 0, 0, 3, 0, 0); if (!f || f == (void*)(unsigned long long)0xFFFFFFFF) { // fuckass - WriteConsoleA(h, "video: failed to open video.bk2\n", 32, 0, 0); + WriteConsoleA(h, "video: failed to open input file\n", 33, 0, 0); ExitProcess(1); } @@ -34,7 +70,7 @@ int main(const char* cmdline) { WriteConsoleA(h, "video: ReadFile failed\n", 23, 0, 0); ExitProcess(1); } - WriteConsoleA(h, "video: video.bk2 loaded\n", 24, 0, 0); + WriteConsoleA(h, "video: input file loaded\n", 25, 0, 0); CloseHandle(h); diff --git a/code/win32/kernel32.c b/code/win32/kernel32.c index aad7567..2c6fa29 100644 --- a/code/win32/kernel32.c +++ b/code/win32/kernel32.c @@ -1,1577 +1,1723 @@ -// kernel32.c - Win32 personality layer -// -// Thin wrappers around syscall.dll stubs that expose a familiar Win32 API -// surface to user-mode code originally written for Windows. -// -// Design notes: -// - HANDLE is void*; we store the uint32_t kernel handle as a pointer value -// so Win32 code can pass it around opaquely without knowing its size. -// HANDLE_TO_KH / KH_TO_HANDLE do that conversion in one place. -// - INVALID_HANDLE_VALUE maps to kernel handle 0 (the always-invalid slot). -// - HeapAlloc / HeapFree / HeapReAlloc ignore the HANDLE heap argument and -// always use the single kernel user-heap. This is fine: we don't expose -// multiple heaps, and NULL is a legal (no-op) heap argument under Win32. -// - GetLastError / SetLastError are per-thread on Windows. We keep a single -// global for now; enough for Bink and similar single-threaded guests. -// - File I/O uses SYS_OPEN_FILE / SYS_READ_FILE / SYS_SEEK_FILE / -// SYS_GET_FILE_SIZE / SYS_CLOSE_HANDLE (unified close path - no separate -// SysCloseFile). -// - WriteFile / CreateFileA in write-mode / SetEndOfFile are stubs because -// the VFS has no write path yet. - -#include -#include -#include -#include -#include -#include - -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- -typedef void* HANDLE; -typedef unsigned long DWORD; -typedef int BOOL; -typedef unsigned char BYTE; -typedef short SHORT; -typedef long LONG; -typedef struct { LONG x; LONG y; } POINT; - -#define MAX_PATH 260 -#define TRUE 1 -#define FALSE 0 - -#define INVALID_HANDLE_VALUE ((HANDLE)(intptr_t)-1) - -// File-find structures -typedef struct _FILETIME { - DWORD dwLowDateTime; - DWORD dwHighDateTime; -} FILETIME; - -typedef struct _WIN32_FIND_DATAA { - DWORD dwFileAttributes; - FILETIME ftCreationTime; - FILETIME ftLastAccessTime; - FILETIME ftLastWriteTime; - DWORD nFileSizeHigh; - DWORD nFileSizeLow; - DWORD dwReserved0; - DWORD dwReserved1; - char cFileName[260]; // MAX_PATH - char cAlternateFileName[14]; -} WIN32_FIND_DATAA; - -// CreateFileA disposition / access flags (subset used by Bink and similar) -#define GENERIC_READ 0x80000000UL -#define GENERIC_WRITE 0x40000000UL -#define GENERIC_READ 0x80000000UL -#define GENERIC_WRITE 0x40000000UL -#define OPEN_EXISTING 3 -#define OPEN_ALWAYS 4 -#define CREATE_ALWAYS 2 -#define CREATE_NEW 1 -#define TRUNCATE_EXISTING 5 -#define FILE_BEGIN 0 -#define FILE_CURRENT 1 -#define FILE_END 2 - -// HeapAlloc flags -#define HEAP_ZERO_MEMORY 0x00000008UL - -// --------------------------------------------------------------------------- -// Handle conversion helpers -// uint32_t kernel handle <-> opaque Win32 HANDLE -// Kernel handle 0 == invalid; we map that to INVALID_HANDLE_VALUE (NULL). -// --------------------------------------------------------------------------- -static inline HANDLE KH_TO_HANDLE(uint32_t kh) { return (HANDLE)(uintptr_t)kh; } -static inline uint32_t HANDLE_TO_KH(HANDLE h) { return (uint32_t)(uintptr_t)h; } - -// Path translator -static void TranslatePath(const char* input, char* output) { - if (input && input[0] != '\0' && input[1] == ':' && (input[2] == '\\' || input[2] == '/')) { - // Translate "C:\" -> "0:/" (assuming 'C' maps to '0', etc.) - // Map drive letter to index: 'C' or 'c' -> '0' - char drive = input[0]; - if (drive >= 'a') drive -= ('a' - 'A'); // To uppercase - - output[0] = (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'; - } -} - -// --------------------------------------------------------------------------- -// GetLastError / SetLastError (single-threaded stub) -// --------------------------------------------------------------------------- -static DWORD gLastError = 0; - -__declspec(dllexport) DWORD GetLastError(void) { - return gLastError; -} -__declspec(dllexport) void SetLastError(DWORD err) { - gLastError = err; -} - -// --------------------------------------------------------------------------- -// Process termination -// --------------------------------------------------------------------------- -__declspec(dllexport) _Noreturn void ExitProcess(unsigned int code) { - SysExitProcess((int)code); - while (true) ; // unreachable -} - -// --------------------------------------------------------------------------- -// Debug output -// --------------------------------------------------------------------------- -__declspec(dllexport) void OutputDebugStringA(const char* str) { - if (str) SysDebugPrint(str); -} - -// --------------------------------------------------------------------------- -// Timer -// --------------------------------------------------------------------------- -__declspec(dllexport) DWORD GetTickCount(void) { - return (DWORD)(SysGetTick() / 1000); -} - -__declspec(dllexport) uint64_t GetTickCount64(void) { - return SysGetTick() / 1000; -} - -// --------------------------------------------------------------------------- -// Memory (heap argument is ignored - single kernel user-heap) -// --------------------------------------------------------------------------- -__declspec(dllexport) void* HeapAlloc(void* heap, DWORD flags, size_t size) { - (void)heap; - if (!size) return NULL; - void* ptr = SysAlloc(size); - if (ptr && (flags & HEAP_ZERO_MEMORY)) - memset(ptr, 0, size); - if (!ptr) gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY - return ptr; -} - -__declspec(dllexport) BOOL HeapFree(void* heap, DWORD flags, void* ptr) { - (void)heap; (void)flags; - // Win32's HeapFree doesn't need the size; we pass 0 and the kernel's - // allocator handles it (the block header carries the size). - if (ptr) SysFree(ptr, 0); - return TRUE; -} - -__declspec(dllexport) void* HeapReAlloc(void* heap, DWORD flags, - void* ptr, size_t size) { - (void)heap; (void)flags; - void* newptr = SysRealloc(ptr, size); - if (!newptr && size) gLastError = 8; - return newptr; -} - -// GetProcessHeap - return a sentinel non-NULL value so callers can pass it -// back into HeapAlloc/HeapFree without crashing (we ignore it anyway). -__declspec(dllexport) void* GetProcessHeap(void) { - return (void*)1; -} - -// --- Win32 API --- - -// Updated GetAsyncKeyState implementation -__declspec(dllexport) SHORT GetAsyncKeyState(int vKey) { - static const struct { int vk; uint8_t sc; bool ext; } vk_to_sc[] = { - // Letters - { 0x41, 0x1E, false }, { 0x42, 0x30, false }, { 0x43, 0x2E, false }, - { 0x44, 0x20, false }, { 0x45, 0x12, false }, { 0x46, 0x21, false }, - { 0x47, 0x22, false }, { 0x48, 0x23, false }, { 0x49, 0x17, false }, - { 0x4A, 0x24, false }, { 0x4B, 0x25, false }, { 0x4C, 0x26, false }, - { 0x4D, 0x32, false }, { 0x4E, 0x31, false }, { 0x4F, 0x18, false }, - { 0x50, 0x19, false }, { 0x51, 0x10, false }, { 0x52, 0x13, false }, - { 0x53, 0x1F, false }, { 0x54, 0x14, false }, { 0x55, 0x16, false }, - { 0x56, 0x2F, false }, { 0x57, 0x11, false }, { 0x58, 0x2D, false }, - { 0x59, 0x15, false }, { 0x5A, 0x2C, false }, - - // Numbers - { 0x31, 0x02, false }, { 0x32, 0x03, false }, { 0x33, 0x04, false }, - { 0x34, 0x05, false }, { 0x35, 0x06, false }, { 0x36, 0x07, false }, - { 0x37, 0x08, false }, { 0x38, 0x09, false }, { 0x39, 0x0A, false }, - { 0x30, 0x0B, false }, - - // Arrows (Extended) - { 0x26, 0x48, true }, // VK_UP - { 0x28, 0x50, true }, // VK_DOWN - { 0x25, 0x4B, true }, // VK_LEFT - { 0x27, 0x4D, true }, // VK_RIGHT - - // Controls - { 0x1B, 0x01, false }, // ESC - { 0x0D, 0x1C, false }, // ENTER - { 0x20, 0x39, false }, // SPACE - { 0x11, 0x1D, false }, // CTRL (Generic) - { 0x10, 0x2A, false }, // SHIFT (Generic) - { 0x12, 0x38, false }, // ALT (Generic) - { 0x08, 0x0E, false }, // BACKSPACE - { 0x09, 0x0F, false }, // TAB - - // Function Keys - { 0x70, 0x3B, false }, { 0x71, 0x3C, false }, { 0x72, 0x3D, false }, - { 0x73, 0x3E, false }, { 0x74, 0x3F, false }, { 0x75, 0x40, false }, - { 0x76, 0x41, false }, { 0x77, 0x42, false }, { 0x78, 0x43, false }, - { 0x79, 0x44, false }, { 0x7A, 0x57, false }, { 0x7B, 0x58, false }, - - // Symbols - { 0xBD, 0x0C, false }, // MINUS '-' - { 0xBB, 0x0D, false }, // EQUALS '=' - { 0xBC, 0x33, false }, // COMMA ',' - { 0xBE, 0x34, false }, // PERIOD '.' - }; - - for (size_t i = 0; i < sizeof(vk_to_sc)/sizeof(vk_to_sc[0]); ++i) { - if (vk_to_sc[i].vk == vKey) { - // Check the hardware state array via our HAL helper - if (SysKbIsKeyDown(vk_to_sc[i].sc, vk_to_sc[i].ext)) { - 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 - while (SysKbPoll()) { - (void)SysKbReadChar(); - } - return TRUE; -} - -// !Made-up! clear console api. -__declspec(dllexport) BOOL ClearConsoleScreenBuffer(HANDLE hConsole) { - SysScreenClear(0x00000000); - 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; -} - -__declspec(dllexport) DWORD GetACP(void) { return 1252; } // Windows-1252 -__declspec(dllexport) DWORD GetOEMCP(void) { return 437; } // DOS Latin US - -// --------------------------------------------------------------------------- -// File I/O -// --------------------------------------------------------------------------- - -// CreateFileA - opens or creates a file. -// Supported modes: -// OPEN_EXISTING + GENERIC_READ -> open existing, read-only -// OPEN_EXISTING + GENERIC_WRITE -> open existing, read-write -// OPEN_EXISTING + GENERIC_READ|GENERIC_WRITE -> open existing, read-write -// CREATE_ALWAYS + GENERIC_WRITE -> create/truncate, write -// OPEN_ALWAYS + GENERIC_WRITE -> create if absent, write -// TRUNCATE_EXISTING + GENERIC_WRITE -> open & truncate to zero -__declspec(dllexport) HANDLE CreateFileA(const char* name, - DWORD access, DWORD share, - void* sa, DWORD disposition, - DWORD flags, void* templ) { - (void)share; (void)sa; (void)flags; (void)templ; - - // Translate the path from Windows format to custom 0:/ format - char translatedName[260]; - TranslatePath(name, translatedName); - - int wantWrite = (access & GENERIC_WRITE) != 0; - - if (disposition == OPEN_EXISTING || disposition == TRUNCATE_EXISTING) { - // Open the file via the read path (it must already exist). - uint32_t kh = 0; - uint32_t s = SysOpenFile(translatedName, &kh); - if (s != 0) { gLastError = 2; return INVALID_HANDLE_VALUE; } - - if (disposition == TRUNCATE_EXISTING && wantWrite) { - // Immediately truncate to zero. - uint32_t ts = SysTruncateFile(kh, 0); - if (ts != 0) { - SysCloseHandle(kh); - gLastError = 5; - return INVALID_HANDLE_VALUE; - } - } - return KH_TO_HANDLE(kh); - } - - if (disposition == CREATE_ALWAYS && wantWrite) { - // Create-or-replace: try to create; if the file already exists, - // open it and truncate it to zero instead. - uint32_t kh = 0; - uint32_t s = SysCreateFile(translatedName, &kh); - if (s != 0) { - // File may already exist - open and truncate. - s = SysOpenFile(translatedName, &kh); - if (s != 0) { gLastError = 5; return INVALID_HANDLE_VALUE; } - SysTruncateFile(kh, 0); - } - return KH_TO_HANDLE(kh); - } - - if (disposition == OPEN_ALWAYS && wantWrite) { - // Open if exists, create if not. - uint32_t kh = 0; - if (SysOpenFile(translatedName, &kh) != 0) { - if (SysCreateFile(translatedName, &kh) != 0) { - gLastError = 5; - return INVALID_HANDLE_VALUE; - } - } - return KH_TO_HANDLE(kh); - } - - if (disposition == CREATE_NEW && wantWrite) { - // Fail if the file already exists (we can't check atomically, - // but SysCreateFile returns NAME_COLLISION for existing files). - uint32_t kh = 0; - uint32_t s = SysCreateFile(translatedName, &kh); - if (s != 0) { gLastError = 80; return INVALID_HANDLE_VALUE; } // ERROR_FILE_EXISTS - return KH_TO_HANDLE(kh); - } - - // Any other combination (e.g. read-only CREATE_ALWAYS) is not supported. - gLastError = 87; // ERROR_INVALID_PARAMETER - return INVALID_HANDLE_VALUE; -} - -// GetFileSize - fills optional high-DWORD pointer, returns low DWORD. -// Returns 0xFFFFFFFF on error, matching Win32 behaviour. -__declspec(dllexport) DWORD GetFileSize(HANDLE hFile, DWORD* fileSizeHigh) { - if (!hFile || hFile == INVALID_HANDLE_VALUE) { gLastError = 6; return 0xFFFFFFFFUL; } - uint64_t sz = SysGetFileSize(HANDLE_TO_KH(hFile)); - if (fileSizeHigh) *fileSizeHigh = (DWORD)(sz >> 32); - return (DWORD)(sz & 0xFFFFFFFFULL); -} - -// ReadFile - sequential read at the file's current position. -__declspec(dllexport) BOOL ReadFile(HANDLE hFile, void* buf, DWORD toRead, - DWORD* bytesRead, void* overlapped) { - (void)overlapped; - if (!hFile || hFile == INVALID_HANDLE_VALUE || !buf) { gLastError = 6; return FALSE; } - size_t got = 0; - uint32_t s = SysReadFile(HANDLE_TO_KH(hFile), buf, (size_t)toRead, &got); - if (bytesRead) *bytesRead = (DWORD)got; - if (s != 0) { gLastError = 1; return FALSE; } - return TRUE; -} - -// WriteFile - sequential write at the file's current position. -// Extends the file if writing past EOF. Overlapped I/O is not supported. -__declspec(dllexport) BOOL WriteFile(HANDLE hFile, const void* buf, DWORD toWrite, - DWORD* bytesWritten, void* overlapped) { - (void)overlapped; - if (!hFile || !buf) { gLastError = 6; return FALSE; } - size_t written = 0; - uint32_t s = SysWriteFile(HANDLE_TO_KH(hFile), buf, (size_t)toWrite, &written); - if (bytesWritten) *bytesWritten = (DWORD)written; - if (s != 0) { gLastError = 29; return FALSE; } // ERROR_WRITE_FAULT - return TRUE; -} - -// SetFilePointer - seek to an absolute, relative, or end-relative position. -// Returns new position low DWORD, or 0xFFFFFFFF on error. -__declspec(dllexport) DWORD SetFilePointer(HANDLE hFile, long dist, - long* distHigh, DWORD method) { - if (!hFile || hFile == INVALID_HANDLE_VALUE) { gLastError = 6; return 0xFFFFFFFFUL; } - - uint32_t kh = HANDLE_TO_KH(hFile); - uint64_t offset = 0; - int64_t delta = distHigh - ? ((int64_t)(uint64_t)(uint32_t)*distHigh << 32) | (uint32_t)dist - : (int64_t)dist; - - if (method == FILE_BEGIN) { - offset = (uint64_t)delta; - } else if (method == FILE_CURRENT) { - uint64_t cur = SysGetFilePos(kh); - if (cur == (uint64_t)-1) { gLastError = 6; return 0xFFFFFFFFUL; } - offset = (uint64_t)((int64_t)cur + delta); - } else if (method == FILE_END) { - uint64_t sz = SysGetFileSize(kh); - offset = (uint64_t)((int64_t)sz + delta); - } else { - gLastError = 87; return 0xFFFFFFFFUL; - } - - uint32_t s = SysSeekFile(kh, offset); - if (s != 0) { gLastError = 1; return 0xFFFFFFFFUL; } - if (distHigh) *distHigh = (long)(int32_t)(offset >> 32); - return (DWORD)(offset & 0xFFFFFFFFULL); -} - -// SetEndOfFile - truncate or extend the file to the current seek position. -__declspec(dllexport) BOOL SetEndOfFile(HANDLE hFile) { - if (!hFile || hFile == INVALID_HANDLE_VALUE) { gLastError = 6; return FALSE; } - uint32_t kh = HANDLE_TO_KH(hFile); - uint64_t pos = SysGetFilePos(kh); - if (pos == (uint64_t)-1) { gLastError = 6; return FALSE; } - uint32_t s = SysTruncateFile(kh, pos); - if (s != 0) { gLastError = 1; return FALSE; } - return TRUE; -} - -// DeleteFileA - delete a file by name; no open handle required. -__declspec(dllexport) BOOL DeleteFileA(const char* lpFileName) { - if (!lpFileName) { gLastError = 87; return FALSE; } - - char translatedPath[MAX_PATH]; - TranslatePath(lpFileName, translatedPath); - - uint32_t s = SysDeleteFile(translatedPath); - if (s != 0) { gLastError = 2; return FALSE; } - return TRUE; -} - -// CloseHandle - unified close for files, mutexes, semaphores, events, queues. -__declspec(dllexport) BOOL CloseHandle(HANDLE hObject) { - if (!hObject) { gLastError = 6; return FALSE; } - SysCloseHandle(HANDLE_TO_KH(hObject)); - return TRUE; -} - -// =========================================================================== -// Extended shims - funcs.txt additions -// All functions here return plausible values; no real implementation exists -// unless a matching syscall stub is available. -// =========================================================================== - -// --------------------------------------------------------------------------- -// Additional Heap APIs -// We have one kernel heap; the HANDLE is always ignored. -// --------------------------------------------------------------------------- - -typedef int HEAP_INFORMATION_CLASS; // opaque - -__declspec(dllexport) HANDLE HeapCreate(DWORD flOptions, - size_t dwInitialSize, - size_t dwMaximumSize) { - (void)flOptions; (void)dwInitialSize; (void)dwMaximumSize; - return (HANDLE)(uintptr_t)1; // non-NULL sentinel -} - -__declspec(dllexport) BOOL HeapDestroy(HANDLE hHeap) { - (void)hHeap; - return TRUE; -} - -__declspec(dllexport) size_t HeapSize(HANDLE heap, DWORD flags, const void* ptr) { - (void)heap; (void)flags; (void)ptr; - return (size_t)-1; // Win32 returns (SIZE_T)-1 on error / unsupported -} - -__declspec(dllexport) BOOL HeapSetInformation(HANDLE HeapHandle, - HEAP_INFORMATION_CLASS cls, - void* info, size_t infoLen) { - (void)HeapHandle; (void)cls; (void)info; (void)infoLen; - return TRUE; -} - -// --------------------------------------------------------------------------- -// Process & Module pseudo-handles / queries -// --------------------------------------------------------------------------- - -// Pseudo-handles matching Windows convention: -1 = process, -2 = thread. -__declspec(dllexport) HANDLE GetCurrentProcess(void) { - return (HANDLE)(uintptr_t)(~(uintptr_t)0); -} - -__declspec(dllexport) HANDLE GetCurrentThread(void) { - return (HANDLE)(uintptr_t)(~(uintptr_t)1); -} - -__declspec(dllexport) DWORD GetCurrentThreadId(void) { return (DWORD)SysGetCurrentTid(); } -__declspec(dllexport) DWORD GetCurrentProcessId(void) { return (DWORD)SysGetCurrentPid(); } - -// GetModuleHandleA/W - no PE map; return NULL so callers that check can fail -// gracefully. GetModuleHandleA(NULL) on Windows returns the exe base; we -// return a recognisable non-NULL sentinel so code that just passes it to -// GetProcAddress doesn't immediately crash. -__declspec(dllexport) HANDLE GetModuleHandleA(const char* lpModuleName) { - return lpModuleName ? NULL : (HANDLE)(uintptr_t)0x00400000UL; -} - -__declspec(dllexport) HANDLE GetModuleHandleW(const unsigned short* lpModuleName) { - return lpModuleName ? NULL : (HANDLE)(uintptr_t)0x00400000UL; -} - -// GetProcAddress - no dynamic linker; always fail. -__declspec(dllexport) void* GetProcAddress(HANDLE hModule, const char* lpProcName) { - (void)hModule; (void)lpProcName; - gLastError = 127; // ERROR_PROC_NOT_FOUND - return NULL; -} - -// LoadLibraryA - no dynamic loader; always fail. -__declspec(dllexport) HANDLE LoadLibraryA(const char* lpLibFileName) { - (void)lpLibFileName; - gLastError = 2; // ERROR_FILE_NOT_FOUND - return NULL; -} - -__declspec(dllexport) BOOL FreeLibrary(HANDLE hLibModule) { - (void)hLibModule; - return TRUE; -} - -__declspec(dllexport) DWORD GetModuleFileNameA(HANDLE hModule, - char* lpFilename, DWORD nSize) { - (void)hModule; - if (lpFilename && nSize) lpFilename[0] = '\0'; - return 0; -} - -__declspec(dllexport) BOOL TerminateProcess(HANDLE hProcess, unsigned int uExitCode) { - (void)hProcess; - SysExitProcess((int)uExitCode); - while (true) ; -} - -// STARTUPINFOA - only cb matters for our stub. -typedef struct { - DWORD cb; - char* lpReserved; - char* lpDesktop; - char* lpTitle; - DWORD dwX, dwY, dwXSize, dwYSize; - DWORD dwXCountChars, dwYCountChars; - DWORD dwFillAttribute, dwFlags; - unsigned short wShowWindow, cbReserved2; - unsigned char* lpReserved2; - HANDLE hStdInput, hStdOutput, hStdError; -} STARTUPINFOA; - -__declspec(dllexport) void GetStartupInfoA(STARTUPINFOA* lpStartupInfo) { - if (!lpStartupInfo) return; - // Zero every field, then set the mandatory cb. - unsigned char* p = (unsigned char*)lpStartupInfo; - for (size_t i = 0; i < sizeof(*lpStartupInfo); i++) p[i] = 0; - lpStartupInfo->cb = (DWORD)sizeof(*lpStartupInfo); -} - -__declspec(dllexport) char* GetCommandLineA(void) { - // Return a valid but empty-ish command line. - static char cmdline[4] = ""; - return cmdline; -} - -// --------------------------------------------------------------------------- -// Threading -// --------------------------------------------------------------------------- - -#define WAIT_OBJECT_0 0x00000000UL -#define WAIT_TIMEOUT 0x00000102UL -#define INFINITE 0xFFFFFFFFUL - -__declspec(dllexport) HANDLE CreateThread(void* lpThreadAttributes, - size_t dwStackSize, - void* lpStartAddress, - void* lpParameter, - DWORD dwCreationFlags, - DWORD* lpThreadId) { - (void)lpThreadAttributes; (void)dwStackSize; (void)dwCreationFlags; - uint32_t tid = 0; - uint32_t s = SysCreateThread((uint64_t)(uintptr_t)lpStartAddress, - (uint64_t)(uintptr_t)lpParameter, - 0 /* normal priority */, &tid); - if (s != 0) { gLastError = 11; return INVALID_HANDLE_VALUE; } - if (lpThreadId) *lpThreadId = (DWORD)tid; - return KH_TO_HANDLE(tid); -} - -// WaitForSingleObject - blocks on process handles via SysWaitForProcess; -// thread and other handle types are not yet waitable so return immediately. -__declspec(dllexport) DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) { - (void)dwMilliseconds; - uint32_t kh = HANDLE_TO_KH(hHandle); - if (kh) SysWaitForProcess(kh); // no-op KSTATUS if not a process handle - return WAIT_OBJECT_0; -} - -// --------------------------------------------------------------------------- -// Process creation -// --------------------------------------------------------------------------- - -typedef struct { - HANDLE hProcess; - HANDLE hThread; - DWORD dwProcessId; - DWORD dwThreadId; -} PROCESS_INFORMATION; - -#define STILL_ACTIVE 259UL - -__declspec(dllexport) BOOL CreateProcessA( - const char* lpApplicationName, - char* lpCommandLine, - void* lpProcessAttributes, - void* lpThreadAttributes, - BOOL bInheritHandles, - DWORD dwCreationFlags, - void* lpEnvironment, - const char* lpCurrentDirectory, - STARTUPINFOA* lpStartupInfo, - PROCESS_INFORMATION* lpProcessInformation) { - (void)lpCommandLine; (void)lpProcessAttributes; (void)lpThreadAttributes; - (void)bInheritHandles; (void)dwCreationFlags; (void)lpEnvironment; - (void)lpCurrentDirectory; (void)lpStartupInfo; - - // lpProcessInformation is mandatory - if (!lpProcessInformation) { - gLastError = 87; // ERROR_INVALID_PARAMETER - return FALSE; - } - - // Standard Win32 behavior: use lpCommandLine if lpApplicationName is NULL - const char* targetPath = lpApplicationName; - if (!targetPath) { - targetPath = lpCommandLine; - } - - // If both are NULL, then it's truly an invalid parameter - if (!targetPath) { - gLastError = 87; - return FALSE; - } - - // Build the cmdline: prefer lpCommandLine if given, otherwise fall back to path - const char* cmdline = lpCommandLine ? lpCommandLine : targetPath; - - char translatedPath[MAX_PATH]; - TranslatePath(targetPath, translatedPath); - - uint32_t kh = 0; - // Call the syscall.dll stub - uint32_t s = SysCreateProcess(translatedPath, &kh, cmdline); - if (s != 0 || kh == 0) { - gLastError = 2; // ERROR_FILE_NOT_FOUND - return FALSE; - } - - lpProcessInformation->hProcess = KH_TO_HANDLE(kh); - lpProcessInformation->hThread = INVALID_HANDLE_VALUE; - lpProcessInformation->dwProcessId = 0; - lpProcessInformation->dwThreadId = 0; - return TRUE; -} - -__declspec(dllexport) BOOL GetExitCodeProcess(HANDLE hProcess, DWORD* lpExitCode) { - if (!lpExitCode) { gLastError = 87; return FALSE; } - uint32_t kh = HANDLE_TO_KH(hProcess); - if (!kh) { gLastError = 6; return FALSE; } // ERROR_INVALID_HANDLE - int code = 0; - uint32_t s = SysGetProcessExitCode(kh, &code); - if (s != 0) { gLastError = 6; return FALSE; } - *lpExitCode = (DWORD)code; - return TRUE; -} - -__declspec(dllexport) DWORD ResumeThread(HANDLE hThread) { - (void)hThread; - return 0; // previous suspend count -} - -#define THREAD_PRIORITY_NORMAL 0 - -__declspec(dllexport) BOOL SetThreadPriority(HANDLE hThread, int nPriority) { - (void)hThread; (void)nPriority; - return TRUE; -} - -__declspec(dllexport) int GetThreadPriority(HANDLE hThread) { - (void)hThread; - return THREAD_PRIORITY_NORMAL; -} - -__declspec(dllexport) uintptr_t SetThreadAffinityMask(HANDLE hThread, - uintptr_t dwThreadAffinityMask) { - (void)hThread; (void)dwThreadAffinityMask; - return 1; // previous mask: CPU 0 only -} - -__declspec(dllexport) void Sleep(DWORD dwMilliseconds) { - SysSleep(dwMilliseconds); -} - -// --------------------------------------------------------------------------- -// Console I/O -// --------------------------------------------------------------------------- - -#define STD_INPUT_HANDLE ((DWORD)-10) -#define STD_OUTPUT_HANDLE ((DWORD)-11) -#define STD_ERROR_HANDLE ((DWORD)-12) - -// We have no real console handles; return a sentinel the other APIs recognise. -__declspec(dllexport) HANDLE GetStdHandle(DWORD nStdHandle) { - return (HANDLE)(uintptr_t)nStdHandle; -} - -// ReadConsoleA: blocking read of up to nNumberOfCharsToRead characters. -// Each character is read individually via SysKbReadChar so the caller can -// do its own line-editing (echo, backspace, etc.) one char at a time. -__declspec(dllexport) BOOL ReadConsoleA(HANDLE hConsoleInput, - void* lpBuffer, - DWORD nNumberOfCharsToRead, - DWORD* lpNumberOfCharsRead, - void* pInputControl) { - (void)hConsoleInput; (void)pInputControl; - if (!lpBuffer || nNumberOfCharsToRead == 0) return FALSE; - - char* buf = (char*)lpBuffer; - DWORD i = 0; // Notice we switched to a while loop - - while (i < nNumberOfCharsToRead) { - char c = SysKbReadChar(); - - // Ignore unmapped keys (Shift, Ctrl, etc.) - if (c == 0) continue; - - // Deliver the raw character; the caller handles echo and line editing. - buf[i] = c; - i++; - - // Stop reading when the user presses Enter - if (c == '\n' || c == '\r') { - // If your shell expects strings to be \n terminated, leave this. - // If your shell expects \0 termination without a newline, - // you might want to replace buf[i-1] with \0 here! - break; - } - } - - if (lpNumberOfCharsRead) *lpNumberOfCharsRead = i; - return TRUE; -} - -// WriteConsoleA: write nNumberOfCharsToWrite characters from lpBuffer to the -// console as white-on-black text via SYS_WRITE_CONSOLE. -__declspec(dllexport) BOOL WriteConsoleA(HANDLE hConsoleOutput, - const void* lpBuffer, - DWORD nNumberOfCharsToWrite, - DWORD* lpNumberOfCharsWritten, - void* lpReserved) { - (void)hConsoleOutput; (void)lpReserved; - if (!lpBuffer || nNumberOfCharsToWrite == 0) { - if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = 0; - return TRUE; - } - SysWriteConsole((const char*)lpBuffer, (size_t)nNumberOfCharsToWrite); - if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = nNumberOfCharsToWrite; - return TRUE; -} - -// --------------------------------------------------------------------------- -// Directory enumeration (FindFirstFileA / FindNextFileA / FindClose) -// --------------------------------------------------------------------------- - -// KDIR_ENTRY layout must exactly match the definition in io_file.h. -#define KDIR_NAME_MAX 260 - -typedef struct { - char Name[KDIR_NAME_MAX]; - uint8_t Attr; - uint32_t FileSize; -} KDIR_ENTRY; - -// FIND_HANDLE is a user-side wrapper: it stores the kernel dir handle and -// the wildcard pattern so FindNextFileA can filter subsequent entries. -// We return a pointer to this struct cast to HANDLE; it is NOT a kernel -// handle, so FindClose must NOT go through CloseHandle/SysCloseHandle. -typedef struct { - uint32_t kh; // kernel DIR_OBJECT handle (raw pointer) - char pattern[MAX_PATH]; // wildcard pattern, e.g. "*" or "*.txt" -} FIND_HANDLE; - -// Minimal wildcard match: supports '*' (any sequence) and '?' (any char). -// Case-insensitive to match FAT32/Windows behaviour. -static int WildMatch(const char* pat, const char* name) { - while (*pat && *name) { - if (*pat == '*') { - // Skip consecutive stars - while (*pat == '*') pat++; - if (!*pat) return 1; // trailing * matches everything - // Try matching the rest of the pattern at every position - while (*name) { - if (WildMatch(pat, name)) return 1; - name++; - } - return 0; - } - char p = (*pat >= 'A' && *pat <= 'Z') ? *pat + 32 : *pat; - char n = (*name >= 'A' && *name <= 'Z') ? *name + 32 : *name; - if (*pat != '?' && p != n) return 0; - pat++; name++; - } - // Consume trailing stars in the pattern - while (*pat == '*') pat++; - return (*pat == '\0' && *name == '\0'); -} - -// Split a FindFirstFileA-style search path into directory and pattern parts. -// "apps\\*" -> dir="apps" pat="*" -// "*.txt" -> dir="" pat="*.txt" -// "*" -> dir="" pat="*" -// "apps\\" -> dir="apps" pat="*" -static void SplitFindPath(const char* searchPath, - char* dirOut, size_t dirSize, - char* patOut, size_t patSize) { - // Find last backslash or forward-slash - const char* last = NULL; - for (const char* p = searchPath; *p; p++) - if (*p == '\\' || *p == '/') last = p; - - if (!last) { - // No separator: whole string is the pattern, dir is CWD - if (dirOut && dirSize) dirOut[0] = '\0'; - if (patOut && patSize) { - size_t len = strlen(searchPath); - if (len + 1 > patSize) len = patSize - 1; - memcpy(patOut, searchPath, len); - patOut[len] = '\0'; - } - } else { - // Everything before the last separator is the directory - if (dirOut) { - size_t len = (size_t)(last - searchPath); - if (len + 1 > dirSize) len = dirSize - 1; - memcpy(dirOut, searchPath, len); - dirOut[len] = '\0'; - } - // Everything after the last separator is the pattern - if (patOut) { - const char* p = last + 1; - size_t len = strlen(p); - if (len + 1 > patSize) len = patSize - 1; - memcpy(patOut, p, len); - patOut[len] = '\0'; - } - } - - // Empty pattern defaults to "*" - if (patOut && patOut[0] == '\0') { - patOut[0] = '*'; patOut[1] = '\0'; - } -} - -// Fill a WIN32_FIND_DATAA from a KDIR_ENTRY. -#define FILE_ATTRIBUTE_DIRECTORY 0x00000010UL -#define FILE_ATTRIBUTE_ARCHIVE 0x00000020UL -#define FILE_ATTRIBUTE_NORMAL 0x00000080UL - -static void KdirToFindData(const KDIR_ENTRY* ke, WIN32_FIND_DATAA* fd) { - // Zero the entire struct (covers all FILETIME and reserved fields) - unsigned char* p = (unsigned char*)fd; - for (size_t i = 0; i < sizeof(*fd); i++) p[i] = 0; - - fd->dwFileAttributes = ke->Attr; - if (!(fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && - !(fd->dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)) - fd->dwFileAttributes |= FILE_ATTRIBUTE_NORMAL; - - fd->nFileSizeLow = ke->FileSize; - fd->nFileSizeHigh = 0; - - size_t nameLen = strlen(ke->Name); - if (nameLen >= MAX_PATH) nameLen = MAX_PATH - 1; - memcpy(fd->cFileName, ke->Name, nameLen); - fd->cFileName[nameLen] = '\0'; -} - -__declspec(dllexport) HANDLE FindFirstFileA(const char* lpFileName, - WIN32_FIND_DATAA* lpFindFileData) { - if (!lpFileName || !lpFindFileData) { - gLastError = 87; // ERROR_INVALID_PARAMETER - return INVALID_HANDLE_VALUE; - } - - // 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 = SysOpenDir(dir[0] ? dir : NULL, &kh); - if (s != 0 || kh == 0) { - gLastError = 2; // ERROR_FILE_NOT_FOUND - return INVALID_HANDLE_VALUE; - } - - // Allocate our user-side FIND_HANDLE wrapper - FIND_HANDLE* fh = (FIND_HANDLE*)HeapAlloc(NULL, 0, sizeof(FIND_HANDLE)); - if (!fh) { - SysCloseHandle(kh); - gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY - return INVALID_HANDLE_VALUE; - } - fh->kh = kh; - size_t patLen = strlen(pat); - if (patLen >= MAX_PATH) patLen = MAX_PATH - 1; - memcpy(fh->pattern, pat, patLen); - fh->pattern[patLen] = '\0'; - - // Read the first entry that matches the pattern - KDIR_ENTRY ke; - for (;;) { - uint32_t rs = SysReadDir(kh, &ke); - if (rs != 0) { - // Directory exhausted or error - SysCloseHandle(kh); - HeapFree(NULL, 0, fh); - gLastError = 18; // ERROR_NO_MORE_FILES - return INVALID_HANDLE_VALUE; - } - if (WildMatch(fh->pattern, ke.Name)) { - KdirToFindData(&ke, lpFindFileData); - return (HANDLE)fh; - } - } -} - -__declspec(dllexport) BOOL FindNextFileA(HANDLE hFindFile, - WIN32_FIND_DATAA* lpFindFileData) { - if (hFindFile == INVALID_HANDLE_VALUE || !lpFindFileData) { - gLastError = 6; // ERROR_INVALID_HANDLE - return FALSE; - } - - FIND_HANDLE* fh = (FIND_HANDLE*)hFindFile; - KDIR_ENTRY ke; - - for (;;) { - uint32_t rs = SysReadDir(fh->kh, &ke); - if (rs != 0) { - gLastError = 18; // ERROR_NO_MORE_FILES - return FALSE; - } - if (WildMatch(fh->pattern, ke.Name)) { - KdirToFindData(&ke, lpFindFileData); - return TRUE; - } - } -} - -__declspec(dllexport) BOOL FindClose(HANDLE hFindFile) { - if (hFindFile == INVALID_HANDLE_VALUE) { - gLastError = 6; - return FALSE; - } - FIND_HANDLE* fh = (FIND_HANDLE*)hFindFile; - SysCloseHandle(fh->kh); - HeapFree(NULL, 0, fh); - return TRUE; -} - -// --------------------------------------------------------------------------- -// Current Working Directory -// --------------------------------------------------------------------------- - -__declspec(dllexport) BOOL SetCurrentDirectoryA(const char* lpPathName) { - if (!lpPathName) { gLastError = 87; return FALSE; } - - char translatedPath[MAX_PATH]; - TranslatePath(lpPathName, translatedPath); - - uint32_t s = SysSetCwd(translatedPath); - if (s != 0) { gLastError = 2; return FALSE; } - return TRUE; -} - -__declspec(dllexport) DWORD GetCurrentDirectoryA(DWORD nBufferLength, char* lpBuffer) { - if (!lpBuffer || nBufferLength == 0) { - // Return required size (including NUL). Max CWD is 512 bytes + "C:\". - return 515; - } - - // Retrieve the raw relative CWD from the kernel (e.g. "" or "apps\\data") - char raw[512]; - uint32_t s = SysGetCwd(raw, sizeof(raw)); - if (s != 0) { gLastError = 1; return 0; } - - // Prepend the Windows-style drive letter so callers see "C:\apps\data" - // rather than just "apps\data". - char full[515]; - if (raw[0] == '\0') { - // Root - full[0] = 'C'; full[1] = ':'; full[2] = '\\'; full[3] = '\0'; - } else { - // "C:\" - full[0] = 'C'; full[1] = ':'; full[2] = '\\'; - size_t rawLen = strlen(raw); - if (rawLen + 4 > sizeof(full)) rawLen = sizeof(full) - 4; - memcpy(full + 3, raw, rawLen); - full[3 + rawLen] = '\0'; - } - - size_t needed = strlen(full) + 1; - if ((DWORD)needed > nBufferLength) { - gLastError = 122; // ERROR_INSUFFICIENT_BUFFER - return (DWORD)needed; - } - memcpy(lpBuffer, full, needed); - return (DWORD)(needed - 1); // length without NUL, matching Win32 -} - -// --------------------------------------------------------------------------- - -__declspec(dllexport) HANDLE CreateMutexA(void* lpMutexAttributes, BOOL bInitialOwner, - const char* lpName) { - (void)lpMutexAttributes; (void)bInitialOwner; (void)lpName; - return (HANDLE)(uintptr_t)0x4D555458UL; // 'MUTX' sentinel -} - -__declspec(dllexport) BOOL ReleaseMutex(HANDLE hMutex) { - (void)hMutex; - return TRUE; -} - -__declspec(dllexport) HANDLE CreateSemaphoreA(void* lpSemaphoreAttributes, - long lInitialCount, long lMaximumCount, - const char* lpName) { - (void)lpSemaphoreAttributes; (void)lInitialCount; - (void)lMaximumCount; (void)lpName; - return (HANDLE)(uintptr_t)0x53454D41UL; // 'SEMA' sentinel -} - -__declspec(dllexport) BOOL ReleaseSemaphore(HANDLE hSemaphore, long lReleaseCount, - long* lpPreviousCount) { - (void)hSemaphore; (void)lReleaseCount; - if (lpPreviousCount) *lpPreviousCount = 0; - return TRUE; -} - -// --------------------------------------------------------------------------- -// Critical Sections & Condition Variables -// Single-threaded: all locks are no-ops. -// --------------------------------------------------------------------------- - -// Minimal opaque types; callers allocate these from their Windows headers - -// we just need the pointer to be accepted by our stubs. -typedef struct { uintptr_t _opaque[5]; } CRITICAL_SECTION; -typedef struct { void* _ptr; } CONDITION_VARIABLE; - -__declspec(dllexport) void InitializeCriticalSection(CRITICAL_SECTION* lpCS) { - (void)lpCS; -} -__declspec(dllexport) BOOL InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION* lpCS, - DWORD dwSpinCount) { - (void)lpCS; (void)dwSpinCount; - return TRUE; -} -__declspec(dllexport) void DeleteCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; } -__declspec(dllexport) void EnterCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; } -__declspec(dllexport) void LeaveCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; } -__declspec(dllexport) BOOL TryEnterCriticalSection(CRITICAL_SECTION* lpCS) { - (void)lpCS; return TRUE; -} - -__declspec(dllexport) void InitializeConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; } - -__declspec(dllexport) BOOL SleepConditionVariableCS(CONDITION_VARIABLE* cv, - CRITICAL_SECTION* cs, - DWORD dwMilliseconds) { - (void)cv; (void)cs; (void)dwMilliseconds; - return TRUE; -} -__declspec(dllexport) void WakeConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; } -__declspec(dllexport) void WakeAllConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; } - -// --------------------------------------------------------------------------- -// Timing & Performance Counters -// --------------------------------------------------------------------------- - -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; -} - -// Calibrate TSC against SysGetTick (1 unit = 1 microsecond). -static long long TscFreqCalibrate(void) { - // We'll wait for 100ms (100,000 microseconds) to get a stable sample. - const uint64_t CALIBRATION_US = 100000; - - uint64_t t0 = SysGetTick(); - uint64_t tsc0 = __rdtsc(); - - // Simple busy wait for the duration - while ((SysGetTick() - t0) < CALIBRATION_US) { - _mm_pause(); - } - - uint64_t t1 = SysGetTick(); - uint64_t tsc1 = __rdtsc(); - - uint64_t delta_tsc = tsc1 - tsc0; - uint64_t delta_us = t1 - t0; - - // Hz = (Delta TSC / Delta Microseconds) * 1,000,000 - return (long long)(delta_tsc * 1000000ULL / delta_us); -} - -static long long TscFreqGet(void) { - if (gTscFreq) return gTscFreq; - - long long f = TscFreqFromCpuid(); - if (!f) f = TscFreqCalibrate(); - - gTscFreq = f; - return f; -} - -__declspec(dllexport) BOOL QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency) { - if (lpFrequency) lpFrequency->QuadPart = TscFreqGet(); - return TRUE; -} - -__declspec(dllexport) BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount) { - // Ensure calibration happens before the first read. - TscFreqGet(); - if (lpPerformanceCount) - lpPerformanceCount->QuadPart = (long long)__rdtsc(); - return TRUE; -} - -__declspec(dllexport) void GetSystemTimeAsFileTime(void* lpSystemTimeAsFileTime) { - if (!lpSystemTimeAsFileTime) return; - - // Baseline: 2024-01-01 00:00:00 UTC in 100-ns intervals - const unsigned long long BASELINE_FT = 133485408000000000ULL; - - // SysGetTick() is in microseconds. - // 1 microsecond = 10 units of 100-nanoseconds. - unsigned long long now_us = SysGetTick(); - unsigned long long ft = BASELINE_FT + (now_us * 10ULL); - - // Copy the 64-bit value into the output buffer. - // We use memcpy to safely handle potential alignment issues with the void pointer. - 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; - -__declspec(dllexport) void GetSystemInfo(SYSTEM_INFO* lpSystemInfo) { - if (!lpSystemInfo) return; - unsigned char* p = (unsigned char*)lpSystemInfo; - for (size_t i = 0; i < sizeof(*lpSystemInfo); i++) p[i] = 0; - lpSystemInfo->wProcessorArchitecture = 9; // PROCESSOR_ARCHITECTURE_AMD64 - lpSystemInfo->dwPageSize = 4096; - lpSystemInfo->lpMinimumApplicationAddress = (void*)0x10000; - lpSystemInfo->lpMaximumApplicationAddress = (void*)0x7FFFFFFEFFFFull; - lpSystemInfo->dwActiveProcessorMask = 1; - lpSystemInfo->dwNumberOfProcessors = 1; - lpSystemInfo->dwProcessorType = 8664; // PROCESSOR_AMD_X8664 - lpSystemInfo->dwAllocationGranularity= 65536; - lpSystemInfo->wProcessorLevel = 6; - lpSystemInfo->wProcessorRevision = 0; -} - -typedef struct { - DWORD dwLength; - DWORD dwMemoryLoad; - unsigned long long ullTotalPhys; - unsigned long long ullAvailPhys; - unsigned long long ullTotalPageFile; - unsigned long long ullAvailPageFile; - unsigned long long ullTotalVirtual; - unsigned long long ullAvailVirtual; - unsigned long long ullAvailExtendedVirtual; -} MEMORYSTATUSEX; - -__declspec(dllexport) BOOL GetLogicalProcessorInformation(void* Buffer, - DWORD* ReturnedLength) { - (void)Buffer; - // Signal that the buffer is too small so callers know to handle failure. - if (ReturnedLength) *ReturnedLength = 0; - gLastError = 122; // ERROR_INSUFFICIENT_BUFFER - return FALSE; -} - -#define MAX_PATH 260 - -__declspec(dllexport) unsigned int GetSystemDirectoryA(char* lpBuffer, unsigned int uSize) { - static const char sysdir[] = "C:\\Windows\\System32"; - unsigned int needed = sizeof(sysdir); // includes NUL - if (lpBuffer && uSize >= needed) { - for (unsigned int i = 0; i < needed; i++) lpBuffer[i] = sysdir[i]; - } - return needed - 1; // return length without NUL, matching Win32 -} - -__declspec(dllexport) unsigned int GetWindowsDirectoryA(char* lpBuffer, unsigned int uSize) { - static const char windir[] = "C:\\Windows"; - unsigned int needed = sizeof(windir); - if (lpBuffer && uSize >= needed) { - for (unsigned int i = 0; i < needed; i++) lpBuffer[i] = windir[i]; - } - return needed - 1; -} - -__declspec(dllexport) DWORD GetEnvironmentVariableA(const char* lpName, char* lpBuffer, - DWORD nSize) { - (void)lpName; (void)lpBuffer; (void)nSize; - gLastError = 203; // ERROR_ENVVAR_NOT_FOUND - return 0; -} - -__declspec(dllexport) char* GetEnvironmentStrings(void) { - // Return a double-NUL-terminated empty environment block. - static char emptyEnv[2] = { '\0', '\0' }; - return emptyEnv; -} - -__declspec(dllexport) BOOL FreeEnvironmentStringsA(char* penv) { - (void)penv; return TRUE; -} - -__declspec(dllexport) unsigned short* GetEnvironmentStringsW(void) { - static unsigned short emptyEnvW[2] = { 0, 0 }; - return emptyEnvW; -} - -__declspec(dllexport) BOOL FreeEnvironmentStringsW(unsigned short* penv) { - (void)penv; return TRUE; -} - -// --------------------------------------------------------------------------- -// Handle / Error helpers -// --------------------------------------------------------------------------- - -__declspec(dllexport) unsigned int SetErrorMode(unsigned int uMode) { - (void)uMode; - return 0; // previous mode -} - -__declspec(dllexport) unsigned int SetHandleCount(unsigned int uNumber) { - return uNumber < 256 ? 256 : uNumber; -} - -// --------------------------------------------------------------------------- -// File helpers -// --------------------------------------------------------------------------- - -#define FILE_TYPE_UNKNOWN 0x0000 -#define FILE_TYPE_DISK 0x0001 -#define FILE_TYPE_CHAR 0x0002 -#define FILE_TYPE_PIPE 0x0003 - -#define STD_INPUT_HANDLE ((DWORD)-10) -#define STD_OUTPUT_HANDLE ((DWORD)-11) -#define STD_ERROR_HANDLE ((DWORD)-12) - -__declspec(dllexport) DWORD GetFileType(HANDLE hFile) { - if (!hFile || hFile == INVALID_HANDLE_VALUE) return FILE_TYPE_UNKNOWN; - return FILE_TYPE_DISK; -} - -// --------------------------------------------------------------------------- -// Exception Handling & Debugging -// --------------------------------------------------------------------------- - -#define EXCEPTION_CONTINUE_SEARCH 0 -#define EXCEPTION_EXECUTE_HANDLER 1 - -__declspec(dllexport) void* AddVectoredExceptionHandler(unsigned long First, void* Handler) { - (void)First; (void)Handler; - // Return a non-NULL sentinel so callers can pass it to RemoveVectoredExceptionHandler. - return (void*)(uintptr_t)0x56455843UL; // 'VEXC' -} - -__declspec(dllexport) unsigned long RemoveVectoredExceptionHandler(void* Handle) { - (void)Handle; - return 1; // success -} - -__declspec(dllexport) void RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, - DWORD nNumberOfArguments, - const uintptr_t* lpArguments) { - (void)dwExceptionCode; (void)dwExceptionFlags; - (void)nNumberOfArguments; (void)lpArguments; - // Best-effort: nothing to unwind; just halt. - while (true) ; -} - -typedef struct _EXCEPTION_POINTERS { void* ExceptionRecord; void* ContextRecord; } EXCEPTION_POINTERS; - -__declspec(dllexport) long UnhandledExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) { - (void)ExceptionInfo; - return EXCEPTION_CONTINUE_SEARCH; -} - -typedef long (*LPTOP_LEVEL_EXCEPTION_FILTER)(EXCEPTION_POINTERS*); - -__declspec(dllexport) LPTOP_LEVEL_EXCEPTION_FILTER -SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpFilter) { - (void)lpFilter; - return NULL; // previous filter: none -} - -__declspec(dllexport) BOOL IsDebuggerPresent(void) { - return FALSE; -} - -// --------------------------------------------------------------------------- -// RTL Exception / Unwind (SEH infrastructure - all stubs) -// --------------------------------------------------------------------------- - -typedef struct _RUNTIME_FUNCTION { - unsigned int BeginAddress; - unsigned int EndAddress; - unsigned int UnwindInfoAddress; -} RUNTIME_FUNCTION; - -// We have no runtime function table, so RtlLookupFunctionEntry always fails. -__declspec(dllexport) RUNTIME_FUNCTION* -RtlLookupFunctionEntry(unsigned long long ControlPc, - unsigned long long* ImageBase, - void* HistoryTable) { - (void)ControlPc; (void)HistoryTable; - if (ImageBase) *ImageBase = 0; - return NULL; -} - -__declspec(dllexport) void* RtlVirtualUnwind(unsigned long HandlerType, - unsigned long long ImageBase, - unsigned long long ControlPc, - RUNTIME_FUNCTION* FunctionEntry, - void* ContextRecord, - void** HandlerData, - unsigned long long* EstablisherFrame, - void* ContextPointers) { - (void)HandlerType; (void)ImageBase; (void)ControlPc; (void)FunctionEntry; - (void)ContextRecord; (void)ContextPointers; - if (HandlerData) *HandlerData = NULL; - if (EstablisherFrame)*EstablisherFrame = 0; - return NULL; -} - -__declspec(dllexport) void RtlCaptureContext(void* ContextRecord) { - // Zero out the context record so callers don't read garbage. - if (!ContextRecord) return; - // CONTEXT is ~1232 bytes on x64; zero a conservative upper bound. - unsigned char* p = (unsigned char*)ContextRecord; - for (int i = 0; i < 1232; i++) p[i] = 0; -} - -__declspec(dllexport) void RtlUnwindEx(void* TargetFrame, void* TargetIp, - void* ExceptionRecord, void* ReturnValue, - void* ContextRecord, void* HistoryTable) { - (void)TargetFrame; (void)TargetIp; (void)ExceptionRecord; - (void)ReturnValue; (void)ContextRecord; (void)HistoryTable; - // No real unwind possible; halt rather than corrupt the stack silently. - while (true) ; -} - -// --------------------------------------------------------------------------- -// Code Pages & Locale helpers -// --------------------------------------------------------------------------- - -typedef struct { unsigned int MaxCharSize; unsigned char DefaultChar[2]; unsigned char LeadByte[12]; } CPINFO; - -__declspec(dllexport) BOOL GetCPInfo(unsigned int CodePage, CPINFO* lpCPInfo) { - (void)CodePage; - if (lpCPInfo) { - lpCPInfo->MaxCharSize = 1; - lpCPInfo->DefaultChar[0] = '?'; - lpCPInfo->DefaultChar[1] = 0; - for (int i = 0; i < 12; i++) lpCPInfo->LeadByte[i] = 0; - } - return TRUE; -} - -__declspec(dllexport) BOOL IsValidCodePage(unsigned int CodePage) { - // Acknowledge the two code pages we claim to support. - return (CodePage == 1252 || CodePage == 437) ? TRUE : FALSE; -} - -__declspec(dllexport) int LCMapStringA(unsigned int Locale, DWORD dwMapFlags, - const char* lpSrcStr, int cchSrc, - char* lpDestStr, int cchDest) { - (void)Locale; (void)dwMapFlags; (void)lpSrcStr; (void)cchSrc; - (void)lpDestStr; (void)cchDest; - return 0; -} - -__declspec(dllexport) int LCMapStringW(unsigned int Locale, DWORD dwMapFlags, - const unsigned short* lpSrcStr, int cchSrc, - unsigned short* lpDestStr, int cchDest) { - (void)Locale; (void)dwMapFlags; (void)lpSrcStr; (void)cchSrc; - (void)lpDestStr; (void)cchDest; - return 0; -} - -__declspec(dllexport) BOOL GetStringTypeA(unsigned int Locale, DWORD dwInfoType, - const char* lpSrcStr, int cchSrc, - unsigned short* lpCharType) { - (void)Locale; (void)dwInfoType; (void)lpSrcStr; (void)cchSrc; (void)lpCharType; - return FALSE; -} - -__declspec(dllexport) BOOL GetStringTypeW(DWORD dwInfoType, - const unsigned short* lpSrcStr, int cchSrc, - unsigned short* lpCharType) { - (void)dwInfoType; (void)lpSrcStr; (void)cchSrc; (void)lpCharType; - return FALSE; -} - -__declspec(dllexport) int GetLocaleInfoA(unsigned int Locale, DWORD LCType, - char* lpLCData, int cchData) { - (void)Locale; (void)LCType; (void)lpLCData; (void)cchData; - return 0; -} - -// --------------------------------------------------------------------------- -// Fiber Local Storage (FLS) -// We implement a minimal 64-slot static table so callers that use FLS for -// thread-local-ish state don't immediately fail. -// --------------------------------------------------------------------------- - -#define FLS_MAX_SLOTS 64 -#define FLS_OUT_OF_INDEXES 0xFFFFFFFFUL - -static void* gFlsSlots[FLS_MAX_SLOTS]; -static int gFlsUsed[FLS_MAX_SLOTS]; // 0 = free, 1 = allocated - -__declspec(dllexport) DWORD FlsAlloc(void* lpCallback) { - (void)lpCallback; - for (int i = 0; i < FLS_MAX_SLOTS; i++) { - if (!gFlsUsed[i]) { - gFlsUsed[i] = 1; - gFlsSlots[i] = NULL; - return (DWORD)i; - } - } - gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY - return (DWORD)FLS_OUT_OF_INDEXES; -} - -__declspec(dllexport) void* FlsGetValue(DWORD dwFlsIndex) { - if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) { - gLastError = 87; // ERROR_INVALID_PARAMETER - return NULL; - } - return gFlsSlots[dwFlsIndex]; -} - -__declspec(dllexport) BOOL FlsSetValue(DWORD dwFlsIndex, void* lpFlsData) { - if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) { - gLastError = 87; - return FALSE; - } - gFlsSlots[dwFlsIndex] = lpFlsData; - return TRUE; -} - -__declspec(dllexport) BOOL FlsFree(DWORD dwFlsIndex) { - if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) { - gLastError = 87; - return FALSE; - } - gFlsUsed[dwFlsIndex] = 0; - gFlsSlots[dwFlsIndex] = NULL; - return TRUE; -} - -// --------------------------------------------------------------------------- -// Pointer encoding (security cookie obfuscation) -// We have no security cookie; return the pointer unchanged. -// --------------------------------------------------------------------------- - -__declspec(dllexport) void* EncodePointer(void* Ptr) { return Ptr; } -__declspec(dllexport) void* DecodePointer(void* Ptr) { return Ptr; } +// kernel32.c - Win32 personality layer +// +// Thin wrappers around syscall.dll stubs that expose a familiar Win32 API +// surface to user-mode code originally written for Windows. +// +// Design notes: +// - HANDLE is void*; we store the uint32_t kernel handle as a pointer value +// so Win32 code can pass it around opaquely without knowing its size. +// HANDLE_TO_KH / KH_TO_HANDLE do that conversion in one place. +// - INVALID_HANDLE_VALUE maps to kernel handle 0 (the always-invalid slot). +// - HeapAlloc / HeapFree / HeapReAlloc ignore the HANDLE heap argument and +// always use the single kernel user-heap. This is fine: we don't expose +// multiple heaps, and NULL is a legal (no-op) heap argument under Win32. +// - GetLastError / SetLastError are per-thread on Windows. We keep a single +// global for now; enough for Bink and similar single-threaded guests. +// - File I/O uses SYS_OPEN_FILE / SYS_READ_FILE / SYS_SEEK_FILE / +// SYS_GET_FILE_SIZE / SYS_CLOSE_HANDLE (unified close path - no separate +// SysCloseFile). +// - WriteFile / CreateFileA in write-mode / SetEndOfFile are stubs because +// the VFS has no write path yet. + +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- +typedef void* HANDLE; +typedef unsigned long DWORD; +typedef int BOOL; +typedef unsigned char BYTE; +typedef short SHORT; +typedef long LONG; +typedef struct { LONG x; LONG y; } POINT; + +#define MAX_PATH 260 +#define TRUE 1 +#define FALSE 0 + +#define INVALID_HANDLE_VALUE ((HANDLE)(intptr_t)-1) + +// File-find structures +typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} FILETIME; + +typedef struct _WIN32_FIND_DATAA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + char cFileName[260]; // MAX_PATH + char cAlternateFileName[14]; +} WIN32_FIND_DATAA; + +// CreateFileA disposition / access flags (subset used by Bink and similar) +#define GENERIC_READ 0x80000000UL +#define GENERIC_WRITE 0x40000000UL +#define GENERIC_READ 0x80000000UL +#define GENERIC_WRITE 0x40000000UL +#define OPEN_EXISTING 3 +#define OPEN_ALWAYS 4 +#define CREATE_ALWAYS 2 +#define CREATE_NEW 1 +#define TRUNCATE_EXISTING 5 +#define FILE_BEGIN 0 +#define FILE_CURRENT 1 +#define FILE_END 2 + +// --------------------------------------------------------------------------- +// 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; } + +// Thread IDs are not kernel handles. Keep Win32 thread pseudo-handles out of +// the low numeric handle namespace so CloseHandle/WaitForSingleObject cannot +// accidentally operate on an unrelated file/process handle with the same value. +#define WIN32_THREAD_HANDLE_TAG ((uintptr_t)1ULL << 63) +#define WIN32_MUTEX_SENTINEL ((uintptr_t)0x4D555458UL) // 'MUTX' +#define WIN32_SEMA_SENTINEL ((uintptr_t)0x53454D41UL) // 'SEMA' +static inline HANDLE TID_TO_THREAD_HANDLE(uint32_t tid) { + return (HANDLE)(WIN32_THREAD_HANDLE_TAG | (uintptr_t)tid); +} +static inline BOOL IS_THREAD_HANDLE(HANDLE h) { + return h && h != INVALID_HANDLE_VALUE && (((uintptr_t)h & WIN32_THREAD_HANDLE_TAG) != 0); +} +static inline uint32_t THREAD_HANDLE_TO_TID(HANDLE h) { + return (uint32_t)((uintptr_t)h & 0xFFFFFFFFu); +} +static inline BOOL IS_NOOP_SYNC_HANDLE(HANDLE h) { + uintptr_t v = (uintptr_t)h; + return v == WIN32_MUTEX_SENTINEL || v == WIN32_SEMA_SENTINEL; +} + +// 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:/" (assuming 'C' maps to '0', etc.) + // Map drive letter to index: 'C' or 'c' -> '0' + char drive = input[0]; + if (drive >= 'a') drive -= ('a' - 'A'); // To uppercase + + output[0] = (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'; + } +} + +// --------------------------------------------------------------------------- +// GetLastError / SetLastError (single-threaded stub) +// --------------------------------------------------------------------------- +static DWORD gLastError = 0; + +static SYS_PEB* gProcessPeb = NULL; + +static SYS_PEB* Win32Peb(void) { + if (!gProcessPeb) gProcessPeb = SysGetPeb(); + return gProcessPeb; +} + +__declspec(dllexport) DWORD GetLastError(void) { + return gLastError; +} +__declspec(dllexport) void SetLastError(DWORD err) { + gLastError = err; +} + +// --------------------------------------------------------------------------- +// Process termination +// --------------------------------------------------------------------------- +__declspec(dllexport) _Noreturn void ExitProcess(unsigned int code) { + SysExitProcess((int)code); + while (true) ; // unreachable +} + +// --------------------------------------------------------------------------- +// Debug output +// --------------------------------------------------------------------------- +__declspec(dllexport) void OutputDebugStringA(const char* str) { + if (str) SysDebugPrint(str); +} + +// --------------------------------------------------------------------------- +// Timer +// --------------------------------------------------------------------------- +__declspec(dllexport) DWORD GetTickCount(void) { + return (DWORD)(SysGetTick() / 1000); +} + +__declspec(dllexport) uint64_t GetTickCount64(void) { + return SysGetTick() / 1000; +} + +// --------------------------------------------------------------------------- +// Memory (heap argument is ignored - single kernel user-heap) +// --------------------------------------------------------------------------- +__declspec(dllexport) void* HeapAlloc(void* heap, DWORD flags, size_t size) { + (void)heap; + if (!size) return NULL; + void* ptr = SysAlloc(size); + if (!ptr) gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY + return ptr; +} + +__declspec(dllexport) BOOL HeapFree(void* heap, DWORD flags, void* ptr) { + (void)heap; (void)flags; + // Win32's HeapFree doesn't need the size; we pass 0 and the kernel's + // allocator handles it (the block header carries the size). + if (ptr) SysFree(ptr, 0); + return TRUE; +} + +__declspec(dllexport) void* HeapReAlloc(void* heap, DWORD flags, + void* ptr, size_t size) { + (void)heap; (void)flags; + void* newptr = SysRealloc(ptr, size); + if (!newptr && size) gLastError = 8; + return newptr; +} + +// GetProcessHeap - return a sentinel non-NULL value so callers can pass it +// back into HeapAlloc/HeapFree without crashing (we ignore it anyway). +__declspec(dllexport) void* GetProcessHeap(void) { + return (void*)1; +} + +// --- Win32 API --- + +// Updated GetAsyncKeyState implementation +__declspec(dllexport) SHORT GetAsyncKeyState(int vKey) { + static const struct { int vk; uint8_t sc; bool ext; } vk_to_sc[] = { + // Letters + { 0x41, 0x1E, false }, { 0x42, 0x30, false }, { 0x43, 0x2E, false }, + { 0x44, 0x20, false }, { 0x45, 0x12, false }, { 0x46, 0x21, false }, + { 0x47, 0x22, false }, { 0x48, 0x23, false }, { 0x49, 0x17, false }, + { 0x4A, 0x24, false }, { 0x4B, 0x25, false }, { 0x4C, 0x26, false }, + { 0x4D, 0x32, false }, { 0x4E, 0x31, false }, { 0x4F, 0x18, false }, + { 0x50, 0x19, false }, { 0x51, 0x10, false }, { 0x52, 0x13, false }, + { 0x53, 0x1F, false }, { 0x54, 0x14, false }, { 0x55, 0x16, false }, + { 0x56, 0x2F, false }, { 0x57, 0x11, false }, { 0x58, 0x2D, false }, + { 0x59, 0x15, false }, { 0x5A, 0x2C, false }, + + // Numbers + { 0x31, 0x02, false }, { 0x32, 0x03, false }, { 0x33, 0x04, false }, + { 0x34, 0x05, false }, { 0x35, 0x06, false }, { 0x36, 0x07, false }, + { 0x37, 0x08, false }, { 0x38, 0x09, false }, { 0x39, 0x0A, false }, + { 0x30, 0x0B, false }, + + // Arrows (Extended) + { 0x26, 0x48, true }, // VK_UP + { 0x28, 0x50, true }, // VK_DOWN + { 0x25, 0x4B, true }, // VK_LEFT + { 0x27, 0x4D, true }, // VK_RIGHT + + // Controls + { 0x1B, 0x01, false }, // ESC + { 0x0D, 0x1C, false }, // ENTER + { 0x20, 0x39, false }, // SPACE + { 0x11, 0x1D, false }, // CTRL (Generic) + { 0x10, 0x2A, false }, // SHIFT (Generic) + { 0x12, 0x38, false }, // ALT (Generic) + { 0x08, 0x0E, false }, // BACKSPACE + { 0x09, 0x0F, false }, // TAB + + // Function Keys + { 0x70, 0x3B, false }, { 0x71, 0x3C, false }, { 0x72, 0x3D, false }, + { 0x73, 0x3E, false }, { 0x74, 0x3F, false }, { 0x75, 0x40, false }, + { 0x76, 0x41, false }, { 0x77, 0x42, false }, { 0x78, 0x43, false }, + { 0x79, 0x44, false }, { 0x7A, 0x57, false }, { 0x7B, 0x58, false }, + + // Symbols + { 0xBD, 0x0C, false }, // MINUS '-' + { 0xBB, 0x0D, false }, // EQUALS '=' + { 0xBC, 0x33, false }, // COMMA ',' + { 0xBE, 0x34, false }, // PERIOD '.' + }; + + for (size_t i = 0; i < sizeof(vk_to_sc)/sizeof(vk_to_sc[0]); ++i) { + if (vk_to_sc[i].vk == vKey) { + // Check the hardware state array via our HAL helper + if (SysKbIsKeyDown(vk_to_sc[i].sc, vk_to_sc[i].ext)) { + 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 + while (SysKbPoll()) { + (void)SysKbReadChar(); + } + return TRUE; +} + +// !Made-up! clear console api. +__declspec(dllexport) BOOL ClearConsoleScreenBuffer(HANDLE hConsole) { + SysScreenClear(0x00000000); + 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; +} + +__declspec(dllexport) DWORD GetACP(void) { return 1252; } // Windows-1252 +__declspec(dllexport) DWORD GetOEMCP(void) { return 437; } // DOS Latin US + +// --------------------------------------------------------------------------- +// File I/O +// --------------------------------------------------------------------------- + +// CreateFileA - opens or creates a file. +// Supported modes: +// OPEN_EXISTING + GENERIC_READ -> open existing, read-only +// OPEN_EXISTING + GENERIC_WRITE -> open existing, read-write +// OPEN_EXISTING + GENERIC_READ|GENERIC_WRITE -> open existing, read-write +// CREATE_ALWAYS + GENERIC_WRITE -> create/truncate, write +// OPEN_ALWAYS + GENERIC_WRITE -> create if absent, write +// TRUNCATE_EXISTING + GENERIC_WRITE -> open & truncate to zero +__declspec(dllexport) HANDLE CreateFileA(const char* name, + DWORD access, DWORD share, + void* sa, DWORD disposition, + DWORD flags, void* templ) { + (void)share; (void)sa; (void)flags; (void)templ; + + // Translate the path from Windows format to custom 0:/ format + char translatedName[260]; + TranslatePath(name, translatedName); + + int wantWrite = (access & GENERIC_WRITE) != 0; + + if (disposition == OPEN_EXISTING || disposition == TRUNCATE_EXISTING) { + // Open the file via the read path (it must already exist). + uint32_t kh = 0; + uint32_t s = SysOpenFile(translatedName, &kh); + if (s != 0) { gLastError = 2; return INVALID_HANDLE_VALUE; } + + if (disposition == TRUNCATE_EXISTING && wantWrite) { + // Immediately truncate to zero. + uint32_t ts = SysTruncateFile(kh, 0); + if (ts != 0) { + SysCloseHandle(kh); + gLastError = 5; + return INVALID_HANDLE_VALUE; + } + } + return KH_TO_HANDLE(kh); + } + + if (disposition == CREATE_ALWAYS && wantWrite) { + // Create-or-replace: try to create; if the file already exists, + // open it and truncate it to zero instead. + uint32_t kh = 0; + uint32_t s = SysCreateFile(translatedName, &kh); + if (s != 0) { + // File may already exist - open and truncate. + s = SysOpenFile(translatedName, &kh); + if (s != 0) { gLastError = 5; return INVALID_HANDLE_VALUE; } + SysTruncateFile(kh, 0); + } + return KH_TO_HANDLE(kh); + } + + if (disposition == OPEN_ALWAYS && wantWrite) { + // Open if exists, create if not. + uint32_t kh = 0; + if (SysOpenFile(translatedName, &kh) != 0) { + if (SysCreateFile(translatedName, &kh) != 0) { + gLastError = 5; + return INVALID_HANDLE_VALUE; + } + } + return KH_TO_HANDLE(kh); + } + + if (disposition == CREATE_NEW && wantWrite) { + // Fail if the file already exists (we can't check atomically, + // but SysCreateFile returns NAME_COLLISION for existing files). + uint32_t kh = 0; + uint32_t s = SysCreateFile(translatedName, &kh); + if (s != 0) { gLastError = 80; return INVALID_HANDLE_VALUE; } // ERROR_FILE_EXISTS + return KH_TO_HANDLE(kh); + } + + // Any other combination (e.g. read-only CREATE_ALWAYS) is not supported. + gLastError = 87; // ERROR_INVALID_PARAMETER + return INVALID_HANDLE_VALUE; +} + +// GetFileSize - fills optional high-DWORD pointer, returns low DWORD. +// Returns 0xFFFFFFFF on error, matching Win32 behaviour. +__declspec(dllexport) DWORD GetFileSize(HANDLE hFile, DWORD* fileSizeHigh) { + if (!hFile || hFile == INVALID_HANDLE_VALUE || IS_THREAD_HANDLE(hFile)) { gLastError = 6; return 0xFFFFFFFFUL; } + uint64_t sz = SysGetFileSize(HANDLE_TO_KH(hFile)); + if (fileSizeHigh) *fileSizeHigh = (DWORD)(sz >> 32); + return (DWORD)(sz & 0xFFFFFFFFULL); +} + +// ReadFile - sequential read at the file's current position. +__declspec(dllexport) BOOL ReadFile(HANDLE hFile, void* buf, DWORD toRead, + DWORD* bytesRead, void* overlapped) { + (void)overlapped; + if (bytesRead) *bytesRead = 0; + if (!hFile || hFile == INVALID_HANDLE_VALUE || IS_THREAD_HANDLE(hFile) || !buf) { + gLastError = 6; + return FALSE; + } + + size_t got = 0; + uint32_t s = SysReadFile(HANDLE_TO_KH(hFile), buf, (size_t)toRead, &got); + if (bytesRead) *bytesRead = (DWORD)got; + + // Harden callers that accidentally look past *bytesRead. EOF/short reads + // are legal for files, but consuming stale stack/heap bytes after them makes + // the failure mode look like random game-data corruption. + if (got < (size_t)toRead) + memset((unsigned char*)buf + got, 0, (size_t)toRead - got); + + if (s != 0) { + gLastError = 1; + return FALSE; + } + return TRUE; +} + +// WriteFile - sequential write at the file's current position. +// Extends the file if writing past EOF. Overlapped I/O is not supported. +__declspec(dllexport) BOOL WriteFile(HANDLE hFile, const void* buf, DWORD toWrite, + DWORD* bytesWritten, void* overlapped) { + (void)overlapped; + if (!hFile || hFile == INVALID_HANDLE_VALUE || IS_THREAD_HANDLE(hFile) || !buf) { gLastError = 6; return FALSE; } + size_t written = 0; + uint32_t s = SysWriteFile(HANDLE_TO_KH(hFile), buf, (size_t)toWrite, &written); + if (bytesWritten) *bytesWritten = (DWORD)written; + if (s != 0) { gLastError = 29; return FALSE; } // ERROR_WRITE_FAULT + return TRUE; +} + +// SetFilePointer - seek to an absolute, relative, or end-relative position. +// Returns new position low DWORD, or 0xFFFFFFFF on error. +__declspec(dllexport) DWORD SetFilePointer(HANDLE hFile, long dist, + long* distHigh, DWORD method) { + if (!hFile || hFile == INVALID_HANDLE_VALUE || IS_THREAD_HANDLE(hFile)) { + 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 = SysGetFilePos(kh); + if (cur == (uint64_t)-1 || cur > 0x7FFFFFFFFFFFFFFFULL) { gLastError = 6; return 0xFFFFFFFFUL; } + target = (int64_t)cur + delta; + } else if (method == FILE_END) { + uint64_t sz = SysGetFileSize(kh); + if (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 || IS_THREAD_HANDLE(hFile)) { gLastError = 6; return FALSE; } + uint32_t kh = HANDLE_TO_KH(hFile); + uint64_t pos = SysGetFilePos(kh); + if (pos == (uint64_t)-1) { gLastError = 6; return FALSE; } + uint32_t s = SysTruncateFile(kh, pos); + if (s != 0) { gLastError = 1; return FALSE; } + return TRUE; +} + +// DeleteFileA - delete a file by name; no open handle required. +__declspec(dllexport) BOOL DeleteFileA(const char* lpFileName) { + if (!lpFileName) { gLastError = 87; return FALSE; } + + char translatedPath[MAX_PATH]; + TranslatePath(lpFileName, translatedPath); + + uint32_t s = SysDeleteFile(translatedPath); + if (s != 0) { gLastError = 2; return FALSE; } + return TRUE; +} + +// CloseHandle - unified close for files, mutexes, semaphores, events, queues. +__declspec(dllexport) BOOL CloseHandle(HANDLE hObject) { + if (!hObject || hObject == INVALID_HANDLE_VALUE) { gLastError = 6; return FALSE; } + + // Our thread/sentinel sync handles are not entries in the kernel handle table. + // There is nothing to close; the thread object is reaped by the scheduler. + if (IS_THREAD_HANDLE(hObject) || IS_NOOP_SYNC_HANDLE(hObject)) return TRUE; + + SysCloseHandle(HANDLE_TO_KH(hObject)); + return TRUE; +} + +// =========================================================================== +// Extended shims - funcs.txt additions +// All functions here return plausible values; no real implementation exists +// unless a matching syscall stub is available. +// =========================================================================== + +// --------------------------------------------------------------------------- +// Additional Heap APIs +// We have one kernel heap; the HANDLE is always ignored. +// --------------------------------------------------------------------------- + +typedef int HEAP_INFORMATION_CLASS; // opaque + +__declspec(dllexport) HANDLE HeapCreate(DWORD flOptions, + size_t dwInitialSize, + size_t dwMaximumSize) { + (void)flOptions; (void)dwInitialSize; (void)dwMaximumSize; + return (HANDLE)(uintptr_t)1; // non-NULL sentinel +} + +__declspec(dllexport) BOOL HeapDestroy(HANDLE hHeap) { + (void)hHeap; + return TRUE; +} + +__declspec(dllexport) size_t HeapSize(HANDLE heap, DWORD flags, const void* ptr) { + (void)heap; (void)flags; (void)ptr; + return (size_t)-1; // Win32 returns (SIZE_T)-1 on error / unsupported +} + +__declspec(dllexport) BOOL HeapSetInformation(HANDLE HeapHandle, + HEAP_INFORMATION_CLASS cls, + void* info, size_t infoLen) { + (void)HeapHandle; (void)cls; (void)info; (void)infoLen; + return TRUE; +} + +// --------------------------------------------------------------------------- +// Process & Module pseudo-handles / queries +// --------------------------------------------------------------------------- + +// Pseudo-handles matching Windows convention: -1 = process, -2 = thread. +__declspec(dllexport) HANDLE GetCurrentProcess(void) { + return (HANDLE)(uintptr_t)(~(uintptr_t)0); +} + +__declspec(dllexport) HANDLE GetCurrentThread(void) { + return (HANDLE)(uintptr_t)(~(uintptr_t)1); +} + +__declspec(dllexport) DWORD GetCurrentThreadId(void) { return (DWORD)SysGetCurrentTid(); } +__declspec(dllexport) DWORD GetCurrentProcessId(void) { return (DWORD)SysGetCurrentPid(); } + +// GetModuleHandleA/W - no PE map; return NULL so callers that check can fail +// gracefully. GetModuleHandleA(NULL) on Windows returns the exe base; we +// return a recognisable non-NULL sentinel so code that just passes it to +// GetProcAddress doesn't immediately crash. +__declspec(dllexport) HANDLE GetModuleHandleA(const char* lpModuleName) { + return lpModuleName ? NULL : (HANDLE)(uintptr_t)0x00400000UL; +} + +__declspec(dllexport) HANDLE GetModuleHandleW(const unsigned short* lpModuleName) { + return lpModuleName ? NULL : (HANDLE)(uintptr_t)0x00400000UL; +} + +// GetProcAddress - no dynamic linker; always fail. +__declspec(dllexport) void* GetProcAddress(HANDLE hModule, const char* lpProcName) { + (void)hModule; (void)lpProcName; + gLastError = 127; // ERROR_PROC_NOT_FOUND + return NULL; +} + +// LoadLibraryA - no dynamic loader; always fail. +__declspec(dllexport) HANDLE LoadLibraryA(const char* lpLibFileName) { + (void)lpLibFileName; + gLastError = 2; // ERROR_FILE_NOT_FOUND + return NULL; +} + +__declspec(dllexport) BOOL FreeLibrary(HANDLE hLibModule) { + (void)hLibModule; + return TRUE; +} + +__declspec(dllexport) DWORD GetModuleFileNameA(HANDLE hModule, + char* lpFilename, DWORD nSize) { + (void)hModule; + if (lpFilename && nSize) lpFilename[0] = '\0'; + return 0; +} + +__declspec(dllexport) BOOL TerminateProcess(HANDLE hProcess, unsigned int uExitCode) { + (void)hProcess; + SysExitProcess((int)uExitCode); + while (true) ; +} + +// STARTUPINFOA - only cb matters for our stub. +typedef struct { + DWORD cb; + char* lpReserved; + char* lpDesktop; + char* lpTitle; + DWORD dwX, dwY, dwXSize, dwYSize; + DWORD dwXCountChars, dwYCountChars; + DWORD dwFillAttribute, dwFlags; + unsigned short wShowWindow, cbReserved2; + unsigned char* lpReserved2; + HANDLE hStdInput, hStdOutput, hStdError; +} STARTUPINFOA; + +__declspec(dllexport) void GetStartupInfoA(STARTUPINFOA* lpStartupInfo) { + if (!lpStartupInfo) return; + // Zero every field, then set the mandatory cb. + unsigned char* p = (unsigned char*)lpStartupInfo; + for (size_t i = 0; i < sizeof(*lpStartupInfo); i++) p[i] = 0; + lpStartupInfo->cb = (DWORD)sizeof(*lpStartupInfo); +} + +__declspec(dllexport) char* GetCommandLineA(void) { + SYS_PEB* peb = Win32Peb(); + if (!peb || peb->Magic != SYS_PEB_MAGIC || !peb->CommandLine) return ""; + return (char*)peb->CommandLine; +} + +// --------------------------------------------------------------------------- +// Threading +// --------------------------------------------------------------------------- + +#define WAIT_OBJECT_0 0x00000000UL +#define WAIT_TIMEOUT 0x00000102UL +#define INFINITE 0xFFFFFFFFUL + +__declspec(dllexport) HANDLE CreateThread(void* lpThreadAttributes, + size_t dwStackSize, + void* lpStartAddress, + void* lpParameter, + DWORD dwCreationFlags, + DWORD* lpThreadId) { + (void)lpThreadAttributes; (void)dwStackSize; (void)dwCreationFlags; + uint32_t tid = 0; + uint32_t s = SysCreateThread((uint64_t)(uintptr_t)lpStartAddress, + (uint64_t)(uintptr_t)lpParameter, + 0 /* normal priority */, &tid); + if (s != 0) { gLastError = 11; return INVALID_HANDLE_VALUE; } + if (lpThreadId) *lpThreadId = (DWORD)tid; + return TID_TO_THREAD_HANDLE(tid); +} + +// WaitForSingleObject - blocks on process handles via SysWaitForProcess; +// thread and other handle types are not yet waitable so return immediately. +__declspec(dllexport) DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) { + (void)dwMilliseconds; + if (!hHandle || hHandle == INVALID_HANDLE_VALUE) { gLastError = 6; return 0xFFFFFFFFUL; } + + if (IS_THREAD_HANDLE(hHandle)) { + uint32_t tid = THREAD_HANDLE_TO_TID(hHandle); + uint32_t s = SysWaitThread(tid); + if (s != 0 && s != 0x0022) { gLastError = 6; return 0xFFFFFFFFUL; } + return WAIT_OBJECT_0; + } + + if (IS_NOOP_SYNC_HANDLE(hHandle)) return WAIT_OBJECT_0; + + uint32_t kh = HANDLE_TO_KH(hHandle); + if (kh) { + uint32_t s = SysWaitForProcess(kh); + if (s != 0) { gLastError = 6; return 0xFFFFFFFFUL; } + } + return WAIT_OBJECT_0; +} + + +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 = SysGetCwd(savedCwd, sizeof(savedCwd)); + if (gs != 0) { gLastError = 3; return FALSE; } + + TranslatePath(lpCurrentDirectory, translatedCwd); + gs = SysSetCwd(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 = SysCreateProcess(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'; + SysSetCwd(restoreCwd); + } else { + SysSetCwd("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; +} + +__declspec(dllexport) BOOL GetExitCodeProcess(HANDLE hProcess, DWORD* lpExitCode) { + if (!lpExitCode) { gLastError = 87; return FALSE; } + uint32_t kh = HANDLE_TO_KH(hProcess); + if (!kh || IS_THREAD_HANDLE(hProcess)) { gLastError = 6; return FALSE; } // ERROR_INVALID_HANDLE + int code = 0; + uint32_t s = SysGetProcessExitCode(kh, &code); + if (s != 0) { gLastError = 6; return FALSE; } + *lpExitCode = (DWORD)code; + return TRUE; +} + +__declspec(dllexport) DWORD ResumeThread(HANDLE hThread) { + (void)hThread; + return 0; // previous suspend count +} + +#define THREAD_PRIORITY_NORMAL 0 + +__declspec(dllexport) BOOL SetThreadPriority(HANDLE hThread, int nPriority) { + (void)hThread; (void)nPriority; + return TRUE; +} + +__declspec(dllexport) int GetThreadPriority(HANDLE hThread) { + (void)hThread; + return THREAD_PRIORITY_NORMAL; +} + +__declspec(dllexport) uintptr_t SetThreadAffinityMask(HANDLE hThread, + uintptr_t dwThreadAffinityMask) { + (void)hThread; (void)dwThreadAffinityMask; + return 1; // previous mask: CPU 0 only +} + +__declspec(dllexport) void Sleep(DWORD dwMilliseconds) { + SysSleep(dwMilliseconds); +} + +// --------------------------------------------------------------------------- +// Console I/O +// --------------------------------------------------------------------------- + +#define STD_INPUT_HANDLE ((DWORD)-10) +#define STD_OUTPUT_HANDLE ((DWORD)-11) +#define STD_ERROR_HANDLE ((DWORD)-12) + +// We have no real console handles; return a sentinel the other APIs recognise. +__declspec(dllexport) HANDLE GetStdHandle(DWORD nStdHandle) { + return (HANDLE)(uintptr_t)nStdHandle; +} + +// ReadConsoleA: blocking read of up to nNumberOfCharsToRead characters. +// Each character is read individually via SysKbReadChar so the caller can +// do its own line-editing (echo, backspace, etc.) one char at a time. +__declspec(dllexport) BOOL ReadConsoleA(HANDLE hConsoleInput, + void* lpBuffer, + DWORD nNumberOfCharsToRead, + DWORD* lpNumberOfCharsRead, + void* pInputControl) { + (void)hConsoleInput; (void)pInputControl; + if (!lpBuffer || nNumberOfCharsToRead == 0) return FALSE; + + char* buf = (char*)lpBuffer; + DWORD i = 0; // Notice we switched to a while loop + + while (i < nNumberOfCharsToRead) { + char c = SysKbReadChar(); + + // Ignore unmapped keys (Shift, Ctrl, etc.) + if (c == 0) continue; + + // Deliver the raw character; the caller handles echo and line editing. + buf[i] = c; + i++; + + // Stop reading when the user presses Enter + if (c == '\n' || c == '\r') { + // If your shell expects strings to be \n terminated, leave this. + // If your shell expects \0 termination without a newline, + // you might want to replace buf[i-1] with \0 here! + break; + } + } + + if (lpNumberOfCharsRead) *lpNumberOfCharsRead = i; + return TRUE; +} + +// WriteConsoleA: write nNumberOfCharsToWrite characters from lpBuffer to the +// console as white-on-black text via SYS_WRITE_CONSOLE. +__declspec(dllexport) BOOL WriteConsoleA(HANDLE hConsoleOutput, + const void* lpBuffer, + DWORD nNumberOfCharsToWrite, + DWORD* lpNumberOfCharsWritten, + void* lpReserved) { + (void)hConsoleOutput; (void)lpReserved; + if (!lpBuffer || nNumberOfCharsToWrite == 0) { + if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = 0; + return TRUE; + } + SysWriteConsole((const char*)lpBuffer, (size_t)nNumberOfCharsToWrite); + if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = nNumberOfCharsToWrite; + return TRUE; +} + +// --------------------------------------------------------------------------- +// Directory enumeration (FindFirstFileA / FindNextFileA / FindClose) +// --------------------------------------------------------------------------- + +// KDIR_ENTRY layout must exactly match the definition in io_file.h. +#define KDIR_NAME_MAX 260 + +typedef struct { + char Name[KDIR_NAME_MAX]; + uint8_t Attr; + uint32_t FileSize; +} KDIR_ENTRY; + +// FIND_HANDLE is a user-side wrapper: it stores the kernel dir handle and +// the wildcard pattern so FindNextFileA can filter subsequent entries. +// We return a pointer to this struct cast to HANDLE; it is NOT a kernel +// handle, so FindClose must NOT go through CloseHandle/SysCloseHandle. +typedef struct { + uint32_t kh; // kernel DIR_OBJECT handle (raw pointer) + char pattern[MAX_PATH]; // wildcard pattern, e.g. "*" or "*.txt" +} FIND_HANDLE; + +// Minimal wildcard match: supports '*' (any sequence) and '?' (any char). +// Case-insensitive to match FAT32/Windows behaviour. +static int WildMatch(const char* pat, const char* name) { + while (*pat && *name) { + if (*pat == '*') { + // Skip consecutive stars + while (*pat == '*') pat++; + if (!*pat) return 1; // trailing * matches everything + // Try matching the rest of the pattern at every position + while (*name) { + if (WildMatch(pat, name)) return 1; + name++; + } + return 0; + } + char p = (*pat >= 'A' && *pat <= 'Z') ? *pat + 32 : *pat; + char n = (*name >= 'A' && *name <= 'Z') ? *name + 32 : *name; + if (*pat != '?' && p != n) return 0; + pat++; name++; + } + // Consume trailing stars in the pattern + while (*pat == '*') pat++; + return (*pat == '\0' && *name == '\0'); +} + +// Split a FindFirstFileA-style search path into directory and pattern parts. +// "apps\\*" -> dir="apps" pat="*" +// "*.txt" -> dir="" pat="*.txt" +// "*" -> dir="" pat="*" +// "apps\\" -> dir="apps" pat="*" +static void SplitFindPath(const char* searchPath, + char* dirOut, size_t dirSize, + char* patOut, size_t patSize) { + // Find last backslash or forward-slash + const char* last = NULL; + for (const char* p = searchPath; *p; p++) + if (*p == '\\' || *p == '/') last = p; + + if (!last) { + // No separator: whole string is the pattern, dir is CWD + if (dirOut && dirSize) dirOut[0] = '\0'; + if (patOut && patSize) { + size_t len = strlen(searchPath); + if (len + 1 > patSize) len = patSize - 1; + memcpy(patOut, searchPath, len); + patOut[len] = '\0'; + } + } else { + // Everything before the last separator is the directory + if (dirOut) { + size_t len = (size_t)(last - searchPath); + if (len + 1 > dirSize) len = dirSize - 1; + memcpy(dirOut, searchPath, len); + dirOut[len] = '\0'; + } + // Everything after the last separator is the pattern + if (patOut) { + const char* p = last + 1; + size_t len = strlen(p); + if (len + 1 > patSize) len = patSize - 1; + memcpy(patOut, p, len); + patOut[len] = '\0'; + } + } + + // Empty pattern defaults to "*" + if (patOut && patOut[0] == '\0') { + patOut[0] = '*'; patOut[1] = '\0'; + } +} + +// Fill a WIN32_FIND_DATAA from a KDIR_ENTRY. +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010UL +#define FILE_ATTRIBUTE_ARCHIVE 0x00000020UL +#define FILE_ATTRIBUTE_NORMAL 0x00000080UL + +static void KdirToFindData(const KDIR_ENTRY* ke, WIN32_FIND_DATAA* fd) { + // Zero the entire struct (covers all FILETIME and reserved fields) + unsigned char* p = (unsigned char*)fd; + for (size_t i = 0; i < sizeof(*fd); i++) p[i] = 0; + + fd->dwFileAttributes = ke->Attr; + if (!(fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + !(fd->dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)) + fd->dwFileAttributes |= FILE_ATTRIBUTE_NORMAL; + + fd->nFileSizeLow = ke->FileSize; + fd->nFileSizeHigh = 0; + + size_t nameLen = strlen(ke->Name); + if (nameLen >= MAX_PATH) nameLen = MAX_PATH - 1; + memcpy(fd->cFileName, ke->Name, nameLen); + fd->cFileName[nameLen] = '\0'; +} + +__declspec(dllexport) HANDLE FindFirstFileA(const char* lpFileName, + WIN32_FIND_DATAA* lpFindFileData) { + if (!lpFileName || !lpFindFileData) { + gLastError = 87; // ERROR_INVALID_PARAMETER + return INVALID_HANDLE_VALUE; + } + + // 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 = SysOpenDir(dir[0] ? dir : NULL, &kh); + if (s != 0 || kh == 0) { + gLastError = 2; // ERROR_FILE_NOT_FOUND + return INVALID_HANDLE_VALUE; + } + + // Allocate our user-side FIND_HANDLE wrapper + FIND_HANDLE* fh = (FIND_HANDLE*)HeapAlloc(NULL, 0, sizeof(FIND_HANDLE)); + if (!fh) { + SysCloseHandle(kh); + gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY + return INVALID_HANDLE_VALUE; + } + fh->kh = kh; + size_t patLen = strlen(pat); + if (patLen >= MAX_PATH) patLen = MAX_PATH - 1; + memcpy(fh->pattern, pat, patLen); + fh->pattern[patLen] = '\0'; + + // Read the first entry that matches the pattern + KDIR_ENTRY ke; + for (;;) { + uint32_t rs = SysReadDir(kh, &ke); + if (rs != 0) { + // Directory exhausted or error + SysCloseHandle(kh); + HeapFree(NULL, 0, fh); + gLastError = 18; // ERROR_NO_MORE_FILES + return INVALID_HANDLE_VALUE; + } + if (WildMatch(fh->pattern, ke.Name)) { + KdirToFindData(&ke, lpFindFileData); + return (HANDLE)fh; + } + } +} + +__declspec(dllexport) BOOL FindNextFileA(HANDLE hFindFile, + WIN32_FIND_DATAA* lpFindFileData) { + if (hFindFile == INVALID_HANDLE_VALUE || !lpFindFileData) { + gLastError = 6; // ERROR_INVALID_HANDLE + return FALSE; + } + + FIND_HANDLE* fh = (FIND_HANDLE*)hFindFile; + KDIR_ENTRY ke; + + for (;;) { + uint32_t rs = SysReadDir(fh->kh, &ke); + if (rs != 0) { + gLastError = 18; // ERROR_NO_MORE_FILES + return FALSE; + } + if (WildMatch(fh->pattern, ke.Name)) { + KdirToFindData(&ke, lpFindFileData); + return TRUE; + } + } +} + +__declspec(dllexport) BOOL FindClose(HANDLE hFindFile) { + if (hFindFile == INVALID_HANDLE_VALUE) { + gLastError = 6; + return FALSE; + } + FIND_HANDLE* fh = (FIND_HANDLE*)hFindFile; + SysCloseHandle(fh->kh); + HeapFree(NULL, 0, fh); + return TRUE; +} + +// --------------------------------------------------------------------------- +// Current Working Directory +// --------------------------------------------------------------------------- + +__declspec(dllexport) BOOL SetCurrentDirectoryA(const char* lpPathName) { + if (!lpPathName) { gLastError = 87; return FALSE; } + + char translatedPath[MAX_PATH]; + TranslatePath(lpPathName, translatedPath); + + uint32_t s = SysSetCwd(translatedPath); + if (s != 0) { gLastError = 2; return FALSE; } + return TRUE; +} + +__declspec(dllexport) DWORD GetCurrentDirectoryA(DWORD nBufferLength, char* lpBuffer) { + if (!lpBuffer || nBufferLength == 0) { + // Return required size (including NUL). Max CWD is 512 bytes + "C:\". + return 515; + } + + // Retrieve the raw relative CWD from the kernel (e.g. "" or "apps\\data") + char raw[512]; + uint32_t s = SysGetCwd(raw, sizeof(raw)); + if (s != 0) { gLastError = 1; return 0; } + + // Prepend the Windows-style drive letter so callers see "C:\apps\data" + // rather than just "apps\data". + char full[515]; + if (raw[0] == '\0') { + // Root + full[0] = 'C'; full[1] = ':'; full[2] = '\\'; full[3] = '\0'; + } else { + // "C:\" + full[0] = 'C'; full[1] = ':'; full[2] = '\\'; + size_t rawLen = strlen(raw); + if (rawLen + 4 > sizeof(full)) rawLen = sizeof(full) - 4; + memcpy(full + 3, raw, rawLen); + full[3 + rawLen] = '\0'; + } + + size_t needed = strlen(full) + 1; + if ((DWORD)needed > nBufferLength) { + gLastError = 122; // ERROR_INSUFFICIENT_BUFFER + return (DWORD)needed; + } + memcpy(lpBuffer, full, needed); + return (DWORD)(needed - 1); // length without NUL, matching Win32 +} + +// --------------------------------------------------------------------------- + +__declspec(dllexport) HANDLE CreateMutexA(void* lpMutexAttributes, BOOL bInitialOwner, + const char* lpName) { + (void)lpMutexAttributes; (void)bInitialOwner; (void)lpName; + return (HANDLE)WIN32_MUTEX_SENTINEL; +} + +__declspec(dllexport) BOOL ReleaseMutex(HANDLE hMutex) { + (void)hMutex; + return TRUE; +} + +__declspec(dllexport) HANDLE CreateSemaphoreA(void* lpSemaphoreAttributes, + long lInitialCount, long lMaximumCount, + const char* lpName) { + (void)lpSemaphoreAttributes; (void)lInitialCount; + (void)lMaximumCount; (void)lpName; + return (HANDLE)WIN32_SEMA_SENTINEL; +} + +__declspec(dllexport) BOOL ReleaseSemaphore(HANDLE hSemaphore, long lReleaseCount, + long* lpPreviousCount) { + (void)hSemaphore; (void)lReleaseCount; + if (lpPreviousCount) *lpPreviousCount = 0; + return TRUE; +} + +// --------------------------------------------------------------------------- +// Critical Sections & Condition Variables +// Single-threaded: all locks are no-ops. +// --------------------------------------------------------------------------- + +// Minimal opaque types; callers allocate these from their Windows headers - +// we just need the pointer to be accepted by our stubs. +typedef struct { uintptr_t _opaque[5]; } CRITICAL_SECTION; +typedef struct { void* _ptr; } CONDITION_VARIABLE; + +__declspec(dllexport) void InitializeCriticalSection(CRITICAL_SECTION* lpCS) { + (void)lpCS; +} +__declspec(dllexport) BOOL InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION* lpCS, + DWORD dwSpinCount) { + (void)lpCS; (void)dwSpinCount; + return TRUE; +} +__declspec(dllexport) void DeleteCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; } +__declspec(dllexport) void EnterCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; } +__declspec(dllexport) void LeaveCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; } +__declspec(dllexport) BOOL TryEnterCriticalSection(CRITICAL_SECTION* lpCS) { + (void)lpCS; return TRUE; +} + +__declspec(dllexport) void InitializeConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; } + +__declspec(dllexport) BOOL SleepConditionVariableCS(CONDITION_VARIABLE* cv, + CRITICAL_SECTION* cs, + DWORD dwMilliseconds) { + (void)cv; (void)cs; (void)dwMilliseconds; + return TRUE; +} +__declspec(dllexport) void WakeConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; } +__declspec(dllexport) void WakeAllConditionVariable(CONDITION_VARIABLE* cv) { (void)cv; } + +// --------------------------------------------------------------------------- +// Timing & Performance Counters +// --------------------------------------------------------------------------- + +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; +} + +// Calibrate TSC against SysGetTick (1 unit = 1 microsecond). +static long long TscFreqCalibrate(void) { + // We'll wait for 100ms (100,000 microseconds) to get a stable sample. + const uint64_t CALIBRATION_US = 100000; + + uint64_t t0 = SysGetTick(); + uint64_t tsc0 = __rdtsc(); + + // Simple busy wait for the duration + while ((SysGetTick() - t0) < CALIBRATION_US) { + _mm_pause(); + } + + uint64_t t1 = SysGetTick(); + uint64_t tsc1 = __rdtsc(); + + uint64_t delta_tsc = tsc1 - tsc0; + uint64_t delta_us = t1 - t0; + + // Hz = (Delta TSC / Delta Microseconds) * 1,000,000 + return (long long)(delta_tsc * 1000000ULL / delta_us); +} + +static long long TscFreqGet(void) { + if (gTscFreq) return gTscFreq; + + long long f = TscFreqFromCpuid(); + if (!f) f = TscFreqCalibrate(); + + gTscFreq = f; + return f; +} + +__declspec(dllexport) BOOL QueryPerformanceFrequency(LARGE_INTEGER* lpFrequency) { + if (lpFrequency) lpFrequency->QuadPart = TscFreqGet(); + return TRUE; +} + +__declspec(dllexport) BOOL QueryPerformanceCounter(LARGE_INTEGER* lpPerformanceCount) { + // Ensure calibration happens before the first read. + TscFreqGet(); + if (lpPerformanceCount) + lpPerformanceCount->QuadPart = (long long)__rdtsc(); + return TRUE; +} + +__declspec(dllexport) void GetSystemTimeAsFileTime(void* lpSystemTimeAsFileTime) { + if (!lpSystemTimeAsFileTime) return; + + // Baseline: 2024-01-01 00:00:00 UTC in 100-ns intervals + const unsigned long long BASELINE_FT = 133485408000000000ULL; + + // SysGetTick() is in microseconds. + // 1 microsecond = 10 units of 100-nanoseconds. + unsigned long long now_us = SysGetTick(); + unsigned long long ft = BASELINE_FT + (now_us * 10ULL); + + // Copy the 64-bit value into the output buffer. + // We use memcpy to safely handle potential alignment issues with the void pointer. + 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; + +__declspec(dllexport) void GetSystemInfo(SYSTEM_INFO* lpSystemInfo) { + if (!lpSystemInfo) return; + unsigned char* p = (unsigned char*)lpSystemInfo; + for (size_t i = 0; i < sizeof(*lpSystemInfo); i++) p[i] = 0; + lpSystemInfo->wProcessorArchitecture = 9; // PROCESSOR_ARCHITECTURE_AMD64 + lpSystemInfo->dwPageSize = 4096; + lpSystemInfo->lpMinimumApplicationAddress = (void*)0x10000; + lpSystemInfo->lpMaximumApplicationAddress = (void*)0x7FFFFFFEFFFFull; + lpSystemInfo->dwActiveProcessorMask = 1; + lpSystemInfo->dwNumberOfProcessors = 1; + lpSystemInfo->dwProcessorType = 8664; // PROCESSOR_AMD_X8664 + lpSystemInfo->dwAllocationGranularity= 65536; + lpSystemInfo->wProcessorLevel = 6; + lpSystemInfo->wProcessorRevision = 0; +} + +typedef struct { + DWORD dwLength; + DWORD dwMemoryLoad; + unsigned long long ullTotalPhys; + unsigned long long ullAvailPhys; + unsigned long long ullTotalPageFile; + unsigned long long ullAvailPageFile; + unsigned long long ullTotalVirtual; + unsigned long long ullAvailVirtual; + unsigned long long ullAvailExtendedVirtual; +} MEMORYSTATUSEX; + +__declspec(dllexport) BOOL GetLogicalProcessorInformation(void* Buffer, + DWORD* ReturnedLength) { + (void)Buffer; + // Signal that the buffer is too small so callers know to handle failure. + if (ReturnedLength) *ReturnedLength = 0; + gLastError = 122; // ERROR_INSUFFICIENT_BUFFER + return FALSE; +} + +#define MAX_PATH 260 + +__declspec(dllexport) unsigned int GetSystemDirectoryA(char* lpBuffer, unsigned int uSize) { + static const char sysdir[] = "C:\\Windows\\System32"; + unsigned int needed = sizeof(sysdir); // includes NUL + if (lpBuffer && uSize >= needed) { + for (unsigned int i = 0; i < needed; i++) lpBuffer[i] = sysdir[i]; + } + return needed - 1; // return length without NUL, matching Win32 +} + +__declspec(dllexport) unsigned int GetWindowsDirectoryA(char* lpBuffer, unsigned int uSize) { + static const char windir[] = "C:\\Windows"; + unsigned int needed = sizeof(windir); + if (lpBuffer && uSize >= needed) { + for (unsigned int i = 0; i < needed; i++) lpBuffer[i] = windir[i]; + } + return needed - 1; +} + +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; +} + +__declspec(dllexport) char* GetEnvironmentStrings(void) { + // Double-NUL-terminated environment block. + static char env[] = "HOME=.\0USERPROFILE=.\0HOMEDRIVE=C:\0HOMEPATH=\\\0\0"; + return env; +} + +__declspec(dllexport) BOOL FreeEnvironmentStringsA(char* penv) { + (void)penv; return TRUE; +} + +__declspec(dllexport) unsigned short* GetEnvironmentStringsW(void) { + static unsigned short emptyEnvW[2] = { 0, 0 }; + return emptyEnvW; +} + +__declspec(dllexport) BOOL FreeEnvironmentStringsW(unsigned short* penv) { + (void)penv; return TRUE; +} + +// --------------------------------------------------------------------------- +// Handle / Error helpers +// --------------------------------------------------------------------------- + +__declspec(dllexport) unsigned int SetErrorMode(unsigned int uMode) { + (void)uMode; + return 0; // previous mode +} + +__declspec(dllexport) unsigned int SetHandleCount(unsigned int uNumber) { + return uNumber < 256 ? 256 : uNumber; +} + +// --------------------------------------------------------------------------- +// File helpers +// --------------------------------------------------------------------------- + +#define FILE_TYPE_UNKNOWN 0x0000 +#define FILE_TYPE_DISK 0x0001 +#define FILE_TYPE_CHAR 0x0002 +#define FILE_TYPE_PIPE 0x0003 + +#define STD_INPUT_HANDLE ((DWORD)-10) +#define STD_OUTPUT_HANDLE ((DWORD)-11) +#define STD_ERROR_HANDLE ((DWORD)-12) + +__declspec(dllexport) DWORD GetFileType(HANDLE hFile) { + if (!hFile || hFile == INVALID_HANDLE_VALUE) return FILE_TYPE_UNKNOWN; + return FILE_TYPE_DISK; +} + +// --------------------------------------------------------------------------- +// Exception Handling & Debugging +// --------------------------------------------------------------------------- + +#define EXCEPTION_CONTINUE_SEARCH 0 +#define EXCEPTION_EXECUTE_HANDLER 1 + +__declspec(dllexport) void* AddVectoredExceptionHandler(unsigned long First, void* Handler) { + (void)First; (void)Handler; + // Return a non-NULL sentinel so callers can pass it to RemoveVectoredExceptionHandler. + return (void*)(uintptr_t)0x56455843UL; // 'VEXC' +} + +__declspec(dllexport) unsigned long RemoveVectoredExceptionHandler(void* Handle) { + (void)Handle; + return 1; // success +} + +__declspec(dllexport) void RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, + DWORD nNumberOfArguments, + const uintptr_t* lpArguments) { + (void)dwExceptionCode; (void)dwExceptionFlags; + (void)nNumberOfArguments; (void)lpArguments; + // Best-effort: nothing to unwind; just halt. + while (true) ; +} + +typedef struct _EXCEPTION_POINTERS { void* ExceptionRecord; void* ContextRecord; } EXCEPTION_POINTERS; + +__declspec(dllexport) long UnhandledExceptionFilter(EXCEPTION_POINTERS* ExceptionInfo) { + (void)ExceptionInfo; + return EXCEPTION_CONTINUE_SEARCH; +} + +typedef long (*LPTOP_LEVEL_EXCEPTION_FILTER)(EXCEPTION_POINTERS*); + +__declspec(dllexport) LPTOP_LEVEL_EXCEPTION_FILTER +SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpFilter) { + (void)lpFilter; + return NULL; // previous filter: none +} + +__declspec(dllexport) BOOL IsDebuggerPresent(void) { + return FALSE; +} + +// --------------------------------------------------------------------------- +// RTL Exception / Unwind (SEH infrastructure - all stubs) +// --------------------------------------------------------------------------- + +typedef struct _RUNTIME_FUNCTION { + unsigned int BeginAddress; + unsigned int EndAddress; + unsigned int UnwindInfoAddress; +} RUNTIME_FUNCTION; + +// We have no runtime function table, so RtlLookupFunctionEntry always fails. +__declspec(dllexport) RUNTIME_FUNCTION* +RtlLookupFunctionEntry(unsigned long long ControlPc, + unsigned long long* ImageBase, + void* HistoryTable) { + (void)ControlPc; (void)HistoryTable; + if (ImageBase) *ImageBase = 0; + return NULL; +} + +__declspec(dllexport) void* RtlVirtualUnwind(unsigned long HandlerType, + unsigned long long ImageBase, + unsigned long long ControlPc, + RUNTIME_FUNCTION* FunctionEntry, + void* ContextRecord, + void** HandlerData, + unsigned long long* EstablisherFrame, + void* ContextPointers) { + (void)HandlerType; (void)ImageBase; (void)ControlPc; (void)FunctionEntry; + (void)ContextRecord; (void)ContextPointers; + if (HandlerData) *HandlerData = NULL; + if (EstablisherFrame)*EstablisherFrame = 0; + return NULL; +} + +__declspec(dllexport) void RtlCaptureContext(void* ContextRecord) { + // Zero out the context record so callers don't read garbage. + if (!ContextRecord) return; + // CONTEXT is ~1232 bytes on x64; zero a conservative upper bound. + unsigned char* p = (unsigned char*)ContextRecord; + for (int i = 0; i < 1232; i++) p[i] = 0; +} + +__declspec(dllexport) void RtlUnwindEx(void* TargetFrame, void* TargetIp, + void* ExceptionRecord, void* ReturnValue, + void* ContextRecord, void* HistoryTable) { + (void)TargetFrame; (void)TargetIp; (void)ExceptionRecord; + (void)ReturnValue; (void)ContextRecord; (void)HistoryTable; + // No real unwind possible; halt rather than corrupt the stack silently. + while (true) ; +} + +// --------------------------------------------------------------------------- +// Code Pages & Locale helpers +// --------------------------------------------------------------------------- + +typedef struct { unsigned int MaxCharSize; unsigned char DefaultChar[2]; unsigned char LeadByte[12]; } CPINFO; + +__declspec(dllexport) BOOL GetCPInfo(unsigned int CodePage, CPINFO* lpCPInfo) { + (void)CodePage; + if (lpCPInfo) { + lpCPInfo->MaxCharSize = 1; + lpCPInfo->DefaultChar[0] = '?'; + lpCPInfo->DefaultChar[1] = 0; + for (int i = 0; i < 12; i++) lpCPInfo->LeadByte[i] = 0; + } + return TRUE; +} + +__declspec(dllexport) BOOL IsValidCodePage(unsigned int CodePage) { + // Acknowledge the two code pages we claim to support. + return (CodePage == 1252 || CodePage == 437) ? TRUE : FALSE; +} + +__declspec(dllexport) int LCMapStringA(unsigned int Locale, DWORD dwMapFlags, + const char* lpSrcStr, int cchSrc, + char* lpDestStr, int cchDest) { + (void)Locale; (void)dwMapFlags; (void)lpSrcStr; (void)cchSrc; + (void)lpDestStr; (void)cchDest; + return 0; +} + +__declspec(dllexport) int LCMapStringW(unsigned int Locale, DWORD dwMapFlags, + const unsigned short* lpSrcStr, int cchSrc, + unsigned short* lpDestStr, int cchDest) { + (void)Locale; (void)dwMapFlags; (void)lpSrcStr; (void)cchSrc; + (void)lpDestStr; (void)cchDest; + return 0; +} + +__declspec(dllexport) BOOL GetStringTypeA(unsigned int Locale, DWORD dwInfoType, + const char* lpSrcStr, int cchSrc, + unsigned short* lpCharType) { + (void)Locale; (void)dwInfoType; (void)lpSrcStr; (void)cchSrc; (void)lpCharType; + return FALSE; +} + +__declspec(dllexport) BOOL GetStringTypeW(DWORD dwInfoType, + const unsigned short* lpSrcStr, int cchSrc, + unsigned short* lpCharType) { + (void)dwInfoType; (void)lpSrcStr; (void)cchSrc; (void)lpCharType; + return FALSE; +} + +__declspec(dllexport) int GetLocaleInfoA(unsigned int Locale, DWORD LCType, + char* lpLCData, int cchData) { + (void)Locale; (void)LCType; (void)lpLCData; (void)cchData; + return 0; +} + +// --------------------------------------------------------------------------- +// Fiber Local Storage (FLS) +// We implement a minimal 64-slot static table so callers that use FLS for +// thread-local-ish state don't immediately fail. +// --------------------------------------------------------------------------- + +#define FLS_MAX_SLOTS 64 +#define FLS_OUT_OF_INDEXES 0xFFFFFFFFUL + +static void* gFlsSlots[FLS_MAX_SLOTS]; +static int gFlsUsed[FLS_MAX_SLOTS]; // 0 = free, 1 = allocated + +__declspec(dllexport) DWORD FlsAlloc(void* lpCallback) { + (void)lpCallback; + for (int i = 0; i < FLS_MAX_SLOTS; i++) { + if (!gFlsUsed[i]) { + gFlsUsed[i] = 1; + gFlsSlots[i] = NULL; + return (DWORD)i; + } + } + gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY + return (DWORD)FLS_OUT_OF_INDEXES; +} + +__declspec(dllexport) void* FlsGetValue(DWORD dwFlsIndex) { + if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) { + gLastError = 87; // ERROR_INVALID_PARAMETER + return NULL; + } + return gFlsSlots[dwFlsIndex]; +} + +__declspec(dllexport) BOOL FlsSetValue(DWORD dwFlsIndex, void* lpFlsData) { + if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) { + gLastError = 87; + return FALSE; + } + gFlsSlots[dwFlsIndex] = lpFlsData; + return TRUE; +} + +__declspec(dllexport) BOOL FlsFree(DWORD dwFlsIndex) { + if (dwFlsIndex >= FLS_MAX_SLOTS || !gFlsUsed[dwFlsIndex]) { + gLastError = 87; + return FALSE; + } + gFlsUsed[dwFlsIndex] = 0; + gFlsSlots[dwFlsIndex] = NULL; + return TRUE; +} + +// --------------------------------------------------------------------------- +// Pointer encoding (security cookie obfuscation) +// We have no security cookie; return the pointer unchanged. +// --------------------------------------------------------------------------- + +__declspec(dllexport) void* EncodePointer(void* Ptr) { return Ptr; } +__declspec(dllexport) void* DecodePointer(void* Ptr) { return Ptr; } diff --git a/readme.md b/readme.md index d1a4688..6c13c94 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,7 @@ # NT-Lite -- an x86-64 kernel +## The Monster From ~~Redmond~~ Kraków + Boots. Runs processes. Mimics Windows to some extent. ## what is it? @@ -17,7 +19,7 @@ The codebase is roughly 20k lines of C and assembly. - preemptive scheduler with priority levels - processes and threads - VFS with IRPs -- drive-letter paths such as `0:\apps\shell.exe` +- drive-letter paths such as `0:/apps/shell.exe` - INI-based boot and driver configuration - PE import resolution and dynamic driver loading - partial Win32 personality @@ -52,7 +54,7 @@ Currently shipping: AHCI (interrupts), IDE (PIO/DMA), and FAT32. All file access flows through the VFS layer. That keeps disk access, filesystem parsing, and path translation separate instead of spreading storage logic across the rest of the kernel. -Paths are not interpreted as Windows paths by default. A drive such as `0:\` is a mounted namespace, while the Win32 and POSIX personalities translate their own path styles into the same underlying VFS. +Paths are not interpreted as Windows paths by default. A drive such as `0:/` is a mounted namespace, while the Win32 and POSIX personalities translate their own path styles into the same underlying VFS. ### processes, threads, and syscalls @@ -84,9 +86,9 @@ The Win32 personality supports programs that expect drive letters, familiar proc ## paths ```text -"apps\shell.exe" -> current working directory -"0:\apps\shell.exe" -> drive 0 -"2:\data\config.ini" -> drive 2 +"apps/shell.exe" -> current working directory +"0:/apps/shell.exe" -> drive 0 +"2:/data/config.ini" -> drive 2 ``` Drives 0-7 are available. A drive only works if a filesystem is registered on it. The Win32 personality translates drive letters to drive numbers, while the POSIX personality maps drives onto a tree. @@ -290,6 +292,9 @@ A: Learning. Fun. Q: Does it run Doom? A: `doom.exe` +Q: How often does it crash? +A: Yes. + Q: Is the Win32 layer real? A: Real enough to load binaries that expect it. Not real enough to run Notepad. @@ -299,7 +304,7 @@ A: No. But IPC is free. Q: Is this finished? A: No. -Will it corrupt my data? +Q: Will it corrupt my data? A: It shouldn't. Q: Why is the path syntax so weird? @@ -313,3 +318,5 @@ A: This document. Q: Can I use this in production? A: Don't. + +#### Projekt dedykowany dla szanownego Pana profesora Leszka Wójcika przez którego znienawidziłem Unixa.