physis/layer/
mod.rs

1// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#![allow(unused_variables)] // just binrw things with br(temp)
5
6use std::io::{Cursor, Read, Seek, SeekFrom, Write};
7
8use crate::common_file_operations::{read_bool_from, write_bool_as, write_string};
9use crate::{ByteBuffer, ByteSpan};
10use binrw::{BinRead, BinReaderExt, BinWrite, binread};
11use binrw::{Endian, Error, binrw};
12
13mod aetheryte;
14pub use aetheryte::AetheryteInstanceObject;
15
16mod bg;
17pub use bg::BGInstanceObject;
18pub use bg::ModelCollisionType;
19
20mod common;
21pub use common::Color;
22pub use common::ColorHDRI;
23pub use common::Transformation;
24
25mod env_set;
26pub use env_set::EnvSetInstanceObject;
27pub use env_set::EnvSetShape;
28
29mod exit_range;
30pub use exit_range::ExitRangeInstanceObject;
31pub use exit_range::ExitType;
32
33mod light;
34pub use light::LightInstanceObject;
35pub use light::LightType;
36pub use light::PointLightType;
37
38mod npc;
39pub use npc::BNPCInstanceObject;
40pub use npc::ENPCInstanceObject;
41pub use npc::GameInstanceObject;
42pub use npc::NPCInstanceObject;
43
44mod pop;
45pub use pop::PopRangeInstanceObject;
46pub use pop::PopType;
47
48mod position_marker;
49pub use position_marker::PositionMarkerInstanceObject;
50pub use position_marker::PositionMarkerType;
51
52mod shared_group;
53pub use shared_group::ColourState;
54pub use shared_group::DoorState;
55pub use shared_group::RotationState;
56pub use shared_group::SharedGroupInstance;
57pub use shared_group::TransformState;
58
59mod sound;
60pub use sound::SoundInstanceObject;
61
62mod trigger_box;
63pub use trigger_box::TriggerBoxInstanceObject;
64pub use trigger_box::TriggerBoxShape;
65
66// From https://github.com/NotAdam/Lumina/tree/40dab50183eb7ddc28344378baccc2d63ae71d35/src/Lumina/Data/Parsing/Layer
67// Also see https://github.com/aers/FFXIVClientStructs/blob/6b62122cae38bfbc016bf697bef75f80f37abac1/FFXIVClientStructs/FFXIV/Client/LayoutEngine/ILayoutInstance.cs
68
69/// "LGB1"
70pub const LGB1_ID: u32 = u32::from_le_bytes(*b"LGB1");
71/// "LGP1"
72pub const LGP1_ID: u32 = u32::from_le_bytes(*b"LGP1");
73
74/// A string that exists in a different location in the file, usually a heap with a bunch of other strings.
75#[binrw]
76#[br(import(string_heap: &StringHeap), stream = r)]
77#[bw(import(string_heap: &mut StringHeap))]
78#[derive(Clone, Debug)]
79pub struct HeapString {
80    #[br(temp)]
81    // TODO: this cast is stupid
82    #[bw(calc = string_heap.get_free_offset_string(value) as u32)]
83    pub offset: u32,
84    #[br(calc = string_heap.read_string(r, offset,))]
85    #[bw(ignore)]
86    pub value: String,
87}
88
89#[derive(Debug)]
90pub struct StringHeap {
91    pub pos: u64,
92    pub bytes: Vec<u8>,
93    pub free_pos: u64,
94}
95
96impl StringHeap {
97    pub fn from(pos: u64) -> Self {
98        Self {
99            pos,
100            bytes: Vec::new(),
101            free_pos: 0, // unused, so it doesn't matter
102        }
103    }
104
105    pub fn get_free_offset_args<T>(&mut self, obj: &T) -> i32
106    where
107        T: for<'a> BinWrite<Args<'a> = (&'a mut StringHeap,)> + std::fmt::Debug,
108    {
109        // figure out size of it
110        let mut buffer = ByteBuffer::new();
111        {
112            let mut cursor = Cursor::new(&mut buffer);
113            obj.write_le_args(&mut cursor, (self,)).unwrap();
114        }
115
116        self.bytes.append(&mut buffer);
117
118        let old_pos = self.free_pos;
119        self.free_pos += buffer.len() as u64;
120
121        old_pos as i32
122    }
123
124    pub fn get_free_offset<T>(&mut self, obj: &T) -> i32
125    where
126        T: for<'a> BinWrite<Args<'a> = ()> + std::fmt::Debug,
127    {
128        // figure out size of it
129        let mut buffer = ByteBuffer::new();
130        {
131            let mut cursor = Cursor::new(&mut buffer);
132            obj.write_le(&mut cursor).unwrap();
133        }
134
135        self.bytes.append(&mut buffer);
136
137        let old_pos = self.free_pos;
138        self.free_pos += buffer.len() as u64;
139
140        old_pos as i32
141    }
142
143    pub fn get_free_offset_string(&mut self, str: &String) -> i32 {
144        let bytes = write_string(str);
145        self.get_free_offset(&bytes)
146    }
147
148    pub fn read<R, T>(&self, reader: &mut R, offset: i32) -> T
149    where
150        R: Read + Seek,
151        T: for<'a> BinRead<Args<'a> = ()>,
152    {
153        let old_pos = reader.stream_position().unwrap();
154        reader
155            .seek(SeekFrom::Start((self.pos as i32 + offset) as u64))
156            .unwrap();
157        let obj = reader.read_le::<T>().unwrap();
158        reader.seek(SeekFrom::Start(old_pos)).unwrap();
159        obj
160    }
161
162    pub fn read_args<R, T>(&self, reader: &mut R, offset: i32) -> T
163    where
164        R: Read + Seek,
165        T: for<'a> BinRead<Args<'a> = (&'a StringHeap,)>,
166    {
167        let old_pos = reader.stream_position().unwrap();
168        reader
169            .seek(SeekFrom::Start((self.pos as i32 + offset) as u64))
170            .unwrap();
171        let obj = reader.read_le_args::<T>((self,)).unwrap();
172        reader.seek(SeekFrom::Start(old_pos)).unwrap();
173        obj
174    }
175
176    pub fn read_string<R>(&self, reader: &mut R, offset: u32) -> String
177    where
178        R: Read + Seek,
179    {
180        let offset = self.pos + offset as u64;
181
182        let mut string = String::new();
183
184        let old_pos = reader.stream_position().unwrap();
185
186        reader.seek(SeekFrom::Start(offset)).unwrap();
187        let mut next_char = reader.read_le::<u8>().unwrap() as char;
188        while next_char != '\0' {
189            string.push(next_char);
190            next_char = reader.read_le::<u8>().unwrap() as char;
191        }
192        reader.seek(SeekFrom::Start(old_pos)).unwrap();
193        string
194    }
195}
196
197impl BinWrite for StringHeap {
198    type Args<'a> = ();
199
200    fn write_options<W: Write + Seek>(
201        &self,
202        writer: &mut W,
203        endian: Endian,
204        (): Self::Args<'_>,
205    ) -> Result<(), Error> {
206        self.bytes.write_options(writer, endian, ())?;
207
208        Ok(())
209    }
210}
211
212// TODO: convert these all to magic
213#[binrw]
214#[brw(repr = i32)]
215#[repr(i32)]
216#[derive(Debug, PartialEq)]
217pub enum LayerEntryType {
218    AssetNone = 00,
219    BG = 0x1,
220    Attribute = 0x2,
221    LayLight = 0x3,
222    Vfx = 0x4,
223    PositionMarker = 0x5,
224    SharedGroup = 0x6,
225    Sound = 0x7,
226    EventNPC = 0x8,
227    BattleNPC = 0x9,
228    RoutePath = 0xA,
229    Character = 0xB,
230    Aetheryte = 0xC,
231    EnvSet = 0xD,
232    Gathering = 0xE,
233    HelperObject = 0xF,
234    Treasure = 0x10,
235    Clip = 0x11,
236    ClipCtrlPoint = 0x12,
237    ClipCamera = 0x13,
238    ClipLight = 0x14,
239    ClipReserve00 = 0x15,
240    ClipReserve01 = 0x16,
241    ClipReserve02 = 0x17,
242    ClipReserve03 = 0x18,
243    ClipReserve04 = 0x19,
244    ClipReserve05 = 0x1A,
245    ClipReserve06 = 0x1B,
246    ClipReserve07 = 0x1C,
247    ClipReserve08 = 0x1D,
248    ClipReserve09 = 0x1E,
249    ClipReserve10 = 0x1F,
250    ClipReserve11 = 0x20,
251    ClipReserve12 = 0x21,
252    ClipReserve13 = 0x22,
253    ClipReserve14 = 0x23,
254    CutAssetOnlySelectable = 0x24,
255    Player = 0x25,
256    Monster = 0x26,
257    Weapon = 0x27,
258    PopRange = 0x28,
259    /// Zone Transitions (the visible part is probably LineVFX?)
260    ExitRange = 0x29,
261    Lvb = 0x2A,
262    MapRange = 0x2B,
263    NaviMeshRange = 0x2C,
264    EventObject = 0x2D,
265    DemiHuman = 0x2E,
266    EnvLocation = 0x2F,
267    ControlPoint = 0x30,
268    EventRange = 0x31,
269    RestBonusRange = 0x32,
270    QuestMarker = 0x33,
271    Timeline = 0x34,
272    ObjectBehaviorSet = 0x35,
273    Movie = 0x36,
274    ScenarioExd = 0x37,
275    ScenarioText = 0x38,
276    CollisionBox = 0x39,
277    DoorRange = 0x3A,
278    LineVFX = 0x3B,
279    SoundEnvSet = 0x3C,
280    CutActionTimeline = 0x3D,
281    CharaScene = 0x3E,
282    CutAction = 0x3F,
283    EquipPreset = 0x40,
284    ClientPath = 0x41,
285    ServerPath = 0x42,
286    GimmickRange = 0x43,
287    TargetMarker = 0x44,
288    ChairMarker = 0x45,
289    ClickableRange = 0x46,
290    PrefetchRange = 0x47,
291    FateRange = 0x48,
292    PartyMember = 0x49,
293    KeepRange = 0x4A,
294    SphereCastRange = 0x4B,
295    IndoorObject = 0x4C,
296    OutdoorObject = 0x4D,
297    EditGroup = 0x4E,
298    StableChocobo = 0x4F,
299    MaxAssetType = 0x50,
300    Unk1 = 90,
301    Unk2 = 86, // seen in bg/ex5/02_ykt_y6/fld/y6f1/level/bg.lgb
302    Unk3 = 89, // seen in bg/ffxiv/sea_s1/fld/s1f3/level/planevent.lgb
303}
304
305#[binread]
306#[derive(Debug)]
307#[br(import(magic: &LayerEntryType, string_heap: &StringHeap))]
308pub enum LayerEntryData {
309    #[br(pre_assert(*magic == LayerEntryType::BG))]
310    BG(#[br(args(string_heap))] BGInstanceObject),
311    #[br(pre_assert(*magic == LayerEntryType::LayLight))]
312    LayLight(LightInstanceObject),
313    #[br(pre_assert(*magic == LayerEntryType::Vfx))]
314    Vfx(VFXInstanceObject),
315    #[br(pre_assert(*magic == LayerEntryType::PositionMarker))]
316    PositionMarker(PositionMarkerInstanceObject),
317    #[br(pre_assert(*magic == LayerEntryType::SharedGroup))]
318    SharedGroup(SharedGroupInstance),
319    #[br(pre_assert(*magic == LayerEntryType::Sound))]
320    Sound(SoundInstanceObject),
321    #[br(pre_assert(*magic == LayerEntryType::EventNPC))]
322    EventNPC(ENPCInstanceObject),
323    #[br(pre_assert(*magic == LayerEntryType::BattleNPC))]
324    BattleNPC(BNPCInstanceObject),
325    #[br(pre_assert(*magic == LayerEntryType::Aetheryte))]
326    Aetheryte(AetheryteInstanceObject),
327    #[br(pre_assert(*magic == LayerEntryType::EnvSet))]
328    EnvSet(EnvSetInstanceObject),
329    #[br(pre_assert(*magic == LayerEntryType::Gathering))]
330    Gathering(GatheringInstanceObject),
331    #[br(pre_assert(*magic == LayerEntryType::Treasure))]
332    Treasure(TreasureInstanceObject),
333    #[br(pre_assert(*magic == LayerEntryType::PopRange))]
334    PopRange(PopRangeInstanceObject),
335    #[br(pre_assert(*magic == LayerEntryType::ExitRange))]
336    ExitRange(ExitRangeInstanceObject),
337    #[br(pre_assert(*magic == LayerEntryType::MapRange))]
338    MapRange(MapRangeInstanceObject),
339    #[br(pre_assert(*magic == LayerEntryType::EventObject))]
340    EventObject(EventInstanceObject),
341    #[br(pre_assert(*magic == LayerEntryType::EnvLocation))]
342    EnvLocation(EnvLocationObject),
343    #[br(pre_assert(*magic == LayerEntryType::EventRange))]
344    EventRange(EventRangeInstanceObject),
345    #[br(pre_assert(*magic == LayerEntryType::QuestMarker))]
346    QuestMarker(QuestMarkerInstanceObject),
347    #[br(pre_assert(*magic == LayerEntryType::CollisionBox))]
348    CollisionBox(CollisionBoxInstanceObject),
349    #[br(pre_assert(*magic == LayerEntryType::LineVFX))]
350    LineVFX(LineVFXInstanceObject),
351    #[br(pre_assert(*magic == LayerEntryType::ClientPath))]
352    ClientPath(ClientPathInstanceObject),
353    #[br(pre_assert(*magic == LayerEntryType::ServerPath))]
354    ServerPath(ServerPathInstanceObject),
355    #[br(pre_assert(*magic == LayerEntryType::GimmickRange))]
356    GimmickRange(GimmickRangeInstanceObject),
357    #[br(pre_assert(*magic == LayerEntryType::TargetMarker))]
358    TargetMarker(TargetMarkerInstanceObject),
359    #[br(pre_assert(*magic == LayerEntryType::ChairMarker))]
360    ChairMarker(ChairMarkerInstanceObject),
361    #[br(pre_assert(*magic == LayerEntryType::PrefetchRange))]
362    PrefetchRange(PrefetchRangeInstanceObject),
363    #[br(pre_assert(*magic == LayerEntryType::FateRange))]
364    FateRange(FateRangeInstanceObject),
365    #[br(pre_assert(*magic == LayerEntryType::Unk1))]
366    Unk1(),
367    #[br(pre_assert(*magic == LayerEntryType::Unk2))]
368    Unk2(),
369    #[br(pre_assert(*magic == LayerEntryType::Unk3))]
370    Unk3(),
371}
372
373#[binread]
374#[derive(Debug)]
375#[br(little)]
376pub struct VFXInstanceObject {
377    pub asset_path_offset: u32,
378    #[brw(pad_after = 4)] // padding
379    pub soft_particle_fade_range: f32,
380    pub color: Color,
381    #[br(map = read_bool_from::<u8>)]
382    pub auto_play: bool,
383    #[br(map = read_bool_from::<u8>)]
384    #[brw(pad_after = 2)] // padding
385    pub no_far_clip: bool,
386    pub fade_near_start: f32,
387    pub fade_near_end: f32,
388    pub fade_far_start: f32,
389    pub fade_far_end: f32,
390    pub z_correct: f32,
391}
392
393#[binread]
394#[derive(Debug)]
395#[br(little)]
396pub struct GatheringInstanceObject {
397    #[brw(pad_after = 4)] // padding
398    pub gathering_point_id: u32,
399}
400
401#[binread]
402#[derive(Debug)]
403#[br(little)]
404pub struct TreasureInstanceObject {
405    #[brw(pad_after = 11)] // padding
406    pub nonpop_init_zone: u8,
407}
408
409// Unimplemented because I haven't needed it yet:
410#[binread]
411#[derive(Debug)]
412#[br(little)]
413pub struct MapRangeInstanceObject {}
414
415#[binread]
416#[derive(Debug)]
417#[br(little)]
418pub struct EventInstanceObject {}
419
420#[binread]
421#[derive(Debug)]
422#[br(little)]
423pub struct EnvLocationObject {}
424
425#[binread]
426#[derive(Debug)]
427#[br(little)]
428pub struct EventRangeInstanceObject {}
429
430#[binread]
431#[derive(Debug)]
432#[br(little)]
433pub struct QuestMarkerInstanceObject {}
434
435#[binread]
436#[derive(Debug)]
437#[br(little)]
438pub struct CollisionBoxInstanceObject {}
439
440#[binread]
441#[derive(Debug)]
442#[br(little)]
443pub struct LineVFXInstanceObject {}
444
445#[binread]
446#[derive(Debug)]
447#[br(little)]
448pub struct ClientPathInstanceObject {}
449
450#[binread]
451#[derive(Debug)]
452#[br(little)]
453pub struct ServerPathInstanceObject {}
454
455#[binread]
456#[derive(Debug)]
457#[br(little)]
458pub struct GimmickRangeInstanceObject {}
459
460#[binread]
461#[derive(Debug)]
462#[br(little)]
463pub struct TargetMarkerInstanceObject {}
464
465#[binread]
466#[derive(Debug)]
467#[br(little)]
468pub struct ChairMarkerInstanceObject {}
469
470#[binread]
471#[derive(Debug)]
472#[br(little)]
473pub struct PrefetchRangeInstanceObject {}
474
475#[binread]
476#[derive(Debug)]
477#[br(little)]
478pub struct FateRangeInstanceObject {}
479
480#[binrw]
481#[brw(repr = i32)]
482#[derive(Debug, PartialEq)]
483enum LayerSetReferencedType {
484    All = 0x0,
485    Include = 0x1,
486    Exclude = 0x2,
487    Undetermined = 0x3,
488}
489
490#[binrw]
491#[derive(Debug)]
492#[brw(little)]
493#[br(import(data_heap: &StringHeap, string_heap: &StringHeap), stream = r)]
494#[bw(import(data_heap: &mut StringHeap, string_heap: &mut StringHeap))]
495#[allow(dead_code)] // most of the fields are unused at the moment
496pub struct LayerHeader {
497    pub layer_id: u32,
498
499    #[brw(args(string_heap))]
500    pub name: HeapString,
501
502    pub instance_object_offset: i32,
503    pub instance_object_count: i32,
504
505    #[br(map = read_bool_from::<u8>)]
506    #[bw(map = write_bool_as::<u8>)]
507    pub tool_mode_visible: bool,
508    #[br(map = read_bool_from::<u8>)]
509    #[bw(map = write_bool_as::<u8>)]
510    pub tool_mode_read_only: bool,
511
512    #[br(map = read_bool_from::<u8>)]
513    #[bw(map = write_bool_as::<u8>)]
514    pub is_bush_layer: bool,
515    #[br(map = read_bool_from::<u8>)]
516    #[bw(map = write_bool_as::<u8>)]
517    pub ps3_visible: bool,
518    #[br(temp)]
519    #[bw(calc = data_heap.get_free_offset_args(&layer_set_referenced_list))]
520    pub layer_set_referenced_list_offset: i32,
521    #[br(calc = data_heap.read_args(r, layer_set_referenced_list_offset))]
522    #[bw(ignore)]
523    pub layer_set_referenced_list: LayerSetReferencedList,
524    pub festival_id: u16,
525    pub festival_phase_id: u16,
526    pub is_temporary: u8,
527    pub is_housing: u8,
528    pub version_mask: u16,
529
530    #[brw(pad_before = 4)]
531    pub ob_set_referenced_list: i32,
532    pub ob_set_referenced_list_count: i32,
533    pub ob_set_enable_referenced_list: i32,
534    pub ob_set_enable_referenced_list_count: i32,
535}
536
537#[binrw]
538#[derive(Debug)]
539#[brw(little)]
540#[allow(dead_code)] // most of the fields are unused at the moment
541pub struct LayerSetReferenced {
542    pub layer_set_id: u32,
543}
544
545#[binrw]
546#[derive(Debug)]
547#[brw(little)]
548#[br(import(data_heap: &StringHeap), stream = r)]
549#[bw(import(data_heap: &mut StringHeap))]
550pub struct LayerSetReferencedList {
551    referenced_type: LayerSetReferencedType,
552    #[br(temp)]
553    #[bw(calc = data_heap.get_free_offset(&layer_sets))]
554    layer_set_offset: i32,
555    #[bw(calc = layer_sets.len() as i32)]
556    pub layer_set_count: i32,
557
558    #[br(count = layer_set_count)]
559    #[bw(ignore)]
560    pub layer_sets: Vec<LayerSetReferenced>,
561}
562
563#[binread]
564#[derive(Debug)]
565#[br(little)]
566#[allow(dead_code)] // most of the fields are unused at the moment
567struct OBSetReferenced {
568    asset_type: LayerEntryType,
569    instance_id: u32,
570    ob_set_asset_path_offset: u32,
571}
572
573#[binread]
574#[derive(Debug)]
575#[br(little)]
576#[allow(dead_code)] // most of the fields are unused at the moment
577struct OBSetEnableReferenced {
578    asset_type: LayerEntryType,
579    instance_id: u32,
580    #[br(map = read_bool_from::<u8>)]
581    ob_set_enable: bool,
582    #[br(map = read_bool_from::<u8>)]
583    ob_set_emissive_enable: bool,
584    padding: [u8; 2],
585}
586
587#[binrw]
588#[derive(Debug)]
589#[brw(little)]
590#[allow(dead_code)] // most of the fields are unused at the moment
591struct LgbHeader {
592    // Example: "LGB1"
593    file_id: u32,
594    // File size *including* this header
595    file_size: i32,
596    total_chunk_count: i32,
597}
598
599#[binrw]
600#[derive(Debug)]
601#[br(import(string_heap: &StringHeap), stream = r)]
602#[bw(import(string_heap: &mut StringHeap))]
603#[brw(little)]
604#[allow(dead_code)] // most of the fields are unused at the moment
605struct LayerChunkHeader {
606    chunk_id: u32,
607    chunk_size: i32,
608    layer_group_id: i32,
609    #[brw(args(string_heap))]
610    pub name: HeapString,
611    layer_offset: i32,
612    layer_count: i32,
613}
614
615const LAYER_CHUNK_HEADER_SIZE: usize = 24;
616
617#[binread]
618#[derive(Debug)]
619#[br(little)]
620#[br(import(string_heap: &StringHeap))]
621#[allow(dead_code)] // most of the fields are unused at the moment
622pub struct InstanceObject {
623    asset_type: LayerEntryType,
624    pub instance_id: u32,
625    #[br(args(string_heap))]
626    pub name: HeapString,
627    pub transform: Transformation,
628    #[br(args(&asset_type, string_heap))]
629    pub data: LayerEntryData,
630}
631
632#[derive(Debug)]
633pub struct Layer {
634    pub header: LayerHeader,
635    pub objects: Vec<InstanceObject>,
636}
637
638#[derive(Debug)]
639pub struct LayerChunk {
640    // Example: "LGP1"
641    pub chunk_id: u32,
642    pub layer_group_id: i32,
643    pub name: String,
644    pub layers: Vec<Layer>,
645}
646
647#[derive(Debug)]
648pub struct LayerGroup {
649    pub file_id: u32,
650    pub chunks: Vec<LayerChunk>,
651}
652
653impl LayerGroup {
654    /// Reads an existing PBD file
655    pub fn from_existing(buffer: ByteSpan) -> Option<LayerGroup> {
656        let mut cursor = Cursor::new(buffer);
657
658        let file_header = LgbHeader::read(&mut cursor).unwrap();
659        if file_header.file_size <= 0 || file_header.total_chunk_count <= 0 {
660            return None;
661        }
662
663        // yes, for some reason it begins at 8 bytes in?!?!
664        let chunk_string_heap = StringHeap::from(cursor.position() + 8);
665
666        let chunk_header =
667            LayerChunkHeader::read_le_args(&mut cursor, (&chunk_string_heap,)).unwrap();
668        if chunk_header.chunk_size <= 0 {
669            return Some(LayerGroup {
670                file_id: file_header.file_id,
671                chunks: Vec::new(),
672            });
673        }
674
675        let old_pos = cursor.position();
676
677        let mut layer_offsets = vec![0i32; chunk_header.layer_count as usize];
678        for i in 0..chunk_header.layer_count {
679            layer_offsets[i as usize] = cursor.read_le::<i32>().unwrap();
680        }
681
682        let mut layers = Vec::new();
683
684        for i in 0..chunk_header.layer_count {
685            cursor
686                .seek(SeekFrom::Start(old_pos + layer_offsets[i as usize] as u64))
687                .unwrap();
688
689            let old_pos = cursor.position();
690
691            let string_heap = StringHeap::from(old_pos);
692            let data_heap = StringHeap::from(old_pos);
693
694            let header =
695                LayerHeader::read_le_args(&mut cursor, (&data_heap, &string_heap)).unwrap();
696
697            let mut objects = Vec::new();
698            // read instance objects
699            {
700                let mut instance_offsets = vec![0i32; header.instance_object_count as usize];
701                for i in 0..header.instance_object_count {
702                    instance_offsets[i as usize] = cursor.read_le::<i32>().unwrap();
703                }
704
705                for i in 0..header.instance_object_count {
706                    cursor
707                        .seek(SeekFrom::Start(
708                            old_pos
709                                + header.instance_object_offset as u64
710                                + instance_offsets[i as usize] as u64,
711                        ))
712                        .unwrap();
713
714                    let start = cursor.stream_position().unwrap();
715                    let string_heap = StringHeap::from(start);
716
717                    objects.push(InstanceObject::read_le_args(&mut cursor, (&string_heap,)).ok()?);
718                }
719            }
720
721            // read ob set referenced
722            {
723                cursor
724                    .seek(SeekFrom::Start(
725                        old_pos + header.ob_set_referenced_list as u64,
726                    ))
727                    .unwrap();
728                for _ in 0..header.ob_set_referenced_list_count {
729                    OBSetReferenced::read(&mut cursor).unwrap();
730                }
731            }
732
733            // read ob set enable referenced list
734            {
735                cursor
736                    .seek(SeekFrom::Start(
737                        old_pos + header.ob_set_enable_referenced_list as u64,
738                    ))
739                    .unwrap();
740                for _ in 0..header.ob_set_enable_referenced_list_count {
741                    OBSetEnableReferenced::read(&mut cursor).unwrap();
742                }
743            }
744
745            layers.push(Layer { header, objects });
746        }
747
748        let layer_chunk = LayerChunk {
749            chunk_id: chunk_header.chunk_id,
750            layer_group_id: chunk_header.layer_group_id,
751            name: chunk_header.name.value,
752            layers,
753        };
754
755        Some(LayerGroup {
756            file_id: file_header.file_id,
757            chunks: vec![layer_chunk],
758        })
759    }
760
761    pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
762        let mut buffer = ByteBuffer::new();
763
764        {
765            let mut cursor = Cursor::new(&mut buffer);
766
767            // skip header, will be writing it later
768            cursor
769                .seek(SeekFrom::Start(std::mem::size_of::<LgbHeader>() as u64))
770                .unwrap();
771
772            // base offset for deferred data
773            let mut data_base = cursor.stream_position().unwrap();
774
775            let mut chunk_data_heap = StringHeap {
776                pos: data_base + 4,
777                bytes: Vec::new(),
778                free_pos: data_base + 4,
779            };
780
781            let mut chunk_string_heap = StringHeap {
782                pos: data_base + 4,
783                bytes: Vec::new(),
784                free_pos: data_base + 4,
785            };
786
787            // we will write this later, when we have a working string heap
788            let layer_chunk_header_pos = cursor.stream_position().unwrap();
789            cursor
790                .seek(SeekFrom::Current(LAYER_CHUNK_HEADER_SIZE as i64))
791                .unwrap();
792
793            // skip offsets for now, they will be written later
794            let offset_pos = cursor.position();
795            cursor
796                .seek(SeekFrom::Current(
797                    (std::mem::size_of::<i32>() * self.chunks[0].layers.len()) as i64,
798                ))
799                .ok()?;
800
801            let mut offsets: Vec<i32> = Vec::new();
802
803            let layer_data_offset = cursor.position();
804
805            // first pass: write layers, we want to get a correct *chunk_data_heap*
806            for layer in &self.chunks[0].layers {
807                // set offset
808                // this is also used to reference positions inside this layer
809                let layer_offset = cursor.position() as i32;
810                offsets.push(layer_offset);
811
812                layer
813                    .header
814                    .write_le_args(&mut cursor, (&mut chunk_data_heap, &mut chunk_string_heap))
815                    .ok()?;
816            }
817
818            // make sure the heaps are at the end of the layer data
819            data_base += cursor.stream_position().unwrap() - layer_data_offset;
820
821            // second pass: write layers again, we want to get a correct *chunk_string_heap* now that we know of the size of chunk_data_heap
822            chunk_string_heap = StringHeap {
823                pos: data_base + 4 + chunk_data_heap.bytes.len() as u64,
824                bytes: Vec::new(),
825                free_pos: data_base + 4 + chunk_data_heap.bytes.len() as u64,
826            };
827            chunk_data_heap = StringHeap {
828                pos: data_base + 4,
829                bytes: Vec::new(),
830                free_pos: data_base + 4,
831            };
832
833            // write header now, because it has a string
834            cursor
835                .seek(SeekFrom::Start(layer_chunk_header_pos))
836                .unwrap();
837            // TODO: support multiple layer chunks
838            let layer_chunk = LayerChunkHeader {
839                chunk_id: self.chunks[0].chunk_id,
840                chunk_size: 24, // double lol
841                layer_group_id: self.chunks[0].layer_group_id,
842                name: HeapString {
843                    value: self.chunks[0].name.clone(),
844                },
845                layer_offset: 16, // lol
846                layer_count: self.chunks[0].layers.len() as i32,
847            };
848            layer_chunk
849                .write_le_args(&mut cursor, (&mut chunk_string_heap,))
850                .ok()?;
851
852            // now write the layer data for the final time
853            cursor.seek(SeekFrom::Start(layer_data_offset)).unwrap();
854            for layer in &self.chunks[0].layers {
855                layer
856                    .header
857                    .write_le_args(&mut cursor, (&mut chunk_data_heap, &mut chunk_string_heap))
858                    .ok()?;
859            }
860
861            // write the heaps
862            chunk_data_heap.write_le(&mut cursor).ok()?;
863            chunk_string_heap.write_le(&mut cursor).ok()?;
864
865            // write offsets
866            assert_eq!(offsets.len(), self.chunks[0].layers.len());
867            cursor.seek(SeekFrom::Start(offset_pos)).ok()?;
868            for offset in offsets {
869                offset.write_le(&mut cursor).ok()?;
870            }
871        }
872
873        let file_size = buffer.len() as i32;
874
875        {
876            let mut cursor = Cursor::new(&mut buffer);
877
878            // write the header, now that we now the file size
879            cursor.seek(SeekFrom::Start(0)).ok()?;
880            let lgb_header = LgbHeader {
881                file_id: self.file_id,
882                file_size,
883                total_chunk_count: self.chunks.len() as i32,
884            };
885            lgb_header.write_le(&mut cursor).ok()?;
886        }
887
888        Some(buffer)
889    }
890}
891
892#[cfg(test)]
893mod tests {
894    use std::fs::read;
895    use std::path::PathBuf;
896
897    use super::*;
898
899    #[test]
900    fn test_invalid() {
901        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
902        d.push("resources/tests");
903        d.push("random");
904
905        // Feeding it invalid data should not panic
906        LayerGroup::from_existing(&read(d).unwrap());
907    }
908
909    #[test]
910    fn read_empty_planlive() {
911        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
912        d.push("resources/tests");
913        d.push("empty_planlive.lgb");
914
915        let lgb = LayerGroup::from_existing(&read(d).unwrap()).unwrap();
916        assert_eq!(lgb.file_id, LGB1_ID);
917        assert_eq!(lgb.chunks.len(), 1);
918
919        let chunk = &lgb.chunks[0];
920        assert_eq!(chunk.chunk_id, LGP1_ID);
921        assert_eq!(chunk.layer_group_id, 261);
922        assert_eq!(chunk.name, "PlanLive".to_string());
923        assert!(chunk.layers.is_empty());
924    }
925
926    #[test]
927    fn write_empty_planlive() {
928        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
929        d.push("resources/tests");
930        d.push("empty_planlive.lgb");
931
932        let good_lgb_bytes = read(d).unwrap();
933
934        let lgb = LayerGroup {
935            file_id: LGB1_ID,
936            chunks: vec![LayerChunk {
937                chunk_id: LGP1_ID,
938                layer_group_id: 261,
939                name: "PlanLive".to_string(),
940                layers: Vec::new(),
941            }],
942        };
943        assert_eq!(lgb.write_to_buffer().unwrap(), good_lgb_bytes);
944    }
945}