diff --git a/AssetStudio/Classes/AnimationClip.cs b/AssetStudio/Classes/AnimationClip.cs index 886d535..29b26fa 100644 --- a/AssetStudio/Classes/AnimationClip.cs +++ b/AssetStudio/Classes/AnimationClip.cs @@ -6,7 +6,7 @@ using System.Runtime.InteropServices; namespace AssetStudio { - public class Keyframe + public class Keyframe : IYAMLExportable where T : IYAMLExportable { public float time; public T value; @@ -16,6 +16,16 @@ namespace AssetStudio public T inWeight; public T outWeight; + public Keyframe(float time, T value, T inSlope, T outSlope, T weight) + { + this.time = time; + this.value = value; + this.inSlope = inSlope; + this.outSlope = outSlope; + weightedMode = 0; + inWeight = weight; + outWeight = weight; + } public Keyframe(ObjectReader reader, Func readerFunc) { @@ -30,23 +40,61 @@ namespace AssetStudio outWeight = readerFunc(); } } + + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.AddSerializedVersion(ToSerializedVersion(version)); + node.Add(nameof(time), time); + node.Add(nameof(value), value.ExportYAML(version)); + node.Add(nameof(inSlope), inSlope.ExportYAML(version)); + node.Add(nameof(outSlope), outSlope.ExportYAML(version)); + if (version[0] >= 2018) //2018 and up + { + node.Add(nameof(weightedMode), weightedMode); + node.Add(nameof(inWeight), inWeight.ExportYAML(version)); + node.Add(nameof(outWeight), outWeight.ExportYAML(version)); + } + return node; + } + + private int ToSerializedVersion(int[] version) + { + if (version[0] >= 2018) //2018 and up + { + return 3; + } + else if (version[0] > 5 || (version[0] == 5 && version[1] >= 5)) + { + return 2; + } + return 1; + } } - public class AnimationCurve + public class AnimationCurve : IYAMLExportable where T : IYAMLExportable { - public Keyframe[] m_Curve; + public List> m_Curve; public int m_PreInfinity; public int m_PostInfinity; public int m_RotationOrder; + public AnimationCurve() + { + m_PreInfinity = 2; + m_PostInfinity = 2; + m_RotationOrder = 4; + m_Curve = new List>(); + } + public AnimationCurve(ObjectReader reader, Func readerFunc) { var version = reader.version; int numCurves = reader.ReadInt32(); - m_Curve = new Keyframe[numCurves]; + m_Curve = new List>(numCurves); for (int i = 0; i < numCurves; i++) { - m_Curve[i] = new Keyframe(reader, readerFunc); + m_Curve.Add(new Keyframe(reader, readerFunc)); } m_PreInfinity = reader.ReadInt32(); @@ -56,21 +104,76 @@ namespace AssetStudio m_RotationOrder = reader.ReadInt32(); } } + + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.AddSerializedVersion(ToSerializedVersion(version)); + node.Add(nameof(m_Curve), m_Curve.ExportYAML(version)); + node.Add(nameof(m_PreInfinity), m_PreInfinity); + node.Add(nameof(m_PostInfinity), m_PostInfinity); + if (version[0] > 5 || (version[0] == 5 && version[1] >= 3))//5.3 and up + { + node.Add(nameof(m_RotationOrder), m_RotationOrder); + } + return node; + } + + private int ToSerializedVersion(int[] version) + { + if (version[0] > 2 || (version[0] == 2 && version[1] >= 1)) + { + return 2; + } + return 1; + } } - public class QuaternionCurve + public class QuaternionCurve : IYAMLExportable { public AnimationCurve curve; public string path; + public QuaternionCurve(string path) + { + curve = new AnimationCurve(); + this.path = path; + } + public QuaternionCurve(ObjectReader reader) { curve = new AnimationCurve(reader, reader.ReadQuaternion); path = reader.ReadAlignedString(); } + + public YAMLNode ExportYAML(int[] version) + { + YAMLMappingNode node = new YAMLMappingNode(); + node.Add(nameof(curve), curve.ExportYAML(version)); + node.Add(nameof(path), path); + return node; + } + public override bool Equals(object obj) + { + if (obj is QuaternionCurve quaternionCurve) + { + return path == quaternionCurve.path; + } + return false; + } + + public override int GetHashCode() + { + int hash = 199; + unchecked + { + hash = 617 + hash * path.GetHashCode(); + } + return hash; + } } - public class PackedFloatVector + public class PackedFloatVector : IYAMLExportable { public uint m_NumItems; public float m_Range; @@ -92,6 +195,17 @@ namespace AssetStudio reader.AlignStream(); } + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.Add(nameof(m_NumItems), m_NumItems); + node.Add(nameof(m_Range), m_Range); + node.Add(nameof(m_Start), m_Start); + node.Add(nameof(m_Data), m_Data.ExportYAML()); + node.Add(nameof(m_BitSize), m_BitSize); + return node; + } + public float[] UnpackFloats(int itemCountInChunk, int chunkStride, int start = 0, int numChunks = -1) { int bitPos = m_BitSize * start; @@ -131,7 +245,7 @@ namespace AssetStudio } } - public class PackedIntVector + public class PackedIntVector : IYAMLExportable { public uint m_NumItems; public byte[] m_Data; @@ -148,6 +262,14 @@ namespace AssetStudio m_BitSize = reader.ReadByte(); reader.AlignStream(); } + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.Add(nameof(m_NumItems), m_NumItems); + node.Add(nameof(m_Data), m_Data.ExportYAML()); + node.Add(nameof(m_BitSize), m_BitSize); + return node; + } public int[] UnpackInts() { @@ -176,7 +298,7 @@ namespace AssetStudio } } - public class PackedQuatVector + public class PackedQuatVector : IYAMLExportable { public uint m_NumItems; public byte[] m_Data; @@ -191,6 +313,14 @@ namespace AssetStudio reader.AlignStream(); } + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.Add(nameof(m_NumItems), m_NumItems); + node.Add(nameof(m_Data), m_Data.ExportYAML()); + return node; + } + public Quaternion[] UnpackQuats() { var data = new Quaternion[m_NumItems]; @@ -256,7 +386,7 @@ namespace AssetStudio } } - public class CompressedAnimationCurve + public class CompressedAnimationCurve : IYAMLExportable { public string m_Path; public PackedIntVector m_Times; @@ -274,68 +404,174 @@ namespace AssetStudio m_PreInfinity = reader.ReadInt32(); m_PostInfinity = reader.ReadInt32(); } + + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.Add(nameof(m_Path), m_Path); + node.Add(nameof(m_Times), m_Times.ExportYAML(version)); + node.Add(nameof(m_Values), m_Values.ExportYAML(version)); + node.Add(nameof(m_Slopes), m_Slopes.ExportYAML(version)); + node.Add(nameof(m_PreInfinity), m_PreInfinity); + node.Add(nameof(m_PostInfinity), m_PostInfinity); + return node; + } } - public class Vector3Curve + public class Vector3Curve : IYAMLExportable { public AnimationCurve curve; public string path; + public Vector3Curve(string path) + { + curve = new AnimationCurve(); + this.path = path; + } + public Vector3Curve(ObjectReader reader) { curve = new AnimationCurve(reader, reader.ReadVector3); path = reader.ReadAlignedString(); } + + public YAMLNode ExportYAML(int[] version) + { + YAMLMappingNode node = new YAMLMappingNode(); + node.Add(nameof(curve), curve.ExportYAML(version)); + node.Add(nameof(path), path); + return node; + } + + public override bool Equals(object obj) + { + if (obj is Vector3Curve vector3Curve) + { + return path == vector3Curve.path; + } + return false; + } + + public override int GetHashCode() + { + int hash = 577; + unchecked + { + hash = 419 + hash * path.GetHashCode(); + } + return hash; + } } - public class FloatCurve + public class FloatCurve : IYAMLExportable { - public AnimationCurve curve; + public AnimationCurve curve; public string attribute; public string path; public ClassIDType classID; public PPtr script; + public FloatCurve(string path, string attribute, ClassIDType classID, PPtr script) + { + curve = new AnimationCurve(); + this.attribute = attribute; + this.path = path; + this.classID = classID; + this.script = script; + } public FloatCurve(ObjectReader reader) { - curve = new AnimationCurve(reader, reader.ReadSingle); + curve = new AnimationCurve(reader, reader.ReadFloat); attribute = reader.ReadAlignedString(); path = reader.ReadAlignedString(); classID = (ClassIDType)reader.ReadInt32(); script = new PPtr(reader); } + + public YAMLNode ExportYAML(int[] version) + { + YAMLMappingNode node = new YAMLMappingNode(); + node.Add(nameof(curve), curve.ExportYAML(version)); + node.Add(nameof(attribute), attribute); + node.Add(nameof(path), path); + node.Add(nameof(classID), (int)classID); + if (version[0] >= 2) + { + node.Add(nameof(script), script.ExportYAML(version)); + } + return node; + } + + public override bool Equals(object obj) + { + if (obj is FloatCurve floatCurve) + { + return attribute == floatCurve.attribute && path == floatCurve.path && classID == floatCurve.classID; + } + return false; + } + + public override int GetHashCode() + { + int hash = 17; + unchecked + { + hash = hash * 23 + path.GetHashCode(); + } + return hash; + } } - public class PPtrKeyframe + public class PPtrKeyframe : IYAMLExportable { public float time; public PPtr value; + public PPtrKeyframe(float time, PPtr value) + { + this.time = time; + this.value = value; + } public PPtrKeyframe(ObjectReader reader) { time = reader.ReadSingle(); value = new PPtr(reader); } + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.Add(nameof(time), time); + node.Add(nameof(value), value.ExportYAML(version)); + return node; + } } - public class PPtrCurve + public class PPtrCurve : IYAMLExportable { - public PPtrKeyframe[] curve; + public List curve; public string attribute; public string path; public int classID; public PPtr script; + public PPtrCurve(string path, string attribute, ClassIDType classID, PPtr script) + { + curve = new List(); + this.attribute = attribute; + this.path = path; + this.classID = (int)classID; + this.script = script; + } public PPtrCurve(ObjectReader reader) { int numCurves = reader.ReadInt32(); - curve = new PPtrKeyframe[numCurves]; + curve = new List(numCurves); for (int i = 0; i < numCurves; i++) { - curve[i] = new PPtrKeyframe(reader); + curve.Add(new PPtrKeyframe(reader)); } attribute = reader.ReadAlignedString(); @@ -343,9 +579,42 @@ namespace AssetStudio classID = reader.ReadInt32(); script = new PPtr(reader); } + + public YAMLNode ExportYAML(int[] version) + { + YAMLMappingNode node = new YAMLMappingNode(); + node.Add(nameof(curve), curve.ExportYAML(version)); + node.Add(nameof(attribute), attribute); + node.Add(nameof(path), path); + node.Add(nameof(classID), ((int)classID).ToString()); + node.Add(nameof(script), script.ExportYAML(version)); + return node; + } + + public override bool Equals(object obj) + { + if (obj is PPtrCurve pptrCurve) + { + return this == pptrCurve; + } + return false; + } + + public override int GetHashCode() + { + int hash = 113; + unchecked + { + hash = hash + 457 * attribute.GetHashCode(); + hash = hash * 433 + path.GetHashCode(); + hash = hash * 223 + classID.GetHashCode(); + hash = hash * 911 + script.GetHashCode(); + } + return hash; + } } - public class AABB + public class AABB : IYAMLExportable { public Vector3 m_Center; public Vector3 m_Extent; @@ -355,55 +624,64 @@ namespace AssetStudio m_Center = reader.ReadVector3(); m_Extent = reader.ReadVector3(); } - } - public class xform - { - public Vector3 t; - public Quaternion q; - public Vector3 s; - - public xform(ObjectReader reader) + public YAMLNode ExportYAML(int[] version) { - var version = reader.version; - t = version[0] > 5 || (version[0] == 5 && version[1] >= 4) ? reader.ReadVector3() : (Vector3)reader.ReadVector4();//5.4 and up - q = reader.ReadQuaternion(); - s = version[0] > 5 || (version[0] == 5 && version[1] >= 4) ? reader.ReadVector3() : (Vector3)reader.ReadVector4();//5.4 and up + var node = new YAMLMappingNode(); + node.Add(nameof(m_Center), m_Center.ExportYAML(version)); + node.Add(nameof(m_Extent), m_Extent.ExportYAML(version)); + return node; } } public class HandPose { - public xform m_GrabX; + public XForm m_GrabX; public float[] m_DoFArray; public float m_Override; public float m_CloseOpen; public float m_InOut; public float m_Grab; + public HandPose() { } public HandPose(ObjectReader reader) { - m_GrabX = new xform(reader); + m_GrabX = reader.ReadXForm(reader.version); m_DoFArray = reader.ReadSingleArray(); m_Override = reader.ReadSingle(); m_CloseOpen = reader.ReadSingle(); m_InOut = reader.ReadSingle(); m_Grab = reader.ReadSingle(); } + + public static HandPose ParseGI(ObjectReader reader) + { + var handPose = new HandPose(); + + handPose.m_GrabX = reader.ReadXForm(reader.version, true); + handPose.m_DoFArray = reader.ReadSingleArray(20); + handPose.m_Override = reader.ReadSingle(); + handPose.m_CloseOpen = reader.ReadSingle(); + handPose.m_InOut = reader.ReadSingle(); + handPose.m_Grab = reader.ReadSingle(); + + return handPose; + } } public class HumanGoal { - public xform m_X; + public XForm m_X; public float m_WeightT; public float m_WeightR; public Vector3 m_HintT; public float m_HintWeightT; + public HumanGoal() { } public HumanGoal(ObjectReader reader) { var version = reader.version; - m_X = new xform(reader); + m_X = reader.ReadXForm(reader.version); m_WeightT = reader.ReadSingle(); m_WeightR = reader.ReadSingle(); if (version[0] >= 5)//5.0 and up @@ -412,11 +690,28 @@ namespace AssetStudio m_HintWeightT = reader.ReadSingle(); } } + + public static HumanGoal ParseGI(ObjectReader reader) + { + var humanGoal = new HumanGoal(); + + humanGoal.m_X = reader.ReadXForm(reader.version, true); + humanGoal.m_WeightT = reader.ReadSingle(); + humanGoal.m_WeightR = reader.ReadSingle(); + + humanGoal.m_HintT = (Vector3)reader.ReadVector4(); + humanGoal.m_HintWeightT = reader.ReadSingle(); + + var m_HintR = (Vector3)reader.ReadVector4(); + var m_HintWeightR = reader.ReadSingle(); + + return humanGoal; + } } public class HumanPose { - public xform m_RootX; + public XForm m_RootX; public Vector3 m_LookAtPosition; public Vector4 m_LookAtWeight; public HumanGoal[] m_GoalArray; @@ -424,11 +719,12 @@ namespace AssetStudio public HandPose m_RightHandPose; public float[] m_DoFArray; public Vector3[] m_TDoFArray; + public HumanPose() { } public HumanPose(ObjectReader reader) { var version = reader.version; - m_RootX = new xform(reader); + m_RootX = reader.ReadXForm(reader.version); m_LookAtPosition = version[0] > 5 || (version[0] == 5 && version[1] >= 4) ? reader.ReadVector3() : (Vector3)reader.ReadVector4();//5.4 and up m_LookAtWeight = reader.ReadVector4(); @@ -454,11 +750,43 @@ namespace AssetStudio } } } + + public static HumanPose ParseGI(ObjectReader reader) + { + var version = reader.version; + var humanPose = new HumanPose(); + + humanPose.m_RootX = reader.ReadXForm(version, true); + humanPose.m_LookAtPosition = (Vector3)reader.ReadVector4(); + humanPose.m_LookAtWeight = reader.ReadVector4(); + + humanPose.m_GoalArray = new HumanGoal[4]; + for (int i = 0; i < 4; i++) + { + humanPose.m_GoalArray[i] = HumanGoal.ParseGI(reader); + } + + humanPose.m_LeftHandPose = HandPose.ParseGI(reader); + humanPose.m_RightHandPose = HandPose.ParseGI(reader); + + humanPose.m_DoFArray = reader.ReadSingleArray(0x37); + + humanPose.m_TDoFArray = new Vector3[0x15]; + for (int i = 0; i < 0x15; i++) + { + humanPose.m_TDoFArray[i] = (Vector3)reader.ReadVector4(); + } + + reader.Position += 4; + + return humanPose; + } } public class ACLClip { public byte[] m_ClipData; + public byte[] m_DatabaseData; public uint m_CurveCount; public uint m_ConstCurveCount; @@ -468,6 +796,7 @@ namespace AssetStudio m_CurveCount = 0; m_ConstCurveCount = 0; m_ClipData = Array.Empty(); + m_DatabaseData = Array.Empty(); } public void Read(ObjectReader reader) { @@ -488,18 +817,89 @@ namespace AssetStudio m_ConstCurveCount = reader.ReadUInt32(); } } + public void ParseGI(ObjectReader reader) + { + var aclTracksCount = (int)reader.ReadUInt64(); + var aclTracksOffset = reader.Position + reader.ReadInt64(); + var aclTracksCurveCount = reader.ReadUInt32(); + if (aclTracksOffset > reader.Length) + { + throw new IOException("Offset outside of range"); + } + + var pos = reader.Position; + reader.Position = aclTracksOffset; + + var tracksBytes = reader.ReadBytes(aclTracksCount); + reader.AlignStream(); + + using var tracksMS = new MemoryStream(); + tracksMS.Write(tracksBytes); + tracksMS.AlignStream(); + m_CurveCount = aclTracksCurveCount; + m_ClipData = tracksMS.ToArray(); + + reader.Position = pos; + + var aclDatabaseCount = reader.ReadInt32(); + var aclDatabaseOffset = reader.Position + reader.ReadInt64(); + var aclDatabaseCurveCount = (uint)reader.ReadUInt64(); + if (aclDatabaseOffset > reader.Length) + { + throw new IOException("Offset outside of range"); + } + + pos = reader.Position; + reader.Position = aclDatabaseOffset; + + var databaseBytes = reader.ReadBytes(aclDatabaseCount); + reader.AlignStream(); + + using var databaseMS = new MemoryStream(); + databaseMS.Write(databaseBytes); + databaseMS.AlignStream(); + + m_ConstCurveCount = aclDatabaseCurveCount; + m_DatabaseData = databaseMS.ToArray(); + + reader.Position = pos; + } } public class StreamedClip { public uint[] data; public uint curveCount; + public StreamedClip() { } public StreamedClip(ObjectReader reader) { data = reader.ReadUInt32Array(); curveCount = reader.ReadUInt32(); } + public static StreamedClip ParseGI(ObjectReader reader) + { + var streamedClipCount = (int)reader.ReadUInt64(); + var streamedClipOffset = reader.Position + reader.ReadInt64(); + var streamedClipCurveCount = (uint)reader.ReadUInt64(); + if (streamedClipOffset > reader.Length) + { + throw new IOException("Offset outside of range"); + } + + var pos = reader.Position; + reader.Position = streamedClipOffset; + + var streamedClip = new StreamedClip() + { + data = reader.ReadUInt32Array(streamedClipCount), + curveCount = streamedClipCurveCount + }; + + reader.Position = pos; + + return streamedClip; + } public class StreamedCurveKey { @@ -595,6 +995,7 @@ namespace AssetStudio public float m_SampleRate; public float m_BeginTime; public float[] m_SampleArray; + public DenseClip() { } public DenseClip(ObjectReader reader) { @@ -604,16 +1005,61 @@ namespace AssetStudio m_BeginTime = reader.ReadSingle(); m_SampleArray = reader.ReadSingleArray(); } + public static DenseClip ParseGI(ObjectReader reader) + { + var denseClip = new DenseClip(); + + denseClip.m_FrameCount = reader.ReadInt32(); + denseClip.m_CurveCount = reader.ReadUInt32(); + denseClip.m_SampleRate = reader.ReadSingle(); + denseClip.m_BeginTime = reader.ReadSingle(); + + var denseClipCount = (int)reader.ReadUInt64(); + var denseClipOffset = reader.Position + reader.ReadInt64(); + if (denseClipOffset > reader.Length) + { + throw new IOException("Offset outside of range"); + } + + var pos = reader.Position; + reader.Position = denseClipOffset; + + denseClip.m_SampleArray = reader.ReadSingleArray(denseClipCount); + + reader.Position = pos; + + return denseClip; + } } public class ConstantClip { public float[] data; + public ConstantClip() { } public ConstantClip(ObjectReader reader) { data = reader.ReadSingleArray(); } + public static ConstantClip ParseGI(ObjectReader reader) + { + var constantClipCount = (int)reader.ReadUInt64(); + var constantClipOffset = reader.Position + reader.ReadInt64(); + if (constantClipOffset > reader.Length) + { + throw new IOException("Offset outside of range"); + } + + var pos = reader.Position; + reader.Position = constantClipOffset; + + var constantClip = new ConstantClip(); + constantClip.data = reader.ReadSingleArray(constantClipCount); + + reader.Position = pos; + + return constantClip; + } } public class ValueConstant @@ -658,6 +1104,7 @@ namespace AssetStudio public DenseClip m_DenseClip; public ConstantClip m_ConstantClip; public ValueArrayConstant m_Binding; + public Clip() { } public Clip(ObjectReader reader) { @@ -681,6 +1128,27 @@ namespace AssetStudio m_Binding = new ValueArrayConstant(reader); } } + public static Clip ParseGI(ObjectReader reader) + { + var clipOffset = reader.Position + reader.ReadInt64(); + if (clipOffset > reader.Length) + { + throw new IOException("Offset outside of range"); + } + + var pos = reader.Position; + reader.Position = clipOffset; + + var clip = new Clip(); + clip.m_StreamedClip = StreamedClip.ParseGI(reader); + clip.m_DenseClip = DenseClip.ParseGI(reader); + clip.m_ConstantClip = ConstantClip.ParseGI(reader); + clip.m_ACLClip.ParseGI(reader); + + reader.Position = pos; + + return clip; + } public AnimationClipBindingConstant ConvertValueArrayToGenericBinding() { @@ -739,15 +1207,15 @@ namespace AssetStudio } } - public class ClipMuscleConstant + public class ClipMuscleConstant : IYAMLExportable { public HumanPose m_DeltaPose; - public xform m_StartX; - public xform m_StopX; - public xform m_LeftFootStartX; - public xform m_RightFootStartX; - public xform m_MotionStartX; - public xform m_MotionStopX; + public XForm m_StartX; + public XForm m_StopX; + public XForm m_LeftFootStartX; + public XForm m_RightFootStartX; + public XForm m_MotionStartX; + public XForm m_MotionStopX; public Vector3 m_AverageSpeed; public Clip m_Clip; public float m_StartTime; @@ -770,22 +1238,23 @@ namespace AssetStudio public bool m_KeepOriginalPositionY; public bool m_KeepOriginalPositionXZ; public bool m_HeightFromFeet; + public ClipMuscleConstant() { } public ClipMuscleConstant(ObjectReader reader) { var version = reader.version; m_DeltaPose = new HumanPose(reader); - m_StartX = new xform(reader); + m_StartX = reader.ReadXForm(reader.version); if (version[0] > 5 || (version[0] == 5 && version[1] >= 5))//5.5 and up { - m_StopX = new xform(reader); + m_StopX = reader.ReadXForm(reader.version); } - m_LeftFootStartX = new xform(reader); - m_RightFootStartX = new xform(reader); + m_LeftFootStartX = reader.ReadXForm(reader.version); + m_RightFootStartX = reader.ReadXForm(reader.version); if (version[0] < 5)//5.0 down { - m_MotionStartX = new xform(reader); - m_MotionStopX = new xform(reader); + m_MotionStartX = reader.ReadXForm(reader.version); + m_MotionStopX = reader.ReadXForm(reader.version); } m_AverageSpeed = version[0] > 5 || (version[0] == 5 && version[1] >= 4) ? reader.ReadVector3() : (Vector3)reader.ReadVector4();//5.4 and up m_Clip = new Clip(reader); @@ -831,10 +1300,115 @@ namespace AssetStudio m_HeightFromFeet = reader.ReadBoolean(); reader.AlignStream(); } + public static ClipMuscleConstant ParseGI(ObjectReader reader) + { + var version = reader.version; + var clipMuscleConstant = new ClipMuscleConstant(); + + clipMuscleConstant.m_DeltaPose = HumanPose.ParseGI(reader); + clipMuscleConstant.m_StartX = reader.ReadXForm(version, true); + clipMuscleConstant.m_StopX = reader.ReadXForm(version, true); + clipMuscleConstant.m_LeftFootStartX = reader.ReadXForm(version, true); + clipMuscleConstant.m_RightFootStartX = reader.ReadXForm(version, true); + + clipMuscleConstant.m_AverageSpeed = (Vector3)reader.ReadVector4(); + + clipMuscleConstant.m_Clip = Clip.ParseGI(reader); + + clipMuscleConstant.m_StartTime = reader.ReadSingle(); + clipMuscleConstant.m_StopTime = reader.ReadSingle(); + clipMuscleConstant.m_OrientationOffsetY = reader.ReadSingle(); + clipMuscleConstant.m_Level = reader.ReadSingle(); + clipMuscleConstant.m_CycleOffset = reader.ReadSingle(); + clipMuscleConstant.m_AverageAngularSpeed = reader.ReadSingle(); + + clipMuscleConstant.m_IndexArray = reader.ReadInt16Array(0xC8).Select(x => (int)x).ToArray(); + + var valueArrayDeltaCount = (int)reader.ReadUInt64(); + var valueArrayDeltaOffset = reader.Position + reader.ReadInt64(); + + if (valueArrayDeltaOffset > reader.Length) + { + throw new IOException("Offset outside of range"); + } + + var valueArrayReferencePoseCount = (int)reader.ReadUInt64(); + var valueArrayReferencePoseOffset = reader.Position + reader.ReadInt64(); + + if (valueArrayReferencePoseOffset > reader.Length) + { + throw new IOException("Offset outside of range"); + } + + clipMuscleConstant.m_Mirror = reader.ReadBoolean(); + clipMuscleConstant.m_LoopTime = reader.ReadBoolean(); + clipMuscleConstant.m_LoopBlend = reader.ReadBoolean(); + clipMuscleConstant.m_LoopBlendOrientation = reader.ReadBoolean(); + clipMuscleConstant.m_LoopBlendPositionY = reader.ReadBoolean(); + clipMuscleConstant.m_LoopBlendPositionXZ = reader.ReadBoolean(); + clipMuscleConstant.m_StartAtOrigin = reader.ReadBoolean(); + clipMuscleConstant.m_KeepOriginalOrientation = reader.ReadBoolean(); + clipMuscleConstant.m_KeepOriginalPositionY = reader.ReadBoolean(); + clipMuscleConstant.m_KeepOriginalPositionXZ = reader.ReadBoolean(); + clipMuscleConstant.m_HeightFromFeet = reader.ReadBoolean(); + reader.AlignStream(); + + if (valueArrayDeltaCount > 0) + { + reader.Position = valueArrayDeltaOffset; + clipMuscleConstant.m_ValueArrayDelta = new ValueDelta[valueArrayDeltaCount]; + for (int i = 0; i < valueArrayDeltaCount; i++) + { + clipMuscleConstant.m_ValueArrayDelta[i] = new ValueDelta(reader); + } + } + + if (valueArrayReferencePoseCount > 0) + { + reader.Position = valueArrayReferencePoseOffset; + clipMuscleConstant.m_ValueArrayReferencePose = reader.ReadSingleArray(valueArrayReferencePoseCount); + } + + return clipMuscleConstant; + } + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.AddSerializedVersion(ToSerializedVersion(version)); + node.Add(nameof(m_StartTime), m_StartTime); + node.Add(nameof(m_StopTime), m_StopTime); + node.Add(nameof(m_OrientationOffsetY), m_OrientationOffsetY); + node.Add(nameof(m_Level), m_Level); + node.Add(nameof(m_CycleOffset), m_CycleOffset); + node.Add(nameof(m_LoopTime), m_LoopTime); + node.Add(nameof(m_LoopBlend), m_LoopBlend); + node.Add(nameof(m_LoopBlendOrientation), m_LoopBlendOrientation); + node.Add(nameof(m_LoopBlendPositionY), m_LoopBlendPositionY); + node.Add(nameof(m_LoopBlendPositionXZ), m_LoopBlendPositionXZ); + node.Add(nameof(m_KeepOriginalOrientation), m_KeepOriginalOrientation); + node.Add(nameof(m_KeepOriginalPositionY), m_KeepOriginalPositionY); + node.Add(nameof(m_KeepOriginalPositionXZ), m_KeepOriginalPositionXZ); + node.Add(nameof(m_HeightFromFeet), m_HeightFromFeet); + node.Add(nameof(m_Mirror), m_Mirror); + return node; + } + private int ToSerializedVersion(int[] version) + { + if (version[0] > 5 || (version[0] == 5 && version[1] >= 6)) + { + return 3; + } + else if (version[0] > 4 || (version[0] == 4 && version[1] >= 3)) + { + return 2; + } + return 1; + } } - public class GenericBinding + public class GenericBinding : IYAMLExportable { + public int[] version; public uint path; public uint attribute; public PPtr script; @@ -847,7 +1421,7 @@ namespace AssetStudio public GenericBinding(ObjectReader reader) { - var version = reader.version; + version = reader.version; path = reader.ReadUInt32(); attribute = reader.ReadUInt32(); script = new PPtr(reader); @@ -867,9 +1441,21 @@ namespace AssetStudio } reader.AlignStream(); } + + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.Add(nameof(path), path); + node.Add(nameof(attribute), attribute); + node.Add(nameof(script), script.ExportYAML(version)); + node.Add("classID", ((int)typeID).ToString()); + node.Add(nameof(customType), customType); + node.Add(nameof(isPPtrCurve), isPPtrCurve); + return node; + } } - public class AnimationClipBindingConstant + public class AnimationClipBindingConstant : IYAMLExportable { public GenericBinding[] genericBindings; public PPtr[] pptrCurveMapping; @@ -893,6 +1479,14 @@ namespace AssetStudio } } + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.Add(nameof(genericBindings), genericBindings.ExportYAML(version)); + node.Add(nameof(pptrCurveMapping), pptrCurveMapping.ExportYAML(version)); + return node; + } + public GenericBinding FindBinding(int index) { int curves = 0; @@ -929,7 +1523,7 @@ namespace AssetStudio } } - public class AnimationEvent + public class AnimationEvent : IYAMLExportable { public float time; public string functionName; @@ -954,6 +1548,19 @@ namespace AssetStudio } messageOptions = reader.ReadInt32(); } + + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.Add(nameof(time), time); + node.Add(nameof(functionName), functionName); + node.Add(nameof(data), data); + node.Add(nameof(objectReferenceParameter), objectReferenceParameter.ExportYAML(version)); + node.Add(nameof(floatParameter), floatParameter); + node.Add(nameof(intParameter), intParameter); + node.Add(nameof(messageOptions), messageOptions); + return node; + } } public enum AnimationType @@ -985,7 +1592,9 @@ namespace AssetStudio public ClipMuscleConstant m_MuscleClip; public AnimationClipBindingConstant m_ClipBindingConstant; public AnimationEvent[] m_Events; + public StreamingInfo m_StreamData; + private bool hasStreamingInfo = false; public AnimationClip(ObjectReader reader) : base(reader) { @@ -1072,8 +1681,26 @@ namespace AssetStudio } if (version[0] >= 4)//4.0 and up { - m_MuscleClipSize = reader.ReadUInt32(); - m_MuscleClip = new ClipMuscleConstant(reader); + if (reader.Game.Type.IsGI()) + { + var muscleClipSize = reader.ReadInt32(); + if (muscleClipSize < 0) + { + hasStreamingInfo = true; + m_MuscleClipSize = reader.ReadUInt32(); + m_MuscleClip = ClipMuscleConstant.ParseGI(reader); + } + else + { + m_MuscleClipSize = (uint)muscleClipSize; + m_MuscleClip = new ClipMuscleConstant(reader); + } + } + else + { + m_MuscleClipSize = reader.ReadUInt32(); + m_MuscleClip = new ClipMuscleConstant(reader); + } } if (reader.Game.Type.IsSRGroup()) { @@ -1106,6 +1733,21 @@ namespace AssetStudio { reader.AlignStream(); } + if (hasStreamingInfo) + { + m_StreamData = new StreamingInfo(reader); + if (!string.IsNullOrEmpty(m_StreamData?.path)) + { + var resourceReader = new ResourceReader(m_StreamData.path, assetsFile, m_StreamData.offset, m_StreamData.size); + var ms = new MemoryStream(); + ms.Write(m_MuscleClip.m_Clip.m_ACLClip.m_DatabaseData); + + ms.Write(resourceReader.GetData()); + ms.AlignStream(); + + m_MuscleClip.m_Clip.m_ACLClip.m_DatabaseData = ms.ToArray(); + } + } } } } diff --git a/AssetStudio/Classes/Avatar.cs b/AssetStudio/Classes/Avatar.cs index e2a8932..2b00923 100644 --- a/AssetStudio/Classes/Avatar.cs +++ b/AssetStudio/Classes/Avatar.cs @@ -93,15 +93,15 @@ namespace AssetStudio public class SkeletonPose { - public xform[] m_X; + public XForm[] m_X; public SkeletonPose(ObjectReader reader) { int numXforms = reader.ReadInt32(); - m_X = new xform[numXforms]; + m_X = new XForm[numXforms]; for (int i = 0; i < numXforms; i++) { - m_X[i] = new xform(reader); + m_X[i] = reader.ReadXForm(reader.version); } } } @@ -118,13 +118,13 @@ namespace AssetStudio public class Handle { - public xform m_X; + public XForm m_X; public uint m_ParentHumanIndex; public uint m_ID; public Handle(ObjectReader reader) { - m_X = new xform(reader); + m_X = reader.ReadXForm(reader.version); m_ParentHumanIndex = reader.ReadUInt32(); m_ID = reader.ReadUInt32(); } @@ -132,7 +132,7 @@ namespace AssetStudio public class Collider { - public xform m_X; + public XForm m_X; public uint m_Type; public uint m_XMotionType; public uint m_YMotionType; @@ -144,7 +144,7 @@ namespace AssetStudio public Collider(ObjectReader reader) { - m_X = new xform(reader); + m_X = reader.ReadXForm(reader.version); m_Type = reader.ReadUInt32(); m_XMotionType = reader.ReadUInt32(); m_YMotionType = reader.ReadUInt32(); @@ -158,7 +158,7 @@ namespace AssetStudio public class Human { - public xform m_RootX; + public XForm m_RootX; public Skeleton m_Skeleton; public SkeletonPose m_SkeletonPose; public Hand m_LeftHand; @@ -183,7 +183,7 @@ namespace AssetStudio public Human(ObjectReader reader) { var version = reader.version; - m_RootX = new xform(reader); + m_RootX = reader.ReadXForm(reader.version); m_Skeleton = new Skeleton(reader); m_SkeletonPose = new SkeletonPose(reader); m_LeftHand = new Hand(reader); @@ -243,7 +243,7 @@ namespace AssetStudio public int[] m_HumanSkeletonIndexArray; public int[] m_HumanSkeletonReverseIndexArray; public int m_RootMotionBoneIndex; - public xform m_RootMotionBoneX; + public XForm m_RootMotionBoneX; public Skeleton m_RootMotionSkeleton; public SkeletonPose m_RootMotionSkeletonPose; public int[] m_RootMotionSkeletonIndexArray; @@ -271,7 +271,7 @@ namespace AssetStudio } m_RootMotionBoneIndex = reader.ReadInt32(); - m_RootMotionBoneX = new xform(reader); + m_RootMotionBoneX = reader.ReadXForm(reader.version); if (version[0] > 4 || (version[0] == 4 && version[1] >= 3)) //4.3 and up { diff --git a/AssetStudio/Classes/Mesh.cs b/AssetStudio/Classes/Mesh.cs index 07f3959..ec3a34e 100644 --- a/AssetStudio/Classes/Mesh.cs +++ b/AssetStudio/Classes/Mesh.cs @@ -297,6 +297,7 @@ namespace AssetStudio public class MeshBlendShape { + public string name; public uint firstVertex; public uint vertexCount; public bool hasNormals; @@ -308,7 +309,7 @@ namespace AssetStudio if (version[0] == 4 && version[1] < 3) //4.3 down { - var name = reader.ReadAlignedString(); + name = reader.ReadAlignedString(); } firstVertex = reader.ReadUInt32(); vertexCount = reader.ReadUInt32(); diff --git a/AssetStudio/Classes/PPtr.cs b/AssetStudio/Classes/PPtr.cs index b31de67..d69b081 100644 --- a/AssetStudio/Classes/PPtr.cs +++ b/AssetStudio/Classes/PPtr.cs @@ -1,11 +1,10 @@ using System; using System.IO; using System.Collections.Generic; -using static AssetStudio.AssetsHelper; namespace AssetStudio { - public sealed class PPtr where T : Object + public sealed class PPtr : IYAMLExportable where T : Object { public int m_FileID; public long m_PathID; @@ -13,6 +12,13 @@ namespace AssetStudio private SerializedFile assetsFile; private int index = -2; //-2 - Prepare, -1 - Missing + public PPtr(int m_FileID, long m_PathID, SerializedFile assetsFile) + { + this.m_FileID = m_FileID; + this.m_PathID = m_PathID; + this.assetsFile = assetsFile; + } + public PPtr(ObjectReader reader) { m_FileID = reader.ReadInt32(); @@ -20,6 +26,14 @@ namespace AssetStudio assetsFile = reader.assetsFile; } + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.Style = MappingStyle.Flow; + node.Add("fileID", m_FileID); + return node; + } + private bool TryGetAssetsFile(out SerializedFile result) { result = null; @@ -129,6 +143,11 @@ namespace AssetStudio m_PathID = m_Object.m_PathID; } + public PPtr Cast() where T2 : Object + { + return new PPtr(m_FileID, m_PathID, assetsFile); + } + public bool IsNull => m_PathID == 0 || m_FileID < 0; } } diff --git a/AssetStudio/EndianBinaryReader.cs b/AssetStudio/EndianBinaryReader.cs index a6cd87d..3214ede 100644 --- a/AssetStudio/EndianBinaryReader.cs +++ b/AssetStudio/EndianBinaryReader.cs @@ -184,6 +184,20 @@ namespace AssetStudio return new Matrix4x4(ReadSingleArray(16)); } + public XForm ReadXForm(int[] version, bool isVector4 = false) + { + var t = (version[0] > 5 || (version[0] == 5 && version[1] >= 4)) && !isVector4 ? ReadVector3() : (Vector3)ReadVector4();//5.4 and up + var q = ReadQuaternion(); + var s = (version[0] > 5 || (version[0] == 5 && version[1] >= 4)) && !isVector4 ? ReadVector3() : (Vector3)ReadVector4();//5.4 and up + + return new XForm(t, q, s); + } + + public Float ReadFloat() + { + return new Float(ReadSingle()); + } + public int ReadMhy0Int() { var buffer = ReadBytes(6); @@ -224,6 +238,16 @@ namespace AssetStudio return ReadBytes(ReadInt32()); } + public short[] ReadInt16Array() + { + return ReadArray(ReadInt16, ReadInt32()); + } + + public short[] ReadInt16Array(int length) + { + return ReadArray(ReadInt16, length); + } + public ushort[] ReadUInt16Array() { return ReadArray(ReadUInt16, ReadInt32()); diff --git a/AssetStudio/Extensions/StreamExtensions.cs b/AssetStudio/Extensions/StreamExtensions.cs index 3f75403..1042d8f 100644 --- a/AssetStudio/Extensions/StreamExtensions.cs +++ b/AssetStudio/Extensions/StreamExtensions.cs @@ -20,5 +20,29 @@ namespace AssetStudio } } } + + public static void AlignStream(this Stream stream) + { + stream.AlignStream(4); + } + + public static void AlignStream(this Stream stream, int alignment) + { + var pos = stream.Position; + var mod = pos % alignment; + if (mod != 0) + { + var rem = alignment - mod; + for (int _ = 0; _ < rem; _++) + { + if (!stream.CanWrite) + { + throw new IOException("End of stream"); + } + + stream.WriteByte(0); + } + } + } } } diff --git a/AssetStudio/Math/Float.cs b/AssetStudio/Math/Float.cs new file mode 100644 index 0000000..b082ac2 --- /dev/null +++ b/AssetStudio/Math/Float.cs @@ -0,0 +1,27 @@ +namespace AssetStudio +{ + public struct Float : IYAMLExportable + { + public float Value; + + public Float(float value) + { + Value = value; + } + + public static implicit operator Float(float value) + { + return new Float(value); + } + + public static implicit operator float(Float value) + { + return value.Value; + } + + public YAMLNode ExportYAML(int[] version) + { + return new YAMLScalarNode(Value); + } + } +} diff --git a/AssetStudio/Math/Quaternion.cs b/AssetStudio/Math/Quaternion.cs index 66bf727..15c9137 100644 --- a/AssetStudio/Math/Quaternion.cs +++ b/AssetStudio/Math/Quaternion.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; namespace AssetStudio { [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct Quaternion : IEquatable + public struct Quaternion : IEquatable, IYAMLExportable { public float X; public float Y; @@ -83,6 +83,16 @@ namespace AssetStudio { return !(lhs == rhs); } + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.Style = MappingStyle.Flow; + node.Add("x", X); + node.Add("y", Y); + node.Add("z", Z); + node.Add("w", W); + return node; + } private const float kEpsilon = 0.000001F; } diff --git a/AssetStudio/Math/Vector3.cs b/AssetStudio/Math/Vector3.cs index 2426552..3e14485 100644 --- a/AssetStudio/Math/Vector3.cs +++ b/AssetStudio/Math/Vector3.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; namespace AssetStudio { [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct Vector3 : IEquatable + public struct Vector3 : IEquatable, IYAMLExportable { public float X; public float Y; @@ -87,6 +87,16 @@ namespace AssetStudio return X * X + Y * Y + Z * Z; } + public YAMLNode ExportYAML(int[] version) + { + var node = new YAMLMappingNode(); + node.Style = MappingStyle.Flow; + node.Add("x", X); + node.Add("y", Y); + node.Add("z", Z); + return node; + } + public static Vector3 Zero => new Vector3(); public static Vector3 One => new Vector3(1.0f, 1.0f, 1.0f); diff --git a/AssetStudio/Math/XForm.cs b/AssetStudio/Math/XForm.cs new file mode 100644 index 0000000..08dd0bb --- /dev/null +++ b/AssetStudio/Math/XForm.cs @@ -0,0 +1,71 @@ +using System; +using System.Runtime.InteropServices; + +namespace AssetStudio +{ + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct XForm : IEquatable + { + public Vector3 t; + public Quaternion q; + public Vector3 s; + + public XForm(Vector3 t, Quaternion q, Vector3 s) + { + this.t = t; + this.q = q; + this.s = s; + } + public float this[int index] + { + get + { + switch (index) + { + case 0: return t.X; + case 1: return t.Y; + case 2: return t.Z; + case 3: return q.X; + case 4: return q.Y; + case 5: return q.Z; + case 6: return q.W; + case 7: return s.X; + case 8: return s.Y; + case 9: return s.Z; + default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid xform index!"); + } + } + + set + { + switch (index) + { + case 0: t.X = value; break; + case 1: t.Y = value; break; + case 2: t.Z = value; break; + case 3: q.X = value; break; + case 4: q.Y = value; break; + case 5: q.Z = value; break; + case 6: q.W = value; break; + case 7: s.X = value; break; + case 8: s.Y = value; break; + case 9: s.Z = value; break; + default: + throw new ArgumentOutOfRangeException(nameof(index), "Invalid xform index!"); + } + } + } + + public override int GetHashCode() + { + return t.GetHashCode() ^ (q.GetHashCode() << 2) ^ (s.GetHashCode() >> 2); + } + + bool IEquatable.Equals(XForm other) + { + return t.Equals(other.t) && q.Equals(other.q) && s.Equals(other.s); + } + + public static XForm Zero => new XForm(Vector3.Zero, Quaternion.Zero, Vector3.One); + } +} diff --git a/AssetStudio/YAML/Base/Emitter.cs b/AssetStudio/YAML/Base/Emitter.cs new file mode 100644 index 0000000..a1604fb --- /dev/null +++ b/AssetStudio/YAML/Base/Emitter.cs @@ -0,0 +1,231 @@ +using System; +using System.IO; +using System.Text; + +namespace AssetStudio +{ + internal class Emitter + { + public Emitter(TextWriter writer, bool formatKeys) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + m_stream = writer; + IsFormatKeys = formatKeys; + if (formatKeys) + { + m_sb = new StringBuilder(); + } + } + + public Emitter IncreaseIndent() + { + m_indent++; + return this; + } + + public Emitter DecreaseIndent() + { + if (m_indent == 0) + { + throw new Exception($"Increase/decrease indent mismatch"); + } + m_indent--; + return this; + } + + public Emitter Write(char value) + { + WriteDelayed(); + m_stream.Write(value); + return this; + } + + public Emitter WriteRaw(char value) + { + m_stream.Write(value); + return this; + } + + public Emitter Write(byte value) + { + WriteDelayed(); + m_stream.Write(value); + return this; + } + + public Emitter Write(ushort value) + { + WriteDelayed(); + m_stream.Write(value); + return this; + } + + public Emitter Write(short value) + { + WriteDelayed(); + m_stream.Write(value); + return this; + } + + public Emitter Write(uint value) + { + WriteDelayed(); + m_stream.Write(value); + return this; + } + + public Emitter Write(int value) + { + WriteDelayed(); + m_stream.Write(value); + return this; + } + + public Emitter Write(ulong value) + { + WriteDelayed(); + m_stream.Write(value); + return this; + } + + public Emitter Write(long value) + { + WriteDelayed(); + m_stream.Write(value); + return this; + } + + public Emitter Write(float value) + { + WriteDelayed(); + m_stream.Write(value); + return this; + } + + public Emitter Write(double value) + { + WriteDelayed(); + m_stream.Write(value); + return this; + } + + public Emitter Write(string value) + { + if (value.Length > 0) + { + WriteDelayed(); + m_stream.Write(value); + } + return this; + } + + public Emitter WriteFormat(string value) + { + if (value.Length > 0) + { + WriteDelayed(); + if (value.Length > 2 && value.StartsWith("m_", StringComparison.Ordinal)) + { + m_sb.Append(value, 2, value.Length - 2); + if (char.IsUpper(m_sb[0])) + { + m_sb[0] = char.ToLower(m_sb[0]); + } + value = m_sb.ToString(); + m_sb.Clear(); + } + m_stream.Write(value); + } + return this; + } + + public Emitter WriteRaw(string value) + { + m_stream.Write(value); + return this; + } + + public Emitter WriteClose(char @char) + { + m_isNeedSeparator = false; + m_isNeedWhitespace = false; + m_isNeedLineBreak = false; + return Write(@char); + } + + public Emitter WriteClose(string @string) + { + m_isNeedSeparator = false; + m_isNeedWhitespace = false; + return Write(@string); + } + + public Emitter WriteWhitespace() + { + m_isNeedWhitespace = true; + return this; + } + + public Emitter WriteSeparator() + { + m_isNeedSeparator = true; + return this; + } + + public Emitter WriteLine() + { + m_isNeedLineBreak = true; + return this; + } + + public void WriteMeta(MetaType type, string value) + { + Write('%').Write(type.ToString()).WriteWhitespace(); + Write(value).WriteLine(); + } + + public void WriteDelayed() + { + if (m_isNeedLineBreak) + { + m_stream.Write('\n'); + m_isNeedSeparator = false; + m_isNeedWhitespace = false; + m_isNeedLineBreak = false; + WriteIndent(); + } + if (m_isNeedSeparator) + { + m_stream.Write(','); + m_isNeedSeparator = false; + } + if (m_isNeedWhitespace) + { + m_stream.Write(' '); + m_isNeedWhitespace = false; + } + } + + private void WriteIndent() + { + for (int i = 0; i < m_indent * 2; i++) + { + m_stream.Write(' '); + } + } + + public bool IsFormatKeys { get; } + public bool IsKey { get; set; } + + private readonly TextWriter m_stream; + private readonly StringBuilder m_sb; + + private int m_indent = 0; + private bool m_isNeedWhitespace = false; + private bool m_isNeedSeparator = false; + private bool m_isNeedLineBreak = false; + } +} diff --git a/AssetStudio/YAML/Base/IYAMLExportable.cs b/AssetStudio/YAML/Base/IYAMLExportable.cs new file mode 100644 index 0000000..ead27a8 --- /dev/null +++ b/AssetStudio/YAML/Base/IYAMLExportable.cs @@ -0,0 +1,7 @@ +namespace AssetStudio +{ + public interface IYAMLExportable + { + YAMLNode ExportYAML(int[] version); + } +} diff --git a/AssetStudio/YAML/Base/MappingStyle.cs b/AssetStudio/YAML/Base/MappingStyle.cs new file mode 100644 index 0000000..2c5ac25 --- /dev/null +++ b/AssetStudio/YAML/Base/MappingStyle.cs @@ -0,0 +1,18 @@ +namespace AssetStudio +{ + /// + /// Specifies the style of a mapping. + /// + public enum MappingStyle + { + /// + /// The block mapping style. + /// + Block, + + /// + /// The flow mapping style. + /// + Flow + } +} diff --git a/AssetStudio/YAML/Base/MetaType.cs b/AssetStudio/YAML/Base/MetaType.cs new file mode 100644 index 0000000..fc6ab82 --- /dev/null +++ b/AssetStudio/YAML/Base/MetaType.cs @@ -0,0 +1,8 @@ +namespace AssetStudio +{ + internal enum MetaType + { + YAML, + TAG, + } +} diff --git a/AssetStudio/YAML/Base/ScalarStyle.cs b/AssetStudio/YAML/Base/ScalarStyle.cs new file mode 100644 index 0000000..50e88ef --- /dev/null +++ b/AssetStudio/YAML/Base/ScalarStyle.cs @@ -0,0 +1,28 @@ +namespace AssetStudio +{ + /// + /// Specifies the style of a YAML scalar. + /// + public enum ScalarStyle + { + /// + /// The plain scalar style. + /// + Plain, + + /// + /// + /// + Hex, + + /// + /// The single-quoted scalar style. + /// + SingleQuoted, + + /// + /// The double-quoted scalar style. + /// + DoubleQuoted, + } +} diff --git a/AssetStudio/YAML/Base/ScalarType.cs b/AssetStudio/YAML/Base/ScalarType.cs new file mode 100644 index 0000000..a6e6cf8 --- /dev/null +++ b/AssetStudio/YAML/Base/ScalarType.cs @@ -0,0 +1,17 @@ +namespace AssetStudio +{ + internal enum ScalarType + { + Boolean, + Byte, + UInt16, + Int16, + UInt32, + Int32, + UInt64, + Int64, + Single, + Double, + String, + } +} diff --git a/AssetStudio/YAML/Base/SequenceStyle.cs b/AssetStudio/YAML/Base/SequenceStyle.cs new file mode 100644 index 0000000..3c61132 --- /dev/null +++ b/AssetStudio/YAML/Base/SequenceStyle.cs @@ -0,0 +1,51 @@ +namespace AssetStudio +{ + /// + /// Specifies the style of a sequence. + /// + public enum SequenceStyle + { + /// + /// The block sequence style + /// + Block, + + /// + /// The block sequence style but with curly braces + /// + BlockCurve, + + /// + /// The flow sequence style + /// + Flow, + + /// + /// Single line with hex data + /// + Raw, + } + + public static class SequenceStyleExtensions + { + public static bool IsRaw(this SequenceStyle _this) + { + return _this == SequenceStyle.Raw; + } + + public static bool IsAnyBlock(this SequenceStyle _this) + { + return _this == SequenceStyle.Block || _this == SequenceStyle.BlockCurve; + } + + /// + /// Get scalar style corresponding to current sequence style + /// + /// Sequence style + /// Corresponding scalar style + public static ScalarStyle ToScalarStyle(this SequenceStyle _this) + { + return _this == SequenceStyle.Raw ? ScalarStyle.Hex : ScalarStyle.Plain; + } + } +} diff --git a/AssetStudio/YAML/Base/YAMLDocument.cs b/AssetStudio/YAML/Base/YAMLDocument.cs new file mode 100644 index 0000000..1052f63 --- /dev/null +++ b/AssetStudio/YAML/Base/YAMLDocument.cs @@ -0,0 +1,42 @@ +namespace AssetStudio +{ + public sealed class YAMLDocument + { + public YAMLDocument() + { + } + + public YAMLScalarNode CreateScalarRoot() + { + YAMLScalarNode root = new YAMLScalarNode(); + Root = root; + return root; + } + + public YAMLSequenceNode CreateSequenceRoot() + { + YAMLSequenceNode root = new YAMLSequenceNode(); + Root = root; + return root; + } + + public YAMLMappingNode CreateMappingRoot() + { + YAMLMappingNode root = new YAMLMappingNode(); + Root = root; + return root; + } + + internal void Emit(Emitter emitter, bool isSeparator) + { + if(isSeparator) + { + emitter.Write("---").WriteWhitespace(); + } + + Root.Emit(emitter); + } + + public YAMLNode Root { get; private set; } + } +} diff --git a/AssetStudio/YAML/Base/YAMLMappingNode.cs b/AssetStudio/YAML/Base/YAMLMappingNode.cs new file mode 100644 index 0000000..75d60c1 --- /dev/null +++ b/AssetStudio/YAML/Base/YAMLMappingNode.cs @@ -0,0 +1,330 @@ +using System; +using System.Collections.Generic; + +namespace AssetStudio +{ + public sealed class YAMLMappingNode : YAMLNode + { + public YAMLMappingNode() + { + } + + public YAMLMappingNode(MappingStyle style) + { + Style = style; + } + + public void Add(int key, long value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(int key, string value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(int key, YAMLNode value) + { + YAMLScalarNode keyNode = new YAMLScalarNode(key); + InsertEnd(keyNode, value); + } + + public void Add(uint key, string value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(uint key, YAMLNode value) + { + YAMLScalarNode keyNode = new YAMLScalarNode(key); + InsertEnd(keyNode, value); + } + + public void Add(long key, string value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(long key, YAMLNode value) + { + YAMLScalarNode keyNode = new YAMLScalarNode(key); + InsertEnd(keyNode, value); + } + + public void Add(string key, bool value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(string key, byte value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(string key, short value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(string key, ushort value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(string key, int value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(string key, uint value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(string key, long value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(string key, ulong value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(string key, float value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(string key, string value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(string key, YAMLNode value) + { + YAMLScalarNode keyNode = new YAMLScalarNode(key, true); + InsertEnd(keyNode, value); + } + + public void Add(YAMLNode key, bool value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(YAMLNode key, byte value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(YAMLNode key, short value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(YAMLNode key, ushort value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(YAMLNode key, int value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(YAMLNode key, uint value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(YAMLNode key, long value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(YAMLNode key, ulong value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(YAMLNode key, float value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(YAMLNode key, string value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + Add(key, valueNode); + } + + public void Add(YAMLNode key, YAMLNode value) + { + if (key.NodeType != YAMLNodeType.Scalar) + { + throw new Exception($"Only {YAMLNodeType.Scalar} node as a key supported"); + } + + InsertEnd(key, value); + } + + public void Append(YAMLMappingNode map) + { + foreach (KeyValuePair child in map.m_children) + { + Add(child.Key, child.Value); + } + } + + public void InsertBegin(string key, int value) + { + YAMLScalarNode valueNode = new YAMLScalarNode(value); + InsertBegin(key, valueNode); + } + + public void InsertBegin(string key, YAMLNode value) + { + YAMLScalarNode keyNode = new YAMLScalarNode(key, true); + InsertBegin(keyNode, value); + } + + public void InsertBegin(YAMLNode key, YAMLNode value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + KeyValuePair pair = new KeyValuePair(key, value); + m_children.Insert(0, pair); + } + + internal override void Emit(Emitter emitter) + { + base.Emit(emitter); + + StartChildren(emitter); + foreach (var kvp in m_children) + { + YAMLNode key = kvp.Key; + YAMLNode value = kvp.Value; + + bool iskey = emitter.IsKey; + emitter.IsKey = true; + key.Emit(emitter); + emitter.IsKey = false; + StartTransition(emitter, value); + value.Emit(emitter); + EndTransition(emitter, value); + emitter.IsKey = iskey; + } + EndChildren(emitter); + } + + private void StartChildren(Emitter emitter) + { + if (Style == MappingStyle.Block) + { + if (m_children.Count == 0) + { + emitter.Write('{'); + } + } + else if (Style == MappingStyle.Flow) + { + emitter.Write('{'); + } + } + + private void EndChildren(Emitter emitter) + { + if (Style == MappingStyle.Block) + { + if (m_children.Count == 0) + { + emitter.Write('}'); + } + emitter.WriteLine(); + } + else if (Style == MappingStyle.Flow) + { + emitter.WriteClose('}'); + } + } + + private void StartTransition(Emitter emitter, YAMLNode next) + { + emitter.Write(':').WriteWhitespace(); + if (Style == MappingStyle.Block) + { + if (next.IsMultiline) + { + emitter.WriteLine(); + } + } + if (next.IsIndent) + { + emitter.IncreaseIndent(); + } + } + + private void EndTransition(Emitter emitter, YAMLNode next) + { + if (Style == MappingStyle.Block) + { + emitter.WriteLine(); + } + else if (Style == MappingStyle.Flow) + { + emitter.WriteSeparator().WriteWhitespace(); + } + if (next.IsIndent) + { + emitter.DecreaseIndent(); + } + } + + private void InsertEnd(YAMLNode key, YAMLNode value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + KeyValuePair pair = new KeyValuePair(key, value); + m_children.Add(pair); + } + + public static YAMLMappingNode Empty { get; } = new YAMLMappingNode(MappingStyle.Flow); + + public override YAMLNodeType NodeType => YAMLNodeType.Mapping; + public override bool IsMultiline => Style == MappingStyle.Block && m_children.Count > 0; + public override bool IsIndent => Style == MappingStyle.Block; + + public MappingStyle Style { get; set; } + + private readonly List> m_children = new List>(); + } +} diff --git a/AssetStudio/YAML/Base/YAMLNode.cs b/AssetStudio/YAML/Base/YAMLNode.cs new file mode 100644 index 0000000..f946aaa --- /dev/null +++ b/AssetStudio/YAML/Base/YAMLNode.cs @@ -0,0 +1,40 @@ +namespace AssetStudio +{ + public abstract class YAMLNode + { + internal virtual void Emit(Emitter emitter) + { + bool isWrote = false; + if (!CustomTag.IsEmpty) + { + emitter.Write(CustomTag.ToString()).WriteWhitespace(); + isWrote = true; + } + if (Anchor.Length > 0) + { + emitter.Write("&").Write(Anchor).WriteWhitespace(); + isWrote = true; + } + + if (isWrote) + { + if (IsMultiline) + { + emitter.WriteLine(); + } + } + } + + public abstract YAMLNodeType NodeType { get; } + public abstract bool IsMultiline { get; } + public abstract bool IsIndent { get; } + + public string Tag + { + get => CustomTag.Content; + set => CustomTag = new YAMLTag(YAMLWriter.DefaultTagHandle, value); + } + public YAMLTag CustomTag { get; set; } + public string Anchor { get; set; } = string.Empty; + } +} diff --git a/AssetStudio/YAML/Base/YAMLNodeType.cs b/AssetStudio/YAML/Base/YAMLNodeType.cs new file mode 100644 index 0000000..04f8cdf --- /dev/null +++ b/AssetStudio/YAML/Base/YAMLNodeType.cs @@ -0,0 +1,20 @@ +namespace AssetStudio +{ + public enum YAMLNodeType + { + /// + /// The node is a . + /// + Mapping, + + /// + /// The node is a . + /// + Scalar, + + /// + /// The node is a . + /// + Sequence + } +} diff --git a/AssetStudio/YAML/Base/YAMLScalarNode.cs b/AssetStudio/YAML/Base/YAMLScalarNode.cs new file mode 100644 index 0000000..7d40dce --- /dev/null +++ b/AssetStudio/YAML/Base/YAMLScalarNode.cs @@ -0,0 +1,456 @@ +//#define USE_HEX_FLOAT + +using System; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace AssetStudio +{ + public sealed class YAMLScalarNode : YAMLNode + { + public YAMLScalarNode() + { + } + + public YAMLScalarNode(bool value) : + this(value, false) + { + } + + public YAMLScalarNode(bool value, bool isHex) + { + SetValue(value); + Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain; + } + + public YAMLScalarNode(byte value) : + this(value, false) + { + } + + public YAMLScalarNode(byte value, bool isHex) + { + SetValue(value); + Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain; + } + + public YAMLScalarNode(short value) : + this(value, false) + { + } + + public YAMLScalarNode(short value, bool isHex) + { + SetValue(value); + Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain; + } + + public YAMLScalarNode(ushort value) : + this(value, false) + { + } + + public YAMLScalarNode(ushort value, bool isHex) + { + SetValue(value); + Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain; + } + + public YAMLScalarNode(int value) : + this(value, false) + { + } + + public YAMLScalarNode(int value, bool isHex) + { + SetValue(value); + Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain; + } + + public YAMLScalarNode(uint value) : + this(value, false) + { + } + + public YAMLScalarNode(uint value, bool isHex) + { + SetValue(value); + Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain; + } + + public YAMLScalarNode(long value) : + this(value, false) + { + } + + public YAMLScalarNode(long value, bool isHex) + { + SetValue(value); + Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain; + } + + public YAMLScalarNode(ulong value) : + this(value, false) + { + } + + public YAMLScalarNode(ulong value, bool isHex) + { + SetValue(value); + Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain; + } + + public YAMLScalarNode(float value) : + this(value, false) + { + } + + public YAMLScalarNode(float value, bool isHex) + { + SetValue(value); + Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain; + } + + public YAMLScalarNode(double value) : + this(value, false) + { + } + + public YAMLScalarNode(double value, bool isHex) + { + SetValue(value); + Style = isHex ? ScalarStyle.Hex : ScalarStyle.Plain; + } + + public YAMLScalarNode(string value) + { + SetValue(value); + Style = GetStringStyle(value); + } + + internal YAMLScalarNode(string value, bool _) + { + SetValue(value); + Style = ScalarStyle.Plain; + } + + public void SetValue(bool value) + { + m_value = value ? 1u : 0u; + m_objectType = ScalarType.Boolean; + } + + public void SetValue(byte value) + { + m_value = value; + m_objectType = ScalarType.Byte; + } + + public void SetValue(short value) + { + m_value = unchecked((ushort)value); + m_objectType = ScalarType.Int16; + } + + public void SetValue(ushort value) + { + m_value = value; + m_objectType = ScalarType.UInt16; + } + + public void SetValue(int value) + { + m_value = unchecked((uint)value); + m_objectType = ScalarType.Int32; + } + + public void SetValue(uint value) + { + m_value = value; + m_objectType = ScalarType.UInt32; + } + + public void SetValue(long value) + { + m_value = unchecked((ulong)value); + m_objectType = ScalarType.Int64; + } + + public void SetValue(ulong value) + { + m_value = value; + m_objectType = ScalarType.UInt64; + } + + public void SetValue(float value) + { +#if USE_HEX_FLOAT + // It is more precise technic but output looks vague and less readable + uint hex = BitConverterExtensions.ToUInt32(value); + m_string = $"0x{hex.ToHexString()}({value.ToString(CultureInfo.InvariantCulture)})"; + m_objectType = ScalarType.String; +#else + m_value = BitConverterExtensions.ToUInt32(value); + m_objectType = ScalarType.Single; +#endif + } + + public void SetValue(double value) + { +#if USE_HEX_FLOAT + // It is more precise technic but output looks vague and less readable + ulong hex = BitConverterExtensions.ToUInt64(value); + m_string = $"0x{hex.ToHexString()}({value.ToString(CultureInfo.InvariantCulture)})"; + m_objectType = ScalarType.String; +#else + m_value = BitConverterExtensions.ToUInt64(value); + m_objectType = ScalarType.Double; +#endif + } + + public void SetValue(string value) + { + m_string = value; + m_objectType = ScalarType.String; + } + + internal Emitter ToString(Emitter emitter) + { + if (Style == ScalarStyle.Hex) + { + switch (m_objectType) + { + case ScalarType.Byte: + return emitter.WriteHex((byte)m_value); + case ScalarType.Int16: + return emitter.WriteHex(unchecked((short)m_value)); + case ScalarType.UInt16: + return emitter.WriteHex((ushort)m_value); + case ScalarType.Int32: + return emitter.WriteHex(unchecked((int)m_value)); + case ScalarType.UInt32: + return emitter.WriteHex((uint)m_value); + case ScalarType.Int64: + return emitter.WriteHex(unchecked((long)m_value)); + case ScalarType.UInt64: + return emitter.WriteHex(m_value); + case ScalarType.Single: + return emitter.WriteHex((uint)m_value); + case ScalarType.Double: + return emitter.WriteHex(m_value); + default: + throw new NotImplementedException(m_objectType.ToString()); + } + } + + switch (m_objectType) + { + case ScalarType.Boolean: + return emitter.Write(m_value); + case ScalarType.Byte: + return emitter.Write(m_value); + case ScalarType.Int16: + return emitter.Write(unchecked((short)m_value)); + case ScalarType.UInt16: + return emitter.Write(m_value); + case ScalarType.Int32: + return emitter.Write(unchecked((int)m_value)); + case ScalarType.UInt32: + return emitter.Write(m_value); + case ScalarType.Int64: + return emitter.Write(unchecked((long)m_value)); + case ScalarType.UInt64: + return emitter.Write(m_value); + case ScalarType.Single: + return emitter.Write(BitConverterExtensions.ToSingle((uint)m_value)); + case ScalarType.Double: + return emitter.Write(BitConverterExtensions.ToDouble(m_value)); + case ScalarType.String: + return WriteString(emitter); + + default: + throw new NotImplementedException(m_objectType.ToString()); + } + } + + internal override void Emit(Emitter emitter) + { + base.Emit(emitter); + + switch (Style) + { + case ScalarStyle.Hex: + case ScalarStyle.Plain: + ToString(emitter); + break; + + case ScalarStyle.SingleQuoted: + emitter.Write('\''); + ToString(emitter); + emitter.Write('\''); + break; + + case ScalarStyle.DoubleQuoted: + emitter.Write('"'); + ToString(emitter); + emitter.Write('"'); + break; + + default: + throw new Exception($"Unsupported scalar style {Style}"); + } + } + + private Emitter WriteString(Emitter emitter) + { + if (Style == ScalarStyle.Plain) + { + if (emitter.IsFormatKeys && emitter.IsKey) + { + emitter.WriteFormat(m_string); + } + else + { + emitter.Write(m_string); + } + } + else if (Style == ScalarStyle.SingleQuoted) + { + emitter.WriteDelayed(); + for (int i = 0; i < m_string.Length; i++) + { + char c = m_string[i]; + emitter.WriteRaw(c); + if (c == '\'') + { + emitter.WriteRaw(c); + } + else if (c == '\n') + { + emitter.WriteRaw("\n "); + } + } + } + else if (Style == ScalarStyle.DoubleQuoted) + { + emitter.WriteDelayed(); + for (int i = 0; i < m_string.Length; i++) + { + char c = m_string[i]; + switch (c) + { + case '\\': + emitter.WriteRaw('\\').WriteRaw('\\'); + break; + case '\n': + emitter.WriteRaw('\\').WriteRaw('n'); + break; + case '\r': + emitter.WriteRaw('\\').WriteRaw('r'); + break; + case '\t': + emitter.WriteRaw('\\').WriteRaw('t'); + break; + case '"': + emitter.WriteRaw('\\').WriteRaw('"'); + break; + + default: + emitter.WriteRaw(c); + break; + } + } + } + else + { + throw new NotSupportedException(Style.ToString()); + } + return emitter; + } + + private static ScalarStyle GetStringStyle(string value) + { + if (s_illegal.IsMatch(value)) + { + return value.Contains("\n ") ? ScalarStyle.DoubleQuoted : ScalarStyle.SingleQuoted; + } + return ScalarStyle.Plain; + } + + public static YAMLScalarNode Empty { get; } = new YAMLScalarNode(); + + public override YAMLNodeType NodeType => YAMLNodeType.Scalar; + public override bool IsMultiline => false; + public override bool IsIndent => false; + + public string Value + { + get + { + if (Style == ScalarStyle.Hex) + { + switch (m_objectType) + { + case ScalarType.Byte: + return unchecked((byte)m_value).ToHexString(); + case ScalarType.Int16: + return unchecked((short)m_value).ToHexString(); + case ScalarType.UInt16: + return unchecked((ushort)m_value).ToHexString(); + case ScalarType.Int32: + return unchecked((int)m_value).ToHexString(); + case ScalarType.UInt32: + return unchecked((uint)m_value).ToHexString(); + case ScalarType.Int64: + return unchecked((long)m_value).ToHexString(); + case ScalarType.UInt64: + return m_value.ToHexString(); + case ScalarType.Single: + return BitConverterExtensions.ToSingle((uint)m_value).ToHexString(); + case ScalarType.Double: + return BitConverterExtensions.ToDouble(m_value).ToHexString(); + default: + throw new NotImplementedException(m_objectType.ToString()); + } + } + + switch (m_objectType) + { + case ScalarType.Boolean: + return m_value == 1 ? "true" : "false"; + case ScalarType.Byte: + return m_value.ToString(); + case ScalarType.Int16: + return unchecked((short)m_value).ToString(); + case ScalarType.UInt16: + return m_value.ToString(); + case ScalarType.Int32: + return unchecked((int)m_value).ToString(); + case ScalarType.UInt32: + return m_value.ToString(); + case ScalarType.Int64: + return unchecked((long)m_value).ToString(); + case ScalarType.UInt64: + return m_value.ToString(); + case ScalarType.Single: + return BitConverterExtensions.ToSingle((uint)m_value).ToString(CultureInfo.InvariantCulture); + case ScalarType.Double: + return BitConverterExtensions.ToDouble(m_value).ToString(CultureInfo.InvariantCulture); + case ScalarType.String: + return m_string; + + default: + throw new NotImplementedException(m_objectType.ToString()); + } + } + set => m_string = value; + } + public ScalarStyle Style { get; } + + private static readonly Regex s_illegal = new Regex("(^\\s)|(^-\\s)|(^-$)|(^[\\:\\[\\]'\"*&!@#%{}?<>,\\`])|([:@]\\s)|([\\n\\r])|([:\\s]$)", RegexOptions.Compiled); + + private ScalarType m_objectType = ScalarType.String; + private string m_string = string.Empty; + private ulong m_value = 0; + } +} diff --git a/AssetStudio/YAML/Base/YAMLSequenceNode.cs b/AssetStudio/YAML/Base/YAMLSequenceNode.cs new file mode 100644 index 0000000..8483ee2 --- /dev/null +++ b/AssetStudio/YAML/Base/YAMLSequenceNode.cs @@ -0,0 +1,213 @@ +using System.Collections.Generic; + +namespace AssetStudio +{ + public sealed class YAMLSequenceNode : YAMLNode + { + public YAMLSequenceNode() + { + } + + public YAMLSequenceNode(SequenceStyle style) + { + Style = style; + } + + public void Add(bool value) + { + YAMLScalarNode node = new YAMLScalarNode(value, Style.IsRaw()); + Add(node); + } + + public void Add(byte value) + { + YAMLScalarNode node = new YAMLScalarNode(value, Style.IsRaw()); + Add(node); + } + + public void Add(short value) + { + YAMLScalarNode node = new YAMLScalarNode(value, Style.IsRaw()); + Add(node); + } + + public void Add(ushort value) + { + YAMLScalarNode node = new YAMLScalarNode(value, Style.IsRaw()); + Add(node); + } + + public void Add(int value) + { + YAMLScalarNode node = new YAMLScalarNode(value, Style.IsRaw()); + Add(node); + } + + public void Add(uint value) + { + YAMLScalarNode node = new YAMLScalarNode(value, Style.IsRaw()); + Add(node); + } + + public void Add(long value) + { + YAMLScalarNode node = new YAMLScalarNode(value, Style.IsRaw()); + Add(node); + } + + public void Add(ulong value) + { + YAMLScalarNode node = new YAMLScalarNode(value, Style.IsRaw()); + Add(node); + } + + public void Add(float value) + { + YAMLScalarNode node = new YAMLScalarNode(value, Style.IsRaw()); + Add(node); + } + + public void Add(double value) + { + YAMLScalarNode node = new YAMLScalarNode(value, Style.IsRaw()); + Add(node); + } + + public void Add(string value) + { + YAMLScalarNode node = new YAMLScalarNode(value); + Add(node); + } + + public void Add(YAMLNode child) + { + m_children.Add(child); + } + + internal override void Emit(Emitter emitter) + { + base.Emit(emitter); + + StartChildren(emitter); + foreach (YAMLNode child in m_children) + { + StartChild(emitter, child); + child.Emit(emitter); + EndChild(emitter, child); + } + EndChildren(emitter); + } + + private void StartChildren(Emitter emitter) + { + switch (Style) + { + case SequenceStyle.Block: + if (m_children.Count == 0) + { + emitter.Write('['); + } + break; + + case SequenceStyle.BlockCurve: + if (m_children.Count == 0) + { + emitter.Write('{'); + } + break; + + case SequenceStyle.Flow: + emitter.Write('['); + break; + + case SequenceStyle.Raw: + if (m_children.Count == 0) + { + emitter.Write('['); + } + break; + } + } + + private void EndChildren(Emitter emitter) + { + switch (Style) + { + case SequenceStyle.Block: + if (m_children.Count == 0) + { + emitter.Write(']'); + } + emitter.WriteLine(); + break; + + case SequenceStyle.BlockCurve: + if (m_children.Count == 0) + { + emitter.WriteClose('}'); + } + emitter.WriteLine(); + break; + + case SequenceStyle.Flow: + emitter.WriteClose(']'); + break; + + case SequenceStyle.Raw: + if (m_children.Count == 0) + { + emitter.Write(']'); + } + emitter.WriteLine(); + break; + } + } + + private void StartChild(Emitter emitter, YAMLNode next) + { + if (Style.IsAnyBlock()) + { + emitter.Write('-').Write(' '); + + if (next.NodeType == NodeType) + { + emitter.IncreaseIndent(); + } + } + if (next.IsIndent) + { + emitter.IncreaseIndent(); + } + } + + private void EndChild(Emitter emitter, YAMLNode next) + { + if (Style.IsAnyBlock()) + { + emitter.WriteLine(); + if (next.NodeType == NodeType) + { + emitter.DecreaseIndent(); + } + } + else if (Style == SequenceStyle.Flow) + { + emitter.WriteSeparator().WriteWhitespace(); + } + if (next.IsIndent) + { + emitter.DecreaseIndent(); + } + } + + public static YAMLSequenceNode Empty { get; } = new YAMLSequenceNode(); + + public override YAMLNodeType NodeType => YAMLNodeType.Sequence; + public override bool IsMultiline => Style.IsAnyBlock() && m_children.Count > 0; + public override bool IsIndent => false; + + public SequenceStyle Style { get; } + + private readonly List m_children = new List(); + } +} diff --git a/AssetStudio/YAML/Base/YAMLTag.cs b/AssetStudio/YAML/Base/YAMLTag.cs new file mode 100644 index 0000000..073a941 --- /dev/null +++ b/AssetStudio/YAML/Base/YAMLTag.cs @@ -0,0 +1,26 @@ +namespace AssetStudio +{ + public readonly struct YAMLTag + { + public YAMLTag(string handle, string content) + { + Handle = handle; + Content = content; + } + + public override string ToString() + { + return IsEmpty ? string.Empty : $"{Handle}{Content}"; + } + + public string ToHeaderString() + { + return IsEmpty ? string.Empty : $"{Handle} {Content}"; + } + + public bool IsEmpty => string.IsNullOrEmpty(Handle); + + public string Handle { get; } + public string Content { get; } + } +} diff --git a/AssetStudio/YAML/Base/YAMLWriter.cs b/AssetStudio/YAML/Base/YAMLWriter.cs new file mode 100644 index 0000000..9a78e74 --- /dev/null +++ b/AssetStudio/YAML/Base/YAMLWriter.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace AssetStudio +{ + using Version = System.Version; + + public class YAMLWriter + { + public void AddDocument(YAMLDocument document) + { +#if DEBUG + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + if (m_documents.Contains(document)) + { + throw new ArgumentException($"Document {document} is added already", nameof(document)); + } +#endif + m_documents.Add(document); + } + + public void AddTag(string handle, string content) + { + if(m_tags.Any(t => t.Handle == handle)) + { + throw new Exception($"Writer already contains tag {handle}"); + } + YAMLTag tag = new YAMLTag(handle, content); + m_tags.Add(tag); + } + + public void Write(TextWriter output) + { + WriteHead(output); + foreach (YAMLDocument doc in m_documents) + { + WriteDocument(doc); + } + WriteTail(output); + } + + public void WriteHead(TextWriter output) + { + m_emitter = new Emitter(output, IsFormatKeys); + m_isWriteSeparator = false; + + if (IsWriteVersion) + { + m_emitter.WriteMeta(MetaType.YAML, Version.ToString()); + m_isWriteSeparator = true; + } + + if (IsWriteDefaultTag) + { + m_emitter.WriteMeta(MetaType.TAG, DefaultTag.ToHeaderString()); + m_isWriteSeparator = true; + } + foreach (YAMLTag tag in m_tags) + { + m_emitter.WriteMeta(MetaType.TAG, tag.ToHeaderString()); + m_isWriteSeparator = true; + } + } + + public void WriteDocument(YAMLDocument doc) + { + doc.Emit(m_emitter, m_isWriteSeparator); + m_isWriteSeparator = true; + } + + public void WriteTail(TextWriter output) + { + output.Write('\n'); + } + + public static Version Version { get; } = new Version(1, 1); + + public const string DefaultTagHandle = "!u!"; + public const string DefaultTagContent = "tag:unity3d.com,2011:"; + + public readonly YAMLTag DefaultTag = new YAMLTag(DefaultTagHandle, DefaultTagContent); + + public bool IsWriteVersion { get; set; } = true; + public bool IsWriteDefaultTag { get; set; } = true; + public bool IsFormatKeys { get; set; } + + private readonly HashSet m_documents = new HashSet(); + private readonly List m_tags = new List(); + + private Emitter m_emitter; + private bool m_isWriteSeparator; + } +} diff --git a/AssetStudio/YAML/Utils/Extensions/ArrayYAMLExtensions.cs b/AssetStudio/YAML/Utils/Extensions/ArrayYAMLExtensions.cs new file mode 100644 index 0000000..87d3901 --- /dev/null +++ b/AssetStudio/YAML/Utils/Extensions/ArrayYAMLExtensions.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; + +namespace AssetStudio +{ + public static class ArrayYAMLExtensions + { + public static YAMLNode ExportYAML(this byte[] _this) + { + StringBuilder sb = new StringBuilder(_this.Length * 2); + for (int i = 0; i < _this.Length; i++) + { + sb.AppendHex(_this[i]); + } + return new YAMLScalarNode(sb.ToString(), true); + } + + public static YAMLNode ExportYAML(this T[][] _this, int[] version) + where T : IYAMLExportable + { + return ((IEnumerable>)_this).ExportYAML(version); + } + } +} diff --git a/AssetStudio/YAML/Utils/Extensions/BitConverterExtensions.cs b/AssetStudio/YAML/Utils/Extensions/BitConverterExtensions.cs new file mode 100644 index 0000000..ac57b66 --- /dev/null +++ b/AssetStudio/YAML/Utils/Extensions/BitConverterExtensions.cs @@ -0,0 +1,107 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace AssetStudio +{ + public static class BitConverterExtensions + { + [StructLayout(LayoutKind.Explicit)] + private struct FloatUIntUnion + { + [FieldOffset(0)] + public uint Int; + [FieldOffset(0)] + public float Float; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ToUInt32(float value) + { + return new FloatUIntUnion { Float = value }.Int; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ToUInt64(double value) + { + return unchecked((ulong)BitConverter.DoubleToInt64Bits(value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ToSingle(uint value) + { + return new FloatUIntUnion { Int = value }.Float; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static double ToDouble(ulong value) + { + return BitConverter.Int64BitsToDouble(unchecked((long)value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void GetBytes(ushort value, byte[] buffer, int offset) + { + buffer[offset + 0] = unchecked((byte)(value >> 0)); + buffer[offset + 1] = unchecked((byte)(value >> 8)); + } + + public static void GetBytes(uint value, byte[] buffer, int offset) + { + buffer[offset + 0] = unchecked((byte)(value >> 0)); + buffer[offset + 1] = unchecked((byte)(value >> 8)); + buffer[offset + 2] = unchecked((byte)(value >> 16)); + buffer[offset + 3] = unchecked((byte)(value >> 24)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short Swap(short value) + { + return unchecked((short)(Swap(unchecked((ushort)value)))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort Swap(ushort value) + { + return unchecked((ushort)(value >> 8 | value << 8)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Swap(int value) + { + return unchecked((int)(Swap(unchecked((uint)value)))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Swap(uint value) + { + value = value >> 16 | value << 16; + return ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long Swap(long value) + { + return unchecked((long)(Swap(unchecked((ulong)value)))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Swap(ulong value) + { + value = value >> 32 | value << 32; + value = ((value & 0xFFFF0000FFFF0000) >> 16) | ((value & 0x0000FFFF0000FFFF) << 16); + return ((value & 0xFF00FF00FF00FF00) >> 8) | ((value & 0x00FF00FF00FF00FF) << 8); + } + + public static int GetDigitsCount(uint value) + { + int count = 0; + while (value != 0) + { + value /= 10; + count++; + } + return count; + } + } +} diff --git a/AssetStudio/YAML/Utils/Extensions/EmitterExtensions.cs b/AssetStudio/YAML/Utils/Extensions/EmitterExtensions.cs new file mode 100644 index 0000000..4fa0af5 --- /dev/null +++ b/AssetStudio/YAML/Utils/Extensions/EmitterExtensions.cs @@ -0,0 +1,82 @@ +namespace AssetStudio +{ + internal static class EmitterExtensions + { + public static Emitter WriteHex(this Emitter _this, byte value) + { + _this.Write(HexAlphabet[value >> 4]); + _this.Write(HexAlphabet[value & 0xF]); + return _this; + } + + public static Emitter WriteHex(this Emitter _this, ushort value) + { + _this.Write(HexAlphabet[(value >> 4) & 0xF]); + _this.Write(HexAlphabet[(value >> 0) & 0xF]); + _this.Write(HexAlphabet[(value >> 12) & 0xF]); + _this.Write(HexAlphabet[(value >> 8) & 0xF]); + return _this; + } + + public static Emitter WriteHex(this Emitter _this, short value) + { + return WriteHex(_this, unchecked((ushort)value)); + } + + public static Emitter WriteHex(this Emitter _this, uint value) + { + _this.Write(HexAlphabet[unchecked((int)(value >> 4) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 0) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 12) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 8) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 20) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 16) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 28) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 24) & 0xF)]); + return _this; + } + + public static Emitter WriteHex(this Emitter _this, int value) + { + return WriteHex(_this, unchecked((uint)value)); + } + + public static Emitter WriteHex(this Emitter _this, ulong value) + { + _this.Write(HexAlphabet[unchecked((int)(value >> 4) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 0) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 12) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 8) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 20) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 16) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 28) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 24) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 36) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 32) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 44) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 40) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 52) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 48) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 60) & 0xF)]); + _this.Write(HexAlphabet[unchecked((int)(value >> 56) & 0xF)]); + return _this; + } + + public static Emitter WriteHex(this Emitter _this, long value) + { + return WriteHex(_this, unchecked((ulong)value)); + } + + public static Emitter WriteHex(this Emitter _this, float value) + { + return WriteHex(_this, BitConverterExtensions.ToUInt32(value)); + } + + public static Emitter WriteHex(this Emitter _this, double value) + { + return WriteHex(_this, BitConverterExtensions.ToUInt64(value)); + } + + private static readonly string HexAlphabet = "0123456789ABCDEF"; + } +} diff --git a/AssetStudio/YAML/Utils/Extensions/IDictionaryExportYAMLExtensions.cs b/AssetStudio/YAML/Utils/Extensions/IDictionaryExportYAMLExtensions.cs new file mode 100644 index 0000000..d198f62 --- /dev/null +++ b/AssetStudio/YAML/Utils/Extensions/IDictionaryExportYAMLExtensions.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; + +namespace AssetStudio +{ + public static class IDictionaryExportYAMLExtensions + { + public static YAMLNode ExportYAML(this IReadOnlyDictionary _this, int[] version) + where T : IYAMLExportable + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.BlockCurve); + foreach (var kvp in _this) + { + YAMLMappingNode map = new YAMLMappingNode(); + map.Add(kvp.Key, kvp.Value.ExportYAML(version)); + node.Add(map); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary _this, int[] version) + where T : IYAMLExportable + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.BlockCurve); + foreach (var kvp in _this) + { + YAMLMappingNode map = new YAMLMappingNode(); + map.Add(kvp.Key, kvp.Value.ExportYAML(version)); + node.Add(map); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary, T2> _this, int[] version) + where T1 : IYAMLExportable + where T2 : IYAMLExportable + { + // TODO: test + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.BlockCurve); + foreach (var kvp in _this) + { + YAMLMappingNode kvpMap = new YAMLMappingNode(); + YAMLMappingNode keyMap = new YAMLMappingNode(); + keyMap.Add("first", kvp.Key.Item1.ExportYAML(version)); + keyMap.Add("second", kvp.Key.Item2); + kvpMap.Add("first", keyMap); + kvpMap.Add("second", kvp.Value.ExportYAML(version)); + node.Add(kvpMap); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary _this, int[] version) + where T : IYAMLExportable + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.BlockCurve); + foreach (var kvp in _this) + { + YAMLMappingNode map = new YAMLMappingNode(); + YAMLNode key = kvp.Key.ExportYAML(version); + if (key.NodeType == YAMLNodeType.Scalar) + { + map.Add(key, kvp.Value); + } + else + { + map.Add("first", key); + map.Add("second", kvp.Value); + } + node.Add(map); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary _this, int[] version) + where T : IYAMLExportable + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.BlockCurve); + foreach (var kvp in _this) + { + YAMLMappingNode map = new YAMLMappingNode(); + YAMLNode key = kvp.Key.ExportYAML(version); + if (key.NodeType == YAMLNodeType.Scalar) + { + map.Add(key, kvp.Value); + } + else + { + map.Add("first", key); + map.Add("second", kvp.Value); + } + node.Add(map); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary _this, int[] version) + where T1 : IYAMLExportable + where T2 : IYAMLExportable + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.BlockCurve); + foreach (var kvp in _this) + { + YAMLMappingNode map = new YAMLMappingNode(); + YAMLNode key = kvp.Key.ExportYAML(version); + if (key.NodeType == YAMLNodeType.Scalar) + { + map.Add(key, kvp.Value.ExportYAML(version)); + } + else + { + map.Add("first", key); + map.Add("second", kvp.Value.ExportYAML(version)); + } + node.Add(map); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary _this, int[] version) + where T1 : IYAMLExportable + where T2 : IYAMLExportable + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.BlockCurve); + foreach (var kvp in _this) + { + YAMLMappingNode map = new YAMLMappingNode(); + YAMLNode key = kvp.Key.ExportYAML(version); + if (key.NodeType == YAMLNodeType.Scalar) + { + map.Add(key, kvp.Value.ExportYAML(version)); + } + else + { + map.Add("first", key); + map.Add("second", kvp.Value.ExportYAML(version)); + } + node.Add(map); + } + return node; + } + } +} diff --git a/AssetStudio/YAML/Utils/Extensions/IDictionaryYAMLExtensions.cs b/AssetStudio/YAML/Utils/Extensions/IDictionaryYAMLExtensions.cs new file mode 100644 index 0000000..6cd07b5 --- /dev/null +++ b/AssetStudio/YAML/Utils/Extensions/IDictionaryYAMLExtensions.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; + +namespace AssetStudio +{ + public static class IDictionaryYAMLExtensions + { + public static YAMLNode ExportYAML(this IReadOnlyDictionary _this) + { + YAMLMappingNode node = new YAMLMappingNode(); + foreach (var kvp in _this) + { + node.Add(kvp.Key, kvp.Value); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary _this) + { + YAMLMappingNode node = new YAMLMappingNode(); + foreach (var kvp in _this) + { + node.Add(kvp.Key, kvp.Value); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary _this) + { + YAMLMappingNode node = new YAMLMappingNode(); + foreach (var kvp in _this) + { + node.Add(kvp.Key, kvp.Value); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary _this) + { + YAMLMappingNode node = new YAMLMappingNode(); + foreach (var kvp in _this) + { + node.Add(kvp.Key, kvp.Value); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary _this) + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.BlockCurve); + foreach (var kvp in _this) + { + YAMLMappingNode map = new YAMLMappingNode(); + map.Add(kvp.Key, kvp.Value); + node.Add(map); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary, float> _this) + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.BlockCurve); + foreach (var kvp in _this) + { + YAMLMappingNode keyNode = new YAMLMappingNode(); + keyNode.Add(kvp.Key.Item1, kvp.Key.Item2); + YAMLMappingNode kvpMap = new YAMLMappingNode(); + kvpMap.Add("first", keyNode); + kvpMap.Add("second", kvp.Value); + node.Add(kvpMap); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary, string> _this) + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.BlockCurve); + foreach (var kvp in _this) + { + YAMLMappingNode keyNode = new YAMLMappingNode(); + keyNode.Add(kvp.Key.Item1, kvp.Key.Item2); + YAMLMappingNode kvpMap = new YAMLMappingNode(); + kvpMap.Add("first", keyNode); + kvpMap.Add("second", kvp.Value); + node.Add(kvpMap); + } + return node; + } + + public static YAMLNode ExportYAML(this IReadOnlyDictionary, string> _this, Func converter) + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.BlockCurve); + foreach (var kvp in _this) + { + YAMLMappingNode keyNode = new YAMLMappingNode(); + keyNode.Add(converter(kvp.Key.Item1), kvp.Key.Item2); + YAMLMappingNode kvpMap = new YAMLMappingNode(); + kvpMap.Add("first", keyNode); + kvpMap.Add("second", kvp.Value); + node.Add(kvpMap); + } + return node; + } + } +} diff --git a/AssetStudio/YAML/Utils/Extensions/IEnumerableYAMLExtensions.cs b/AssetStudio/YAML/Utils/Extensions/IEnumerableYAMLExtensions.cs new file mode 100644 index 0000000..ade9ef8 --- /dev/null +++ b/AssetStudio/YAML/Utils/Extensions/IEnumerableYAMLExtensions.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace AssetStudio +{ + public static class IEnumerableYAMLExtensions + { + public static YAMLNode ExportYAML(this IEnumerable _this) + { + StringBuilder sb = new StringBuilder(); + foreach (bool value in _this) + { + byte bvalue = unchecked((byte)(value ? 1 : 0)); + sb.AppendHex(bvalue); + } + return new YAMLScalarNode(sb.ToString(), true); + } + + public static YAMLNode ExportYAML(this IEnumerable _this) + { + StringBuilder sb = new StringBuilder(); + foreach (char value in _this) + { + sb.AppendHex((ushort)value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + + public static YAMLNode ExportYAML(this IEnumerable _this) + { + StringBuilder sb = new StringBuilder(); + foreach (byte value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + + public static YAMLNode ExportYAML(this IEnumerable _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(); + foreach (ushort value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (ushort value in _this) + { + node.Add(value); + } + return node; + } + } + + public static YAMLNode ExportYAML(this IEnumerable _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(); + foreach (short value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (short value in _this) + { + node.Add(value); + } + return node; + } + } + + public static YAMLNode ExportYAML(this IEnumerable _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(); + foreach (uint value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (uint value in _this) + { + node.Add(value); + } + return node; + } + } + + public static YAMLNode ExportYAML(this IEnumerable _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(); + foreach (int value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (int value in _this) + { + node.Add(value); + } + return node; + } + } + + public static YAMLNode ExportYAML(this IEnumerable _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(); + foreach (ulong value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (ulong value in _this) + { + node.Add(value); + } + return node; + } + } + + public static YAMLNode ExportYAML(this IEnumerable _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(); + foreach (long value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (long value in _this) + { + node.Add(value); + } + return node; + } + } + + public static YAMLNode ExportYAML(this IEnumerable _this) + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (float value in _this) + { + node.Add(value); + } + return node; + } + + public static YAMLNode ExportYAML(this IEnumerable _this) + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (double value in _this) + { + node.Add(value); + } + return node; + } + + public static YAMLNode ExportYAML(this IEnumerable _this) + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (string value in _this) + { + node.Add(value); + } + return node; + } + + public static YAMLNode ExportYAML(this IEnumerable> _this) + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (IEnumerable export in _this) + { + node.Add(export.ExportYAML()); + } + return node; + } + + public static YAMLNode ExportYAML(this IEnumerable _this, int[] version) + where T : IYAMLExportable + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (T export in _this) + { + node.Add(export.ExportYAML(version)); + } + return node; + } + + public static YAMLNode ExportYAML(this IEnumerable> _this, int[] version) + where T : IYAMLExportable + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (IEnumerable export in _this) + { + node.Add(export.ExportYAML(version)); + } + return node; + } + + public static YAMLNode ExportYAML(this IEnumerable> _this, int[] version) + where T : IYAMLExportable + { + YAMLSequenceNode node = new YAMLSequenceNode(); + foreach (var kvp in _this) + { + YAMLMappingNode map = new YAMLMappingNode(); + map.Add(kvp.Item1, kvp.Item2.ExportYAML(version)); + node.Add(map); + } + return node; + } + + public static YAMLNode ExportYAML(this IEnumerable> _this, Func converter, int[] version) + where T2 : IYAMLExportable + { + YAMLSequenceNode node = new YAMLSequenceNode(); + foreach (var kvp in _this) + { + YAMLMappingNode map = new YAMLMappingNode(); + map.Add(converter(kvp.Item1), kvp.Item2.ExportYAML(version)); + node.Add(map); + } + return node; + } + + public static YAMLNode ExportYAML(this IEnumerable> _this, int[] version) + where T : IYAMLExportable + { + YAMLSequenceNode node = new YAMLSequenceNode(); + foreach (var kvp in _this) + { + YAMLMappingNode map = new YAMLMappingNode(); + map.Add(kvp.Key, kvp.Value.ExportYAML(version)); + node.Add(map); + } + return node; + } + } +} diff --git a/AssetStudio/YAML/Utils/Extensions/IListYAMLExtensions.cs b/AssetStudio/YAML/Utils/Extensions/IListYAMLExtensions.cs new file mode 100644 index 0000000..93a654f --- /dev/null +++ b/AssetStudio/YAML/Utils/Extensions/IListYAMLExtensions.cs @@ -0,0 +1,171 @@ +using System.Collections.Generic; +using System.Text; + +namespace AssetStudio +{ + public static class IListYAMLExtensions + { + public static YAMLNode ExportYAML(this IReadOnlyList _this) + { + StringBuilder sb = new StringBuilder(_this.Count * 2); + foreach (bool value in _this) + { + byte bvalue = unchecked((byte)(value ? 1 : 0)); + sb.AppendHex(bvalue); + } + return new YAMLScalarNode(sb.ToString(), true); + } + + public static YAMLNode ExportYAML(this IReadOnlyList _this) + { + StringBuilder sb = new StringBuilder(_this.Count * 4); + foreach (char value in _this) + { + sb.AppendHex((ushort)value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + + public static YAMLNode ExportYAML(this IReadOnlyList _this) + { + StringBuilder sb = new StringBuilder(_this.Count * 2); + foreach (byte value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + + public static YAMLNode ExportYAML(this IReadOnlyList _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(_this.Count * 4); + foreach (ushort value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (ushort value in _this) + { + node.Add(value); + } + return node; + } + } + + public static YAMLNode ExportYAML(this IReadOnlyList _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(_this.Count * 4); + foreach (short value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (short value in _this) + { + node.Add(value); + } + return node; + } + } + + public static YAMLNode ExportYAML(this IReadOnlyList _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(_this.Count * 8); + foreach (uint value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (uint value in _this) + { + node.Add(value); + } + return node; + } + } + + public static YAMLNode ExportYAML(this IReadOnlyList _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(_this.Count * 8); + foreach (int value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (int value in _this) + { + node.Add(value); + } + return node; + } + } + + public static YAMLNode ExportYAML(this IReadOnlyList _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(_this.Count * 16); + foreach (ulong value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (ulong value in _this) + { + node.Add(value); + } + return node; + } + } + + public static YAMLNode ExportYAML(this IReadOnlyList _this, bool isRaw) + { + if (isRaw) + { + StringBuilder sb = new StringBuilder(_this.Count * 16); + foreach (long value in _this) + { + sb.AppendHex(value); + } + return new YAMLScalarNode(sb.ToString(), true); + } + else + { + YAMLSequenceNode node = new YAMLSequenceNode(SequenceStyle.Block); + foreach (long value in _this) + { + node.Add(value); + } + return node; + } + } + } +} diff --git a/AssetStudio/YAML/Utils/Extensions/PrimitiveExtensions.cs b/AssetStudio/YAML/Utils/Extensions/PrimitiveExtensions.cs new file mode 100644 index 0000000..af49cbb --- /dev/null +++ b/AssetStudio/YAML/Utils/Extensions/PrimitiveExtensions.cs @@ -0,0 +1,79 @@ +namespace AssetStudio +{ + public static class PrimitiveExtensions + { + public static int ParseDigit(this char _this) + { + return _this - '0'; + } + + public static string ToHexString(this byte _this) + { + return _this.ToString("x2"); + } + + public static string ToHexString(this short _this) + { + ushort value = unchecked((ushort)_this); + return ToHexString(value); + } + + public static string ToHexString(this ushort _this) + { + ushort reverse = unchecked((ushort)(((0xFF00 & _this) >> 8) | ((0x00FF & _this) << 8))); + return reverse.ToString("x4"); + } + + public static string ToHexString(this int _this) + { + uint value = unchecked((uint)_this); + return ToHexString(value); + } + + public static string ToHexString(this uint _this) + { + uint reverse = ((0xFF000000 & _this) >> 24) | ((0x00FF0000 & _this) >> 8) | ((0x0000FF00 & _this) << 8) | ((0x000000FF & _this) << 24); + return reverse.ToString("x8"); + } + + public static string ToHexString(this long _this) + { + ulong value = unchecked((ulong)_this); + return ToHexString(value); + } + + public static string ToHexString(this ulong _this) + { + ulong reverse = (_this & 0x00000000000000FFUL) << 56 | (_this & 0x000000000000FF00UL) << 40 | + (_this & 0x0000000000FF0000UL) << 24 | (_this & 0x00000000FF000000UL) << 8 | + (_this & 0x000000FF00000000UL) >> 8 | (_this & 0x0000FF0000000000UL) >> 24 | + (_this & 0x00FF000000000000UL) >> 40 | (_this & 0xFF00000000000000UL) >> 56; + return reverse.ToString("x16"); + } + + public static string ToHexString(this float _this) + { + uint value = BitConverterExtensions.ToUInt32(_this); + return ToHexString(value); + } + + public static string ToHexString(this double _this) + { + ulong value = BitConverterExtensions.ToUInt64(_this); + return ToHexString(value); + } + + public static int ToClosestInt(this long _this) + { + if (_this > int.MaxValue) + { + return int.MaxValue; + } + if (_this < int.MinValue) + { + return int.MinValue; + } + return unchecked((int)_this); + } + } +} diff --git a/AssetStudio/YAML/Utils/Extensions/StringBuilderExtensions.cs b/AssetStudio/YAML/Utils/Extensions/StringBuilderExtensions.cs new file mode 100644 index 0000000..04b0057 --- /dev/null +++ b/AssetStudio/YAML/Utils/Extensions/StringBuilderExtensions.cs @@ -0,0 +1,87 @@ +using System.Text; + +namespace AssetStudio +{ + public static class StringBuilderExtensions + { + static StringBuilderExtensions() + { + for (int i = 0; i <= byte.MaxValue; i++) + { + ByteHexRepresentations[i] = i.ToString("x2"); + } + } + + public static StringBuilder AppendHex(this StringBuilder _this, byte value) + { + _this.Append(ByteHexRepresentations[value]); + return _this; + } + + public static StringBuilder AppendHex(this StringBuilder _this, ushort value) + { + _this.Append(ByteHexRepresentations[(value >> 0) & 0xFF]); + _this.Append(ByteHexRepresentations[(value >> 8) & 0xFF]); + return _this; + } + + public static StringBuilder AppendHex(this StringBuilder _this, short value) + { + return AppendHex(_this, unchecked((ushort)value)); + } + + public static StringBuilder AppendHex(this StringBuilder _this, uint value) + { + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 0) & 0xFF)]); + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 8) & 0xFF)]); + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 16) & 0xFF)]); + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 24) & 0xFF)]); + return _this; + } + + public static StringBuilder AppendHex(this StringBuilder _this, int value) + { + return AppendHex(_this, unchecked((uint)value)); + } + + public static StringBuilder AppendHex(this StringBuilder _this, ulong value) + { + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 0) & 0xFF)]); + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 8) & 0xFF)]); + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 16) & 0xFF)]); + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 24) & 0xFF)]); + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 32) & 0xFF)]); + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 40) & 0xFF)]); + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 48) & 0xFF)]); + _this.Append(ByteHexRepresentations[unchecked((int)(value >> 56) & 0xFF)]); + return _this; + } + + public static StringBuilder AppendHex(this StringBuilder _this, long value) + { + return AppendHex(_this, unchecked((ulong)value)); + } + + public static StringBuilder AppendHex(this StringBuilder _this, float value) + { + return AppendHex(_this, BitConverterExtensions.ToUInt32(value)); + } + + public static StringBuilder AppendHex(this StringBuilder _this, double value) + { + return AppendHex(_this, BitConverterExtensions.ToUInt64(value)); + } + + public static StringBuilder AppendIndent(this StringBuilder _this, int count) + { + for (int i = 0; i < count; i++) + { + _this.Append('\t'); + } + return _this; + } + + public static readonly string HexAlphabet = "0123456789abcdef"; + public static readonly string[] ByteHexRepresentations = new string[256]; + } +} diff --git a/AssetStudio/YAML/Utils/Extensions/YAMLMappingNodeExtensions.cs b/AssetStudio/YAML/Utils/Extensions/YAMLMappingNodeExtensions.cs new file mode 100644 index 0000000..034b37f --- /dev/null +++ b/AssetStudio/YAML/Utils/Extensions/YAMLMappingNodeExtensions.cs @@ -0,0 +1,31 @@ +namespace AssetStudio +{ + public static class YAMLMappingNodeExtensions + { + public static void AddSerializedVersion(this YAMLMappingNode _this, int version) + { + if(version > 1) + { + _this.Add(SerializedVersionName, version); + } + } + + public static void ForceAddSerializedVersion(this YAMLMappingNode _this, int version) + { + if (version > 0) + { + _this.Add(SerializedVersionName, version); + } + } + + public static void InsertSerializedVersion(this YAMLMappingNode _this, int version) + { + if(version > 1) + { + _this.InsertBegin(SerializedVersionName, version); + } + } + + public const string SerializedVersionName = "serializedVersion"; + } +} diff --git a/AssetStudioCLI/Exporter.cs b/AssetStudioCLI/Exporter.cs index a27b031..b684669 100644 --- a/AssetStudioCLI/Exporter.cs +++ b/AssetStudioCLI/Exporter.cs @@ -316,6 +316,16 @@ namespace AssetStudioCLI return false; } + public static bool ExportAnimationClip(AssetItem item, string exportPath) + { + if (!TryExportFile(exportPath, item, ".anim", out var exportFullPath)) + return false; + var m_AnimationClip = (AnimationClip)item.Asset; + var str = m_AnimationClip.Convert(); + File.WriteAllText(exportFullPath, str); + return true; + } + public static bool ExportAnimator(AssetItem item, string exportPath, List animationList = null) { var exportFullPath = Path.Combine(exportPath, item.Text, item.Text + ".fbx"); @@ -415,7 +425,7 @@ namespace AssetStudioCLI case ClassIDType.Animator: return ExportAnimator(item, exportPath); case ClassIDType.AnimationClip: - return false; + return ExportAnimationClip(item, exportPath); case ClassIDType.MiHoYoBinData: return ExportMiHoYoBinData(item, exportPath); default: diff --git a/AssetStudioGUI/AssetStudioGUI.csproj b/AssetStudioGUI/AssetStudioGUI.csproj index 2a0b098..845fca1 100644 --- a/AssetStudioGUI/AssetStudioGUI.csproj +++ b/AssetStudioGUI/AssetStudioGUI.csproj @@ -78,6 +78,17 @@ + + + PreserveNewest + x86\acldb.dll + + + PreserveNewest + x64\acldb.dll + + + diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 06b8061..cd0188a 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -835,8 +835,8 @@ namespace AssetStudioGUI //StatusStripUpdate("Can be exported to FBX file."); PreviewAnimator(m_Animator); break; - case AnimationClip _: - StatusStripUpdate("Can be exported with Animator or Objects"); + case AnimationClip m_AnimationClip: + PreviewAnimationClip(m_AnimationClip); break; case MiHoYoBinData m_MiHoYoBinData: PreviewText(m_MiHoYoBinData.AsString); @@ -1288,6 +1288,12 @@ namespace AssetStudioGUI var model = new ModelConverter(m_Animator, Properties.Settings.Default.convertType, Studio.Game, false, Array.Empty()); PreviewModel(model); } + + private void PreviewAnimationClip(AnimationClip clip) + { + var str = clip.Convert(); + PreviewText(str.Replace("\n", "\r\n")); + } private void PreviewModel(ModelConverter model) { diff --git a/AssetStudioGUI/Exporter.cs b/AssetStudioGUI/Exporter.cs index c3c424a..9d758be 100644 --- a/AssetStudioGUI/Exporter.cs +++ b/AssetStudioGUI/Exporter.cs @@ -315,6 +315,15 @@ namespace AssetStudioGUI } return false; } + public static bool ExportAnimationClip(AssetItem item, string exportPath) + { + if (!TryExportFile(exportPath, item, ".anim", out var exportFullPath)) + return false; + var m_AnimationClip = (AnimationClip)item.Asset; + var str = m_AnimationClip.Convert(); + File.WriteAllText(exportFullPath, str); + return true; + } public static bool ExportAnimator(AssetItem item, string exportPath, List animationList = null) { @@ -427,7 +436,7 @@ namespace AssetStudioGUI case ClassIDType.Animator: return ExportAnimator(item, exportPath); case ClassIDType.AnimationClip: - return false; + return ExportAnimationClip(item, exportPath); case ClassIDType.MiHoYoBinData: return ExportMiHoYoBinData(item, exportPath); default: diff --git a/AssetStudioGUI/Libraries/x64/acldb.dll b/AssetStudioGUI/Libraries/x64/acldb.dll new file mode 100644 index 0000000..3ec4435 Binary files /dev/null and b/AssetStudioGUI/Libraries/x64/acldb.dll differ diff --git a/AssetStudioGUI/Libraries/x86/acldb.dll b/AssetStudioGUI/Libraries/x86/acldb.dll new file mode 100644 index 0000000..0698c85 Binary files /dev/null and b/AssetStudioGUI/Libraries/x86/acldb.dll differ diff --git a/AssetStudioUtility/ACL/ACL.cs b/AssetStudioUtility/ACL/ACL.cs index 07f2677..f0fa4b1 100644 --- a/AssetStudioUtility/ACL/ACL.cs +++ b/AssetStudioUtility/ACL/ACL.cs @@ -74,4 +74,48 @@ namespace ACLLibs #endregion } + + public static class DBACL + { + private const string DLL_NAME = "acldb"; + static DBACL() + { + DllLoader.PreloadDll(DLL_NAME); + } + public static void DecompressTracks(byte[] data, byte[] db, out float[] values, out float[] times) + { + var decompressedClip = new DecompressedClip(); + + var dataPtr = Marshal.AllocHGlobal(data.Length + 8); + var dataAligned = new IntPtr(16 * (((long)dataPtr + 15) / 16)); + Marshal.Copy(data, 0, dataPtr, data.Length); + + var dbPtr = Marshal.AllocHGlobal(db.Length + 8); + var dbAligned = new IntPtr(16 * (((long)dbPtr + 15) / 16)); + Marshal.Copy(db, 0, dbAligned, db.Length); + + DecompressTracks(dataAligned, dbAligned, ref decompressedClip); + + Marshal.FreeHGlobal(dataPtr); + Marshal.FreeHGlobal(dbPtr); + + values = new float[decompressedClip.ValuesCount]; + Marshal.Copy(decompressedClip.Values, values, 0, decompressedClip.ValuesCount); + + times = new float[decompressedClip.TimesCount]; + Marshal.Copy(decompressedClip.Times, times, 0, decompressedClip.TimesCount); + + Dispose(ref decompressedClip); + } + + #region importfunctions + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + private static extern void DecompressTracks(nint data, nint db, ref DecompressedClip decompressedClip); + + [DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)] + private static extern void Dispose(ref DecompressedClip decompressedClip); + + #endregion + } } diff --git a/AssetStudioUtility/ACL/ACLExtensions.cs b/AssetStudioUtility/ACL/ACLExtensions.cs index d64967b..9feb908 100644 --- a/AssetStudioUtility/ACL/ACLExtensions.cs +++ b/AssetStudioUtility/ACL/ACLExtensions.cs @@ -4,7 +4,23 @@ namespace AssetStudio { public static class ACLExtensions { - public static void Process(this ACLClip m_ACLClip, out float[] values, out float[] times) => ACL.DecompressAll(m_ACLClip.m_ClipData, out values, out times); - public static void ProcessSR(this ACLClip m_ACLClip, out float[] values, out float[] times) => SRACL.DecompressAll(m_ACLClip.m_ClipData, out values, out times); + public static void Process(this ACLClip m_ACLClip, Game game, out float[] values, out float[] times) + { + if (game.Type.IsSRGroup()) + { + SRACL.DecompressAll(m_ACLClip.m_ClipData, out values, out times); + } + else + { + if (!m_ACLClip.m_DatabaseData.IsNullOrEmpty()) + { + DBACL.DecompressTracks(m_ACLClip.m_ClipData, m_ACLClip.m_DatabaseData, out values, out times); + } + else + { + ACL.DecompressAll(m_ACLClip.m_ClipData, out values, out times); + } + } + } } } diff --git a/AssetStudioUtility/ModelConverter.cs b/AssetStudioUtility/ModelConverter.cs index 9796e80..d3b8896 100644 --- a/AssetStudioUtility/ModelConverter.cs +++ b/AssetStudioUtility/ModelConverter.cs @@ -899,7 +899,7 @@ namespace AssetStudio var aclCount = m_ACLClip.m_CurveCount; if (!m_ACLClip.m_ClipData.IsNullOrEmpty() && !Game.Type.IsSRGroup()) { - m_ACLClip.Process(out var values, out var times); + m_ACLClip.Process(Game, out var values, out var times); for (int frameIndex = 0; frameIndex < times.Length; frameIndex++) { var time = times[frameIndex]; @@ -940,7 +940,7 @@ namespace AssetStudio } if (!m_ACLClip.m_ClipData.IsNullOrEmpty() && Game.Type.IsSRGroup()) { - m_ACLClip.ProcessSR(out var values, out var times); + m_ACLClip.Process(Game, out var values, out var times); for (int frameIndex = 0; frameIndex < times.Length; frameIndex++) { var time = times[frameIndex]; diff --git a/AssetStudioUtility/YAML/AnimationClipConverter.cs b/AssetStudioUtility/YAML/AnimationClipConverter.cs new file mode 100644 index 0000000..942c0ba --- /dev/null +++ b/AssetStudioUtility/YAML/AnimationClipConverter.cs @@ -0,0 +1,538 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using SevenZip; + +namespace AssetStudio +{ + public class AnimationClipConverter + { + public static readonly Regex UnknownPathRegex = new Regex($@"^{UnknownPathPrefix}[0-9]{{1,10}}$", RegexOptions.Compiled); + + private const string UnknownPathPrefix = "path_"; + private const string MissedPropertyPrefix = "missed_"; + private const string ScriptPropertyPrefix = "script_"; + private const string TypeTreePropertyPrefix = "typetree_"; + + private readonly Game game; + private readonly AnimationClip animationClip; + private readonly CustomCurveResolver m_customCurveResolver; + + private readonly Dictionary>> m_translations = new Dictionary>>(); + private readonly Dictionary>> m_rotations = new Dictionary>>(); + private readonly Dictionary>> m_scales = new Dictionary>>(); + private readonly Dictionary>> m_eulers = new Dictionary>>(); + private readonly Dictionary>> m_floats = new Dictionary>>(); + private readonly Dictionary> m_pptrs = new Dictionary>(); + + public Vector3Curve[] Translations { get; private set; } + public QuaternionCurve[] Rotations { get; private set; } + public Vector3Curve[] Scales { get; private set; } + public Vector3Curve[] Eulers { get; private set; } + public FloatCurve[] Floats { get; private set; } + public PPtrCurve[] PPtrs { get; private set; } + + public AnimationClipConverter(AnimationClip clip) + { + game = clip.assetsFile.game; + animationClip = clip; + m_customCurveResolver = new CustomCurveResolver(animationClip); + } + + public static AnimationClipConverter Process(AnimationClip clip) + { + var converter = new AnimationClipConverter(clip); + converter.ProcessInner(); + return converter; + } + private void ProcessInner() + { + var m_Clip = animationClip.m_MuscleClip.m_Clip; + var bindings = animationClip.m_ClipBindingConstant; + var tos = animationClip.FindTOS(); + + var streamedFrames = m_Clip.m_StreamedClip.ReadData(); + var lastDenseFrame = m_Clip.m_DenseClip.m_FrameCount / m_Clip.m_DenseClip.m_SampleRate; + var lastSampleFrame = streamedFrames.Count > 1 ? streamedFrames[streamedFrames.Count - 2].time : 0.0f; + var lastFrame = Math.Max(lastDenseFrame, lastSampleFrame); + + if (!m_Clip.m_ACLClip.m_ClipData.IsNullOrEmpty() && !game.Type.IsSRGroup()) + { + var lastACLFrame = ProcessACLClip(m_Clip, bindings, tos); + lastFrame = Math.Max(lastFrame, lastACLFrame); + animationClip.m_Compressed = false; + } + ProcessStreams(streamedFrames, bindings, tos, m_Clip.m_DenseClip.m_SampleRate); + ProcessDenses(m_Clip, bindings, tos); + if (!m_Clip.m_ACLClip.m_ClipData.IsNullOrEmpty() && game.Type.IsSRGroup()) + { + var lastACLFrame = ProcessACLClip(m_Clip, bindings, tos); + lastFrame = Math.Max(lastFrame, lastACLFrame); + animationClip.m_Compressed = false; + } + if (m_Clip.m_ConstantClip != null) + { + ProcessConstant(m_Clip, bindings, tos, lastFrame); + } + CreateCurves(); + } + + private void CreateCurves() + { + m_translations.AsEnumerable().ToList().ForEach(x => x.Key.curve.m_Curve.AddRange(x.Value)); + Translations = m_translations.Keys.ToArray(); + m_rotations.AsEnumerable().ToList().ForEach(x => x.Key.curve.m_Curve.AddRange(x.Value)); + Rotations = m_rotations.Keys.ToArray(); + m_scales.AsEnumerable().ToList().ForEach(x => x.Key.curve.m_Curve.AddRange(x.Value)); + Scales = m_scales.Keys.ToArray(); + m_eulers.AsEnumerable().ToList().ForEach(x => x.Key.curve.m_Curve.AddRange(x.Value)); + Eulers = m_eulers.Keys.ToArray(); + m_floats.AsEnumerable().ToList().ForEach(x => x.Key.curve.m_Curve.AddRange(x.Value)); + Floats = m_floats.Keys.ToArray(); + m_pptrs.AsEnumerable().ToList().ForEach(x => x.Key.curve.AddRange(x.Value)); + PPtrs = m_pptrs.Keys.ToArray(); + } + + private void ProcessStreams(List streamFrames, AnimationClipBindingConstant bindings, Dictionary tos, float sampleRate) + { + var curveValues = new float[4]; + var inSlopeValues = new float[4]; + var outSlopeValues = new float[4]; + var interval = 1.0f / sampleRate; + + // first (index [0]) stream frame is for slope calculation for the first real frame (index [1]) + // last one (index [count - 1]) is +Infinity + // it is made for slope processing, but we don't need them + for (var frameIndex = 1; frameIndex < streamFrames.Count - 1; frameIndex++) + { + var frame = streamFrames[frameIndex]; + for (var curveIndex = 0; curveIndex < frame.keyList.Length;) + { + var curve = frame.keyList[curveIndex]; + var index = curve.index; + if (!game.Type.IsSRGroup()) + index += (int)animationClip.m_MuscleClip.m_Clip.m_ACLClip.m_CurveCount; + var binding = bindings.FindBinding(index); + + var path = GetCurvePath(tos, binding.path); + if (binding.typeID == ClassIDType.Transform) + { + GetPreviousFrame(streamFrames, curve.index, frameIndex, out var prevFrameIndex, out var prevCurveIndex); + var dimension = binding.GetDimension(); + for (int key = 0; key < dimension; key++) + { + var keyCurve = frame.keyList[curveIndex]; + var prevFrame = streamFrames[prevFrameIndex]; + var prevKeyCurve = prevFrame.keyList[prevCurveIndex + key]; + var deltaTime = frame.time - prevFrame.time; + curveValues[key] = keyCurve.value; + inSlopeValues[key] = prevKeyCurve.CalculateNextInSlope(deltaTime, keyCurve); + outSlopeValues[key] = keyCurve.outSlope; + curveIndex = GetNextCurve(frame, curveIndex); + } + + AddTransformCurve(frame.time, binding.attribute, curveValues, inSlopeValues, outSlopeValues, 0, path); + } + else if ((BindingCustomType)binding.customType == BindingCustomType.None) + { + AddDefaultCurve(binding, path, frame.time, frame.keyList[curveIndex].value); + curveIndex = GetNextCurve(frame, curveIndex); + } + else + { + AddCustomCurve(bindings, binding, path, frame.time, frame.keyList[curveIndex].value); + curveIndex = GetNextCurve(frame, curveIndex); + } + } + } + } + + private void ProcessDenses(Clip clip, AnimationClipBindingConstant bindings, Dictionary tos) + { + var dense = clip.m_DenseClip; + var streamCount = clip.m_StreamedClip.curveCount; + var slopeValues = new float[4]; // no slopes - 0 values + for (var frameIndex = 0; frameIndex < dense.m_FrameCount; frameIndex++) + { + var time = frameIndex / dense.m_SampleRate; + var frameOffset = frameIndex * (int)dense.m_CurveCount; + for (var curveIndex = 0; curveIndex < dense.m_CurveCount;) + { + var index = (int)streamCount + curveIndex; + if (!game.Type.IsSRGroup()) + index += (int)clip.m_ACLClip.m_CurveCount; + var binding = bindings.FindBinding(index); + var path = GetCurvePath(tos, binding.path); + var framePosition = frameOffset + curveIndex; + if (binding.typeID == ClassIDType.Transform) + { + AddTransformCurve(time, binding.attribute, dense.m_SampleArray, slopeValues, slopeValues, framePosition, path); + curveIndex += binding.GetDimension(); + } + else if ((BindingCustomType)binding.customType == BindingCustomType.None) + { + AddDefaultCurve(binding, path, time, dense.m_SampleArray[framePosition]); + curveIndex++; + } + else + { + AddCustomCurve(bindings, binding, path, time, dense.m_SampleArray[framePosition]); + curveIndex++; + } + } + } + } + private float ProcessACLClip(Clip clip, AnimationClipBindingConstant bindings, Dictionary tos) + { + var acl = clip.m_ACLClip; + acl.Process(game, out var values, out var times); + float[] slopeValues = new float[4]; // no slopes - 0 values + + int frameCount = times.Length; + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float time = times[frameIndex]; + int frameOffset = frameIndex * (int)acl.m_CurveCount; + for (int curveIndex = 0; curveIndex < acl.m_CurveCount;) + { + var index = curveIndex; + if (game.Type.IsSRGroup()) + index += (int)(clip.m_DenseClip.m_CurveCount + clip.m_StreamedClip.curveCount); + GenericBinding binding = bindings.FindBinding(index); + string path = GetCurvePath(tos, binding.path); + int framePosition = frameOffset + curveIndex; + if (binding.typeID == ClassIDType.Transform) + { + AddTransformCurve(time, binding.attribute, values, slopeValues, slopeValues, framePosition, path); + curveIndex += binding.GetDimension(); + } + else if ((BindingCustomType)binding.customType == BindingCustomType.None) + { + AddDefaultCurve(binding, path, time, values[framePosition]); + curveIndex++; + } + else + { + AddCustomCurve(bindings, binding, path, time, values[framePosition]); + curveIndex++; + } + } + } + + return times[frameCount - 1]; + } + private void ProcessConstant(Clip clip, AnimationClipBindingConstant bindings, Dictionary tos, float lastFrame) + { + var constant = clip.m_ConstantClip; + var streamCount = clip.m_StreamedClip.curveCount; + var denseCount = clip.m_DenseClip.m_CurveCount; + var slopeValues = new float[4]; // no slopes - 0 values + + // only first and last frames + var time = 0.0f; + for (var i = 0; i < 2; i++, time += lastFrame) + { + for (var curveIndex = 0; curveIndex < constant.data.Length;) + { + var index = (int)(streamCount + denseCount + curveIndex); + if (!clip.m_ACLClip.m_ClipData.IsNullOrEmpty()) + index += (int)clip.m_ACLClip.m_CurveCount; + GenericBinding binding = bindings.FindBinding(index); + string path = GetCurvePath(tos, binding.path); + if (binding.typeID == ClassIDType.Transform) + { + AddTransformCurve(time, binding.attribute, constant.data, slopeValues, slopeValues, curveIndex, path); + curveIndex += binding.GetDimension(); + } + else if ((BindingCustomType)binding.customType == BindingCustomType.None) + { + AddDefaultCurve(binding, path, time, constant.data[curveIndex]); + curveIndex++; + } + else + { + AddCustomCurve(bindings, binding, path, time, constant.data[curveIndex]); + curveIndex++; + } + } + } + } + + private void AddCustomCurve(AnimationClipBindingConstant bindings, GenericBinding binding, string path, float time, float value) + { + switch ((BindingCustomType)binding.customType) + { + case BindingCustomType.AnimatorMuscle: + AddAnimatorMuscleCurve(binding, time, value); + break; + default: + string attribute = m_customCurveResolver.ToAttributeName((BindingCustomType)binding.customType, binding.attribute, path); + if (binding.isPPtrCurve == 0x01) + { + PPtrCurve curve = new PPtrCurve(path, attribute, binding.typeID, binding.script.Cast()); + AddPPtrKeyframe(curve, bindings, time, (int)value); + } + else + { + FloatCurve curve = new FloatCurve(path, attribute, binding.typeID, binding.script.Cast()); + AddFloatKeyframe(curve, time, value); + } + break; + } + } + + private void AddTransformCurve(float time, uint transType, float[] curveValues, + float[] inSlopeValues, float[] outSlopeValues, int offset, string path) + { + switch (transType) + { + case 1: + { + var curve = new Vector3Curve(path); + if (!m_translations.TryGetValue(curve, out List> transCurve)) + { + transCurve = new List>(); + m_translations.Add(curve, transCurve); + } + + float x = curveValues[offset + 0]; + float y = curveValues[offset + 1]; + float z = curveValues[offset + 2]; + + float inX = inSlopeValues[0]; + float inY = inSlopeValues[1]; + float inZ = inSlopeValues[2]; + + float outX = outSlopeValues[0]; + float outY = outSlopeValues[1]; + float outZ = outSlopeValues[2]; + + Vector3 value = new Vector3(x, y, z); + Vector3 inSlope = new Vector3(inX, inY, inZ); + Vector3 outSlope = new Vector3(outX, outY, outZ); + Keyframe transKey = new Keyframe(time, value, inSlope, outSlope, AnimationClipExtensions.DefaultVector3Weight); + transCurve.Add(transKey); + } + break; + case 2: + { + var curve = new QuaternionCurve(path); + if (!m_rotations.TryGetValue(curve, out List> rotCurve)) + { + rotCurve = new List>(); + m_rotations.Add(curve, rotCurve); + } + + float x = curveValues[offset + 0]; + float y = curveValues[offset + 1]; + float z = curveValues[offset + 2]; + float w = curveValues[offset + 3]; + + float inX = inSlopeValues[0]; + float inY = inSlopeValues[1]; + float inZ = inSlopeValues[2]; + float inW = inSlopeValues[3]; + + float outX = outSlopeValues[0]; + float outY = outSlopeValues[1]; + float outZ = outSlopeValues[2]; + float outW = outSlopeValues[3]; + + Quaternion value = new Quaternion(x, y, z, w); + Quaternion inSlope = new Quaternion(inX, inY, inZ, inW); + Quaternion outSlope = new Quaternion(outX, outY, outZ, outW); + Keyframe rotKey = new Keyframe(time, value, inSlope, outSlope, AnimationClipExtensions.DefaultQuaternionWeight); + rotCurve.Add(rotKey); + } + break; + case 3: + { + var curve = new Vector3Curve(path); + if (!m_scales.TryGetValue(curve, out List> scaleCurve)) + { + scaleCurve = new List>(); + m_scales.Add(curve, scaleCurve); + } + + float x = curveValues[offset + 0]; + float y = curveValues[offset + 1]; + float z = curveValues[offset + 2]; + + float inX = inSlopeValues[0]; + float inY = inSlopeValues[1]; + float inZ = inSlopeValues[2]; + + float outX = outSlopeValues[0]; + float outY = outSlopeValues[1]; + float outZ = outSlopeValues[2]; + + Vector3 value = new Vector3(x, y, z); + Vector3 inSlope = new Vector3(inX, inY, inZ); + Vector3 outSlope = new Vector3(outX, outY, outZ); + Keyframe scaleKey = new Keyframe(time, value, inSlope, outSlope, AnimationClipExtensions.DefaultVector3Weight); + scaleCurve.Add(scaleKey); + } + break; + case 4: + { + var curve = new Vector3Curve(path); + if (!m_eulers.TryGetValue(curve, out List> eulerCurve)) + { + eulerCurve = new List>(); + m_eulers.Add(curve, eulerCurve); + } + + float x = curveValues[offset + 0]; + float y = curveValues[offset + 1]; + float z = curveValues[offset + 2]; + + float inX = inSlopeValues[0]; + float inY = inSlopeValues[1]; + float inZ = inSlopeValues[2]; + + float outX = outSlopeValues[0]; + float outY = outSlopeValues[1]; + float outZ = outSlopeValues[2]; + + Vector3 value = new Vector3(x, y, z); + Vector3 inSlope = new Vector3(inX, inY, inZ); + Vector3 outSlope = new Vector3(outX, outY, outZ); + Keyframe eulerKey = new Keyframe(time, value, inSlope, outSlope, AnimationClipExtensions.DefaultVector3Weight); + eulerCurve.Add(eulerKey); + } + break; + default: + throw new NotImplementedException(transType.ToString()); + } + } + + private void AddDefaultCurve(GenericBinding binding, string path, float time, float value) + { + switch (binding.typeID) + { + case ClassIDType.GameObject: + { + AddGameObjectCurve(binding, path, time, value); + } + break; + + case ClassIDType.MonoBehaviour: + { + AddScriptCurve(binding, path, time, value); + } + break; + + default: + AddEngineCurve(binding, path, time, value); + break; + } + } + + private void AddGameObjectCurve(GenericBinding binding, string path, float time, float value) + { + if (binding.attribute == CRC.CalculateDigestAscii("m_IsActive")) + { + FloatCurve curve = new FloatCurve(path, "m_IsActive", ClassIDType.GameObject, new PPtr(0, 0, null)); + AddFloatKeyframe(curve, time, value); + return; + } + else + { + // that means that dev exported animation clip with missing component + FloatCurve curve = new FloatCurve(path, MissedPropertyPrefix + binding.attribute, ClassIDType.GameObject, new PPtr(0, 0, null)); + AddFloatKeyframe(curve, time, value); + } + } + + private void AddScriptCurve(GenericBinding binding, string path, float time, float value) + { +#warning TODO: + FloatCurve curve = new FloatCurve(path, ScriptPropertyPrefix + binding.attribute, ClassIDType.MonoBehaviour, binding.script.Cast()); + AddFloatKeyframe(curve, time, value); + } + + private void AddEngineCurve(GenericBinding binding, string path, float time, float value) + { +#warning TODO: + FloatCurve curve = new FloatCurve(path, TypeTreePropertyPrefix + binding.attribute, binding.typeID, new PPtr(0, 0, null)); + AddFloatKeyframe(curve, time, value); + } + + private void AddAnimatorMuscleCurve(GenericBinding binding, float time, float value) + { + FloatCurve curve = new FloatCurve(string.Empty, binding.GetHumanoidMuscle().ToAttributeString(), ClassIDType.Animator, new PPtr(0, 0, null)); + AddFloatKeyframe(curve, time, value); + } + + private void AddFloatKeyframe(FloatCurve curve, float time, float value) + { + if (!m_floats.TryGetValue(curve, out List> floatCurve)) + { + floatCurve = new List>(); + m_floats.Add(curve, floatCurve); + } + + Keyframe floatKey = new Keyframe(time, value, default, default, AnimationClipExtensions.DefaultFloatWeight); + floatCurve.Add(floatKey); + } + + private void AddPPtrKeyframe(PPtrCurve curve, AnimationClipBindingConstant bindings, float time, int index) + { + if (!m_pptrs.TryGetValue(curve, out List pptrCurve)) + { + pptrCurve = new List(); + m_pptrs.Add(curve, pptrCurve); + AddPPtrKeyframe(curve, bindings, 0.0f, index - 1); + } + + PPtr value = bindings.pptrCurveMapping[index]; + PPtrKeyframe pptrKey = new PPtrKeyframe(time, value); + pptrCurve.Add(pptrKey); + } + + private void GetPreviousFrame(List streamFrames, int curveID, int currentFrame, out int frameIndex, out int curveIndex) + { + for (frameIndex = currentFrame - 1; frameIndex >= 0; frameIndex--) + { + var frame = streamFrames[frameIndex]; + for (curveIndex = 0; curveIndex < frame.keyList.Length; curveIndex++) + { + var curve = frame.keyList[curveIndex]; + if (curve.index == curveID) + { + return; + } + } + } + throw new Exception($"There is no curve with index {curveID} in any of previous frames"); + } + + private int GetNextCurve(StreamedClip.StreamedFrame frame, int currentCurve) + { + var curve = frame.keyList[currentCurve]; + int i = currentCurve + 1; + for (; i < frame.keyList.Length; i++) + { + if (frame.keyList[i].index != curve.index) + { + return i; + } + } + return i; + } + + private static string GetCurvePath(Dictionary tos, uint hash) + { + if (tos.TryGetValue(hash, out string path)) + { + return path; + } + else + { + return UnknownPathPrefix + hash; + } + } + + } +} \ No newline at end of file diff --git a/AssetStudioUtility/YAML/AnimationClipExtensions.cs b/AssetStudioUtility/YAML/AnimationClipExtensions.cs new file mode 100644 index 0000000..e4976b7 --- /dev/null +++ b/AssetStudioUtility/YAML/AnimationClipExtensions.cs @@ -0,0 +1,335 @@ +using System.IO; +using System.Linq; +using System.Text; +using System.Collections.Generic; +using SevenZip; +using System; + +namespace AssetStudio +{ + public static class AnimationClipExtensions + { + public static float DefaultFloatWeight => 1.0f / 3.0f; + public static Vector3 DefaultVector3Weight => new Vector3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f); + public static Quaternion DefaultQuaternionWeight => new Quaternion(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f); + + #region AnimationClip + public static IEnumerable FindRoots(this AnimationClip clip) + { + foreach (var asset in clip.assetsFile.assetsManager.assetsFileList.SelectMany(x => x.Objects)) + { + switch (asset.type) + { + case ClassIDType.Animator: + Animator animator = (Animator)asset; + if (clip.IsAnimatorContainsClip(animator)) + { + if (animator.m_GameObject.TryGet(out var go)) + { + yield return go; + } + } + break; + + case ClassIDType.Animation: + Animation animation = (Animation)asset; + if (clip.IsAnimationContainsClip(animation)) + { + if (animation.m_GameObject.TryGet(out var go)) + { + yield return go; + } + } + break; + } + } + + yield break; + } + public static Dictionary FindTOS(this AnimationClip clip) + { + var tos = new Dictionary() { { 0, string.Empty } }; + foreach (var asset in clip.assetsFile.assetsManager.assetsFileList.SelectMany(x => x.Objects).OrderBy(x => x.type).ToArray()) + { + switch (asset.type) + { + case ClassIDType.Avatar: + var avatar = asset as Avatar; + if (clip.AddAvatarTOS(avatar, tos)) + { + return tos; + } + break; + case ClassIDType.Animator: + var animator = asset as Animator; + if (clip.IsAnimatorContainsClip(animator)) + { + if (clip.AddAnimatorTOS(animator, tos)) + { + return tos; + } + } + break; + case ClassIDType.Animation: + var animation = asset as Animation; + if (clip.IsAnimationContainsClip(animation)) + { + if (clip.AddAnimationTOS(animation, tos)) + { + return tos; + } + } + break; + } + } + return tos; + } + private static bool AddAvatarTOS(this AnimationClip clip, Avatar avatar, Dictionary tos) + { + return clip.AddTOS(avatar.m_TOS.ToDictionary(x => x.Key, x => x.Value), tos); + } + private static bool AddAnimatorTOS(this AnimationClip clip, Animator animator, Dictionary tos) + { + if (animator.m_Avatar.TryGet(out var avatar)) + { + if (clip.AddAvatarTOS(avatar, tos)) + { + return true; + } + } + + Dictionary animatorTOS = animator.BuildTOS(); + return clip.AddTOS(animatorTOS, tos); + } + private static bool AddAnimationTOS(this AnimationClip clip, Animation animation, Dictionary tos) + { + if (animation.m_GameObject.TryGet(out var go)) + { + Dictionary animationTOS = go.BuildTOS(); + return clip.AddTOS(animationTOS, tos); + } + return false; + } + private static bool AddTOS(this AnimationClip clip, Dictionary src, Dictionary dest) + { + int tosCount = clip.m_ClipBindingConstant.genericBindings.Length; + for (int i = 0; i < tosCount; i++) + { + ref GenericBinding binding = ref clip.m_ClipBindingConstant.genericBindings[i]; + if (src.TryGetValue(binding.path, out string path)) + { + dest[binding.path] = path; + if (dest.Count == tosCount) + { + return true; + } + } + } + return false; + } + private static bool IsAnimationContainsClip(this AnimationClip clip, Animation animation) + { + return animation.IsContainsAnimationClip(clip); + } + private static bool IsAnimatorContainsClip(this AnimationClip clip, Animator animator) + { + if (animator.m_Controller.TryGet(out var runtime)) + { + return runtime.IsContainsAnimationClip(clip); + } + else + { + return false; + } + } + public static string Convert(this AnimationClip clip) + { + var converter = AnimationClipConverter.Process(clip); + clip.m_RotationCurves = converter.Rotations.Union(clip.m_RotationCurves).ToArray(); + clip.m_EulerCurves = converter.Eulers.Union(clip.m_EulerCurves).ToArray(); + clip.m_PositionCurves = converter.Translations.Union(clip.m_PositionCurves).ToArray(); + clip.m_ScaleCurves = converter.Scales.Union(clip.m_ScaleCurves).ToArray(); + clip.m_FloatCurves = converter.Floats.Union(clip.m_FloatCurves).ToArray(); + clip.m_PPtrCurves = converter.PPtrs.Union(clip.m_PPtrCurves).ToArray(); + return ConvertSerializedAnimationClip(clip); + } + public static string ConvertSerializedAnimationClip(AnimationClip animationClip) + { + var sb = new StringBuilder(); + using (var stringWriter = new StringWriter(sb)) + { + YAMLWriter writer = new YAMLWriter(); + YAMLDocument doc = ExportYAMLDocument(animationClip); + writer.AddDocument(doc); + writer.Write(stringWriter); + return sb.ToString(); + } + } + + public static YAMLDocument ExportYAMLDocument(AnimationClip animationClip) + { + var document = new YAMLDocument(); + var root = document.CreateMappingRoot(); + root.Tag = ((int)ClassIDType.AnimationClip).ToString(); + root.Anchor = ((int)ClassIDType.AnimationClip * 100000).ToString(); + var node = animationClip.ExportYAML(animationClip.version); + root.Add(ClassIDType.AnimationClip.ToString(), node); + return document; + } + public static YAMLMappingNode ExportYAML(this AnimationClip clip, int[] version) + { + var node = new YAMLMappingNode(); + node.Add(nameof(clip.m_Name), clip.m_Name); + node.AddSerializedVersion(ToSerializedVersion(version)); + node.Add(nameof(clip.m_Legacy), clip.m_Legacy); + node.Add(nameof(clip.m_Compressed), clip.m_Compressed); + node.Add(nameof(clip.m_UseHighQualityCurve), clip.m_UseHighQualityCurve); + node.Add(nameof(clip.m_RotationCurves), clip.m_RotationCurves.ExportYAML(version)); + node.Add(nameof(clip.m_CompressedRotationCurves), clip.m_CompressedRotationCurves.ExportYAML(version)); + node.Add(nameof(clip.m_EulerCurves), clip.m_EulerCurves.ExportYAML(version)); + node.Add(nameof(clip.m_PositionCurves), clip.m_PositionCurves.ExportYAML(version)); + node.Add(nameof(clip.m_ScaleCurves), clip.m_ScaleCurves.ExportYAML(version)); + node.Add(nameof(clip.m_FloatCurves), clip.m_FloatCurves.ExportYAML(version)); + node.Add(nameof(clip.m_PPtrCurves), clip.m_PPtrCurves.ExportYAML(version)); + node.Add(nameof(clip.m_SampleRate), clip.m_SampleRate); + node.Add(nameof(clip.m_WrapMode), clip.m_WrapMode); + node.Add(nameof(clip.m_Bounds), clip.m_Bounds.ExportYAML(version)); + node.Add(nameof(clip.m_ClipBindingConstant), clip.m_ClipBindingConstant.ExportYAML(version)); + node.Add("m_AnimationClipSettings", clip.m_MuscleClip.ExportYAML(version)); + node.Add(nameof(clip.m_Events), clip.m_Events.ExportYAML(version)); + return node; + } + + public static int ToSerializedVersion(int[] version) + { + if (version[0] >= 5) + { + return 6; + } + if (version[0] > 4 || (version[0] == 4 && version[1] >= 3)) + { + return 4; + } + if (version[0] > 2 || (version[0] == 2 && version[1] >= 6)) + { + return 3; + } + return 2; + } + #endregion + + #region Others + private static bool IsContainsAnimationClip(this Animation animation, AnimationClip clip) + { + foreach (PPtr ptr in animation.m_Animations) + { + if (ptr.TryGet(out var animationClip) && animationClip.Equals(clip)) + { + return true; + } + } + return false; + } + + private static Dictionary BuildTOS(this Animator animator) + { + if (animator.version[0] > 4 || (animator.version[0] == 4 && animator.version[1] >= 3)) + { + if (animator.m_HasTransformHierarchy) + { + if (animator.m_GameObject.TryGet(out var go)) + { + return go.BuildTOS(); + } + } + else + { + return new Dictionary() { { 0, string.Empty } }; + } + } + else + { + if (animator.m_GameObject.TryGet(out var go)) + { + return go.BuildTOS(); + } + } + return null; + } + private static Dictionary BuildTOS(this GameObject gameObject) + { + Dictionary tos = new Dictionary() { { 0, string.Empty } }; + gameObject.BuildTOS(string.Empty, tos); + return tos; + } + private static void BuildTOS(this GameObject parent, string parentPath, Dictionary tos) + { + Transform transform = parent.m_Transform; + foreach (PPtr childPtr in transform.m_Children) + { + if (childPtr.TryGet(out var childTransform)) + { + if (childTransform.m_GameObject.TryGet(out var child)) + { + string path = parentPath != string.Empty ? parentPath + '/' + child.m_Name : child.m_Name; + var pathHash = CRC.CalculateDigestUTF8(path); + tos[pathHash] = path; + BuildTOS(child, path, tos); + } + } + } + } + + + private static bool IsContainsAnimationClip(this RuntimeAnimatorController runtimeAnimatorController, AnimationClip clip) + { + if (runtimeAnimatorController is AnimatorController animatorController) + { + foreach (PPtr ptr in animatorController.m_AnimationClips) + { + if (ptr.TryGet(out var animationClip) && animationClip.Equals(clip)) + { + return true; + } + } + } + return false; + } + public static int GetDimension(this GenericBinding binding) + { + return binding.attribute == 2 ? 4 : 3; + } + + public static HumanoidMuscleType GetHumanoidMuscle(this GenericBinding binding) + { + return ((HumanoidMuscleType)binding.attribute).Update(binding.version); + } + #endregion + } + + public enum BindingCustomType : byte + { + None = 0, + Transform = 4, + AnimatorMuscle = 8, + + BlendShape = 20, + Renderer = 21, + RendererMaterial = 22, + SpriteRenderer = 23, + MonoBehaviour = 24, + Light = 25, + RendererShadows = 26, + ParticleSystem = 27, + RectTransform = 28, + LineRenderer = 29, + TrailRenderer = 30, + PositionConstraint = 31, + RotationConstraint = 32, + ScaleConstraint = 33, + AimConstraint = 34, + ParentConstraint = 35, + LookAtConstraint = 36, + Camera = 37, + } +} diff --git a/AssetStudioUtility/YAML/CustomCurveResolver.cs b/AssetStudioUtility/YAML/CustomCurveResolver.cs new file mode 100644 index 0000000..ef398c5 --- /dev/null +++ b/AssetStudioUtility/YAML/CustomCurveResolver.cs @@ -0,0 +1,672 @@ +using System; +using SevenZip; +using System.Linq; +using System.Xml.Linq; + +namespace AssetStudio +{ + public sealed class CustomCurveResolver + { + public CustomCurveResolver(AnimationClip clip) + { + if (clip == null) + { + throw new ArgumentNullException(nameof(clip)); + } + m_clip = clip; + } + + public string ToAttributeName(BindingCustomType type, uint attribute, string path) + { + switch (type) + { + case BindingCustomType.BlendShape: + { + const string Prefix = "blendShape."; + if (AnimationClipConverter.UnknownPathRegex.IsMatch(path)) + { + return Prefix + attribute; + } + + foreach (GameObject root in Roots) + { + var rootTransform = root.m_Transform; + var child = rootTransform.FindChild(path); + if (child == null) + { + continue; + } + SkinnedMeshRenderer skin = null; + if (child.m_GameObject.TryGet(out var go)) + { + skin = go.m_SkinnedMeshRenderer; + } + if (skin == null) + { + continue; + } + if (!skin.m_Mesh.TryGet(out var mesh)) + { + continue; + } + string shapeName = mesh.FindBlendShapeNameByCRC(attribute); + if (shapeName == null) + { + continue; + } + + return Prefix + shapeName; + } + return Prefix + attribute; + } + + case BindingCustomType.Renderer: + return "m_Materials." + CommonString.StringBuffer[0x31] + "." + CommonString.StringBuffer[0x6A] + $"[{attribute}]"; + + case BindingCustomType.RendererMaterial: + { + const string Prefix = "material."; + if (AnimationClipConverter.UnknownPathRegex.IsMatch(path)) + { + return Prefix + attribute; + } + + foreach (GameObject root in Roots) + { + Transform rootTransform = root.m_Transform; + Transform child = rootTransform.FindChild(path); + if (child == null) + { + continue; + } + + uint crc28 = attribute & 0xFFFFFFF; + Renderer renderer = null; + if (child.m_GameObject.TryGet(out var go)) + { + renderer = (Renderer)go.m_SkinnedMeshRenderer ?? go.m_MeshRenderer; + } + if (renderer == null) + { + continue; + } + string property = renderer.FindMaterialPropertyNameByCRC28(crc28); + if (property == null) + { + continue; + } + + if ((attribute & 0x80000000) != 0) + { + return Prefix + property; + } + char subProperty; + uint subPropIndex = attribute >> 28 & 3; + bool isRgba = (attribute & 0x40000000) != 0; + switch (subPropIndex) + { + case 0: + subProperty = isRgba ? 'r' : 'x'; + break; + case 1: + subProperty = isRgba ? 'g' : 'y'; + break; + case 2: + subProperty = isRgba ? 'b' : 'z'; + break; + + default: + subProperty = isRgba ? 'a' : 'w'; + break; + } + return Prefix + property + "." + subProperty; + } + return Prefix + attribute; + } + + case BindingCustomType.SpriteRenderer: + { + if (attribute == 0) + { + return "m_Sprite"; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + + case BindingCustomType.MonoBehaviour: + { + if (attribute == CRC.CalculateDigestAscii("m_Enabled")) + { + return "m_Enabled"; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + + case BindingCustomType.Light: + { + string ColorR = "m_Color.r"; + if (attribute == CRC.CalculateDigestAscii(ColorR)) + { + return ColorR; + } + string ColorG = "m_Color.g"; + if (attribute == CRC.CalculateDigestAscii(ColorG)) + { + return ColorG; + } + string ColorB = "m_Color.b"; + if (attribute == CRC.CalculateDigestAscii(ColorB)) + { + return ColorB; + } + string ColorA = "m_Color.a"; + if (attribute == CRC.CalculateDigestAscii(ColorA)) + { + return ColorA; + } + if (attribute == CRC.CalculateDigestAscii("m_CookieSize")) + { + return "m_CookieSize"; + } + if (attribute == CRC.CalculateDigestAscii("m_DrawHalo")) + { + return "m_DrawHalo"; + } + if (attribute == CRC.CalculateDigestAscii("m_Intensity")) + { + return "m_Intensity"; + } + if (attribute == CRC.CalculateDigestAscii("m_Range")) + { + return "m_Range"; + } + const string ShadowsStrength = "m_Shadows.m_Strength"; + if (attribute == CRC.CalculateDigestAscii(ShadowsStrength)) + { + return ShadowsStrength; + } + const string ShadowsBias = "m_Shadows.m_Bias"; + if (attribute == CRC.CalculateDigestAscii(ShadowsBias)) + { + return ShadowsBias; + } + const string ShadowsNormalBias = "m_Shadows.m_NormalBias"; + if (attribute == CRC.CalculateDigestAscii(ShadowsNormalBias)) + { + return ShadowsNormalBias; + } + const string ShadowsNearPlane = "m_Shadows.m_NearPlane"; + if (attribute == CRC.CalculateDigestAscii(ShadowsNearPlane)) + { + return ShadowsNearPlane; + } + if (attribute == CRC.CalculateDigestAscii("m_SpotAngle")) + { + return "m_SpotAngle"; + } + if (attribute == CRC.CalculateDigestAscii("m_ColorTemperature")) + { + return "m_ColorTemperature"; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + + case BindingCustomType.RendererShadows: + { + if (attribute == CRC.CalculateDigestAscii("m_ReceiveShadows")) + { + return "m_ReceiveShadows"; + } + if (attribute == CRC.CalculateDigestAscii("m_SortingOrder")) + { + return "m_SortingOrder"; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + +#warning TODO: + case BindingCustomType.ParticleSystem: + return "ParticleSystem_" + attribute; + /*{ + // TODO: ordinal propertyName + } + throw new ArgumentException($"Unknown attribute {attribute} for {_this}");*/ + + case BindingCustomType.RectTransform: + { + string LocalPositionZ = "m_LocalPosition.z"; + if (attribute == CRC.CalculateDigestAscii(LocalPositionZ)) + { + return LocalPositionZ; + } + string AnchoredPositionX = "m_AnchoredPosition.x"; + if (attribute == CRC.CalculateDigestAscii(AnchoredPositionX)) + { + return AnchoredPositionX; + } + string AnchoredPositionY = "m_AnchoredPosition.y"; + if (attribute == CRC.CalculateDigestAscii(AnchoredPositionY)) + { + return AnchoredPositionY; + } + string AnchorMinX = "m_AnchorMin.x"; + if (attribute == CRC.CalculateDigestAscii(AnchorMinX)) + { + return AnchorMinX; + } + string AnchorMinY = "m_AnchorMin.y"; + if (attribute == CRC.CalculateDigestAscii(AnchorMinY)) + { + return AnchorMinY; + } + string AnchorMaxX = "m_AnchorMax.x"; + if (attribute == CRC.CalculateDigestAscii(AnchorMaxX)) + { + return AnchorMaxX; + } + string AnchorMaxY = "m_AnchorMax.y"; + if (attribute == CRC.CalculateDigestAscii(AnchorMaxY)) + { + return AnchorMaxY; + } + string SizeDeltaX = "m_SizeDelta.x"; + if (attribute == CRC.CalculateDigestAscii(SizeDeltaX)) + { + return SizeDeltaX; + } + string SizeDeltaY = "m_SizeDelta.y"; + if (attribute == CRC.CalculateDigestAscii(SizeDeltaY)) + { + return SizeDeltaY; + } + string PivotX = "m_Pivot.x"; + if (attribute == CRC.CalculateDigestAscii(PivotX)) + { + return PivotX; + } + string PivotY = "m_Pivot.y"; + if (attribute == CRC.CalculateDigestAscii(PivotY)) + { + return PivotY; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + +#warning TODO: + case BindingCustomType.LineRenderer: + { + const string ParametersWidthMultiplier = "m_Parameters" + "." + "widthMultiplier"; + if (attribute == CRC.CalculateDigestAscii(ParametersWidthMultiplier)) + { + return ParametersWidthMultiplier; + } + } + // TODO: old versions animate all properties as custom curves + return "LineRenderer_" + attribute; + +#warning TODO: + case BindingCustomType.TrailRenderer: + { + const string ParametersWidthMultiplier = "m_Parameters" + "." + "widthMultiplier"; + if (attribute == CRC.CalculateDigestAscii(ParametersWidthMultiplier)) + { + return ParametersWidthMultiplier; + } + } + // TODO: old versions animate all properties as custom curves + return "TrailRenderer_" + attribute; + +#warning TODO: + case BindingCustomType.PositionConstraint: + { + uint property = attribute & 0xF; + switch (property) + { + case 0: + return "m_RestTranslation.x"; + case 1: + return "m_RestTranslation.y"; + case 2: + return "m_RestTranslation.z"; + case 3: + return "m_Weight"; + case 4: + return "m_TranslationOffset.x"; + case 5: + return "m_TranslationOffset.y"; + case 6: + return "m_TranslationOffset.z"; + case 7: + return "m_AffectTranslationX"; + case 8: + return "m_AffectTranslationY"; + case 9: + return "m_AffectTranslationZ"; + case 10: + return "m_Active"; + case 11: + return $"m_Sources.Array.data[{attribute >> 8}].sourceTransform"; + case 12: + return $"m_Sources.Array.data[{attribute >> 8}].weight"; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + +#warning TODO: + case BindingCustomType.RotationConstraint: + { + uint property = attribute & 0xF; + switch (property) + { + case 0: + return "m_RestRotation.x"; + case 1: + return "m_RestRotation.y"; + case 2: + return "m_RestRotation.z"; + case 3: + return "m_Weight"; + case 4: + return "m_RotationOffset.x"; + case 5: + return "m_RotationOffset.y"; + case 6: + return "m_RotationOffset.z"; + case 7: + return "m_AffectRotationX"; + case 8: + return "m_AffectRotationY"; + case 9: + return "m_AffectRotationZ"; + case 10: + return "m_Active"; + case 11: + return $"m_Sources.Array.data[{attribute >> 8}].sourceTransform"; + case 12: + return $"m_Sources.Array.data[{attribute >> 8}].weight"; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + +#warning TODO: + case BindingCustomType.ScaleConstraint: + { + uint property = attribute & 0xF; + switch (property) + { + case 0: + return "m_ScaleAtRest.x"; + case 1: + return "m_ScaleAtRest.y"; + case 2: + return "m_ScaleAtRest.z"; + case 3: + return "m_Weight"; + case 4: + return "m_ScalingOffset.x"; + case 5: + return "m_ScalingOffset.y"; + case 6: + return "m_ScalingOffset.z"; + case 7: + return "m_AffectScalingX"; + case 8: + return "m_AffectScalingY"; + case 9: + return "m_AffectScalingZ"; + case 10: + return "m_Active"; + case 11: + return $"m_Sources.Array.data[{attribute >> 8}].sourceTransform"; + case 12: + return $"m_Sources.Array.data[{attribute >> 8}].weight"; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + +#warning TODO: + case BindingCustomType.AimConstraint: + { + uint property = attribute & 0xF; + switch (property) + { + case 0: + return "m_Weight"; + case 1: + return "m_AffectRotationX"; + case 2: + return "m_AffectRotationY"; + case 3: + return "m_AffectRotationZ"; + case 4: + return "m_Active"; + case 5: + return "m_WorldUpObject"; + case 6: + return $"m_Sources.Array.data[{attribute >> 8}].sourceTransform"; + case 7: + return $"m_Sources.Array.data[{attribute >> 8}].weight"; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + +#warning TODO: + case BindingCustomType.ParentConstraint: + { + uint property = attribute & 0xF; + switch (property) + { + case 0: + return "m_Weight"; + case 1: + return "m_AffectTranslationX"; + case 2: + return "m_AffectTranslationY"; + case 3: + return "m_AffectTranslationZ"; + case 4: + return "m_AffectRotationX"; + case 5: + return "m_AffectRotationY"; + case 6: + return "m_AffectRotationZ"; + case 7: + return "m_Active"; + case 8: + return $"m_TranslationOffsets.Array.data[{attribute >> 8}].x"; + case 9: + return $"m_TranslationOffsets.Array.data[{attribute >> 8}].y"; + case 10: + return $"m_TranslationOffsets.Array.data[{attribute >> 8}].z"; + case 11: + return $"m_RotationOffsets.Array.data[{attribute >> 8}].x"; + case 12: + return $"m_RotationOffsets.Array.data[{attribute >> 8}].y"; + case 13: + return $"m_RotationOffsets.Array.data[{attribute >> 8}].z"; + case 14: + return $"m_Sources.Array.data[{attribute >> 8}].sourceTransform"; + case 15: + return $"m_Sources.Array.data[{attribute >> 8}].weight"; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + +#warning TODO: + case BindingCustomType.LookAtConstraint: + { + uint property = attribute & 0xF; + switch (property) + { + case 0: + return "m_Weight"; + case 1: + return "m_Active"; + case 2: + return "m_WorldUpObject"; + case 3: + return $"m_Sources.Array.data[{attribute >> 8}].sourceTransform"; + case 4: + return $"m_Sources.Array.data[{attribute >> 8}].weight"; + case 5: + return "m_Roll"; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + + case BindingCustomType.Camera: + { + if (attribute == CRC.CalculateDigestAscii("field of view")) + { + return "field of view"; + } + if (attribute == CRC.CalculateDigestAscii("m_FocalLength")) + { + return "m_FocalLength"; + } + } + throw new ArgumentException($"Unknown attribute {attribute} for {type}"); + + default: + throw new ArgumentException(type.ToString()); + } + } + + private GameObject[] Roots + { + get + { + if (!m_rootInited) + { + m_roots = m_clip.FindRoots().ToArray(); + m_rootInited = true; + } + return m_roots; + } + } + + private readonly AnimationClip m_clip = null; + + private GameObject[] m_roots = null; + private bool m_rootInited = false; + } + + public static class CustomCurveResolverExtensions + { + public static Transform FindChild(this Transform transform, string path) + { + if (path.Length == 0) + { + return transform; + } + return transform.FindChild(path, 0); + } + + private static Transform FindChild(this Transform transform, string path, int startIndex) + { + int separatorIndex = path.IndexOf('/', startIndex); + string childName = separatorIndex == -1 ? + path.Substring(startIndex, path.Length - startIndex) : + path.Substring(startIndex, separatorIndex - startIndex); + + foreach (PPtr childPtr in transform.m_Children) + { + if (childPtr.TryGet(out var child)) + { + if (child.m_GameObject.TryGet(out var childGO) && childGO.m_Name == childName) + { + return separatorIndex == -1 ? child : child.FindChild(path, separatorIndex + 1); + } + } + } + return null; + } + + public static string FindBlendShapeNameByCRC(this Mesh mesh, uint crc) + { + if (mesh.version[0] > 4 || (mesh.version[0] == 4 && mesh.version[1] >= 3)) + { + return mesh.m_Shapes.FindShapeNameByCRC(crc); + } + else + { + foreach (var blendShape in mesh.m_Shapes.shapes) + { + if (CRC.VerifyDigestUTF8(blendShape.name, crc)) + { + return blendShape.name; + } + } + return null; + } + } + + public static string FindShapeNameByCRC(this BlendShapeData blendShapeData, uint crc) + { + foreach (var blendChannel in blendShapeData.channels) + { + if (blendChannel.nameHash == crc) + { + return blendChannel.name; + } + } + return null; + } + + public static string FindMaterialPropertyNameByCRC28(this Renderer renderer, uint crc) + { + foreach (PPtr materialPtr in renderer.m_Materials) + { + if (!materialPtr.TryGet(out var material)) + { + continue; + } + string property = material.FindPropertyNameByCRC28(crc); + if (property == null) + { + continue; + } + + return property; + } + return null; + } + + public static string FindPropertyNameByCRC28(this Material material, uint crc) + { + foreach (var property in material.m_SavedProperties.m_TexEnvs.Keys) + { + string hdrName = $"{property}_HDR"; + if (CRC.Verify28DigestUTF8(hdrName, crc)) + { + return hdrName; + } + string stName = $"{property}_ST"; + if (CRC.Verify28DigestUTF8(stName, crc)) + { + return stName; + } + string texelName = $"{property}_TexelSize"; + if (CRC.Verify28DigestUTF8(texelName, crc)) + { + return texelName; + } + } + foreach (var property in material.m_SavedProperties.m_Floats.Keys) + { + if (CRC.Verify28DigestUTF8(property, crc)) + { + return property; + } + } + foreach (var property in material.m_SavedProperties.m_Colors.Keys) + { + if (CRC.Verify28DigestUTF8(property, crc)) + { + return property; + } + } + return null; + } + } +} \ No newline at end of file diff --git a/AssetStudioUtility/YAML/MuscleHelper.cs b/AssetStudioUtility/YAML/MuscleHelper.cs new file mode 100644 index 0000000..931272c --- /dev/null +++ b/AssetStudioUtility/YAML/MuscleHelper.cs @@ -0,0 +1,648 @@ +using System; + +namespace AssetStudio +{ + public enum HumanoidMuscleType + { + Motion = 0, + Root = Motion + 7, + Limbs = Root + 7, + Muscles = Limbs + LimbType.Last * 7, + Fingers = Muscles + MuscleType.Last, + TDoFBones = Fingers + ArmType.Last * FingerType.Last * FingerDoFType.Last, + + Last = TDoFBones + TDoFBoneType.Last * 3, + } + + public static class AnimationMuscleTypeExtensions + { + public static HumanoidMuscleType Update(this HumanoidMuscleType _this, int[] version) + { + if (_this < HumanoidMuscleType.Muscles) + { + return _this; + } + + MuscleType muscle = (MuscleType)(_this - HumanoidMuscleType.Muscles); + MuscleType fixedMuscle = muscle.Update(version); + _this = HumanoidMuscleType.Muscles + (int)fixedMuscle; + if (_this < HumanoidMuscleType.TDoFBones) + { + return _this; + } + + TDoFBoneType tdof = (TDoFBoneType)(_this - HumanoidMuscleType.TDoFBones); + TDoFBoneType fixedTdof = tdof.Update(version); + _this = HumanoidMuscleType.TDoFBones + (int)fixedTdof; + return _this; + } + + public static string ToAttributeString(this HumanoidMuscleType _this) + { + if (_this < HumanoidMuscleType.Root) + { + int delta = _this - HumanoidMuscleType.Motion; + return nameof(HumanoidMuscleType.Motion) + GetTransformPostfix(delta % 7); + } + if (_this < HumanoidMuscleType.Limbs) + { + int delta = _this - HumanoidMuscleType.Root; + return nameof(HumanoidMuscleType.Root) + GetTransformPostfix(delta % 7); + } + if (_this < HumanoidMuscleType.Muscles) + { + int delta = _this - HumanoidMuscleType.Limbs; + LimbType limb = (LimbType)(delta / 7); + return limb.ToBoneType().ToAttributeString() + GetTransformPostfix(delta % 7); + } + if (_this < HumanoidMuscleType.Fingers) + { + int delta = _this - HumanoidMuscleType.Muscles; + MuscleType muscle = (MuscleType)delta; + return muscle.ToAttributeString(); + } + if (_this < HumanoidMuscleType.TDoFBones) + { + const int armSize = (int)FingerType.Last * (int)FingerDoFType.Last; + const int dofSize = (int)FingerDoFType.Last; + int delta = _this - HumanoidMuscleType.Fingers; + ArmType arm = (ArmType)(delta / armSize); + delta = delta % armSize; + FingerType finger = (FingerType)(delta / dofSize); + delta = delta % dofSize; + FingerDoFType dof = (FingerDoFType)delta; + return $"{arm.ToBoneType().ToAttributeString()}.{finger.ToAttributeString()}.{dof.ToAttributeString()}"; + } + if (_this < HumanoidMuscleType.Last) + { + int delta = _this - HumanoidMuscleType.TDoFBones; + TDoFBoneType tdof = (TDoFBoneType)(delta / 3); + return $"{tdof.ToBoneType().ToAttributeString()}{GetTDoFTransformPostfix(delta % 3)}"; + } + throw new ArgumentException(_this.ToString()); + } + + private static string GetTransformPostfix(int index) + { + switch (index) + { + case 0: + return "T.x"; + case 1: + return "T.y"; + case 2: + return "T.z"; + + case 3: + return "Q.x"; + case 4: + return "Q.y"; + case 5: + return "Q.z"; + case 6: + return "Q.w"; + + default: + throw new ArgumentException(index.ToString()); + } + } + + private static string GetTDoFTransformPostfix(int index) + { + switch (index) + { + case 0: + return "TDOF.x"; + case 1: + return "TDOF.y"; + case 2: + return "TDOF.z"; + + default: + throw new ArgumentException(index.ToString()); + } + } + } + + public enum LimbType + { + LeftFoot = 0, + RightFoot = 1, + LeftHand = 2, + RightHand = 3, + + Last, + } + + public static class LimbTypeExtensions + { + public static BoneType ToBoneType(this LimbType _this) + { + switch (_this) + { + case LimbType.LeftFoot: + return BoneType.LeftFoot; + case LimbType.RightFoot: + return BoneType.RightFoot; + case LimbType.LeftHand: + return BoneType.LeftHand; + case LimbType.RightHand: + return BoneType.RightHand; + + default: + throw new ArgumentException(_this.ToString()); + } + } + } + + public enum MuscleType + { + SpineFrontBack = 0, + SpineLeftRight = 1, + SpineTwistLeftRight = 2, + ChestFrontBack = 3, + ChestLeftRight = 4, + ChestTwistLeftRight = 5, + UpperchestFrontBack = 6, + UpperchestLeftRight = 7, + UpperchestTwisLeftRight = 8, + NeckNodDownUp = 9, + NeckTiltLeftRight = 10, + NeckTurnLeftRight = 11, + HeadNodDownUp = 12, + HeadTiltLeftRight = 13, + HeadTurnLeftRight = 14, + LeftEyeDownUp = 15, + LeftEyeInOut = 16, + RightEyeDownUp = 17, + RightEyeInOut = 18, + JawClose = 19, + JawLeftRight = 20, + LeftUpperLegFrontBack = 21, + LeftUpperLegInOut = 22, + LeftUpperLegTwistInOut = 23, + LeftLowerLegStretch = 24, + LeftLowerLegTwistInOut = 25, + LeftFootUpDown = 26, + LeftFootTwistInOut = 27, + LeftToesUpDown = 28, + RightUpperLegFrontBack = 29, + RightUpperLegInOut = 30, + RightUpperLegTwistInOut = 31, + RightLowerLegStretch = 32, + RightLowerLegTwistInOut = 33, + RightFootUpDown = 34, + RightFootTwistInOut = 35, + RightToesUpDown = 36, + LeftShoulderDownUp = 37, + LeftShoulderFrontBack = 38, + LeftArmDownUp = 39, + LeftArmFrontBack = 40, + LeftArmTwistInOut = 41, + LeftForearmStretch = 42, + LeftForearmTwistInOut = 43, + LeftHandDownUp = 44, + LeftHandInOut = 45, + RightShoulderDownUp = 46, + RightShoulderFrontBack = 47, + RightArmDownUp = 48, + RightArmFrontBack = 49, + RightArmTwistInOut = 50, + RightForearmStretch = 51, + RightForearmTwistInOut = 52, + RightHandDownUp = 53, + RightHandInOut = 54, + + Last, + } + + public static class MuscleTypeExtensions + { + public static MuscleType Update(this MuscleType _this, int[] version) + { + if (!(version[0] > 5 || (version[0] == 5 && version[1] >= 6))) + { + if (_this >= MuscleType.UpperchestFrontBack) + { + _this += 3; + } + } + return _this; + } + + public static string ToAttributeString(this MuscleType _this) + { + switch (_this) + { + case MuscleType.SpineFrontBack: + return "Spine Front-Back"; + case MuscleType.SpineLeftRight: + return "Spine Left-Right"; + case MuscleType.SpineTwistLeftRight: + return "Spine Twist Left-Right"; + case MuscleType.ChestFrontBack: + return "Chest Front-Back"; + case MuscleType.ChestLeftRight: + return "Chest Left-Right"; + case MuscleType.ChestTwistLeftRight: + return "Chest Twist Left-Right"; + case MuscleType.UpperchestFrontBack: + return "UpperChest Front-Back"; + case MuscleType.UpperchestLeftRight: + return "UpperChest Left-Right"; + case MuscleType.UpperchestTwisLeftRight: + return "UpperChest Twist Left-Right"; + case MuscleType.NeckNodDownUp: + return "Neck Nod Down-Up"; + case MuscleType.NeckTiltLeftRight: + return "Neck Tilt Left-Right"; + case MuscleType.NeckTurnLeftRight: + return "Neck Turn Left-Right"; + case MuscleType.HeadNodDownUp: + return "Head Nod Down-Up"; + case MuscleType.HeadTiltLeftRight: + return "Head Tilt Left-Right"; + case MuscleType.HeadTurnLeftRight: + return "Head Turn Left-Right"; + case MuscleType.LeftEyeDownUp: + return "Left Eye Down-Up"; + case MuscleType.LeftEyeInOut: + return "Left Eye In-Out"; + case MuscleType.RightEyeDownUp: + return "Right Eye Down-Up"; + case MuscleType.RightEyeInOut: + return "Right Eye In-Out"; + case MuscleType.JawClose: + return "Jaw Close"; + case MuscleType.JawLeftRight: + return "Jaw Left-Right"; + case MuscleType.LeftUpperLegFrontBack: + return "Left Upper Leg Front-Back"; + case MuscleType.LeftUpperLegInOut: + return "Left Upper Leg In-Out"; + case MuscleType.LeftUpperLegTwistInOut: + return "Left Upper Leg Twist In-Out"; + case MuscleType.LeftLowerLegStretch: + return "Left Lower Leg Stretch"; + case MuscleType.LeftLowerLegTwistInOut: + return "Left Lower Leg Twist In-Out"; + case MuscleType.LeftFootUpDown: + return "Left Foot Up-Down"; + case MuscleType.LeftFootTwistInOut: + return "Left Foot Twist In-Out"; + case MuscleType.LeftToesUpDown: + return "Left Toes Up-Down"; + case MuscleType.RightUpperLegFrontBack: + return "Right Upper Leg Front-Back"; + case MuscleType.RightUpperLegInOut: + return "Right Upper Leg In-Out"; + case MuscleType.RightUpperLegTwistInOut: + return "Right Upper Leg Twist In-Out"; + case MuscleType.RightLowerLegStretch: + return "Right Lower Leg Stretch"; + case MuscleType.RightLowerLegTwistInOut: + return "Right Lower Leg Twist In-Out"; + case MuscleType.RightFootUpDown: + return "Right Foot Up-Down"; + case MuscleType.RightFootTwistInOut: + return "Right Foot Twist In-Out"; + case MuscleType.RightToesUpDown: + return "Right Toes Up-Down"; + case MuscleType.LeftShoulderDownUp: + return "Left Shoulder Down-Up"; + case MuscleType.LeftShoulderFrontBack: + return "Left Shoulder Front-Back"; + case MuscleType.LeftArmDownUp: + return "Left Arm Down-Up"; + case MuscleType.LeftArmFrontBack: + return "Left Arm Front-Back"; + case MuscleType.LeftArmTwistInOut: + return "Left Arm Twist In-Out"; + case MuscleType.LeftForearmStretch: + return "Left Forearm Stretch"; + case MuscleType.LeftForearmTwistInOut: + return "Left Forearm Twist In-Out"; + case MuscleType.LeftHandDownUp: + return "Left Hand Down-Up"; + case MuscleType.LeftHandInOut: + return "Left Hand In-Out"; + case MuscleType.RightShoulderDownUp: + return "Right Shoulder Down-Up"; + case MuscleType.RightShoulderFrontBack: + return "Right Shoulder Front-Back"; + case MuscleType.RightArmDownUp: + return "Right Arm Down-Up"; + case MuscleType.RightArmFrontBack: + return "Right Arm Front-Back"; + case MuscleType.RightArmTwistInOut: + return "Right Arm Twist In-Out"; + case MuscleType.RightForearmStretch: + return "Right Forearm Stretch"; + case MuscleType.RightForearmTwistInOut: + return "Right Forearm Twist In-Out"; + case MuscleType.RightHandDownUp: + return "Right Hand Down-Up"; + case MuscleType.RightHandInOut: + return "Right Hand In-Out"; + + default: + throw new ArgumentException(_this.ToString()); + } + } + } + + public enum BoneType + { + Hips = 0, + LeftUpperLeg = 1, + RightUpperLeg = 2, + LeftLowerLeg = 3, + RightLowerLeg = 4, + LeftFoot = 5, + RightFoot = 6, + Spine = 7, + Chest = 8, + UpperChest = 9, + Neck = 10, + Head = 11, + LeftShoulder = 12, + RightShoulder = 13, + LeftUpperArm = 14, + RightUpperArm = 15, + LeftLowerArm = 16, + RightLowerArm = 17, + LeftHand = 18, + RightHand = 19, + LeftToes = 20, + RightToes = 21, + LeftEye = 22, + RightEye = 23, + Jaw = 24, + + Last, + } + + public static class BoneTypeExtensions + { + public static BoneType Update(this BoneType _this, int[] version) + { + if (version[0] > 5 || (version[0] == 5 && version[1] >= 6)) + { + if (_this >= BoneType.UpperChest) + { + _this++; + } + } + return _this; + } + + public static string ToAttributeString(this BoneType _this) + { + if (_this < BoneType.Last) + { + return _this.ToString(); + } + throw new ArgumentException(_this.ToString()); + } + } + + public enum TransformType + { + Translation = 1, + Rotation = 2, + Scaling = 3, + EulerRotation = 4, + } + + public static class BindingTypeExtensions + { + public static bool IsValid(this TransformType _this) + { + return _this >= TransformType.Translation && _this <= TransformType.EulerRotation; + } + + public static int GetDimension(this TransformType _this) + { + switch (_this) + { + case TransformType.Translation: + case TransformType.Scaling: + case TransformType.EulerRotation: + return 3; + + case TransformType.Rotation: + return 4; + + default: + throw new NotImplementedException($"Binding type {_this} is not implemented"); + } + } + } + + public enum ArmType + { + LeftHand = 0, + RightHand = 1, + + Last, + } + + public static class ArmTypeExtensions + { + public static BoneType ToBoneType(this ArmType _this) + { + switch (_this) + { + case ArmType.LeftHand: + return BoneType.LeftHand; + case ArmType.RightHand: + return BoneType.RightHand; + + default: + throw new ArgumentException(_this.ToString()); + } + } + } + + public enum FingerType + { + Thumb = 0, + Index = 1, + Middle = 2, + Ring = 3, + Little = 4, + + Last, + } + + public static class FingerTypeExtensions + { + public static string ToAttributeString(this FingerType _this) + { + if (_this < FingerType.Last) + { + return _this.ToString(); + } + throw new ArgumentException(_this.ToString()); + } + } + + public enum FingerDoFType + { + _1Stretched = 0, + Spread = 1, + _2Stretched = 2, + _3Stretched = 3, + + Last, + } + + public static class FingerDoFTypeExtensions + { + public static string ToAttributeString(this FingerDoFType _this) + { + switch (_this) + { + case FingerDoFType._1Stretched: + return "1 Stretched"; + case FingerDoFType.Spread: + return "Spread"; + case FingerDoFType._2Stretched: + return "2 Stretched"; + case FingerDoFType._3Stretched: + return "3 Stretched"; + + default: + throw new ArgumentException(_this.ToString()); + } + } + } + public enum TDoFBoneType + { + Spine = 0, + Chest = 1, + UpperChest = 2, + Neck = 3, + Head = 4, + LeftUpperLeg = 5, + LeftLowerLeg = 6, + LeftFoot = 7, + LeftToes = 8, + RightUpperLeg = 9, + RightLowerLeg = 10, + RightFoot = 11, + RightToes = 12, + LeftShoulder = 13, + LeftUpperArm = 14, + LeftLowerArm = 15, + LeftHand = 16, + RightShoulder = 17, + RightUpperArm = 18, + RightLowerArm = 19, + RightHand = 20, + + Last, + } + + public static class TDoFBoneTypeExtensions + { + public static TDoFBoneType Update(this TDoFBoneType _this, int[] version) + { + if (!(version[0] > 5 || (version[0] == 5 && version[1] >= 6))) + { + if (_this >= TDoFBoneType.UpperChest) + { + _this++; + } + } + if (!(version[0] > 2017 || (version[0] == 2017 && version[1] >= 3))) + { + if (_this >= TDoFBoneType.Head) + { + _this++; + } + } + if (!(version[0] > 2017 || (version[0] == 2017 && version[1] >= 3))) + { + if (_this >= TDoFBoneType.LeftLowerLeg) + { + _this += 3; + } + } + if (!(version[0] > 2017 || (version[0] == 2017 && version[1] >= 3))) + { + if (_this >= TDoFBoneType.RightLowerLeg) + { + _this += 3; + } + } + if (!(version[0] > 2017 || (version[0] == 2017 && version[1] >= 3))) + { + if (_this >= TDoFBoneType.LeftUpperArm) + { + _this += 3; + } + } + if (!(version[0] > 2017 || (version[0] == 2017 && version[1] >= 3))) + { + if (_this >= TDoFBoneType.RightUpperArm) + { + _this += 3; + } + } + return _this; + } + + public static BoneType ToBoneType(this TDoFBoneType _this) + { + switch (_this) + { + case TDoFBoneType.Spine: + return BoneType.Spine; + case TDoFBoneType.Chest: + return BoneType.Chest; + case TDoFBoneType.UpperChest: + return BoneType.UpperChest; + case TDoFBoneType.Neck: + return BoneType.Neck; + case TDoFBoneType.Head: + return BoneType.Head; + case TDoFBoneType.LeftUpperLeg: + return BoneType.LeftUpperLeg; + case TDoFBoneType.LeftLowerLeg: + return BoneType.LeftLowerLeg; + case TDoFBoneType.LeftFoot: + return BoneType.LeftFoot; + case TDoFBoneType.LeftToes: + return BoneType.LeftToes; + case TDoFBoneType.RightUpperLeg: + return BoneType.RightUpperLeg; + case TDoFBoneType.RightLowerLeg: + return BoneType.RightLowerLeg; + case TDoFBoneType.RightFoot: + return BoneType.RightFoot; + case TDoFBoneType.RightToes: + return BoneType.RightToes; + case TDoFBoneType.LeftShoulder: + return BoneType.LeftShoulder; + case TDoFBoneType.LeftUpperArm: + return BoneType.LeftUpperArm; + case TDoFBoneType.LeftLowerArm: + return BoneType.LeftLowerArm; + case TDoFBoneType.LeftHand: + return BoneType.LeftHand; + case TDoFBoneType.RightShoulder: + return BoneType.RightShoulder; + case TDoFBoneType.RightUpperArm: + return BoneType.RightUpperArm; + case TDoFBoneType.RightLowerArm: + return BoneType.RightLowerArm; + case TDoFBoneType.RightHand: + return BoneType.RightHand; + + default: + throw new ArgumentException(_this.ToString()); + } + } + } +}