rex/src/rco.zig
2025-05-27 22:59:15 -06:00

352 lines
10 KiB
Zig

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;