mirror of
https://github.com/redstrate/Physis.git
synced 2025-04-19 17:36:50 +00:00
Improve layer group parsing (lgb files)
I guessed the name, it's more like a collection of layers. This actually fixes a bunch of mistakes (on my part) as I copied the layout from Lumina, but added a few changes to make it work with modern zones. There's a whole lot more work here to do, but now it works to parse New & Old Gridania.
This commit is contained in:
parent
4269d41179
commit
f8db40d74d
16 changed files with 1015 additions and 539 deletions
|
@ -54,6 +54,26 @@ pub(crate) fn strings_parser(
|
|||
Ok(strings)
|
||||
}
|
||||
|
||||
#[binrw::parser(reader)]
|
||||
pub(crate) fn string_from_offset(start: u64) -> BinResult<String> {
|
||||
let offset: u32 = reader.read_le::<u32>()?;
|
||||
|
||||
let mut string = String::new();
|
||||
|
||||
let old_pos = reader.stream_position()?;
|
||||
|
||||
reader.seek(SeekFrom::Start(start + offset as u64))?;
|
||||
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;
|
||||
}
|
||||
|
||||
reader.seek(SeekFrom::Start(old_pos))?;
|
||||
|
||||
Ok(string)
|
||||
}
|
||||
|
||||
fn read_half1(data: [u16; 1]) -> Half1 {
|
||||
Half1 {
|
||||
value: f16::from_bits(data[0]),
|
||||
|
|
12
src/layer/aetheryte.rs
Normal file
12
src/layer/aetheryte.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use binrw::binread;
|
||||
|
||||
use super::GameInstanceObject;
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct AetheryteInstanceObject {
|
||||
pub parent_data: GameInstanceObject,
|
||||
pub bound_instance_id: u32,
|
||||
padding: u32,
|
||||
}
|
32
src/layer/bg.rs
Normal file
32
src/layer/bg.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use binrw::{binread, binrw};
|
||||
|
||||
use super::read_bool_from;
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ModelCollisionType {
|
||||
None = 0x0,
|
||||
Replace = 0x1,
|
||||
Box = 0x2,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct BGInstanceObject {
|
||||
pub asset_path_string_offset: u32,
|
||||
pub collision_asset_path_string_offset: u32,
|
||||
pub collision_type: ModelCollisionType,
|
||||
pub attribute_mask: u32,
|
||||
pub attribute: u32,
|
||||
pub collision_config: i32,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub is_visible: bool,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub render_shadow_enabled: bool,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub render_light_shadow_enabeld: bool,
|
||||
padding: u8,
|
||||
pub render_model_clip_range: f32,
|
||||
}
|
40
src/layer/common.rs
Normal file
40
src/layer/common.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use binrw::binrw;
|
||||
|
||||
#[binrw]
|
||||
#[derive(Debug)]
|
||||
#[brw(little)]
|
||||
pub struct RelativePositions {
|
||||
pos: i32,
|
||||
pos_count: i32,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Debug)]
|
||||
#[brw(little)]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
pub struct Transformation {
|
||||
pub translation: [f32; 3],
|
||||
pub rotation: [f32; 3],
|
||||
pub scale: [f32; 3],
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Debug)]
|
||||
#[brw(little)]
|
||||
pub struct Color {
|
||||
pub red: u8,
|
||||
pub green: u8,
|
||||
pub blue: u8,
|
||||
pub alpha: u8,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Debug)]
|
||||
#[brw(little)]
|
||||
pub struct ColorHDRI {
|
||||
pub red: u8,
|
||||
pub green: u8,
|
||||
pub blue: u8,
|
||||
pub alpha: u8,
|
||||
pub intensity: f32,
|
||||
}
|
30
src/layer/env_set.rs
Normal file
30
src/layer/env_set.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use binrw::{binread, binrw};
|
||||
|
||||
use super::read_bool_from;
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum EnvSetShape {
|
||||
Ellipsoid = 0x1,
|
||||
Cuboid = 0x2,
|
||||
Cylinder = 0x3,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct EnvSetInstanceObject {
|
||||
pub asset_path_offset: u32,
|
||||
pub bound_instance_id: u32,
|
||||
pub shape: EnvSetShape,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub is_env_map_shooting_point: bool,
|
||||
pub priority: u8,
|
||||
padding: u16,
|
||||
pub effective_range: f32,
|
||||
pub interpolation_time: i32,
|
||||
pub reverb: f32,
|
||||
pub filter: f32,
|
||||
pub sound_asset_path_offset: u32,
|
||||
}
|
25
src/layer/exit_range.rs
Normal file
25
src/layer/exit_range.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use binrw::{binread, binrw};
|
||||
|
||||
use super::TriggerBoxInstanceObject;
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ExitType {
|
||||
ZoneLine = 0x1,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct ExitRangeInstanceObject {
|
||||
pub parent_data: TriggerBoxInstanceObject,
|
||||
pub exit_type: ExitType,
|
||||
pub zone_id: u16,
|
||||
pub territory_type: u16,
|
||||
pub index: i32,
|
||||
pub destination_instance_id: u32,
|
||||
pub return_instance_id: u32,
|
||||
pub player_running_direction: f32,
|
||||
padding: u32,
|
||||
}
|
54
src/layer/light.rs
Normal file
54
src/layer/light.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use binrw::{binread, binrw};
|
||||
|
||||
use super::{ColorHDRI, read_bool_from};
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum LightType {
|
||||
None = 0x0,
|
||||
Directional = 0x1,
|
||||
Point = 0x2,
|
||||
Spot = 0x3,
|
||||
Plane = 0x4,
|
||||
Line = 0x5,
|
||||
Specular = 0x6,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PointLightType {
|
||||
Sphere = 0x0,
|
||||
Hemisphere = 0x1,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct LightInstanceObject {
|
||||
pub light_type: LightType,
|
||||
pub attenuation: f32,
|
||||
pub range_rate: f32,
|
||||
pub point_light_type: PointLightType,
|
||||
pub attenuation_cone_coefficient: f32,
|
||||
pub cone_degree: f32,
|
||||
pub texture_path_offset: u32,
|
||||
pub diffuse_color_hdri: ColorHDRI,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub follows_directional_light: bool,
|
||||
padding1: u8,
|
||||
padding2: u16,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub specular_enabled: bool,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub bg_shadow_enabled: bool,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub character_shadow_enabled: bool,
|
||||
padding3: u8,
|
||||
pub shadow_clip_range: f32,
|
||||
pub plane_light_rotation_x: f32,
|
||||
pub plane_light_rotation_y: f32,
|
||||
pub merge_group_id: u16,
|
||||
padding4: u8,
|
||||
}
|
578
src/layer/mod.rs
Normal file
578
src/layer/mod.rs
Normal file
|
@ -0,0 +1,578 @@
|
|||
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
|
||||
use crate::ByteSpan;
|
||||
use crate::common_file_operations::{read_bool_from, string_from_offset};
|
||||
use binrw::binrw;
|
||||
use binrw::{BinRead, BinReaderExt, binread};
|
||||
|
||||
mod aetheryte;
|
||||
pub use aetheryte::AetheryteInstanceObject;
|
||||
|
||||
mod bg;
|
||||
pub use bg::BGInstanceObject;
|
||||
pub use bg::ModelCollisionType;
|
||||
|
||||
mod common;
|
||||
pub use common::Color;
|
||||
pub use common::ColorHDRI;
|
||||
pub use common::Transformation;
|
||||
|
||||
mod env_set;
|
||||
pub use env_set::EnvSetInstanceObject;
|
||||
pub use env_set::EnvSetShape;
|
||||
|
||||
mod exit_range;
|
||||
pub use exit_range::ExitRangeInstanceObject;
|
||||
pub use exit_range::ExitType;
|
||||
|
||||
mod light;
|
||||
pub use light::LightInstanceObject;
|
||||
pub use light::LightType;
|
||||
pub use light::PointLightType;
|
||||
|
||||
mod npc;
|
||||
pub use npc::BNPCInstanceObject;
|
||||
pub use npc::ENPCInstanceObject;
|
||||
pub use npc::GameInstanceObject;
|
||||
pub use npc::NPCInstanceObject;
|
||||
|
||||
mod pop;
|
||||
pub use pop::PopRangeInstanceObject;
|
||||
pub use pop::PopType;
|
||||
|
||||
mod position_marker;
|
||||
pub use position_marker::PositionMarkerInstanceObject;
|
||||
pub use position_marker::PositionMarkerType;
|
||||
|
||||
mod shared_group;
|
||||
pub use shared_group::ColourState;
|
||||
pub use shared_group::DoorState;
|
||||
pub use shared_group::RotationState;
|
||||
pub use shared_group::SharedGroupInstance;
|
||||
pub use shared_group::TransformState;
|
||||
|
||||
mod sound;
|
||||
pub use sound::SoundInstanceObject;
|
||||
|
||||
mod trigger_box;
|
||||
pub use trigger_box::TriggerBoxInstanceObject;
|
||||
pub use trigger_box::TriggerBoxShape;
|
||||
|
||||
// From https://github.com/NotAdam/Lumina/tree/40dab50183eb7ddc28344378baccc2d63ae71d35/src/Lumina/Data/Parsing/Layer
|
||||
// Also see https://github.com/aers/FFXIVClientStructs/blob/6b62122cae38bfbc016bf697bef75f80f37abac1/FFXIVClientStructs/FFXIV/Client/LayoutEngine/ILayoutInstance.cs
|
||||
|
||||
// TODO: convert these all to magic
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum LayerEntryType {
|
||||
AssetNone = 00,
|
||||
BG = 0x1,
|
||||
Attribute = 0x2,
|
||||
LayLight = 0x3,
|
||||
Vfx = 0x4,
|
||||
PositionMarker = 0x5,
|
||||
SharedGroup = 0x6,
|
||||
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,
|
||||
/// Zone Transitions (the visible part is probably LineVFX?)
|
||||
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,
|
||||
Unk1 = 90,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(import(magic: &LayerEntryType))]
|
||||
enum LayerEntryData {
|
||||
#[br(pre_assert(*magic == LayerEntryType::BG))]
|
||||
BG(BGInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::LayLight))]
|
||||
LayLight(LightInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::Vfx))]
|
||||
Vfx(VFXInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::PositionMarker))]
|
||||
PositionMarker(PositionMarkerInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::SharedGroup))]
|
||||
SharedGroup(SharedGroupInstance),
|
||||
#[br(pre_assert(*magic == LayerEntryType::Sound))]
|
||||
Sound(SoundInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::EventNPC))]
|
||||
EventNPC(ENPCInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::BattleNPC))]
|
||||
BattleNPC(BNPCInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::Aetheryte))]
|
||||
Aetheryte(AetheryteInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::EnvSet))]
|
||||
EnvSet(EnvSetInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::Gathering))]
|
||||
Gathering(GatheringInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::Treasure))]
|
||||
Treasure(TreasureInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::PopRange))]
|
||||
PopRange(PopRangeInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::ExitRange))]
|
||||
ExitRange(ExitRangeInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::MapRange))]
|
||||
MapRange(MapRangeInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::EventObject))]
|
||||
EventObject(EventInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::EnvLocation))]
|
||||
EnvLocation(EnvLocationObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::EventRange))]
|
||||
EventRange(EventRangeInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::QuestMarker))]
|
||||
QuestMarker(QuestMarkerInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::CollisionBox))]
|
||||
CollisionBox(CollisionBoxInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::LineVFX))]
|
||||
LineVFX(LineVFXInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::ClientPath))]
|
||||
ClientPath(ClientPathInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::ServerPath))]
|
||||
ServerPath(ServerPathInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::GimmickRange))]
|
||||
GimmickRange(GimmickRangeInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::TargetMarker))]
|
||||
TargetMarker(TargetMarkerInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::ChairMarker))]
|
||||
ChairMarker(ChairMarkerInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::PrefetchRange))]
|
||||
PrefetchRange(PrefetchRangeInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::FateRange))]
|
||||
FateRange(FateRangeInstanceObject),
|
||||
#[br(pre_assert(*magic == LayerEntryType::Unk1))]
|
||||
Unk1(),
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct VFXInstanceObject {
|
||||
asset_path_offset: u32,
|
||||
soft_particle_fade_range: f32,
|
||||
padding: u32,
|
||||
color: Color,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
auto_play: bool,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
no_far_clip: bool,
|
||||
padding1: u16,
|
||||
fade_near_start: f32,
|
||||
fade_near_end: f32,
|
||||
fade_far_start: f32,
|
||||
fade_far_end: f32,
|
||||
z_correct: f32,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct GatheringInstanceObject {
|
||||
gathering_point_id: u32,
|
||||
padding: u32,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct TreasureInstanceObject {
|
||||
nonpop_init_zone: u8,
|
||||
padding1: [u8; 3],
|
||||
padding2: [u32; 2],
|
||||
}
|
||||
|
||||
// Unimplemented because I haven't needed it yet:
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct MapRangeInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct EventInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct EnvLocationObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct EventRangeInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct QuestMarkerInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct CollisionBoxInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct LineVFXInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct ClientPathInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct ServerPathInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct GimmickRangeInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct TargetMarkerInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct ChairMarkerInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct PrefetchRangeInstanceObject {}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
struct FateRangeInstanceObject {}
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum LayerSetReferencedType {
|
||||
All = 0x0,
|
||||
Include = 0x1,
|
||||
Exclude = 0x2,
|
||||
Undetermined = 0x3,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
#[br(import(start: u64))]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
struct LayerHeader {
|
||||
layer_id: u32,
|
||||
|
||||
#[br(parse_with = string_from_offset, args(start))]
|
||||
name: String,
|
||||
|
||||
instance_object_offset: i32,
|
||||
instance_object_count: i32,
|
||||
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
tool_mode_visible: bool,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
tool_mode_read_only: bool,
|
||||
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
is_bush_layer: bool,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
ps3_visible: bool,
|
||||
layer_set_referenced_list_offset: i32,
|
||||
festival_id: u16,
|
||||
festival_phase_id: u16,
|
||||
is_temporary: u8,
|
||||
is_housing: u8,
|
||||
version_mask: u16,
|
||||
|
||||
#[br(pad_before = 4)]
|
||||
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)]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
struct LayerSetReferencedList {
|
||||
referenced_type: LayerSetReferencedType,
|
||||
layer_sets: i32,
|
||||
layer_set_count: i32,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
struct OBSetReferenced {
|
||||
asset_type: LayerEntryType,
|
||||
instance_id: u32,
|
||||
ob_set_asset_path_offset: u32,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
struct OBSetEnableReferenced {
|
||||
asset_type: LayerEntryType,
|
||||
instance_id: u32,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
ob_set_enable: bool,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
ob_set_emissive_enable: bool,
|
||||
padding: [u8; 2],
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
struct LgbHeader {
|
||||
#[br(count = 4)]
|
||||
file_id: Vec<u8>,
|
||||
file_size: i32,
|
||||
total_chunk_count: i32,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
struct LayerChunk {
|
||||
#[br(count = 4)]
|
||||
chunk_id: Vec<u8>,
|
||||
chunk_size: i32,
|
||||
layer_group_id: i32,
|
||||
name_offset: u32,
|
||||
layer_offset: i32,
|
||||
layer_count: i32,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
#[br(import(start: u64))]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
struct InstanceObject {
|
||||
asset_type: LayerEntryType,
|
||||
pub instance_id: u32,
|
||||
#[br(parse_with = string_from_offset, args(start))]
|
||||
pub name: String,
|
||||
pub transform: Transformation,
|
||||
#[br(args(&asset_type))]
|
||||
pub data: LayerEntryData,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Layer {
|
||||
pub id: u32,
|
||||
pub name: String,
|
||||
pub objects: Vec<InstanceObject>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LayerGroup {
|
||||
pub layers: Vec<Layer>,
|
||||
}
|
||||
|
||||
impl LayerGroup {
|
||||
/// Reads an existing PBD file
|
||||
pub fn from_existing(buffer: ByteSpan) -> Option<LayerGroup> {
|
||||
let mut cursor = Cursor::new(buffer);
|
||||
|
||||
let file_header = LgbHeader::read(&mut cursor).unwrap();
|
||||
if file_header.file_size < 0 || file_header.total_chunk_count < 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let 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::<i32>().unwrap();
|
||||
}
|
||||
|
||||
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 old_pos = cursor.position();
|
||||
|
||||
let header = LayerHeader::read_le_args(&mut cursor, (old_pos,)).unwrap();
|
||||
|
||||
let mut objects = Vec::new();
|
||||
// read instance objects
|
||||
{
|
||||
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::<i32>().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 start = cursor.stream_position().unwrap();
|
||||
|
||||
objects.push(InstanceObject::read_le_args(&mut cursor, (start,)).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
// read layer set referenced list
|
||||
{
|
||||
// NOTE: this casting is INTENTIONALLY like this, layer_set_referenced_list_offset CAN be negative!
|
||||
cursor
|
||||
.seek(SeekFrom::Start(
|
||||
(old_pos as i32 + header.layer_set_referenced_list_offset) as u64,
|
||||
))
|
||||
.unwrap();
|
||||
let ref_list = LayerSetReferencedList::read(&mut cursor).unwrap();
|
||||
}
|
||||
|
||||
// read ob set referenced
|
||||
{
|
||||
cursor
|
||||
.seek(SeekFrom::Start(
|
||||
old_pos + header.ob_set_referenced_list as u64,
|
||||
))
|
||||
.unwrap();
|
||||
for _ in 0..header.ob_set_referenced_list_count {
|
||||
OBSetReferenced::read(&mut cursor).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// read ob set enable referenced list
|
||||
{
|
||||
cursor
|
||||
.seek(SeekFrom::Start(
|
||||
old_pos + header.ob_set_enable_referenced_list as u64,
|
||||
))
|
||||
.unwrap();
|
||||
for _ in 0..header.ob_set_enable_referenced_list_count {
|
||||
OBSetEnableReferenced::read(&mut cursor).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
layers.push(Layer {
|
||||
id: header.layer_id,
|
||||
name: header.name,
|
||||
objects,
|
||||
});
|
||||
}
|
||||
|
||||
Some(LayerGroup { layers })
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
LayerGroup::from_existing(&read(d).unwrap());
|
||||
}
|
||||
}
|
74
src/layer/npc.rs
Normal file
74
src/layer/npc.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use binrw::{binread, binrw};
|
||||
|
||||
use super::common::RelativePositions;
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct GameInstanceObject {
|
||||
base_id: u32,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct NPCInstanceObject {
|
||||
parent_data: GameInstanceObject,
|
||||
pop_weather: u32,
|
||||
pop_time_start: u8,
|
||||
pop_time_end: u8,
|
||||
padding: u16,
|
||||
move_ai: u32,
|
||||
wandering_range: u8,
|
||||
route: u8,
|
||||
event_group: u16,
|
||||
padding1: [u32; 2],
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct ENPCInstanceObject {
|
||||
pub parent_data: NPCInstanceObject,
|
||||
pub behavior: u32,
|
||||
padding: [u32; 2],
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct BNPCInstanceObject {
|
||||
pub parent_data: NPCInstanceObject,
|
||||
pub name_id: u32,
|
||||
pub drop_item: u32,
|
||||
pub sense_range_rate: f32,
|
||||
pub level: u16,
|
||||
pub active_type: u8,
|
||||
pub pop_interval: u8,
|
||||
pub pop_rate: u8,
|
||||
pub pop_event: u8,
|
||||
pub link_group: u8,
|
||||
pub link_family: u8,
|
||||
pub link_range: u8,
|
||||
pub link_count_limit: u8,
|
||||
pub nonpop_init_zone: u8,
|
||||
pub invalid_repop: u8,
|
||||
pub link_parent: u8,
|
||||
pub link_override: u8,
|
||||
pub link_reply: u8,
|
||||
pub nonpop: u8,
|
||||
pub relative_positions: RelativePositions,
|
||||
pub horizontal_pop_range: f32,
|
||||
pub vertical_pop_range: f32,
|
||||
pub bnpc_base_data: i32,
|
||||
pub repop_id: u8,
|
||||
pub bnpc_ran_id: u8,
|
||||
pub territory_range: u16,
|
||||
pub bound_instance_id: u32,
|
||||
pub fate_layout_label_id: u32,
|
||||
pub normal_ai: u32,
|
||||
pub server_path_id: u32,
|
||||
pub equipment_id: u32,
|
||||
pub customize_id: u32,
|
||||
// TODO: read relativeposition data
|
||||
}
|
25
src/layer/pop.rs
Normal file
25
src/layer/pop.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use binrw::{binread, binrw};
|
||||
|
||||
use super::common::RelativePositions;
|
||||
|
||||
#[binread]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PopType {
|
||||
PC = 0x1,
|
||||
Npc = 0x2,
|
||||
Content = 0x3,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct PopRangeInstanceObject {
|
||||
pub pop_type: PopType,
|
||||
pub relative_positions: RelativePositions,
|
||||
pub inner_radius_ratio: f32,
|
||||
pub index: u8,
|
||||
padding1: [u8; 3],
|
||||
padding2: u32,
|
||||
// TODO: read relative positions
|
||||
}
|
20
src/layer/position_marker.rs
Normal file
20
src/layer/position_marker.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use binrw::{binread, binrw};
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PositionMarkerType {
|
||||
DebugZonePop = 0x1,
|
||||
DebugJump = 0x2,
|
||||
NaviMesh = 0x3,
|
||||
LQEvent = 0x4,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct PositionMarkerInstanceObject {
|
||||
pub position_marker_type: PositionMarkerType,
|
||||
pub comment_jp_offset: u32,
|
||||
pub comment_en_offset: u32,
|
||||
}
|
66
src/layer/shared_group.rs
Normal file
66
src/layer/shared_group.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use binrw::{binread, binrw};
|
||||
|
||||
use super::read_bool_from;
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DoorState {
|
||||
Auto = 0x1,
|
||||
Open = 0x2,
|
||||
Closed = 0x3,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum RotationState {
|
||||
Rounding = 0x1,
|
||||
Stopped = 0x2,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TransformState {
|
||||
Play = 0x0,
|
||||
Stop = 0x1,
|
||||
Replay = 0x2,
|
||||
Reset = 0x3,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ColourState {
|
||||
Play = 0x0,
|
||||
Stop = 0x1,
|
||||
Replay = 0x2,
|
||||
Reset = 0x3,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct SharedGroupInstance {
|
||||
pub asset_path_offset: u32,
|
||||
pub initial_door_state: DoorState,
|
||||
pub overriden_members: i32,
|
||||
pub overriden_members_count: i32,
|
||||
pub initial_rotation_state: RotationState,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub random_timeline_auto_play: bool,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub random_timeline_loop_playback: bool,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub collision_controllable_without_eobj: bool,
|
||||
padding: u8,
|
||||
pub bound_client_path_instance_id: u32,
|
||||
pub move_path_settings: i32,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub not_create_navimesh_door: bool,
|
||||
padding1: [u8; 3],
|
||||
pub initial_transform_state: TransformState,
|
||||
pub initial_color_state: ColourState,
|
||||
// TODO: read move path settings
|
||||
}
|
10
src/layer/sound.rs
Normal file
10
src/layer/sound.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use binrw::binread;
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct SoundInstanceObject {
|
||||
pub sound_effect_param: i32,
|
||||
pub asset_path_offset: u32,
|
||||
// TODO: read separam
|
||||
}
|
27
src/layer/trigger_box.rs
Normal file
27
src/layer/trigger_box.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use binrw::{binread, binrw};
|
||||
|
||||
use super::read_bool_from;
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = i32)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TriggerBoxShape {
|
||||
Box = 0x1,
|
||||
Sphere = 0x2,
|
||||
Cylinder = 0x3,
|
||||
Board = 0x4,
|
||||
Mesh = 0x5,
|
||||
BoardBothSides = 0x6,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
pub struct TriggerBoxInstanceObject {
|
||||
pub trigger_box_shape: TriggerBoxShape,
|
||||
pub priority: i16,
|
||||
#[br(map = read_bool_from::<u8>)]
|
||||
pub enabled: bool,
|
||||
padidng: u8,
|
||||
padding1: u32,
|
||||
}
|
538
src/lgb.rs
538
src/lgb.rs
|
@ -1,538 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use std::io::{Cursor, Seek, SeekFrom};
|
||||
|
||||
use crate::ByteSpan;
|
||||
use binrw::binrw;
|
||||
use binrw::{BinRead, BinReaderExt, binread};
|
||||
|
||||
// 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 {
|
||||
Play = 0x0,
|
||||
Stop = 0x1,
|
||||
Replay = 0x2,
|
||||
Reset = 0x3,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = u8)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ColourState {
|
||||
Play = 0x0,
|
||||
Stop = 0x1,
|
||||
Replay = 0x2,
|
||||
Reset = 0x3,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[brw(repr = u8)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum TriggerBoxShape {
|
||||
Box = 0x1,
|
||||
Sphere = 0x2,
|
||||
Cylinder = 0x3,
|
||||
Board = 0x4,
|
||||
Mesh = 0x5,
|
||||
BoardBothSides = 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 {
|
||||
Ellipsoid = 0x1,
|
||||
Cuboid = 0x2,
|
||||
Cylinder = 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 = 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)]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
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(pad_before = 4)]
|
||||
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)]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
struct LayerSetReferencedList {
|
||||
referenced_type: LayerSetReferencedType,
|
||||
layer_sets: i32,
|
||||
layer_set_count: i32,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
struct LgbHeader {
|
||||
#[br(count = 4)]
|
||||
file_id: Vec<u8>,
|
||||
file_size: i32,
|
||||
total_chunk_count: i32,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
struct LayerChunk {
|
||||
#[br(count = 4)]
|
||||
chunk_id: Vec<u8>,
|
||||
chunk_size: i32,
|
||||
layer_group_id: i32,
|
||||
name_offset: u32,
|
||||
layer_offset: i32,
|
||||
layer_count: i32,
|
||||
}
|
||||
|
||||
#[binread]
|
||||
#[derive(Debug)]
|
||||
#[br(little)]
|
||||
#[allow(dead_code)] // most of the fields are unused at the moment
|
||||
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<Layer> {
|
||||
let mut cursor = Cursor::new(buffer);
|
||||
|
||||
let file_header = LgbHeader::read(&mut cursor).unwrap();
|
||||
if file_header.file_size < 0 || file_header.total_chunk_count < 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let 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::<i32>().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 header = LayerHeader::read(&mut cursor).unwrap();
|
||||
|
||||
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::<i32>().unwrap();
|
||||
}
|
||||
|
||||
cursor
|
||||
.seek(SeekFrom::Start(
|
||||
old_pos + header.layer_set_referenced_list_offset as u64,
|
||||
))
|
||||
.unwrap();
|
||||
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();
|
||||
|
||||
InstanceObject::read(&mut cursor).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Some(Layer {
|
||||
//header
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
Layer::from_existing(&read(d).unwrap());
|
||||
}
|
||||
}
|
|
@ -94,7 +94,8 @@ mod model_file_operations;
|
|||
|
||||
pub mod model_vertex_declarations;
|
||||
|
||||
pub mod lgb;
|
||||
/// Reading layer information for a map (LGB)
|
||||
pub mod layer;
|
||||
|
||||
pub mod tera;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue