diff --git a/ME3Tweaks.Wwiser.Tests/ActionTests/ActionTypeTests.cs b/ME3Tweaks.Wwiser.Tests/ActionTests/ActionTypeTests.cs new file mode 100644 index 0000000..bb29d81 --- /dev/null +++ b/ME3Tweaks.Wwiser.Tests/ActionTests/ActionTypeTests.cs @@ -0,0 +1,37 @@ +using ME3Tweaks.Wwiser.Model.Action; + +namespace ME3Tweaks.Wwiser.Tests.ActionTests; + +public class ActionTypeTests +{ + [TestCase(0x01, ActionTypeValue.Stop)] + [TestCase(0x09, ActionTypeValue.SetPitch2)] + [TestCase(0x0A, ActionTypeValue.SetBusVolume1)] + [TestCase(0x0C, ActionTypeValue.SetLFE1)] + [TestCase(0x0E, ActionTypeValue.SetLPF1)] + public void ActionTypeParsesAndReserializes_V56(int hex, ActionTypeValue expected) + { + var (_, result) = TestHelpers.Deserialize((byte)hex, 56); + Assert.That(result.Value, Is.EqualTo(expected)); + + var reserialized = TestHelpers.Serialize(result, 56); + Assert.That(reserialized[0], Is.EqualTo(hex)); + } + + [TestCase(0x01, ActionTypeValue.Stop)] + [TestCase(0x09, ActionTypeValue.SetPitch2)] + [TestCase(0x0A, ActionTypeValue.SetNone1)] + [TestCase(0x0B, ActionTypeValue.SetNone2)] + [TestCase(0x0C, ActionTypeValue.SetBusVolume1)] + [TestCase(0x0E, ActionTypeValue.SetLPF1)] + [TestCase(0x19, ActionTypeValue.SetSwitch)] + [TestCase(0x1A, ActionTypeValue.BypassFX1)] + public void ActionTypeParsesAndReserializes_V72(byte hex, ActionTypeValue expected) + { + var (_, result) = TestHelpers.Deserialize(hex, 72); + Assert.That(result.Value, Is.EqualTo(expected)); + + var reserialized = TestHelpers.Serialize(result, 72); + Assert.That(reserialized[0], Is.EqualTo(hex)); + } +} \ No newline at end of file diff --git a/ME3Tweaks.Wwiser/ME3Tweaks.Wwiser.csproj b/ME3Tweaks.Wwiser/ME3Tweaks.Wwiser.csproj index 22633a2..43d189d 100644 --- a/ME3Tweaks.Wwiser/ME3Tweaks.Wwiser.csproj +++ b/ME3Tweaks.Wwiser/ME3Tweaks.Wwiser.csproj @@ -11,4 +11,8 @@ + + + + diff --git a/ME3Tweaks.Wwiser/Model/Action/ActionType.cs b/ME3Tweaks.Wwiser/Model/Action/ActionType.cs new file mode 100644 index 0000000..8a055fc --- /dev/null +++ b/ME3Tweaks.Wwiser/Model/Action/ActionType.cs @@ -0,0 +1,129 @@ +using BinarySerialization; + +namespace ME3Tweaks.Wwiser.Model.Action; + +public class ActionType : IBinarySerializable +{ + [Ignore] + public ActionTypeValue Value { get; set; } + + public void Serialize(Stream stream, Endianness endianness, BinarySerializationContext serializationContext) + { + var version = serializationContext.FindAncestor().Version; + byte output; + if (version <= 65) + { + output = Value switch + { + ActionTypeValue.Event1 => 0x20, + ActionTypeValue.Event2 => 0x30, + ActionTypeValue.Event3 => 0x40, + ActionTypeValue.Duck => 0x50, + ActionTypeValue.SetSwitch => 0x60, + ActionTypeValue.SetRTPC => 0x61, + ActionTypeValue.BypassFX1 => 0x70, + ActionTypeValue.BypassFX2 => 0x80, + ActionTypeValue.Break => 0x90, + ActionTypeValue.Trigger => 0xA0, + ActionTypeValue.Seek => 0xB0, + >= ActionTypeValue.SetBusVolume1 => (byte)(Value - 2), + _ => (byte)Value + }; + } + else + { + output = Value switch + { + >= ActionTypeValue.BypassFX1 => (byte)(Value - 3), + >= ActionTypeValue.SetLPF1 => (byte)(Value - 2), + _ => (byte)Value + }; + } + stream.WriteByte(output); + } + + public void Deserialize(Stream stream, Endianness endianness, BinarySerializationContext serializationContext) + { + var version = serializationContext.FindAncestor().Version; + var value = (byte)stream.ReadByte(); + if (version <= 65) + { + Value = value switch + { + 0x20 => ActionTypeValue.Event1, + 0x30 => ActionTypeValue.Event2, + 0x40 => ActionTypeValue.Event3, + 0x50 => ActionTypeValue.Duck, + 0x60 => ActionTypeValue.SetSwitch, + 0x61 => ActionTypeValue.SetRTPC, + 0x70 => ActionTypeValue.BypassFX1, + 0x80 => ActionTypeValue.BypassFX2, + 0x90 => ActionTypeValue.Break, + 0xA0 => ActionTypeValue.Trigger, + 0xB0 => ActionTypeValue.Seek, + >= 0x0A => (ActionTypeValue)(value + 2), + _ => (ActionTypeValue)value + }; + } + else + { + Value = value switch + { + >= 0x1A => (ActionTypeValue)(value + 3), + >= 0x0E => (ActionTypeValue)(value + 2), + _ => (ActionTypeValue)value + }; + } + } +} + +public enum ActionTypeValue : byte +{ + Unknown, + Stop, + Pause, + Resume, + Play, + PlayAndContinue, + Mute1, + Mute2, + SetPitch1, + SetPitch2, + SetNone1, + SetNone2, + SetBusVolume1, + SetBusVolume2, + SetLFE1, + SetLFE2, + SetLPF1, + SetLPF2, + UseState1, + UseState2, + SetState, + SetGameParameter1, + SetGameParameter2, + Event1, + Event2, + Event3, + Duck, + SetSwitch, + SetRTPC, + BypassFX1, + BypassFX2, + Break, + Trigger, + Seek, + Release, + SetHPF1, + PlayEvent, + ResetPlaylist, + PlayEventUnknown, + SetHPF2, + SetFX1, + SetFX2, + BypassFX3, + BypassFX4, + BypassFX5, + BypassFX6, + BypassFX7 +} \ No newline at end of file diff --git a/ME3Tweaks.Wwiser/Model/Hierarchy/ActorMixer.cs b/ME3Tweaks.Wwiser/Model/Hierarchy/ActorMixer.cs index d416ce4..207e5f4 100644 --- a/ME3Tweaks.Wwiser/Model/Hierarchy/ActorMixer.cs +++ b/ME3Tweaks.Wwiser/Model/Hierarchy/ActorMixer.cs @@ -7,11 +7,7 @@ public class ActorMixer : HircItem { [FieldOrder(0)] public NodeBaseParameters NodeBaseParameters { get; set; } = new(); - - [FieldOrder(1)] - public uint ChildrenCount { get; set; } - [FieldOrder(2)] - [FieldCount(nameof(ChildrenCount))] - public List Children { get; set; } = new(); + [FieldOrder(1)] + public Children Children { get; set; } = new(); } \ No newline at end of file diff --git a/ME3Tweaks.Wwiser/Model/Hierarchy/Children.cs b/ME3Tweaks.Wwiser/Model/Hierarchy/Children.cs new file mode 100644 index 0000000..8007dc5 --- /dev/null +++ b/ME3Tweaks.Wwiser/Model/Hierarchy/Children.cs @@ -0,0 +1,30 @@ +using System.Collections; +using BinarySerialization; + +namespace ME3Tweaks.Wwiser.Model.Hierarchy; + +public class Children : IEnumerable +{ + [FieldOrder(1)] + public uint ChildrenCount { get; set; } + + [FieldOrder(2)] + [FieldCount(nameof(ChildrenCount))] + public List ChildrenValues { get; set; } = new(); + + public uint this[int i] + { + get => ChildrenValues[i]; + set => ChildrenValues[i] = value; + } + + public IEnumerator GetEnumerator() + { + return ChildrenValues.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} \ No newline at end of file diff --git a/ME3Tweaks.Wwiser/Model/Hierarchy/HircTypeFactory.cs b/ME3Tweaks.Wwiser/Model/Hierarchy/HircTypeFactory.cs index 78e71cd..c6cb5f4 100644 --- a/ME3Tweaks.Wwiser/Model/Hierarchy/HircTypeFactory.cs +++ b/ME3Tweaks.Wwiser/Model/Hierarchy/HircTypeFactory.cs @@ -9,9 +9,11 @@ public class HircTypeFactory : ISubtypeFactory private static readonly Dictionary TypeToEnum = new() { { typeof(Event), HircType.Event }, - {typeof(Attenuation), HircType.Attenuation }, + { typeof(Attenuation), HircType.Attenuation }, { typeof(FxShareSet), HircType.FxShareSet }, - { typeof(FxCustom), HircType.FxCustom} + { typeof(FxCustom), HircType.FxCustom }, + { typeof(Sound), HircType.Sound }, + { typeof(ActorMixer), HircType.ActorMixer }, }; public bool TryGetKey(Type valueType, [UnscopedRef] out object key) @@ -40,6 +42,8 @@ public bool TryGetType(object key, [UnscopedRef] out Type type) HircType.Attenuation => typeof(Attenuation), HircType.FxShareSet => typeof(FxShareSet), HircType.FxCustom => typeof(FxCustom), + HircType.Sound => typeof(Sound), + HircType.ActorMixer => typeof(ActorMixer), _ => typeof(HircItem) }; return true; diff --git a/ME3Tweaks.Wwiser/Model/Hierarchy/RandSeqContainer.cs b/ME3Tweaks.Wwiser/Model/Hierarchy/RandSeqContainer.cs new file mode 100644 index 0000000..4dc4cf9 --- /dev/null +++ b/ME3Tweaks.Wwiser/Model/Hierarchy/RandSeqContainer.cs @@ -0,0 +1,200 @@ +using BinarySerialization; +using ME3Tweaks.Wwiser.Attributes; +using ME3Tweaks.Wwiser.Model.Hierarchy; +using ME3Tweaks.Wwiser.Model.ParameterNode; + +namespace ME3Tweaks.Wwiser.Model; + +public class RandSeqContainer : HircItem +{ + [FieldOrder(0)] + public NodeBaseParameters NodeBaseParameters { get; set; } = new(); + + [FieldOrder(1)] + public ushort LoopCount { get; set; } + + [FieldOrder(2)] + [SerializeWhenVersion(72, ComparisonOperator.GreaterThan)] + public ushort LoopModMin { get; set; } + + [FieldOrder(3)] + [SerializeWhenVersion(72, ComparisonOperator.GreaterThan)] + public ushort LoopModMax { get; set; } + + [FieldOrder(4)] + public float TransitionTime { get; set; } + + [FieldOrder(5)] + public float TransitionTimeModMin { get; set; } + + [FieldOrder(6)] + public float TransitionTimeModMax { get; set; } + + [FieldOrder(7)] + public ushort AvoidRepeatCount { get; set; } + + [FieldOrder(8)] + [SerializeWhenVersion(36)] + public ushort Unknown { get; set; } + + //TODO: There's some bullshit on V44 and V45 here - relevant for ME2! + + [FieldOrder(10)] + public TransitionMode TransitionMode { get; set; } + + [FieldOrder(11)] + public RandomMode RandomMode { get; set; } + + [FieldOrder(12)] + public ContainerMode Mode { get; set; } + + [FieldOrder(13)] + public RanSeqFlags RanSeqFlags { get; set; } = new(); + + [FieldOrder(14)] + public Children Children { get; set; } = new(); +} + +public class RanSeqFlags : IBinarySerializable +{ + [Ignore] + public bool IsUsingWeight { get; set; } + + [Ignore] + public bool ResetPlayListAtEachPlay { get; set; } + + [Ignore] + public bool IsRestartBackward { get; set; } + + [Ignore] + public bool IsContinuous { get; set; } + + [Ignore] + public bool IsGlobal { get; set; } + + public void Serialize(Stream stream, Endianness endianness, BinarySerializationContext serializationContext) + { + var version = serializationContext.FindAncestor().Version; + if (version <= 89) + { + stream.WriteBoolByte(IsUsingWeight); + stream.WriteBoolByte(ResetPlayListAtEachPlay); + stream.WriteBoolByte(IsRestartBackward); + stream.WriteBoolByte(IsContinuous); + stream.WriteBoolByte(IsGlobal); + } + else + { + RanSeqInner f = 0x0; + if (IsUsingWeight) f |= RanSeqInner.IsUsingWeight; + if (ResetPlayListAtEachPlay) f |= RanSeqInner.ResetPlayListAtEachEnd; + if (IsRestartBackward) f |= RanSeqInner.IsRestartBackward; + if (IsContinuous) f |= RanSeqInner.IsContinuous; + if (IsGlobal) f |= RanSeqInner.IsGlobal; + stream.WriteByte((byte)f); + } + } + + public void Deserialize(Stream stream, Endianness endianness, BinarySerializationContext serializationContext) + { + var version = serializationContext.FindAncestor().Version; + if (version <= 89) + { + IsUsingWeight = stream.ReadBoolByte(); + ResetPlayListAtEachPlay = stream.ReadBoolByte(); + IsRestartBackward = stream.ReadBoolByte(); + IsContinuous = stream.ReadBoolByte(); + IsGlobal = stream.ReadBoolByte(); + } + else + { + var f = (RanSeqInner)stream.ReadByte(); + IsUsingWeight = f.HasFlag(RanSeqInner.IsUsingWeight); + ResetPlayListAtEachPlay = f.HasFlag(RanSeqInner.ResetPlayListAtEachEnd); + IsRestartBackward = f.HasFlag(RanSeqInner.IsRestartBackward); + IsContinuous = f.HasFlag(RanSeqInner.IsContinuous); + IsGlobal = f.HasFlag(RanSeqInner.IsGlobal); + } + } + + [Flags] + private enum RanSeqInner : byte + { + IsUsingWeight = 1 << 0, + ResetPlayListAtEachEnd = 1 << 1, + IsRestartBackward = 1 << 2, + IsContinuous = 1 << 3, + IsGlobal = 1 << 4 + } +} + +public class Playlist +{ + [FieldOrder(0)] + public ushort PlaylistLength { get; set; } + + [FieldOrder(1)] + [FieldCount(nameof(PlaylistLength))] + public List Items { get; set; } = new(); +} + +public class PlaylistItem : IBinarySerializable, IAkIdentifiable +{ + [Ignore] + public uint Id { get; set; } + + [Ignore] + public int Weight { get; set; } + + public void Serialize(Stream stream, Endianness endianness, BinarySerializationContext serializationContext) + { + var version = serializationContext.FindAncestor().Version; + stream.Write(BitConverter.GetBytes(Id)); + if (version <= 56) + { + stream.WriteByte((byte)Weight); + } + else + { + stream.Write(BitConverter.GetBytes(Weight)); + } + } + + public void Deserialize(Stream stream, Endianness endianness, BinarySerializationContext serializationContext) + { + var version = serializationContext.FindAncestor().Version; + if (version <= 56) + { + Weight = stream.ReadByte(); + } + else + { + Span span = stackalloc byte[4]; + var read = stream.Read(span); + if (read != 4) throw new Exception(); + Weight = BitConverter.ToInt32(span); + } + } +} + +public enum TransitionMode : byte +{ + Disabled, + CrossFadeAmp, + CrossFadePower, + Delay, + SampleAccurate, + TriggerRate +} + +public enum RandomMode : byte +{ + Normal, + Shuffle +} + +public enum ContainerMode : byte +{ + Random, + Sequence +} \ No newline at end of file