Oxyd .DAT file format Written by Jeremy Sawicki (jeremy AT sawicki DOT us) Last modified: 9-Feb-2003 This document describes the file format of the .DAT files used by the Oxyd series of games by Dongleware. It is based on studying the PC versions of Oxyd 1, Oxyd Magnum, PerOxyd, and Oxyd Extra. It is still a work in progress. Most of the reverse engineering work was done with PerOxyd, so some of the information may not be accurate for the other games. Documenting the differences between the games is one area where more work is needed. The remaining types also need to be documented, as do and . Work is also needed to document how the data in interacts with each type of piece and object. A note on byte order: Unless otherwise noted, all multiple-byte quantities are stored most significant byte first. For example, the integer 0x01020304 would be represented by the four bytes 0x01, 0x02, 0x03, 0x04 in that order. This is opposite from the normal order used on IBM PCs. = : 600 bytes Contains programmer names, null bytes, etc. Probably ignored. : 4 bytes = sizeof( ) : 4 bytes Used in Oxyd 1 to determine (somehow) the level codes. First two bytes for one-player levels, next two for two-player levels. : 400 bytes = sizeof(1) sizeof(2) ... sizeof(200) Each size is represented by two bytes = 1 2 ... 200 Single player levels: 1 to 100. Two-player levels: 101 to 200. = a with each byte transformed according to a 16-byte repeating pattern. Bytes are transformed as follows: n = (n + pattern[n % 16]) % 256 n = (n - pattern[n % 16]) % 256 For Oxyd 1, Oxyd Magnum, and Oxyd Extra the pattern is 0x45, 0x23, 0xEA, 0xB9, 0x11, 0xF1, 0x9F, 0x5A, 0x33, 0x3E, 0x0F, 0xB4, 0x41, 0x56, 0xAF, 0xAA For PerOxyd the pattern is 0xAA, 0xE1, 0x82, 0x34, 0xAF, 0x24, 0x72, 0x42, 0x37, 0x3E, 0x83, 0x98, 0x22, 0xFF, 0x2F, 0x31 = ... Each level contains several distinct types of information. One block of each type is included. must be last, and must come before , , and . Otherwise, order is not important. If the Oxyd version has fewer than 200 levels, an unused can also be zero bytes long. Oxyd Magnum has 100 levels, and Oxyd Extra has 30 levels. But unused levels are not required to be zero bytes long. In Oxyd Magnum, several of the unused levels are present. = | | | | | | = 0x0000 sizeof() = 0x0001 = 0x0002 sizeof() = 0x0003 = 0x0004 sizeof() = 0x0005 sizeof() = 0x0006 sizeof() All sizes are stored using 2 bytes. = Represents the "surfaces": brick, ice, abyss, space, etc. = Represents the "pieces": walls, puzzle pieces, doors, movable planks, etc. = Represents the "objects": coins, hammers, bumps, springboards, etc. : 68 bytes = 0 1 ... 7 Stores the level grid dimensions and the marble starting locations : 2 bytes Width of the level grid, in blocks : 2 bytes Height of the level grid, in blocks n = 0x00 0x00 0x00 0x00 : 2 bytes X coordinate of marble starting location, in pixels : 2 bytes Y coordinate of marble starting location, in pixels = [ ...] 0xFFFF Determines which items in the grid send signals to which others. For instance, signals can be used to create buttons that open and close doors, coin slots that operate lasers, etc. There are also less obvious uses of signals. For instance, when a marble enters a hole, the location where it reappears is determined by a signal. = [ ...] A represents signal connections between one sender and one or more recipients. The first is the sender. The remaining s are the recipients. The number of recipients is specified by . is 2 bytes. : 2 bytes A represents an item in the grid that sends or receives a signal. Bits 15 and 14 are always 0. Bit 13 is 0 if the item is a piece, and 1 if it is an object. Bits 12 through 0 represent the block number of the item. (In practice, bit 12 is always 0, so it may not be part of the block number.) = [(0x20 ) ...] 0x00 Stores such things as what marbles are present in the level, the text of any notes, the presence of rubber bands, etc. = | | | | | | | | | | | | | | There are additional types of that I haven't figured out yet. = 'G' [] Indicates the goal of the level. = | = 'N' = 'M' If the is , the goal of the level is to open oxyds (a normal level). If it is , tho goal is to put marbles in pits (a meditation level). If no item is present in a level, it defaults to a normal level. I don't know what this is yet. = 'n' If present, the black marble can safely come into contact with meditation marbles. If absent, the black marble shatters when it touches a meditation marble. (It seems white marbles can always safely touch meditation marbles.) The item should come immediately before the items. = 'F' [] = | | | | | | = 'B' = 'b' = 'M' = 'G' = 'K' = 'C' = 'D' A "life spitter marble" must be present in levels containing life spitter pieces (0x94), and a "dynamite holder marble" must be present in levels containing dynamite holder pieces (0x95), otherwise those pieces don't function. Presumably these so-called marbles are used to implement the animations associated with those pieces. Still have to figure this stuff out. = 'B' 1 [0xF9 2] [0xF8] The first number in 1 is the natural length of the rubber band in pixels. When it is shorter than the natural length, it supplies no force. The second number in 1 controls the amount of force supplied by the rubber band. A value of 0 means no force; the largest observed value is 4000. The third number in 1 is the block number that one end of the rubber band connects to. 2 contains up to two values, which are marble numbers that the rubber band connects to. The above values can be combined in different ways to represent different kinds of rubber bands. To represent a rubber band between two marbles, both marble numbers are present the block number is empty. The natural length and force are also present. To represent a rubber band between a piece and a marble, the block number is present, the first marble number may be present or absent (but not empty), and the second marble number is absent. The natural length and force are also present. The first marble number is only absent in levels containing a single marble, in which case it is clear which marble the rubber band is attached to. If the first marble number is absent, the 0xF9 byte is still present. The remaining type of rubber band description does not refer to a rubber band that is present when the level begins, but rather it describes the properties of rubber bands that appear later. A level can contain up to three rubber band descriptions of this type. The first describes rubber bands created when a black marble touches a rubber band piece. The second describes rubber bands created when a white marble touches a rubber band piece. The third describes rubber bands created when a rubber band object is activated. The rubber band description may contain only the letter 'B' with no further information, which is used if the level doesn't contain a particular type of rubber band. Otherwise, the natural length and force are present, the block number is absent or empty, the 0xF9 byte may or may not be present, and both marble numbers are absent. The 0xF8 byte is rarely present. It only occurs once after a rubber band between a piece and a marble, and once after a 1 with the third number empty and no 0xF9 byte or 2. The meaning of the 0xF8 byte, if any, is not known. = 'N' When a marble activates a rubber band object, the other marble that it becomes attached to is determined by the item. The contains two numbers, both always present. The first number is the marble number that a black marble will become attached to, and the second is the marble number that a white marble will become attached to. If the level contains no item, the default behavior is 'N(1)(0)' -- in other words, a black marble becomes attached to marble 1 and a white marble becomes attached to marble 0. = '!' Magic piece 0x3B is always present when the user restarts the level. The magic piece reappears regardless of whether or not the level is completely reset, though it does not reappear when the level restarts due to the marble being shattered. = 'f' The board scrolls freely (rather than one screen at a time). = 's' The level completely resets when the marble dies. = 'j' Changes the behavior of piece 0x5C. If is present, the piece is a puzzle piece with connections in all four directions that marbles can walk under. Otherwise, the piece is a stand-alone movable piece with no puzzle piece graphics. Strangely, in every level that includes piece 0x5C, is present. = 'g' The number list can contain up to 3 numbers. The first number determines the amount of force supplied by certain force-supplying surface types, specifically those that don't look sloped. The number can be between 0 (no force) and 1000 (very large force). If the number is absent, a default amount of force somewhere between 0 and 45 is used. Surface types affected by the first number: 0x1B, 0x21, 0x22, 0x2F, 0x30, 0x31, 0x32, 0x4F, 0x50, 0x51, 0x52 The second number determines the amount of friction on certain surface types. The number can be between 0 and 999. A value of 0 means infinite friction. Surface types affected by the second number: 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x57, 0x58, 0x59, 0x5A The third number determines the amount of force supplied by certain force-supplying surface types, specifically those that look sloped. The number can be between 0 (no force) and some unknown upper limit. The two actual values used are 10 and 70. Surface types affected by the third number: 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E = 'P' This describes the scrambling that should be applied to puzzle pieces in the level. The number list is grouped into pairs of numbers. The first number in each pair is a block number, and the second number is a value between 0 and 3 (inclusive), representing one of the four directions on the screen (0 = up, 1 = down, 2 = left, 3 = right). Each pair determines a row or column of puzzle pieces that will be rotated during the scrambling. The row or column begins at the specified block number and extends in the specified direction. The scrambling process basically consists of rotating the various rows and columns of puzzle pieces by random amounts. The exact details of the process are not known. = 'L' [0xF9 | 0xF8] The contains two numbers. The first is a laser type number, from 1 to 3. The three laser types correspond to the bytes 0x3E, 0x3F, and 0x40 in the piece grid. The second number is a direction number (0 = up, 1 = down, 2 = left, 3 = right) that indicates which direction lasers of the specified type point. If the final optional byte is 0xF9, lasers of the specified type are initially on. If it is 0xF8 or not present, they are initially off. At most one item is present for each laser type. If no item is present for a given laser type, lasers of that type are initially off, and they point in a default direction based on type (type 1 = up, type 2 = down, type 3 = right). = '~' The contains two numbers. The first is the block number of an oscillator piece. The second is a value between 1 and 15 that indicates the period of the oscillator. The period p is approximately (n + 1) * 0.678 seconds, where n is the second number in the number list. The oscillator sends an "on" signal and an "off" signal every period, evenly spaced. If a level contains an oscillator piece with no corresponding item, the oscillator behaves as though the second number is 0. If a level contains more than one item for a given oscillator piece, the last one takes precedence. = 'O' The number list contains a single number, the block number of an oscillator piece. If a item is present for an oscillator, the oscillator is initially inactive, otherwise it is initially active. = '"' '"' = | = 'Z' = 'z' = | | = 'g' = 'e' = 'f' The note text may contain space characters (0x20). = [ ...] = '(' [] ')' A is a non-negative integer represented in base 10 using the ASCII characters '0' through '9'. = ... A is a compressed representation of a two-dimensional grid of s. Each represents a sequence of one or more s. The concatenation of the sequences forms one long sequence that represents the contents of the grid from top to bottom, left to right. (It seems to be necessary to parse a by starting at the end and working backwards. That could explain why a few things are stored least significant byte first in a .) : 1 byte Each in interpreted as a particular type of surface, piece, or object, depending on what type of grid it appears in. The various values are documented in grid-bytes.txt. = | | | = The represents itself literally. Just about any byte can be represented this way, but definitely not 0xFD, 0xFE, or 0xFF, since they will be interpreted as a compressed during parsing. = 0xFF This is equivalent to repeating +1 times. is 2 bytes. It is stored LEAST significant byte first. = 0xFD This is equivalent to repeating +1 times. is 1 byte. = 1 0 0xFE This is equivalent to a sequence of +1 s, each of which is either 1 or 0 as determined by . contains ceil[(+1) / 8] bytes. These bytes can be interpreted as a single integer, LEAST significant byte first. When it contains more than +1 bits, the most significant extra bits are ignored. Of the remaining bits, the most significant corresponds to the first and the least significant corresponds to the last . is 1 byte. : 2 bytes = the number of chunks The remainder of the .DAT file after the levels is made up of a series of "chunks." A chunk can contain audio, graphics, or perhaps other things. = 1 ... n There is one for every in the .DAT file. : 24 bytes An is a with all of the bits inverted, and with a transformation applied to each group of four bytes. The transformation is a rotation by three bits. To encode, the value is shifted right by three bits, with the three least significant bits becoming the new most significant bits. To decode, reverse the process. : 24 bytes = : 4 bytes The number of bytes occupied by the chunk in the file. : 4 bytes Some chunks use a type of compression. For compressed chunks, this is the size of the chunk when uncompressed. For chunks that are stored uncompressed, is the same as . : 4 bytes The offset from to the beginning of the chunk. : 12 bytes This is the name of the chunk. It is a string of up to 12 characters. Any unused bytes past the end of the string are nulls (0x00). The names appear to be DOS filenames with a base name of up to 8 characters followed by a dot and a 3 character extension. The extension is .PIB for bitmaps, .SDD for audio, and .SIB for some other type of information (font data?). = 1 ... n = | If the chunk's and differ, the chunk is a , otherwise it is an . = ... A is a stream of bytes which stands for another longer stream of bytes, an . = 0 ... 7 The is 1 byte, where bit n determines the interpretation of n. The last may contain anywhere between 1 and 8 items. Unused bits in the last are 0. = | If the corresponding bit is 1, is a , otherwise it is a . : 1 byte This byte is interpreted as the same byte uncompressed. : 2 bytes This is made up of a 4-bit and a 12-bit . Bits 3 to 0 of the second byte are the . Bits 7 to 4 of the second byte are bits 11 to 8 of the , and bits 7 to 0 of the first byte are bits 7 to 0 of the . The interpretation of is that +3 bytes should be copied to the uncompressed stream from somewhere earlier in the uncompressed stream. The location to start copying from is the largest location before the current location that is congruent to + 0x12 (modulo 0x1000). If the copy starts before the beginning of the stream, any bytes before the beginning of the stream are assumed to be 0x20. The copy never starts more than +3 bytes before the beginning of the stream. (The byte value 0x20 seems a strange choice. Perhaps this compression method was originally used for text, since 0x20 is the space character in ASCII.) If the copy starts fewer than +3 bytes before the current stream location, the bytes should be copied from lowest to highest location, so that by the time a byte needs to be copied from after the current stream location, it will have already been written by copying an earlier byte. =