physis/layer/
mod.rs

1// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use std::io::{Cursor, Read, Seek, SeekFrom, Write};
5
6use crate::common_file_operations::{
7    read_bool_from, string_from_offset, write_bool_as, write_string,
8};
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"
70const LGB1_ID: u32 = u32::from_le_bytes(*b"LGB1");
71/// "LGP1"
72const 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}
302
303#[binread]
304#[derive(Debug)]
305#[br(import(magic: &LayerEntryType, string_heap: &StringHeap))]
306pub enum LayerEntryData {
307    #[br(pre_assert(*magic == LayerEntryType::BG))]
308    BG(#[br(args(string_heap))] BGInstanceObject),
309    #[br(pre_assert(*magic == LayerEntryType::LayLight))]
310    LayLight(LightInstanceObject),
311    #[br(pre_assert(*magic == LayerEntryType::Vfx))]
312    Vfx(VFXInstanceObject),
313    #[br(pre_assert(*magic == LayerEntryType::PositionMarker))]
314    PositionMarker(PositionMarkerInstanceObject),
315    #[br(pre_assert(*magic == LayerEntryType::SharedGroup))]
316    SharedGroup(SharedGroupInstance),
317    #[br(pre_assert(*magic == LayerEntryType::Sound))]
318    Sound(SoundInstanceObject),
319    #[br(pre_assert(*magic == LayerEntryType::EventNPC))]
320    EventNPC(ENPCInstanceObject),
321    #[br(pre_assert(*magic == LayerEntryType::BattleNPC))]
322    BattleNPC(BNPCInstanceObject),
323    #[br(pre_assert(*magic == LayerEntryType::Aetheryte))]
324    Aetheryte(AetheryteInstanceObject),
325    #[br(pre_assert(*magic == LayerEntryType::EnvSet))]
326    EnvSet(EnvSetInstanceObject),
327    #[br(pre_assert(*magic == LayerEntryType::Gathering))]
328    Gathering(GatheringInstanceObject),
329    #[br(pre_assert(*magic == LayerEntryType::Treasure))]
330    Treasure(TreasureInstanceObject),
331    #[br(pre_assert(*magic == LayerEntryType::PopRange))]
332    PopRange(PopRangeInstanceObject),
333    #[br(pre_assert(*magic == LayerEntryType::ExitRange))]
334    ExitRange(ExitRangeInstanceObject),
335    #[br(pre_assert(*magic == LayerEntryType::MapRange))]
336    MapRange(MapRangeInstanceObject),
337    #[br(pre_assert(*magic == LayerEntryType::EventObject))]
338    EventObject(EventInstanceObject),
339    #[br(pre_assert(*magic == LayerEntryType::EnvLocation))]
340    EnvLocation(EnvLocationObject),
341    #[br(pre_assert(*magic == LayerEntryType::EventRange))]
342    EventRange(EventRangeInstanceObject),
343    #[br(pre_assert(*magic == LayerEntryType::QuestMarker))]
344    QuestMarker(QuestMarkerInstanceObject),
345    #[br(pre_assert(*magic == LayerEntryType::CollisionBox))]
346    CollisionBox(CollisionBoxInstanceObject),
347    #[br(pre_assert(*magic == LayerEntryType::LineVFX))]
348    LineVFX(LineVFXInstanceObject),
349    #[br(pre_assert(*magic == LayerEntryType::ClientPath))]
350    ClientPath(ClientPathInstanceObject),
351    #[br(pre_assert(*magic == LayerEntryType::ServerPath))]
352    ServerPath(ServerPathInstanceObject),
353    #[br(pre_assert(*magic == LayerEntryType::GimmickRange))]
354    GimmickRange(GimmickRangeInstanceObject),
355    #[br(pre_assert(*magic == LayerEntryType::TargetMarker))]
356    TargetMarker(TargetMarkerInstanceObject),
357    #[br(pre_assert(*magic == LayerEntryType::ChairMarker))]
358    ChairMarker(ChairMarkerInstanceObject),
359    #[br(pre_assert(*magic == LayerEntryType::PrefetchRange))]
360    PrefetchRange(PrefetchRangeInstanceObject),
361    #[br(pre_assert(*magic == LayerEntryType::FateRange))]
362    FateRange(FateRangeInstanceObject),
363    #[br(pre_assert(*magic == LayerEntryType::Unk1))]
364    Unk1(),
365}
366
367#[binread]
368#[derive(Debug)]
369#[br(little)]
370pub struct VFXInstanceObject {
371    asset_path_offset: u32,
372    soft_particle_fade_range: f32,
373    padding: u32,
374    color: Color,
375    #[br(map = read_bool_from::<u8>)]
376    auto_play: bool,
377    #[br(map = read_bool_from::<u8>)]
378    no_far_clip: bool,
379    padding1: u16,
380    fade_near_start: f32,
381    fade_near_end: f32,
382    fade_far_start: f32,
383    fade_far_end: f32,
384    z_correct: f32,
385}
386
387#[binread]
388#[derive(Debug)]
389#[br(little)]
390pub struct GatheringInstanceObject {
391    gathering_point_id: u32,
392    padding: u32,
393}
394
395#[binread]
396#[derive(Debug)]
397#[br(little)]
398pub struct TreasureInstanceObject {
399    nonpop_init_zone: u8,
400    padding1: [u8; 3],
401    padding2: [u32; 2],
402}
403
404// Unimplemented because I haven't needed it yet:
405#[binread]
406#[derive(Debug)]
407#[br(little)]
408pub struct MapRangeInstanceObject {}
409
410#[binread]
411#[derive(Debug)]
412#[br(little)]
413pub struct EventInstanceObject {}
414
415#[binread]
416#[derive(Debug)]
417#[br(little)]
418pub struct EnvLocationObject {}
419
420#[binread]
421#[derive(Debug)]
422#[br(little)]
423pub struct EventRangeInstanceObject {}
424
425#[binread]
426#[derive(Debug)]
427#[br(little)]
428pub struct QuestMarkerInstanceObject {}
429
430#[binread]
431#[derive(Debug)]
432#[br(little)]
433pub struct CollisionBoxInstanceObject {}
434
435#[binread]
436#[derive(Debug)]
437#[br(little)]
438pub struct LineVFXInstanceObject {}
439
440#[binread]
441#[derive(Debug)]
442#[br(little)]
443pub struct ClientPathInstanceObject {}
444
445#[binread]
446#[derive(Debug)]
447#[br(little)]
448pub struct ServerPathInstanceObject {}
449
450#[binread]
451#[derive(Debug)]
452#[br(little)]
453pub struct GimmickRangeInstanceObject {}
454
455#[binread]
456#[derive(Debug)]
457#[br(little)]
458pub struct TargetMarkerInstanceObject {}
459
460#[binread]
461#[derive(Debug)]
462#[br(little)]
463pub struct ChairMarkerInstanceObject {}
464
465#[binread]
466#[derive(Debug)]
467#[br(little)]
468pub struct PrefetchRangeInstanceObject {}
469
470#[binread]
471#[derive(Debug)]
472#[br(little)]
473pub struct FateRangeInstanceObject {}
474
475#[binrw]
476#[brw(repr = i32)]
477#[derive(Debug, PartialEq)]
478enum LayerSetReferencedType {
479    All = 0x0,
480    Include = 0x1,
481    Exclude = 0x2,
482    Undetermined = 0x3,
483}
484
485#[binrw]
486#[derive(Debug)]
487#[brw(little)]
488#[br(import(data_heap: &StringHeap, string_heap: &StringHeap), stream = r)]
489#[bw(import(data_heap: &mut StringHeap, string_heap: &mut StringHeap))]
490#[allow(dead_code)] // most of the fields are unused at the moment
491pub struct LayerHeader {
492    pub layer_id: u32,
493
494    #[brw(args(string_heap))]
495    pub name: HeapString,
496
497    pub instance_object_offset: i32,
498    pub instance_object_count: i32,
499
500    #[br(map = read_bool_from::<u8>)]
501    #[bw(map = write_bool_as::<u8>)]
502    pub tool_mode_visible: bool,
503    #[br(map = read_bool_from::<u8>)]
504    #[bw(map = write_bool_as::<u8>)]
505    pub tool_mode_read_only: bool,
506
507    #[br(map = read_bool_from::<u8>)]
508    #[bw(map = write_bool_as::<u8>)]
509    pub is_bush_layer: bool,
510    #[br(map = read_bool_from::<u8>)]
511    #[bw(map = write_bool_as::<u8>)]
512    pub ps3_visible: bool,
513    #[br(temp)]
514    #[bw(calc = data_heap.get_free_offset_args(&layer_set_referenced_list))]
515    pub layer_set_referenced_list_offset: i32,
516    #[br(calc = data_heap.read_args(r, layer_set_referenced_list_offset))]
517    #[bw(ignore)]
518    pub layer_set_referenced_list: LayerSetReferencedList,
519    pub festival_id: u16,
520    pub festival_phase_id: u16,
521    pub is_temporary: u8,
522    pub is_housing: u8,
523    pub version_mask: u16,
524
525    #[brw(pad_before = 4)]
526    pub ob_set_referenced_list: i32,
527    pub ob_set_referenced_list_count: i32,
528    pub ob_set_enable_referenced_list: i32,
529    pub ob_set_enable_referenced_list_count: i32,
530}
531
532#[binrw]
533#[derive(Debug)]
534#[brw(little)]
535#[allow(dead_code)] // most of the fields are unused at the moment
536struct LayerSetReferenced {
537    layer_set_id: u32,
538}
539
540#[binrw]
541#[derive(Debug)]
542#[brw(little)]
543#[br(import(data_heap: &StringHeap), stream = r)]
544#[bw(import(data_heap: &mut StringHeap))]
545#[allow(dead_code)] // most of the fields are unused at the moment
546struct LayerSetReferencedList {
547    referenced_type: LayerSetReferencedType,
548    #[br(temp)]
549    #[bw(calc = data_heap.get_free_offset(&layer_sets))]
550    layer_set_offset: i32,
551    #[bw(calc = layer_sets.len() as i32)]
552    layer_set_count: i32,
553
554    #[br(count = layer_set_count)]
555    #[bw(ignore)]
556    layer_sets: Vec<LayerSetReferenced>,
557}
558
559#[binread]
560#[derive(Debug)]
561#[br(little)]
562#[allow(dead_code)] // most of the fields are unused at the moment
563struct OBSetReferenced {
564    asset_type: LayerEntryType,
565    instance_id: u32,
566    ob_set_asset_path_offset: u32,
567}
568
569#[binread]
570#[derive(Debug)]
571#[br(little)]
572#[allow(dead_code)] // most of the fields are unused at the moment
573struct OBSetEnableReferenced {
574    asset_type: LayerEntryType,
575    instance_id: u32,
576    #[br(map = read_bool_from::<u8>)]
577    ob_set_enable: bool,
578    #[br(map = read_bool_from::<u8>)]
579    ob_set_emissive_enable: bool,
580    padding: [u8; 2],
581}
582
583#[binrw]
584#[derive(Debug)]
585#[brw(little)]
586#[allow(dead_code)] // most of the fields are unused at the moment
587struct LgbHeader {
588    // Example: "LGB1"
589    file_id: u32,
590    // File size *including* this header
591    file_size: i32,
592    total_chunk_count: i32,
593}
594
595#[binrw]
596#[derive(Debug)]
597#[br(import(string_heap: &StringHeap), stream = r)]
598#[bw(import(string_heap: &mut StringHeap))]
599#[brw(little)]
600#[allow(dead_code)] // most of the fields are unused at the moment
601struct LayerChunkHeader {
602    chunk_id: u32,
603    chunk_size: i32,
604    layer_group_id: i32,
605    #[brw(args(string_heap))]
606    pub name: HeapString,
607    layer_offset: i32,
608    layer_count: i32,
609}
610
611const LAYER_CHUNK_HEADER_SIZE: usize = 24;
612
613#[binread]
614#[derive(Debug)]
615#[br(little)]
616#[br(import(start: u64, string_heap: &StringHeap))]
617#[allow(dead_code)] // most of the fields are unused at the moment
618pub struct InstanceObject {
619    asset_type: LayerEntryType,
620    pub instance_id: u32,
621    #[br(args(string_heap))]
622    pub name: HeapString,
623    pub transform: Transformation,
624    #[br(args(&asset_type, string_heap))]
625    pub data: LayerEntryData,
626}
627
628#[derive(Debug)]
629pub struct Layer {
630    pub header: LayerHeader,
631    pub objects: Vec<InstanceObject>,
632}
633
634#[derive(Debug)]
635pub struct LayerChunk {
636    // Example: "LGP1"
637    pub chunk_id: u32,
638    pub layer_group_id: i32,
639    pub name: String,
640    pub layers: Vec<Layer>,
641}
642
643#[derive(Debug)]
644pub struct LayerGroup {
645    pub file_id: u32,
646    pub chunks: Vec<LayerChunk>,
647}
648
649impl LayerGroup {
650    /// Reads an existing PBD file
651    pub fn from_existing(buffer: ByteSpan) -> Option<LayerGroup> {
652        let mut cursor = Cursor::new(buffer);
653
654        let file_header = LgbHeader::read(&mut cursor).unwrap();
655        if file_header.file_size <= 0 || file_header.total_chunk_count <= 0 {
656            return None;
657        }
658
659        // yes, for some reason it begins at 8 bytes in?!?!
660        let chunk_string_heap = StringHeap::from(cursor.position() + 8);
661
662        let chunk_header =
663            LayerChunkHeader::read_le_args(&mut cursor, (&chunk_string_heap,)).unwrap();
664
665        if chunk_header.chunk_size <= 0 {
666            return None;
667        }
668
669        let old_pos = cursor.position();
670
671        let mut layer_offsets = vec![0i32; chunk_header.layer_count as usize];
672        for i in 0..chunk_header.layer_count {
673            layer_offsets[i as usize] = cursor.read_le::<i32>().unwrap();
674        }
675
676        let mut layers = Vec::new();
677
678        for i in 0..chunk_header.layer_count {
679            cursor
680                .seek(SeekFrom::Start(old_pos + layer_offsets[i as usize] as u64))
681                .unwrap();
682
683            let old_pos = cursor.position();
684
685            let string_heap = StringHeap::from(old_pos);
686            let data_heap = StringHeap::from(old_pos);
687
688            let header =
689                LayerHeader::read_le_args(&mut cursor, (&data_heap, &string_heap)).unwrap();
690
691            let mut objects = Vec::new();
692            // read instance objects
693            {
694                let mut instance_offsets = vec![0i32; header.instance_object_count as usize];
695                for i in 0..header.instance_object_count {
696                    instance_offsets[i as usize] = cursor.read_le::<i32>().unwrap();
697                }
698
699                for i in 0..header.instance_object_count {
700                    cursor
701                        .seek(SeekFrom::Start(
702                            old_pos
703                                + header.instance_object_offset as u64
704                                + instance_offsets[i as usize] as u64,
705                        ))
706                        .unwrap();
707
708                    let start = cursor.stream_position().unwrap();
709                    let string_heap = StringHeap::from(start);
710
711                    objects.push(
712                        InstanceObject::read_le_args(&mut cursor, (start, &string_heap)).unwrap(),
713                    );
714                }
715            }
716
717            // read ob set referenced
718            {
719                cursor
720                    .seek(SeekFrom::Start(
721                        old_pos + header.ob_set_referenced_list as u64,
722                    ))
723                    .unwrap();
724                for _ in 0..header.ob_set_referenced_list_count {
725                    OBSetReferenced::read(&mut cursor).unwrap();
726                }
727            }
728
729            // read ob set enable referenced list
730            {
731                cursor
732                    .seek(SeekFrom::Start(
733                        old_pos + header.ob_set_enable_referenced_list as u64,
734                    ))
735                    .unwrap();
736                for _ in 0..header.ob_set_enable_referenced_list_count {
737                    OBSetEnableReferenced::read(&mut cursor).unwrap();
738                }
739            }
740
741            layers.push(Layer { header, objects });
742        }
743
744        let layer_chunk = LayerChunk {
745            chunk_id: chunk_header.chunk_id,
746            layer_group_id: chunk_header.layer_group_id,
747            name: chunk_header.name.value,
748            layers,
749        };
750
751        Some(LayerGroup {
752            file_id: file_header.file_id,
753            chunks: vec![layer_chunk],
754        })
755    }
756
757    pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
758        let mut buffer = ByteBuffer::new();
759
760        {
761            let mut cursor = Cursor::new(&mut buffer);
762
763            // skip header, will be writing it later
764            cursor
765                .seek(SeekFrom::Start(std::mem::size_of::<LgbHeader>() as u64))
766                .unwrap();
767
768            // base offset for deferred data
769            let mut data_base = cursor.stream_position().unwrap();
770
771            let mut chunk_data_heap = StringHeap {
772                pos: data_base + 4,
773                bytes: Vec::new(),
774                free_pos: data_base + 4,
775            };
776
777            let mut chunk_string_heap = StringHeap {
778                pos: data_base + 4,
779                bytes: Vec::new(),
780                free_pos: data_base + 4,
781            };
782
783            // we will write this later, when we have a working string heap
784            let layer_chunk_header_pos = cursor.stream_position().unwrap();
785            cursor
786                .seek(SeekFrom::Current(LAYER_CHUNK_HEADER_SIZE as i64))
787                .unwrap();
788
789            // skip offsets for now, they will be written later
790            let offset_pos = cursor.position();
791            cursor
792                .seek(SeekFrom::Current(
793                    (std::mem::size_of::<i32>() * self.chunks[0].layers.len()) as i64,
794                ))
795                .ok()?;
796
797            let mut offsets: Vec<i32> = Vec::new();
798
799            let layer_data_offset = cursor.position();
800
801            // first pass: write layers, we want to get a correct *chunk_data_heap*
802            for layer in &self.chunks[0].layers {
803                // set offset
804                // this is also used to reference positions inside this layer
805                let layer_offset = cursor.position() as i32;
806                offsets.push(layer_offset);
807
808                layer
809                    .header
810                    .write_le_args(&mut cursor, (&mut chunk_data_heap, &mut chunk_string_heap))
811                    .ok()?;
812            }
813
814            // make sure the heaps are at the end of the layer data
815            data_base += cursor.stream_position().unwrap() - layer_data_offset;
816
817            // 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
818            chunk_string_heap = StringHeap {
819                pos: data_base + 4 + chunk_data_heap.bytes.len() as u64,
820                bytes: Vec::new(),
821                free_pos: data_base + 4 + chunk_data_heap.bytes.len() as u64,
822            };
823            chunk_data_heap = StringHeap {
824                pos: data_base + 4,
825                bytes: Vec::new(),
826                free_pos: data_base + 4,
827            };
828
829            // write header now, because it has a string
830            cursor
831                .seek(SeekFrom::Start(layer_chunk_header_pos))
832                .unwrap();
833            // TODO: support multiple layer chunks
834            let layer_chunk = LayerChunkHeader {
835                chunk_id: self.chunks[0].chunk_id,
836                chunk_size: 24, // double lol
837                layer_group_id: self.chunks[0].layer_group_id,
838                name: HeapString {
839                    value: self.chunks[0].name.clone(),
840                },
841                layer_offset: 16, // lol
842                layer_count: self.chunks[0].layers.len() as i32,
843            };
844            layer_chunk
845                .write_le_args(&mut cursor, (&mut chunk_string_heap,))
846                .ok()?;
847
848            // now write the layer data for the final time
849            cursor.seek(SeekFrom::Start(layer_data_offset)).unwrap();
850            for layer in &self.chunks[0].layers {
851                layer
852                    .header
853                    .write_le_args(&mut cursor, (&mut chunk_data_heap, &mut chunk_string_heap))
854                    .ok()?;
855            }
856
857            // write the heaps
858            chunk_data_heap.write_le(&mut cursor).ok()?;
859            chunk_string_heap.write_le(&mut cursor).ok()?;
860
861            // write offsets
862            assert_eq!(offsets.len(), self.chunks[0].layers.len());
863            cursor.seek(SeekFrom::Start(offset_pos)).ok()?;
864            for offset in offsets {
865                offset.write_le(&mut cursor).ok()?;
866            }
867        }
868
869        let file_size = buffer.len() as i32;
870
871        {
872            let mut cursor = Cursor::new(&mut buffer);
873
874            // write the header, now that we now the file size
875            cursor.seek(SeekFrom::Start(0)).ok()?;
876            let lgb_header = LgbHeader {
877                file_id: self.file_id,
878                file_size,
879                total_chunk_count: self.chunks.len() as i32,
880            };
881            lgb_header.write_le(&mut cursor).ok()?;
882        }
883
884        Some(buffer)
885    }
886}
887
888#[cfg(test)]
889mod tests {
890    use std::fs::read;
891    use std::path::PathBuf;
892
893    use super::*;
894
895    #[test]
896    fn test_invalid() {
897        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
898        d.push("resources/tests");
899        d.push("random");
900
901        // Feeding it invalid data should not panic
902        LayerGroup::from_existing(&read(d).unwrap());
903    }
904
905    #[test]
906    fn read_empty_planlive() {
907        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
908        d.push("resources/tests");
909        d.push("empty_planlive.lgb");
910
911        let lgb = LayerGroup::from_existing(&read(d).unwrap()).unwrap();
912        assert_eq!(lgb.file_id, LGB1_ID);
913        assert_eq!(lgb.chunks.len(), 1);
914
915        let chunk = &lgb.chunks[0];
916        assert_eq!(chunk.chunk_id, LGP1_ID);
917        assert_eq!(chunk.layer_group_id, 261);
918        assert_eq!(chunk.name, "PlanLive".to_string());
919        assert!(chunk.layers.is_empty());
920    }
921
922    #[test]
923    fn write_empty_planlive() {
924        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
925        d.push("resources/tests");
926        d.push("empty_planlive.lgb");
927
928        let good_lgb_bytes = read(d).unwrap();
929
930        let lgb = LayerGroup {
931            file_id: LGB1_ID,
932            chunks: vec![LayerChunk {
933                chunk_id: LGP1_ID,
934                layer_group_id: 261,
935                name: "PlanLive".to_string(),
936                layers: Vec::new(),
937            }],
938        };
939        assert_eq!(lgb.write_to_buffer().unwrap(), good_lgb_bytes);
940    }
941}