|
Partition 0 is a read-only partition containing factory settings such as region code and default language. Partition 1 only contains zeroes, and is not used for anything. The rest are read/write partitions using a block allocated scheme described below. Details on the contents of the individual partitions is given afterwards.
|
Note that for simplicity, the calculation of the number of bitmap blocks is based on the total number of blocks, not the number of user blocks. That is, the number of bitmap blocks is partition_size / 32768 rounded upwards. This means that unnecessarily many bitmap blocks will actually be allocated for partitions of some sizes. For the partition sizes actually used on the Dreamcast (powers of two smaller than 16M) it doesn't make any difference though.
This is the contents of the header:
|
For a partition to be considered a valid block allocated partition, the magic cookie string must be present, and the partition number field must match the actual number of the partition. The version number is used to check what version of the specification that the partition conforms to. Versions 0 and 1 are compatible with what is described on this page. Other versions may or may not be partially or fully incompatible.
The reason for the logical blocks is the way that the flash memory works. The flash memory is able to write a 0 to a bit previously containing a 1 at any time. In order to change a 0 into a 1 however, the entire partition has to be erased (turning it into all 1:s). Erasing the partition is an expensive operation as all data has to be written back afterwards, and each erase operation also reduces the lifetime of the chip (a flash memory chip is only guaranteed to hande a certain number of erases (typically several thousands though)). So in order to improve performance and chip lifetime, it is better to write new data to a previously unused section of the flash than trying to replace old data (requires an erase operation).
Thus, when new data gets written to a (logical) block, a new physical block is allocated and the new contents is written to this block. In this way, any number of physical blocks can be allocated to a given logical block. These physical blocks will contain various historical contents of the logical block. Only the most recently allocated of them (the one with the higest address) represents the corrent contents of the logical block. When no more unallocated physical blocks remain, the partition is erased, and each logical block is written back to one (1) physical block, leaving the rest of the physical user blocks unallocated.
It should be obvious from the above description that the bitmap is used to manage physical blocks. It does not reflect which logical blocks are in use. Neither does it give any hint which physical blocks contain up to date data, all allocated blocks must be considered to find the lastest contents of a particular logical block. Also note that since the bitmap used "1" to mark a block as free, and "0" to mark it as allocated, updating the bitmap to reflect the fact that another block has been allocated does not require an erase operation. As long as no physical blocks are freed (this only happens when we erase the partition anyway), the updated bitmap can simply be written back to the flash memory with the desired result.
Now for the internal structure of a user block:
|
Each block thus holds 60 bytes of data. In addition, the logical block number and a checksum of the block is stored. For each logical block, the last physical block which has both this particular logical block number and a correct checksum is considered to contain the data for the logical block. Logical block numbers start with 0, so although the physical blocks for the user data range from 1 to M-1, the logical block numbers will be in the range 0..[M-2]. (It is not allowed to have more logical blocks than physical blocks, because that would make it impossible to fit them all into physical blocks.)
The checksum is computed on the first 62 bytes of the block (the logical block number and the user data payload). The CRC calculation algorithm is as follows (C code):
int calcCRC(const unsigned char *buf, int size) { int i, c, n = 0xffff; for (i = 0; i < size; i++) { n ^= (buf[i]<<8); for (c = 0; c < 8; c++) if (n & 0x8000) n = (n << 1) ^ 4129; else n = (n << 1); } return (~n) & 0xffff; }(This is exactly the same CRC algorithm as for the IP0000.BIN checksum, except that the result is inverted.)
The logical block number and checksum fields are stored in little endian byte order.
Important: The SEGA system libraries require that physical blocks be allocated in a strictly sequential order. That is, it is illegal to allocate block K before any of the blocks 1..[K-1]. While it is unlikely that anyone should desire to use a different allocation order, I'd like to stress this rule anyway, since the system libraries will corrupt the partition unless it is obeyed. Also note that this allocation scheme makes it simple to determine the most recent version of a logical block: Higher physical block number <=> More recent contents.
The format of each file is as follows:
|
The checksum is calculated on the 110 bytes starting at offset $02 and ending at offset $6F, that is everything before the CRC itself except the initial two bytes of the header. The CRC algorithm is exactly the same as the one used for the physical blocks (see above).
|
In order to allocate a slot, the game simply checks all slots in order until it finds one that doesn't have a valid header. The game can then save its own file in this slot (with a valid header of course). Naturally, the game must first check that it doesn't already have a slot allocated to it, by comparing the product number of all valid slots with its own.
If the game doesn't need all 120 bytes of user data, it doesn't have to write to all four blocks it has allocated. At least the first two must be written so that a valid header is established though. Be careful when checking for free slots not to assume that more than the first two blocks of a slot in use actually exist.