yog_script_build/
index.rs1mod 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
20const CACHE: &str = "class-index";
22
23pub 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
46fn 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 .filter(|c| yog_script_analyze::index::namespace::is_game_class(&c.name))
60 .collect())
61}
62
63pub 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}