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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use crate::{Result, Endian, Header, ResourceSizeTable, DIGEST};
use crc::Hasher32;
use indexmap::IndexMap;
use serde_derive::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

static WIIU_DATA: &str = include_str!("../data/wiiu.json");
static SWITCH_DATA: &str = include_str!("../data/switch.json");

lazy_static::lazy_static! {
    /// A hash table of vanilla BOTW resource hash values and
    /// their corresponding canonical paths.
    pub static ref STOCK_NAMES: HashMap<u32, String> = {
        let wiiu: Value = serde_json::from_str(WIIU_DATA).unwrap();
        let switch: Value = serde_json::from_str(SWITCH_DATA).unwrap();
        wiiu.as_object()
            .unwrap()
            .get("hash_map")
            .unwrap()
            .as_object()
            .unwrap()
            .keys()
            .map(|k| k.to_owned())
            .chain(
                switch.as_object()
                    .unwrap()
                    .get("hash_map")
                    .unwrap()
                    .as_object()
                    .unwrap()
                    .keys()
                    .map(|k| k.to_owned())
            )
            .map(|f| (crate::hash(&f), f))
            .collect()
    };
}

/// Creates a string representation of an entry hash. If the hash is in the default
/// hash table, the canonical path is returned. Otherwise the hash is converted to 
/// string.
pub fn string_from_hash(crc: u32) -> String {
    match STOCK_NAMES.get(&crc) {
        Some(s) => s.to_owned(),
        None => crc.to_string(),
    }
}

#[derive(Debug, Serialize, Deserialize)]
struct JsonRstb {
    hash_map: IndexMap<String, u32>,
    name_map: IndexMap<String, u32>,
}

impl From<&ResourceSizeTable> for JsonRstb {
    fn from(rstb: &ResourceSizeTable) -> JsonRstb {
        JsonRstb {
            hash_map: rstb
                .crc_entries
                .iter()
                .map(|(k, v)| (string_from_hash(*k), *v))
                .collect(),
            name_map: rstb.name_entries.clone(),
        }
    }
}

impl Into<ResourceSizeTable> for JsonRstb {
    fn into(self) -> ResourceSizeTable {
        let mut digest = DIGEST.lock().unwrap();
        ResourceSizeTable {
            header: Some(Header {
                crc_table_size: self.hash_map.len() as u32,
                name_table_size: self.name_map.len() as u32,
            }),
            crc_entries: self
                .hash_map
                .iter()
                .map(|(k, v)| {
                    let hash = match k.parse::<u32>() {
                        Ok(crc) => crc,
                        Err(_) => {
                            digest.write(k.as_bytes());
                            let h = digest.sum32();
                            digest.reset();
                            h
                        }
                    };
                    (hash, *v)
                })
                .collect(),
            name_entries: self.name_map,
        }
    }
}

impl ResourceSizeTable {
    /// Creates a new, blank RSTB. Probably not very useful, to be honest.
    pub fn new() -> ResourceSizeTable {
        ResourceSizeTable {
            header: Some(crate::Header {
                crc_table_size: 0,
                name_table_size: 0,
            }),
            crc_entries: IndexMap::new(),
            name_entries: IndexMap::new(),
        }
    }

    /// Creates a new copy of a stock BOTW RSTB. Passing `Endian::Big` will return the RSTB from
    /// the 1.5.0 Wii U version, and passing `Endian::Little` will return the RSTB from the 1.6.0
    /// Switch version.
    pub fn new_from_stock(endian: Endian) -> ResourceSizeTable {
        let json_rstb: JsonRstb = match endian {
            Endian::Big => serde_json::from_str(WIIU_DATA).unwrap(),
            Endian::Little => serde_json::from_str(SWITCH_DATA).unwrap(),
        };
        json_rstb.into()
    }

    /// Creates a text representation of an RSTB as a JSON string.
    pub fn to_text(&self) -> Result<String> {
        let table = JsonRstb::from(self);
        Ok(serde_json::to_string_pretty(&table)?)
    }

    /// Reads an RSTB from a JSON representation.
    pub fn from_text(text: &str) -> Result<ResourceSizeTable> {
        let table: JsonRstb = serde_json::from_str(text)?;
        Ok(table.into())
    }
}