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