96 changes: 96 additions & 0 deletions lib/AuroraLip/Archives/Formats/ShrekDir.cs
@@ -0,0 +1,96 @@
using AuroraLib.Archives;
using AuroraLib.Common;
using AuroraLip.Compression.Formats;

namespace AuroraLip.Archives.Formats
{
public class ShrekDir : Archive, IFileAccess
{

public bool CanRead => true;

public bool CanWrite => false;

public const string Extension = ".DIR";

public bool IsMatch(Stream stream, in string extension = "")
=> Matcher(stream, extension);

public static bool Matcher(Stream stream, in string extension = "")
{
if (extension == Extension)
{
Endian endian = stream.DetectByteOrder<uint>();
uint firstOffset = stream.ReadUInt32(endian);
stream.Position = firstOffset - 8;
uint lastOffset = stream.ReadUInt32(endian);
uint end = stream.ReadUInt32(endian); // 0
stream.Position = lastOffset;
Entry entry = new(stream, endian);
return stream.Position == stream.Length && end == 0;
}
return false;
}

protected override void Read(Stream stream)
{
//try to request an external file.
string datname = Path.ChangeExtension(Path.GetFileNameWithoutExtension(FullPath), ".DAT");
try
{
reference_stream = FileRequest.Invoke(datname);
}
catch (Exception)
{
throw new Exception($"{nameof(ShrekDir)}: could not request the file {datname}.");
}
Root = new ArchiveDirectory() { OwnerArchive = this };

Endian endian = stream.DetectByteOrder<uint>();
//Starts with a pointer list, last entry ends with 0x0
uint firstOffset = stream.ReadUInt32(endian);

uint files = (firstOffset - 8) / 4;
stream.Position = firstOffset;
for (int i = 0; i < files; i++)
{
Entry entry = new(stream, endian);
if (!Root.Items.ContainsKey(entry.Name))
{
MemoryStream decomp = new(Shrek.Decompress_ALG(reference_stream.At(entry.Offset, s => s.Read((int)entry.CompSize)), (int)entry.DecompSize).ToArray());
Root.AddArchiveFile(decomp, entry.Name);
}
}
}

protected override void Write(Stream stream) => throw new NotImplementedException();

private struct Entry
{
public uint Offset;
public uint DecompSize;
public uint CompSize;
public string Name;

public Entry(Stream stream, Endian endian)
{
Offset = stream.ReadUInt32(endian);
DecompSize = stream.ReadUInt32(endian);
CompSize = stream.ReadUInt32(endian);
Name = stream.ReadString();
stream.Align(4);
}
}

private Stream reference_stream;

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
reference_stream?.Dispose();
}
}
}
}
31 changes: 30 additions & 1 deletion lib/AuroraLip/Archives/Formats/bres.cs
Expand Up @@ -45,8 +45,26 @@ protected override void Read(Stream stream)
throw new InvalidIdentifierException("root");
uint RootSize = stream.ReadUInt32(ByteOrder);
Root = new ArchiveDirectory() { Name = "root", OwnerArchive = this };
ReadIndex(stream, (int)(stream.Position + RootSize - 8), Root);
//Index Group
ReadIndex(stream, (int)(stream.Position + RootSize - 8), Root);

//is brtex & brplt pair
if (Root.Items.Count == 1 && Root.ItemExists("Textures(NW4R)"))
{
//try to request an external file.
string datname = Path.ChangeExtension(Path.GetFileNameWithoutExtension(FullPath), ".brplt");
try
{
reference_stream = FileRequest.Invoke(datname);
Bres plt = new (reference_stream, FullPath);
foreach (var item in plt.Root.Items)
{
Root.Items.Add(item.Key, item.Value);
}
}
catch (Exception)
{ }
}
}

private void ReadIndex(Stream stream, in int EndOfRoot, ArchiveDirectory ParentDirectory)
Expand Down Expand Up @@ -118,6 +136,17 @@ protected override void Write(Stream ArchiveFile)
throw new NotImplementedException();
}

private Stream reference_stream;

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
reference_stream?.Dispose();
}
}

public unsafe struct Header
{
private fixed char magic[4];
Expand Down
2 changes: 1 addition & 1 deletion lib/AuroraLip/Common/Extensions/StreamEx.cs
Expand Up @@ -430,7 +430,7 @@ public static long Align(this Stream stream, long offset, SeekOrigin origin, int
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long Align(this Stream stream, int boundary = 32)
=> stream.Align(CalculatePadding(stream.Position, boundary), SeekOrigin.Begin);
=> stream.Seek(CalculatePadding(stream.Position, boundary), SeekOrigin.Begin);

/// <summary>
/// Writes padding bytes to the stream until its position aligns with the specified boundary.
Expand Down
11 changes: 9 additions & 2 deletions lib/AuroraLip/Common/FormatDictionary.cs
Expand Up @@ -64,15 +64,22 @@ static FormatDictionary()
{
try
{
file.Class = Reflection.FileAccess.GetByMagic(file.Header.Magic);
file.IsMatch = Reflection.FileAccess.GetInstance(file.Class).IsMatch;
if (file.Class == null)
{
file.Class = Reflection.FileAccess.GetByMagic(file.Header.Magic);
file.IsMatch = Reflection.FileAccess.GetInstance(file.Class).IsMatch;
}
}
catch (Exception) { }
if (file.Header.Magic.Length > 1)
Header.Add(file.Header.Magic, file);

Formats.Add(file);
}
else if (file.Class != null)
{
Formats.Add(file);
}
else
{
Exten.Add(file);
Expand Down
43 changes: 34 additions & 9 deletions lib/AuroraLip/Common/FormatDictionary_List.cs
Expand Up @@ -323,6 +323,7 @@ public static partial class FormatDictionary
new FormatInfo(".gvr", "GBIX", FormatType.Texture, "VR Texture", "SEGA"),
new FormatInfo(".gvr", "GCIX", FormatType.Texture, "VR Texture", "SEGA"),
new FormatInfo(".gvrt","GVRT", FormatType.Texture, "VR Texture", "SEGA"),
new FormatInfo(".pvr","PVRT", FormatType.Texture, "VR Texture", "SEGA"),
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
Expand All @@ -345,13 +346,13 @@ public static partial class FormatDictionary
new FormatInfo(".FREB","FREB", FormatType.Archive, "Crystal Bearers Archive", "Square Enix"),
new FormatInfo(".MPD", new byte[]{(byte)'M', (byte)'P', (byte)'D',0}, 0, FormatType.Unknown, "Crystal Bearers data", "Square Enix"),

//Tecmo & Grasshopper Manufacture
new FormatInfo(".RSL","RMHG", FormatType.Archive, "Fatal Frame Archive", "Tecmo"),
new FormatInfo(".bin","GCT0", FormatType.Texture, "Fatal Frame Texture", "Tecmo"),
new FormatInfo(".bin","CGMG", FormatType.Model, "Fatal Frame Model", "Tecmo"),
//Grasshopper Manufacture
new FormatInfo(".RSL","RMHG", FormatType.Archive, "Grasshopper Archive", "Grasshopper Manufacture"),
new FormatInfo(".bin","GCT0", FormatType.Texture, "Grasshopper Texture", "Grasshopper Manufacture"),
new FormatInfo(".bin","CGMG", FormatType.Model, "Grasshopper Model", "Grasshopper Manufacture"),

//Treasure
new FormatInfo(".RSC", FormatType.Archive, "Wario World archive", "Treasure"),
new FormatInfo(".RSC", FormatType.Archive, "Wario World archive", "Treasure") { Class = typeof(RSC), IsMatch = RSC.Matcher},
new FormatInfo(".arc", "NARC", FormatType.Archive, "Sin and Punishment archive", "Treasure"),
new FormatInfo(".nj", "NJTL", FormatType.Model, "Ninja Model", "Treasure"),//https://gitlab.com/dashgl/ikaruga/-/snippets/2046285

Expand Down Expand Up @@ -407,21 +408,43 @@ public static partial class FormatDictionary
new FormatInfo(".bin", FormatType.Archive, "Mario Party Archive", "Hudson Soft"){ Class = typeof(BIN_MP), IsMatch = BIN_MP.Matcher },
new FormatInfo(".hsf", "HSFV037" , FormatType.Texture,"Mario Party Model","Hudson Soft"),
new FormatInfo(".atb", FormatType.Texture,"Mario Party texture","Hudson Soft"){ Class = typeof(ATB), IsMatch = ATB.Matcher },
new FormatInfo(".h4m", "HVQM4 1.3" , FormatType.Video,"","Hudson Soft"),
new FormatInfo(".h4m", "HVQM4 1.4" , FormatType.Video,"","Hudson Soft"),
new FormatInfo(".h4m", "HVQM4 1.5", FormatType.Video,"","Hudson Soft"),
new FormatInfo(".h4m", "HVQM4" , FormatType.Video,"","Hudson Soft"),

//Radical Entertainment
new FormatInfo(".rcf", "ATG CORE CEMENT LIBRARY", FormatType.Archive,"","Radical Entertainment"),
new FormatInfo(".rcf", "RADCORE CEMENT LIBRARY", FormatType.Archive,"","Radical Entertainment"),
new FormatInfo(".p3d", "P3DZ", FormatType.Archive,"Pure3D file","Radical Entertainment"),

//Eurocom
new FormatInfo(".000", FormatType.Archive, "Eurocom Archive","Eurocom") {Class = typeof(Filelist)}, //https://github.com/eurotools/eurochef
new FormatInfo(".csb","MUSX", FormatType.Audio, "Eurocom Audio","Eurocom"),
new FormatInfo(".edb","GEOM", FormatType.Archive, "Eurocom","Eurocom"),

//EA
new FormatInfo(".viv","BIG4", FormatType.Audio,"BIG Audio","EA"),
new FormatInfo(".vpa","VPAK", FormatType.Unknown,"V Archive","EA"),
new FormatInfo(".vp6","MVhd", FormatType.Video,"VP6","EA"),
new FormatInfo(".moi","MOIR", FormatType.Unknown,"","EA"),
new FormatInfo(".abk","ABKC", FormatType.Audio,"audio bank","EA"),
new FormatInfo(".bnk","BNKb", FormatType.Audio,"audio","EA"),
new FormatInfo(".asf","SCHl", FormatType.Audio,"audio","EA"),
new FormatInfo(".loc","LOCH", FormatType.Unknown,"","EA"),
new FormatInfo(".pfd","PFDx", FormatType.Unknown,"","EA"),

//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(".fpk", 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
new FormatInfo(".gct","GCNT", FormatType.Texture, "GameCube Texture"), //http://wiki.xentax.com/index.php/GCT_Image

new FormatInfo(".DIR", FormatType.Archive, "Shrek SuperSlam Dir"){ Class = typeof(ShrekDir), IsMatch = ShrekDir.Matcher},
new FormatInfo(".texpack","TXPK", FormatType.Texture, "Shrek Texture"),

//Audio
new FormatInfo(".mul", FormatType.Audio),
Expand Down Expand Up @@ -449,13 +472,15 @@ public static partial class FormatDictionary
new FormatInfo(".bfn", "FONTbfn1", FormatType.Font),
new FormatInfo(".pkb", "RFNA", FormatType.Font),
//Model
new FormatInfo(".HGO","HGOF", FormatType.Model),
new FormatInfo(".CMDL", FormatType.Model),
new FormatInfo(".MREA", FormatType.Model, "Area"),
new FormatInfo(".fpc", FormatType.Model, "pac file container"),
//else
new FormatInfo(".pdf","%PDF",FormatType.Text,"Portable Document Format","Adobe Inc."),
new FormatInfo(".json",FormatType.Text,"JavaScript Object Notation"),
new FormatInfo(".py",FormatType.Skript,"Python Skript","Python Software"),
new FormatInfo(".ssf","SEC ",FormatType.Archive,"Deadly Alliance"),
new FormatInfo(".kxe","KXER",FormatType.Skript,"Kuribo Mod","riidefi"),
new FormatInfo(".pdb","BSJB",FormatType.Else,"PDB Symboles"),
new FormatInfo(".bat",FormatType.Skript,"Batch file"),
Expand Down
67 changes: 67 additions & 0 deletions lib/AuroraLip/Compression/FlagReader.cs
@@ -0,0 +1,67 @@
using AuroraLib.Common;

namespace AuroraLip.Compression
{
/// <summary>
/// Reads individual bits from a stream and provides methods for interpreting the flag values.
/// </summary>
public class FlagReader
{
private byte CurrentFlag;
public byte BitsLeft { get; private set; }
public readonly Stream Base;
public readonly Endian Order;

public FlagReader(Stream source, Endian order)
{
Base = source;
Order = order;
}

/// <summary>
/// Reads a single bit from the stream.
/// </summary>
/// <returns>The value of the read bit.</returns>
public bool Readbit()
{
if (BitsLeft == 0)
{
CurrentFlag = Base.ReadUInt8();
BitsLeft = 8;
}

bool flag;
if (Order == Endian.Little)
{
flag = (CurrentFlag & 1) == 1;
CurrentFlag >>= 1;
}
else
{
flag = (CurrentFlag & 128) == 128;
CurrentFlag <<= 1;
}
BitsLeft--;
return flag;
}

/// <summary>
/// Reads an integer value with the specified number of bits from the stream.
/// </summary>
/// <param name="bits">The number of bits to read.</param>
/// <returns>The integer value read from the stream.</returns>
public int ReadInt(int bits = 1)
{
int vaule = 0;
for (int i = 0; i < bits; i++)
{
vaule <<= 1;
if (Readbit())
{
vaule |= 1;
}
}
return vaule;
}
}
}
84 changes: 84 additions & 0 deletions lib/AuroraLip/Compression/FlagWriter.cs
@@ -0,0 +1,84 @@
using AuroraLib.Common;

namespace AuroraLib.Compression
{
/// <summary>
/// Represents a flag writer used for compressing data. It provides methods to write individual bits.
/// </summary>
public class FlagWriter
{
private byte CurrentFlag;
public byte BitsLeft { get; private set; }
public readonly Stream Base;
public readonly MemoryStream Buffer;
public readonly Endian Order;

public FlagWriter(Stream destination, MemoryStream buffer, Endian order)
{
Base = destination;
Order = order;
Buffer = buffer;
}

/// <summary>
/// Writes a single bit as a flag. The bits are accumulated in a byte and flushed to the destination stream when necessary.
/// </summary>
/// <param name="bit">The bit value to write (true for 1, false for 0).</param>
public void WriteBit(bool bit)
{
if (BitsLeft == 0)
{
CurrentFlag = 0;
BitsLeft = 8;
}

if (bit)
{
if (Order == Endian.Little)
CurrentFlag |= (byte)(1 << (8 - BitsLeft));
else
CurrentFlag |= (byte)(1 << (BitsLeft - 1));
}

BitsLeft--;

if (BitsLeft == 0)
{
Base.WriteByte(CurrentFlag);
Buffer.WriteTo(Base);
Buffer.SetLength(0);
}
}

/// <summary>
/// Writes an integer value as a sequence of bits with the specified number of bits. The bits are written from the most significant bit to the least significant bit.
/// </summary>
/// <param name="value">The integer value to write.</param>
/// <param name="bits">The number of bits to write (default is 1).</param>
public void WriteInt(int value, int bits = 1)
{
for (int i = bits - 1; i >= 0; i--)
{
int bit = (value >> i) & 1;
WriteBit(bit == 1);
}
}

/// <summary>
/// Flushes any remaining bits in the buffer to the underlying stream.
/// </summary>
public void Flush()
{
if (BitsLeft != 0)
{
Base.WriteByte(CurrentFlag);
BitsLeft = 0;
}
if (Buffer.Length != 0)
{
Buffer.WriteTo(Base);
Buffer.SetLength(0);
}
}
}
}
201 changes: 69 additions & 132 deletions lib/AuroraLip/Compression/Formats/PRS.cs
@@ -1,4 +1,5 @@
using AuroraLib.Common;
using AuroraLip.Compression;

namespace AuroraLib.Compression.Formats
{
Expand All @@ -12,45 +13,51 @@ namespace AuroraLib.Compression.Formats
/// <summary>
/// The PRS compression algorithm is based on LZ77 with run-length encoding emulation and extended matches.
/// </summary>
public class PRS : ICompression
public partial class PRS : ICompression
{
public bool CanRead => true;

public bool CanWrite => true;

public static Endian Order => Endian.Little;

public bool IsMatch(Stream stream, in string extension = "")
=> stream.Length > 3 && (stream.ReadByte() & 0x1) == 1 && stream.At(-2, SeekOrigin.End, S => S.ReadUInt16()) == 0;
=> stream.Length > 6 & stream.PeekByte() > 17 && (stream.ReadByte() & 0x1) == 1 && stream.At(-2, SeekOrigin.End, S => S.ReadUInt16()) == 0;

public byte[] Decompress(Stream source)
=> Decompress_ALG(source, Order);

public void Compress(in byte[] source, Stream destination)
=> Compress_ALG(source, destination, Order);

public static byte[] Decompress_ALG(Stream source, Endian order = Endian.Little)
{
int bitPos = 9;
byte currentByte;
int lookBehindOffset, lookBehindLength;

Stream destination = new MemoryStream();
FlagReader Flag = new(source, order);

currentByte = source.ReadUInt8();
while (true)
while (source.Position < source.Length)
{
if (GetControlBit(ref bitPos, ref currentByte, source) != 0)
if (Flag.Readbit()) // Uncompressed value
{
// Direct byte
destination.WriteByte(source.ReadUInt8());
continue;
}

if (GetControlBit(ref bitPos, ref currentByte, source) != 0)
if (Flag.Readbit()) // Compressed value
{
lookBehindOffset = source.ReadUInt8();
lookBehindOffset |= source.ReadUInt8() << 8;
if (lookBehindOffset == 0)
// Long
lookBehindOffset = source.ReadUInt16(order);

if (lookBehindOffset == 0 && source.Position < source.Length)
{
// End of the compressed data
break;
}

lookBehindLength = lookBehindOffset & 7;
lookBehindOffset = (lookBehindOffset >> 3) | -0x2000;
lookBehindOffset >>= 3;
lookBehindOffset |= -0x2000;
if (lookBehindLength == 0)
{
lookBehindLength = source.ReadUInt8() + 1;
Expand All @@ -62,73 +69,47 @@ public byte[] Decompress(Stream source)
}
else
{
lookBehindLength = 0;
lookBehindLength = (lookBehindLength << 1) | GetControlBit(ref bitPos, ref currentByte, source);
lookBehindLength = (lookBehindLength << 1) | GetControlBit(ref bitPos, ref currentByte, source);
lookBehindOffset = source.ReadUInt8() | -0x100;
lookBehindLength += 2;
// Short
lookBehindLength = Flag.ReadInt(2) + 2;
lookBehindOffset = source.ReadUInt8();
lookBehindOffset |= -0x100;
}

for (int i = 0; i < lookBehindLength; i++)
{
long writePosition = destination.Position;
destination.Seek(writePosition + lookBehindOffset, SeekOrigin.Begin);
byte b = destination.ReadUInt8();
destination.Seek(writePosition, SeekOrigin.Begin);
destination.WriteByte(b);
destination.WriteByte(destination.At(lookBehindOffset, SeekOrigin.Current, s => s.ReadUInt8()));
}
}

return destination.ToArray();
}

public void Compress(in byte[] Data, Stream destination)
public static void Compress_ALG(in byte[] source, Stream destination, Endian order = Endian.Little)
{
Stream source = new MemoryStream(Data);

// Get the source length
int sourceLength = (int)(source.Length - source.Position);

byte[] sourceArray = new byte[sourceLength];
var totalSourceBytesRead = 0;
int sourceBytesRead;
do
{
sourceBytesRead = source.Read(sourceArray, totalSourceBytesRead, sourceLength - totalSourceBytesRead);
if (sourceBytesRead == 0)
{
throw new IOException($"Unable to read all bytes in {nameof(source)}");
}
totalSourceBytesRead += sourceBytesRead;
}
while (totalSourceBytesRead < sourceLength);

byte bitPos = 0;
byte controlByte = 0;

int position = 0;
int currentLookBehindPosition, currentLookBehindLength;
int lookBehindOffset, lookBehindLength;

MemoryStream data = new MemoryStream();
MemoryStream buffer = new();
FlagWriter flagWriter = new(destination, buffer, order);

while (position < sourceLength)
while (position < source.Length)
{
currentLookBehindLength = 0;
lookBehindOffset = 0;
lookBehindLength = 0;

for (currentLookBehindPosition = position - 1; (currentLookBehindPosition >= 0) && (currentLookBehindPosition >= position - 0x1FF0) && (lookBehindLength < 256); currentLookBehindPosition--)
{
currentLookBehindLength = 1;
if (sourceArray[currentLookBehindPosition] == sourceArray[position])
if (source[currentLookBehindPosition] == source[position])
{
do
{
currentLookBehindLength++;
} while ((currentLookBehindLength <= 256) &&
(position + currentLookBehindLength <= sourceArray.Length) &&
sourceArray[currentLookBehindPosition + currentLookBehindLength - 1] == sourceArray[position + currentLookBehindLength - 1]);
}
while ((currentLookBehindLength <= 256) &&
(position + currentLookBehindLength <= source.Length) &&
source[currentLookBehindPosition + currentLookBehindLength - 1] == source[position + currentLookBehindLength - 1]);

currentLookBehindLength--;
if (((currentLookBehindLength >= 2 && currentLookBehindPosition - position >= -0x100) || currentLookBehindLength >= 3) && currentLookBehindLength > lookBehindLength)
Expand All @@ -139,96 +120,52 @@ public void Compress(in byte[] Data, Stream destination)
}
}

if (lookBehindLength == 0)
if (lookBehindLength == 0) // Uncompressed value
{
data.WriteByte(sourceArray[position++]);
PutControlBit(1, ref controlByte, ref bitPos, data, destination);
buffer.WriteByte(source[position++]);
flagWriter.WriteBit(true);
}
else
else // Compressed value
{
Copy(lookBehindOffset, lookBehindLength, ref controlByte, ref bitPos, data, destination);
position += lookBehindLength;
flagWriter.WriteBit(false);
if ((lookBehindOffset >= -0x100) && (lookBehindLength <= 5))
{
// Short
flagWriter.WriteBit(false);
lookBehindLength -= 2;
flagWriter.WriteBit(((lookBehindLength >> 1) & 1) == 1);
buffer.WriteByte((byte)(lookBehindOffset & 0xFF));
flagWriter.WriteBit((lookBehindLength & 1) == 1);
}
else
{
// Long
if (lookBehindLength <= 9)
{
lookBehindLength -= 2;
ushort value = (ushort)((lookBehindOffset << 3) | (lookBehindLength & 0x07));
buffer.Write(value, order);
}
else
{
ushort value = (ushort)((lookBehindOffset << 3));
buffer.Write(value, order);
buffer.WriteByte((byte)(lookBehindLength - 1));
}
flagWriter.WriteBit(true);
}
}
}

PutControlBit(0, ref controlByte, ref bitPos, data, destination);
PutControlBit(1, ref controlByte, ref bitPos, data, destination);
if (bitPos != 0)
{
controlByte = (byte)((controlByte << bitPos) >> 8);
Flush(ref controlByte, ref bitPos, data, destination);
}
flagWriter.WriteBit(false);
flagWriter.WriteBit(true);
flagWriter.Flush();

destination.WriteByte(0);
destination.WriteByte(0);
return;
}

private static void Copy(int offset, int size, ref byte controlByte, ref byte bitPos, MemoryStream data, Stream destination)
{
if ((offset >= -0x100) && (size <= 5))
{
size -= 2;
PutControlBit(0, ref controlByte, ref bitPos, data, destination);
PutControlBit(0, ref controlByte, ref bitPos, data, destination);
PutControlBit((size >> 1) & 1, ref controlByte, ref bitPos, data, destination);
data.WriteByte((byte)(offset & 0xFF));
PutControlBit(size & 1, ref controlByte, ref bitPos, data, destination);
}
else
{
if (size <= 9)
{
PutControlBit(0, ref controlByte, ref bitPos, data, destination);
data.WriteByte((byte)(((offset << 3) & 0xF8) | ((size - 2) & 0x07)));
data.WriteByte((byte)((offset >> 5) & 0xFF));
PutControlBit(1, ref controlByte, ref bitPos, data, destination);
}
else
{
PutControlBit(0, ref controlByte, ref bitPos, data, destination);
data.WriteByte((byte)((offset << 3) & 0xF8));
data.WriteByte((byte)((offset >> 5) & 0xFF));
data.WriteByte((byte)(size - 1));
PutControlBit(1, ref controlByte, ref bitPos, data, destination);
}
}
}

private static void PutControlBit(int bit, ref byte controlByte, ref byte bitPos, MemoryStream data, Stream destination)
{
controlByte >>= 1;
controlByte |= (byte)(bit << 7);
bitPos++;
if (bitPos >= 8)
{
Flush(ref controlByte, ref bitPos, data, destination);
}
}

private static void Flush(ref byte controlByte, ref byte bitPos, MemoryStream data, Stream destination)
{
destination.WriteByte(controlByte);
controlByte = 0;
bitPos = 0;

byte[] bytes = data.ToArray();
destination.Write(bytes, 0, bytes.Length);
data.SetLength(0);
}

private static int GetControlBit(ref int bitPos, ref byte currentByte, Stream source)
{
bitPos--;
if (bitPos == 0)
{
currentByte = source.ReadUInt8();
bitPos = 8;
}

int flag = currentByte & 1;
currentByte >>= 1;
return flag;
}
}
}
25 changes: 25 additions & 0 deletions lib/AuroraLip/Compression/Formats/PRS_BE.cs
@@ -0,0 +1,25 @@
using AuroraLib.Common;

namespace AuroraLib.Compression.Formats
{
/// <summary>
/// The PRS compression algorithm is based on LZ77 with run-length encoding emulation and extended matches.
/// </summary>
public partial class PRS_BE : ICompression
{
public bool CanRead => true;

public bool CanWrite => true;

public static Endian Order => Endian.Big;

public bool IsMatch(Stream stream, in string extension = "")
=> stream.Length > 6 && (stream.ReadByte() & 128) == 128 && stream.At(-2, SeekOrigin.End, S => S.ReadUInt16()) == 0;

public byte[] Decompress(Stream source)
=> PRS.Decompress_ALG(source, Order);

public void Compress(in byte[] source, Stream destination)
=> PRS.Compress_ALG(source, destination, Order);
}
}
149 changes: 149 additions & 0 deletions lib/AuroraLip/Compression/Formats/RefPack.cs
@@ -0,0 +1,149 @@
using AuroraLib.Common;
using AuroraLib.Compression;

namespace AuroraLip.Compression.Formats
{
/// <summary>
/// RefPack is an LZ77/LZSS compression format made by Frank Barchard of EA Canada
/// </summary>
public class RefPack : ICompression
{
public bool CanRead => true;

public bool CanWrite => false;

public byte[] Decompress(Stream source)
{
Header header = new(source);
return Decompress_ALG(source, header.UncompressedSize);
}

public void Compress(in byte[] source, Stream destination) => throw new NotImplementedException();

public bool IsMatch(Stream stream, in string extension = "")
{
Header header = new(stream);
return header.IsValid;
}

public static byte[] Decompress_ALG(Stream source, uint decomLength)
{
byte[] data = new byte[decomLength];
uint offset = 0;
while (true)
{
bool stop = false;
uint plainSize;
uint copySize = 0u;
uint copyOffset = 0u;

byte prefix = source.ReadUInt8();

if (prefix < 0x80)
{
byte data0 = source.ReadUInt8();

plainSize = (uint)(prefix & 0x03);
copySize = (uint)(((prefix & 0x1C) >> 2) + 3);
copyOffset = (uint)((((prefix & 0x60) << 3) | data0) + 1);
}
else if (prefix < 0xC0)
{
byte data0 = source.ReadUInt8();
byte data1 = source.ReadUInt8();

plainSize = (uint)(data0 >> 6);
copySize = (uint)((prefix & 0x3F) + 4);
copyOffset = (uint)((((data0 & 0x3F) << 8) | data1) + 1);
}
else if (prefix < 0xE0)
{
byte data0 = source.ReadUInt8();
byte data1 = source.ReadUInt8();
byte data2 = source.ReadUInt8();

plainSize = (uint)(prefix & 3);
copySize = (uint)((((prefix & 0x0C) << 6) | data2) + 5);
copyOffset = (uint)((((((prefix & 0x10) << 4) | data0) << 8) | data1) + 1);
}
else if (prefix < 0xFC)
{
plainSize = (uint)(((prefix & 0x1F) + 1) * 4);
}
else
{
plainSize = (uint)(prefix & 3);
stop = true;
}

if (plainSize > 0)
{
if (source.Read(data, (int)offset, (int)plainSize) != (int)plainSize)
{
throw new EndOfStreamException("could not read data");
}

offset += plainSize;
}

if (copySize > 0)
{
for (uint i = 0; i < copySize; i++)
{
data[offset + i] = data[(offset - copyOffset) + i];
}

offset += copySize;
}

if (stop)
{
return data;
}
}
}

public struct Header
{
public uint UncompressedSize;
public uint UncompressedSize2;

public bool IsValid => UncompressedSize != 0;
public bool HasMore => UncompressedSize2 != 0;
public bool IsLong => ((UncompressedSize2 | UncompressedSize) & 0xFF000000) != 0;

public Header(Stream stream)
{
UncompressedSize = UncompressedSize2 = 0;

Span<byte> Header = stackalloc byte[2];
stream.Read(Header);

if ((Header[0] & 0x3E) != 0x10 || (Header[1] != 0xFB))
{
return;
}
bool IsLong = ((Header[0] & 0x80) != 0);
bool hasMore = ((Header[0] & 0x01) != 0);

if (IsLong)
{
UncompressedSize = stream.ReadUInt32(Endian.Big);
if (hasMore)
{
UncompressedSize2 = stream.ReadUInt32(Endian.Big);
}
}
else
{
UncompressedSize = (uint)stream.ReadUInt24(Endian.Big);
if (hasMore)
{
UncompressedSize2 = (uint)stream.ReadUInt24(Endian.Big);
}
}
}
}

}
}
98 changes: 98 additions & 0 deletions lib/AuroraLip/Compression/Formats/Shrek.cs
@@ -0,0 +1,98 @@
using AuroraLib.Compression;

namespace AuroraLip.Compression.Formats
{
//base on https://github.com/ShrekBoards/shrek-superslam/blob/master/src/compression.rs
public class Shrek : ICompression
{
public bool CanRead => false;

public bool CanWrite => false;

private const int MAX_DISTANCE = 0x1011D;

public byte[] Decompress(Stream source)
{
throw new NotImplementedException();
}

public static byte[] Decompress_ALG(byte[] compressed, int decomLength)
{
List<byte> decompressed = new(decomLength);
int index = 0;

while (true)
{
int current = compressed[index++];
int length = (current & 7) + 1;
int distance = current >> 3;

if (distance == 0x1E)
{
current = compressed[index++];
distance = current + 0x1E;
}
else if (distance > 0x1E)
{
distance += compressed[index++];
current = compressed[index++];
distance += (current << 8) + 0xFF;
if (distance == MAX_DISTANCE)
{
length--;
}
}

if (distance != 0)
{
Span<byte> segment = compressed.AsSpan(index, distance);
decompressed.AddRange(segment.ToArray());
index += distance;
}

int bound = length;
for (int i = 0; i < bound; i++)
{
current = compressed[index++];
length = current & 7;
distance = current >> 3;

if (length == 0)
{
length = compressed[index++];
if (length == 0)
{
return decompressed.ToArray();
}
length += 7;
}

if (distance == 0x1E)
{
current = compressed[index++];
distance = current + 0x1E;
}
else if (distance > 0x1E)
{
current = compressed[index++];
distance += current;
current = compressed[index++];
distance += (current << 8) + 0xFF;
}

for (int j = 0; j < length; j++)
{
decompressed.Add(decompressed[decompressed.Count - 1 - distance]);
}
}
}
}


public void Compress(in byte[] source, Stream destination) => throw new NotImplementedException();

public bool IsMatch(Stream stream, in string extension = "")
=> false;
}

}
10 changes: 7 additions & 3 deletions lib/AuroraLip/Texture/Formats/TEX0.cs
Expand Up @@ -71,15 +71,15 @@ protected void Read(Stream stream, byte[] PaletteData, GXPaletteFormat PaletteFo
Archive ParentBres = substream.Parent.OwnerArchive;
if (ParentBres.ItemExists("Palettes(NW4R)"))
{
var PalletNames = ((ArchiveDirectory)ParentBres["Palettes(NW4R)"]).FindItems(name + "*");
List<string> PalletNames = ((ArchiveDirectory)ParentBres["Palettes(NW4R)"]).FindItems(name + "*");

if (PalletNames.Count == 0)
{
throw new PaletteException("No palette data could be found");
throw new PaletteException("No linked pallete palette data could be found");
}

stream.Position = SectionOffsets;
var tex = new TexEntry(stream, Format, ImageWidth, ImageHeight, TotalImageCount - 1)
TexEntry tex = new TexEntry(stream, Format, ImageWidth, ImageHeight, TotalImageCount - 1)
{
LODBias = 0,
MagnificationFilter = GXFilterMode.Nearest,
Expand All @@ -103,6 +103,10 @@ protected void Read(Stream stream, byte[] PaletteData, GXPaletteFormat PaletteFo
Add(tex);
return;
}
else
{
throw new PaletteException("No palette data could be found");
}
}
}
stream.Position = SectionOffsets;
Expand Down
4 changes: 4 additions & 0 deletions lib/AuroraLip/Texture/ImageEX.cs
Expand Up @@ -6,6 +6,7 @@
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using System.Drawing;
using System.Numerics;
using System.Reflection.PortableExecutable;

namespace AuroraLib.Texture
{
Expand Down Expand Up @@ -75,6 +76,9 @@ public static Image DecodeImage(this GXImageFormat format, ReadOnlySpan<byte> da

public static Image<XPixel> ApplyPalette<TPixel, XPixel>(this Image<TPixel> image, ReadOnlySpan<XPixel> palette, Converter<TPixel, int> pixelToIndex) where TPixel : unmanaged, IPixel<TPixel> where XPixel : unmanaged, IPixel<XPixel>
{
if (palette.Length == 0)
throw new PaletteException("No palette data to apply.");

Image<XPixel> paletteImage = new(image.Width, image.Height);

IMemoryGroup<XPixel> pixelsPalette = paletteImage.GetPixelMemoryGroup();
Expand Down