bevy_histrion_packer/
lib.rs1#![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
19pub const MAGIC: [u8; 4] = *b"HPAK";
21
22pub 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 Autoload(&'static str),
65 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}