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 Unk4 = 83, Unk2 = 86, Unk3 = 89, }
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)] 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)] 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)] pub gathering_point_id: u32,
404}
405
406#[binread]
407#[derive(Debug)]
408#[br(little)]
409pub struct TreasureInstanceObject {
410 #[brw(pad_after = 11)] pub nonpop_init_zone: u8,
412}
413
414#[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)] pub 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)] pub 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)] struct 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)] struct 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)] struct LgbHeader {
597 file_id: u32,
599 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)] struct 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)] pub 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 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 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 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 {
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 {
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 {
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 cursor
774 .seek(SeekFrom::Start(std::mem::size_of::<LgbHeader>() as u64))
775 .unwrap();
776
777 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 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 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 for layer in &self.chunks[0].layers {
812 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 data_base += cursor.stream_position().unwrap() - layer_data_offset;
825
826 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 cursor
840 .seek(SeekFrom::Start(layer_chunk_header_pos))
841 .unwrap();
842 let layer_chunk = LayerChunkHeader {
844 chunk_id: self.chunks[0].chunk_id,
845 chunk_size: 24, layer_group_id: self.chunks[0].layer_group_id,
847 name: HeapString {
848 value: self.chunks[0].name.clone(),
849 },
850 layer_offset: 16, 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 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 chunk_data_heap.write_le(&mut cursor).ok()?;
868 chunk_string_heap.write_le(&mut cursor).ok()?;
869
870 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 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 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}