238 lines
8.3 KiB
Zig
238 lines
8.3 KiB
Zig
const std = @import("std");
|
|
const p2 = @import("p2");
|
|
|
|
const PackedFileEntry = @import("PackedFileEntry.zig");
|
|
|
|
const packerfs = @import("packerfs.zig");
|
|
pub const PackerFS = packerfs.PackerFS;
|
|
|
|
pub const PackerBytesMapping = packerfs.PackerBytesMapping;
|
|
|
|
const constants = @import("constants.zig");
|
|
pub const PackerMagic = constants.PackerMagic;
|
|
pub const littleEndian = constants.littleEndian;
|
|
|
|
// this is invalidated if a PackedArhive is modified
|
|
pub const PackerBytesRef = struct {
|
|
typeTag: []const u8,
|
|
raw_bytes: []const u8,
|
|
};
|
|
|
|
const PackedArchiveContents = std.ArrayListAlignedUnmanaged(u8, 8);
|
|
|
|
// only responsible for reading and writing archives from disk
|
|
// not intended to be the end API
|
|
pub const PackedArchive = struct {
|
|
allocator: std.mem.Allocator,
|
|
contents: *PackedArchiveContents,
|
|
// file by convention is little-endian for u32s and u64s
|
|
headerEntries: std.ArrayListUnmanaged(PackedFileEntry),
|
|
entriesByString: std.StringHashMapUnmanaged(usize), // does not get serialized.
|
|
contentBuffer: []const u8 = undefined,
|
|
finished: bool = false,
|
|
|
|
pub fn initEmpty(allocator: std.mem.Allocator) !@This() {
|
|
const self: @This() = .{
|
|
.contents = try allocator.create(PackedArchiveContents),
|
|
.allocator = allocator,
|
|
.headerEntries = .{},
|
|
.entriesByString = .{},
|
|
};
|
|
self.contents.* = .{};
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn appendFileByBytes(self: *@This(), fileType: []const u8, fileName: []const u8, bytes: []const u8) !void {
|
|
try p2.assert(self.finished != true);
|
|
const writer = self.contents.writer(self.allocator);
|
|
const fileOffset = self.contents.items.len;
|
|
|
|
try writer.writeAll(bytes);
|
|
const bytesWritten = bytes.len;
|
|
|
|
// if bytes written + fileOffset results in a non-64-bit aligned offset, then we shall pad with zeroes until we hit a 32 bit aligned offset
|
|
const padding = 8 - ((bytesWritten + fileOffset) % 8);
|
|
try writer.writeByteNTimes(0x0, padding);
|
|
|
|
const newHeaderEntry = try PackedFileEntry.init(fileType, fileName, fileOffset, @intCast(bytes.len));
|
|
try self.addHeader(newHeaderEntry);
|
|
}
|
|
|
|
// finish building, set externalContentData if this contents does NOT contain
|
|
// the actual content data, such as in the case where the content is
|
|
// sent over a network or if the content is embedded with @embedFile
|
|
pub fn finishBuilding(self: *@This(), externalContentData: bool) !void {
|
|
if (!externalContentData) {
|
|
self.contentBuffer = self.contents.items;
|
|
}
|
|
|
|
for (self.headerEntries.items, 0..) |*entry, i| {
|
|
try self.entriesByString.put(self.allocator, entry.getFileName(), i);
|
|
}
|
|
self.finished = true;
|
|
}
|
|
|
|
fn loadHeadersFromReader(self: *@This(), reader: anytype) !usize {
|
|
var iterator = try PackedFileEntry.ReaderIterator.init(reader);
|
|
while (try iterator.next(reader)) |entry| {
|
|
try self.headerEntries.append(self.allocator, entry);
|
|
}
|
|
return iterator.bytesRead;
|
|
}
|
|
|
|
fn loadContentFromReader(self: *@This(), reader: anytype) !usize {
|
|
const writer = self.contents.writer(self.allocator);
|
|
var totalRead: usize = 0;
|
|
|
|
var readBuffer: [1024]u8 = undefined;
|
|
var bytesRead = try reader.read(&readBuffer);
|
|
totalRead += bytesRead;
|
|
_ = try writer.write((&readBuffer)[0..bytesRead]);
|
|
|
|
while (bytesRead > 0) {
|
|
bytesRead = try reader.read(&readBuffer);
|
|
_ = try writer.write((&readBuffer)[0..bytesRead]);
|
|
totalRead += bytesRead;
|
|
}
|
|
|
|
return totalRead;
|
|
}
|
|
|
|
fn setContentRefToOffset(self: *@This(), bytes: []const u8, offset: usize) void {
|
|
self.contentBuffer = bytes[offset..];
|
|
}
|
|
|
|
pub fn loadFromBytes(self: *@This(), bytes: []const u8) !void {
|
|
try p2.assert(self.finished != true);
|
|
var reader = std.io.fixedBufferStream(bytes);
|
|
const bytesRead = try self.loadHeadersFromReader(&reader);
|
|
self.setContentRefToOffset(bytes, bytesRead);
|
|
try self.finishBuilding(true);
|
|
}
|
|
|
|
pub fn loadFromFile(self: *@This(), filePath: []const u8) !void {
|
|
try p2.assert(self.finished != true);
|
|
// open file for reading
|
|
const file = try std.fs.cwd().openFile(filePath, .{});
|
|
defer file.close();
|
|
|
|
const reader = file.reader();
|
|
_ = try self.loadHeadersFromReader(reader);
|
|
const contentBytes = try self.loadContentFromReader(reader);
|
|
|
|
try self.finishBuilding(false);
|
|
|
|
std.debug.print("total content bytes {d}\n", .{contentBytes});
|
|
}
|
|
|
|
pub fn writeToFile(self: @This(), filePath: []const u8) !void {
|
|
|
|
// create file
|
|
const file = try p2.createFileWithPath(filePath);
|
|
defer file.close();
|
|
|
|
const writer = file.writer();
|
|
// write out pack magic and number of header elements to indicate start of file.
|
|
try writer.writeAll(&@as([4]u8, @bitCast(PackerMagic)));
|
|
try writer.writeAll(&@as([4]u8, @bitCast(
|
|
@as(
|
|
u32,
|
|
@intCast(self.headerEntries.items.len),
|
|
),
|
|
)));
|
|
|
|
var headerSize: usize = 0;
|
|
|
|
for (self.headerEntries.items) |entry| {
|
|
headerSize += try entry.writeHeader(writer, @intCast(self.headerEntries.items.len));
|
|
}
|
|
|
|
std.debug.print("total header size = {d}\n", .{headerSize});
|
|
|
|
// write out contents buffer,
|
|
try writer.writeAll(self.contents.items);
|
|
}
|
|
|
|
pub fn addHeader(self: *@This(), fileEntry: PackedFileEntry) !void {
|
|
try self.headerEntries.append(self.allocator, fileEntry);
|
|
}
|
|
|
|
pub fn updateHeaderOffset(self: @This(), offset: u64) void {
|
|
const p = @as(*u64, @ptrCast(@alignCast(self.contents.items.ptr)));
|
|
p.* = offset;
|
|
}
|
|
|
|
pub fn debugPrintAllFiles(self: @This()) void {
|
|
for (self.headerEntries.items) |entry| {
|
|
std.debug.print("> {s}({s}, size = {d} bytes)\n", .{
|
|
entry.getFileName(),
|
|
entry.getTypeName(),
|
|
entry.fileLen,
|
|
});
|
|
}
|
|
|
|
std.debug.print("totalContentSize = {d}\n", .{
|
|
self.contents.items.len - 8,
|
|
});
|
|
}
|
|
|
|
// only used for debugging or while generating a PackerFileSystem.
|
|
//
|
|
// using a PackerFileSystem is the better method of getting file contents
|
|
pub fn getFileByName(self: *@This(), fileName: []const u8) ?PackerBytesRef {
|
|
if (!self.finished) {
|
|
return null;
|
|
}
|
|
|
|
if (self.entriesByString.get(fileName)) |entryOffset| {
|
|
const header = &self.headerEntries.items[entryOffset];
|
|
var bytes: []const u8 = undefined;
|
|
|
|
bytes.ptr = @ptrCast(&self.contentBuffer[header.fileOffset]);
|
|
bytes.len = header.fileLen;
|
|
|
|
return .{
|
|
.typeTag = header.getTypeName(),
|
|
.raw_bytes = bytes,
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn deinit(self: *@This()) void {
|
|
self.contents.deinit(self.allocator);
|
|
self.headerEntries.deinit(self.allocator);
|
|
if (self.finished) {
|
|
self.entriesByString.deinit(self.allocator);
|
|
}
|
|
self.allocator.destroy(self.contents);
|
|
}
|
|
};
|
|
|
|
fn loadHeadersFromReader(self: *@This(), reader: anytype) !usize {
|
|
var bytesRead: usize = 0;
|
|
// read and verify magic
|
|
var magic: [4]u8 align(4) = undefined;
|
|
try p2.assert(try reader.read(&magic) == 4);
|
|
try p2.assert(p2.arrayTo_u32(magic) == PackerMagic);
|
|
std.debug.print("magic = {s}\n", .{magic});
|
|
bytesRead += 4;
|
|
|
|
// read header entries count
|
|
var entriesCount_read: [4]u8 align(4) = undefined;
|
|
try p2.assert(try reader.read(&entriesCount_read) == 4);
|
|
const entriesCount = p2.arrayTo_u32(entriesCount_read);
|
|
std.debug.print("entriesCount = {d}\n", .{entriesCount});
|
|
bytesRead += 4;
|
|
|
|
for (0..entriesCount) |_| {
|
|
const newEntry = try self.headerEntries.addOne(self.allocator);
|
|
try newEntry.loadFromReader(reader);
|
|
bytesRead += PackedFileEntry.calculateHeaderLen();
|
|
}
|
|
|
|
return bytesRead;
|
|
}
|