-
-
Save barncastle/a21b62df945445b38daf91ede021a3ec to your computer and use it in GitHub Desktop.
// 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); | |
} | |
} |
@KevitoLegal: As @Talafhah1 explained, this is not something supported nor something I'm interested in doing. To be completely honest, I have no interest in this game - I just like reverse engineering file formats, and part of my process is writing functional snippets of code to help me understand how the logic works. As a byproduct, these snippets (hopefully) document the format and provide other developers a basis to create proper tools from.
The attached app is something I knocked together in a few minutes just to provide something more user-friendly than just code. If @Talafhah1 or anyone else wants to build a proper tool, please feel to use as much or little of my code - consider it licensed as WTFPL :)
Also I noticed filenames and extensions aren't preserved when dumping. The following code takes a file stored as
string data
and gives the correctstring file_name
.string file_name; if (data[0] == '<') { if (data.Substring(0, 10) == "<LevelDesc") file_name = data.Split("LevelName=\"")[1].Split('"')[0] + ".xml"; else file_name = data.Substring(1, data.IndexOf('>') - 1) + ".xml"; } else file_name = data.Substring(0, data.IndexOf('\n')) + ".csv";
Was trying to implement this into a little program but am not able to substitute the data string with anything, man :c
The Decrypt
static method is called 4 times, once for each .swz
file stream; its output is a string[]
that has all the files included in the .swz
as a string
.
So, simply use a foreach
loop on the elements of the output of Decrypt
and run the filename block, then use the file_name
string in the file stream writer.
@barncastle update please, I'm getting an error. It's working fine for all the files besides BrawlhallaAir.swf
@ClickHubs BrawlhallaAir isnt a swz
Also I noticed filenames and extensions aren't preserved when dumping.
The following code takes a file stored as
string data
and gives the correctstring file_name
.