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
use lazy_static::lazy_static;
use regex::Regex;
use std::path::Path;

pub mod extensions;
pub mod hashes;

/// Convert a path relative to a BOTW content root into a [canonical resource
/// path](https://zeldamods.org/wiki/Canonical_resource_path). Example:
///
/// ```
/// use botw_utils::get_canon_name;
/// assert_eq!(
///    get_canon_name("content\\Actor\\Pack\\Enemy_Lizalfos_Senior.sbactorpack").unwrap(),
///    "Actor/Pack/Enemy_Lizalfos_Senior.bactorpack"
/// );
/// ```
///
/// # Arguments
///
/// * `file_path` - The path of the BOTW game file relative to the root folder
///
/// # Returns
///
/// Returns an Option with the canonical resource path as a String or None if the path does not
/// appear valid
pub fn get_canon_name<P: AsRef<Path>>(file_path: P) -> Option<String> {
    lazy_static! {
        static ref RE: Regex = Regex::new(
            "(?i)(((Content|(atmosphere/(titles|contents)/)?01007EF00011E000/romfs)/)|\
            ((Aoc(/0010)?|(atmosphere/(titles|contents)/)?01007EF00011[ef]00[0-2]/romfs)/))"
        )
        .unwrap();
    }
    let mut normalized = file_path
        .as_ref()
        .to_str()
        .unwrap()
        .replace("\\", "/")
        .replace(".s", ".");
    normalized = RE
        .replace_all(&normalized, |caps: &regex::Captures| {
            if caps[0].starts_with("content") || caps[0].contains("01007EF00011E000") {
                "content/"
            } else {
                "aoc/0010/"
            }
        })
        .to_string();
    if normalized.starts_with("aoc/") {
        Some(
            normalized
                .replace("aoc/content", "Aoc")
                .replace("aoc", "Aoc"),
        )
    } else if normalized.starts_with("content") && !normalized.contains("/aoc") {
        Some(normalized.replace("content/", ""))
    } else {
        None
    }
}

/// Convert a BOTW game resource path without a root folder into a [canonical resource
/// path](https://zeldamods.org/wiki/Canonical_resource_path). Most useful for normalizing paths
/// to resources inside of SARC archives. Example:
///
/// ```
/// use botw_utils::get_canon_name_without_root;
/// assert_eq!(
///    get_canon_name_without_root("Actor/Pack/GameROMPlayer.sbactorpack"),
///    "Actor/Pack/GameROMPlayer.bactorpack"
/// );
/// ```
///
/// # Arguments
///
/// * `file_path` - The path of the BOTW game file, not relative to a root folder but bare. In most
/// cases this would only come from a path inside of a SARC.
///
/// # Returns
///
/// Returns the canonical resource path as a String.
pub fn get_canon_name_without_root<P: AsRef<Path>>(file_path: P) -> String {
    file_path
        .as_ref()
        .to_str()
        .unwrap()
        .replace("\\", "/")
        .replace(".s", ".")
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn canon_names() {
        assert_eq!(
            get_canon_name("content\\Actor\\Pack\\Enemy_Lizal_Senior.sbactorpack",).unwrap(),
            "Actor/Pack/Enemy_Lizal_Senior.bactorpack"
        );
        assert_eq!(
            get_canon_name("aoc/0010/Map/MainField/A-1/A-1_Dynamic.smubin",).unwrap(),
            "Aoc/0010/Map/MainField/A-1/A-1_Dynamic.mubin"
        );
        assert_eq!(
            get_canon_name(
                "atmosphere/contents/01007EF00011E000/romfs/Actor/ActorInfo.product.sbyml",
            )
            .unwrap(),
            "Actor/ActorInfo.product.byml"
        );
        assert_eq!(
            get_canon_name("atmosphere/contents/01007EF00011F001/romfs/Pack/AocMainField.pack",)
                .unwrap(),
            "Aoc/0010/Pack/AocMainField.pack"
        );
        assert_eq!(get_canon_name("Hellow/Sweetie.tardis"), None);
        assert_eq!(
            get_canon_name_without_root("Event/EventInfo.product.sbyml"),
            "Event/EventInfo.product.byml"
        )
    }
}