-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Tales.Compression based off topdec
- Loading branch information
1 parent
63f8bbb
commit 89512b5
Showing
4 changed files
with
307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace HyoutaTools.Tales.Compression { | ||
public static class Decompression { | ||
// ported from https://github.com/AdmiralCurtiss/topdec | ||
|
||
private static void InitializeDictionary(byte[] dict) { | ||
int offset = 0; | ||
for (int i = 0; i < 0x100; ++i) { | ||
dict[offset++] = (byte)(i); | ||
dict[offset++] = (byte)(0); | ||
dict[offset++] = (byte)(i); | ||
dict[offset++] = (byte)(0); | ||
dict[offset++] = (byte)(i); | ||
dict[offset++] = (byte)(0); | ||
dict[offset++] = (byte)(i); | ||
dict[offset++] = (byte)(0); | ||
} | ||
for (int i = 0; i < 0x100; ++i) { | ||
dict[offset++] = (byte)(i); | ||
dict[offset++] = (byte)(0xff); | ||
dict[offset++] = (byte)(i); | ||
dict[offset++] = (byte)(0xff); | ||
dict[offset++] = (byte)(i); | ||
dict[offset++] = (byte)(0xff); | ||
dict[offset++] = (byte)(i); | ||
} | ||
for (int i = 0; i < 0x100; ++i) { | ||
dict[offset++] = 0; | ||
} | ||
} | ||
|
||
public static int decompress_reserve_extra_bytes() { | ||
return 273; | ||
} | ||
|
||
private static long decompress_internal(bool HasDict, | ||
bool HasMultiByte, | ||
System.IO.Stream compressed, | ||
uint compressedLength, | ||
System.IO.Stream uncompressed, | ||
uint uncompressedLength) { | ||
byte[] dict = new byte[0x1000]; | ||
ulong inpos = 0; | ||
ulong outpos = 0; | ||
uint dictpos = 0; | ||
|
||
if (HasDict) { | ||
InitializeDictionary(dict); | ||
dictpos = HasMultiByte ? 0xfefu : 0xfeeu; | ||
} | ||
|
||
int literalBits = 0; | ||
while (true) { | ||
if (outpos >= uncompressedLength) { | ||
return (long)outpos; | ||
} | ||
if (inpos >= compressedLength) { | ||
return -1; | ||
} | ||
|
||
int isLiteralByte = (literalBits & 1); | ||
literalBits = (literalBits >> 1); | ||
if (literalBits == 0) { | ||
literalBits = (byte)(compressed.ReadByte()); | ||
++inpos; | ||
isLiteralByte = (literalBits & 1); | ||
literalBits = (0x80 | (literalBits >> 1)); | ||
} | ||
if (isLiteralByte != 0) { | ||
byte c = (byte)(compressed.ReadByte()); | ||
uncompressed.WriteByte(c); | ||
dict[dictpos] = c; | ||
dictpos = (dictpos + 1u) & 0xfffu; | ||
++inpos; | ||
++outpos; | ||
continue; | ||
} | ||
|
||
if ((inpos + 1) >= compressedLength) { | ||
return -1; | ||
} | ||
|
||
byte bnext = (byte)(compressed.ReadByte()); | ||
byte b = (byte)(compressed.ReadByte()); | ||
byte blow = (byte)(b & 0xf); | ||
byte bhigh = (byte)((b & 0xf0) >> 4); | ||
byte nibble1 = HasDict ? blow : bhigh; | ||
byte nibble2 = HasDict ? bhigh : blow; | ||
if (HasMultiByte && (nibble1 == 0xf)) { | ||
// multiple copies of the same byte | ||
|
||
if (nibble2 == 0) { | ||
if ((inpos + 2) >= compressedLength) { | ||
return -1; | ||
} | ||
|
||
// 19 to 274 bytes | ||
ulong count = (ulong)((byte)(bnext)) + 19; | ||
byte c = (byte)(compressed.ReadByte()); | ||
for (ulong i = 0; i < count; ++i) { | ||
uncompressed.WriteByte(c); | ||
dict[dictpos] = c; | ||
dictpos = (dictpos + 1u) & 0xfffu; | ||
++outpos; | ||
} | ||
inpos += 3; | ||
} else { | ||
// 4 to 18 bytes | ||
ulong count = (ulong)(nibble2) + 3; | ||
byte c = bnext; | ||
for (ulong i = 0; i < count; ++i) { | ||
uncompressed.WriteByte(c); | ||
dict[dictpos] = c; | ||
dictpos = (dictpos + 1u) & 0xfffu; | ||
++outpos; | ||
} | ||
inpos += 2; | ||
} | ||
} else { | ||
ushort offset = (ushort)((ushort)((byte)(bnext)) | ((ushort)(nibble2) << 8)); | ||
ulong count = (ushort)(nibble1) + 3u; | ||
|
||
if (HasDict) { | ||
// reference into dictionary | ||
for (ulong i = 0; i < count; ++i) { | ||
byte c = dict[(offset + i) & 0xfffu]; | ||
uncompressed.WriteByte(c); | ||
dict[dictpos] = c; | ||
dictpos = (dictpos + 1u) & 0xfffu; | ||
++outpos; | ||
} | ||
} else { | ||
// backref into decompressed data | ||
if (offset == 0) { | ||
// the game just reads the unwritten output buffer and copies it over itself inpos | ||
// this case... while I suppose one *could* use this behavior inpos a really | ||
// creative way by pre-initializing the output buffer to something known, I | ||
// doubt it actually does that. so consider this a corrupted data stream. | ||
return -1; | ||
} | ||
if (outpos < offset) { | ||
// backref to before start of uncompressed data. this is invalid. | ||
return -1; | ||
} | ||
|
||
for (ulong i = 0; i < count; ++i) { | ||
byte c = dict[(outpos - offset) & 0xfffu]; | ||
uncompressed.WriteByte(c); | ||
dict[dictpos] = c; | ||
dictpos = (dictpos + 1u) & 0xfffu; | ||
++outpos; | ||
} | ||
} | ||
|
||
inpos += 2; | ||
} | ||
} | ||
} | ||
|
||
public static long decompress_81(System.IO.Stream compressed, uint compressedLength, System.IO.Stream uncompressed, uint uncompressedLength) { | ||
return decompress_internal(false, false, compressed, compressedLength, uncompressed, uncompressedLength); | ||
} | ||
|
||
public static long decompress_83(System.IO.Stream compressed, uint compressedLength, System.IO.Stream uncompressed, uint uncompressedLength) { | ||
return decompress_internal(false, true, compressed, compressedLength, uncompressed, uncompressedLength); | ||
} | ||
|
||
public static long decompress_01(System.IO.Stream compressed, uint compressedLength, System.IO.Stream uncompressed, uint uncompressedLength) { | ||
return decompress_internal(true, false, compressed, compressedLength, uncompressed, uncompressedLength); | ||
} | ||
|
||
public static long decompress_03(System.IO.Stream compressed, uint compressedLength, System.IO.Stream uncompressed, uint uncompressedLength) { | ||
return decompress_internal(true, true, compressed, compressedLength, uncompressed, uncompressedLength); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
using HyoutaPluginBase; | ||
using HyoutaUtils; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Numerics; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace HyoutaTools.Tales.Compression { | ||
internal class Decompressor : HyoutaPluginBase.IDecompressor { | ||
public CanDecompressAnswer CanDecompress(DuplicatableStream stream) { | ||
long pos = stream.Position; | ||
try { | ||
int b0 = stream.ReadByte(); | ||
if (b0 == 0x00 || b0 == 0x01 || b0 == 0x03 || b0 == 0x81 || b0 == 0x83) { | ||
uint compressed = stream.ReadUInt32(); | ||
uint uncompressed = stream.ReadUInt32(); | ||
|
||
if (b0 == 0) { | ||
return (compressed == uncompressed && compressed + 9 == stream.Length) ? CanDecompressAnswer.Yes : CanDecompressAnswer.No; | ||
} else { | ||
return (compressed + 9 == stream.Length) ? CanDecompressAnswer.Yes : CanDecompressAnswer.No; | ||
} | ||
} | ||
|
||
return CanDecompressAnswer.No; | ||
} finally { | ||
stream.Position = pos; | ||
} | ||
} | ||
|
||
// returns negative on failure, or number of bytes written to output on success | ||
public static long DecompressToStream(DuplicatableStream input, Stream output) { | ||
int b0 = input.ReadByte(); | ||
uint compressed = input.ReadUInt32(); | ||
uint uncompressed = input.ReadUInt32(); | ||
long result = -1; | ||
if (b0 == 0x00 && compressed == uncompressed) { | ||
StreamUtils.CopyStream(input, output, compressed); | ||
result = compressed; | ||
} else if (b0 == 0x01) { | ||
result = Decompression.decompress_01(input, compressed, output, uncompressed); | ||
} else if (b0 == 0x03) { | ||
result = Decompression.decompress_03(input, compressed, output, uncompressed); | ||
} else if (b0 == 0x81) { | ||
result = Decompression.decompress_81(input, compressed, output, uncompressed); | ||
} else if (b0 == 0x83) { | ||
result = Decompression.decompress_83(input, compressed, output, uncompressed); | ||
} | ||
return result; | ||
} | ||
|
||
public DuplicatableStream Decompress(DuplicatableStream input) { | ||
using (MemoryStream ms = new MemoryStream()) { | ||
input.Position = 0; | ||
long result = DecompressToStream(input, ms); | ||
if (result < 0 || result > ms.Length) { | ||
return null; | ||
} | ||
|
||
ms.Position = 0; | ||
byte[] data = new byte[(int)result]; | ||
ms.Read(data, 0, (int)result); | ||
return new HyoutaUtils.Streams.DuplicatableByteArrayStream(data); | ||
} | ||
} | ||
|
||
public string GetId() { | ||
return "compto"; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace HyoutaTools.Tales.Compression { | ||
public class Program { | ||
public static void PrintDecompressUsage() { | ||
Console.WriteLine("Usage: compressed.bin decompressed.bin"); | ||
} | ||
|
||
public static int Decompress(List<string> args) { | ||
string inpath = null; | ||
string outpath = null; | ||
|
||
try { | ||
for (int i = 0; i < args.Count; ++i) { | ||
switch (args[i]) { | ||
default: | ||
if (inpath == null) { inpath = args[i]; } else if (outpath == null) { outpath = args[i]; } else { PrintDecompressUsage(); return -1; } | ||
break; | ||
} | ||
} | ||
} catch (IndexOutOfRangeException) { | ||
PrintDecompressUsage(); | ||
return -1; | ||
} | ||
|
||
if (inpath == null) { | ||
PrintDecompressUsage(); | ||
return -1; | ||
} | ||
|
||
if (outpath == null) { | ||
outpath = inpath + ".dec"; | ||
} | ||
|
||
using (var infile = new HyoutaUtils.Streams.DuplicatableFileStream(inpath)) | ||
using (var outfile = new FileStream(outpath, FileMode.Create)) { | ||
if (Decompressor.DecompressToStream(infile, outfile) < 0) { | ||
Console.WriteLine("decompression failure"); | ||
return -1; | ||
} | ||
} | ||
|
||
return 0; | ||
} | ||
} | ||
} |