1
Fork 0
mirror of https://github.com/redstrate/Physis.git synced 2025-07-20 07:47:45 +00:00

Begin parsing LVB files

These are actually quite useful, and contains stuff like the LGBs
used for the territory.
This commit is contained in:
Joshua Goins 2025-07-04 11:11:05 -04:00
parent b04121dfba
commit a8cb676e97
3 changed files with 224 additions and 0 deletions

View file

@ -54,6 +54,18 @@ pub(crate) fn strings_parser(
Ok(strings) Ok(strings)
} }
#[binrw::parser(reader)]
pub(crate) fn read_string_until_null() -> BinResult<String> {
let mut string = String::new();
let mut next_char = reader.read_le::<u8>().unwrap() as char;
while next_char != '\0' {
string.push(next_char);
next_char = reader.read_le::<u8>().unwrap() as char;
}
Ok(string)
}
fn read_half1(data: [u16; 1]) -> Half1 { fn read_half1(data: [u16; 1]) -> Half1 {
Half1 { Half1 {
value: f16::from_bits(data[0]), value: f16::from_bits(data[0]),

View file

@ -143,6 +143,9 @@ pub mod uwb;
/// Reading LCB files /// Reading LCB files
pub mod lcb; pub mod lcb;
/// Reading LVB files
pub mod lvb;
mod bcn; mod bcn;
mod error; mod error;

209
src/lvb.rs Normal file
View file

@ -0,0 +1,209 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
use std::io::Cursor;
use std::io::SeekFrom;
use crate::ByteSpan;
use crate::common_file_operations::read_bool_from;
use crate::common_file_operations::read_string_until_null;
use binrw::BinRead;
use binrw::BinReaderExt;
use binrw::BinResult;
use binrw::binread;
#[binread]
#[derive(Debug)]
#[brw(little)]
#[brw(magic = b"LVB1")]
pub struct Lvb {
/// Including this header
pub file_size: u32,
/// Number of Scn's
#[br(temp)]
#[bw(calc = scns.len() as u32)]
scn_count: u32,
#[br(count = scn_count)]
pub scns: Vec<Scn>,
}
#[binread]
#[derive(Debug)]
#[brw(little)]
#[brw(magic = b"SCN1")]
pub struct Scn {
total_size: u32,
pub header: ScnHeader,
#[br(seek_before = SeekFrom::Current(header.offset_general as i64 - ScnHeader::SIZE as i64))]
#[br(restore_position)]
pub general: ScnGeneralSection,
#[br(seek_before = SeekFrom::Current(header.offset_unk1 as i64 - ScnHeader::SIZE as i64))]
#[br(restore_position)]
pub unk1: ScnUnknown1Section,
#[br(seek_before = SeekFrom::Current(header.offset_unk2 as i64 - ScnHeader::SIZE as i64))]
#[br(restore_position)]
pub unk2: ScnUnknown2Section,
}
#[binrw::parser(reader)]
pub(crate) fn strings_from_offsets(offsets: &Vec<i32>) -> BinResult<Vec<String>> {
let base_offset = reader.stream_position()?;
let mut strings: Vec<String> = vec![];
for offset in offsets {
let string_offset = *offset as u64;
let mut string = String::new();
reader.seek(SeekFrom::Start(base_offset + string_offset))?;
let mut next_char = reader.read_le::<u8>().unwrap() as char;
while next_char != '\0' {
string.push(next_char);
next_char = reader.read_le::<u8>().unwrap() as char;
}
strings.push(string);
}
Ok(strings)
}
#[binread]
#[derive(Debug)]
#[brw(little)]
pub struct ScnHeader {
/// offset to FileLayerGroupHeader[NumEmbeddedLayerGroups]
offset_embedded_layer_groups: i32,
num_embedded_layer_groups: i32,
/// offset to FileSceneGeneral
offset_general: i32,
/// offset to FileSceneFilterList
offset_filters: i32,
offset_unk1: i32,
/// offset to a list of path offsets (ints)
offset_layer_group_resources: i32,
num_layer_group_resources: i32,
unk2: i32,
offset_unk2: i32,
unk4: i32,
unk5: i32,
unk6: i32,
unk7: i32,
unk8: i32,
unk9: i32,
unk10: i32,
#[br(count = num_layer_group_resources)]
#[br(seek_before = SeekFrom::Current(offset_layer_group_resources as i64 - ScnHeader::SIZE as i64))]
#[br(restore_position)]
offset_path_layer_group_resources: Vec<i32>,
#[br(parse_with = strings_from_offsets)]
#[br(args(&offset_path_layer_group_resources))]
#[br(restore_position)]
#[br(seek_before = SeekFrom::Current(offset_layer_group_resources as i64 - ScnHeader::SIZE as i64))]
pub path_layer_group_resources: Vec<String>,
}
impl ScnHeader {
pub const SIZE: usize = 0x40;
}
#[binread]
#[derive(Debug)]
#[br(little)]
pub struct ScnGeneralSection {
#[br(map = read_bool_from::<i32>)]
pub have_layer_groups: bool,
offset_path_terrain: i32,
offset_env_spaces: i32,
num_env_spaces: i32,
unk1: i32,
offset_path_sky_visibility: i32,
unk2: i32,
unk3: i32,
unk4: i32,
unk5: i32,
unk6: i32,
unk7: i32,
unk8: i32,
offset_path_lcb: i32,
unk10: i32,
unk11: i32,
unk12: i32,
unk13: i32,
unk14: i32,
unk15: i32,
unk16: i32,
#[br(map = read_bool_from::<i32>)]
pub have_lcbuw: bool,
#[br(seek_before = SeekFrom::Current(offset_path_terrain as i64 - ScnGeneralSection::SIZE as i64))]
#[br(restore_position, parse_with = read_string_until_null)]
pub path_terrain: String,
#[br(seek_before = SeekFrom::Current(offset_path_sky_visibility as i64 - ScnGeneralSection::SIZE as i64))]
#[br(restore_position, parse_with = read_string_until_null)]
pub path_sky_visibility: String,
#[br(seek_before = SeekFrom::Current(offset_path_lcb as i64 - ScnGeneralSection::SIZE as i64))]
#[br(restore_position, parse_with = read_string_until_null)]
pub path_lcb: String,
}
impl ScnGeneralSection {
pub const SIZE: usize = 0x58;
}
#[binread]
#[derive(Debug)]
#[br(little)]
pub struct ScnUnknown1Section {
unk1: i32,
unk2: i32,
}
impl ScnUnknown1Section {
pub const SIZE: usize = 0x8;
}
// TODO: definitely not correct
#[binread]
#[derive(Debug)]
#[br(little)]
pub struct ScnUnknown2Section {
#[br(dbg)]
unk1: i32,
unk2: i32,
}
impl ScnUnknown2Section {
pub const SIZE: usize = 0x8;
}
impl Lvb {
/// Reads an existing UWB file
pub fn from_existing(buffer: ByteSpan) -> Option<Self> {
let mut cursor = Cursor::new(buffer);
Lvb::read(&mut cursor).ok()
}
}
#[cfg(test)]
mod tests {
use std::fs::read;
use std::path::PathBuf;
use super::*;
#[test]
fn test_invalid() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("resources/tests");
d.push("random");
// Feeding it invalid data should not panic
Lvb::from_existing(&read(d).unwrap());
}
}