Wasteland Wiki
Advertisement

The GAMEx Files' MSQ-block Map Data Encryption Routine[ | ]

The Wasteland GAMEx files are divided into blocks. Each block is delimited using a header which contains the letters "msq"

For example, the first six bytes of the game1 file are:

6d 73 71 30 94 f0

which are the ascii characters m s q 0 : � q

so the '0' indicates that this is a block in the first 'game' file, Game1, and the next two bytes (3a f0) are the start of a rotating encryptor. The encryption only runs to cover the map data section. Because of the way that the game has to load up new maps and yet reduce the memory requirements, each block is encoded separately, and also each block is self-contained. Anything to do with that map is located within that MSQ block. Because of the 2-byte limitations on pointers within the blocks, this means that no map may have more than 64K of data associated with it. For big or complex maps, MSQ blocks run to 8k or so. so the 65K limit is probably not a problem.

Decrypting Blocks[ | ]

When the player 'enters' a map, the game loads in the map data. In order to do this, it locates the relevant MSQ block, and decrypts the Map Data Section. The following pseudocode applies. It should be noted that everything is byte-based, except for the checksum, which is a short (2 bytes).

  1. READ in msqX characters
  2. READ in byte1
  3. READ in byte2
  4. encryption byte = byte1 XOR byte2
  5. checksum = 0
  6. FOR EACH successive byte in the file until you reach the location where checksum == byte2<<8

| byte1:

  1. --READ in crypt byte
  2. --plaintext byte = data byte XOR encryption byte
  3. --WRITE plaintext byte
  4. --checksum = checksum - plaintext byte
  5. --encryption byte = encryption byte + 0x1f
  6. FOR EACH remaining byte in the block
  7. --WRITE plaintext byte

Encrypting Blocks[ | ]

Should a programmer want to modify a block, they must re-encrypt the block, which involves reversing the above process.

  1. checksum = 0
  2. FOR EACH successive byte in the current block of data to encrypt:
  3. --READ in the plaintext byte
  4. --checksum = checksum - plaintext byte
  5. --WRITE "msqX" where X is the current block number
  6. --WRITE the two checksum bytes.
  7. --encryption byte = checksum[0] XOR checksum[1]
  8. FOR EACH successive byte in the current block of data to encrypt:
  9. --READ in the plaintext byte
  10. --crypt byte = plaintext byte XOR encryption byte
  11. --encryption byte = encryption byte + 0x1f
  12. FOR EACH byte in the strings and bytecode data
  13. --write it into the file.

If the block's size has changed after an edit, then there may be some problems (there WILL be some problems!) with the game, as it won't know how to locate an MSQ block for any maps after the one edited, since they will have moved within the file. Luckily, all pointers within the blocks are offsets from the LOCAL block, so modifying a block does NOT mean you have to go through all successive blocks to change their pointers. Which is nice.

Decryption Example[ | ]

So, the first 10 bytes of the game1 file can be interpreted thus. We note that the encryption byte will be 0x94 ^ 0xf0 = 0x64 on the first byte of the block

//==CRYPT BYTES==// 6d 73 71 30 94 f0 df 38 19 7a 5b 44 a5 ...
//==ASCII==// m s q 0 b1 b2 data --- --- --- --- --- --> ...
//==ENCRYPTION BYTE==// 0 0 0 0 0 0 64 83 a2 c1 e0 ff 1e ...
//==DECRYPTED BYTE==// m s q 0 bq b2 bb bb bb bb bb bb bb ...

Note that at the >1e< point, the encryption byte has 'wrapped around' at 0xff to become 0x1e.. Also note that the data consists of nothing but the characters 'bb' 'bb' and so on... This is because the first 2048 bytes of the file contain the main world map for the game.. and the character code 'b' indicates 'impassable rock', and the world is chopped into nibbles, hence each 'b' is a tile...So the next 64 rows of 32 columns defines a 64x64 tile-map (the main world map).

Each byte in a block is encryped by XORring with an encryption byte. The byte start is given by the first byte after the MSQ header, and then increases by 0x1f, rotating back after it reaches 0xff.

How this was found[ | ]

Several members of the Wasteland group discovered the rotating 0x1f encryption at roughly the same time, by trial and error, in 2004, and others have probably done so before this time. In 2005 the process was rediscovered by disassembling the game code, and doing a memory dump of the loaded and decrypted data. This was compared with the source data and the encryption deciphered.

In 2005, the highpool map was used, and it was noted that the first byte of the pointer table seemed to point off to the string table, and it was wondered why. It is now known that this value is then fed back into the decryptor, which keeps running until this point is reached. All data after this point is encrypted using a different algorithm , Huffman Coding Before the wasteland game code was disassembled, however, another observation was used to discover the location of this delineation of the msq block between encrypted and non-encrypted:

The checksum byte is constantly changing with each byte of the decryption block. The game itself uses this to //verify// a valid block. When the end of the encrypted area is reached, the initial bytes and checksum are compared.

An alternative way of looking at it is to keep the decryption running //UNTIL// the checksum reaches the value given by the initial bytes. Because the checksum is so small, the checksum actually tends to hits the value several times in each block. But it was noted that in EVERY CASE, one of the valid points happens to sit at the byte before point where the data in the file has the values 0x20 0x65 and then a string of letters and numbers. The 0x20 byte happened to be the exact first byte pointed at by the string-table offset in the map data header.

Advertisement