4 changes: 3 additions & 1 deletion lib/AuroraLip/Archives/Formats/PAK_RetroWii.cs
Expand Up @@ -12,7 +12,9 @@ public override bool IsMatch(Stream stream, in string extension = "")
=> Matcher(stream, extension);

public new static bool Matcher(Stream stream, in string extension = "")
=> extension.ToLower().Equals(Extension) && stream.ReadUInt32(Endian.Big) == 2;
{
return extension.ToLower().Equals(Extension) && stream.ReadUInt32(Endian.Big) == 2 && stream.ReadUInt32(Endian.Big) == 64 && stream.ReadUInt64(Endian.Big) != 0 && stream.ReadUInt64(Endian.Big) != 0;
}

protected override void Read(Stream stream)
{
Expand Down
60 changes: 60 additions & 0 deletions lib/AuroraLip/Archives/Formats/PAK_TM2.cs
@@ -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;
}
}
}
161 changes: 161 additions & 0 deletions lib/AuroraLip/Archives/Formats/PK_AQ.cs
@@ -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();
}
}
}
}
}
2 changes: 1 addition & 1 deletion lib/AuroraLip/Archives/Formats/RSC.cs
@@ -1,7 +1,7 @@
using AuroraLib.Archives;
using AuroraLib.Common;

namespace AuroraLip.Archives.Formats
namespace AuroraLib.Archives.Formats
{
public class RSC : Archive, IFileAccess
{
Expand Down
4 changes: 2 additions & 2 deletions lib/AuroraLip/Archives/Formats/ShrekDir.cs
@@ -1,8 +1,8 @@
using AuroraLib.Archives;
using AuroraLib.Common;
using AuroraLip.Compression.Formats;
using AuroraLib.Compression.Formats;

namespace AuroraLip.Archives.Formats
namespace AuroraLib.Archives.Formats
{
public class ShrekDir : Archive, IFileAccess
{
Expand Down
2 changes: 1 addition & 1 deletion lib/AuroraLip/AuroraLib.csproj
Expand Up @@ -11,7 +11,7 @@
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RootNamespace>AuroraLip</RootNamespace>
<RootNamespace>AuroraLib</RootNamespace>
<AssemblyName>$(AssemblyTitle)</AssemblyName>
<SignAssembly>False</SignAssembly>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
46 changes: 41 additions & 5 deletions lib/AuroraLip/Common/FormatDictionary_List.cs
@@ -1,7 +1,6 @@
using AuroraLib.Archives.Formats;
using AuroraLib.Compression.Formats;
using AuroraLib.Texture.Formats;
using AuroraLip.Archives.Formats;

namespace AuroraLib.Common
{
Expand Down Expand Up @@ -76,6 +75,7 @@ public static partial class FormatDictionary
new FormatInfo(".bars","BARS", FormatType.Audio, "Audio ReSource archive", Nin_),
new FormatInfo(".arsl","ARSL", FormatType.Audio, "Audio ReSource LIST", Nin_),
new FormatInfo(".dsp", FormatType.Audio, "Nintendo ADPCM codec", Nin_),
new FormatInfo(".dsp", "DSP", FormatType.Audio, "Nintendo ADPCM codec", Nin_),
new FormatInfo(".idsp","IDSP", FormatType.Audio,"Nintendo ADPCM codec", Nin_),
new FormatInfo(".baa", FormatType.Audio, "JAudio archive", Nin_),
new FormatInfo(".aw", FormatType.Audio, "JAudio wave archive", Nin_),
Expand Down Expand Up @@ -179,8 +179,10 @@ public static partial class FormatDictionary

//Next Level Games
new FormatInfo(".rlt","PTLG", FormatType.Texture, "Strikers Texture","Next Level Games"),
new FormatInfo(".Res", FormatType.Texture, "Strikers RES Texture","Next Level Games"){ Class = typeof(RES_NLG), IsMatch = RES_NLG.Matcher},
new FormatInfo(".sanim", FormatType.Animation, "Striker Skeleton Animation","Next Level Games"),
new FormatInfo(".nlxwb", FormatType.Audio, "Next Level Wave","Next Level Games"),
new FormatInfo(".fen","FENL", FormatType.Unknown, "","Next Level Games"),

//HAL Laboratory & Sora Ltd.
new FormatInfo(".pac",new byte[]{65,82,67,0},0, FormatType.Archive, "Brawl Archive"){ Class = typeof(ARC0)},
Expand Down Expand Up @@ -327,6 +329,8 @@ public static partial class FormatDictionary
new FormatInfo("", new byte[]{128,0,0,1,0},0, FormatType.Archive, "Sonic Riders lzss", "SEGA"), //https://github.com/romhack/sonic_riders_lzss
new FormatInfo(".rvm","CVMH", FormatType.Archive, "Sonic Riders Archive", "SEGA"),
new FormatInfo(".XVRs", FormatType.Texture, "Sonic Riders Texture", "SEGA"), //https://github.com/Sewer56/SonicRiders.Index/tree/master/Source
//SEGA Hitmaker
new FormatInfo(".bin","PK_0", FormatType.Archive, "Zip "+comp_, "Hitmaker"),

//Imageepoch
new FormatInfo(".vol", "RTDP", FormatType.Archive, "Arc Rise Archive", "Imageepoch"),
Expand Down Expand Up @@ -361,10 +365,14 @@ public static partial class FormatDictionary

//Vanillaware
new FormatInfo(".fcmp", "FCMP", FormatType.Archive, "Muramasa "+comp_,"Vanillaware"),//Muramasa - The Demon Blade Decompressor http://www.jaytheham.com/code/
new FormatInfo(".ftx", "FTEX", FormatType.Texture, "Muramasa Texture","Vanillaware"),
new FormatInfo(".ftx", "FTEX", FormatType.Archive, "Muramasa Texture Archive","Vanillaware"),
new FormatInfo(".mbs", "FMBS", FormatType.Model, "Muramasa Model","Vanillaware"),
new FormatInfo(".nms", "NMSB", FormatType.Text, "Muramasa Text","Vanillaware"),
new FormatInfo(".nsb", "NSBD", FormatType.Skript, "Muramasa Skript","Vanillaware"),
new FormatInfo(".abf", "MLIB", FormatType.Parameter, "Muramasa Skript","Vanillaware"),
new FormatInfo(".esb", "EMBP", FormatType.Parameter, "Muramasa Skript","Vanillaware"),
new FormatInfo(".otb", "OTB ", FormatType.Audio, "SE"),
new FormatInfo(".nsi", "NSI ", FormatType.Audio, "SE Info"),

//Krome Studios
new FormatInfo(".rkv", "RKV2", FormatType.Archive, "Star Wars Force Unleashed", "Krome Studios"),
Expand Down Expand Up @@ -430,14 +438,43 @@ public static partial class FormatDictionary
new FormatInfo(".asf","SCHl", FormatType.Audio,"audio","EA"),
new FormatInfo(".loc","LOCH", FormatType.Unknown,"","EA"),
new FormatInfo(".pfd","PFDx", FormatType.Unknown,"","EA"),

//Rebellion
new FormatInfo(".asrBE","AsuraZlb", FormatType.Archive,comp_, "Rebellion"),
new FormatInfo(".asrBE","Asura ", FormatType.Archive,"Asura Archive", "Rebellion"),
new FormatInfo(".txth","TXTH", FormatType.Text,"Asura Text", "Rebellion"),
new FormatInfo(".fcsr","FCSR", FormatType.Archive,"Asura archive entry", "Rebellion"),
new FormatInfo(".txtt","TXTT", FormatType.Parameter,"Asura texture info", "Rebellion"),
new FormatInfo(".ofnf","OFNF", FormatType.Parameter,"Asura archive info", "Rebellion"),
new FormatInfo(".lfsr","LFSR", FormatType.Parameter,"", "Rebellion"),
new FormatInfo(".msds","MSDS", FormatType.Parameter,"", "Rebellion"),
new FormatInfo(".veld","VELD", FormatType.Parameter,"", "Rebellion"),

//Red Entertainment
new FormatInfo(".pak", FormatType.Archive, "Tengai Makyō II Archive", "Red Entertainment"){ Class = typeof(PAK_TM2), IsMatch = PAK_TM2.Matcher },
new FormatInfo(".cns","@CNS", FormatType.Archive, "CNS Compressed", "Red Entertainment"),
new FormatInfo(".hps"," HALPST", FormatType.Audio, "Audio", "Red Entertainment"),
new FormatInfo(".iwf"," FWS4", FormatType.Unknown, "?", "Red Entertainment"),
new FormatInfo(".exi"," MROF", FormatType.Unknown, "?", "Red Entertainment"),

//AQ Interactive
new FormatInfo(".pk", FormatType.Archive, "","AQ Interactive") { Class = typeof(PK_AQ), IsMatch = PK_AQ.Matcher, },
new FormatInfo(".texture",new byte[]{ 0x63, 0x68,0x6E,0x6B,0x64,0x61,0x74,0x61, 0x00, 0x00, 0x00, 0x00, 0x77, 0x69, 0x69, 0x20, 0x74, 0x65, 0x78, 0x74},0, FormatType.Texture, "Texture data","AQ Interactive"){ Class = typeof(text), IsMatch = text.Matcher, },
new FormatInfo(".model",new byte[]{0x63,0x68,0x6E,0x6B,0x64,0x61,0x74,0x61, 0x00, 0x00, 0x00, 0x00, 0x77, 0x69, 0x69, 0x20, 0x6D, 0x6F, 0x64, 0x6C},0, FormatType.Model, "Model data","AQ Interactive"),
new FormatInfo(".motion",new byte[]{0x63,0x68,0x6E,0x6B,0x64,0x61,0x74,0x61, 0x00, 0x00, 0x00, 0x00, 0x77, 0x69, 0x69, 0x20, 0x61 , 0x6E, 0x69, 0x6D},0, FormatType.Animation, "Animation data","AQ Interactive"),
new FormatInfo(".locator",new byte[]{0x63,0x68,0x6E,0x6B,0x64,0x61,0x74,0x61, 0x00, 0x00, 0x00, 0x00, 0x77, 0x69, 0x69, 0x20, 0x6C, 0x6F, 0x63, 0x74},0, FormatType.Parameter, "Locator data","AQ Interactive"),
new FormatInfo(".hocb"," COH@", FormatType.Collision, "collision data","AQ Interactive"),
new FormatInfo(".eff"," @EFF", FormatType.Effect, "effect data","AQ Interactive"),
new FormatInfo(".xsca"," @FSX", FormatType.Unknown, "data","AQ Interactive"),
new FormatInfo(".hcb"," BCH@", FormatType.Unknown, "data","AQ Interactive"),

//mix
//new FormatInfo(".cmpr","CMPR", FormatType.Archive, "compressed Data"),
new FormatInfo(".ash","ASH0", FormatType.Archive, comp_), //https://github.com/trapexit/wiiqt/blob/master/WiiQt/ash.cpp
new FormatInfo(".mio","MIO0", FormatType.Archive, comp_),
new FormatInfo(".fpk", FormatType.Archive, "Archive","Eighting"){ Class = typeof(FPK), IsMatch = FPK.Matcher},
new FormatInfo(".dir", FormatType.Else, "Archive Info"),
new FormatInfo(".pk", FormatType.Archive), //https://github.com/RGBA-CRT/LSPK-Extracter
new FormatInfo(".asr","AsuraZlb", FormatType.Archive, "Rebellion"),

new FormatInfo(".dict", new byte[]{169,243,36,88,6,1},0, FormatType.Archive),
new FormatInfo(".dat", "AKLZ~?Qd=ÌÌÍ", FormatType.Archive,"Skies of Arcadia Legends"),
new FormatInfo(".cmn", FormatType.Archive) { Class = typeof(CMN)}, //http://wiki.xentax.com/index.php/NHL_2K3_CMN
Expand Down Expand Up @@ -488,7 +525,6 @@ public static partial class FormatDictionary
new FormatInfo(".lfxt","LFXT", FormatType.Texture, "Pitfall Texture"),
new FormatInfo(".txfl","TXFL", FormatType.Texture, "Pitfall Texture"),
new FormatInfo(".arc", FormatType.Archive, "Pitfall Archive"){ Class = typeof(ARC_Pit), IsMatch = ARC_Pit.Matcher},
new FormatInfo(".asrBE","Asura TXTH", FormatType.Text, "Rebellion"),
new FormatInfo(".bas", FormatType.Animation, "Sound Animation"),
new FormatInfo(".blight", "LGHT", FormatType.Effect, "Light"),
new FormatInfo(".bfog", "FOGM", FormatType.Else, "Fog"),
Expand Down
2 changes: 1 addition & 1 deletion lib/AuroraLip/Compression/FlagReader.cs
@@ -1,6 +1,6 @@
using AuroraLib.Common;

namespace AuroraLip.Compression
namespace AuroraLib.Compression
{
/// <summary>
/// Reads individual bits from a stream and provides methods for interpreting the flag values.
Expand Down
32 changes: 32 additions & 0 deletions lib/AuroraLip/Compression/Formats/AKLZ.cs
@@ -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();
}
}
37 changes: 37 additions & 0 deletions lib/AuroraLip/Compression/Formats/AsuraZlb.cs
@@ -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);
}

}
}
149 changes: 149 additions & 0 deletions lib/AuroraLip/Compression/Formats/CNS.cs
@@ -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);
}
}
}
}
}
10 changes: 5 additions & 5 deletions lib/AuroraLip/Compression/Formats/LZSS.cs
Expand Up @@ -49,23 +49,23 @@ public byte[] Decompress(Stream source)
uint compressedSize = source.ReadUInt32(Endian.Big);
uint unk = source.ReadUInt32(Endian.Big);

return Decompress(source, (int)compressedSize).ToArray();
return Decompress(source, (int)decompressedSize).ToArray();
}

public MemoryStream Decompress(Stream inputStream, int compressedSize)
public MemoryStream Decompress(Stream inputStream, int decomLength)
{
int n = 1 << EI;
int f = 1 << EJ;

int flags = 0;
byte[] slidingWindow = new byte[n];
MemoryStream outStream = new MemoryStream(compressedSize);
Span<byte> slidingWindow = EI <= 14 ? stackalloc byte[n] : new byte[n];
MemoryStream outStream = new(decomLength);

int r = n - f - P;
n--;
f--;

while (outStream.Position < compressedSize)
while (outStream.Position < decomLength)
{
//Reads New Code Word from Compressed Stream if Expired
if ((flags & 0x100) == 0)
Expand Down
38 changes: 38 additions & 0 deletions lib/AuroraLip/Compression/Formats/LZSS_Sega.cs
@@ -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();
}
}
}
2 changes: 1 addition & 1 deletion lib/AuroraLip/Compression/Formats/PRS.cs
@@ -1,5 +1,5 @@
using AuroraLib.Common;
using AuroraLip.Compression;
using AuroraLib.Compression;

namespace AuroraLib.Compression.Formats
{
Expand Down
2 changes: 1 addition & 1 deletion lib/AuroraLip/Compression/Formats/RefPack.cs
@@ -1,7 +1,7 @@
using AuroraLib.Common;
using AuroraLib.Compression;

namespace AuroraLip.Compression.Formats
namespace AuroraLib.Compression.Formats
{
/// <summary>
/// RefPack is an LZ77/LZSS compression format made by Frank Barchard of EA Canada
Expand Down
2 changes: 1 addition & 1 deletion lib/AuroraLip/Compression/Formats/Shrek.cs
@@ -1,6 +1,6 @@
using AuroraLib.Compression;

namespace AuroraLip.Compression.Formats
namespace AuroraLib.Compression.Formats
{
//base on https://github.com/ShrekBoards/shrek-superslam/blob/master/src/compression.rs
public class Shrek : ICompression
Expand Down
2 changes: 1 addition & 1 deletion lib/AuroraLip/Compression/Formats/ZLB.cs
Expand Up @@ -2,7 +2,7 @@
using AuroraLib.Compression;
using AuroraLib.Compression.Formats;

namespace AuroraLip.Compression.Formats
namespace AuroraLib.Compression.Formats
{
public class ZLB : ICompression, IMagicIdentify
{
Expand Down
42 changes: 20 additions & 22 deletions lib/AuroraLip/Compression/Formats/ZLib.cs
Expand Up @@ -64,39 +64,37 @@ public byte[] Compress(byte[] Data, CompressionLevel level)

public MemoryStream Compress(byte[] Data, CompressionLevel level, int bufferSize = 4096, bool noHeader = false)
{
MemoryStream ms = new MemoryStream();
byte[] buffer = new byte[bufferSize];
MemoryStream ms = new();
Compress(Data, ms, level, bufferSize, noHeader);
return ms;
}

int dlevel = -1;
switch (level)
public void Compress(byte[] Data, Stream dst, CompressionLevel level, int bufferSize = 4096, bool noHeader = false)
{
int dlevel = level switch
{
case CompressionLevel.NoCompression:
dlevel = 0;
break;

case CompressionLevel.Optimal:
dlevel = -1;
break;

case CompressionLevel.Fastest:
dlevel = 1;
break;
CompressionLevel.NoCompression => 0,
CompressionLevel.Optimal => -1,
CompressionLevel.Fastest => 1,
CompressionLevel.SmallestSize => 9,
_ => -1,
};
Compress(Data, dst, dlevel, bufferSize, noHeader);
}
public void Compress(byte[] Data, Stream dst, int level, int bufferSize = 4096, bool noHeader = false)
{
byte[] buffer = new byte[bufferSize];

case CompressionLevel.SmallestSize:
dlevel = 9;
break;
}
Deflater deflater = new(dlevel, noHeader);
Deflater deflater = new(level, noHeader);
deflater.SetInput(Data);

while (!deflater.IsFinished)
{
deflater.Deflate(buffer);
ms.Write(buffer, 0, buffer.Length);
dst.Write(buffer, 0, buffer.Length);
}
Adler = deflater.Adler;
deflater.Reset();
return ms;
}

private static bool IsMatch(in byte[] Data)
Expand Down
110 changes: 110 additions & 0 deletions lib/AuroraLip/Texture/Formats/ALIG.cs
@@ -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(),
};
}
}
25 changes: 25 additions & 0 deletions lib/AuroraLip/Texture/Formats/ALTX.cs
@@ -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);
}
}
}
2 changes: 1 addition & 1 deletion lib/AuroraLip/Texture/Formats/GCNT.cs
@@ -1,7 +1,7 @@
using AuroraLib.Common;
using AuroraLib.Texture;

namespace AuroraLip.Texture.Formats
namespace AuroraLib.Texture.Formats
{
public class GCNT : JUTTexture, IMagicIdentify, IFileAccess
{
Expand Down
2 changes: 1 addition & 1 deletion lib/AuroraLip/Texture/Formats/LFXT.cs
Expand Up @@ -15,7 +15,7 @@ public class LFXT : JUTTexture, IMagicIdentify, IFileAccess
private const string magic = "LFXT";

public bool IsMatch(Stream stream, in string extension = "")
=> stream.MatchString(magic);
=> stream.Length > 0x30 && stream.MatchString(magic) && stream.ReadString().Length > 3;

protected override void Read(Stream stream)
{
Expand Down
146 changes: 94 additions & 52 deletions lib/AuroraLip/Texture/Formats/PTLG.cs
Expand Up @@ -25,75 +25,118 @@ public PTLG(string filepath) : base(filepath)
}

public bool IsMatch(Stream stream, in string extension = "")
=> stream.MatchString(magic);
=> stream.MatchString(magic) || (extension == string.Empty && stream.At(0x10, s => s.MatchString(magic)));

protected override void Read(Stream stream)
{
if (!stream.MatchString(magic))
throw new InvalidIdentifierException(Magic);
{
stream.Seek(0x10, SeekOrigin.Begin);
if (!stream.MatchString(magic))
throw new InvalidIdentifierException(Magic);
}

uint numTextures = stream.ReadUInt32(Endian.Big);
uint unk = stream.ReadUInt32(Endian.Big);
uint hash = stream.ReadUInt32(Endian.Big); // GC 0 Wii != 0
uint padding = stream.ReadUInt32(Endian.Big);

uint Off = stream.ReadUInt32(Endian.Big);

PTLGType type = Off == 0 ? PTLGType.GC : hash == 0 ? PTLGType.GCCompact : PTLGType.Wii;
if (Off == 0)
stream.Seek(12, SeekOrigin.Current); //GC
stream.Seek(12, SeekOrigin.Current);
else
stream.Seek(-4, SeekOrigin.Current); //wii
stream.Seek(-4, SeekOrigin.Current);

Entry[] Entrys = stream.For((int)numTextures, s => s.Read<Entry>(Endian.Big));
long startPos = stream.Position;

List<PTLGEntry> Entries = new List<PTLGEntry>();
for (int i = 0; i < numTextures; i++)
{
Entries.Add(new PTLGEntry(stream));
stream.Seek(startPos + Entrys[i].ImageOffset, SeekOrigin.Begin);
if (ReadTexture(stream, Entrys[i].SectionSize, out TexEntry current, Entrys[i].Flag))
{
Add(current);
}
}
}

public static bool ReadTexture(Stream stream, uint size, out TexEntry texture, uint flag = 0)
{
long endPos = stream.Position + size;

long startPos = stream.Position; //1008
uint Images = stream.ReadUInt32(Endian.Big);

for (int i = 0; i < numTextures; i++)
if (Images > 0x10)
{
stream.Seek(startPos + Entries[i].ImageOffset, SeekOrigin.Begin);
//1313621792 "NLG " Font Description file
//1600939625 "_lfi" maybe a pallete or other game data?
//2142000 TPL

long pos = stream.Position;
if (Images == 2142000)
{
List<TexEntry> entries = new();
TPL.ProcessStream(stream, stream.Position - 4, entries);

uint Images = stream.ReadUInt32(Endian.Big);
uint unknown2 = stream.ReadUInt32(Endian.Big); //1 3 2 8 ||Unknown ==12 2439336753|1267784150|3988437873
byte unknown4 = (byte)stream.ReadByte(); //5 0 8
PTLGImageFormat PTLGFormat = (PTLGImageFormat)stream.ReadByte();
GXImageFormat Format = (GXImageFormat)Enum.Parse(typeof(GXImageFormat), PTLGFormat.ToString());
byte unknown5 = (byte)stream.ReadByte(); //5 0 8
byte unknown6 = (byte)stream.ReadByte(); //3 4 0 2 6 8
ushort ImageWidth = stream.ReadUInt16(Endian.Big);//GC
texture = entries.First();
return true;
}

if (ImageWidth == 0)
ImageWidth = stream.ReadUInt16(Endian.Big); //wii
ushort ImageHeight = stream.ReadUInt16(Endian.Big);
texture = null;
return false;
}

ushort unknown7 = stream.ReadUInt16(Endian.Big);//0
uint unknown8 = stream.ReadUInt32(Endian.Big); //256
uint unknown9 = stream.ReadUInt32(Endian.Big);//is 65535 Unknown ==12
uint unknown10 = stream.ReadUInt32(Endian.Big);//0
uint Format0 = stream.ReadUInt32(Endian.Big); //RGB5A3 1 CMPR 2 RGBA32 3 C8 8
byte Format1 = (byte)stream.ReadByte(); //5 RGBA32 8
PTLGImageFormat PTLGFormat = (PTLGImageFormat)stream.ReadByte();
byte Format3 = (byte)stream.ReadByte(); //5 RGBA32 8
byte Format4 = (byte)stream.ReadByte(); //CMPR 0 RGB5A3 3 RGBA32 8 C8 0 || 1 || 4

//No a texture?
if (Entries[i].Unknown != 0) continue;
GXImageFormat Format = (GXImageFormat)Enum.Parse(typeof(GXImageFormat), PTLGFormat.ToString());
ReadOnlySpan<byte> Palette = ReadOnlySpan<byte>.Empty;
ushort ImageWidth, ImageHeight;
uint Collors = 0;

TexEntry current = new TexEntry(stream, null, Format, GXPaletteFormat.IA8, 0, ImageWidth, ImageHeight, (int)Images - 1)
{
LODBias = 0,
MagnificationFilter = GXFilterMode.Nearest,
MinificationFilter = GXFilterMode.Nearest,
WrapS = GXWrapMode.CLAMP,
WrapT = GXWrapMode.CLAMP,
EnableEdgeLOD = false,
MinLOD = 0,
MaxLOD = Images - 1
};
Add(current);

stream.Seek(pos + Entries[i].SectionSize, SeekOrigin.Begin);
ImageWidth = stream.ReadUInt16(Endian.Big);
if (ImageWidth == 0)
{
ImageWidth = stream.ReadUInt16(Endian.Big);
ImageHeight = stream.ReadUInt16(Endian.Big);

ushort pad1 = stream.ReadUInt16(Endian.Big);
Collors = stream.ReadUInt32(Endian.Big);
}
else
{
ImageHeight = stream.ReadUInt16(Endian.Big);
}

if (Collors != 0)
{
Format = GXImageFormat.C8;
Palette = stream.At(endPos - Collors * 2, SeekOrigin.Begin, s => s.Read((int)Collors * 2));
}

//The image files are aligned from end.
int imageSize = Format.GetCalculatedTotalDataSize(ImageWidth, ImageHeight, (int)Images - 1);
stream.Seek(endPos - Collors * 2 - imageSize, SeekOrigin.Begin);

texture = new(stream, Palette, Format, GXPaletteFormat.RGB5A3, (int)Collors, ImageWidth, ImageHeight, (int)Images - 1)
{
LODBias = 0,
MagnificationFilter = GXFilterMode.Nearest,
MinificationFilter = GXFilterMode.Nearest,
WrapS = GXWrapMode.CLAMP,
WrapT = GXWrapMode.CLAMP,
EnableEdgeLOD = false,
MinLOD = 0,
MaxLOD = Images - 1
};

return true;
}


protected override void Write(Stream stream)
{
throw new NotImplementedException();
Expand All @@ -104,26 +147,25 @@ public enum PTLGImageFormat : byte
I4 = 0x02,
I8 = 0x03,
IA4 = 0x04,
RGB5A3 = 0x05,
RGB5A3 = 0x05, // or C8
CMPR = 0x06,
RGB565 = 0x07,
RGBA32 = 0x08
}

public class PTLGEntry
public struct Entry
{
public uint Hash;
public uint ImageOffset;
public uint SectionSize;
public uint Unknown; //0 or 12
public uint Flag; //0 or 12
}

public PTLGEntry(Stream stream)
{
Hash = stream.ReadUInt32(Endian.Big);
ImageOffset = stream.ReadUInt32(Endian.Big);
SectionSize = stream.ReadUInt32(Endian.Big);
Unknown = stream.ReadUInt32(Endian.Big);
}
public enum PTLGType
{
Wii,
GC,
GCCompact
}
}
}
68 changes: 68 additions & 0 deletions lib/AuroraLip/Texture/Formats/RES_NLG.cs
@@ -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);
}
}
}
}
19 changes: 15 additions & 4 deletions lib/AuroraLip/Texture/Formats/TPL_0.cs
Expand Up @@ -16,10 +16,21 @@ public bool IsMatch(Stream stream, in string extension = "")

public static bool Matcher(Stream stream, in string extension = "")
{
if (extension.ToLower().Equals(".tpl") && stream.ReadUInt32(Endian.Big) >= 1)
uint ImageCount = stream.ReadUInt32(Endian.Big);
if (extension.ToLower().Equals(".tpl") && ImageCount >= 1)
{
Entry entry = stream.At(4, S => S.Read<Entry>(Endian.Big));
return Enum.IsDefined(typeof(GXImageFormat), entry.Format) && entry.ImageOffset > 10 && entry.Width > 2 && entry.Height > 2 && entry.Width < 1024 && entry.Height < 1024 && entry.MaxLOD != 0;
for (int i = 0; i < ImageCount; i++)
{
Entry entry = stream.Read<Entry>(Endian.Big);
if (Enum.IsDefined(typeof(GXImageFormat), entry.Format) && entry.ImageOffset > 10 && entry.Width > 2 && entry.Height > 2 && entry.Width < 1024 && entry.Height < 1024 && entry.MaxLOD != 0)
{
return true;
}
else if (entry.ImageOffset != 0 || entry.Width != 0 || entry.Height != 0)
{
break;
}
}
}
return false;
}
Expand All @@ -36,7 +47,7 @@ protected override void Read(Stream stream)

stream.Seek(entry.ImageOffset, SeekOrigin.Begin);

TexEntry current = new TexEntry(stream, null, entry.Format, GXPaletteFormat.IA8, 16, entry.Width, entry.Height, entry.MaxLOD - 1)
TexEntry current = new(stream, null, entry.Format, GXPaletteFormat.IA8, 16, entry.Width, entry.Height, entry.MaxLOD - 1)
{
LODBias = 0,
MagnificationFilter = GXFilterMode.Linear,
Expand Down
2 changes: 1 addition & 1 deletion lib/AuroraLip/Texture/Formats/TXE.cs
Expand Up @@ -64,7 +64,7 @@ protected override void Read(Stream stream)
stream.Seek(16, SeekOrigin.Current);
DataSize = stream.ReadInt32(Endian.Big);
}
int Mipmaps = Format.GetMipmapsFromSize(DataSize, ImageWidth, ImageHeight);
int Mipmaps = DataSize == 0 ? 0 : Format.GetMipmapsFromSize(DataSize, ImageWidth, ImageHeight);

TexEntry current = new TexEntry(stream, null, Format, GXPaletteFormat.IA8, 0, ImageWidth, ImageHeight, Mipmaps)
{
Expand Down
67 changes: 67 additions & 0 deletions lib/AuroraLip/Texture/Formats/text.cs
@@ -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;
}
}
}
}
10 changes: 9 additions & 1 deletion lib/AuroraLip/Texture/TexEntry.cs
Expand Up @@ -90,7 +90,15 @@ public TexEntry(Stream Stream, GXImageFormat Format, int ImageWidth, int ImageHe
//reads all row image data.
for (int i = 0; i <= Mipmap; i++)
{
RawImages.Add(Stream.Read(Format.CalculatedDataSize(ImageWidth, ImageHeight, i)));
if (ImageWidth == 0 || ImageHeight == 0)
{
Events.NotificationEvent.Invoke(NotificationType.Info, $"{Mipmap} Mips are too many, possible are {i - 1}.");
break;
}
RawImages.Add(Stream.Read(Format.CalculatedDataSize(ImageWidth, ImageHeight)));

ImageWidth >>= 1;
ImageHeight >>= 1;
}
Hash = HashDepot.XXHash.Hash64(RawImages[0]);
}
Expand Down