An attempt to make a resource package format that allows to efficiently load resources at runtime with additional information on how to load and keep such resources in memory.
Provides an API that allows to build packages synchronously or asynchronosuly.
The Asynchronous API requires the user to properly handle write requests.
This library is provided under the MIT license. See LICENSE file for details.
The package format is separated into multiple sections allowing to quickly load and process available resource information without loading actual resource data or keeping the whole file in memory.
Streaming data from it should be the preffered way of usage. However, some data chunks might request to be always loaded in memory. In such cases, it's assumed that the data is crucial and accessed frequently.
Provides high-level information on specific format sections.
Tags: Stable, Base
Contains the formats MAGIC
and Version
and HeaderSize
fields. The ABI should never change, even for major versions.
This header should be used to properly determine the ABI version of the data and the size of the version-specific header section.
Tags: Overview, Version Specific,
Contains general information about the package and it's contents. The information can be grouped into the following:
- Pack info:
- Is Expansion, Is Patch
- Next Pack Offset (Total Size)
- Multiple packs can be present in a single file. This field points to where the next header would be stored. This also provides the packs total-size
- General data info:
- Num Chunks, Num Resources, Is Baked, Data Start Offset
- Other:
- App Version
- App version the pack was created with. This value shouldn't be used for picking logic paths at runtime, so apps are allowed to reuse it at their own discretion.
- App Custom Values
- App specific values that can contain additional pack specific information
- App Version
Tags: Data, Metadata, Version Specific
Contains information about a chunk of data that may contain resource data, resource metadata or app-specific data. Chunks should be used to group resource data that can be loaded and unloaded together to allow faster access at runtime.
For example, when loading a material it may consist of a shader and some textures. Storing them in the same chunk allows to load everything at once, and just point at the proper location during runtime. Once loaded into GPU memory the chunk can be released all at once.
The
offset
andsize
fields of a chunk are using the decompressed / decrypted representation.
When loading a chunk the memory allocated needs to be aligned to the chunks preffered align
value to avoid issues at runtime.
Other information available in chunks:
- Chunk Type - Type of data does the chunk stores.
AppSpecific/Undefined
- Data that cannot be defined or is specific to the application writing / readingt the pack.Data
- Raw resource dataMetadata
- Key-Value like structure containing additional information about the resource.Mixed
- Both resource raw data and metadata are stored in this chunk.
- Persistance - Suggested way of memory management at runtime
File Temporary
- Only load the resource in question, Once unloaded data should be freed immediately.File Regular
- Only load the resource in question, keep loaded unless memory needs to be reclaimed.Chunk Load If Possible
- Load the whole chunk and keep in memory if possible.Chunk Force Load
- Load the whole chunk and keep in memory. Data in this chunks is crucial and accessed frequently.
- Count Entries - How many entries are stored in this chunk.
- App Custom Value - Similar to pack custom values, this is a application defined value and can be used for anything.
Tags: Resource, Version Specific
Entries contain information about a single resource and where the name, data and metadata is stored. Accessing that data requires access to specific chunks or paths info.
Tags: Resource Name, Version Specific
A single block of data containing names of each stored resource. Each Entry
has an offset and size pointing into this block that defines it's actual name.
The following section provides some basic examples on how to read and write Hailstorm packages.
// Reads 'size' bytes of data into 'memory' from a file starting at 'file_offset'.
void read_from_file(FILE* file, void* memory, size_t size, size_t file_offset) { ... }
bool process_pack(FILE* file)
{
hailstorm::HailstormHeaderBase base_header;
read_from_file(file, &base_header, sizeof(base_header), 0);
if (base_header.magic != hailstorm::Constant_HailstormMagic)
{
return false;
}
if (base_header.version != hailstorm::Constant_HailstormHeaderVersionV0)
{
return false;
}
void* memory_full_header = malloc(base_header.header_size);
// Read again from file offset '0'
read_from_file(file, memory_full_header, base_header.header_size, 0);
hailstorm::Data const file_data{
.location = memory_full_header,
.size = base_header.size,
.align = 8 /* 64bit assumed allignment, just an example */
};
// Read the file data and fill in a helper struct.
hailstorm::HailstormData out_hailstorm_data;
hailstorm::Result const result =
// This function does NOT allocate, the 'out_hailstorm_data' struct is only valid as long as the backing memory 'file_data' is not released.
hailstorm::read_header(file_data, out_hailstorm_data);
if (result != hailstorm::Result::Success)
{
return false;
}
// Process the hailstorm header information...
// Release the header data. Invalidates 'out_hailstorm_data' struct.
free(memory_full_header);
}
Since writing a package is a bit more complex, even for the synchronous API's, it's not currently showcased in this repository.