RFF Format

From ModdingWiki
Jump to: navigation, search
RFF Format
Format typeArchive
Max files2,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 0x3000 or 0x3001, 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 0x3000 or 0x3001. 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:

// 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);
    }
  }
}

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:

// En/de-crypt a file
if (flags & 0x10) {
  for (int i = 0; i < 256; i++) {
    data[i] ^= (i >> 1);
  }
}

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 PlatformExtract files? Decompress on extract?Decrypt? Create new? Modify? Compress on insert?Encrypt? Access hidden data? Edit metadata? Notes
Camoto Linux/WindowsYesN/AYesYesYesN/AYesNoN/A
KBARF Linux/WindowsYesN/AYesNoNoN/ANoNoN/A with C source code, supports mask like *.KVX

External links

Credits

This file format was documented here by following the source code of the Rebuild project's RFF utility.