diff --git a/lib/AuroraLip/Compression/FlagReader.cs b/lib/AuroraLip/Compression/FlagReader.cs
new file mode 100644
index 00000000..7b33d984
--- /dev/null
+++ b/lib/AuroraLip/Compression/FlagReader.cs
@@ -0,0 +1,67 @@
+using AuroraLib.Common;
+
+namespace AuroraLip.Compression
+{
+ ///
+ /// Reads individual bits from a stream and provides methods for interpreting the flag values.
+ ///
+ 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;
+ }
+
+ ///
+ /// Reads a single bit from the stream.
+ ///
+ /// The value of the read bit.
+ 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;
+ }
+
+ ///
+ /// Reads an integer value with the specified number of bits from the stream.
+ ///
+ /// The number of bits to read.
+ /// The integer value read from the stream.
+ public int ReadInt(int bits = 1)
+ {
+ int vaule = 0;
+ for (int i = 0; i < bits; i++)
+ {
+ vaule <<= 1;
+ if (Readbit())
+ {
+ vaule |= 1;
+ }
+ }
+ return vaule;
+ }
+ }
+}
diff --git a/lib/AuroraLip/Compression/FlagWriter.cs b/lib/AuroraLip/Compression/FlagWriter.cs
new file mode 100644
index 00000000..5271d4d3
--- /dev/null
+++ b/lib/AuroraLip/Compression/FlagWriter.cs
@@ -0,0 +1,84 @@
+using AuroraLib.Common;
+
+namespace AuroraLib.Compression
+{
+ ///
+ /// Represents a flag writer used for compressing data. It provides methods to write individual bits.
+ ///
+ 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;
+ }
+
+ ///
+ /// Writes a single bit as a flag. The bits are accumulated in a byte and flushed to the destination stream when necessary.
+ ///
+ /// The bit value to write (true for 1, false for 0).
+ 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);
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The integer value to write.
+ /// The number of bits to write (default is 1).
+ public void WriteInt(int value, int bits = 1)
+ {
+ for (int i = bits - 1; i >= 0; i--)
+ {
+ int bit = (value >> i) & 1;
+ WriteBit(bit == 1);
+ }
+ }
+
+ ///
+ /// Flushes any remaining bits in the buffer to the underlying stream.
+ ///
+ public void Flush()
+ {
+ if (BitsLeft != 0)
+ {
+ Base.WriteByte(CurrentFlag);
+ BitsLeft = 0;
+ }
+ if (Buffer.Length != 0)
+ {
+ Buffer.WriteTo(Base);
+ Buffer.SetLength(0);
+ }
+ }
+ }
+}
diff --git a/lib/AuroraLip/Compression/Formats/PRS.cs b/lib/AuroraLip/Compression/Formats/PRS.cs
index 883ff5b1..6f3fca7e 100644
--- a/lib/AuroraLip/Compression/Formats/PRS.cs
+++ b/lib/AuroraLip/Compression/Formats/PRS.cs
@@ -1,4 +1,5 @@
using AuroraLib.Common;
+using AuroraLip.Compression;
namespace AuroraLib.Compression.Formats
{
@@ -12,45 +13,53 @@ namespace AuroraLib.Compression.Formats
///
/// The PRS compression algorithm is based on LZ77 with run-length encoding emulation and extended matches.
///
- 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)
{
- int bitPos = 9;
- byte currentByte;
+ return 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 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;
@@ -62,73 +71,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)
@@ -139,96 +122,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;
- }
}
}
diff --git a/lib/AuroraLip/Compression/Formats/PRS_BE.cs b/lib/AuroraLip/Compression/Formats/PRS_BE.cs
new file mode 100644
index 00000000..4978de37
--- /dev/null
+++ b/lib/AuroraLip/Compression/Formats/PRS_BE.cs
@@ -0,0 +1,25 @@
+using AuroraLib.Common;
+
+namespace AuroraLib.Compression.Formats
+{
+ ///
+ /// The PRS compression algorithm is based on LZ77 with run-length encoding emulation and extended matches.
+ ///
+ 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);
+ }
+}