ebook-reader/src/main.zig

278 lines
8.5 KiB
Zig

const std = @import("std");
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
fn print(comptime str: []const u8, params: anytype) void {
stdout.print(str ++ "\n", params) catch {};
}
fn pErr(comptime str: []const u8, params: anytype) void {
stderr.print(str ++ "\n", params) catch {};
}
fn exit(comptime str: []const u8, params: anytype, exitCode: u8) void {
pErr(str, params);
std.os.exit(exitCode);
}
fn usage() void {
pErr("reader <path>", .{});
exit("", .{}, 1);
}
const BitWalker = struct {
const Self = @This();
data: *[]u8,
position: usize = 0,
in_byte_position: u3 = 0,
direction: bool = false,
fn init(data: *[]u8, direction: bool) Self {
return Self{
.data = data,
.direction = direction,
};
}
// TODO direction
fn walk(self: *Self, bits: u3) !u8 {
if (bits > 8 or bits == 0) return error.invalid_bit_number;
var byte = self.data.ptr[self.position];
// jumps over bytes
if (self.in_byte_position + @as(u4, bits) > 8) {
// Generate a mast that covers the last part of the old byte
var old_mask: u8 = 0;
var i: usize = 0;
while (i < bits - self.in_byte_position) : (i += 1) {
old_mask = @shlExact(old_mask, 1) + 1;
}
old_mask = @shlExact(old_mask, self.in_byte_position);
var next_byte = self.data.ptr[self.position + 1];
var new_byte_pos: u3 = self.in_byte_position + bits % 8;
var new_mask: u8 = 0;
var j: usize = 0;
while (j < new_byte_pos) : (j += 1) {
new_mask = @shlExact(new_mask, 1) + 1;
}
print("{} mask: {b}, new_mask", .{ bits, old_mask, new_mask });
print("here {b} {b}", .{ byte, old_mask });
print("here_new {b} {b}", .{ next_byte, new_mask });
self.position += 1;
self.in_byte_position = new_byte_pos;
return byte & old_mask + next_byte & new_mask;
}
// Generate a mast that covers the last part of the old byte
var old_mask: u8 = 0;
var i: usize = 0;
while (i < bits) : (i += 1) {
old_mask = @shlExact(old_mask, 1) + 1;
}
old_mask = @shlExact(old_mask, self.in_byte_position);
if (self.in_byte_position + bits == 8) {
self.position += 1;
self.in_byte_position = 0;
}
const result = @shrExact(byte & old_mask, self.in_byte_position);
self.in_byte_position += bits;
return result;
}
};
const LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
const ZipFileHeader = struct {
version: u16,
general: u16,
compression_method: u16,
last_mod_time: u16,
last_mod_date: u16,
crc_32: u32,
compressed_size: u32,
uncompressed_size: u32,
file_name_length: u16,
extra_field_length: u16,
file_name: []u8,
extra_field: []u8,
compressed_content: []u8,
uncompressed_content: []u8,
decompressed: bool,
allocator: std.mem.Allocator,
const Self = @This();
fn init(allocator: std.mem.Allocator, reader: std.fs.File.Reader) !Self {
if (try reader.readInt(u32, .Big) == LOCAL_FILE_HEADER_SIGNATURE) {
return error.InvalidError;
}
var self = Self{
.allocator = allocator,
.version = try reader.readInt(u16, .Little),
.general = try reader.readInt(u16, .Little),
.compression_method = try reader.readInt(u16, .Little),
.last_mod_time = try reader.readInt(u16, .Little),
.last_mod_date = try reader.readInt(u16, .Little),
.crc_32 = try reader.readInt(u32, .Little),
.compressed_size = try reader.readInt(u32, .Little),
.uncompressed_size = try reader.readInt(u32, .Little),
.file_name_length = try reader.readInt(u16, .Little),
.extra_field_length = try reader.readInt(u16, .Little),
.file_name = undefined,
.extra_field = undefined,
.compressed_content = undefined,
.uncompressed_content = undefined,
.decompressed = false,
};
self.file_name = try allocator.alloc(u8, self.file_name_length);
self.extra_field = try allocator.alloc(u8, self.extra_field_length);
self.compressed_content = try allocator.alloc(u8, self.compressed_size);
_ = try reader.read(self.file_name);
_ = try reader.read(self.extra_field);
_ = try reader.read(self.compressed_content);
return self;
}
fn extract(self: *Self) !void {
if (self.decompressed) {
return error.AlreadyDecompressed;
}
if (self.compression_method == 0) {
return error.uncompressed_file;
}
if (self.compression_method != 8) {
return error.unsuported_compression_method;
}
self.uncompressed_content = try self.allocator.alloc(u8, self.uncompressed_size);
errdefer self.allocator.free(self.uncompressed_content);
var bitw = BitWalker.init(&self.compressed_content, false);
var lastBlock1 = try bitw.walk(1);
var blockType1 = try bitw.walk(2);
var number_of_literal_codes1 = try bitw.walk(5);
var number_of_dist_codes1 = try bitw.walk(5);
print("{} {} {} {}", .{ lastBlock1, blockType1, number_of_literal_codes1, number_of_dist_codes1 });
var byte = self.compressed_content[0];
var lastBlock = byte & 0b0000_0001 == 0b0000_0001;
var blockType = @shrExact(byte & 0b0000_0110, 1);
var number_of_literal_codes = @shrExact(byte & 0b1111_1000, 3); // 256
byte = self.compressed_content[1];
var number_of_dist_codes = byte & 0b0001_1111;
var next_byte = self.compressed_content[2];
var number_of_length_codes = @shrExact(byte & 0b1110_0000, 5) + @shlExact(next_byte & 0b0000_0001, 3);
byte = next_byte;
if (lastBlock) {
print("last block", .{});
} else {
print("not last block", .{});
}
print("block_type: {}", .{blockType});
if (blockType != 2) {
return error.unsuported_block_type;
}
print("number of literal codes: {}", .{number_of_literal_codes});
print("number of dist codes: {}", .{number_of_dist_codes});
print("number_of_length_coes: {}", .{number_of_length_codes});
self.decompressed = true;
}
fn deinit(self: *Self) void {
self.allocator.free(self.file_name);
self.allocator.free(self.extra_field);
self.allocator.free(self.compressed_content);
if (self.decompressed) {
self.allocator.free(self.uncompressed_content);
}
}
};
pub fn main() !void {
var args = std.process.args();
var filePath: ?[]const u8 = null;
// Skip the current path
_ = args.next();
while (args.next()) |arg| {
if (filePath == null) {
filePath = arg;
} else {
pErr("Invalid argument: {s}", .{arg});
usage();
}
}
if (filePath == null) {
pErr("File path not provided. Please provide a path", .{});
usage();
}
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var allocator = gpa.allocator();
var book_path = try std.fs.realpathAlloc(allocator, filePath.?);
defer allocator.free(book_path);
var file = std.fs.openFileAbsolute(book_path, .{}) catch |err| {
exit("Please provide a file path! Error: {?}", .{err}, 1);
return err;
};
defer file.close();
var stat = try file.stat();
if (stat.kind != .File) {
exit("Please provide a valid file", .{}, 1);
}
var reader = file.reader();
var first_file = try ZipFileHeader.init(allocator, reader);
defer first_file.deinit();
if (!std.mem.eql(u8, first_file.file_name, "mimetype")) {
exit("Invalid file provided", .{}, 1);
}
if (!std.mem.startsWith(u8, first_file.compressed_content, "application/epub")) {
exit("Invalid file provided", .{}, 1);
}
print("H: {}", .{first_file.compression_method});
var second_file = try ZipFileHeader.init(allocator, reader);
defer second_file.deinit();
try second_file.extract();
print("G: {s}", .{second_file.file_name});
print("GI: {}", .{second_file.compression_method});
print("xml stuff:\n{s}", .{second_file.compressed_content});
}