summaryrefslogtreecommitdiff
path: root/shared/src/respack.rs
blob: 8c0b9d8e400fb153c2577eae64fe794b51a8579a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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)
}