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)
|
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 {
|
fn read_half1(data: [u16; 1]) -> Half1 {
|
||||||
Half1 {
|
Half1 {
|
||||||
value: f16::from_bits(data[0]),
|
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 model_vertex_declarations;
|
||||||
|
|
||||||
pub mod lgb;
|
/// Reading layer information for a map (LGB)
|
||||||
|
pub mod layer;
|
||||||
|
|
||||||
pub mod tera;
|
pub mod tera;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue