diff --git a/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs b/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs index 3e00c1d..48c9d44 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/YcdFile.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Xml; namespace CodeWalker.GameFiles { @@ -19,6 +20,7 @@ namespace CodeWalker.GameFiles public ClipMapEntry[] ClipMapEntries { get; set; } public AnimationMapEntry[] AnimMapEntries { get; set; } + public string LoadException { get; set; } public YcdFile() : base(null, GameFileType.Ycd) { @@ -41,91 +43,58 @@ namespace CodeWalker.GameFiles throw new Exception("File entry wasn't a resource! (is it binary data?)"); } - ResourceDataReader rd = new ResourceDataReader(resentry, data); - - - ClipDictionary = rd.ReadBlock(); - - ClipMap = new Dictionary(); - AnimMap = new Dictionary(); - if (ClipDictionary != null) + ResourceDataReader rd = null; + try { - if ((ClipDictionary.Clips != null) && (ClipDictionary.Clips.data_items != null)) - { - foreach (var cme in ClipDictionary.Clips.data_items) - { - if (cme != null) - { - ClipMap[cme.Hash] = cme; - var nxt = cme.Next; - while (nxt != null) - { - ClipMap[nxt.Hash] = nxt; - nxt = nxt.Next; - } - } - } - } - if ((ClipDictionary.Animations != null) && (ClipDictionary.Animations.Animations != null) && (ClipDictionary.Animations.Animations.data_items != null)) - { - foreach (var ame in ClipDictionary.Animations.Animations.data_items) - { - if (ame != null) - { - AnimMap[ame.Hash] = ame; - var nxt = ame.NextEntry; - while (nxt != null) - { - AnimMap[nxt.Hash] = nxt; - nxt = nxt.NextEntry; - } - } - } - } + rd = new ResourceDataReader(resentry, data); } + catch (Exception ex) + { + //data = entry.File.DecompressBytes(data); //?? + LoadException = ex.ToString(); + } + + ClipDictionary = rd?.ReadBlock(); + + InitDictionaries(); + } + + public void InitDictionaries() + { + ClipMap = ClipDictionary?.ClipMap ?? new Dictionary(); + AnimMap = ClipDictionary?.AnimMap ?? new Dictionary(); foreach (var cme in ClipMap.Values) { - var clip = cme.Clip; - if (clip == null) continue; - clip.Ycd = this; - if (string.IsNullOrEmpty(clip.Name)) continue; - string name = clip.Name.Replace('\\', '/'); - var slidx = name.LastIndexOf('/'); - if ((slidx >= 0) && (slidx < name.Length - 1)) - { - name = name.Substring(slidx + 1); - } - var didx = name.LastIndexOf('.'); - if ((didx > 0) && (didx < name.Length)) - { - name = name.Substring(0, didx); - } - clip.ShortName = name; - name = name.ToLowerInvariant(); - JenkIndex.Ensure(name); - - - //if (name.EndsWith("_uv_0")) //hash for these entries match string with this removed, +1 - //{ - //} - //if (name.EndsWith("_uv_1")) //same as above, but +2 - //{ - //} - + if (cme?.Clip != null) cme.Clip.Ycd = this; } foreach (var ame in AnimMap.Values) { - var anim = ame.Animation; - if (anim == null) continue; - anim.Ycd = this; + if (ame?.Animation != null) ame.Animation.Ycd = this; } - ClipMapEntries = ClipMap.Values.ToArray(); AnimMapEntries = AnimMap.Values.ToArray(); + } + + + + public byte[] Save() + { + //if (BuildStructsOnSave) + //{ + // BuildStructs(); + //} + + byte[] data = ResourceBuilder.Build(ClipDictionary, 46); //ycd is 46... + + return data; + } + + + public void SaveOpenFormatsAnimation(Animation crAnim, Stream outStream) { var seqs = new int[(crAnim.Frames / crAnim.SequenceFrameLimit) + 1]; @@ -171,7 +140,7 @@ namespace CodeWalker.GameFiles } else if (chList.Length == 1) { - if (chList[0] is AnimChannelStaticSmallestThreeQuaternion) + if (chList[0] is AnimChannelStaticQuaternion) { isRotation = true; } @@ -201,7 +170,7 @@ namespace CodeWalker.GameFiles return " Static"; } } - else if (chan is AnimChannelStaticFloat || chan is AnimChannelStaticVector3 || chan is AnimChannelStaticSmallestThreeQuaternion) + else if (chan is AnimChannelStaticFloat || chan is AnimChannelStaticVector3 || chan is AnimChannelStaticQuaternion) { return " Static"; } @@ -245,10 +214,10 @@ namespace CodeWalker.GameFiles switch (chan) { case AnimChannelStaticFloat sf: - return $" {sf.FloatValue}\r\n"; + return $" {sf.Value}\r\n"; case AnimChannelStaticVector3 v3: return $" {v3.Value[0]} {v3.Value[1]} {v3.Value[2]}\r\n"; - case AnimChannelStaticSmallestThreeQuaternion q3: + case AnimChannelStaticQuaternion q3: return $" {q3.Value[0]} {q3.Value[1]} {q3.Value[2]} {q3.Value[3]}\r\n"; default: { @@ -303,4 +272,61 @@ namespace CodeWalker.GameFiles writer.Flush(); } } + + + + + + + + + + + public class YcdXml : MetaXmlBase + { + + public static string GetXml(YcdFile ycd) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine(XmlHeader); + + if ((ycd != null) && (ycd.ClipDictionary != null)) + { + var name = "ClipDictionary"; + + OpenTag(sb, 0, name); + + ycd.ClipDictionary.WriteXml(sb, 1); + + CloseTag(sb, 0, name); + } + + return sb.ToString(); + } + + } + + public class XmlYcd + { + + public static YcdFile GetYcd(string xml) + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + return GetYcd(doc); + } + + public static YcdFile GetYcd(XmlDocument doc) + { + YcdFile ycd = new YcdFile(); + ycd.ClipDictionary = new ClipDictionary(); + ycd.ClipDictionary.ReadXml(doc.DocumentElement); + ycd.InitDictionaries(); + //ycd.BuildStructsOnSave = false; //structs don't need to be rebuilt here! + return ycd; + } + + } + + } \ No newline at end of file diff --git a/CodeWalker.Core/GameFiles/GameFileCache.cs b/CodeWalker.Core/GameFiles/GameFileCache.cs index ee8aef6..2b8e9ba 100644 --- a/CodeWalker.Core/GameFiles/GameFileCache.cs +++ b/CodeWalker.Core/GameFiles/GameFileCache.cs @@ -2964,38 +2964,274 @@ namespace CodeWalker.GameFiles } public void TestYcds() { + var errorfiles = new List(); + var errorentries = new List(); + foreach (RpfFile file in AllRpfs) { foreach (RpfEntry entry in file.AllEntries) { - try + //try + //{ + if (entry.NameLower.EndsWith(".ycd")) { - if (entry.NameLower.EndsWith(".ycd")) + UpdateStatus(string.Format(entry.Path)); + YcdFile ycd1 = RpfMan.GetFile(entry); + if (ycd1 == null) { - UpdateStatus(string.Format(entry.Path)); - YcdFile ycdfile = RpfMan.GetFile(entry); - if ((ycdfile != null))// && (ycdfile.Meta != null)) - { } + errorentries.Add(entry); + } + else if (ycd1?.LoadException != null) + { + errorfiles.Add(ycd1);//these ones have file corruption issues and won't load as resource... + } + else + { + if (ycd1.ClipDictionary == null) + { continue; } + + var t = true; + if (t)//just here to test loading only + { continue; } + + var xml = YcdXml.GetXml(ycd1); + var ycdX = XmlYcd.GetYcd(xml); + var data = ycdX.Save(); + var ycd2 = new YcdFile(); + RpfFile.LoadResourceFile(ycd2, data, 46);//full roundtrip + + + if (ycd2 == null) + { continue; } + if (ycd2.ClipDictionary == null) + { continue; } + + var c1 = ycd1.ClipDictionary.Clips?.data_items; + var c2 = ycd2.ClipDictionary.Clips?.data_items; + if ((c1 == null) || (c2 == null)) + { continue; } + if (c1.Length != c2.Length) + { continue; } + + var a1 = ycd1.ClipDictionary.Animations?.Animations?.data_items; + var a2 = ycd2.ClipDictionary.Animations?.Animations?.data_items; + if ((a1 == null) || (a2 == null)) + { continue; } + if (a1.Length != a2.Length) + { continue; } + + var m1 = ycd1.AnimMap; + var m2 = ycd2.AnimMap; + if ((m1 == null) || (m2 == null)) + { continue; } + if (m1.Count != m2.Count) + { continue; } + foreach (var kvp1 in m1) + { + var an1 = kvp1.Value; + var an2 = an1; + if(!m2.TryGetValue(kvp1.Key, out an2)) + { continue; } + + var sa1 = an1?.Animation?.Sequences?.data_items; + var sa2 = an2?.Animation?.Sequences?.data_items; + if ((sa1 == null) || (sa2 == null)) + { continue; } + if (sa1.Length != sa2.Length) + { continue; } + for (int s = 0; s < sa1.Length; s++) + { + var s1 = sa1[s]; + var s2 = sa2[s]; + if ((s1?.Sequences == null) || (s2?.Sequences == null)) + { continue; } + + if (s1.NumFrames != s2.NumFrames) + { } + if (s1.ChunkSize != s2.ChunkSize) + { } + if (s1.FrameOffset != s2.FrameOffset) + { } + if (s1.DataLength != s2.DataLength) + { } + else + { + //for (int b = 0; b < s1.DataLength; b++) + //{ + // var b1 = s1.Data[b]; + // var b2 = s2.Data[b]; + // if (b1 != b2) + // { } + //} + } + + for (int ss = 0; ss < s1.Sequences.Length; ss++) + { + var ss1 = s1.Sequences[ss]; + var ss2 = s2.Sequences[ss]; + if ((ss1?.Channels == null) || (ss2?.Channels == null)) + { continue; } + if (ss1.Channels.Length != ss2.Channels.Length) + { continue; } + + + for (int c = 0; c < ss1.Channels.Length; c++) + { + var sc1 = ss1.Channels[c]; + var sc2 = ss2.Channels[c]; + if ((sc1 == null) || (sc2 == null)) + { continue; } + if (sc1.Type == AnimChannelType.LinearFloat) + { continue; } + if (sc1.Type != sc2.Type) + { continue; } + if (sc1.Index != sc2.Index) + { continue; } + if (sc1.Type == AnimChannelType.StaticQuaternion) + { + var acsq1 = sc1 as AnimChannelStaticQuaternion; + var acsq2 = sc2 as AnimChannelStaticQuaternion; + var vdiff = acsq1.Value - acsq2.Value; + var len = vdiff.Length(); + var v1len = Math.Max(acsq1.Value.Length(), 1); + if (len > 1e-2f * v1len) + { continue; } + } + else if (sc1.Type == AnimChannelType.StaticVector3) + { + var acsv1 = sc1 as AnimChannelStaticVector3; + var acsv2 = sc2 as AnimChannelStaticVector3; + var vdiff = acsv1.Value - acsv2.Value; + var len = vdiff.Length(); + var v1len = Math.Max(acsv1.Value.Length(), 1); + if (len > 1e-2f * v1len) + { continue; } + } + else if (sc1.Type == AnimChannelType.StaticFloat) + { + var acsf1 = sc1 as AnimChannelStaticFloat; + var acsf2 = sc2 as AnimChannelStaticFloat; + var vdiff = Math.Abs(acsf1.Value - acsf2.Value); + var v1len = Math.Max(Math.Abs(acsf1.Value), 1); + if (vdiff > 1e-2f * v1len) + { continue; } + } + else if (sc1.Type == AnimChannelType.RawFloat) + { + var acrf1 = sc1 as AnimChannelRawFloat; + var acrf2 = sc2 as AnimChannelRawFloat; + for (int v = 0; v < acrf1.Values.Length; v++) + { + var v1 = acrf1.Values[v]; + var v2 = acrf2.Values[v]; + var vdiff = Math.Abs(v1 - v2); + var v1len = Math.Max(Math.Abs(v1), 1); + if (vdiff > 1e-2f * v1len) + { break; } + } + } + else if (sc1.Type == AnimChannelType.QuantizeFloat) + { + var acqf1 = sc1 as AnimChannelQuantizeFloat; + var acqf2 = sc2 as AnimChannelQuantizeFloat; + if (acqf1.ValueBits != acqf2.ValueBits) + { continue; } + if (Math.Abs(acqf1.Offset - acqf2.Offset) > (0.001f * Math.Abs(acqf1.Offset))) + { continue; } + if (Math.Abs(acqf1.Quantum - acqf2.Quantum) > 0.00001f) + { continue; } + for (int v = 0; v < acqf1.Values.Length; v++) + { + var v1 = acqf1.Values[v]; + var v2 = acqf2.Values[v]; + var vdiff = Math.Abs(v1 - v2); + var v1len = Math.Max(Math.Abs(v1), 1); + if (vdiff > 1e-2f * v1len) + { break; } + } + } + else if (sc1.Type == AnimChannelType.IndirectQuantizeFloat) + { + var aciqf1 = sc1 as AnimChannelIndirectQuantizeFloat; + var aciqf2 = sc2 as AnimChannelIndirectQuantizeFloat; + if (aciqf1.FrameBits != aciqf2.FrameBits) + { continue; } + if (aciqf1.ValueBits != aciqf2.ValueBits) + { continue; } + if (Math.Abs(aciqf1.Offset - aciqf2.Offset) > (0.001f * Math.Abs(aciqf1.Offset))) + { continue; } + if (Math.Abs(aciqf1.Quantum - aciqf2.Quantum) > 0.00001f) + { continue; } + for (int f = 0; f < aciqf1.Frames.Length; f++) + { + if (aciqf1.Frames[f] != aciqf2.Frames[f]) + { break; } + } + for (int v = 0; v < aciqf1.Values.Length; v++) + { + var v1 = aciqf1.Values[v]; + var v2 = aciqf2.Values[v]; + var vdiff = Math.Abs(v1 - v2); + var v1len = Math.Max(Math.Abs(v1), 1); + if (vdiff > 1e-2f * v1len) + { break; } + } + } + else if ((sc1.Type == AnimChannelType.CachedQuaternion1)||(sc1.Type == AnimChannelType.CachedQuaternion2)) + { + var acrf1 = sc1 as AnimChannelCachedQuaternion; + var acrf2 = sc2 as AnimChannelCachedQuaternion; + if (acrf1.QuatIndex != acrf2.QuatIndex) + { continue; } + } + + + + + } + + + //for (int f = 0; f < s1.NumFrames; f++) + //{ + // var v1 = ss1.EvaluateVector(f); + // var v2 = ss2.EvaluateVector(f); + // var vdiff = v1 - v2; + // var len = vdiff.Length(); + // var v1len = Math.Max(v1.Length(), 1); + // if (len > 1e-2f*v1len) + // { } + //} + } + + + } + + + } + + + + } - //if (entry.NameLower.EndsWith(".awc")) //awcs can also contain clip dicts.. - //{ - // UpdateStatus(string.Format(entry.Path)); - // AwcFile awcfile = RpfMan.GetFile(entry); - // if ((awcfile != null)) - // { } - //} - } - catch (Exception ex) - { - UpdateStatus("Error! " + ex.ToString()); } + //if (entry.NameLower.EndsWith(".awc")) //awcs can also contain clip dicts.. + //{ + // UpdateStatus(string.Format(entry.Path)); + // AwcFile awcfile = RpfMan.GetFile(entry); + // if ((awcfile != null)) + // { } + //} + //} + //catch (Exception ex) + //{ + // UpdateStatus("Error! " + ex.ToString()); + //} } } - //var sd = Sequence.SeqDict; - //if (sd != null) - //{ - //} + if (errorfiles.Count > 0) + { } + } public void TestYtds() { diff --git a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs index 869dc32..f51d0b9 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/MetaXml.cs @@ -1384,18 +1384,6 @@ namespace CodeWalker.GameFiles } - public static string XmlEscape(string unescaped) - { - if (unescaped == null) return null; - XmlDocument doc = new XmlDocument(); - XmlNode node = doc.CreateElement("root"); - node.InnerText = unescaped; - var escaped = node.InnerXml; - if (escaped != unescaped) - { } - return node.InnerXml; - } - public class PsoCont { @@ -1742,9 +1730,16 @@ namespace CodeWalker.GameFiles var cind2 = ind + 2; for (int i = 0; i < itemCount; i++) { - OpenTag(sb, cind, "Item"); - arr[i].WriteXml(sb, cind2); - CloseTag(sb, cind, "Item"); + if (arr[i] != null) + { + OpenTag(sb, cind, "Item"); + arr[i].WriteXml(sb, cind2); + CloseTag(sb, cind, "Item"); + } + else + { + SelfClosingTag(sb, cind, "Item"); + } } CloseTag(sb, ind, name); } @@ -1891,6 +1886,22 @@ namespace CodeWalker.GameFiles + + public static string XmlEscape(string unescaped) + { + if (unescaped == null) return null; + XmlDocument doc = new XmlDocument(); + XmlNode node = doc.CreateElement("root"); + node.InnerText = unescaped; + var escaped = node.InnerXml; + if (escaped != unescaped) + { } + return node.InnerXml; + } + + + + public enum XmlTagMode { None = 0, diff --git a/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs b/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs index bd22978..91f6b2f 100644 --- a/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs +++ b/CodeWalker.Core/GameFiles/MetaTypes/XmlMeta.cs @@ -813,6 +813,78 @@ namespace CodeWalker.GameFiles return 0; } + + + + public static T[] ReadItemArray(XmlNode node, string name) where T : IMetaXmlItem, new() + { + var vnode2 = node.SelectSingleNode(name); + if (vnode2 != null) + { + var inodes = vnode2.SelectNodes("Item"); + if (inodes?.Count > 0) + { + var vlist = new List(); + foreach (XmlNode inode in inodes) + { + var v = new T(); + v.ReadXml(inode); + vlist.Add(v); + } + return vlist.ToArray(); + } + } + return null; + } + + public static T[] ReadItemArrayNullable(XmlNode node, string name) where T : IMetaXmlItem, new() + { + var vnode2 = node.SelectSingleNode(name); + if (vnode2 != null) + { + var inodes = vnode2.SelectNodes("Item"); + if (inodes?.Count > 0) + { + var vlist = new List(); + foreach (XmlNode inode in inodes) + { + if (inode.HasChildNodes) + { + var v = new T(); + v.ReadXml(inode); + vlist.Add(v); + } + else + { + vlist.Add(default(T)); + } + } + return vlist.ToArray(); + } + } + return null; + } + + + public static MetaHash[] ReadHashItemArray(XmlNode node, string name) + { + var vnode = node.SelectSingleNode(name); + if (vnode != null) + { + var inodes = vnode.SelectNodes("Item"); + if (inodes?.Count > 0) + { + var vlist = new List(); + foreach (XmlNode inode in inodes) + { + vlist.Add(GetHash(inode.InnerText)); + } + return vlist.ToArray(); + } + } + return null; + } + } struct ArrayResults diff --git a/CodeWalker.Core/GameFiles/Resources/Clip.cs b/CodeWalker.Core/GameFiles/Resources/Clip.cs index 7e53153..58f6137 100644 --- a/CodeWalker.Core/GameFiles/Resources/Clip.cs +++ b/CodeWalker.Core/GameFiles/Resources/Clip.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Xml; /* Copyright(c) 2016 Neodymium @@ -48,12 +50,12 @@ namespace CodeWalker.GameFiles public uint Unknown_10h { get; set; } // 0x00000000 public uint Unknown_14h { get; set; } // 0x00000000 public ulong AnimationsPointer { get; set; } - public uint Unknown_20h { get; set; } // 0x00000101 + public uint Unknown_20h { get; set; } = 0x00000101; public uint Unknown_24h { get; set; } // 0x00000000 public ulong ClipsPointer { get; set; } public ushort ClipsMapCapacity { get; set; } public ushort ClipsMapEntries { get; set; } - public uint Unknown_34h { get; set; } // 0x01000000 + public uint Unknown_34h { get; set; } = 0x01000000; public uint Unknown_38h { get; set; } // 0x00000000 public uint Unknown_3Ch { get; set; } // 0x00000000 @@ -61,6 +63,11 @@ namespace CodeWalker.GameFiles public AnimationMap Animations { get; set; } public ResourcePointerArray64 Clips { get; set; } + //data used by CW for loading/saving + public Dictionary ClipMap { get; set; } + public Dictionary AnimMap { get; set; } + + public override void Read(ResourceDataReader reader, params object[] parameters) { base.Read(reader, parameters); @@ -86,6 +93,8 @@ namespace CodeWalker.GameFiles this.ClipsPointer, // offset this.ClipsMapCapacity ); + + BuildMaps(); } public override void Write(ResourceDataWriter writer, params object[] parameters) @@ -95,7 +104,9 @@ namespace CodeWalker.GameFiles // update structure data this.AnimationsPointer = (ulong)(this.Animations != null ? this.Animations.FilePosition : 0); this.ClipsPointer = (ulong)(this.Clips != null ? this.Clips.FilePosition : 0); - //this.c1 = (ushort)(this.Clips != null ? this.Clips.Count : 0); + this.ClipsMapCapacity = (ushort)((Clips != null) ? Clips.Count : 0); + this.ClipsMapEntries = (ushort)((ClipMap != null) ? ClipMap.Count : 0); + // write structure data writer.Write(this.Unknown_10h); @@ -114,10 +125,226 @@ namespace CodeWalker.GameFiles public override IResourceBlock[] GetReferences() { var list = new List(base.GetReferences()); - if (Animations != null) list.Add(Animations); + if (Animations != null) + { + list.Add(Animations); + Animations.AnimationsMapEntries = (ushort)(AnimMap?.Count ?? 0); + } if (Clips != null) list.Add(Clips); return list.ToArray(); } + + + + public void BuildMaps() + { + ClipMap = new Dictionary(); + AnimMap = new Dictionary(); + + if ((Clips != null) && (Clips.data_items != null)) + { + foreach (var cme in Clips.data_items) + { + if (cme != null) + { + ClipMap[cme.Hash] = cme; + var nxt = cme.Next; + while (nxt != null) + { + ClipMap[nxt.Hash] = nxt; + nxt = nxt.Next; + } + } + } + } + if ((Animations != null) && (Animations.Animations != null) && (Animations.Animations.data_items != null)) + { + foreach (var ame in Animations.Animations.data_items) + { + if (ame != null) + { + AnimMap[ame.Hash] = ame; + var nxt = ame.NextEntry; + while (nxt != null) + { + AnimMap[nxt.Hash] = nxt; + nxt = nxt.NextEntry; + } + } + } + } + + foreach (var cme in ClipMap.Values) + { + var clip = cme.Clip; + if (clip == null) continue; + if (string.IsNullOrEmpty(clip.Name)) continue; + string name = clip.Name.Replace('\\', '/'); + var slidx = name.LastIndexOf('/'); + if ((slidx >= 0) && (slidx < name.Length - 1)) + { + name = name.Substring(slidx + 1); + } + var didx = name.LastIndexOf('.'); + if ((didx > 0) && (didx < name.Length)) + { + name = name.Substring(0, didx); + } + clip.ShortName = name; + name = name.ToLowerInvariant(); + JenkIndex.Ensure(name); + + + //if (name.EndsWith("_uv_0")) //hash for these entries match string with this removed, +1 + //{ + //} + //if (name.EndsWith("_uv_1")) //same as above, but +2 + //{ + //} + + } + foreach (var ame in AnimMap.Values) + { + var anim = ame.Animation; + if (anim == null) continue; + } + } + + + + public void WriteXml(StringBuilder sb, int indent) + { + var anims = new List(); + if (AnimMap != null) + { + foreach (var ame in AnimMap.Values) + { + if (ame?.Animation == null) continue; + anims.Add(ame.Animation); + } + } + var clips = new List(); + if (ClipMap != null) + { + foreach (var cme in ClipMap.Values) + { + if (cme?.Clip == null) continue; + clips.Add(cme.Clip); + } + } + + YcdXml.WriteItemArray(sb, Clips?.data_items, indent, "ClipMap"); + YcdXml.WriteItemArray(sb, clips.ToArray(), indent, "Clips"); + YcdXml.WriteItemArray(sb, Animations?.Animations?.data_items, indent, "AnimationMap"); + YcdXml.WriteItemArray(sb, anims.ToArray(), indent, "Animations"); + + } + public void ReadXml(XmlNode node) + { + + + var animmap = XmlMeta.ReadItemArrayNullable(node, "AnimationMap"); + var clipmap = XmlMeta.ReadItemArrayNullable(node, "ClipMap"); + + Animations = new AnimationMap(); + if (animmap != null) + { + Animations.Animations = new ResourcePointerArray64(); + Animations.Animations.data_items = animmap; + Animations.AnimationsMapCapacity = (ushort)(animmap?.Length ?? 0); + Animations.AnimationsMapEntries = (ushort)(animmap?.Length ?? 0); + } + if (clipmap != null) + { + Clips = new ResourcePointerArray64(); + Clips.data_items = clipmap; + ClipsMapCapacity = (ushort)(clipmap?.Length ?? 0); + ClipsMapEntries = (ushort)(clipmap?.Length ?? 0); + } + + + var animDict = new Dictionary(); + var clipDict = new Dictionary(); + var clipsNode = node.SelectSingleNode("Clips"); + if (clipsNode != null) + { + var inodes = clipsNode.SelectNodes("Item"); + if (inodes?.Count > 0) + { + foreach (XmlNode inode in inodes) + { + var type = Xml.GetEnumValue(Xml.GetChildStringAttribute(inode, "Type", "value")); + var c = ClipBase.ConstructClip(type); + c.ReadXml(inode); + clipDict[c.Name?.ToLowerInvariant()] = c; + } + } + } + var anims = XmlMeta.ReadItemArrayNullable(node, "Animations"); + if (anims != null) + { + foreach (var anim in anims) + { + animDict[anim.AnimationHash] = anim; + } + } + + + if (animmap != null) + { + foreach (var anim in animmap) + { + var a = anim; + while (a != null) + { + Animation aa = null; + animDict.TryGetValue(a.Hash, out aa); + a.Animation = aa; + if (aa == null) + { } + a = a.NextEntry; + } + } + } + if (clipmap != null) + { + foreach (var clip in clipmap) + { + var c = clip; + while (c != null) + { + ClipBase cb = null; + clipDict.TryGetValue(c.ClipName?.ToLowerInvariant(), out cb); + c.Clip = cb; + if (cb == null) + { } + + var clipanim = cb as ClipAnimation; + if (clipanim != null) + { + animDict.TryGetValue(clipanim.AnimationHash, out Animation a); + clipanim.Animation = a; + } + var clipanimlist = cb as ClipAnimationList; + if (clipanimlist?.Animations?.Data != null) + { + foreach (var cae in clipanimlist.Animations.Data) + { + animDict.TryGetValue(cae.AnimationHash, out Animation a); + cae.Animation = a; + } + } + + c = c.Next; + } + } + } + + + BuildMaps(); + + } + } @@ -130,17 +357,17 @@ namespace CodeWalker.GameFiles // structure data public uint VFT { get; set; } - public uint Unknown_04h { get; set; } // 0x00000001 - public uint Unknown_08h { get; set; } // 0x00000000 - public uint Unknown_0Ch { get; set; } // 0x00000000 - public uint Unknown_10h { get; set; } // 0x00000000 - public uint Unknown_14h { get; set; } // 0x00000000 + public uint Unknown_04h { get; set; } = 1; // 0x00000001 + public uint Unknown_08h { get; set; } = 0; // 0x00000000 + public uint Unknown_0Ch { get; set; } = 0; // 0x00000000 + public uint Unknown_10h { get; set; } = 0; // 0x00000000 + public uint Unknown_14h { get; set; } = 0; // 0x00000000 public ulong AnimationsPointer { get; set; } public ushort AnimationsMapCapacity { get; set; } public ushort AnimationsMapEntries { get; set; } - public uint Unknown_24h { get; set; } - public uint Unknown_28h { get; set; } // 0x00000001 - public uint Unknown_2Ch { get; set; } // 0x00000000 + public uint Unknown_24h { get; set; } = 16777216; + public uint Unknown_28h { get; set; } = 1; // 0x00000001 + public uint Unknown_2Ch { get; set; } = 0; // 0x00000000 // reference data public ResourcePointerArray64 Animations { get; set; } @@ -172,7 +399,8 @@ namespace CodeWalker.GameFiles { // update structure data this.AnimationsPointer = (ulong)(this.Animations != null ? this.Animations.FilePosition : 0); - //this.c1 = (ushort)(this.Anims != null ? this.Anims.Count : 0); + this.AnimationsMapCapacity = (ushort)(this.Animations != null ? this.Animations.Count : 0); + //this.AnimationsMapEntries //this is already set by ClipDictionary // write structure data writer.Write(this.VFT); @@ -196,7 +424,7 @@ namespace CodeWalker.GameFiles return list.ToArray(); } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimationMapEntry : ResourceSystemBlock + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimationMapEntry : ResourceSystemBlock, IMetaXmlItem { public override long BlockLength { @@ -232,6 +460,13 @@ namespace CodeWalker.GameFiles this.NextEntry = reader.ReadBlockAt( this.NextEntryPtr // offset ); + + if (Animation != null) + { + if (Animation.AnimationHash != 0) + { } + Animation.AnimationHash = Hash; + } } public override void Write(ResourceDataWriter writer, params object[] parameters) @@ -261,8 +496,31 @@ namespace CodeWalker.GameFiles { return Hash.ToString(); } + + + public void WriteXml(StringBuilder sb, int indent) + { + YcdXml.StringTag(sb, indent, "Hash", YcdXml.HashString(Hash)); + if (NextEntry != null) + { + YcdXml.OpenTag(sb, indent, "Next"); + NextEntry.WriteXml(sb, indent + 1); + YcdXml.CloseTag(sb, indent, "Next"); + } + } + public void ReadXml(XmlNode node) + { + Hash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Hash")); + var nextnode = node.SelectSingleNode("Next"); + if (nextnode != null) + { + var next = new AnimationMapEntry(); + next.ReadXml(nextnode); + NextEntry = next; + } + } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class Animation : ResourceSystemBlock + [TypeConverter(typeof(ExpandableObjectConverter))] public class Animation : ResourceSystemBlock, IMetaXmlItem { public override long BlockLength { @@ -271,7 +529,7 @@ namespace CodeWalker.GameFiles // structure data public uint VFT { get; set; } - public uint Unknown_04h { get; set; } // 0x00000001 + public uint Unknown_04h { get; set; } = 1; // 0x00000001 public uint Unknown_08h { get; set; } // 0x00000000 public uint Unknown_0Ch { get; set; } // 0x00000000 public ushort Unknown_10h { get; set; } @@ -286,15 +544,14 @@ namespace CodeWalker.GameFiles public uint Unknown_2Ch { get; set; } // 0x00000000 public uint Unknown_30h { get; set; } // 0x00000000 public uint Unknown_34h { get; set; } // 0x00000000 - public uint RawDataSize { get; set; } + public uint MaxSeqBlockLength { get; set; } public uint UsageCount { get; set; } public ResourcePointerList64 Sequences { get; set; } public ResourceSimpleList64_s BoneIds { get; set; } - //public ResourceSimpleList64Ptr BoneIdsPtr { get; set; } - //public AnimationBoneId[] BoneIds { get; set; } public YcdFile Ycd { get; set; } + public MetaHash AnimationHash { get; set; } //updated by CW, for use when reading/writing files public override void Read(ResourceDataReader reader, params object[] parameters) { @@ -315,13 +572,12 @@ namespace CodeWalker.GameFiles this.Unknown_2Ch = reader.ReadUInt32(); //0 0 0 0 this.Unknown_30h = reader.ReadUInt32(); //0 0 0 0 this.Unknown_34h = reader.ReadUInt32(); //0 0 0 0 - this.RawDataSize = reader.ReadUInt32(); //314 174 1238 390 sequences length? + this.MaxSeqBlockLength = reader.ReadUInt32(); //314 174 1238 390 maximum sequence block size this.UsageCount = reader.ReadUInt32(); //2 2 2 2 material/type? this.Sequences = reader.ReadBlock>(); this.BoneIds = reader.ReadBlock>(); - //this.BoneIdsPtr = reader.ReadStruct(); - ////this.BoneIds = reader.ReadUintsAt(this.BoneIdsPtr.EntriesPointer, this.BoneIdsPtr.EntriesCount); - //this.BoneIds = reader.ReadStructsAt(this.BoneIdsPtr.EntriesPointer, this.BoneIdsPtr.EntriesCount); + + AssignSequenceBoneIds(); } public override void Write(ResourceDataWriter writer, params object[] parameters) @@ -343,7 +599,7 @@ namespace CodeWalker.GameFiles writer.Write(this.Unknown_2Ch); writer.Write(this.Unknown_30h); writer.Write(this.Unknown_34h); - writer.Write(this.RawDataSize); + writer.Write(this.MaxSeqBlockLength); writer.Write(this.UsageCount); writer.WriteBlock(this.Sequences); writer.WriteBlock(this.BoneIds); @@ -356,33 +612,124 @@ namespace CodeWalker.GameFiles new Tuple(0x50, BoneIds) }; } + + + public void AssignSequenceBoneIds() + { + if (Sequences?.data_items != null) + { + foreach (var seq in Sequences.data_items) + { + for (int i = 0; i < seq?.Sequences?.Length; i++) + { + if (i < BoneIds?.data_items?.Length) + { + seq.Sequences[i].BoneId = BoneIds.data_items[i]; + } + } + } + } + } + + public void CalculateMaxSeqBlockLength() + { + if (Sequences?.data_items != null) + { + uint maxSize = 0; + foreach (var seq in Sequences.data_items) + { + maxSize = Math.Max(maxSize, (uint)seq.BlockLength); + } + MaxSeqBlockLength = maxSize; + } + } + + + public void WriteXml(StringBuilder sb, int indent) + { + YcdXml.StringTag(sb, indent, "Hash", YcdXml.HashString(AnimationHash)); + YcdXml.ValueTag(sb, indent, "Unknown10", Unknown_10h.ToString()); + YcdXml.ValueTag(sb, indent, "Unknown12", Unknown_12h.ToString()); + YcdXml.ValueTag(sb, indent, "FrameCount", Frames.ToString()); + YcdXml.ValueTag(sb, indent, "SequenceFrameLimit", SequenceFrameLimit.ToString());//sequences should be transparent to this! + YcdXml.ValueTag(sb, indent, "Duration", FloatUtil.ToString(Duration)); + YcdXml.StringTag(sb, indent, "Unknown1C", YcdXml.HashString(Unknown_1Ch)); + YcdXml.ValueTag(sb, indent, "UsageCount", UsageCount.ToString());//is this needed? + YcdXml.WriteItemArray(sb, BoneIds?.data_items, indent, "BoneIds"); + YcdXml.WriteItemArray(sb, Sequences?.data_items, indent, "Sequences"); + } + public void ReadXml(XmlNode node) + { + AnimationHash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Hash")); + Unknown_10h = (ushort)Xml.GetChildUIntAttribute(node, "Unknown10", "value"); + Unknown_12h = (ushort)Xml.GetChildUIntAttribute(node, "Unknown12", "value"); + Frames = (ushort)Xml.GetChildUIntAttribute(node, "FrameCount", "value"); + SequenceFrameLimit = (ushort)Xml.GetChildUIntAttribute(node, "SequenceFrameLimit", "value"); + Duration = Xml.GetChildFloatAttribute(node, "Duration", "value"); + Unknown_1Ch = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Unknown1C")); + UsageCount = Xml.GetChildUIntAttribute(node, "UsageCount", "value"); + + BoneIds = new ResourceSimpleList64_s(); + BoneIds.data_items = XmlMeta.ReadItemArray(node, "BoneIds"); + + Sequences = new ResourcePointerList64(); + Sequences.data_items = XmlMeta.ReadItemArrayNullable(node, "Sequences"); + + AssignSequenceBoneIds(); + CalculateMaxSeqBlockLength(); + } } - [TypeConverter(typeof(ExpandableObjectConverter))] public struct AnimationBoneId + [TypeConverter(typeof(ExpandableObjectConverter))] public struct AnimationBoneId : IMetaXmlItem { public ushort BoneId { get; set; } public byte Unk0 { get; set; } public byte Track { get; set; } + public override string ToString() { return BoneId.ToString() + ": " + Unk0.ToString() + ", " + Track.ToString(); } + + public void WriteXml(StringBuilder sb, int indent) + { + YcdXml.ValueTag(sb, indent, "BoneId", BoneId.ToString()); + YcdXml.ValueTag(sb, indent, "Track", Track.ToString()); + YcdXml.ValueTag(sb, indent, "Unk0", Unk0.ToString()); + } + public void ReadXml(XmlNode node) + { + BoneId = (ushort)Xml.GetChildUIntAttribute(node, "BoneId", "value"); + Track = (byte)Xml.GetChildUIntAttribute(node, "Track", "value"); + Unk0 = (byte)Xml.GetChildUIntAttribute(node, "Unk0", "value"); + } } - [TypeConverter(typeof(ExpandableObjectConverter))] public abstract class AnimChannel + + public enum AnimChannelType : int { - public int Sequence { get; private set; } - public int Index { get; private set; } + StaticQuaternion = 0, + StaticVector3 = 1, + StaticFloat = 2, + RawFloat = 3, + QuantizeFloat = 4, + IndirectQuantizeFloat = 5, + LinearFloat = 6, + CachedQuaternion1 = 7, + CachedQuaternion2 = 8, + } + [TypeConverter(typeof(ExpandableObjectConverter))] public abstract class AnimChannel : IMetaXmlItem + { + public AnimChannelType Type { get; set; } + public int Sequence { get; set; } + public int Index { get; set; } - public abstract void Read(Sequence blockStream, ref int channelOffset); + public abstract void Read(AnimChannelDataReader reader); + public virtual void Write(AnimChannelDataWriter writer) + { } + public virtual void ReadFrame(AnimChannelDataReader reader) + { } + public virtual void WriteFrame(AnimChannelDataWriter writer) + { } - public virtual void ReadData(Sequence blockStream, ref int channelOffset) - { - - } - - public virtual void ReadFrame(Sequence blockStream, int frame, ref int frameOffset) - { - - } public virtual float EvaluateFloat(int frame) => 0.0f; @@ -391,114 +738,267 @@ namespace CodeWalker.GameFiles Sequence = sequence; Index = index; } + + public virtual void WriteXml(StringBuilder sb, int indent) + { + YcdXml.ValueTag(sb, indent, "Type", Type.ToString()); + //YcdXml.ValueTag(sb, indent, "Sequence", Sequence.ToString()); + //YcdXml.ValueTag(sb, indent, "Index", Index.ToString()); + } + public virtual void ReadXml(XmlNode node) + { + //not necessary to read Type as it's already read and set in constructor + //Type = Xml.GetEnumValue(Xml.GetChildStringAttribute(node, "Type", "value")); + //Sequence = Xml.GetChildIntAttribute(node, "Sequence", "value"); + //Index = Xml.GetChildIntAttribute(node, "Index", "value"); + } + + + public static AnimChannel ConstructChannel(AnimChannelType type) + { + switch (type) + { + case AnimChannelType.StaticQuaternion: + return new AnimChannelStaticQuaternion(); + case AnimChannelType.StaticVector3: + return new AnimChannelStaticVector3(); + case AnimChannelType.StaticFloat: + return new AnimChannelStaticFloat(); + case AnimChannelType.RawFloat: + return new AnimChannelRawFloat(); + case AnimChannelType.QuantizeFloat: + return new AnimChannelQuantizeFloat(); + case AnimChannelType.IndirectQuantizeFloat: + return new AnimChannelIndirectQuantizeFloat(); + case AnimChannelType.LinearFloat: + return new AnimChannelLinearFloat(); + case AnimChannelType.CachedQuaternion1: + // normalized W from quaternion (evaluate first three channels, calculate W) + return new AnimChannelCachedQuaternion(AnimChannelType.CachedQuaternion1); + case AnimChannelType.CachedQuaternion2: + // unknown extra + // kind of the same as above but different at runtime? + return new AnimChannelCachedQuaternion(AnimChannelType.CachedQuaternion2); + default: + return null; + } + + } + } [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelStaticFloat : AnimChannel { - public float FloatValue { get; set; } + public float Value { get; set; } - public override void Read(Sequence blockStream, ref int channelOffset) + public AnimChannelStaticFloat() { - FloatValue = BitConverter.ToSingle(blockStream.Data, channelOffset); - channelOffset += 4; + Type = AnimChannelType.StaticFloat; + } + + public override void Read(AnimChannelDataReader reader) + { + Value = reader.ReadSingle(); + } + public override void Write(AnimChannelDataWriter writer) + { + writer.Write(Value); } public override float EvaluateFloat(int frame) { - return FloatValue; + return Value; + } + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.ValueTag(sb, indent, "Value", FloatUtil.ToString(Value)); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Value = Xml.GetChildFloatAttribute(node, "Value", "value"); } } [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelStaticVector3 : AnimChannel { public Vector3 Value { get; set; } - public override void Read(Sequence blockStream, ref int channelOffset) + public AnimChannelStaticVector3() { - Value = new Vector3( - BitConverter.ToSingle(blockStream.Data, channelOffset), - BitConverter.ToSingle(blockStream.Data, channelOffset + 4), - BitConverter.ToSingle(blockStream.Data, channelOffset + 8) - ); + Type = AnimChannelType.StaticVector3; + } - channelOffset += 12; + public override void Read(AnimChannelDataReader reader) + { + Value = reader.ReadVector3(); + } + public override void Write(AnimChannelDataWriter writer) + { + writer.Write(Value); + } + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.SelfClosingTag(sb, indent, "Value " + FloatUtil.GetVector3XmlString(Value)); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Value = Xml.GetChildVector3Attributes(node, "Value", "x", "y", "z"); } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelStaticSmallestThreeQuaternion : AnimChannel + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelStaticQuaternion : AnimChannel { public Quaternion Value { get; set; } - public override void Read(Sequence blockStream, ref int channelOffset) + public AnimChannelStaticQuaternion() { - var vec = new Vector3( - BitConverter.ToSingle(blockStream.Data, channelOffset), - BitConverter.ToSingle(blockStream.Data, channelOffset + 4), - BitConverter.ToSingle(blockStream.Data, channelOffset + 8) - ); + Type = AnimChannelType.StaticQuaternion; + } + + public override void Read(AnimChannelDataReader reader) + { + var vec = reader.ReadVector3(); Value = new Quaternion( vec, (float)Math.Sqrt(Math.Max(1.0f - vec.LengthSquared(), 0.0)) ); + } + public override void Write(AnimChannelDataWriter writer) + { + writer.Write(Value.ToVector4().XYZ());//heh + } - channelOffset += 12; + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.SelfClosingTag(sb, indent, "Value " + FloatUtil.GetVector4XmlString(Value.ToVector4())); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Value = new Quaternion(Xml.GetChildVector4Attributes(node, "Value", "x", "y", "z", "w")); } } [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelIndirectQuantizeFloat : AnimChannel { public int FrameBits { get; set; } public int ValueBits { get; set; } + public int NumInts { get; set; } public float Quantum { get; set; } public float Offset { get; set; } public float[] Values { get; set; } public uint[] Frames { get; set; } - private int numInts; - public override void Read(Sequence blockStream, ref int channelOffset) + public AnimChannelIndirectQuantizeFloat() { - FrameBits = BitConverter.ToInt32(blockStream.Data, channelOffset); - ValueBits = BitConverter.ToInt32(blockStream.Data, channelOffset + 4); - numInts = BitConverter.ToInt32(blockStream.Data, channelOffset + 8); - Quantum = BitConverter.ToSingle(blockStream.Data, channelOffset + 12); - Offset = BitConverter.ToSingle(blockStream.Data, channelOffset + 16); + Type = AnimChannelType.IndirectQuantizeFloat; + } - channelOffset += 20; + public override void Read(AnimChannelDataReader reader) + { + FrameBits = reader.ReadInt32(); + ValueBits = reader.ReadInt32(); + NumInts = reader.ReadInt32(); + Quantum = reader.ReadSingle(); + Offset = reader.ReadSingle(); - var bit = channelOffset * 8; - var endBit = bit + (numInts * 32); + Frames = new uint[reader.NumFrames]; - channelOffset += numInts * 4; - - var valueList = new List(); - - while (bit < endBit) + int numValues = (NumInts * 32) / ValueBits; + Values = new float[numValues]; + reader.BitPosition = reader.Position * 8; + for (int i = 0; i < numValues; i++) { - valueList.Add((blockStream.GetBit(bit, ValueBits) * Quantum) + Offset); + uint bits = reader.ReadBits(ValueBits); + Values[i] = (bits * Quantum) + Offset; + } + reader.Position += NumInts * 4; - bit += ValueBits; + + //var endBit = bit + (NumInts * 32); + //var valueList = new List(); + //while (bit < endBit) // this actually seems to be reading too far..... + //{ + // valueList.Add((reader.GetBit(bit, ValueBits) * Quantum) + Offset); + // bit += ValueBits; + //} + //Values = valueList.ToArray(); + + } + public override void Write(AnimChannelDataWriter writer) + { + FrameBits = writer.BitCount(Frames); + + var valueCount = Values?.Length ?? 0; + var valueList = new uint[valueCount]; + for (int i = 0; i < valueCount; i++) + { + valueList[i] = GetQuanta(Values[i]); + } + ValueBits = writer.BitCount(valueList); + + writer.ResetBitstream(); + for (int i = 0; i < valueCount; i++) + { + var u = valueList[i]; + writer.WriteBits(u, ValueBits); } - Values = valueList.ToArray(); + NumInts = writer.Bitstream.Count; - Frames = new uint[blockStream.NumFrames]; + + writer.Write(FrameBits); + writer.Write(ValueBits); + writer.Write(NumInts); + writer.Write(Quantum); + writer.Write(Offset); + writer.WriteBitstream(); } - public override void ReadData(Sequence blockStream, ref int channelOffset) + public override void ReadFrame(AnimChannelDataReader reader) { - + Frames[reader.Frame] = reader.ReadFrameBits(FrameBits); } - - public override void ReadFrame(Sequence blockStream, int frame, ref int frameOffset) + public override void WriteFrame(AnimChannelDataWriter writer) { - Frames[frame] = blockStream.GetBit(frameOffset, FrameBits); - - frameOffset += FrameBits; + writer.WriteFrameBits(Frames[writer.Frame], FrameBits); } + + private uint GetQuanta(float v) + { + var q = (v - Offset) / Quantum; + return (uint)Math.Round(Math.Max(q, 0));//any better way? + } + + public override float EvaluateFloat(int frame) { if (Frames?.Length > 0) return Values[Frames[frame % Frames.Length]]; return Offset; } + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.ValueTag(sb, indent, "Quantum", FloatUtil.ToString(Quantum)); + YcdXml.ValueTag(sb, indent, "Offset", FloatUtil.ToString(Offset)); + YcdXml.WriteRawArray(sb, Values, indent, "Values", "", FloatUtil.ToString, 10);// (Values?.Length ?? 0) + 1); + YcdXml.WriteRawArray(sb, Frames, indent, "Frames", "", null, 10);// (Frames?.Length ?? 0) + 1); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Quantum = Xml.GetChildFloatAttribute(node, "Quantum", "value"); + Offset = Xml.GetChildFloatAttribute(node, "Offset", "value"); + Values = Xml.GetChildRawFloatArray(node, "Values"); + Frames = Xml.GetChildRawUintArray(node, "Frames"); + } } [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelQuantizeFloat : AnimChannel { @@ -507,141 +1007,374 @@ namespace CodeWalker.GameFiles public float Offset { get; set; } public float[] Values { get; set; } - public override void Read(Sequence blockStream, ref int channelOffset) + public AnimChannelQuantizeFloat() { - ValueBits = BitConverter.ToInt32(blockStream.Data, channelOffset); - Quantum = BitConverter.ToSingle(blockStream.Data, channelOffset + 4); - Offset = BitConverter.ToSingle(blockStream.Data, channelOffset + 8); - Values = new float[blockStream.NumFrames]; - - channelOffset += 12; + Type = AnimChannelType.QuantizeFloat; } - public override void ReadFrame(Sequence blockStream, int frame, ref int frameOffset) + public override void Read(AnimChannelDataReader reader) { - Values[frame] = (blockStream.GetBit(frameOffset, ValueBits) * Quantum) + Offset; - frameOffset += ValueBits; + ValueBits = reader.ReadInt32(); + Quantum = reader.ReadSingle(); + Offset = reader.ReadSingle(); + Values = new float[reader.NumFrames]; } + public override void Write(AnimChannelDataWriter writer) + { + var valueCount = Values?.Length ?? 0; + var valueList = new uint[valueCount]; + for (int i = 0; i < valueCount; i++) + { + valueList[i] = GetQuanta(Values[i]); + } + ValueBits = writer.BitCount(valueList); + + writer.Write(ValueBits); + writer.Write(Quantum); + writer.Write(Offset); + } + + public override void ReadFrame(AnimChannelDataReader reader) + { + uint bits = reader.ReadFrameBits(ValueBits); + Values[reader.Frame] = (bits * Quantum) + Offset; + } + public override void WriteFrame(AnimChannelDataWriter writer) + { + uint bits = GetQuanta(Values[writer.Frame]); + writer.WriteFrameBits(bits, ValueBits); + } + + private uint GetQuanta(float v) + { + var q = (v - Offset) / Quantum; + return (uint)Math.Round(Math.Max(q, 0));//any better way? + } + public override float EvaluateFloat(int frame) { if (Values?.Length > 0) return Values[frame%Values.Length]; return Offset; } + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.ValueTag(sb, indent, "Quantum", FloatUtil.ToString(Quantum)); + YcdXml.ValueTag(sb, indent, "Offset", FloatUtil.ToString(Offset)); + YcdXml.WriteRawArray(sb, Values, indent, "Values", "", FloatUtil.ToString, 10);// (Values?.Length ?? 0) + 1); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Quantum = Xml.GetChildFloatAttribute(node, "Quantum", "value"); + Offset = Xml.GetChildFloatAttribute(node, "Offset", "value"); + Values = Xml.GetChildRawFloatArray(node, "Values"); + } } [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelLinearFloat : AnimChannel { + private int NumInts { get; set; } + private int Counts { get; set; } public float Quantum { get; set; } public float Offset { get; set; } + + private int Bit { get; set; } //chunks start bit + private int Count1 { get; set; } //number of offset bits for each chunk + private int Count2 { get; set; } //number of value bits for each chunk + private int Count3 { get; set; } //number of delta bits for each frame + public float[] Values { get; set; } - private Sequence blockStream; - private int NumInts { get; set; } - - private int Count1 { get; set; } - private int Count2 { get; set; } - private int Count3 { get; set; } - - private int Bit { get; set; } - - public override void Read(Sequence blockStream, ref int channelOffset) + public AnimChannelLinearFloat() { - NumInts = BitConverter.ToInt32(blockStream.Data, channelOffset); - var counts = BitConverter.ToInt32(blockStream.Data, channelOffset + 4); - Quantum = BitConverter.ToSingle(blockStream.Data, channelOffset + 8); - Offset = BitConverter.ToSingle(blockStream.Data, channelOffset + 12); - - var count1 = counts & 0xFF; - var count2 = (counts >> 8) & 0xFF; - var count3 = (counts >> 16) & 0xFF; - - Count1 = count1; - Count2 = count2; - Count3 = count3; - - Values = new float[blockStream.NumFrames]; - - Bit = (channelOffset * 8) + 128; - - this.blockStream = blockStream; - - var numChunks = (blockStream.ChunkSize + blockStream.NumFrames) / blockStream.ChunkSize; - - for (int i = 0; i < blockStream.NumFrames; i++) - { - Values[i] = ReconstructFloat(i); - } - - channelOffset += NumInts * 4; + Type = AnimChannelType.LinearFloat; } - private float ReconstructFloat(int frame) + public override void Read(AnimChannelDataReader reader) { - var chunkSize = blockStream.ChunkSize; - var numChunks = (blockStream.ChunkSize + blockStream.NumFrames) / chunkSize; + NumInts = reader.ReadInt32(); + Counts = reader.ReadInt32(); + Quantum = reader.ReadSingle(); + Offset = reader.ReadSingle(); - var startBit = Bit; - var startBit2 = (startBit + (numChunks * Count1)); - uint startBit3 = (uint)(startBit2 + (numChunks * Count2)); + Bit = (reader.Position * 8); //chunks start bit + Count1 = Counts & 0xFF; //number of offset bits for each chunk + Count2 = (Counts >> 8) & 0xFF; //number of value bits for each chunk + Count3 = (Counts >> 16) & 0xFF; //number of delta bits for each frame - var chunkIdx = (frame / chunkSize); - var chunkOff = (frame % chunkSize); + var streamLength = (reader.Data?.Length ?? 0) * 8; + var numFrames = reader.NumFrames; + var numChunks = reader.NumChunks; + var chunkSize = reader.ChunkSize;//64 or 255(-1?) + var deltaOffset = Bit + (numChunks * (Count1 + Count2));//base offset to delta bits - var offset = (Count1 != 0) ? blockStream.GetBit(startBit + (chunkIdx * Count1), Count1) : 0; - var value = (Count2 != 0) ? (int)blockStream.GetBit(startBit2 + (chunkIdx * Count2), Count2) : 0; - - var inc = 0; - - for (int i = 0; i < chunkOff; i++) + reader.BitPosition = Bit; + var chunkOffsets = new int[numChunks]; + var chunkValues = new int[numChunks]; + var frameValues = new float[numFrames]; + for (int i = 0; i < numChunks; i++) { - var delta = (Count3 != 0) ? (int)blockStream.GetBit((int)(startBit3 + offset), Count3) : 0; - offset += (uint)Count3; - - // scan for a '1' bit - uint bitIdx = 0; - + chunkOffsets[i] = (Count1 > 0) ? (int)reader.ReadBits(Count1) : 0; + } + for (int i = 0; i < numChunks; i++) + { + chunkValues[i] = (Count2 > 0) ? (int)reader.ReadBits(Count2) : 0; + } + for (int i = 0; i < numChunks; i++) + { + var doffs = chunkOffsets[i] + deltaOffset;//bit offset for chunk deltas + var value = chunkValues[i];//chunk start frame value + var cframe = (i * chunkSize);//chunk start frame + ////if ((reader.BitPosition != doffs)) + ////{ } + reader.BitPosition = doffs; + var inc = 0; + for (int j = 0; j < chunkSize; j++) { + int frame = cframe + j; + if (frame >= numFrames) break; + + frameValues[frame] = (value * Quantum) + Offset; + + if ((j + 1) >= chunkSize) break;//that's the last frame in the chunk, don't go further + + var delta = (Count3 != 0) ? (int)reader.ReadBits(Count3) : 0; + var so = reader.BitPosition; + var maxso = Math.Min(so + 32 - Count3, streamLength); // streamLength;// uint b = 0; - - do + while (b == 0) // scan for a '1' bit { - b = blockStream.GetBit((int)(startBit3 + offset + bitIdx), 1); - bitIdx++; - } while (b == 0); + b = reader.ReadBits(1); + if (reader.BitPosition >= maxso) + { break; } //trying to read more than 32 bits, or end of data... don't get into an infinite loop..! + } + delta |= ((reader.BitPosition - so - 1) << Count3); //add the found bit onto the delta. (position-so-1) is index of found bit + if (delta != 0) + { + var sign = reader.ReadBits(1); + if (sign == 1) + { + delta = -delta; + } + } + inc += delta; + value += inc; } + } + Values = frameValues; - offset += bitIdx; - delta |= (int)(((bitIdx - 1) << Count3)); + //for (int i = 1; i < numChunks; i++) + //{ + // if ((chunkOffsets[i] <= chunkOffsets[i - 1]) && (i != numChunks - 1)) + // { break; }//what's going on here? chunks not in order..? only seems to affect the final chunks? + //} - if (delta != 0) + + reader.Position -= 16;//TODO: fix this? + reader.Position += NumInts * 4; + } + public override void Write(AnimChannelDataWriter writer) + { + var numFrames = writer.NumFrames; + var numChunks = writer.NumChunks; + byte chunkSize = 64; //seems to always be 64 for this + if (writer.ChunkSize != chunkSize) + { writer.ChunkSize = chunkSize; } + + var valueCount = Values?.Length ?? 0; + var valueList = new uint[valueCount]; + for (int i = 0; i < valueCount; i++) + { + valueList[i] = GetQuanta(Values[i]); + } + + + var chunkOffsets = new uint[numChunks]; + var chunkValues = new uint[numChunks]; + var chunkDeltas = new int[numChunks][]; + var chunkDeltaBits = new uint[numFrames]; + for (int i = 0; i < numChunks; i++) + { + var cframe = (i * chunkSize);//chunk start frame + var cvalue = (cframe < numFrames) ? valueList[cframe] : valueList[numFrames - 1]; + var cdeltas = new int[chunkSize]; + var cinc = 0; + chunkValues[i] = cvalue; + chunkDeltas[i] = cdeltas; + for (int j = 1; j < chunkSize; j++) { - var sign = blockStream.GetBit((int)(startBit3 + offset), 1); - offset += 1; + int frame = cframe + j; + if (frame >= numFrames) break; + var value = valueList[frame]; + var inc = (int)value - (int)cvalue; + var delta = inc - cinc; + var deltaa = (uint)Math.Abs(delta); + cinc = inc; + cvalue = value; + cdeltas[j] = delta; + chunkDeltaBits[frame] = deltaa; + } + } + Count3 = writer.BitCount(chunkDeltaBits); //number of delta bits for each frame + uint coffset = 0; + for (int i = 0; i < numChunks; i++) + { + chunkOffsets[i] = coffset; + var cdeltas = chunkDeltas[i]; + for (int j = 1; j < chunkSize; j++) + { + var delta = cdeltas[j]; + coffset += (uint)Count3 + ((delta == 0) ? 1u : 2u); + } + } + Count1 = writer.BitCount(chunkOffsets); //number of offset bits for each chunk + Count2 = writer.BitCount(chunkValues); //number of value bits for each chunk - if (sign == 1) + + + writer.ResetBitstream(); + if (Count1 > 0) ////write chunk delta offsets + { + for (int i = 0; i < numChunks; i++) + { + writer.WriteBits(chunkOffsets[i], Count1); + } + } + if (Count2 > 0) ////write chunk start values + { + for (int i = 0; i < numChunks; i++) + { + writer.WriteBits(chunkValues[i], Count2); + } + } + for (int i = 0; i < numChunks; i++) ////write chunk frame deltas + { + var cdeltas = chunkDeltas[i]; + for (int j = 1; j < chunkSize; j++) + { + var delta = cdeltas[j]; + var deltaa = (uint)Math.Abs(delta); + writer.WriteBits(deltaa, Count3); + writer.WriteBits(1, 1);//"stop" bit + if (delta < 0) { - delta = -delta; + writer.WriteBits(1, 1);//sign bit } } - - inc += delta; - value += inc; } - return (value * Quantum) + Offset; + + Counts = Count1 & 0xFF; + Counts += (Count2 & 0xFF) << 8; + Counts += (Count3 & 0xFF) << 16; + NumInts = 4 + writer.Bitstream.Count; + + writer.Write(NumInts); + writer.Write(Counts); + writer.Write(Quantum); + writer.Write(Offset); + writer.WriteBitstream(); } + public override float EvaluateFloat(int frame) { if (Values?.Length > 0) return Values[frame % Values.Length]; return Offset; } + + + private uint GetQuanta(float v) + { + var q = (v - Offset) / Quantum; + return (uint)Math.Round(Math.Max(q, 0));//any better way? + } + + + public override void WriteXml(StringBuilder sb, int indent) + { + Type = AnimChannelType.QuantizeFloat;//just export this as a quantize float to avoid import issues.. + base.WriteXml(sb, indent); + Type = AnimChannelType.LinearFloat; + float minVal = float.MaxValue; + for (int i = 0; i < Values.Length; i++) + { + minVal = Math.Min(minVal, Values[i]); + } + if (minVal != Offset) + { + Offset = minVal; + } + + + YcdXml.ValueTag(sb, indent, "Quantum", FloatUtil.ToString(Quantum)); + YcdXml.ValueTag(sb, indent, "Offset", FloatUtil.ToString(Offset)); + YcdXml.WriteRawArray(sb, Values, indent, "Values", "", FloatUtil.ToString, 10);// (Values?.Length ?? 0) + 1); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Quantum = Xml.GetChildFloatAttribute(node, "Quantum", "value"); + Offset = Xml.GetChildFloatAttribute(node, "Offset", "value"); + Values = Xml.GetChildRawFloatArray(node, "Values"); + } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelType7 : AnimChannel + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelRawFloat : AnimChannel { - private Sequence blockStream; + public float[] Values { get; set; } + + public AnimChannelRawFloat() + { + Type = AnimChannelType.RawFloat; + } + + public override void Read(AnimChannelDataReader reader) + { + Values = new float[reader.NumFrames]; + } + public override void Write(AnimChannelDataWriter writer) + { + //nothing to do here + } + + public override void ReadFrame(AnimChannelDataReader reader) + { + uint bits = reader.ReadFrameBits(32); + float v = MetaTypes.ConvertData(MetaTypes.ConvertToBytes(bits)); + Values[reader.Frame] = v; + } + public override void WriteFrame(AnimChannelDataWriter writer) + { + float v = Values[writer.Frame]; + var b = BitConverter.GetBytes(v); + uint bits = b[0] + (b[1]*256u) + (b[2]*65536u) + (b[3]*16777216u); + writer.WriteFrameBits(bits, 32); + } + + public override float EvaluateFloat(int frame) + { + if (Values?.Length > 0) return Values[frame % Values.Length]; + return base.EvaluateFloat(frame); + } + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.WriteRawArray(sb, Values, indent, "Values", "", FloatUtil.ToString, 10);// (Values?.Length ?? 0) + 1); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Values = Xml.GetChildRawFloatArray(node, "Values"); + } + } + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelCachedQuaternion : AnimChannel + { + private AnimChannelDataReader blockStream; private float[] valueCache; @@ -685,22 +1418,392 @@ namespace CodeWalker.GameFiles public int QuatIndex { get; internal set; } + public AnimChannelCachedQuaternion(AnimChannelType type) + { + Type = type; + } + + public override void Read(AnimChannelDataReader reader) + { + this.blockStream = reader; + } + public override float EvaluateFloat(int frame) { if (Values?.Length > 0) return Values[frame % Values.Length]; return 0.0f; } - public override void Read(Sequence blockStream, ref int channelOffset) + public override void WriteXml(StringBuilder sb, int indent) { - this.blockStream = blockStream; + base.WriteXml(sb, indent); + YcdXml.ValueTag(sb, indent, "QuatIndex", QuatIndex.ToString()); + //data is already written in other channels... + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + QuatIndex = Xml.GetChildIntAttribute(node, "QuatIndex", "value"); + //data was already read in other channels... } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimSequence + public class AnimChannelDataReader + { + public byte[] Data { get; set; } + public ushort NumFrames { get; set; } + public ushort NumChunks { get; set; } + public byte ChunkSize { get; set; } //stride of channel frame items (in frames) + public int Position { get; set; } //current byte that the main data reader is on + public int Frame { get; set; } //current frame that the reader is on + public uint FrameOffset { get; set; } //offset to frame data items / bytes + public ushort FrameLength { get; set; } //stride of frame data item + public int ChannelListOffset { get; set; }//offset to channel counts + public int ChannelDataOffset { get; set; }//offset to channel data/info ushorts + public int ChannelFrameOffset { get; set; }//offset to channel current frame data (in bits!) + public AnimSequence[] Sequences { get; set; }//used by AnimChannelCachedQuaternion when accessing values (when evaluating) + public int BitPosition { get; set; } //for use with ReadBits() + + public AnimChannelDataReader(byte[] data, ushort numFrames, byte chunkSize, uint frameOffset, ushort frameLength) + { + Data = data; + NumFrames = numFrames; + NumChunks = (ushort)((chunkSize + numFrames) / chunkSize); + ChunkSize = chunkSize; + Position = 0; + Frame = 0; + FrameOffset = frameOffset; + FrameLength = frameLength; + ChannelListOffset = (int)FrameOffset + (FrameLength * NumFrames); + if (ChannelListOffset > Data?.Length) ChannelListOffset = 0;//any reason for this really? + ChannelDataOffset = ChannelListOffset + (9 * 2); + ChannelFrameOffset = 0; + } + + public int ReadInt32() + { + int i = BitConverter.ToInt32(Data, Position); + Position += 4; + return i; + } + public float ReadSingle() + { + float f = BitConverter.ToSingle(Data, Position); + Position += 4; + return f; + } + public Vector3 ReadVector3() + { + var v = new Vector3(); + v.X = BitConverter.ToSingle(Data, Position); + v.Y = BitConverter.ToSingle(Data, Position + 4); + v.Z = BitConverter.ToSingle(Data, Position + 8); + Position += 12; + return v; + } + + public uint GetBits(int startBit, int length) + { + //dexyfex version that won't read too many bytes - probably won't perform as well + if (startBit < 0) + { return 0; } //something must have went wrong reading other data... happening in fos_ep_1_p6-35.ycd + int startByte = startBit / 8; + int bitOffset = startBit % 8; + uint result = 0; + int shift = -bitOffset; + int curByte = startByte; + int bitsRemaining = length; + while (bitsRemaining > 0) + { + var b = (curByte < Data.Length) ? (uint)Data[curByte++] : 0; + var sb = (shift < 0) ? (b >> -shift) : (b << shift); + var bm = ((1u << Math.Min(bitsRemaining, 8)) - 1u) << (Math.Max(shift, 0)); + var mb = (sb & bm); + result += mb; + bitsRemaining -= (8 + Math.Min(shift, 0)); + shift += 8; + } + return result; + + + + ////original calcium version - has issues near the end of the data from trying to read too many bytes.// + //var mask = MaskTable[length]; + //var lowByte = BitConverter.ToUInt32(Data, (startBit / 32) * 4); + //var highByte = BitConverter.ToUInt32(Data, ((startBit / 32) + 1) * 4); + //var pair = ((ulong)highByte << 32) | lowByte; + //var res = (uint)((pair >> (startBit % 32)) & mask); + + //if (result != res)//dexyfex sanity check + //{ } + //return res; + //private static uint[] MaskTable = new uint[] + //{ + // 0, 1, 3, 7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, + // 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF, 0x1FFFF, + // 0x3FFFF, 0x7FFFF, 0xFFFFF, 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, + // 0xFFFFFF, 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF, + // 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF + //}; + } + public uint ReadBits(int length) + { + uint bits = GetBits(BitPosition, length); + BitPosition += length; + return bits; + } + + + public ushort ReadChannelCount() + { + ushort channelCount = BitConverter.ToUInt16(Data, ChannelListOffset); + ChannelListOffset += 2; + return channelCount; + } + public ushort ReadChannelDataBits() + { + ushort channelDataBit = BitConverter.ToUInt16(Data, ChannelDataOffset); + ChannelDataOffset += 2; + return channelDataBit; + } + + public void AlignChannelDataOffset(int channelCount) + { + int remainder = channelCount % 4; + if (remainder > 0) + { + int addamt = (4 - remainder) * 2; + ChannelDataOffset += addamt; + } + } + + + public void BeginFrame(int f) + { + Frame = f; + ChannelFrameOffset = (int)((FrameOffset + (FrameLength * f)) * 8); + } + public uint ReadFrameBits(int n) + { + uint b = GetBits(ChannelFrameOffset, n); + ChannelFrameOffset += n; + return b; + } + + } + public class AnimChannelDataWriter + { + public int ChannelListOffset { get; set; }//offset to channel counts + public int ChannelItemOffset { get; set; }//offset to channel data/info ushorts + public int ChannelFrameOffset { get; set; }//offset to channel current frame data (in bits!) + public int Position { get; set; } //current byte that the main data reader is on + public int Frame { get; set; } //current frame that the reader is on + public ushort NumFrames { get; set; } + public ushort NumChunks { get; set; } + public byte ChunkSize { get; set; } //stride of channel frame items - starts at 0 and will be set to 64 if need be + public ushort FrameLength { get; set; } = 0; //stride of frame data item, calculated when ending frames + + MemoryStream ChannelListStream = new MemoryStream(); + MemoryStream ChannelItemStream = new MemoryStream(); + MemoryStream MainStream = new MemoryStream(); + BinaryWriter ChannelListWriter = null; + BinaryWriter ChannelItemWriter = null; + BinaryWriter MainWriter = null; + public List ChannelFrameStream { get; private set; } = new List(); //frame bits stream. + public List ChannelFrames { get; private set; } = new List();//bitstreams for each frame + + public List Bitstream { get; private set; } = new List(); //temporary bitstream, used from WriteBits() + public int BitstreamPos { get; set; } = 0; + + public AnimChannelDataWriter(ushort numFrames) + { + Position = 0; + Frame = 0; + NumFrames = numFrames; + NumChunks = (ushort)((64 + numFrames) / 64);//default value, if chunks used, chunkSize is always 64! + ChunkSize = 0; //default 0 value means chunks not used + ChannelListWriter = new BinaryWriter(ChannelListStream); + ChannelItemWriter = new BinaryWriter(ChannelItemStream); + MainWriter = new BinaryWriter(MainStream); + } + + public void WriteChannelListData(ushort c) + { + ChannelListWriter.Write(c); + ChannelListOffset += 2; + } + public void WriteChannelItemData(ushort c) + { + ChannelItemWriter.Write(c); + ChannelItemOffset += 2; + } + public void AlignChannelItemData(int channelCount) + { + int remainder = channelCount % 4; + if (remainder > 0) + { + int addamt = (4 - remainder); + for (int i = 0; i < addamt; i++) + { + WriteChannelItemData(0); + } + } + } + + + public void Write(int i) + { + MainWriter.Write(i); + Position += 4; + } + public void Write(float f) + { + MainWriter.Write(f); + Position += 4; + } + public void Write(Vector3 v) + { + MainWriter.Write(v.X); + MainWriter.Write(v.Y); + MainWriter.Write(v.Z); + Position += 12; + } + + + public void BeginFrame(int f) + { + Frame = f; + ChannelFrameStream.Clear(); + ChannelFrameOffset = 0; + } + public void WriteFrameBits(uint bits, int n) + { + WriteToBitstream(ChannelFrameStream, ChannelFrameOffset, bits, n); + ChannelFrameOffset += n; + } + public void EndFrame() + { + FrameLength = Math.Max(FrameLength, (ushort)(ChannelFrameStream.Count * 4)); + ChannelFrames.Add(ChannelFrameStream.ToArray()); + ChannelFrameStream.Clear(); + } + + public int BitCount(uint bits)//could be static, but not for convenience + { + int bc = 0; + for (int i = 0; i < 32; i++) + { + uint mask = 1u << i; + if ((bits & mask) > 0) bc = (i + 1); + } + return bc; + } + public int BitCount(uint[] values) + { + uint maxValue = 0; + for (int i = 0; i < values?.Length; i++) + { + maxValue = Math.Max(maxValue, values[i]); + } + return BitCount(maxValue); + } + + + public void ResetBitstream() + { + Bitstream.Clear(); + BitstreamPos = 0; + } + public void WriteBits(uint bits, int n)//write n bits to the bitstream. + { + WriteToBitstream(Bitstream, BitstreamPos, bits, n); + BitstreamPos += n; + } + public void WriteBitstream()//write the contents of the bitstream (as uints) to the main writer + { + for (int i = 0; i < Bitstream.Count; i++) + { + MainWriter.Write(Bitstream[i]); + } + Position += (Bitstream.Count * 4); + } + + + private void WriteToBitstream(List stream, int offset, uint bits, int n) + { + if (stream == null) return; + + uint mask = (uint)((1L << n) - 1); + uint masked = bits & mask; + if (bits != masked) + { } + + int soffset = offset % 32; + int sindex = offset / 32; + while (sindex >= stream.Count) stream.Add(0); //pad beginning of the stream + uint sval = stream[sindex]; + uint sbits = bits << soffset; + stream[sindex] = sval + sbits; + + int endbit = (soffset + n) - 32; + if (endbit > 0) + { + int eindex = sindex + 1; + while (eindex >= stream.Count) stream.Add(0);//pad end of stream + int eoffset = 32 - soffset; + uint eval = stream[eindex]; + uint ebits = bits >> eoffset; + stream[eindex] = eval + ebits; + } + + } + + + public byte[] GetStreamData(MemoryStream ms) + { + var length = (int)ms.Length; + var data = new byte[length]; + ms.Flush(); + ms.Position = 0; + ms.Read(data, 0, length); + return data; + } + public byte[] GetChannelListDataBytes() + { + return GetStreamData(ChannelListStream); + } + public byte[] GetChannelItemDataBytes() + { + return GetStreamData(ChannelItemStream); + } + public byte[] GetMainDataBytes() + { + return GetStreamData(MainStream); + } + public byte[] GetFrameDataBytes() + { + var ms = new MemoryStream(); + var bw = new BinaryWriter(ms); + var frameUintCount = FrameLength / 4; + for (int i = 0; i < ChannelFrames.Count; i++) + { + var frameData = ChannelFrames[i]; + for (int f = 0; f < frameUintCount; f++) + { + bw.Write((f < frameData.Length) ? frameData[f] : 0); + } + } + return GetStreamData(ms); + } + + } + + [TypeConverter(typeof(ExpandableObjectConverter))] public class AnimSequence : IMetaXmlItem { public AnimChannel[] Channels { get; set; } public bool IsType7Quat { get; internal set; } + public AnimationBoneId BoneId { get; set; }//for convenience + public Quaternion EvaluateQuaternion(int frame) { if (!IsType7Quat) @@ -713,7 +1816,7 @@ namespace CodeWalker.GameFiles ); } - var t7 = (AnimChannelType7)Channels[3]; + var t7 = (AnimChannelCachedQuaternion)Channels[3]; var x = Channels[0].EvaluateFloat(frame); var y = Channels[1].EvaluateFloat(frame); @@ -746,7 +1849,7 @@ namespace CodeWalker.GameFiles if (c >= 4) break; var channel = Channels[i]; var sv3c = channel as AnimChannelStaticVector3; - var ssqc = channel as AnimChannelStaticSmallestThreeQuaternion; + var ssqc = channel as AnimChannelStaticQuaternion; if (sv3c != null) { for (int n = 0; n < 3; n++) @@ -775,25 +1878,56 @@ namespace CodeWalker.GameFiles } + public void WriteXml(StringBuilder sb, int indent) + { + //YcdXml.ValueTag(sb, indent, "BoneId", BoneId.BoneId.ToString()); //just for convenience really..... + YcdXml.WriteItemArray(sb, Channels, indent, "Channels"); + } + public void ReadXml(XmlNode node) + { + //AnimationBoneId b = new AnimationBoneId(); + //b.BoneId = (ushort)Xml.GetChildUIntAttribute(node, "BoneId", "value"); + //BoneId = b; + + //Channels = XmlMeta.ReadItemArrayNullable(node, "Channels"); + var chansNode = node.SelectSingleNode("Channels"); + if (chansNode != null) + { + var inodes = chansNode.SelectNodes("Item"); + if (inodes?.Count > 0) + { + var clist = new List(); + foreach (XmlNode inode in inodes) + { + var type = Xml.GetEnumValue(Xml.GetChildStringAttribute(inode, "Type", "value")); + var c = AnimChannel.ConstructChannel(type); + c.ReadXml(inode); + clist.Add(c); + } + Channels = clist.ToArray(); + } + } + + } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class Sequence : ResourceSystemBlock + [TypeConverter(typeof(ExpandableObjectConverter))] public class Sequence : ResourceSystemBlock, IMetaXmlItem { public override long BlockLength { - get { return 32 + Data.Length; } + get { return 32 + (Data?.Length??0); } } // structure data public MetaHash Unknown_00h { get; set; } //identifier / name? public uint DataLength { get; set; } public uint Unused_08h { get; set; } // 0x00000000 - public uint FrameOffset { get; set; } //offset to data items / bytes used by "Part0"? - public uint UnkLength { get; set; } //total block length? usually == BlockLength + public uint FrameOffset { get; set; } //offset to frame data items / bytes + public uint TotalLength { get; set; } //total block length, usually == BlockLength public ushort Unused_14h { get; set; } //0x0000 - public ushort NumFrames { get; set; } // count of data items - public ushort FrameLength { get; set; } //stride of data item - public ushort Unknown_1Ah { get; set; } //? - public ushort Unknown_1Ch { get; set; } //? + public ushort NumFrames { get; set; } // count of frame data items + public ushort FrameLength { get; set; } //stride of frame data item + public ushort IndirectQuantizeFloatNumInts { get; set; } //total number of ints that the indirect quantize float channels take (not the frames data though!) + public ushort QuantizeFloatValueBits { get; set; } //total number of quantize float value bits per frame? public byte ChunkSize { get; set; } //64|255 0x40|0xFF public byte Unknown_1Fh_Type { get; set; } //0|17|20|21|49|52|53 0x11|0x14|0x15|0x31|0x34|0x35 public byte[] Data { get; set; } @@ -822,191 +1956,23 @@ namespace CodeWalker.GameFiles public override void Read(ResourceDataReader reader, params object[] parameters) { // read structure data - this.Unknown_00h = reader.ReadUInt32();//2965995365 2837183178 - this.DataLength = reader.ReadUInt32(); //282 142 1206 358 - this.Unused_08h = reader.ReadUInt32();//0 0 0 0 - this.FrameOffset = reader.ReadUInt32();//224 (E0) 32 (20) 536 (218) 300 - this.UnkLength = reader.ReadUInt32();//314 174 1238 390 (=Length) - this.Unused_14h = reader.ReadUInt16();//0 0 0 0 - this.NumFrames = reader.ReadUInt16();//221 (DD) 17 (11) 151 (97) 201 - this.FrameLength = reader.ReadUInt16();//0 4 4 0 - this.Unknown_1Ah = reader.ReadUInt16();//0 0 106 0 - this.Unknown_1Ch = reader.ReadUInt16();//0 17 0 0 bone? - this.ChunkSize = reader.ReadByte(); //64 255 255 64 - this.Unknown_1Fh_Type = reader.ReadByte(); //0 0 0 0 - + this.Unknown_00h = reader.ReadUInt32(); //2965995365 2837183178 + this.DataLength = reader.ReadUInt32(); //282 142 1206 358 + this.Unused_08h = reader.ReadUInt32(); //0 0 0 0 + this.FrameOffset = reader.ReadUInt32(); //224 (E0) 32 (20) 536 (218) 300 + this.TotalLength = reader.ReadUInt32(); //314 174 1238 390 (=Length) + this.Unused_14h = reader.ReadUInt16(); //0 0 0 0 + this.NumFrames = reader.ReadUInt16(); //221 (DD) 17 (11) 151 (97) 201 + this.FrameLength = reader.ReadUInt16(); //0 4 4 0 + this.IndirectQuantizeFloatNumInts = reader.ReadUInt16();//0 0 106 0 + this.QuantizeFloatValueBits = reader.ReadUInt16(); //0 17 0 0 + this.ChunkSize = reader.ReadByte(); //64 255 255 64 + this.Unknown_1Fh_Type = reader.ReadByte(); //0 0 0 0 this.Data = reader.ReadBytes((int)DataLength); + ParseData(); - int Part2Offset = 0;//replacement calculation from old dexyfex parsing code - int offset = (int)FrameOffset + (FrameLength * NumFrames); - int p2cnt = ((int)DataLength - offset) / 2; - if (p2cnt > 0) - { - Part2Offset = offset; - } - - - - - - int channelBitOffset = 0; - int channelFrameOffset = unchecked((int)(FrameOffset * 8)); - int channelListOffset = Part2Offset; - int channelDataOffset = Part2Offset + (9 * 2); - - var animChannelList = new List(); - - var channelLists = new AnimChannel[9][]; - - for (int i = 0; i < 9; i++) - { - int channelCount = BitConverter.ToUInt16(Data, channelListOffset); - - /*if (channelCount > 4) - { - Debug.Assert(false, "More than 4 channels per type are currently unsupported!"); - }*/ - - var channels = new AnimChannel[channelCount]; - - for (int c = 0; c < channelCount; c++) - { - AnimChannel channel = null; - - switch (i) - { - case 0: - channel = new AnimChannelStaticSmallestThreeQuaternion(); - break; - case 1: - channel = new AnimChannelStaticVector3(); - break; - case 2: - channel = new AnimChannelStaticFloat(); - break; - case 3: - // crAnimChannelRawFloat - break; - case 4: - channel = new AnimChannelQuantizeFloat(); - break; - case 5: - channel = new AnimChannelIndirectQuantizeFloat(); - break; - case 6: - channel = new AnimChannelLinearFloat(); - break; - case 7: - // normalized W from quaternion (evaluate first three channels, calculate W) - channel = new AnimChannelType7(); - break; - case 8: - // unknown extra - // kind of the same as above but different at runtime? - channel = new AnimChannelType7(); - break; - default: - break; - } - - //Debug.Assert(channel != null, "Unsupported channel"); - - if (channel != null) - { - channel.Read(this, ref channelBitOffset); - } - - channels[c] = channel; - } - - for (int c = 0; c < channelCount; c++) - { - var channel = channels[c]; - - if (channel != null) - { - channel.ReadData(this, ref channelBitOffset); - } - - var channelDataBit = BitConverter.ToUInt16(Data, channelDataOffset + (c * 2)); - var sequence = channelDataBit >> 2; - var index = channelDataBit & 3; - - if (channel != null) - { - if (i == 7 || i == 8) - { - if (channel is AnimChannelType7 t7) - { - t7.QuatIndex = index; - } - - index = 3; - } - - channel.Associate(sequence, index); - - animChannelList.Add(new AnimChannelListItem(sequence, index, channel)); - } - } - - if (channelCount > 0) - { - var listSize = ((channelCount + 3) / 4) * 4; - - channelDataOffset += (2 * listSize); - } - - channelListOffset += 2; - - channelLists[i] = channels; - } - - for (int f = 0; f < NumFrames; f++) - { - channelFrameOffset = unchecked((int)((FrameOffset + (FrameLength * f)) * 8)); - - for (int i = 0; i < channelLists.Length; i++) - { - var channels = channelLists[i]; - - for (int c = 0; c < channels.Length; c++) - { - var channel = channels[c]; - - if (channel != null) - { - channel.ReadFrame(this, f, ref channelFrameOffset); - } - } - } - } - - Sequences = new AnimSequence[animChannelList.Max(a => a.Sequence) + 1]; - - for (int i = 0; i < Sequences.Length; i++) - { - Sequences[i] = new AnimSequence(); - - var thisSeq = animChannelList.Where(a => a.Sequence == i); - if (thisSeq.Count() == 0) - { continue; } - - - Sequences[i].Channels = new AnimChannel[thisSeq.Max(a => a.Index) + 1]; - - for (int j = 0; j < Sequences[i].Channels.Length; j++) - { - Sequences[i].Channels[j] = thisSeq.First(a => a.Index == j).Channel; - - if (Sequences[i].Channels[j] is AnimChannelType7) - { - Sequences[i].IsType7Quat = true; - } - } - } } public override void Write(ResourceDataWriter writer, params object[] parameters) @@ -1016,11 +1982,14 @@ namespace CodeWalker.GameFiles writer.Write(this.DataLength); writer.Write(this.Unused_08h); writer.Write(this.FrameOffset); - writer.Write(this.UnkLength); + writer.Write(this.TotalLength); writer.Write(this.Unused_14h); + writer.Write(this.NumFrames); writer.Write(this.FrameLength); - writer.Write(this.Unknown_1Ch); + writer.Write(this.IndirectQuantizeFloatNumInts); + writer.Write(this.QuantizeFloatValueBits); writer.Write(this.ChunkSize); + writer.Write(this.Unknown_1Fh_Type); writer.Write(this.Data); } @@ -1029,30 +1998,275 @@ namespace CodeWalker.GameFiles return Unknown_00h.ToString() + ": " + DataLength.ToString(); } - public uint GetBit(int startBit, int length) + + + public void ParseData() { - var mask = MaskTable[length]; + var reader = new AnimChannelDataReader(Data, NumFrames, ChunkSize, FrameOffset, FrameLength); + var channelList = new List(); + var channelLists = new AnimChannel[9][]; + for (int i = 0; i < 9; i++)//iterate through anim channel types + { + var ctype = (AnimChannelType)i; + int channelCount = reader.ReadChannelCount(); + var channels = new AnimChannel[channelCount]; + for (int c = 0; c < channelCount; c++) //construct and read channels + { + var channel = AnimChannel.ConstructChannel(ctype); + channel?.Read(reader); + channels[c] = channel; + var channelDataBit = reader.ReadChannelDataBits(); + if (channel != null)//read channel sequences and indexes + { + var sequence = channelDataBit >> 2; + var index = channelDataBit & 3; + if (channel is AnimChannelCachedQuaternion t7) + { + t7.QuatIndex = index; + index = 3; + } + channel.Associate(sequence, index); + channelList.Add(new AnimChannelListItem(sequence, index, channel)); + } + } + reader.AlignChannelDataOffset(channelCount); + channelLists[i] = channels; + } - var lowByte = BitConverter.ToUInt32(Data, (startBit / 32) * 4); - var highByte = BitConverter.ToUInt32(Data, ((startBit / 32) + 1) * 4); + for (int f = 0; f < NumFrames; f++)//read channel frame data + { + reader.BeginFrame(f); + for (int i = 0; i < 9; i++) + { + var channels = channelLists[i]; + for (int c = 0; c < channels.Length; c++) + { + var channel = channels[c]; + channel?.ReadFrame(reader); + } + } + } - var pair = ((ulong)highByte << 32) | lowByte; + Sequences = new AnimSequence[channelList.Max(a => a.Sequence) + 1]; + for (int i = 0; i < Sequences.Length; i++) //assign channels to sequences according to read indices + { + Sequences[i] = new AnimSequence(); - return (uint)((pair >> (startBit % 32)) & mask); + var thisSeq = channelList.Where(a => a.Sequence == i); + if (thisSeq.Count() == 0) + { continue; } + + Sequences[i].Channels = new AnimChannel[thisSeq.Max(a => a.Index) + 1]; + + for (int j = 0; j < Sequences[i].Channels.Length; j++) + { + Sequences[i].Channels[j] = thisSeq.FirstOrDefault(a => a.Index == j)?.Channel; + + if (Sequences[i].Channels[j] is AnimChannelCachedQuaternion) + { + Sequences[i].IsType7Quat = true; + } + } + } + + reader.Sequences = Sequences; + + + + var qfvb = GetQuantizeFloatValueBits(); + if (qfvb != QuantizeFloatValueBits) + { } + + var ifni = GetIndirectQuantizeFloatNumInts(); + if (ifni != IndirectQuantizeFloatNumInts) + { } + + switch (Unknown_1Fh_Type) + { + case 0: + break; + case 17: + break; + case 20: + break; + case 21: + break; + case 49: + break; + case 52: + break; + case 53: + break; + default: + break; + } } - private static uint[] MaskTable = new uint[] + + public void BuildData() { - 0, 1, 3, 7, 0xF, 0x1F, 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, - 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF, 0x1FFFF, - 0x3FFFF, 0x7FFFF, 0xFFFFF, 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, - 0xFFFFFF, 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF, - 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF - }; + // convert parsed sequences into Data byte array............ + + if (Sequences == null) return;//this shouldn't happen... + + var writer = new AnimChannelDataWriter(NumFrames); + + var channelLists = new List[9]; + + for (int s = 0; s < Sequences.Length; s++) + { + var seq = Sequences[s]; + if (seq?.Channels == null) continue; + for (int c = 0; c < seq.Channels.Length; c++) + { + var chan = seq.Channels[c]; + if (chan == null) continue; + int typeid = (int)chan.Type; + if ((typeid < 0) || (typeid >= 9)) + { continue; } + var chanList = channelLists[typeid]; + if (chanList == null) + { + chanList = new List(); + channelLists[typeid] = chanList; + } + if (chan is AnimChannelCachedQuaternion accq) + { + chan.Index = accq.QuatIndex;//seems to have QuatIndex stored in there (for channelDataBit below) + } + chanList.Add(chan); + } + } + + for (int i = 0; i < 9; i++) + { + var channelList = channelLists[i]; + var channelCount = (ushort)(channelList?.Count ?? 0); + writer.WriteChannelListData(channelCount); + for (int c = 0; c < channelCount; c++) + { + var channel = channelList[c]; + var channelDataBit = (ushort)((channel?.Index ?? 0) + ((channel?.Sequence ?? 0) << 2)); + writer.WriteChannelItemData(channelDataBit); + channel?.Write(writer); + } + writer.AlignChannelItemData(channelCount); + } + + for (int f = 0; f < NumFrames; f++)//write channel frame data + { + writer.BeginFrame(f); + for (int i = 0; i < 9; i++) + { + var channelList = channelLists[i]; + var channelCount = (ushort)(channelList?.Count ?? 0); + for (int c = 0; c < channelCount; c++) + { + var channel = channelList[c]; + channel?.WriteFrame(writer); + } + } + writer.EndFrame(); + } + + + var mainData = writer.GetMainDataBytes(); + var frameData = writer.GetFrameDataBytes(); + var channelListData = writer.GetChannelListDataBytes(); + var channelItemData = writer.GetChannelItemDataBytes(); + + var dataLen = mainData.Length + frameData.Length + channelListData.Length + channelItemData.Length; + var data = new byte[dataLen]; + var curpos = 0; + Buffer.BlockCopy(mainData, 0, data, 0, mainData.Length); curpos += mainData.Length; + Buffer.BlockCopy(frameData, 0, data, curpos, frameData.Length); curpos += frameData.Length; + Buffer.BlockCopy(channelListData, 0, data, curpos, channelListData.Length); curpos += channelListData.Length; + Buffer.BlockCopy(channelItemData, 0, data, curpos, channelItemData.Length); + + Data = data; + DataLength = (uint)data.Length; + FrameOffset = (uint)mainData.Length; + TotalLength = (uint)BlockLength; + FrameLength = writer.FrameLength; + ChunkSize = (writer.ChunkSize > 0) ? writer.ChunkSize : (byte)255; + QuantizeFloatValueBits = GetQuantizeFloatValueBits(); + IndirectQuantizeFloatNumInts = GetIndirectQuantizeFloatNumInts(); + } + + + public void AssociateSequenceChannels()//assigns Sequence and Index to all channels + { + if (Sequences == null) return;//this shouldn't happen... + for (int s = 0; s < Sequences.Length; s++) + { + var seq = Sequences[s]; + if (seq?.Channels == null) continue; + for (int c = 0; c < seq.Channels.Length; c++) + { + var chan = seq.Channels[c]; + chan?.Associate(s, c); + } + } + } + + + public ushort GetQuantizeFloatValueBits() + { + int b = 0; + foreach (var seq in Sequences) + { + foreach (var chan in seq.Channels) + { + if (chan.Type == AnimChannelType.QuantizeFloat) + { + var acqf = chan as AnimChannelQuantizeFloat; + b += acqf.ValueBits; + } + } + } + return (ushort)b; + } + public ushort GetIndirectQuantizeFloatNumInts() + { + int b = 0; + foreach (var seq in Sequences) + { + foreach (var chan in seq.Channels) + { + if (chan.Type == AnimChannelType.IndirectQuantizeFloat) + { + var acif = chan as AnimChannelIndirectQuantizeFloat; + b += acif.NumInts+5; + } + } + } + return (ushort)b; + } + + + + public void WriteXml(StringBuilder sb, int indent) + { + YcdXml.StringTag(sb, indent, "Hash", YcdXml.HashString(Unknown_00h)); + YcdXml.ValueTag(sb, indent, "FrameCount", NumFrames.ToString()); + YcdXml.ValueTag(sb, indent, "Unknown1F", Unknown_1Fh_Type.ToString()); + YcdXml.WriteItemArray(sb, Sequences, indent, "SequenceData"); + } + public void ReadXml(XmlNode node) + { + Unknown_00h = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Hash")); + NumFrames = (ushort)Xml.GetChildUIntAttribute(node, "FrameCount", "value"); + Unknown_1Fh_Type = (byte)Xml.GetChildUIntAttribute(node, "Unknown1F", "value"); + Sequences = XmlMeta.ReadItemArray(node, "SequenceData"); + + AssociateSequenceChannels(); + BuildData(); + } + } - [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipMapEntry : ResourceSystemBlock + [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipMapEntry : ResourceSystemBlock, IMetaXmlItem { public override long BlockLength { @@ -1071,6 +2285,11 @@ namespace CodeWalker.GameFiles public ClipBase Clip { get; set; } public ClipMapEntry Next { get; set; } + public string ClipName { get; set; } //used when reading XML. + + public bool EnableRootMotion { get; set; } = false; //used by CW to toggle whether or not to include root motion when playing animations + + public override void Read(ResourceDataReader reader, params object[] parameters) { // read structure data @@ -1118,8 +2337,35 @@ namespace CodeWalker.GameFiles return Clip?.Name ?? Hash.ToString(); } + + + public void WriteXml(StringBuilder sb, int indent) + { + YcdXml.StringTag(sb, indent, "Hash", YcdXml.HashString(Hash)); + YcdXml.StringTag(sb, indent, "ClipName", MetaXml.XmlEscape(Clip?.Name)); + + if (Next != null) + { + YcdXml.OpenTag(sb, indent, "Next"); + Next.WriteXml(sb, indent + 1); + YcdXml.CloseTag(sb, indent, "Next"); + } + } + public void ReadXml(XmlNode node) + { + Hash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Hash")); + ClipName = Xml.GetChildInnerText(node, "ClipName"); + + var nextnode = node.SelectSingleNode("Next"); + if (nextnode != null) + { + var next = new ClipMapEntry(); + next.ReadXml(nextnode); + Next = next; + } + } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipBase : ResourceSystemBlock, IResourceXXSystemBlock + [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipBase : ResourceSystemBlock, IResourceXXSystemBlock, IMetaXmlItem { public override long BlockLength { @@ -1128,21 +2374,21 @@ namespace CodeWalker.GameFiles // structure data public uint VFT { get; set; } - public uint Unknown_04h { get; set; } // 0x00000001 + public uint Unknown_04h { get; set; } = 1; // 0x00000001 public uint Unknown_08h { get; set; } // 0x00000000 public uint Unknown_0Ch { get; set; } // 0x00000000 - public uint Unknown_10h { get; set; } + public ClipType Type { get; set; } // 1, 2 public uint Unknown_14h { get; set; } // 0x00000000 public ulong NamePointer { get; set; } public ushort NameLength { get; set; } // short, name length public ushort NameCapacity { get; set; } // short, name length +1 public uint Unknown_24h { get; set; } // 0x00000000 - public ulong Unknown_28hPtr { get; set; } // 0x50000000 - public uint Unknown_30h { get; set; } + public ulong Unknown_28hPtr { get; set; } = 0x50000000; // 0x50000000 + public uint Unknown_30h { get; set; } // 0, 1 public uint Unknown_34h { get; set; } // 0x00000000 public ulong TagsPointer { get; set; } public ulong PropertiesPointer { get; set; } - public uint Unknown_48h { get; set; } // 0x00000001 + public uint Unknown_48h { get; set; } = 1; // 0x00000001 public uint Unknown_4Ch { get; set; } // 0x00000000 // reference data @@ -1150,6 +2396,8 @@ namespace CodeWalker.GameFiles public ClipTagList Tags { get; set; } public ClipPropertyMap Properties { get; set; } + private string_r NameBlock = null; + public YcdFile Ycd { get; set; } public string ShortName { get; set; } @@ -1160,7 +2408,7 @@ namespace CodeWalker.GameFiles this.Unknown_04h = reader.ReadUInt32(); this.Unknown_08h = reader.ReadUInt32(); this.Unknown_0Ch = reader.ReadUInt32(); - this.Unknown_10h = reader.ReadUInt32(); + this.Type = (ClipType)reader.ReadUInt32(); this.Unknown_14h = reader.ReadUInt32(); this.NamePointer = reader.ReadUInt64(); this.NameLength = reader.ReadUInt16(); @@ -1183,28 +2431,51 @@ namespace CodeWalker.GameFiles this.PropertiesPointer // offset ); - if ((Unknown_28hPtr != 0) && (Unknown_28hPtr != 0x50000000)) + if (Unknown_28hPtr != 0x50000000) + { } + + switch (VFT)//some examples { + case 1079664808: + case 1079656584: + case 1079607128: + break; + default: + break; + } + switch (Unknown_30h) + { + case 0: + case 1: + break; + default: + break; } + if (Tags?.Tags?.data_items == null) + { } } public override void Write(ResourceDataWriter writer, params object[] parameters) { // update structure data - //this.NamePointer = (ulong)(this.Name != null ? this.Name.Position : 0); + this.NamePointer = (ulong)(this.NameBlock != null ? this.NameBlock.FilePosition : 0); + this.NameLength = (ushort)(Name?.Length ?? 0); + this.NameCapacity = (ushort)((Name != null) ? Name.Length + 1 : 0); this.TagsPointer = (ulong)(this.Tags != null ? this.Tags.FilePosition : 0); this.PropertiesPointer = (ulong)(this.Properties != null ? this.Properties.FilePosition : 0); + // write structure data writer.Write(this.VFT); writer.Write(this.Unknown_04h); writer.Write(this.Unknown_08h); writer.Write(this.Unknown_0Ch); - writer.Write(this.Unknown_10h); + writer.Write((uint)this.Type); writer.Write(this.Unknown_14h); writer.Write(this.NamePointer); writer.Write(this.NameLength); + writer.Write(this.NameCapacity); writer.Write(this.Unknown_24h); writer.Write(this.Unknown_28hPtr); writer.Write(this.Unknown_30h); @@ -1218,7 +2489,11 @@ namespace CodeWalker.GameFiles public override IResourceBlock[] GetReferences() { var list = new List(); - //if (Name != null) list.Add(Name); + if (Name != null) + { + NameBlock = (string_r)Name; + list.Add(NameBlock); + } if (Tags != null) list.Add(Tags); if (Properties != null) list.Add(Properties); return list.ToArray(); @@ -1230,10 +2505,16 @@ namespace CodeWalker.GameFiles var type = reader.ReadByte(); reader.Position -= 17; + return ConstructClip((ClipType)type); + } + + + public static ClipBase ConstructClip(ClipType type) + { switch (type) { - case 1: return new ClipAnimation(); - case 2: return new ClipAnimationList(); + case ClipType.Animation: return new ClipAnimation(); + case ClipType.AnimationList: return new ClipAnimationList(); default: return null;// throw new Exception("Unknown type"); } } @@ -1244,6 +2525,41 @@ namespace CodeWalker.GameFiles return Name; } + + public virtual void WriteXml(StringBuilder sb, int indent) + { + YcdXml.StringTag(sb, indent, "Name", MetaXml.XmlEscape(Name)); + YcdXml.ValueTag(sb, indent, "Type", Type.ToString()); + YcdXml.ValueTag(sb, indent, "Unknown30", Unknown_30h.ToString()); + YcdXml.ValueTag(sb, indent, "TagsUnknown10", Tags?.Unknown_10h.ToString() ?? "0"); + YcdXml.WriteItemArray(sb, Tags?.Tags?.data_items, indent, "Tags"); + YcdXml.WriteItemArray(sb, Properties?.Properties?.data_items, indent, "Properties"); + } + public virtual void ReadXml(XmlNode node) + { + Name = Xml.GetChildInnerText(node, "Name"); + Unknown_30h = Xml.GetChildUIntAttribute(node, "Unknown30", "value"); + + var tags = XmlMeta.ReadItemArrayNullable(node, "Tags"); + Tags = new ClipTagList(); + Tags.Unknown_10h = Xml.GetChildUIntAttribute(node, "TagsUnknown10", "value"); + if (tags != null) + { + Tags.Tags = new ResourcePointerArray64(); + Tags.Tags.data_items = tags; + Tags.BuildAllTags(); + Tags.AssignTagOwners(); + } + + var props = XmlMeta.ReadItemArrayNullable(node, "Properties"); + Properties = new ClipPropertyMap(); + if (props != null) + { + Properties.Properties = new ResourcePointerArray64(); + Properties.Properties.data_items = props; + Properties.BuildPropertyMap(); + } + } } [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipAnimation : ClipBase { @@ -1263,6 +2579,12 @@ namespace CodeWalker.GameFiles // reference data public Animation Animation { get; set; } + public MetaHash AnimationHash { get; set; } //used when reading XML. + + public ClipAnimation() + { + Type = ClipType.Animation; + } public override void Read(ResourceDataReader reader, params object[] parameters) { @@ -1310,6 +2632,23 @@ namespace CodeWalker.GameFiles double curpos = scaledTime % duration; return StartTime + (float)curpos; } + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.StringTag(sb, indent, "AnimationHash", YcdXml.HashString(Animation?.AnimationHash ?? 0)); + YcdXml.ValueTag(sb, indent, "StartTime", FloatUtil.ToString(StartTime)); + YcdXml.ValueTag(sb, indent, "EndTime", FloatUtil.ToString(EndTime)); + YcdXml.ValueTag(sb, indent, "Rate", FloatUtil.ToString(Rate)); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + AnimationHash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "AnimationHash")); + StartTime = Xml.GetChildFloatAttribute(node, "StartTime", "value"); + EndTime = Xml.GetChildFloatAttribute(node, "EndTime", "value"); + Rate = Xml.GetChildFloatAttribute(node, "Rate", "value"); + } } [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipAnimationList : ClipBase { @@ -1324,13 +2663,19 @@ namespace CodeWalker.GameFiles public ushort AnimationsCount2 { get; set; } public uint Unknown_5Ch { get; set; } // 0x00000000 public float Duration { get; set; } - public uint Unknown_64h { get; set; } // 0x00000001 + public uint Unknown_64h { get; set; } = 1; // 0x00000001 public uint Unknown_68h { get; set; } // 0x00000000 public uint Unknown_6Ch { get; set; } // 0x00000000 // reference data public ResourceSimpleArray Animations { get; set; } + + public ClipAnimationList() + { + Type = ClipType.AnimationList; + } + public override void Read(ResourceDataReader reader, params object[] parameters) { base.Read(reader, parameters); @@ -1354,8 +2699,8 @@ namespace CodeWalker.GameFiles base.Write(writer, parameters); this.AnimationsPointer = (ulong)(this.Animations != null ? this.Animations.FilePosition : 0); - //this.p4 = (ulong)(this.p4data != null ? this.p4data.Position : 0); - //this.c1 = (ushort)(this.p4data != null ? this.p4data.Count : 0); + this.AnimationsCount1 = (ushort)(this.Animations != null ? this.Animations.Count : 0); + this.AnimationsCount2 = this.AnimationsCount1; writer.Write(this.AnimationsPointer); writer.Write(this.AnimationsCount1); @@ -1383,8 +2728,27 @@ namespace CodeWalker.GameFiles double curpos = scaledTime % duration; return /*StartTime +*/ (float)curpos; } + + + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.ValueTag(sb, indent, "Duration", FloatUtil.ToString(Duration)); + YcdXml.WriteItemArray(sb, Animations?.Data.ToArray(), indent, "Animations"); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Duration = Xml.GetChildFloatAttribute(node, "Duration", "value"); + + Animations = new ResourceSimpleArray(); + Animations.Data = new List(); + var anims = XmlMeta.ReadItemArrayNullable(node, "Animations"); + if (anims != null) Animations.Data.AddRange(anims); + } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipAnimationsEntry : ResourceSystemBlock + [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipAnimationsEntry : ResourceSystemBlock, IMetaXmlItem { public override long BlockLength { @@ -1400,6 +2764,7 @@ namespace CodeWalker.GameFiles // reference data public Animation Animation { get; set; } + public MetaHash AnimationHash { get; set; } //used when reading XML. public override void Read(ResourceDataReader reader, params object[] parameters) { @@ -1444,6 +2809,27 @@ namespace CodeWalker.GameFiles double curpos = scaledTime % duration; return StartTime + (float)curpos; } + + + public void WriteXml(StringBuilder sb, int indent) + { + YcdXml.StringTag(sb, indent, "AnimationHash", YcdXml.HashString(Animation?.AnimationHash ?? 0)); + YcdXml.ValueTag(sb, indent, "StartTime", FloatUtil.ToString(StartTime)); + YcdXml.ValueTag(sb, indent, "EndTime", FloatUtil.ToString(EndTime)); + YcdXml.ValueTag(sb, indent, "Rate", FloatUtil.ToString(Rate)); + } + public void ReadXml(XmlNode node) + { + AnimationHash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "AnimationHash")); + StartTime = Xml.GetChildFloatAttribute(node, "StartTime", "value"); + EndTime = Xml.GetChildFloatAttribute(node, "EndTime", "value"); + Rate = Xml.GetChildFloatAttribute(node, "Rate", "value"); + } + } + public enum ClipType : uint + { + Animation = 1, + AnimationList = 2, } @@ -1456,9 +2842,9 @@ namespace CodeWalker.GameFiles // structure data public ulong PropertyEntriesPointer { get; set; } - public ushort PropertyEntriesCount { get; set; } public ushort PropertyEntriesCapacity { get; set; } - public uint Unknown_0Ch { get; set; } // 0x01000000 + public ushort PropertyEntriesCount { get; set; } + public uint Unknown_0Ch { get; set; } = 0x01000000; // 0x01000000 // reference data public ResourcePointerArray64 Properties { get; set; } @@ -1471,19 +2857,49 @@ namespace CodeWalker.GameFiles { // read structure data this.PropertyEntriesPointer = reader.ReadUInt64(); - this.PropertyEntriesCount = reader.ReadUInt16(); this.PropertyEntriesCapacity = reader.ReadUInt16(); + this.PropertyEntriesCount = reader.ReadUInt16(); this.Unknown_0Ch = reader.ReadUInt32(); // read reference data this.Properties = reader.ReadBlockAt>( this.PropertyEntriesPointer, // offset - this.PropertyEntriesCount + this.PropertyEntriesCapacity ); + BuildPropertyMap(); + } + + public override void Write(ResourceDataWriter writer, params object[] parameters) + { + // update structure data + this.PropertyEntriesPointer = (ulong)(this.Properties != null ? this.Properties.FilePosition : 0); + this.PropertyEntriesCapacity = (ushort)(this.Properties != null ? this.Properties.Count : 0); + this.PropertyEntriesCount = (ushort)(this.AllProperties?.Length ?? 0); + + // write structure data + writer.Write(this.PropertyEntriesPointer); + writer.Write(this.PropertyEntriesCapacity); + writer.Write(this.PropertyEntriesCount); + writer.Write(this.Unknown_0Ch); + } + + public override IResourceBlock[] GetReferences() + { + var list = new List(); + if (Properties != null) list.Add(Properties); + return list.ToArray(); + } + + public override string ToString() + { + return "Count: " + (AllProperties?.Length ?? 0).ToString(); + } - if ((Properties != null) && (Properties.data_items != null)) + public void BuildPropertyMap() + { + if (Properties?.data_items != null) { List pl = new List(); foreach (var pme in Properties.data_items) @@ -1506,32 +2922,8 @@ namespace CodeWalker.GameFiles } - public override void Write(ResourceDataWriter writer, params object[] parameters) - { - // update structure data - this.PropertyEntriesPointer = (ulong)(this.Properties != null ? this.Properties.FilePosition : 0); - //this.c1 = (ushort)(this.p1data != null ? this.p1data.Count : 0); - - // write structure data - writer.Write(this.PropertyEntriesPointer); - writer.Write(this.PropertyEntriesCount); - writer.Write(this.PropertyEntriesCapacity); - writer.Write(this.Unknown_0Ch); - } - - public override IResourceBlock[] GetReferences() - { - var list = new List(); - if (Properties != null) list.Add(Properties); - return list.ToArray(); - } - - public override string ToString() - { - return "Count: " + (AllProperties?.Length ?? 0).ToString(); - } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyMapEntry : ResourceSystemBlock + [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyMapEntry : ResourceSystemBlock, IMetaXmlItem { public override long BlockLength { @@ -1591,8 +2983,43 @@ namespace CodeWalker.GameFiles if (Next != null) list.Add(Next); return list.ToArray(); } + + + public void WriteXml(StringBuilder sb, int indent) + { + YcdXml.StringTag(sb, indent, "NameHash", YcdXml.HashString(PropertyNameHash)); + if (Data != null) + { + YcdXml.OpenTag(sb, indent, "PropertyData"); + Data.WriteXml(sb, indent + 1); + YcdXml.CloseTag(sb, indent, "PropertyData"); + } + if (Next != null) + { + YcdXml.OpenTag(sb, indent, "Next"); + Next.WriteXml(sb, indent + 1); + YcdXml.CloseTag(sb, indent, "Next"); + } + } + public void ReadXml(XmlNode node) + { + PropertyNameHash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "NameHash")); + var datanode = node.SelectSingleNode("PropertyData"); + if (datanode != null) + { + Data = new ClipProperty(); + Data.ReadXml(datanode); + } + var nextnode = node.SelectSingleNode("Next"); + if (nextnode != null) + { + var next = new ClipPropertyMapEntry(); + next.ReadXml(nextnode); + Next = next; + } + } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipProperty : ResourceSystemBlock + [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipProperty : ResourceSystemBlock, IMetaXmlItem { public override long BlockLength { @@ -1601,7 +3028,7 @@ namespace CodeWalker.GameFiles // structure data public uint VFT { get; set; } - public uint Unknown_04h { get; set; } // 0x00000001 + public uint Unknown_04h { get; set; } = 1; // 0x00000001 public uint Unknown_08h { get; set; } // 0x00000000 public uint Unknown_0Ch { get; set; } // 0x00000000 public uint Unknown_10h { get; set; } // 0x00000000 @@ -1645,13 +3072,26 @@ namespace CodeWalker.GameFiles this.AttributesPointer, // offset this.AttributesCount ); + + switch (VFT)//some examples + { + case 1080111464: + case 1080103160: + case 1080119200: + case 1080069168: + case 1080053176: + break; + default: + break; + } } public override void Write(ResourceDataWriter writer, params object[] parameters) { // update structure data this.AttributesPointer = (ulong)(this.Attributes != null ? this.Attributes.FilePosition : 0); - //this.c1 = (ushort)(this.p1data != null ? this.p1data.Count : 0); + this.AttributesCount = (ushort)(this.Attributes != null ? this.Attributes.Count : 0); + this.AttributesCapacity = this.AttributesCount; // write structure data writer.Write(this.VFT); @@ -1692,8 +3132,41 @@ namespace CodeWalker.GameFiles } return NameHash.ToString() + ": " + UnkHash.ToString() + ": " + sb.ToString(); } + + + public virtual void WriteXml(StringBuilder sb, int indent) + { + YcdXml.StringTag(sb, indent, "NameHash", YcdXml.HashString(NameHash)); + YcdXml.StringTag(sb, indent, "UnkHash", YcdXml.HashString(UnkHash)); + YcdXml.WriteItemArray(sb, Attributes?.data_items, indent, "Attributes"); + } + public virtual void ReadXml(XmlNode node) + { + NameHash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "NameHash")); + UnkHash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "UnkHash")); + + var attrsNode = node.SelectSingleNode("Attributes"); + if (attrsNode != null) + { + var inodes = attrsNode.SelectNodes("Item"); + if (inodes?.Count > 0) + { + var alist = new List(); + foreach (XmlNode inode in inodes) + { + var item = new ClipPropertyAttribute(); + item.ReadXml(inode); + var v = ClipPropertyAttribute.ConstructItem(item.Type); + v.ReadXml(inode);//slightly wasteful but meh + alist.Add(v); + } + Attributes = new ResourcePointerArray64(); + Attributes.data_items = alist.ToArray(); + } + } + } } - [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttribute : ResourceSystemBlock, IResourceXXSystemBlock + [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttribute : ResourceSystemBlock, IResourceXXSystemBlock, IMetaXmlItem { public override long BlockLength { @@ -1701,8 +3174,8 @@ namespace CodeWalker.GameFiles } public uint VFT { get; set; } - public uint Unknown_04h { get; set; } // 0x00000001 - public byte Type { get; set; } + public uint Unknown_04h { get; set; } = 1; // 0x00000001 + public ClipPropertyAttributeType Type { get; set; } public byte Unknown_09h { get; set; } // 0x00 public ushort Unknown_Ah { get; set; } // 0x0000 public uint Unknown_Ch { get; set; } // 0x00000000 @@ -1715,7 +3188,7 @@ namespace CodeWalker.GameFiles { this.VFT = reader.ReadUInt32(); this.Unknown_04h = reader.ReadUInt32(); - this.Type = reader.ReadByte(); + this.Type = (ClipPropertyAttributeType)reader.ReadByte(); this.Unknown_09h = reader.ReadByte(); this.Unknown_Ah = reader.ReadUInt16(); this.Unknown_Ch = reader.ReadUInt32(); @@ -1723,13 +3196,31 @@ namespace CodeWalker.GameFiles this.Unknown_14h = reader.ReadUInt32(); this.NameHash = reader.ReadUInt32(); this.Unknown_1Ch = reader.ReadUInt32(); + + switch (VFT)//some examples + { + case 1080119416://type 1 + case 1080119528://type 2 + case 1080119640://type 3 + case 1080119752://type 4 + case 1080119832://type 6 + case 1080120088://type 8 + case 1080120472://type 12 + case 1080069384://type 1 + case 1080069496://type 2 + case 1080127976: + case 1080069800://type 6 + break; + default: + break; + } } public override void Write(ResourceDataWriter writer, params object[] parameters) { writer.Write(this.VFT); writer.Write(this.Unknown_04h); - writer.Write(this.Type); + writer.Write((byte)this.Type); writer.Write(this.Unknown_09h); writer.Write(this.Unknown_Ah); writer.Write(this.Unknown_Ch); @@ -1742,21 +3233,36 @@ namespace CodeWalker.GameFiles public IResourceSystemBlock GetType(ResourceDataReader reader, params object[] parameters) { reader.Position += 8; - var type = reader.ReadByte(); + var type = (ClipPropertyAttributeType)reader.ReadByte(); reader.Position -= 9; + return ConstructItem(type); + } + public static ClipPropertyAttribute ConstructItem(ClipPropertyAttributeType type) + { switch (type) { - case 1: return new ClipPropertyAttributeFloat(); - case 2: return new ClipPropertyAttributeInt(); - case 3: return new ClipPropertyAttributeBool(); - case 4: return new ClipPropertyAttributeString(); - case 6: return new ClipPropertyAttributeVector3(); - case 8: return new ClipPropertyAttributeVector4(); - case 12: return new ClipPropertyAttributeHashString(); + case ClipPropertyAttributeType.Float: return new ClipPropertyAttributeFloat(); + case ClipPropertyAttributeType.Int: return new ClipPropertyAttributeInt(); + case ClipPropertyAttributeType.Bool: return new ClipPropertyAttributeBool(); + case ClipPropertyAttributeType.String: return new ClipPropertyAttributeString(); + case ClipPropertyAttributeType.Vector3: return new ClipPropertyAttributeVector3(); + case ClipPropertyAttributeType.Vector4: return new ClipPropertyAttributeVector4(); + case ClipPropertyAttributeType.HashString: return new ClipPropertyAttributeHashString(); default: return null;// throw new Exception("Unknown type"); } } + + public virtual void WriteXml(StringBuilder sb, int indent) + { + YcdXml.StringTag(sb, indent, "NameHash", YcdXml.HashString(NameHash)); + YcdXml.ValueTag(sb, indent, "Type", Type.ToString()); + } + public virtual void ReadXml(XmlNode node) + { + NameHash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "NameHash")); + Type = Xml.GetEnumValue(Xml.GetChildStringAttribute(node, "Type", "value")); + } } [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttributeFloat : ClipPropertyAttribute { @@ -1796,6 +3302,18 @@ namespace CodeWalker.GameFiles { return "Float:" + FloatUtil.ToString(Value); } + + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.ValueTag(sb, indent, "Value", FloatUtil.ToString(Value)); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Value = Xml.GetChildFloatAttribute(node, "Value", "value"); + } } [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttributeInt : ClipPropertyAttribute { @@ -1835,6 +3353,18 @@ namespace CodeWalker.GameFiles { return "Int:" + Value.ToString(); } + + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.ValueTag(sb, indent, "Value", Value.ToString()); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Value = Xml.GetChildIntAttribute(node, "Value", "value"); + } } [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttributeBool : ClipPropertyAttribute { @@ -1874,6 +3404,18 @@ namespace CodeWalker.GameFiles { return "Uint:" + Value.ToString(); } + + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.ValueTag(sb, indent, "Value", Value.ToString()); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Value = Xml.GetChildUIntAttribute(node, "Value", "value"); + } } [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttributeString : ClipPropertyAttribute { @@ -1883,11 +3425,12 @@ namespace CodeWalker.GameFiles } public ulong ValuePointer { get; set; } - public ushort ValueLength1 { get; set; } - public ushort ValueLength2 { get; set; } + public ushort ValueLength { get; set; } + public ushort ValueCapacity { get; set; } public uint Unknown_02Ch { get; set; } // 0x00000000 public string Value; + private string_r ValueBlock = null; public override void Read(ResourceDataReader reader, params object[] parameters) { @@ -1895,14 +3438,11 @@ namespace CodeWalker.GameFiles // read structure data this.ValuePointer = reader.ReadUInt64(); - this.ValueLength1 = reader.ReadUInt16(); - this.ValueLength2 = reader.ReadUInt16(); + this.ValueLength = reader.ReadUInt16(); + this.ValueCapacity = reader.ReadUInt16(); this.Unknown_02Ch = reader.ReadUInt32(); //// read reference data - //this.Value = reader.ReadBlockAt( - // this.ValuePointer // offset - //); Value = reader.ReadStringAt(ValuePointer); } @@ -1911,21 +3451,25 @@ namespace CodeWalker.GameFiles base.Write(writer, parameters); // update structure data - //this.ValuePointer = (ulong)(this.Value != null ? this.Value.Position : 0); - //this.ValueLength1 = (ushort)(this.Value != null ? this.Value.Value.Length : 0); - //this.ValueLength2 = (ushort)(this.Value != null ? this.Value.Value.Length + 1 : 0); + this.ValuePointer = (ulong)(this.ValueBlock != null ? this.ValueBlock.FilePosition : 0); + this.ValueLength = (ushort)(Value?.Length ?? 0); + this.ValueCapacity = (ushort)((Value != null) ? Value.Length + 1 : 0); // write structure data writer.Write(this.ValuePointer); - writer.Write(this.ValueLength1); - writer.Write(this.ValueLength2); + writer.Write(this.ValueLength); + writer.Write(this.ValueCapacity); writer.Write(this.Unknown_02Ch); } public override IResourceBlock[] GetReferences() { var list = new List(base.GetReferences()); - //if (p1data != null) list.Add(p1data); + if (Value != null) + { + ValueBlock = (string_r)Value; + list.Add(ValueBlock); + } return list.ToArray(); } @@ -1933,6 +3477,20 @@ namespace CodeWalker.GameFiles { return "String:" + Value; } + + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.StringTag(sb, indent, "Value", MetaXml.XmlEscape(Value)); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Value = Xml.GetChildInnerText(node, "Value"); + ValueLength = (ushort)(Value?.Length??0); + ValueCapacity = ValueLength; + } } [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttributeVector3 : ClipPropertyAttribute { @@ -1941,9 +3499,7 @@ namespace CodeWalker.GameFiles get { return 48; } } - public float X { get; set; } - public float Y { get; set; } - public float Z { get; set; } + public Vector3 Value { get; set; } public float Unknown_02Ch { get; set; } public override void Read(ResourceDataReader reader, params object[] parameters) @@ -1951,9 +3507,7 @@ namespace CodeWalker.GameFiles base.Read(reader, parameters); // read structure data - this.X = reader.ReadSingle(); - this.Y = reader.ReadSingle(); - this.Z = reader.ReadSingle(); + Value = reader.ReadVector3(); this.Unknown_02Ch = reader.ReadSingle(); } @@ -1962,15 +3516,27 @@ namespace CodeWalker.GameFiles base.Write(writer, parameters); // write structure data - writer.Write(this.X); - writer.Write(this.Y); - writer.Write(this.Z); + writer.Write(this.Value); writer.Write(this.Unknown_02Ch); } public override string ToString() { - return "Vector3:" + FloatUtil.GetVector3String(new Vector3(X, Y, Z)); + return "Vector3:" + FloatUtil.GetVector3String(Value); + } + + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.SelfClosingTag(sb, indent, "Value " + FloatUtil.GetVector3XmlString(Value)); + YcdXml.ValueTag(sb, indent, "Unknown2C", FloatUtil.ToString(Unknown_02Ch)); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Value = Xml.GetChildVector3Attributes(node, "Value", "x", "y", "z"); + Unknown_02Ch = Xml.GetChildFloatAttribute(node, "Unknown2C", "value"); } } [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttributeVector4 : ClipPropertyAttribute @@ -1980,20 +3546,14 @@ namespace CodeWalker.GameFiles get { return 48; } } - public float X { get; set; } - public float Y { get; set; } - public float Z { get; set; } - public float W { get; set; } + public Vector4 Value { get; set; } public override void Read(ResourceDataReader reader, params object[] parameters) { base.Read(reader, parameters); // read structure data - this.X = reader.ReadSingle(); - this.Y = reader.ReadSingle(); - this.Z = reader.ReadSingle(); - this.W = reader.ReadSingle(); + this.Value = reader.ReadVector4(); } public override void Write(ResourceDataWriter writer, params object[] parameters) @@ -2001,15 +3561,24 @@ namespace CodeWalker.GameFiles base.Write(writer, parameters); // write structure data - writer.Write(this.X); - writer.Write(this.Y); - writer.Write(this.Z); - writer.Write(this.W); + writer.Write(this.Value); } public override string ToString() { - return "Vector4:" + FloatUtil.GetVector4String(new Vector4(X, Y, Z, W)); + return "Vector4:" + FloatUtil.GetVector4String(Value); + } + + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.SelfClosingTag(sb, indent, "Value " + FloatUtil.GetVector4XmlString(Value)); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Value = Xml.GetChildVector4Attributes(node, "Value", "x", "y", "z", "w"); } } [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttributeHashString : ClipPropertyAttribute @@ -2047,6 +3616,28 @@ namespace CodeWalker.GameFiles { return "Hash:" + Value.ToString(); } + + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.StringTag(sb, indent, "Value", YcdXml.HashString(Value)); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Value = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Value")); + } + } + public enum ClipPropertyAttributeType : byte + { + Float = 1, + Int = 2, + Bool = 3, + String = 4, + Vector3 = 6, + Vector4 = 8, + HashString = 12, } @@ -2062,7 +3653,7 @@ namespace CodeWalker.GameFiles public ushort TagCount1 { get; set; } public ushort TagCount2 { get; set; } public uint Unknown_0Ch { get; set; } // 0x00000000 - public uint Unknown_10h { get; set; } + public uint Unknown_10h { get; set; } // 0, 1 public uint Unknown_14h { get; set; } // 0x00000000 public uint Unknown_18h { get; set; } // 0x00000000 public uint Unknown_1Ch { get; set; } // 0x00000000 @@ -2091,17 +3682,18 @@ namespace CodeWalker.GameFiles this.TagCount1 ); - if ((Tags != null) && (Tags.data_items != null)) + BuildAllTags(); + + if (TagCount1 != TagCount2) + { } + + switch (Unknown_10h) { - List tl = new List(); - foreach (var te in Tags.data_items) - { - if (te != null) - { - tl.Add(te); - } - } - AllTags = tl.ToArray(); + case 0: + case 1: + break; + default: + break; } } @@ -2109,7 +3701,8 @@ namespace CodeWalker.GameFiles { // update structure data this.TagsPointer = (ulong)(this.Tags != null ? this.Tags.FilePosition : 0); - //this.c1 = (ushort)(this.p1data != null ? this.p1data.Count : 0); + this.TagCount1 = (ushort)(this.Tags != null ? this.Tags.Count : 0); + this.TagCount2 = this.TagCount1; // write structure data writer.Write(this.TagsPointer); @@ -2133,6 +3726,35 @@ namespace CodeWalker.GameFiles { return "Count: " + (AllTags?.Length ?? 0).ToString(); } + + public void BuildAllTags() + { + + if ((Tags != null) && (Tags.data_items != null)) + { + List tl = new List(); + foreach (var te in Tags.data_items) + { + if (te.Tags != this) + { } + if (te != null) + { + tl.Add(te); + } + } + AllTags = tl.ToArray(); + } + + } + + public void AssignTagOwners() + { + if (Tags?.data_items == null) return; + foreach (var tag in Tags.data_items) + { + tag.Tags = this; + } + } } [TypeConverter(typeof(ExpandableObjectConverter))] public class ClipTag : ClipProperty { @@ -2141,8 +3763,8 @@ namespace CodeWalker.GameFiles get { return 80; } } - public MetaHash Unknown_40h { get; set; } - public MetaHash Unknown_44h { get; set; } + public float Unknown_40h { get; set; } + public float Unknown_44h { get; set; } public ulong TagsPointer { get; set; } // reference data @@ -2154,8 +3776,8 @@ namespace CodeWalker.GameFiles base.Read(reader, parameters); // read structure data - this.Unknown_40h = reader.ReadUInt32(); - this.Unknown_44h = reader.ReadUInt32(); + this.Unknown_40h = reader.ReadSingle(); + this.Unknown_44h = reader.ReadSingle(); this.TagsPointer = reader.ReadUInt64(); // read reference data @@ -2188,6 +3810,20 @@ namespace CodeWalker.GameFiles { return base.ToString() + ": " + Unknown_40h.ToString() + ", " + Unknown_44h.ToString(); } + + + public override void WriteXml(StringBuilder sb, int indent) + { + base.WriteXml(sb, indent); + YcdXml.ValueTag(sb, indent, "Unknown40", FloatUtil.ToString(Unknown_40h)); + YcdXml.ValueTag(sb, indent, "Unknown44", FloatUtil.ToString(Unknown_44h)); + } + public override void ReadXml(XmlNode node) + { + base.ReadXml(node); + Unknown_40h = Xml.GetChildFloatAttribute(node, "Unknown40", "value"); + Unknown_44h = Xml.GetChildFloatAttribute(node, "Unknown44", "value"); + } } diff --git a/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs b/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs index f433fc9..716d1a7 100644 --- a/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs +++ b/CodeWalker.Core/GameFiles/Resources/ResourceBaseTypes.cs @@ -748,7 +748,7 @@ namespace CodeWalker.GameFiles public ushort EntriesCapacity { get; private set; } // reference data - public T[] data_items { get; private set; } + public T[] data_items { get; set; } private ResourceSystemStructBlock data_block;//used for saving. diff --git a/CodeWalker.Core/Utils/Xml.cs b/CodeWalker.Core/Utils/Xml.cs index 29f3b25..2c7b912 100644 --- a/CodeWalker.Core/Utils/Xml.cs +++ b/CodeWalker.Core/Utils/Xml.cs @@ -218,11 +218,35 @@ namespace CodeWalker return GetRawByteArray(cnode); } + public static uint[] GetRawUintArray(XmlNode node) + { + if (node == null) return new uint[0]; + var data = new List(); + var split = Regex.Split(node.InnerText, @"[\s\r\n\t]"); + for (int i = 0; i < split.Length; i++) + { + if (!string.IsNullOrEmpty(split[i])) + { + var str = split[i]; + if (string.IsNullOrEmpty(str)) continue; + var val = 0u; + uint.TryParse(str, out val); + data.Add(val); + } + } + return data.ToArray(); + } + public static uint[] GetChildRawUintArray(XmlNode node, string name) + { + var cnode = node.SelectSingleNode(name); + return GetRawUintArray(cnode); + } + public static float[] GetRawFloatArray(XmlNode node) { if (node == null) return new float[0]; var items = new List(); - var split = node.InnerText.Split('\n');// Regex.Split(node.InnerText, @"[\s\r\n\t]"); + var split = Regex.Split(node.InnerText, @"[\s\r\n\t]");//node.InnerText.Split('\n');// for (int i = 0; i < split.Length; i++) { var s = split[i]?.Trim(); diff --git a/Forms/YcdForm.Designer.cs b/Forms/YcdForm.Designer.cs index 6321c04..274e64c 100644 --- a/Forms/YcdForm.Designer.cs +++ b/Forms/YcdForm.Designer.cs @@ -28,6 +28,7 @@ /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); System.Windows.Forms.ListViewGroup listViewGroup1 = new System.Windows.Forms.ListViewGroup("Clips", System.Windows.Forms.HorizontalAlignment.Left); System.Windows.Forms.ListViewGroup listViewGroup2 = new System.Windows.Forms.ListViewGroup("Animations", System.Windows.Forms.HorizontalAlignment.Left); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(YcdForm)); @@ -35,10 +36,18 @@ this.MainListView = new System.Windows.Forms.ListView(); this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.MainPropertyGrid = new CodeWalker.WinForms.ReadOnlyPropertyGrid(); + this.MainTabControl = new System.Windows.Forms.TabControl(); + this.DetailsTabPage = new System.Windows.Forms.TabPage(); + this.XmlTabPage = new System.Windows.Forms.TabPage(); + this.XmlTextBox = new FastColoredTextBoxNS.FastColoredTextBox(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); this.splitContainer1.Panel1.SuspendLayout(); this.splitContainer1.Panel2.SuspendLayout(); this.splitContainer1.SuspendLayout(); + this.MainTabControl.SuspendLayout(); + this.DetailsTabPage.SuspendLayout(); + this.XmlTabPage.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.XmlTextBox)).BeginInit(); this.SuspendLayout(); // // splitContainer1 @@ -55,7 +64,7 @@ // splitContainer1.Panel2 // this.splitContainer1.Panel2.Controls.Add(this.MainPropertyGrid); - this.splitContainer1.Size = new System.Drawing.Size(763, 474); + this.splitContainer1.Size = new System.Drawing.Size(751, 442); this.splitContainer1.SplitterDistance = 254; this.splitContainer1.TabIndex = 1; // @@ -78,7 +87,7 @@ this.MainListView.Location = new System.Drawing.Point(3, 3); this.MainListView.MultiSelect = false; this.MainListView.Name = "MainListView"; - this.MainListView.Size = new System.Drawing.Size(248, 468); + this.MainListView.Size = new System.Drawing.Size(248, 436); this.MainListView.TabIndex = 0; this.MainListView.UseCompatibleStateImageBehavior = false; this.MainListView.View = System.Windows.Forms.View.Details; @@ -98,15 +107,92 @@ this.MainPropertyGrid.Location = new System.Drawing.Point(3, 3); this.MainPropertyGrid.Name = "MainPropertyGrid"; this.MainPropertyGrid.ReadOnly = false; - this.MainPropertyGrid.Size = new System.Drawing.Size(499, 468); + this.MainPropertyGrid.Size = new System.Drawing.Size(487, 436); this.MainPropertyGrid.TabIndex = 0; // + // MainTabControl + // + this.MainTabControl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.MainTabControl.Controls.Add(this.DetailsTabPage); + this.MainTabControl.Controls.Add(this.XmlTabPage); + this.MainTabControl.Location = new System.Drawing.Point(2, 3); + this.MainTabControl.Name = "MainTabControl"; + this.MainTabControl.SelectedIndex = 0; + this.MainTabControl.Size = new System.Drawing.Size(759, 468); + this.MainTabControl.TabIndex = 2; + this.MainTabControl.SelectedIndexChanged += new System.EventHandler(this.MainTabControl_SelectedIndexChanged); + // + // DetailsTabPage + // + this.DetailsTabPage.Controls.Add(this.splitContainer1); + this.DetailsTabPage.Location = new System.Drawing.Point(4, 22); + this.DetailsTabPage.Name = "DetailsTabPage"; + this.DetailsTabPage.Size = new System.Drawing.Size(751, 442); + this.DetailsTabPage.TabIndex = 0; + this.DetailsTabPage.Text = "Details"; + this.DetailsTabPage.UseVisualStyleBackColor = true; + // + // XmlTabPage + // + this.XmlTabPage.Controls.Add(this.XmlTextBox); + this.XmlTabPage.Location = new System.Drawing.Point(4, 22); + this.XmlTabPage.Name = "XmlTabPage"; + this.XmlTabPage.Size = new System.Drawing.Size(751, 442); + this.XmlTabPage.TabIndex = 1; + this.XmlTabPage.Text = "XML"; + this.XmlTabPage.UseVisualStyleBackColor = true; + // + // XmlTextBox + // + this.XmlTextBox.AutoCompleteBracketsList = new char[] { + '(', + ')', + '{', + '}', + '[', + ']', + '\"', + '\"', + '\'', + '\''}; + this.XmlTextBox.AutoIndentChars = false; + this.XmlTextBox.AutoIndentCharsPatterns = ""; + this.XmlTextBox.AutoIndentExistingLines = false; + this.XmlTextBox.AutoScrollMinSize = new System.Drawing.Size(27, 14); + this.XmlTextBox.BackBrush = null; + this.XmlTextBox.CharHeight = 14; + this.XmlTextBox.CharWidth = 8; + this.XmlTextBox.CommentPrefix = null; + this.XmlTextBox.Cursor = System.Windows.Forms.Cursors.IBeam; + this.XmlTextBox.DelayedEventsInterval = 1; + this.XmlTextBox.DisabledColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(180)))), ((int)(((byte)(180)))), ((int)(((byte)(180))))); + this.XmlTextBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.XmlTextBox.Font = new System.Drawing.Font("Courier New", 9.75F); + this.XmlTextBox.IsReplaceMode = false; + this.XmlTextBox.Language = FastColoredTextBoxNS.Language.XML; + this.XmlTextBox.LeftBracket = '<'; + this.XmlTextBox.LeftBracket2 = '('; + this.XmlTextBox.Location = new System.Drawing.Point(0, 0); + this.XmlTextBox.Name = "XmlTextBox"; + this.XmlTextBox.Paddings = new System.Windows.Forms.Padding(0); + this.XmlTextBox.RightBracket = '>'; + this.XmlTextBox.RightBracket2 = ')'; + this.XmlTextBox.SelectionColor = System.Drawing.Color.FromArgb(((int)(((byte)(60)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(255))))); + this.XmlTextBox.ServiceColors = ((FastColoredTextBoxNS.ServiceColors)(resources.GetObject("XmlTextBox.ServiceColors"))); + this.XmlTextBox.Size = new System.Drawing.Size(751, 442); + this.XmlTextBox.TabIndex = 1; + this.XmlTextBox.Zoom = 100; + this.XmlTextBox.TextChanged += new System.EventHandler(this.XmlTextBox_TextChanged); + this.XmlTextBox.VisibleRangeChangedDelayed += new System.EventHandler(this.XmlTextBox_VisibleRangeChangedDelayed); + // // YcdForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(763, 474); - this.Controls.Add(this.splitContainer1); + this.Controls.Add(this.MainTabControl); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Name = "YcdForm"; this.Text = "Clip Dictionary Inspector - CodeWalker by dexyfex"; @@ -114,6 +200,10 @@ this.splitContainer1.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); this.splitContainer1.ResumeLayout(false); + this.MainTabControl.ResumeLayout(false); + this.DetailsTabPage.ResumeLayout(false); + this.XmlTabPage.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.XmlTextBox)).EndInit(); this.ResumeLayout(false); } @@ -123,5 +213,9 @@ private System.Windows.Forms.ListView MainListView; private System.Windows.Forms.ColumnHeader columnHeader1; private WinForms.ReadOnlyPropertyGrid MainPropertyGrid; + private System.Windows.Forms.TabControl MainTabControl; + private System.Windows.Forms.TabPage DetailsTabPage; + private System.Windows.Forms.TabPage XmlTabPage; + private FastColoredTextBoxNS.FastColoredTextBox XmlTextBox; } } \ No newline at end of file diff --git a/Forms/YcdForm.cs b/Forms/YcdForm.cs index 2b7690a..e92bc70 100644 --- a/Forms/YcdForm.cs +++ b/Forms/YcdForm.cs @@ -1,4 +1,5 @@ using CodeWalker.GameFiles; +using FastColoredTextBoxNS; using System; using System.Collections.Generic; using System.ComponentModel; @@ -29,6 +30,8 @@ namespace CodeWalker.Forms } public string FilePath { get; set; } + private bool LoadingXml = false; + private bool DelayHighlight = false; public YcdForm() @@ -43,30 +46,7 @@ namespace CodeWalker.Forms private void ExportOnim_Click(object sender, EventArgs e) { - if (MainListView.SelectedItems[0].Tag is Animation anim) - { - var saveFileDialog = new SaveFileDialog(); - string newfn = $"{Path.GetFileNameWithoutExtension(Ycd.Name)}_{MainListView.SelectedItems[0].Text}.onim"; - - saveFileDialog.FileName = newfn; - if (saveFileDialog.ShowDialog() == DialogResult.OK) - { - string path = saveFileDialog.FileName; - - try - { - using (var file = File.OpenWrite(path)) - { - Ycd.SaveOpenFormatsAnimation(anim, file); - } - } - catch (Exception ex) - { - MessageBox.Show("Error saving file " + path + ":\n" + ex.ToString()); - } - } - } } private void UpdateFormTitle() @@ -74,6 +54,49 @@ namespace CodeWalker.Forms Text = fileName + " - Clip Dictionary Inspector - CodeWalker by dexyfex"; } + private void UpdateXmlTextBox(string xml) + { + LoadingXml = true; + XmlTextBox.Text = ""; + XmlTextBox.Language = Language.XML; + DelayHighlight = false; + + if (string.IsNullOrEmpty(xml)) + { + LoadingXml = false; + return; + } + //if (xml.Length > (1048576 * 5)) + //{ + // XmlTextBox.Language = Language.Custom; + // XmlTextBox.Text = "[XML size > 10MB - Not shown due to performance limitations - Please use an external viewer for this file.]"; + // return; + //} + //else + if (xml.Length > (1024 * 512)) + { + XmlTextBox.Language = Language.Custom; + DelayHighlight = true; + } + //else + //{ + // XmlTextBox.Language = Language.XML; + //} + + + Cursor = Cursors.WaitCursor; + + + + XmlTextBox.Text = xml; + //XmlTextBox.IsChanged = false; + XmlTextBox.ClearUndo(); + + Cursor = Cursors.Default; + LoadingXml = false; + } + + public void LoadYcd(YcdFile ycd) { @@ -120,8 +143,60 @@ namespace CodeWalker.Forms } + public void LoadXml() + { + if (Ycd != null) + { + var xml = YcdXml.GetXml(Ycd); + UpdateXmlTextBox(xml); + } + } + private void HTMLSyntaxHighlight(Range range) + { + try + { + Style BlueStyle = new TextStyle(Brushes.Blue, null, FontStyle.Regular); + Style RedStyle = new TextStyle(Brushes.Red, null, FontStyle.Regular); + Style MaroonStyle = new TextStyle(Brushes.Maroon, null, FontStyle.Regular); + + //clear style of changed range + range.ClearStyle(BlueStyle, MaroonStyle, RedStyle); + //tag brackets highlighting + range.SetStyle(BlueStyle, @"<|/>|"); + //tag name + range.SetStyle(MaroonStyle, @"<(?[!\w]+)"); + //end of tag + range.SetStyle(MaroonStyle, @"\w+)>"); + //attributes + range.SetStyle(RedStyle, @"(?\S+?)='[^']*'|(?\S+)=""[^""]*""|(?\S+)=\S+"); + //attribute values + range.SetStyle(BlueStyle, @"\S+?=(?'[^']*')|\S+=(?""[^""]*"")|\S+=(?\S+)"); + } + catch + { } + } + + private void XmlTextBox_VisibleRangeChangedDelayed(object sender, EventArgs e) + { + //this approach is much faster to load, but no outlining is available + + //highlight only visible area of text + if (DelayHighlight) + { + HTMLSyntaxHighlight(XmlTextBox.VisibleRange); + } + } + + private void XmlTextBox_TextChanged(object sender, TextChangedEventArgs e) + { + if (!LoadingXml) + { + + } + } + private void MainListView_SelectedIndexChanged(object sender, EventArgs e) { if (MainListView.SelectedItems.Count == 1) @@ -143,5 +218,16 @@ namespace CodeWalker.Forms //MainPropertyGrid.SelectedObject = null; } } + + private void MainTabControl_SelectedIndexChanged(object sender, EventArgs e) + { + if (MainTabControl.SelectedTab == XmlTabPage) + { + if (string.IsNullOrEmpty(XmlTextBox.Text)) + { + LoadXml(); + } + } + } } } \ No newline at end of file diff --git a/Forms/YcdForm.resx b/Forms/YcdForm.resx index 1431f6b..4d27add 100644 --- a/Forms/YcdForm.resx +++ b/Forms/YcdForm.resx @@ -117,6 +117,24 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFdGYXN0Q29sb3JlZFRleHRCb3gsIFZlcnNpb249Mi4xNi4yNC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWZiOGFhMTJiOTk0ZWY2MWIMAwAAAFFTeXN0 + ZW0uRHJhd2luZywgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2Vu + PWIwM2Y1ZjdmMTFkNTBhM2EFAQAAACJGYXN0Q29sb3JlZFRleHRCb3hOUy5TZXJ2aWNlQ29sb3JzBgAA + ACg8Q29sbGFwc2VNYXJrZXJGb3JlQ29sb3I+a19fQmFja2luZ0ZpZWxkKDxDb2xsYXBzZU1hcmtlckJh + Y2tDb2xvcj5rX19CYWNraW5nRmllbGQqPENvbGxhcHNlTWFya2VyQm9yZGVyQ29sb3I+a19fQmFja2lu + Z0ZpZWxkJjxFeHBhbmRNYXJrZXJGb3JlQ29sb3I+a19fQmFja2luZ0ZpZWxkJjxFeHBhbmRNYXJrZXJC + YWNrQ29sb3I+a19fQmFja2luZ0ZpZWxkKDxFeHBhbmRNYXJrZXJCb3JkZXJDb2xvcj5rX19CYWNraW5n + RmllbGQEBAQEBAQUU3lzdGVtLkRyYXdpbmcuQ29sb3IDAAAAFFN5c3RlbS5EcmF3aW5nLkNvbG9yAwAA + ABRTeXN0ZW0uRHJhd2luZy5Db2xvcgMAAAAUU3lzdGVtLkRyYXdpbmcuQ29sb3IDAAAAFFN5c3RlbS5E + cmF3aW5nLkNvbG9yAwAAABRTeXN0ZW0uRHJhd2luZy5Db2xvcgMAAAACAAAABfz///8UU3lzdGVtLkRy + YXdpbmcuQ29sb3IEAAAABG5hbWUFdmFsdWUKa25vd25Db2xvcgVzdGF0ZQEAAAAJBwcDAAAACgAAAAAA + AAAAlgABAAH7/////P///woAAAAAAAAAAKQAAQAB+v////z///8KAAAAAAAAAACWAAEAAfn////8//// + CgAAAAAAAAAATgABAAH4/////P///woAAAAAAAAAAKQAAQAB9/////z///8KAAAAAAAAAACWAAEACw== + + diff --git a/Peds/PedsForm.Designer.cs b/Peds/PedsForm.Designer.cs index 6aa7d84..2767d6c 100644 --- a/Peds/PedsForm.Designer.cs +++ b/Peds/PedsForm.Designer.cs @@ -108,6 +108,7 @@ this.label4 = new System.Windows.Forms.Label(); this.ToolsTabControl = new System.Windows.Forms.TabControl(); this.ToolsPanel = new System.Windows.Forms.Panel(); + this.EnableRootMotionCheckBox = new System.Windows.Forms.CheckBox(); this.ConsolePanel.SuspendLayout(); this.ToolsOptionsTabPage.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.TimeOfDayTrackBar)).BeginInit(); @@ -656,6 +657,7 @@ // // ToolsPedTabPage // + this.ToolsPedTabPage.Controls.Add(this.EnableRootMotionCheckBox); this.ToolsPedTabPage.Controls.Add(this.label23); this.ToolsPedTabPage.Controls.Add(this.label22); this.ToolsPedTabPage.Controls.Add(this.ClipComboBox); @@ -1064,6 +1066,17 @@ this.ToolsPanel.TabIndex = 7; this.ToolsPanel.Visible = false; // + // EnableRootMotionCheckBox + // + this.EnableRootMotionCheckBox.AutoSize = true; + this.EnableRootMotionCheckBox.Location = new System.Drawing.Point(54, 503); + this.EnableRootMotionCheckBox.Name = "EnableRootMotionCheckBox"; + this.EnableRootMotionCheckBox.Size = new System.Drawing.Size(114, 17); + this.EnableRootMotionCheckBox.TabIndex = 32; + this.EnableRootMotionCheckBox.Text = "Enable root motion"; + this.EnableRootMotionCheckBox.UseVisualStyleBackColor = true; + this.EnableRootMotionCheckBox.CheckedChanged += new System.EventHandler(this.EnableRootMotionCheckBox_CheckedChanged); + // // PedsForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -1184,5 +1197,6 @@ private System.Windows.Forms.Label label21; private System.Windows.Forms.ComboBox ClipDictComboBox; private System.Windows.Forms.Label label23; + private System.Windows.Forms.CheckBox EnableRootMotionCheckBox; } } \ No newline at end of file diff --git a/Peds/PedsForm.cs b/Peds/PedsForm.cs index 6897f63..0659172 100644 --- a/Peds/PedsForm.cs +++ b/Peds/PedsForm.cs @@ -92,6 +92,7 @@ namespace CodeWalker.Peds public ClipMapEntry AnimClip { get; set; } = null; public Drawable[] Drawables { get; set; } = new Drawable[12]; public Texture[] Textures { get; set; } = new Texture[12]; + public bool EnableRootMotion { get; set; } = false; //used to toggle whether or not to include root motion when playing animations } PedSelection SelectedPed = new PedSelection(); @@ -971,6 +972,80 @@ namespace CodeWalker.Peds + private void LoadClipDict(string name) + { + var ycdhash = JenkHash.GenHash(name.ToLowerInvariant()); + var ycd = GameFileCache.GetYcd(ycdhash); + while ((ycd != null) && (!ycd.Loaded)) + { + Thread.Sleep(20);//kinda hacky + ycd = GameFileCache.GetYcd(ycdhash); + } + + + + //if (ycd != null) + //{ + // ////// TESTING XML CONVERSIONS + // var xml = YcdXml.GetXml(ycd); + // var ycd2 = XmlYcd.GetYcd(xml); + // var data = ycd2.Save(); + // var ycd3 = new YcdFile(); + // RpfFile.LoadResourceFile(ycd3, data, 46); + // //var xml2 = YcdXml.GetXml(ycd3); + // //if (xml != xml2) + // //{ } + // ycd = ycd3; + //} + + + + SelectedPed.Ycd = ycd; + + ClipComboBox.Items.Clear(); + ClipComboBox.Items.Add(""); + + if (ycd?.ClipMapEntries == null) + { + ClipComboBox.SelectedIndex = 0; + SelectedPed.AnimClip = null; + return; + } + + List items = new List(); + + foreach (var cme in ycd.ClipMapEntries) + { + var animclip = cme.Clip as ClipAnimation; + if (animclip != null) + { + items.Add(animclip.ShortName); + continue; + } + var animcliplist = cme.Clip as ClipAnimationList; + if (animcliplist?.Animations?.Data != null) + { + items.Add(animcliplist.ShortName); + continue; + } + } + + + items.Sort(); + foreach (var item in items) + { + ClipComboBox.Items.Add(item); + } + } + + private void SelectClip(string name) + { + MetaHash cliphash = JenkHash.GenHash(name); + ClipMapEntry cme = null; + SelectedPed.Ycd?.ClipMap?.TryGetValue(cliphash, out cme); + SelectedPed.AnimClip = cme; + } + @@ -1193,6 +1268,10 @@ namespace CodeWalker.Peds var td = SelectedPed.Ytd?.TextureDict; var ac = SelectedPed.AnimClip; + if (ac != null) + { + ac.EnableRootMotion = SelectedPed.EnableRootMotion; + } var skel = SelectedPed.Yft?.Fragment?.Drawable?.Skeleton; if (skel != null) @@ -1788,69 +1867,22 @@ namespace CodeWalker.Peds private void ClipDictComboBox_TextChanged(object sender, EventArgs e) { - - var ycdhash = JenkHash.GenHash(ClipDictComboBox.Text.ToLowerInvariant()); - var ycd = GameFileCache.GetYcd(ycdhash); - while ((ycd != null) && (!ycd.Loaded)) - { - Thread.Sleep(20);//kinda hacky - ycd = GameFileCache.GetYcd(ycdhash); - } - - SelectedPed.Ycd = ycd; - - ClipComboBox.Items.Clear(); - ClipComboBox.Items.Add(""); - - if (ycd?.ClipMapEntries == null) - { - ClipComboBox.SelectedIndex = 0; - SelectedPed.AnimClip = null; - return; - } - - List items = new List(); - - foreach (var cme in ycd.ClipMapEntries) - { - var animclip = cme.Clip as ClipAnimation; - if (animclip != null) - { - items.Add(animclip.ShortName); - continue; - } - var animcliplist = cme.Clip as ClipAnimationList; - if (animcliplist?.Animations?.Data != null) - { - items.Add(animcliplist.ShortName); - continue; - } - } - - - items.Sort(); - foreach (var item in items) - { - ClipComboBox.Items.Add(item); - } - - + LoadClipDict(ClipDictComboBox.Text); } private void ClipComboBox_SelectedIndexChanged(object sender, EventArgs e) { - - var name = ClipComboBox.Text; - - MetaHash cliphash = JenkHash.GenHash(name); - ClipMapEntry cme = null; - SelectedPed.Ycd?.ClipMap?.TryGetValue(cliphash, out cme); - SelectedPed.AnimClip = cme; + SelectClip(ClipComboBox.Text); } private void ClipComboBox_TextChanged(object sender, EventArgs e) { - ClipComboBox_SelectedIndexChanged(sender, e); + SelectClip(ClipComboBox.Text); + } + + private void EnableRootMotionCheckBox_CheckedChanged(object sender, EventArgs e) + { + SelectedPed.EnableRootMotion = EnableRootMotionCheckBox.Checked; } } } diff --git a/Rendering/Renderable.cs b/Rendering/Renderable.cs index a33b715..e352248 100644 --- a/Rendering/Renderable.cs +++ b/Rendering/Renderable.cs @@ -79,6 +79,8 @@ namespace CodeWalker.Rendering public Matrix3_s[] BoneTransforms; public List Bones; + public bool EnableRootMotion = false; //used to toggle whether or not to include root motion when playing animations + public override void Init(DrawableBase drawable) { @@ -385,39 +387,39 @@ namespace CodeWalker.Rendering BoneTransforms[i] = bt; } - //var drawbl = Key; - //if (AllModels == null) return; - //for (int i = 0; i < AllModels.Length; i++) - //{ - // var model = AllModels[i]; - // if (model?.Geometries == null) continue; - // for (int g = 0; g < model.Geometries.Length; g++) - // { - // var geom = model.Geometries[g]; - // var boneids = geom?.DrawableGeom?.BoneIds; - // if (boneids == null) continue; - // if (boneids.Length != Bones.Count) - // { - // var idc = boneids.Length; - // if (geom.BoneTransforms == null) - // { - // geom.BoneTransforms = new Matrix3_s[idc]; - // } - // for (int b = 0; b < idc; b++) - // { - // var id = boneids[b]; - // if (id < BoneTransforms.Length) - // { - // geom.BoneTransforms[b] = BoneTransforms[id]; - // if (id != b) - // { } - // } - // else - // { } - // } - // } - // } - //} + var drawbl = Key; + if (AllModels == null) return; + for (int i = 0; i < AllModels.Length; i++) + { + var model = AllModels[i]; + if (model?.Geometries == null) continue; + for (int g = 0; g < model.Geometries.Length; g++) + { + var geom = model.Geometries[g]; + var boneids = geom?.DrawableGeom?.BoneIds; + if (boneids == null) continue; + if (boneids.Length != Bones.Count) + { + var idc = boneids.Length; + if (geom.BoneTransforms == null) + { + geom.BoneTransforms = new Matrix3_s[idc]; + } + for (int b = 0; b < idc; b++) + { + var id = boneids[b]; + if (id < BoneTransforms.Length) + { + geom.BoneTransforms[b] = BoneTransforms[id]; + if (id != b) + { } + } + else + { } + } + } + } + } } @@ -428,6 +430,8 @@ namespace CodeWalker.Rendering if (CurrentAnimTime == realTime) return;//already updated this! CurrentAnimTime = realTime; + EnableRootMotion = ClipMapEntry?.EnableRootMotion ?? false; + if (ClipMapEntry != null) { UpdateAnim(ClipMapEntry); //animate skeleton/models @@ -451,6 +455,7 @@ namespace CodeWalker.Rendering } private void UpdateAnim(ClipMapEntry cme) { + if (cme.Next != null) { UpdateAnim(cme.Next); @@ -545,10 +550,13 @@ namespace CodeWalker.Rendering case 5://root motion vector if (bone.Tag != 0) { } - //v0 = aseq.EvaluateVector(f0); - //v1 = aseq.EvaluateVector(f1); - //v = interpolate ? (v0 * ialpha) + (v1 * falpha) : v0; - //bone.AnimTranslation += v.XYZ(); + if (EnableRootMotion) + { + v0 = aseq.EvaluateVector(f0); + v1 = aseq.EvaluateVector(f1); + v = interpolate ? (v0 * ialpha) + (v1 * falpha) : v0; + bone.AnimTranslation += v.XYZ(); + } break; case 6://quaternion... root rotation? if (bone.Tag != 0) @@ -574,6 +582,8 @@ namespace CodeWalker.Rendering { } break; default: + if (bone.Tag != 0) + { } break; } } @@ -832,7 +842,7 @@ namespace CodeWalker.Rendering public bool isHair = false; public bool disableRendering = false; - //public Matrix3_s[] BoneTransforms = null; + public Matrix3_s[] BoneTransforms = null; public static ShaderParamNames[] GetTextureSamplerList() { diff --git a/Rendering/Shaders/BasicShader.cs b/Rendering/Shaders/BasicShader.cs index 40eb297..d5d7e43 100644 --- a/Rendering/Shaders/BasicShader.cs +++ b/Rendering/Shaders/BasicShader.cs @@ -801,11 +801,11 @@ namespace CodeWalker.Rendering } - //if (geom.BoneTransforms != null) - //{ - // SetBoneMatrices(context, geom.BoneTransforms); - // defaultBoneMatricesBound = false; - //} + if (geom.BoneTransforms != null) + { + SetBoneMatrices(context, geom.BoneTransforms); + defaultBoneMatricesBound = false; + } } diff --git a/Rendering/Shaders/ShadowShader.cs b/Rendering/Shaders/ShadowShader.cs index a2b18d0..ad0a2f0 100644 --- a/Rendering/Shaders/ShadowShader.cs +++ b/Rendering/Shaders/ShadowShader.cs @@ -358,11 +358,11 @@ namespace CodeWalker.Rendering } - //if (geom.BoneTransforms != null) - //{ - // SetBoneMatrices(context, geom.BoneTransforms); - // defaultBoneMatricesBound = false; - //} + if (geom.BoneTransforms != null) + { + SetBoneMatrices(context, geom.BoneTransforms); + defaultBoneMatricesBound = false; + } }