Encrypt and ship your DLC! Create your DLC logic and assets and securely unlock it at runtime with signed licenses (generated using bevy-dlc CLI).
Works with Bevy's asset pipeline.
Features
- AES-256-GCM encryption of DLC packs
- Efficient random-access decryption of specific assets without reading full packs
- Arbitrary pack-level metadata with typed access through
DlcPack - Product binding — prevent token reuse across games
- Security checks to prevent common mistakes like packing executables or other packs
- V5 pack format only; older
.dlcpackfiles must be repacked with the current CLI
Recommended Approach
- Use
bevy-dlcCLI to generate a public/private key pair and a signed license for your DLC. - Use
bevy-dlcCLI to create abase.dlcpackcontaining your base game assets, signing it with the same license. - In your game, include the public key and signed license (see examples) and add
DlcPluginto your app. - Load DLC assets with
AssetServer::load("base.dlcpack")and then you can useAssetServer::load("base.dlcpack#path/to/asset.png")or viaDlcPackLoadedevents to access specific assets from the pack. - When you want to ship DLC, create a new pack (e.g.
dlcA.dlcpack) with the new assets, signing it with the same license. The game can load it withAssetServer::load("dlcA.dlcpack")and access its assets withAssetServer::load("dlcA.dlcpack#path/to/dlc_asset.png")or viaDlcPackLoadedevents.
Install
Add to your Cargo.toml:
[!NOTE]
bevy-dlcwill always be compatible with Bevy 1.## with the minor version being used for bug fixes and new features. Sobevy-dlc = "1.18"will also work and automatically get you any compatible bug fixes and pack format version updates. Later update it usingcargo updateorcargo add bevy-dlc@latestto get the latest compatible version.
To use the CLI tool:
[!NOTE] For
v1.18.22install viacargo install --git https://github.com/captkirk88/bevy-dlc --tag v1.18.22untilsecure-gate v0.8.0is released.
Then bevy-dlc --help for available commands.
Quick Start
Generate a license
This will generate two files in keys/:
dlcA.slicense— a secure license token that can be safely embedded in your game binary (e.g. withsecure::include_secure_str_aes!()) or stored securely on disk. This token contains the encrypted symmetric key needed to unlock the DLC, but can't be decrypted without the private key.dlcA.pubkey— the public key that your game uses to verify the license and extract the symmetric key to unlock the DLC.- If you later want to add more DLC regenerate
Create a pack
my-game— product name bound into the pack and used for default.slicense/.pubkeylookupassets/dlcA— directory or file(s) to packdlcA— DLC ID (used in licenses to unlock this pack)-o dlc— output path for the generated.dlcpack--types— optional list of asset type paths to include in the pack index (e.g.bevy::prelude::Image), otherwise all assets will be indexed with their full type paths. This can be used to normalize type paths across different versions of Bevy or your game. The types you specify are fuzzy matched against the actual asset types in the pack, so you can just specifyassets::MyAssetand it will matchmy_game::assets::MyAssetin the pack if that's the actual type.--metadata key=value— optional pack-level metadata entry. Values are parsed as JSON when possible and otherwise stored as strings.
This creates a v5 dlcA.dlcpack and prints a signed license token.
Alternatively you can use bevy-dlc generate --help to review how to generate a signed license without packing, or bevy-dlc check --help to verify it.
[!NOTE]
bevy-dlc help <command>for detailed usage of each CLI command.
Edit a pack
You can edit the contents of a .dlcpack with bevy-dlc edit:
This opens an interactive REPL where you can add/remove files, list contents, or even merge entries from another .dlcpack. When merging or adding content you must supply an Signed License — just run bevy-dlc edit --signed-license <token> [--pubkey <key>] or keep .slicense/.pubkey files next to the pack (created using bevy-dlc generate). Changes are saved back to the .dlcpack when you save and if you forget and exit, REPL will ask you. REPL is not a AI.
Use metadata add <key> <value>, metadata remove <key>, and metadata list to manage pack-level metadata from the REPL. Metadata values are parsed as JSON when possible and otherwise stored as strings.
You can also use bevy-dlc edit <mydlc>.dlcpack -- <commands> to run REPL commands non-interactively (e.g. from a script or Makefile).
Use help within the REPL for available commands.
Watch source files
You can also ask the CLI to scan for .dlcpack files and watch their matching source files:
watch takes no arguments right now. It recursively scans the current directory for .dlcpack files, resolves each archived entry path against a real file on disk, and then watches only those resolved source paths. When a tracked file changes, bevy-dlc reloads just the metadata it needs and re-packs the changed entry back into the originating .dlcpack file. The command does not keep decrypted pack contents resident in memory between changes.
Press Ctrl+C to stop the watcher cleanly.
If a pack's product does not have a matching .slicense file available, or an archived path cannot be matched to a real file, that pack entry is skipped and reported at startup.
Usage
Review the examples for a complete example (run with cargo run --release --example <example>).
API Overview
DlcPackis a custom BevyAssetthat represents a loaded DLC pack. In v5, it uses a binary manifest, encrypted pack-level metadata, and block metadata to support efficient random-access decryption of assets from the.dlcpackfile on disk. You can load it directly withAssetServer::load("my_pack.dlcpack").- Pack-level metadata is encrypted with the same DLC encryption key as the archive blocks. It can be authored programmatically with
pack_encrypted_pack_with_metadata(...), inspected from raw containers withparse_encrypted_pack_info(reader, Some(&key)), and read back from loaded packs throughDlcPack::get_metadata::<T>(...)orDlcPack::get_metadata_raw(...)when you explicitly need the underlying JSON value. DlcPackEntryrepresents a single asset within a pack. Loading viaAssetServer::load("my_pack.dlcpack#path/to/asset.png")only decrypts the specific asset.DlcLoaderis the internal low-level loader that handles granular decryption and forwards resulting bytes to the appropriate concrete loader.- Events are emitted when packs are loaded:
DlcPackLoaded— emitted when a pack manifest is successfully parsed and ready for use.
- Finally,
DlcPluginis the main plugin that sets up the DLC system. It requires aDlcKey::Public(orPrivate) andSignedLicenseto unlock packs.
Suggestions and Contributions
Contributions are very welcome! Please open an issue or submit a pull request with any improvements, bug fixes, or new features.
[!NOTE] If your PR affects the pack format or encryption logic, it will be reviewed with extra scrutiny to ensure it doesn't introduce any security issues. Please include tests. The current format is v5-only.
Benchmarks
Benchmarks are included in benches/dlc_bench.rs and can be run with:
See the generated reports in reports/criterion for results.
Compatibility
| bevy | bevy-dlc |
|---|---|
| 0.18 | 1.18 |
License
MIT