bevy_dlc/macros.rs
1//! A collection of helper macros to reduce boilerplate when working with DLC packs
2//! and assets. These are primarily convenience utilities used by tests and examples
3//! (and available to library users) for quickly defining common patterns.
4//!
5//! The macros are all exported at the crate root so you can invoke them as
6//! `bevy_dlc::pack_items!()`, `bevy_dlc::dlc_register_types!()`, etc.
7
8#[doc(hidden)]
9pub fn __decode_embedded_signed_license_aes(encrypted_b64: &str, key: &str) -> crate::SignedLicense {
10 use base64::Engine as _;
11
12 let value = base64::prelude::BASE64_STANDARD
13 .decode(encrypted_b64.as_bytes())
14 .expect("invalid embedded base64 in signed license");
15 let cryptor = byte_aes::Aes256Cryptor::try_from(key)
16 .expect("invalid AES key for signed license");
17 let decoded_data = cryptor
18 .decrypt(value)
19 .expect("failed to decrypt embedded signed license");
20
21 // String::from_utf8 moves decoded_data without cloning — same heap
22 // allocation, now owned by SignedLicense, which zeroes it on drop
23 // via dynamic_alias!.
24 crate::SignedLicense::from(
25 String::from_utf8(decoded_data)
26 .expect("embedded signed license is not valid UTF-8"),
27 )
28}
29
30/// format a byte count into a human-readable string (KB/MB/GB)
31///
32/// The macro returns a `String`; the formatting matches the previous
33/// `human_bytes` helper, using two decimal places of precision for units
34/// greater than bytes.
35///
36/// Example usage (hidden from docs):
37/// ```ignore
38/// use bevy_dlc::human_bytes;
39/// let s = human_bytes!(123456);
40/// ```
41#[macro_export]
42#[doc(hidden)]
43macro_rules! human_bytes {
44 ($bytes:expr) => {{
45 let bytes: u64 = $bytes as u64;
46 const UNITS: [&str; 5] = ["B", "KB", "MB", "GB", "TB"];
47 let mut size = bytes as f64;
48 let mut unit = 0;
49 while size >= 1024.0 && unit < UNITS.len() - 1 {
50 size /= 1024.0;
51 unit += 1;
52 }
53 if unit == 0 {
54 format!("{} {}", size as u64, UNITS[unit])
55 } else {
56 format!("{:.2} {}", size, UNITS[unit])
57 }
58 }};
59}
60
61/// Builds a `Vec<PackItem>` from a list of entries. Each entry specifies a
62/// path and its raw contents; optional query-style suffixes allow specifying an
63/// explicit original extension or a type path as well.
64///
65/// Examples:
66///
67/// ```ignore
68/// use bevy_dlc::pack_items;
69///
70/// let items = pack_items![
71/// "note.txt" => b"hello",
72/// // override the extension that would otherwise be inferred
73/// "other" => b"data"; ext="bin",
74/// // supply a type path to help the runtime choose a loader (optional)
75/// "model" => b"objdata"; type="bevy::gltf::Gltf",
76/// ];
77/// ```
78#[macro_export]
79macro_rules! pack_items {
80 (
81 $(
82 $path:expr => $bytes:expr
83 $(; ext=$ext:expr)?
84 $(; type=$type_path:expr)?
85 ),* $(,)?
86 ) => {{
87 let mut v = Vec::new();
88 $(
89 let mut item = $crate::PackItem::new($path, $bytes).expect("forbidden extension or invalid dlcpack content");
90 $( item = item.with_extension($ext).expect("forbidden extension"); )?
91 $( item = item.with_type_path($type_path); )?
92 v.push(item);
93 )*
94 v
95 }};
96}
97
98/// Calls `App::register_dlc_type::<T>()` for each given type on the provided
99/// Bevy `App` instance.
100///
101/// Useful when registering several DLC-supported asset types in one line:
102///
103/// ```ignore
104/// use bevy::prelude::*;
105/// use bevy_dlc::prelude::*;
106///
107/// let mut app = App::new();
108/// // an `AssetPlugin` (or AssetLoaders for the asset types) must be added before registering
109/// // asset types; this mirrors real usage inside a Bevy application.
110/// app.add_plugins(AssetPlugin::default());
111/// // bring the `AppExt` trait into scope so the generated calls resolve
112/// dlc_register_types!(app, Image, Mesh);
113/// ```
114#[macro_export]
115macro_rules! dlc_register_types {
116 ($app:expr, $($ty:ty),* $(,)?) => {{
117 $(
118 $app.register_dlc_type::<$ty>();
119 )*
120 }};
121}
122
123/// Defines a simple "text-like" asset type along with a loader and a Bevy
124/// plugin for it. The generated loader reads the file as UTF-8 and stores a
125/// `String` in the asset. The plugin registers the loader and the DLC type.
126///
127/// This is the same pattern used throughout the repository for example assets
128/// (see `examples/mod.rs`) and in the test helpers. Rather than copy/paste the
129/// struct/loader/plugin boilerplate each time, this macro generates it for you.
130///
131/// The macro takes explicit identifiers for the asset struct, its loader, and
132/// the plugin; this avoids any need for identifier concatenation in
133/// `macro_rules!`.
134///
135/// Parameters:
136/// * `$name`: identifier for the new asset struct.
137/// * `$loader`: identifier for the asset/loader type.
138/// * `$plugin`: identifier for the plugin struct.
139/// * `$($ext:expr),+`: one or more file extensions the loader will recognise.
140///
141/// Example:
142///
143/// ```ignore
144/// // demonstration only; we don't actually run a Bevy app inside the doctest
145/// use bevy::prelude::AssetApp;
146/// use bevy_dlc::dlc_simple_asset;
147///
148/// dlc_simple_asset!(TextAsset, TextAssetLoader, TextAssetPlugin, "txt", "text");
149/// ```
150#[macro_export]
151macro_rules! dlc_simple_asset {
152 ($name:ident, $loader:ident, $plugin:ident, $($ext:expr),+ $(,)?) => {
153 #[derive(bevy::asset::Asset, bevy::reflect::Reflect, serde::Serialize, serde::Deserialize)]
154 pub struct $name(pub String);
155
156 #[derive(Default, bevy::reflect::Reflect)]
157 pub struct $loader;
158
159 impl bevy::asset::AssetLoader for $loader {
160 type Asset = $name;
161 type Settings = ();
162 type Error = std::io::Error;
163
164 async fn load(
165 &self,
166 reader: &mut dyn bevy::asset::io::Reader,
167 _settings: &(),
168 _load_context: &mut bevy::asset::LoadContext<'_>,
169 ) -> Result<Self::Asset, Self::Error> {
170 let mut bytes = Vec::new();
171 reader.read_to_end(&mut bytes).await?;
172 let s = String::from_utf8(bytes)
173 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
174 Ok($name(s))
175 }
176
177 fn extensions(&self) -> &[&str] {
178 static EXTS: &[&str] = &[$($ext),+];
179 EXTS
180 }
181 }
182
183 pub struct $plugin;
184
185 impl bevy::prelude::Plugin for $plugin {
186 fn build(&self, app: &mut bevy::prelude::App) {
187 app.init_asset_loader::<$loader>();
188 // call via fully qualified path so the `AppExt` trait need not be
189 // imported by the macro user.
190 // use `$crate` to refer to the current crate from derived code. `AppExt`
191 // is re-exported at the crate root, so we do not need to touch the
192 // private `ext` module here.
193 $crate::AppExt::register_dlc_type::<$name>(app);
194 }
195 }
196
197 impl Default for $plugin {
198 fn default() -> Self {
199 $plugin
200 }
201 }
202 };
203}
204
205/// Includes a secure license token and a public key from files.
206///
207/// Returns a `(DlcKey, SignedLicense)` tuple.
208#[macro_export]
209macro_rules! include_dlc_key_and_license_aes {
210 ($pubkey_path:expr, $license_path:expr, $license_key:expr $(,)?) => {{
211 let signed_license = $crate::include_signed_license_aes!($license_path, $license_key);
212 let dlc_key = $crate::DlcKey::public(include_str!($pubkey_path))
213 .expect("invalid dlc pubkey");
214 (dlc_key, signed_license)
215 }};
216}
217