// Volition PEG Texture Format // Used by: Saints Row 2, Red Faction: Guerrilla, Red Faction: Armageddon // Magic: 0x564B4547 ("GEKV" / "VKEG") // Versions: 10, 13 // Frame size: 48 bytes (0x30) type volition_peg [root, display="Volition PEG Texture"] byteorder LE { criteria { require _ext == "peg_pc" || _ext == "g_peg_pc" || _ext == "cvbm_pc" || _ext == "peg"; // Check magic in both endianness le_magic = u32at(0); require le_magic == 0x564B4547 || le_magic == 0x47454B56; } // Detect endianness from magic magic_check = u32at(0); if (magic_check == 0x47454B56) { _is_big_endian = 1; set_display("PEG Texture (Big Endian)"); } else { _is_big_endian = 0; } magic = hex(read(4)); ui("magic", "Magic"); u16 version; ui("version", "Version"); u16 platform; ui("platform", "Platform"); if (version == 10) { parse_here("volition_peg_v10"); } else if (version == 13) { parse_here("volition_peg_v13"); } else { unknown_version = version; ui("unknown_version", "Unsupported Version"); set_preview("peg_data.bin", read(size() - pos())); set_viewer("hex"); } } // PEG Version 10 - Saints Row 2 type volition_peg_v10 [display="PEG V10"] byteorder LE { set_name("PEG Texture (V10)"); // Header continues after magic/version/platform (already read 8 bytes) u32 header_size; ui("header_size", "Header Size"); u32 data_size; ui("data_size", "Data Size"); u16 texture_count; ui("texture_count", "Texture Count"); u16 unknown0E; ui("unknown0E", "Unknown 0x0E"); u16 frame_count; ui("frame_count", "Frame Count"); u8 unknown12; ui("unknown12", "Unknown 0x12"); u8 unknown13; ui("unknown13", "Unknown 0x13"); // Parse frames (48 bytes each) skip_tree("frames"); frame_idx = 0; repeat(frame_count) { u32 f_data_offset; u16 f_width; u16 f_height; u32 f_format; u32 f_unknown0C; u16 f_frame_count; u16 f_flags; u32 f_name_offset; u32 f_unknown18; u32 f_size; u32 f_unknown20; u32 f_unknown24; u32 f_unknown28; u32 f_unknown2C; ctx_set("_peg_frame_idx", frame_idx); ctx_set("_peg_frame_offset", f_data_offset); ctx_set("_peg_frame_width", f_width); ctx_set("_peg_frame_height", f_height); ctx_set("_peg_frame_format", f_format); ctx_set("_peg_frame_size", f_size); ctx_set("_peg_frame_flags", f_flags); // Determine format name if (f_format == 400) { format_name = "DXT1"; } else if (f_format == 401) { format_name = "DXT3"; } else if (f_format == 402) { format_name = "DXT5"; } else if (f_format == 403) { format_name = "R5G6B5"; } else if (f_format == 404) { format_name = "A1R5G5B5"; } else if (f_format == 405) { format_name = "A4R4G4B4"; } else if (f_format == 406) { format_name = "R8G8B8"; } else if (f_format == 407) { format_name = "A8R8G8B8"; } else if (f_format == 408) { format_name = "V8U8"; } else if (f_format == 409) { format_name = "CxV8U8"; } else if (f_format == 410) { format_name = "A8"; } else if (f_format == 603) { format_name = "Unknown_603"; } else { format_name = "Unknown"; } ctx_set("_peg_frame_format_name", format_name); dummy = bytesat(0, 1); frame_child = dummy |> parse volition_peg_frame_row; push("frames", frame_child); frame_idx = frame_idx + 1; } // Parse texture names (null-terminated strings) name_idx = 0; repeat(texture_count) { tex_name = cstring(); ctx_set("_peg_tex_name_" + name_idx, tex_name); name_idx = name_idx + 1; } ui_table("frames", "frames", "Index,Width,Height,Format,Size,Flags"); total_frames = frame_count; ui("total_frames", "Total Frames"); total_textures = texture_count; ui("total_textures", "Total Textures"); file_size = size(); ui("file_size", "File Size"); } // PEG Version 13 - Red Faction: Armageddon type volition_peg_v13 [display="PEG V13"] byteorder LE { set_name("PEG Texture (V13)"); // Header continues after magic/version/platform (already read 8 bytes) u32 header_size; ui("header_size", "Header Size"); u32 data_size; ui("data_size", "Data Size"); u16 texture_count; ui("texture_count", "Texture Count"); u16 unknown0E; ui("unknown0E", "Unknown 0x0E"); u16 frame_count; ui("frame_count", "Frame Count"); u16 unknown12; ui("unknown12", "Unknown 0x12"); // Parse frames (48 bytes each - same as V10) skip_tree("frames"); frame_idx = 0; repeat(frame_count) { u32 f_data_offset; u16 f_width; u16 f_height; u32 f_format; u32 f_unknown0C; u16 f_frame_count; u16 f_flags; u32 f_name_offset; u32 f_unknown18; u32 f_size; u32 f_unknown20; u32 f_unknown24; u32 f_unknown28; u32 f_unknown2C; ctx_set("_peg_frame_idx", frame_idx); ctx_set("_peg_frame_offset", f_data_offset); ctx_set("_peg_frame_width", f_width); ctx_set("_peg_frame_height", f_height); ctx_set("_peg_frame_format", f_format); ctx_set("_peg_frame_size", f_size); ctx_set("_peg_frame_flags", f_flags); // Determine format name if (f_format == 400) { format_name = "DXT1"; } else if (f_format == 401) { format_name = "DXT3"; } else if (f_format == 402) { format_name = "DXT5"; } else if (f_format == 403) { format_name = "R5G6B5"; } else if (f_format == 404) { format_name = "A1R5G5B5"; } else if (f_format == 405) { format_name = "A4R4G4B4"; } else if (f_format == 406) { format_name = "R8G8B8"; } else if (f_format == 407) { format_name = "A8R8G8B8"; } else if (f_format == 408) { format_name = "V8U8"; } else if (f_format == 409) { format_name = "CxV8U8"; } else if (f_format == 410) { format_name = "A8"; } else if (f_format == 603) { format_name = "Unknown_603"; } else { format_name = "Unknown"; } ctx_set("_peg_frame_format_name", format_name); dummy = bytesat(0, 1); frame_child = dummy |> parse volition_peg_frame_row; push("frames", frame_child); frame_idx = frame_idx + 1; } // Parse texture names (null-terminated strings) name_idx = 0; repeat(texture_count) { tex_name = cstring(); ctx_set("_peg_tex_name_" + name_idx, tex_name); name_idx = name_idx + 1; } ui_table("frames", "frames", "Index,Width,Height,Format,Size,Flags"); total_frames = frame_count; ui("total_frames", "Total Frames"); total_textures = texture_count; ui("total_textures", "Total Textures"); file_size = size(); ui("file_size", "File Size"); } // Frame row for table display type volition_peg_frame_row [display="Frame"] byteorder LE { set_hidden(); Index = ctx_get("_peg_frame_idx"); Width = ctx_get("_peg_frame_width"); Height = ctx_get("_peg_frame_height"); Format = ctx_get("_peg_frame_format_name"); Size = ctx_get("_peg_frame_size"); Flags = ctx_get("_peg_frame_flags"); }