278 lines
8.5 KiB
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});
|
|
}
|