mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2025-12-09 11:11:24 +04:00
fxid
This commit is contained in:
255
ParkanPlayground/Effects/FxidReader.cs
Normal file
255
ParkanPlayground/Effects/FxidReader.cs
Normal file
@@ -0,0 +1,255 @@
|
||||
using Common;
|
||||
|
||||
namespace ParkanPlayground.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Static reader methods for parsing FXID effect definition structures from binary streams.
|
||||
/// </summary>
|
||||
public static class FxidReader
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads a Vector3 (3 floats: X, Y, Z) from the binary stream.
|
||||
/// </summary>
|
||||
public static Vector3 ReadVector3(BinaryReader br)
|
||||
{
|
||||
float x = br.ReadSingle();
|
||||
float y = br.ReadSingle();
|
||||
float z = br.ReadSingle();
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the 60-byte effect header from the binary stream.
|
||||
/// </summary>
|
||||
public static EffectHeader ReadEffectHeader(BinaryReader br)
|
||||
{
|
||||
EffectHeader h;
|
||||
h.ComponentCount = br.ReadUInt32();
|
||||
h.Unknown1 = br.ReadUInt32();
|
||||
h.Duration = br.ReadSingle();
|
||||
h.Unknown2 = br.ReadSingle();
|
||||
h.Flags = br.ReadUInt32();
|
||||
h.Unknown3 = br.ReadUInt32();
|
||||
h.Reserved = br.ReadBytes(24);
|
||||
h.ScaleX = br.ReadSingle();
|
||||
h.ScaleY = br.ReadSingle();
|
||||
h.ScaleZ = br.ReadSingle();
|
||||
return h;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a BillboardComponentData (type 1) from the binary stream.
|
||||
/// </summary>
|
||||
public static BillboardComponentData ReadBillboardComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
BillboardComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.Unknown04 = br.ReadSingle();
|
||||
d.ScalarAMin = br.ReadSingle();
|
||||
d.ScalarAMax = br.ReadSingle();
|
||||
d.ScalarAExp = br.ReadSingle();
|
||||
d.ActiveTimeStart = br.ReadSingle();
|
||||
d.ActiveTimeEnd = br.ReadSingle();
|
||||
d.SampleSpreadParam = br.ReadSingle();
|
||||
d.Unknown20 = br.ReadUInt32();
|
||||
d.PrimarySampleCount = br.ReadUInt32();
|
||||
d.SecondarySampleCount = br.ReadUInt32();
|
||||
d.ExtentVec0 = ReadVector3(br);
|
||||
d.ExtentVec1 = ReadVector3(br);
|
||||
d.ExtentVec2 = ReadVector3(br);
|
||||
d.ExponentTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet1 = ReadVector3(br);
|
||||
d.RadiusTriplet2 = ReadVector3(br);
|
||||
d.ExponentTriplet1 = ReadVector3(br);
|
||||
d.NoiseAmplitude = br.ReadSingle();
|
||||
d.Reserved = br.ReadBytes(0x50);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a SoundComponentData (type 2) from the binary stream.
|
||||
/// </summary>
|
||||
public static SoundComponentData ReadSoundComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
SoundComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.PlayMode = br.ReadUInt32();
|
||||
d.StartTime = br.ReadSingle();
|
||||
d.EndTime = br.ReadSingle();
|
||||
d.Pos0 = ReadVector3(br);
|
||||
d.Pos1 = ReadVector3(br);
|
||||
d.Offset0 = ReadVector3(br);
|
||||
d.Offset1 = ReadVector3(br);
|
||||
d.Scalar0Min = br.ReadSingle();
|
||||
d.Scalar0Max = br.ReadSingle();
|
||||
d.Scalar1Min = br.ReadSingle();
|
||||
d.Scalar1Max = br.ReadSingle();
|
||||
d.SoundFlags = br.ReadUInt32();
|
||||
d.SoundNameAndReserved = br.ReadBytes(0x40);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an AnimParticleComponentData (type 3) from the binary stream.
|
||||
/// </summary>
|
||||
public static AnimParticleComponentData ReadAnimParticleComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
AnimParticleComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.Unknown04 = br.ReadSingle();
|
||||
d.ScalarAMin = br.ReadSingle();
|
||||
d.ScalarAMax = br.ReadSingle();
|
||||
d.ScalarAExp = br.ReadSingle();
|
||||
d.ActiveTimeStart = br.ReadSingle();
|
||||
d.ActiveTimeEnd = br.ReadSingle();
|
||||
d.SampleSpreadParam = br.ReadSingle();
|
||||
d.Unknown20 = br.ReadUInt32();
|
||||
d.PrimarySampleCount = br.ReadUInt32();
|
||||
d.SecondarySampleCount = br.ReadUInt32();
|
||||
d.ExtentVec0 = ReadVector3(br);
|
||||
d.ExtentVec1 = ReadVector3(br);
|
||||
d.ExtentVec2 = ReadVector3(br);
|
||||
d.ExponentTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet1 = ReadVector3(br);
|
||||
d.RadiusTriplet2 = ReadVector3(br);
|
||||
d.ExponentTriplet1 = ReadVector3(br);
|
||||
d.NoiseAmplitude = br.ReadSingle();
|
||||
d.Reserved = br.ReadBytes(0x38);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an AnimBillboardComponentData (type 4) from the binary stream.
|
||||
/// </summary>
|
||||
public static AnimBillboardComponentData ReadAnimBillboardComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
AnimBillboardComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.Unknown04 = br.ReadSingle();
|
||||
d.ScalarAMin = br.ReadSingle();
|
||||
d.ScalarAMax = br.ReadSingle();
|
||||
d.ScalarAExp = br.ReadSingle();
|
||||
d.ActiveTimeStart = br.ReadSingle();
|
||||
d.ActiveTimeEnd = br.ReadSingle();
|
||||
d.SampleSpreadParam = br.ReadSingle();
|
||||
d.Unknown20 = br.ReadUInt32();
|
||||
d.PrimarySampleCount = br.ReadUInt32();
|
||||
d.SecondarySampleCount = br.ReadUInt32();
|
||||
d.ExtentVec0 = ReadVector3(br);
|
||||
d.ExtentVec1 = ReadVector3(br);
|
||||
d.ExtentVec2 = ReadVector3(br);
|
||||
d.ExponentTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet1 = ReadVector3(br);
|
||||
d.RadiusTriplet2 = ReadVector3(br);
|
||||
d.ExponentTriplet1 = ReadVector3(br);
|
||||
d.NoiseAmplitude = br.ReadSingle();
|
||||
d.Reserved = br.ReadBytes(0x3C);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a TrailComponentData (type 5) from the binary stream.
|
||||
/// </summary>
|
||||
public static TrailComponentData ReadTrailComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
TrailComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.Unknown04To10 = br.ReadBytes(0x10);
|
||||
d.SegmentCount = br.ReadUInt32();
|
||||
d.Param0 = br.ReadSingle();
|
||||
d.Param1 = br.ReadSingle();
|
||||
d.Unknown20 = br.ReadUInt32();
|
||||
d.Unknown24 = br.ReadUInt32();
|
||||
d.ActiveTimeStart = br.ReadSingle();
|
||||
d.ActiveTimeEnd = br.ReadSingle();
|
||||
d.TextureNameAndReserved = br.ReadBytes(0x40);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a PointComponentData (type 6) from the binary stream.
|
||||
/// Note: Point components have no payload beyond the typeAndFlags header.
|
||||
/// </summary>
|
||||
public static PointComponentData ReadPointComponent(uint typeAndFlags)
|
||||
{
|
||||
PointComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a PlaneComponentData (type 7) from the binary stream.
|
||||
/// </summary>
|
||||
public static PlaneComponentData ReadPlaneComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
PlaneComponentData d;
|
||||
d.Base = ReadAnimParticleComponent(br, typeAndFlags);
|
||||
d.ExtraPlaneParam0 = br.ReadUInt32();
|
||||
d.ExtraPlaneParam1 = br.ReadUInt32();
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a ModelComponentData (type 8) from the binary stream.
|
||||
/// </summary>
|
||||
public static ModelComponentData ReadModelComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
ModelComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.Unk04 = br.ReadBytes(0x14);
|
||||
d.ActiveTimeStart = br.ReadSingle();
|
||||
d.ActiveTimeEnd = br.ReadSingle();
|
||||
d.Unknown20 = br.ReadUInt32();
|
||||
d.InstanceCount = br.ReadUInt32();
|
||||
d.BasePos = ReadVector3(br);
|
||||
d.OffsetPos = ReadVector3(br);
|
||||
d.ScatterExtent = ReadVector3(br);
|
||||
d.Axis0 = ReadVector3(br);
|
||||
d.Axis1 = ReadVector3(br);
|
||||
d.Axis2 = ReadVector3(br);
|
||||
d.Reserved70 = br.ReadBytes(0x18);
|
||||
d.RadiusTriplet0 = ReadVector3(br);
|
||||
d.RadiusTriplet1 = ReadVector3(br);
|
||||
d.ReservedA0 = br.ReadBytes(0x18);
|
||||
d.TextureNameAndFlags = br.ReadBytes(0x40);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an AnimModelComponentData (type 9) from the binary stream.
|
||||
/// </summary>
|
||||
public static AnimModelComponentData ReadAnimModelComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
AnimModelComponentData d;
|
||||
d.TypeAndFlags = typeAndFlags;
|
||||
d.AnimSpeed = br.ReadSingle();
|
||||
d.MinTime = br.ReadSingle();
|
||||
d.MaxTime = br.ReadSingle();
|
||||
d.Exponent = br.ReadSingle();
|
||||
d.Reserved14 = br.ReadBytes(0x14);
|
||||
d.DirVec0 = ReadVector3(br);
|
||||
d.Reserved34 = br.ReadBytes(0x0C);
|
||||
d.RadiusTriplet0 = ReadVector3(br);
|
||||
d.DirVec1 = ReadVector3(br);
|
||||
d.RadiusTriplet1 = ReadVector3(br);
|
||||
d.ExtentVec0 = ReadVector3(br);
|
||||
d.ExtentVec1 = ReadVector3(br);
|
||||
d.Reserved7C = br.ReadBytes(0x0C);
|
||||
d.TextureNameAndFlags = br.ReadBytes(0x48);
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a CubeComponentData (type 10) from the binary stream.
|
||||
/// </summary>
|
||||
public static CubeComponentData ReadCubeComponent(BinaryReader br, uint typeAndFlags)
|
||||
{
|
||||
CubeComponentData d;
|
||||
d.Base = ReadAnimBillboardComponent(br, typeAndFlags);
|
||||
d.ExtraCubeParam0 = br.ReadUInt32();
|
||||
return d;
|
||||
}
|
||||
}
|
||||
237
ParkanPlayground/Effects/FxidTypes.cs
Normal file
237
ParkanPlayground/Effects/FxidTypes.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using Common;
|
||||
|
||||
namespace ParkanPlayground.Effects;
|
||||
|
||||
/// <summary>
|
||||
/// Effect-level header at the start of each FXID file (60 bytes total).
|
||||
/// Parsed from CEffect_InitFromDef: defines component count, global duration/flags,
|
||||
/// some unknown control fields, and the uniform scale vector applied to the effect.
|
||||
/// </summary>
|
||||
public struct EffectHeader
|
||||
{
|
||||
public uint ComponentCount;
|
||||
public uint Unknown1;
|
||||
public float Duration;
|
||||
public float Unknown2;
|
||||
public uint Flags;
|
||||
public uint Unknown3;
|
||||
public byte[] Reserved; // 24 bytes
|
||||
public float ScaleX;
|
||||
public float ScaleY;
|
||||
public float ScaleZ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared on-disk definition layout for billboard-style components (type 1).
|
||||
/// Used by CBillboardComponent_Initialize/Update/Render to drive size/color/alpha
|
||||
/// curves and sample scattering within a 3D extent volume.
|
||||
/// </summary>
|
||||
public struct BillboardComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // type (low byte) and flags as seen in CEffect_InitFromDef
|
||||
public float Unknown04; // mode / flag-like float, semantics not fully clear
|
||||
public float ScalarAMin; // base scalar A (e.g. base radius)
|
||||
public float ScalarAMax; // max scalar A
|
||||
public float ScalarAExp; // exponent applied to scalar A curve
|
||||
public float ActiveTimeStart; // activation window start (seconds)
|
||||
public float ActiveTimeEnd; // activation window end (seconds)
|
||||
public float SampleSpreadParam; // extra param used when scattering samples
|
||||
public uint Unknown20; // used as integer param in billboard code, exact meaning unknown
|
||||
public uint PrimarySampleCount; // number of samples along primary axis
|
||||
public uint SecondarySampleCount; // number of samples along secondary axis
|
||||
public Vector3 ExtentVec0; // base extent/origin vector
|
||||
public Vector3 ExtentVec1; // extent center / offset
|
||||
public Vector3 ExtentVec2; // extent size used for random boxing
|
||||
public Vector3 ExponentTriplet0;// exponent triplet for size/color curve
|
||||
public Vector3 RadiusTriplet0; // radius curve key 0
|
||||
public Vector3 RadiusTriplet1; // radius curve key 1
|
||||
public Vector3 RadiusTriplet2; // radius curve key 2
|
||||
public Vector3 ExponentTriplet1;// second exponent triplet (e.g. alpha curve)
|
||||
public float NoiseAmplitude; // per-sample noise amplitude
|
||||
public byte[] Reserved; // 0x50-byte tail, currently not touched by billboard code
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3D sound component definition (type 2).
|
||||
/// Used by CSoundComponent_Initialize/Update to drive positional audio, playback
|
||||
/// window, and scalar ranges (e.g. volume / pitch), plus a 0x40-byte sound name tail.
|
||||
/// </summary>
|
||||
public struct SoundComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // component type and flags
|
||||
public uint PlayMode; // playback mode (looping, one-shot, etc.)
|
||||
public float StartTime; // playback window start (seconds)
|
||||
public float EndTime; // playback window end (seconds)
|
||||
public Vector3 Pos0; // base 3D position or path start
|
||||
public Vector3 Pos1; // secondary position / path end
|
||||
public Vector3 Offset0; // random offset range 0
|
||||
public Vector3 Offset1; // random offset range 1
|
||||
public float Scalar0Min; // scalar range 0 min (e.g. volume)
|
||||
public float Scalar0Max; // scalar range 0 max
|
||||
public float Scalar1Min; // scalar range 1 min (e.g. pitch)
|
||||
public float Scalar1Max; // scalar range 1 max
|
||||
public uint SoundFlags; // misc sound control flags
|
||||
public byte[] SoundNameAndReserved; // 0x40-byte tail; sound name plus padding/unused
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animated particle component definition (type 3).
|
||||
/// Prefix layout matches BillboardComponentData and is used to allocate a grid of
|
||||
/// particle objects; the 0x38-byte tail is passed into CFxManager_LoadTexture.
|
||||
/// </summary>
|
||||
public struct AnimParticleComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // type (low byte) and flags as seen in CEffect_InitFromDef
|
||||
public float Unknown04; // mode / flag-like float, semantics not fully clear
|
||||
public float ScalarAMin; // base scalar A (e.g. base radius)
|
||||
public float ScalarAMax; // max scalar A
|
||||
public float ScalarAExp; // exponent applied to scalar A curve
|
||||
public float ActiveTimeStart; // activation window start (seconds)
|
||||
public float ActiveTimeEnd; // activation window end (seconds)
|
||||
public float SampleSpreadParam; // extra param used when scattering particles
|
||||
public uint Unknown20; // used as integer param in anim particle code, exact meaning unknown
|
||||
public uint PrimarySampleCount; // number of particles along primary axis
|
||||
public uint SecondarySampleCount; // number of particles along secondary axis
|
||||
public Vector3 ExtentVec0; // base extent/origin vector
|
||||
public Vector3 ExtentVec1; // extent center / offset
|
||||
public Vector3 ExtentVec2; // extent size used for random boxing
|
||||
public Vector3 ExponentTriplet0;// exponent triplet for size/color curve
|
||||
public Vector3 RadiusTriplet0; // radius curve key 0
|
||||
public Vector3 RadiusTriplet1; // radius curve key 1
|
||||
public Vector3 RadiusTriplet2; // radius curve key 2
|
||||
public Vector3 ExponentTriplet1;// second exponent triplet (e.g. alpha curve)
|
||||
public float NoiseAmplitude; // per-particle noise amplitude
|
||||
public byte[] Reserved; // 0x38-byte tail; forwarded to CFxManager_LoadTexture unchanged
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animated billboard component definition (type 4).
|
||||
/// Shares the same prefix layout as BillboardComponentData, including extents and
|
||||
/// radius/exponent triplets, but uses a 0x3C-byte tail passed to CFxManager_LoadTexture.
|
||||
/// </summary>
|
||||
public struct AnimBillboardComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // type (low byte) and flags as seen in CEffect_InitFromDef
|
||||
public float Unknown04; // mode / flag-like float, semantics not fully clear
|
||||
public float ScalarAMin; // base scalar A (e.g. base radius)
|
||||
public float ScalarAMax; // max scalar A
|
||||
public float ScalarAExp; // exponent applied to scalar A curve
|
||||
public float ActiveTimeStart; // activation window start (seconds)
|
||||
public float ActiveTimeEnd; // activation window end (seconds)
|
||||
public float SampleSpreadParam; // extra param used when scattering animated billboards
|
||||
public uint Unknown20; // used as integer param in anim billboard code, exact meaning unknown
|
||||
public uint PrimarySampleCount; // number of samples along primary axis
|
||||
public uint SecondarySampleCount; // number of samples along secondary axis
|
||||
public Vector3 ExtentVec0; // base extent/origin vector
|
||||
public Vector3 ExtentVec1; // extent center / offset
|
||||
public Vector3 ExtentVec2; // extent size used for random boxing
|
||||
public Vector3 ExponentTriplet0;// exponent triplet for size/color curve
|
||||
public Vector3 RadiusTriplet0; // radius curve key 0
|
||||
public Vector3 RadiusTriplet1; // radius curve key 1
|
||||
public Vector3 RadiusTriplet2; // radius curve key 2
|
||||
public Vector3 ExponentTriplet1;// second exponent triplet (e.g. alpha curve)
|
||||
public float NoiseAmplitude; // per-sample noise amplitude
|
||||
public byte[] Reserved; // 0x3C-byte tail; forwarded to CFxManager_LoadTexture unchanged
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compact definition for trail / ribbon components (type 5).
|
||||
/// CTrailComponent_Initialize interprets this as segment count, width/alpha/UV
|
||||
/// ranges, timing, and a shared texture name at +0x30.
|
||||
/// </summary>
|
||||
public struct TrailComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // component type and flags
|
||||
public byte[] Unknown04To10; // 0x10 bytes at +4..+0x13, used only indirectly; types unknown
|
||||
public uint SegmentCount; // number of trail segments (particles)
|
||||
public float Param0; // first width/alpha/UV control value (start)
|
||||
public float Param1; // second width/alpha/UV control value (end)
|
||||
public uint Unknown20; // extra integer parameter, purpose unknown
|
||||
public uint Unknown24; // extra integer parameter, purpose unknown
|
||||
public float ActiveTimeStart; // trail activation start time (>= 0)
|
||||
public float ActiveTimeEnd; // trail activation end time
|
||||
public byte[] TextureNameAndReserved; // 0x40-byte tail containing texture name and padding/flags
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple point component definition (type 6).
|
||||
/// Definition block is just the 4-byte typeAndFlags header; no extra data on disk.
|
||||
/// </summary>
|
||||
public struct PointComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // component type and flags; definition block has no payload
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plane component definition (type 7).
|
||||
/// Shares the same 0xC8-byte prefix layout as AnimParticleComponentData (type 3),
|
||||
/// followed by two dwords of plane-specific data.
|
||||
/// </summary>
|
||||
public struct PlaneComponentData
|
||||
{
|
||||
public AnimParticleComponentData Base; // shared 0xC8-byte prefix: time window, sample counts, extents, curves
|
||||
public uint ExtraPlaneParam0; // plane-specific parameter, semantics not yet reversed
|
||||
public uint ExtraPlaneParam1; // plane-specific parameter, semantics not yet reversed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static model component definition (type 8).
|
||||
/// Layout fully matches the IDA typedef used by CModelComponent_Initialize:
|
||||
/// time window, instance count, spatial extents/axes, radius triplets, and a
|
||||
/// 0x40-byte texture name tail.
|
||||
/// </summary>
|
||||
public struct ModelComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // component type and flags
|
||||
public byte[] Unk04; // 0x14-byte blob at +0x04..+0x17, purpose unclear
|
||||
public float ActiveTimeStart; // activation window start (seconds), +0x18
|
||||
public float ActiveTimeEnd; // activation window end (seconds), +0x1C
|
||||
public uint Unknown20; // extra flags/int parameter at +0x20
|
||||
public uint InstanceCount; // number of model instances to spawn at +0x24
|
||||
public Vector3 BasePos; // base position of the emitter / origin, +0x28
|
||||
public Vector3 OffsetPos; // positional offset applied per-instance, +0x34
|
||||
public Vector3 ScatterExtent; // extent volume used for random scattering, +0x40
|
||||
public Vector3 Axis0; // local axis 0 (orientation / shape), +0x4C
|
||||
public Vector3 Axis1; // local axis 1 (orientation / shape), +0x58
|
||||
public Vector3 Axis2; // local axis 2 (orientation / shape), +0x64
|
||||
public byte[] Reserved70; // 0x18 bytes at +0x70..+0x87, not directly used
|
||||
public Vector3 RadiusTriplet0; // radius / extent triplet 0 at +0x88
|
||||
public Vector3 RadiusTriplet1; // radius / extent triplet 1 at +0x94
|
||||
public byte[] ReservedA0; // 0x18 bytes at +0xA0..+0xB7, not directly used
|
||||
public byte[] TextureNameAndFlags; // 0x40-byte tail at +0xB8: texture name + padding/flags
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Animated model component definition (type 9).
|
||||
/// Layout derived from CAnimModelComponent_Initialize: time params, direction vectors,
|
||||
/// radius triplets, extent vectors, and a 0x48-byte texture name tail.
|
||||
/// </summary>
|
||||
public struct AnimModelComponentData
|
||||
{
|
||||
public uint TypeAndFlags; // component type and flags
|
||||
public float AnimSpeed; // animation speed multiplier at +0x04
|
||||
public float MinTime; // activation window start (clamped >= 0) at +0x08
|
||||
public float MaxTime; // activation window end at +0x0C
|
||||
public float Exponent; // exponent for time interpolation at +0x10
|
||||
public byte[] Reserved14; // 0x14 bytes at +0x14..+0x27, padding
|
||||
public Vector3 DirVec0; // normalized direction vector 0 at +0x28
|
||||
public byte[] Reserved34; // 0x0C bytes at +0x34..+0x3F, padding
|
||||
public Vector3 RadiusTriplet0; // radius triplet 0 at +0x40
|
||||
public Vector3 DirVec1; // normalized direction vector 1 at +0x4C
|
||||
public Vector3 RadiusTriplet1; // radius triplet 1 at +0x58
|
||||
public Vector3 ExtentVec0; // extent vector 0 at +0x64
|
||||
public Vector3 ExtentVec1; // extent vector 1 at +0x70
|
||||
public byte[] Reserved7C; // 0x0C bytes at +0x7C..+0x87, padding
|
||||
public byte[] TextureNameAndFlags; // 0x48-byte tail at +0x88: texture/model name + padding
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cube component definition (type 10).
|
||||
/// Shares the same 0xCC-byte prefix layout as AnimBillboardComponentData (type 4),
|
||||
/// followed by one dword of cube-specific data.
|
||||
/// </summary>
|
||||
public struct CubeComponentData
|
||||
{
|
||||
public AnimBillboardComponentData Base; // shared 0xCC-byte prefix: billboard-style time window, extents, curves
|
||||
public uint ExtraCubeParam0; // cube-specific parameter, semantics not yet reversed
|
||||
}
|
||||
@@ -26,20 +26,20 @@ MSH файлы — это NRes архивы, содержащие несколь
|
||||
| Тип | Название | Размер элемента | Описание |
|
||||
|:---:|----------|:---------------:|----------|
|
||||
| 01 | Pieces | 38 (0x26) | Части меша / тайлы с LOD-ссылками |
|
||||
| 02 | Submeshes | 68 (0x44) | Сабмеши с баундинг-боксами |
|
||||
| 02 | Submeshes | 68 (0x44) | LOD части с баундинг-боксами |
|
||||
| 03 | Vertices | 12 (0x0C) | Позиции вершин (Vector3) |
|
||||
| 04 | VertexData1 | 4 | Данные на вершину (неизвестно) |
|
||||
| 05 | VertexData2 | 4 | Данные на вершину (неизвестно) |
|
||||
| 04 | неизвестно | 4 | (неизвестно) |
|
||||
| 05 | неизвестно | 4 | (неизвестно) |
|
||||
| 06 | Indices | 2 | Индексы вершин треугольников (только Модель) |
|
||||
| 07 | TriangleData | 16 | Данные рендера на треугольник (только Модель) |
|
||||
| 07 | неизвестно | 16 | (только Модель) |
|
||||
| 08 | Animations | 4 | Кейфреймы анимации меша |
|
||||
| 0A | ExternalRefs | переменный | Внешние ссылки на меши (строки) |
|
||||
| 0B | MaterialData | 4 | Материалы на треугольник (только Ландшафт) |
|
||||
| 0D | DrawBatches | 20 (0x14) | Батчи отрисовки (только Модель) |
|
||||
| 0E | VertexData3 | 4 | Данные на вершину (только Ландшафт) |
|
||||
| 12 | MicrotextureMap | 4 | Микротекстурный маппинг на вершину |
|
||||
| 0B | неизвестно | 4 | неизвестно (только Ландшафт) |
|
||||
| 0D | неизвестно | 20 (0x14) | неизвестно (только Модель) |
|
||||
| 0E | неизвестно | 4 | неизвестно (только Ландшафт) |
|
||||
| 12 | MicrotextureMap | 4 | неизвестно |
|
||||
| 13 | ShortAnims | 2 | Короткие индексы анимаций |
|
||||
| 15 | Triangles | 28 (0x1C) | Прямые определения треугольников |
|
||||
| 15 | неизвестно | 28 (0x1C) | неизвестно |
|
||||
|
||||
---
|
||||
|
||||
@@ -201,7 +201,7 @@ MSH файлы — это NRes архивы, содержащие несколь
|
||||
| Смещение | Размер | Тип | Поле | Описание |
|
||||
|:--------:|:------:|:---:|------|----------|
|
||||
| 0x00 | 4 | uint32 | Flags | Флаги треугольника (0x20000 = коллизия) |
|
||||
| 0x04 | 4 | uint32 | Magic04 | Неизвестно (часто 0xFFFFFF01) |
|
||||
| 0x04 | 4 | uint32 | MaterialData | Данные материала (см. ниже) |
|
||||
| 0x08 | 2 | ushort | Vertex1Index | Индекс первой вершины |
|
||||
| 0x0A | 2 | ushort | Vertex2Index | Индекс второй вершины |
|
||||
| 0x0C | 2 | ushort | Vertex3Index | Индекс третьей вершины |
|
||||
@@ -210,6 +210,24 @@ MSH файлы — это NRes архивы, содержащие несколь
|
||||
| 0x16 | 4 | uint32 | Magic16 | Неизвестно |
|
||||
| 0x1A | 2 | ushort | Magic1A | Неизвестно |
|
||||
|
||||
#### MaterialData (0x04) - Структура материала
|
||||
|
||||
```
|
||||
MaterialData = 0xFFFF_SSPP
|
||||
│ │└─ PP: Основной материал (byte 0)
|
||||
│ └─── SS: Вторичный материал для блендинга (byte 1)
|
||||
└────── Всегда 0xFFFF (байты 2-3)
|
||||
```
|
||||
|
||||
| Значение SS | Описание |
|
||||
|:-----------:|----------|
|
||||
| 0xFF | Сплошной материал (без блендинга) |
|
||||
| 0x01-0xFE | Индекс вторичного материала для блендинга |
|
||||
|
||||
Примеры:
|
||||
- `0xFFFFFF01` = Сплошной материал 1
|
||||
- `0xFFFF0203` = Материал 3 с блендингом в материал 2
|
||||
|
||||
---
|
||||
|
||||
### Компонент 0A - External References (переменный размер)
|
||||
@@ -261,6 +279,71 @@ var type = MshConverter.DetectMeshType(archive);
|
||||
|
||||
---
|
||||
|
||||
## Формат WEA - Файлы материалов ландшафта
|
||||
|
||||
Файлы `.wea` — текстовые файлы, определяющие таблицу материалов для ландшафта.
|
||||
|
||||
### Формат
|
||||
|
||||
```
|
||||
{count}
|
||||
{index} {material_name}
|
||||
{index} {material_name}
|
||||
...
|
||||
```
|
||||
|
||||
### Связь с Land.msh
|
||||
|
||||
Каждая карта имеет два файла материалов:
|
||||
|
||||
| Файл | Используется для | Треугольники в Comp15 |
|
||||
|------|------------------|----------------------|
|
||||
| `Land1.wea` | LOD0 (высокая детализация) | Первые N (сумма CountIn07 для LOD0) |
|
||||
| `Land2.wea` | LOD1 (низкая детализация) | Остальные |
|
||||
|
||||
### Пример (SC_1)
|
||||
|
||||
**Land1.wea:**
|
||||
```
|
||||
4
|
||||
0 B_S0
|
||||
1 L04
|
||||
2 L02
|
||||
3 L00
|
||||
```
|
||||
|
||||
**Land2.wea:**
|
||||
```
|
||||
4
|
||||
0 DEFAULT
|
||||
1 L05
|
||||
2 L03
|
||||
3 L01
|
||||
```
|
||||
|
||||
### Маппинг материалов
|
||||
|
||||
Индекс материала в `Comp15.MaterialData & 0xFF` → строка в `.wea` файле.
|
||||
|
||||
```
|
||||
Треугольник с MaterialData = 0xFFFF0102
|
||||
└─ Основной материал = 02 → Land1.wea[2] = "L02"
|
||||
└─ Блендинг с материалом = 01 → Land1.wea[1] = "L04"
|
||||
```
|
||||
|
||||
### Типичные имена материалов
|
||||
|
||||
| Префикс | Назначение |
|
||||
|---------|------------|
|
||||
| L00-L05 | Текстуры ландшафта (grass, dirt, etc.) |
|
||||
| B_S0 | Базовая текстура |
|
||||
| DEFAULT | Фолбэк для LOD1 |
|
||||
| WATER | Вода (поверхность) |
|
||||
| WATER_BOT | Вода (дно) |
|
||||
| WATER_M | Вода LOD1 |
|
||||
|
||||
---
|
||||
|
||||
## Источники
|
||||
|
||||
- Реверс-инжиниринг `Terrain.dll` (класс CLandscape)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
||||
<ProjectReference Include="..\VarsetLib\VarsetLib.csproj" />
|
||||
<ProjectReference Include="..\MaterialLib\MaterialLib.csproj" />
|
||||
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,33 +1,332 @@
|
||||
using ParkanPlayground;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using ParkanPlayground.Effects;
|
||||
using static ParkanPlayground.Effects.FxidReader;
|
||||
|
||||
// ========== MSH CONVERTER TEST - AUTO-DETECTING MODEL VS LANDSCAPE ==========
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
|
||||
var converter = new MshConverter();
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("Usage: ParkanPlayground <effects-directory-or-fxid-file>");
|
||||
return;
|
||||
}
|
||||
|
||||
// Test with landscape
|
||||
Console.WriteLine("=".PadRight(60, '='));
|
||||
converter.Convert(
|
||||
@"C:\Program Files (x86)\Nikita\Iron Strategy\DATA\MAPS\SC_1\Land.msh",
|
||||
"landscape_lod0.obj",
|
||||
lodLevel: 0
|
||||
);
|
||||
var path = args[0];
|
||||
bool anyError = false;
|
||||
var sizeByType = new Dictionary<byte, int>
|
||||
{
|
||||
[1] = 0xE0, // 1: Billboard
|
||||
[2] = 0x94, // 2: Sound
|
||||
[3] = 0xC8, // 3: AnimParticle
|
||||
[4] = 0xCC, // 4: AnimBillboard
|
||||
[5] = 0x70, // 5: Trail
|
||||
[6] = 0x04, // 6: Point
|
||||
[7] = 0xD0, // 7: Plane
|
||||
[8] = 0xF8, // 8: Model
|
||||
[9] = 0xD0, // 9: AnimModel
|
||||
[10] = 0xD0, // 10: Cube
|
||||
};
|
||||
|
||||
Console.WriteLine();
|
||||
// Check for --dump-headers flag
|
||||
bool dumpHeaders = args.Length > 1 && args[1] == "--dump-headers";
|
||||
|
||||
// Test with landscape LOD 1 (lower detail)
|
||||
Console.WriteLine("=".PadRight(60, '='));
|
||||
converter.Convert(
|
||||
@"C:\Program Files (x86)\Nikita\Iron Strategy\DATA\MAPS\SC_1\Land.msh",
|
||||
"landscape_lod1.obj",
|
||||
lodLevel: 1
|
||||
);
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
var files = Directory.EnumerateFiles(path, "*.bin").ToList();
|
||||
|
||||
if (dumpHeaders)
|
||||
{
|
||||
// Collect all headers for analysis
|
||||
var headers = new List<(string name, EffectHeader h)>();
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var br = new BinaryReader(fs, Encoding.ASCII, leaveOpen: false);
|
||||
if (fs.Length >= 60)
|
||||
{
|
||||
headers.Add((Path.GetFileName(file), ReadEffectHeader(br)));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// Analyze unique values
|
||||
Console.WriteLine("=== UNIQUE VALUES ANALYSIS ===\n");
|
||||
|
||||
var uniqueUnk1 = headers.Select(x => x.h.Unknown1).Distinct().OrderBy(x => x).ToList();
|
||||
Console.WriteLine($"Unknown1 unique values ({uniqueUnk1.Count}): {string.Join(", ", uniqueUnk1)}");
|
||||
|
||||
var uniqueUnk2 = headers.Select(x => x.h.Unknown2).Distinct().OrderBy(x => x).ToList();
|
||||
Console.WriteLine($"Unknown2 unique values ({uniqueUnk2.Count}): {string.Join(", ", uniqueUnk2.Select(x => x.ToString("F2")))}");
|
||||
|
||||
var uniqueFlags = headers.Select(x => x.h.Flags).Distinct().OrderBy(x => x).ToList();
|
||||
Console.WriteLine($"Flags unique values ({uniqueFlags.Count}): {string.Join(", ", uniqueFlags.Select(x => $"0x{x:X4}"))}");
|
||||
|
||||
var uniqueUnk3 = headers.Select(x => x.h.Unknown3).Distinct().OrderBy(x => x).ToList();
|
||||
Console.WriteLine($"Unknown3 unique values ({uniqueUnk3.Count}): {string.Join(", ", uniqueUnk3)}");
|
||||
Console.WriteLine($"Unknown3 as hex: {string.Join(", ", uniqueUnk3.Select(x => $"0x{x:X3}"))}");
|
||||
Console.WriteLine($"Unknown3 decoded (hi.lo): {string.Join(", ", uniqueUnk3.Select(x => $"{x >> 8}.{x & 0xFF}"))}");
|
||||
|
||||
// Check reserved bytes
|
||||
var nonZeroReserved = headers.Where(x => x.h.Reserved.Any(b => b != 0)).ToList();
|
||||
Console.WriteLine($"\nFiles with non-zero Reserved bytes: {nonZeroReserved.Count} / {headers.Count}");
|
||||
|
||||
// Check scales
|
||||
var uniqueScales = headers.Select(x => (x.h.ScaleX, x.h.ScaleY, x.h.ScaleZ)).Distinct().ToList();
|
||||
Console.WriteLine($"Unique scale combinations: {string.Join(", ", uniqueScales.Select(s => $"({s.ScaleX:F2},{s.ScaleY:F2},{s.ScaleZ:F2})"))}");
|
||||
|
||||
Console.WriteLine("\n=== SAMPLE HEADERS (first 30) ===");
|
||||
Console.WriteLine($"{"File",-40} | {"Cnt",3} | {"U1",2} | {"Duration",8} | {"U2",6} | {"Flags",6} | {"U3",4} | Scale");
|
||||
Console.WriteLine(new string('-', 100));
|
||||
|
||||
foreach (var (name, h) in headers.Take(30))
|
||||
{
|
||||
Console.WriteLine($"{name,-40} | {h.ComponentCount,3} | {h.Unknown1,2} | {h.Duration,8:F2} | {h.Unknown2,6:F2} | 0x{h.Flags:X4} | {h.Unknown3,4} | ({h.ScaleX:F1},{h.ScaleY:F1},{h.ScaleZ:F1})");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!ValidateFxidFile(file))
|
||||
{
|
||||
anyError = true;
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(anyError
|
||||
? "Validation finished with errors."
|
||||
: "All FXID files parsed successfully.");
|
||||
}
|
||||
}
|
||||
else if (File.Exists(path))
|
||||
{
|
||||
anyError = !ValidateFxidFile(path);
|
||||
Console.WriteLine(anyError ? "Validation failed." : "Validation OK.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Path not found: {path}");
|
||||
}
|
||||
|
||||
// Test with model
|
||||
Console.WriteLine("=".PadRight(60, '='));
|
||||
converter.Convert(
|
||||
@"E:\ParkanUnpacked\fortif.rlb\133_fr_m_bunker.msh",
|
||||
"bunker_lod0.obj",
|
||||
lodLevel: 0
|
||||
);
|
||||
void DumpEffectHeader(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var br = new BinaryReader(fs, Encoding.ASCII, leaveOpen: false);
|
||||
|
||||
if (fs.Length < 60)
|
||||
{
|
||||
Console.WriteLine($"{Path.GetFileName(path)}: file too small");
|
||||
return;
|
||||
}
|
||||
|
||||
var h = ReadEffectHeader(br);
|
||||
|
||||
// Format reserved bytes as hex (show first 8 bytes for brevity)
|
||||
var reservedHex = BitConverter.ToString(h.Reserved, 0, Math.Min(8, h.Reserved.Length)).Replace("-", " ");
|
||||
if (h.Reserved.Length > 8) reservedHex += "...";
|
||||
|
||||
// Check if reserved has any non-zero bytes
|
||||
bool reservedAllZero = h.Reserved.All(b => b == 0);
|
||||
|
||||
Console.WriteLine($"{Path.GetFileName(path),-40} | {h.ComponentCount,7} | {h.Unknown1,4} | {h.Duration,8:F2} | {h.Unknown2,8:F2} | 0x{h.Flags:X4} | {h.Unknown3,4} | {(reservedAllZero ? "(all zero)" : reservedHex),-20} | ({h.ScaleX:F2}, {h.ScaleY:F2}, {h.ScaleZ:F2})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"{Path.GetFileName(path)}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateFxidFile(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var br = new BinaryReader(fs, Encoding.ASCII, leaveOpen: false);
|
||||
|
||||
const int headerSize = 60; // sizeof(EffectHeader) on disk
|
||||
if (fs.Length < headerSize)
|
||||
{
|
||||
Console.WriteLine($"{path}: file too small ({fs.Length} bytes).");
|
||||
return false;
|
||||
}
|
||||
|
||||
var header = ReadEffectHeader(br);
|
||||
|
||||
var typeCounts = new Dictionary<byte, int>();
|
||||
|
||||
for (int i = 0; i < header.ComponentCount; i++)
|
||||
{
|
||||
long blockStart = fs.Position;
|
||||
if (fs.Position + 4 > fs.Length)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: unexpected EOF before type (offset 0x{fs.Position:X}, size 0x{fs.Length:X}).");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint typeAndFlags = br.ReadUInt32();
|
||||
byte type = (byte)(typeAndFlags & 0xFF);
|
||||
|
||||
if (!typeCounts.TryGetValue(type, out var count))
|
||||
{
|
||||
count = 0;
|
||||
}
|
||||
typeCounts[type] = count + 1;
|
||||
|
||||
if (!sizeByType.TryGetValue(type, out int blockSize))
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: unknown type {type} (typeAndFlags=0x{typeAndFlags:X8}).");
|
||||
return false;
|
||||
}
|
||||
|
||||
int remaining = blockSize - 4;
|
||||
if (fs.Position + remaining > fs.Length)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: block size 0x{blockSize:X} runs past EOF (blockStart=0x{blockStart:X}, fileSize=0x{fs.Length:X}).");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type == 1)
|
||||
{
|
||||
var def = ReadBillboardComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Reserved.Length != 0x50)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 1 reserved length {def.Reserved.Length}, expected 0x50.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 2)
|
||||
{
|
||||
var def = ReadSoundComponent(br, typeAndFlags);
|
||||
|
||||
if (def.SoundNameAndReserved.Length != 0x40)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 2 reserved length {def.SoundNameAndReserved.Length}, expected 0x40.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 3)
|
||||
{
|
||||
var def = ReadAnimParticleComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Reserved.Length != 0x38)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 3 reserved length {def.Reserved.Length}, expected 0x38.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 4)
|
||||
{
|
||||
var def = ReadAnimBillboardComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Reserved.Length != 0x3C)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 4 reserved length {def.Reserved.Length}, expected 0x3C.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 5)
|
||||
{
|
||||
var def = ReadTrailComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Unknown04To10.Length != 0x10)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 5 prefix length {def.Unknown04To10.Length}, expected 0x10.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (def.TextureNameAndReserved.Length != 0x40)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 5 tail length {def.TextureNameAndReserved.Length}, expected 0x40.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 6)
|
||||
{
|
||||
// Point components have no extra bytes beyond the 4-byte typeAndFlags header.
|
||||
var def = ReadPointComponent(typeAndFlags);
|
||||
}
|
||||
else if (type == 7)
|
||||
{
|
||||
var def = ReadPlaneComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Base.Reserved.Length != 0x38)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 7 base reserved length {def.Base.Reserved.Length}, expected 0x38.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 8)
|
||||
{
|
||||
var def = ReadModelComponent(br, typeAndFlags);
|
||||
|
||||
if (def.TextureNameAndFlags.Length != 0x40)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 8 tail length {def.TextureNameAndFlags.Length}, expected 0x40.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 9)
|
||||
{
|
||||
var def = ReadAnimModelComponent(br, typeAndFlags);
|
||||
|
||||
if (def.TextureNameAndFlags.Length != 0x48)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 9 tail length {def.TextureNameAndFlags.Length}, expected 0x48.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == 10)
|
||||
{
|
||||
var def = ReadCubeComponent(br, typeAndFlags);
|
||||
|
||||
if (def.Base.Reserved.Length != 0x3C)
|
||||
{
|
||||
Console.WriteLine($"{path}: component {i}: type 10 base reserved length {def.Base.Reserved.Length}, expected 0x3C.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip the remaining bytes for other component types.
|
||||
fs.Position += remaining;
|
||||
}
|
||||
}
|
||||
|
||||
// Dump a compact per-file summary of component types and counts.
|
||||
var sb = new StringBuilder();
|
||||
bool first = true;
|
||||
foreach (var kv in typeCounts)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
sb.Append(kv.Key);
|
||||
sb.Append('x');
|
||||
sb.Append(kv.Value);
|
||||
first = false;
|
||||
}
|
||||
Console.WriteLine($"{path}: components={header.ComponentCount}, types=[{sb}]");
|
||||
|
||||
if (fs.Position != fs.Length)
|
||||
{
|
||||
Console.WriteLine($"{path}: parsed to 0x{fs.Position:X}, but file size is 0x{fs.Length:X} (leftover {fs.Length - fs.Position} bytes).");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"{path}: exception while parsing: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user