From 8c7564d5e191c9fe2f3238d6974bb8dfb8660f63 Mon Sep 17 00:00:00 2001 From: Maksymilian Wilczynski Date: Mon, 4 May 2026 16:27:10 +0200 Subject: [PATCH] add /IGNORE:4216 to HAL link step, add writing and directory listing to the FAT32 driver, add the concept of current working directory to the VFS, add writing to the VFS, more operations in the shell, rename KeCurrentThread to KeGetCurrentThread and add KeGetCurrentProcess, add STATUS_NO_MORE_FILES, change KeFault* to KePanic, add syscalls for writing to files, add a proper syscall to writing to the console, move Screen.Clear to after memory status debug print, add ObjectTypeDir to OBJECT_TYPE, extend kernel32 by file operations and upgrade user32 (and add gdi32) to match windows a bit more closely, change CreateProcessA to accept the first argument as NULL. --- OVMF/OVMF_VARS_4M_d.fd | Bin 540672 -> 540672 bytes build.bat | 2 +- code/drivers/fat32/fat32.c | 987 +++++++++++++++++++++++++++------ code/kernel/io_file.c | 299 +++++++++- code/kernel/io_file.h | 72 +++ code/kernel/ke_debug.c | 9 + code/kernel/ke_debug.h | 3 +- code/kernel/ke_event.c | 2 +- code/kernel/ke_mutex.c | 4 +- code/kernel/ke_process.c | 9 + code/kernel/ke_process.h | 6 +- code/kernel/ke_queue.c | 4 +- code/kernel/ke_sched.c | 6 +- code/kernel/ke_sched.h | 2 +- code/kernel/ke_semaphore.c | 2 +- code/kernel/ke_status.h | 3 + code/kernel/ke_switch.asm | 4 +- code/kernel/ke_syscall.asm | 17 +- code/kernel/ke_syscall.c | 206 ++++++- code/kernel/ke_thread.c | 2 +- code/kernel/ke_thread.h | 2 +- code/kernel/kmain.c | 4 +- code/kernel/ob_object.h | 1 + code/kernel/syscall_nr.h | 25 +- code/libc.c | 9 + code/shell/shell.c | 185 +++++- code/syscall/syscall_nr.inc | 18 +- code/syscall/syscall_stubs.asm | 22 + code/video/video.c | 208 +++++-- code/win32/gdi32.c | 83 +++ code/win32/kernel32.c | 486 ++++++++++++++-- code/win32/user32.c | 86 ++- readme.md | 4 +- 33 files changed, 2343 insertions(+), 429 deletions(-) create mode 100644 code/win32/gdi32.c diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 2b8c7a4a7abe5fe46bdb6eb4aeb47674742a8a81..62c1822754565bf63a4119d21b1ade6bc13bac15 100644 GIT binary patch delta 566 zcmZo@P;6*W+;GTovYDaNW)`PQT$BGyPMcYrE^)Qr+rbFLOxy46U{(rmFWASpy #include #include @@ -22,15 +21,14 @@ typedef struct { uint8_t SectorsPerCluster; uint16_t ReservedSectors; uint8_t NumFats; - uint16_t RootEntryCount; // 0 for FAT32 + uint16_t RootEntryCount; uint16_t TotalSectors16; uint8_t MediaType; - uint16_t FatSize16; // 0 for FAT32 + uint16_t FatSize16; uint16_t SectorsPerTrack; uint16_t NumHeads; uint32_t HiddenSectors; uint32_t TotalSectors32; - // FAT32 extended BPB uint32_t FatSize32; uint16_t ExtFlags; uint16_t FsVersion; @@ -65,7 +63,7 @@ typedef struct { typedef struct { uint8_t Order; uint16_t Name1[5]; - uint8_t Attr; // 0x0F + uint8_t Attr; uint8_t Type; uint8_t Checksum; uint16_t Name2[6]; @@ -73,7 +71,6 @@ typedef struct { uint16_t Name3[2]; } LFN_ENTRY; -// GPT Structures typedef struct { uint32_t Data1; uint16_t Data2; @@ -82,7 +79,7 @@ typedef struct { } EFI_GUID; typedef struct { - char Signature[8]; // "EFI PART" + char Signature[8]; uint32_t Revision; uint32_t HeaderSize; uint32_t HeaderCRC32; @@ -109,112 +106,286 @@ typedef struct { #pragma pack(pop) -#define ATTR_DIRECTORY 0x10 -#define ATTR_LFN 0x0F -#define FAT32_EOC 0x0FFFFFF8 +#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 // --------------------------------------------------------------------------- // Driver state // --------------------------------------------------------------------------- typedef struct { uint8_t DriveIndex; - uint32_t PartitionLba; // LBA of the partition's first sector + uint32_t PartitionLba; uint32_t BytesPerSector; uint32_t SectorsPerCluster; uint32_t BytesPerCluster; uint32_t ReservedSectors; uint32_t NumFats; - uint32_t FatSize; // in sectors + uint32_t FatSize; // sectors per FAT copy uint32_t RootCluster; - uint32_t DataStart; // LBA of cluster 2 + uint32_t DataStart; // absolute LBA of cluster 2 + uint32_t TotalClusters; // usable data clusters const HAL_API* Hal; } FAT32_VOL; typedef struct { - uint32_t StartCluster; - uint32_t FileSize; + uint32_t StartCluster; + uint32_t FileSize; FAT32_VOL* Vol; + // Location of this file's DIRENT on disk (for in-place updates). + uint64_t DirentLba; // LBA of the sector containing the DIRENT + uint32_t DirentOffset; // byte offset of the DIRENT within that sector } FAT32_FILE; // --------------------------------------------------------------------------- -// Helpers +// Low-level sector I/O // --------------------------------------------------------------------------- static KSTATUS ReadSectors(FAT32_VOL* v, uint64_t lba, - uint32_t count, void* buf) { - if (!v || !v->Hal || !v->Hal->Disk.ReadSectors) { + 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 WriteSectors(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); +} + static uint64_t ClusterToLba(FAT32_VOL* v, uint32_t cluster) { return v->DataStart + (uint64_t)(cluster - 2) * v->SectorsPerCluster; } -// Read the FAT entry for a given cluster. Returns 0 on error. +// --------------------------------------------------------------------------- +// FAT read / write +// --------------------------------------------------------------------------- + +// Returns the FAT value for 'cluster', masked to 28 bits. Returns 0 on error. static uint32_t FatEntry(FAT32_VOL* v, uint32_t cluster) { uint32_t fatOffset = cluster * 4; - uint32_t sector = v->PartitionLba + v->ReservedSectors + uint64_t sector = v->PartitionLba + v->ReservedSectors + fatOffset / v->BytesPerSector; uint32_t offInSec = fatOffset % v->BytesPerSector; - uint8_t* sec = (uint8_t *)MmAllocatePool(v->BytesPerSector); + uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); if (!sec) return 0; if (!KSUCCESS(ReadSectors(v, sector, 1, sec))) { - DbgPrint("fat32: FatEntry: ReadSectors failed!\n"); + MmFreePool(sec); + return 0; } - uint32_t val = *(uint32_t *)(sec + offInSec) & 0x0FFFFFFF; + uint32_t val = *(uint32_t*)(sec + offInSec) & 0x0FFFFFFF; MmFreePool(sec); return val; } +// Write 'value' (28-bit) into every FAT copy for 'cluster'. +// Preserves the upper nibble of the existing entry as required by the spec. +static KSTATUS WriteFatEntry(FAT32_VOL* v, uint32_t cluster, uint32_t value) { + uint32_t fatOffset = cluster * 4; + uint32_t sectorIdx = fatOffset / v->BytesPerSector; + uint32_t offInSec = fatOffset % v->BytesPerSector; + + uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); + if (!sec) return STATUS_NO_MEMORY; + + for (uint32_t f = 0; f < v->NumFats; f++) { + uint64_t lba = (uint64_t)v->PartitionLba + v->ReservedSectors + + (uint64_t)f * v->FatSize + sectorIdx; + + KSTATUS s = ReadSectors(v, lba, 1, sec); + if (!KSUCCESS(s)) { MmFreePool(sec); return s; } + + // Upper nibble is reserved; preserve it. + uint32_t existing = *(uint32_t*)(sec + offInSec); + *(uint32_t*)(sec + offInSec) = (existing & 0xF0000000u) + | (value & 0x0FFFFFFFu); + + s = WriteSectors(v, lba, 1, sec); + if (!KSUCCESS(s)) { MmFreePool(sec); return s; } + } + + MmFreePool(sec); + return STATUS_SUCCESS; +} + // --------------------------------------------------------------------------- -// 8.3 name matching +// Cluster allocation // --------------------------------------------------------------------------- + +// Scan FAT for a free cluster, mark it EOC, zero its data, and return it. +// Returns 0 if the disk is full or an error occurs. +static uint32_t AllocCluster(FAT32_VOL* v) { + uint8_t* sec = (uint8_t*)MmAllocatePool(v->BytesPerSector); + if (!sec) return 0; + + uint32_t entriesPerSector = v->BytesPerSector / 4; + uint64_t fatStart = (uint64_t)v->PartitionLba + v->ReservedSectors; + uint32_t found = 0; + + for (uint32_t s = 0; s < v->FatSize && !found; s++) { + if (!KSUCCESS(ReadSectors(v, fatStart + s, 1, sec))) break; + for (uint32_t i = 0; i < entriesPerSector; i++) { + uint32_t cluster = s * entriesPerSector + i; + if (cluster < 2) continue; + if (v->TotalClusters && cluster >= v->TotalClusters + 2) goto done; + uint32_t val = *(uint32_t*)(sec + i * 4) & 0x0FFFFFFF; + if (val == FAT32_FREE) { found = cluster; break; } + } + } +done: + MmFreePool(sec); + if (!found) return 0; + + // Mark the cluster as end-of-chain. + if (!KSUCCESS(WriteFatEntry(v, found, FAT32_EOC_MARK))) return 0; + + // Zero the cluster data (mandatory for correctness in directories). + uint8_t* zero = (uint8_t*)MmAllocatePool(v->BytesPerCluster); + if (zero) { + memset(zero, 0, v->BytesPerCluster); + WriteSectors(v, ClusterToLba(v, found), v->SectorsPerCluster, zero); + MmFreePool(zero); + } + return found; +} + +// Allocate a new cluster and link it after 'lastCluster'. Returns 0 on failure. +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; +} + +// Walk the chain starting at 'start'; return the last cluster and optionally +// the total cluster count. +static uint32_t GetChainEnd(FAT32_VOL* v, uint32_t start, + uint32_t* outCount) { + uint32_t cluster = start; + uint32_t prev = start; + uint32_t count = 0; + while (cluster >= 2 && cluster < FAT32_EOC) { + prev = cluster; + count++; + cluster = FatEntry(v, cluster); + } + if (outCount) *outCount = count; + return prev; +} + +// Free every cluster in the chain starting at 'start'. +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); + cluster = next; + } +} + +// --------------------------------------------------------------------------- +// Directory entry (DIRENT) on-disk 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++) { + for (int i = 0; i < 8 && dirName[i] != ' '; i++) buf[n++] = (char)(dirName[i] >= 'A' && dirName[i] <= 'Z' - ? dirName[i] + 32 : dirName[i]); - } + ? dirName[i] + 32 : dirName[i]); if (dirExt[0] != ' ') { buf[n++] = '.'; - for (int i = 0; i < 3 && dirExt[i] != ' '; i++) { + 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++; + 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'; + return *want == '\0' && *p == '\0'; +} + +// Generate a space-padded, uppercased 8.3 name from a plain filename. +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 name assembly +// LFN assembly / matching // --------------------------------------------------------------------------- -#define MAX_LFN 256 +#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; } @@ -229,17 +400,22 @@ static int LfnMatch(const char* lfn, const char* want) { if (b >= 'A' && b <= 'Z') b += 32; if (a != b) return 0; } - return* lfn == '\0' && *want == '\0'; + return *lfn == '\0' && *want == '\0'; } // --------------------------------------------------------------------------- // Directory search +// Passes back the on-disk location of the matched DIRENT when outDirentLba +// and outDirentOffset are non-NULL. // --------------------------------------------------------------------------- static int SearchDir(FAT32_VOL* v, uint32_t dirCluster, const char* name, uint32_t* outCluster, uint32_t* outSize, - uint8_t* outAttr) { - uint8_t* clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + uint8_t* outAttr, + uint64_t* outDirentLba, // nullable + uint32_t* outDirentOffset) // nullable +{ + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) return 0; char lfn[MAX_LFN]; @@ -247,41 +423,47 @@ static int SearchDir(FAT32_VOL* v, uint32_t dirCluster, memset(lfn, 0, sizeof(lfn)); uint32_t cluster = dirCluster; - while (cluster < FAT32_EOC) { - if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, cluster), - v->SectorsPerCluster, clusterBuf))) { + 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; + DIRENT* dir = (DIRENT*)clusterBuf; for (uint32_t i = 0; i < entries; i++) { - if (dir[i].Name[0] == 0x00) goto done; // end of directory - if ((uint8_t)dir[i].Name[0] == 0xE5) { // deleted + 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]); + 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); - } + 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; + *outCluster = ((uint32_t)dir[i].FirstClusterHi << 16) + | dir[i].FirstClusterLo; *outSize = dir[i].FileSize; *outAttr = dir[i].Attr; + + // Compute the exact on-disk location of this DIRENT. + if (outDirentLba || outDirentOffset) { + uint64_t entryByte = (uint64_t)i * sizeof(DIRENT); + if (outDirentLba) + *outDirentLba = clusterLba + entryByte / v->BytesPerSector; + if (outDirentOffset) + *outDirentOffset = (uint32_t)(entryByte % v->BytesPerSector); + } + MmFreePool(clusterBuf); return 1; } @@ -294,14 +476,21 @@ done: } // --------------------------------------------------------------------------- -// Path traversal +// Path traversal (with optional DIRENT-location output) // --------------------------------------------------------------------------- -static KSTATUS Traverse(FAT32_VOL* v, const char* path, - uint32_t* outCluster, uint32_t* outSize) { +static KSTATUS TraverseEx(FAT32_VOL* v, const char* path, + uint32_t* outCluster, uint32_t* outSize, + uint64_t* outDirentLba, // nullable + uint32_t* outDirentOffset) // nullable +{ uint32_t cluster = v->RootCluster; uint32_t size = 0; uint8_t attr = ATTR_DIRECTORY; + // Sentinel values: root directory has no DIRENT of its own. + if (outDirentLba) *outDirentLba = 0; + if (outDirentOffset) *outDirentOffset = 0; + char component[256]; const char* p = path; @@ -319,12 +508,18 @@ static KSTATUS Traverse(FAT32_VOL* v, const char* path, uint32_t childCluster, childSize; uint8_t childAttr; - if (!SearchDir(v, cluster, component, &childCluster, &childSize, &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; @@ -332,20 +527,106 @@ static KSTATUS Traverse(FAT32_VOL* v, const char* path, 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); +} + // --------------------------------------------------------------------------- -// IO_FILESYSTEM vtable implementations +// Path helpers for Fat32Create // --------------------------------------------------------------------------- -static KSTATUS Fat32Open(IO_FILESYSTEM* fs, const char* path, void **fileCtx) { - FAT32_VOL* v = (FAT32_VOL *)fs->DriverCtx; + +// Split "/dir/sub/file.txt" into parent="/dir/sub" and name="file.txt". +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 in the directory starting at 'dirCluster'. +// Extends the directory with a new cluster if no slot is available. +// Returns 1 on success and fills *outLba / *outOffset. +// --------------------------------------------------------------------------- +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; + uint32_t 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 entryByte = (uint64_t)i * sizeof(DIRENT); + *outLba = clusterLba + entryByte / v->BytesPerSector; + *outOffset = (uint32_t)(entryByte % v->BytesPerSector); + MmFreePool(clusterBuf); + return 1; + } + } + + prevCluster = cluster; + cluster = FatEntry(v, cluster); + } + + // No free slot — extend the directory with one more cluster. + uint32_t newCluster = AppendCluster(v, prevCluster); + if (newCluster) { + *outLba = ClusterToLba(v, newCluster); + *outOffset = 0; + MmFreePool(clusterBuf); + return 1; + } + + MmFreePool(clusterBuf); + return 0; +} + +// --------------------------------------------------------------------------- +// IO_FILESYSTEM vtable — read path (unchanged logic, updated signatures) +// --------------------------------------------------------------------------- + +static KSTATUS Fat32Open(IO_FILESYSTEM* fs, const char* path, void** fileCtx) { + FAT32_VOL* v = (FAT32_VOL*)fs->DriverCtx; uint32_t cluster, size; - KSTATUS s = Traverse(v, path, &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)); + 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; @@ -355,41 +636,36 @@ 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; + 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); - uint8_t* clusterBuf = (uint8_t *)MmAllocatePool(v->BytesPerCluster); + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); if (!clusterBuf) 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; + 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 < FAT32_EOC) { + while (size > 0 && cluster >= 2 && cluster < FAT32_EOC) { if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, cluster), - v->SectorsPerCluster, clusterBuf))) { + v->SectorsPerCluster, clusterBuf))) break; - } - size_t avail = v->BytesPerCluster - offsetInCluster; size_t copy = avail < size ? avail : size; - memcpy(out, clusterBuf + offsetInCluster, copy); out += copy; totalRead += copy; size -= copy; - offsetInCluster = 0; - - cluster = FatEntry(v, cluster); + offsetInCluster = 0; + cluster = FatEntry(v, cluster); } MmFreePool(clusterBuf); @@ -397,9 +673,263 @@ static KSTATUS Fat32Read(IO_FILESYSTEM* fs, void* fileCtx, return STATUS_SUCCESS; } +// --------------------------------------------------------------------------- +// IO_FILESYSTEM vtable — write path (new) +// --------------------------------------------------------------------------- + +// Write up to 'size' bytes at 'offset' in the file, extending it as needed. +// Performs a read-modify-write for any partial cluster at the head or tail. +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 the file has at least one cluster. + 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 the cluster chain to the cluster that contains 'offset', + // allocating new clusters along the way if required. + 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; + } + + // Write loop — one cluster per iteration. + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); + if (!clusterBuf) { *bytesWritten = 0; return STATUS_NO_MEMORY; } + + size_t written = 0; + size_t remaining = size; + + while (remaining > 0 && cluster >= 2) { + // Read first when this is a partial-cluster write (head or tail). + int partial = (offsetInCluster > 0) || + (remaining < v->BytesPerCluster); + if (partial) { + if (!KSUCCESS(ReadSectors(v, ClusterToLba(v, cluster), + v->SectorsPerCluster, clusterBuf))) { + break; + } + } + + size_t avail = v->BytesPerCluster - offsetInCluster; + size_t copy = avail < remaining ? avail : remaining; + + memcpy(clusterBuf + offsetInCluster, src, copy); + + if (!KSUCCESS(WriteSectors(v, ClusterToLba(v, cluster), + v->SectorsPerCluster, clusterBuf))) { + break; + } + + src += copy; + written += copy; + remaining -= copy; + offsetInCluster = 0; + + if (remaining > 0) { + uint32_t next = FatEntry(v, cluster); + if (next < 2 || next >= FAT32_EOC) { + next = AppendCluster(v, cluster); + if (!next) break; // disk full + } + cluster = next; + } + } + + MmFreePool(clusterBuf); + + // Update file size in the directory entry if we extended the file. + 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 a new file at 'path'. The parent directory must already exist. +// Returns an open FAT32_FILE context on success. +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; + + // Resolve the parent directory. + uint32_t parentCluster, parentSize; + KSTATUS s = Traverse(v, parentPath, &parentCluster, &parentSize); + if (!KSUCCESS(s)) return s; + + // Reject if a file with that name already exists. + { + uint32_t existCluster, existSize; + uint8_t existAttr; + if (SearchDir(v, parentCluster, filename, &existCluster, &existSize, + &existAttr, NULL, NULL)) + return STATUS_OBJECT_NAME_COLLISION; + } + + // Find (or create) a free slot in the parent directory. + uint64_t direntLba; + uint32_t direntOffset; + if (!FindFreeDirent(v, parentCluster, &direntLba, &direntOffset)) + return STATUS_NO_MEMORY; + + // Build and write the 8.3 directory entry. + 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; + d->FirstClusterHi = 0; + d->FirstClusterLo = 0; + d->FileSize = 0; + + s = WriteSectors(v, direntLba, 1, sec); + MmFreePool(sec); + if (!KSUCCESS(s)) return s; + + // Allocate and return a file context. + 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 or extend the file to exactly 'newSize' bytes. +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; + return DirentUpdateSize(v, f->DirentLba, f->DirentOffset, 0); + } + + uint32_t newClusters = (uint32_t) + ((newSize + v->BytesPerCluster - 1) / v->BytesPerCluster); + + if (newSize < f->FileSize) { + // Shorten: walk to the new last cluster, then free the tail. + if (f->StartCluster < 2) goto update_size; // nothing to free + + uint32_t cluster = f->StartCluster; + uint32_t lastGood = cluster; + for (uint32_t i = 1; i < newClusters && cluster < FAT32_EOC; i++) { + lastGood = cluster; + cluster = FatEntry(v, cluster); + } + // lastGood is now the new end-of-chain. + uint32_t tail = FatEntry(v, lastGood); + WriteFatEntry(v, lastGood, FAT32_EOC_MARK); + FreeClusterChain(v, tail); + + } else { + // Extend: allocate the extra clusters (AllocCluster zeroes them). + uint32_t curClusters = f->FileSize + ? (f->FileSize + v->BytesPerCluster - 1) / v->BytesPerCluster + : 0; + + uint32_t lastCluster = 0; + + if (f->StartCluster < 2) { + // Empty file — allocate the first cluster. + 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; + return DirentUpdateSize(v, f->DirentLba, f->DirentOffset, (uint32_t)newSize); +} + +// Delete the file at 'path': marks its DIRENT as deleted and frees its clusters. +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; // root directory + + // Mark the DIRENT as deleted (0xE5). + 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] = (uint8_t)0xE5; + s = WriteSectors(v, direntLba, 1, sec); + } + MmFreePool(sec); + if (!KSUCCESS(s)) return s; + + // Release the cluster chain. + if (cluster >= 2) FreeClusterChain(v, cluster); + return STATUS_SUCCESS; +} + static uint64_t Fat32GetSize(IO_FILESYSTEM* fs, void* fileCtx) { (void)fs; - return ((FAT32_FILE *)fileCtx)->FileSize; + return ((FAT32_FILE*)fileCtx)->FileSize; } static void Fat32Close(IO_FILESYSTEM* fs, void* fileCtx) { @@ -408,119 +938,227 @@ static void Fat32Close(IO_FILESYSTEM* fs, void* fileCtx) { } // --------------------------------------------------------------------------- -// Partition scan - Supports MBR and GPT +// Directory enumeration +// --------------------------------------------------------------------------- + +typedef struct { + FAT32_VOL* Vol; + uint32_t DirCluster; // start cluster of the directory + uint32_t CurCluster; // cluster currently being scanned + uint32_t EntryIndex; // entry index within the current cluster + char LfnBuf[MAX_LFN]; // accumulated LFN for the next entry + int HasLfn; +} FAT32_DIR_CTX; + +static KSTATUS Fat32OpenDir(IO_FILESYSTEM* fs, const char* dirPath, void** dirCtx) { + FAT32_VOL* v = (FAT32_VOL*)fs->DriverCtx; + + uint32_t cluster, size; + KSTATUS s; + + if (!dirPath || dirPath[0] == '\0') { + // Empty path = root directory + cluster = v->RootCluster; + } else { + s = Traverse(v, dirPath, &cluster, &size); + if (!KSUCCESS(s)) return s; + // The path must refer to a directory (size==0 for dirs in FAT32) + // Traverse doesn't give us the attr here, but root-style open is fine; + // if the caller passes a file path we'll just enumerate its "cluster" + // harmlessly and get zero results. + } + + FAT32_DIR_CTX* ctx = (FAT32_DIR_CTX*)MmAllocatePool(sizeof(FAT32_DIR_CTX)); + if (!ctx) return STATUS_NO_MEMORY; + + ctx->Vol = v; + ctx->DirCluster = cluster; + ctx->CurCluster = cluster; + 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; + + uint8_t* clusterBuf = (uint8_t*)MmAllocatePool(v->BytesPerCluster); + if (!clusterBuf) return STATUS_NO_MEMORY; + + uint32_t entriesPerCluster = v->BytesPerCluster / sizeof(DIRENT); + + while (ctx->CurCluster >= 2 && ctx->CurCluster < FAT32_EOC) { + uint64_t clusterLba = ClusterToLba(v, ctx->CurCluster); + if (!KSUCCESS(ReadSectors(v, clusterLba, v->SectorsPerCluster, clusterBuf))) + break; + + DIRENT* dir = (DIRENT*)clusterBuf; + + while (ctx->EntryIndex < entriesPerCluster) { + DIRENT* e = &dir[ctx->EntryIndex]; + ctx->EntryIndex++; + + // End-of-directory marker + if (e->Name[0] == 0x00) { + MmFreePool(clusterBuf); + ctx->CurCluster = FAT32_EOC; // mark exhausted + return STATUS_NO_MORE_FILES; + } + + // Deleted entry + if ((uint8_t)e->Name[0] == 0xE5) { + ctx->HasLfn = 0; + memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); + continue; + } + + // LFN entry + if (e->Attr == ATTR_LFN) { + LfnAppend(ctx->LfnBuf, (LFN_ENTRY*)e); + ctx->HasLfn = 1; + continue; + } + + // Skip volume label (attr 0x08) + if (e->Attr & 0x08 && !(e->Attr & ATTR_DIRECTORY)) { + ctx->HasLfn = 0; + memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); + continue; + } + + // Build display name: prefer LFN, fall back to 8.3 + 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 { + // Reconstruct 8.3 name + 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)); + + MmFreePool(clusterBuf); + return STATUS_SUCCESS; + } + + // Move to next cluster + ctx->CurCluster = FatEntry(v, ctx->CurCluster); + ctx->EntryIndex = 0; + ctx->HasLfn = 0; + memset(ctx->LfnBuf, 0, sizeof(ctx->LfnBuf)); + } + + MmFreePool(clusterBuf); + return STATUS_NO_MORE_FILES; +} + +static void Fat32CloseDir(IO_FILESYSTEM* fs, void* dirCtx) { + (void)fs; + MmFreePool(dirCtx); +} + +// --------------------------------------------------------------------------- +// Partition scan (MBR + GPT) — unchanged // --------------------------------------------------------------------------- static const EFI_GUID GUID_EFI_SYSTEM = { - 0xC12A7328, 0xF81F, 0x11D2, {0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B} + 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} + 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} + 0x0FC63DAF, 0x8483, 0x4772, {0x8E,0x79,0x3D,0x69,0xD8,0x47,0x7D,0xE4} }; -// Validates whether the VBR actually contains a FAT32 BPB 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; + BPB* bpb = (BPB*)sector; if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) return 0; if (bpb->FatSize32 == 0) return 0; - return 1; } 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; - KSTATUS s = ReadSectors(v, 0, 1, mbr); - if (!KSUCCESS(s)) { - DbgPrint("FAT32: [DEBUG] ReadSectors for MBR failed.\n"); - return 0; - } - - if (mbr[510] != 0x55 || mbr[511] != 0xAA) { - DbgPrint("FAT32: [DEBUG] Invalid signature at LBA 0.\n"); - return 0; - } - - // Check for GPT Protective MBR (Type 0xEE) int isGpt = 0; - for (int i = 0; i < 4; i++) { - if (mbr[0x1BE + i * 16 + 4] == 0xEE) { - isGpt = 1; - break; - } - } + for (int i = 0; i < 4; i++) + if (mbr[0x1BE + i * 16 + 4] == 0xEE) { isGpt = 1; break; } if (isGpt) { - DbgPrint("FAT32: [DEBUG] Protective MBR found. Parsing GPT...\n"); 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; - GPT_HEADER* gpt = (GPT_HEADER *)gptSector; - if (memcmp(gpt->Signature, "EFI PART", 8) != 0) { - DbgPrint("FAT32: [DEBUG] Invalid GPT signature.\n"); - return 0; - } - - uint64_t entryLBA = gpt->PartitionEntryLBA; - uint32_t numEntries = gpt->NumberOfPartitionEntries; - uint32_t entrySize = gpt->SizeOfPartitionEntry; - + uint64_t entryLBA = gpt->PartitionEntryLBA; + uint32_t numEntries = gpt->NumberOfPartitionEntries; + uint32_t entrySize = gpt->SizeOfPartitionEntry; if (entrySize < sizeof(GPT_ENTRY)) return 0; uint32_t entriesPerSector = 512 / entrySize; - uint8_t entrySector[512]; + uint8_t entrySector[512]; for (uint32_t i = 0; i < numEntries; i++) { if (i % entriesPerSector == 0) { - if (!KSUCCESS(ReadSectors(v, entryLBA + (i / entriesPerSector), 1, entrySector))) { + if (!KSUCCESS(ReadSectors(v, entryLBA + i / entriesPerSector, + 1, entrySector))) return 0; - } } - - GPT_ENTRY* entry = (GPT_ENTRY *)(entrySector + (i % entriesPerSector) * entrySize); - + GPT_ENTRY* entry = (GPT_ENTRY*) + (entrySector + (i % entriesPerSector) * entrySize); static const EFI_GUID emptyGuid = {0}; - if (memcmp(&entry->PartitionTypeGUID, &emptyGuid, sizeof(EFI_GUID)) == 0) continue; - - // Microsoft Basic Data maps to FAT, NTFS, and exFAT. VBR validation checks true FAT32 nature. - 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) { - + 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)) { - DbgPrint("FAT32: [DEBUG] Found FAT32 partition via GPT at LBA %u\n", lba); - return lba; - } + if (IsFat32Vbr(v, lba)) return lba; } } - DbgPrint("FAT32: [DEBUG] No FAT32 partition found in GPT.\n"); return 0; } - // Fallback: MBR Parsing for (int i = 0; i < 4; i++) { uint8_t* entry = mbr + 0x1BE + i * 16; uint8_t type = entry[4]; - if (type == 0x0B || type == 0x0C) { - uint32_t lba = *(uint32_t *)(entry + 8); - if (IsFat32Vbr(v, lba)) { - DbgPrint("FAT32: [DEBUG] Found FAT32 partition via MBR at LBA %u\n", lba); - return lba; - } + uint32_t lba = *(uint32_t*)(entry + 8); + if (IsFat32Vbr(v, lba)) return lba; } } - - DbgPrint("FAT32: [DEBUG] No FAT32 partition found in MBR.\n"); - return 0; + return 0; } // --------------------------------------------------------------------------- @@ -533,30 +1171,23 @@ KSTATUS DriverEntry(const HAL_API* hal) { DbgPrint("FAT32: Init\n"); if (!hal) return STATUS_INVALID_PARAMETER; - gVol.Hal = hal; + gVol.Hal = hal; gVol.DriveIndex = 0; - - uint32_t partLba = FindFat32Partition(&gVol); - if (partLba == 0) { - DbgPrint("FAT32: [DEBUG] Could not locate a valid FAT32 volume.\n"); + + 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))) { - DbgPrint("FAT32: [DEBUG] ReadSectors FAILED!\n"); + if (!KSUCCESS(ReadSectors(&gVol, partLba, 1, sector))) return STATUS_DEVICE_ERROR; - } - BPB* bpb = (BPB *)sector; - - // IsFat32Vbr guarantees these pass, but retained for absolute sanity checking. - if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) { - DbgPrint("FAT32: [DEBUG] Validation failed - Not a FAT32 volume.\n"); + BPB* bpb = (BPB*)sector; + if (bpb->FatSize16 != 0 || bpb->RootEntryCount != 0) return STATUS_NOT_SUPPORTED; - } gVol.BytesPerSector = bpb->BytesPerSector; gVol.SectorsPerCluster = bpb->SectorsPerCluster; @@ -569,23 +1200,33 @@ KSTATUS DriverEntry(const HAL_API* hal) { + bpb->ReservedSectors + bpb->NumFats * bpb->FatSize32; + 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; - // Populate IO_VOLUME_PARAMS IO_VOLUME_PARAMS volParams; - volParams.DiskIndex = gVol.DriveIndex; + volParams.DiskIndex = gVol.DriveIndex; volParams.PartitionLba = partLba; - // FAT32 sizes are stored in TotalSectors32 if TotalSectors16 is 0 - volParams.PartitionSize = (bpb->TotalSectors16 != 0) ? bpb->TotalSectors16 : bpb->TotalSectors32; + volParams.PartitionSize = totalSectors; - // Pass the new volParams structure KSTATUS status = IoRegisterFilesystem(gVol.DriveIndex, &gFs, &volParams); - DbgPrint("FAT32: [DEBUG] IoRegisterFilesystem returned: %d\n", status); - - DbgPrint("FAT32: Init complete.\n"); + DbgPrint("FAT32: Init complete (status=%d, clusters=%u).\n", + status, gVol.TotalClusters); return STATUS_SUCCESS; } diff --git a/code/kernel/io_file.c b/code/kernel/io_file.c index 1b6a19e..d68a4cf 100644 --- a/code/kernel/io_file.c +++ b/code/kernel/io_file.c @@ -1,6 +1,7 @@ // io_file.c - VFS dispatch layer #include #include +#include #include #include #include @@ -14,6 +15,142 @@ typedef struct { 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; + if (path[0] >= '0' && path[0] <= '7' && + path[1] == ':' && (path[2] == '\\' || path[2] == '/')) { + drive = (uint8_t)(path[0] - '0'); + path = path + 3; + } + + char work[CWD_MAX * 2]; + work[0] = '\0'; + + // Initialize our working base + if (path[0] == '\\' || path[0] == '/') { + // 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 // --------------------------------------------------------------------------- @@ -29,20 +166,6 @@ __declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t volumeIndex, return STATUS_SUCCESS; } -// --------------------------------------------------------------------------- -// Path parsing - "0:\apps\shell.exe" or just "apps\shell.exe" (drive 0) -// --------------------------------------------------------------------------- -static uint8_t ParseDrive(const char* path, const char **pathOut) { - // Accept "N:\..." prefix, otherwise default to drive 0. - if (path[0] >= '0' && path[0] <= '7' && - path[1] == ':' && (path[2] == '\\' || path[2] == '/')) { - *pathOut = path + 3; - return (uint8_t)(path[0] - '0'); - } - *pathOut = path; - return 0; -} - // --------------------------------------------------------------------------- // Destructor // --------------------------------------------------------------------------- @@ -57,8 +180,8 @@ static void FileDestroy(OBJECT* obj) { __declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) { if (!path || !out) return STATUS_INVALID_PARAMETER; - const char* relPath; - uint8_t drive = ParseDrive(path, &relPath); + char relPath[CWD_MAX]; + uint8_t drive = ResolvePath(path, relPath, sizeof(relPath)); IO_FILESYSTEM* fs = gVolumes[drive].Fs; if (!fs) return STATUS_NOT_FOUND; @@ -97,6 +220,90 @@ __declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t si 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); +} + +// --------------------------------------------------------------------------- +// 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; @@ -111,3 +318,63 @@ __declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file) { __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/io_file.h b/code/kernel/io_file.h index f77db54..6707216 100644 --- a/code/kernel/io_file.h +++ b/code/kernel/io_file.h @@ -7,6 +7,18 @@ #include #include +// --------------------------------------------------------------------------- +// KDIR_ENTRY - one directory entry returned by ReadDir / SYS_READ_DIR +// Attribute flags match FAT32/Win32: ATTR_DIRECTORY=0x10, ATTR_ARCHIVE=0x20 +// --------------------------------------------------------------------------- +#define KDIR_NAME_MAX 260 + +typedef struct KDIR_ENTRY { + char Name[KDIR_NAME_MAX]; // null-terminated filename + uint8_t Attr; // FAT attribute byte (0x10=dir, 0x20=file) + uint32_t FileSize; // 0 for directories +} KDIR_ENTRY; + // --------------------------------------------------------------------------- // Filesystem driver vtable - filled in by fat32.sys (or any future fs driver) // --------------------------------------------------------------------------- @@ -22,6 +34,21 @@ typedef struct IO_FILESYSTEM { uint64_t offset, void* buf, size_t size, size_t* bytesRead); + KSTATUS (*Write)(struct IO_FILESYSTEM* fs, void* fileCtx, + uint64_t offset, const void* buf, + size_t size, size_t* bytesWritten); + + // Create a new file at 'path'; returns an open fileCtx on success. + KSTATUS (*Create)(struct IO_FILESYSTEM* fs, const char* path, + void** fileCtx); + + // Delete the file at 'path'. + KSTATUS (*Delete)(struct IO_FILESYSTEM* fs, const char* path); + + // Truncate or extend the file to exactly 'newSize' bytes. + KSTATUS (*Truncate)(struct IO_FILESYSTEM* fs, void* fileCtx, + uint64_t newSize); + // Return the file size in bytes. uint64_t (*GetSize)(struct IO_FILESYSTEM* fs, void* fileCtx); @@ -30,6 +57,24 @@ typedef struct IO_FILESYSTEM { // Optional driver-private data. void* DriverCtx; + + // --------------------------------------------------------------------------- + // Directory enumeration (optional - may be NULL if not supported) + // --------------------------------------------------------------------------- + + // Open a directory iterator for 'dirPath' (relative or drive-prefixed). + // Returns an opaque per-iteration driver cookie in *dirCtx. + KSTATUS (*OpenDir)(struct IO_FILESYSTEM* fs, + const char* dirPath, void** dirCtx); + + // Advance to the next entry. + // Fills 'out' with name, attributes, and size. + // Returns STATUS_NO_MORE_FILES when the directory is exhausted. + KSTATUS (*ReadDir)(struct IO_FILESYSTEM* fs, void* dirCtx, + struct KDIR_ENTRY* out); + + // Release resources allocated by OpenDir. + void (*CloseDir)(struct IO_FILESYSTEM* fs, void* dirCtx); } IO_FILESYSTEM; // --------------------------------------------------------------------------- @@ -43,6 +88,15 @@ typedef struct FILE_OBJECT { uint64_t Offset; // current position for sequential reads } FILE_OBJECT; +// --------------------------------------------------------------------------- +// DIR_OBJECT - kernel object for an open directory iterator +// --------------------------------------------------------------------------- +typedef struct DIR_OBJECT { + OBJECT Header; // must be first + IO_FILESYSTEM* Fs; + void* DirCtx; // driver cookie (opaque iteration state) +} DIR_OBJECT; + typedef struct IO_VOLUME_PARAMS { uint8_t DiskIndex; // which disk in HAL_DISK table uint64_t PartitionLba; // starting LBA of this partition @@ -61,6 +115,24 @@ __declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t driveIndex, IO_FILESY __declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out); __declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t size, size_t* bytesRead); +__declspec(dllexport) KSTATUS IoWriteFile (FILE_OBJECT* file, const void* buf, + size_t size, size_t* bytesWritten); +__declspec(dllexport) KSTATUS IoCreateFile (const char* path, FILE_OBJECT** out); +__declspec(dllexport) KSTATUS IoDeleteFile (const char* path); +__declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize); __declspec(dllexport) KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset); __declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file); __declspec(dllexport) void IoCloseFile(FILE_OBJECT* file); + +// --------------------------------------------------------------------------- +// Directory enumeration API +// --------------------------------------------------------------------------- +__declspec(dllexport) KSTATUS IoOpenDir (const char* dirPath, DIR_OBJECT** out); +__declspec(dllexport) KSTATUS IoReadDir (DIR_OBJECT* dir, KDIR_ENTRY* entryOut); +__declspec(dllexport) void IoCloseDir(DIR_OBJECT* dir); + +// --------------------------------------------------------------------------- +// Current Working Directory +// --------------------------------------------------------------------------- +__declspec(dllexport) KSTATUS IoSetCwd(const char* path); +__declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size); diff --git a/code/kernel/ke_debug.c b/code/kernel/ke_debug.c index a436a30..5ca52b8 100644 --- a/code/kernel/ke_debug.c +++ b/code/kernel/ke_debug.c @@ -53,3 +53,12 @@ __declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...) { // Halt it! while (true) __halt(); } + +// KePrintConsole - write user-visible console text in white-on-black. +// Used by SYS_WRITE_CONSOLE so user programs get a distinct colour from +// kernel debug output (green) and panic output (red). +__declspec(dllexport) void KePrintConsole(const char* buf, size_t len) { + if (!buf || !PrintChar) return; + for (size_t i = 0; i < len; i++) + PrintChar(buf[i], 0xFFFFFF, 0x000000); +} diff --git a/code/kernel/ke_debug.h b/code/kernel/ke_debug.h index 8b7ba6b..d7a6e2b 100644 --- a/code/kernel/ke_debug.h +++ b/code/kernel/ke_debug.h @@ -4,7 +4,8 @@ #include void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), int v); -__declspec(dllexport) void DbgPrint(const char* fmt, ...); +__declspec(dllexport) void DbgPrint(const char* fmt, ...); +__declspec(dllexport) void KePrintConsole(const char* buf, size_t len); __declspec(dllexport) void KePanic(const char *fmt, ...); #endif // KE_DEBUG_H diff --git a/code/kernel/ke_event.c b/code/kernel/ke_event.c index 60581b2..7cc8b89 100644 --- a/code/kernel/ke_event.c +++ b/code/kernel/ke_event.c @@ -55,7 +55,7 @@ __declspec(dllexport) void KeClearEvent(EVENT* event) { __declspec(dllexport) KSTATUS KeWaitForEvent(EVENT* event) { if (!event) return STATUS_INVALID_PARAMETER; - THREAD* self = KeCurrentThread(); + THREAD* self = KeGetCurrentThread(); while (true) { uintptr_t flags = KeAcquireSpinlock(&event->Lock); diff --git a/code/kernel/ke_mutex.c b/code/kernel/ke_mutex.c index 8d4f048..742ed31 100644 --- a/code/kernel/ke_mutex.c +++ b/code/kernel/ke_mutex.c @@ -22,7 +22,7 @@ KSTATUS MutexCreate(MUTEX **out) { KSTATUS KeAcquireMutex(MUTEX* mutex) { if (!mutex) return STATUS_INVALID_PARAMETER; - THREAD* self = KeCurrentThread(); + THREAD* self = KeGetCurrentThread(); while (true) { uintptr_t flags = KeAcquireSpinlock(&mutex->Lock); @@ -39,7 +39,7 @@ KSTATUS KeAcquireMutex(MUTEX* mutex) { KSTATUS KeTryAcquireMutex(MUTEX* mutex) { if (!mutex) return STATUS_INVALID_PARAMETER; - THREAD* self = KeCurrentThread(); + THREAD* self = KeGetCurrentThread(); uintptr_t flags = KeAcquireSpinlock(&mutex->Lock); if (!mutex->Owner) { mutex->Owner = self; diff --git a/code/kernel/ke_process.c b/code/kernel/ke_process.c index 0909bf1..7ec5efd 100644 --- a/code/kernel/ke_process.c +++ b/code/kernel/ke_process.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,14 @@ LIST_ENTRY* KeGetProcessList(void) { return &gProcessList; } +PROCESS* KeGetCurrentProcess(void) { + THREAD* currentThread = KeGetCurrentThread(); + if (currentThread) { + return currentThread->Owner; + } + return NULL; +} + // --------------------------------------------------------------------------- // Destructor (called by ObDereferenceObject when RefCount hits zero) // --------------------------------------------------------------------------- diff --git a/code/kernel/ke_process.h b/code/kernel/ke_process.h index bc455a3..cfb58c4 100644 --- a/code/kernel/ke_process.h +++ b/code/kernel/ke_process.h @@ -16,6 +16,9 @@ typedef struct PROCESS { 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; @@ -23,7 +26,7 @@ typedef struct PROCESS { /* Handle table. Index 0 is always NULL (the invalid handle). Entries are OBJECT pointers, each holding a reference. */ - OBJECT* Handles[HANDLE_TABLE_SIZE]; + OBJECT* Handles[HANDLE_TABLE_SIZE]; SPINLOCK HandleLock; int ExitCode; @@ -46,3 +49,4 @@ OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle); /* Return the global process list head (kernel-internal). */ LIST_ENTRY* KeGetProcessList(void); +PROCESS* KeGetCurrentProcess(void); diff --git a/code/kernel/ke_queue.c b/code/kernel/ke_queue.c index 5b1421e..c90eaa9 100644 --- a/code/kernel/ke_queue.c +++ b/code/kernel/ke_queue.c @@ -22,7 +22,7 @@ KSTATUS QueueCreate(QUEUE **out) { KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, bool blocking) { if (!queue || !msg) return STATUS_INVALID_PARAMETER; - THREAD* self = KeCurrentThread(); + THREAD* self = KeGetCurrentThread(); while (true) { uintptr_t flags = KeAcquireSpinlock(&queue->Lock); @@ -54,7 +54,7 @@ KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, bool blocking) { KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, bool blocking) { if (!queue || !msg) return STATUS_INVALID_PARAMETER; - THREAD* self = KeCurrentThread(); + THREAD* self = KeGetCurrentThread(); while (true) { uintptr_t flags = KeAcquireSpinlock(&queue->Lock); diff --git a/code/kernel/ke_sched.c b/code/kernel/ke_sched.c index b71f426..b246ad0 100644 --- a/code/kernel/ke_sched.c +++ b/code/kernel/ke_sched.c @@ -17,8 +17,8 @@ static THREAD* gIdleThread; static THREAD* gOutgoingThread; static uint32_t gAgingCounter = 0; -THREAD* KeCurrentThread(void) { return gCurrentThread; } -void KeSetCurrentThread(THREAD* t){ gCurrentThread = t; } +THREAD* KeGetCurrentThread(void) { return gCurrentThread; } +void KeSetCurrentThread(THREAD* t) { gCurrentThread = t; } static void IdleLoop(void) { while (true) { __halt(); } @@ -60,7 +60,7 @@ void KeInitializeScheduler(void) { // Safely release the lock and clean up any dead threads natively post-switch void KeSchedulerPostSwitch(void) { - THREAD* cur = KeCurrentThread(); + THREAD* cur = KeGetCurrentThread(); KeReleaseSpinlock(&gSchedLock, cur->SchedLockFlags); if (gOutgoingThread && gOutgoingThread->State == ThreadDead) { diff --git a/code/kernel/ke_sched.h b/code/kernel/ke_sched.h index a0f6644..c6aeaea 100644 --- a/code/kernel/ke_sched.h +++ b/code/kernel/ke_sched.h @@ -19,7 +19,7 @@ void KeSchedulerTick(void); __declspec(dllexport) void KeYield(void); // Returns the thread currently running on this CPU (reads GS:[0]). -THREAD* KeCurrentThread(void); +THREAD* KeGetCurrentThread(void); // Set the current-thread GS slot. Called once per CPU at boot. void KeSetCurrentThread(THREAD* thread); diff --git a/code/kernel/ke_semaphore.c b/code/kernel/ke_semaphore.c index 4621857..8ba70ae 100644 --- a/code/kernel/ke_semaphore.c +++ b/code/kernel/ke_semaphore.c @@ -23,7 +23,7 @@ __declspec(dllexport) KSTATUS KeSemaphoreCreate(int32_t initial, int32_t max, SE __declspec(dllexport) KSTATUS KeWaitSemaphore(SEMAPHORE* sem) { if (!sem) return STATUS_INVALID_PARAMETER; - THREAD* self = KeCurrentThread(); + THREAD* self = KeGetCurrentThread(); while (true) { uintptr_t flags = KeAcquireSpinlock(&sem->Lock); diff --git a/code/kernel/ke_status.h b/code/kernel/ke_status.h index f3a90c4..b7096a2 100644 --- a/code/kernel/ke_status.h +++ b/code/kernel/ke_status.h @@ -24,6 +24,7 @@ typedef uint32_t KSTATUS; #define STATUS_ACCESS_DENIED ((KSTATUS)0x0021) #define STATUS_NOT_FOUND ((KSTATUS)0x0022) #define STATUS_ALREADY_EXISTS ((KSTATUS)0x0023) +#define STATUS_OBJECT_NAME_COLLISION ((KSTATUS)0x0024) // --- State & Lifecycle (0x0030) --- #define STATUS_OBJECT_DEAD ((KSTATUS)0x0030) @@ -33,4 +34,6 @@ typedef uint32_t KSTATUS; // --- Hardware & External (0x0040) --- #define STATUS_DEVICE_ERROR ((KSTATUS)0x0040) +#define STATUS_NO_MORE_FILES ((KSTATUS)0x0050) + #define KSUCCESS(s) ((s) == STATUS_SUCCESS) diff --git a/code/kernel/ke_switch.asm b/code/kernel/ke_switch.asm index 7bb9b1b..7b4f4ba 100644 --- a/code/kernel/ke_switch.asm +++ b/code/kernel/ke_switch.asm @@ -76,7 +76,7 @@ KeSwitchContext ENDP ; We load RCX from the stack (first arg per MS x64), then IRETQ. ; ------------------------------------------------------------------------- EXTERN KeSchedulerPostSwitch : PROC -EXTERN KeCurrentThread : PROC +EXTERN KeGetCurrentThread : PROC EXTERN KeExitThread : PROC KeThreadEntry PROC @@ -115,7 +115,7 @@ KeKernelThreadEntry PROC ; Thread function returned -- exit cleanly. sub rsp, 32 - call KeCurrentThread ; rax = current THREAD* + call KeGetCurrentThread ; rax = current THREAD* add rsp, 32 mov rcx, rax ; arg1 = thread* xor edx, edx ; arg2 = exitCode 0 diff --git a/code/kernel/ke_syscall.asm b/code/kernel/ke_syscall.asm index 3d19aea..f128492 100644 --- a/code/kernel/ke_syscall.asm +++ b/code/kernel/ke_syscall.asm @@ -39,7 +39,7 @@ KeSyscallEntry ENDP ; --------------------------------------------------------------------------- ; Fault stubs - print a message and halt. A real kernel would do a bugcheck. ; --------------------------------------------------------------------------- -EXTERN DbgPrint:PROC +EXTERN KePanic:PROC FaultMsg DB "KERNEL FAULT: vector %d at RIP %016llx", 0Ah, 0 @@ -48,10 +48,7 @@ KeFaultDE PROC lea rcx, FaultMsg mov rdx, 0 pop r8 ; RIP from the fault frame - call DbgPrint - add rsp, 28h -@@: hlt - jmp @B + call KePanic KeFaultDE ENDP KeFaultGP PROC @@ -60,10 +57,7 @@ KeFaultGP PROC mov rdx, 13 pop rax ; error code pop r8 ; RIP - call DbgPrint - add rsp, 28h -@@: hlt - jmp @B + call KePanic KeFaultGP ENDP KeFaultPF PROC @@ -72,10 +66,7 @@ KeFaultPF PROC mov rdx, 14 pop rax ; error code pop r8 ; RIP - call DbgPrint - add rsp, 28h -@@: hlt - jmp @B + call KePanic KeFaultPF ENDP EXTERN KeIrqDispatch:PROC diff --git a/code/kernel/ke_syscall.c b/code/kernel/ke_syscall.c index 79f3a29..998904f 100644 --- a/code/kernel/ke_syscall.c +++ b/code/kernel/ke_syscall.c @@ -58,7 +58,7 @@ void KeSyscallSetHal(const HAL_API* hal) { gHal = hal; } // Helper: get the current process, or NULL if unavailable. // --------------------------------------------------------------------------- static inline PROCESS* CurrentProcess(void) { - THREAD* t = KeCurrentThread(); + THREAD* t = KeGetCurrentThread(); return t ? t->Owner : NULL; } @@ -102,7 +102,7 @@ static KSTATUS AllocHandleForCurrent(OBJECT* obj, uint32_t* handleOut) { 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 = KeCurrentThread(); + THREAD* t = KeGetCurrentThread(); PROCESS* proc = t ? t->Owner : NULL; if (proc) KeExitProcess(proc, (int)a0); if (t) KeExitThread(t, (int)a0); @@ -115,7 +115,7 @@ static uint64_t SyscallExitProcess(uint64_t a0, uint64_t a1, uint64_t a2, 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 = KeCurrentThread(); + THREAD* t = KeGetCurrentThread(); if (t) KeExitThread(t, (int)a0); while (true) ; } @@ -144,7 +144,7 @@ static uint64_t SyscallCreateThread(uint64_t a0, uint64_t a1, uint64_t a2, 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 = KeCurrentThread(); + THREAD* t = KeGetCurrentThread(); return t ? t->ThreadId : 0; } @@ -188,7 +188,7 @@ 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 = KeCurrentThread(); + THREAD* t = KeGetCurrentThread(); if (!t) return STATUS_FAILURE; t->Priority = (THREAD_PRIORITY)a0; return STATUS_SUCCESS; @@ -492,6 +492,78 @@ static uint64_t SyscallReadFile(uint64_t a0, uint64_t a1, uint64_t a2, return s; } +// --------------------------------------------------------------------------- +// SYS_WRITE_FILE (43) 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 (44) 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 (45) 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 (46) 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 (47) 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 (29) a0=handle a1=offset // --------------------------------------------------------------------------- @@ -704,6 +776,88 @@ static uint64_t SyscallGetProcessExitCode(uint64_t a0, uint64_t a1, uint64_t a2, return s; } +// --------------------------------------------------------------------------- +// SYS_WRITE_CONSOLE (48) 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 (49) 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 (50) 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 (51) 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 (52) 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); +} + // =========================================================================== // Dispatch table - index must match SYS_* in syscall_nr.h exactly. // =========================================================================== @@ -738,22 +892,32 @@ static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = { /* 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_SEEK_FILE */ SyscallSeekFile, - /* 30 SYS_GET_FILE_SIZE */ SyscallGetFileSize, - /* 31 SYS_SCREEN_BLIT */ SyscallScreenBlit, - /* 32 SYS_SCREEN_CLEAR */ SyscallScreenClear, - /* 33 SYS_SCREEN_GET_SIZE */ SyscallScreenGetSize, - /* 34 SYS_SCREEN_PUT_CHAR */ SyscallScreenPutChar, - /* 35 SYS_KB_POLL */ SyscallKbPoll, - /* 36 SYS_KB_READ_CHAR */ SyscallKbReadChar, - /* 37 SYS_KB_READ_SCANCODE */ SyscallKbReadScancode, - /* 38 SYS_GET_TICK */ SyscallGetTick, - /* 39 SYS_DEBUG_PRINT */ SyscallDebugPrint, - /* 40 SYS_CREATE_PROCESS */ SyscallCreateProcess, - /* 41 SYS_WAIT_FOR_PROCESS */ SyscallWaitForProcess, - /* 42 SYS_GET_PROCESS_EXIT_CODE */ SyscallGetProcessExitCode, + /* 27 SYS_OPEN_FILE */ SyscallOpenFile, + /* 28 SYS_READ_FILE */ SyscallReadFile, + /* 29 SYS_SEEK_FILE */ SyscallSeekFile, + /* 30 SYS_GET_FILE_SIZE */ SyscallGetFileSize, + /* 31 SYS_SCREEN_BLIT */ SyscallScreenBlit, + /* 32 SYS_SCREEN_CLEAR */ SyscallScreenClear, + /* 33 SYS_SCREEN_GET_SIZE */ SyscallScreenGetSize, + /* 34 SYS_SCREEN_PUT_CHAR */ SyscallScreenPutChar, + /* 35 SYS_KB_POLL */ SyscallKbPoll, + /* 36 SYS_KB_READ_CHAR */ SyscallKbReadChar, + /* 37 SYS_KB_READ_SCANCODE */ SyscallKbReadScancode, + /* 38 SYS_GET_TICK */ SyscallGetTick, + /* 39 SYS_DEBUG_PRINT */ SyscallDebugPrint, + /* 40 SYS_CREATE_PROCESS */ SyscallCreateProcess, + /* 41 SYS_WAIT_FOR_PROCESS */ SyscallWaitForProcess, + /* 42 SYS_GET_PROCESS_EXIT_CODE */ SyscallGetProcessExitCode, + /* 43 SYS_WRITE_FILE */ SyscallWriteFile, + /* 44 SYS_CREATE_FILE */ SyscallCreateFile, + /* 45 SYS_DELETE_FILE */ SyscallDeleteFile, + /* 46 SYS_TRUNCATE_FILE */ SyscallTruncateFile, + /* 47 SYS_GET_FILE_POS */ SyscallGetFilePos, + /* 48 SYS_WRITE_CONSOLE */ SyscallWriteConsole, + /* 49 SYS_OPEN_DIR */ SyscallOpenDir, + /* 50 SYS_READ_DIR */ SyscallReadDir, + /* 51 SYS_SET_CWD */ SyscallSetCwd, + /* 52 SYS_GET_CWD */ SyscallGetCwd, }; // --------------------------------------------------------------------------- diff --git a/code/kernel/ke_thread.c b/code/kernel/ke_thread.c index ffce41e..3bd8ef8 100644 --- a/code/kernel/ke_thread.c +++ b/code/kernel/ke_thread.c @@ -137,7 +137,7 @@ __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 = KeCurrentThread(); // safe to call before scheduler starts + 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; diff --git a/code/kernel/ke_thread.h b/code/kernel/ke_thread.h index 6f383fc..2d05661 100644 --- a/code/kernel/ke_thread.h +++ b/code/kernel/ke_thread.h @@ -41,7 +41,7 @@ typedef struct THREAD { // Quanta consumed without blocking - used by the MLFQ for demotion. uint32_t QuantaConsumed; - struct PROCESS* Owner; + PROCESS* Owner; LIST_ENTRY ProcessLink; /* node in Owner->ThreadList */ LIST_ENTRY SchedulerLink; /* node in run queue or wait list */ diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index d8ad71a..bf67c2d 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -310,14 +310,14 @@ void SystemInitThread(void* arg) { // (e.g., win32.dll resolving its imports against syscall.dll) ResolveSystemImports(args->bootInfo); + args->hal->Screen.Clear(0x000000); + // Print memory status now that the system is fully alive DbgPrint("Memory Status:\n"); DbgPrint(" -> Free (PMM): %.2f MB\n", (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f)); DbgPrint(" -> Kernel Pool: %.2f MB\n", (float)MmGetKernelPoolUsage() / (1024.0f * 1024.0f)); DbgPrint(" -> User Heap: %.2f MB\n", (float)MmGetUserHeapUsage() / (1024.0f * 1024.0f)); - args->hal->Screen.Clear(0x000000); - // Launch User-Mode Shell (this will trigger AHCI & FAT32 which CAN safely block now) PROCESS* shellProc = LaunchUserProcess(args->bootInfo->CommandLine); diff --git a/code/kernel/ob_object.h b/code/kernel/ob_object.h index 03acf1a..64f63ba 100644 --- a/code/kernel/ob_object.h +++ b/code/kernel/ob_object.h @@ -14,6 +14,7 @@ typedef enum OBJECT_TYPE { ObjectTypeEvent = 5, ObjectTypeQueue = 6, ObjectTypeFile = 7, + ObjectTypeDir = 8, } OBJECT_TYPE; typedef struct OBJECT { diff --git a/code/kernel/syscall_nr.h b/code/kernel/syscall_nr.h index 06b3481..586fe00 100644 --- a/code/kernel/syscall_nr.h +++ b/code/kernel/syscall_nr.h @@ -74,6 +74,11 @@ #define SYS_READ_FILE 28 // (uint32_t handle, void* buf, size_t size, size_t* bytesReadOut) -> KSTATUS #define SYS_SEEK_FILE 29 // (uint32_t handle, uint64_t offset) -> KSTATUS #define SYS_GET_FILE_SIZE 30 // (uint32_t handle) -> uint64_t +#define SYS_WRITE_FILE 43 +#define SYS_CREATE_FILE 44 +#define SYS_DELETE_FILE 45 +#define SYS_TRUNCATE_FILE 46 +#define SYS_GET_FILE_POS 47 // --------------------------------------------------------------------------- // Screen / Graphics @@ -107,7 +112,25 @@ #define SYS_WAIT_FOR_PROCESS 41 // (uint32_t handle) -> KSTATUS #define SYS_GET_PROCESS_EXIT_CODE 42 // (uint32_t handle, int* exitCodeOut) -> KSTATUS +// --------------------------------------------------------------------------- +// Console output (white text, distinct from green SYS_DEBUG_PRINT) +// --------------------------------------------------------------------------- +#define SYS_WRITE_CONSOLE 48 // (const char* buf, size_t len) -> void + +// --------------------------------------------------------------------------- +// Directory enumeration +// (Close the handle via SYS_CLOSE_HANDLE like any other object.) +// --------------------------------------------------------------------------- +#define SYS_OPEN_DIR 49 // (const char* dirPath, uint32_t* handleOut) -> KSTATUS +#define SYS_READ_DIR 50 // (uint32_t handle, KDIR_ENTRY* entryOut) -> KSTATUS + +// --------------------------------------------------------------------------- +// Current Working Directory +// --------------------------------------------------------------------------- +#define SYS_SET_CWD 51 // (const char* path) -> KSTATUS +#define SYS_GET_CWD 52 // (char* buf, size_t size) -> KSTATUS + // --------------------------------------------------------------------------- // Sentinel // --------------------------------------------------------------------------- -#define SYS_MAX 43 +#define SYS_MAX 53 diff --git a/code/libc.c b/code/libc.c index 46f3e2c..892ef72 100644 --- a/code/libc.c +++ b/code/libc.c @@ -50,6 +50,15 @@ int strcmp(const char* s1, const char* s2) { return (unsigned char)*s1 - (unsigned char)*s2; } +int strncmp(const char *s1, const char *s2, size_t n) { + while (n && *s1 && (*s1 == *s2)) { + s1++; + s2++; + n--; + } + return n == 0 ? 0 : *(unsigned char *)s1 - *(unsigned char *)s2; +} + int _stricmp(const char* s1, const char* s2) { // 0x20 (32) is the bit that differs between 'A' and 'a' while (*s1 && ((*s1 | 0x20) == (*s2 | 0x20))) { diff --git a/code/shell/shell.c b/code/shell/shell.c index c75ae58..fee49ce 100644 --- a/code/shell/shell.c +++ b/code/shell/shell.c @@ -4,28 +4,43 @@ #include // --------------------------------------------------------------------------- -// Win32 imports +// Win32 imports & definitions // --------------------------------------------------------------------------- _Noreturn void ExitProcess(unsigned int code); void OutputDebugStringA(const char* s); -typedef void* HANDLE; +typedef void* HANDLE; typedef unsigned long DWORD; typedef int BOOL; -#define INVALID_HANDLE_VALUE ((HANDLE)0) -#define INFINITE 0xFFFFFFFFUL -#define STD_INPUT_HANDLE ((DWORD)-10) + +#define INVALID_HANDLE_VALUE ((HANDLE)(intptr_t)-1) +#define INFINITE 0xFFFFFFFFUL +#define STD_INPUT_HANDLE ((DWORD)-10) +#define STD_OUTPUT_HANDLE ((DWORD)-11) +#define MAX_PATH 260 + +// File I/O constants +#define GENERIC_READ 0x80000000L +#define OPEN_EXISTING 3 +#define FILE_SHARE_READ 0x00000001 +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 HANDLE GetStdHandle(DWORD nStdHandle); -BOOL ReadConsoleA(HANDLE hConsoleInput, void* lpBuffer, - DWORD nNumberOfCharsToRead, DWORD* lpNumberOfCharsRead, - void* pInputControl); +DWORD GetLastError(); + +BOOL ReadConsoleA(HANDLE hConsoleInput, void* lpBuffer, + DWORD nNumberOfCharsToRead, DWORD* lpNumberOfCharsRead, + void* pInputControl); + +BOOL WriteConsoleA(HANDLE hConsoleOutput, const void* lpBuffer, + DWORD nNumberOfCharsToWrite, DWORD* lpNumberOfCharsWritten, + void* lpReserved); typedef struct { DWORD cb; - char* lpReserved; - char* lpDesktop; - char* lpTitle; + char* lpReserved; + char* lpDesktop; + char* lpTitle; DWORD dwX, dwY, dwXSize, dwYSize; DWORD dwXCountChars, dwYCountChars; DWORD dwFillAttribute, dwFlags; @@ -41,6 +56,24 @@ typedef struct { DWORD dwThreadId; } PROCESS_INFORMATION; +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[MAX_PATH]; + char cAlternateFileName[14]; +} WIN32_FIND_DATAA; + BOOL CreateProcessA(const char* lpApp, char* lpCmdLine, void* lpPA, void* lpTA, BOOL bInherit, DWORD dwFlags, void* lpEnv, const char* lpDir, @@ -49,11 +82,35 @@ DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMs); BOOL GetExitCodeProcess(HANDLE hProcess, DWORD* lpExitCode); BOOL CloseHandle(HANDLE hObject); +// File operation imports +HANDLE FindFirstFileA(const char* lpFileName, WIN32_FIND_DATAA* lpFindFileData); +BOOL FindNextFileA(HANDLE hFindFile, WIN32_FIND_DATAA* lpFindFileData); +BOOL FindClose(HANDLE hFindFile); +BOOL SetCurrentDirectoryA(const char* lpPathName); +BOOL DeleteFileA(const char* lpFileName); +HANDLE CreateFileA(const char* lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, + void* lpSecurityAttributes, DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); +BOOL ReadFile(HANDLE hFile, void* lpBuffer, DWORD nNumberOfBytesToRead, + DWORD* lpNumberOfBytesRead, void* lpOverlapped); + +// --------------------------------------------------------------------------- +// Global handles +// --------------------------------------------------------------------------- +static HANDLE hIn = INVALID_HANDLE_VALUE; +static HANDLE hOut = INVALID_HANDLE_VALUE; + // --------------------------------------------------------------------------- // Output helpers // --------------------------------------------------------------------------- static void print(const char* s) { - OutputDebugStringA(s); + DWORD written; + // Write to the actual console window + if (hOut != INVALID_HANDLE_VALUE) { + WriteConsoleA(hOut, s, (DWORD)strlen(s), &written, 0); + } else { + OutputDebugStringA(s); + } } static void printUInt(uint32_t n) { @@ -70,12 +127,10 @@ static void printUInt(uint32_t n) { // --------------------------------------------------------------------------- // Line editor -// Reads one character at a time via ReadConsoleA, echoes it, handles -// backspace, returns on Enter. // --------------------------------------------------------------------------- #define MAX_CMD 256 -static void readLine(HANDLE hIn, char* buf, int len) { +static void readLine(char* buf, int len) { int pos = 0; while (true) { char c; @@ -106,9 +161,6 @@ static void readLine(HANDLE hIn, char* buf, int len) { buf[pos] = '\0'; } -// --------------------------------------------------------------------------- -// Trim leading/trailing spaces in-place; returns pointer into buf. -// --------------------------------------------------------------------------- static char* trim(char* s) { while (*s == ' ') s++; int len = (int)strlen(s); @@ -117,6 +169,51 @@ static char* trim(char* s) { return s; } +// --------------------------------------------------------------------------- +// Built-in commands +// --------------------------------------------------------------------------- +static void cmd_dir() { + WIN32_FIND_DATAA fd; + HANDLE hFind = FindFirstFileA("*", &fd); + + if (hFind == INVALID_HANDLE_VALUE) { + print("dir: failed to read directory\n"); + return; + } + + do { + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + print(" "); + } else { + print(" "); + } + 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: could not open '"); + print(filename); + print("'\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"); +} + // --------------------------------------------------------------------------- // Run one command. Returns 1 if the shell should exit. // --------------------------------------------------------------------------- @@ -124,19 +221,59 @@ static int runCommand(const char* cmd) { if (!cmd || *cmd == '\0') return 0; - if (strcmp(cmd, "exit") == 0) + // --- Built-in commands --- + if (strcmp(cmd, "exit") == 0) { return 1; + } + if (strcmp(cmd, "dir") == 0) { + cmd_dir(); + return 0; + } + + if (strncmp(cmd, "cd ", 3) == 0) { + const char* path = cmd + 3; + if (!SetCurrentDirectoryA(path)) { + print("cd: failed to change directory to '"); + print(path); + print("'\n"); + } + return 0; + } + + if (strncmp(cmd, "del ", 4) == 0) { + const char* file = cmd + 4; + if (!DeleteFileA(file)) { + print("del: failed to delete '"); + print(file); + print("'\n"); + } + return 0; + } + + if (strncmp(cmd, "type ", 5) == 0) { + cmd_type(cmd + 5); + return 0; + } + + // --- External Process Execution --- STARTUPINFOA si; PROCESS_INFORMATION pi; unsigned char* p = (unsigned char*)&si; for (size_t i = 0; i < sizeof(si); i++) p[i] = 0; si.cb = (DWORD)sizeof(si); - if (!CreateProcessA(cmd, 0, 0, 0, 0, 0, 0, 0, &si, &pi)) { + // CreateProcessA requires a mutable buffer for the command line argument + char cmdBuf[MAX_CMD]; + strncpy(cmdBuf, cmd, MAX_CMD - 1); + cmdBuf[MAX_CMD - 1] = '\0'; + + if (!CreateProcessA(0, cmdBuf, 0, 0, 0, 0, 0, 0, &si, &pi)) { print("shell: could not launch '"); print(cmd); - print("'\n"); + print("', error code: "); + printUInt(GetLastError()); + print("\n"); return 0; } @@ -145,6 +282,7 @@ static int runCommand(const char* cmd) { DWORD exitCode = 0; GetExitCodeProcess(pi.hProcess, &exitCode); CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); if (exitCode != 0) { print("shell: '"); @@ -161,14 +299,15 @@ static int runCommand(const char* cmd) { // Entry point // --------------------------------------------------------------------------- int main() { - HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + hIn = GetStdHandle(STD_INPUT_HANDLE); + hOut = GetStdHandle(STD_OUTPUT_HANDLE); char lineBuf[MAX_CMD]; print("shell: ready\n"); while (true) { print("> "); - readLine(hIn, lineBuf, MAX_CMD); + readLine(lineBuf, MAX_CMD); if (runCommand(trim(lineBuf))) break; } diff --git a/code/syscall/syscall_nr.inc b/code/syscall/syscall_nr.inc index 9a7cbcc..1bb8ad3 100644 --- a/code/syscall/syscall_nr.inc +++ b/code/syscall/syscall_nr.inc @@ -47,6 +47,11 @@ SYS_OPEN_FILE EQU 27 SYS_READ_FILE EQU 28 SYS_SEEK_FILE EQU 29 SYS_GET_FILE_SIZE EQU 30 +SYS_WRITE_FILE EQU 43 ; a0=handle a1=buf a2=size a3=*bytesWritten +SYS_CREATE_FILE EQU 44 ; a0=*path a1=*handleOut +SYS_DELETE_FILE EQU 45 ; a0=*path +SYS_TRUNCATE_FILE EQU 46 ; a0=handle a1=newSize +SYS_GET_FILE_POS EQU 47 ; a0=handle -> current offset ; Screen SYS_SCREEN_BLIT EQU 31 @@ -70,5 +75,16 @@ SYS_CREATE_PROCESS EQU 40 SYS_WAIT_FOR_PROCESS EQU 41 SYS_GET_PROCESS_EXIT_CODE EQU 42 +; Console output +SYS_WRITE_CONSOLE EQU 48 + +; Directory enumeration +SYS_OPEN_DIR EQU 49 +SYS_READ_DIR EQU 50 + +; Current Working Directory +SYS_SET_CWD EQU 51 +SYS_GET_CWD EQU 52 + ; Sentinel -SYS_MAX EQU 43 +SYS_MAX EQU 53 diff --git a/code/syscall/syscall_stubs.asm b/code/syscall/syscall_stubs.asm index 3e05ad9..a5a16b6 100644 --- a/code/syscall/syscall_stubs.asm +++ b/code/syscall/syscall_stubs.asm @@ -109,6 +109,11 @@ SYSCALL_STUB2 OpenFile, SYS_OPEN_FILE SYSCALL_STUB2 ReadFile, SYS_READ_FILE SYSCALL_STUB2 SeekFile, SYS_SEEK_FILE SYSCALL_STUB2 GetFileSize, SYS_GET_FILE_SIZE +SYSCALL_STUB2 WriteFile, SYS_WRITE_FILE ; handle, buf, size, *bytesWritten +SYSCALL_STUB2 CreateFile, SYS_CREATE_FILE ; *path, *handleOut +SYSCALL_STUB2 DeleteFile, SYS_DELETE_FILE ; *path +SYSCALL_STUB2 TruncateFile, SYS_TRUNCATE_FILE ; handle, newSize +SYSCALL_STUB2 GetFilePos, SYS_GET_FILE_POS ; handle -> offset ; --------------------------------------------------------------------------- ; Screen @@ -144,4 +149,21 @@ SYSCALL_STUB2 CreateProcess, SYS_CREATE_PROCESS SYSCALL_STUB2 WaitForProcess, SYS_WAIT_FOR_PROCESS SYSCALL_STUB2 GetProcessExitCode, SYS_GET_PROCESS_EXIT_CODE +; --------------------------------------------------------------------------- +; Console output (white text, for user programs) +; --------------------------------------------------------------------------- +SYSCALL_STUB2 WriteConsole, SYS_WRITE_CONSOLE + +; --------------------------------------------------------------------------- +; Directory enumeration +; --------------------------------------------------------------------------- +SYSCALL_STUB2 OpenDir, SYS_OPEN_DIR +SYSCALL_STUB2 ReadDir, SYS_READ_DIR + +; --------------------------------------------------------------------------- +; Current Working Directory +; --------------------------------------------------------------------------- +SYSCALL_STUB2 SetCwd, SYS_SET_CWD +SYSCALL_STUB2 GetCwd, SYS_GET_CWD + END diff --git a/code/video/video.c b/code/video/video.c index 5c2d88a..92464b4 100644 --- a/code/video/video.c +++ b/code/video/video.c @@ -4,83 +4,169 @@ #include #include "bink.h" -void OutputDebugStringA(const char* s); +// --------------------------------------------------------------------------- +// Win32 Boilerplate & API declarations +// --------------------------------------------------------------------------- +typedef void* HWND; +typedef void* HDC; +typedef unsigned long DWORD; +typedef unsigned short WORD; +typedef unsigned int UINT; +typedef long LONG; +typedef int BOOL; + +#define BI_RGB 0L +#define DIB_RGB_COLORS 0 +#define SRCCOPY 0x00CC0020 + +#define STD_OUTPUT_HANDLE ((DWORD)-11) + +typedef struct _RECT { + LONG left, top, right, bottom; +} RECT; + +typedef struct tagPAINTSTRUCT { + HDC hdc; + BOOL fErase; + RECT rcPaint; + BOOL fRestore; + BOOL fIncUpdate; + char rgbReserved[32]; +} PAINTSTRUCT; + +typedef struct tagBITMAPINFOHEADER { + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} BITMAPINFOHEADER; + +typedef struct tagRGBQUAD { + unsigned char rgbBlue, rgbGreen, rgbRed, rgbReserved; +} RGBQUAD; + +typedef struct tagBITMAPINFO { + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[1]; +} BITMAPINFO; + +// Kernel/Win32 Exports +void* GetStdHandle(DWORD nStdHandle); +int WriteConsoleA(void* hConsoleOutput, const void* lpBuffer, unsigned int nNumberOfCharsToWrite, unsigned int* lpNumberOfCharsWritten, void* lpReserved); _Noreturn void ExitProcess(unsigned int code); void* HeapAlloc(void* heap, unsigned long flags, size_t size); -void BitBlt(int x, int y, int w, int h, const uint32_t* pixels); -void* CreateFileA(const char* name, unsigned long access, - unsigned long share, void* sa, - unsigned long disp, unsigned long flags, - void* templ); +void* CreateFileA(const char* name, unsigned long access, unsigned long share, void* sa, unsigned long disp, unsigned long flags, void* templ); unsigned long GetFileSize(void* handle, unsigned long* high); -int ReadFile(void* handle, void* buf, unsigned long toRead, - unsigned long* read, void* overlapped); +int ReadFile(void* handle, void* buf, unsigned long toRead, unsigned long* read, void* overlapped); +BOOL CloseHandle(void* hObject); + +// New GDI/User32 Exports +HDC BeginPaint(HWND hwnd, PAINTSTRUCT* lpPaint); +BOOL EndPaint(HWND hwnd, const PAINTSTRUCT* lpPaint); +int StretchDIBits(HDC hdc, int xDest, int yDest, int DestWidth, int DestHeight, + int xSrc, int ySrc, int SrcWidth, int SrcHeight, + const void *lpBits, const BITMAPINFO *lpbmi, + UINT iUsage, DWORD rop); + int main() { - OutputDebugStringA("video: starting\n"); + void* h = GetStdHandle(STD_OUTPUT_HANDLE); + WriteConsoleA(h, "video: starting\n", 16, 0, 0); - // void* f = CreateFileA("video.bk2", 0, 0, 0, 3, 0, 0); - // if (!f) { - // OutputDebugStringA("video: failed to open video.bk2\n"); - // ExitProcess(1); - // } + void* f = CreateFileA("video.bk2", 0, 0, 0, 3, 0, 0); + if (!f) { + WriteConsoleA(h, "video: failed to open video.bk2\n", 32, 0, 0); + ExitProcess(1); + } - // unsigned long s = GetFileSize(f, 0); - // void* buf = HeapAlloc(0, 0, s); - // if (!buf) { - // OutputDebugStringA("video: HeapAlloc failed\n"); - // ExitProcess(1); - // } + unsigned long s = GetFileSize(f, 0); + void* buf = HeapAlloc(0, 0, s); + if (!buf) { + WriteConsoleA(h, "video: HeapAlloc failed\n", 24, 0, 0); + ExitProcess(1); + } - // int r = ReadFile(f, buf, s, 0, 0); - // if (!r) { - // OutputDebugStringA("video: ReadFile failed\n"); - // ExitProcess(1); - // } - // OutputDebugStringA("video: video.bk2 loaded\n"); + int r = ReadFile(f, buf, s, 0, 0); + if (!r) { + WriteConsoleA(h, "video: ReadFile failed\n", 23, 0, 0); + ExitProcess(1); + } + WriteConsoleA(h, "video: video.bk2 loaded\n", 24, 0, 0); + + CloseHandle(h); - // HBINK bink = BinkOpen(buf, BINKFROMMEMORY); - // if (!bink) { - // OutputDebugStringA("video: BinkOpen failed: "); - // OutputDebugStringA(BinkGetError()); - // OutputDebugStringA("\n"); - // ExitProcess(1); - // } + HBINK bink = BinkOpen(buf, BINKFROMMEMORY); + if (!bink) { + WriteConsoleA(h, "video: BinkOpen failed: ", 24, 0, 0); + const char* err = BinkGetError(); + WriteConsoleA(h, err, strlen(err), 0, 0); + WriteConsoleA(h, "\n", 1, 0, 0); + ExitProcess(1); + } - // uint32_t width = bink->Width; - // uint32_t height = bink->Height; + uint32_t width = bink->Width; + uint32_t height = bink->Height; - // uint32_t* fb = HeapAlloc(0, 0, width * height * sizeof(uint32_t)); - // if (!fb) { - // OutputDebugStringA("video: framebuffer alloc failed\n"); - // ExitProcess(1); - // } + uint32_t* fb = HeapAlloc(0, 0, width * height * sizeof(uint32_t)); + if (!fb) { + WriteConsoleA(h, "video: framebuffer alloc failed\n", 32, 0, 0); + ExitProcess(1); + } - // while (1) { - // if (BinkWait(bink)) { - // _mm_pause(); - // continue; - // } + // Prepare the Win32 BITMAPINFO structure + BITMAPINFO bmi; + memset(&bmi, 0, sizeof(bmi)); + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -(LONG)height; // Negative means top-down drawing! + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; - // BinkDoFrame(bink); + while (1) { + if (BinkWait(bink)) { + _mm_pause(); + continue; + } - // if (BinkShouldSkip(bink)) { - // BinkNextFrame(bink); - // BinkDoFrame(bink); - // continue; - // } + BinkDoFrame(bink); - // BinkCopyToBuffer(bink, fb, width * 4, height, 0, 0, BINKSURFACE32); - // BitBlt(0, 0, width, height, fb); + if (BinkShouldSkip(bink)) { + BinkNextFrame(bink); + continue; + } - // if (bink->FrameNum == bink->Frames) - // break; + BinkCopyToBuffer(bink, fb, width * 4, height, 0, 0, + BINKSURFACE32); - // BinkNextFrame(bink); - // } + // Welcome to the GDI pipeline! + PAINTSTRUCT ps; + // Using NULL/0 as the HWND since we don't have a real window handle yet + HDC hdc = BeginPaint((HWND)0, &ps); - // BinkClose(bink); - for (int i = 0; i < 10000000; i++) _mm_pause(); - OutputDebugStringA("video: done\n"); - ExitProcess(0); + StretchDIBits( + hdc, + 0, 0, width, height, // Destination rect + 0, 0, width, height, // Source rect + fb, + &bmi, + DIB_RGB_COLORS, + SRCCOPY + ); + + EndPaint((HWND)0, &ps); + + BinkNextFrame(bink); + } + + WriteConsoleA(h, "shell: goodbye\n", 15, 0, 0); + + return 0; } diff --git a/code/win32/gdi32.c b/code/win32/gdi32.c new file mode 100644 index 0000000..54126b6 --- /dev/null +++ b/code/win32/gdi32.c @@ -0,0 +1,83 @@ +// gdi32.c - Win32 Graphics Device Interface stubs +#include +#include + +// --------------------------------------------------------------------------- +// Syscall stubs (from syscall.dll) +// --------------------------------------------------------------------------- +extern void SysScreenBlit(int x, int y, int w, int h, const uint32_t* pixels); + +// --------------------------------------------------------------------------- +// Win32 Base Types +// --------------------------------------------------------------------------- +typedef void* HDC; +typedef unsigned long DWORD; +typedef unsigned short WORD; +typedef unsigned int UINT; +typedef long LONG; +typedef int BOOL; + +#define TRUE 1 +#define FALSE 0 + +// StretchDIBits Constants +#define BI_RGB 0L +#define DIB_RGB_COLORS 0 +#define SRCCOPY (DWORD)0x00CC0020 + +// --------------------------------------------------------------------------- +// GDI Structs +// --------------------------------------------------------------------------- +typedef struct tagBITMAPINFOHEADER { + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} BITMAPINFOHEADER; + +typedef struct tagRGBQUAD { + unsigned char rgbBlue; + unsigned char rgbGreen; + unsigned char rgbRed; + unsigned char rgbReserved; +} RGBQUAD; + +typedef struct tagBITMAPINFO { + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[1]; +} BITMAPINFO; + +// --------------------------------------------------------------------------- +// GDI Exports +// --------------------------------------------------------------------------- +__declspec(dllexport) int StretchDIBits( + HDC hdc, + int xDest, int yDest, int DestWidth, int DestHeight, + int xSrc, int ySrc, int SrcWidth, int SrcHeight, + const void *lpBits, const BITMAPINFO *lpbmi, + UINT iUsage, DWORD rop) +{ + (void)hdc; (void)xSrc; (void)ySrc; (void)SrcWidth; (void)SrcHeight; + (void)lpbmi; (void)iUsage; (void)rop; + + // Direct mapping to kernel screen blit + SysScreenBlit(xDest, yDest, DestWidth, DestHeight, (const uint32_t*)lpBits); + + // Return the number of scanlines copied + return DestHeight; +} + +__declspec(dllexport) BOOL BitBlt(HDC hdcDest, int xDest, int yDest, int w, int h, + HDC hdcSrc, int xSrc, int ySrc, DWORD rop) { + (void)hdcDest; (void)xDest; (void)yDest; (void)w; (void)h; + (void)hdcSrc; (void)xSrc; (void)ySrc; (void)rop; + // Stubbed. Applications manipulating pixels directly will use StretchDIBits. + return TRUE; +} diff --git a/code/win32/kernel32.c b/code/win32/kernel32.c index 2d4c619..030ba69 100644 --- a/code/win32/kernel32.c +++ b/code/win32/kernel32.c @@ -33,6 +33,12 @@ extern void* SysRealloc(void* ptr, size_t newSize); extern uint32_t SysOpenFile(const char* path, uint32_t* handleOut); extern uint32_t SysReadFile(uint32_t handle, void* buf, size_t size, size_t* bytesReadOut); +extern uint32_t SysWriteFile(uint32_t handle, const void* buf, + size_t size, size_t* bytesWrittenOut); +extern uint32_t SysCreateFile(const char* path, uint32_t* handleOut); +extern uint32_t SysDeleteFile(const char* path); +extern uint32_t SysTruncateFile(uint32_t handle, uint64_t newSize); +extern uint64_t SysGetFilePos(uint32_t handle); extern uint32_t SysSeekFile(uint32_t handle, uint64_t offset); extern uint64_t SysGetFileSize(uint32_t handle); extern void SysCloseHandle(uint32_t handle); @@ -54,6 +60,13 @@ extern void SysSleep(uint32_t ms); extern uint64_t SysGetTick(void); extern void SysDebugPrint(const char* str); +// Console & directory & CWD stubs (syscall.dll) +extern void SysWriteConsole(const char* buf, size_t len); +extern uint32_t SysOpenDir(const char* dirPath, uint32_t* handleOut); +extern uint32_t SysReadDir(uint32_t handle, void* entryOut); +extern uint32_t SysSetCwd(const char* path); +extern uint32_t SysGetCwd(char* buf, size_t size); + // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- @@ -61,16 +74,41 @@ typedef void* HANDLE; typedef unsigned long DWORD; typedef int BOOL; +#define MAX_PATH 260 #define TRUE 1 #define FALSE 0 -#define INVALID_HANDLE_VALUE ((HANDLE)(uintptr_t)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 @@ -200,41 +238,90 @@ __declspec(dllexport) DWORD GetOEMCP(void) { return 437; } // DOS Latin US // File I/O // --------------------------------------------------------------------------- -// CreateFileA - open an existing file for reading. -// Write access, CREATE_ALWAYS and other modes return INVALID_HANDLE_VALUE -// because the VFS has no write path yet. +// 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; - // Only OPEN_EXISTING + read supported for now. - if (disposition != OPEN_EXISTING || (access & GENERIC_WRITE)) { - gLastError = 5; // ERROR_ACCESS_DENIED - return INVALID_HANDLE_VALUE; + int wantWrite = (access & GENERIC_WRITE) != 0; + + if (disposition == OPEN_EXISTING || disposition == TRUNCATE_EXISTING) { + // Open the file via the read path (it must already exist). + uint32_t kh = 0; + uint32_t s = SysOpenFile(name, &kh); + if (s != 0) { gLastError = 2; return INVALID_HANDLE_VALUE; } + + if (disposition == TRUNCATE_EXISTING && wantWrite) { + // Immediately truncate to zero. + uint32_t ts = SysTruncateFile(kh, 0); + if (ts != 0) { + SysCloseHandle(kh); + gLastError = 5; + return INVALID_HANDLE_VALUE; + } + } + return KH_TO_HANDLE(kh); } - uint32_t kh = 0; - uint32_t s = SysOpenFile(name, &kh); - if (s != 0 /* STATUS_SUCCESS */) { - gLastError = 2; // ERROR_FILE_NOT_FOUND - return INVALID_HANDLE_VALUE; + if (disposition == CREATE_ALWAYS && wantWrite) { + // Create-or-replace: try to create; if the file already exists, + // open it and truncate it to zero instead. + uint32_t kh = 0; + uint32_t s = SysCreateFile(name, &kh); + if (s != 0) { + // File may already exist - open and truncate. + s = SysOpenFile(name, &kh); + if (s != 0) { gLastError = 5; return INVALID_HANDLE_VALUE; } + SysTruncateFile(kh, 0); + } + return KH_TO_HANDLE(kh); } - return KH_TO_HANDLE(kh); + + if (disposition == OPEN_ALWAYS && wantWrite) { + // Open if exists, create if not. + uint32_t kh = 0; + if (SysOpenFile(name, &kh) != 0) { + if (SysCreateFile(name, &kh) != 0) { + gLastError = 5; + return INVALID_HANDLE_VALUE; + } + } + return KH_TO_HANDLE(kh); + } + + if (disposition == CREATE_NEW && wantWrite) { + // Fail if the file already exists (we can't check atomically, + // but SysCreateFile returns NAME_COLLISION for existing files). + uint32_t kh = 0; + uint32_t s = SysCreateFile(name, &kh); + if (s != 0) { gLastError = 80; return INVALID_HANDLE_VALUE; } // ERROR_FILE_EXISTS + return KH_TO_HANDLE(kh); + } + + // Any other combination (e.g. read-only CREATE_ALWAYS) is not supported. + gLastError = 87; // ERROR_INVALID_PARAMETER + return INVALID_HANDLE_VALUE; } // GetFileSize - fills optional high-DWORD pointer, returns low DWORD. // Returns 0xFFFFFFFF on error, matching Win32 behaviour. __declspec(dllexport) DWORD GetFileSize(HANDLE hFile, DWORD* fileSizeHigh) { - if (!hFile) { gLastError = 6; return 0xFFFFFFFFUL; } // ERROR_INVALID_HANDLE + if (!hFile) { gLastError = 6; return 0xFFFFFFFFUL; } uint64_t sz = SysGetFileSize(HANDLE_TO_KH(hFile)); if (fileSizeHigh) *fileSizeHigh = (DWORD)(sz >> 32); return (DWORD)(sz & 0xFFFFFFFFULL); } // ReadFile - sequential read at the file's current position. -// Overlapped is not supported (must be NULL). __declspec(dllexport) BOOL ReadFile(HANDLE hFile, void* buf, DWORD toRead, DWORD* bytesRead, void* overlapped) { (void)overlapped; @@ -242,36 +329,73 @@ __declspec(dllexport) BOOL ReadFile(HANDLE hFile, void* buf, DWORD toRead, 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; } // ERROR_INVALID_FUNCTION + if (s != 0) { gLastError = 1; return FALSE; } return TRUE; } -// SetFilePointer - seek to an absolute or relative position. +// WriteFile - sequential write at the file's current position. +// Extends the file if writing past EOF. Overlapped I/O is not supported. +__declspec(dllexport) BOOL WriteFile(HANDLE hFile, const void* buf, DWORD toWrite, + DWORD* bytesWritten, void* overlapped) { + (void)overlapped; + if (!hFile || !buf) { gLastError = 6; return FALSE; } + size_t written = 0; + uint32_t s = SysWriteFile(HANDLE_TO_KH(hFile), buf, (size_t)toWrite, &written); + if (bytesWritten) *bytesWritten = (DWORD)written; + if (s != 0) { gLastError = 29; return FALSE; } // ERROR_WRITE_FAULT + return TRUE; +} + +// SetFilePointer - seek to an absolute, relative, or end-relative position. // Returns new position low DWORD, or 0xFFFFFFFF on error. __declspec(dllexport) DWORD SetFilePointer(HANDLE hFile, long dist, long* distHigh, DWORD method) { if (!hFile) { gLastError = 6; return 0xFFFFFFFFUL; } + uint32_t kh = HANDLE_TO_KH(hFile); uint64_t offset = 0; + int64_t delta = distHigh + ? ((int64_t)(uint64_t)(uint32_t)*distHigh << 32) | (uint32_t)dist + : (int64_t)dist; + if (method == FILE_BEGIN) { - offset = (uint64_t)(int64_t)dist; - if (distHigh) offset |= (uint64_t)(uint32_t)*distHigh << 32; - } else if (method == FILE_CURRENT || method == FILE_END) { - // FILE_CURRENT and FILE_END require knowing the current offset or - // the file size; expose as unsupported for now. - gLastError = 1; // ERROR_INVALID_FUNCTION - return 0xFFFFFFFFUL; + 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; // ERROR_INVALID_PARAMETER - return 0xFFFFFFFFUL; + gLastError = 87; return 0xFFFFFFFFUL; } - uint32_t s = SysSeekFile(HANDLE_TO_KH(hFile), offset); + uint32_t s = SysSeekFile(kh, offset); if (s != 0) { gLastError = 1; return 0xFFFFFFFFUL; } - if (distHigh) *distHigh = (long)(offset >> 32); + if (distHigh) *distHigh = (long)(int32_t)(offset >> 32); return (DWORD)(offset & 0xFFFFFFFFULL); } +// SetEndOfFile - truncate or extend the file to the current seek position. +__declspec(dllexport) BOOL SetEndOfFile(HANDLE hFile) { + if (!hFile) { gLastError = 6; return FALSE; } + uint32_t kh = HANDLE_TO_KH(hFile); + uint64_t pos = SysGetFilePos(kh); + if (pos == (uint64_t)-1) { gLastError = 6; return FALSE; } + uint32_t s = SysTruncateFile(kh, pos); + if (s != 0) { gLastError = 1; return FALSE; } + return TRUE; +} + +// DeleteFileA - delete a file by name; no open handle required. +__declspec(dllexport) BOOL DeleteFileA(const char* lpFileName) { + if (!lpFileName) { gLastError = 87; return FALSE; } + uint32_t s = SysDeleteFile(lpFileName); + if (s != 0) { gLastError = 2; return FALSE; } // ERROR_FILE_NOT_FOUND + return TRUE; +} + // CloseHandle - unified close for files, mutexes, semaphores, events, queues. __declspec(dllexport) BOOL CloseHandle(HANDLE hObject) { if (!hObject) { gLastError = 6; return FALSE; } @@ -279,15 +403,6 @@ __declspec(dllexport) BOOL CloseHandle(HANDLE hObject) { return TRUE; } -// WriteFile stub - no write VFS support yet. -__declspec(dllexport) BOOL WriteFile(HANDLE hFile, const void* buf, DWORD toWrite, - DWORD* bytesWritten, void* overlapped) { - (void)hFile; (void)buf; (void)toWrite; (void)overlapped; - if (bytesWritten) *bytesWritten = 0; - gLastError = 1; // ERROR_INVALID_FUNCTION - return FALSE; -} - // =========================================================================== // Extended shims - funcs.txt additions // All functions here return plausible values; no real implementation exists @@ -474,21 +589,35 @@ __declspec(dllexport) BOOL CreateProcessA( (void)bInheritHandles; (void)dwCreationFlags; (void)lpEnvironment; (void)lpCurrentDirectory; (void)lpStartupInfo; - if (!lpApplicationName || !lpProcessInformation) { + // lpProcessInformation is mandatory + if (!lpProcessInformation) { gLastError = 87; // ERROR_INVALID_PARAMETER return FALSE; } - uint32_t kh = 0; - uint32_t s = SysCreateProcess(lpApplicationName, &kh); - if (s != 0 || kh == 0) { - gLastError = 2; // ERROR_FILE_NOT_FOUND / generic launch failure + // 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; } - lpProcessInformation->hProcess = KH_TO_HANDLE(kh); - lpProcessInformation->hThread = INVALID_HANDLE_VALUE; - lpProcessInformation->dwProcessId = 0; + uint32_t kh = 0; + // Call the syscall.dll stub + uint32_t s = SysCreateProcess(targetPath, &kh); + 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; } @@ -581,6 +710,271 @@ __declspec(dllexport) BOOL ReadConsoleA(HANDLE hConsoleInput, return TRUE; } +// WriteConsoleA: write nNumberOfCharsToWrite characters from lpBuffer to the +// console as white-on-black text via SYS_WRITE_CONSOLE. +__declspec(dllexport) BOOL WriteConsoleA(HANDLE hConsoleOutput, + const void* lpBuffer, + DWORD nNumberOfCharsToWrite, + DWORD* lpNumberOfCharsWritten, + void* lpReserved) { + (void)hConsoleOutput; (void)lpReserved; + if (!lpBuffer || nNumberOfCharsToWrite == 0) { + if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = 0; + return TRUE; + } + SysWriteConsole((const char*)lpBuffer, (size_t)nNumberOfCharsToWrite); + if (lpNumberOfCharsWritten) *lpNumberOfCharsWritten = nNumberOfCharsToWrite; + return TRUE; +} + +// --------------------------------------------------------------------------- +// Directory enumeration (FindFirstFileA / FindNextFileA / FindClose) +// --------------------------------------------------------------------------- + +// KDIR_ENTRY layout must exactly match the definition in io_file.h. +#define KDIR_NAME_MAX 260 + +typedef struct { + char Name[KDIR_NAME_MAX]; + uint8_t Attr; + uint32_t FileSize; +} KDIR_ENTRY; + +// FIND_HANDLE is a user-side wrapper: it stores the kernel dir handle and +// the wildcard pattern so FindNextFileA can filter subsequent entries. +// We return a pointer to this struct cast to HANDLE; it is NOT a kernel +// handle, so FindClose must NOT go through CloseHandle/SysCloseHandle. +typedef struct { + uint32_t kh; // kernel DIR_OBJECT handle (raw pointer) + char pattern[MAX_PATH]; // wildcard pattern, e.g. "*" or "*.txt" +} FIND_HANDLE; + +// Minimal wildcard match: supports '*' (any sequence) and '?' (any char). +// Case-insensitive to match FAT32/Windows behaviour. +static int WildMatch(const char* pat, const char* name) { + while (*pat && *name) { + if (*pat == '*') { + // Skip consecutive stars + while (*pat == '*') pat++; + if (!*pat) return 1; // trailing * matches everything + // Try matching the rest of the pattern at every position + while (*name) { + if (WildMatch(pat, name)) return 1; + name++; + } + return 0; + } + char p = (*pat >= 'A' && *pat <= 'Z') ? *pat + 32 : *pat; + char n = (*name >= 'A' && *name <= 'Z') ? *name + 32 : *name; + if (*pat != '?' && p != n) return 0; + pat++; name++; + } + // Consume trailing stars in the pattern + while (*pat == '*') pat++; + return (*pat == '\0' && *name == '\0'); +} + +// Split a FindFirstFileA-style search path into directory and pattern parts. +// "apps\\*" -> dir="apps" pat="*" +// "*.txt" -> dir="" pat="*.txt" +// "*" -> dir="" pat="*" +// "apps\\" -> dir="apps" pat="*" +static void SplitFindPath(const char* searchPath, + char* dirOut, size_t dirSize, + char* patOut, size_t patSize) { + // Find last backslash or forward-slash + const char* last = NULL; + for (const char* p = searchPath; *p; p++) + if (*p == '\\' || *p == '/') last = p; + + if (!last) { + // No separator: whole string is the pattern, dir is CWD + if (dirOut && dirSize) dirOut[0] = '\0'; + if (patOut && patSize) { + size_t len = strlen(searchPath); + if (len + 1 > patSize) len = patSize - 1; + memcpy(patOut, searchPath, len); + patOut[len] = '\0'; + } + } else { + // Everything before the last separator is the directory + if (dirOut) { + size_t len = (size_t)(last - searchPath); + if (len + 1 > dirSize) len = dirSize - 1; + memcpy(dirOut, searchPath, len); + dirOut[len] = '\0'; + } + // Everything after the last separator is the pattern + if (patOut) { + const char* p = last + 1; + size_t len = strlen(p); + if (len + 1 > patSize) len = patSize - 1; + memcpy(patOut, p, len); + patOut[len] = '\0'; + } + } + + // Empty pattern defaults to "*" + if (patOut && patOut[0] == '\0') { + patOut[0] = '*'; patOut[1] = '\0'; + } +} + +// Fill a WIN32_FIND_DATAA from a KDIR_ENTRY. +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010UL +#define FILE_ATTRIBUTE_ARCHIVE 0x00000020UL +#define FILE_ATTRIBUTE_NORMAL 0x00000080UL + +static void KdirToFindData(const KDIR_ENTRY* ke, WIN32_FIND_DATAA* fd) { + // Zero the entire struct (covers all FILETIME and reserved fields) + unsigned char* p = (unsigned char*)fd; + for (size_t i = 0; i < sizeof(*fd); i++) p[i] = 0; + + fd->dwFileAttributes = ke->Attr; + if (!(fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + !(fd->dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)) + fd->dwFileAttributes |= FILE_ATTRIBUTE_NORMAL; + + fd->nFileSizeLow = ke->FileSize; + fd->nFileSizeHigh = 0; + + size_t nameLen = strlen(ke->Name); + if (nameLen >= MAX_PATH) nameLen = MAX_PATH - 1; + memcpy(fd->cFileName, ke->Name, nameLen); + fd->cFileName[nameLen] = '\0'; +} + +__declspec(dllexport) HANDLE FindFirstFileA(const char* lpFileName, + WIN32_FIND_DATAA* lpFindFileData) { + if (!lpFileName || !lpFindFileData) { + gLastError = 87; // ERROR_INVALID_PARAMETER + return INVALID_HANDLE_VALUE; + } + + // Split path into directory and wildcard pattern + char dir[MAX_PATH]; + char pat[MAX_PATH]; + SplitFindPath(lpFileName, dir, sizeof(dir), pat, sizeof(pat)); + + // Open the kernel directory iterator + uint32_t kh = 0; + uint32_t s = SysOpenDir(dir[0] ? dir : NULL, &kh); + if (s != 0 || kh == 0) { + gLastError = 2; // ERROR_FILE_NOT_FOUND + return INVALID_HANDLE_VALUE; + } + + // Allocate our user-side FIND_HANDLE wrapper + FIND_HANDLE* fh = (FIND_HANDLE*)HeapAlloc(NULL, 0, sizeof(FIND_HANDLE)); + if (!fh) { + SysCloseHandle(kh); + gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY + return INVALID_HANDLE_VALUE; + } + fh->kh = kh; + size_t patLen = strlen(pat); + if (patLen >= MAX_PATH) patLen = MAX_PATH - 1; + memcpy(fh->pattern, pat, patLen); + fh->pattern[patLen] = '\0'; + + // Read the first entry that matches the pattern + KDIR_ENTRY ke; + for (;;) { + uint32_t rs = SysReadDir(kh, &ke); + if (rs != 0) { + // Directory exhausted or error + SysCloseHandle(kh); + HeapFree(NULL, 0, fh); + gLastError = 18; // ERROR_NO_MORE_FILES + return INVALID_HANDLE_VALUE; + } + if (WildMatch(fh->pattern, ke.Name)) { + KdirToFindData(&ke, lpFindFileData); + return (HANDLE)fh; + } + } +} + +__declspec(dllexport) BOOL FindNextFileA(HANDLE hFindFile, + WIN32_FIND_DATAA* lpFindFileData) { + if (hFindFile == INVALID_HANDLE_VALUE || !lpFindFileData) { + gLastError = 6; // ERROR_INVALID_HANDLE + return FALSE; + } + + FIND_HANDLE* fh = (FIND_HANDLE*)hFindFile; + KDIR_ENTRY ke; + + for (;;) { + uint32_t rs = SysReadDir(fh->kh, &ke); + if (rs != 0) { + gLastError = 18; // ERROR_NO_MORE_FILES + return FALSE; + } + if (WildMatch(fh->pattern, ke.Name)) { + KdirToFindData(&ke, lpFindFileData); + return TRUE; + } + } +} + +__declspec(dllexport) BOOL FindClose(HANDLE hFindFile) { + if (hFindFile == INVALID_HANDLE_VALUE) { + gLastError = 6; + return FALSE; + } + FIND_HANDLE* fh = (FIND_HANDLE*)hFindFile; + SysCloseHandle(fh->kh); + HeapFree(NULL, 0, fh); + return TRUE; +} + +// --------------------------------------------------------------------------- +// Current Working Directory +// --------------------------------------------------------------------------- + +__declspec(dllexport) BOOL SetCurrentDirectoryA(const char* lpPathName) { + if (!lpPathName) { gLastError = 87; return FALSE; } + uint32_t s = SysSetCwd(lpPathName); + if (s != 0) { gLastError = 2; return FALSE; } + return TRUE; +} + +__declspec(dllexport) DWORD GetCurrentDirectoryA(DWORD nBufferLength, char* lpBuffer) { + if (!lpBuffer || nBufferLength == 0) { + // Return required size (including NUL). Max CWD is 512 bytes + "C:\". + return 515; + } + + // Retrieve the raw relative CWD from the kernel (e.g. "" or "apps\\data") + char raw[512]; + uint32_t s = SysGetCwd(raw, sizeof(raw)); + if (s != 0) { gLastError = 1; return 0; } + + // Prepend the Windows-style drive letter so callers see "C:\apps\data" + // rather than just "apps\data". + char full[515]; + if (raw[0] == '\0') { + // Root + full[0] = 'C'; full[1] = ':'; full[2] = '\\'; full[3] = '\0'; + } else { + // "C:\" + 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, diff --git a/code/win32/user32.c b/code/win32/user32.c index 1253c24..8bf0c4b 100644 --- a/code/win32/user32.c +++ b/code/win32/user32.c @@ -1,62 +1,53 @@ -// user32.c - Win32 user interface stubs (no window manager yet) +// user32.c - Win32 User Interface #include #include -// --------------------------------------------------------------------------- -// Syscall stubs (from syscall.dll) -// --------------------------------------------------------------------------- -extern void SysScreenBlit(int x, int y, int w, int h, const uint32_t* pixels); extern void SysYield(void); extern void SysDebugPrint(const char* str); -extern uint32_t SysKbPoll(void); -// --------------------------------------------------------------------------- -// Types -// --------------------------------------------------------------------------- typedef void* HWND; +typedef void* HDC; +typedef long LONG; +typedef int BOOL; -#define IDOK 1 -#define IDCANCEL 2 +typedef struct _RECT { LONG left, top, right, bottom; } RECT; +typedef struct tagPAINTSTRUCT { + HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; char res[32]; +} PAINTSTRUCT; -// --------------------------------------------------------------------------- -// Keyboard -// --------------------------------------------------------------------------- - -// GetAsyncKeyState: returns non-zero if a key is available in the input -// buffer. We have no per-key state, so the vKey argument is ignored and -// the high bit (key-down) is set whenever SysKbPoll reports data waiting. -__declspec(dllexport) short GetAsyncKeyState(int vKey) { - (void)vKey; - // High bit (0x8000 / INT16_MIN) = key is down. - // Low bit = key pressed since last call; we have no per-key history to - // track, so it is left clear. - return SysKbPoll() ? (short)(-32768) : 0; +// --- Painting Functions --- +__declspec(dllexport) HDC BeginPaint(HWND hwnd, PAINTSTRUCT* lpPaint) { + (void)hwnd; + if (lpPaint) { + lpPaint->hdc = (HDC)1; + lpPaint->rcPaint.right = 1920; lpPaint->rcPaint.bottom = 1080; + } + return (HDC)1; } -// --------------------------------------------------------------------------- -// Screen output -// --------------------------------------------------------------------------- +__declspec(dllexport) BOOL EndPaint(HWND hwnd, const PAINTSTRUCT* lp) { (void)hwnd; (void)lp; return 1; } -// BitBlt is the only graphical primitive available at this stage. -__declspec(dllexport) void BitBlt(int x, int y, int w, int h, const uint32_t* pixels) { - SysScreenBlit(x, y, w, h, pixels); +// --- Message Loop Stubs --- +__declspec(dllexport) int GetMessage(void* m, HWND h, uint32_t f, uint32_t l) { + (void)m; (void)h; (void)f; (void)l; SysYield(); return 0; } - -// --------------------------------------------------------------------------- -// Message loop (stubs - always return 0 / no messages) -// --------------------------------------------------------------------------- -__declspec(dllexport) int GetMessage(void* msg, HWND hwnd, - unsigned int min, unsigned int max) { - (void)msg; (void)hwnd; (void)min; (void)max; - SysYield(); +__declspec(dllexport) int PeekMessage(void* m, HWND h, uint32_t f, uint32_t l, uint32_t r) { + (void)m; (void)h; (void)f; (void)l; (void)r; return 0; } +__declspec(dllexport) int DispatchMessage(const void* m) { (void)m; return 0; } +__declspec(dllexport) int TranslateMessage(const void* m) { (void)m; return 0; } -__declspec(dllexport) int DispatchMessage(const void* msg) { (void)msg; return 0; } +// --- Dialogs & UI --- +__declspec(dllexport) int MessageBoxA(HWND h, const char* txt, const char* cap, uint32_t type) { + (void)h; (void)cap; (void)type; + SysDebugPrint("USER32: MessageBoxA: "); + SysDebugPrint(txt); + SysDebugPrint("\n"); + return 1; // IDOK +} -// --------------------------------------------------------------------------- -// Window queries (stubs - no WM exists yet, all return NULL / 0) -// --------------------------------------------------------------------------- +// --- Window Queries --- __declspec(dllexport) HWND GetDesktopWindow(void) { return (HWND)0; } __declspec(dllexport) HWND GetActiveWindow(void) { return (HWND)0; } __declspec(dllexport) HWND GetTopWindow(HWND hwnd){ (void)hwnd; return (HWND)0; } @@ -78,14 +69,3 @@ __declspec(dllexport) unsigned long GetWindowThreadProcessId(HWND hwnd, unsigned if (pid) *pid = 1; return 1; // Thread ID } - -// --------------------------------------------------------------------------- -// Dialogs -// --------------------------------------------------------------------------- -__declspec(dllexport) int MessageBoxA(HWND hwnd, const char* text, - const char* caption, unsigned int type) { - (void)hwnd; (void)text; (void)caption; (void)type; - // No UI available, console - SysDebugPrint(text); - return IDOK; -} diff --git a/readme.md b/readme.md index c60f2f0..2fde100 100644 --- a/readme.md +++ b/readme.md @@ -65,7 +65,7 @@ drivers to be loaded have their path stored in `drivers.ini`, split into essenti a disk driver fills in a `DISK_DRIVER` vtable and calls `IoRegisterDisk(index, &driver)`. a filesystem driver fills in an `IO_FILESYSTEM` vtable and calls `IoRegisterFilesystem(index, &fs, &vol)`. -currently shipping: AHCI (interrupts), IDE (PIO/DMA), FAT32 (read-only). +currently shipping: AHCI (interrupts), IDE (PIO/DMA) and FAT32. ## how it boots @@ -106,7 +106,7 @@ Q: Is the scheduler preemptive? A: As much as it will ever be. Q: Will it corrupt my data? -A: How would it? It's read-only. +A: I hope not. Q: Is this finished? A: Not in the slightest.