-
-
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); | |
} | |
} |
I've updated the gist with encryption methods.
Since WELL512 is a LFSR, if you provide the same seed it will always produce the same random number at each call - hence why the checksums HAVE to be validated for decryption. Additionally, XOR'ing is a reversable operation. The only real difference between encrypting and decrypting is the order of operations - compress, update checksum, encrypt vs decrypt, update checksum, decompress.
If I wanted to decrypt the swzs, what should I do? Pop this into a compiler then run it?
If I wanted to decrypt the swzs, what should I do? Pop this into a compiler then run it?
I've wrapped this in a simple application for you. Download the zip file, extract the contents anywhere and drag and drop one-or-more swz
files onto it. It will prompt for a "global encryption key" which is the value used by ANE_RawData.Init
in BrawlhallaAir.swf
- currently 124416110
for patch 6.11. It will then create a folder called Dump
in the same directory as your swz
files and export all the decrypted text files there.
Encrypting and repackaging will require a separate tool and is not something I'd be willing to make however everything required for it is already documented above.
Thank you so much! I completely missed on the zip file ahah :3 However, when I put the global encryption key and then press enter the Dump folder appears, but empty even tho I have some swzs in my folder. Is this normal?
Thank you so much! I completely missed on the zip file ahah :3 However, when I put the global encryption key and then press enter the Dump folder appears, but empty even tho I have some swzs in my folder. Is this normal? edit* second acc cuz forgot password lol
I only uploaded it an hour ago :) Did you drag and drop the swz
files onto it?
Ohhh onto the program itself! My b :)
Thanks for uploading it ^^
Sorry for bothering, but is the global encryption key under the ANE_RawData in BHAir.swf? I cant find it, maybe its obfuscated?
Yes it is. Using a flash decompiler (like JPEXS Free Flash Decompiler), you need to locate the ANE_RawData.Init
function call within BrawlhallaAir.swf
. With FFDec you can simply use the Text Search functionality to find it.
Did you drag and drop the swz files onto the application? If so, it might be a different encryption key is required.
I didn't know it was supposed to drag the file directly into the application, thanks!
I already modified the files I wanted, how do I apply them to the game?
Encrypting and repackaging will require a separate tool and is not something I'd be willing to make however everything required for it is already documented above.
The class implementation has encryption methods that allow you to compile the files into a .swz
, but you'll need to build that yourself.
Also be mindful when editing .csv
files, as some of the linebreaks they use are LF characters instead of CRLF characters ($0A
vs. $0D 0A
), and some text editors will squash both of those into one or the other depending on you settings, so be sure to verify the file with a hex editor. This issue is most noticeable with the file headers, the very first line in any .csv
file that has the file name.
Also I noticed filenames and extensions aren't preserved when dumping.
The following code takes a file stored as string data
and gives the correct string 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";
@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
Is there a way to reverse the algorithm to create an encryption method? Or is that near impossible because of WELL512?