From c6ad1ee5eb3dd89b86ebcaa3cd23215301206183 Mon Sep 17 00:00:00 2001
From: Tinifan <30804632+Tiniifan@users.noreply.github.com>
Date: Tue, 31 Oct 2023 01:48:43 +0100
Subject: [PATCH 1/6] Major L5 updates
- target end of line to windows format
- remove the restriction from RES to view iego, yokai 3D model
- support XRES from iego
- add mtn3 support for motion
- add mtninf support
- use huffman decompression from kuriimu2 instead of metanoia huffman decompression
- use rle decompression from kuriimu2 instead of metanoia rle decompression
- can import animations from xpck (.xc)
---
Metanoia/App.config | 6 +-
Metanoia/Formats/3DS/Level5/Level5_MINF.cs | 37 ++
Metanoia/Formats/3DS/Level5/Level5_MINF2.cs | 74 ++++
Metanoia/Formats/3DS/Level5/Level5_MTN2.cs | 87 +++++
Metanoia/Formats/3DS/Level5/Level5_MTN3.cs | 316 ++++++++++++++++++
Metanoia/Formats/3DS/Level5/Level5_Res.cs | 223 +++++++-----
Metanoia/Formats/3DS/Level5/Level5_XC.cs | 119 ++++++-
Metanoia/Formats/3DS/Level5/Level5_XI.cs | 3 +
Metanoia/Metanoia.csproj | 7 +-
Metanoia/Modeling/GenericAnimation.cs | 55 ++-
.../Modeling/GenericAnimationTransform.cs | 2 +-
Metanoia/Modeling/GenericTexture.cs | 2 +
Metanoia/Properties/Resources.Designer.cs | 44 +--
Metanoia/Properties/Settings.Designer.cs | 30 +-
Metanoia/Rendering/ModelViewer.Designer.cs | 38 +--
Metanoia/Rendering/ModelViewer.cs | 18 +-
Metanoia/Tools/3DSImageTools.cs | 36 +-
Metanoia/Tools/DataReader.cs | 20 ++
Metanoia/Tools/Decompress.cs | 302 +++++++----------
Metanoia/Tools/ImageCleaner.cs | 63 ++++
20 files changed, 1148 insertions(+), 334 deletions(-)
create mode 100644 Metanoia/Formats/3DS/Level5/Level5_MINF.cs
create mode 100644 Metanoia/Formats/3DS/Level5/Level5_MINF2.cs
create mode 100644 Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
create mode 100644 Metanoia/Tools/ImageCleaner.cs
diff --git a/Metanoia/App.config b/Metanoia/App.config
index c33a384..7a7f089 100644
--- a/Metanoia/App.config
+++ b/Metanoia/App.config
@@ -1,11 +1,11 @@
-
+
-
+
-
\ No newline at end of file
+
diff --git a/Metanoia/Formats/3DS/Level5/Level5_MINF.cs b/Metanoia/Formats/3DS/Level5/Level5_MINF.cs
new file mode 100644
index 0000000..ec6c220
--- /dev/null
+++ b/Metanoia/Formats/3DS/Level5/Level5_MINF.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Metanoia.Formats._3DS.Level5
+{
+ public class Level5_MINF
+ {
+ public uint AnimationName;
+
+ public uint AnimationSubName;
+
+ public int FrameStart;
+
+ public int FrameEnd;
+
+ public void Open(byte[] data)
+ {
+ using (DataReader reader = new DataReader(data))
+ {
+ reader.BigEndian = false;
+
+ reader.Seek(0x1C);
+ AnimationSubName = reader.ReadUInt32();
+
+ reader.Seek(0x44);
+ AnimationName = reader.ReadUInt32();
+
+ reader.Seek(0x4C);
+ FrameStart = reader.ReadInt32();
+ FrameEnd = reader.ReadInt32();
+ }
+ }
+ }
+}
diff --git a/Metanoia/Formats/3DS/Level5/Level5_MINF2.cs b/Metanoia/Formats/3DS/Level5/Level5_MINF2.cs
new file mode 100644
index 0000000..2c46152
--- /dev/null
+++ b/Metanoia/Formats/3DS/Level5/Level5_MINF2.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Metanoia.Formats._3DS.Level5
+{
+ public class Level5_MINF2
+ {
+ public List SubAnimations;
+
+ public class SubAnimation
+ {
+ public uint AnimationName;
+
+ public string AnimationSubName;
+
+ public int FrameStart;
+
+ public int FrameEnd;
+ }
+
+ public void Open(byte[] data)
+ {
+ using (DataReader reader = new DataReader(data))
+ {
+ reader.BigEndian = false;
+
+ List offsets = new List();
+
+ while (true)
+ {
+ string tryName = reader.ReadString(4);
+
+ if (tryName == "MINF")
+ {
+ reader.Skip(0x04);
+ int minfDataOffset = reader.ReadInt32();
+ offsets.Add(minfDataOffset);
+ reader.Skip(0x0C);
+ } else
+ {
+ reader.Seek(reader.Position - 4);
+ break;
+ }
+ }
+
+ SubAnimations = new List();
+
+ Console.WriteLine("minf2pose = " + reader.Position);
+
+ using (DataReader minfDataReader = new DataReader(reader.GetSection(reader.Position, (int)(reader.Length - reader.Position))))
+ {
+ for (int i = 0; i < offsets.Count; i++)
+ {
+ minfDataReader.Seek((uint)offsets[i]);
+ uint hash = minfDataReader.ReadUInt32();
+ string name = minfDataReader.ReadString(Encoding.GetEncoding("Shift-JIS"));
+
+ minfDataReader.Seek((uint)offsets[i] + 0x28);
+
+ SubAnimation newSubAnimation = new SubAnimation();
+ newSubAnimation.AnimationName = minfDataReader.ReadUInt32();
+ minfDataReader.Skip(0x04);
+ newSubAnimation.AnimationSubName = name;
+ newSubAnimation.FrameStart = minfDataReader.ReadInt32();
+ newSubAnimation.FrameEnd = minfDataReader.ReadInt32();
+
+ SubAnimations.Add(newSubAnimation);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Metanoia/Formats/3DS/Level5/Level5_MTN2.cs b/Metanoia/Formats/3DS/Level5/Level5_MTN2.cs
index 46dc30e..678449a 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_MTN2.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_MTN2.cs
@@ -25,6 +25,93 @@ public class AnimTrack
public int End;
}
+ public void Open(string name, byte[] fileContent)
+ {
+ anim.Name = name;
+
+ using (DataReader r = new DataReader(fileContent))
+ {
+ r.BigEndian = false;
+
+ r.Seek(0x08);
+ var decomSize = r.ReadInt32();
+ var nameOffset = r.ReadUInt32();
+ var compDataOffset = r.ReadUInt32();
+ var positionCount = r.ReadInt32();
+ var rotationCount = r.ReadInt32();
+ var scaleCount = r.ReadInt32();
+ var unknownCount = r.ReadInt32();
+ var boneCount = r.ReadInt32();
+
+ r.Seek(0x54);
+ anim.FrameCount = r.ReadInt32();
+
+ r.Seek(nameOffset);
+ var hash = r.ReadUInt32();
+ anim.Name = r.ReadString(r.Position, -1);
+
+ var data = Decompress.Level5Decom(r.GetSection(compDataOffset, (int)(r.Length - compDataOffset)));
+
+ using (DataReader d = new DataReader(data))
+ {
+ // Header
+ var boneHashTableOffset = d.ReadUInt32();
+ var trackInfoOffset = d.ReadUInt32();
+ var dataOffset = d.ReadUInt32();
+
+ // Bone Hashes
+ List boneNameHashes = new List();
+ d.Seek(boneHashTableOffset);
+ while (d.Position < trackInfoOffset)
+ boneNameHashes.Add(d.ReadUInt32());
+
+ // Track Information
+ List Tracks = new List();
+ for (int i = 0; i < 4; i++)
+ {
+ d.Seek((uint)(trackInfoOffset + 2 * i));
+ d.Seek(d.ReadUInt16());
+
+ Tracks.Add(new AnimTrack()
+ {
+ Type = d.ReadByte(),
+ DataType = d.ReadByte(),
+ unk = d.ReadByte(),
+ DataCount = d.ReadByte(),
+ Start = d.ReadUInt16(),
+ End = d.ReadUInt16()
+ });
+ }
+
+ foreach (var v in Tracks)
+ Console.WriteLine(v.Type + " "
+ + v.DataType + " "
+ + v.DataCount
+ + " " + v.Start.ToString("X")
+ + " " + v.End.ToString("X"));
+
+ // Data
+
+ foreach (var v in boneNameHashes)
+ {
+ var node = new GenericAnimationTransform();
+ node.Hash = v;
+ node.HashType = AnimNodeHashType.CRC32C;
+ anim.TransformNodes.Add(node);
+ }
+
+ var offset = 0;
+ ReadFrameData(d, offset, positionCount, dataOffset, boneCount, Tracks[0]);
+ offset += positionCount;
+ ReadFrameData(d, offset, rotationCount, dataOffset, boneCount, Tracks[1]);
+ offset += rotationCount;
+ ReadFrameData(d, offset, scaleCount, dataOffset, boneCount, Tracks[2]);
+ offset += scaleCount;
+ //ReadFrameData(d, unknownCount, dataOffset, boneCount, Tracks[3]);
+ }
+ }
+ }
+
public void Open(FileItem file)
{
anim.Name = file.FilePath;
diff --git a/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs b/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
new file mode 100644
index 0000000..83c9a86
--- /dev/null
+++ b/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
@@ -0,0 +1,316 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Metanoia.Modeling;
+using Metanoia.Tools;
+
+namespace Metanoia.Formats._3DS.Level5
+{
+ public class Level5_MTN3 : IAnimationFormat
+ {
+ public string Name => "Level5 Motion";
+ public string Extension => ".mtn3";
+ public string Description => "";
+ public bool CanOpen => true;
+ public bool CanSave => false;
+
+ private GenericAnimation anim = new GenericAnimation();
+
+ public class AnimTrack
+ {
+ public int Type;
+ public int DataType;
+ public int unk;
+ public int DataCount;
+ public int Start;
+ public int End;
+ }
+
+ public class AnimTableOffset
+ {
+ public int FlagOffset;
+ public int KeyFrameOffset;
+ public int KeyDataOffset;
+ }
+
+ public void Open(string name, byte[] fileContent)
+ {
+ anim.Name = name;
+
+ using (DataReader reader = new DataReader(fileContent))
+ {
+ reader.BigEndian = false;
+
+ reader.Seek(0x04);
+ int hashOffset = reader.ReadInt16() - 4;
+ int nameOffset = reader.ReadInt16() - 4;
+ int animOffset = reader.ReadInt16() - hashOffset - 4;
+ reader.Skip(0x06);
+ int compDataLength = reader.ReadInt32();
+ reader.Skip(0x04);
+ int positionCount = reader.ReadInt32();
+ int rotationCount = reader.ReadInt32();
+ int scaleCount = reader.ReadInt32();
+ int unknownCount = reader.ReadInt32();
+ int boneCount = reader.ReadInt32();
+
+ reader.Seek((uint)hashOffset);
+ var hash = reader.ReadUInt32();
+ anim.Name = reader.ReadString(reader.Position, -1);
+
+ reader.Seek((uint)animOffset);
+ Console.WriteLine("hashOffset = " + hashOffset);
+ anim.FrameCount = reader.ReadInt32();
+ short positionTrackOffset = reader.ReadInt16();
+ short rotationTrackOffset = reader.ReadInt16();
+ short scaleTrackOffset = reader.ReadInt16();
+ short unknownTrackOffset = reader.ReadInt16();
+
+ List animTableOffset = new List();
+ for (int i = 0; i < positionCount + rotationCount + scaleCount + unknownCount; i++)
+ {
+ animTableOffset.Add(new AnimTableOffset()
+ {
+ FlagOffset = reader.ReadInt32(),
+ KeyFrameOffset = reader.ReadInt32(),
+ KeyDataOffset = reader.ReadInt32(),
+ });
+ reader.Skip(0x04);
+ }
+
+ Console.WriteLine("FrameOffset = " + reader.Position);
+
+ using (DataReader dataReader = new DataReader(Decompress.Level5Decom(reader.GetSection(reader.Position, (int)(reader.Length - reader.Position)))))
+ {
+ // Bone Hashes
+ List boneNameHashes = new List();
+ for (int i = 0; i < boneCount; i++)
+ {
+ boneNameHashes.Add(dataReader.ReadUInt32());
+ }
+
+ // Track Information
+ List Tracks = new List();
+ dataReader.Seek((uint)positionTrackOffset);
+ for (int i = 0; i < 4; i++)
+ {
+ Tracks.Add(new AnimTrack()
+ {
+ Type = dataReader.ReadByte(),
+ DataType = dataReader.ReadByte(),
+ unk = dataReader.ReadByte(),
+ DataCount = dataReader.ReadByte(),
+ Start = dataReader.ReadUInt16(),
+ End = dataReader.ReadUInt16()
+ });
+ }
+
+ foreach (var v in Tracks)
+ Console.WriteLine(v.Type + " "
+ + v.DataType + " "
+ + v.DataCount
+ + " " + v.Start.ToString("X")
+ + " " + v.End.ToString("X"));
+
+ // Data
+
+ foreach (var v in boneNameHashes)
+ {
+ var node = new GenericAnimationTransform();
+ node.Hash = v;
+ node.HashType = AnimNodeHashType.CRC32C;
+ anim.TransformNodes.Add(node);
+ }
+
+ Console.WriteLine("FlagOffset " + animTableOffset[0].FlagOffset);
+ Console.WriteLine("KeyFrameOffset " + animTableOffset[0].KeyFrameOffset);
+ Console.WriteLine("KeyDataOffset " + animTableOffset[0].KeyDataOffset);
+
+ using (DataReader animDataReader = new DataReader(dataReader.GetSection(dataReader.Position, (int)(dataReader.Length - dataReader.Position))))
+ {
+ ReadFrameData(animDataReader, animTableOffset.Take(positionCount).ToList(), boneCount, Tracks[0]);
+ ReadFrameData(animDataReader, animTableOffset.Skip(positionCount).Take(rotationCount).ToList(), boneCount, Tracks[1]);
+ ReadFrameData(animDataReader, animTableOffset.Skip(positionCount + rotationCount).Take(scaleCount).ToList(), boneCount, Tracks[2]);
+ }
+ }
+ }
+ }
+
+ public void Open(FileItem file)
+ {
+ anim.Name = file.FilePath;
+
+ using (DataReader reader = new DataReader(file))
+ {
+ reader.BigEndian = false;
+
+ reader.Seek(0x04);
+ int hashOffset = reader.ReadInt16() - 4;
+ int nameOffset = reader.ReadInt16() - 4;
+ int animOffset = reader.ReadInt16() - hashOffset - 4;
+ reader.Skip(0x06);
+ int compDataLength = reader.ReadInt32();
+ reader.Skip(0x04);
+ int positionCount = reader.ReadInt32();
+ int rotationCount = reader.ReadInt32();
+ int scaleCount = reader.ReadInt32();
+ int unknownCount = reader.ReadInt32();
+ int boneCount = reader.ReadInt32();
+
+ reader.Seek((uint)hashOffset);
+ var hash = reader.ReadUInt32();
+ anim.Name = reader.ReadString(reader.Position, -1);
+
+ reader.Seek((uint)animOffset);
+ Console.WriteLine("hashOffset = " + hashOffset);
+ anim.FrameCount = reader.ReadInt32();
+ short positionTrackOffset = reader.ReadInt16();
+ short rotationTrackOffset = reader.ReadInt16();
+ short scaleTrackOffset = reader.ReadInt16();
+ short unknownTrackOffset = reader.ReadInt16();
+
+ List animTableOffset = new List();
+ for (int i = 0; i < positionCount + rotationCount + scaleCount + unknownCount; i++)
+ {
+ animTableOffset.Add(new AnimTableOffset()
+ {
+ FlagOffset = reader.ReadInt32(),
+ KeyFrameOffset = reader.ReadInt32(),
+ KeyDataOffset = reader.ReadInt32(),
+ });
+ reader.Skip(0x04);
+ }
+
+ Console.WriteLine("FrameOffset = " + reader.Position);
+
+ using (DataReader dataReader = new DataReader(Decompress.Level5Decom(reader.GetSection(reader.Position, (int)(reader.Length - reader.Position)))))
+ {
+ // Bone Hashes
+ List boneNameHashes = new List();
+ for (int i = 0; i < boneCount; i++)
+ {
+ boneNameHashes.Add(dataReader.ReadUInt32());
+ }
+
+ // Track Information
+ List Tracks = new List();
+ dataReader.Seek((uint)positionTrackOffset);
+ for (int i = 0; i < 4; i++)
+ {
+ Tracks.Add(new AnimTrack()
+ {
+ Type = dataReader.ReadByte(),
+ DataType = dataReader.ReadByte(),
+ unk = dataReader.ReadByte(),
+ DataCount = dataReader.ReadByte(),
+ Start = dataReader.ReadUInt16(),
+ End = dataReader.ReadUInt16()
+ });
+ }
+
+ foreach (var v in Tracks)
+ Console.WriteLine(v.Type + " "
+ + v.DataType + " "
+ + v.DataCount
+ + " " + v.Start.ToString("X")
+ + " " + v.End.ToString("X"));
+
+ // Data
+
+ foreach (var v in boneNameHashes)
+ {
+ var node = new GenericAnimationTransform();
+ node.Hash = v;
+ node.HashType = AnimNodeHashType.CRC32C;
+ anim.TransformNodes.Add(node);
+ }
+
+ Console.WriteLine("FlagOffset " + animTableOffset[0].FlagOffset);
+ Console.WriteLine("KeyFrameOffset " + animTableOffset[0].KeyFrameOffset);
+ Console.WriteLine("KeyDataOffset " + animTableOffset[0].KeyDataOffset);
+
+ using (DataReader animDataReader = new DataReader(dataReader.GetSection(dataReader.Position, (int)(dataReader.Length - dataReader.Position))))
+ {
+ ReadFrameData(animDataReader, animTableOffset.Take(positionCount).ToList(), boneCount, Tracks[0]);
+ ReadFrameData(animDataReader, animTableOffset.Skip(positionCount).Take(rotationCount).ToList(), boneCount, Tracks[1]);
+ ReadFrameData(animDataReader, animTableOffset.Skip(positionCount + rotationCount).Take(scaleCount).ToList(), boneCount, Tracks[2]);
+ }
+ }
+ }
+ }
+
+ private void ReadFrameData(DataReader dataReader, List animTableOffset, int boneCount, AnimTrack Track)
+ {
+ for (int i = 0; i < animTableOffset.Count; i++)
+ {
+ dataReader.Seek((uint)animTableOffset[i].FlagOffset);
+ var boneIndex = dataReader.ReadInt16();
+ var keyFrameCount = dataReader.ReadByte();
+ var flag = dataReader.ReadByte();
+
+ var node = anim.TransformNodes[boneIndex + (flag == 0 ? boneCount : 0)];
+
+ dataReader.Seek((uint)animTableOffset[i].KeyDataOffset);
+ for (int k = 0; k < keyFrameCount; k++)
+ {
+ var temp = dataReader.Position;
+ dataReader.Seek((uint)(animTableOffset[i].KeyFrameOffset + k * 2));
+ var frame = dataReader.ReadInt16();
+ dataReader.Seek(temp);
+
+ float[] animdata = new float[Track.DataCount];
+ for (int j = 0; j < Track.DataCount; j++)
+ switch (Track.DataType)
+ {
+ case 1:
+ animdata[j] = dataReader.ReadInt16() / (float)short.MaxValue;
+ break;
+ case 2:
+ animdata[j] = dataReader.ReadSingle();
+ break;
+ case 4:
+ animdata[j] = dataReader.ReadInt16();
+ break;
+ default:
+ throw new NotImplementedException("Data Type " + Track.DataType + " not implemented");
+ }
+
+ switch (Track.Type)
+ {
+ case 1:
+ node.AddKey(frame, animdata[0], AnimationTrackFormat.TranslateX);
+ node.AddKey(frame, animdata[1], AnimationTrackFormat.TranslateY);
+ node.AddKey(frame, animdata[2], AnimationTrackFormat.TranslateZ);
+ break;
+ case 2:
+ var e = GenericBone.ToEulerAngles(new OpenTK.Quaternion(animdata[0], animdata[1], animdata[2], animdata[3]).Inverted());
+ node.AddKey(frame, e.X, AnimationTrackFormat.RotateX);
+ node.AddKey(frame, e.Y, AnimationTrackFormat.RotateY);
+ node.AddKey(frame, e.Z, AnimationTrackFormat.RotateZ);
+ break;
+ case 3:
+ node.AddKey(frame, animdata[0], AnimationTrackFormat.ScaleX);
+ node.AddKey(frame, animdata[1], AnimationTrackFormat.ScaleY);
+ node.AddKey(frame, animdata[2], AnimationTrackFormat.ScaleZ);
+ break;
+ }
+ }
+ }
+ }
+
+ public void Save(string filePath)
+ {
+ throw new NotImplementedException();
+ }
+
+ public GenericAnimation ToGenericAnimation()
+ {
+ return anim;
+ }
+
+ public bool Verify(FileItem file)
+ {
+ return (file.MagicString == "XMTN");
+ }
+ }
+}
diff --git a/Metanoia/Formats/3DS/Level5/Level5_Res.cs b/Metanoia/Formats/3DS/Level5/Level5_Res.cs
index 199d6fe..ee0a466 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_Res.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_Res.cs
@@ -28,96 +28,167 @@ public string GetResourceName(uint crc)
return "";
}
- public Level5_Resource(byte[] data)
+ public void RES(DataReader r)
{
- data = Decompress.Level5Decom(data);
- using (DataReader r = new DataReader(new System.IO.MemoryStream(data)))
+ var unknown0 = r.ReadInt16();
+ var stringTableOffset = r.ReadInt16() << 2;
+ var unknown1 = r.ReadInt16();
+ var materialTableOffset = r.ReadInt16() << 2;
+ var materialTableSectionCount = r.ReadInt16();
+ var resourceNodeOffsets = r.ReadInt16() << 2;
+ var resourceNodeCount = r.ReadInt16();
+
+ r.Seek((uint)stringTableOffset);
+ while (r.Position < r.BaseStream.Length)
{
- var magic = new string(r.ReadChars(6));
- if (magic != "CHRC00" && magic != "CHRN01")
- throw new FormatException("RES file is corrupt");
-
- // -----------------------
- var unknown0 = r.ReadInt16();
- var stringTableOffset = r.ReadInt16() << 2;
- var unknown1 = r.ReadInt16();
- var materialTableOffset = r.ReadInt16() << 2;
- var materialTableSectionCount = r.ReadInt16();
- var resourceNodeOffsets = r.ReadInt16() << 2;
- var resourceNodeCount = r.ReadInt16();
-
- r.Seek((uint)stringTableOffset);
- while (r.Position < r.BaseStream.Length)
- {
- string mname = r.ReadString();
- if (mname == "")
- break;
- if (!ResourceNames.ContainsKey(CRC32.Crc32C(mname)))
- ResourceNames.Add(CRC32.Crc32C(mname), mname);
- }
+ string mname = r.ReadString();
+ if (mname == "")
+ break;
+ if (!ResourceNames.ContainsKey(CRC32.Crc32C(mname)))
+ ResourceNames.Add(CRC32.Crc32C(mname), mname);
+ }
- r.Seek((uint)materialTableOffset);
- for (int i = 0; i < materialTableSectionCount; i++)
- {
- var offset = r.ReadInt16() << 2;
- var count = r.ReadInt16();
- var unknown = r.ReadInt16();
- var size = r.ReadInt16();
+ r.Seek((uint)materialTableOffset);
+ for (int i = 0; i < materialTableSectionCount; i++)
+ {
+ var offset = r.ReadInt16() << 2;
+ var count = r.ReadInt16();
+ var unknown = r.ReadInt16();
+ var size = r.ReadInt16();
+
+ Console.WriteLine("test " + offset + " " + count + unknown + " " + size);
- if (unknown == 0x270F)
- continue;
+ if (unknown == 0x270F)
+ continue;
+
+ var temp = r.Position;
+ for (int j = 0; j < count; j++)
+ {
+ r.Position = (uint)(offset + j * size);
+ var key = r.ReadUInt32();
+ string resourceName = (ResourceNames.ContainsKey(key) ? ResourceNames[key] : key.ToString("X"));
+ //Console.WriteLine(resourceName + " " + unknown.ToString("X") + " " + size.ToString("X"));
- var temp = r.Position;
- for(int j = 0; j 0 && textureList.Count % 2 == 0)
+ {
+ for (int i = 0; i < textureList.Count/2+1; i+=2)
+ {
+ _3DSImageTools.Tex_Formats pixelFormat1 = (_3DSImageTools.Tex_Formats)textureList[i].PixelFormatTrue;
+ _3DSImageTools.Tex_Formats pixelFormat2 = (_3DSImageTools.Tex_Formats)textureList[i+1].PixelFormatTrue;
+
+ if (pixelFormat1 == _3DSImageTools.Tex_Formats.ETC1 && pixelFormat2 == _3DSImageTools.Tex_Formats.RGB565)
+ {
+ Bitmap etc1 = GenericTexture.GetBitmap((int)textureList[i].Width, (int)textureList[i].Height, textureList[i].Mipmaps[0]);
+ Bitmap rgb = GenericTexture.GetBitmap((int)textureList[i+1].Width, (int)textureList[i+1].Height, textureList[i+1].Mipmaps[0]);
+
+ ImageCleaner imageCleaner = new ImageCleaner(rgb, etc1);
+ imageCleaner.Cleaner();
+
+ textureList[i + 1].Mipmaps.Clear();
+ textureList[i + 1].FromBitmap(imageCleaner.Result);
+ }
+ }
+ }
+
return model;
}
+ public GenericAnimation[] ToGenericAnimation()
+ {
+ int motionCount = Files.Count(x => x.Key.EndsWith("mtn2") || x.Key.EndsWith("mtn3"));
+ int subMotionCount = Files.Count(x => x.Key.EndsWith("mtninf") || x.Key.EndsWith("mtninf2"));
+
+ if (motionCount + subMotionCount > 0)
+ {
+ List animations = new List();
+ Dictionary animDict = new Dictionary();
+ Dictionary> subAnimDict = new Dictionary>();
+ Dictionary> subAnimDict2 = new Dictionary>();
+
+ foreach (var f in Files)
+ {
+ if (f.Key.EndsWith(".mtn2"))
+ {
+ var anim = new Level5_MTN2();
+ anim.Open(f.Key, f.Value);
+ GenericAnimation newAnimation = anim.ToGenericAnimation();
+ animDict.Add(CRC32.Crc32C(newAnimation.Name), newAnimation);
+ }
+ else if (f.Key.EndsWith(".mtn3"))
+ {
+ var anim = new Level5_MTN3();
+ anim.Open(f.Key, f.Value);
+ GenericAnimation newAnimation = anim.ToGenericAnimation();
+ animDict.Add(CRC32.Crc32C(newAnimation.Name), newAnimation);
+ }
+ else if (f.Key.EndsWith(".mtninf"))
+ {
+ var subAnim = new Level5_MINF();
+ subAnim.Open(f.Value);
+
+ if (!subAnimDict.ContainsKey(subAnim.AnimationName))
+ {
+ subAnimDict.Add(subAnim.AnimationName, new List());
+ }
+
+ subAnimDict[subAnim.AnimationName].Add(subAnim);
+ }
+ else if (f.Key.EndsWith(".mtninf2"))
+ {
+ var mtninf2 = new Level5_MINF2();
+ mtninf2.Open(f.Value);
+
+ Console.WriteLine("mtninf2 " + mtninf2.SubAnimations.Count);
+
+ foreach (Level5_MINF2.SubAnimation subAnimation in mtninf2.SubAnimations)
+ {
+ Console.WriteLine(subAnimation.AnimationName.ToString("X8"));
+
+ if (!subAnimDict2.ContainsKey(subAnimation.AnimationName))
+ {
+ subAnimDict2.Add(subAnimation.AnimationName, new List());
+ }
+
+ subAnimDict2[subAnimation.AnimationName].Add(subAnimation);
+ }
+ }
+ }
+
+ foreach (KeyValuePair kvp in animDict)
+ {
+ animations.Add(kvp.Value);
+
+ if (subAnimDict.ContainsKey(kvp.Key))
+ {
+ foreach (Level5_MINF minf in subAnimDict[kvp.Key])
+ {
+ GenericAnimation newAnimation = kvp.Value.TrimAnimation(minf.FrameStart, minf.FrameEnd);
+ newAnimation.Name = kvp.Value.Name + "_" + minf.AnimationSubName.ToString("X8");
+ animations.Add(newAnimation);
+ }
+ }
+
+ if (subAnimDict2.ContainsKey(kvp.Key))
+ {
+ foreach (Level5_MINF2.SubAnimation subAnimations in subAnimDict2[kvp.Key])
+ {
+ GenericAnimation newAnimation = kvp.Value.TrimAnimation(subAnimations.FrameStart, subAnimations.FrameEnd);
+ newAnimation.Name = kvp.Value.Name + "_" + subAnimations.AnimationSubName;
+ animations.Add(newAnimation);
+ }
+ }
+
+
+ }
+
+ return animations.ToArray();
+ } else
+ {
+ return null;
+ }
+ }
+
public bool Verify(FileItem file)
{
return file.MagicString == "XPCK";
diff --git a/Metanoia/Formats/3DS/Level5/Level5_XI.cs b/Metanoia/Formats/3DS/Level5/Level5_XI.cs
index ed52df8..bc68b8d 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_XI.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_XI.cs
@@ -57,6 +57,9 @@ public static GenericTexture ToGenericTexture(byte[] xifile)
tex.FromBitmap(texture);
texture.Dispose();
}
+
+ tex.PixelFormatTrue = xi.ImageFormat;
+
return tex;
}
diff --git a/Metanoia/Metanoia.csproj b/Metanoia/Metanoia.csproj
index 502edc6..a6b386f 100644
--- a/Metanoia/Metanoia.csproj
+++ b/Metanoia/Metanoia.csproj
@@ -8,9 +8,10 @@
WinExe
Metanoia
Metanoia
- v4.6.1
+ v4.8
512
true
+
AnyCPU
@@ -83,7 +84,10 @@
+
+
+
@@ -181,6 +185,7 @@
+
diff --git a/Metanoia/Modeling/GenericAnimation.cs b/Metanoia/Modeling/GenericAnimation.cs
index a808771..41fd177 100644
--- a/Metanoia/Modeling/GenericAnimation.cs
+++ b/Metanoia/Modeling/GenericAnimation.cs
@@ -6,19 +6,14 @@ namespace Metanoia.Modeling
public class GenericAnimation
{
public string Name { get; set; }
-
+
public List TransformNodes = new List();
public int FrameCount { get; set; } = 0;
- ///
- ///
- ///
- ///
- ///
public void UpdateSkeleton(float frame, GenericSkeleton skeleton)
{
- foreach(var v in TransformNodes)
+ foreach (var v in TransformNodes)
{
GenericBone bone = null;
switch (v.HashType)
@@ -30,13 +25,57 @@ public void UpdateSkeleton(float frame, GenericSkeleton skeleton)
bone = skeleton.Bones.Find(e => e.NameHash == v.Hash);
break;
}
- if(bone != null)
+ if (bone != null)
{
bone.AnimatedTransform = v.GetTransformAt(frame, bone);
}
}
}
+ public GenericAnimation TrimAnimation(float startFrame, float endFrame)
+ {
+ // Create a new animation to store the trimmed data
+ var trimmedAnimation = new GenericAnimation
+ {
+ Name = this.Name,
+ FrameCount = (int)(endFrame - startFrame + 1)
+ };
+
+ // Copy transform nodes
+ foreach (var transformNode in this.TransformNodes)
+ {
+ var trimmedTransformNode = new GenericAnimationTransform
+ {
+ Name = transformNode.Name,
+ Hash = transformNode.Hash,
+ HashType = transformNode.HashType
+ };
+
+ // Copy tracks
+ foreach (var track in transformNode.Tracks)
+ {
+ var trimmedTrack = new GenericTransformTrack(track.Type);
+
+ // Copy keys within the specified frame range
+ foreach (var key in track.Keys.Keys)
+ {
+ if (key.Frame >= startFrame && key.Frame <= endFrame || key.Frame == 0)
+ {
+ float trimmedFrame = key.Frame - startFrame;
+ trimmedTrack.AddKey(trimmedFrame, key.Value, key.InterpolationType, key.InTan, key.OutTan);
+ }
+ }
+
+ trimmedTransformNode.Tracks.Add(trimmedTrack);
+ }
+
+ trimmedAnimation.TransformNodes.Add(trimmedTransformNode);
+ }
+
+ return trimmedAnimation;
+ }
+
+
public override string ToString()
{
return Name;
diff --git a/Metanoia/Modeling/GenericAnimationTransform.cs b/Metanoia/Modeling/GenericAnimationTransform.cs
index c96c602..1026f6f 100644
--- a/Metanoia/Modeling/GenericAnimationTransform.cs
+++ b/Metanoia/Modeling/GenericAnimationTransform.cs
@@ -152,7 +152,7 @@ public class GenericTransformTrack
{
public AnimationTrackFormat Type { get; internal set; }
- public GenericKeyGroup Keys { get; } = new GenericKeyGroup();
+ public GenericKeyGroup Keys { get; set; } = new GenericKeyGroup();
public GenericTransformTrack(AnimationTrackFormat type)
{
diff --git a/Metanoia/Modeling/GenericTexture.cs b/Metanoia/Modeling/GenericTexture.cs
index c8196f2..a219fa5 100644
--- a/Metanoia/Modeling/GenericTexture.cs
+++ b/Metanoia/Modeling/GenericTexture.cs
@@ -17,6 +17,8 @@ public class GenericTexture
public uint Height { get; set; }
+ public byte PixelFormatTrue { get; set; }
+
public int Id;
public List Mipmaps = new List();
diff --git a/Metanoia/Properties/Resources.Designer.cs b/Metanoia/Properties/Resources.Designer.cs
index abdc15f..a500de9 100644
--- a/Metanoia/Properties/Resources.Designer.cs
+++ b/Metanoia/Properties/Resources.Designer.cs
@@ -1,10 +1,10 @@
//------------------------------------------------------------------------------
//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
+// Ce code a été généré par un outil.
+// Version du runtime :4.0.30319.42000
//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
+// Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si
+// le code est régénéré.
//
//------------------------------------------------------------------------------
@@ -13,13 +13,13 @@ namespace Metanoia.Properties {
///
- /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// Une classe de ressource fortement typée destinée, entre autres, à la consultation des chaînes localisées.
///
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // class via a tool like ResGen or Visual Studio.
- // To add or remove a member, edit your .ResX file then rerun ResGen
- // with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
+ // Cette classe a été générée automatiquement par la classe StronglyTypedResourceBuilder
+ // à l'aide d'un outil, tel que ResGen ou Visual Studio.
+ // Pour ajouter ou supprimer un membre, modifiez votre fichier .ResX, puis réexécutez ResGen
+ // avec l'option /str ou régénérez votre projet VS.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
@@ -33,7 +33,7 @@ internal Resources() {
}
///
- /// Returns the cached ResourceManager instance used by this class.
+ /// Retourne l'instance ResourceManager mise en cache utilisée par cette classe.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
@@ -47,8 +47,8 @@ internal Resources() {
}
///
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
+ /// Remplace la propriété CurrentUICulture du thread actuel pour toutes
+ /// les recherches de ressources à l'aide de cette classe de ressource fortement typée.
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
@@ -61,7 +61,7 @@ internal Resources() {
}
///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
+ /// Recherche une ressource localisée de type System.Drawing.Bitmap.
///
internal static System.Drawing.Bitmap icon_about {
get {
@@ -71,7 +71,7 @@ internal static System.Drawing.Bitmap icon_about {
}
///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
+ /// Recherche une ressource localisée de type System.Drawing.Bitmap.
///
internal static System.Drawing.Bitmap icon_begin {
get {
@@ -81,7 +81,7 @@ internal static System.Drawing.Bitmap icon_begin {
}
///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
+ /// Recherche une ressource localisée de type System.Drawing.Bitmap.
///
internal static System.Drawing.Bitmap icon_bone_off {
get {
@@ -91,7 +91,7 @@ internal static System.Drawing.Bitmap icon_bone_off {
}
///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
+ /// Recherche une ressource localisée de type System.Drawing.Bitmap.
///
internal static System.Drawing.Bitmap icon_bone_on {
get {
@@ -101,7 +101,7 @@ internal static System.Drawing.Bitmap icon_bone_on {
}
///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
+ /// Recherche une ressource localisée de type System.Drawing.Bitmap.
///
internal static System.Drawing.Bitmap icon_end {
get {
@@ -111,7 +111,7 @@ internal static System.Drawing.Bitmap icon_end {
}
///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
+ /// Recherche une ressource localisée de type System.Drawing.Bitmap.
///
internal static System.Drawing.Bitmap icon_export {
get {
@@ -121,7 +121,7 @@ internal static System.Drawing.Bitmap icon_export {
}
///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
+ /// Recherche une ressource localisée de type System.Drawing.Bitmap.
///
internal static System.Drawing.Bitmap icon_pause {
get {
@@ -131,7 +131,7 @@ internal static System.Drawing.Bitmap icon_pause {
}
///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
+ /// Recherche une ressource localisée de type System.Drawing.Bitmap.
///
internal static System.Drawing.Bitmap icon_play {
get {
@@ -141,7 +141,7 @@ internal static System.Drawing.Bitmap icon_play {
}
///
- /// Looks up a localized resource of type System.Drawing.Bitmap.
+ /// Recherche une ressource localisée de type System.Drawing.Bitmap.
///
internal static System.Drawing.Bitmap icon_view {
get {
diff --git a/Metanoia/Properties/Settings.Designer.cs b/Metanoia/Properties/Settings.Designer.cs
index 7365b22..3f4c321 100644
--- a/Metanoia/Properties/Settings.Designer.cs
+++ b/Metanoia/Properties/Settings.Designer.cs
@@ -1,28 +1,24 @@
//------------------------------------------------------------------------------
//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
+// Ce code a été généré par un outil.
+// Version du runtime :4.0.30319.42000
//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
+// Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si
+// le code est régénéré.
//
//------------------------------------------------------------------------------
-namespace Metanoia.Properties
-{
-
-
+namespace Metanoia.Properties {
+
+
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
- internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
- {
-
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.2.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
-
- public static Settings Default
- {
- get
- {
+
+ public static Settings Default {
+ get {
return defaultInstance;
}
}
diff --git a/Metanoia/Rendering/ModelViewer.Designer.cs b/Metanoia/Rendering/ModelViewer.Designer.cs
index 568f685..efba209 100644
--- a/Metanoia/Rendering/ModelViewer.Designer.cs
+++ b/Metanoia/Rendering/ModelViewer.Designer.cs
@@ -31,10 +31,12 @@ protected override void Dispose(bool disposing)
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ModelViewer));
- this.Viewport = new OpenTK.GLControl(new GraphicsMode(new ColorFormat(8, 8, 8, 8), 24, 8, 16));
+ this.Viewport = new OpenTK.GLControl();
this.toolStrip1 = new System.Windows.Forms.ToolStrip();
this.toolStripDropDownButton1 = new System.Windows.Forms.ToolStripDropDownButton();
+ this.exportModelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.importAnimationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.exportAnimationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.exportButton = new System.Windows.Forms.ToolStripButton();
this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator();
this.toolStripButton1 = new System.Windows.Forms.ToolStripButton();
@@ -55,8 +57,6 @@ private void InitializeComponent()
this.buttonNext = new System.Windows.Forms.ToolStripButton();
this.buttonEnd = new System.Windows.Forms.ToolStripButton();
this.frameLabel = new System.Windows.Forms.ToolStripLabel();
- this.exportAnimationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.exportModelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStrip1.SuspendLayout();
this.animationTS.SuspendLayout();
this.SuspendLayout();
@@ -110,13 +110,27 @@ private void InitializeComponent()
this.toolStripDropDownButton1.Size = new System.Drawing.Size(38, 22);
this.toolStripDropDownButton1.Text = "File";
//
+ // exportModelToolStripMenuItem
+ //
+ this.exportModelToolStripMenuItem.Name = "exportModelToolStripMenuItem";
+ this.exportModelToolStripMenuItem.Size = new System.Drawing.Size(182, 22);
+ this.exportModelToolStripMenuItem.Text = "Export Model";
+ this.exportModelToolStripMenuItem.Click += new System.EventHandler(this.exportModelToolStripMenuItem_Click);
+ //
// importAnimationToolStripMenuItem
//
this.importAnimationToolStripMenuItem.Name = "importAnimationToolStripMenuItem";
- this.importAnimationToolStripMenuItem.Size = new System.Drawing.Size(169, 22);
+ this.importAnimationToolStripMenuItem.Size = new System.Drawing.Size(182, 22);
this.importAnimationToolStripMenuItem.Text = "Import Animation(s)";
this.importAnimationToolStripMenuItem.Click += new System.EventHandler(this.importAnimationToolStripMenuItem_Click);
//
+ // exportAnimationToolStripMenuItem
+ //
+ this.exportAnimationToolStripMenuItem.Name = "exportAnimationToolStripMenuItem";
+ this.exportAnimationToolStripMenuItem.Size = new System.Drawing.Size(182, 22);
+ this.exportAnimationToolStripMenuItem.Text = "Export Animation";
+ this.exportAnimationToolStripMenuItem.Click += new System.EventHandler(this.exportAnimationToolStripMenuItem_Click);
+ //
// exportButton
//
this.exportButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
@@ -229,7 +243,7 @@ private void InitializeComponent()
//
this.animationCB.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.animationCB.Name = "animationCB";
- this.animationCB.Size = new System.Drawing.Size(121, 25);
+ this.animationCB.Size = new System.Drawing.Size(130, 25);
this.animationCB.SelectedIndexChanged += new System.EventHandler(this.animationCB_SelectedIndexChanged);
//
// buttonBegin
@@ -288,20 +302,6 @@ private void InitializeComponent()
this.frameLabel.Size = new System.Drawing.Size(69, 22);
this.frameLabel.Text = "Frame: 0 / 0";
//
- // exportAnimationToolStripMenuItem
- //
- this.exportAnimationToolStripMenuItem.Name = "exportAnimationToolStripMenuItem";
- this.exportAnimationToolStripMenuItem.Size = new System.Drawing.Size(169, 22);
- this.exportAnimationToolStripMenuItem.Text = "Export Animation";
- this.exportAnimationToolStripMenuItem.Click += new System.EventHandler(this.exportAnimationToolStripMenuItem_Click);
- //
- // exportModelToolStripMenuItem
- //
- this.exportModelToolStripMenuItem.Name = "exportModelToolStripMenuItem";
- this.exportModelToolStripMenuItem.Size = new System.Drawing.Size(169, 22);
- this.exportModelToolStripMenuItem.Text = "Export Model";
- this.exportModelToolStripMenuItem.Click += new System.EventHandler(this.exportModelToolStripMenuItem_Click);
- //
// ModelViewer
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
diff --git a/Metanoia/Rendering/ModelViewer.cs b/Metanoia/Rendering/ModelViewer.cs
index cfc8181..80f103a 100644
--- a/Metanoia/Rendering/ModelViewer.cs
+++ b/Metanoia/Rendering/ModelViewer.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using System.Drawing;
using System.Windows.Forms;
using OpenTK;
@@ -8,6 +9,7 @@
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using Metanoia.Formats;
+using Metanoia.Formats._3DS.Level5;
namespace Metanoia.Rendering
{
@@ -506,7 +508,21 @@ private void importAnimationToolStripMenuItem_Click(object sender, EventArgs e)
{
foreach (String filename in d.FileNames)
{
- if (FormatManager.Instance.Open(new FileItem(filename)) is IAnimationFormat anim)
+ if (Path.GetExtension(filename) == ".xc")
+ {
+ Level5_XC animArchive = new Level5_XC();
+ animArchive.Open(new FileItem(filename, File.ReadAllBytes(filename)));
+ GenericAnimation[] animations = animArchive.ToGenericAnimation();
+
+ if (animations != null)
+ {
+ foreach (GenericAnimation animation in animArchive.ToGenericAnimation())
+ {
+ AddAnimation(animation);
+ }
+ }
+
+ } else if (FormatManager.Instance.Open(new FileItem(filename)) is IAnimationFormat anim)
{
AddAnimation(anim.ToGenericAnimation());
}
diff --git a/Metanoia/Tools/3DSImageTools.cs b/Metanoia/Tools/3DSImageTools.cs
index e597caa..d8fae7d 100644
--- a/Metanoia/Tools/3DSImageTools.cs
+++ b/Metanoia/Tools/3DSImageTools.cs
@@ -60,7 +60,41 @@ public static Bitmap DecodeImage(byte[] data, int width, int height, Tex_Formats
case Tex_Formats.RGBA8: colors[i] = Decode8888(data[p++], data[p++], data[p++], data[p++]); break;
case Tex_Formats.RGB8: colors[i] = Decode888(data[p++], data[p++], data[p++]); break;
case Tex_Formats.RGBA5551: colors[i] = Decode5551(data[p++], data[p++]); break;
- case Tex_Formats.RGB565: colors[i] = Decode565(data[p++], data[p++]); break;
+ case Tex_Formats.RGB565:
+ {
+ try
+ {
+ colors[i] = _3DSImageTools.Decode565((int)data[p++], (int)data[p++]);
+ break;
+ }
+ catch (IndexOutOfRangeException)
+ {
+ for (h = 0; h < height; h += 8)
+ {
+ for (w = 0; w < width; w += 8)
+ {
+ colors = new int[64];
+ for (i = 0; i < 64; i++)
+ {
+ if (type == _3DSImageTools.Tex_Formats.RGB565)
+ {
+ colors[i] = 100;
+ }
+ }
+ }
+ }
+ for (int bh = 0; bh < 8; bh++)
+ {
+ for (int bw = 0; bw < 8; bw++)
+ {
+ }
+ }
+ }
+ BitmapData bmpData2 = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
+ Marshal.Copy(pixels, 0, bmpData2.Scan0, pixels.Length);
+ bmp.UnlockBits(bmpData2);
+ return bmp;
+ }
case Tex_Formats.RGBA4444: colors[i] = Decode4444(data[p++], data[p++]); break;
case Tex_Formats.LA8: colors[i] = DecodeLA8(data[p++], data[p++]); break;
case Tex_Formats.HILO8: colors[i] = decodeHILO8(data[p++], data[p++]); break;
diff --git a/Metanoia/Tools/DataReader.cs b/Metanoia/Tools/DataReader.cs
index 5fb303b..ac65c14 100644
--- a/Metanoia/Tools/DataReader.cs
+++ b/Metanoia/Tools/DataReader.cs
@@ -1,6 +1,8 @@
using System;
using System.IO;
+using System.Text;
using System.Linq;
+using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Metanoia
@@ -19,6 +21,11 @@ public DataReader(byte[] data) : base(new MemoryStream(data))
}
+ public DataReader(byte[] data, Encoding encoding) : base(new MemoryStream(data), encoding)
+ {
+
+ }
+
public DataReader(Stream input) : base(input)
{
}
@@ -97,6 +104,19 @@ public override string ReadString()
return str;
}
+ public string ReadString(Encoding encoding)
+ {
+ List text = new List();
+
+ byte ch;
+ while ((ch = ReadByte()) != 0)
+ {
+ text.Add(ch);
+ }
+
+ return encoding.GetString(text.ToArray());
+ }
+
public string ReadString(int Size)
{
string str = "";
diff --git a/Metanoia/Tools/Decompress.cs b/Metanoia/Tools/Decompress.cs
index 56c6e27..cd8c6b5 100644
--- a/Metanoia/Tools/Decompress.cs
+++ b/Metanoia/Tools/Decompress.cs
@@ -1,4 +1,6 @@
using System;
+using System.Linq;
+using System.Text;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
@@ -41,10 +43,10 @@ public static byte[] Level5Decom(byte[] b)
t = (lzss_Decompress(b));
break;
case 0x02:
- t = (Huffman_Decompress(b, (byte)0x24));
+ t = Huffman_Decompress(b, 4);
break;
case 0x03:
- t = (Huffman_Decompress(b, (byte)0x28));
+ t = Huffman_Decompress(b, 8);
break;
case 0x04:
t = (rle_Decompress(b));
@@ -428,240 +430,172 @@ public static byte[] lzss_Decompress(byte[] data)
public static byte[] rle_Decompress(byte[] instream)
{
- long inLength = instream.Length;
- long ReadBytes = 0;
- int p = 0;
-
- p++;
-
- int decompressedSize = (instream[p++] & 0xFF)
- | ((instream[p++] & 0xFF) << 8)
- | ((instream[p++] & 0xFF) << 16);
- ReadBytes += 4;
- if (decompressedSize == 0)
+ using (var input = new MemoryStream(instream))
+ using (var output = new MemoryStream())
{
- decompressedSize = decompressedSize
- | ((instream[p++] & 0xFF) << 24);
- ReadBytes += 4;
+ var decoder = new RleDecoder();
+ decoder.Decode(input, output);
+
+ return output.ToArray();
}
+ }
- List outstream = new List();
+ // RLE From Kuriimu2
- while (p < instream.Length)
+ public class RleHeaderlessDecoder
+ {
+ public void Decode(Stream input, Stream output, int decompressedSize)
{
-
- int flag = (byte)instream[p++];
- ReadBytes++;
-
- bool compressed = (flag & 0x80) > 0;
- int length = flag & 0x7F;
-
- if (compressed)
- length += 3;
- else
- length += 1;
-
- if (compressed)
+ while (output.Length < decompressedSize)
{
-
- int data = (byte)instream[p++];
- ReadBytes++;
-
- byte bdata = (byte)data;
- for (int i = 0; i < length; i++)
+ var flag = input.ReadByte();
+ if ((flag & 0x80) > 0)
{
- outstream.Add(bdata);
+ var repetitions = (flag & 0x7F) + 3;
+ output.Write(Enumerable.Repeat((byte)input.ReadByte(), repetitions).ToArray(), 0, repetitions);
}
-
- }
- else
- {
-
- int tryReadLength = length;
- if (ReadBytes + length > inLength)
- tryReadLength = (int)(inLength - ReadBytes);
-
- ReadBytes += tryReadLength;
-
- for (int i = 0; i < tryReadLength; i++)
+ else
{
- outstream.Add((byte)(instream[p++] & 0xFF));
+ var length = flag + 1;
+ var uncompressedData = new byte[length];
+ input.Read(uncompressedData, 0, length);
+ output.Write(uncompressedData, 0, length);
}
}
}
-
- if (ReadBytes < inLength)
- {
- }
-
- return outstream.ToArray();
}
- public class HUFF_STREAM
+ public class RleDecoder
{
- public byte[] bytes;
- public int p = 0;
- public int length;
- public HUFF_STREAM(byte[] b)
- {
- bytes = b;
- length = b.Length;
- }
+ private readonly RleHeaderlessDecoder _decoder;
- public bool hasBytes()
+ public RleDecoder()
{
- return p < bytes.Length;
+ _decoder = new RleHeaderlessDecoder();
}
- public int ReadByte()
+ public void Decode(Stream input, Stream output)
{
- return bytes[p++] & 0xFF;
+ var compressionHeader = new byte[4];
+ input.Read(compressionHeader, 0, 4);
+ if ((compressionHeader[0] & 0x7) != 0x4)
+ throw new Exception("Level5 Rle");
+
+ var decompressedSize = (compressionHeader[0] >> 3) | (compressionHeader[1] << 5) |
+ (compressionHeader[2] << 13) | (compressionHeader[3] << 21);
+
+ _decoder.Decode(input, output, decompressedSize);
}
- public int readThree()
+
+ public void Dispose()
{
- return ((bytes[p++] & 0xFF)) | ((bytes[p++] & 0xFF) << 8) | ((bytes[p++] & 0xFF) << 16);
- }
- public int ReadInt32()
- {
- if (p >= length)
- return 0;
- else
- return ((bytes[p++] & 0xFF)) | ((bytes[p++] & 0xFF) << 8) | ((bytes[p++] & 0xFF) << 16) | ((bytes[p++] & 0xFF) << 24);
+ // Nothing to dispose
}
}
- public class HuffTreeNode
+ // HuffmanDecoder & HuffmanHeaderlessDecoder From Kuriimu2
+ public class HuffmanDecoder
{
- public byte data;
- public bool isData;
- public HuffTreeNode child0; public HuffTreeNode child1;
- public HuffTreeNode(HUFF_STREAM stream, bool isData, long relOffset, long maxStreamPos)
+ private readonly int _bitDepth;
+ private readonly HuffmanHeaderlessDecoder _decoder;
+
+ public HuffmanDecoder(int bitDepth, NibbleOrder nibbleOrder)
{
- if (stream.p >= maxStreamPos)
- {
- return;
- }
- int readData = stream.ReadByte();
- this.data = (byte)readData;
+ _bitDepth = bitDepth;
- this.isData = isData;
+ _decoder = new HuffmanHeaderlessDecoder(bitDepth, nibbleOrder);
+ }
- if (!this.isData)
- {
- int offset = this.data & 0x3F;
- bool zeroIsData = (this.data & 0x80) > 0;
- bool oneIsData = (this.data & 0x40) > 0;
+ public void Decode(Stream input, Stream output)
+ {
+ var compressionHeader = new byte[4];
+ input.Read(compressionHeader, 0, 4);
- long zeroRelOffset = (relOffset ^ (relOffset & 1)) + (offset * 2) + 2;
+ var huffmanMode = _bitDepth == 4 ? 2 : 3;
+ if ((compressionHeader[0] & 0x7) != huffmanMode)
+ throw new InvalidDataException($"Level5 Huffman{_bitDepth}");
- int currStreamPos = stream.p;
- stream.p += (int)(zeroRelOffset - relOffset) - 1;
- this.child0 = new HuffTreeNode(stream, zeroIsData, zeroRelOffset, maxStreamPos);
- this.child1 = new HuffTreeNode(stream, oneIsData, zeroRelOffset + 1, maxStreamPos);
+ var decompressedSize = (compressionHeader[0] >> 3) | (compressionHeader[1] << 5) |
+ (compressionHeader[2] << 13) | (compressionHeader[3] << 21);
- stream.p = currStreamPos;
- }
+ _decoder.Decode(input, output, decompressedSize);
}
+ public void Dispose()
+ {
+ // nothing to dispose
+ }
}
- public static byte[] Huffman_Decompress(byte[] b, byte atype)
+ public class HuffmanHeaderlessDecoder
{
- HUFF_STREAM instream = new HUFF_STREAM(b);
- long ReadBytes = 0;
-
- byte type = (byte)instream.ReadByte();
- type = atype;
- if (type != 0x28 && type != 0x24) return b;
- int decompressedSize = instream.readThree();
- ReadBytes += 4;
- if (decompressedSize == 0)
+ private readonly int _bitDepth;
+ private readonly NibbleOrder _nibbleOrder;
+
+ public HuffmanHeaderlessDecoder(int bitDepth, NibbleOrder nibbleOrder)
{
- instream.p -= 3;
- decompressedSize = instream.ReadInt32();
- ReadBytes += 4;
+ _bitDepth = bitDepth;
+ _nibbleOrder = nibbleOrder;
}
- List o = new List();
-
- int treeSize = instream.ReadByte(); ReadBytes++;
- treeSize = (treeSize + 1) * 2;
-
- long treeEnd = (instream.p - 1) + treeSize;
-
- // the relative offset may be 4 more (when the initial decompressed size is 0), but
- // since it's relative that doesn't matter, especially when it only matters if
- // the given value is odd or even.
- HuffTreeNode rootNode = new HuffTreeNode(instream, false, 5, treeEnd);
-
- ReadBytes += treeSize;
- // re-position the stream after the tree (the stream is currently positioned after the root
- // node, which is located at the start of the tree definition)
- instream.p = (int)treeEnd;
+ public void Decode(Stream input, Stream output, int decompressedSize)
+ {
+ var result = new byte[decompressedSize * 8 / _bitDepth];
- // the current u32 we are reading bits from.
- int data = 0;
- // the amount of bits left to read from
- byte bitsLeft = 0;
+ using (var br = new BinaryReader(input, Encoding.ASCII, true))
+ {
+ var treeSize = br.ReadByte();
+ var treeRoot = br.ReadByte();
+ var treeBuffer = br.ReadBytes(treeSize * 2);
- // a cache used for writing when the block size is four bits
- int cachedByte = -1;
+ for (int i = 0, code = 0, next = 0, pos = treeRoot, resultPos = 0; resultPos < result.Length; i++)
+ {
+ if (i % 32 == 0)
+ code = br.ReadInt32();
- // the current output size
- HuffTreeNode currentNode = rootNode;
+ next += ((pos & 0x3F) << 1) + 2;
+ var direction = (code >> (31 - i)) % 2 == 0 ? 2 : 1;
+ var leaf = (pos >> 5 >> direction) % 2 != 0;
- while (instream.hasBytes())
- {
- while (!currentNode.isData)
- {
- // if there are no bits left to read in the data, get a new byte from the input
- if (bitsLeft == 0)
- {
- ReadBytes += 4;
- data = instream.ReadInt32();
- bitsLeft = 32;
+ pos = treeBuffer[next - direction];
+ if (leaf)
+ {
+ result[resultPos++] = (byte)pos;
+ pos = treeRoot;
+ next = 0;
+ }
}
- // get the next bit
- bitsLeft--;
- bool nextIsOne = (data & (1 << bitsLeft)) != 0;
- // go to the next node, the direction of the child depending on the value of the current/next bit
- currentNode = nextIsOne ? currentNode.child1 : currentNode.child0;
}
- switch (type)
+ if (_bitDepth == 8)
+ output.Write(result, 0, result.Length);
+ else
{
- case 0x28:
- {
- // just copy the data if the block size is a full byte
- // outstream.WriteByte(currentNode.Data);
- o.Add(currentNode.data);
- break;
- }
- case 0x24:
- {
- // cache the first half of the data if the block size is a half byte
- if (cachedByte < 0)
- {
- cachedByte = currentNode.data;
- }
- else
- {
- cachedByte |= currentNode.data << 4;
- o.Add((byte)cachedByte);
- cachedByte = -1;
- }
- break;
- }
- }
+ var combinedData = _nibbleOrder == NibbleOrder.LowNibbleFirst ?
+ Enumerable.Range(0, decompressedSize).Select(j => (byte)(result[2 * j] | (result[2 * j + 1] << 4))).ToArray() :
+ Enumerable.Range(0, decompressedSize).Select(j => (byte)((result[2 * j] << 4) | result[2 * j + 1])).ToArray();
- currentNode = rootNode;
+ output.Write(combinedData, 0, combinedData.Length);
+ }
}
+ }
- if (ReadBytes % 4 != 0)
- ReadBytes += 4 - (ReadBytes % 4);
+ public enum NibbleOrder
+ {
+ LowNibbleFirst,
+ HighNibbleFirst
+ }
+ public static byte[] Huffman_Decompress(byte[] b, byte atype)
+ {
+ using (var input = new MemoryStream(b))
+ using (var output = new MemoryStream())
+ {
+ var decoder = new HuffmanDecoder(atype, NibbleOrder.LowNibbleFirst);
+ decoder.Decode(input, output);
- return o.ToArray();
+ return output.ToArray();
+ }
}
public static byte[] ZLIB(byte[] data)
@@ -842,4 +776,4 @@ public static byte[] SRC_DEC_CHUNK(byte[] chunk, string cmp_mode)
}
}
-}
+}
\ No newline at end of file
diff --git a/Metanoia/Tools/ImageCleaner.cs b/Metanoia/Tools/ImageCleaner.cs
new file mode 100644
index 0000000..c5bbf55
--- /dev/null
+++ b/Metanoia/Tools/ImageCleaner.cs
@@ -0,0 +1,63 @@
+using System.Drawing;
+
+namespace Metanoia.Tools
+{
+ public class ImageCleaner
+ {
+ private Bitmap RGB565;
+ private Bitmap ETC1;
+ public Bitmap Result;
+ private Color Hair;
+ private Color Skin;
+ private Color Eyes;
+
+ public ImageCleaner(Bitmap _RGB565, Bitmap _ETC1)
+ {
+ RGB565 = _RGB565;
+ ETC1 = _ETC1;
+ Hair = RGB565.GetPixel(0, 0);
+ Skin = RGB565.GetPixel(0, 1);
+ Eyes = RGB565.GetPixel(2, 0);
+ }
+
+ public void Cleaner()
+ {
+ Result = new Bitmap(RGB565.Width, RGB565.Height);
+ for (int y = 0; y < RGB565.Height; y++)
+ {
+ for (int x = 0; x < RGB565.Width; x++)
+ {
+ if (x < 3 & y == 0 || x < 2 & y == 1 || x == 0 & y == 2)
+ {
+ Result.SetPixel(x, y, SwitchColor(3, 0));
+ }
+ else
+ {
+ Result.SetPixel(x, y, SwitchColor(x, y));
+ }
+ }
+ }
+ }
+
+ private Color SwitchColor(int x, int y)
+ {
+ if (RGB565.GetPixel(x, y).R > 0)
+ {
+ return Color.FromArgb(Hair.R * RGB565.GetPixel(x, y).R / 255, Hair.G * RGB565.GetPixel(x, y).R / 255, Hair.B * RGB565.GetPixel(x, y).R / 255);
+ }
+ else if (RGB565.GetPixel(x, y).G > 0)
+ {
+ return Color.FromArgb(Eyes.R * RGB565.GetPixel(x, y).G / 255, Eyes.G * RGB565.GetPixel(x, y).G / 255, Eyes.B * RGB565.GetPixel(x, y).G / 255);
+ }
+ else if (RGB565.GetPixel(x, y).B > 0)
+ {
+ return Color.FromArgb(Skin.R * RGB565.GetPixel(x, y).B / 255, Skin.G * RGB565.GetPixel(x, y).B / 255, Skin.B * RGB565.GetPixel(x, y).B / 255);
+ }
+ else
+ {
+ return ETC1.GetPixel(x, y);
+ }
+
+ }
+ }
+}
From 9cdb7249f861b95ea88cdf133b63f1f309a076cb Mon Sep 17 00:00:00 2001
From: Tinifan <30804632+Tiniifan@users.noreply.github.com>
Date: Tue, 31 Oct 2023 02:31:17 +0100
Subject: [PATCH 2/6] fixed mtn3 animoffset
---
Metanoia/Formats/3DS/Level5/Level5_MTN3.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs b/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
index 83c9a86..16b9c8f 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
@@ -44,7 +44,7 @@ public void Open(string name, byte[] fileContent)
reader.Seek(0x04);
int hashOffset = reader.ReadInt16() - 4;
int nameOffset = reader.ReadInt16() - 4;
- int animOffset = reader.ReadInt16() - hashOffset - 4;
+ int unkOffset = reader.ReadInt16();
reader.Skip(0x06);
int compDataLength = reader.ReadInt32();
reader.Skip(0x04);
@@ -58,7 +58,7 @@ public void Open(string name, byte[] fileContent)
var hash = reader.ReadUInt32();
anim.Name = reader.ReadString(reader.Position, -1);
- reader.Seek((uint)animOffset);
+ reader.Seek((uint)0x58);
Console.WriteLine("hashOffset = " + hashOffset);
anim.FrameCount = reader.ReadInt32();
short positionTrackOffset = reader.ReadInt16();
From 4de39b646f8cac69f7abeee81811a3c8855fc557 Mon Sep 17 00:00:00 2001
From: Tinifan <30804632+Tiniifan@users.noreply.github.com>
Date: Wed, 8 Nov 2023 19:23:12 +0100
Subject: [PATCH 3/6] improve animation reader/exporter
- fix mtn3 loader
- can view splitted animation name from mtn2
- can export multiple animation
---
Metanoia/Formats/3DS/Level5/Level5_MTN3.cs | 106 +----------
Metanoia/Formats/3DS/Level5/Level5_Res.cs | 8 +-
Metanoia/Formats/3DS/Level5/Level5_XC.cs | 22 ++-
Metanoia/Rendering/ModelViewer.cs | 28 ++-
Metanoia/Tools/CRC32.cs | 193 ++++++++++++---------
Metanoia/Tools/DataReader.cs | 2 +
6 files changed, 168 insertions(+), 191 deletions(-)
diff --git a/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs b/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
index 16b9c8f..62f9d78 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
@@ -78,8 +78,6 @@ public void Open(string name, byte[] fileContent)
reader.Skip(0x04);
}
- Console.WriteLine("FrameOffset = " + reader.Position);
-
using (DataReader dataReader = new DataReader(Decompress.Level5Decom(reader.GetSection(reader.Position, (int)(reader.Length - reader.Position)))))
{
// Bone Hashes
@@ -122,10 +120,6 @@ public void Open(string name, byte[] fileContent)
anim.TransformNodes.Add(node);
}
- Console.WriteLine("FlagOffset " + animTableOffset[0].FlagOffset);
- Console.WriteLine("KeyFrameOffset " + animTableOffset[0].KeyFrameOffset);
- Console.WriteLine("KeyDataOffset " + animTableOffset[0].KeyDataOffset);
-
using (DataReader animDataReader = new DataReader(dataReader.GetSection(dataReader.Position, (int)(dataReader.Length - dataReader.Position))))
{
ReadFrameData(animDataReader, animTableOffset.Take(positionCount).ToList(), boneCount, Tracks[0]);
@@ -138,105 +132,7 @@ public void Open(string name, byte[] fileContent)
public void Open(FileItem file)
{
- anim.Name = file.FilePath;
-
- using (DataReader reader = new DataReader(file))
- {
- reader.BigEndian = false;
-
- reader.Seek(0x04);
- int hashOffset = reader.ReadInt16() - 4;
- int nameOffset = reader.ReadInt16() - 4;
- int animOffset = reader.ReadInt16() - hashOffset - 4;
- reader.Skip(0x06);
- int compDataLength = reader.ReadInt32();
- reader.Skip(0x04);
- int positionCount = reader.ReadInt32();
- int rotationCount = reader.ReadInt32();
- int scaleCount = reader.ReadInt32();
- int unknownCount = reader.ReadInt32();
- int boneCount = reader.ReadInt32();
-
- reader.Seek((uint)hashOffset);
- var hash = reader.ReadUInt32();
- anim.Name = reader.ReadString(reader.Position, -1);
-
- reader.Seek((uint)animOffset);
- Console.WriteLine("hashOffset = " + hashOffset);
- anim.FrameCount = reader.ReadInt32();
- short positionTrackOffset = reader.ReadInt16();
- short rotationTrackOffset = reader.ReadInt16();
- short scaleTrackOffset = reader.ReadInt16();
- short unknownTrackOffset = reader.ReadInt16();
-
- List animTableOffset = new List();
- for (int i = 0; i < positionCount + rotationCount + scaleCount + unknownCount; i++)
- {
- animTableOffset.Add(new AnimTableOffset()
- {
- FlagOffset = reader.ReadInt32(),
- KeyFrameOffset = reader.ReadInt32(),
- KeyDataOffset = reader.ReadInt32(),
- });
- reader.Skip(0x04);
- }
-
- Console.WriteLine("FrameOffset = " + reader.Position);
-
- using (DataReader dataReader = new DataReader(Decompress.Level5Decom(reader.GetSection(reader.Position, (int)(reader.Length - reader.Position)))))
- {
- // Bone Hashes
- List boneNameHashes = new List();
- for (int i = 0; i < boneCount; i++)
- {
- boneNameHashes.Add(dataReader.ReadUInt32());
- }
-
- // Track Information
- List Tracks = new List();
- dataReader.Seek((uint)positionTrackOffset);
- for (int i = 0; i < 4; i++)
- {
- Tracks.Add(new AnimTrack()
- {
- Type = dataReader.ReadByte(),
- DataType = dataReader.ReadByte(),
- unk = dataReader.ReadByte(),
- DataCount = dataReader.ReadByte(),
- Start = dataReader.ReadUInt16(),
- End = dataReader.ReadUInt16()
- });
- }
-
- foreach (var v in Tracks)
- Console.WriteLine(v.Type + " "
- + v.DataType + " "
- + v.DataCount
- + " " + v.Start.ToString("X")
- + " " + v.End.ToString("X"));
-
- // Data
-
- foreach (var v in boneNameHashes)
- {
- var node = new GenericAnimationTransform();
- node.Hash = v;
- node.HashType = AnimNodeHashType.CRC32C;
- anim.TransformNodes.Add(node);
- }
-
- Console.WriteLine("FlagOffset " + animTableOffset[0].FlagOffset);
- Console.WriteLine("KeyFrameOffset " + animTableOffset[0].KeyFrameOffset);
- Console.WriteLine("KeyDataOffset " + animTableOffset[0].KeyDataOffset);
-
- using (DataReader animDataReader = new DataReader(dataReader.GetSection(dataReader.Position, (int)(dataReader.Length - dataReader.Position))))
- {
- ReadFrameData(animDataReader, animTableOffset.Take(positionCount).ToList(), boneCount, Tracks[0]);
- ReadFrameData(animDataReader, animTableOffset.Skip(positionCount).Take(rotationCount).ToList(), boneCount, Tracks[1]);
- ReadFrameData(animDataReader, animTableOffset.Skip(positionCount + rotationCount).Take(scaleCount).ToList(), boneCount, Tracks[2]);
- }
- }
- }
+ Open(file.FilePath, file.GetFileBinary());
}
private void ReadFrameData(DataReader dataReader, List animTableOffset, int boneCount, AnimTrack Track)
diff --git a/Metanoia/Formats/3DS/Level5/Level5_Res.cs b/Metanoia/Formats/3DS/Level5/Level5_Res.cs
index ee0a466..f6e98b2 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_Res.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_Res.cs
@@ -1,5 +1,6 @@
using Metanoia.Tools;
using System;
+using System.Text;
using System.Collections.Generic;
namespace Metanoia.Formats._3DS.Level5
@@ -39,13 +40,14 @@ public void RES(DataReader r)
var resourceNodeCount = r.ReadInt16();
r.Seek((uint)stringTableOffset);
+ Encoding shiftJIS = Encoding.GetEncoding("Shift-JIS");
while (r.Position < r.BaseStream.Length)
{
- string mname = r.ReadString();
+ string mname = r.ReadString(shiftJIS);
if (mname == "")
break;
- if (!ResourceNames.ContainsKey(CRC32.Crc32C(mname)))
- ResourceNames.Add(CRC32.Crc32C(mname), mname);
+ if (!ResourceNames.ContainsKey(CRC32.Crc32C(mname, shiftJIS)))
+ ResourceNames.Add(CRC32.Crc32C(mname, shiftJIS), mname);
}
r.Seek((uint)materialTableOffset);
diff --git a/Metanoia/Formats/3DS/Level5/Level5_XC.cs b/Metanoia/Formats/3DS/Level5/Level5_XC.cs
index d53721d..a9bde04 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_XC.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_XC.cs
@@ -230,6 +230,8 @@ public GenericAnimation[] ToGenericAnimation()
if (motionCount + subMotionCount > 0)
{
+ Level5_Resource resourceFile = null;
+
List animations = new List();
Dictionary animDict = new Dictionary();
Dictionary> subAnimDict = new Dictionary>();
@@ -237,7 +239,11 @@ public GenericAnimation[] ToGenericAnimation()
foreach (var f in Files)
{
- if (f.Key.EndsWith(".mtn2"))
+ if (f.Key.EndsWith("RES.bin"))
+ {
+ resourceFile = new Level5_Resource(f.Value);
+ }
+ else if (f.Key.EndsWith(".mtn2"))
{
var anim = new Level5_MTN2();
anim.Open(f.Key, f.Value);
@@ -293,7 +299,19 @@ public GenericAnimation[] ToGenericAnimation()
foreach (Level5_MINF minf in subAnimDict[kvp.Key])
{
GenericAnimation newAnimation = kvp.Value.TrimAnimation(minf.FrameStart, minf.FrameEnd);
- newAnimation.Name = kvp.Value.Name + "_" + minf.AnimationSubName.ToString("X8");
+
+ string animationSubName = minf.AnimationSubName.ToString("X8");
+ if (resourceFile != null)
+ {
+ animationSubName = resourceFile.GetResourceName(minf.AnimationSubName);
+
+ if (animationSubName == "")
+ {
+ animationSubName = minf.AnimationSubName.ToString("X8");
+ }
+ }
+
+ newAnimation.Name = kvp.Value.Name + "_" + animationSubName;
animations.Add(newAnimation);
}
}
diff --git a/Metanoia/Rendering/ModelViewer.cs b/Metanoia/Rendering/ModelViewer.cs
index 80f103a..8fe0a3c 100644
--- a/Metanoia/Rendering/ModelViewer.cs
+++ b/Metanoia/Rendering/ModelViewer.cs
@@ -600,11 +600,37 @@ private void exportModelToolStripMenuItem_Click(object sender, EventArgs e)
///
private void exportAnimationToolStripMenuItem_Click(object sender, EventArgs e)
{
- if(animationCB.SelectedItem is GenericAnimation anim)
+ if (animationCB.Items.Count > 1)
+ {
+ DialogResult dialogResult = MessageBox.Show("Multiple animation detected, do you want to export all animations as SMD?", "Export Multiple Animation", MessageBoxButtons.YesNo);
+ if (dialogResult == DialogResult.Yes)
+ {
+ using (FolderBrowserDialog dialog = new FolderBrowserDialog())
+ {
+ if (dialog.ShowDialog() == DialogResult.OK)
+ {
+ string path = dialog.SelectedPath;
+
+ foreach(GenericAnimation animItem in animationCB.Items)
+ {
+ Exporting.ExportSMD smdExporter = new Exporting.ExportSMD();
+ smdExporter.Export(path + "/" + animItem.Name + ".smd", Model.Skeleton, animItem);
+ }
+
+ MessageBox.Show("Exported!");
+ }
+ }
+
+ return;
+ }
+ }
+
+ if (animationCB.SelectedItem is GenericAnimation anim)
{
IsPlaying = false;
Frame = 0;
FormatManager.Instance.ExportAnimation(Model.Skeleton, anim);
+ MessageBox.Show("Exported!");
}
}
diff --git a/Metanoia/Tools/CRC32.cs b/Metanoia/Tools/CRC32.cs
index a79ba5a..ab3c00d 100644
--- a/Metanoia/Tools/CRC32.cs
+++ b/Metanoia/Tools/CRC32.cs
@@ -1,95 +1,128 @@
-namespace Metanoia.Tools
+using System;
+using System.Text;
+using System.Linq;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+
+namespace Metanoia.Tools
{
- public class CRC32
+ // Copyright (c) Damien Guard. All rights reserved.
+ // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
+ // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+
+ public sealed class CRC32 : HashAlgorithm
{
- public static uint Crc32C(byte[] input)
+ public const UInt32 DefaultPolynomial = 0xedb88320u;
+ public const UInt32 DefaultSeed = 0xffffffffu;
+
+ static UInt32[] defaultTable;
+
+ readonly UInt32 seed;
+ readonly UInt32[] table;
+ UInt32 hash;
+
+ public CRC32()
+ : this(DefaultPolynomial, DefaultSeed)
{
- var hash = 0xffffffffu;
- for (var i = 0; i < input.Length; i++)
- hash = (hash >> 8) ^ CrcTable[input[i] ^ hash & 0xff];
- return hash;
}
- public static uint Crc32C(string input)
+ public CRC32(UInt32 polynomial, UInt32 seed)
+ {
+ if (!System.BitConverter.IsLittleEndian)
+ throw new PlatformNotSupportedException("Not supported on Big Endian processors");
+
+ table = InitializeTable(polynomial);
+ this.seed = hash = seed;
+ }
+
+ public override void Initialize()
+ {
+ hash = seed;
+ }
+
+ protected override void HashCore(byte[] array, int ibStart, int cbSize)
+ {
+ hash = CalculateHash(table, hash, array, ibStart, cbSize);
+ }
+
+ protected override byte[] HashFinal()
+ {
+ var hashBuffer = UInt32ToBigEndianBytes(~hash);
+ HashValue = hashBuffer;
+ return hashBuffer;
+ }
+
+ public override int HashSize { get { return 32; } }
+
+ public static UInt32 Compute(byte[] buffer)
+ {
+ return Compute(DefaultSeed, buffer);
+ }
+
+ public static UInt32 Compute(UInt32 seed, byte[] buffer)
{
- uint crc = 0xFFFFFFFF;
+ return Compute(DefaultPolynomial, seed, buffer);
+ }
+
+ public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer)
+ {
+ return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length);
+ }
- foreach (char d in input)
+ static UInt32[] InitializeTable(UInt32 polynomial)
+ {
+ if (polynomial == DefaultPolynomial && defaultTable != null)
+ return defaultTable;
+
+ var createTable = new UInt32[256];
+ for (var i = 0; i < 256; i++)
{
- char c = d;
- //if ((uint)(c - 0x41) < 0x1A) c += (char)0x20;
- crc = CrcTable[(byte)crc ^ c] ^ (crc >> 8);
+ var entry = (UInt32)i;
+ for (var j = 0; j < 8; j++)
+ if ((entry & 1) == 1)
+ entry = (entry >> 1) ^ polynomial;
+ else
+ entry >>= 1;
+ createTable[i] = entry;
}
- return ~crc;
+ if (polynomial == DefaultPolynomial)
+ defaultTable = createTable;
+
+ return createTable;
}
- private static readonly uint[] CrcTable =
+ static UInt32 CalculateHash(UInt32[] table, UInt32 seed, IList buffer, int start, int size)
{
- 0, 0x77073096, 0xEE0E612C, 0x990951BA,
- 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
- 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
- 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
- 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
- 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
- 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
- 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
- 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
- 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
- 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
- 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
- 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
- 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
- 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
- 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
- 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
- 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
- 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
- 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
- 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
- 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
- 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
- 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
- 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
- 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
- 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
- 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
- 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
- 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
- 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
- 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
- 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
- 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
- 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
- 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
- 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
- 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
- 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
- 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
- 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
- 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
- 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
- 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
- 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
- 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
- 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
- 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
- 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
- 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
- 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
- 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
- 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
- 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
- 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
- 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
- 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
- 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
- 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
- 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
- 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
- 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
- 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
- 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
- };
+ var hash = seed;
+ for (var i = start; i < start + size; i++)
+ hash = (hash >> 8) ^ table[buffer[i] ^ hash & 0xff];
+ return hash;
+ }
+
+ static byte[] UInt32ToBigEndianBytes(UInt32 uint32)
+ {
+ var result = System.BitConverter.GetBytes(uint32);
+
+ if (System.BitConverter.IsLittleEndian)
+ Array.Reverse(result);
+
+ return result;
+ }
+
+ public static uint Crc32C(byte[] input)
+ {
+ return BitConverter.ToUInt32(new CRC32().ComputeHash(input).Reverse().ToArray(), 0);
+ }
+
+ public static uint Crc32C(string input)
+ {
+ return BitConverter.ToUInt32(new CRC32().ComputeHash(Encoding.UTF8.GetBytes(input)).Reverse().ToArray(), 0);
+ }
+
+ public static uint Crc32C(string input, Encoding encoding)
+ {
+ return BitConverter.ToUInt32(new CRC32().ComputeHash(encoding.GetBytes(input)).Reverse().ToArray(), 0);
+ }
}
}
diff --git a/Metanoia/Tools/DataReader.cs b/Metanoia/Tools/DataReader.cs
index ac65c14..5a3443b 100644
--- a/Metanoia/Tools/DataReader.cs
+++ b/Metanoia/Tools/DataReader.cs
@@ -114,6 +114,8 @@ public string ReadString(Encoding encoding)
text.Add(ch);
}
+ Console.WriteLine(BitConverter.ToString(text.ToArray()).Replace("-",""));
+
return encoding.GetString(text.ToArray());
}
From 6ce36d8392237c0a6564b5afa913526b032d2ef1 Mon Sep 17 00:00:00 2001
From: Tinifan <30804632+Tiniifan@users.noreply.github.com>
Date: Thu, 9 Nov 2023 01:00:21 +0100
Subject: [PATCH 4/6] fix mtn frame reader
Metanoia had a problem retrieving frames and could not read a model with more than 255 frames
---
Metanoia/Formats/3DS/Level5/Level5_MTN2.cs | 8 +++++---
Metanoia/Formats/3DS/Level5/Level5_MTN3.cs | 8 +++++---
2 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/Metanoia/Formats/3DS/Level5/Level5_MTN2.cs b/Metanoia/Formats/3DS/Level5/Level5_MTN2.cs
index 678449a..c8e6709 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_MTN2.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_MTN2.cs
@@ -210,10 +210,12 @@ private void ReadFrameData(DataReader d, int offset, int count, uint dataOffset,
d.Seek(flagOffset);
var boneIndex = d.ReadInt16();
- var keyFrameCount = d.ReadByte();
- var flag = d.ReadByte();
+ var lowFrameCount = d.ReadByte();
+ var hightFrameCount = d.ReadByte() - 32;
+
+ int keyFrameCount = (hightFrameCount << 8) | lowFrameCount;
- var node = anim.TransformNodes[boneIndex + (flag == 0 ? boneCount : 0)];
+ var node = anim.TransformNodes[boneIndex];
d.Seek(keyDataOffset);
for (int k = 0; k < keyFrameCount; k++)
diff --git a/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs b/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
index 62f9d78..3b88780 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs
@@ -141,10 +141,12 @@ private void ReadFrameData(DataReader dataReader, List animTabl
{
dataReader.Seek((uint)animTableOffset[i].FlagOffset);
var boneIndex = dataReader.ReadInt16();
- var keyFrameCount = dataReader.ReadByte();
- var flag = dataReader.ReadByte();
+ var lowFrameCount = dataReader.ReadByte();
+ var hightFrameCount = dataReader.ReadByte() - 32;
- var node = anim.TransformNodes[boneIndex + (flag == 0 ? boneCount : 0)];
+ int keyFrameCount = (hightFrameCount << 8) | lowFrameCount;
+
+ var node = anim.TransformNodes[boneIndex];
dataReader.Seek((uint)animTableOffset[i].KeyDataOffset);
for (int k = 0; k < keyFrameCount; k++)
From 1f02d4421d3cda0e5552ff578e528997f265ec3c Mon Sep 17 00:00:00 2001
From: Tinifan <30804632+Tiniifan@users.noreply.github.com>
Date: Thu, 9 Nov 2023 15:36:40 +0100
Subject: [PATCH 5/6] Add zoom feature with mouse wheel
---
Metanoia/Rendering/ModelViewer.cs | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/Metanoia/Rendering/ModelViewer.cs b/Metanoia/Rendering/ModelViewer.cs
index 8fe0a3c..c92418d 100644
--- a/Metanoia/Rendering/ModelViewer.cs
+++ b/Metanoia/Rendering/ModelViewer.cs
@@ -418,7 +418,7 @@ private void Viewport_Resize(object sender, EventArgs e)
Viewport.Invalidate();
}
- private int PrevX, PrevY;
+ private int PrevX, PrevY, PrevZ;
private ModelInfoPanel ModelPanel = new ModelInfoPanel();
@@ -634,6 +634,17 @@ private void exportAnimationToolStripMenuItem_Click(object sender, EventArgs e)
}
}
+ private void ZoomIn(object sender, MouseEventArgs e)
+ {
+ if (e.Delta > 0)
+ {
+ Z += PrevZ + 0.1f / 5;
+ } else if (e.Delta < 0)
+ {
+ Z += PrevZ - 0.1f / 5;
+ }
+ }
+
///
///
///
@@ -655,6 +666,8 @@ private void Viewport_MouseMove(object sender, MouseEventArgs e)
X -= (PrevX - e.X) * 0.75f * speed;
Y += (PrevY - e.Y) * 0.75f * speed;
}
+ Viewport.MouseWheel += ZoomIn;
+ ZoomIn(sender, e);
PrevX = e.X;
PrevY = e.Y;
}
From bdb8063d11f0d2a048e4e79e76136cdb40f0853c Mon Sep 17 00:00:00 2001
From: Tinifan <30804632+Tiniifan@users.noreply.github.com>
Date: Wed, 27 Mar 2024 14:11:11 +0100
Subject: [PATCH 6/6] Fix compression and texture problem
- Use Kuriimu LZ10 for better decompression
- Fix compression header type
- Fix problem with some XI (large texture)
---
Metanoia/Formats/3DS/Level5/Level5_Res.cs | 7 +
Metanoia/Formats/3DS/Level5/Level5_XC.cs | 2 +-
Metanoia/Formats/3DS/Level5/Level5_XI.cs | 9 +-
Metanoia/Metanoia.csproj | 2 +
Metanoia/Tools/Decompress.cs | 178 +++++++++++++++++-----
5 files changed, 159 insertions(+), 39 deletions(-)
diff --git a/Metanoia/Formats/3DS/Level5/Level5_Res.cs b/Metanoia/Formats/3DS/Level5/Level5_Res.cs
index f6e98b2..a69b1e2 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_Res.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_Res.cs
@@ -39,6 +39,12 @@ public void RES(DataReader r)
var resourceNodeOffsets = r.ReadInt16() << 2;
var resourceNodeCount = r.ReadInt16();
+ Console.WriteLine("stringTableOffset " + stringTableOffset);
+ Console.WriteLine("materialTableOffset " + materialTableOffset);
+ Console.WriteLine("materialTableSectionCount " + materialTableSectionCount);
+ Console.WriteLine("resourceNodeOffsets " + resourceNodeOffsets);
+ Console.WriteLine("resourceNodeCount " + resourceNodeCount);
+
r.Seek((uint)stringTableOffset);
Encoding shiftJIS = Encoding.GetEncoding("Shift-JIS");
while (r.Position < r.BaseStream.Length)
@@ -189,6 +195,7 @@ public Level5_Resource(byte[] data)
} else
{
r.Skip(0x02);
+ Console.WriteLine("POSITION " + r.Position);
RES(r);
}
}
diff --git a/Metanoia/Formats/3DS/Level5/Level5_XC.cs b/Metanoia/Formats/3DS/Level5/Level5_XC.cs
index a9bde04..e1e83ee 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_XC.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_XC.cs
@@ -78,7 +78,7 @@ public void Open(FileItem File)
byte[] nameTable = r.GetSection((uint)fileTableOffset, filenameTableSize);
if(!Decompress.CheckLevel5Zlib(nameTable, out nameTable))
- nameTable = Decompress.lzss_Decompress(nameTable);
+ nameTable = Decompress.LZ10Decompress(nameTable);
using (DataReader nt = new DataReader(new MemoryStream(nameTable)))
{
for (int i = 0; i < fileCount; i++)
diff --git a/Metanoia/Formats/3DS/Level5/Level5_XI.cs b/Metanoia/Formats/3DS/Level5/Level5_XI.cs
index bc68b8d..a7919d1 100644
--- a/Metanoia/Formats/3DS/Level5/Level5_XI.cs
+++ b/Metanoia/Formats/3DS/Level5/Level5_XI.cs
@@ -242,8 +242,15 @@ public Bitmap ToBitmap()
if (code != -1)
{
for (int h = 0; h < 8; h++)
+ {
for (int w = 0; w < 8; w++)
- pixels[(x + w) + (y + h) * Width] = srcpix[(code * 8 + w) + (h) * tileSheet.Width];
+ {
+ if ((x + w) + (y + h) * Width < pixels.Length && (code * 8 + w) + (h) * tileSheet.Width < srcpix.Length)
+ {
+ pixels[(x + w) + (y + h) * Width] = srcpix[(code * 8 + w) + (h) * tileSheet.Width];
+ }
+ }
+ }
}
if (code == -1 && (ImageFormat == 0xC || ImageFormat == 0xD))
{
diff --git a/Metanoia/Metanoia.csproj b/Metanoia/Metanoia.csproj
index a6b386f..744d7b3 100644
--- a/Metanoia/Metanoia.csproj
+++ b/Metanoia/Metanoia.csproj
@@ -22,6 +22,7 @@
DEBUG;TRACE
prompt
4
+ true
AnyCPU
@@ -31,6 +32,7 @@
TRACE
prompt
4
+ true
diff --git a/Metanoia/Tools/Decompress.cs b/Metanoia/Tools/Decompress.cs
index cd8c6b5..518cb6b 100644
--- a/Metanoia/Tools/Decompress.cs
+++ b/Metanoia/Tools/Decompress.cs
@@ -30,17 +30,19 @@ public static bool CheckLevel5Zlib(byte[] input, out byte[] t)
public static byte[] Level5Decom(byte[] b)
{
- int tableType = (b[0] & 0xFF);
+ int compressionType = (b[0] & 0x7);
byte[] t;
if (CheckLevel5Zlib(b, out t))
return t;
- switch (tableType & 0xF)
+ Console.WriteLine(compressionType);
+
+ switch (compressionType)
{
case 0x01:
- t = (lzss_Decompress(b));
+ t = LZ10Decompress(b);
break;
case 0x02:
t = Huffman_Decompress(b, 4);
@@ -49,7 +51,7 @@ public static byte[] Level5Decom(byte[] b)
t = Huffman_Decompress(b, 8);
break;
case 0x04:
- t = (rle_Decompress(b));
+ t = rle_Decompress(b);
break;
default:
t = new byte[b.Length - 4];
@@ -377,55 +379,159 @@ public static byte[] LZ11(byte[] instream, bool header = true)
}
- public static byte[] lzss_Decompress(byte[] data)
+ public static byte[] LZ10Decompress(byte[] data)
{
- List o = new List();
+ using (var input = new MemoryStream(data))
+ {
+ return LZ10Decoder(input, (int)input.Length);
+ }
+ }
+
+ public static byte[] LZ10Decoder(Stream instream, int decompressedSize)
+ {
+ var compressionHeader = new byte[4];
+ instream.Read(compressionHeader, 0, 4);
+ if ((compressionHeader[0] & 0x7) != 0x1)
+ throw new Exception("Level5 LZ10");
+
+ decompressedSize = (compressionHeader[0] >> 3) | (compressionHeader[1] << 5) |
+ (compressionHeader[2] << 13) | (compressionHeader[3] << 21);
- int p = 4;
- int op = 0;
+ long inLength = instream.Length;
+ MemoryStream outstream = new MemoryStream();
- int mask = 0;
- int flag = 0;
+ long readBytes = 0;
- while (p < data.Length)
+ // the maximum 'DISP-1' is 0xFFF.
+ int bufferLength = 0x1000;
+ byte[] buffer = new byte[bufferLength];
+ int bufferOffset = 0;
+
+
+ int currentOutSize = 0;
+ int flags = 0, mask = 1;
+ while (currentOutSize < decompressedSize)
{
- if (mask == 0)
+ // (throws when requested new flags byte is not available)
+ #region Update the mask. If all flag bits have been read, get a new set.
+ // the current mask is the mask used in the previous run. So if it masks the
+ // last flag bit, get a new flags byte.
+ if (mask == 1)
{
- flag = (data[p++] & 0xFF);
- // System.out.println(Integer.toHexString(flag));
+ if (readBytes >= inLength)
+ throw new Exception("Not enough data: " + currentOutSize.ToString() + ", " + decompressedSize.ToString());
+ flags = instream.ReadByte(); readBytes++;
+ if (flags < 0)
+ throw new Exception("Stream too short!");
mask = 0x80;
}
-
- if ((flag & mask) == 0)
+ else
{
- if (p + 1 > data.Length) break;
- o.Add(data[p++]);
- op++;
+ mask >>= 1;
}
- else
+ #endregion
+
+ // bit = 1 <=> compressed.
+ if ((flags & mask) > 0)
{
- if (p + 2 > data.Length) break;
- int dat = ((data[p++] & 0xFF) << 8) | (data[p++] & 0xFF);
- int pos = (dat & 0x0FFF) + 1;
- int length = (dat >> 12) + 3;
+ // (throws when < 2 bytes are available)
+ #region Get length and displacement('disp') values from next 2 bytes
+ // there are < 2 bytes available when the end is at most 1 byte away
+ if (readBytes + 1 >= inLength)
+ {
+ // make sure the stream is at the end
+ if (readBytes < inLength)
+ {
+ instream.ReadByte(); readBytes++;
+ }
+ throw new Exception("Not enough data: " + currentOutSize.ToString() + ", " + decompressedSize.ToString());
+ }
+ int byte1 = instream.ReadByte(); readBytes++;
+ int byte2 = instream.ReadByte(); readBytes++;
+ if (byte2 < 0)
+ throw new Exception("Stream too short!");
+
+ // the number of bytes to copy
+ int length = byte1 >> 4;
+ length += 3;
- // System.out.println(Integer.toHexString(dat) + "\t" + pos + "\t" + length);
+ // from where the bytes should be copied (relatively)
+ int disp = ((byte1 & 0x0F) << 8) | byte2;
+ disp += 1;
+ if (disp > currentOutSize)
+ throw new Exception("Cannot go back more than already written. "
+ + "DISP = 0x" + disp.ToString("X") + ", #written bytes = 0x" + currentOutSize.ToString("X")
+ + " at 0x" + (instream.Position - 2).ToString("X"));
+ #endregion
+
+ int bufIdx = bufferOffset + bufferLength - disp;
for (int i = 0; i < length; i++)
{
- if (op - pos >= 0)
- {
- o.Add(o[op - pos >= o.Count ? 0 : op - pos]);
- op++;
- }
+ byte next = buffer[bufIdx % bufferLength];
+ bufIdx++;
+ outstream.WriteByte(next);
+ buffer[bufferOffset] = next;
+ bufferOffset = (bufferOffset + 1) % bufferLength;
}
+ currentOutSize += length;
}
- mask >>= 1;
+ else
+ {
+ if (readBytes >= inLength)
+ throw new Exception("Not enough data: " + currentOutSize.ToString() + ", " + decompressedSize.ToString());
+ int next = instream.ReadByte(); readBytes++;
+ if (next < 0)
+ throw new Exception("Stream too short!");
+
+ currentOutSize++;
+ outstream.WriteByte((byte)next);
+ buffer[bufferOffset] = (byte)next;
+ bufferOffset = (bufferOffset + 1) % bufferLength;
+ }
+ outstream.Flush();
}
+ outstream.Position = 0;
+ return outstream.ToArray();
+ }
+
+ public static unsafe int GetOccurrenceLength(byte* newPtr, int newLength, byte* oldPtr, int oldLength, out int disp, int minDisp = 1)
+ {
+ disp = 0;
+ if (newLength == 0)
+ return 0;
+ int maxLength = 0;
+ // try every possible 'disp' value (disp = oldLength - i)
+ for (int i = 0; i < oldLength - minDisp; i++)
+ {
+ // work from the start of the old data to the end, to mimic the original implementation's behaviour
+ // (and going from start to end or from end to start does not influence the compression ratio anyway)
+ byte* currentOldStart = oldPtr + i;
+ int currentLength = 0;
+ // determine the length we can copy if we go back (oldLength - i) bytes
+ // always check the next 'newLength' bytes, and not just the available 'old' bytes,
+ // as the copied data can also originate from what we're currently trying to compress.
+ for (int j = 0; j < newLength; j++)
+ {
+ // stop when the bytes are no longer the same
+ if (*(currentOldStart + j) != *(newPtr + j))
+ break;
+ currentLength++;
+ }
- //Console.WriteLine("decompress " + p.ToString("x") + " " + data.Length.ToString("x"));
- return o.ToArray();
+ // update the optimal value
+ if (currentLength > maxLength)
+ {
+ maxLength = currentLength;
+ disp = oldLength - i;
+
+ // if we cannot do better anyway, stop trying.
+ if (maxLength == newLength)
+ break;
+ }
+ }
+ return maxLength;
}
public static byte[] rle_Decompress(byte[] instream)
@@ -493,8 +599,8 @@ public void Dispose()
}
}
- // HuffmanDecoder & HuffmanHeaderlessDecoder From Kuriimu2
- public class HuffmanDecoder
+ // HuffmanDecoder & HuffmanHeaderlessDecoder From Kuriimu2
+ public class HuffmanDecoder
{
private readonly int _bitDepth;
private readonly HuffmanHeaderlessDecoder _decoder;
@@ -721,8 +827,6 @@ private static byte[] PRS_8ing(int dlen, byte[] sbuf, int slen)
return dbuf;
}
-
-
public static byte[] SRD_Decomp(byte[] data)
{
List o = new List();