Refactoring & Beginnings of Compression

Using linq to make some code cleaner; likely runs a little slower but
the tradeoff is good enough.
This commit is contained in:
Kurt
2015-05-23 02:12:10 -07:00
parent 943dec58e5
commit 2592b726c4
2 changed files with 110 additions and 86 deletions

View File

@@ -124,7 +124,7 @@ private void getScriptData()
{
byte[] ScriptData = data;
uint length = BitConverter.ToUInt32(ScriptData, 0);
RTB_MS.Text = Util.getHexString(ScriptData);
RTB_MS.Lines = Scripts.getHexLines(ScriptData, 8);
int start = BitConverter.ToInt32(ScriptData, 0xC);
@@ -145,7 +145,7 @@ private void getScriptData()
int diff = declength - decompressed.Length;
Array.Resize(ref decompressed, declength);
RTB_MSCMD.Text = Scripts.getu32line(decompressed);
RTB_MSCMD.Lines = Scripts.getHexLines(decompressed);
RTB_MSCMD.Width = RTB_MSCMD.Lines.Length < 20 ? 75 : 85;
if (diff > 20 || diff < -20)
RTB_MSCMD.Text = "DCMP FAIL";
@@ -194,7 +194,7 @@ private void getOWSData()
{
byte[] ScriptData = OWScriptData;
uint length = BitConverter.ToUInt32(ScriptData, 0);
RTB_S.Text = Util.getHexString(ScriptData);
RTB_S.Lines = Scripts.getHexLines(ScriptData, 8);
// RTB_S.Width = RTB_S.Text.Length < 25 * 3 * 16 ? 245 : 260;
int start = BitConverter.ToInt32(ScriptData, 0xC);
@@ -216,7 +216,7 @@ private void getOWSData()
int diff = declength - decompressed.Length;
Array.Resize(ref decompressed, declength);
RTB_OWSCMD.Text = Scripts.getu32line(decompressed);
RTB_OWSCMD.Lines = Scripts.getHexLines(decompressed);
RTB_OWSCMD.Width = RTB_OWSCMD.Lines.Length < 20 ? 75 : 85;
if (diff > 20 || diff < -20)
RTB_OWSCMD.Text = "DCMP FAIL";

View File

@@ -1,116 +1,140 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;
namespace pk3DS
{
// Big thanks to FireFly for figuring out the 7-bit compression routine for scripts.
// Big thanks to FireFly for figuring out the 7/6-bit compression routine for scripts.
class Scripts
{
// Decompression
internal static byte[] decompressScript(byte[] data)
{
using (BinaryReader br = new BinaryReader(new MemoryStream(data)))
if (data == null || data.Length % 4 != 0) // Bad Input
return null;
using (MemoryStream mn = new MemoryStream())
using (BinaryWriter bw = new BinaryWriter(mn))
{
// Read away!
while (br.BaseStream.Position < br.BaseStream.Length)
int pos = 0;
while (pos < data.Length)
{
// Read until the top bit is not set
List<byte>raw = new List<byte>();
byte val = br.ReadByte(); raw.Add(val);
while ((val >> 7) > 0)
{
if (br.BaseStream.Position == br.BaseStream.Length) return mn.ToArray();
val = br.ReadByte(); raw.Add(val);
}
byte[] compressedBytes = raw.ToArray();
byte[] cb = readCompressed(data, pos);
pos += cb.Length;
// Interpret the bytecode
val = compressedBytes[0];
if ((val & 0x40) > 0) // Signed Parameter
{
// Check the next bytecode
if (compressedBytes.Length > 1 && (compressedBytes[1] >> 7) > 0) // Many-bits-required command
{
// 2 Byte Signed Parameter
int cmd = (compressedBytes[0] & 0x3 << 14) | (compressedBytes[1] & 0x7F << 7) | compressedBytes[2]; // 16 Bits total
bw.Write(BitConverter.GetBytes(cmd), 0, 2);
int deviation = ((val & 0x3F) - 0x40) >> 2; // Lowest 2 bits have already been used for the command
bw.Write(BitConverter.GetBytes(deviation), 0, 2); // 16 Bits
}
else if ((val >> 7) > 0)
{
// 3 Byte Signed Parameter
int cmd = ((compressedBytes[0] << 7) | compressedBytes[1]);
bw.Write(BitConverter.GetBytes(cmd), 0, 1); // 8 Bits Total
int deviation = ((val & 0x3F) - 0x40) >> 1; // Lowest bit has already been used for the command
bw.Write(BitConverter.GetBytes(deviation), 0, 3); // 24 Bits
}
else
{
// 4 Byte Signed Parameter
int deviation = ((val & 0x3F) - 0x40) >> 0; // No bits have already been used; no command
bw.Write(BitConverter.GetBytes(deviation), 0, 4); // 32 Bits
}
}
else if ((val >> 7) > 0) // Manybit
{
Array.Reverse(compressedBytes);
int cmd = 0;
for (int i = 0; i < compressedBytes.Length; i++)
{
cmd |= ((compressedBytes[i] & 0x7F) << (7*i));
}
bw.Write(BitConverter.GetBytes((uint)cmd), 0, 4);
}
else // Literal
{
bw.Write(BitConverter.GetBytes((uint)val), 0, 4);
}
uint result = BitConverter.ToUInt32(mn.GetBuffer(), (int) bw.BaseStream.Position - 4);
byte[] db = decompressBytes(cb);
// Write Bytes
bw.Write(db);
}
return mn.ToArray();
}
}
internal static byte[] readCompressed(byte[] data, int pos)
{
byte[] c1 = data.Skip(pos).TakeWhile(b => b >> 7 > 0).ToArray(); // Take while >= 0x80
return c1.Concat(data.Skip(pos + c1.Count()).Take(1)).ToArray(); // Take another
}
internal static byte[] decompressBytes(byte[] cb)
{
byte[] db = new byte[0];
if ((cb[0] & 0x40) > 0) // Signed Parameter
{
// Check the next bytecode
if (cb.Length > 1 && (cb[1] >> 7) > 0) // Many-bits-required command
{
// 2 Byte Signed Parameter
int cmd = (cb[0] & 0x3 << 14) | (cb[1] & 0x7F << 7) | cb[2]; // 16 Bits total
db = db.Concat(BitConverter.GetBytes(cmd).Take(2)).ToArray(); // 16 Bits
int dev = ((cb[0] & 0x3F) - 0x40) >> 2; // Lowest 2 bits have already been used for the command
db = db.Concat(BitConverter.GetBytes(dev).Take(2)).ToArray(); // 16 Bits
}
else if ((cb[0] >> 7) > 0) // Signed Command
{
// 3 Byte Signed Parameter
int cmd = ((cb[0] << 7) | cb[1]);
db = db.Concat(BitConverter.GetBytes(cmd).Take(1)).ToArray(); // 8 Bits Total
int dev = ((cb[0] & 0x3F) - 0x40) >> 1; // Lowest bit has already been used for the command
db = db.Concat(BitConverter.GetBytes(dev).Take(3)).ToArray(); // 24 Bits
}
else // Signed Value
{
// 4 Byte Signed Parameter
int dev = ((cb[0] & 0x3F) - 0x40) >> 0; // No bits have already been used; no command
db = db.Concat(BitConverter.GetBytes(dev).Take(4)).ToArray(); // 32 Bits
}
}
else if ((cb[0] >> 7) > 0) // Manybit
{
Array.Reverse(cb);
int cmd = 0;
for (int i = 0; i < cb.Length; i++)
cmd |= ((cb[i] & 0x7F) << (7 * i));
db = db.Concat(BitConverter.GetBytes((uint)cmd).Take(4)).ToArray();
}
else // Literal
{
db = db.Concat(BitConverter.GetBytes((uint)cb[0]).Take(4)).ToArray();
}
return db;
}
// Compression
internal static byte[] compressScript(byte[] data)
{
// Need decompression functional first.
using (BinaryReader br = new BinaryReader(new MemoryStream(data)))
if (data == null || data.Length % 4 != 0) // Bad Input
return null;
using (MemoryStream mn = new MemoryStream())
using (BinaryWriter bw = new BinaryWriter(mn))
{
// Compress away!
while (br.BaseStream.Position < br.BaseStream.Length)
int pos = 0;
while (pos < data.Length)
{
// Read the command to compress
uint val = br.ReadUInt32();
// Write byte if more bits are needed
while ((val >> 7) > 0) // 8th+ bit is set
{
bw.Write((byte)((val & 0x7F) | 0x80));
val >>= 7;
}
// Write the remaining bits
bw.Write((byte)(val & 0x7F));
byte[] db = data.Skip(pos+=4).Take(4).ToArray();
byte[] cb = compressBytes(db);
bw.Write(cb);
}
return mn.ToArray();
}
}
internal static string getu32line(byte[] data)
internal static byte[] compressBytes(byte[] cb)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.Length; i+= 4)
sb.Append(BitConverter.ToString(data, i, 4).Replace('-', ' ') + "\r\n");
return sb.ToString();
short cmd = BitConverter.ToInt16(cb, 0);
short val = BitConverter.ToInt16(cb, 2);
byte[] blank = new byte[0];
bool manyb = cmd > 0x3F; // manybit
bool sign4 = cmd < 0 && !manyb; // 4 byte signed
bool sign3 = cmd < 0 && manyb; // 3 byte signed
bool sign2 = val < 0; // 2 byte signed
bool liter = cmd >= 0 && cmd < 0x40;
if (sign4)
return blank;
if (sign3)
return blank;
if (sign2)
return blank;
if (manyb)
return blank;
if (liter)
return blank;
return blank;
}
// General Viewing Utility
internal static string[] getHexLines(byte[] data, int count = 4)
{
// Generates an x-byte wide space separated string array; leftovers included at the end.
string[] s = new string[data.Length/count + ((data.Length % count > 0) ? 1 : 0)];
for (int i = 0; i < s.Length;i++)
s[i] += BitConverter.ToString(data, i*count, i+1<s.Length ? count : data.Length % count).Replace('-', ' ');
return s;
}
}
}