-
Notifications
You must be signed in to change notification settings - Fork 0
Asset Packer
Void's asset packer protects your work. When you release a game, your assets are your intellectual property. Within days of release, someone will extract your art, music, and levels and upload them to asset stores for profit or use them in their own games. Void prevents this.
Here is how asset theft works in practice:
- Someone buys your game on Steam or Itch.io
- They use readily available extraction tools to open your asset files
- They pull out every image, sound file, and level
- They list your entire asset library on asset stores as Royalty Free or Commercial Use
- Other developers buy these assets thinking they are legitimate
- Those developers use your assets in their own games and release them
- You discover your work being sold and used without your permission
- Those developers get DMCA takedown notices because they unknowingly used stolen work
- Everyone loses time, money, and reputation
Most engines leave your assets exposed by default. Unity asset bundles can be opened with free tools. Unreal PAK files are well documented. MonoGame and FNA leave everything as loose files on disk.
Void packs assets into encrypted, tamper-proof archives.
Security Features
- AES-GCM 256-bit encryption, the same standard used by governments and militaries
- Separate encryption for header and data sections
- PBKDF2 key derivation with salt
- Per-file CRC32 integrity verification
- Adaptive compression that never makes files larger
- Fast incremental updates that take seconds, not minutes
- Chunked encryption for large packs with per-chunk authentication
Performance Features
- Stream-based reading from disk - no full pack loaded into memory
- Lazy open - pack file opened on first read, closed after inactivity
- Only needed chunks decrypted on demand
- Memory usage = header size + current chunk size (not pack size)
- Handles 1000+ mod packs without resource exhaustion
- Thread-safe for concurrent asset loading
Pack Format Overview:
-
Bootstrap Header: The first 32 bytes of the file, never encrypted. Contains just enough information to decode the rest: magic bytes identifying the file as a Void pack, version number, flags indicating encryption and compression, header size, data size, file count, the encryption nonce, chunk size, and the compression algorithm used.
-
Encrypted Header: Contains the file table with every file in the pack. For each file, the header stores the virtual path, data offset, uncompressed size, stored size, compression flag, and CRC32 checksum. The header is fully encrypted. Without the key, it is completely unreadable.
-
Encrypted Data Section: Contains your actual asset data. All files are stored sequentially. For large packs, the data section is split into chunks. Each chunk is encrypted separately with its own authentication tag. Without the key, it is just random bytes.
-
Why Separate Encryption?: The header and data sections use different encryption nonces. An attacker cannot see file names or sizes without decrypting the header. They cannot extract individual files without decrypting the data section. Each section has its own authentication tag. This provides multiple layers of security and makes the pack format resistant to tampering.
-
Chunked Encryption: Large packs are split into configurable chunks (default 1MB). Each chunk is encrypted with a unique nonce derived from the chunk index and an AAD that binds it to the header. This means reading a single file only decrypts the chunk containing that file - not the entire pack. Tampering with any chunk or the header causes authentication failure.
-
Adaptive Compression: Void tests each file individually. If compression makes it smaller, it compresses. If compression doesn't help, it stores the file as-is. You never get a larger file than you started with. PNG and OGG files are stored uncompressed because they are already compressed. JSON, XML, and WAV files are compressed because they benefit from it.
Void includes a command-line tool for pack creation and management. See the CLI Tool page for full documentation.
var pack = AssetManager.Instance.LoadPack("GameAssets.pack");
AssetManager.Instance.AddMountToStart(pack);
The key is auto-detected from GameAssets.key next to the pack file.
You can split your assets by type for better organization:
var graphicsPack = AssetManager.Instance.LoadPack("Graphics.pack");
var audioPack = AssetManager.Instance.LoadPack("Audio.pack");
var levelsPack = AssetManager.Instance.LoadPack("Levels.pack");
AssetManager.Instance.AddMountToStart(graphicsPack);
AssetManager.Instance.AddMountToStart(audioPack);
AssetManager.Instance.AddMountToStart(levelsPack);
For large projects or teams, packs can be split into multiple files:
GameAssets-0.pack
GameAssets-1.pack
GameAssets-2.pack
LoadAllPacks discovers and loads all indexed packs in a directory:
var packs = AssetManager.Instance.LoadAllPacks("Packs/");
Each pack is loaded but not automatically mounted. You control the mount order:
foreach (var pack in packs)
{
AssetManager.Instance.AddMountToEnd(pack);
}
Pack loading never throws for runtime use. Use TryLoadPack for graceful error handling:
if (!Packer.TryLoadPack("Mod.pack", out var reader, out var error))
{
Console.WriteLine($"Failed to load mod: {error}");
return;
}
Error codes include: PackNotFound, InvalidKey, UnsupportedVersion, HeaderCorrupted, ChunkCorrupted, and more.
The key is stored separately from the pack. You decide how to distribute it:
- Embed in the game executable
- Download from a CDN
- Store on a secure server
- Distribute with the game
Each method has trade-offs between security and convenience. Embedded keys cannot be changed without rebuilding. CDN keys can be updated or revoked but require internet access. Separate files are simple but can be discovered.
No system is 100 percent secure. AES-GCM 256-bit encryption makes extraction extremely difficult, but a determined attacker with enough time and resources could theoretically find the key. The goal is to stop casual theft and make it harder to steal your work than it is to steal from other engines.