More documentation I don't want to lose and some code cleanup as I understand LZH better...er?

This commit is contained in:
Lani Aung
2024-09-22 16:22:04 -06:00
parent 3425a28bf7
commit d39e73a6fa
16 changed files with 5852 additions and 0 deletions
@@ -0,0 +1,5 @@
namespace fxl.codes.kisekae.data.Archives.Algorithms;
public class HuffmanDynamic : IAlgorithm
{
}
@@ -0,0 +1,5 @@
namespace fxl.codes.kisekae.data.Archives.Algorithms;
public class HuffmanStatic : IAlgorithm
{
}
@@ -0,0 +1,5 @@
namespace fxl.codes.kisekae.data.Archives.Algorithms;
public interface IAlgorithm
{
}
@@ -0,0 +1,58 @@
using System.Buffers.Binary;
namespace fxl.codes.kisekae.data.Archives;
internal static class EndianUtility
{
public static int ToLittleEndian(this ReadOnlySpan<byte> bytes)
{
var x = BitConverter.ToInt32(bytes);
return BitConverter.IsLittleEndian ? x : BinaryPrimitives.ReverseEndianness(x);
}
public static int ReadLittleEndian(this Stream stream, int length = 4)
{
var buffer = new byte[length];
stream.ReadExactly(buffer);
return ToLittleEndian(buffer);
}
public static async Task<int> ReadLittleEndianAsync(this Stream stream, int length = 4)
{
var buffer = new byte[length];
await stream.ReadExactlyAsync(buffer);
return ToLittleEndian(buffer);
}
public static DateTime ToDateTime(this ReadOnlySpan<byte> bytes)
{
var time = ToLittleEndian(bytes);
return ToDateTime(time);
}
private static DateTime ToDateTime(int time)
{
var year = ((time >> 25) & 0x7f) + 1980;
var month = (time >> 21) & 0x0f;
var day = (time >> 16) & 0x1f;
var hour = (time >> 11) & 0x1f;
var minute = (time >> 5) & 0x3f;
var second = (time & 0x1f) * 2;
if (month is < 1 or > 12 || day is < 1 or > 31 || hour >= 24 || minute >= 60 || second > 60) return DateTime.MinValue;
return new DateTime(year, month, day, hour, minute, second);
}
public static DateTime ReadLittleEndianDateTime(this Stream stream)
{
var time = ReadLittleEndian(stream);
return ToDateTime(time);
}
public static async Task<DateTime> ReadLittleEndianDateTimeAsync(this Stream stream)
{
var time = await ReadLittleEndianAsync(stream);
return ToDateTime(time);
}
}
@@ -0,0 +1,36 @@
using fxl.codes.kisekae.data.Archives.LhHeaders;
using fxl.codes.kisekae.data.Entities;
using Microsoft.Extensions.Logging;
namespace fxl.codes.kisekae.data.Archives;
public class LhCodecService(ILogger<LhCodecService> logger)
{
public async Task<Kisekae?> ReadArchive(Stream stream)
{
try
{
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;
}
}
@@ -0,0 +1,153 @@
using System.Text;
namespace fxl.codes.kisekae.data.Archives.LhHeaders;
internal class LhHeader
{
public LhHeader(ref readonly Stream stream)
{
var bytes = new byte[21];
stream.Position = 0;
stream.ReadExactly(bytes);
var span = bytes.AsSpan();
Level = span[20];
MethodId = Encoding.ASCII.GetString(span[2..7]);
CompressedSize = EndianUtility.ToLittleEndian(span[7..11]);
UncompressedSize = EndianUtility.ToLittleEndian(span[11..15]);
Created = EndianUtility.ToDateTime(span[15..19]);
FileOrDirectory = span[19];
switch (Level)
{
case 0:
Level0(span, in stream);
break;
case 1:
Level1(span, in stream);
break;
case 2:
Level2(span, in stream);
break;
default: throw new Exception("Invalid LH Header");
}
}
public int HeaderSize { get; private set; }
public int HeaderCheckSum { get; private set; }
public string MethodId { get; private set; }
public int CompressedSize { get; private set; }
public int UncompressedSize { get; private set; }
public DateTime Created { get; private set; }
public int FileOrDirectory { get; private set; }
public int Level { get; }
public short Crc16 { get; private set; }
public string? FileName { get; private set; }
public long FileDataPosition { get; private set; }
private void Level0(ReadOnlySpan<byte> span, ref readonly Stream stream)
{
HeaderSize = span[0];
HeaderCheckSum = span[1];
stream.Position = span.Length;
FileName = GetFilename(in stream);
Crc16 = (short)stream.ReadLittleEndian(2);
FileDataPosition = stream.Position;
}
private void Level1(ReadOnlySpan<byte> span, ref readonly Stream stream)
{
Level0(span, in stream);
OperatingSystem = (char)stream.ReadByte();
SetExtendedHeaders(in stream);
FileDataPosition = stream.Position;
}
private void Level2(ReadOnlySpan<byte> span, ref readonly Stream stream)
{
HeaderSize = span[..2].ToLittleEndian();
Crc16 = (short)stream.ReadLittleEndian(2);
OperatingSystem = (char)stream.ReadByte();
SetExtendedHeaders(in stream);
FileDataPosition = stream.Position;
}
private static string GetFilename(ref readonly Stream stream)
{
var length = stream.ReadByte();
var buffer = new byte[length];
stream.ReadExactly(buffer);
return Encoding.ASCII.GetString(buffer);
}
private void SetExtendedHeaders(ref readonly Stream stream)
{
var extendedHeaderSize = stream.ReadLittleEndian(2);
while (extendedHeaderSize != 0)
{
var type = stream.ReadByte();
var nextSize = stream.ReadLittleEndian(2);
switch (type)
{
case 0x00:
Crc16 = (short)stream.ReadLittleEndian(extendedHeaderSize);
break;
case 0x01:
FileName = GetString(in stream, extendedHeaderSize);
break;
case 0x02:
DirectoryName = GetString(in stream, extendedHeaderSize);
break;
case 0x3f:
Comment = GetString(in stream, extendedHeaderSize);
break;
case 0x50:
FilePermission = stream.ReadLittleEndian(extendedHeaderSize);
break;
case 0x51:
GroupId = stream.ReadLittleEndian(2);
UserId = stream.ReadLittleEndian(2);
break;
case 0x52:
GroupName = GetString(in stream, extendedHeaderSize);
break;
case 0x53:
UserName = GetString(in stream, extendedHeaderSize);
break;
case 0x54:
LastModified = stream.ReadLittleEndianDateTime();
break;
default: // Unknown, skip
stream.Position += extendedHeaderSize;
break;
}
extendedHeaderSize = nextSize;
}
}
private static string GetString(ref readonly Stream stream, int length)
{
var buffer = new byte[length];
stream.ReadExactly(buffer);
return Encoding.ASCII.GetString(buffer);
}
#region Extended Headers
public char? OperatingSystem { get; private set; }
public string? DirectoryName { get; private set; }
public string? Comment { get; private set; }
public int? FilePermission { get; private set; }
public int? GroupId { get; private set; }
public string? GroupName { get; private set; }
public int? UserId { get; private set; }
public string? UserName { get; private set; }
public DateTime? LastModified { get; private set; }
#endregion
}
@@ -0,0 +1,29 @@
using fxl.codes.kisekae.data.Archives.Algorithms;
using fxl.codes.kisekae.data.Archives.LhHeaders;
namespace fxl.codes.kisekae.data.Archives.LhMethods;
internal abstract class BaseLhMethod(ref readonly Stream stream, LhHeader header, int slidingWindowBytes, int matchingWindowBytes, IAlgorithm? algorithm)
{
public int SlidingWindow => slidingWindowBytes * 1024;
}
internal class Lzs(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 2, 17, null);
internal class Lz4(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 0, 0, null);
internal class Lh0(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 0, 0, null);
internal class Lh1(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 4, 60, new HuffmanDynamic());
internal class Lh2(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 8, 256, new HuffmanDynamic());
internal class Lh3(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 8, 256, new HuffmanStatic());
internal class Lh4(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 4, 256, new HuffmanStatic());
internal class Lh5(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 8, 256, new HuffmanStatic());
internal class Lh6(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 32, 256, new HuffmanStatic());
internal class Lh7(ref readonly Stream stream, LhHeader header) : BaseLhMethod(in stream, header, 64, 256, new HuffmanStatic());