diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 2b8c7a4..62c1822 100644 Binary files a/OVMF/OVMF_VARS_4M_d.fd and b/OVMF/OVMF_VARS_4M_d.fd differ diff --git a/build.bat b/build.bat index 06cbeba..ab74cad 100644 --- a/build.bat +++ b/build.bat @@ -54,7 +54,7 @@ for %%f in (%SRC%\hal\*.c) do ( :: ---- Link hal.dll ---- echo Linking hal.dll... -link %LFLAGS% /DLL /OUT:"bin\hal.dll" /ENTRY:HalInitialize obj\printf.obj obj\libc.obj obj\hal\*.obj || exit /b 1 +link %LFLAGS% /DLL /OUT:"bin\hal.dll" /ENTRY:HalInitialize /IGNORE:4216 obj\printf.obj obj\libc.obj obj\hal\*.obj || exit /b 1 :: ---- Compile kernel ---- echo Compiling kernel... diff --git a/code/drivers/fat32/fat32.c b/code/drivers/fat32/fat32.c index 58c7f22..b097130 100644 --- a/code/drivers/fat32/fat32.c +++ b/code/drivers/fat32/fat32.c @@ -1,6 +1,5 @@ // fat32.c - FAT32 filesystem driver -// Registers itself via IoRegisterFilesystem after finding a FAT32 partition. -// Supports read-only access; write support is not implemented. +// Read + write support: file data, cluster allocation, directory entries. #include #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.