RFF Format
Format type | Archive |
---|---|
Max files | 2,147,483,647 |
File Allocation Table (FAT) | End |
Filenames? | Yes, 8.3 |
Metadata? | None |
Supports compression? | No |
Supports encryption? | Yes |
Supports subdirectories? | No |
Hidden data? | Yes |
Games |
The RFF format is used by Blood, instead of the typical GRP Format used by other Build engine games. Later versions of the format encrypt the start of each file to avoid extraction by ripping utilities.
File format
Signature
All files begin with the four bytes: "RFF" followed by EOF (0x1A).
Header
The header is never encrypted.
Data type | Name | Description |
---|---|---|
char[4] | signature | "RFF\x1A" |
INT16LE | version | Format revision |
char[2] | pad1 | padding (unused) |
INT32LE | dictOffset | Offset of FAT |
INT32LE | dictEntries | Number of files |
char[16] | pad2 | header padding up to 32 bytes (unused) |
Known versions
- 0x0200 - shareware 0.99 (CD version) - FAT is not encrypted
- 0x0300 - registered 1.00 - FAT is encrypted
- 0x0301 - patches for registered and later shareware releases - FAT is encrypted
Note that Blood Alpha uses completely different format (except the four bytes signature).
File entry
At dictOffset, the following structure is repeated dictEntries times. When version are 0x300 or 0x301, the entire FAT is encrypted.
Data type | Name | Description |
---|---|---|
BYTE[16] | CACHENODE | preallocated buffer for internal structure (fill with 0x00) |
UINT32LE | offset | Offset of file's data, relative to start of archive file |
UINT32LE | size | File size |
UINT32LE | packedSize | always 0 (unused) |
time_t | time | Last modified time (in seconds since the UNIX epoch) |
UINT8 | flags | File state flags |
char[3] | type | File extension (see below for arrangement) |
char[8] | name | File name (see below for arrangement) |
UINT32LE | id | File id |
Valid flags values:
- 0x01 = kDictID - type and id pair will be used instead of type and name to find file in the archive
- 0x02 = kDictExternal - actual file not stored inside .RFF and must be loaded from the folder as name.type; note that game didn't obtain actual file size and reads only size bytes instead (WARNING: internal flag, BARF.EXE will remove it on archive update)
- 0x04 = kDictPreload - file will be preloaded on .RFF open
- 0x08 = kDictPrelock - file will be prelocked on .RFF open
- 0x10 = kDictEncrypted - the first 256 bytes of the file are encrypted (version >= 0x300)
The type and name field contains a standard 8.3 filename. Each will be terminated with zero byte if shorter than the actual field length.
The time is stored as UINT32LE in seconds since midnight, January 1st 1970. One quick example of use is the PHP code echo date('Y-m-d', time);
Encryption
FAT
The encryption algorithm for the FAT only applies to files of version 0x300 or 0x301. It is an 8-bit XOR cipher, with the key incrementing by one every two bytes. The tricky part is that the initial value for the key is the lower 8-bits of the FAT offset, so if the FAT moves for any reason it will need to be re-encrypted.
Example code: <syntaxhighlight lang="c"> // En/de-crypt the FAT if (version >= 0x300) {
uint8_t key = dictOffset & 0xFF; for (int i = 0; i < lenFAT; i++) { if (version == 0x300) { fat[i] ^= (key >> 1); key++; } else { fat[i] ^= key; key += (i & 1); } }
} </syntaxhightlight>
Since it is an XOR cipher, the same code applies to both encrypt and decrypt the FAT.
Files
If the flags value in the FAT entry has the fifth bit set (0x10) then the file is encrypted. The algorithm is a variation on that used for the FAT. The key is half the offset into the file (i.e. it is offset >> 1, which will cause it to start at zero for all files.) Only the first 256 bytes of each file are encrypted, the rest of the data is unchanged.
Example code: <syntaxhighlight lang="c"> // En/de-crypt a file if (flags & 0x10) {
for (int i = 0; i < 256; i++) { data[i] ^= (i >> 1); }
} </syntaxhightlight>
Again since it is an XOR cipher, the same code applies to both encrypt and decrypt the FAT.
Hidden data
Because there is much redundancy in the way the RFF format records the locations of files, there are a number of different ways that data can be hidden within the RFF that would not normally be visible.
As the FAT is located at an arbitrary location within the RFF file (although by convention at the end) it is possible to hide data by inserting it between the end of the last file and the start of the FAT. Although no known files or utilities store the FAT anywhere else except at the end of the file, it would be technically possible to store the FAT in the middle of the file, which would allow data to be hidden both before and after the FAT.
Due to the FAT storing both an offset and a size for each file, it is also trivial to store data in between files. This could be most easily accomplished by adding files as normal, then simply removing some entries from the FAT. The data will remain in the file but will be invisible to most utilities, and the other files will be unaffected by the change.
Tools
The following tools are able to work with files in this format.
Name | Platform | Extract files? | Decompress on extract? | Decrypt? | Create new? | Modify? | Compress on insert? | Encrypt? | Access hidden data? | Edit metadata? | Notes |
---|---|---|---|---|---|---|---|---|---|---|---|
Camoto | Linux/Windows | Yes | N/A | Yes | Yes | Yes | N/A | Yes | No | N/A | |
Camoto/gamearchive.js | Any | Yes | N/A | Yes | Yes | Yes | N/A | Yes | No | N/A | |
KBARF | Linux/Windows | Yes | N/A | Yes | No | No | N/A | No | No | N/A | with C source code, supports mask like *.KVX |
External links
- Planet Blood RFF info has some notes from the Blood project manager
Credits
This file format was documented here by following the source code of the Rebuild project's RFF utility.