/* 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 . */ use crate::{packets::Resource, resources::RespackEntry, store::ResourceStore}; use anyhow::{Result, bail}; use log::{info, warn}; use std::{ collections::HashMap, 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>, ) -> Result<()> { info!("begin save"); 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::() + (32 + size_of::() * 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())?; } info!("end save"); Ok(()) } /// Copies the entire respack to the store. This is usually a dumb idea because the pack supportes on-demand reading. pub fn load_respack( input: impl Read + Seek, store: &ResourceStore, ) -> Result>> { let mut pack = RespackReader::open(input)?; let mut load_queue = Vec::new(); for res in pack.index.keys() { if store.get_raw_size(*res)?.is_none() { load_queue.push(*res); } } info!( "loading {} of {} resources from pack", load_queue.len(), pack.index.len() ); for res in load_queue { let mut buf = Vec::new(); pack.read(res)?.unwrap().read_to_end(&mut buf)?; let key = store.set_raw(&buf)?; if key != res { warn!("respack containes mislabeled resources") } } Ok(pack.entry) } pub struct RespackReader { entry: Option>, index: HashMap, inner: T, } impl RespackReader { pub fn open(mut inner: T) -> Result { let mut magic = [0u8; MAGIC.len()]; inner.read_exact(&mut magic)?; if magic != *MAGIC { bail!("wrong magic bytes"); } let mut entry = [0u8; 32]; inner.read_exact(&mut entry)?; let entry = if entry != [0u8; 32] { Some(Resource(entry, PhantomData)) } else { None }; let mut count = [0u8; size_of::()]; inner.read_exact(&mut count)?; let count = u64::from_be_bytes(count); let mut index = HashMap::new(); let mut found_entry = false; for _ in 0..count { let mut res = [0u8; 32]; let mut off = [0u8; size_of::()]; let mut size = [0u8; size_of::()]; inner.read_exact(&mut res)?; inner.read_exact(&mut off)?; inner.read_exact(&mut size)?; found_entry |= Some(Resource(res, PhantomData)) == entry; index.insert( Resource(res, PhantomData), (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!("opened respack with {} resources", index.len()); Ok(Self { entry, index, inner, }) } pub fn entry(&self) -> Option> { self.entry.clone() } pub fn get_size(&self, key: Resource) -> Option { self.index.get(&key).map(|(_off, size)| *size as usize) } pub fn iter(&self, mut cb: impl FnMut(Resource, usize)) { for (r, (_, s)) in &self.index { cb(*r, *s as usize) } } pub fn read(&mut self, key: Resource) -> Result> { let Some((off, size)) = self.index.get(&key).copied() else { return Ok(None); }; self.inner.seek(SeekFrom::Start(off))?; Ok(Some(self.inner.by_ref().take(size))) } }