pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var rco_file = try std.fs.cwd().openFile("vsh/resource/explore_plugin_game.rco", .{}); defer rco_file.close(); var rco = try RcoFile.init(allocator, rco_file); defer rco.deinit(); try rco_file.seekTo(rco.toc_main_tree_offset.?); const packed_size = try rco_file.reader().readInt(u32, .big); const unpacked_size = try rco_file.reader().readInt(u32, .big); const longest_text_size = try rco_file.reader().readInt(u32, .big); std.debug.print("{d} {d} {d}\n", .{ packed_size, unpacked_size, longest_text_size }); const packed_buf = try allocator.alloc(u8, packed_size); defer allocator.free(packed_buf); _ = try rco_file.readAll(packed_buf); var fb = std.io.fixedBufferStream(packed_buf); var al = std.ArrayList(u8).init(allocator); defer al.deinit(); try std.compress.zlib.decompress(fb.reader(), al.writer()); if (al.items.len != unpacked_size) return error.DecompressedSizeDoesNotMatch; var nfb = std.io.fixedBufferStream(al.items); while (nfb.pos < try nfb.getEndPos()) { try parseTocEntry(nfb.reader()); } std.debug.print("{any}\n", .{rco}); } fn parseTocEntry(reader: anytype) !void { const entry_type: TocEntryType = @enumFromInt(try reader.readInt(u16, .big)); try reader.skipBytes(2, .{}); const entry_label_offset = try reader.readInt(u32, .big); const attributes_offset = try reader.readInt(u32, .big); const children_offset = try reader.readInt(u32, .big); const children_number = try reader.readInt(u32, .big); const next_sibling_offset = try reader.readInt(u32, .big); const prev_sibling_offset = try reader.readInt(u32, .big); const parent_offset = try reader.readInt(u32, .big); try reader.skipBytes(8, .{}); _ = .{ entry_label_offset, attributes_offset, children_offset, children_number, next_sibling_offset, prev_sibling_offset, parent_offset }; std.log.debug("Found TOC Entry: {any}", .{entry_type}); switch (entry_type) { .MainTree, .ScriptTree, .TextTree, .ImageTree, .ModelTree, .SoundTree, .FontTree, .ObjectTree, .AnimTree, => {}, .Text => { const text = try RcoFile.Text.init(reader); std.debug.print("Text: {any}\n", .{text}); }, .Image => { const image = try RcoFile.ImageTree.Image.init(reader); std.debug.print("Image: {any}\n", .{image}); }, else => {}, } } pub const TocEntryType = enum(u16) { MainTree = 0x0101, ScriptTree = 0x0200, TextTree = 0x0300, Text = 0x0301, ImageTree = 0x0400, Image = 0x0401, ModelTree = 0x0500, Model = 0x0501, SoundTree = 0x0600, Sound = 0x0601, FontTree = 0x0700, Font = 0x0701, ObjectTree = 0x0800, Page = 0x0801, Plane = 0x0802, Button = 0x0803, XMenu, XMList, Progress, Scroll, MList, MItem, _object_unk1 = 0x080B, XItem, TextObject, ModelObject, Spin, Action, ItemSpin, Group, LList, LItem, Edit, Clock, IList, IItem, Icon, UButton, _object_unk2 = 0x081B, CheckboxGroup, CheckboxItem, Meter, EditBox = 0x081F, AnimTree = 0x0900, Anim = 0x0901, MoveTo = 0x0902, Recolour = 0x0903, Rotate = 0x0904, Resize = 0x0905, Fade = 0x0906, Delay = 0x0907, FireEvent = 0x0908, Lock = 0x0909, Unlock = 0x090A, SlideOut = 0x090B, _, }; pub const RcoFile = struct { allocator: Allocator, file: std.fs.File, compression: Compression, toc_main_tree_offset: ?u32, toc_script_tree_offset: ?u32, toc_text_tree_offset: ?u32, toc_sound_tree_offset: ?u32, toc_model_tree_offset: ?u32, toc_image_tree_offset: ?u32, toc_font_tree_offset: ?u32, toc_object_tree_offset: ?u32, toc_anim_tree_offset: ?u32, pub fn parse(self: *RcoFile) !void { const rco_header = try self.file.reader().readStructEndian(RcoHeader, .big); self.compression = @enumFromInt(rco_header.prf_compress); self.toc_main_tree_offset = parseOffset(rco_header.toc_main_tree_offset); self.toc_script_tree_offset = parseOffset(rco_header.toc_script_tree_offset); self.toc_text_tree_offset = parseOffset(rco_header.toc_text_tree_offset); self.toc_sound_tree_offset = parseOffset(rco_header.toc_sound_tree_offset); self.toc_model_tree_offset = parseOffset(rco_header.toc_model_tree_offset); self.toc_image_tree_offset = parseOffset(rco_header.toc_image_tree_offset); self.toc_font_tree_offset = parseOffset(rco_header.toc_font_tree_offset); self.toc_object_tree_offset = parseOffset(rco_header.toc_object_tree_offset); self.toc_anim_tree_offset = parseOffset(rco_header.toc_anim_tree_offset); } pub fn init(allocator: Allocator, file: std.fs.File) !RcoFile { var self: RcoFile = undefined; self.allocator = allocator; self.file = file; try self.parse(); return self; } pub fn deinit(_: *RcoFile) void {} fn parseOffset(offset: u32) ?u32 { return if (offset == std.math.maxInt(u32)) null else offset; } pub fn format( self: RcoFile, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) !void { try writer.writeAll("RcoFile{\n"); try writer.print(" .compression = {any},\n", .{self.compression}); try self.fmtField("toc_main_tree_offset", writer); try self.fmtField("toc_script_tree_offset", writer); try self.fmtField("toc_text_tree_offset", writer); try self.fmtField("toc_sound_tree_offset", writer); try self.fmtField("toc_model_tree_offset", writer); try self.fmtField("toc_image_tree_offset", writer); try self.fmtField("toc_font_tree_offset", writer); try self.fmtField("toc_object_tree_offset", writer); try self.fmtField("toc_anim_tree_offset", writer); try writer.writeAll("}"); } inline fn fmtField(self: RcoFile, comptime field_name: []const u8, writer: anytype) !void { const field = @field(self, field_name); const format_string = std.fmt.comptimePrint(" .{s} = {{s}}{{?X}},\n", .{field_name}); try writer.print(format_string, .{ if (field != null) "0x" else "", field, }); } pub const Compression = enum(u32) { none = 0, zlib = 0x10, rlz = 0x20, }; pub const Text = struct { language: Language, encoding: Encoding, number_of: u32, pub const Language = enum(u16) { Japanese = 0, English_US = 1, French = 2, Spanish_Spain = 3, German = 4, Italian = 5, Dutch = 6, Portuguese_Portugal = 7, Russian = 8, Korean = 9, Chinese_Traditional = 10, Chinese_Simplified = 11, Finnish = 12, Swedish = 13, Danish = 14, Norwegian = 15, Polish = 16, Portuguese_Brazil = 17, English_UK = 18, Turkish = 19, }; pub const Encoding = enum(u16) { utf8 = 0, utf16 = 1, utf32 = 2, }; pub const String = extern struct { label_offset: u32, string_length: u32, string_offset: u32, }; }; pub const ImageTree = struct { pub const Image = struct { format: Format, compression: Image.Compression, size: u32, offset: u32, uncompressed_size: ?u32, pub fn init(reader: anytype) !Image { var self: Image = undefined; self.format = @enumFromInt(try reader.readInt(u16, .big)); self.compression = @enumFromInt(try reader.readInt(u16, .big)); self.size = try reader.readInt(u32, .big); self.offset = try reader.readInt(u32, .big); try reader.skipBytes(4, .{}); self.uncompressed_size = if (self.compression == .none) null else try reader.readInt(u32, .big); return self; } pub const Format = enum(u16) { png = 0, jpeg = 1, tiff = 2, gif = 3, bmp = 4, gim = 5, }; pub const Compression = enum(u16) { none = 0, zlib = 1, rlz = 2, }; }; }; }; pub const RcoHeader = extern struct { prf_signature: [4]u8, prf_version: [4]u8, prf_unk: u32 = 0, prf_compress: u32, toc_main_tree_offset: u32, toc_script_tree_offset: u32, toc_text_tree_offset: u32, toc_sound_tree_offset: u32, toc_model_tree_offset: u32, toc_image_tree_offset: u32, toc_unk: u32 = std.math.maxInt(u32), toc_font_tree_offset: u32, toc_object_tree_offset: u32, toc_anim_tree_offset: u32, str_text_table_offset: u32, str_text_table_length: u32, str_text_label_offset: u32, str_text_label_length: u32, str_text_event_offset: u32, str_text_event_length: u32, ptr_text_table_offset: u32, ptr_text_table_length: u32, ptr_image_table_offset: u32, ptr_image_table_length: u32, ptr_model_table_offset: u32, ptr_model_table_length: u32, ptr_sound_table_offset: u32, ptr_sound_table_length: u32, ptr_object_table_offset: u32, ptr_object_table_length: u32, ptr_anim_table_offset: u32, ptr_anim_table_length: u32, dat_image_table_offset: u32, dat_image_table_length: u32, dat_sound_table_offset: u32, dat_sound_table_length: u32, dat_model_table_offset: u32, dat_model_table_length: u32, dat_unk: [3]u32 = [_]u32{std.math.maxInt(u32)} ** 3, }; const std = @import("std"); const Allocator = std.mem.Allocator;