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..c8e6709 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; @@ -123,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 new file mode 100644 index 0000000..3b88780 --- /dev/null +++ b/Metanoia/Formats/3DS/Level5/Level5_MTN3.cs @@ -0,0 +1,214 @@ +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 unkOffset = reader.ReadInt16(); + 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)0x58); + 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); + } + + 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); + } + + 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) + { + Open(file.FilePath, file.GetFileBinary()); + } + + 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 lowFrameCount = dataReader.ReadByte(); + var hightFrameCount = dataReader.ReadByte() - 32; + + int keyFrameCount = (hightFrameCount << 8) | lowFrameCount; + + var node = anim.TransformNodes[boneIndex]; + + 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..a69b1e2 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 @@ -28,96 +29,175 @@ 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(); + + 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) { - 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(shiftJIS); + if (mname == "") + break; + if (!ResourceNames.ContainsKey(CRC32.Crc32C(mname, shiftJIS))) + ResourceNames.Add(CRC32.Crc32C(mname, shiftJIS), 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) + { + Level5_Resource resourceFile = null; + + 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("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); + 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); + + 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); + } + } + + 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..a7919d1 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; } @@ -239,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 502edc6..744d7b3 100644 --- a/Metanoia/Metanoia.csproj +++ b/Metanoia/Metanoia.csproj @@ -8,9 +8,10 @@ WinExe Metanoia Metanoia - v4.6.1 + v4.8 512 true + AnyCPU @@ -21,6 +22,7 @@ DEBUG;TRACE prompt 4 + true AnyCPU @@ -30,6 +32,7 @@ TRACE prompt 4 + true @@ -83,7 +86,10 @@ + + + @@ -181,6 +187,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..c92418d 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 { @@ -416,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(); @@ -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()); } @@ -584,11 +600,48 @@ 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!"); + } + } + + 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; } } @@ -613,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; } 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/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 5fb303b..5a3443b 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,21 @@ public override string ReadString() return str; } + public string ReadString(Encoding encoding) + { + List text = new List(); + + byte ch; + while ((ch = ReadByte()) != 0) + { + text.Add(ch); + } + + Console.WriteLine(BitConverter.ToString(text.ToArray()).Replace("-","")); + + 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..518cb6b 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; @@ -28,26 +30,28 @@ 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, (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)); + t = rle_Decompress(b); break; default: t = new byte[b.Length - 4]; @@ -375,293 +379,329 @@ 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); + } + } - int p = 4; - int op = 0; + 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); + + 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) - { - if (p + 1 > data.Length) break; - o.Add(data[p++]); - op++; - } else { - 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; - - // System.out.println(Integer.toHexString(dat) + "\t" + pos + "\t" + length); + mask >>= 1; + } + #endregion - for (int i = 0; i < length; i++) + // bit = 1 <=> compressed. + if ((flags & mask) > 0) + { + // (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) { - if (op - pos >= 0) + // make sure the stream is at the end + if (readBytes < inLength) { - o.Add(o[op - pos >= o.Count ? 0 : op - pos]); - op++; + instream.ReadByte(); readBytes++; } + throw new Exception("Not enough data: " + currentOutSize.ToString() + ", " + decompressedSize.ToString()); } - } - mask >>= 1; - } - - - //Console.WriteLine("decompress " + p.ToString("x") + " " + data.Length.ToString("x")); - return o.ToArray(); - } + int byte1 = instream.ReadByte(); readBytes++; + int byte2 = instream.ReadByte(); readBytes++; + if (byte2 < 0) + throw new Exception("Stream too short!"); - 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) - { - decompressedSize = decompressedSize - | ((instream[p++] & 0xFF) << 24); - ReadBytes += 4; - } - - List outstream = new List(); - - while (p < instream.Length) - { - - int flag = (byte)instream[p++]; - ReadBytes++; - - bool compressed = (flag & 0x80) > 0; - int length = flag & 0x7F; - - if (compressed) + // the number of bytes to copy + int length = byte1 >> 4; length += 3; - else - length += 1; - if (compressed) - { + // from where the bytes should be copied (relatively) + int disp = ((byte1 & 0x0F) << 8) | byte2; + disp += 1; - int data = (byte)instream[p++]; - ReadBytes++; + 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 - byte bdata = (byte)data; + int bufIdx = bufferOffset + bufferLength - disp; for (int i = 0; i < length; i++) { - outstream.Add(bdata); + byte next = buffer[bufIdx % bufferLength]; + bufIdx++; + outstream.WriteByte(next); + buffer[bufferOffset] = next; + bufferOffset = (bufferOffset + 1) % bufferLength; } - + currentOutSize += length; } 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!"); - int tryReadLength = length; - if (ReadBytes + length > inLength) - tryReadLength = (int)(inLength - ReadBytes); - - ReadBytes += tryReadLength; - - for (int i = 0; i < tryReadLength; i++) - { - outstream.Add((byte)(instream[p++] & 0xFF)); - } + currentOutSize++; + outstream.WriteByte((byte)next); + buffer[bufferOffset] = (byte)next; + bufferOffset = (bufferOffset + 1) % bufferLength; } + outstream.Flush(); } - if (ReadBytes < inLength) - { - } - + outstream.Position = 0; return outstream.ToArray(); } - public class HUFF_STREAM + public static unsafe int GetOccurrenceLength(byte* newPtr, int newLength, byte* oldPtr, int oldLength, out int disp, int minDisp = 1) { - public byte[] bytes; - public int p = 0; - public int length; - public HUFF_STREAM(byte[] b) + 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++) { - bytes = b; - length = b.Length; - } + // 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++; + } - public bool hasBytes() - { - return p < bytes.Length; - } + // update the optimal value + if (currentLength > maxLength) + { + maxLength = currentLength; + disp = oldLength - i; - public int ReadByte() - { - return bytes[p++] & 0xFF; - } - public int readThree() - { - return ((bytes[p++] & 0xFF)) | ((bytes[p++] & 0xFF) << 8) | ((bytes[p++] & 0xFF) << 16); + // if we cannot do better anyway, stop trying. + if (maxLength == newLength) + break; + } } - public int ReadInt32() + return maxLength; + } + + public static byte[] rle_Decompress(byte[] instream) + { + using (var input = new MemoryStream(instream)) + using (var output = new MemoryStream()) { - if (p >= length) - return 0; - else - return ((bytes[p++] & 0xFF)) | ((bytes[p++] & 0xFF) << 8) | ((bytes[p++] & 0xFF) << 16) | ((bytes[p++] & 0xFF) << 24); + var decoder = new RleDecoder(); + decoder.Decode(input, output); + + return output.ToArray(); } } - public class HuffTreeNode + // RLE From Kuriimu2 + + public class RleHeaderlessDecoder { - public byte data; - public bool isData; - public HuffTreeNode child0; public HuffTreeNode child1; - public HuffTreeNode(HUFF_STREAM stream, bool isData, long relOffset, long maxStreamPos) + public void Decode(Stream input, Stream output, int decompressedSize) { - if (stream.p >= maxStreamPos) + while (output.Length < decompressedSize) { - return; + var flag = input.ReadByte(); + if ((flag & 0x80) > 0) + { + var repetitions = (flag & 0x7F) + 3; + output.Write(Enumerable.Repeat((byte)input.ReadByte(), repetitions).ToArray(), 0, repetitions); + } + else + { + var length = flag + 1; + var uncompressedData = new byte[length]; + input.Read(uncompressedData, 0, length); + output.Write(uncompressedData, 0, length); + } } - int readData = stream.ReadByte(); - this.data = (byte)readData; + } + } - this.isData = isData; + public class RleDecoder + { + private readonly RleHeaderlessDecoder _decoder; - if (!this.isData) - { - int offset = this.data & 0x3F; - bool zeroIsData = (this.data & 0x80) > 0; - bool oneIsData = (this.data & 0x40) > 0; + public RleDecoder() + { + _decoder = new RleHeaderlessDecoder(); + } - long zeroRelOffset = (relOffset ^ (relOffset & 1)) + (offset * 2) + 2; + public void Decode(Stream input, Stream output) + { + var compressionHeader = new byte[4]; + input.Read(compressionHeader, 0, 4); + if ((compressionHeader[0] & 0x7) != 0x4) + throw new Exception("Level5 Rle"); - 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) + // HuffmanDecoder & HuffmanHeaderlessDecoder From Kuriimu2 + public class HuffmanDecoder { - 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 HuffmanHeaderlessDecoder _decoder; + + public HuffmanDecoder(int bitDepth, NibbleOrder nibbleOrder) { - instream.p -= 3; - decompressedSize = instream.ReadInt32(); - ReadBytes += 4; - } + _bitDepth = bitDepth; - List o = new List(); + _decoder = new HuffmanHeaderlessDecoder(bitDepth, nibbleOrder); + } - int treeSize = instream.ReadByte(); ReadBytes++; - treeSize = (treeSize + 1) * 2; + public void Decode(Stream input, Stream output) + { + var compressionHeader = new byte[4]; + input.Read(compressionHeader, 0, 4); - long treeEnd = (instream.p - 1) + treeSize; + var huffmanMode = _bitDepth == 4 ? 2 : 3; + if ((compressionHeader[0] & 0x7) != huffmanMode) + throw new InvalidDataException($"Level5 Huffman{_bitDepth}"); - // 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); + var decompressedSize = (compressionHeader[0] >> 3) | (compressionHeader[1] << 5) | + (compressionHeader[2] << 13) | (compressionHeader[3] << 21); - 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; + _decoder.Decode(input, output, decompressedSize); + } - // the current u32 we are reading bits from. - int data = 0; - // the amount of bits left to read from - byte bitsLeft = 0; + public void Dispose() + { + // nothing to dispose + } + } - // a cache used for writing when the block size is four bits - int cachedByte = -1; + public class HuffmanHeaderlessDecoder + { + private readonly int _bitDepth; + private readonly NibbleOrder _nibbleOrder; - // the current output size - HuffTreeNode currentNode = rootNode; + public HuffmanHeaderlessDecoder(int bitDepth, NibbleOrder nibbleOrder) + { + _bitDepth = bitDepth; + _nibbleOrder = nibbleOrder; + } - while (instream.hasBytes()) + public void Decode(Stream input, Stream output, int decompressedSize) { - while (!currentNode.isData) + var result = new byte[decompressedSize * 8 / _bitDepth]; + + using (var br = new BinaryReader(input, Encoding.ASCII, true)) { - // if there are no bits left to read in the data, get a new byte from the input - if (bitsLeft == 0) + var treeSize = br.ReadByte(); + var treeRoot = br.ReadByte(); + var treeBuffer = br.ReadBytes(treeSize * 2); + + for (int i = 0, code = 0, next = 0, pos = treeRoot, resultPos = 0; resultPos < result.Length; i++) { - ReadBytes += 4; - data = instream.ReadInt32(); - bitsLeft = 32; - } - // 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; - } + if (i % 32 == 0) + code = br.ReadInt32(); - switch (type) - { - 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: + next += ((pos & 0x3F) << 1) + 2; + var direction = (code >> (31 - i)) % 2 == 0 ? 2 : 1; + var leaf = (pos >> 5 >> direction) % 2 != 0; + + pos = treeBuffer[next - direction]; + if (leaf) { - // 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; + result[resultPos++] = (byte)pos; + pos = treeRoot; + next = 0; } + } } - currentNode = rootNode; + if (_bitDepth == 8) + output.Write(result, 0, result.Length); + else + { + 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(); + + 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) @@ -787,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(); @@ -842,4 +880,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); + } + + } + } +}