diff --git a/src/oar.zig b/src/oar.zig index f1e3b0f..ed8db92 100644 --- a/src/oar.zig +++ b/src/oar.zig @@ -1,29 +1,16 @@ const std = @import("std"); const sys = @import("./sys.zig"); +const table = @import("./table.zig"); /// -/// An entry block of an Oar archive file. /// -/// Typically, following this block in memory is the file data it holds the meta-information for. /// -pub const Entry = extern struct { - signature: [3]u8 = signature_magic, - revision: u8, - name_buffer: [255]u8 = std.mem.zeroes([255]u8), - name_length: u8 = 0, - file_size: u64, - file_offset: u64, - padding: [232]u8 = std.mem.zeroes([232]u8), - - comptime { - const entry_size = @sizeOf(Entry); - - if (entry_size != 512) - @compileError("Entry is " ++ std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); - } +pub const Archive = struct { + file_access: sys.FileAccess, + index_cache: IndexCache, /// - /// [FindError.EntryNotFound] happens when an entry could not be found. + /// [OpenError.EntryNotFound] happens when an entry could not be found. /// pub const FindError = sys.FileAccess.Error || error { EntryNotFound, @@ -32,15 +19,110 @@ pub const Entry = extern struct { /// /// /// - pub fn find(file_access: sys.FileAccess, entry_name: []const u8) FindError!Entry { - _ = file_access; - _ = entry_name; + pub const InitError = std.mem.Allocator.Error; - return error.EntryNotFound; + /// + /// + /// + const IndexCache = table.Hashed([]const u8, Entry.Header, table.string_context); + + /// + /// Finds an entry matching `entry_path` in `archive`. + /// + /// The found [Entry] value is returned or a [FindError] if it failed to be found. + /// + pub fn find(archive: *Archive, entry_path: []const u8) FindError!Entry { + return Entry{ + .header = find_header: { + if (archive.index_cache.lookup(entry_path)) |entry_header| + break: find_header entry_header.*; + + // Start from beginning of archive. + try archive.file_access.seek(0); + + var entry_header = Entry.Header{ + .revision = 0, + .file_size = 0, + .file_offset = 0 + }; + + const read_buffer = std.mem.asBytes(&entry_header); + + // Read first entry. + while ((try archive.file_access.read(read_buffer)) == @sizeOf(Entry.Header)) { + if (std.mem.eql(u8, entry_path, entry_header. + name_buffer[0 .. entry_header.name_length])) { + + // If caching fails... oh well... + archive.index_cache.insert(entry_path, entry_header) catch {}; + + break: find_header entry_header; + } + + // Move over file data following the entry. + var to_skip = entry_header.file_size; + + while (to_skip != 0) { + const skipped = std.math.min(to_skip, std.math.maxInt(i64)); + + try archive.file_access.skip(@intCast(i64, skipped)); + + to_skip -= skipped; + } + } + + return error.EntryNotFound; + }, + + .owner = &archive.file_access, + .cursor = 0, + }; } /// - /// Magic identifier used to validate [Entry] data. /// - const signature_magic = [3]u8{'o', 'a', 'r'}; + /// + pub fn init(allocator: std.mem.Allocator, archive_file_access: sys.FileAccess) InitError!Archive { + return Archive{ + .index_cache = try IndexCache.init(allocator), + .file_access = archive_file_access, + }; + } +}; + +/// +/// Handles the state of an opened archive entry. +/// +pub const Entry = struct { + owner: ?*sys.FileAccess, + cursor: u64, + header: Header, + + /// + /// An entry block of an Oar archive file. + /// + /// Typically, following the block in memory is the file data it holds the meta-information for. + /// + pub const Header = extern struct { + signature: [signature_magic.len]u8 = signature_magic, + revision: u8, + name_buffer: [255]u8 = std.mem.zeroes([255]u8), + name_length: u8 = 0, + file_size: u64, + file_offset: u64, + padding: [232]u8 = std.mem.zeroes([232]u8), + + comptime { + const entry_size = @sizeOf(Header); + + if (entry_size != 512) + @compileError("Entry is " ++ + std.fmt.comptimePrint("{d}", .{entry_size}) ++ " bytes"); + } + + /// + /// Magic identifier used to validate [Entry] data. + /// + pub const signature_magic = [3]u8{'o', 'a', 'r'}; + }; }; diff --git a/src/sys.zig b/src/sys.zig index e431003..6b3dc10 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -8,7 +8,6 @@ const meta = @import("./meta.zig"); const oar = @import("./oar.zig"); const stack = @import("./stack.zig"); const std = @import("std"); -const table = @import("./table.zig"); /// /// A thread-safe platform abstraction over multiplexing system I/O processing and event handling. @@ -131,28 +130,25 @@ pub const AppContext = opaque { } /// - /// Initializes a new [Implemenation] with `data_archive_file_access` as the data archive to - /// read from and `user_path_prefix` as the native writable user data directory. + /// Initializes a new [Implemenation] with `data_access` as the data archive to read from + /// and `user_path_prefix` as the native writable user data directory. /// /// Returns the created [Implementation] value on success or [InitError] on failure. /// - fn init(allocator: std.mem.Allocator, - data_archive_file_access: FileAccess) InitError!Implementation { - + fn init(allocator: std.mem.Allocator, data_access: FileAccess) InitError!Implementation { const user_path_prefix = ext.SDL_GetPrefPath("ona", "ona") orelse return error.OutOfMemory; return Implementation{ - .user_file_system = .{.native = .{.path_prefix = - user_path_prefix[0 .. std.mem.len(user_path_prefix)]}}, + .user_file_system = .{.native = + user_path_prefix[0 .. std.mem.len(user_path_prefix)]}, .message_semaphore = ext.SDL_CreateSemaphore(0) orelse return error.OutOfSemaphores, .message_mutex = ext.SDL_CreateMutex() orelse return error.OutOfMutexes, .user_path_prefix = user_path_prefix, .data_file_system = .{.archive = .{ - .file_access = data_archive_file_access, - .index_cache = try FileSystem.ArchiveIndexCache.init(allocator), + .instance = try oar.Archive.init(allocator, data_access), }}, .message_thread = null, @@ -283,6 +279,7 @@ pub const FileAccess = struct { read: fn (*anyopaque, []u8) Error!usize, seek: fn (*anyopaque, u64) Error!void, seekToEnd: fn (*anyopaque) Error!void, + skip: fn (*anyopaque, i64) Error!void, }; /// @@ -343,14 +340,22 @@ pub const FileAccess = struct { } /// - /// Attempts to seek `file_access` to the end of the file while using `app_context` as the execution - /// context. + /// Attempts to seek `file_access` to the end of the file. /// /// Returns [Error] on failure. /// pub fn seekToEnd(file_access: *FileAccess) Error!void { return file_access.implementation.seekToEnd(file_access.context); } + + /// + /// Attempts to seek `file_access` by `offset` from the current file position. + /// + /// Returns [Error] on failure; + /// + pub fn skip(file_access: *FileAccess, offset: i64) Error!void { + return file_access.implementation.skip(file_access.context, offset); + } }; /// @@ -358,31 +363,15 @@ pub const FileAccess = struct { /// available to the application in a sandboxed environment. /// pub const FileSystem = union(enum) { - native: struct { - path_prefix: []const u8, - }, + native: []const u8, archive: struct { - file_access: FileAccess, - index_cache: ArchiveIndexCache, + instance: oar.Archive, + entry_table: [max_open_entries]oar.Entry = std.mem.zeroes([max_open_entries]oar.Entry), - entry_table: [max_open_entries]ArchiveEntry = - std.mem.zeroes([max_open_entries]ArchiveEntry), - - const max_open_entries = 16; + pub const max_open_entries = 16; }, - /// - /// Handles the state of an opened archive entry. - /// - const ArchiveEntry = struct { - using: ?*FileAccess, - header: oar.Entry, - cursor: u64, - }; - - const ArchiveIndexCache = table.Hashed([]const u8, oar.Entry, table.string_context); - /// /// Platform-agnostic mechanism for referencing files and directories on a [FileSystem]. /// @@ -462,57 +451,37 @@ pub const FileSystem = union(enum) { .archive => |*archive| { if (mode != .readonly) return error.ModeUnsupported; - for (archive.entry_table) |*entry| if (entry.using == null) { - const entry_path = path.buffer[0 .. path.length]; - - entry.* = .{ - .header = find_header: { - if (archive.index_cache.lookup(entry_path)) |header| - break: find_header header.*; - - const header = oar.Entry.find(archive.file_access, - entry_path) catch return error.FileNotFound; - - // If caching fails... oh well... - archive.index_cache.insert(entry_path, header) catch {}; - - break: find_header header; - }, - - .using = &archive.file_access, - .cursor = 0, - }; - + for (archive.entry_table) |*entry| if (entry.owner == null) { const Implementation = struct { - fn archiveEntryCast(context: *anyopaque) *ArchiveEntry { - return @ptrCast(*ArchiveEntry, @alignCast( - @alignOf(ArchiveEntry), context)); + fn close(context: *anyopaque) void { + entryCast(context).owner = null; } - fn close(context: *anyopaque) void { - archiveEntryCast(context).using = null; + fn entryCast(context: *anyopaque) *oar.Entry { + return @ptrCast(*oar.Entry, @alignCast( + @alignOf(oar.Entry), context)); } fn queryCursor(context: *anyopaque) FileAccess.Error!u64 { - const archive_entry = archiveEntryCast(context); + const archive_entry = entryCast(context); - if (archive_entry.using == null) return error.FileInaccessible; + if (archive_entry.owner == null) return error.FileInaccessible; return archive_entry.cursor; } fn queryLength(context: *anyopaque) FileAccess.Error!u64 { - const archive_entry = archiveEntryCast(context); + const archive_entry = entryCast(context); - if (archive_entry.using == null) return error.FileInaccessible; + if (archive_entry.owner == null) return error.FileInaccessible; return archive_entry.header.file_size; } fn read(context: *anyopaque, buffer: []u8) FileAccess.Error!usize { - const archive_entry = archiveEntryCast(context); + const archive_entry = entryCast(context); - const file_access = archive_entry.using orelse + const file_access = archive_entry.owner orelse return error.FileInaccessible; if (archive_entry.cursor >= archive_entry.header.file_size) @@ -525,22 +494,42 @@ pub const FileSystem = union(enum) { } fn seek(context: *anyopaque, cursor: usize) FileAccess.Error!void { - const archive_entry = archiveEntryCast(context); + const archive_entry = entryCast(context); - if (archive_entry.using == null) return error.FileInaccessible; + if (archive_entry.owner == null) return error.FileInaccessible; archive_entry.cursor = cursor; } fn seekToEnd(context: *anyopaque) FileAccess.Error!void { - const archive_entry = archiveEntryCast(context); + const archive_entry = entryCast(context); - if (archive_entry.using == null) return error.FileInaccessible; + if (archive_entry.owner == null) return error.FileInaccessible; archive_entry.cursor = archive_entry.header.file_size; } + + fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void { + const math = std.math; + const archive_entry = entryCast(context); + + if (archive_entry.owner == null) return error.FileInaccessible; + + if (offset < 0) { + archive_entry.cursor = math.max(0, + archive_entry.cursor - math.absCast(offset)); + } else { + archive_entry.cursor += @intCast(u64, offset); + } + } }; + entry.* = archive.instance.find(path.buffer[0 .. path.length]) catch |err| + return switch (err) { + error.FileInaccessible => error.FileNotFound, + error.EntryNotFound => error.FileNotFound, + }; + return FileAccess{ .context = entry, @@ -551,6 +540,7 @@ pub const FileSystem = union(enum) { .read = Implementation.read, .seek = Implementation.seek, .seekToEnd = Implementation.seekToEnd, + .skip = Implementation.skip, }, }; }; @@ -559,22 +549,19 @@ pub const FileSystem = union(enum) { }, .native => |native| { - if (native.path_prefix.len == 0) return error.FileNotFound; + if (native.len == 0) return error.FileNotFound; var path_buffer = std.mem.zeroes([4096]u8); + const seperator_length = @boolToInt(native[native.len - 1] != seperator); - const seperator_length = @boolToInt(native.path_prefix[ - native.path_prefix.len - 1] != seperator); - - if ((native.path_prefix.len + seperator_length + path.length) >= + if ((native.len + seperator_length + path.length) >= path_buffer.len) return error.FileNotFound; - std.mem.copy(u8, path_buffer[0 ..], native.path_prefix); + std.mem.copy(u8, path_buffer[0 ..], native); - if (seperator_length != 0) - path_buffer[native.path_prefix.len] = seperator; + if (seperator_length != 0) path_buffer[native.len] = seperator; - std.mem.copy(u8, path_buffer[native.path_prefix.len .. + std.mem.copy(u8, path_buffer[native.len .. path_buffer.len], path.buffer[0 .. path.length]); ext.SDL_ClearError(); @@ -629,15 +616,14 @@ pub const FileSystem = union(enum) { while (to_seek != 0) { const math = std.math; - const sought = @intCast(i64, math.min(to_seek, math.maxInt(i64))); + const sought = math.min(to_seek, math.maxInt(i64)); ext.SDL_ClearError(); - if (ext.SDL_RWseek(rwOpsCast(context), sought, ext.RW_SEEK_CUR) < 0) - return error.FileInaccessible; + if (ext.SDL_RWseek(rwOpsCast(context), @intCast(i64, sought), + ext.RW_SEEK_CUR) < 0) return error.FileInaccessible; - // Cannot be less than zero because it is derived from `read`. - to_seek -= @intCast(u64, sought); + to_seek -= sought; } } @@ -647,6 +633,13 @@ pub const FileSystem = union(enum) { if (ext.SDL_RWseek(rwOpsCast(context), 0, ext.RW_SEEK_END) < 0) return error.FileInaccessible; } + + fn skip(context: *anyopaque, offset: i64) FileAccess.Error!void { + ext.SDL_ClearError(); + + if (ext.SDL_RWseek(rwOpsCast(context), offset, ext.RW_SEEK_SET) < 0) + return error.FileInaccessible; + } }; return FileAccess{ @@ -663,6 +656,7 @@ pub const FileSystem = union(enum) { .read = Implementation.read, .seek = Implementation.seek, .seekToEnd = Implementation.seekToEnd, + .skip = Implementation.skip, }, }; }, @@ -855,7 +849,7 @@ pub fn runGraphics(comptime Error: anytype, defer ext.SDL_DestroyRenderer(renderer); - var cwd_file_system = FileSystem{.native =.{.path_prefix = "./"}}; + var cwd_file_system = FileSystem{.native ="./"}; var data_access = try (try cwd_file_system.joinedPath(&.{"./data.oar"})).open(.readonly); defer data_access.close();