Skip to content

Instantly share code, notes, and snippets.

@wildbook
Last active December 24, 2018 00:19
Show Gist options
  • Save wildbook/a6375a179d3ad1994af93927e2c20b5b to your computer and use it in GitHub Desktop.
Save wildbook/a6375a179d3ad1994af93927e2c20b5b to your computer and use it in GitHub Desktop.
using System;
namespace BDODecryptFramework
{
public static class BdoDecryptCores
{
public static int LolReplayCore(byte[] source, int startPos, out byte[] destination, int decompressedSize)
{
destination = new byte[decompressedSize];
var index = startPos;
var dIndex = 0;
var blockGroupHeader = 1u;
while (true)
{
// The last bit of BlockGroupHeader is 1
// If there's only one 1 left, we want to load the next BlockGroupHeader
if (blockGroupHeader == 1)
{
// Read next BlockGroupHeader
blockGroupHeader = BitConverter.ToUInt32(source, index);
index += 4;
}
if ((blockGroupHeader & 1) == 0)
{
// If there's less than 10 bytes left, we're finished with
// the while loop, which is most of the decryption/decompression.
if (dIndex > decompressedSize - 10)
break;
// Copy 4 bytes without moving index
for (var i = 0; i < 4; i++)
destination[dIndex + i] = source[index + i];
// Skip until there's either a bit set, or until we've skipped 4 times
for (var step = 0; step < 4; step++)
{
// Abort if there's a bit set
if ((blockGroupHeader & 1) != 0)
break;
// Skip one byte in both source and destination
index++;
dIndex++;
// Shift group header one bit right
blockGroupHeader >>= 1;
}
}
else
{
// Move header one step, as we just parsed the current bit
blockGroupHeader >>= 1;
// Read 4 bytes
// This could be done by using PeekChar instead to avoid
// rolling back the bytes we're not using, but this avoids the issue
// where there is no way to read 3 bytes to one value without reading
// to an array and converting to a number from there.
//
// (Also, I'm very very tired.)
// TODO: Do it the right way instead, preventing issues if there's ever not 4 bytes available
var blockHeader = BitConverter.ToUInt32(source, index);
// Read first 2 bits
var headerType = blockHeader & 0b0011;
// We just read 2 bits, so move 2 bits forward
blockHeader >>= 2;
uint blockLength;
uint repeatIndex;
switch (headerType)
{
// Size is 8 bits (1 byte)
// HeaderType = 2
// BlockLength = 0
// RepeatIndex = 6
case 0:
// Header size is 1 byte (+ sometimes 1 more later, but we take care of that then)
index += 1;
// BlockLength = 0
blockLength = 0;
// The next 6 bits are RepeatIndex (1 byte - 2 bits for headerType)
repeatIndex = blockHeader & 0b0011_1111;
break;
// Size is 24 / 32 bits (3 / 4 bytes)
// Note: This
// HeaderType = 2
// BlockLength = 5 (+ 8)
// RepeatIndex = 17
//
// Note: If 5 bits isn't enough for BlockLength, an
// extra byte is read and parsed as size + 1
case 3:
// Header size is 3 byte (+ sometimes 1 more later, but we take care of that then)
index += 3;
// BlockLength = first 5 bits
blockLength = blockHeader & 0b0001_1111;
// We just read 5 bits, so move 5 bits forward
blockHeader >>= 5;
// If size == 0, size exceeds 5 bits
if (blockLength == 0)
{
// Read BlockLength as a single byte
blockLength = (blockHeader & 0b1111_1111);
// We just read an extra byte, so we move index and blockHeader again
index += 1;
blockHeader >>= 8;
}
else
{
// Because length == 0 means "too large" there
// has to be a way to specify length as 0 as well.
// We solve this by adding one to length, meaning
// length = 0 is stored as length = 1 instead.
// Because of this, we need to -1 here to get the correct length.
blockLength--;
}
// The next 17 bits are RepeatIndex
repeatIndex = blockHeader & 0b0001_1111_1111_1111_1111;
break;
// Size is 16 bits (2 bytes)
// HeaderType = 2
// BlockLength = 4
// RepeatIndex = 10
case 2:
// Header size is 2 byte
index += 2;
// BlockLength = first 4 bits
blockLength = (blockHeader & 0b1111);
// Advance 4 bits
blockHeader >>= 4;
// The next 10 bits are RepeatIndex
repeatIndex = blockHeader & 0b0011_1111_1111;
break;
// Size is 16 bits (2 bytes)
// HeaderType = 2
// BlockLength = 0
// RepeatIndex = 14
case 1:
// Header size is 2 byte
index += 2;
// BlockLength = 0
blockLength = 0;
// The next 14 bits are RepeatIndex
repeatIndex = blockHeader & 0b0011_1111_1111_1111;
break;
default:
throw new NotSupportedException($"Header type {headerType} is not supported.");
}
blockLength += 3;
for (var currentByte = 0; currentByte < blockLength; currentByte += 3)
{
var dest = dIndex + currentByte;
var src = dest - (int)repeatIndex;
// Copy 4 bytes without moving index
for (var i = 0; i < 4; i++)
destination[dest + i] = destination[src + i];
}
// We just wrote blockLength bytes to output, so move the index as well
dIndex += (int)blockLength;
}
}
// If decompressedSize is larger than current output size, copy the last bytes
while (dIndex < decompressedSize)
{
if (blockGroupHeader == 1)
{
// Set header to "32 bits left"
blockGroupHeader = 0b1000_0000_0000_0000_0000_0000_0000_0000;
var hm = BitConverter.ToUInt32(source, index);
// Skip actual header?
index += 4;
}
// Copy one byte over to the end of the output array
destination[dIndex++] = source[index++];
// Move one step forward
blockGroupHeader >>= 1;
}
return decompressedSize;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment