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