1use 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
66const LGB1_ID: u32 = u32::from_le_bytes(*b"LGB1");
71const 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}
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#[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)] 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)] struct 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)] struct 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)] struct 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)] struct 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)] struct LgbHeader {
588 file_id: u32,
590 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)] struct 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)] pub 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 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 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 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 {
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 {
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 {
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 cursor
762 .seek(SeekFrom::Start(std::mem::size_of::<LgbHeader>() as u64))
763 .unwrap();
764
765 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 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 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 for layer in &self.chunks[0].layers {
800 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 data_base += cursor.stream_position().unwrap() - layer_data_offset;
813
814 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 cursor
828 .seek(SeekFrom::Start(layer_chunk_header_pos))
829 .unwrap();
830 let layer_chunk = LayerChunkHeader {
832 chunk_id: self.chunks[0].chunk_id,
833 chunk_size: 24, layer_group_id: self.chunks[0].layer_group_id,
835 name: HeapString {
836 value: self.chunks[0].name.clone(),
837 },
838 layer_offset: 16, 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 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 chunk_data_heap.write_le(&mut cursor).ok()?;
856 chunk_string_heap.write_le(&mut cursor).ok()?;
857
858 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 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 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}