rs2cache/
js5_masterindex.rs

1use crate::{
2    js5_compression::Js5Compression,
3    js5_index::{Js5Index, Js5Protocol},
4    store::{Store, ARCHIVESET},
5};
6use crc32fast::hash;
7use osrs_bytes::WriteExt;
8use std::{cmp, io::Write};
9
10const MASTERINDEXFORMAT_ORIGINAL: u8 = 0;
11const MASTERINDEXFORMAT_VERSIONED: u8 = 1;
12const MASTERINDEXFORMAT_DIGESTS: u8 = 2;
13const MASTERINDEXFORMAT_LENGTHS: u8 = 3;
14
15pub struct Js5MasterIndexEntry {
16    pub version: i32,
17    pub checksum: u32,
18    pub groups: usize,
19    pub total_uncompressed_length: u32,
20    pub digest: Option<[u8; 32]>,
21}
22
23pub struct Js5MasterIndex {
24    pub format: u8,
25    pub entries: Vec<Js5MasterIndexEntry>,
26}
27
28impl Js5MasterIndex {
29    pub fn write(&self) -> Vec<u8> {
30        let mut buf = Vec::new();
31
32        if self.format >= MASTERINDEXFORMAT_DIGESTS {
33            buf.write_u8(self.entries.len() as u8).unwrap();
34        }
35
36        for entry in &self.entries {
37            buf.write_u32(entry.checksum).unwrap();
38
39            if self.format >= MASTERINDEXFORMAT_VERSIONED {
40                buf.write_i32(entry.version).unwrap();
41            }
42
43            if self.format >= MASTERINDEXFORMAT_LENGTHS {
44                buf.write_i32(entry.groups as i32).unwrap();
45                buf.write_u32(entry.total_uncompressed_length).unwrap();
46            }
47
48            if self.format >= MASTERINDEXFORMAT_DIGESTS {
49                if let Some(digest) = &entry.digest {
50                    buf.write_all(digest).unwrap();
51                } else {
52                    buf.write_all(&[0; 32]).unwrap();
53                }
54            }
55        }
56
57        // TODO: More digest stuff on masterindex, likely caught by tests, so impl this later
58
59        buf
60    }
61
62    pub fn create(store: &Box<dyn Store>) -> Js5MasterIndex {
63        let mut master_index = Js5MasterIndex {
64            format: MASTERINDEXFORMAT_ORIGINAL,
65            entries: Vec::new(),
66        };
67
68        let mut next_archive = 0;
69        for archive in store.list(ARCHIVESET).unwrap() {
70            let read = store.read(ARCHIVESET, archive).unwrap();
71
72            let checksum = hash(&read);
73
74            let uncompress = Js5Compression::uncompress(read, None).unwrap();
75
76            let index = Js5Index::read(uncompress).unwrap();
77
78            if index.has_lengths {
79                master_index.format = cmp::max(master_index.format, MASTERINDEXFORMAT_LENGTHS);
80            } else if index.has_digests {
81                master_index.format = cmp::max(master_index.format, MASTERINDEXFORMAT_DIGESTS);
82            } else if index.protocol >= Js5Protocol::Versioned as u8 {
83                master_index.format = cmp::max(master_index.format, MASTERINDEXFORMAT_VERSIONED);
84            }
85
86            let version = index.version;
87            let groups = index.groups.len();
88            let total_uncompressed_length: u32 = index
89                .groups
90                .iter()
91                .map(|group| group.1.uncompressed_length)
92                .sum();
93
94            for _ in next_archive..archive {
95                master_index.entries.push(Js5MasterIndexEntry {
96                    version: 0,
97                    checksum: 0,
98                    groups: 0,
99                    total_uncompressed_length: 0,
100                    digest: None,
101                });
102            }
103
104            master_index.entries.push(Js5MasterIndexEntry {
105                version,
106                checksum,
107                groups,
108                total_uncompressed_length,
109                digest: None,
110            });
111
112            next_archive = archive + 1;
113        }
114
115        master_index
116    }
117}
118
119/*
120Implement tests once FlatFileStore is implemented
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::store::store_open;
126
127    #[test]
128    fn test_create_original() {
129        let storee = store_open("tests/data/master-index/original").unwrap();
130
131        let index = Js5MasterIndex::create(storee);
132    }
133
134    //const INVALID_KEY: [u32; 4] = [0x01234567, 0x89ABCDEF, 0x01234567, 0x89ABCDEF];
135}
136*/