2019-08-04 00:00:51 +02:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Toolbox;
|
|
|
|
|
using System.Windows.Forms;
|
|
|
|
|
using Toolbox.Library;
|
|
|
|
|
using Toolbox.Library.IO;
|
|
|
|
|
using Toolbox.Library.Animations;
|
|
|
|
|
|
|
|
|
|
namespace FirstPlugin
|
|
|
|
|
{
|
2019-11-10 22:24:54 +01:00
|
|
|
|
public class CSAB : STSkeletonAnimation, IFileFormat
|
2019-08-04 00:00:51 +02:00
|
|
|
|
{
|
|
|
|
|
public FileType FileType { get; set; } = FileType.Animation;
|
|
|
|
|
|
|
|
|
|
public bool CanSave { get; set; }
|
|
|
|
|
public string[] Description { get; set; } = new string[] { "CTR Skeletal Animation Binary" };
|
|
|
|
|
public string[] Extension { get; set; } = new string[] { "*.csab" };
|
|
|
|
|
public string FileName { get; set; }
|
|
|
|
|
public string FilePath { get; set; }
|
|
|
|
|
public IFileInfo IFileInfo { get; set; }
|
|
|
|
|
|
|
|
|
|
public bool Identify(System.IO.Stream stream)
|
|
|
|
|
{
|
|
|
|
|
using (var reader = new Toolbox.Library.IO.FileReader(stream, true))
|
|
|
|
|
{
|
|
|
|
|
return reader.CheckSignature(4, "csab");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Type[] Types
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
List<Type> types = new List<Type>();
|
|
|
|
|
return types.ToArray();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Header header;
|
|
|
|
|
|
|
|
|
|
public void Load(System.IO.Stream stream)
|
|
|
|
|
{
|
|
|
|
|
header = new Header();
|
|
|
|
|
header.Read(new FileReader(stream));
|
|
|
|
|
|
2019-11-10 22:24:54 +01:00
|
|
|
|
foreach (var bone in header.Nodes)
|
|
|
|
|
AnimGroups.Add(bone);
|
2019-08-04 00:00:51 +02:00
|
|
|
|
}
|
2019-11-10 22:24:54 +01:00
|
|
|
|
|
2019-08-04 00:00:51 +02:00
|
|
|
|
public void Unload()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-06 23:35:18 +02:00
|
|
|
|
public void Save(System.IO.Stream stream)
|
2019-08-04 00:00:51 +02:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public enum GameVersion
|
|
|
|
|
{
|
|
|
|
|
OOT3D,
|
|
|
|
|
MM3D,
|
|
|
|
|
LM3DS,
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-04 01:33:29 +02:00
|
|
|
|
public enum AnimationTrackType
|
|
|
|
|
{
|
|
|
|
|
LINEAR = 0x01,
|
|
|
|
|
HERMITE = 0x02,
|
|
|
|
|
};
|
|
|
|
|
|
2019-08-04 00:00:51 +02:00
|
|
|
|
public class Header
|
|
|
|
|
{
|
|
|
|
|
public GameVersion Version;
|
|
|
|
|
|
2019-08-04 01:33:29 +02:00
|
|
|
|
public List<AnimationNode> Nodes = new List<AnimationNode>();
|
|
|
|
|
|
2019-08-04 00:00:51 +02:00
|
|
|
|
public void Read(FileReader reader)
|
|
|
|
|
{
|
2019-08-04 01:33:29 +02:00
|
|
|
|
reader.SetByteOrder(false);
|
|
|
|
|
|
|
|
|
|
reader.ReadSignature(4, "csab");
|
2019-08-04 00:00:51 +02:00
|
|
|
|
uint FileSize = reader.ReadUInt32();
|
|
|
|
|
uint versionNum = reader.ReadUInt32();
|
|
|
|
|
if (versionNum == 5)
|
|
|
|
|
Version = GameVersion.MM3D;
|
|
|
|
|
else if (versionNum == 3)
|
|
|
|
|
Version = GameVersion.OOT3D;
|
|
|
|
|
else
|
|
|
|
|
Version = GameVersion.LM3DS;
|
|
|
|
|
|
|
|
|
|
uint padding = reader.ReadUInt32(); //Unsure
|
|
|
|
|
if (Version >= GameVersion.MM3D)
|
|
|
|
|
{
|
|
|
|
|
uint unknown = reader.ReadUInt32();//0x42200000
|
|
|
|
|
uint unknown2 = reader.ReadUInt32();//0x42200000
|
|
|
|
|
uint unknown3 = reader.ReadUInt32();//0x42200000
|
|
|
|
|
}
|
|
|
|
|
uint numAnimations = reader.ReadUInt32(); //Unsure
|
|
|
|
|
uint location = reader.ReadUInt32(); //Unsure
|
|
|
|
|
uint unknown4 = reader.ReadUInt32();//0x00
|
|
|
|
|
uint unknown5 = reader.ReadUInt32();//0x00
|
|
|
|
|
uint unknown6 = reader.ReadUInt32();//0x00
|
|
|
|
|
uint unknown7 = reader.ReadUInt32();//0x00
|
2019-08-04 01:33:29 +02:00
|
|
|
|
uint unknown8 = reader.ReadUInt32();//0x00
|
2019-08-04 00:00:51 +02:00
|
|
|
|
uint duration = reader.ReadUInt32();
|
|
|
|
|
uint nodeCount = reader.ReadUInt32();
|
|
|
|
|
uint boneCount = reader.ReadUInt32();
|
|
|
|
|
if (nodeCount != boneCount) throw new Exception("Unexpected bone and node count!");
|
|
|
|
|
|
|
|
|
|
ushort[] BoneIndexTable = reader.ReadUInt16s((int)boneCount);
|
2019-08-04 01:33:29 +02:00
|
|
|
|
reader.Align(4);
|
|
|
|
|
uint[] nodeOffsets = reader.ReadUInt32s((int)nodeCount);
|
|
|
|
|
for (int i = 0; i < nodeCount; i++)
|
|
|
|
|
{
|
|
|
|
|
reader.SeekBegin(nodeOffsets[i] + 0x18);
|
|
|
|
|
AnimationNode node = new AnimationNode();
|
|
|
|
|
node.Read(reader, Version);
|
|
|
|
|
Nodes.Add(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-10 22:24:54 +01:00
|
|
|
|
public class AnimationNode : STAnimGroup
|
2019-08-04 01:33:29 +02:00
|
|
|
|
{
|
|
|
|
|
public ushort BoneIndex { get; set; }
|
|
|
|
|
|
|
|
|
|
public AnimTrack TranslateX { get; set; }
|
|
|
|
|
public AnimTrack TranslateY { get; set; }
|
|
|
|
|
public AnimTrack TranslateZ { get; set; }
|
|
|
|
|
public AnimTrack RotationX { get; set; }
|
|
|
|
|
public AnimTrack RotationY { get; set; }
|
|
|
|
|
public AnimTrack RotationZ { get; set; }
|
|
|
|
|
public AnimTrack ScaleX { get; set; }
|
|
|
|
|
public AnimTrack ScaleY { get; set; }
|
|
|
|
|
public AnimTrack ScaleZ { get; set; }
|
|
|
|
|
|
2019-11-10 22:24:54 +01:00
|
|
|
|
public override List<STAnimationTrack> GetTracks()
|
|
|
|
|
{
|
|
|
|
|
List<STAnimationTrack> tracks = new List<STAnimationTrack>();
|
|
|
|
|
tracks.Add(TranslateX);
|
|
|
|
|
tracks.Add(TranslateY);
|
|
|
|
|
tracks.Add(TranslateZ);
|
|
|
|
|
tracks.Add(RotationX);
|
|
|
|
|
tracks.Add(RotationY);
|
|
|
|
|
tracks.Add(RotationZ);
|
|
|
|
|
tracks.Add(ScaleX);
|
|
|
|
|
tracks.Add(ScaleY);
|
|
|
|
|
tracks.Add(ScaleZ);
|
|
|
|
|
return tracks;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-04 01:33:29 +02:00
|
|
|
|
public void Read(FileReader reader, GameVersion version)
|
|
|
|
|
{
|
|
|
|
|
long pos = reader.Position;
|
|
|
|
|
reader.ReadSignature(4, "anod");
|
|
|
|
|
BoneIndex = reader.ReadUInt16();
|
|
|
|
|
reader.ReadUInt16();//0x00
|
|
|
|
|
TranslateX = ParseTrack(reader, version, pos);
|
|
|
|
|
TranslateY = ParseTrack(reader, version, pos);
|
|
|
|
|
TranslateZ = ParseTrack(reader, version, pos);
|
|
|
|
|
RotationX = ParseTrack(reader, version, pos);
|
|
|
|
|
RotationY = ParseTrack(reader, version, pos);
|
|
|
|
|
RotationZ = ParseTrack(reader, version, pos);
|
|
|
|
|
ScaleX = ParseTrack(reader, version, pos);
|
|
|
|
|
ScaleY = ParseTrack(reader, version, pos);
|
|
|
|
|
ScaleZ = ParseTrack(reader, version, pos);
|
|
|
|
|
reader.ReadUInt16();//0x00
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static AnimTrack ParseTrack(FileReader reader, GameVersion version, long startPos)
|
|
|
|
|
{
|
|
|
|
|
long pos = reader.Position;
|
|
|
|
|
|
|
|
|
|
uint Offset = reader.ReadUInt16();
|
|
|
|
|
if (Offset == 0) return null;
|
|
|
|
|
|
|
|
|
|
reader.SeekBegin(startPos + Offset);
|
|
|
|
|
var track = new AnimTrack(reader, version);
|
|
|
|
|
|
|
|
|
|
reader.SeekBegin(pos + sizeof(ushort)); //Seek back to next offset
|
|
|
|
|
return track;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-10 22:24:54 +01:00
|
|
|
|
public class AnimTrack : STAnimationTrack
|
2019-08-04 01:33:29 +02:00
|
|
|
|
{
|
|
|
|
|
public List<LinearKeyFrame> KeyFramesLinear = new List<LinearKeyFrame>();
|
|
|
|
|
public List<HermiteKeyFrame> KeyFramesHermite = new List<HermiteKeyFrame>();
|
|
|
|
|
|
|
|
|
|
public uint InterpolationType;
|
|
|
|
|
|
|
|
|
|
public AnimTrack(FileReader reader, GameVersion version)
|
|
|
|
|
{
|
|
|
|
|
uint numKeyFrames =0;
|
|
|
|
|
|
|
|
|
|
if (version >= GameVersion.MM3D)
|
2019-08-04 00:00:51 +02:00
|
|
|
|
{
|
2019-08-04 01:33:29 +02:00
|
|
|
|
InterpolationType = reader.ReadByte();
|
|
|
|
|
numKeyFrames = reader.ReadUInt16();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
InterpolationType = reader.ReadUInt32();
|
|
|
|
|
numKeyFrames = reader.ReadUInt32();
|
|
|
|
|
uint unknown = reader.ReadUInt32();
|
|
|
|
|
uint endFrame = reader.ReadUInt32();
|
|
|
|
|
}
|
2019-08-04 00:00:51 +02:00
|
|
|
|
|
2019-08-04 01:33:29 +02:00
|
|
|
|
if (InterpolationType == (uint)AnimationTrackType.LINEAR)
|
|
|
|
|
{
|
|
|
|
|
if (version >= GameVersion.MM3D)
|
|
|
|
|
{
|
|
|
|
|
float scale = reader.ReadSingle();
|
|
|
|
|
float bias = reader.ReadSingle();
|
|
|
|
|
|
|
|
|
|
for (uint i = 0; i < numKeyFrames; i++)
|
|
|
|
|
{
|
|
|
|
|
LinearKeyFrame keyFrame = new LinearKeyFrame();
|
|
|
|
|
keyFrame.Time = i;
|
|
|
|
|
keyFrame.Value = reader.ReadUInt16() * scale - bias;
|
|
|
|
|
KeyFramesLinear.Add(keyFrame);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < numKeyFrames; i++)
|
|
|
|
|
{
|
|
|
|
|
LinearKeyFrame keyFrame = new LinearKeyFrame();
|
|
|
|
|
keyFrame.Time = reader.ReadUInt32();
|
|
|
|
|
keyFrame.Value = reader.ReadSingle();
|
|
|
|
|
KeyFramesLinear.Add(keyFrame);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (InterpolationType == (uint)AnimationTrackType.HERMITE)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < numKeyFrames; i++)
|
|
|
|
|
{
|
|
|
|
|
HermiteKeyFrame keyFrame = new HermiteKeyFrame();
|
|
|
|
|
keyFrame.Time = reader.ReadUInt32();
|
|
|
|
|
keyFrame.Value = reader.ReadSingle();
|
|
|
|
|
keyFrame.TangentIn = reader.ReadSingle();
|
|
|
|
|
keyFrame.TangentOut = reader.ReadSingle();
|
|
|
|
|
KeyFramesHermite.Add(keyFrame);
|
|
|
|
|
}
|
2019-08-04 00:00:51 +02:00
|
|
|
|
}
|
2019-08-04 01:33:29 +02:00
|
|
|
|
else
|
|
|
|
|
throw new Exception("Unknown interpolation type! " + InterpolationType);
|
2019-08-04 00:00:51 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-04 01:33:29 +02:00
|
|
|
|
|
|
|
|
|
public class HermiteKeyFrame
|
|
|
|
|
{
|
|
|
|
|
public uint Time { get; set; }
|
|
|
|
|
public float Value { get; set; }
|
|
|
|
|
public float TangentIn { get; set; }
|
|
|
|
|
public float TangentOut { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class LinearKeyFrame
|
|
|
|
|
{
|
|
|
|
|
public uint Time { get; set; }
|
|
|
|
|
public float Value { get; set; }
|
|
|
|
|
}
|
2019-08-04 00:00:51 +02:00
|
|
|
|
}
|
|
|
|
|
}
|