This commit is contained in:
Lani Aung
2024-09-25 20:01:32 -06:00
parent d39e73a6fa
commit 94ab274246
10 changed files with 146 additions and 59 deletions
Binary file not shown.
@@ -0,0 +1,50 @@
using System.Reflection;
using fxl.codes.kisekae.data.Archives;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace fxl.codes.kisekae.data.test;
public class LhContainerTests
{
private readonly Assembly _assembly = typeof(LhContainerTests).Assembly;
private readonly List<string> _resourceNames = new();
private LhCodecService _service;
[SetUp]
public void Setup()
{
foreach (var name in _assembly.GetManifestResourceNames()) _resourceNames.Add(name);
var logger = new Logger<LhCodecService>(NullLoggerFactory.Instance);
_service = new LhCodecService(logger);
}
[Test]
public void TestHeader()
{
foreach (var resource in _resourceNames)
{
using var stream = _assembly.GetManifestResourceStream(resource);
if (stream == null) continue;
var files = LhCodecService.GetFiles(in stream);
Assert.Multiple(() =>
{
Assert.That(files, Is.Not.Empty);
foreach (var file in files)
{
Assert.That(file.FileName, Is.Not.Null, "Filename is {0}", file.FileName);
Assert.That(file.CompressedSize, Is.GreaterThan(0), "Compressed size is {0}", file.CompressedSize);
Assert.That(file.UncompressedSize, Is.GreaterThan(0), "Uncompressed size is {0}", file.UncompressedSize);
Assert.That(file.UncompressedSize, Is.GreaterThan(file.CompressedSize), "Uncompressed is larger than compressed");
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.EndWith("-"));
Console.WriteLine($"Archive name: {file.FileName}, compression method: {file.MethodId}");
}
});
}
}
}
@@ -1,15 +0,0 @@
namespace fxl.codes.kisekae.data.test;
public class LhMethodTests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
Assert.Pass();
}
}
@@ -23,4 +23,19 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Examples\"/>
</ItemGroup>
<ItemGroup>
<None Remove="Examples\2RANMAS.lzh"/>
<EmbeddedResource Include="Examples\2RANMAS.lzh">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\fxl.codes.kisekae.data\fxl.codes.kisekae.data.csproj"/>
</ItemGroup>
</Project> </Project>
@@ -0,0 +1,35 @@
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
}
@@ -6,7 +6,7 @@ internal static class EndianUtility
{ {
public static int ToLittleEndian(this ReadOnlySpan<byte> bytes) public static int ToLittleEndian(this ReadOnlySpan<byte> bytes)
{ {
var x = BitConverter.ToInt32(bytes); var x = bytes.Length == 2 ? BitConverter.ToInt16(bytes) : BitConverter.ToInt32(bytes);
return BitConverter.IsLittleEndian ? x : BinaryPrimitives.ReverseEndianness(x); return BitConverter.IsLittleEndian ? x : BinaryPrimitives.ReverseEndianness(x);
} }
@@ -1,4 +1,3 @@
using fxl.codes.kisekae.data.Archives.LhHeaders;
using fxl.codes.kisekae.data.Entities; using fxl.codes.kisekae.data.Entities;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -8,29 +7,13 @@ public class LhCodecService(ILogger<LhCodecService> logger)
{ {
public async Task<Kisekae?> ReadArchive(Stream stream) public async Task<Kisekae?> ReadArchive(Stream stream)
{ {
try throw new NotImplementedException();
{ }
var header = new LhHeader(in stream);
switch (header.MethodId)
{
case "-lh0-": break;
case "-lzs-": break;
case "-lz4-": break;
case "-lh1-": break;
case "-lh2-": break;
case "-lh3-": break;
case "-lh4-": break;
case "-lh5-": break;
case "-lh6-": break;
case "-lh7-": break;
default: throw new Exception("Unknown method");
}
}
catch (Exception e)
{
logger.LogError(e, "Unable to open archive");
}
return null; internal static IEnumerable<LhContainer> GetFiles(ref readonly Stream stream)
{
var files = new List<LhContainer>();
while (stream.Position + 21 < stream.Length) files.Add(new LhContainer(in stream));
return files;
} }
} }
@@ -1,19 +1,20 @@
using System.Text; using System.Text;
namespace fxl.codes.kisekae.data.Archives.LhHeaders; namespace fxl.codes.kisekae.data.Archives;
internal class LhHeader internal class LhContainer
{ {
public LhHeader(ref readonly Stream stream) public LhContainer(ref readonly Stream stream)
{ {
HeaderDataPosition = stream.Position;
var bytes = new byte[21]; var bytes = new byte[21];
stream.Position = 0;
stream.ReadExactly(bytes); stream.ReadExactly(bytes);
var span = bytes.AsSpan(); var span = bytes.AsSpan();
Level = span[20]; Level = span[20];
MethodId = Encoding.ASCII.GetString(span[2..7]); MethodId = Encoding.ASCII.GetString(span[2..7]);
CompressedSize = EndianUtility.ToLittleEndian(span[7..11]); CompressedSize = EndianUtility.ToLittleEndian(span[7..11]);
CompressedFile = new byte[CompressedSize];
UncompressedSize = EndianUtility.ToLittleEndian(span[11..15]); UncompressedSize = EndianUtility.ToLittleEndian(span[11..15]);
Created = EndianUtility.ToDateTime(span[15..19]); Created = EndianUtility.ToDateTime(span[15..19]);
FileOrDirectory = span[19]; FileOrDirectory = span[19];
@@ -36,7 +37,7 @@ internal class LhHeader
public int HeaderSize { get; private set; } public int HeaderSize { get; private set; }
public int HeaderCheckSum { get; private set; } public int HeaderCheckSum { get; private set; }
public string MethodId { get; private set; } public string MethodId { get; private set; }
public int CompressedSize { get; private set; } public int CompressedSize { get; }
public int UncompressedSize { get; private set; } public int UncompressedSize { get; private set; }
public DateTime Created { get; private set; } public DateTime Created { get; private set; }
public int FileOrDirectory { get; private set; } public int FileOrDirectory { get; private set; }
@@ -45,25 +46,33 @@ internal class LhHeader
public string? FileName { get; private set; } public string? FileName { get; private set; }
public long HeaderDataPosition { get; }
public long FileDataPosition { get; private set; } public long FileDataPosition { get; private set; }
public byte[] CompressedFile { get; }
private void Level0(ReadOnlySpan<byte> span, ref readonly Stream stream) private void Level0(ReadOnlySpan<byte> span, ref readonly Stream stream)
{ {
HeaderSize = span[0]; HeaderSize = span[0];
HeaderCheckSum = span[1]; HeaderCheckSum = span[1];
stream.Position = span.Length;
FileName = GetFilename(in stream); FileName = GetFilename(in stream);
Crc16 = (short)stream.ReadLittleEndian(2); Crc16 = (short)stream.ReadLittleEndian(2);
FileDataPosition = stream.Position; FileDataPosition = stream.Position;
stream.ReadExactly(CompressedFile);
} }
private void Level1(ReadOnlySpan<byte> span, ref readonly Stream stream) private void Level1(ReadOnlySpan<byte> span, ref readonly Stream stream)
{ {
Level0(span, in stream); HeaderSize = span[0];
HeaderCheckSum = span[1];
FileName = GetFilename(in stream);
Crc16 = (short)stream.ReadLittleEndian(2);
OperatingSystem = (char)stream.ReadByte(); OperatingSystem = (char)stream.ReadByte();
SetExtendedHeaders(in stream); SetExtendedHeaders(in stream);
FileDataPosition = stream.Position; FileDataPosition = stream.Position;
stream.ReadExactly(CompressedFile);
} }
private void Level2(ReadOnlySpan<byte> span, ref readonly Stream stream) private void Level2(ReadOnlySpan<byte> span, ref readonly Stream stream)
@@ -71,8 +80,11 @@ internal class LhHeader
HeaderSize = span[..2].ToLittleEndian(); HeaderSize = span[..2].ToLittleEndian();
Crc16 = (short)stream.ReadLittleEndian(2); Crc16 = (short)stream.ReadLittleEndian(2);
OperatingSystem = (char)stream.ReadByte(); OperatingSystem = (char)stream.ReadByte();
SetExtendedHeaders(in stream); SetExtendedHeaders(in stream);
FileDataPosition = stream.Position; FileDataPosition = stream.Position;
stream.ReadExactly(CompressedFile);
} }
private static string GetFilename(ref readonly Stream stream) private static string GetFilename(ref readonly Stream stream)
@@ -1,29 +1,32 @@
using fxl.codes.kisekae.data.Archives.Algorithms; using fxl.codes.kisekae.data.Archives.Algorithms;
using fxl.codes.kisekae.data.Archives.LhHeaders;
namespace fxl.codes.kisekae.data.Archives.LhMethods; namespace fxl.codes.kisekae.data.Archives.LhMethods;
internal abstract class BaseLhMethod(ref readonly Stream stream, LhHeader header, int slidingWindowBytes, int matchingWindowBytes, IAlgorithm? algorithm) internal abstract class BaseLhMethod(LhContainer container, int slidingWindowBytes, int matchingWindowBytes, IAlgorithm? algorithm)
{ {
public int SlidingWindow => slidingWindowBytes * 1024; public int SlidingWindow => slidingWindowBytes * 1024;
public void InitializeAlgorithm()
{
}
} }
internal class Lzs(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 2, 17, null); internal class Lzs(LhContainer container) : BaseLhMethod(container, 2, 17, null);
internal class Lz4(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 0, 0, null); internal class Lz4(LhContainer container) : BaseLhMethod(container, 0, 0, null);
internal class Lh0(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 0, 0, null); internal class Lh0(LhContainer container) : BaseLhMethod(container, 0, 0, null);
internal class Lh1(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 4, 60, new HuffmanDynamic()); internal class Lh1(LhContainer container) : BaseLhMethod(container, 4, 60, new HuffmanDynamic());
internal class Lh2(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 8, 256, new HuffmanDynamic()); internal class Lh2(LhContainer container) : BaseLhMethod(container, 8, 256, new HuffmanDynamic());
internal class Lh3(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 8, 256, new HuffmanStatic()); internal class Lh3(LhContainer container) : BaseLhMethod(container, 8, 256, new HuffmanStatic());
internal class Lh4(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 4, 256, new HuffmanStatic()); internal class Lh4(LhContainer container) : BaseLhMethod(container, 4, 256, new HuffmanStatic());
internal class Lh5(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 8, 256, new HuffmanStatic()); internal class Lh5(LhContainer container) : BaseLhMethod(container, 8, 256, new HuffmanStatic());
internal class Lh6(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 32, 256, new HuffmanStatic()); internal class Lh6(LhContainer container) : BaseLhMethod(container, 32, 256, new HuffmanStatic());
internal class Lh7(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 64, 256, new HuffmanStatic()); internal class Lh7(LhContainer container) : BaseLhMethod(container, 64, 256, new HuffmanStatic());
@@ -19,4 +19,8 @@
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4"/> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4"/>
</ItemGroup> </ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="fxl.codes.kisekae.data.test"/>
</ItemGroup>
</Project> </Project>