use std::io::{Cursor, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt, BinWrite, binrw};
use crate::{
ByteBuffer, ByteSpan, ReadableFile, WritableFile,
common::Platform,
layer::Layer,
string_heap::{HeapPointer, HeapString, StringHeap},
};
#[binrw]
#[derive(Debug)]
#[allow(dead_code)] #[brw(magic = b"LGB1")]
struct LgbHeader {
file_size: i32,
total_chunk_count: i32,
}
impl LgbHeader {
pub(crate) const SIZE: usize = 0x0C;
}
#[binrw]
#[derive(Debug)]
#[br(import(string_heap: &StringHeap), stream = r)]
#[bw(import(string_heap: &mut StringHeap))]
#[allow(dead_code)] #[brw(magic = b"LGP1")]
struct LayerChunkHeader {
chunk_size: i32,
#[br(temp)]
#[bw(ignore)]
heap_pointer: HeapPointer,
layer_group_id: i32,
#[br(args(heap_pointer, string_heap))]
#[bw(args(string_heap))]
pub name: HeapString,
layer_offset: i32,
layer_count: i32,
}
impl LayerChunkHeader {
const SIZE: usize = 24;
}
#[derive(Debug)]
pub struct LayerChunk {
pub layer_group_id: i32,
pub name: String,
pub layers: Vec<Layer>,
}
#[derive(Debug)]
pub struct Lgb {
pub chunks: Vec<LayerChunk>,
}
impl ReadableFile for Lgb {
fn from_existing(platform: Platform, buffer: ByteSpan) -> Option<Self> {
let mut cursor = Cursor::new(buffer);
let endianness = platform.endianness();
let string_heap = StringHeap::from(cursor.position() as i64);
let data_heap = StringHeap::from(cursor.position() as i64);
let file_header = LgbHeader::read_options(&mut cursor, endianness, ()).ok()?;
if file_header.file_size <= 0 || file_header.total_chunk_count <= 0 {
return None;
}
loop {
let magic = <[u8; 4]>::read_options(&mut cursor, endianness, ()).ok()?;
if &magic == b"LGP1" {
cursor.seek(SeekFrom::Current(-4)).ok()?;
break;
}
}
let chunk_header =
LayerChunkHeader::read_options(&mut cursor, endianness, (&string_heap,)).unwrap();
if chunk_header.chunk_size <= 0 {
return Some(Lgb { chunks: Vec::new() });
}
let old_pos = cursor.position();
let mut layer_offsets = vec![0i32; chunk_header.layer_count as usize];
for i in 0..chunk_header.layer_count {
layer_offsets[i as usize] = cursor.read_type_args::<i32>(endianness, ()).ok()?;
}
let mut layers = Vec::new();
for i in 0..chunk_header.layer_count {
cursor
.seek(SeekFrom::Start(old_pos + layer_offsets[i as usize] as u64))
.unwrap();
let layer = Layer::read(endianness, &mut cursor, &data_heap, &string_heap)?;
layers.push(layer);
}
let layer_chunk = LayerChunk {
layer_group_id: chunk_header.layer_group_id,
name: chunk_header.name.value,
layers,
};
Some(Lgb {
chunks: vec![layer_chunk],
})
}
}
impl WritableFile for Lgb {
fn write_to_buffer(&self, _platform: Platform) -> Option<ByteBuffer> {
let mut buffer = ByteBuffer::new();
{
let mut cursor = Cursor::new(&mut buffer);
cursor
.seek(SeekFrom::Start(LgbHeader::SIZE as u64))
.unwrap();
let mut data_base = cursor.stream_position().unwrap();
let mut chunk_data_heap = StringHeap {
pos: data_base as i64 + 4,
bytes: Vec::new(),
free_pos: data_base + 4,
};
let mut chunk_string_heap = StringHeap {
pos: data_base as i64 + 4,
bytes: Vec::new(),
free_pos: data_base + 4,
};
let layer_chunk_header_pos = cursor.stream_position().unwrap();
cursor
.seek(SeekFrom::Current(LayerChunkHeader::SIZE as i64))
.unwrap();
let offset_pos = cursor.position();
cursor
.seek(SeekFrom::Current(
(std::mem::size_of::<i32>() * self.chunks[0].layers.len()) as i64,
))
.ok()?;
let mut offsets: Vec<i32> = Vec::new();
let layer_data_offset = cursor.position();
for layer in &self.chunks[0].layers {
let layer_offset = cursor.position() as i32;
offsets.push(layer_offset);
layer
.header
.write_le_args(&mut cursor, (&mut chunk_data_heap, &mut chunk_string_heap))
.ok()?;
for obj in &layer.objects {
obj.write_le_args(&mut cursor, (&mut chunk_string_heap,))
.ok()?;
}
}
data_base += cursor.stream_position().unwrap() - layer_data_offset
+ (offsets.len() * std::mem::size_of::<u32>()) as u64;
chunk_string_heap = StringHeap {
pos: data_base as i64 + 4 + chunk_data_heap.bytes.len() as i64,
bytes: Vec::new(),
free_pos: data_base + 4 + chunk_data_heap.bytes.len() as u64,
};
chunk_data_heap = StringHeap {
pos: data_base as i64 + 4,
bytes: Vec::new(),
free_pos: data_base + 4,
};
cursor
.seek(SeekFrom::Start(layer_chunk_header_pos))
.unwrap();
let layer_chunk = LayerChunkHeader {
chunk_size: 24, layer_group_id: self.chunks[0].layer_group_id,
name: HeapString {
value: self.chunks[0].name.clone(),
},
layer_offset: 16, layer_count: self.chunks[0].layers.len() as i32,
};
layer_chunk
.write_le_args(&mut cursor, (&mut chunk_string_heap,))
.ok()?;
cursor.seek(SeekFrom::Start(layer_data_offset)).unwrap();
for layer in &self.chunks[0].layers {
chunk_data_heap.free_pos = layer_data_offset + 12; chunk_string_heap.free_pos =
chunk_data_heap.free_pos + chunk_string_heap.bytes.len() as u64 + 16;
layer
.header
.write_le_args(&mut cursor, (&mut chunk_data_heap, &mut chunk_string_heap))
.ok()?;
for obj in &layer.objects {
obj.write_le_args(&mut cursor, (&mut chunk_string_heap,))
.ok()?;
}
}
chunk_data_heap.write_le(&mut cursor).ok()?;
chunk_string_heap.write_le(&mut cursor).ok()?;
assert_eq!(offsets.len(), self.chunks[0].layers.len());
cursor.seek(SeekFrom::Start(offset_pos)).ok()?;
for offset in offsets {
(offset - offset_pos as i32).write_le(&mut cursor).ok()?;
}
}
let file_size = buffer.len() as i32;
{
let mut cursor = Cursor::new(&mut buffer);
cursor.seek(SeekFrom::Start(0)).ok()?;
let lgb_header = LgbHeader {
file_size,
total_chunk_count: self.chunks.len() as i32,
};
lgb_header.write_le(&mut cursor).ok()?;
}
Some(buffer)
}
}
#[cfg(test)]
mod tests {
use std::fs::read;
use std::path::PathBuf;
use crate::layer::{
LayerHeader, LayerSetReferenced, LayerSetReferencedList, LayerSetReferencedType,
};
use crate::pass_random_invalid;
use crate::string_heap::HeapString;
use super::*;
#[test]
fn test_invalid() {
pass_random_invalid::<Lgb>();
}
#[test]
fn read_empty_planlive() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests");
d.push("empty_planlive.lgb");
let lgb = Lgb::from_existing(Platform::Win32, &read(d).unwrap()).unwrap();
assert_eq!(lgb.chunks.len(), 1);
let chunk = &lgb.chunks[0];
assert_eq!(chunk.layer_group_id, 261);
assert_eq!(chunk.name, "PlanLive".to_string());
assert!(chunk.layers.is_empty());
}
#[test]
fn read_padded_planlive() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests");
d.push("padded_planner.lgb");
assert!(Lgb::from_existing(Platform::Win32, &read(d).unwrap()).is_none());
}
#[test]
fn write_empty_planlive() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests");
d.push("empty_planlive.lgb");
let good_lgb_bytes = read(d).unwrap();
let lgb = Lgb {
chunks: vec![LayerChunk {
layer_group_id: 261,
name: "PlanLive".to_string(),
layers: Vec::new(),
}],
};
assert_eq!(
lgb.write_to_buffer(Platform::Win32).unwrap(),
good_lgb_bytes
);
}
#[test]
fn read_simple_planevent() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests");
d.push("simple_planevent.lgb");
let lgb = Lgb::from_existing(Platform::Win32, &read(d).unwrap()).unwrap();
assert_eq!(lgb.chunks.len(), 1);
let chunk = &lgb.chunks[0];
assert_eq!(chunk.layer_group_id, 260);
assert_eq!(chunk.name, "PlanEvent".to_string());
assert_eq!(
chunk.layers,
vec![Layer {
header: LayerHeader {
layer_id: 133894,
name: HeapString {
value: "QST_StmBdr102".to_string()
},
instance_object_offset: 52,
instance_object_count: 0,
tool_mode_visible: true,
tool_mode_read_only: false,
is_bush_layer: false,
ps3_visible: true,
layer_set_referenced_list: LayerSetReferencedList {
referenced_type: LayerSetReferencedType::Include,
layer_sets: vec![LayerSetReferenced {
layer_set_id: 132261
}]
},
festival_id: 0,
festival_phase_id: 0,
is_temporary: false,
is_housing: false,
version_mask: 47,
ob_set_referenced_list_offset: 68,
ob_set_referenced_list_count: 0,
object_set_referenced: vec![],
ob_set_enable_referenced_list_offset: 68,
ob_set_enable_referenced_list_count: 0,
object_set_enable_referenced: vec![],
},
objects: vec![]
}]
);
}
#[test]
fn write_simple_planevent() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests");
d.push("simple_planevent.lgb");
let good_lgb_bytes = read(d).unwrap();
let lgb = Lgb {
chunks: vec![LayerChunk {
layer_group_id: 260,
name: "PlanEvent".to_string(),
layers: vec![Layer {
header: LayerHeader {
layer_id: 133894,
name: HeapString {
value: "QST_StmBdr102".to_string(),
},
instance_object_offset: 52,
instance_object_count: 0,
tool_mode_visible: true,
tool_mode_read_only: false,
is_bush_layer: false,
ps3_visible: true,
layer_set_referenced_list: LayerSetReferencedList {
referenced_type: LayerSetReferencedType::Include,
layer_sets: vec![LayerSetReferenced {
layer_set_id: 132261,
}],
},
festival_id: 0,
festival_phase_id: 0,
is_temporary: false,
is_housing: false,
version_mask: 47,
ob_set_referenced_list_offset: 68,
ob_set_referenced_list_count: 0,
object_set_referenced: vec![],
ob_set_enable_referenced_list_offset: 68,
ob_set_enable_referenced_list_count: 0,
object_set_enable_referenced: vec![],
},
objects: vec![],
}],
}],
};
assert_eq!(
lgb.write_to_buffer(Platform::Win32).unwrap(),
good_lgb_bytes
);
}
}