wah getting fucked up on endian business
This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
using fxl.codes.kisekae.data.Archives;
|
||||||
|
|
||||||
|
namespace fxl.codes.kisekae.data.test;
|
||||||
|
|
||||||
|
public class BitStreamTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestReadBasic()
|
||||||
|
{
|
||||||
|
var bytes = BitConverter.GetBytes(uint.MaxValue);
|
||||||
|
var stream = new MemoryStream(bytes);
|
||||||
|
|
||||||
|
var bitStream = new BitStream(stream);
|
||||||
|
var value = bitStream.ReadBits(8);
|
||||||
|
Assert.That(value, Is.EqualTo(byte.MaxValue), "8 bits match failed");
|
||||||
|
|
||||||
|
value = bitStream.ReadBits(24);
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(value, Is.EqualTo(Math.Pow(2, 24) - 1), "24 bits match failed");
|
||||||
|
Assert.That(bitStream.Position, Is.EqualTo(bitStream.Length), "Stream position is wrong");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReadRandom()
|
||||||
|
{
|
||||||
|
var bytes = BitConverter.GetBytes(uint.MaxValue);
|
||||||
|
var stream = new MemoryStream(bytes);
|
||||||
|
|
||||||
|
var bitStream = new BitStream(stream);
|
||||||
|
var list = new List<int>();
|
||||||
|
var sum = 0;
|
||||||
|
while (sum < 32)
|
||||||
|
{
|
||||||
|
var temp = Random.Shared.Next(1, 10);
|
||||||
|
if (sum + temp < 32) list.Add(temp);
|
||||||
|
sum += temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.Sum() < 32) list.Add(32 - list.Sum());
|
||||||
|
|
||||||
|
foreach (var num in list)
|
||||||
|
{
|
||||||
|
var value = bitStream.ReadBits(num);
|
||||||
|
Assert.That(value, Is.EqualTo(Math.Pow(2, num) - 1), $"{num} bits match failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReadLong()
|
||||||
|
{
|
||||||
|
var random = (ulong)Random.Shared.NextInt64();
|
||||||
|
var bytes = BitConverter.GetBytes(random);
|
||||||
|
var stream = new MemoryStream(bytes);
|
||||||
|
|
||||||
|
var bitStream = new BitStream(stream);
|
||||||
|
var list = new List<int>();
|
||||||
|
var sum = 0;
|
||||||
|
while (sum < 64)
|
||||||
|
{
|
||||||
|
var temp = Random.Shared.Next(1, 20);
|
||||||
|
if (sum + temp < 64) list.Add(temp);
|
||||||
|
sum += temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.Sum() < 64) list.Add(64 - list.Sum());
|
||||||
|
|
||||||
|
var runningTotal = 0;
|
||||||
|
foreach (var num in list)
|
||||||
|
{
|
||||||
|
var temp = random;
|
||||||
|
temp <<= runningTotal;
|
||||||
|
temp >>= 64 - num;
|
||||||
|
Console.WriteLine($"Original value: {random:b}, bit shifted number: {temp:b}");
|
||||||
|
var value = bitStream.ReadBits(num);
|
||||||
|
Assert.That(value, Is.EqualTo(temp), $"{num} bits match failed");
|
||||||
|
runningTotal += num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using fxl.codes.kisekae.data.Archives;
|
||||||
|
using fxl.codes.kisekae.data.Archives.Algorithms;
|
||||||
|
|
||||||
|
namespace fxl.codes.kisekae.data.test;
|
||||||
|
|
||||||
|
public class LZWTests
|
||||||
|
{
|
||||||
|
private readonly byte[] _encoded = new[] { 65, 66, 66, 256, 257, 259, 65 }.SelectMany(BitConverter.GetBytes).ToArray();
|
||||||
|
private readonly byte[] _unencoded = "ABBABBBABBA"u8.ToArray();
|
||||||
|
private LZW _lzw;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_lzw = new LZW();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEncodingAndDecodingBasic()
|
||||||
|
{
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(_lzw.Encode(_unencoded), Is.EqualTo(_encoded), "Encoding failed");
|
||||||
|
Assert.That(_lzw.Decode(_encoded), Is.EqualTo(_unencoded), "Decoding failed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecompressLzh()
|
||||||
|
{
|
||||||
|
var assembly = typeof(LZWTests).Assembly;
|
||||||
|
foreach (var resource in assembly.GetManifestResourceNames())
|
||||||
|
{
|
||||||
|
using var stream = assembly.GetManifestResourceStream(resource);
|
||||||
|
if (stream == null) continue;
|
||||||
|
|
||||||
|
var files = LhCodecService.GetFiles(in stream);
|
||||||
|
foreach (var file in files)
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(file.CompressedFile, Is.Not.Null);
|
||||||
|
Assert.That(file.CompressedFile, Is.Not.Empty);
|
||||||
|
|
||||||
|
var decompressed = _lzw.Decode(file.CompressedFile);
|
||||||
|
Assert.That(decompressed, Is.Not.Null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,9 +42,23 @@ public class LhContainerTests
|
|||||||
Assert.That(file.MethodId, Is.Not.Null, "Method id is {0}", file.MethodId);
|
Assert.That(file.MethodId, Is.Not.Null, "Method id is {0}", file.MethodId);
|
||||||
Assert.That(file.MethodId, Does.StartWith("-l"));
|
Assert.That(file.MethodId, Does.StartWith("-l"));
|
||||||
Assert.That(file.MethodId, Does.EndWith("-"));
|
Assert.That(file.MethodId, Does.EndWith("-"));
|
||||||
Console.WriteLine($"Archive name: {file.FileName}, compression method: {file.MethodId}");
|
Console.WriteLine($"Archive name: {file.FileName}, compression method: {file.MethodId}, file at {file.FileDataPosition}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBody()
|
||||||
|
{
|
||||||
|
foreach (var resource in _resourceNames)
|
||||||
|
{
|
||||||
|
using var stream = _assembly.GetManifestResourceStream(resource);
|
||||||
|
if (stream == null) continue;
|
||||||
|
|
||||||
|
var files = LhCodecService.GetFiles(in stream);
|
||||||
|
foreach (var file in files)
|
||||||
|
Assert.Multiple(() => { Assert.That(file.CompressedFile, Is.Not.Null); });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace fxl.codes.kisekae.data.Archives.Algorithms;
|
|
||||||
|
|
||||||
public class HuffmanDynamic : IAlgorithm
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
namespace fxl.codes.kisekae.data.Archives.Algorithms;
|
|
||||||
|
|
||||||
public class HuffmanNode
|
|
||||||
{
|
|
||||||
public HuffmanNode(char value)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public HuffmanNode(HuffmanNode left, HuffmanNode right)
|
|
||||||
{
|
|
||||||
Left = left;
|
|
||||||
Left.Side = HuffmanNodeSide.Left;
|
|
||||||
Left.Parent = this;
|
|
||||||
|
|
||||||
Right = right;
|
|
||||||
Right.Side = HuffmanNodeSide.Right;
|
|
||||||
Right.Parent = this;
|
|
||||||
|
|
||||||
Weight = left.Weight + right.Weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public char? Value { get; set; }
|
|
||||||
public int Weight { get; set; } = 1;
|
|
||||||
|
|
||||||
public HuffmanNode? Parent { get; set; }
|
|
||||||
public HuffmanNode? Left { get; set; }
|
|
||||||
public HuffmanNode? Right { get; set; }
|
|
||||||
public HuffmanNodeSide? Side { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum HuffmanNodeSide
|
|
||||||
{
|
|
||||||
Left,
|
|
||||||
Right
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace fxl.codes.kisekae.data.Archives.Algorithms;
|
|
||||||
|
|
||||||
public class HuffmanStatic : IAlgorithm
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -2,4 +2,6 @@ namespace fxl.codes.kisekae.data.Archives.Algorithms;
|
|||||||
|
|
||||||
public interface IAlgorithm
|
public interface IAlgorithm
|
||||||
{
|
{
|
||||||
|
byte[] Encode(ReadOnlySpan<byte> stream);
|
||||||
|
byte[] Decode(ReadOnlySpan<byte> stream);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace fxl.codes.kisekae.data.Archives.Algorithms;
|
||||||
|
|
||||||
|
internal class LZW : IAlgorithm
|
||||||
|
{
|
||||||
|
private readonly int Max;
|
||||||
|
|
||||||
|
internal LZW(int max = short.MaxValue)
|
||||||
|
{
|
||||||
|
Max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Decode(ReadOnlySpan<byte> stream)
|
||||||
|
{
|
||||||
|
uint next = byte.MaxValue + 1;
|
||||||
|
var dictionary = new Dictionary<uint, string>();
|
||||||
|
for (uint i = 0; i <= byte.MaxValue; i++) dictionary.Add(i, $"{(char)i}");
|
||||||
|
|
||||||
|
var buffer = "";
|
||||||
|
var decoded = new List<string>();
|
||||||
|
for (var i = 0; i < stream.Length; i += 4)
|
||||||
|
{
|
||||||
|
var code = BitConverter.ToUInt32(stream[i..(i + 4)]);
|
||||||
|
if (!dictionary.ContainsKey(code)) dictionary[code] = buffer + buffer[0];
|
||||||
|
|
||||||
|
decoded.Add(dictionary[code]);
|
||||||
|
if (buffer.Length > 0 && next <= Max) dictionary[next++] = buffer + dictionary[code][0];
|
||||||
|
|
||||||
|
buffer = dictionary[code];
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded.SelectMany(Encoding.UTF8.GetBytes).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Encode(ReadOnlySpan<byte> stream)
|
||||||
|
{
|
||||||
|
var next = byte.MaxValue + 1;
|
||||||
|
var dictionary = new Dictionary<string, int>();
|
||||||
|
for (var i = 0; i <= byte.MaxValue; i++) dictionary.Add($"{(char)i}", i);
|
||||||
|
|
||||||
|
var buffer = "";
|
||||||
|
var encoded = new List<int>();
|
||||||
|
foreach (var part in stream)
|
||||||
|
{
|
||||||
|
var charValue = (char)part;
|
||||||
|
buffer += charValue;
|
||||||
|
if (dictionary.ContainsKey(buffer)) continue;
|
||||||
|
if (next <= Max) dictionary.Add(buffer, next++);
|
||||||
|
encoded.Add(dictionary[$"{buffer[..^1]}"]);
|
||||||
|
buffer = $"{charValue}";
|
||||||
|
}
|
||||||
|
|
||||||
|
encoded.Add(dictionary[buffer]);
|
||||||
|
return encoded.SelectMany(BitConverter.GetBytes).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
namespace fxl.codes.kisekae.data.Archives;
|
||||||
|
|
||||||
|
public class BitStream : Stream
|
||||||
|
{
|
||||||
|
private readonly Stream _stream;
|
||||||
|
private uint _bitBuffer;
|
||||||
|
private int _bitBufferCount;
|
||||||
|
private long _length;
|
||||||
|
|
||||||
|
public BitStream(Stream stream)
|
||||||
|
{
|
||||||
|
_stream = stream;
|
||||||
|
#if DEBUG
|
||||||
|
var min = Math.Min(stream.Length, 4);
|
||||||
|
var bytes = new byte[min];
|
||||||
|
_stream.ReadExactly(bytes);
|
||||||
|
var temp = BitConverter.ToUInt32(bytes);
|
||||||
|
Console.WriteLine($"BitStream first 4 bytes: {temp:b}");
|
||||||
|
_stream.Position = 0;
|
||||||
|
#endif
|
||||||
|
_length = _stream.Length * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead => true;
|
||||||
|
public override bool CanSeek => true;
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
public override long Length => _length;
|
||||||
|
|
||||||
|
public override long Position { get; set; }
|
||||||
|
|
||||||
|
public uint ReadBits(int bitLength)
|
||||||
|
{
|
||||||
|
// Fill buffer
|
||||||
|
if (_bitBufferCount <= 24)
|
||||||
|
while (_bitBufferCount < 32 && _stream.Position < _stream.Length)
|
||||||
|
{
|
||||||
|
_bitBuffer <<= 8;
|
||||||
|
_bitBuffer |= (uint)_stream.ReadByte();
|
||||||
|
_bitBufferCount += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = _bitBuffer;
|
||||||
|
buffer >>= 32 - bitLength;
|
||||||
|
#if DEBUG
|
||||||
|
Console.WriteLine($"BitStream buffer: {_bitBuffer:b}, extracted number: {buffer:b}");
|
||||||
|
_stream.Position = 0;
|
||||||
|
#endif
|
||||||
|
_bitBuffer <<= bitLength;
|
||||||
|
Position += bitLength;
|
||||||
|
_bitBufferCount -= bitLength;
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
_length = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@ using fxl.codes.kisekae.data.Archives.Algorithms;
|
|||||||
|
|
||||||
namespace fxl.codes.kisekae.data.Archives.LhMethods;
|
namespace fxl.codes.kisekae.data.Archives.LhMethods;
|
||||||
|
|
||||||
internal abstract class BaseLhMethod(LhContainer container, int slidingWindowBytes, int matchingWindowBytes, IAlgorithm? algorithm)
|
internal abstract class BaseLhMethod(LhContainer container, int slidingWindowBytes, int matchingWindowBytes, IAlgorithm? algorithm = null)
|
||||||
{
|
{
|
||||||
|
internal static readonly IAlgorithm Lzw = new LZW();
|
||||||
public int SlidingWindow => slidingWindowBytes * 1024;
|
public int SlidingWindow => slidingWindowBytes * 1024;
|
||||||
|
|
||||||
public void InitializeAlgorithm()
|
public void InitializeAlgorithm()
|
||||||
@@ -11,22 +12,22 @@ internal abstract class BaseLhMethod(LhContainer container, int slidingWindowByt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Lzs(LhContainer container) : BaseLhMethod(container, 2, 17, null);
|
internal class Lzs(LhContainer container) : BaseLhMethod(container, 2, 17);
|
||||||
|
|
||||||
internal class Lz4(LhContainer container) : BaseLhMethod(container, 0, 0, null);
|
internal class Lz4(LhContainer container) : BaseLhMethod(container, 0, 0);
|
||||||
|
|
||||||
internal class Lh0(LhContainer container) : BaseLhMethod(container, 0, 0, null);
|
internal class Lh0(LhContainer container) : BaseLhMethod(container, 0, 0);
|
||||||
|
|
||||||
internal class Lh1(LhContainer container) : BaseLhMethod(container, 4, 60, new HuffmanDynamic());
|
internal class Lh1(LhContainer container) : BaseLhMethod(container, 4, 60, Lzw);
|
||||||
|
|
||||||
internal class Lh2(LhContainer container) : BaseLhMethod(container, 8, 256, new HuffmanDynamic());
|
internal class Lh2(LhContainer container) : BaseLhMethod(container, 8, 256, Lzw);
|
||||||
|
|
||||||
internal class Lh3(LhContainer container) : BaseLhMethod(container, 8, 256, new HuffmanStatic());
|
internal class Lh3(LhContainer container) : BaseLhMethod(container, 8, 256, Lzw);
|
||||||
|
|
||||||
internal class Lh4(LhContainer container) : BaseLhMethod(container, 4, 256, new HuffmanStatic());
|
internal class Lh4(LhContainer container) : BaseLhMethod(container, 4, 256, Lzw);
|
||||||
|
|
||||||
internal class Lh5(LhContainer container) : BaseLhMethod(container, 8, 256, new HuffmanStatic());
|
internal class Lh5(LhContainer container) : BaseLhMethod(container, 8, 256, Lzw);
|
||||||
|
|
||||||
internal class Lh6(LhContainer container) : BaseLhMethod(container, 32, 256, new HuffmanStatic());
|
internal class Lh6(LhContainer container) : BaseLhMethod(container, 32, 256, Lzw);
|
||||||
|
|
||||||
internal class Lh7(LhContainer container) : BaseLhMethod(container, 64, 256, new HuffmanStatic());
|
internal class Lh7(LhContainer container) : BaseLhMethod(container, 64, 256, Lzw);
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user