Skip to content

Commit

Permalink
Set up a new type for decompressing LZ10/LZ11 streams.
Browse files Browse the repository at this point in the history
  • Loading branch information
MeltyPlayer committed Dec 1, 2024
1 parent 089c1d4 commit 09d20d4
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 12 deletions.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace fin.decompression;

public interface IArrayDecompressor {
public interface IArrayToArrayDecompressor {
bool TryDecompress(byte[] src, out byte[] dst);

byte[] Decompress(byte[] src);
}

public abstract class BArrayDecompressor : IArrayDecompressor {
public abstract class BArrayToArrayDecompressor : IArrayToArrayDecompressor {
public abstract bool TryDecompress(byte[] src, out byte[] dst);

public byte[] Decompress(byte[] src) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;

using schema.binary;

namespace fin.decompression;

public interface IBinaryReaderToArrayDecompressor {
bool TryDecompress(IBinaryReader br, out byte[] dst);
byte[] Decompress(IBinaryReader br);
}

public abstract class BBinaryReaderToArrayDecompressor
: IBinaryReaderToArrayDecompressor {
public abstract bool TryDecompress(IBinaryReader br, out byte[] dst);

public byte[] Decompress(IBinaryReader br) {
if (this.TryDecompress(br, out byte[] dst)) {
return dst;
}

throw new Exception("Failed to decompress bytes.");
}
}
179 changes: 179 additions & 0 deletions FinModelUtility/Fin/Fin/src/decompression/Lz77Decompressor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Runtime.ConstrainedExecution;
using System.Text.RegularExpressions;

using fin.math;
using fin.schema;
using fin.util.asserts;
using fin.util.strings;

using schema.binary;

namespace fin.decompression;

/// <summary>
/// Shamelessly stolen from:
/// https://github.com/scurest/apicula/blob/3d4e91e14045392a49c89e86dab8cb936225588c/src/decompress/mod.rs
/// </summary>
public class Lz77Decompressor : BBinaryReaderToArrayDecompressor {
public override bool TryDecompress(IBinaryReader br, out byte[] data) {
br.AssertString("LZ77");

var compressionType = br.ReadByte();
switch (compressionType) {
case 0x10: {
data = Decompress10_(br);
return true;
}
case 0x11: {
data = Decompress11_(br);
return true;
}
}

data = default;
return false;
}

private static byte[] Decompress10_(IBinaryReader br) {
var decompressedSize = ReadDecompressedSize_(br);
var data = new List<byte>((int) decompressedSize);

while (data.Count < decompressedSize) {
var flags = br.ReadByte();

for (var i = 0; i < 8; ++i) {
var compressed = (flags & 0x80) != 0;
flags <<= 1;

if (!compressed) {
// Uncompressed byte
data.Add(br.ReadByte());
} else {
// LZ backreference
var ofsSub1And3 = br.ReadUInt16();
var ofsSub1 = ofsSub1And3.ExtractFromRight(0, 12);
var ofsSub3 = ofsSub1And3.ExtractFromRight(12, 4);

var ofs = ofsSub1 + 1;
var n = ofsSub3 + 3;

if (data.Count + n > decompressedSize) {
Asserts.Fail("Too much data!");
}

if (data.Count < ofs) {
Asserts.Fail("Not enough data!");
}

for (var ii = 0; ii < 8; ++ii) {
var x = data[data.Count - ofs];
data.Add(x);
}
}

if (data.Count >= decompressedSize) {
break;
}
}
}

return data.ToArray();
}

private static byte[] Decompress11_(IBinaryReader br) {
var decompressedSize = ReadDecompressedSize_(br);
var data = new List<byte>((int) decompressedSize);

while (data.Count < decompressedSize) {
var flags = br.ReadByte();
for (var i = 0; i < 8; ++i) {
var compressed = (flags & 0x80) != 0;
flags <<= 1;

if (!compressed) {
// Uncompressed byte
data.Add(br.ReadByte());
} else {
br.ReadByte().SplitNibbles(out var a, out var b);
var cd = br.ReadByte();

int n, ofs;
switch (a) {
case 0: {
// ab cd ef
// =>
// n = abc + 0x11 = bc + 0x11
// ofs = def + 1
cd.SplitNibbles(out var c, out var d);
var ef = br.ReadByte();

n = ((b << 4) | c) + 0x11;
ofs = ((d << 8) | ef) + 1;
break;
}
case 1: {
// ab cd ef gh
// =>
// n = bcde + 0x111
// ofs = fgh + 1
br.ReadByte().SplitNibbles(out var e, out var f);
var gh = br.ReadByte();

n = ((b << 12) | (cd << 4) | e) +
0x111;
ofs = ((f << 8) | gh) + 1;
break;
}
default: {
// ab cd
// =>
// n = a + 1
// ofs = bcd + 1
n = a + 1;
ofs = ((b << 8) | cd) + 1;
break;
}
}

if (data.Count + n > decompressedSize) {
Asserts.Fail("Too much data!");
}

if (data.Count < ofs) {
Asserts.Fail("Not enough data!");
}

for (var ii = 0; ii < n; ii++) {
var x = data[data.Count - ofs];
data.Add(x);
}
}

if (data.Count >= decompressedSize) {
break;
}
}
}

return data.ToArray();
}

private static uint ReadDecompressedSize_(IBinaryReader br) {
var decompressedSize = br.ReadUInt24();
if (decompressedSize == 0) {
decompressedSize = br.ReadUInt32();
}

if (decompressedSize < 40) {
Asserts.Fail($"LZ77 decompressed size is too small: {decompressedSize}");
}

if (decompressedSize > (1 << 19) * 4) {
Asserts.Fail($"LZ77 decompressed size is too big: {decompressedSize}");
}

return decompressedSize;
}
}
7 changes: 7 additions & 0 deletions FinModelUtility/Fin/Fin/src/math/BitLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ namespace fin.math;
public static class BitLogic {
private static readonly byte[] TEMP_ = new byte[4];

public static void SplitNibbles(this byte value,
out byte high,
out byte low) {
high = (byte) (value >> 4);
low = (byte) (value & 0xF);
}

public static uint ToUint32(byte a, byte b, byte c, byte d) {
TEMP_[0] = a;
TEMP_[1] = b;
Expand Down
6 changes: 3 additions & 3 deletions FinModelUtility/Formats/F3dzex2/io/IReadOnlyN64Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public interface IN64Memory : IReadOnlyN64Memory {
void AddSegment(uint segmentIndex,
uint offset,
uint length,
IArrayDecompressor? decompressor = null);
IArrayToArrayDecompressor? decompressor = null);

void AddSegment(uint segmentIndex, Segment segment);
}
Expand Down Expand Up @@ -128,7 +128,7 @@ public bool IsSegmentCompressed(uint segmentIndex)
public void AddSegment(uint segmentIndex,
uint offset,
uint length,
IArrayDecompressor? decompressor = null)
IArrayToArrayDecompressor? decompressor = null)
=> this.AddSegment(segmentIndex,
new Segment {
Offset = offset,
Expand Down Expand Up @@ -162,5 +162,5 @@ private bool TryToGetSegmentsAtSegmentedAddress_(
public readonly struct Segment {
public required uint Offset { get; init; }
public required uint Length { get; init; }
public IArrayDecompressor? Decompressor { get; init; }
public IArrayToArrayDecompressor? Decompressor { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

namespace level5.decompression;

public class Level5Decompressor : BArrayDecompressor {
public class Level5Decompressor : BIArrayToArrayDecompressor {

Check failure on line 5 in FinModelUtility/Formats/Level5/Level5/src/decompression/Level5Decompressor.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'BIArrayToArrayDecompressor' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 5 in FinModelUtility/Formats/Level5/Level5/src/decompression/Level5Decompressor.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'BIArrayToArrayDecompressor' could not be found (are you missing a using directive or an assembly reference?)
public override bool TryDecompress(byte[] src, out byte[] dst) {
int tableType = (src[0] & 0xFF);

DecompressionUtils.GetLengthAndType(src,
out _,
out var decompressionType);

if (new ZlibArrayDecompressor().TryDecompress(src, out dst)) {
if (new ZlibArrayToArrayDecompressor().TryDecompress(src, out dst)) {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace level5.decompression;

public class ZlibArrayDecompressor : BArrayDecompressor {
public class ZlibArrayToArrayDecompressor : BIArrayToArrayDecompressor {

Check failure on line 7 in FinModelUtility/Formats/Level5/Level5/src/decompression/ZlibDecompressor.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'BIArrayToArrayDecompressor' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 7 in FinModelUtility/Formats/Level5/Level5/src/decompression/ZlibDecompressor.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'BIArrayToArrayDecompressor' could not be found (are you missing a using directive or an assembly reference?)
public override bool TryDecompress(byte[] src, out byte[] dst) {
var b = src;
if (b.Length < 6) {
Expand Down
2 changes: 1 addition & 1 deletion FinModelUtility/Formats/Level5/Level5/src/schema/Xc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public void Read(IBinaryReader br) {
}

var inNameTable = br.SubreadAt(fileTableOffset, () => br.ReadBytes(filenameTableSize));
if (!new ZlibArrayDecompressor().TryDecompress(inNameTable, out var nameTable)) {
if (!new ZlibArrayToArrayDecompressor().TryDecompress(inNameTable, out var nameTable)) {
nameTable = new LzssDecompressor().Decompress(inNameTable);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public async Task ExtractAsync(IReadOnlyGenericFile strFile,
}
}

var refPackDecompressor = new RefPackArrayDecompressor();
var refPackDecompressor = new RefPackArrayToArrayDecompressor();
await Parallel.ForEachAsync(
headerBlocks,
new ParallelOptions { MaxDegreeOfParallelism = -1, },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

namespace visceral.decompression;

public class RefPackArrayDecompressor : BArrayDecompressor {
public class RefPackArrayToArrayDecompressor : BIArrayToArrayDecompressor {

Check failure on line 27 in FinModelUtility/Formats/Visceral/Visceral/src/decompression/RefPackDecompressor.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'BIArrayToArrayDecompressor' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in FinModelUtility/Formats/Visceral/Visceral/src/decompression/RefPackDecompressor.cs

View workflow job for this annotation

GitHub Actions / build

The type or namespace name 'BIArrayToArrayDecompressor' could not be found (are you missing a using directive or an assembly reference?)
public override bool TryDecompress(byte[] inData, out byte[] outData) {
using var input = new MemoryStream(inData);
Span<byte> dummy = stackalloc byte[4];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public bool IsSegmentCompressed(uint segmentIndex) {
public void AddSegment(uint segmentIndex,
uint offset,
uint length,
IArrayDecompressor? decompressor = null) {
IArrayToArrayDecompressor? decompressor = null) {
throw new NotImplementedException();
}

Expand Down

0 comments on commit 09d20d4

Please sign in to comment.