bevy_histrion_packer/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3#[cfg(not(any(windows, unix)))]
4compile_error!("bevy-histrion-packer is not supported on this platform");
5
6mod encoding;
7mod format;
8
9use std::path::PathBuf;
10
11use bevy::{
12    asset::io::{AssetReaderError, AssetSource, AssetSourceId},
13    prelude::*,
14};
15use thiserror::Error;
16
17pub use format::{CompressionMethod, HpakReader};
18
19/// The magic of the HPAK file format.
20pub const MAGIC: [u8; 4] = *b"HPAK";
21
22/// The format version of the HPAK file format.
23pub const VERSION: u32 = 6;
24
25pub type Result<T> = core::result::Result<T, Error>;
26
27#[derive(Debug, Error)]
28pub enum Error {
29    #[error("the archive has already been finalized")]
30    AlreadyFinalized,
31    #[error("duplicated hpak entry: {0}")]
32    DuplicateEntry(PathBuf),
33    #[error("hpak entry not found: {0}")]
34    EntryNotFound(PathBuf),
35    #[error("invalid hpak file format")]
36    InvalidFileFormat,
37    #[error("bad hpak version: {0}")]
38    BadVersion(u32),
39    #[error("invalid asset metadata: {0}")]
40    InvalidAssetMeta(String),
41    #[error("encountered an io error: {0}")]
42    Io(#[from] std::io::Error),
43    #[error("encountered an invalid alignment: {0}, must be a power of 2")]
44    InvalidAlignment(u64),
45    #[error("encountered an invalid utf8 error: {0}")]
46    InvalidUtf8(#[from] std::string::FromUtf8Error),
47}
48
49impl From<Error> for AssetReaderError {
50    fn from(err: Error) -> Self {
51        use Error::*;
52
53        match err {
54            EntryNotFound(path) => AssetReaderError::NotFound(path),
55            Io(err) => AssetReaderError::Io(err.into()),
56            err => AssetReaderError::Io(std::io::Error::other(format!("{}", err)).into()),
57        }
58    }
59}
60
61#[derive(Clone, Debug)]
62pub enum HistrionPackerMode {
63    /// Add a new [`AssetSource`] available through the `<source_id>://` source.
64    Autoload(&'static str),
65    /// Replace the default [`AssetSource`] with the hpak source for processed files only,
66    ///
67    /// it uses the default source for the current platform for unprocessed files.
68    ReplaceDefaultProcessed,
69}
70
71impl Default for HistrionPackerMode {
72    fn default() -> Self {
73        Self::ReplaceDefaultProcessed
74    }
75}
76
77pub struct HistrionPackerPlugin {
78    pub source: String,
79    pub mode: HistrionPackerMode,
80}
81
82impl Plugin for HistrionPackerPlugin {
83    fn build(&self, app: &mut bevy::prelude::App) {
84        let source = match std::env::current_exe() {
85            Ok(exe) => exe,
86            Err(err) => {
87                error!("cannot get current executable path: {err}");
88                return;
89            }
90        };
91
92        if !source.exists() {
93            error!("the source path does not exist or is not a file");
94            return;
95        }
96
97        let mut source = match source.canonicalize() {
98            Ok(path) => path,
99            Err(err) => {
100                error!("cannot canonicalize current executable path: {err}");
101                return;
102            }
103        };
104
105        source.pop();
106        source.push(&self.source);
107
108        match self.mode {
109            HistrionPackerMode::Autoload(source_id) => {
110                app.register_asset_source(
111                    AssetSourceId::Name(source_id.into()),
112                    AssetSource::build().with_reader(move || {
113                        let source = source.clone();
114                        Box::new(HpakReader::new(&source).unwrap())
115                    }),
116                );
117            }
118            HistrionPackerMode::ReplaceDefaultProcessed => {
119                if app.is_plugin_added::<AssetPlugin>() {
120                    error!("plugin HistrionPackerPlugin must be added before plugin AssetPlugin");
121                    return;
122                }
123
124                app.register_asset_source(
125                    AssetSourceId::Default,
126                    AssetSource::build()
127                        .with_reader(|| AssetSource::get_default_reader("assets".to_string())())
128                        .with_processed_reader(move || {
129                            let source = source.clone();
130                            Box::new(HpakReader::new(&source).unwrap())
131                        }),
132                );
133            }
134        }
135    }
136}
137
138#[cfg(feature = "writer")]
139pub mod writer {
140    use super::*;
141
142    pub use format::writer::*;
143}