Skip to main content

yog_script_build/
index.rs

1//! The class index of a Minecraft version
2//! (`yog_script_analyze::index`), built from the game's own client and server jars
3//! (Mojang's names: an obfuscated version's renamed by its mappings) and
4//! kept in Yog's cache. A class in both jars is shared, in one only that
5//! side's.
6
7mod jar;
8mod remap;
9
10use std::collections::{HashMap, HashSet};
11use std::path::PathBuf;
12
13pub use yog_script_analyze::index::{Class, ClassIndex, Side};
14
15use crate::classfile::ClassFile;
16use remap::Remap;
17use yog_mappings::fetch::{self, CLIENT_JAR, CLIENT_MAPPINGS, SERVER_JAR, SERVER_MAPPINGS};
18use yog_mappings::proguard;
19
20/// Where indexes are kept in the cache, and their files' extension.
21const CACHE: &str = "class-index";
22
23/// The class index of `mc`, from the cache or built (fetching the jars).
24pub fn load(mc: &str) -> Result<ClassIndex, String> {
25    let path = cache_path(mc)?;
26    if let Some(index) = std::fs::read(&path)
27        .ok()
28        .and_then(|b| yog_data::read_bytes::<ClassIndex>(&b).ok())
29    {
30        return Ok(index);
31    }
32    let index = build(mc)?;
33    if let Some(dir) = path.parent() {
34        std::fs::create_dir_all(dir).map_err(|e| format!("{}: {e}", dir.display()))?;
35    }
36    std::fs::write(&path, yog_data::to_bytes(&index))
37        .map_err(|e| format!("{}: {e}", path.display()))?;
38    Ok(index)
39}
40
41fn cache_path(mc: &str) -> Result<PathBuf, String> {
42    let file = format!("{mc}.{}", yog_ids::Format::Data.ext());
43    Ok(yog_home::Home::locate()?.cache_dir().join(CACHE).join(file))
44}
45
46/// One side's classes: its jar read, renamed to Mojang's names.
47fn side(mc: &str, jar: &str, mappings: &str) -> Result<Vec<ClassFile>, String> {
48    let bytes = jar::game_classes(&fetch::game_jar(mc, jar)?)?;
49    let remap = fetch::side_mappings(mc, mappings)?.map(|t| Remap::new(&proguard::parse(&t)));
50    Ok(bytes
51        .into_iter()
52        .filter_map(|b| crate::classfile::read(&b))
53        .map(|c| match &remap {
54            Some(r) => r.apply(c),
55            None => c,
56        })
57        // The game's own classes (the jars carry no others but obfuscated
58        // ones, which renaming puts in these).
59        .filter(|c| yog_script_analyze::index::namespace::is_game_class(&c.name))
60        .collect())
61}
62
63/// The index of `mc`, built from its jars.
64pub fn build(mc: &str) -> Result<ClassIndex, String> {
65    let client = side(mc, CLIENT_JAR, CLIENT_MAPPINGS)?;
66    let server = side(mc, SERVER_JAR, SERVER_MAPPINGS)?;
67    let on_server: HashSet<&str> = server.iter().map(|c| c.name.as_str()).collect();
68    let on_client: HashSet<&str> = client.iter().map(|c| c.name.as_str()).collect();
69    let mut classes: HashMap<String, Class> = HashMap::new();
70    for c in &client {
71        let side = match on_server.contains(c.name.as_str()) {
72            true => Side::Shared,
73            false => Side::Client,
74        };
75        classes.insert(c.name.clone(), class(c, side));
76    }
77    for c in server
78        .iter()
79        .filter(|c| !on_client.contains(c.name.as_str()))
80    {
81        classes.insert(c.name.clone(), class(c, Side::Server));
82    }
83    let mut classes: Vec<Class> = classes.into_values().collect();
84    classes.sort_by(|a, b| a.name.cmp(&b.name));
85    Ok(ClassIndex {
86        mc: mc.to_owned(),
87        classes,
88    })
89}
90
91fn class(c: &ClassFile, side: Side) -> Class {
92    Class {
93        name: c.name.clone(),
94        side,
95        access: c.access,
96        extends: c.extends.clone(),
97        implements: c.implements.clone(),
98        fields: c.fields.clone(),
99        methods: c.methods.clone(),
100    }
101}