const std = @import("std"); const Errors = error{ InvalidSyntax, ReadSectors, FileNotFound }; var buffer: [1024]u8 = undefined; var g_Fat: []u8 = undefined; pub const std_options: std.Options = .{ .log_level = .info, }; const BootSector = extern struct { BootJumpInstruction: [3]u8 align(1), OemIdentifier: [8]u8 align(1), BytesPerSector: u16 align(1), SectorsPerCluster: u8 align(1), ReservedSectors: u16 align(1), FatCount: u8 align(1), DirEntryCount: u16 align(1), TotalSectors: u16 align(1), MediaDescriptorType: u8 align(1), SectorsPerFat: u16 align(1), SectorsPerTrack: u16 align(1), Heads: u16 align(1), HiddenSectors: u32 align(1), LargeSectorCount: u32 align(1), // ebr DriveNumber: u8 align(1), _Reserved: u8 align(1), Signature: u8 align(1), VolumeId: u32 align(1), VolumeLaber: [11]u8 align(1), SystemId: [8]u8 align(1), }; var g_BootSector: BootSector = undefined; const DirectoryEntry = extern struct { Name: [11]u8 align(1), Attributes: u8 align(1), _Reserved: u8 align(1), CreatedTimeTenths: u8 align(1), CreatedTime: u16 align(1), CreatedDate: u16 align(1), AccessedDate: u16 align(1), FirstClusterHigh: u16 align(1), ModifiedTime: u16 align(1), ModifiedDate: u16 align(1), FirstClusterLow: u16 align(1), Size: u32 align(1), }; var g_RootDirectory: []DirectoryEntry = undefined; var g_RootDirectoryEnd: u32 = undefined; fn readStruct(reader: *std.fs.File.Reader, T: type) !T { var ret: [@sizeOf(T)]u8 = undefined; _ = try reader.*.readStreaming(&ret); return @bitCast(ret); } fn readBootSector(disk: *std.fs.File) !void { var reader = disk.*.reader(&buffer); g_BootSector = try readStruct(&reader, BootSector); } fn readSectors(disk: *std.fs.File, lba: u32, count: u32, bufferOut: *[]u8) !void { const dest: []u8 = try std.heap.smp_allocator.alloc(u8, g_BootSector.BytesPerSector * count); defer std.heap.smp_allocator.free(dest); try disk.seekTo(lba * g_BootSector.BytesPerSector); var reader = disk.readerStreaming(&buffer); if (try reader.readStreaming(dest) != g_BootSector.BytesPerSector * count) return Errors.ReadSectors; @memcpy(bufferOut.*, dest); } fn readFat(disk: *std.fs.File) !void { g_Fat = try std.heap.smp_allocator.alloc(u8, g_BootSector.SectorsPerFat * g_BootSector.BytesPerSector); errdefer std.heap.smp_allocator.free(g_Fat); try readSectors(disk, g_BootSector.ReservedSectors, g_BootSector.SectorsPerFat, &g_Fat); } fn readRootDirectory(disk: *std.fs.File) !void { const lba: u32 = g_BootSector.ReservedSectors + g_BootSector.SectorsPerFat * g_BootSector.FatCount; const size: u32 = @sizeOf(DirectoryEntry) * g_BootSector.DirEntryCount; var sectors: u32 = (size / g_BootSector.BytesPerSector); if (@rem(size, g_BootSector.BytesPerSector) > 0) sectors += 1; g_RootDirectory = try std.heap.smp_allocator.alloc(DirectoryEntry, sectors * g_BootSector.BytesPerSector); g_RootDirectoryEnd = lba + sectors; try readSectors(disk, lba, sectors, @ptrCast(&g_RootDirectory)); for (g_RootDirectory) |Entry| { std.log.debug("Entry: {any}, Entry.Name: {s}", .{ Entry, Entry.Name, }); } } fn findFile(name: []const u8) !*DirectoryEntry { for (g_RootDirectory, 0..) |Entry, i| { std.log.debug("looking at {s}", .{Entry.Name}); if (std.mem.eql(u8, &Entry.Name, name)) return &g_RootDirectory[i]; } return Errors.FileNotFound; } fn readFile(file: *DirectoryEntry, disk: *std.fs.File, outputBuffer: *[]u8) !void { var currentCluster: u16 = file.FirstClusterLow; const offset = g_BootSector.SectorsPerCluster * g_BootSector.BytesPerSector; var i: usize = 0; while (currentCluster < 0x0FF8) { std.log.debug("currentCluster: {}", .{currentCluster}); const lba: u32 = g_RootDirectoryEnd + (currentCluster -% 2) * g_BootSector.SectorsPerCluster; var sizedOutput = outputBuffer.*[i .. i + offset]; try readSectors(disk, lba, g_BootSector.SectorsPerCluster, &sizedOutput); std.log.debug("sizedOutput: {s}", .{sizedOutput}); i += offset; const fatindex = currentCluster * 3 / 2; std.log.debug("@as(u16, g_Fat[fatindex]): {}, g_Fat[fatindex]: {}", .{ @as(*align(1) u16, @ptrCast(&g_Fat[fatindex])).*, g_Fat[fatindex] }); if (@rem(currentCluster, 2) == 0) { currentCluster = @as(*align(1) u16, @ptrCast(&g_Fat[fatindex])).* & 0x0FFF; } else { currentCluster = @as(*align(1) u16, @ptrCast(&g_Fat[fatindex])).* >> 4; } } } pub fn main() !void { var args = try std.process.argsWithAllocator(std.heap.smp_allocator); defer args.deinit(); if (args.inner.count < 3) { std.log.err("syntax: {s} ", .{std.os.argv[0]}); return Errors.InvalidSyntax; } _ = args.skip(); var disk = try std.fs.cwd().openFileZ(args.next().?, .{ .mode = .read_only }); std.log.debug("opened disk {any}", .{disk}); try readBootSector(&disk); std.log.debug("read BootSector from disk, {any}", .{g_BootSector}); try readFat(&disk); defer std.heap.smp_allocator.free(g_Fat); std.log.debug("read file allocation table from disk, {any}", .{g_Fat}); try readRootDirectory(&disk); defer std.heap.smp_allocator.free(g_RootDirectory); std.log.debug("read root directory from disk, {any}", .{g_RootDirectory}); const fileEntry = try findFile(try std.fmt.allocPrint(std.heap.smp_allocator, "{s}", .{args.next().?})); var fileBuffer = try std.heap.smp_allocator.alloc(u8, fileEntry.Size + g_BootSector.BytesPerSector); @memset(fileBuffer, 0); defer std.heap.smp_allocator.free(fileBuffer); try readFile(fileEntry, &disk, &fileBuffer); const stdout = std.fs.File.stdout(); defer stdout.close(); var writer = stdout.writer(&buffer); try writer.interface.print("{s}\n", .{@as(*[:0]u8, @ptrCast(&fileBuffer)).*}); const newline = "\n"; _ = try std.posix.write(0, fileBuffer); _ = try std.posix.write(0, newline); }