// // POD Binary Data File (*.*) // // It’s a generic container format for various files used by Planet of Death. // Two 'secret' parameters are required to successfully decode/encode a PBDF: // A 32-bit BlockSize and a 32-bit CoderKey. The original game implementation // loads PBDF data in blocks - therefore the FileSize has to be a multiple of // BlockSize. A block contains (BlockSize - 4) bytes of encoded data followed // by a 32-bit Checksum. The Checksum is calculated by summing up the decoded // data as an array of unsigned 32-bit values (32-bit overflows are ignored). // The data of the first block is always encoded with the standard XOR-Coder: // // for (int i = 0; i < BlockSize / 4 - 1; ++i) // ((uint32_t*)BlockData)[i] ^= CoderKey; // // Due to the way how XOR works, the same Coder can be used for the decoding. // In 'almost all' cases the XOR-Coder is also used for the following blocks, // but for some CoderKeys (e.g. 0x5CA8 and 0xD13F) a Special-Coder is used... // // for (uint32_t LastValue = 0, int i = 0; i < BlockSize / 4 - 1; ++i) // { // uint32_t KeyValue; // uint32_t OldValue = ((uint32_t*)BlockData)[i]; // switch (LastValue & 0x00030000) // { // case 0x00000000: KeyValue = LastValue - 0x50A4A89D; break; // case 0x00010000: KeyValue = 0x3AF70BC4 - LastValue; break; // case 0x00020000: KeyValue = (LastValue + 0x07091971) << 1; break; // case 0x00030000: KeyValue = (0x11E67319 - LastValue) << 1; break; // } // switch (LastValue & 0x00000003) // { // case 0x00000000: NewValue = ~OldValue ^ KeyValue; break; // case 0x00000001: NewValue = ~OldValue ^ ~KeyValue; break; // case 0x00000002: NewValue = OldValue ^ ~KeyValue; break; // case 0x00000003: NewValue = OldValue ^ KeyValue ^ 0x0000FFFF; break; // } // if (Decoder) // LastValue = OldValue; // else // LastValue = NewValue; // ((uint32_t*)BlockData)[i] = NewValue; // } // // POD Binary Data Files always start with the following Header that includes // the FileSize and absolute seek pointers (Offsets) to several data streams. // If you are working with decoded POD Binary Data Files (without checksums), // you should note, that an Offset includes the Checksums of the data blocks. // There should be at least one Offset (points to the data after the Header). // Due to the way how the original implementation works, unused Offsets often // contain the CoderKey value (instead of 0 ... which would make more sense). // struct Header { uint32_t FileSize; uint32_t OffsetCount; uint32_t OffsetTable[OffsetCount]; } // // Due to the fact that (a) the Header contains the FileSize and (b) that the // XOR-Coder is always used for the first block - you can XOR the first DWORD // with the to calculate the CoderKey for an unknown PBDF. // // Due to the fact that the BlockSize must be a multiple of the FileSize, the // BlockSize can be 'guessed' by consecutively decoding (using the XOR-Coder) // the data, and verifying if the following 4 bytes would be a valid Checksum // and if the resulting BlockSize (DataSize+4) is a multiple of the FileSize. // // -------------------------------------------------------------------------- // // Even if you successfully decoded the PBDF’s data, you might 'miss' strings // that 'must be somewhere' in the file. The reason is quite simple, they are // encoded. Encoded strings start with a Length byte, followed by an array of // encoded 8-bit characters (the data does not include a terminating 0 char). // The characters can be encoded and decoded with the following code snippet: // // for (uint8_t i = 0; i < Length; ++i) // Data[i] ^= ~i; // That is: The XOR key goes from 0xFF down to 0x00. // // For example: "...Sinon..." (idle event name) in a BLx will be stored as: // 0x0B, {0xD1, 0xD0, 0xD3, 0xAF, 0x92, 0x94, 0x96, 0x96, 0xD9, 0xD8, 0xDB} //