This blog post is a long overdue update to the info I found years ago.
Polarium is a Nintendo DS puzzle game where you try to invert the black and white tiles, so that they match row by row, with a non-overlapping path. Polarium Advance is the Gameboy Advance sequel to Polarium. These games can save and load puzzles as a series of characters that can be written down and shared with other people.
The process that transforms the puzzles you make into passwords is simple. First the puzzle is represented as a series of 8-bit numbers called octets, then those octets are optionally encrypted with a 4-bit key, next they are converted to another number base (radix), and finally the converted numbers are mapped to the characters that's written down.
Keep in mind that everything is processed in little-endian order, even the individual bits in the radix conversion. This means that sometimes things will read from right to left, backwards from how we normally write.
The information contained in Polarium [DS] are 8x8 tiles that are black or white, what portion of these tiles are used, and start/end hints coordinates. This information is packed according to Table 1.
|octets 0-7||Tile data|
|octet 8||Start hint Y||Start hint X|
|octet 9||End hint Y||End hint X|
The tile data always consists of 8x8 bits. 0 is white and 1 is black. octet 0 is the topmost row, and bit 0 is the leftmost column. All 64 tiles are packed from left to right, top to bottom. The tile data is always 8x8 bits even if the width and height is smaller. The Width and Height defines a window of the tile data that's used. Usually the bits left outside this window is changed to the default checkerboard pattern, but they can be anything.
There's some trickiness comparing hint coordinates with Width and Height. Hint coordinates include the implied border around the whole puzzle, and starts at 0. Width and Height is the portion of tiles that are included in the puzzle and starts at 1. So for a puzzle that has a width of 5 and a height of 2 the coordinates range from (0,0) to (6,3) seeing the whole board as 7 by 4.
The checksum is the sum of octets 0 through 10 module 256.
The way that Polarium [DS] codes octets to characters is that is takes 4 octets at a time, interprets them as three 32-bit little-endian unsigned integers, converts them to decimal, 0 fills them to 10 characters, and then reverses those characters.
The Game's editor won't allow you to make entirely white or black rows, but it's a legal game mechanic and is still accepted in password form. The code is invalid if the entire visible board is white or black. The code is also invalid if the start and end hints are in the same location.
Polarium Advance codes are, in a lot of ways, different from Polarium [DS] codes. The codes are variable length, has some weak encryption, and can contain two different mode of tile data, for which I'll call basic and advance mode. The header comes first and is packed into octets according to Table 2.
|octet 0||Encryption key||0||0||0||1|
|octet 2||Start hint Y||Start hint X|
|octet 3||End hint Y||End hint X|
|octet 5||Number of steps hint|
|octet 6||?||0||0||0||tile mode||difficulty hint|
|octet 7...||Tile data|
The encryption key is a number from 1 to 15 indicating how octets 2 and beyond are scrambled, 0 is unencrypted. The checksum is the sum of unencrypted octets 2 and beyond, module 256. The width and height are one less then the total number of tiles horizontally or vertically. They range from 3 (4x4) to 15 (16x16), which is much easier than the DS version to match up with tile coordinates. The tile coordinates range from 0 to width/height. The tile mode determines the number of bits per tile in the tile data. Tile mode 0 means 2 tile types (1 bit) per tile, and tile mode 1 means 8 tile types (3 bit) per tile. Number of steps and Difficulty hints can be anything. The Number of steps should be the minimum number of covered places required to solve the puzzle. The question mark bit can be 1 or 0, but it apparently does nothing. It's always resets to 0 when exporting the code from the game.
The number of octets the tile data has is determined by the puzzle width, height, and tile mode. The border in included in the tile data and uses the same number of bits as the rest of the tiles. So the total number of octets is
ceiling(width * height * (mode ? 3 : 1) / 8). For example, a puzzle with a width and height of 5 tiles and advance tile mode is ceil(5 * 5 * 3 / 8) = ceil(9.375) = 10. All tiles are packed from left to right, top to bottom in little-endian order. List 1 is a list of tile codes in advance mode, basic mode is only the first two tile types with codes 0 and 1.
If the tile data is the advance tiles hurdle, white, black, multi, and space, then the bits are 111 000 001 100 110. All together and in reverse tile order that becomes 110100001000111. Then it's padded and split into 8-bit octets, 01000111 01101000. All this because everything is done in little-endian order.
Now that all the octets are coded they are run through some weak encryption. Each octet is separately bit rotated and xor'ed with a corresponding mask. The pairs of bit rotation and masks cycle every 8 octets. I've prepared two tables of numbers via brute force (see Tables 3 and 4). The encryption starts on the 3'rd octet(labled 2), and I also put that on the 3'rd column of the tables. So you could simply write
bit_mask[key][octet_no%8] in programing languages. To encode, xor the mask then bit rotate left. To decode, bit rotate right then xor the mask.
plain = (cipher >>> rotate[key][n%8]) ^ mask[key][n%8]
cipher = (plain ^ mask[key][n%8]) <<< rotate[key][n%8]
The final step is to code the encrypted octets into characters. This works much like packing tile values in the tile data. Each character is 5 bits from 1 or 2 octet. Everything is little-endian. The last character value is padded to an even 5 bits. The values then are printed according to Table 5.
The Game's editor doesn't provide a Solid Multi-tile, but it is completely valid and works as expected. Unlike Polarium for the DS, lines of the same same color will make the whole puzzle invalid.