mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2024-08-20 00:49:25 +08:00
4467 lines
160 KiB
C#
4467 lines
160 KiB
C#
using SharpDX;
|
|
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
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
*/
|
|
|
|
|
|
//ruthlessly stolen
|
|
|
|
|
|
namespace CodeWalker.GameFiles
|
|
{
|
|
|
|
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipDictionary : ResourceFileBase
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 64; }
|
|
}
|
|
|
|
// structure data
|
|
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_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_38h { get; set; } // 0x00000000
|
|
public uint Unknown_3Ch { get; set; } // 0x00000000
|
|
|
|
// reference data
|
|
public AnimationMap Animations { get; set; }
|
|
public ResourcePointerArray64<ClipMapEntry> Clips { get; set; }
|
|
|
|
//data used by CW for loading/saving
|
|
public Dictionary<MetaHash, ClipMapEntry> ClipMap { get; set; }
|
|
public Dictionary<MetaHash, AnimationMapEntry> AnimMap { get; set; }
|
|
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
base.Read(reader, parameters);
|
|
|
|
// read structure data
|
|
this.Unknown_10h = reader.ReadUInt32();
|
|
this.Unknown_14h = reader.ReadUInt32();
|
|
this.AnimationsPointer = reader.ReadUInt64();
|
|
this.Unknown_20h = reader.ReadUInt32();
|
|
this.Unknown_24h = reader.ReadUInt32();
|
|
this.ClipsPointer = reader.ReadUInt64();
|
|
this.ClipsMapCapacity = reader.ReadUInt16();
|
|
this.ClipsMapEntries = reader.ReadUInt16();
|
|
this.Unknown_34h = reader.ReadUInt32();
|
|
this.Unknown_38h = reader.ReadUInt32();
|
|
this.Unknown_3Ch = reader.ReadUInt32();
|
|
|
|
// read reference data
|
|
this.Animations = reader.ReadBlockAt<AnimationMap>(
|
|
this.AnimationsPointer // offset
|
|
);
|
|
this.Clips = reader.ReadBlockAt<ResourcePointerArray64<ClipMapEntry>>(
|
|
this.ClipsPointer, // offset
|
|
this.ClipsMapCapacity
|
|
);
|
|
|
|
BuildMaps();
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
base.Write(writer, parameters);
|
|
|
|
// update structure data
|
|
this.AnimationsPointer = (ulong)(this.Animations != null ? this.Animations.FilePosition : 0);
|
|
this.ClipsPointer = (ulong)(this.Clips != null ? this.Clips.FilePosition : 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);
|
|
writer.Write(this.Unknown_14h);
|
|
writer.Write(this.AnimationsPointer);
|
|
writer.Write(this.Unknown_20h);
|
|
writer.Write(this.Unknown_24h);
|
|
writer.Write(this.ClipsPointer);
|
|
writer.Write(this.ClipsMapCapacity);
|
|
writer.Write(this.ClipsMapEntries);
|
|
writer.Write(this.Unknown_34h);
|
|
writer.Write(this.Unknown_38h);
|
|
writer.Write(this.Unknown_3Ch);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>(base.GetReferences());
|
|
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<MetaHash, ClipMapEntry>();
|
|
AnimMap = new Dictionary<MetaHash, AnimationMapEntry>();
|
|
|
|
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;
|
|
|
|
var name = clip.ShortName; //just to make sure ShortName is generated and in JenkIndex...
|
|
|
|
//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 UpdateUsageCounts()
|
|
{
|
|
|
|
var usages = new Dictionary<MetaHash, uint>();
|
|
|
|
void addUsage(MetaHash h)
|
|
{
|
|
uint u = 0;
|
|
usages.TryGetValue(h, out u);
|
|
u++;
|
|
usages[h] = u;
|
|
}
|
|
|
|
if ((Animations != null) && (Animations.Animations != null) && (Animations.Animations.data_items != null))
|
|
{
|
|
foreach (var ame in Animations.Animations.data_items)
|
|
{
|
|
if (ame != null)
|
|
{
|
|
addUsage(ame.Hash);
|
|
var nxt = ame.NextEntry;
|
|
while (nxt != null)
|
|
{
|
|
addUsage(nxt.Hash);
|
|
nxt = nxt.NextEntry;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var cme in ClipMap.Values)
|
|
{
|
|
var ca = cme.Clip as ClipAnimation;
|
|
var cal = cme.Clip as ClipAnimationList;
|
|
if (ca?.Animation != null)
|
|
{
|
|
addUsage(ca.Animation.Hash);
|
|
}
|
|
if (cal?.Animations != null)
|
|
{
|
|
foreach (var cae in cal.Animations)
|
|
{
|
|
if (cae?.Animation != null)
|
|
{
|
|
addUsage(cae.Animation.Hash);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var ame in AnimMap.Values)
|
|
{
|
|
if (ame.Animation != null)
|
|
{
|
|
uint u = 0;
|
|
if (usages.TryGetValue(ame.Animation.Hash, out u))
|
|
{
|
|
if (ame.Animation.UsageCount != u)
|
|
{ }
|
|
ame.Animation.UsageCount = u;
|
|
}
|
|
else
|
|
{ }
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
var clips = new List<ClipBase>();
|
|
if (ClipMap != null)
|
|
{
|
|
foreach (var cme in ClipMap.Values)
|
|
{
|
|
if (cme?.Clip == null) continue;
|
|
clips.Add(cme.Clip);
|
|
}
|
|
}
|
|
var anims = new List<Animation>();
|
|
if (AnimMap != null)
|
|
{
|
|
foreach (var ame in AnimMap.Values)
|
|
{
|
|
if (ame?.Animation == null) continue;
|
|
anims.Add(ame.Animation);
|
|
}
|
|
}
|
|
|
|
YcdXml.WriteItemArray(sb, clips.ToArray(), indent, "Clips");
|
|
YcdXml.WriteItemArray(sb, anims.ToArray(), indent, "Animations");
|
|
|
|
}
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
|
|
var clipList = new List<ClipMapEntry>();
|
|
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<ClipType>(Xml.GetChildStringAttribute(inode, "Type", "value"));
|
|
var c = ClipBase.ConstructClip(type);
|
|
c.ReadXml(inode);
|
|
|
|
var cme = new ClipMapEntry();
|
|
cme.Hash = c.Hash;
|
|
cme.Clip = c;
|
|
clipList.Add(cme);
|
|
}
|
|
}
|
|
}
|
|
|
|
var animDict = new Dictionary<MetaHash, Animation>();
|
|
var animList = new List<AnimationMapEntry>();
|
|
var anims = XmlMeta.ReadItemArrayNullable<Animation>(node, "Animations");
|
|
if (anims != null)
|
|
{
|
|
foreach (var anim in anims)
|
|
{
|
|
animDict[anim.Hash] = anim;
|
|
|
|
var ame = new AnimationMapEntry();
|
|
ame.Hash = anim.Hash;
|
|
ame.Animation = anim;
|
|
animList.Add(ame);
|
|
}
|
|
}
|
|
|
|
foreach (var cme in clipList)
|
|
{
|
|
var cb = cme?.Clip;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
CreateAnimationsMap(animList.ToArray());
|
|
CreateClipsMap(clipList.ToArray());
|
|
|
|
|
|
|
|
BuildMaps();
|
|
UpdateUsageCounts();
|
|
}
|
|
|
|
|
|
|
|
|
|
public void CreateClipsMap(ClipMapEntry[] clips)
|
|
{
|
|
var numClipBuckets = GetNumHashBuckets(clips?.Length ?? 0);
|
|
var clipBuckets = new List<ClipMapEntry>[numClipBuckets];
|
|
if (clips != null)
|
|
{
|
|
foreach (var cme in clips)
|
|
{
|
|
var b = cme.Hash % numClipBuckets;
|
|
var bucket = clipBuckets[b];
|
|
if (bucket == null)
|
|
{
|
|
bucket = new List<ClipMapEntry>();
|
|
clipBuckets[b] = bucket;
|
|
}
|
|
bucket.Add(cme);
|
|
}
|
|
}
|
|
|
|
var newClips = new List<ClipMapEntry>();
|
|
foreach (var b in clipBuckets)
|
|
{
|
|
if ((b?.Count ?? 0) == 0) newClips.Add(null);
|
|
else
|
|
{
|
|
newClips.Add(b[0]);
|
|
var p = b[0];
|
|
for (int i = 1; i < b.Count; i++)
|
|
{
|
|
var c = b[i];
|
|
c.Next = null;
|
|
p.Next = c;
|
|
p = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
Clips = new ResourcePointerArray64<ClipMapEntry>();
|
|
Clips.data_items = newClips.ToArray();
|
|
|
|
}
|
|
public void CreateAnimationsMap(AnimationMapEntry[] anims)
|
|
{
|
|
var numAnimBuckets = GetNumHashBuckets(anims?.Length ?? 0);
|
|
var animBuckets = new List<AnimationMapEntry>[numAnimBuckets];
|
|
if (anims != null)
|
|
{
|
|
foreach (var ame in anims)
|
|
{
|
|
var b = ame.Hash % numAnimBuckets;
|
|
var bucket = animBuckets[b];
|
|
if (bucket == null)
|
|
{
|
|
bucket = new List<AnimationMapEntry>();
|
|
animBuckets[b] = bucket;
|
|
}
|
|
bucket.Add(ame);
|
|
}
|
|
}
|
|
|
|
var newAnims = new List<AnimationMapEntry>();
|
|
foreach (var b in animBuckets)
|
|
{
|
|
if ((b?.Count ?? 0) == 0) newAnims.Add(null);
|
|
else
|
|
{
|
|
newAnims.Add(b[0]);
|
|
var p = b[0];
|
|
for (int i = 1; i < b.Count; i++)
|
|
{
|
|
var c = b[i];
|
|
c.NextEntry = null;
|
|
p.NextEntry = c;
|
|
p = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
Animations = new AnimationMap();
|
|
Animations.Animations = new ResourcePointerArray64<AnimationMapEntry>();
|
|
Animations.Animations.data_items = newAnims.ToArray();
|
|
|
|
|
|
|
|
}
|
|
|
|
public static uint GetNumHashBuckets(int nHashes)
|
|
{
|
|
if (nHashes < 11) return 11;
|
|
else if (nHashes < 29) return 29;
|
|
else if (nHashes < 59) return 59;
|
|
else if (nHashes < 107) return 107;
|
|
else if (nHashes < 191) return 191;
|
|
else if (nHashes < 331) return 331;
|
|
else if (nHashes < 563) return 563;
|
|
else if (nHashes < 953) return 953;
|
|
else if (nHashes < 1609) return 1609;
|
|
else if (nHashes < 2729) return 2729;
|
|
else if (nHashes < 4621) return 4621;
|
|
else if (nHashes < 7841) return 7841;
|
|
else if (nHashes < 13297) return 13297;
|
|
else if (nHashes < 22571) return 22571;
|
|
else if (nHashes < 38351) return 38351;
|
|
else if (nHashes < 65167) return 65167;
|
|
else /*if (nHashes < 65521)*/ return 65521;
|
|
//return ((uint)nHashes / 4) * 4 + 3;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class AnimationMap : ResourceSystemBlock
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 48; }
|
|
}
|
|
|
|
// structure data
|
|
public uint VFT { get; set; }
|
|
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; } = 16777216;
|
|
public uint Unknown_28h { get; set; } = 1; // 0x00000001
|
|
public uint Unknown_2Ch { get; set; } = 0; // 0x00000000
|
|
|
|
// reference data
|
|
public ResourcePointerArray64<AnimationMapEntry> Animations { get; set; }
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
// read structure data
|
|
this.VFT = reader.ReadUInt32();
|
|
this.Unknown_04h = reader.ReadUInt32();
|
|
this.Unknown_08h = reader.ReadUInt32();
|
|
this.Unknown_0Ch = reader.ReadUInt32();
|
|
this.Unknown_10h = reader.ReadUInt32();
|
|
this.Unknown_14h = reader.ReadUInt32();
|
|
this.AnimationsPointer = reader.ReadUInt64();
|
|
this.AnimationsMapCapacity = reader.ReadUInt16();
|
|
this.AnimationsMapEntries = reader.ReadUInt16();
|
|
this.Unknown_24h = reader.ReadUInt32();
|
|
this.Unknown_28h = reader.ReadUInt32();
|
|
this.Unknown_2Ch = reader.ReadUInt32();
|
|
|
|
// read reference data
|
|
this.Animations = reader.ReadBlockAt<ResourcePointerArray64<AnimationMapEntry>>(
|
|
this.AnimationsPointer, // offset
|
|
this.AnimationsMapCapacity
|
|
);
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
// update structure data
|
|
this.AnimationsPointer = (ulong)(this.Animations != null ? this.Animations.FilePosition : 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);
|
|
writer.Write(this.Unknown_04h);
|
|
writer.Write(this.Unknown_08h);
|
|
writer.Write(this.Unknown_0Ch);
|
|
writer.Write(this.Unknown_10h);
|
|
writer.Write(this.Unknown_14h);
|
|
writer.Write(this.AnimationsPointer);
|
|
writer.Write(this.AnimationsMapCapacity);
|
|
writer.Write(this.AnimationsMapEntries);
|
|
writer.Write(this.Unknown_24h);
|
|
writer.Write(this.Unknown_28h);
|
|
writer.Write(this.Unknown_2Ch);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>();
|
|
if (Animations != null) list.Add(Animations);
|
|
return list.ToArray();
|
|
}
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class AnimationMapEntry : ResourceSystemBlock
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 32; }
|
|
}
|
|
|
|
// structure data
|
|
public MetaHash Hash { get; set; }
|
|
public uint Unknown_04h { get; set; } // 0x00000000
|
|
public ulong AnimationPtr { get; set; }
|
|
public ulong NextEntryPtr { get; set; }
|
|
public uint Unknown_18h { get; set; } // 0x00000000
|
|
public uint Unknown_1Ch { get; set; } // 0x00000000
|
|
|
|
// reference data
|
|
public Animation Animation { get; set; }
|
|
public AnimationMapEntry NextEntry { get; set; }
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
// read structure data
|
|
this.Hash = new MetaHash(reader.ReadUInt32());
|
|
this.Unknown_04h = reader.ReadUInt32();
|
|
this.AnimationPtr = reader.ReadUInt64();
|
|
this.NextEntryPtr = reader.ReadUInt64();
|
|
this.Unknown_18h = reader.ReadUInt32();
|
|
this.Unknown_1Ch = reader.ReadUInt32();
|
|
|
|
// read reference data
|
|
this.Animation = reader.ReadBlockAt<Animation>(
|
|
this.AnimationPtr // offset
|
|
);
|
|
this.NextEntry = reader.ReadBlockAt<AnimationMapEntry>(
|
|
this.NextEntryPtr // offset
|
|
);
|
|
|
|
if (Animation != null)
|
|
{
|
|
if (Animation.Hash != 0)
|
|
{ }
|
|
Animation.Hash = Hash;
|
|
}
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
// update structure data
|
|
this.AnimationPtr = (ulong)(this.Animation != null ? this.Animation.FilePosition : 0);
|
|
this.NextEntryPtr = (ulong)(this.NextEntry != null ? this.NextEntry.FilePosition : 0);
|
|
|
|
// write structure data
|
|
writer.Write(this.Hash);
|
|
writer.Write(this.Unknown_04h);
|
|
writer.Write(this.AnimationPtr);
|
|
writer.Write(this.NextEntryPtr);
|
|
writer.Write(this.Unknown_18h);
|
|
writer.Write(this.Unknown_1Ch);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>();
|
|
if (Animation != null) list.Add(Animation);
|
|
if (NextEntry != null) list.Add(NextEntry);
|
|
return list.ToArray();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Hash.ToString();
|
|
}
|
|
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class Animation : ResourceSystemBlock, IMetaXmlItem
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 96; }
|
|
}
|
|
|
|
// structure data
|
|
public uint VFT { get; set; }
|
|
public uint Unknown_04h { get; set; } = 1; // 0x00000001
|
|
public uint Unused_08h { get; set; } // 0x00000000
|
|
public uint Unused_0Ch { get; set; } // 0x00000000
|
|
public byte Unknown_10h { get; set; }
|
|
public byte Unknown_11h { get; set; } = 1; // 0x01
|
|
public ushort Unused_12h { get; set; } // 0x0000
|
|
public ushort Frames { get; set; }
|
|
public ushort SequenceFrameLimit { get; set; }
|
|
public float Duration { get; set; }
|
|
public MetaHash Unknown_1Ch { get; set; }
|
|
public uint Unused_20h { get; set; } // 0x00000000
|
|
public uint Unused_24h { get; set; } // 0x00000000
|
|
public uint Unused_28h { get; set; } // 0x00000000
|
|
public uint Unused_2Ch { get; set; } // 0x00000000
|
|
public uint Unused_30h { get; set; } // 0x00000000
|
|
public uint Unused_34h { get; set; } // 0x00000000
|
|
public uint MaxSeqBlockLength { get; set; }
|
|
public uint UsageCount { get; set; }
|
|
public ResourcePointerList64<Sequence> Sequences { get; set; }
|
|
public ResourceSimpleList64_s<AnimationBoneId> BoneIds { get; set; }
|
|
|
|
public YcdFile Ycd { get; set; }
|
|
|
|
public MetaHash Hash { get; set; } //updated by CW, for use when reading/writing files
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
// read structure data
|
|
this.VFT = reader.ReadUInt32();
|
|
this.Unknown_04h = reader.ReadUInt32(); //1 1 1 1
|
|
this.Unused_08h = reader.ReadUInt32(); //0 0 0 0
|
|
this.Unused_0Ch = reader.ReadUInt32(); //0 0 0 0
|
|
this.Unknown_10h = reader.ReadByte(); //1 1 1 1
|
|
this.Unknown_11h = reader.ReadByte(); //1
|
|
this.Unused_12h = reader.ReadUInt16(); //0 0 0 0
|
|
this.Frames = reader.ReadUInt16(); //221 17 151 201 frames
|
|
this.SequenceFrameLimit = reader.ReadUInt16(); //223 31 159 207 sequence limit
|
|
this.Duration = reader.ReadSingle(); //7.34 0.53 5.0 6.66 duration
|
|
this.Unknown_1Ch = reader.ReadUInt32();
|
|
this.Unused_20h = reader.ReadUInt32(); //0 0 0 0
|
|
this.Unused_24h = reader.ReadUInt32(); //0 0 0 0
|
|
this.Unused_28h = reader.ReadUInt32(); //0 0 0 0
|
|
this.Unused_2Ch = reader.ReadUInt32(); //0 0 0 0
|
|
this.Unused_30h = reader.ReadUInt32(); //0 0 0 0
|
|
this.Unused_34h = reader.ReadUInt32(); //0 0 0 0
|
|
this.MaxSeqBlockLength = reader.ReadUInt32(); //314 174 1238 390 maximum sequence block size
|
|
this.UsageCount = reader.ReadUInt32(); //2 2 2 2
|
|
this.Sequences = reader.ReadBlock<ResourcePointerList64<Sequence>>();
|
|
this.BoneIds = reader.ReadBlock<ResourceSimpleList64_s<AnimationBoneId>>();
|
|
|
|
AssignSequenceBoneIds();
|
|
|
|
|
|
|
|
//bool hasUVs = false;
|
|
//if (BoneIds?.data_items != null)
|
|
//{
|
|
// foreach (var boneid in BoneIds.data_items)
|
|
// {
|
|
// if (boneid.Track == 17)//UV0
|
|
// { hasUVs = true; }
|
|
// if (boneid.Track == 18)//UV1
|
|
// { hasUVs = true; }
|
|
// }
|
|
//}
|
|
|
|
//bool hasRootMotion = false; // (Unknown_10h & 16) == hasRootMotion
|
|
//if (Sequences?.data_items != null)
|
|
//{
|
|
// foreach (var seq in Sequences.data_items)
|
|
// {
|
|
// if (seq == null) continue;
|
|
// if (seq.RootMotionRefCounts != 0) { hasRootMotion = true; }
|
|
// }
|
|
//}
|
|
|
|
//var b0 = (Unknown_1Ch) & 0xFF;
|
|
//var b1 = (Unknown_1Ch >> 8) & 0xFF;
|
|
//var b2 = (Unknown_1Ch >> 16) & 0xFF;
|
|
//var b3 = (Unknown_1Ch >> 24) & 0xFF;
|
|
//if (hasUVs)
|
|
//{
|
|
// if (Unknown_1Ch != 0x6B002400)
|
|
// { }
|
|
//}
|
|
//else
|
|
//{
|
|
//}
|
|
|
|
|
|
//switch (Unknown_10h)
|
|
//{
|
|
// case 0:
|
|
// if (hasRootMotion) { }
|
|
// break;
|
|
// case 1://is prop?
|
|
// if (hasRootMotion) { }
|
|
// break;
|
|
// case 8:
|
|
// if (hasRootMotion) { }
|
|
// break;
|
|
// case 16:
|
|
// if (!hasRootMotion) { }
|
|
// break;
|
|
// case 24:
|
|
// if (!hasRootMotion) { }
|
|
// break;
|
|
// default: break;
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//if (Unknown_04h != 1)
|
|
//{ }
|
|
//if (Unknown_11h != 1)
|
|
//{ }
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
//BuildSequencesData();
|
|
|
|
// write structure data
|
|
writer.Write(this.VFT);
|
|
writer.Write(this.Unknown_04h);
|
|
writer.Write(this.Unused_08h);
|
|
writer.Write(this.Unused_0Ch);
|
|
writer.Write(this.Unknown_10h);
|
|
writer.Write(this.Unknown_11h);
|
|
writer.Write(this.Unused_12h);
|
|
writer.Write(this.Frames);
|
|
writer.Write(this.SequenceFrameLimit);
|
|
writer.Write(this.Duration);
|
|
writer.Write(this.Unknown_1Ch);
|
|
writer.Write(this.Unused_20h);
|
|
writer.Write(this.Unused_24h);
|
|
writer.Write(this.Unused_28h);
|
|
writer.Write(this.Unused_2Ch);
|
|
writer.Write(this.Unused_30h);
|
|
writer.Write(this.Unused_34h);
|
|
writer.Write(this.MaxSeqBlockLength);
|
|
writer.Write(this.UsageCount);
|
|
writer.WriteBlock(this.Sequences);
|
|
writer.WriteBlock(this.BoneIds);
|
|
}
|
|
|
|
public override Tuple<long, IResourceBlock>[] GetParts()
|
|
{
|
|
BuildSequencesData();//TODO: move this somewhere better?
|
|
|
|
return new Tuple<long, IResourceBlock>[] {
|
|
new Tuple<long, IResourceBlock>(0x40, Sequences),
|
|
new Tuple<long, IResourceBlock>(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 BuildSequencesData()
|
|
{
|
|
AssignSequenceBoneIds();
|
|
|
|
if (Sequences?.data_items != null)
|
|
{
|
|
foreach (var seq in Sequences.data_items)
|
|
{
|
|
seq.BuildData();
|
|
}
|
|
}
|
|
|
|
CalculateMaxSeqBlockLength();
|
|
}
|
|
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
YcdXml.StringTag(sb, indent, "Hash", YcdXml.HashString(Hash));
|
|
YcdXml.ValueTag(sb, indent, "Unknown10", Unknown_10h.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.WriteItemArray(sb, BoneIds?.data_items, indent, "BoneIds");
|
|
YcdXml.WriteItemArray(sb, Sequences?.data_items, indent, "Sequences");
|
|
}
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
Hash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Hash"));
|
|
Unknown_10h = (byte)Xml.GetChildUIntAttribute(node, "Unknown10", "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"));
|
|
|
|
BoneIds = new ResourceSimpleList64_s<AnimationBoneId>();
|
|
BoneIds.data_items = XmlMeta.ReadItemArray<AnimationBoneId>(node, "BoneIds");
|
|
|
|
Sequences = new ResourcePointerList64<Sequence>();
|
|
Sequences.data_items = XmlMeta.ReadItemArrayNullable<Sequence>(node, "Sequences");
|
|
|
|
AssignSequenceBoneIds();
|
|
}
|
|
|
|
|
|
|
|
public struct FramePosition
|
|
{
|
|
public int Frame0;
|
|
public int Frame1;
|
|
public float Alpha0;
|
|
public float Alpha1;
|
|
}
|
|
public FramePosition GetFramePosition(float t)
|
|
{
|
|
bool ignoreLastFrame = true;//if last frame is equivalent to the first one, eg rollercoaster small light "globes" don't
|
|
|
|
FramePosition p = new FramePosition();
|
|
var nframes = (ignoreLastFrame) ? (Frames - 1) : Frames;
|
|
|
|
var curPos = (t / Duration) * nframes;
|
|
p.Frame0 = ((ushort)curPos) % Frames;
|
|
p.Frame1 = (p.Frame0 + 1);// % frames;
|
|
p.Alpha1 = (float)(curPos - Math.Floor(curPos));
|
|
p.Alpha0 = 1.0f - p.Alpha1;
|
|
|
|
return p;
|
|
}
|
|
public Vector4 EvaluateVector4(FramePosition frame, int boneIndex, bool interpolate)
|
|
{
|
|
var s = frame.Frame0 / SequenceFrameLimit;
|
|
int f0 = frame.Frame0 % SequenceFrameLimit;
|
|
int f1 = f0 + 1;
|
|
var seq = Sequences.data_items[s];
|
|
var aseq = seq.Sequences[boneIndex];
|
|
var v0 = aseq.EvaluateVector(f0);
|
|
var v1 = aseq.EvaluateVector(f1);
|
|
var v = interpolate ? (v0 * frame.Alpha0) + (v1 * frame.Alpha1) : v0;
|
|
return v;
|
|
}
|
|
public Quaternion EvaluateQuaternion(FramePosition frame, int boneIndex, bool interpolate)
|
|
{
|
|
var s = frame.Frame0 / SequenceFrameLimit;
|
|
int f0 = frame.Frame0 % SequenceFrameLimit;
|
|
int f1 = f0 + 1;
|
|
var seq = Sequences.data_items[s];
|
|
var aseq = seq.Sequences[boneIndex];
|
|
var q0 = aseq.EvaluateQuaternion(f0);
|
|
var q1 = aseq.EvaluateQuaternion(f1);
|
|
var q = interpolate ? Quaternion.Slerp(q0, q1, frame.Alpha1) : q0;
|
|
return q;
|
|
}
|
|
|
|
public int FindBoneIndex(ushort boneTag, byte track)
|
|
{
|
|
//TODO: make this use a dict??
|
|
if (BoneIds?.data_items != null)
|
|
{
|
|
for (int i = 0; i < BoneIds.data_items.Length; i++)
|
|
{
|
|
var b = BoneIds.data_items[i];
|
|
if ((b.BoneId == boneTag) && (b.Track == track)) return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
[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");
|
|
}
|
|
}
|
|
|
|
public enum AnimChannelType : int
|
|
{
|
|
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 int DataOffset { get; set; }
|
|
public int FrameOffset { get; set; }
|
|
|
|
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 int GetReferenceIndex()
|
|
{ return Index; }
|
|
public virtual int GetFrameBits()
|
|
{ return 0; }
|
|
|
|
public virtual float EvaluateFloat(int frame) => 0.0f;
|
|
|
|
public void Associate(int sequence, int index)
|
|
{
|
|
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<AnimChannelType>(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;
|
|
}
|
|
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Sequence.ToString() + ": " + Index.ToString() + ": " + Type.ToString() + " DataOffset: " + DataOffset.ToString() + " FrameOffset: " + FrameOffset.ToString();
|
|
}
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelStaticFloat : AnimChannel
|
|
{
|
|
public float Value { get; set; }
|
|
|
|
public AnimChannelStaticFloat()
|
|
{
|
|
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 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 AnimChannelStaticVector3()
|
|
{
|
|
Type = AnimChannelType.StaticVector3;
|
|
}
|
|
|
|
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");
|
|
}
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class AnimChannelStaticQuaternion : AnimChannel
|
|
{
|
|
public Quaternion Value { get; set; }
|
|
|
|
public AnimChannelStaticQuaternion()
|
|
{
|
|
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
|
|
}
|
|
|
|
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"));
|
|
}
|
|
}
|
|
[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[] ValueList { get; set; }
|
|
public uint[] Frames { get; set; }
|
|
|
|
|
|
public AnimChannelIndirectQuantizeFloat()
|
|
{
|
|
Type = AnimChannelType.IndirectQuantizeFloat;
|
|
}
|
|
|
|
public override void Read(AnimChannelDataReader reader)
|
|
{
|
|
FrameBits = reader.ReadInt32();
|
|
ValueBits = reader.ReadInt32();
|
|
NumInts = reader.ReadInt32();
|
|
Quantum = reader.ReadSingle();
|
|
Offset = reader.ReadSingle();
|
|
|
|
Frames = new uint[reader.NumFrames];
|
|
|
|
var numValues0 = (NumInts * 32) / ValueBits;
|
|
var numValues1 = (1u << FrameBits) - 1;
|
|
var numValues = Math.Min(numValues0, numValues1); //any better way to calculate this?
|
|
Values = new float[numValues];
|
|
ValueList = new uint[numValues];
|
|
reader.BitPosition = reader.Position * 8;
|
|
for (int i = 0; i < numValues; i++)
|
|
{
|
|
uint bits = reader.ReadBits(ValueBits);
|
|
Values[i] = (bits * Quantum) + Offset;
|
|
ValueList[i] = bits;
|
|
}
|
|
reader.Position += NumInts * 4;
|
|
|
|
|
|
//var endBit = bit + (NumInts * 32);
|
|
//var valueList = new List<float>();
|
|
//while (bit < endBit) // this actually seems to be reading too far.....
|
|
//{
|
|
// valueList.Add((reader.GetBit(bit, ValueBits) * Quantum) + Offset);
|
|
// bit += ValueBits;
|
|
//}
|
|
//Values = valueList.ToArray();
|
|
|
|
if (FrameBits < 2)
|
|
{ }
|
|
if (ValueBits < 3)
|
|
{ }
|
|
|
|
}
|
|
public override void Write(AnimChannelDataWriter writer)
|
|
{
|
|
var frameBits = Math.Max(writer.BitCount((uint)((Values?.Length ?? 1))), 2);// Math.Max(writer.BitCount(Frames), 2);
|
|
//if ((frameBits != FrameBits)&&(ValueList!=null))
|
|
//{ } // ######### DEBUG TEST
|
|
FrameBits = frameBits;
|
|
|
|
var valueCount = Values?.Length ?? 0;
|
|
var valueList = new uint[valueCount];
|
|
for (int i = 0; i < valueCount; i++)
|
|
{
|
|
var bits = GetQuanta(Values[i]);
|
|
valueList[i] = bits;
|
|
|
|
//if (ValueList != null) // ######### DEBUG TEST
|
|
//{
|
|
// var testbits = ValueList[i];
|
|
// if (bits != testbits)
|
|
// { }
|
|
//}
|
|
}
|
|
var valueBits = Math.Max(writer.BitCount(valueList), 3);
|
|
//if ((valueBits != ValueBits)&&(ValueList!=null))
|
|
//{ }// ######### DEBUG TEST
|
|
ValueBits = valueBits;
|
|
|
|
writer.ResetBitstream();
|
|
for (int i = 0; i < valueCount; i++)
|
|
{
|
|
var u = valueList[i];
|
|
writer.WriteBits(u, ValueBits);
|
|
}
|
|
|
|
NumInts = writer.Bitstream.Count;
|
|
|
|
|
|
writer.Write(FrameBits);
|
|
writer.Write(ValueBits);
|
|
writer.Write(NumInts);
|
|
writer.Write(Quantum);
|
|
writer.Write(Offset);
|
|
writer.WriteBitstream();
|
|
}
|
|
|
|
public override void ReadFrame(AnimChannelDataReader reader)
|
|
{
|
|
Frames[reader.Frame] = reader.ReadFrameBits(FrameBits);
|
|
}
|
|
public override void WriteFrame(AnimChannelDataWriter writer)
|
|
{
|
|
writer.WriteFrameBits(Frames[writer.Frame], FrameBits);
|
|
}
|
|
|
|
public override int GetFrameBits()
|
|
{
|
|
return FrameBits;
|
|
}
|
|
|
|
|
|
private uint GetQuanta(float v)
|
|
{
|
|
var q = (v - Offset) / Quantum;
|
|
return (uint)(q + 0.5f);
|
|
//return (uint)Math.Round(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
|
|
{
|
|
public int ValueBits { get; set; }
|
|
public float Quantum { get; set; }
|
|
public float Offset { get; set; }
|
|
public float[] Values { get; set; }
|
|
public uint[] ValueList { get; set; }
|
|
|
|
public AnimChannelQuantizeFloat()
|
|
{
|
|
Type = AnimChannelType.QuantizeFloat;
|
|
}
|
|
|
|
public override void Read(AnimChannelDataReader reader)
|
|
{
|
|
ValueBits = reader.ReadInt32();
|
|
Quantum = reader.ReadSingle();
|
|
Offset = reader.ReadSingle();
|
|
Values = new float[reader.NumFrames];
|
|
ValueList = new uint[reader.NumFrames];
|
|
|
|
if (ValueBits < 1)
|
|
{ }
|
|
}
|
|
public override void Write(AnimChannelDataWriter writer)
|
|
{
|
|
var valueCount = Values?.Length ?? 0;
|
|
var valueList = new uint[valueCount];
|
|
for (int i = 0; i < valueCount; i++)
|
|
{
|
|
var bits = GetQuanta(Values[i]);
|
|
valueList[i] = bits;
|
|
|
|
//if (ValueList != null) // ######### DEBUG TEST
|
|
//{
|
|
// var testbits = ValueList[i];
|
|
// if (bits != testbits)
|
|
// { }
|
|
//}
|
|
}
|
|
var valueBits = Math.Max(writer.BitCount(valueList), 1);
|
|
//if ((valueBits != ValueBits)&&(ValueList!=null))
|
|
//{ } // ######### DEBUG TEST
|
|
ValueBits = valueBits;
|
|
|
|
writer.Write(ValueBits);
|
|
writer.Write(Quantum);
|
|
writer.Write(Offset);
|
|
}
|
|
|
|
public override void ReadFrame(AnimChannelDataReader reader)
|
|
{
|
|
uint bits = reader.ReadFrameBits(ValueBits);
|
|
float val = (bits * Quantum) + Offset;
|
|
Values[reader.Frame] = val;
|
|
ValueList[reader.Frame] = bits;
|
|
}
|
|
public override void WriteFrame(AnimChannelDataWriter writer)
|
|
{
|
|
uint bits = GetQuanta(Values[writer.Frame]);
|
|
writer.WriteFrameBits(bits, ValueBits);
|
|
}
|
|
|
|
public override int GetFrameBits()
|
|
{
|
|
return ValueBits;
|
|
}
|
|
|
|
|
|
private uint GetQuanta(float v)
|
|
{
|
|
var q = (v - Offset) / Quantum;
|
|
return (uint)(q + 0.5f);
|
|
//return (uint)Math.Round(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; }
|
|
public int[] ValueList { get; set; }
|
|
|
|
|
|
public AnimChannelLinearFloat()
|
|
{
|
|
Type = AnimChannelType.LinearFloat;
|
|
}
|
|
|
|
public override void Read(AnimChannelDataReader reader)
|
|
{
|
|
NumInts = reader.ReadInt32();
|
|
Counts = reader.ReadInt32();
|
|
Quantum = reader.ReadSingle();
|
|
Offset = reader.ReadSingle();
|
|
|
|
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 streamLength = (reader.Data?.Length ?? 0) * 8;
|
|
var numFrames = reader.NumFrames;
|
|
var chunkSize = reader.ChunkSize;//64 or 255(-1?)
|
|
var numChunks = (ushort)((chunkSize + numFrames - 1) / chunkSize);
|
|
var deltaOffset = Bit + (numChunks * (Count1 + Count2));//base offset to delta bits
|
|
|
|
reader.BitPosition = Bit;
|
|
var chunkOffsets = new int[numChunks];
|
|
var chunkValues = new int[numChunks];
|
|
var frameValues = new float[numFrames];
|
|
var frameBits = new int[numFrames];
|
|
for (int i = 0; i < numChunks; i++)
|
|
{
|
|
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;
|
|
frameBits[frame] = value;
|
|
|
|
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 = streamLength;//Math.Min(so + 32 - Count3, streamLength); //
|
|
uint b = 0;
|
|
while (b == 0) // scan for a '1' bit
|
|
{
|
|
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;
|
|
ValueList = frameBits;
|
|
|
|
//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?
|
|
//}
|
|
|
|
|
|
reader.Position -= 16;//TODO: fix this?
|
|
reader.Position += NumInts * 4;
|
|
}
|
|
public override void Write(AnimChannelDataWriter writer)
|
|
{
|
|
//TODO: fix this!
|
|
var numFrames = writer.NumFrames;
|
|
var numChunks = (ushort)((64 + numFrames - 1) / 64);//default value, if chunks used, chunkSize is always 64!
|
|
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 int[valueCount];
|
|
for (int i = 0; i < valueCount; i++)
|
|
{
|
|
var bits = GetQuanta(Values[i]);
|
|
valueList[i] = bits;
|
|
|
|
//if (ValueList != null) // ######### DEBUG TEST
|
|
//{
|
|
// var testbits = ValueList[i];
|
|
// if (bits != testbits)
|
|
// { }
|
|
//}
|
|
}
|
|
|
|
|
|
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 < valueCount) ? valueList[cframe] : valueList[0];
|
|
var cdeltas = new int[chunkSize];
|
|
var cinc = 0;
|
|
chunkValues[i] = (uint)cvalue;
|
|
chunkDeltas[i] = cdeltas;
|
|
for (int j = 1; j < chunkSize; j++)
|
|
{
|
|
int frame = cframe + j;
|
|
if (frame >= numFrames) break;
|
|
var value = valueList[frame];
|
|
var inc = value - 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) ? 2u : 1u);
|
|
}
|
|
}
|
|
Count1 = writer.BitCount(chunkOffsets); //number of offset bits for each chunk
|
|
Count2 = writer.BitCount(chunkValues); //number of value bits for each chunk
|
|
|
|
|
|
|
|
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)
|
|
{
|
|
writer.WriteBits(1, 1);//sign bit
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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 int GetQuanta(float v)
|
|
{
|
|
var q = (v - Offset) / Quantum;
|
|
return (int)(q + 0.5f);
|
|
//return (uint)Math.Round(Math.Max(q, 0));//any better way?
|
|
}
|
|
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
//base.WriteXml(sb, indent);
|
|
|
|
Type = AnimChannelType.QuantizeFloat;//TODO - FIX! temporary: 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 AnimChannelRawFloat : AnimChannel
|
|
{
|
|
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<float>(MetaTypes.ConvertToBytes(bits));
|
|
Values[reader.Frame] = v;
|
|
}
|
|
public override void WriteFrame(AnimChannelDataWriter writer)
|
|
{
|
|
float v = Values[writer.Frame];
|
|
var b = BitConverter.GetBytes(v);
|
|
var bits = BitConverter.ToUInt32(b, 0);
|
|
writer.WriteFrameBits(bits, 32);
|
|
}
|
|
|
|
public override int GetFrameBits()
|
|
{
|
|
return 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;
|
|
|
|
public float[] Values
|
|
{
|
|
get
|
|
{
|
|
if (valueCache != null)
|
|
{
|
|
return valueCache;
|
|
}
|
|
|
|
valueCache = new float[blockStream.NumFrames];
|
|
|
|
var channels = new AnimChannel[3];
|
|
var ch = 0;
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (i != 3)
|
|
{
|
|
channels[ch] = blockStream.Sequences[Sequence].Channels[i];
|
|
ch++;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < valueCache.Length; i++)
|
|
{
|
|
var vec = new Vector3(
|
|
channels[0].EvaluateFloat(i),
|
|
channels[1].EvaluateFloat(i),
|
|
channels[2].EvaluateFloat(i)
|
|
);
|
|
|
|
valueCache[i] = (float)Math.Sqrt(Math.Max(1.0f - vec.LengthSquared(), 0.0));
|
|
}
|
|
|
|
return valueCache;
|
|
}
|
|
}
|
|
|
|
public int QuatIndex { get; set; }
|
|
|
|
public AnimChannelCachedQuaternion(AnimChannelType type)
|
|
{
|
|
Type = type;
|
|
}
|
|
|
|
public override void Read(AnimChannelDataReader reader)
|
|
{
|
|
this.blockStream = reader;
|
|
}
|
|
|
|
public override int GetReferenceIndex()
|
|
{
|
|
return QuatIndex;
|
|
}
|
|
|
|
public override float EvaluateFloat(int frame)
|
|
{
|
|
if (Values?.Length > 0) return Values[frame % Values.Length];
|
|
return 0.0f;
|
|
}
|
|
|
|
public override void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
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...
|
|
}
|
|
}
|
|
public class AnimChannelDataReader
|
|
{
|
|
public byte[] Data { get; set; }
|
|
public ushort NumFrames { 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;
|
|
ChunkSize = chunkSize;
|
|
Position = 0;
|
|
Frame = 0;
|
|
FrameOffset = frameOffset;
|
|
FrameLength = frameLength;
|
|
ChannelListOffset = (int)FrameOffset + (FrameLength * NumFrames);
|
|
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 byte[] ReadChannelDataBytes(int n)
|
|
{
|
|
var r = new byte[n];
|
|
Buffer.BlockCopy(Data, ChannelDataOffset, r, 0, n);
|
|
ChannelDataOffset += n;
|
|
return r;
|
|
}
|
|
|
|
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 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<uint> ChannelFrameStream { get; private set; } = new List<uint>(); //frame bits stream.
|
|
public List<uint[]> ChannelFrames { get; private set; } = new List<uint[]>();//bitstreams for each frame
|
|
|
|
public List<uint> Bitstream { get; private set; } = new List<uint>(); //temporary bitstream, used from WriteBits()
|
|
public int BitstreamPos { get; set; } = 0;
|
|
|
|
public AnimChannelDataWriter(ushort numFrames)
|
|
{
|
|
Position = 0;
|
|
Frame = 0;
|
|
NumFrames = numFrames;
|
|
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 sequenceCount)
|
|
{
|
|
int remainder = channelCount % 4;
|
|
if (remainder > 0)
|
|
{
|
|
ushort writeval = (ushort)(sequenceCount << 2);
|
|
int addamt = (4 - remainder);
|
|
for (int i = 0; i < addamt; i++)
|
|
{
|
|
WriteChannelItemData(writeval);
|
|
}
|
|
}
|
|
}
|
|
public void WriteChannelItemDataBytes(byte[] data)
|
|
{
|
|
if (data?.Length > 0)
|
|
{
|
|
ChannelItemWriter.Write(data);
|
|
ChannelItemOffset += data.Length;
|
|
}
|
|
}
|
|
|
|
|
|
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<uint> 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 EvaluateQuaternionType7(int frame)
|
|
{
|
|
if (!IsType7Quat)
|
|
{
|
|
return new Quaternion(
|
|
Channels[0].EvaluateFloat(frame),
|
|
Channels[1].EvaluateFloat(frame),
|
|
Channels[2].EvaluateFloat(frame),
|
|
Channels[3].EvaluateFloat(frame)
|
|
);
|
|
}
|
|
|
|
var t7 = Channels[3] as AnimChannelCachedQuaternion;//type 1
|
|
if (t7 == null) t7 = Channels[4] as AnimChannelCachedQuaternion;//type 2
|
|
|
|
var x = Channels[0].EvaluateFloat(frame);
|
|
var y = Channels[1].EvaluateFloat(frame);
|
|
var z = Channels[2].EvaluateFloat(frame);
|
|
var normalized = t7.EvaluateFloat(frame);
|
|
|
|
switch (t7.QuatIndex)
|
|
{
|
|
case 0:
|
|
return new Quaternion(normalized, x, y, z);
|
|
case 1:
|
|
return new Quaternion(x, normalized, y, z);
|
|
case 2:
|
|
return new Quaternion(x, y, normalized, z);
|
|
case 3:
|
|
return new Quaternion(x, y, z, normalized);
|
|
default:
|
|
return Quaternion.Identity;
|
|
}
|
|
}
|
|
|
|
public Quaternion EvaluateQuaternion(int frame)
|
|
{
|
|
if (IsType7Quat) return EvaluateQuaternionType7(frame);
|
|
return EvaluateVector(frame).ToQuaternion();
|
|
}
|
|
|
|
public Vector4 EvaluateVector(int frame)
|
|
{
|
|
if (Channels == null) return Vector4.Zero;
|
|
if (IsType7Quat) return Quaternion.Normalize(EvaluateQuaternionType7(frame)).ToVector4();//normalization shouldn't be necessary, but saves explosions in case of incorrectness
|
|
var v = Vector4.Zero;
|
|
int c = 0;
|
|
for (int i = 0; i < Channels.Length; i++)
|
|
{
|
|
if (c >= 4) break;
|
|
var channel = Channels[i];
|
|
var sv3c = channel as AnimChannelStaticVector3;
|
|
var ssqc = channel as AnimChannelStaticQuaternion;
|
|
if (sv3c != null)
|
|
{
|
|
for (int n = 0; n < 3; n++)
|
|
{
|
|
if ((c + n) >= 4) break;
|
|
v[c + n] = sv3c.Value[n];
|
|
}
|
|
c += 3;
|
|
}
|
|
else if (ssqc != null)
|
|
{
|
|
for (int n = 0; n < 4; n++)
|
|
{
|
|
if ((c + n) >= 4) break;
|
|
v[c + n] = ssqc.Value[n];
|
|
}
|
|
c += 4;
|
|
}
|
|
else
|
|
{
|
|
v[c] = channel.EvaluateFloat(frame);
|
|
c++;
|
|
}
|
|
}
|
|
return v;
|
|
}
|
|
|
|
|
|
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<AnimChannel>(node, "Channels");
|
|
var chansNode = node.SelectSingleNode("Channels");
|
|
if (chansNode != null)
|
|
{
|
|
var inodes = chansNode.SelectNodes("Item");
|
|
if (inodes?.Count > 0)
|
|
{
|
|
var clist = new List<AnimChannel>();
|
|
foreach (XmlNode inode in inodes)
|
|
{
|
|
var type = Xml.GetEnumValue<AnimChannelType>(Xml.GetChildStringAttribute(inode, "Type", "value"));
|
|
var c = AnimChannel.ConstructChannel(type);
|
|
c.ReadXml(inode);
|
|
clist.Add(c);
|
|
}
|
|
Channels = clist.ToArray();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "AnimSequence: " + (Channels?.Length??0).ToString() + " channels";
|
|
}
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class SequenceRootChannelRef : IMetaXmlItem
|
|
{
|
|
public byte[] Bytes { get; set; }
|
|
public byte ChannelType { get { return Bytes[0]; } set { Bytes[0] = value; } }
|
|
public byte ChannelIndex { get { return Bytes[1]; } set { Bytes[1] = value; } }
|
|
public ushort DataIntOffset
|
|
{
|
|
get { return (ushort)(Bytes[2] + (Bytes[3] << 8)); }
|
|
set
|
|
{
|
|
Bytes[2] = (byte)(value & 0xFF);
|
|
Bytes[3] = (byte)((value >> 8) & 0xFF);
|
|
}
|
|
}
|
|
public ushort FrameBitOffset
|
|
{
|
|
get { return (ushort)(Bytes[4] + (Bytes[5] << 8)); }
|
|
set
|
|
{
|
|
Bytes[4] = (byte)(value & 0xFF);
|
|
Bytes[5] = (byte)((value >> 8) & 0xFF);
|
|
}
|
|
}
|
|
|
|
|
|
public SequenceRootChannelRef()
|
|
{
|
|
Bytes = new byte[6];
|
|
}
|
|
public SequenceRootChannelRef(AnimChannelType type, int channelIndex)
|
|
{
|
|
Bytes = new byte[6];
|
|
ChannelType = (byte)type;
|
|
ChannelIndex = (byte)channelIndex;
|
|
}
|
|
public SequenceRootChannelRef(byte[] bytes)
|
|
{
|
|
Bytes = bytes;
|
|
}
|
|
public override string ToString()
|
|
{
|
|
if (Bytes?.Length >= 6)
|
|
{
|
|
return ChannelType.ToString() + ", " + ChannelIndex.ToString() + ", " + DataIntOffset.ToString() + ", " + FrameBitOffset.ToString();
|
|
}
|
|
return "(empty)";
|
|
}
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
YcdXml.WriteRawArray(sb, Bytes, indent, "Bytes", "", YcdXml.FormatHexByte, 6);
|
|
}
|
|
public void ReadXml(XmlNode node)
|
|
{
|
|
Bytes = Xml.GetChildRawByteArray(node, "Bytes");
|
|
}
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class Sequence : ResourceSystemBlock, IMetaXmlItem
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
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 frame data items / bytes
|
|
public uint RootMotionRefsOffset { get; set; } //offset to root motion items (relative to start of the chunk, -32), ==BlockLength when no root motion
|
|
public ushort Unused_14h { get; set; } //0x0000
|
|
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 RootMotionRefCounts { get; set; } //0|17|20|21|49|52|53 0x11|0x14|0x15|0x31|0x34|0x35
|
|
public byte[] Data { get; set; }
|
|
|
|
|
|
|
|
// parsed data
|
|
public AnimSequence[] Sequences { get; set; }
|
|
|
|
public SequenceRootChannelRef[] RootPositionRefs { get; set; }
|
|
public SequenceRootChannelRef[] RootRotationRefs { get; set; }
|
|
public int RootPositionRefCount
|
|
{
|
|
get { return (RootMotionRefCounts >> 4) & 0xF; }
|
|
set
|
|
{
|
|
var rrc = RootMotionRefCounts & 0xF;
|
|
RootMotionRefCounts = (byte)(rrc + ((value & 0xF) << 4));
|
|
}
|
|
}
|
|
public int RootRotationRefCount
|
|
{
|
|
get { return RootMotionRefCounts & 0xF; }
|
|
set
|
|
{
|
|
var rpc = (RootMotionRefCounts >> 4) & 0xF;
|
|
RootMotionRefCounts = (byte)(rpc + (value & 0xF));
|
|
}
|
|
}
|
|
|
|
|
|
class AnimChannelListItem
|
|
{
|
|
public int Sequence;
|
|
public int Index;
|
|
public AnimChannel Channel;
|
|
public AnimChannelListItem(int seq, int ind, AnimChannel channel)
|
|
{
|
|
Sequence = seq;
|
|
Index = ind;
|
|
Channel = channel;
|
|
}
|
|
}
|
|
|
|
|
|
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.RootMotionRefsOffset = 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.RootMotionRefCounts = reader.ReadByte(); //0 0 0 0
|
|
|
|
this.Data = reader.ReadBytes((int)DataLength);
|
|
|
|
ParseData();
|
|
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
//BuildData should be called before this
|
|
|
|
// write structure data
|
|
writer.Write(this.Unknown_00h);
|
|
writer.Write(this.DataLength);
|
|
writer.Write(this.Unused_08h);
|
|
writer.Write(this.FrameOffset);
|
|
writer.Write(this.RootMotionRefsOffset);
|
|
writer.Write(this.Unused_14h);
|
|
writer.Write(this.NumFrames);
|
|
writer.Write(this.FrameLength);
|
|
writer.Write(this.IndirectQuantizeFloatNumInts);
|
|
writer.Write(this.QuantizeFloatValueBits);
|
|
writer.Write(this.ChunkSize);
|
|
writer.Write(this.RootMotionRefCounts);
|
|
writer.Write(this.Data);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Unknown_00h.ToString() + ": " + DataLength.ToString();
|
|
}
|
|
|
|
|
|
|
|
public void ParseData()
|
|
{
|
|
var reader = new AnimChannelDataReader(Data, NumFrames, ChunkSize, FrameOffset, FrameLength);
|
|
var channelList = new List<AnimChannelListItem>();
|
|
var channelLists = new AnimChannel[9][];
|
|
var frameOffset = 0;
|
|
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);
|
|
var channelDataBit = reader.ReadChannelDataBits();
|
|
if (channel != null)//read channel sequences and indexes
|
|
{
|
|
channel.DataOffset = reader.Position / 4;
|
|
channel.Read(reader);
|
|
channels[c] = channel;
|
|
var sequence = channelDataBit >> 2;
|
|
var index = channelDataBit & 3;
|
|
if (channel is AnimChannelCachedQuaternion t7)
|
|
{
|
|
t7.QuatIndex = index;
|
|
index = (channel.Type == AnimChannelType.CachedQuaternion1) ? 3 : 4;
|
|
}
|
|
channel.Associate(sequence, index);
|
|
channelList.Add(new AnimChannelListItem(sequence, index, channel));
|
|
channel.FrameOffset = frameOffset;
|
|
frameOffset += channel.GetFrameBits();
|
|
}
|
|
}
|
|
reader.AlignChannelDataOffset(channelCount);
|
|
channelLists[i] = channels;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
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].Type == AnimChannelType.CachedQuaternion1)// is AnimChannelCachedQuaternion)
|
|
{
|
|
Sequences[i].IsType7Quat = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
reader.Sequences = Sequences;
|
|
|
|
|
|
|
|
int numPosRefs = RootPositionRefCount;
|
|
int numRotRefs = RootRotationRefCount;
|
|
if (numPosRefs > 0)
|
|
{
|
|
RootPositionRefs = new SequenceRootChannelRef[numPosRefs];
|
|
for (int i = 0; i < numPosRefs; i++)
|
|
{
|
|
var pref = new SequenceRootChannelRef(reader.ReadChannelDataBytes(6));
|
|
RootPositionRefs[i] = pref;
|
|
}
|
|
}
|
|
if (numRotRefs > 0)
|
|
{
|
|
RootRotationRefs = new SequenceRootChannelRef[numRotRefs];
|
|
for (int i = 0; i < numRotRefs; i++)
|
|
{
|
|
var rref = new SequenceRootChannelRef(reader.ReadChannelDataBytes(6));
|
|
RootRotationRefs[i] = rref;
|
|
}
|
|
}
|
|
if (reader.ChannelDataOffset != Data.Length)
|
|
{
|
|
var brem = Data.Length - reader.ChannelDataOffset;
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
public void BuildData()
|
|
{
|
|
// convert parsed sequences into Data byte array............
|
|
|
|
if (Sequences == null) return;//this shouldn't happen...
|
|
|
|
var writer = new AnimChannelDataWriter(NumFrames);
|
|
|
|
var channelLists = new List<AnimChannel>[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<AnimChannel>();
|
|
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);
|
|
if (channel != null)
|
|
{
|
|
channel.DataOffset = writer.Position / 4;
|
|
channel?.Write(writer);
|
|
}
|
|
}
|
|
writer.AlignChannelItemData(channelCount, Sequences.Length);
|
|
}
|
|
|
|
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 frameOffset = 0;
|
|
for (int i = 0; i < channelLists.Length; i++)
|
|
{
|
|
var chanList = channelLists[i];
|
|
if (chanList == null) continue;
|
|
for (int c = 0; c < chanList.Count; c++)
|
|
{
|
|
var chan = chanList[c];
|
|
if (chan == null) continue;
|
|
chan.FrameOffset = frameOffset;
|
|
frameOffset += chan.GetFrameBits();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
UpdateRootMotionRefs();
|
|
if (RootPositionRefs != null)
|
|
{
|
|
for (int i = 0; i < RootPositionRefs.Length; i++)
|
|
{
|
|
writer.WriteChannelItemDataBytes(RootPositionRefs[i].Bytes);
|
|
}
|
|
}
|
|
if (RootRotationRefs != null)
|
|
{
|
|
for (int i = 0; i < RootRotationRefs.Length; i++)
|
|
{
|
|
writer.WriteChannelItemDataBytes(RootRotationRefs[i].Bytes);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
//if (FrameLength != writer.FrameLength)
|
|
//{ }
|
|
//else if (Data != null)
|
|
//{
|
|
// if (Data.Length != data.Length)
|
|
// {
|
|
// if ((channelLists[6]?.Count ?? 0) == 0)
|
|
// { }
|
|
// }
|
|
// else
|
|
// {
|
|
// for (int b = 0; b < Data.Length; b++)
|
|
// {
|
|
// if (Data[b] != data[b])
|
|
// {
|
|
// if ((channelLists[6]?.Count ?? 0) == 0)
|
|
// { }
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
|
|
|
|
Data = data;
|
|
DataLength = (uint)data.Length;
|
|
FrameOffset = (uint)mainData.Length;
|
|
FrameLength = writer.FrameLength;
|
|
ChunkSize = (writer.ChunkSize > 0) ? writer.ChunkSize : (byte)255;
|
|
QuantizeFloatValueBits = GetQuantizeFloatValueBits();
|
|
IndirectQuantizeFloatNumInts = GetIndirectQuantizeFloatNumInts();
|
|
RootMotionRefCounts = (byte)((((uint)(RootPositionRefs?.Length??0))<<4) | ((uint)(RootRotationRefs?.Length ?? 0)));
|
|
RootMotionRefsOffset = (uint)(BlockLength - ((RootPositionRefCount + RootRotationRefCount) * 6));
|
|
}
|
|
|
|
|
|
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 UpdateRootMotionRefs()
|
|
{
|
|
// OFFSETS - [ChannelType], [Index], [DataIntOffset 0xFFFF], [FrameBitOffset 0xFFFF]
|
|
|
|
var newPosRefs = new List<SequenceRootChannelRef>();
|
|
var newRotRefs = new List<SequenceRootChannelRef>();
|
|
|
|
for (int i = 0; i < Sequences.Length; i++)
|
|
{
|
|
var seq = Sequences[i];
|
|
if (seq == null) continue;
|
|
if (seq.BoneId.Track == 5) //root position
|
|
{
|
|
for (var c = 0; c < seq.Channels?.Length; c++)
|
|
{
|
|
var chan = seq.Channels[c];
|
|
var newPosRef = new SequenceRootChannelRef(chan.Type, chan.GetReferenceIndex());
|
|
newPosRef.DataIntOffset = (ushort)chan.DataOffset;
|
|
newPosRef.FrameBitOffset = (ushort)chan.FrameOffset;
|
|
newPosRefs.Add(newPosRef);
|
|
}
|
|
}
|
|
if (seq.BoneId.Track == 6) //root rotation
|
|
{
|
|
for (var c = 0; c < seq.Channels?.Length; c++)
|
|
{
|
|
var chan = seq.Channels[c];
|
|
var newRotRef = new SequenceRootChannelRef(chan.Type, chan.GetReferenceIndex());
|
|
newRotRef.DataIntOffset = (ushort)chan.DataOffset;
|
|
newRotRef.FrameBitOffset = (ushort)chan.FrameOffset;
|
|
newRotRefs.Add(newRotRef);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int compare(SequenceRootChannelRef a, SequenceRootChannelRef b)
|
|
{
|
|
var v1 = a.ChannelType.CompareTo(b.ChannelType);
|
|
if (v1 != 0) return v1;
|
|
var v2 = a.ChannelIndex.CompareTo(b.ChannelIndex);
|
|
if (v2 != 0) return v2;
|
|
return 0;
|
|
}
|
|
newPosRefs.Sort((a, b) => { return compare(a, b); });
|
|
newRotRefs.Sort((a, b) => { return compare(a, b); });
|
|
|
|
|
|
//if (RootPositionRefs != null)
|
|
//{
|
|
// if (RootPositionRefs.Length != newPosRefs.Count)
|
|
// { }
|
|
// else
|
|
// {
|
|
// for (int i = 0; i < RootPositionRefs.Length; i++)
|
|
// {
|
|
// var oldRef = RootPositionRefs[i];
|
|
// var newRef = newPosRefs[i];
|
|
// for (int b = 0; b < 6; b++)
|
|
// {
|
|
// if (oldRef.Bytes[b] != newRef.Bytes[b])
|
|
// { }
|
|
// }
|
|
// }
|
|
// }
|
|
//}
|
|
//if (RootRotationRefs != null)
|
|
//{
|
|
// if (RootRotationRefs.Length != newRotRefs.Count)
|
|
// { }
|
|
// else
|
|
// {
|
|
// for (int i = 0; i < RootRotationRefs.Length; i++)
|
|
// {
|
|
// var oldRef = RootRotationRefs[i];
|
|
// var newRef = newRotRefs[i];
|
|
// for (int b = 0; b < 6; b++)
|
|
// {
|
|
// if (oldRef.Bytes[b] != newRef.Bytes[b])
|
|
// { }
|
|
// }
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
|
|
RootPositionRefs = (newPosRefs.Count > 0) ? newPosRefs.ToArray() : null;
|
|
RootRotationRefs = (newRotRefs.Count > 0) ? newRotRefs.ToArray() : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
YcdXml.StringTag(sb, indent, "Hash", YcdXml.HashString(Unknown_00h));
|
|
YcdXml.ValueTag(sb, indent, "FrameCount", NumFrames.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");
|
|
Sequences = XmlMeta.ReadItemArray<AnimSequence>(node, "SequenceData");
|
|
|
|
AssociateSequenceChannels();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipMapEntry : ResourceSystemBlock
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 32; }
|
|
}
|
|
|
|
// structure data
|
|
public MetaHash Hash { get; set; }
|
|
public uint Unknown_04h { get; set; } // 0x00000000
|
|
public ulong ClipPointer { get; set; }
|
|
public ulong NextPointer { get; set; }
|
|
public uint Unknown_18h { get; set; } // 0x00000000
|
|
public uint Unknown_1Ch { get; set; } // 0x00000000
|
|
|
|
// reference data
|
|
public ClipBase Clip { get; set; }
|
|
public ClipMapEntry Next { get; set; }
|
|
|
|
public bool EnableRootMotion { get; set; } = false; //used by CW to toggle whether or not to include root motion when playing animations
|
|
public bool OverridePlayTime { get; set; } = false; //used by CW to manually override the animation playback time
|
|
public float PlayTime { get; set; } = 0.0f;
|
|
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
// read structure data
|
|
this.Hash = new MetaHash(reader.ReadUInt32());
|
|
this.Unknown_04h = reader.ReadUInt32();
|
|
this.ClipPointer = reader.ReadUInt64();
|
|
this.NextPointer = reader.ReadUInt64();
|
|
this.Unknown_18h = reader.ReadUInt32();
|
|
this.Unknown_1Ch = reader.ReadUInt32();
|
|
|
|
// read reference data
|
|
this.Clip = reader.ReadBlockAt<ClipBase>(
|
|
this.ClipPointer // offset
|
|
);
|
|
this.Next = reader.ReadBlockAt<ClipMapEntry>(
|
|
this.NextPointer // offset
|
|
);
|
|
|
|
|
|
if (Clip != null)
|
|
{
|
|
Clip.Hash = Hash;
|
|
}
|
|
else
|
|
{ }
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
// update structure data
|
|
this.ClipPointer = (ulong)(this.Clip != null ? this.Clip.FilePosition : 0);
|
|
this.NextPointer = (ulong)(this.Next != null ? this.Next.FilePosition : 0);
|
|
|
|
// write structure data
|
|
writer.Write(this.Hash);
|
|
writer.Write(this.Unknown_04h);
|
|
writer.Write(this.ClipPointer);
|
|
writer.Write(this.NextPointer);
|
|
writer.Write(this.Unknown_18h);
|
|
writer.Write(this.Unknown_1Ch);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>();
|
|
if (Clip != null) list.Add(Clip);
|
|
if (Next != null) list.Add(Next);
|
|
return list.ToArray();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return Clip?.Name ?? Hash.ToString();
|
|
}
|
|
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipBase : ResourceSystemBlock, IResourceXXSystemBlock, IMetaXmlItem
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 112; }
|
|
}
|
|
|
|
// structure data
|
|
public uint VFT { get; set; }
|
|
public uint Unknown_04h { get; set; } = 1; // 0x00000001
|
|
public uint Unknown_08h { get; set; } // 0x00000000
|
|
public uint Unknown_0Ch { get; set; } // 0x00000000
|
|
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; // 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; } = 1; // 0x00000001
|
|
public uint Unknown_4Ch { get; set; } // 0x00000000
|
|
|
|
// reference data
|
|
public string Name { get; set; }
|
|
public ClipTagList Tags { get; set; }
|
|
public ClipPropertyMap Properties { get; set; }
|
|
|
|
private string_r NameBlock = null;
|
|
|
|
public YcdFile Ycd { get; set; }
|
|
public string ShortName
|
|
{
|
|
get
|
|
{
|
|
if (!string.IsNullOrEmpty(_ShortName)) return _ShortName;
|
|
if (string.IsNullOrEmpty(Name)) return null;
|
|
|
|
string name = 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);
|
|
}
|
|
_ShortName = name.ToLowerInvariant();
|
|
JenkIndex.Ensure(_ShortName);
|
|
return _ShortName;
|
|
}
|
|
}
|
|
private string _ShortName;
|
|
|
|
public MetaHash Hash { get; set; } //used by CW when reading/writing
|
|
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
// read structure data
|
|
this.VFT = reader.ReadUInt32();
|
|
this.Unknown_04h = reader.ReadUInt32();
|
|
this.Unknown_08h = reader.ReadUInt32();
|
|
this.Unknown_0Ch = reader.ReadUInt32();
|
|
this.Type = (ClipType)reader.ReadUInt32();
|
|
this.Unknown_14h = reader.ReadUInt32();
|
|
this.NamePointer = reader.ReadUInt64();
|
|
this.NameLength = reader.ReadUInt16();
|
|
this.NameCapacity = reader.ReadUInt16();
|
|
this.Unknown_24h = reader.ReadUInt32();
|
|
this.Unknown_28hPtr = reader.ReadUInt64();
|
|
this.Unknown_30h = reader.ReadUInt32();
|
|
this.Unknown_34h = reader.ReadUInt32();
|
|
this.TagsPointer = reader.ReadUInt64();
|
|
this.PropertiesPointer = reader.ReadUInt64();
|
|
this.Unknown_48h = reader.ReadUInt32();
|
|
this.Unknown_4Ch = reader.ReadUInt32();
|
|
|
|
|
|
this.Name = reader.ReadStringAt(this.NamePointer);
|
|
this.Tags = reader.ReadBlockAt<ClipTagList>(
|
|
this.TagsPointer // offset
|
|
);
|
|
this.Properties = reader.ReadBlockAt<ClipPropertyMap>(
|
|
this.PropertiesPointer // offset
|
|
);
|
|
|
|
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.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((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);
|
|
writer.Write(this.Unknown_34h);
|
|
writer.Write(this.TagsPointer);
|
|
writer.Write(this.PropertiesPointer);
|
|
writer.Write(this.Unknown_48h);
|
|
writer.Write(this.Unknown_4Ch);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>();
|
|
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();
|
|
}
|
|
|
|
public IResourceSystemBlock GetType(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
reader.Position += 16;
|
|
var type = reader.ReadByte();
|
|
reader.Position -= 17;
|
|
|
|
return ConstructClip((ClipType)type);
|
|
}
|
|
|
|
|
|
public static ClipBase ConstructClip(ClipType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case ClipType.Animation: return new ClipAnimation();
|
|
case ClipType.AnimationList: return new ClipAnimationList();
|
|
default: return null;// throw new Exception("Unknown type");
|
|
}
|
|
}
|
|
|
|
|
|
public override string ToString()
|
|
{
|
|
return Name;
|
|
}
|
|
|
|
|
|
public virtual void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
YcdXml.StringTag(sb, indent, "Hash", YcdXml.HashString(Hash));
|
|
YcdXml.StringTag(sb, indent, "Name", MetaXml.XmlEscape(Name));
|
|
YcdXml.ValueTag(sb, indent, "Type", Type.ToString());
|
|
YcdXml.ValueTag(sb, indent, "Unknown30", Unknown_30h.ToString());
|
|
YcdXml.WriteItemArray(sb, Tags?.Tags?.data_items, indent, "Tags");
|
|
YcdXml.WriteItemArray(sb, Properties?.AllProperties, indent, "Properties");
|
|
}
|
|
public virtual void ReadXml(XmlNode node)
|
|
{
|
|
Hash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Hash"));
|
|
Name = Xml.GetChildInnerText(node, "Name");
|
|
Unknown_30h = Xml.GetChildUIntAttribute(node, "Unknown30", "value");
|
|
|
|
var tags = XmlMeta.ReadItemArrayNullable<ClipTag>(node, "Tags");
|
|
Tags = new ClipTagList();
|
|
if (tags != null)
|
|
{
|
|
Tags.Tags = new ResourcePointerArray64<ClipTag>();
|
|
Tags.Tags.data_items = tags;
|
|
Tags.BuildAllTags();
|
|
Tags.AssignTagOwners();
|
|
}
|
|
|
|
var props = XmlMeta.ReadItemArrayNullable<ClipProperty>(node, "Properties");
|
|
Properties = new ClipPropertyMap();
|
|
Properties.CreatePropertyMap(props);
|
|
}
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipAnimation : ClipBase
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 112; }
|
|
}
|
|
|
|
// structure data
|
|
public ulong AnimationPointer { get; set; }
|
|
public float StartTime { get; set; } //start time
|
|
public float EndTime { get; set; } //end time
|
|
public float Rate { get; set; } //1.0 rate..?
|
|
public uint Unknown_64h { get; set; } // 0x00000000
|
|
public uint Unknown_68h { get; set; } // 0x00000000
|
|
public uint Unknown_6Ch { get; set; } // 0x00000000
|
|
|
|
// 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)
|
|
{
|
|
base.Read(reader, parameters);
|
|
this.AnimationPointer = reader.ReadUInt64();
|
|
this.StartTime = reader.ReadSingle();
|
|
this.EndTime = reader.ReadSingle();
|
|
this.Rate = reader.ReadSingle();
|
|
this.Unknown_64h = reader.ReadUInt32();
|
|
this.Unknown_68h = reader.ReadUInt32();
|
|
this.Unknown_6Ch = reader.ReadUInt32();
|
|
|
|
this.Animation = reader.ReadBlockAt<Animation>(
|
|
this.AnimationPointer // offset
|
|
);
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
base.Write(writer, parameters);
|
|
|
|
this.AnimationPointer = (ulong)(this.Animation != null ? this.Animation.FilePosition : 0);
|
|
|
|
writer.Write(this.AnimationPointer);
|
|
writer.Write(this.StartTime);
|
|
writer.Write(this.EndTime);
|
|
writer.Write(this.Rate);
|
|
writer.Write(this.Unknown_64h);
|
|
writer.Write(this.Unknown_68h);
|
|
writer.Write(this.Unknown_6Ch);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>();
|
|
list.AddRange(base.GetReferences());
|
|
if (Animation != null) list.Add(Animation);
|
|
return list.ToArray();
|
|
}
|
|
|
|
public float GetPlaybackTime(double currentTime)
|
|
{
|
|
double scaledTime = currentTime * Rate;
|
|
double duration = EndTime - StartTime;
|
|
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?.Hash ?? 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
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 112; }
|
|
}
|
|
|
|
// structure data
|
|
public ulong AnimationsPointer { get; set; }
|
|
public ushort AnimationsCount1 { get; set; }
|
|
public ushort AnimationsCount2 { get; set; }
|
|
public uint Unknown_5Ch { get; set; } // 0x00000000
|
|
public float Duration { get; set; }
|
|
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<ClipAnimationsEntry> Animations { get; set; }
|
|
|
|
|
|
public ClipAnimationList()
|
|
{
|
|
Type = ClipType.AnimationList;
|
|
}
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
base.Read(reader, parameters);
|
|
this.AnimationsPointer = reader.ReadUInt64();
|
|
this.AnimationsCount1 = reader.ReadUInt16();
|
|
this.AnimationsCount2 = reader.ReadUInt16();
|
|
this.Unknown_5Ch = reader.ReadUInt32();
|
|
this.Duration = reader.ReadSingle();
|
|
this.Unknown_64h = reader.ReadUInt32();
|
|
this.Unknown_68h = reader.ReadUInt32();
|
|
this.Unknown_6Ch = reader.ReadUInt32();
|
|
|
|
this.Animations = reader.ReadBlockAt<ResourceSimpleArray<ClipAnimationsEntry>>(
|
|
this.AnimationsPointer, // offset
|
|
this.AnimationsCount1
|
|
);
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
base.Write(writer, parameters);
|
|
|
|
this.AnimationsPointer = (ulong)(this.Animations != null ? this.Animations.FilePosition : 0);
|
|
this.AnimationsCount1 = (ushort)(this.Animations != null ? this.Animations.Count : 0);
|
|
this.AnimationsCount2 = this.AnimationsCount1;
|
|
|
|
writer.Write(this.AnimationsPointer);
|
|
writer.Write(this.AnimationsCount1);
|
|
writer.Write(this.AnimationsCount2);
|
|
writer.Write(this.Unknown_5Ch);
|
|
writer.Write(this.Duration);
|
|
writer.Write(this.Unknown_64h);
|
|
writer.Write(this.Unknown_68h);
|
|
writer.Write(this.Unknown_6Ch);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>();
|
|
list.AddRange(base.GetReferences());
|
|
if (Animations != null) list.Add(Animations);
|
|
return list.ToArray();
|
|
}
|
|
|
|
|
|
public float GetPlaybackTime(double currentTime)
|
|
{
|
|
double scaledTime = currentTime;// * Rate;
|
|
double duration = Duration;// EndTime - StartTime;
|
|
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<ClipAnimationsEntry>();
|
|
Animations.Data = new List<ClipAnimationsEntry>();
|
|
var anims = XmlMeta.ReadItemArrayNullable<ClipAnimationsEntry>(node, "Animations");
|
|
if (anims != null) Animations.Data.AddRange(anims);
|
|
}
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipAnimationsEntry : ResourceSystemBlock, IMetaXmlItem
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 24; }
|
|
}
|
|
|
|
// structure data
|
|
public float StartTime { get; set; }
|
|
public float EndTime { get; set; }
|
|
public float Rate { get; set; }
|
|
public uint Unknown_0Ch { get; set; } // 0x00000000
|
|
public ulong AnimationPointer { get; set; }
|
|
|
|
// 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)
|
|
{
|
|
// read structure data
|
|
this.StartTime = reader.ReadSingle();
|
|
this.EndTime = reader.ReadSingle();
|
|
this.Rate = reader.ReadSingle();
|
|
this.Unknown_0Ch = reader.ReadUInt32();
|
|
this.AnimationPointer = reader.ReadUInt64();
|
|
|
|
// read reference data
|
|
this.Animation = reader.ReadBlockAt<Animation>(
|
|
this.AnimationPointer // offset
|
|
);
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
// update structure data
|
|
this.AnimationPointer = (ulong)(this.Animation != null ? this.Animation.FilePosition : 0);
|
|
|
|
// write structure data
|
|
writer.Write(this.StartTime);
|
|
writer.Write(this.EndTime);
|
|
writer.Write(this.Rate);
|
|
writer.Write(this.Unknown_0Ch);
|
|
writer.Write(this.AnimationPointer);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>();
|
|
if (Animation != null) list.Add(Animation);
|
|
return list.ToArray();
|
|
}
|
|
|
|
|
|
public float GetPlaybackTime(double currentTime)
|
|
{
|
|
double scaledTime = currentTime * Rate;
|
|
double duration = EndTime - StartTime;
|
|
double curpos = scaledTime % duration;
|
|
return StartTime + (float)curpos;
|
|
}
|
|
|
|
|
|
public void WriteXml(StringBuilder sb, int indent)
|
|
{
|
|
YcdXml.StringTag(sb, indent, "AnimationHash", YcdXml.HashString(Animation?.Hash ?? 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,
|
|
}
|
|
|
|
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyMap : ResourceSystemBlock
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 16; }
|
|
}
|
|
|
|
// structure data
|
|
public ulong PropertyEntriesPointer { get; set; }
|
|
public ushort PropertyEntriesCapacity { get; set; }
|
|
public ushort PropertyEntriesCount { get; set; }
|
|
public uint Unknown_0Ch { get; set; } = 0x01000000; // 0x01000000
|
|
|
|
// reference data
|
|
public ResourcePointerArray64<ClipPropertyMapEntry> Properties { get; set; }
|
|
|
|
public ClipProperty[] AllProperties { get; set; }
|
|
public Dictionary<MetaHash, ClipProperty> PropertyMap { get; set; }
|
|
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
// read structure data
|
|
this.PropertyEntriesPointer = reader.ReadUInt64();
|
|
this.PropertyEntriesCapacity = reader.ReadUInt16();
|
|
this.PropertyEntriesCount = reader.ReadUInt16();
|
|
this.Unknown_0Ch = reader.ReadUInt32();
|
|
|
|
// read reference data
|
|
this.Properties = reader.ReadBlockAt<ResourcePointerArray64<ClipPropertyMapEntry>>(
|
|
this.PropertyEntriesPointer, // offset
|
|
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<IResourceBlock>();
|
|
if (Properties != null) list.Add(Properties);
|
|
return list.ToArray();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "Count: " + (AllProperties?.Length ?? 0).ToString();
|
|
}
|
|
|
|
|
|
public void BuildPropertyMap()
|
|
{
|
|
if (Properties?.data_items != null)
|
|
{
|
|
List<ClipProperty> pl = new List<ClipProperty>();
|
|
foreach (var pme in Properties.data_items)
|
|
{
|
|
ClipPropertyMapEntry cpme = pme;
|
|
while (cpme?.Data != null)
|
|
{
|
|
pl.Add(cpme.Data);
|
|
cpme = cpme.Next;
|
|
}
|
|
}
|
|
AllProperties = pl.ToArray();
|
|
|
|
PropertyMap = new Dictionary<MetaHash, ClipProperty>();
|
|
foreach (var cp in AllProperties)
|
|
{
|
|
PropertyMap[cp.NameHash] = cp;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void CreatePropertyMap(ClipProperty[] properties)
|
|
{
|
|
var numBuckets = ClipDictionary.GetNumHashBuckets(properties?.Length ?? 0);
|
|
var buckets = new List<ClipPropertyMapEntry>[numBuckets];
|
|
if (properties != null)
|
|
{
|
|
foreach (var prop in properties)
|
|
{
|
|
var b = prop.NameHash % numBuckets;
|
|
var bucket = buckets[b];
|
|
if (bucket == null)
|
|
{
|
|
bucket = new List<ClipPropertyMapEntry>();
|
|
buckets[b] = bucket;
|
|
}
|
|
var pme = new ClipPropertyMapEntry();
|
|
pme.PropertyNameHash = prop.NameHash;
|
|
pme.Data = prop;
|
|
bucket.Add(pme);
|
|
}
|
|
}
|
|
|
|
var newProperties = new List<ClipPropertyMapEntry>();
|
|
foreach (var b in buckets)
|
|
{
|
|
if ((b?.Count ?? 0) == 0) newProperties.Add(null);
|
|
else
|
|
{
|
|
newProperties.Add(b[0]);
|
|
var p = b[0];
|
|
for (int i = 1; i < b.Count; i++)
|
|
{
|
|
var c = b[i];
|
|
c.Next = null;
|
|
p.Next = c;
|
|
p = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
Properties = new ResourcePointerArray64<ClipPropertyMapEntry>();
|
|
Properties.data_items = newProperties.ToArray();
|
|
|
|
AllProperties = properties;
|
|
}
|
|
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyMapEntry : ResourceSystemBlock
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 32; }
|
|
}
|
|
|
|
// structure data
|
|
public MetaHash PropertyNameHash { get; set; }
|
|
public uint Unknown_04h { get; set; } // 0x00000000
|
|
public ulong DataPointer { get; set; }
|
|
public ulong NextPointer { get; set; }
|
|
public uint Unknown_18h { get; set; } // 0x00000000
|
|
public uint Unknown_1Ch { get; set; } // 0x00000000
|
|
|
|
// reference data
|
|
public ClipProperty Data { get; set; }
|
|
public ClipPropertyMapEntry Next { get; set; }
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
// read structure data
|
|
this.PropertyNameHash = reader.ReadUInt32();
|
|
this.Unknown_04h = reader.ReadUInt32();
|
|
this.DataPointer = reader.ReadUInt64();
|
|
this.NextPointer = reader.ReadUInt64();
|
|
this.Unknown_18h = reader.ReadUInt32();
|
|
this.Unknown_1Ch = reader.ReadUInt32();
|
|
|
|
// read reference data
|
|
this.Data = reader.ReadBlockAt<ClipProperty>(
|
|
this.DataPointer // offset
|
|
);
|
|
this.Next = reader.ReadBlockAt<ClipPropertyMapEntry>(
|
|
this.NextPointer // offset
|
|
);
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
// update structure data
|
|
this.DataPointer = (ulong)(this.Data != null ? this.Data.FilePosition : 0);
|
|
this.NextPointer = (ulong)(this.Next != null ? this.Next.FilePosition : 0);
|
|
|
|
// write structure data
|
|
writer.Write(this.PropertyNameHash);
|
|
writer.Write(this.Unknown_04h);
|
|
writer.Write(this.DataPointer);
|
|
writer.Write(this.NextPointer);
|
|
writer.Write(this.Unknown_18h);
|
|
writer.Write(this.Unknown_1Ch);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>();
|
|
if (Data != null) list.Add(Data);
|
|
if (Next != null) list.Add(Next);
|
|
return list.ToArray();
|
|
}
|
|
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipProperty : ResourceSystemBlock, IMetaXmlItem
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 64; }
|
|
}
|
|
|
|
// structure data
|
|
public uint VFT { get; set; }
|
|
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
|
|
public uint Unknown_14h { get; set; } // 0x00000000
|
|
public MetaHash NameHash { get; set; }
|
|
public uint Unknown_1Ch { get; set; } // 0x00000000
|
|
public ulong AttributesPointer { get; set; }
|
|
public ushort AttributesCount { get; set; }
|
|
public ushort AttributesCapacity { get; set; }
|
|
public uint Unknown_2Ch { get; set; } // 0x00000000
|
|
public uint Unknown_30h { get; set; } // 0x00000000
|
|
public uint Unknown_34h { get; set; } // 0x00000000
|
|
public MetaHash UnkHash { get; set; }
|
|
public uint Unknown_3Ch { get; set; } // 0x00000000
|
|
|
|
// reference data
|
|
public ResourcePointerArray64<ClipPropertyAttribute> Attributes { get; set; }
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
// read structure data
|
|
this.VFT = reader.ReadUInt32();
|
|
this.Unknown_04h = reader.ReadUInt32();
|
|
this.Unknown_08h = reader.ReadUInt32();
|
|
this.Unknown_0Ch = reader.ReadUInt32();
|
|
this.Unknown_10h = reader.ReadUInt32();
|
|
this.Unknown_14h = reader.ReadUInt32();
|
|
this.NameHash = reader.ReadUInt32();
|
|
this.Unknown_1Ch = reader.ReadUInt32();
|
|
this.AttributesPointer = reader.ReadUInt64();
|
|
this.AttributesCount = reader.ReadUInt16();
|
|
this.AttributesCapacity = reader.ReadUInt16();
|
|
this.Unknown_2Ch = reader.ReadUInt32();
|
|
this.Unknown_30h = reader.ReadUInt32();
|
|
this.Unknown_34h = reader.ReadUInt32();
|
|
this.UnkHash = reader.ReadUInt32();
|
|
this.Unknown_3Ch = reader.ReadUInt32();
|
|
|
|
// read reference data
|
|
this.Attributes = reader.ReadBlockAt<ResourcePointerArray64<ClipPropertyAttribute>>(
|
|
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.AttributesCount = (ushort)(this.Attributes != null ? this.Attributes.Count : 0);
|
|
this.AttributesCapacity = this.AttributesCount;
|
|
|
|
// 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(this.Unknown_14h);
|
|
writer.Write(this.NameHash);
|
|
writer.Write(this.Unknown_1Ch);
|
|
writer.Write(this.AttributesPointer);
|
|
writer.Write(this.AttributesCount);
|
|
writer.Write(this.AttributesCapacity);
|
|
writer.Write(this.Unknown_2Ch);
|
|
writer.Write(this.Unknown_30h);
|
|
writer.Write(this.Unknown_34h);
|
|
writer.Write(this.UnkHash);
|
|
writer.Write(this.Unknown_3Ch);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>();
|
|
if (Attributes != null) list.Add(Attributes);
|
|
return list.ToArray();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
if ((Attributes != null) && (Attributes.data_items != null))
|
|
{
|
|
foreach (var item in Attributes.data_items)
|
|
{
|
|
if (sb.Length > 0) sb.Append(", ");
|
|
sb.Append(item.ToString());
|
|
}
|
|
}
|
|
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<ClipPropertyAttribute>();
|
|
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<ClipPropertyAttribute>();
|
|
Attributes.data_items = alist.ToArray();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttribute : ResourceSystemBlock, IResourceXXSystemBlock, IMetaXmlItem
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 16; }
|
|
}
|
|
|
|
public uint VFT { 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
|
|
public uint Unknown_10h { get; set; } // 0x00000000
|
|
public uint Unknown_14h { get; set; } // 0x00000000
|
|
public MetaHash NameHash { get; set; }
|
|
public uint Unknown_1Ch { get; set; } // 0x00000000
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
this.VFT = reader.ReadUInt32();
|
|
this.Unknown_04h = reader.ReadUInt32();
|
|
this.Type = (ClipPropertyAttributeType)reader.ReadByte();
|
|
this.Unknown_09h = reader.ReadByte();
|
|
this.Unknown_Ah = reader.ReadUInt16();
|
|
this.Unknown_Ch = reader.ReadUInt32();
|
|
this.Unknown_10h = reader.ReadUInt32();
|
|
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((byte)this.Type);
|
|
writer.Write(this.Unknown_09h);
|
|
writer.Write(this.Unknown_Ah);
|
|
writer.Write(this.Unknown_Ch);
|
|
writer.Write(this.Unknown_10h);
|
|
writer.Write(this.Unknown_14h);
|
|
writer.Write(this.NameHash);
|
|
writer.Write(this.Unknown_1Ch);
|
|
}
|
|
|
|
public IResourceSystemBlock GetType(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
reader.Position += 8;
|
|
var type = (ClipPropertyAttributeType)reader.ReadByte();
|
|
reader.Position -= 9;
|
|
return ConstructItem(type);
|
|
}
|
|
|
|
public static ClipPropertyAttribute ConstructItem(ClipPropertyAttributeType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
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<ClipPropertyAttributeType>(Xml.GetChildStringAttribute(node, "Type", "value"));
|
|
}
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttributeFloat : ClipPropertyAttribute
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 48; }
|
|
}
|
|
|
|
public float Value { get; set; }
|
|
public uint Unknown_24h { get; set; } // 0x00000000
|
|
public uint Unknown_28h { get; set; } // 0x00000000
|
|
public uint Unknown_2Ch { get; set; } // 0x00000000
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
base.Read(reader, parameters);
|
|
|
|
// read structure data
|
|
this.Value = reader.ReadSingle();
|
|
this.Unknown_24h = reader.ReadUInt32();
|
|
this.Unknown_28h = reader.ReadUInt32();
|
|
this.Unknown_2Ch = reader.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
base.Write(writer, parameters);
|
|
|
|
// write structure data
|
|
writer.Write(this.Value);
|
|
writer.Write(this.Unknown_24h);
|
|
writer.Write(this.Unknown_28h);
|
|
writer.Write(this.Unknown_2Ch);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
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
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 48; }
|
|
}
|
|
|
|
public int Value { get; set; }
|
|
public uint Unknown_24h { get; set; } // 0x00000000
|
|
public uint Unknown_28h { get; set; } // 0x00000000
|
|
public uint Unknown_2Ch { get; set; } // 0x00000000
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
base.Read(reader, parameters);
|
|
|
|
// read structure data
|
|
this.Value = reader.ReadInt32();
|
|
this.Unknown_24h = reader.ReadUInt32();
|
|
this.Unknown_28h = reader.ReadUInt32();
|
|
this.Unknown_2Ch = reader.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
base.Write(writer, parameters);
|
|
|
|
// write structure data
|
|
writer.Write(this.Value);
|
|
writer.Write(this.Unknown_24h);
|
|
writer.Write(this.Unknown_28h);
|
|
writer.Write(this.Unknown_2Ch);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
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
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 48; }
|
|
}
|
|
|
|
public uint Value { get; set; }
|
|
public uint Unknown_24h { get; set; } // 0x00000000
|
|
public uint Unknown_28h { get; set; } // 0x00000000
|
|
public uint Unknown_2Ch { get; set; } // 0x00000000
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
base.Read(reader, parameters);
|
|
|
|
// read structure data
|
|
this.Value = reader.ReadUInt32();
|
|
this.Unknown_24h = reader.ReadUInt32();
|
|
this.Unknown_28h = reader.ReadUInt32();
|
|
this.Unknown_2Ch = reader.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
base.Write(writer, parameters);
|
|
|
|
// write structure data
|
|
writer.Write(this.Value);
|
|
writer.Write(this.Unknown_24h);
|
|
writer.Write(this.Unknown_28h);
|
|
writer.Write(this.Unknown_2Ch);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
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
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 48; }
|
|
}
|
|
|
|
public ulong ValuePointer { 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)
|
|
{
|
|
base.Read(reader, parameters);
|
|
|
|
// read structure data
|
|
this.ValuePointer = reader.ReadUInt64();
|
|
this.ValueLength = reader.ReadUInt16();
|
|
this.ValueCapacity = reader.ReadUInt16();
|
|
this.Unknown_02Ch = reader.ReadUInt32();
|
|
|
|
//// read reference data
|
|
Value = reader.ReadStringAt(ValuePointer);
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
base.Write(writer, parameters);
|
|
|
|
// update structure data
|
|
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.ValueLength);
|
|
writer.Write(this.ValueCapacity);
|
|
writer.Write(this.Unknown_02Ch);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>(base.GetReferences());
|
|
if (Value != null)
|
|
{
|
|
ValueBlock = (string_r)Value;
|
|
list.Add(ValueBlock);
|
|
}
|
|
return list.ToArray();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
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
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 48; }
|
|
}
|
|
|
|
public Vector3 Value { get; set; }
|
|
public float Unknown_02Ch { get; set; }
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
base.Read(reader, parameters);
|
|
|
|
// read structure data
|
|
Value = reader.ReadVector3();
|
|
this.Unknown_02Ch = reader.ReadSingle();
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
base.Write(writer, parameters);
|
|
|
|
// write structure data
|
|
writer.Write(this.Value);
|
|
writer.Write(this.Unknown_02Ch);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
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");
|
|
Unknown_02Ch = Xml.GetChildFloatAttribute(node, "Unknown2C", "value");
|
|
}
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttributeVector4 : ClipPropertyAttribute
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 48; }
|
|
}
|
|
|
|
public Vector4 Value { get; set; }
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
base.Read(reader, parameters);
|
|
|
|
// read structure data
|
|
this.Value = reader.ReadVector4();
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
base.Write(writer, parameters);
|
|
|
|
// write structure data
|
|
writer.Write(this.Value);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
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");
|
|
}
|
|
}
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipPropertyAttributeHashString : ClipPropertyAttribute
|
|
{
|
|
public override long BlockLength => 0x30;
|
|
|
|
public MetaHash Value { get; set; }
|
|
public uint Unknown_24h { get; set; } // 0x00000000
|
|
public uint Unknown_28h { get; set; } // 0x00000000
|
|
public uint Unknown_2Ch { get; set; } // 0x00000000
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
base.Read(reader, parameters);
|
|
|
|
// read structure data
|
|
this.Value = reader.ReadUInt32();
|
|
this.Unknown_24h = reader.ReadUInt32();
|
|
this.Unknown_28h = reader.ReadUInt32();
|
|
this.Unknown_2Ch = reader.ReadUInt32();
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
base.Write(writer, parameters);
|
|
|
|
// write structure data
|
|
writer.Write(this.Value);
|
|
writer.Write(this.Unknown_24h);
|
|
writer.Write(this.Unknown_28h);
|
|
writer.Write(this.Unknown_2Ch);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
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,
|
|
}
|
|
|
|
|
|
[TypeConverter(typeof(ExpandableObjectConverter))] public class ClipTagList : ResourceSystemBlock
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 32; }
|
|
}
|
|
|
|
// structure data
|
|
public ulong TagsPointer { get; set; }
|
|
public ushort TagCount1 { get; set; }
|
|
public ushort TagCount2 { get; set; }
|
|
public uint Unknown_0Ch { get; set; } // 0x00000000
|
|
public uint HasBlockTag { 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
|
|
|
|
// reference data
|
|
public ResourcePointerArray64<ClipTag> Tags { get; set; }
|
|
|
|
public ClipTag[] AllTags { get; set; }
|
|
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
// read structure data
|
|
this.TagsPointer = reader.ReadUInt64();
|
|
this.TagCount1 = reader.ReadUInt16();
|
|
this.TagCount2 = reader.ReadUInt16();
|
|
this.Unknown_0Ch = reader.ReadUInt32();
|
|
this.HasBlockTag = reader.ReadUInt32();
|
|
this.Unknown_14h = reader.ReadUInt32();
|
|
this.Unknown_18h = reader.ReadUInt32();
|
|
this.Unknown_1Ch = reader.ReadUInt32();
|
|
|
|
// read reference data
|
|
this.Tags = reader.ReadBlockAt<ResourcePointerArray64<ClipTag>>(
|
|
this.TagsPointer, // offset
|
|
this.TagCount1
|
|
);
|
|
|
|
BuildAllTags();
|
|
|
|
if (TagCount1 != TagCount2)
|
|
{ }
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
// update structure data
|
|
this.TagsPointer = (ulong)(this.Tags != null ? this.Tags.FilePosition : 0);
|
|
this.TagCount1 = (ushort)(this.Tags != null ? this.Tags.Count : 0);
|
|
this.TagCount2 = this.TagCount1;
|
|
|
|
BuildAllTags(); //just in case? updates HasBlockTag
|
|
|
|
// write structure data
|
|
writer.Write(this.TagsPointer);
|
|
writer.Write(this.TagCount1);
|
|
writer.Write(this.TagCount2);
|
|
writer.Write(this.Unknown_0Ch);
|
|
writer.Write(this.HasBlockTag);
|
|
writer.Write(this.Unknown_14h);
|
|
writer.Write(this.Unknown_18h);
|
|
writer.Write(this.Unknown_1Ch);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>();
|
|
if (Tags != null) list.Add(Tags);
|
|
return list.ToArray();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "Count: " + (AllTags?.Length ?? 0).ToString();
|
|
}
|
|
|
|
public void BuildAllTags()
|
|
{
|
|
|
|
if ((Tags != null) && (Tags.data_items != null))
|
|
{
|
|
List<ClipTag> tl = new List<ClipTag>();
|
|
foreach (var te in Tags.data_items)
|
|
{
|
|
if (te.Tags != this)
|
|
{ }
|
|
if (te != null)
|
|
{
|
|
tl.Add(te);
|
|
}
|
|
}
|
|
AllTags = tl.ToArray();
|
|
}
|
|
|
|
|
|
uint hasBlock = 0;
|
|
if (AllTags != null)
|
|
{
|
|
foreach (var tag in AllTags)
|
|
{
|
|
if (tag.NameHash == (uint)MetaName.block)
|
|
{ hasBlock = 1; break; }
|
|
}
|
|
}
|
|
if (HasBlockTag != hasBlock)
|
|
{ }
|
|
HasBlockTag = hasBlock;
|
|
|
|
}
|
|
|
|
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
|
|
{
|
|
public override long BlockLength
|
|
{
|
|
get { return 80; }
|
|
}
|
|
|
|
public float Unknown_40h { get; set; }
|
|
public float Unknown_44h { get; set; }
|
|
public ulong TagsPointer { get; set; }
|
|
|
|
// reference data
|
|
public ClipTagList Tags { get; set; }
|
|
|
|
|
|
public override void Read(ResourceDataReader reader, params object[] parameters)
|
|
{
|
|
base.Read(reader, parameters);
|
|
|
|
// read structure data
|
|
this.Unknown_40h = reader.ReadSingle();
|
|
this.Unknown_44h = reader.ReadSingle();
|
|
this.TagsPointer = reader.ReadUInt64();
|
|
|
|
// read reference data
|
|
this.Tags = reader.ReadBlockAt<ClipTagList>(
|
|
this.TagsPointer // offset
|
|
);
|
|
}
|
|
|
|
public override void Write(ResourceDataWriter writer, params object[] parameters)
|
|
{
|
|
base.Write(writer, parameters);
|
|
|
|
// update structure data
|
|
this.TagsPointer = (ulong)(this.Tags != null ? this.Tags.FilePosition : 0);
|
|
|
|
// write structure data
|
|
writer.Write(this.Unknown_40h);
|
|
writer.Write(this.Unknown_44h);
|
|
writer.Write(this.TagsPointer);
|
|
}
|
|
|
|
public override IResourceBlock[] GetReferences()
|
|
{
|
|
var list = new List<IResourceBlock>(base.GetReferences());
|
|
if (Tags != null) list.Add(Tags);
|
|
return list.ToArray();
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
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");
|
|
}
|
|
}
|
|
|
|
|
|
} |