| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| using AuroraLib.Archives; | ||
| using AuroraLib.Common; | ||
|
|
||
| namespace AuroraLib.Archives.Formats | ||
| { | ||
| public class PAK_TM2 : Archive, IFileAccess | ||
| { | ||
| public bool CanRead => true; | ||
|
|
||
| public bool CanWrite => false; | ||
|
|
||
| public const string Extension = ".pak"; | ||
|
|
||
| public bool IsMatch(Stream stream, in string extension = "") | ||
| => Matcher(stream, extension); | ||
|
|
||
| public static bool Matcher(Stream stream, in string extension = "") | ||
| { | ||
| if ((extension == Extension || extension == string.Empty || extension == ".cns") && stream.Length > 0x20) | ||
| { | ||
| uint entryCount = stream.ReadUInt32(Endian.Big); | ||
| if (entryCount != 0 && entryCount < 1024 && stream.Position + entryCount * 8 < stream.Length) | ||
| { | ||
| Entry[] entrys = stream.For((int)entryCount, s => s.Read<Entry>(Endian.Big)); | ||
|
|
||
| for (int i = 0; i < entryCount - 1; i++) | ||
| { | ||
| if (entrys[i].Offset + entrys[i].Size > entrys[i + 1].Offset) | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
| return entrys.First().Offset >= stream.Position && entrys.Last().Offset + entrys.Last().Size == stream.Length; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| protected override void Read(Stream stream) | ||
| { | ||
| uint entryCount = stream.ReadUInt32(Endian.Big); | ||
| Entry[] entrys = stream.For((int)entryCount, s => s.Read<Entry>(Endian.Big)); | ||
|
|
||
|
|
||
| Root = new ArchiveDirectory() { OwnerArchive = this }; | ||
| for (int i = 0; i < entrys.Length; i++) | ||
| { | ||
| Root.AddArchiveFile(stream, entrys[i].Size, entrys[i].Offset, $"Entry_{i}"); | ||
| } | ||
| } | ||
|
|
||
| protected override void Write(Stream stream) => throw new NotImplementedException(); | ||
|
|
||
| private struct Entry | ||
| { | ||
| public uint Offset; | ||
| public uint Size; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| using AuroraLib.Common; | ||
| using ICSharpCode.SharpZipLib.Checksum; | ||
|
|
||
| namespace AuroraLib.Archives.Formats | ||
| { | ||
| //base on https://forum.xentax.com/viewtopic.php?f=10&t=5938 | ||
| public class PK_AQ : Archive, IFileAccess | ||
| { | ||
| public bool CanRead => true; | ||
|
|
||
| public bool CanWrite => false; | ||
|
|
||
| public static string FSExtension => ".pfs"; | ||
|
|
||
| public static string HeaderExtension => ".pkh"; | ||
|
|
||
| public static string DataExtension => ".pk"; | ||
|
|
||
| public bool IsMatch(Stream stream, in string extension = "") | ||
| => Matcher(stream, extension); | ||
|
|
||
| public static bool Matcher(Stream stream, in string extension = "") | ||
| { | ||
| if (extension.ToLower() == HeaderExtension) | ||
| { | ||
| uint Entrys = stream.ReadUInt32(Endian.Big); | ||
| return Entrys * 0x10 + 4 == stream.Length; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| protected override void Read(Stream hStream) | ||
| { | ||
| //try to request external files. | ||
| string datname = Path.ChangeExtension(Path.GetFileNameWithoutExtension(FullPath), DataExtension); | ||
| try | ||
| { | ||
| pkstream = FileRequest.Invoke(datname); | ||
| } | ||
| catch (Exception) | ||
| { | ||
| throw new Exception($"{nameof(PK_AQ)}: could not request the file {datname}."); | ||
| } | ||
|
|
||
| //Read PKH | ||
| uint Entrys = hStream.ReadUInt32(Endian.Big); | ||
| HEntry[] hEntries = hStream.For((int)Entrys, s => s.Read<HEntry>(Endian.Big)); | ||
|
|
||
| try | ||
| { | ||
| datname = Path.ChangeExtension(Path.GetFileNameWithoutExtension(FullPath), FSExtension); | ||
| Stream fsStream = FileRequest.Invoke(datname); | ||
|
|
||
| //Read PFS | ||
| FSHeader header = fsStream.Read<FSHeader>(Endian.Big); | ||
| DirectoryEntry[] directories = fsStream.For((int)header.Directorys, s => s.Read<DirectoryEntry>(Endian.Big)); | ||
| uint[] directoryNameOffsets = fsStream.For((int)header.Directorys, s => s.ReadUInt32(Endian.Big)); | ||
| uint[] fileNameOffsets = fsStream.For((int)header.Files, s => s.ReadUInt32(Endian.Big)); | ||
| long nameTabelPos = fsStream.Position; | ||
|
|
||
| //Process | ||
| BZip2Crc crc32 = new(); | ||
| Dictionary<int, ArchiveDirectory> directoryPairs = new(); | ||
| for (int i = 0; i < directories.Length; i++) | ||
| { | ||
| DirectoryEntry dirEntry = directories[i]; | ||
| fsStream.Seek(nameTabelPos + directoryNameOffsets[i], SeekOrigin.Begin); | ||
| string name = fsStream.ReadString(); | ||
|
|
||
| ArchiveDirectory dir = new() | ||
| { | ||
| OwnerArchive = this, | ||
| Name = name, | ||
| }; | ||
| directoryPairs.Add(directories[i].Index, dir); | ||
| if (directories[i].ParentIndex != -1) | ||
| { | ||
| directoryPairs[dirEntry.ParentIndex].Items.Add(name, dir); | ||
| dir.Parent = directoryPairs[dirEntry.ParentIndex]; | ||
| } | ||
|
|
||
| //Process FileEntrys | ||
| for (int fi = 0; fi < dirEntry.FileEntrys; fi++) | ||
| { | ||
| int FileIndex = dirEntry.FileStartChild + fi; | ||
| fsStream.Seek(nameTabelPos + fileNameOffsets[FileIndex], SeekOrigin.Begin); | ||
| string filename = fsStream.ReadString(); | ||
| //Get CRC32 | ||
| crc32.Reset(); | ||
| crc32.Update(Path.Combine(dir.FullPath, filename).Replace('\\', '/').ToLower().Replace('?', 'L').ToByte()); | ||
| uint test = (uint)crc32.Value; | ||
| HEntry fileEntry = Array.Find(hEntries, e => e.CRC32 == crc32.Value); | ||
|
|
||
| if (fileEntry.IsCompressed) | ||
| dir.AddArchiveFile(pkstream, fileEntry.ComprSize, fileEntry.Offset, filename + ".lz"); | ||
| else | ||
| dir.AddArchiveFile(pkstream, fileEntry.DecomSize, fileEntry.Offset, filename); | ||
| } | ||
| } | ||
| Root = directoryPairs[0]; | ||
| fsStream.Close(); | ||
| } | ||
| catch (Exception) | ||
| { | ||
| //Process without PFS | ||
| Root = new ArchiveDirectory() { OwnerArchive = this }; | ||
| for (int i = 0; i < Entrys; i++) | ||
| { | ||
| if (hEntries[i].IsCompressed) | ||
| Root.AddArchiveFile(pkstream, hEntries[i].ComprSize, hEntries[i].Offset, $"{i}_{hEntries[i].CRC32}.lz"); | ||
| else | ||
| Root.AddArchiveFile(pkstream, hEntries[i].DecomSize, hEntries[i].Offset, $"{i}_{hEntries[i].CRC32}.bin"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| protected override void Write(Stream stream) => throw new NotImplementedException(); | ||
|
|
||
| public struct FSHeader | ||
| { | ||
| public uint Pad1; | ||
| public uint Pad2; | ||
| public uint Directorys; | ||
| public uint Files; | ||
| } | ||
|
|
||
| public struct DirectoryEntry | ||
| { | ||
| public int Index; | ||
| public int ParentIndex; | ||
| public int DirectoryStartChild; | ||
| public uint DirectoryEntrys; | ||
| public int FileStartChild; | ||
| public uint FileEntrys; | ||
| } | ||
|
|
||
| public struct HEntry | ||
| { | ||
| public uint CRC32; | ||
| public uint Offset; | ||
| public uint DecomSize; | ||
| public uint ComprSize; | ||
|
|
||
| public bool IsCompressed => ComprSize != 0; | ||
| } | ||
|
|
||
| private Stream pkstream; | ||
|
|
||
| protected override void Dispose(bool disposing) | ||
| { | ||
| base.Dispose(disposing); | ||
| if (disposing) | ||
| { | ||
| if (pkstream != null) | ||
| { | ||
| pkstream.Dispose(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| using AuroraLib.Common; | ||
|
|
||
| namespace AuroraLib.Compression.Formats | ||
| { | ||
| /// <summary> | ||
| /// This LZSS header was used in Skies of Arcadia Legends | ||
| /// </summary> | ||
| public class AKLZ : ICompression, IMagicIdentify | ||
| { | ||
| public bool CanWrite { get; } = true; | ||
|
|
||
| public bool CanRead { get; } = true; | ||
|
|
||
| public string Magic => magic; | ||
|
|
||
| private const string magic = "AKLZ~?Qd=ÌÌÍ"; | ||
|
|
||
| private static readonly LZSS LZSS = new(12, 4, 2); | ||
|
|
||
| public bool IsMatch(Stream stream, in string extension = "") | ||
| => stream.Length > 4 && stream.MatchString(Magic); | ||
|
|
||
| public byte[] Decompress(Stream source) | ||
| { | ||
| source.Position += 0xC; | ||
| uint decompressedSize = source.ReadUInt32(Endian.Big); | ||
| return LZSS.Decompress(source, (int)decompressedSize).ToArray(); | ||
| } | ||
|
|
||
| public void Compress(in byte[] source, Stream destination) => throw new NotImplementedException(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| using AuroraLib.Common; | ||
|
|
||
| namespace AuroraLib.Compression.Formats | ||
| { | ||
| public class AsuraZlb : ICompression, IMagicIdentify | ||
| { | ||
| public bool CanWrite { get; } = true; | ||
|
|
||
| public bool CanRead { get; } = true; | ||
|
|
||
| public string Magic => magic; | ||
|
|
||
| private const string magic = "AsuraZlb"; | ||
|
|
||
| private static readonly ZLib ZLib = new(); | ||
|
|
||
| public bool IsMatch(Stream stream, in string extension = "") | ||
| => stream.Length > 4 && stream.MatchString(Magic); | ||
|
|
||
| public byte[] Decompress(Stream source) | ||
| => source.At(0x14, s => ZLib.Decompress(s)); | ||
|
|
||
| public void Compress(in byte[] source, Stream destination) | ||
| { | ||
| long start = destination.Position; | ||
|
|
||
| destination.Write(Magic); | ||
| destination.Write(1); | ||
| destination.Write(0); // Placeholder | ||
| destination.Write(source.Length); | ||
| ZLib.Compress(source, destination, 5); | ||
| destination.Seek(start + 0xC, SeekOrigin.Begin); | ||
| destination.Write(destination.Length - start - 0x14); | ||
| } | ||
|
|
||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| using AuroraLib.Common; | ||
| using AuroraLib.Compression; | ||
|
|
||
| namespace AuroraLib.Compression.Formats | ||
| { | ||
| /// <summary> | ||
| /// CNS compression algorithm, used in Games from Red Entertainment. | ||
| /// </summary> | ||
| public class CNS : ICompression, IMagicIdentify | ||
| { | ||
| public bool CanRead => true; | ||
|
|
||
| public bool CanWrite => true; | ||
|
|
||
| public string Magic => magic; | ||
|
|
||
| public const string magic = "@CNS"; | ||
|
|
||
| public byte[] Decompress(Stream source) | ||
| { | ||
| // CNS files start with the "@CNS" followed by the file extension. | ||
| source.Position += 8; | ||
| uint decompressedSize = source.ReadUInt32(Endian.Little); | ||
| source.Position += 4; // 0 | ||
|
|
||
| return Decompress_ALG(source, (int)decompressedSize); | ||
| } | ||
|
|
||
| public void Compress(in byte[] source, Stream destination) | ||
| { | ||
| destination.Write(magic); | ||
| if (source[0] == 0x0 && source[1] == 0x20 && source[2] == 0xAF && source[3] == 0x30) | ||
| { | ||
| destination.WriteString("TPL"); | ||
| } | ||
| else | ||
| { | ||
| destination.WriteString("PAK"); | ||
| } | ||
| destination.Write(source.Length, Endian.Little); | ||
| destination.Write(0); | ||
|
|
||
| Compress_ALG(source, destination); | ||
| } | ||
|
|
||
| public bool IsMatch(Stream stream, in string extension = "") | ||
| => stream.MatchString(magic); | ||
|
|
||
|
|
||
| public static byte[] Decompress_ALG(Stream source, int decomLength) | ||
| { | ||
| byte data1, data2; | ||
|
|
||
| byte[] destination = new byte[decomLength]; | ||
| int destinationPointer = 0; | ||
|
|
||
| while (destinationPointer < decomLength && source.Position != source.Length) | ||
| { | ||
| data1 = source.ReadUInt8(); | ||
|
|
||
| if ((data1 & 0x80) == 0) // Uncompressed value | ||
| { | ||
| for (int i = 0; i < data1; i++) | ||
| { | ||
| destination[destinationPointer++] = source.ReadUInt8(); | ||
| } | ||
| } | ||
| else // Compressed value | ||
| { | ||
| data2 = source.ReadUInt8(); | ||
|
|
||
| int matchDistance = data2 + 1; | ||
| int matchLength = (data1 & 0x7F) + 3; | ||
|
|
||
| for (int i = 0; i < matchLength; i++) | ||
| { | ||
| destination[destinationPointer] = destination[destinationPointer - matchDistance]; | ||
| destinationPointer++; | ||
| } | ||
| } | ||
| } | ||
| return destination; | ||
| } | ||
|
|
||
|
|
||
| public static void Compress_ALG(byte[] sourceArray, Stream destination) | ||
| { | ||
| int sourceLength = sourceArray.Length, | ||
| sourcePointer = 0x0; | ||
|
|
||
| byte data1, data2; | ||
|
|
||
| // Initalize the LZ dictionary | ||
| LzWindowDictionary dictionary = new(); | ||
| dictionary.SetWindowSize(255); | ||
| dictionary.SetMaxMatchAmount(130); | ||
|
|
||
| using (MemoryStream buffer = new(255)) | ||
| { | ||
| // Start compression | ||
| while (sourcePointer < sourceLength) | ||
| { | ||
| // Search for a match | ||
| int[] match = dictionary.Search(sourceArray, (uint)sourcePointer, (uint)sourceLength); | ||
|
|
||
|
|
||
| if ((buffer.Length != 0 && match[1] > 0) || buffer.Length == 127) | ||
| { | ||
| destination.WriteByte((byte)buffer.Length); | ||
| buffer.WriteTo(destination); | ||
| buffer.SetLength(0); | ||
| } | ||
|
|
||
| if (match[1] > 0) // There is a match | ||
| { | ||
|
|
||
| data1 = (byte)(0x80 | (match[1] - 3)); | ||
| data2 = (byte)(match[0] - 1); | ||
| destination.WriteByte(data1); | ||
| destination.WriteByte(data2); | ||
|
|
||
| dictionary.AddEntryRange(sourceArray, sourcePointer, match[1]); | ||
|
|
||
| sourcePointer += match[1]; | ||
| } | ||
| else // There is not a match | ||
| { | ||
| buffer.WriteByte(sourceArray[sourcePointer]); | ||
|
|
||
| dictionary.AddEntry(sourceArray, sourcePointer); | ||
|
|
||
| sourcePointer++; | ||
| } | ||
|
|
||
| // Check to see if we reached the end of the file | ||
| if (sourcePointer >= sourceLength) | ||
| break; | ||
| } | ||
|
|
||
| if (buffer.Length != 0) | ||
| { | ||
| destination.WriteByte((byte)buffer.Length); | ||
| buffer.WriteTo(destination); | ||
| buffer.SetLength(0); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| using AuroraLib.Common; | ||
|
|
||
| namespace AuroraLib.Compression.Formats | ||
| { | ||
| /// <summary> | ||
| /// This LZSS header was used by Sega in early GC games like F-zero GX or Super Monkey Ball. | ||
| /// </summary> | ||
| public class LZSS_Sega : ICompression | ||
| { | ||
| public bool CanRead => true; | ||
|
|
||
| public bool CanWrite => true; | ||
|
|
||
| public const string Extension = ".lz"; | ||
|
|
||
| private static readonly LZSS LZSS = new(12, 4, 2); | ||
|
|
||
| public bool IsMatch(Stream stream, in string extension = "") | ||
| { | ||
| uint compressedSize = stream.ReadUInt32(); | ||
| uint decompressedSize = stream.ReadUInt32(); | ||
| return (compressedSize == stream.Length - 8 || compressedSize == stream.Length) && decompressedSize != compressedSize && decompressedSize >= 0x20; | ||
| } | ||
|
|
||
| public void Compress(in byte[] source, Stream destination) | ||
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
|
|
||
| public byte[] Decompress(Stream source) | ||
| { | ||
| uint compressedSize = source.ReadUInt32(); | ||
| uint decompressedSize = source.ReadUInt32(); | ||
|
|
||
| return LZSS.Decompress(source, (int)decompressedSize).ToArray(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| using AuroraLib.Common; | ||
| using AuroraLib.Compression; | ||
|
|
||
| namespace AuroraLib.Compression.Formats | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| using AuroraLib.Common; | ||
|
|
||
| namespace AuroraLib.Texture.Formats | ||
| { | ||
| internal class ALIG : JUTTexture, IMagicIdentify, IFileAccess | ||
| { | ||
| public virtual bool CanRead => true; | ||
|
|
||
| public virtual bool CanWrite => false; | ||
|
|
||
| public virtual string Magic => magic; | ||
|
|
||
| private const string magic = "ALIG"; | ||
|
|
||
| public bool IsMatch(Stream stream, in string extension = "") | ||
| => stream.MatchString(Magic); | ||
|
|
||
| protected override void Read(Stream stream) | ||
| { | ||
| long start = stream.Position; | ||
|
|
||
| if (!stream.MatchString(magic)) | ||
| throw new InvalidIdentifierException(magic); | ||
|
|
||
| Header header = stream.Read<Header>(); | ||
| stream.Seek(start + header.Offset, SeekOrigin.Begin); | ||
|
|
||
| GXImageFormat format = AsGXFormat(header.Format); | ||
|
|
||
| Add(new TexEntry(stream, null, format, GXPaletteFormat.IA8, 0, header.Width, header.Height, header.Mips) | ||
| { | ||
| LODBias = 0, | ||
| MagnificationFilter = GXFilterMode.Nearest, | ||
| MinificationFilter = GXFilterMode.Nearest, | ||
| WrapS = GXWrapMode.CLAMP, | ||
| WrapT = GXWrapMode.CLAMP, | ||
| EnableEdgeLOD = false, | ||
| MinLOD = 0, | ||
| MaxLOD = 0 | ||
| }); | ||
|
|
||
| if (header.Format == ImageFormat.GACC) | ||
| { | ||
| Add(new TexEntry(stream, null, format, GXPaletteFormat.IA8, 0, header.Width, header.Height, header.Mips) | ||
| { | ||
| LODBias = 0, | ||
| MagnificationFilter = GXFilterMode.Nearest, | ||
| MinificationFilter = GXFilterMode.Nearest, | ||
| WrapS = GXWrapMode.CLAMP, | ||
| WrapT = GXWrapMode.CLAMP, | ||
| EnableEdgeLOD = false, | ||
| MinLOD = 0, | ||
| MaxLOD = 0 | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| protected override void Write(Stream stream) => throw new NotImplementedException(); | ||
|
|
||
| private struct Header | ||
| { | ||
| public int Unk; | ||
| public ImageFormat Format; | ||
| public int Pad; | ||
|
|
||
| public ushort Width; | ||
| public ushort Height; | ||
| public int Images; | ||
| public int Unk2;// 0x10 | ||
| public int Offset; // 0x40 | ||
|
|
||
| private uint size; | ||
| public int Unk3; | ||
| public int Unk4; | ||
| public ushort Un5; | ||
| public ushort Mips; | ||
|
|
||
| private uint Size | ||
| { | ||
| get => size.Swap(); | ||
| set => size = value.Swap(); | ||
| } | ||
| } | ||
|
|
||
| private enum ImageFormat | ||
| { | ||
| GCI4 = 877216583,//I4 | ||
| GCI8 = 944325447,//I8 | ||
| GIA4 = 876693831,//IA4 | ||
| GIA8 = 943802695,//IA8 | ||
| G565 = 892745031,//RGB565 | ||
| GACC = 1128481095,//2xCMPR | ||
| GCCP = 1346585415,//CMPR | ||
| G5A3 = 859911495, //RGB5A3 | ||
| } | ||
|
|
||
| private static GXImageFormat AsGXFormat(ImageFormat mode) => mode switch | ||
| { | ||
| ImageFormat.GCI4 => GXImageFormat.I4, | ||
| ImageFormat.GCI8 => GXImageFormat.I8, | ||
| ImageFormat.GIA4 => GXImageFormat.IA4, | ||
| ImageFormat.GIA8 => GXImageFormat.IA8, | ||
| ImageFormat.GACC => GXImageFormat.CMPR, | ||
| ImageFormat.GCCP => GXImageFormat.CMPR, | ||
| ImageFormat.G5A3 => GXImageFormat.RGB5A3, | ||
| ImageFormat.G565 => GXImageFormat.RGB565, | ||
| _ => throw new NotImplementedException(), | ||
| }; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| using AuroraLib.Common; | ||
|
|
||
| namespace AuroraLib.Texture.Formats | ||
| { | ||
| internal class ALTX : ALIG | ||
| { | ||
| public override string Magic => magic; | ||
|
|
||
| private const string magic = "ALTX"; | ||
|
|
||
|
|
||
| protected override void Read(Stream stream) | ||
| { | ||
| if (!stream.MatchString(magic)) | ||
| throw new InvalidIdentifierException(Magic); | ||
|
|
||
| uint unk = stream.ReadUInt32(); | ||
| uint Offset = stream.ReadUInt32(Endian.Big); | ||
|
|
||
| stream.Seek(Offset, SeekOrigin.Begin); | ||
|
|
||
| base.Read(stream); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| using AuroraLib.Common; | ||
| using AuroraLib.Texture; | ||
| using AuroraLib.Texture.Formats; | ||
|
|
||
| namespace AuroraLib.Texture.Formats | ||
| { | ||
| public class RES_NLG : JUTTexture, IFileAccess | ||
| { | ||
| public bool CanRead => true; | ||
|
|
||
| public bool CanWrite => false; | ||
|
|
||
| public const string Extension = ".res"; | ||
| public const string Extension2 = ".dmn"; | ||
|
|
||
| public bool IsMatch(Stream stream, in string extension = "") | ||
| => Matcher(stream, extension); | ||
|
|
||
| public static bool Matcher(Stream stream, in string extension = "") | ||
| => (extension.ToLower() == Extension || extension.ToLower() == Extension2) && stream.ReadInt32(Endian.Big) == 0x20 && stream.ReadInt32(Endian.Big) != 0 && stream.ReadInt32(Endian.Big) == 1; | ||
|
|
||
| protected override void Read(Stream stream) | ||
| { | ||
| Header header = stream.Read<Header>(Endian.Big); | ||
| stream.Seek(header.Offset, SeekOrigin.Begin); | ||
| Entry[] entries = stream.For((int)header.Entrys, s => s.Read<Entry>(Endian.Big)); | ||
| for (int i = 0; i < entries.Length; i++) | ||
| { | ||
| stream.Seek(entries[i].Offset, SeekOrigin.Begin); | ||
|
|
||
| if (PTLG.ReadTexture(stream, entries[i].Size, out TexEntry current)) | ||
| { | ||
| Add(current); | ||
| } | ||
|
|
||
| } | ||
| } | ||
|
|
||
| protected override void Write(Stream stream) => throw new NotImplementedException(); | ||
|
|
||
| private struct Header | ||
| { | ||
| public uint Offset; // 0x20 | ||
| public uint Entrys; | ||
| public uint Version; //1 | ||
| private uint firstEntry; | ||
|
|
||
| public long FirstEntryOffset | ||
| { | ||
| get => firstEntry << 5; | ||
| set => firstEntry = (uint)(value >> 5); | ||
| } | ||
| } | ||
|
|
||
| private struct Entry | ||
| { | ||
| public uint Hash; | ||
| private uint offset; | ||
| public uint Size; | ||
|
|
||
| public long Offset | ||
| { | ||
| get => offset << 5; | ||
| set => offset = (uint)(value >> 5); | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| using AuroraLib.Common; | ||
|
|
||
| namespace AuroraLib.Texture.Formats | ||
| { | ||
| public class text : JUTTexture, IMagicIdentify, IFileAccess | ||
| { | ||
| public virtual bool CanRead => true; | ||
|
|
||
| public virtual bool CanWrite => false; | ||
|
|
||
| public virtual string Magic => magic; | ||
|
|
||
| private const string magic = "chnkdata"; | ||
| private const string Platform = "wii "; | ||
| private const string Type = "text"; | ||
|
|
||
| public bool IsMatch(Stream stream, in string extension = "") | ||
| => Matcher(stream, extension); | ||
|
|
||
| public static bool Matcher(Stream stream, in string extension = "") | ||
| => extension.ToLower() != ".pk" && stream.Length > 0x40 && stream.MatchString(magic) && stream.ReadInt32() == 0 && stream.MatchString(Platform) && stream.MatchString(Type); | ||
|
|
||
| protected override void Read(Stream stream) | ||
| { | ||
| stream.Seek(0x10, SeekOrigin.Current); | ||
| Header header = stream.Read<Header>(Endian.Big); | ||
|
|
||
| Add(new TexEntry(stream, null, header.Format, GXPaletteFormat.IA8, 0, (int)header.Width, (int)header.Height, (int)header.Images - 1) | ||
| { | ||
| LODBias = 0, | ||
| MagnificationFilter = GXFilterMode.Nearest, | ||
| MinificationFilter = GXFilterMode.Nearest, | ||
| WrapS = GXWrapMode.CLAMP, | ||
| WrapT = GXWrapMode.CLAMP, | ||
| EnableEdgeLOD = false, | ||
| MinLOD = 0, | ||
| MaxLOD = header.Images - 1 | ||
| }); | ||
| } | ||
|
|
||
| protected override void Write(Stream stream) => throw new NotImplementedException(); | ||
|
|
||
| private struct Header | ||
| { | ||
| public uint Magic; // "text" 1952807028 | ||
| public uint pad; // 0x0 | ||
| public uint HeaderPos; // 0x10 | ||
| public uint Size; | ||
|
|
||
| private uint format; | ||
| public uint Width; | ||
| public uint Height; | ||
| public uint Images; | ||
|
|
||
| public uint Unk3; // 56 | ||
| public uint HeadeSize; // 64 | ||
| public uint Unk5; // 13421772 | ||
| public uint Unk6; // 3435973836 | ||
|
|
||
| public GXImageFormat Format | ||
| { | ||
| get => (GXImageFormat)format; | ||
| set => format = (uint)value; | ||
| } | ||
| } | ||
| } | ||
| } |