Skip to content

Instantly share code, notes, and snippets.

@Nenkai
Created April 4, 2022 22:21
Show Gist options
  • Save Nenkai/745ab77c10a1a57e38e39f3d1705bcfb to your computer and use it in GitHub Desktop.
Save Nenkai/745ab77c10a1a57e38e39f3d1705bcfb to your computer and use it in GitHub Desktop.
Cheat Engine CETRAINER file unpacking
// LinqPad file
// Does not include code initial archive unpacking (CET_Archive.dat)
// File has file names + inflated data chunks, just use offzip - https://aluigi.altervista.org/mytoolz.htm#offzip
void Main()
{
string baseFolder = "folder";
var data = File.ReadAllBytes(baseFolder + "CET_TRAINER.CETRAINER");
Decrypt(data);
var magic = Encoding.ASCII.GetString(data.AsSpan(0, 7));
if (magic != "BRICKES")
throw new InvalidDataException("Invalid magic");
File.WriteAllBytes(baseFolder + "CET_TRAINER.CETRAINER.dec", data);
byte[] decomp = DecompressFile(data.Skip(7).ToArray()); // Skip magic
File.WriteAllBytes(baseFolder + "CET_TRAINER.CETRAINER.dec.decomp", decomp);
ProcessCTFile(decomp, baseFolder);
}
void ProcessCTFile(byte[] data, string outputDir)
{
XmlDocument doc = new XmlDocument();
using var xmlStream = new MemoryStream(data);
doc.Load(xmlStream);
var root = doc["CheatTable"];
// Extract forms and files
// Forms can be openned with MiTeC DFM Editor - https://www.mitec.cz/dfm.html
foreach (XmlElement file in root.SelectNodes("Forms")[0])
{
var textContent = file.InnerText;
var fileName = file.Name;
byte[] decodedDataBuffer = new byte[(textContent.Length / 5) * 4 + (textContent.Length % 5)];
Base85ToBin(textContent, decodedDataBuffer);
byte[] actualFileData = DecompressFile(decodedDataBuffer);
File.WriteAllBytes(outputDir + ("FORM_" + fileName), actualFileData);
}
foreach (XmlElement file in root.SelectNodes("Files")[0])
{
var textContent = file.InnerText;
var fileName = file.Name;
byte[] decodedDataBuffer = new byte[(textContent.Length / 5) * 4 + (textContent.Length % 5)];
Base85ToBin(textContent, decodedDataBuffer);
byte[] actualFileData = DecompressFile(decodedDataBuffer);
File.WriteAllBytes(outputDir + fileName, actualFileData);
}
}
// https://github.com/cheat-engine/cheat-engine/blob/dd1f2f83d45d61f7ff692038e322e32bae7fb96e/Cheat%20Engine/OpenSave.pas#L1420
void Decrypt(byte[] data)
{
int v11 = 2;
if (data.Length >= 2)
{
--v11;
do
{
++v11;
data[v11] ^= data[v11 - 2];
}
while (data.Length - 1 > v11);
}
v11 = data.Length - 2;
if (v11 >= 0)
{
++v11;
do
{
--v11;
data[v11] ^= data[v11 + 1];
}
while (v11 > 0);
}
byte v10 = 0xCD;
int v3 = data.Length - 1;
v11 = 0;
if (v3 >= 0)
{
--v11;
do
{
data[++v11] ^= v10;
v10 += 2;
}
while (v3 > v11);
}
}
byte[] DecompressFile(byte[] data)
{
using var ms = new MemoryStream(data.ToArray());
using var output = new MemoryStream();
using (var ds = new DeflateStream(ms, CompressionMode.Decompress))
{
byte[] sizeBuf = new byte[sizeof(int)];
ds.Read(sizeBuf);
int uncompSize = BinaryPrimitives.ReadInt32LittleEndian(sizeBuf);
byte[] uncompData = new byte[uncompSize];
ds.Read(uncompData);
return uncompData;
}
}
// https://github.com/cheat-engine/cheat-engine/blob/dd1f2f83d45d61f7ff692038e322e32bae7fb96e/Cheat%20Engine/custombase85.pas#L97
void Base85ToBin(string inputStringBase85, byte[] outData)
{
const string customBase85 = "0123456789" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"!#$%()*+,-./:;=?@[]^_{}";
var size = inputStringBase85.Length;
var i = 0;
var j = 0;
var testt = "Hello world".IndexOf("o");
while (i < size)
{
var a = (customBase85.IndexOf(inputStringBase85[i])) * 85 * 85 * 85 * 85;
i++;
if (i < size)
{
a = a + (customBase85.IndexOf(inputStringBase85[i])) * 85 * 85 * 85;
i++;
}
if (i < size)
{
a = a + (customBase85.IndexOf(inputStringBase85[i])) * 85 * 85;
i++;
}
if (i < size)
{
a = a + (customBase85.IndexOf(inputStringBase85[i])) * 85;
i++;
}
if (i < size)
{
a = a + (customBase85.IndexOf(inputStringBase85[i]));
i++;
outData[j + 0] = (byte)((a >> 24) & 0xFF);
outData[j + 1] = (byte)((a >> 16) & 0xFF);
outData[j + 2] = (byte)((a >> 8) & 0xFF);
outData[j + 3] = (byte)(a & 0xFF);
j += 4;
}
switch (size % 5)
{
case 2:
a = a + 84 * 85 * 85 + 84 * 85 * 84;
outData[j + 0] = (byte)((a >> 24) & 0xFF);
break;
case 3:
a = a + 84 * 85 * 84;
outData[j + 0] = (byte)((a >> 24) & 0xFF);
outData[j + 1] = (byte)((a >> 16) & 0xFF);
break;
case 4:
a = a + 84;
outData[j + 0] = (byte)((a >> 24) & 0xFF);
outData[j + 1] = (byte)((a >> 16) & 0xFF);
outData[j + 1] = (byte)((a >> 8) & 0xFF);
break;
}
}
}
void GetBytecode()
{
// Encoding from CE + lua's "encodeFunction"
// From https://github.com/cheat-engine/cheat-engine/blob/dd1f2f83d45d61f7ff692038e322e32bae7fb96e/Cheat%20Engine/LuaHandler.pas#L13180
string luabytecode = "c-n1]%{T;S5XWcf9FpJ^/fMgq6HkJoM]1(3ip4^r!E4!=RTko:Bx$O+6)sTE$yexuY/u#t95w32fnk2k$A4zJS939j-M39_(qD[t:iwUu;}O(uoDrOpGr%r_2sjM5g[;f,Icp4Oua;i,fv2C@z+yLa74($:L[C1n1hBFUmZXyKRs,@BH0$Rtw!{QFf7yo30T_e!eYkO/?@W%I3Bn-8=XW,{v%6dt#Z:[S:#H^Nl]7LdYj5]7TR=A=ipOy[+9mUjDxJh*n#xF{5{iui94$nJF3AZk)-XuvoB6iWj#55W[mUPkef;F;)D({8E?fVc*PM26]xyLZA63Lj.^";
byte[] decodedDataBuffer = new byte[(luabytecode.Length / 5) * 4 + (luabytecode.Length % 5)];
Base85ToBin(luabytecode, decodedDataBuffer);
// Skip 2 bytes and decompress "zlib" to get LuaC bytecode
// Use LuaDec (might need to compile to the correct version afterwards or you will get a precompiled header chunk error)
// Check 0x05 once decompressed, indicates the version of the lua compiler i.e 0x53 = 5.3
// May also need to compile it as x64 if you get a size_t mismatch error
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment