-
1 Attachment(s)
Thief 4
/* Google Translate */
Hello!
I need help from experienced forum users, I'm trying to make a save editor for Thief 4. Decompilation and compilation are successful, but there was a problem understanding crc. Saving is divided into many blocks that are compressed using zlib, crc checking data between them. I was able to allocate 3 crc blocks, the last two of which contain checksums of the decompiled and compiled blocks. But when I try to generate a checksum (for an unchanged block to check), I get incorrect values. Question: can anyone help determine the method for generating checksums and tell what the first crc is ??
Attachment 17242
-
Re: Thief 4
Post a save and some information on the data you've discovered
-
1 Attachment(s)
Re: Thief 4
/* Google Translate */
Sure. The save file is attached to the response. As for the information. The required data is in the third block (I want to change the dificalty of the game). The block is decompiled using the standard zlib library (Used python). To check, I compressed the original block. Almost everything coincided, except for the beginning and the end. At the end of the block, it is enough to add empty bytes, even though there was some information before (checked in the game, it works). From the beginning of the block a lot of problems arose. Highlighted three crc blocks.
s - static bytes (can be copied).
? - not known bytes.
n - not known byte not related to nearby crc
?? ?? ?? ?? ss ss ss ss 06 00 00 00 ?? ?? ?? ??
ss 00 00 00 00 00 00 01 00 00 00 01 00 ?? ?? ?? ??
nn ss 00 00 00 00 00 00
The first checksum is not known where to come from. Having studied the code through ida, I can assume that the second crc is calculated over the entire compressed data block. The third checksum is calculated over the entire uncompressed data block. The last part of nn is not known.
Data is compressed using zlib with the following parameters (level 1, windowsize +15).
-
Re: Thief 4
The data you posted is little endian, I assume this is for PC? If that's the case then why not use or create a trainer?
Memory editing would be a simpler way of going about it because everything would be uncompressed and unsecured (presumably) in memory. If you would still like to edit the save data then could you upload the IDB somewhere and post it?
-
Re: Thief 4
You are right, this is the PC version, but the work with blocks is identical with the version for xbox, so I decided to use the PC version for convenience.
I found information from Mr. Vulnavia on your forum. He worked on this topic and left the following notes.
/ * ---------------------- * /
"No Crypto only alot of CRC's of uncompressed and compressed Data at the beginning
and end of each Block ...
CRC1 (CustomEA with Seed = 0xDEADBEEF) at Blockstart-0x1C calculated for whole uncompressed Block
CRC2 (CustomEA with Seed = 0xDEADBEEF) at Blockstart-0x0C calculated for whole compressed Block
CRC3 (CustomEA with Seed = 0xDEADBEEF) at Blockstart-0x28 calculated for 0x20 Bytes from Blockstart-0x20
CRC4 (Adler32 added by ZLIB) at BlockEnd calculated by ZLIB for uncompressed Block
The Compression Parameters are Level 1, Windowsize 15 and HTTPC disabled, with
this Parameters the Adler32 is also added automatically by ZLIB ...
Also the Re / Compressed Block must be 8 byte aligned / padded before Insertion else
the Game will throw a Save Corruption and can also potential Crash, because their
Routine to locate the Blocks is a bit messy compared to mine which don't care about
any Alignment to find it ... Also who knows, perhaps thats the Source of Save Corruption
that occured on non-modded Savegames ... lol! "
/ * ---------------------- * /
I tried to figure out what "seed" is and how to use it, but due to the language barrier, I couldn’t. Perhaps you can tell.
-
Re: Thief 4
What Vulnavia meant by seed was the initial value of the CRC result, instead of the CRC starting as 0 or 0xFFFFFFFF it starts as 0xDEADBEEF
This is the 'CustomEA' CRC mentioned in the notes (sorry Vulnavia, I decompiled your tool I hope you don't mind)
Code:
uint CalcCRC(uint CRC, byte[] buffer, int index, int count, bool flag)
{
uint[] Table =
{
0x00000000, 0x4c11db7, 0x9823b6e, 0xd4326d9, 0x130476dc, 0x17c56b6b,
0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7,
0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef,
0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb,
0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0,
0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x18aeb13, 0x54bf6a4,
0x808d07d, 0xcc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc,
0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050,
0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34,
0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1,
0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
0x3f9b762c, 0x3b5a6b9b, 0x315d626, 0x7d4cb91, 0xa97ed48, 0xe56f0ff,
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9,
0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd,
0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71,
0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2,
0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
0x18197087, 0x1cd86d30, 0x29f3d35, 0x65e2082, 0xb1d065b, 0xfdc1bec,
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a,
0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676,
0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662,
0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
};
if (flag) CRC = ~CRC;
for (int i = index; i < count; i++)
{
CRC = ((CRC << 8) ^ Table[(buffer[i] ^ (CRC >> 24))]);
}
if (flag) CRC = ~CRC;
return CRC;
}
//for swapping the crc result to big endian
uint bswap32(uint value)
{
unchecked
{
return ((value & 0xff000000) >> 24) | ((value & 0x00ff0000) >> 8) | ((value & 0x0000ff00) << 8) | ((value & 0x000000ff) << 24);
}
}
Code:
string file = "C:\\file.bin"; //some file
byte[] buffer = File.ReadAllBytes(file); //read the file into memory
uint seed = 0xDEADBEEF;
uint crc = CalcCRC(seed, buffer, 0, buffer.Length, true); //flag is true for xbox360 (so it seems)
crc = bswap32(crc); //swap the crc result to big endian for xbox360
I don't know the offsets of the CRC or the data they cover but that is the algorithm for computing the custom CRC
-
Re: Thief 4
As far as I understand, the table is the standard crc32 polynomial "0x04C11DB7". The argument "buffer" is passed to the function, but it is not used anywhere. Any idea why? Also, when using the specified calculation algorithm, the system generates an "index out of range" error. The problem is that with a shift (CRC << 8) and further calculations, a value is obtained that goes beyond the scope of the table. Can you check this algorithm using the example of "123456789" and post the answer to the forum?
-
Re: Thief 4
Yes you are right about the code, it should be buffer[i] and not (i & 0xFF). That was my mistake, I have edited the code in my post. You are also right about the checksum, it is a standard CRC32 checksum - the table is initialized with a polynomial value of 0xEDB88320 and uses an initial CRC value of 0xDEADBEEF
If you need a result example to verify:
Code:
byte[] bytes = new byte[] { 0x15, 0xCD, 0x5D, 0x07 }; //123456789
uint CRC = bswap32(CalcCRC(0xDEADBEEF, bytes, 0, 4, true)); // calculate crc and reverse endian
Console.Write("0x" + CRC.ToString("X2")); //print crc result in hexadecimal
//output: 0xCEBD580C