mirror of
https://github.com/redstrate/Physis.git
synced 2025-04-24 13:37:44 +00:00
774 lines
21 KiB
Rust
774 lines
21 KiB
Rust
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
|
|
|
use crate::common_file_operations::{
|
|
read_bool_from, string_from_offset, write_bool_as, write_string,
|
|
};
|
|
use crate::{ByteBuffer, ByteSpan};
|
|
use binrw::{BinRead, BinReaderExt, BinWrite, binread};
|
|
use binrw::{BinResult, Endian, Error, binrw, binwrite};
|
|
|
|
mod aetheryte;
|
|
pub use aetheryte::AetheryteInstanceObject;
|
|
|
|
mod bg;
|
|
pub use bg::BGInstanceObject;
|
|
pub use bg::ModelCollisionType;
|
|
|
|
mod common;
|
|
pub use common::Color;
|
|
pub use common::ColorHDRI;
|
|
pub use common::Transformation;
|
|
|
|
mod env_set;
|
|
pub use env_set::EnvSetInstanceObject;
|
|
pub use env_set::EnvSetShape;
|
|
|
|
mod exit_range;
|
|
pub use exit_range::ExitRangeInstanceObject;
|
|
pub use exit_range::ExitType;
|
|
|
|
mod light;
|
|
pub use light::LightInstanceObject;
|
|
pub use light::LightType;
|
|
pub use light::PointLightType;
|
|
|
|
mod npc;
|
|
pub use npc::BNPCInstanceObject;
|
|
pub use npc::ENPCInstanceObject;
|
|
pub use npc::GameInstanceObject;
|
|
pub use npc::NPCInstanceObject;
|
|
|
|
mod pop;
|
|
pub use pop::PopRangeInstanceObject;
|
|
pub use pop::PopType;
|
|
|
|
mod position_marker;
|
|
pub use position_marker::PositionMarkerInstanceObject;
|
|
pub use position_marker::PositionMarkerType;
|
|
|
|
mod shared_group;
|
|
pub use shared_group::ColourState;
|
|
pub use shared_group::DoorState;
|
|
pub use shared_group::RotationState;
|
|
pub use shared_group::SharedGroupInstance;
|
|
pub use shared_group::TransformState;
|
|
|
|
mod sound;
|
|
pub use sound::SoundInstanceObject;
|
|
|
|
mod trigger_box;
|
|
pub use trigger_box::TriggerBoxInstanceObject;
|
|
pub use trigger_box::TriggerBoxShape;
|
|
|
|
// From https://github.com/NotAdam/Lumina/tree/40dab50183eb7ddc28344378baccc2d63ae71d35/src/Lumina/Data/Parsing/Layer
|
|
// Also see https://github.com/aers/FFXIVClientStructs/blob/6b62122cae38bfbc016bf697bef75f80f37abac1/FFXIVClientStructs/FFXIV/Client/LayoutEngine/ILayoutInstance.cs
|
|
|
|
/// A string that exists in a different location in the file, usually a heap with a bunch of other strings.
|
|
#[binrw]
|
|
#[br(import(string_heap: &StringHeap), stream = r)]
|
|
#[bw(import(string_heap: &mut StringHeap))]
|
|
#[derive(Clone, Debug)]
|
|
pub struct HeapString {
|
|
#[br(temp)]
|
|
// TODO: this cast is stupid
|
|
#[bw(calc = string_heap.get_free_offset_string(&value) as u32)]
|
|
#[br(dbg)]
|
|
pub offset: u32,
|
|
#[br(calc = string_heap.read_string(r, offset,))]
|
|
#[bw(ignore)]
|
|
#[br(dbg)]
|
|
pub value: String,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct StringHeap {
|
|
pub pos: u64,
|
|
pub bytes: Vec<u8>,
|
|
pub free_pos: u64,
|
|
}
|
|
|
|
impl StringHeap {
|
|
pub fn from(pos: u64) -> Self {
|
|
Self {
|
|
pos,
|
|
bytes: Vec::new(),
|
|
free_pos: 0, // unused, so it doesn't matter
|
|
}
|
|
}
|
|
|
|
pub fn get_free_offset<T>(&mut self, obj: &T) -> i32
|
|
where
|
|
T: for<'a> BinWrite<Args<'a> = ()> + std::fmt::Debug,
|
|
{
|
|
// figure out size of it
|
|
let mut buffer = ByteBuffer::new();
|
|
{
|
|
let mut cursor = Cursor::new(&mut buffer);
|
|
obj.write_le(&mut cursor).unwrap();
|
|
}
|
|
|
|
self.bytes.append(&mut buffer);
|
|
|
|
let old_pos = self.free_pos;
|
|
self.free_pos += buffer.len() as u64;
|
|
|
|
println!("free pos for {:#?} is {}!", obj, old_pos);
|
|
|
|
old_pos as i32
|
|
}
|
|
|
|
pub fn get_free_offset_string(&mut self, str: &String) -> i32 {
|
|
let bytes = write_string(str);
|
|
self.get_free_offset(&bytes)
|
|
}
|
|
|
|
pub fn read<R, T>(&self, reader: &mut R, offset: i32) -> T
|
|
where
|
|
R: Read + Seek,
|
|
T: for<'a> BinRead<Args<'a> = ()>,
|
|
{
|
|
let old_pos = reader.stream_position().unwrap();
|
|
reader
|
|
.seek(SeekFrom::Start((self.pos as i32 + offset) as u64))
|
|
.unwrap();
|
|
let obj = reader.read_le::<T>().unwrap();
|
|
reader.seek(SeekFrom::Start(old_pos)).unwrap();
|
|
obj
|
|
}
|
|
|
|
pub fn read_string<R>(&self, reader: &mut R, offset: u32) -> String
|
|
where
|
|
R: Read + Seek,
|
|
{
|
|
string_from_offset(reader, Endian::Little, (self.pos + offset as u64,)).unwrap()
|
|
}
|
|
}
|
|
|
|
impl BinWrite for StringHeap {
|
|
type Args<'a> = ();
|
|
|
|
fn write_options<W: Write + Seek>(
|
|
&self,
|
|
writer: &mut W,
|
|
endian: Endian,
|
|
(): Self::Args<'_>,
|
|
) -> Result<(), Error> {
|
|
self.bytes.write_options(writer, endian, ())?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// TODO: convert these all to magic
|
|
#[binrw]
|
|
#[brw(repr = i32)]
|
|
#[repr(i32)]
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum LayerEntryType {
|
|
AssetNone = 00,
|
|
BG = 0x1,
|
|
Attribute = 0x2,
|
|
LayLight = 0x3,
|
|
Vfx = 0x4,
|
|
PositionMarker = 0x5,
|
|
SharedGroup = 0x6,
|
|
Sound = 0x7,
|
|
EventNPC = 0x8,
|
|
BattleNPC = 0x9,
|
|
RoutePath = 0xA,
|
|
Character = 0xB,
|
|
Aetheryte = 0xC,
|
|
EnvSet = 0xD,
|
|
Gathering = 0xE,
|
|
HelperObject = 0xF,
|
|
Treasure = 0x10,
|
|
Clip = 0x11,
|
|
ClipCtrlPoint = 0x12,
|
|
ClipCamera = 0x13,
|
|
ClipLight = 0x14,
|
|
ClipReserve00 = 0x15,
|
|
ClipReserve01 = 0x16,
|
|
ClipReserve02 = 0x17,
|
|
ClipReserve03 = 0x18,
|
|
ClipReserve04 = 0x19,
|
|
ClipReserve05 = 0x1A,
|
|
ClipReserve06 = 0x1B,
|
|
ClipReserve07 = 0x1C,
|
|
ClipReserve08 = 0x1D,
|
|
ClipReserve09 = 0x1E,
|
|
ClipReserve10 = 0x1F,
|
|
ClipReserve11 = 0x20,
|
|
ClipReserve12 = 0x21,
|
|
ClipReserve13 = 0x22,
|
|
ClipReserve14 = 0x23,
|
|
CutAssetOnlySelectable = 0x24,
|
|
Player = 0x25,
|
|
Monster = 0x26,
|
|
Weapon = 0x27,
|
|
PopRange = 0x28,
|
|
/// Zone Transitions (the visible part is probably LineVFX?)
|
|
ExitRange = 0x29,
|
|
Lvb = 0x2A,
|
|
MapRange = 0x2B,
|
|
NaviMeshRange = 0x2C,
|
|
EventObject = 0x2D,
|
|
DemiHuman = 0x2E,
|
|
EnvLocation = 0x2F,
|
|
ControlPoint = 0x30,
|
|
EventRange = 0x31,
|
|
RestBonusRange = 0x32,
|
|
QuestMarker = 0x33,
|
|
Timeline = 0x34,
|
|
ObjectBehaviorSet = 0x35,
|
|
Movie = 0x36,
|
|
ScenarioExd = 0x37,
|
|
ScenarioText = 0x38,
|
|
CollisionBox = 0x39,
|
|
DoorRange = 0x3A,
|
|
LineVFX = 0x3B,
|
|
SoundEnvSet = 0x3C,
|
|
CutActionTimeline = 0x3D,
|
|
CharaScene = 0x3E,
|
|
CutAction = 0x3F,
|
|
EquipPreset = 0x40,
|
|
ClientPath = 0x41,
|
|
ServerPath = 0x42,
|
|
GimmickRange = 0x43,
|
|
TargetMarker = 0x44,
|
|
ChairMarker = 0x45,
|
|
ClickableRange = 0x46,
|
|
PrefetchRange = 0x47,
|
|
FateRange = 0x48,
|
|
PartyMember = 0x49,
|
|
KeepRange = 0x4A,
|
|
SphereCastRange = 0x4B,
|
|
IndoorObject = 0x4C,
|
|
OutdoorObject = 0x4D,
|
|
EditGroup = 0x4E,
|
|
StableChocobo = 0x4F,
|
|
MaxAssetType = 0x50,
|
|
Unk1 = 90,
|
|
}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(import(magic: &LayerEntryType))]
|
|
pub enum LayerEntryData {
|
|
#[br(pre_assert(*magic == LayerEntryType::BG))]
|
|
BG(BGInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::LayLight))]
|
|
LayLight(LightInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::Vfx))]
|
|
Vfx(VFXInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::PositionMarker))]
|
|
PositionMarker(PositionMarkerInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::SharedGroup))]
|
|
SharedGroup(SharedGroupInstance),
|
|
#[br(pre_assert(*magic == LayerEntryType::Sound))]
|
|
Sound(SoundInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::EventNPC))]
|
|
EventNPC(ENPCInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::BattleNPC))]
|
|
BattleNPC(BNPCInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::Aetheryte))]
|
|
Aetheryte(AetheryteInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::EnvSet))]
|
|
EnvSet(EnvSetInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::Gathering))]
|
|
Gathering(GatheringInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::Treasure))]
|
|
Treasure(TreasureInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::PopRange))]
|
|
PopRange(PopRangeInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::ExitRange))]
|
|
ExitRange(ExitRangeInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::MapRange))]
|
|
MapRange(MapRangeInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::EventObject))]
|
|
EventObject(EventInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::EnvLocation))]
|
|
EnvLocation(EnvLocationObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::EventRange))]
|
|
EventRange(EventRangeInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::QuestMarker))]
|
|
QuestMarker(QuestMarkerInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::CollisionBox))]
|
|
CollisionBox(CollisionBoxInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::LineVFX))]
|
|
LineVFX(LineVFXInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::ClientPath))]
|
|
ClientPath(ClientPathInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::ServerPath))]
|
|
ServerPath(ServerPathInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::GimmickRange))]
|
|
GimmickRange(GimmickRangeInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::TargetMarker))]
|
|
TargetMarker(TargetMarkerInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::ChairMarker))]
|
|
ChairMarker(ChairMarkerInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::PrefetchRange))]
|
|
PrefetchRange(PrefetchRangeInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::FateRange))]
|
|
FateRange(FateRangeInstanceObject),
|
|
#[br(pre_assert(*magic == LayerEntryType::Unk1))]
|
|
Unk1(),
|
|
}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct VFXInstanceObject {
|
|
asset_path_offset: u32,
|
|
soft_particle_fade_range: f32,
|
|
padding: u32,
|
|
color: Color,
|
|
#[br(map = read_bool_from::<u8>)]
|
|
auto_play: bool,
|
|
#[br(map = read_bool_from::<u8>)]
|
|
no_far_clip: bool,
|
|
padding1: u16,
|
|
fade_near_start: f32,
|
|
fade_near_end: f32,
|
|
fade_far_start: f32,
|
|
fade_far_end: f32,
|
|
z_correct: f32,
|
|
}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct GatheringInstanceObject {
|
|
gathering_point_id: u32,
|
|
padding: u32,
|
|
}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct TreasureInstanceObject {
|
|
nonpop_init_zone: u8,
|
|
padding1: [u8; 3],
|
|
padding2: [u32; 2],
|
|
}
|
|
|
|
// Unimplemented because I haven't needed it yet:
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct MapRangeInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct EventInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct EnvLocationObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct EventRangeInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct QuestMarkerInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct CollisionBoxInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct LineVFXInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct ClientPathInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct ServerPathInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct GimmickRangeInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct TargetMarkerInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct ChairMarkerInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct PrefetchRangeInstanceObject {}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
pub struct FateRangeInstanceObject {}
|
|
|
|
#[binrw]
|
|
#[brw(repr = i32)]
|
|
#[derive(Debug, PartialEq)]
|
|
enum LayerSetReferencedType {
|
|
All = 0x0,
|
|
Include = 0x1,
|
|
Exclude = 0x2,
|
|
Undetermined = 0x3,
|
|
}
|
|
|
|
#[binrw]
|
|
#[derive(Debug)]
|
|
#[brw(little)]
|
|
#[br(import(string_heap: &StringHeap), stream = r)]
|
|
#[bw(import(string_heap: &mut StringHeap))]
|
|
#[allow(dead_code)] // most of the fields are unused at the moment
|
|
struct LayerHeader {
|
|
pub layer_id: u32,
|
|
|
|
#[brw(args(string_heap))]
|
|
pub name: HeapString,
|
|
|
|
pub instance_object_offset: i32,
|
|
pub instance_object_count: i32,
|
|
|
|
#[br(map = read_bool_from::<u8>)]
|
|
#[bw(map = write_bool_as::<u8>)]
|
|
pub tool_mode_visible: bool,
|
|
#[br(map = read_bool_from::<u8>)]
|
|
#[bw(map = write_bool_as::<u8>)]
|
|
pub tool_mode_read_only: bool,
|
|
|
|
#[br(map = read_bool_from::<u8>)]
|
|
#[bw(map = write_bool_as::<u8>)]
|
|
pub is_bush_layer: bool,
|
|
#[br(map = read_bool_from::<u8>)]
|
|
#[bw(map = write_bool_as::<u8>)]
|
|
pub ps3_visible: bool,
|
|
#[br(temp)]
|
|
#[bw(calc = string_heap.get_free_offset(&layer_set_referenced_list))]
|
|
pub layer_set_referenced_list_offset: i32,
|
|
#[br(calc = string_heap.read(r, layer_set_referenced_list_offset))]
|
|
pub layer_set_referenced_list: LayerSetReferencedList,
|
|
pub festival_id: u16,
|
|
pub festival_phase_id: u16,
|
|
pub is_temporary: u8,
|
|
pub is_housing: u8,
|
|
pub version_mask: u16,
|
|
|
|
#[br(pad_before = 4)]
|
|
pub ob_set_referenced_list: i32,
|
|
pub ob_set_referenced_list_count: i32,
|
|
pub ob_set_enable_referenced_list: i32,
|
|
pub ob_set_enable_referenced_list_count: i32,
|
|
}
|
|
|
|
#[binrw]
|
|
#[derive(Debug)]
|
|
#[brw(little)]
|
|
#[allow(dead_code)] // most of the fields are unused at the moment
|
|
struct LayerSetReferencedList {
|
|
referenced_type: LayerSetReferencedType,
|
|
layer_sets: i32,
|
|
layer_set_count: i32,
|
|
}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
#[allow(dead_code)] // most of the fields are unused at the moment
|
|
struct OBSetReferenced {
|
|
asset_type: LayerEntryType,
|
|
instance_id: u32,
|
|
ob_set_asset_path_offset: u32,
|
|
}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
#[allow(dead_code)] // most of the fields are unused at the moment
|
|
struct OBSetEnableReferenced {
|
|
asset_type: LayerEntryType,
|
|
instance_id: u32,
|
|
#[br(map = read_bool_from::<u8>)]
|
|
ob_set_enable: bool,
|
|
#[br(map = read_bool_from::<u8>)]
|
|
ob_set_emissive_enable: bool,
|
|
padding: [u8; 2],
|
|
}
|
|
|
|
#[binrw]
|
|
#[derive(Debug)]
|
|
#[brw(little)]
|
|
#[allow(dead_code)] // most of the fields are unused at the moment
|
|
struct LgbHeader {
|
|
file_id: u32,
|
|
file_size: i32,
|
|
total_chunk_count: i32,
|
|
}
|
|
|
|
#[binrw]
|
|
#[derive(Debug)]
|
|
#[br(import(string_heap: &StringHeap), stream = r)]
|
|
#[bw(import(string_heap: &mut StringHeap))]
|
|
#[brw(little)]
|
|
#[allow(dead_code)] // most of the fields are unused at the moment
|
|
struct LayerChunkHeader {
|
|
chunk_id: u32,
|
|
chunk_size: i32,
|
|
layer_group_id: i32,
|
|
#[brw(args(string_heap))]
|
|
pub name: HeapString,
|
|
layer_offset: i32,
|
|
layer_count: i32,
|
|
}
|
|
|
|
#[binread]
|
|
#[derive(Debug)]
|
|
#[br(little)]
|
|
#[br(import(start: u64))]
|
|
#[allow(dead_code)] // most of the fields are unused at the moment
|
|
pub struct InstanceObject {
|
|
asset_type: LayerEntryType,
|
|
pub instance_id: u32,
|
|
#[br(parse_with = string_from_offset, args(start))]
|
|
pub name: String,
|
|
pub transform: Transformation,
|
|
#[br(args(&asset_type))]
|
|
pub data: LayerEntryData,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Layer {
|
|
pub header: LayerHeader,
|
|
pub objects: Vec<InstanceObject>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct LayerChunk {
|
|
pub chunk_id: u32,
|
|
pub layer_group_id: i32,
|
|
pub name: String,
|
|
pub layers: Vec<Layer>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct LayerGroup {
|
|
pub file_id: u32,
|
|
pub chunks: Vec<LayerChunk>,
|
|
}
|
|
|
|
impl LayerGroup {
|
|
/// Reads an existing PBD file
|
|
pub fn from_existing(buffer: ByteSpan) -> Option<LayerGroup> {
|
|
let mut cursor = Cursor::new(buffer);
|
|
|
|
let file_header = LgbHeader::read(&mut cursor).unwrap();
|
|
if file_header.file_size <= 0 || file_header.total_chunk_count <= 0 {
|
|
return None;
|
|
}
|
|
|
|
// yes, for some reason it begins at 8 bytes in?!?!
|
|
let chunk_string_heap = StringHeap::from(cursor.position() + 8);
|
|
|
|
let chunk_header = LayerChunkHeader::read_le_args(&mut cursor, (&chunk_string_heap,)).unwrap();
|
|
|
|
dbg!(&chunk_header);
|
|
|
|
if chunk_header.chunk_size <= 0 {
|
|
return None;
|
|
}
|
|
|
|
let old_pos = cursor.position();
|
|
|
|
let mut layer_offsets = vec![0i32; chunk_header.layer_count as usize];
|
|
for i in 0..chunk_header.layer_count {
|
|
layer_offsets[i as usize] = cursor.read_le::<i32>().unwrap();
|
|
}
|
|
|
|
dbg!(&layer_offsets);
|
|
|
|
let mut layers = Vec::new();
|
|
|
|
for i in 0..chunk_header.layer_count {
|
|
cursor
|
|
.seek(SeekFrom::Start(old_pos + layer_offsets[i as usize] as u64))
|
|
.unwrap();
|
|
|
|
let old_pos = cursor.position();
|
|
|
|
let string_heap = StringHeap::from(old_pos);
|
|
|
|
let header = LayerHeader::read_le_args(&mut cursor, (&string_heap,)).unwrap();
|
|
|
|
let mut objects = Vec::new();
|
|
// read instance objects
|
|
{
|
|
let mut instance_offsets = vec![0i32; header.instance_object_count as usize];
|
|
for i in 0..header.instance_object_count {
|
|
instance_offsets[i as usize] = cursor.read_le::<i32>().unwrap();
|
|
}
|
|
|
|
for i in 0..header.instance_object_count {
|
|
cursor
|
|
.seek(SeekFrom::Start(
|
|
old_pos
|
|
+ header.instance_object_offset as u64
|
|
+ instance_offsets[i as usize] as u64,
|
|
))
|
|
.unwrap();
|
|
|
|
let start = cursor.stream_position().unwrap();
|
|
|
|
objects.push(InstanceObject::read_le_args(&mut cursor, (start,)).unwrap());
|
|
}
|
|
}
|
|
|
|
// read ob set referenced
|
|
{
|
|
cursor
|
|
.seek(SeekFrom::Start(
|
|
old_pos + header.ob_set_referenced_list as u64,
|
|
))
|
|
.unwrap();
|
|
for _ in 0..header.ob_set_referenced_list_count {
|
|
OBSetReferenced::read(&mut cursor).unwrap();
|
|
}
|
|
}
|
|
|
|
// read ob set enable referenced list
|
|
{
|
|
cursor
|
|
.seek(SeekFrom::Start(
|
|
old_pos + header.ob_set_enable_referenced_list as u64,
|
|
))
|
|
.unwrap();
|
|
for _ in 0..header.ob_set_enable_referenced_list_count {
|
|
OBSetEnableReferenced::read(&mut cursor).unwrap();
|
|
}
|
|
}
|
|
|
|
layers.push(Layer { header, objects });
|
|
}
|
|
|
|
let layer_chunk = LayerChunk {
|
|
chunk_id: chunk_header.chunk_id,
|
|
layer_group_id: chunk_header.layer_group_id,
|
|
name: chunk_header.name.value,
|
|
layers,
|
|
};
|
|
|
|
Some(LayerGroup {
|
|
file_id: file_header.file_id,
|
|
chunks: vec![layer_chunk],
|
|
})
|
|
}
|
|
|
|
pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
|
|
let mut buffer = ByteBuffer::new();
|
|
|
|
{
|
|
let mut cursor = Cursor::new(&mut buffer);
|
|
|
|
let lgb_header = LgbHeader {
|
|
file_id: self.file_id,
|
|
file_size: 0,
|
|
total_chunk_count: 0,
|
|
};
|
|
lgb_header.write_le(&mut cursor).ok()?;
|
|
|
|
let mut chunk_string_heap = StringHeap {
|
|
pos: 0,
|
|
bytes: Vec::new(),
|
|
free_pos: 0,
|
|
};
|
|
|
|
// TODO: support multiple layer chunks
|
|
let layer_chunk = LayerChunkHeader {
|
|
chunk_id: self.chunks[0].chunk_id,
|
|
chunk_size: 0,
|
|
layer_group_id: self.chunks[0].layer_group_id,
|
|
name: HeapString { value: self.chunks[0].name.clone() },
|
|
layer_offset: 16, // lol
|
|
layer_count: self.chunks[0].layers.len() as i32,
|
|
};
|
|
layer_chunk.write_le_args(&mut cursor, (&mut chunk_string_heap, )).ok()?;
|
|
|
|
// skip offsets for now, they will be written later
|
|
let offset_pos = cursor.position();
|
|
cursor
|
|
.seek(SeekFrom::Current(
|
|
(std::mem::size_of::<i32>() * self.chunks[0].layers.len()) as i64,
|
|
))
|
|
.ok()?;
|
|
|
|
let mut offsets: Vec<i32> = Vec::new();
|
|
|
|
// write layers
|
|
for layer in &self.chunks[0].layers {
|
|
// set offset
|
|
// this is also used to reference positions inside this layer
|
|
let layer_offset = cursor.position() as i32;
|
|
offsets.push(layer_offset);
|
|
|
|
println!("creating new heap at {}", layer_offset);
|
|
|
|
let mut heap = StringHeap {
|
|
pos: layer_offset as u64,
|
|
bytes: Vec::new(),
|
|
free_pos: layer_offset as u64,
|
|
};
|
|
|
|
layer.header.write_le_args(&mut cursor, (&mut heap,)).ok()?;
|
|
|
|
// write the heap
|
|
heap.write_le(&mut cursor).ok()?;
|
|
}
|
|
|
|
// write offsets
|
|
cursor.seek(SeekFrom::Start(offset_pos)).ok()?;
|
|
for _ in 0..self.chunks[0].layers.len() {
|
|
let offset = 0i32;
|
|
offset.write_le(&mut cursor).ok()?;
|
|
}
|
|
}
|
|
|
|
Some(buffer)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::fs::read;
|
|
use std::path::PathBuf;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_invalid() {
|
|
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
d.push("resources/tests");
|
|
d.push("random");
|
|
|
|
// Feeding it invalid data should not panic
|
|
LayerGroup::from_existing(&read(d).unwrap());
|
|
}
|
|
}
|