Support OpenKore:
Learn about
the Fund Pool

GRF file format

Contents

Introduction

GRF is the archive file format used by Ragnarok Online.

Algorithms

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.

Multiple versions

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.


Header

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
};
signature
The value is "Master of Magic\0"
allowEncryption
In GRF files that allow encryption of the files inside the archive, the value of this field is:
01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E
In GRF files that do not allow encryption, the value is:
00 00 00 00 00 00 00 00 00 00 00 00 00 00
GRF files that do not allow it are generally found after a "Ragnarok.exe /repak" command has been issued.
version
The GRF archive version.
0x100 ≤ version ≤ 0x1FF
This indicates a version 1 GRF archive. At the moment, only GRF archives with version ≤ 0x103 are found in the wild.
0x102 and 0x103 archives seem to be identical. The difference between 0x101 and 0x102 is in the way the decryption key is generated.
0x101~0x103 archives always encrypt the filenames inside the archive, and they specify the length of the filenames. 0x100 archives obfuscates the filenames by swapping nibbles, and the filenames are NULL terminated.
0x200 ≤ version ≤ 0x2FF
This indicates a version 2 GRF archive. At the moment, only version 0x200 GRF archives are found in the wild.
fileTableOffset
The offset for the file table header, relative to the header length. The file table header is located at the following offset:
sizeof(GRF_Header) + fileTableOffset
number1, number2
The number of files in the GRF archive is 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.


File table header

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
};
compressedLength
The length of the file table body in compressed form.
uncompressedLength
The length of the file table body in uncompressed form.
body
The file table body, in compressed form. It is compressed with the zlib algorithm.


File table body

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


File table item

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
};
filename
The filename of this item, including terminating NULL. This filename is usually encoded in the EUC-KR encoding. The size of this field is variable. The offset of the other fields in this structure depends on the size of this field.
Note: The notation sizeof(filename) is the size of the filename, including terminating NULL.
compressedLength
The size of the file in compressed form.
compressedLength_aligned
compressedLength, rounded up to the nearest multiple of 4. Used for encryption.
uncompressedLength
The size of the file in uncompressed form.
flags
A bitmask which indicates what this item is. The following flags are known:
0x01 (FILE)
Whether this flag is a file. If this flag is not set, then this item is a directory. See also below for notes about directories.
0x02 (MIXCRYPT)
Indicates that the file uses mixed crypto. (TODO: explain this)
0x04 (DES)
Indicates that only the first 0x14 blocks are encrypted. (TODO: explain this)
offset
The offset of the file content (which is in compressed form) inside the GRF archive.

Directory notes

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.