From 6cd43aa304630ac0974abc81625d5800ffd89f06 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 31 Dec 2022 01:18:58 +0100 Subject: [PATCH] Add AVFX parsing. --- Penumbra.GameData/Files/AvfxFile.cs | 282 +++++++++++++++++++++++++++ Penumbra.GameData/Files/AvfxMagic.cs | 137 +++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 Penumbra.GameData/Files/AvfxFile.cs create mode 100644 Penumbra.GameData/Files/AvfxMagic.cs diff --git a/Penumbra.GameData/Files/AvfxFile.cs b/Penumbra.GameData/Files/AvfxFile.cs new file mode 100644 index 00000000..3077d960 --- /dev/null +++ b/Penumbra.GameData/Files/AvfxFile.cs @@ -0,0 +1,282 @@ +using System; +using System.IO; +using System.Numerics; +using System.Text; + +namespace Penumbra.GameData.Files; + +public class AvfxFile : IWritable +{ + public struct Block + { + public uint Name; + public uint Size; + public byte[] Data; + + public Block(BinaryReader r) + { + Name = r.ReadUInt32(); + Size = r.ReadUInt32(); + Data = r.ReadBytes((int)Size.RoundTo4()); + } + + public bool ToBool() + => BitConverter.ToBoolean(Data); + + public uint ToUint() + => BitConverter.ToUInt32(Data); + + public float ToFloat() + => BitConverter.ToSingle(Data); + + public new string ToString() + { + var span = Data.AsSpan(0, (int)Size - 1); + return Encoding.UTF8.GetString(span); + } + } + + + public Vector3 ClipBox; + public Vector3 ClipBoxSize; + public Vector3 RevisedValuesPos; + public Vector3 RevisedValuesRot; + public Vector3 RevisedValuesScale; + public Vector3 RevisedValuesColor; + + public uint Version; + public uint DrawLayerType; + public uint DrawOrderType; + public uint DirectionalLightSourceType; + public uint PointLightsType1; + public uint PointLightsType2; + + public float BiasZmaxScale; + public float BiasZmaxDistance; + public float NearClipBegin; + public float NearClipEnd; + public float FadeInnerX; + public float FadeOuterX; + public float FadeInnerY; + public float FadeOuterY; + public float FadeInnerZ; + public float FadeOuterZ; + public float FarClipBegin; + public float FarClipEnd; + public float SoftParticleFadeRange; + public float SoftKeyOffset; + public float GlobalFogInfluence; + + public bool IsDelayFastParticle; + public bool IsFitGround; + public bool IsTransformSkip; + public bool IsAllStopOnHide; + public bool CanBeClippedOut; + public bool ClipBoxEnabled; + public bool IsCameraSpace; + public bool IsFullEnvLight; + public bool IsClipOwnSetting; + public bool FadeEnabledX; + public bool FadeEnabledY; + public bool FadeEnabledZ; + public bool GlobalFogEnabled; + public bool LtsEnabled; + + public Block[] Schedulers = Array.Empty(); + public Block[] Timelines = Array.Empty(); + public Block[] Emitters = Array.Empty(); + public Block[] Particles = Array.Empty(); + public Block[] Effectors = Array.Empty(); + public Block[] Binders = Array.Empty(); + public string[] Textures = Array.Empty(); + public Block[] Models = Array.Empty(); + + public bool Valid { get; } = true; + + public AvfxFile(byte[] data) + { + using var stream = new MemoryStream(data); + using var r = new BinaryReader(stream); + + var name = r.ReadUInt32(); + var size = r.ReadUInt32(); + var schedulerCount = 0; + var timelineCount = 0; + var emitterCount = 0; + var particleCount = 0; + var effectorCount = 0; + var binderCount = 0; + var textureCount = 0; + var modelCount = 0; + while (r.BaseStream.Position < size) + { + var block = new Block(r); + switch (block.Name) + { + // @formatter:off + case AvfxMagic.Version: Version = block.ToUint(); break; + case AvfxMagic.IsDelayFastParticle: IsDelayFastParticle = block.ToBool(); break; + case AvfxMagic.IsFitGround: IsFitGround = block.ToBool(); break; + case AvfxMagic.IsTransformSkip: IsTransformSkip = block.ToBool(); break; + case AvfxMagic.IsAllStopOnHide: IsAllStopOnHide = block.ToBool(); break; + case AvfxMagic.CanBeClippedOut: CanBeClippedOut = block.ToBool(); break; + case AvfxMagic.ClipBoxEnabled: ClipBoxEnabled = block.ToBool(); break; + case AvfxMagic.ClipBoxX: ClipBox.X = block.ToFloat(); break; + case AvfxMagic.ClipBoxY: ClipBox.Y = block.ToFloat(); break; + case AvfxMagic.ClipBoxZ: ClipBox.Z = block.ToFloat(); break; + case AvfxMagic.ClipBoxSizeX: ClipBoxSize.X = block.ToFloat(); break; + case AvfxMagic.ClipBoxSizeY: ClipBoxSize.Y = block.ToFloat(); break; + case AvfxMagic.ClipBoxSizeZ: ClipBoxSize.Z = block.ToFloat(); break; + case AvfxMagic.BiasZmaxScale: BiasZmaxScale = block.ToFloat(); break; + case AvfxMagic.BiasZmaxDistance: BiasZmaxDistance = block.ToFloat(); break; + case AvfxMagic.IsCameraSpace: IsCameraSpace = block.ToBool(); break; + case AvfxMagic.IsFullEnvLight: IsFullEnvLight = block.ToBool(); break; + case AvfxMagic.IsClipOwnSetting: IsClipOwnSetting = block.ToBool(); break; + case AvfxMagic.NearClipBegin: NearClipBegin = block.ToFloat(); break; + case AvfxMagic.NearClipEnd: NearClipEnd = block.ToFloat(); break; + case AvfxMagic.FarClipBegin: FarClipBegin = block.ToFloat(); break; + case AvfxMagic.FarClipEnd: FarClipEnd = block.ToFloat(); break; + case AvfxMagic.SoftParticleFadeRange: SoftParticleFadeRange = block.ToFloat(); break; + case AvfxMagic.SoftKeyOffset: SoftKeyOffset = block.ToFloat(); break; + case AvfxMagic.DrawLayerType: DrawLayerType = block.ToUint(); break; + case AvfxMagic.DrawOrderType: DrawOrderType = block.ToUint(); break; + case AvfxMagic.DirectionalLightSourceType: DirectionalLightSourceType = block.ToUint(); break; + case AvfxMagic.PointLightsType1: PointLightsType1 = block.ToUint(); break; + case AvfxMagic.PointLightsType2: PointLightsType2 = block.ToUint(); break; + case AvfxMagic.RevisedValuesPosX: RevisedValuesPos.X = block.ToFloat(); break; + case AvfxMagic.RevisedValuesPosY: RevisedValuesPos.Y = block.ToFloat(); break; + case AvfxMagic.RevisedValuesPosZ: RevisedValuesPos.Z = block.ToFloat(); break; + case AvfxMagic.RevisedValuesRotX: RevisedValuesRot.X = block.ToFloat(); break; + case AvfxMagic.RevisedValuesRotY: RevisedValuesRot.Y = block.ToFloat(); break; + case AvfxMagic.RevisedValuesRotZ: RevisedValuesRot.Z = block.ToFloat(); break; + case AvfxMagic.RevisedValuesScaleX: RevisedValuesScale.X = block.ToFloat(); break; + case AvfxMagic.RevisedValuesScaleY: RevisedValuesScale.Y = block.ToFloat(); break; + case AvfxMagic.RevisedValuesScaleZ: RevisedValuesScale.Z = block.ToFloat(); break; + case AvfxMagic.RevisedValuesColorR: RevisedValuesColor.X = block.ToFloat(); break; + case AvfxMagic.RevisedValuesColorG: RevisedValuesColor.Y = block.ToFloat(); break; + case AvfxMagic.RevisedValuesColorB: RevisedValuesColor.Z = block.ToFloat(); break; + case AvfxMagic.FadeEnabledX: FadeEnabledX = block.ToBool(); break; + case AvfxMagic.FadeInnerX: FadeInnerX = block.ToFloat(); break; + case AvfxMagic.FadeOuterX: FadeOuterX = block.ToFloat(); break; + case AvfxMagic.FadeEnabledY: FadeEnabledY = block.ToBool(); break; + case AvfxMagic.FadeInnerY: FadeInnerY = block.ToFloat(); break; + case AvfxMagic.FadeOuterY: FadeOuterY = block.ToFloat(); break; + case AvfxMagic.FadeEnabledZ: FadeEnabledZ = block.ToBool(); break; + case AvfxMagic.FadeInnerZ: FadeInnerZ = block.ToFloat(); break; + case AvfxMagic.FadeOuterZ: FadeOuterZ = block.ToFloat(); break; + case AvfxMagic.GlobalFogEnabled: GlobalFogEnabled = block.ToBool(); break; + case AvfxMagic.GlobalFogInfluence: GlobalFogInfluence = block.ToFloat(); break; + case AvfxMagic.LtsEnabled: LtsEnabled = block.ToBool(); break; + case AvfxMagic.NumSchedulers: Schedulers = new Block[block.ToUint()]; break; + case AvfxMagic.NumTimelines: Timelines = new Block[block.ToUint()]; break; + case AvfxMagic.NumEmitters: Emitters = new Block[block.ToUint()]; break; + case AvfxMagic.NumParticles: Particles = new Block[block.ToUint()]; break; + case AvfxMagic.NumEffectors: Effectors = new Block[block.ToUint()]; break; + case AvfxMagic.NumBinders: Binders = new Block[block.ToUint()]; break; + case AvfxMagic.NumTextures: Textures = new string[block.ToUint()]; break; + case AvfxMagic.NumModels: Models = new Block[block.ToUint()]; break; + case AvfxMagic.Scheduler: Schedulers[schedulerCount++] = block; break; + case AvfxMagic.Timeline: Timelines[timelineCount++] = block; break; + case AvfxMagic.Emitter: Emitters[emitterCount++] = block; break; + case AvfxMagic.Particle: Particles[particleCount++] = block; break; + case AvfxMagic.Effector: Effectors[effectorCount++] = block; break; + case AvfxMagic.Binder: Binders[binderCount++] = block; break; + case AvfxMagic.Texture: Textures[textureCount++] = block.ToString(); break; + case AvfxMagic.Model: Models[modelCount++] = block; break; + // @formatter:on + } + } + } + + + public byte[] Write() + { + using var m = new MemoryStream(512 * 1024); + using var w = new BinaryWriter(m); + + w.Write(AvfxMagic.AvfxBase); + var sizePos = w.BaseStream.Position; + w.Write(0u); + w.WriteBlock(AvfxMagic.Version, Version) + .WriteBlock(AvfxMagic.IsDelayFastParticle, IsDelayFastParticle) + .WriteBlock(AvfxMagic.IsDelayFastParticle, IsDelayFastParticle) + .WriteBlock(AvfxMagic.IsFitGround, IsFitGround) + .WriteBlock(AvfxMagic.IsTransformSkip, IsTransformSkip) + .WriteBlock(AvfxMagic.IsAllStopOnHide, IsAllStopOnHide) + .WriteBlock(AvfxMagic.CanBeClippedOut, CanBeClippedOut) + .WriteBlock(AvfxMagic.ClipBoxEnabled, ClipBoxEnabled) + .WriteBlock(AvfxMagic.ClipBoxX, ClipBox.X) + .WriteBlock(AvfxMagic.ClipBoxY, ClipBox.Y) + .WriteBlock(AvfxMagic.ClipBoxZ, ClipBox.Z) + .WriteBlock(AvfxMagic.ClipBoxSizeX, ClipBoxSize.X) + .WriteBlock(AvfxMagic.ClipBoxSizeY, ClipBoxSize.Y) + .WriteBlock(AvfxMagic.ClipBoxSizeZ, ClipBoxSize.Z) + .WriteBlock(AvfxMagic.BiasZmaxScale, BiasZmaxScale) + .WriteBlock(AvfxMagic.BiasZmaxDistance, BiasZmaxDistance) + .WriteBlock(AvfxMagic.IsCameraSpace, IsCameraSpace) + .WriteBlock(AvfxMagic.IsFullEnvLight, IsFullEnvLight) + .WriteBlock(AvfxMagic.IsClipOwnSetting, IsClipOwnSetting) + .WriteBlock(AvfxMagic.NearClipBegin, NearClipBegin) + .WriteBlock(AvfxMagic.NearClipEnd, NearClipEnd) + .WriteBlock(AvfxMagic.FarClipBegin, FarClipBegin) + .WriteBlock(AvfxMagic.FarClipEnd, FarClipEnd) + .WriteBlock(AvfxMagic.SoftParticleFadeRange, SoftParticleFadeRange) + .WriteBlock(AvfxMagic.SoftKeyOffset, SoftKeyOffset) + .WriteBlock(AvfxMagic.DrawLayerType, DrawLayerType) + .WriteBlock(AvfxMagic.DrawOrderType, DrawOrderType) + .WriteBlock(AvfxMagic.DirectionalLightSourceType, DirectionalLightSourceType) + .WriteBlock(AvfxMagic.PointLightsType1, PointLightsType1) + .WriteBlock(AvfxMagic.PointLightsType2, PointLightsType2) + .WriteBlock(AvfxMagic.RevisedValuesPosX, RevisedValuesPos.X) + .WriteBlock(AvfxMagic.RevisedValuesPosY, RevisedValuesPos.Y) + .WriteBlock(AvfxMagic.RevisedValuesPosZ, RevisedValuesPos.Z) + .WriteBlock(AvfxMagic.RevisedValuesRotX, RevisedValuesRot.X) + .WriteBlock(AvfxMagic.RevisedValuesRotY, RevisedValuesRot.Y) + .WriteBlock(AvfxMagic.RevisedValuesRotZ, RevisedValuesRot.Z) + .WriteBlock(AvfxMagic.RevisedValuesScaleX, RevisedValuesScale.X) + .WriteBlock(AvfxMagic.RevisedValuesScaleY, RevisedValuesScale.Y) + .WriteBlock(AvfxMagic.RevisedValuesScaleZ, RevisedValuesScale.Z) + .WriteBlock(AvfxMagic.RevisedValuesColorR, RevisedValuesColor.X) + .WriteBlock(AvfxMagic.RevisedValuesColorG, RevisedValuesColor.Y) + .WriteBlock(AvfxMagic.RevisedValuesColorB, RevisedValuesColor.Z) + .WriteBlock(AvfxMagic.FadeEnabledX, FadeEnabledX) + .WriteBlock(AvfxMagic.FadeInnerX, FadeInnerX) + .WriteBlock(AvfxMagic.FadeOuterX, FadeOuterX) + .WriteBlock(AvfxMagic.FadeEnabledY, FadeEnabledY) + .WriteBlock(AvfxMagic.FadeInnerY, FadeInnerY) + .WriteBlock(AvfxMagic.FadeOuterY, FadeOuterY) + .WriteBlock(AvfxMagic.FadeEnabledZ, FadeEnabledZ) + .WriteBlock(AvfxMagic.FadeInnerZ, FadeInnerZ) + .WriteBlock(AvfxMagic.FadeOuterZ, FadeOuterZ) + .WriteBlock(AvfxMagic.GlobalFogEnabled, GlobalFogEnabled) + .WriteBlock(AvfxMagic.GlobalFogInfluence, GlobalFogInfluence) + .WriteBlock(AvfxMagic.LtsEnabled, LtsEnabled) + .WriteBlock(AvfxMagic.NumSchedulers, (uint)Schedulers.Length) + .WriteBlock(AvfxMagic.NumTimelines, (uint)Timelines.Length) + .WriteBlock(AvfxMagic.NumEmitters, (uint)Emitters.Length) + .WriteBlock(AvfxMagic.NumParticles, (uint)Particles.Length) + .WriteBlock(AvfxMagic.NumEffectors, (uint)Effectors.Length) + .WriteBlock(AvfxMagic.NumBinders, (uint)Binders.Length) + .WriteBlock(AvfxMagic.NumTextures, (uint)Textures.Length) + .WriteBlock(AvfxMagic.NumModels, (uint)Models.Length); + foreach (var block in Schedulers) + w.WriteBlock(block); + foreach (var block in Timelines) + w.WriteBlock(block); + foreach (var block in Emitters) + w.WriteBlock(block); + foreach (var block in Particles) + w.WriteBlock(block); + foreach (var block in Effectors) + w.WriteBlock(block); + foreach (var block in Binders) + w.WriteBlock(block); + foreach (var texture in Textures) + w.WriteTextureBlock(texture); + foreach (var block in Models) + w.WriteBlock(block); + w.Seek((int)sizePos, SeekOrigin.Begin); + w.Write((uint)w.BaseStream.Length); + return m.ToArray(); + } +} diff --git a/Penumbra.GameData/Files/AvfxMagic.cs b/Penumbra.GameData/Files/AvfxMagic.cs new file mode 100644 index 00000000..1c315711 --- /dev/null +++ b/Penumbra.GameData/Files/AvfxMagic.cs @@ -0,0 +1,137 @@ +using System.IO; +using System.Numerics; +using System.Text; + +// ReSharper disable ShiftExpressionZeroLeftOperand + +namespace Penumbra.GameData.Files; + +public static class AvfxMagic +{ + public const uint AvfxBase = ('A' << 24) | ('V' << 16) | ('F' << 8) | (uint)'X'; + public const uint Version = (000 << 24) | ('V' << 16) | ('e' << 8) | (uint)'r'; + public const uint IsDelayFastParticle = ('b' << 24) | ('D' << 16) | ('F' << 8) | (uint)'P'; + public const uint IsFitGround = (000 << 24) | ('b' << 16) | ('F' << 8) | (uint)'G'; + public const uint IsTransformSkip = (000 << 24) | ('b' << 16) | ('T' << 8) | (uint)'S'; + public const uint IsAllStopOnHide = ('b' << 24) | ('A' << 16) | ('S' << 8) | (uint)'H'; + public const uint CanBeClippedOut = ('b' << 24) | ('C' << 16) | ('B' << 8) | (uint)'C'; + public const uint ClipBoxEnabled = ('b' << 24) | ('C' << 16) | ('u' << 8) | (uint)'l'; + public const uint ClipBoxX = ('C' << 24) | ('B' << 16) | ('P' << 8) | (uint)'x'; + public const uint ClipBoxY = ('C' << 24) | ('B' << 16) | ('P' << 8) | (uint)'y'; + public const uint ClipBoxZ = ('C' << 24) | ('B' << 16) | ('P' << 8) | (uint)'z'; + public const uint ClipBoxSizeX = ('C' << 24) | ('B' << 16) | ('S' << 8) | (uint)'x'; + public const uint ClipBoxSizeY = ('C' << 24) | ('B' << 16) | ('S' << 8) | (uint)'y'; + public const uint ClipBoxSizeZ = ('C' << 24) | ('B' << 16) | ('S' << 8) | (uint)'z'; + public const uint BiasZmaxScale = ('Z' << 24) | ('B' << 16) | ('M' << 8) | (uint)'s'; + public const uint BiasZmaxDistance = ('Z' << 24) | ('B' << 16) | ('M' << 8) | (uint)'d'; + public const uint IsCameraSpace = ('b' << 24) | ('C' << 16) | ('m' << 8) | (uint)'S'; + public const uint IsFullEnvLight = ('b' << 24) | ('F' << 16) | ('E' << 8) | (uint)'L'; + public const uint IsClipOwnSetting = ('b' << 24) | ('O' << 16) | ('S' << 8) | (uint)'t'; + public const uint NearClipBegin = (000 << 24) | ('N' << 16) | ('C' << 8) | (uint)'B'; + public const uint NearClipEnd = (000 << 24) | ('N' << 16) | ('C' << 8) | (uint)'E'; + public const uint FarClipBegin = (000 << 24) | ('F' << 16) | ('C' << 8) | (uint)'B'; + public const uint FarClipEnd = (000 << 24) | ('F' << 16) | ('C' << 8) | (uint)'E'; + public const uint SoftParticleFadeRange = ('S' << 24) | ('P' << 16) | ('F' << 8) | (uint)'R'; + public const uint SoftKeyOffset = (000 << 24) | ('S' << 16) | ('K' << 8) | (uint)'O'; + public const uint DrawLayerType = ('D' << 24) | ('w' << 16) | ('L' << 8) | (uint)'y'; + public const uint DrawOrderType = ('D' << 24) | ('w' << 16) | ('O' << 8) | (uint)'T'; + public const uint DirectionalLightSourceType = ('D' << 24) | ('L' << 16) | ('S' << 8) | (uint)'T'; + public const uint PointLightsType1 = ('P' << 24) | ('L' << 16) | ('1' << 8) | (uint)'S'; + public const uint PointLightsType2 = ('P' << 24) | ('L' << 16) | ('2' << 8) | (uint)'S'; + public const uint RevisedValuesPosX = ('R' << 24) | ('v' << 16) | ('P' << 8) | (uint)'x'; + public const uint RevisedValuesPosY = ('R' << 24) | ('v' << 16) | ('P' << 8) | (uint)'y'; + public const uint RevisedValuesPosZ = ('R' << 24) | ('v' << 16) | ('P' << 8) | (uint)'z'; + public const uint RevisedValuesRotX = ('R' << 24) | ('v' << 16) | ('R' << 8) | (uint)'x'; + public const uint RevisedValuesRotY = ('R' << 24) | ('v' << 16) | ('R' << 8) | (uint)'y'; + public const uint RevisedValuesRotZ = ('R' << 24) | ('v' << 16) | ('R' << 8) | (uint)'z'; + public const uint RevisedValuesScaleX = ('R' << 24) | ('v' << 16) | ('S' << 8) | (uint)'x'; + public const uint RevisedValuesScaleY = ('R' << 24) | ('v' << 16) | ('S' << 8) | (uint)'y'; + public const uint RevisedValuesScaleZ = ('R' << 24) | ('v' << 16) | ('S' << 8) | (uint)'z'; + public const uint RevisedValuesColorR = (000 << 24) | ('R' << 16) | ('v' << 8) | (uint)'R'; + public const uint RevisedValuesColorG = (000 << 24) | ('R' << 16) | ('v' << 8) | (uint)'G'; + public const uint RevisedValuesColorB = (000 << 24) | ('R' << 16) | ('v' << 8) | (uint)'B'; + public const uint FadeEnabledX = ('A' << 24) | ('F' << 16) | ('X' << 8) | (uint)'e'; + public const uint FadeInnerX = ('A' << 24) | ('F' << 16) | ('X' << 8) | (uint)'i'; + public const uint FadeOuterX = ('A' << 24) | ('F' << 16) | ('X' << 8) | (uint)'o'; + public const uint FadeEnabledY = ('A' << 24) | ('F' << 16) | ('Y' << 8) | (uint)'e'; + public const uint FadeInnerY = ('A' << 24) | ('F' << 16) | ('Y' << 8) | (uint)'i'; + public const uint FadeOuterY = ('A' << 24) | ('F' << 16) | ('Y' << 8) | (uint)'o'; + public const uint FadeEnabledZ = ('A' << 24) | ('F' << 16) | ('Z' << 8) | (uint)'e'; + public const uint FadeInnerZ = ('A' << 24) | ('F' << 16) | ('Z' << 8) | (uint)'i'; + public const uint FadeOuterZ = ('A' << 24) | ('F' << 16) | ('Z' << 8) | (uint)'o'; + public const uint GlobalFogEnabled = ('b' << 24) | ('G' << 16) | ('F' << 8) | (uint)'E'; + public const uint GlobalFogInfluence = ('G' << 24) | ('F' << 16) | ('I' << 8) | (uint)'M'; + public const uint LtsEnabled = ('b' << 24) | ('L' << 16) | ('T' << 8) | (uint)'S'; + public const uint NumSchedulers = ('S' << 24) | ('c' << 16) | ('C' << 8) | (uint)'n'; + public const uint NumTimelines = ('T' << 24) | ('l' << 16) | ('C' << 8) | (uint)'n'; + public const uint NumEmitters = ('E' << 24) | ('m' << 16) | ('C' << 8) | (uint)'n'; + public const uint NumParticles = ('P' << 24) | ('r' << 16) | ('C' << 8) | (uint)'n'; + public const uint NumEffectors = ('E' << 24) | ('f' << 16) | ('C' << 8) | (uint)'n'; + public const uint NumBinders = ('B' << 24) | ('d' << 16) | ('C' << 8) | (uint)'n'; + public const uint NumTextures = ('T' << 24) | ('x' << 16) | ('C' << 8) | (uint)'n'; + public const uint NumModels = ('M' << 24) | ('d' << 16) | ('C' << 8) | (uint)'n'; + public const uint Scheduler = ('S' << 24) | ('c' << 16) | ('h' << 8) | (uint)'d'; + public const uint Timeline = ('T' << 24) | ('m' << 16) | ('L' << 8) | (uint)'n'; + public const uint Emitter = ('E' << 24) | ('m' << 16) | ('i' << 8) | (uint)'t'; + public const uint Particle = ('P' << 24) | ('t' << 16) | ('c' << 8) | (uint)'l'; + public const uint Effector = ('E' << 24) | ('f' << 16) | ('c' << 8) | (uint)'t'; + public const uint Binder = ('B' << 24) | ('i' << 16) | ('n' << 8) | (uint)'d'; + public const uint Texture = (000 << 24) | ('T' << 16) | ('e' << 8) | (uint)'x'; + public const uint Model = ('M' << 24) | ('o' << 16) | ('d' << 8) | (uint)'l'; + + internal static uint RoundTo4(this uint size) + { + var rest = size & 0b11u; + return rest > 0 ? (size & ~0b11u) + 4u : size; + } + + internal static BinaryWriter WriteTextureBlock(this BinaryWriter bw, string texture) + { + bw.Write(Texture); + var bytes = Encoding.UTF8.GetBytes(texture); + var size = (uint)bytes.Length + 1u; + bw.Write(size); + bw.Write(bytes); + bw.Write((byte)0); + for (var end = size.RoundTo4(); size < end; ++size) + bw.Write((byte)0); + return bw; + } + + internal static BinaryWriter WriteBlock(this BinaryWriter bw, AvfxFile.Block block) + { + bw.Write(block.Name); + bw.Write(block.Size); + bw.Write(block.Data); + return bw; + } + + internal static BinaryWriter WriteBlock(this BinaryWriter bw, uint magic, uint value) + { + bw.Write(magic); + bw.Write(4u); + bw.Write(value); + return bw; + } + + internal static BinaryWriter WriteBlock(this BinaryWriter bw, uint magic, bool value) + { + bw.Write(magic); + bw.Write(4u); + bw.Write(value ? 1u : 0u); + return bw; + } + + internal static BinaryWriter WriteBlock(this BinaryWriter bw, uint magic, float value) + { + bw.Write(magic); + bw.Write(4u); + bw.Write(value); + return bw; + } + + internal static BinaryWriter WriteBlock(this BinaryWriter bw, uint magicX, uint magicY, uint magicZ, Vector3 value) + => bw.WriteBlock(magicX, value.X) + .WriteBlock(magicY, value.Y) + .WriteBlock(magicZ, value.Z); +}