const coral = @import("coral"); const ext = @import("./ext.zig"); pub const Access = union (enum) { null, sandboxed_path: *const Path, pub fn open_readable(self: Access, readable_path: Path) ?*Readable { switch (self) { .null => return null, .sandboxed_path => |sandboxed_path| { const readable_path_string = sandboxed_path.joined(readable_path).to_string() orelse return null; return @ptrCast(ext.SDL_RWFromFile(readable_path_string.ptr, "rb")); }, } } pub fn query(self: Access, path: Path) ?Info { switch (self) { .null => return null, .sandboxed_path => |sandboxed_path| { const path_string = sandboxed_path.joined(path).to_string() orelse return null; const rw_ops = ext.SDL_RWFromFile(path_string, "rb") orelse return null; const file_size = ext.SDL_RWseek(rw_ops, 0, ext.RW_SEEK_END); if (ext.SDL_RWclose(rw_ops) != 0 or file_size < 0) { return null; } return Info{ .size = @intCast(file_size), }; }, } } }; pub const Info = struct { size: u64, }; pub const Path = extern struct { data: [4096]coral.io.Byte = [_]coral.io.Byte{0} ** 4096, pub const cwd = Path.from(&.{"./"}); pub fn from(components: []const []const u8) Path { // TODO: Implement proper parsing / removal of duplicate path delimiters. var path = Path{}; { var writable_slice = coral.io.FixedBuffer{.bytes = &path.data}; for (components) |component| { if (writable_slice.write(component) != component.len) { break; } } } return path; } pub fn joined(self: Path, other: Path) Path { var path = Path{}; { var writable = coral.io.FixedBuffer{.bytes = &path.data}; var written = @as(usize, 0); for (&self.data) |byte| { if ((byte == 0) or !(writable.put(byte))) { break; } written += 1; } if ((written > 0) and (path.data[written - 1] != '/') and writable.put('/')) { written += 1; } for (&other.data) |byte| { if ((byte == 0) or !(writable.put(byte))) { break; } written += 1; } } return path; } pub fn to_string(self: Path) ?[:0]const coral.io.Byte { const last_index = self.data.len - 1; if (self.data[last_index] != 0) { return null; } return coral.io.slice_sentineled(@as(coral.io.Byte, 0), @as([*:0]const coral.io.Byte, @ptrCast(&self.data))); } }; pub const Readable = opaque { pub fn as_reader(self: *Readable) coral.io.Reader { return coral.io.Reader.bind(Readable, self, read_into); } pub fn close(self: *Readable) void { if (ext.SDL_RWclose(rw_ops_cast(self)) != 0) { @panic("Failed to close file"); } } pub fn read_into(self: *Readable, buffer: []coral.io.Byte) ?usize { ext.SDL_ClearError(); const bytes_read = ext.SDL_RWread(rw_ops_cast(self), buffer.ptr, @sizeOf(coral.io.Byte), buffer.len); const error_message = ext.SDL_GetError(); if (bytes_read == 0 and error_message != null and error_message.* != 0) { return null; } return bytes_read; } pub fn seek_head(self: *Readable, cursor: u64) ?u64 { // TODO: Fix safety of int cast. const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), @intCast(cursor), ext.RW_SEEK_SET); if (byte_offset < 0) { return null; } return @intCast(byte_offset); } pub fn seek_tail(self: *Readable) ?usize { const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), 0, ext.RW_SEEK_END); if (byte_offset < 0) { return error.FileUnavailable; } return @intCast(byte_offset); } pub fn skip(self: *Readable, offset: i64) ?u64 { const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), offset, ext.RW_SEEK_CUR); if (byte_offset < 0) { return error.FileUnavailable; } return @intCast(byte_offset); } }; pub fn allocate_and_load(allocator: coral.io.Allocator, access: Access, path: Path) coral.io.AllocationError!?[]coral.io.Byte { const allocation = try allocator.reallocate(null, query_file_size: { const info = access.query(path) orelse return null; break: query_file_size info.size; }); const readable = access.open_readable(path) orelse { allocator.deallocate(allocation); return null; }; defer _ = readable.close(); const bytes_read = readable.read_into(allocation) orelse { allocator.deallocate(allocation); return null; }; if (bytes_read != allocation.len) { allocator.deallocate(allocation); return null; } return allocation; } fn rw_ops_cast(ptr: *anyopaque) *ext.SDL_RWops { return @ptrCast(@alignCast(ptr)); }