raysan5 / rres

A simple and easy-to-use file-format to package resources
MIT License
391 stars 34 forks source link
c file-format packaging videogames


rres is a simple and easy-to-use file-format to package resources

rres has been designed to package game assets (images, fonts, text, audio, models...) into a simple self-contained comprehensive format, easy to read and write, designed to load data in a fast and efficient way.

rres is inspired by the following file-formats: XNB (used by XNA/MonoGame), RIFF, PNG and ZIP.



Index

  1. Design Goals
  2. Usage Benefits
  3. Design History
  4. File Structure
  5. File Header: rresFileHeader
  6. Resource Chunk: rresResourceChunk
    1. Resource Chunk Info: rresResourceChunkInfo
    2. Resource Chunk Data: rresResourceChunkData
    3. Resource Data Type: rresResourceDataType
    4. Resource Chunk: Central Directory: rresCentralDir
  7. Custom Engine Implementation
  8. License

Design Goals

Usage Benefits

There are some important reasons to package game assets data into a format like rres, here are some good reasons to do it.

Design History

rres has been in development since 2014. I started this project with the aim to create a packaging file-format similar to XNB for raylib. In the last 8 years the project has suffered multiple redesigns and improvements along a file-formats learning process. In that time I implemented loaders/writers for +20 different file formats and also created +12 custom file formats for multiple raylibtech custom tools.

rres file-format has gone through at least 4 big redesigns:

It's been an 8 years project, working on it on-and-off, with many redesigns and revisions but I'm personally very happy with the final result. rres is a resource packaging file-format at the level of any professional engine package format BUT free and open-source, available to any game developer that wants to use it, implement it or create a custom version.

File Structure

rres file format consists of a file header (rresFileHeader) followed by a number of resource chunks (rresResourceChunk). Every resource chunk has a resource info header (rresResourceChunkInfo) that includes a FOURCC data type code and resource data information. The resource data (rresResourceChunkData) contains a small set of properties to identify data, depending on the type and could contain some additional data at the end.

rres v1.0

Fig 01. rres v1.0 file structure.

_NOTE: rresResourceChunk(s) are generated from input files. It's important to note that resources could not be mapped to files 1:1, one input file could generate multiple resource chunks. For example, a .ttf input file could generate an image resource chunk (RRES_DATA_IMAGE type) plus a font glyph info resource chunk (RRES_DATA_FONT_GLYPHS type)._

rresFileHeader               (16 bytes)
    Signature Id              (4 bytes)     // File signature id: 'rres'
    Version                   (2 bytes)     // Format version
    Resource Count            (2 bytes)     // Number of resource chunks contained
    CD Offset                 (4 bytes)     // Central Directory offset (if available)
    Reserved                  (4 bytes)     // <reserved>

rresResourceChunk[]
{
    rresResourceChunkInfo    (32 bytes)
        Type                  (4 bytes)     // Resource type (FourCC)
        Id                    (4 bytes)     // Resource identifier (CRC32 filename hash or custom)
        Compressor            (1 byte)      // Data compression algorithm
        Cipher                (1 byte)      // Data encryption algorithm
        Flags                 (2 bytes)     // Data flags (if required)
        Packed data Size      (4 bytes)     // Packed data size (compressed/encrypted + custom data appended)
        Base data Size        (4 bytes)     // Base data size (uncompressed/unencrypted)
        Next Offset           (4 bytes)     // Next resource chunk offset (if required)
        Reserved              (4 bytes)     // <reserved>
        CRC32                 (4 bytes)     // Resource Chunk Data CRC32

    rresResourceChunkData     (n bytes)     // Packed data
        Property Count        (4 bytes)     // Number of properties contained
        Properties[]          (4*i bytes)   // Resource data required properties, depend on Type
        Data                  (m bytes)     // Resource data
}

File Header: rresFileHeader

The following C struct defines the rresFileHeader:

// rres file header (16 bytes)
typedef struct rresFileHeader {
    unsigned char id[4];            // File identifier: rres
    unsigned short version;         // File version: 100 for version 1.0
    unsigned short chunkCount;      // Number of resource chunks in the file (MAX: 65535)
    unsigned int cdOffset;          // Central Directory offset in file (0 if not available)
    unsigned int reserved;          // <reserved>
} rresFileHeader;
Field Description
id File signature identifier, it must be the four characters: r,r,e,s.
version Defines the version and subversion of the format.
chunkCount Number of resource chunks present in the file. Note that it could be greater than the number of input files processed.
cdOffset Central Directory absolute offset within the file, note that CDIR is just another resource chunk type, Central Directory can be present in the file or not. It's recommended to be placed as the last chunk in the file if a custom rres packer is implemented. Check rresCentralDir section for more details.
reserved This field is reserved for future additions if required.

Table 01. rresFileHeader fields description and details

Considerations:

Resource Chunk: rresResourceChunk

rres file contains a number of resource chunks. Every resource chunk represents a self-contained pack of data. Resource chunks are generated from input files on rres file creation by the rres packer tool; depending on the file extension, the rres packer tool extracts the required data from the file and generates one or more resource chunks. For example, for an image file, a resource chunk of type RRES_DATA_IMAGE is generated containing only the pixel data of the image and the required properties to read that data back from the resource file.

It's important to note that one input file could generate several resource chunks when the rres file is created. For example, a .ttf input file could generate an RRES_DATA_IMAGE resource chunk plus a RRES_DATA_FONT_GLYPHS resource chunk; it's also possible to just pack the file as a plain RRES_DATA_RAW resource chunk type, in that case the input file is not processed, just packed as raw data.

On rres creation, rres packer could create an additional resource chunk of type RRES_DATA_DIRECTORY containing data about the processed input files. It could be useful in some cases, for example to relate the input filename directly to the generated resource(s) id and also to extract the data in a similar file structure to the original input one.

Every resource chunk is divided in two parts: rresResourceChunkInfo + rresResourceData.

Resource Chunk Info: rresResourceChunkInfo

The following C struct defines the rresResourceChunkInfo:

// rres resource chunk info header (32 bytes)
typedef struct rresResourceChunkInfo {
    unsigned char type[4];          // Resource chunk type (FourCC)
    unsigned int id;                // Resource chunk identifier (generated from filename CRC32 hash)
    unsigned char compType;         // Data compression algorithm
    unsigned char cipherType;       // Data encription algorithm
    unsigned short flags;           // Data flags (if required)
    unsigned int packedSize;        // Data chunk size (compressed/encrypted + custom data appended)
    unsigned int baseSize;          // Data base size (uncompressed/unencrypted)
    unsigned int nextOffset;        // Next resource chunk global offset (if resource has multiple chunks)
    unsigned int reserved;          // <reserved>
    unsigned int crc32;             // Data chunk CRC32 (propCount + props[] + data)
} rresResourceChunkInfo;
Field Description
type A FourCC code and identifies the type of resource data contained in the rresResourceChunkData. Enum rresResourceDataType defines several data types, new ones could be added if required.
id A global resource identifier, it's generated from input filename using a CRC32 hash and it's not unique. One input file can generate multiple resource chunks, all the generated chunks share the same identifier and they are loaded together when the resource is loaded. For example, an input .ttf could generate two resource chunks (RRES_DATA_IMAGE + RRES_DATA_FONT_GLYPHS) with the same identifier that will be loaded together when their identifier is requested. It's up to the user to decide what to do with loaded data.
compType Defines the compression algorithm used for the resource chunk data. Compression depends on the middle library between rres and the engine, rres.h just defines some useful algorithm values to be used in case of implementing compression. Compression should always be applied before encryption and it compresses the full rresResourceData (Property Count + Properties[] + Data). If no data encryption is applied, packedSize defines the size of compressed data.
cipherType Defines the encryption algorithm used for the resource chunk data. Like compression, encryption depends on the middle library between rres and the engine, rres.h just defines some useful algorithm values to be used in case of implementing encryption. Encryption should be applied after compression. Depending on the encryption algorithm and encryption mode it could require some extra piece of data to be attached to the resource data (i.e encryption MAC), this is implementation dependent and the rres packer tool / rres middle library for the engines are responsible to manage that extra data. It's recommended to be just appended to resource data and considered on packedSize.
flags Reserved for additional flags, in case they are required by the implementation.
packedSize Defines the packed size (compressed/encrypted + additional user data) of rresResourceChunkData. Packaged data could contain appended user data at the end, after compressed/encrypted data, for example the nonce/MAC for the encrypted data, but it is implementation dependent, managed by the rres packer tool and the user library to load the chunks data into target engine structures.
baseSize Defines the base size (uncompressed/unencrypted) of rresResourceChunkData.
nextOffset Defines the global file position address for the next related resource chunk, it's useful for input files that generate multiple resources, like fonts or meshes.
reserved This field is reserved for future additions if required.
crc32 Calculated over the full rresResourceData chunk (packedSize) and it's intended to detect data corruption errors.

Table 02. rresResourceChunkInfo fields description and details

Resource Chunk Data: rresResourceChunkData

rresResourceChunkData contains the following data:

NOTE: rresResourceChunkData could contain additional user data, in those cases additional data size must be considered in packedSize.

Resource Data Type: rresResourceDataType

The resource type specified in the rresResourceChunkInfo defines the type of data and the number of properties contained in the resource chunk.

Here it is the currently defined data types. Please note that some input files could generate multiple resource chunks of multiple types. Additional resource types could be added if required.

// rres resource chunk data type
// NOTE 1: Data type determines the properties and the data included in every chunk
// NOTE 2: This enum defines the basic resource data types, some input files could generate multiple resource chunks
typedef enum rresResourceDataType {

    RRES_DATA_NULL         = 0,     // FourCC: NULL - Reserved for empty chunks, no props/data
    RRES_DATA_RAW          = 1,     // FourCC: RAWD - Raw file data, input file is not processed, just packed as is
    RRES_DATA_TEXT         = 2,     // FourCC: TEXT - Text file data, byte data extracted from text file
    RRES_DATA_IMAGE        = 3,     // FourCC: IMGE - Image file data, pixel data extracted from image file
    RRES_DATA_WAVE         = 4,     // FourCC: WAVE - Audio file data, samples data extracted from audio file
    RRES_DATA_VERTEX       = 5,     // FourCC: VRTX - Vertex file data, extracted from a mesh file
    RRES_DATA_FONT_GLYPHS  = 6,     // FourCC: FNTG - Font glyphs info, generated from an input font file
    RRES_DATA_LINK         = 99,    // FourCC: LINK - External linked file, filepath as provided on file input
    RRES_DATA_DIRECTORY    = 100,   // FourCC: CDIR - Central directory for input files relation to resource chunks

    // TODO: Add additional data types if required

} rresResourceDataType;

The currently defined data types consist of the following properties and data:

resource type FourCC propsCount props data
RRES_DATA_NULL NULL 0 - -
RRES_DATA_RAW RAWD 4 props[0]:size
props[1]:extension01
props[2]:extension02
props[3]:reserved
raw file bytes
RRES_DATA_TEXT TEXT 4 props[0]:size
props[1]:rresTextEncoding
props[2]:rresCodeLang
props[3]:cultureCode
text data
RRES_DATA_IMAGE IMGE 4 props[0]:width
props[1]:height
props[2]:rresPixelFormat
props[3]:mipmaps
pixel data
RRES_DATA_WAVE WAVE 4 props[0]:frameCount
props[1]:sampleRate
props[2]:sampleSize
props[3]:channels
audio samples data
RRES_DATA_VERTEX VRTX 4 props[0]:vertexCount
props[1]:rresVertexAttribute
props[2]:componentCount
props[3]:rresVertexFormat
vertex data
RRES_DATA_FONT_GLYPHS FNTG 4 props[0]:baseSize
props[1]:glyphCount
props[2]:glyphPadding
props[3]:rresFontStyle
rresFontGlyphInfo[0..glyphCount]
RRES_DATA_LINK LINK 1 props[0]:size filepath data
RRES_DATA_DIRECTORY CDIR 1 props[0]:entryCount rresDirEntry[0..entryCount]

Table 03. rresResourceDataType defined values and details

NOTE: RRES_DATA_RAW contains the packaged file extension as part of its properties, char extension is converted to a 4-byte unsigned int big-endian value, starting with a dot. i.e ".png" => 0x2e706e67. In case the extension was not relevant, the user implementation could decide to set those properties to 0.

rres.h defines the following enums for convenience to assign some of the properties:

Resource Chunk: Central Directory: rresCentralDir

The Central Directory resource chunk is a special chunk that could be present or not in the rres file, it stores information about the input files processed to generate the multiple resource chunks and could be useful to:

rres provides some helpful structures to deal with Central Directory resource chunk data:

// rres central directory entry
typedef struct rresDirEntry {
    unsigned int id;                // Resource id
    unsigned int offset;            // Resource global offset in file
    unsigned int reserved;          // reserved
    unsigned int fileNameSize;      // Resource fileName size (NULL terminator and 4-byte alignment padding considered)
    char fileName[RRES_MAX_CDIR_FILENAME_LENGTH];  // Resource original fileName (NULL terminated and padded to 4-byte alignment)
} rresDirEntry;

// rres central directory
// NOTE: This data represents the rresResourceChunkData
typedef struct rresCentralDir {
    unsigned int count;             // Central directory entries count
    rresDirEntry *entries;          // Central directory entries
} rresCentralDir;

NOTE: Central Directory filename entries are aligned to 4-byte padding to improve file-access times.

rres.h provides a function to load Central Directory from the rres file when available: rresLoadCentralDirectory() and also a function to get a resource identifiers from its original filename: rresGetIdFromFileName().

In case a rres file is generated with no Central Directory, a secondary header file (.h) should be provided with the id references for all resources, to be used in user code.

Custom Engine Implementation

rres is designed as an engine-agnostic file format that can be used with any game engine. Developers adopting rres can implement a custom library and a custom abstraction to map rres generic data to their own engines data structures and also custom rres packaging tools.

The following diagram shows a sample implementation of rres for raylib library.

rres v1.0

Fig 02. rres sample implementation: custom engine libs and tool.

rres implementation consist of several parts:

Base library: rres.h

Base rres library is in charge of reading rres files resource chunks into a generic resource structure, returned to the user. User exposed resource structure rresResourceChunkfollows rres specs structure (rresResourceChunkInfo + rresResourceChunkData). The following structures are provided:

// rres resource chunk
typedef struct rresResourceChunk {
    rresResourceChunkInfo info;     // Resource chunk info
    rresResourceChunkData data;     // Resource chunk packed data, contains propCount, props[] and raw data
} rresResourceChunk;

// rres resource chunk info header (32 bytes)
typedef struct rresResourceChunkInfo {
    unsigned char type[4];          // Resource chunk type (FourCC)
    unsigned int id;                // Resource chunk identifier (generated from filename CRC32 hash)
    unsigned char compType;         // Data compression algorithm
    unsigned char cipherType;       // Data encription algorithm
    unsigned short flags;           // Data flags (if required)
    unsigned int packedSize;        // Data chunk size (compressed/encrypted + custom data appended)
    unsigned int baseSize;          // Data base size (uncompressed/unencrypted)
    unsigned int nextOffset;        // Next resource chunk global offset (if resource has multiple chunks)
    unsigned int reserved;          // <reserved>
    unsigned int crc32;             // Data chunk CRC32 (propCount + props[] + data)
} rresResourceChunkInfo;

// rres resource chunk data
typedef struct rresResourceChunkData {
    unsigned int propCount;         // Resource chunk properties count
    unsigned int *props;            // Resource chunk properties
    void *raw;                      // Resource chunk raw data
} rresResourceChunkData;

// rres resource multi
// NOTE: It supports multiple resource chunks
typedef struct rresResourceMulti {
    unsigned int count;             // Resource chunks count
    rresResourceChunk *chunks;      // Resource chunks
} rresResourceMulti;

A single rresResourceChunk can be loaded from the .rres file with the provided function: rresLoadResourceChunk() and unloaded with rresUnloadResourceChunk().

A full rresResourceMulti can be loaded from the .rres file with the provided function: rresLoadResourceMulti() and unloaded with rresUnloadResourceMulti().

Engine mapping library: rres-raylib.h

The mapping library includes rres.h and provides functionality to map the resource chunks data loaded from the rres file into raylib structures. The API provided is simple and intuitive, following raylib conventions:

RLAPI void *LoadDataFromResource(rresResourceChunk chunk, int *size);   // Load raw data from rres resource chunk
RLAPI char *LoadTextFromResource(rresResourceChunk chunk);              // Load text data from rres resource chunk
RLAPI Image LoadImageFromResource(rresResourceChunk chunk);             // Load Image data from rres resource chunk
RLAPI Wave LoadWaveFromResource(rresResourceChunk chunk);               // Load Wave data from rres resource chunk
RLAPI Font LoadFontFromResource(rresResourceMulti multi);               // Load Font data from rres resource multiple chunks
RLAPI Mesh LoadMeshFromResource(rresResourceMulti multi);               // Load Mesh data from rres resource multiple chunks

RLAPI int UnpackResourceChunk(rresResourceChunk *chunk);                // Unpack resource chunk data (decompres/decrypt data)
RLAPI void SetBaseDirectory(const char *baseDir);                       // Set base directory for externally linked data

Note that data decompression/decryption is implemented in this custom library, UnpackResourceChunk() is provided for the users. rresResourceChunk contains compressor/cipher identifier values for convenience. Compressors and ciphers support depends on user implementation and it must be aligned with the packaging tool (rrespacker).

rres file-format is engine-agnostic, libraries and tools can be created for any engine/framework in any programming language.

Packaging tool: rrespacker

rrespacker is the rres packing tool in charge of processing all the input files and creating the rres file, following the specification. In case some compression/encryption algorithms are supported it must be implemented by this tool and the same algorithms should be supported by the mapping library, in our case rres-raylib.h.

rrespacker v1.0

Fig 03. rrespacker tool, GUI interface, it also supports CLI for batch processing.

rrespacker can be used online (or downloaded) from itch.io.

Versions

License

rres file-format specs, rres.h library and rres-raylib.h library are licensed under MIT license. Check LICENSE for further details.

Copyright (c) 2014-2024 Ramon Santamaria (@raysan5)