summaryrefslogtreecommitdiff
path: root/shared/src/respack.rs
diff options
context:
space:
mode:
Diffstat (limited to 'shared/src/respack.rs')
-rw-r--r--shared/src/respack.rs107
1 files changed, 107 insertions, 0 deletions
diff --git a/shared/src/respack.rs b/shared/src/respack.rs
new file mode 100644
index 0000000..8c0b9d8
--- /dev/null
+++ b/shared/src/respack.rs
@@ -0,0 +1,107 @@
+/*
+ wearechat - generic multiplayer game with voip
+ Copyright (C) 2025 metamuffin
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, version 3 of the License only.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+use crate::{packets::Resource, resources::RespackEntry, store::ResourceStore};
+use anyhow::{Result, bail};
+use log::{info, warn};
+use std::{
+ io::{Read, Seek, SeekFrom, Write},
+ marker::PhantomData,
+};
+
+const MAGIC: &[u8; 16] = b"\x0f\x0cWEARE\x01RESPACK\x02";
+
+pub fn save_respack(
+ mut output: impl Write,
+ store: &ResourceStore,
+ resources: &[Resource],
+ entry: Option<Resource<RespackEntry>>,
+) -> Result<()> {
+ output.write_all(MAGIC)?;
+ output.write_all(&entry.map(|e| e.0).unwrap_or([0u8; 32]))?;
+ output.write_all(&u64::to_be_bytes(resources.len() as u64))?;
+ let mut off =
+ (MAGIC.len() + 32 + size_of::<u64>() + (32 + size_of::<u64>() * 2) * resources.len())
+ as u64;
+ for r in resources {
+ let size = store.get_raw_size(*r)?.unwrap() as u64;
+ output.write_all(&r.0)?;
+ output.write_all(&u64::to_be_bytes(off))?;
+ output.write_all(&u64::to_be_bytes(size))?;
+ off += size;
+ }
+ for r in resources {
+ output.write_all(&store.get_raw(*r)?.unwrap())?;
+ }
+ Ok(())
+}
+
+pub fn load_respack(
+ mut input: impl Read + Seek,
+ store: &ResourceStore,
+) -> Result<Option<Resource<RespackEntry>>> {
+ let mut magic = [0u8; MAGIC.len()];
+ input.read_exact(&mut magic)?;
+ if magic != *MAGIC {
+ bail!("wrong magic bytes");
+ }
+ let mut entry = [0u8; 32];
+ input.read_exact(&mut entry)?;
+ let entry = if entry != [0u8; 32] {
+ Some(Resource(entry, PhantomData))
+ } else {
+ None
+ };
+
+ let mut count = [0u8; size_of::<u64>()];
+ input.read_exact(&mut count)?;
+ let count = u64::from_be_bytes(count);
+
+ let mut load_queue = Vec::new();
+ let mut found_entry = false;
+ for _ in 0..count {
+ let mut res = [0u8; 32];
+ let mut off = [0u8; size_of::<u64>()];
+ let mut size = [0u8; size_of::<u64>()];
+ input.read_exact(&mut res)?;
+ input.read_exact(&mut off)?;
+ input.read_exact(&mut size)?;
+
+ found_entry |= Some(Resource(res, PhantomData)) == entry;
+ if store.get_raw_size(Resource(res, PhantomData))?.is_none() {
+ load_queue.push((res, u64::from_be_bytes(off), u64::from_be_bytes(size)))
+ }
+ }
+ if !found_entry && entry.is_some() {
+ warn!("respack does not contain its entry resource")
+ }
+ info!(
+ "loading {} of {count} resources from pack",
+ load_queue.len(),
+ );
+
+ for (res, off, size) in load_queue {
+ input.seek(SeekFrom::Start(off))?;
+ let mut buf = Vec::new();
+ input.by_ref().take(size).read_to_end(&mut buf)?;
+ let key = store.set_raw(&buf)?;
+ if key.0 != res {
+ warn!("respack containes mislabeled resources")
+ }
+ }
+
+ Ok(entry)
+}