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))]
306pub enum LayerEntryData {
307    #[br(pre_assert(*magic == LayerEntryType::BG))]
308    BG(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
491struct 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))]
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(parse_with = string_from_offset, args(start))]
622    pub name: String,
623    pub transform: Transformation,
624    #[br(args(&asset_type))]
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
710                    objects.push(InstanceObject::read_le_args(&mut cursor, (start,)).unwrap());
711                }
712            }
713
714            // read ob set referenced
715            {
716                cursor
717                    .seek(SeekFrom::Start(
718                        old_pos + header.ob_set_referenced_list as u64,
719                    ))
720                    .unwrap();
721                for _ in 0..header.ob_set_referenced_list_count {
722                    OBSetReferenced::read(&mut cursor).unwrap();
723                }
724            }
725
726            // read ob set enable referenced list
727            {
728                cursor
729                    .seek(SeekFrom::Start(
730                        old_pos + header.ob_set_enable_referenced_list as u64,
731                    ))
732                    .unwrap();
733                for _ in 0..header.ob_set_enable_referenced_list_count {
734                    OBSetEnableReferenced::read(&mut cursor).unwrap();
735                }
736            }
737
738            layers.push(Layer { header, objects });
739        }
740
741        let layer_chunk = LayerChunk {
742            chunk_id: chunk_header.chunk_id,
743            layer_group_id: chunk_header.layer_group_id,
744            name: chunk_header.name.value,
745            layers,
746        };
747
748        Some(LayerGroup {
749            file_id: file_header.file_id,
750            chunks: vec![layer_chunk],
751        })
752    }
753
754    pub fn write_to_buffer(&self) -> Option<ByteBuffer> {
755        let mut buffer = ByteBuffer::new();
756
757        {
758            let mut cursor = Cursor::new(&mut buffer);
759
760            // skip header, will be writing it later
761            cursor
762                .seek(SeekFrom::Start(std::mem::size_of::<LgbHeader>() as u64))
763                .unwrap();
764
765            // base offset for deferred data
766            let mut data_base = cursor.stream_position().unwrap();
767
768            let mut chunk_data_heap = StringHeap {
769                pos: data_base + 4,
770                bytes: Vec::new(),
771                free_pos: data_base + 4,
772            };
773
774            let mut chunk_string_heap = StringHeap {
775                pos: data_base + 4,
776                bytes: Vec::new(),
777                free_pos: data_base + 4,
778            };
779
780            // we will write this later, when we have a working string heap
781            let layer_chunk_header_pos = cursor.stream_position().unwrap();
782            cursor
783                .seek(SeekFrom::Current(LAYER_CHUNK_HEADER_SIZE as i64))
784                .unwrap();
785
786            // skip offsets for now, they will be written later
787            let offset_pos = cursor.position();
788            cursor
789                .seek(SeekFrom::Current(
790                    (std::mem::size_of::<i32>() * self.chunks[0].layers.len()) as i64,
791                ))
792                .ok()?;
793
794            let mut offsets: Vec<i32> = Vec::new();
795
796            let layer_data_offset = cursor.position();
797
798            // first pass: write layers, we want to get a correct *chunk_data_heap*
799            for layer in &self.chunks[0].layers {
800                // set offset
801                // this is also used to reference positions inside this layer
802                let layer_offset = cursor.position() as i32;
803                offsets.push(layer_offset);
804
805                layer
806                    .header
807                    .write_le_args(&mut cursor, (&mut chunk_data_heap, &mut chunk_string_heap))
808                    .ok()?;
809            }
810
811            // make sure the heaps are at the end of the layer data
812            data_base += cursor.stream_position().unwrap() - layer_data_offset;
813
814            // 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
815            chunk_string_heap = StringHeap {
816                pos: data_base + 4 + chunk_data_heap.bytes.len() as u64,
817                bytes: Vec::new(),
818                free_pos: data_base + 4 + chunk_data_heap.bytes.len() as u64,
819            };
820            chunk_data_heap = StringHeap {
821                pos: data_base + 4,
822                bytes: Vec::new(),
823                free_pos: data_base + 4,
824            };
825
826            // write header now, because it has a string
827            cursor
828                .seek(SeekFrom::Start(layer_chunk_header_pos))
829                .unwrap();
830            // TODO: support multiple layer chunks
831            let layer_chunk = LayerChunkHeader {
832                chunk_id: self.chunks[0].chunk_id,
833                chunk_size: 24, // double lol
834                layer_group_id: self.chunks[0].layer_group_id,
835                name: HeapString {
836                    value: self.chunks[0].name.clone(),
837                },
838                layer_offset: 16, // lol
839                layer_count: self.chunks[0].layers.len() as i32,
840            };
841            layer_chunk
842                .write_le_args(&mut cursor, (&mut chunk_string_heap,))
843                .ok()?;
844
845            // now write the layer data for the final time
846            cursor.seek(SeekFrom::Start(layer_data_offset)).unwrap();
847            for layer in &self.chunks[0].layers {
848                layer
849                    .header
850                    .write_le_args(&mut cursor, (&mut chunk_data_heap, &mut chunk_string_heap))
851                    .ok()?;
852            }
853
854            // write the heaps
855            chunk_data_heap.write_le(&mut cursor).ok()?;
856            chunk_string_heap.write_le(&mut cursor).ok()?;
857
858            // write offsets
859            assert_eq!(offsets.len(), self.chunks[0].layers.len());
860            cursor.seek(SeekFrom::Start(offset_pos)).ok()?;
861            for offset in offsets {
862                offset.write_le(&mut cursor).ok()?;
863            }
864        }
865
866        let file_size = buffer.len() as i32;
867
868        {
869            let mut cursor = Cursor::new(&mut buffer);
870
871            // write the header, now that we now the file size
872            cursor.seek(SeekFrom::Start(0)).ok()?;
873            let lgb_header = LgbHeader {
874                file_id: self.file_id,
875                file_size,
876                total_chunk_count: self.chunks.len() as i32,
877            };
878            lgb_header.write_le(&mut cursor).ok()?;
879        }
880
881        Some(buffer)
882    }
883}
884
885#[cfg(test)]
886mod tests {
887    use std::fs::read;
888    use std::path::PathBuf;
889
890    use super::*;
891
892    #[test]
893    fn test_invalid() {
894        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
895        d.push("resources/tests");
896        d.push("random");
897
898        // Feeding it invalid data should not panic
899        LayerGroup::from_existing(&read(d).unwrap());
900    }
901
902    #[test]
903    fn read_empty_planlive() {
904        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
905        d.push("resources/tests");
906        d.push("empty_planlive.lgb");
907
908        let lgb = LayerGroup::from_existing(&read(d).unwrap()).unwrap();
909        assert_eq!(lgb.file_id, LGB1_ID);
910        assert_eq!(lgb.chunks.len(), 1);
911
912        let chunk = &lgb.chunks[0];
913        assert_eq!(chunk.chunk_id, LGP1_ID);
914        assert_eq!(chunk.layer_group_id, 261);
915        assert_eq!(chunk.name, "PlanLive".to_string());
916        assert!(chunk.layers.is_empty());
917    }
918
919    #[test]
920    fn write_empty_planlive() {
921        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
922        d.push("resources/tests");
923        d.push("empty_planlive.lgb");
924
925        let good_lgb_bytes = read(d).unwrap();
926
927        let lgb = LayerGroup {
928            file_id: LGB1_ID,
929            chunks: vec![LayerChunk {
930                chunk_id: LGP1_ID,
931                layer_group_id: 261,
932                name: "PlanLive".to_string(),
933                layers: Vec::new(),
934            }],
935        };
936        assert_eq!(lgb.write_to_buffer().unwrap(), good_lgb_bytes);
937    }
938}