diff options
Diffstat (limited to 'shared/src/respack.rs')
-rw-r--r-- | shared/src/respack.rs | 107 |
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) +} |