From 256b3f9305a9bec152080fb3689b62edee7ec5e6 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Fri, 2 Feb 2024 14:20:53 -0500 Subject: [PATCH] Add basic support for LGB and TERA files This is for the Novus map editor, although TERA is much more complete than LGB right now. --- src/lgb.rs | 531 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 + src/tera.rs | 67 +++++++ 3 files changed, 604 insertions(+) create mode 100644 src/lgb.rs create mode 100644 src/tera.rs diff --git a/src/lgb.rs b/src/lgb.rs new file mode 100644 index 0000000..da049f6 --- /dev/null +++ b/src/lgb.rs @@ -0,0 +1,531 @@ +// SPDX-FileCopyrightText: 2024 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::io::{Cursor, Read, Seek, SeekFrom}; + +use binrw::{BinRead, binread, BinReaderExt}; +use binrw::binrw; +use crate::ByteSpan; + +// From https://github.com/NotAdam/Lumina/tree/40dab50183eb7ddc28344378baccc2d63ae71d35/src/Lumina/Data/Parsing/Layer + +// TODO: convert these all to magic +#[binrw] +#[repr(i32)] +#[derive(Debug, PartialEq)] +enum LayerEntryType +{ + #[brw(magic = 0x0i32)] + AssetNone, + #[brw(magic = 0x1i32)] + BG { + asset_path_offset: u32, + collision_asset_path_offset: i32, + collision_type: ModelCollisionType, + attribute_mask: u32, + attribute: u32, + collision_config: u32, + is_visible: u8, + render_shadow_enabled: u8, + render_light_shadow_enabled: u8, + padding: u8, + render_model_clip_range: f32 + }, + #[brw(magic = 0x2i32)] + Attribute, + #[brw(magic = 0x3i32)] + LayLight, + #[brw(magic = 0x4i32)] + VFX, + #[brw(magic = 0x5i32)] + PositionMarker, + #[brw(magic = 0x6i32)] + SharedGroup, + Sound = 0x7, // // + EventNPC = 0x8, // // + BattleNPC = 0x9, // // + RoutePath = 0xA, + Character = 0xB, + Aetheryte = 0xC, // // + EnvSet = 0xD, // // + Gathering = 0xE, // // + HelperObject = 0xF, // + Treasure = 0x10, // // + Clip = 0x11, + ClipCtrlPoint = 0x12, + ClipCamera = 0x13, + ClipLight = 0x14, + ClipReserve00 = 0x15, + ClipReserve01 = 0x16, + ClipReserve02 = 0x17, + ClipReserve03 = 0x18, + ClipReserve04 = 0x19, + ClipReserve05 = 0x1A, + ClipReserve06 = 0x1B, + ClipReserve07 = 0x1C, + ClipReserve08 = 0x1D, + ClipReserve09 = 0x1E, + ClipReserve10 = 0x1F, + ClipReserve11 = 0x20, + ClipReserve12 = 0x21, + ClipReserve13 = 0x22, + ClipReserve14 = 0x23, + CutAssetOnlySelectable = 0x24, + Player = 0x25, + Monster = 0x26, + Weapon = 0x27, // + PopRange = 0x28, // // + ExitRange = 0x29, // // + LVB = 0x2A, + MapRange = 0x2B, // // + NaviMeshRange = 0x2C, // // + EventObject = 0x2D, // // + DemiHuman = 0x2E, + EnvLocation = 0x2F, // // + ControlPoint = 0x30, + EventRange = 0x31, //? // + RestBonusRange = 0x32, + QuestMarker = 0x33, // // + Timeline = 0x34, + ObjectBehaviorSet = 0x35, + Movie = 0x36, + ScenarioExd = 0x37, + ScenarioText = 0x38, + CollisionBox = 0x39, // // + DoorRange = 0x3A, // + LineVFX = 0x3B, // // + SoundEnvSet = 0x3C, + CutActionTimeline = 0x3D, + CharaScene = 0x3E, + CutAction = 0x3F, + EquipPreset = 0x40, + ClientPath = 0x41, // // + ServerPath = 0x42, // // + GimmickRange = 0x43, // // + TargetMarker = 0x44, // // + ChairMarker = 0x45, // // + ClickableRange = 0x46, // + PrefetchRange = 0x47, // // + FateRange = 0x48, // // + PartyMember = 0x49, + KeepRange = 0x4A, // + SphereCastRange = 0x4B, + IndoorObject = 0x4C, + OutdoorObject = 0x4D, + EditGroup = 0x4E, + StableChocobo = 0x4F, + MaxAssetType = 0x50, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum DoorState +{ + Auto = 0x1, + Open = 0x2, + Closed = 0x3, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum RotationState +{ + Rounding = 0x1, + Stopped = 0x2, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum TransformState +{ + TransformStatePlay = 0x0, + TransformStateStop = 0x1, + TransformStateReplay = 0x2, + TransformStateReset = 0x3, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum ColourState +{ + ColorStatePlay = 0x0, + ColorStateStop = 0x1, + ColorStateReplay = 0x2, + ColorStateReset = 0x3, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum TriggerBoxShape +{ + TriggerBoxShapeBox = 0x1, + TriggerBoxShapeSphere = 0x2, + TriggerBoxShapeCylinder = 0x3, + TriggerBoxShapeBoard = 0x4, + TriggerBoxShapeMesh = 0x5, + TriggerBoxShapeBoardBothSides = 0x6, +} + +#[binrw] +#[brw(repr = i32)] +#[derive(Debug, PartialEq)] +enum ModelCollisionType +{ + None = 0x0, + Replace = 0x1, + Box = 0x2, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum LightType +{ + None = 0x0, + Directional = 0x1, + Point = 0x2, + Spot = 0x3, + Plane = 0x4, + Line = 0x5, + Specular = 0x6, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum PointLightType +{ + Sphere = 0x0, + Hemisphere = 0x1, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum PositionMarkerType +{ + DebugZonePop = 0x1, + DebugJump = 0x2, + NaviMesh = 0x3, + LQEvent = 0x4, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum EnvSetShape +{ + EnvShapeEllipsoid = 0x1, + EnvShapeCuboid = 0x2, + EnvShapeCylinder = 0x3, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum HelperObjectType +{ + ProxyActor = 0x0, + NullObject = 0x1, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum TargetType +{ + None = 0x0, + ENPCInstanceID = 0x1, + Player = 0x2, + PartyMember = 0x3, + ENPCDirect = 0x4, + BNPCDirect = 0x5, + BGObjInstanceID = 0x6, + SharedGroupInstanceID = 0x7, + BGObj = 0x8, + SharedGroup = 0x9, + Weapon = 0xA, + StableChocobo = 0xB, + AllianceMember = 0xC, + Max = 0xD, +} + +#[binread] +#[derive(Debug, PartialEq)] +enum PopType +{ + #[br(magic = 0x1u8)] + PC = 0x1, + #[br(magic = 0x2u8)] + NPC, + #[br(magic = 0x2u8)] + BNPC, + #[br(magic = 0x3u8)] + Content, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum ExitType +{ + ZoneLine = 0x1, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum RangeType +{ + Type01 = 0x1, + Type02 = 0x2, + Type03 = 0x3, + Type04 = 0x4, + Type05 = 0x5, + Type06 = 0x6, + Type07 = 0x7, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum LineStyle +{ + Red = 0x1, + Blue = 0x2, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum GimmickType +{ + Fishing = 0x1, + Content = 0x2, + Room = 0x3, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum TargetMarkerType +{ + UiTarget = 0x0, + UiNameplate = 0x1, + LookAt = 0x2, + BodyDyn = 0x3, + Root = 0x4, +} + +//For ChairMarker +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum ObjectType +{ + ObjectChair = 0x0, + ObjectBed = 0x1, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum CharacterSize +{ + DefaultSize = 0x0, + VerySmall = 0x1, + Small = 0x2, + Medium = 0x3, + Large = 0x4, + VeryLarge = 0x5, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum DrawHeadParts +{ + Default = 0x0, + ForceOn = 0x1, + ForceOff = 0x2, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum RotationType +{ + NoRotate = 0x0, + AllAxis = 0x1, + YAxisOnly = 0x2, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum MovePathMode +{ + None = 0x0, + SharedGroupAction = 0x1, + Timeline = 0x2, +} + +#[binrw] +#[brw(repr = i32)] +#[derive(Debug, PartialEq)] +enum LayerSetReferencedType +{ + All = 0x0, + Include = 0x1, + Exclude = 0x2, + Undetermined = 0x3, +} + +#[binrw] +#[brw(repr = u8)] +#[derive(Debug, PartialEq)] +enum SoundEffectType +{ + Point = 0x3, + PointDir = 0x4, + Line = 0x5, + PolyLine = 0x6, + Surface = 0x7, + BoardObstruction = 0x8, + BoxObstruction = 0x9, + PolyLineObstruction = 0xB, + PolygonObstruction = 0xC, + LineExtController = 0xD, + Polygon = 0xE, +} + +#[binread] +#[derive(Debug)] +#[br(little)] +struct LayerHeader { + layer_id: u32, + name_offset: u32, + + instance_object_offset: i32, + instance_object_count: i32, + + tool_mode_visible: u8, + tool_mode_read_only: u8, + + is_bush_layer: u8, + ps3_visible: u8, + layer_set_referenced_list_offset: i32, + festival_id: u16, + festival_phase_id: u16, + is_temporary: u8, + is_housing: u8, + version_mask: u16, + + #[br(temp)] + padding: u32, + + ob_set_referenced_list: i32, + ob_set_referenced_list_count: i32, + ob_set_enable_referenced_list: i32, + ob_set_enable_referenced_list_count: i32, +} + +#[binread] +#[derive(Debug)] +#[br(little)] +struct LayerSetReferencedList { + referenced_type: LayerSetReferencedType, + layer_sets: i32, + layer_set_count: i32 +} + +#[binread] +#[derive(Debug)] +#[br(little)] +struct LgbHeader { + #[br(count = 4)] + file_id: Vec, + file_size: i32, + total_chunk_count: i32 +} + +#[binread] +#[derive(Debug)] +#[br(little)] +struct LayerChunk { + #[br(count = 4)] + chunk_id: Vec, + chunk_size: i32, + layer_group_id: i32, + name_offset: u32, + layer_offset: i32, + layer_count: i32 +} + +#[binread] +#[derive(Debug)] +#[br(little)] +struct InstanceObject { + asset_type: LayerEntryType, + instance_id: u32, + name_offset: u32 +} + +#[derive(Debug)] +pub struct Layer { + +} + +impl Layer { + /// Reads an existing PBD file + pub fn from_existing(buffer: ByteSpan) -> Option { + let mut cursor = Cursor::new(buffer); + + let mut file_header = LgbHeader::read(&mut cursor).unwrap(); + + let mut chunk_header = LayerChunk::read(&mut cursor).unwrap(); + + 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_le::().unwrap(); + } + + for i in 0.. chunk_header.layer_count { + cursor.seek(SeekFrom::Start(old_pos + layer_offsets[i as usize] as u64)).unwrap(); + + let old_pos = cursor.position(); + + let mut header = LayerHeader::read(&mut cursor).unwrap(); + + println!("{:#?}", header); + + cursor.seek(SeekFrom::Start(old_pos + header.instance_object_offset as u64)).unwrap(); + + let mut instance_offsets = vec![0i32; header.instance_object_count as usize]; + for i in 0..header.instance_object_count { + instance_offsets[i as usize] = cursor.read_le::().unwrap(); + } + + cursor.seek(SeekFrom::Start(old_pos + header.layer_set_referenced_list_offset as u64)).unwrap(); + let mut referenced_list = LayerSetReferencedList::read(&mut cursor).unwrap(); + + for i in 0..header.instance_object_count { + cursor.seek(SeekFrom::Start(old_pos + header.instance_object_offset as u64 + instance_offsets[i as usize] as u64)).unwrap(); + + let instance_object = InstanceObject::read(&mut cursor).unwrap(); + println!("{:#?}", instance_object); + } + } + + Some(Layer { + //header + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index bea705f..71c30e0 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,5 +113,11 @@ mod model_file_operations; #[cfg(feature = "visual_data")] mod model_vertex_declarations; +#[cfg(feature = "visual_data")] +pub mod lgb; + +#[cfg(feature = "visual_data")] +pub mod tera; + #[doc(hidden)] pub const PHYSIS_VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/tera.rs b/src/tera.rs new file mode 100644 index 0000000..fc9e6ba --- /dev/null +++ b/src/tera.rs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2024 Joshua Goins +// SPDX-License-Identifier: GPL-3.0-or-later + +use std::io::{Cursor, Seek}; + +use binrw::BinRead; +use binrw::binrw; +use crate::ByteSpan; + +#[binrw] +#[derive(Debug, Clone, Copy)] +#[brw(little)] +struct PlatePosition { + x: i16, + y: i16 +} + +#[binrw] +#[derive(Debug)] +#[brw(little)] +struct TerrainHeader { + version: u32, + plate_count: u32, + plate_size: u32, + clip_distance: f32, + + unknown: f32, + + #[br(count = 32)] + padding: Vec, + + #[br(count = plate_count)] + positions: Vec +} + +#[derive(Debug)] +pub struct PlateModel { + pub position: (f32, f32), + pub filename: String +} + +#[derive(Debug)] +pub struct Terrain { + pub plates: Vec +} + +impl Terrain { + /// Reads an existing TERA file + pub fn from_existing(buffer: ByteSpan) -> Option { + let mut cursor = Cursor::new(buffer); + let header = TerrainHeader::read(&mut cursor).ok()?; + + let mut plates = vec![]; + + for i in 0..header.plate_count { + plates.push(PlateModel { + position: (header.plate_size as f32 * (header.positions[i as usize].x as f32 + 0.5), + header.plate_size as f32 * (header.positions[i as usize].y as f32 + 0.5)), + filename: format!("{:04}.mdl", i) + }) + } + + Some(Terrain { + plates + }) + } +}