use indexmap::IndexMap; #[cfg(target_os = "windows")] use log::debug; use log::{error, info, LevelFilter}; use rayon::prelude::*; use serde_derive::Deserialize; use simple_logger::SimpleLogger; use std::env; #[cfg(target_os = "windows")] use std::io; use std::io::Error; #[cfg(target_os = "windows")] use std::io::ErrorKind; use std::ops::Deref; use std::path::{Path, PathBuf}; use std::time::Instant; use std::{fs, thread, time}; use time::Duration; #[cfg(target_os = "windows")] use winreg::RegKey; mod cgg_data_source; mod data_source; mod kb_data_source; mod ms_data_source; mod pb_data_source; use cgg_data_source::CGGDataSource; use data_source::DataSource; use kb_data_source::KBDataSource; use ms_data_source::MSDataSource; use pb_data_source::PBDataSource; #[derive(Deserialize)] struct Realm { v: String, } #[derive(Deserialize)] pub struct Champion { data: IndexMap, } #[derive(Deserialize)] pub struct ChampInfo { id: String, name: String, key: String, } const USER_AGENT_VALUE: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0"; const DEFAULT_LOL_CHAMPS_DIR: &str = ".\\champs"; #[cfg(target_os = "windows")] const REG_KEY_LOL_RADS: &str = r"SOFTWARE\WOW6432Node\Riot Games\RADS"; #[cfg(target_os = "windows")] const REG_KEY_LOL_INC: &str = r"SOFTWARE\WOW6432Node\Riot Games, Inc\League of Legends"; #[cfg(target_os = "windows")] const REG_KEY_WIN_64_UNINSTALL: &str = r"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"; #[cfg(target_os = "windows")] const REG_KEY_WIN_UNINSTALL: &str = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"; fn main() -> Result<(), Box> { let args: Vec = env::args().collect(); let mut level = LevelFilter::Info; for s in &args { if s.eq_ignore_ascii_case("-v") || s.eq_ignore_ascii_case("--verbose") { level = LevelFilter::Debug; break; } } SimpleLogger::new() .with_level(level) .with_module_level("ureq", LevelFilter::Error) .with_module_level("rustls", LevelFilter::Error) .init()?; info!("CGG Item Sets"); let lol_champs_dir: PathBuf = match lol_champ_dir() { Ok(x) => x, Err(_e) => PathBuf::from(DEFAULT_LOL_CHAMPS_DIR), }; info!("LoL Champs Folder: {}", lol_champs_dir.to_str().unwrap()); let client: ureq::Agent = ureq::AgentBuilder::new() .user_agent(USER_AGENT_VALUE) .timeout(Duration::from_secs(10)) .build(); let realm: Realm = client .get("https://ddragon.leagueoflegends.com/realms/euw.json") .call()? .into_json()?; info!("LoL version: {}", realm.v); let champion: Champion = client .get(&format!( "https://ddragon.leagueoflegends.com/cdn/{}/data/en_US/champion.json", realm.v )) .call()? .into_json()?; info!("LoL numbers of champs: {}", champion.data.len()); let data_sources: Vec> = vec![ Box::new(PBDataSource), Box::new(CGGDataSource::new()), Box::new(KBDataSource::new()), Box::new(MSDataSource), ]; data_sources.par_iter().for_each(|data_source| { let init = Instant::now(); execute_data_source(data_source.deref(), &client, &champion, &lol_champs_dir); info!( "{}: done in {}s", data_source.get_alias(), init.elapsed().as_secs_f32() ); }); Ok(()) } fn get_champ_from_key(champs: &Champion, key: u32) -> Option { for champ in champs.data.values() { if key.to_string() == champ.key { return Some(champ.id.to_owned()); } } None } fn execute_data_source( data_source: &(dyn DataSource + Sync + Send), client: &ureq::Agent, champion: &Champion, lol_champs_dir: &Path, ) { let champs = data_source.get_champs_with_positions(client, champion); info!( "{} numbers of champs: {}", data_source.get_alias(), champs.len() ); if data_source.get_timeout() == 0 { champs.par_iter().for_each(|(id, positions)| { get_and_write_item_set( data_source, client, champion, lol_champs_dir, *id, positions, ); }); } else { champs.iter().for_each(|(id, positions)| { get_and_write_item_set( data_source, client, champion, lol_champs_dir, *id, positions, ); thread::sleep(Duration::from_millis(data_source.get_timeout())); }); }; } fn get_and_write_item_set( data_source: &(dyn DataSource + Sync + Send), client: &ureq::Agent, champion: &Champion, lol_champs_dir: &Path, id: u32, positions: &[String], ) { if let Some(champ_id) = get_champ_from_key(champion, id) { if let Some(champ) = champion.data.get(&champ_id) { if positions.is_empty() { error!("{}: {} empty positions", data_source.get_alias(), &champ_id); } else { let path = lol_champs_dir.join(&champ_id).join("Recommended"); fs::create_dir_all(&path).unwrap(); data_source.write_item_set(champ, positions, &path, client); } } else { error!("{} not found in LoL champs", &champ_id); } } } #[cfg(target_os = "windows")] fn lol_champ_dir() -> Result { let hklm = RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); let path = if let Ok(node) = hklm.open_subkey(REG_KEY_LOL_RADS) { debug!( "Use registry key {} for relative champ directory", REG_KEY_LOL_RADS ); let val: String = node.get_value("LocalRootFolder")?; // TODO: remplacer ce .unwrap() PathBuf::from(val).parent().unwrap().to_path_buf() } else if let Ok(node) = hklm.open_subkey(REG_KEY_LOL_INC) { debug!( "Use registry key {} for relative champ directory", REG_KEY_LOL_INC ); let val: String = node.get_value("Location")?; PathBuf::from(val) } else if let Ok(node) = find_subnode_from_path(hklm, REG_KEY_WIN_64_UNINSTALL, "League of Legends") { debug!( "Use registry key {} for relative champ directory", REG_KEY_WIN_64_UNINSTALL ); let val: String = node.get_value("InstallLocation")?; PathBuf::from(val) } else if let Ok(node) = find_subnode_from_path( RegKey::predef(winreg::enums::HKEY_CURRENT_USER), REG_KEY_WIN_UNINSTALL, "Riot Game league_of_legends.live", ) { debug!( "Use registry key {} for relative champ directory", REG_KEY_WIN_UNINSTALL ); let val: String = node.get_value("InstallLocation")?; PathBuf::from(val) } else { return Err(Error::from(ErrorKind::NotFound)); }; Ok(path.join("Config").join("Champions")) } #[cfg(not(target_os = "windows"))] fn lol_champ_dir() -> Result { Ok(PathBuf::from(DEFAULT_LOL_CHAMPS_DIR)) } #[cfg(target_os = "windows")] fn find_subnode_from_path(reg: RegKey, path: &str, key: &str) -> io::Result { if let Ok(node) = reg.open_subkey(path) { if let Some(k) = node .enum_keys() .map(|x| x.unwrap()) .find(|x| x.starts_with(key)) { return node.open_subkey(k); } } Err(Error::from(ErrorKind::NotFound)) }