1#![allow(unused_variables)] use 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
66pub const LGB1_ID: u32 = u32::from_le_bytes(*b"LGB1");
71pub const LGP1_ID: u32 = u32::from_le_bytes(*b"LGP1");
73
74#[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 #[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, }
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 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 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#[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 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, Unk3 = 89, }
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)] 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)] 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)] pub gathering_point_id: u32,
399}
400
401#[binread]
402#[derive(Debug)]
403#[br(little)]
404pub struct TreasureInstanceObject {
405 #[brw(pad_after = 11)] pub nonpop_init_zone: u8,
407}
408
409#[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)] pub 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)] pub 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)] struct 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)] struct 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)] struct LgbHeader {
592 file_id: u32,
594 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)] struct 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)] pub 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 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 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 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 {
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 {
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 {
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 cursor
769 .seek(SeekFrom::Start(std::mem::size_of::<LgbHeader>() as u64))
770 .unwrap();
771
772 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 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 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 for layer in &self.chunks[0].layers {
807 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 data_base += cursor.stream_position().unwrap() - layer_data_offset;
820
821 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 cursor
835 .seek(SeekFrom::Start(layer_chunk_header_pos))
836 .unwrap();
837 let layer_chunk = LayerChunkHeader {
839 chunk_id: self.chunks[0].chunk_id,
840 chunk_size: 24, layer_group_id: self.chunks[0].layer_group_id,
842 name: HeapString {
843 value: self.chunks[0].name.clone(),
844 },
845 layer_offset: 16, 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 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 chunk_data_heap.write_le(&mut cursor).ok()?;
863 chunk_string_heap.write_le(&mut cursor).ok()?;
864
865 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 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 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}