Last active
February 16, 2025 20:00
-
-
Save barncastle/a21b62df945445b38daf91ede021a3ec to your computer and use it in GitHub Desktop.
Code for decrypting Blue Mammoth Games' Brawlhalla's SWZ files
This file contains hidden or 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
// WEll512 implementation - https://gist.github.com/barncastle/0fb2279bdc337d2a7d951e1bd2e3c0df | |
using Ionic.Zlib; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Text; | |
static class BrawlhallaSWZ | |
{ | |
public static string[] Decrypt(Stream input, uint globalKey) | |
{ | |
var checksum = ReadUInt32BE(input); | |
var seed = ReadUInt32BE(input); | |
// initialise WELL512 | |
var rand = new WELL512(seed ^ globalKey); | |
// compute and compare the header checksum | |
// this also mixes the WELL512 state so is required | |
var hash = 0x2DF4A1CDu; | |
var hash_rounds = globalKey % 0x1F + 5; | |
for (var i = 0; i < hash_rounds; i++) | |
hash ^= rand.NextUInt(); | |
Debug.Assert(hash == checksum); | |
// decrypt each string object | |
var results = new List<string>(); | |
while (input.Position != input.Length) | |
{ | |
if (ReadStringEntry(input, rand, out var stringEntry)) | |
results.Add(stringEntry); | |
} | |
return results.ToArray(); | |
} | |
public static byte[] Encrypt(uint seed, uint globalKey, params string[] stringEntries) | |
{ | |
// initialise WELL512 | |
var rand = new WELL512(seed ^ globalKey); | |
// compute the header checksum | |
var hash = 0x2DF4A1CDu; | |
var hash_rounds = globalKey % 0x1F + 5; | |
for (var i = 0; i < hash_rounds; i++) | |
hash ^= rand.NextUInt(); | |
using var ms = new MemoryStream(0x1000); | |
WriteUInt32BE(ms, hash); | |
WriteUInt32BE(ms, seed); | |
foreach (var entry in stringEntries) | |
{ | |
var stringBytes = Encoding.UTF8.GetBytes(entry); | |
WriteStringEntry(stringBytes, rand, ms); | |
} | |
return ms.ToArray(); | |
} | |
private static bool ReadStringEntry(Stream input, WELL512 rand, out string result) | |
{ | |
// read the object header XOR'ing the size fields | |
var compressedSize = ReadUInt32BE(input) ^ rand.NextUInt(); | |
var decompressedSize = ReadUInt32BE(input) ^ rand.NextUInt(); | |
var checksum = ReadUInt32BE(input); | |
if (compressedSize + input.Position > input.Length) | |
{ | |
result = null; | |
return false; | |
} | |
// read the compressed data | |
var buffer = new byte[compressedSize]; | |
input.Read(buffer); | |
// again required even if not | |
// validating the checksum | |
var hash = rand.NextUInt(); | |
for (var i = 0; i < compressedSize; i++) | |
{ | |
// decode the byte | |
var shift = i & 0xF; | |
buffer[i] ^= (byte)(((0xFFu << shift) & rand.NextUInt()) >> shift); | |
// update the local checksum | |
hash = buffer[i] ^ RotateRight(hash, i % 7 + 1); | |
} | |
Debug.Assert(checksum == hash); | |
// zlib decompress | |
var decompressedData = ZlibStream.UncompressBuffer(buffer); | |
result = Encoding.UTF8.GetString(decompressedData); | |
return true; | |
} | |
private static void WriteStringEntry(byte[] input, WELL512 rand, Stream output) | |
{ | |
// zlib compress | |
var compressedInput = ZlibStream.CompressBuffer(input); | |
// calculate the field values | |
var compressedSize = (uint)compressedInput.Length ^ rand.NextUInt(); | |
var decompressedSize = (uint)input.Length ^ rand.NextUInt(); | |
// create the checksum | |
var checksum = rand.NextUInt(); | |
for (var i = 0; i < compressedInput.Length; i++) | |
{ | |
// update the checksum | |
checksum = compressedInput[i] ^ RotateRight(checksum, i % 7 + 1); | |
// encode the byte | |
var shift = i & 0xF; | |
compressedInput[i] ^= (byte)(((0xFFu << shift) & rand.NextUInt()) >> shift); | |
} | |
// write the fields and data | |
WriteUInt32BE(output, compressedSize); | |
WriteUInt32BE(output, decompressedSize); | |
WriteUInt32BE(output, checksum); | |
output.Write(compressedInput); | |
} | |
private static uint RotateRight(uint v, int bits) | |
{ | |
return (v >> bits) | (v << (32 - bits)); | |
} | |
private static uint ReadUInt32BE(Stream stream) | |
{ | |
var buffer = new byte[4]; | |
stream.Read(buffer); | |
return (uint)(buffer[3] | (buffer[2] << 8) | (buffer[1] << 16) | (buffer[0] << 24)); | |
} | |
private static void WriteUInt32BE(Stream stream, uint value) | |
{ | |
var buffer = new byte[4] | |
{ | |
(byte)((value >> 24) & 0xFF), | |
(byte)((value >> 16) & 0xFF), | |
(byte)((value >> 08) & 0xFF), | |
(byte)((value >> 00) & 0xFF) | |
}; | |
stream.Write(buffer); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@ClickHubs BrawlhallaAir isnt a swz