Contents |
GRF is the archive file format used by Ragnarok Online.
GRF files uses the zlib algorithm for compression. They use a broken DES implementation for encrypting the filenames inside the archive, or for encrypting the file contents, or both (though sometimes nothing is encrypted at all). The only place their DES implementation is broken, is in generating the key schedule. Rather than bitwise-ORing, they bitwise-AND, thus making the key schedule 128 bytes of 0.
There are two GRF versions: version 1 and version 2. This document describes only version 2.
Note: all integers in this document are in little endian format.
Every GRF file (whether version 1 or version 2) has this header. The version number determines the other structures in the archive.
// Size: 45 bytes
struct GRF_Header { // Offset
unsigned char signature[16]; // 0
unsigned char allowEncryption[14]; // 16
uint32 fileTableOffset; // 30
uint32 number1; // 34
uint32 number2; // 38
uint32 version; // 42
};
"Master of Magic\0"
01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0EIn GRF files that do not allow encryption, the value is:
00 00 00 00 00 00 00 00 00 00 00 00 00 00GRF files that do not allow it are generally found after a "Ragnarok.exe /repak" command has been issued.
0x100 ≤ version ≤ 0x1FF
version ≤ 0x103 are found in the wild.
0x200 ≤ version ≤ 0x2FF
sizeof(GRF_Header) + fileTableOffset
number2 - number1 - 7. This is just Gravity's way to try to obfuscate the file format. So if you write a GRF writer you can put any values in number1 and number2 as long as the mentioned formula is correct.
The file table header contains an array of file metadata, in compressed form.
The location of the file table header can be found as follows:
sizeof(GRF_Header) + GRF_Header.fileTableOffset
The file table structure size is variable.
// Size: 8 + compressedLength
struct GRF2_FileTableHeader { // Offset
uint32 compressedLength; // 0
uint32 uncompressedLength; // 4
unsigned char body[compressedLength]; // 8
};
When we decompress the compressed file table body (which is compressed with zlib), we obtain the following structure:
// Size: GRF2_FileTableHeader.uncompressedLength
struct GRF2_FileTableBody {
GRF2_FileTableItem items[];
};
The file table body contains an array of file table items. The number of files in the archive* is the number of items in this array.
* See GRF_Header.number1, GRF_Header.number2
The file table item structure contains metadata about a file (or directory) inside the archive. The actual file's contents is not in this structure, but elsewhere in the GRF file. The location of the next item in the file table body's array depends on the size of the current item, which is variable.
// Size: sizeof(filename) + 17
struct GRF2_FileTableItem { // Offset
char filename[]; // 0
uint32 compressedLength; // sizeof(filename) + 0
uint32 compressedLength_aligned; // sizeof(filename) + 4
uint32 uncompressedLength; // sizeof(filename) + 8
uint8 flags; // sizeof(filename) + 12
uint32 offset; // sizeof(filename) + 13
};
sizeof(filename) is the size of the filename, including terminating NULL.
A file table item can also represent a directory, depending on the value of the flags field. Directories have have specific sizes and offsets*, even though no data is stored inside the GRF file:
| Field | Value |
|---|---|
| compressedLength | 1094 |
| compressedLength_aligned | 1812 |
| uncompressedLength | 1372 |
| offset | 1418 |
* Note that these directory values do not have to fit any specifications, as they are not used if the FILE flag is not set. They only maintain another way of ascertaining whether or not a file entry is a directory from official GRAVITY GRF packers.