diff --git a/src/client.rs b/src/client.rs index 2de91d4b..d821e2df 100644 --- a/src/client.rs +++ b/src/client.rs @@ -18,7 +18,7 @@ use tokio::runtime::Runtime; use crate::command; use crate::config; use crate::package; -use crate::package::ChoiceInfo; +use crate::package_metadata; use crate::user_env; #[derive(NativeClass)] @@ -45,11 +45,11 @@ impl SignalEmitter { #[inherit(Node)] pub struct LuxClient { receiver: std::option::Option>, - last_downloads: std::option::Option>, + last_downloads: std::option::Option>, last_choice: std::option::Option, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Default, Serialize, Deserialize, Debug)] pub struct StatusObj { pub label: std::option::Option, pub progress: std::option::Option, @@ -92,12 +92,8 @@ impl LuxClient { fn show_error(&mut self, base: &Node, error: std::io::Error) { let status_obj = StatusObj { - label: None, - progress: None, - complete: false, - log_line: None, error: Some(error.to_string()), - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); let emitter = &mut base.get_node("Container/Progress").unwrap(); @@ -177,7 +173,7 @@ impl LuxClient { } }; - match package::update_packages_json() { + match package_metadata::PackageMetadata::update_packages_json() { Ok(()) => {} Err(err) => { return Err(err); @@ -210,124 +206,15 @@ impl LuxClient { } fn ask_for_engine_choice(&mut self, app_id: &str, owner: &Node) -> io::Result<()> { - let game_info = match package::get_game_info(app_id) { + let mut game_info = match package::get_game_info(app_id) { Ok(game_info) => game_info, Err(err) => { return Err(err); } }; - if game_info.is_null() { - return Err(Error::new(ErrorKind::Other, "Unknown app_id")); - } - - if !game_info["choices"].is_null() { - let engines_option = package::get_engines_info(); - - let mut choices: Vec = vec![]; - for entry in game_info["choices"].members() { - if entry["name"].is_null() { - return Err(Error::new(ErrorKind::Other, "missing choice info")); - } - - let mut choice_info = ChoiceInfo { - name: entry["name"].to_string(), - notices: Vec::new(), - }; - - let mut engine_name = entry["name"].to_string(); - if !entry["engine_name"].is_null() { - engine_name = entry["engine_name"].to_string(); - } - - let engine_name_clone = engine_name.clone(); - if let Some((ref engines, ref notice_map)) = engines_option { - if !engines[engine_name_clone].is_null() { - let engine_name_clone_clone = engine_name.clone(); - let engine_name_clone_clone_two = engine_name.clone(); - let engine_name_clone_clone_three = engine_name.clone(); - let engine_name_clone_clone_four = engine_name.clone(); - - if !engines[engine_name_clone_clone]["notices"].is_null() { - for entry in engines[engine_name]["notices"].members() { - choice_info - .notices - .push(package::convert_notice_to_str(entry, notice_map)); - } - } - - let controller_not_supported = - engines[engine_name_clone_clone_two]["controllerNotSupported"] == true; - let controller_supported = - engines[engine_name_clone_clone_three]["controllerSupported"] == true; - let controller_supported_manual = engines[engine_name_clone_clone_four] - ["controllerSupportedManualGame"] - == true; - - if controller_not_supported { - choice_info - .notices - .push("Engine Does Not Have Native Controller Support".to_string()); - } else if controller_supported - && game_info["controllerSteamDefault"] == true - { - choice_info.notices.push( - "Engine Has Native Controller Support And Works Out of the Box" - .to_string(), - ); - } else if controller_supported_manual - && game_info["controllerSteamDefault"] == true - { - choice_info.notices.push( - "Engine Has Native Controller Support But Needs Manual In-Game Settings" - .to_string(), - ); - } else if controller_supported - && (game_info["controllerSteamDefault"].is_null() - || game_info["controllerSteamDefault"] != true) - { - choice_info.notices.push( - "Engine Has Native Controller Support But Needs Manual Steam Settings" - .to_string(), - ); - } - } - - if game_info["cloudNotAvailable"] == true { - choice_info - .notices - .push("Game Does Not Have Cloud Saves".to_string()); - } else if game_info["cloudAvailable"] == true - && (game_info["cloudSupported"].is_null() - || game_info["cloudSupported"] != true) - { - choice_info - .notices - .push("Game Has Cloud Saves But Unknown Status".to_string()); - } else if game_info["cloudAvailable"] == true - && game_info["cloudSupported"] == true - { - choice_info - .notices - .push("Cloud Saves Supported".to_string()); - } else if game_info["cloudAvailable"] == true && game_info["cloudIssue"] == true - { - choice_info - .notices - .push("Cloud Saves Not Supported".to_string()); - } - - if !game_info["notices"].is_null() { - for entry in game_info["notices"].members() { - choice_info - .notices - .push(package::convert_notice_to_str(entry, notice_map)); - } - } - } - - choices.push(choice_info); - } + if game_info.choices.is_some() { + let choices = game_info.choices_with_notices(); let check_default_choice_file_path = package::place_config_file(app_id, "default_engine_choice.txt")?; @@ -453,8 +340,8 @@ impl LuxClient { } } - if !game_info["app_ids_deps"].is_null() { - match package::get_app_id_deps_paths(&game_info["app_ids_deps"]) { + if let Some(app_ids_deps) = &game_info.app_ids_deps { + match package::get_app_id_deps_paths(app_ids_deps) { Some(()) => { info!("download_all. get_app_id_deps_paths completed"); } @@ -464,12 +351,6 @@ impl LuxClient { } } - if game_info["download"].is_null() { - info!("skipping downloads (no urls defined for this package)"); - self.run_game(false); - return; - } - let downloads = package::json_to_downloads(app_id.as_str(), &game_info).unwrap(); if downloads.is_empty() { @@ -478,37 +359,9 @@ impl LuxClient { return; } - let mut dialog_message = String::new(); - - let mut engine_name = game_info["name"].to_string(); - if !game_info["engine_name"].is_null() { - engine_name = game_info["engine_name"].to_string(); - } - - let engines_option = package::get_engines_info(); - let engine_name_clone = engine_name.clone(); - let engine_name_clone2 = engine_name.clone(); - if let Some((ref engines, ref _notice_map)) = engines_option { - if !engines[engine_name_clone].is_null() { - for entry in engines[engine_name]["notices"].members() { - let engine_name_clone3 = engine_name_clone2.clone(); - if !entry["key"].is_null() { - if entry["key"] == "non_free" { - dialog_message = std::format!( - "This engine uses a non-free engine ({0}). Are you sure you want to continue?", - engines[engine_name_clone3]["license"] - ); - } else if entry["key"] == "closed_source" { - dialog_message = "This engine uses assets from the closed source release. Are you sure you want to continue?".to_string(); - } - } - } - } - } - self.last_downloads = Some(downloads); - if !dialog_message.is_empty() { + if let Some(dialog_message) = game_info.find_license_dialog_message() { let prompt_request = PromptRequestData { label: Some(dialog_message), prompt_type: "question".to_string(), @@ -642,11 +495,7 @@ impl LuxClient { let status_obj = StatusObj { label: Some(label_str), - progress: None, - complete: false, - log_line: None, - error: None, - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); @@ -664,12 +513,8 @@ impl LuxClient { error!("{}", error_str); let status_obj = StatusObj { - label: None, - progress: None, - complete: false, - log_line: None, error: Some(error_str), - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); @@ -695,12 +540,8 @@ impl LuxClient { if !found_error { let status_obj = StatusObj { - label: None, - progress: None, complete: true, - log_line: None, - error: None, - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); @@ -711,7 +552,7 @@ impl LuxClient { async fn download( app_id: &str, - info: &package::PackageInfo, + info: &package_metadata::DownloadItem, sender: std::sync::mpsc::Sender, client: &Client, ) -> io::Result<()> { @@ -761,12 +602,8 @@ impl LuxClient { ); let status_obj = StatusObj { - label: None, progress: Some(percentage), - complete: false, - log_line: None, - error: None, - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); @@ -806,12 +643,8 @@ impl LuxClient { error!("command::run err: {:?}", err); let status_obj = StatusObj { - label: None, - progress: None, - complete: false, - log_line: None, error: Some(err.to_string()), - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender_err.send(status_str).unwrap(); @@ -820,90 +653,75 @@ impl LuxClient { } }; - if !game_info["setup"].is_null() - && !after_setup_question_mode - && !package::is_setup_complete(&game_info["setup"]) - { - match command::process_setup_details(&game_info) { - Ok(setup_details) => { - info!("setup details ready: {:?}", setup_details); - - if !setup_details.is_empty() { - let prompt_items = PromptItemsData { - prompt_items: setup_details, - prompt_id: "allpromptssetup".to_string(), - }; + if let Some(setup_info) = &game_info.setup { + if !after_setup_question_mode && !package::is_setup_complete(setup_info) { + match command::process_setup_details(setup_info) { + Ok(setup_details) => { + info!("setup details ready: {:?}", setup_details); + + if !setup_details.is_empty() { + let prompt_items = PromptItemsData { + prompt_items: setup_details, + prompt_id: "allpromptssetup".to_string(), + }; + let status_obj = StatusObj { + log_line: Some("Processing setup items".to_string()), + prompt_items: Some(prompt_items), + ..Default::default() + }; + let status_str = serde_json::to_string(&status_obj).unwrap(); + sender_err.send(status_str).unwrap(); + + return; + } else { + match command::run_setup(setup_info, &sender) { + Ok(()) => {} + Err(err) => { + error!("command::run_setup err: {:?}", err); + + let status_obj = StatusObj { + error: Some(err.to_string()), + ..Default::default() + }; + let status_str = + serde_json::to_string(&status_obj).unwrap(); + sender_err.send(status_str).unwrap(); + + return; + } + } + } + } + Err(err) => { + error!("command::process_setup_details err: {:?}", err); + let status_obj = StatusObj { - label: None, - progress: None, - complete: false, - log_line: Some("Processing setup items".to_string()), - error: None, - prompt_items: Some(prompt_items), + error: Some(err.to_string()), + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender_err.send(status_str).unwrap(); return; - } else { - match command::run_setup(&game_info, &sender) { - Ok(()) => {} - Err(err) => { - error!("command::run_setup err: {:?}", err); - - let status_obj = StatusObj { - label: None, - progress: None, - complete: false, - log_line: None, - error: Some(err.to_string()), - prompt_items: None, - }; - let status_str = serde_json::to_string(&status_obj).unwrap(); - sender_err.send(status_str).unwrap(); - - return; - } - } } } - Err(err) => { - error!("command::process_setup_details err: {:?}", err); - - let status_obj = StatusObj { - label: None, - progress: None, - complete: false, - log_line: None, - error: Some(err.to_string()), - prompt_items: None, - }; - let status_str = serde_json::to_string(&status_obj).unwrap(); - sender_err.send(status_str).unwrap(); - - return; - } } - } - if after_setup_question_mode { - match command::run_setup(&game_info, &sender) { - Ok(()) => {} - Err(err) => { - error!("command::run_setup err: {:?}", err); + if after_setup_question_mode { + match command::run_setup(setup_info, &sender) { + Ok(()) => {} + Err(err) => { + error!("command::run_setup err: {:?}", err); - let status_obj = StatusObj { - label: None, - progress: None, - complete: false, - log_line: None, - error: Some(err.to_string()), - prompt_items: None, - }; - let status_str = serde_json::to_string(&status_obj).unwrap(); - sender_err.send(status_str).unwrap(); + let status_obj = StatusObj { + error: Some(err.to_string()), + ..Default::default() + }; + let status_str = serde_json::to_string(&status_obj).unwrap(); + sender_err.send(status_str).unwrap(); - return; + return; + } } } } @@ -914,12 +732,8 @@ impl LuxClient { error!("command::run_wrapper err: {:?}", err); let status_obj = StatusObj { - label: None, - progress: None, - complete: false, - log_line: None, error: Some(err.to_string()), - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender_err.send(status_str).unwrap(); diff --git a/src/command.rs b/src/command.rs index dbe12f1d..691d40df 100644 --- a/src/command.rs +++ b/src/command.rs @@ -16,6 +16,7 @@ use crate::client; use crate::config; use crate::package; use crate::package::place_state_file; +use crate::package_metadata; use crate::user_env; extern crate log; @@ -43,34 +44,22 @@ pub fn usage() { println!("usage: lux [run | wait-before-run | manual-download] []"); } -fn json_to_args(args: &json::JsonValue) -> Vec { - args.members() - .map(|j| j.as_str()) - .skip_while(|o| o.is_none()) // filter? - .map(|j| j.unwrap().to_string()) - .collect() -} - -fn find_game_command(info: &json::JsonValue, args: &[&str]) -> Option<(String, Vec)> { +fn find_game_command( + info: &package_metadata::Game, + args: &[&str], +) -> Option<(String, Vec)> { let orig_cmd = args.join(" "); - if !info["command"].is_null() { - let new_prog = info["command"].to_string(); - let new_args = json_to_args(&info["command_args"]); - return Some((new_prog, new_args)); + if let Some(command) = &info.command { + return Some((command.to_string(), info.command_args.clone())); } - if info["commands"].is_null() { - return None; - } - - let cmds = &info["commands"]; - for (expr, new_cmd) in cmds.entries() { - let re = Regex::new(expr).unwrap(); - if re.is_match(&orig_cmd) { - let new_prog = new_cmd["cmd"].to_string(); - let new_args = json_to_args(&new_cmd["args"]); - return Some((new_prog, new_args)); + if let Some(cmds) = &info.commands { + for new_cmd in cmds { + let re = Regex::new(&new_cmd.command_name).unwrap(); + if re.is_match(&orig_cmd) { + return Some((new_cmd.cmd.clone(), new_cmd.args.clone())); + } } } @@ -78,38 +67,37 @@ fn find_game_command(info: &json::JsonValue, args: &[&str]) -> Option<(String, V } pub fn process_setup_details( - game_info: &json::JsonValue, + setup_info: &package_metadata::Setup, ) -> io::Result> { - let setup_info = &game_info["setup"]; let mut setup_items = Vec::new(); - if !&setup_info["license_path"].is_null() - && Path::new(&setup_info["license_path"].to_string()).exists() - { - let mut file = File::open(&setup_info["license_path"].to_string())?; - let mut file_buf = vec![]; - file.read_to_end(&mut file_buf)?; - let file_str = String::from_utf8_lossy(&file_buf); - let file_str_milk = file_str.as_ref(); - - let prompt_request = client::PromptRequestData { - label: Some("By clicking Ok below, you are agreeing to the following.".to_string()), - prompt_type: "question".to_string(), - title: "Closed Source Engine EULA".to_string(), - prompt_id: "closedsourceengineeulaconfirm".to_string(), - rich_text: Some(file_str_milk.to_string()), - }; - setup_items.push(prompt_request); + if let Some(license_path) = &setup_info.license_path { + if Path::new(&license_path).exists() { + let mut file = File::open(license_path)?; + let mut file_buf = vec![]; + file.read_to_end(&mut file_buf)?; + let file_str = String::from_utf8_lossy(&file_buf); + let file_str_milk = file_str.as_ref(); + + let prompt_request = client::PromptRequestData { + label: Some("By clicking Ok below, you are agreeing to the following.".to_string()), + prompt_type: "question".to_string(), + title: "Closed Source Engine EULA".to_string(), + prompt_id: "closedsourceengineeulaconfirm".to_string(), + rich_text: Some(file_str_milk.to_string()), + }; + setup_items.push(prompt_request); + } } - if !&setup_info["dialogs"].is_null() { - for entry in setup_info["dialogs"].members() { - if entry["type"] == "input" { + if let Some(dialogs) = &setup_info.dialogs { + for entry in dialogs { + if entry.dialog_type == "input" { let prompt_request = client::PromptRequestData { - label: Some(entry["label"].to_string()), + label: Some(entry.label.to_string()), prompt_type: "input".to_string(), - title: entry["title"].to_string(), - prompt_id: std::format!("dialogentryconfirm%%{}%%", entry["key"]).to_string(), + title: entry.title.to_string(), + prompt_id: std::format!("dialogentryconfirm%%{}%%", entry.key).to_string(), rich_text: None, }; setup_items.push(prompt_request); @@ -121,21 +109,15 @@ pub fn process_setup_details( } pub fn run_setup( - game_info: &json::JsonValue, + setup_info: &package_metadata::Setup, sender: &std::sync::mpsc::Sender, ) -> io::Result<()> { - let setup_info = &game_info["setup"]; - - let command_str = setup_info["command"].to_string(); + let command_str = setup_info.command.to_string(); info!("setup run: \"{}\"", command_str); let status_obj = client::StatusObj { - label: None, - progress: None, - complete: false, log_line: Some(format!("setup run: \"{}\"", command_str)), - error: None, - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); @@ -149,7 +131,7 @@ pub fn run_setup( return Err(Error::new(ErrorKind::Other, "setup failed")); } - File::create(setup_info["complete_path"].to_string())?; + File::create(setup_info.complete_path.clone())?; Ok(()) } @@ -159,7 +141,7 @@ pub fn run( engine_choice: String, sender: &std::sync::mpsc::Sender, after_setup_question_mode: bool, -) -> io::Result { +) -> io::Result { env::set_var(LUX_ERRORS_SUPPORTED, "1"); let app_id = user_env::steam_app_id(); @@ -171,11 +153,7 @@ pub fn run( } }; - if game_info.is_null() { - return Err(Error::new(ErrorKind::Other, "Unknown app_id")); - } - - if !game_info["choices"].is_null() { + if game_info.choices.is_some() { match package::convert_game_info_with_choice(engine_choice, &mut game_info) { Ok(()) => { info!("engine choice complete"); @@ -187,9 +165,9 @@ pub fn run( } info!("json:"); - info!("{:#}", game_info); + info!("{:?}", game_info); - if game_info["use_original_command_directory"] == true { + if game_info.use_original_command_directory { let tmp_path = Path::new(args[0]); let parent_path = tmp_path.parent().unwrap(); env::set_current_dir(parent_path).unwrap(); @@ -199,7 +177,7 @@ pub fn run( info!("tool dir: {:?}", user_env::tool_dir()); } - if !game_info["download"].is_null() && !after_setup_question_mode { + if !after_setup_question_mode { match package::install(&game_info, sender) { Ok(()) => {} Err(err) => { @@ -222,7 +200,7 @@ pub fn run( pub fn run_wrapper( args: &[&str], - game_info: &json::JsonValue, + game_info: &package_metadata::Game, sender: &std::sync::mpsc::Sender, ) -> io::Result<()> { if args.is_empty() { @@ -248,15 +226,11 @@ pub fn run_wrapper( info!("run: \"{}\" with args: {:?} {:?}", cmd, cmd_args, exe_args); let status_obj = client::StatusObj { - label: None, - progress: None, - complete: false, log_line: Some(format!( "run: \"{}\" with args: {:?} {:?}", cmd, cmd_args, exe_args )), - error: None, - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 1bcb19f8..271ddcaa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ mod client; mod command; mod config; mod package; +mod package_metadata; mod user_env; // Function that registers all exposed classes to Godot diff --git a/src/package.rs b/src/package.rs index 8f8d1edd..7b5b5ee3 100644 --- a/src/package.rs +++ b/src/package.rs @@ -1,5 +1,3 @@ -#![allow(clippy::or_fun_call)] - extern crate reqwest; extern crate tar; extern crate xz2; @@ -7,8 +5,6 @@ extern crate xz2; use bzip2::read::BzDecoder; use flate2::read::GzDecoder; use log::{error, info}; -use regex::Regex; -use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::ffi::OsStr; @@ -22,6 +18,7 @@ use xz2::read::XzDecoder; use crate::client; use crate::config; +use crate::package_metadata; use crate::user_env; extern crate steamlocate; @@ -43,7 +40,7 @@ fn find_cached_file(app_id: &str, file: &str) -> Option { // Try to create dirs of path recursively, // if that fails, try to show a helpful UI message -fn create_dir_or_show_error(path: &impl AsRef) { +pub fn create_dir_or_show_error(path: &impl AsRef) { let err = match fs::create_dir_all(path) { Ok(()) => return, Err(err) => err, @@ -77,14 +74,6 @@ pub fn place_config_file(app_id: &str, file: &str) -> io::Result { xdg_dirs.place_config_file(path_str) } -pub fn path_to_packages_file() -> PathBuf { - let xdg_dirs = xdg::BaseDirectories::new().unwrap(); - let config_home = xdg_dirs.get_cache_home(); - let folder_path = config_home.join("luxtorpeda"); - create_dir_or_show_error(&folder_path); - folder_path.join("packagessniper_v2.json") -} - pub fn path_to_config() -> PathBuf { let xdg_dirs = xdg::BaseDirectories::new().unwrap(); let config_home = xdg_dirs.get_config_home(); @@ -105,54 +94,6 @@ pub fn place_state_file(file: &str) -> io::Result { xdg_dirs.place_state_file(path_str) } -#[derive(Serialize, Deserialize, Debug)] -pub struct CmdReplacement { - #[serde(with = "serde_regex")] - pub match_cmd: Regex, - pub cmd: String, - pub args: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct PackageMetadata { - engine_version: String, - commands: Vec, -} - -#[derive(Clone)] -pub struct PackageInfo { - pub name: String, - pub url: String, - pub file: String, - pub cache_by_name: bool, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ChoiceInfo { - pub name: String, - pub notices: Vec, -} - -pub fn get_remote_packages_hash(remote_hash_url: &str) -> Option { - let remote_hash_response = match reqwest::blocking::get(remote_hash_url) { - Ok(s) => s, - Err(err) => { - error!("get_remote_packages_hash error in get: {:?}", err); - return None; - } - }; - - let remote_hash_str = match remote_hash_response.text() { - Ok(s) => s, - Err(err) => { - error!("get_remote_packages_hash error in text: {:?}", err); - return None; - } - }; - - Some(remote_hash_str) -} - pub fn generate_hash_from_file_path(file_path: &Path) -> io::Result { let mut file = fs::File::open(file_path)?; let mut hasher = Sha256::new(); @@ -162,130 +103,19 @@ pub fn generate_hash_from_file_path(file_path: &Path) -> io::Result { Ok(hash_str) } -pub fn update_packages_json() -> io::Result<()> { - let config = config::Config::from_config_file(); - if !config.should_do_update { - return Ok(()); - } - - let packages_json_file = path_to_packages_file(); - let mut should_download = true; - let mut remote_hash_str: String = String::new(); - - let remote_path = "packagessniper_v2"; - - let remote_hash_url = std::format!("{0}/{1}.hash256", config.host_url, remote_path); - match get_remote_packages_hash(&remote_hash_url) { - Some(tmp_hash_str) => { - remote_hash_str = tmp_hash_str; - } - None => { - info!("update_packages_json in get_remote_packages_hash call. received none"); - should_download = false; - } - } - - if should_download { - if !Path::new(&packages_json_file).exists() { - should_download = true; - info!( - "update_packages_json. {:?} does not exist", - packages_json_file - ); - } else { - let hash_str = generate_hash_from_file_path(&packages_json_file)?; - info!("update_packages_json. found hash: {}", hash_str); - - info!( - "update_packages_json. found hash and remote hash: {0} {1}", - hash_str, remote_hash_str - ); - if hash_str != remote_hash_str { - info!("update_packages_json. hash does not match. downloading"); - should_download = true; - } else { - should_download = false; - } - } - } - - if should_download { - info!("update_packages_json. downloading new {}.json", remote_path); - - let remote_packages_url = std::format!("{0}/{1}.json", config.host_url, remote_path); - let mut download_complete = false; - let local_packages_temp_path = - path_to_packages_file().with_file_name(std::format!("{}-temp.json", remote_path)); - - match reqwest::blocking::get(remote_packages_url) { - Ok(mut response) => { - let mut dest = fs::File::create(&local_packages_temp_path)?; - io::copy(&mut response, &mut dest)?; - download_complete = true; - } - Err(err) => { - error!("update_packages_json. download err: {:?}", err); - } - } - - if download_complete { - let new_hash_str = generate_hash_from_file_path(&local_packages_temp_path)?; - if new_hash_str == remote_hash_str { - info!("update_packages_json. new downloaded hash matches"); - fs::rename(local_packages_temp_path, packages_json_file)?; - } else { - info!("update_packages_json. new downloaded hash does not match"); - fs::remove_file(local_packages_temp_path)?; - } - } - } - - Ok(()) -} - -pub fn convert_notice_to_str( - notice_item: &json::JsonValue, - notice_map: &json::JsonValue, -) -> String { - let mut notice = String::new(); - - if !notice_item.is_null() { - if !notice_item["label"].is_null() { - notice = notice_item["label"].to_string(); - } else if !notice_item["value"].is_null() { - if !notice_map[notice_item["value"].to_string()].is_null() { - notice = notice_map[notice_item["value"].to_string()].to_string(); - } else { - notice = notice_item["value"].to_string(); - } - } else if !notice_item["key"].is_null() { - if !notice_map[notice_item["key"].to_string()].is_null() { - notice = notice_map[notice_item["key"].to_string()].to_string(); - } else { - notice = notice_item["key"].to_string(); - } - } - } - - notice -} - pub fn convert_game_info_with_choice( choice_name: String, - game_info: &mut json::JsonValue, + game_info: &mut package_metadata::Game, ) -> io::Result<()> { let mut choice_data = HashMap::new(); - let mut download_array = json::JsonValue::new_array(); - - if game_info["choices"].is_null() { - return Err(Error::new(ErrorKind::Other, "choices array null")); - } + let mut new_downloads: Vec = vec![]; - for entry in game_info["choices"].members() { - if entry["name"].is_null() { - return Err(Error::new(ErrorKind::Other, "missing choice info")); + if let Some(choices) = game_info.choices.clone() { + for entry in choices { + choice_data.insert(entry.name.clone(), entry.clone()); } - choice_data.insert(entry["name"].to_string(), entry.clone()); + } else { + return Err(Error::new(ErrorKind::Other, "choices array null")); } if !choice_data.contains_key(&choice_name) { @@ -295,75 +125,56 @@ pub fn convert_game_info_with_choice( )); } - for (key, value) in choice_data[&choice_name].entries() { - if key == "name" || key == "download" { - continue; + let engine_choice_data = &choice_data[&choice_name]; + + for entry in &game_info.download { + let mut should_push_download = true; + if let Some(choice_download) = &engine_choice_data.download { + if !choice_download.contains(&entry.name) { + should_push_download = false; + } } - game_info[key] = value.clone(); - } - for entry in game_info["download"].members() { - if choice_data[&choice_name]["download"].is_null() - || choice_data[&choice_name]["download"].contains(entry["name"].clone()) - { - match download_array.push(entry.clone()) { - Ok(()) => {} - Err(_) => { - return Err(Error::new( - ErrorKind::Other, - "Error pushing to download array", - )); - } - }; + if should_push_download { + new_downloads.push(entry.clone()); } } - game_info["download"] = download_array; - game_info.remove("choices"); + game_info.download = new_downloads; + game_info.update_from_choice(engine_choice_data); + game_info.choices = None; Ok(()) } pub fn json_to_downloads( app_id: &str, - game_info: &json::JsonValue, -) -> io::Result> { - let mut downloads: Vec = Vec::new(); - - for entry in game_info["download"].members() { - if entry["name"].is_null() || entry["url"].is_null() || entry["file"].is_null() { + game_info: &package_metadata::Game, +) -> io::Result> { + let mut downloads: Vec = Vec::new(); + for entry in &game_info.download { + if entry.name.is_empty() || entry.url.is_empty() || entry.file.is_empty() { return Err(Error::new(ErrorKind::Other, "missing download info")); } - let name = entry["name"].to_string(); - let url = entry["url"].to_string(); - let file = entry["file"].to_string(); - let mut cache_by_name = false; - let mut cache_dir = app_id; - if entry["cache_by_name"] == true { - cache_dir = &name; - cache_by_name = true; + if entry.cache_by_name { + cache_dir = &entry.name; } - if find_cached_file(cache_dir, file.as_str()).is_some() { - info!("{} found in cache (skip)", file); + if find_cached_file(cache_dir, entry.file.as_str()).is_some() { + info!("{} found in cache (skip)", entry.file); continue; } - downloads.push(PackageInfo { - name, - url, - file, - cache_by_name, - }); + downloads.push(entry.clone()); } Ok(downloads) } fn unpack_tarball( tarball: &Path, - game_info: &json::JsonValue, + game_info: &package_metadata::Game, name: &str, sender: &std::sync::mpsc::Sender, ) -> io::Result<()> { @@ -374,12 +185,8 @@ fn unpack_tarball( .ok_or_else(|| Error::new(ErrorKind::Other, "package has no name?"))?; let status_obj = client::StatusObj { - label: None, - progress: None, - complete: false, log_line: Some(format!("Unpacking {}", package_name)), - error: None, - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); @@ -403,19 +210,16 @@ fn unpack_tarball( .and_then(OsStr::to_str) .unwrap_or(""); - if !&game_info["download_config"].is_null() - && !&game_info["download_config"][&name.to_string()].is_null() - { - let file_download_config = &game_info["download_config"][&name.to_string()]; - if !file_download_config["extract_location"].is_null() { - extract_location = file_download_config["extract_location"].to_string(); + if let Some(file_download_config) = game_info.find_download_config_by_name(name) { + if let Some(tmp_extract_location) = file_download_config.extract_location { + extract_location = tmp_extract_location; info!( "install changing extract location with config {}", extract_location ); } - if !file_download_config["strip_prefix"].is_null() { - strip_prefix = file_download_config["strip_prefix"].to_string(); + if let Some(tmp_strip_prefix) = file_download_config.strip_prefix { + strip_prefix = tmp_strip_prefix; info!("install changing prefix with config {}", strip_prefix); } } @@ -525,12 +329,8 @@ fn unpack_tarball( } let status_obj = client::StatusObj { - label: None, - progress: None, - complete: false, log_line: Some(format!("Unpacking complete for {}", package_name)), - error: None, - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); @@ -545,12 +345,9 @@ fn copy_only(path: &Path, sender: &std::sync::mpsc::Sender) -> io::Resul .ok_or_else(|| Error::new(ErrorKind::Other, "package has no name?"))?; let status_obj = client::StatusObj { - label: None, progress: Some(0), - complete: false, log_line: Some(format!("Copying {}", package_name)), - error: None, - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); @@ -559,12 +356,10 @@ fn copy_only(path: &Path, sender: &std::sync::mpsc::Sender) -> io::Resul fs::copy(path, package_name)?; let status_obj_complete = client::StatusObj { - label: None, progress: Some(0), - complete: false, + log_line: Some(format!("Copying complete for {}", package_name)), - error: None, - prompt_items: None, + ..Default::default() }; let status_str_complete = serde_json::to_string(&status_obj_complete).unwrap(); sender.send(status_str_complete).unwrap(); @@ -572,67 +367,54 @@ fn copy_only(path: &Path, sender: &std::sync::mpsc::Sender) -> io::Resul Ok(()) } -pub fn is_setup_complete(setup_info: &json::JsonValue) -> bool { - let setup_complete = Path::new(&setup_info["complete_path"].to_string()).exists(); +pub fn is_setup_complete(setup_info: &package_metadata::Setup) -> bool { + let setup_complete = Path::new(&setup_info.complete_path).exists(); setup_complete } pub fn install( - game_info: &json::JsonValue, + game_info: &package_metadata::Game, sender: &std::sync::mpsc::Sender, ) -> io::Result<()> { let app_id = user_env::steam_app_id(); - let packages: std::slice::Iter<'_, json::JsonValue> = game_info["download"].members(); - let status_obj = client::StatusObj { label: Some("Installing".to_string()), progress: Some(0), - complete: false, - log_line: None, - error: None, - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); let mut setup_complete = false; - if !game_info["setup"].is_null() { - setup_complete = is_setup_complete(&game_info["setup"]); + if let Some(setup) = &game_info.setup { + setup_complete = is_setup_complete(setup); } let config = config::Config::from_config_file(); let hash_check_install = config.hash_check_install; - for file_info in packages { - let file = file_info["file"] - .as_str() - .ok_or_else(|| Error::new(ErrorKind::Other, "missing info about this game"))?; - - let name = file_info["name"].to_string(); + for file_info in &game_info.download { + let file = &file_info.file; + let name = &file_info.name; let mut cache_dir = &app_id; - if file_info["cache_by_name"] == true { - cache_dir = &name; + if file_info.cache_by_name { + cache_dir = name; } - if setup_complete - && !&game_info["download_config"].is_null() - && !&game_info["download_config"][&name.to_string()].is_null() - && !&game_info["download_config"][&name.to_string()]["setup"].is_null() - && game_info["download_config"][&name.to_string()]["setup"] == true - { - continue; + if setup_complete { + if let Some(download_config) = game_info.find_download_config_by_name(name) { + if download_config.setup { + continue; + } + } } if hash_check_install { if let Some(install_file_path) = find_cached_file(cache_dir, file) { let status_obj = client::StatusObj { - label: None, - progress: None, - complete: false, log_line: Some(format!("Checking install for {}", name)), - error: None, - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); @@ -656,15 +438,11 @@ pub fn install( info!("hash for {} is same, skipping install", name); let status_obj = client::StatusObj { - label: None, - progress: None, - complete: false, log_line: Some(format!( "Skipping install for {}, as hash is the same", name )), - error: None, - prompt_items: None, + ..Default::default() }; let status_str = serde_json::to_string(&status_obj).unwrap(); sender.send(status_str).unwrap(); @@ -683,7 +461,7 @@ pub fn install( match find_cached_file(cache_dir, file) { Some(path) => { - match unpack_tarball(&path, game_info, &name, sender) { + match unpack_tarball(&path, game_info, name, sender) { Ok(()) => {} Err(err) => { return Err(err); @@ -698,24 +476,9 @@ pub fn install( Ok(()) } -pub fn get_game_info(app_id: &str) -> io::Result { - let packages_json_file = path_to_packages_file(); - let json_str = match fs::read_to_string(packages_json_file) { - Ok(s) => s, - Err(err) => { - info!("read err: {:?}", err); - return Err(err); - } - }; - let parsed = match json::parse(&json_str) { - Ok(j) => j, - Err(err) => { - let error_message = std::format!("parsing err: {:?}", err); - error!("{}", error_message); - return Err(Error::new(ErrorKind::Other, error_message)); - } - }; - let game_info = parsed[app_id].clone(); +pub fn get_game_info(app_id: &str) -> io::Result { + let package_metadata = package_metadata::PackageMetadata::from_packages_file(); + let game_info = package_metadata.find_game_by_app_id(app_id); match find_user_packages_file() { Some(user_packages_file) => { @@ -742,16 +505,36 @@ pub fn get_game_info(app_id: &str) -> io::Result { let user_game_info = user_parsed[app_id].clone(); if user_game_info.is_null() { if !user_parsed["default"].is_null() - && (game_info.is_null() + && (game_info.is_none() || (!user_parsed["override_all_with_user_default"].is_null() && user_parsed["override_all_with_user_default"] == true)) { info!("game info using user default"); - return Ok(user_parsed["default"].clone()); + match serde_json::from_str::(&json::stringify( + user_parsed["default"].clone(), + )) { + Ok(game) => return Ok(game), + Err(err) => { + let error_message = + std::format!("error parsing user parsed default: {:?}", err); + error!("{:?}", error_message); + return Err(Error::new(ErrorKind::Other, error_message)); + } + } } } else { info!("user_packages_file used for game_info"); - return Ok(user_game_info); + match serde_json::from_str::(&json::stringify( + user_game_info, + )) { + Ok(game) => return Ok(game), + Err(err) => { + let error_message = + std::format!("error parsing user parsed default: {:?}", err); + error!("{:?}", error_message); + return Err(Error::new(ErrorKind::Other, error_message)); + } + } } } None => { @@ -759,53 +542,21 @@ pub fn get_game_info(app_id: &str) -> io::Result { } }; - if game_info.is_null() { - if !parsed["default"].is_null() { - info!("game info using default"); - Ok(parsed["default"].clone()) - } else { - Err(Error::new( - ErrorKind::Other, - "Game info not found & no default", - )) - } - } else { - Ok(game_info) - } -} - -pub fn get_engines_info() -> Option<(json::JsonValue, json::JsonValue)> { - let packages_json_file = path_to_packages_file(); - let json_str = match fs::read_to_string(packages_json_file) { - Ok(s) => s, - Err(err) => { - error!("read err: {:?}", err); - return None; - } - }; - let parsed = match json::parse(&json_str) { - Ok(j) => j, - Err(err) => { - error!("parsing err: {:?}", err); - return None; - } - }; - - if parsed["engines"].is_null() || parsed["noticeMap"].is_null() { - None + if let Some(ret_game_info) = game_info { + Ok(ret_game_info) } else { - Some((parsed["engines"].clone(), parsed["noticeMap"].clone())) + info!("game info using default"); + Ok(package_metadata.default_engine) } } -pub fn get_app_id_deps_paths(deps: &json::JsonValue) -> Option<()> { +pub fn get_app_id_deps_paths(deps: &Vec) -> Option<()> { match SteamDir::locate() { Some(mut steamdir) => { - for entry in deps.members() { - info!("get_app_id_deps_paths. searching for app id {}.", entry); - let app_id = entry.as_u32()?; + for app_id in deps { + info!("get_app_id_deps_paths. searching for app id {}.", app_id); - match steamdir.app(&app_id) { + match steamdir.app(app_id) { Some(app_location) => { let app_location_path = app_location.path.clone(); let app_location_str = diff --git a/src/package_metadata.rs b/src/package_metadata.rs new file mode 100644 index 00000000..1b4559b2 --- /dev/null +++ b/src/package_metadata.rs @@ -0,0 +1,475 @@ +use log::{error, info}; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +use crate::config; +use crate::package; + +const PACKAGE_METADATA_FILENAME: &str = "packagessniper_v2"; + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct PackageMetadata { + pub games: Vec, + pub engines: Vec, + pub default_engine: Game, + pub notice_translation: Vec, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct Game { + pub game_name: String, + pub engine_name: String, + pub command: Option, + pub command_args: Vec, + pub download_config: Option>, + #[serde(alias = "cloudNotAvailable")] + pub cloud_not_available: bool, + #[serde(alias = "cloudSupported")] + pub cloud_supported: bool, + #[serde(alias = "cloudAvailable")] + pub cloud_available: bool, + #[serde(alias = "cloudIssue")] + pub cloud_issue: bool, + pub download: Vec, + pub app_id: String, + pub choices: Option>, + notices: Option>, + #[serde(alias = "controllerSteamDefault")] + pub controller_steam_default: bool, + pub use_original_command_directory: bool, + pub app_ids_deps: Option>, + pub setup: Option, + pub commands: Option>, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct DownloadItem { + pub name: String, + pub url: String, + pub file: String, + pub cache_by_name: bool, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct DownloadConfig { + pub download_name: String, + pub extract_location: Option, + pub setup: bool, + pub strip_prefix: Option, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct EngineChoice { + pub name: String, + pub engine_name: Option, + pub command: Option, + pub command_args: Vec, + pub download: Option>, + pub download_config: Option>, + notices: Option>, + pub commands: Option>, + pub setup: Option, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct SimpleEngineChoice { + pub name: String, + notices: Vec, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct GameCommand { + pub cmd: String, + pub args: Vec, + pub command_name: String, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct Setup { + pub complete_path: String, + pub command: String, + pub uninstall_command: Option, + pub license_path: Option, + pub dialogs: Option>, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct SetupDialog { + #[serde(alias = "type")] + pub dialog_type: String, + pub title: String, + pub label: String, + pub key: String, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct Engine { + pub engine_link: String, + pub version: String, + pub author: String, + pub author_link: String, + pub license: String, + pub license_link: String, + #[serde(alias = "controllerNotSupported")] + pub controller_not_supported: bool, + #[serde(alias = "controllerSupported")] + pub controller_supported: bool, + #[serde(alias = "controllerSupportedManualGame")] + pub controller_supported_manual_game: bool, + pub engine_name: String, + notices: Option>, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct Notice { + pub label: Option, + pub key: Option, + pub value: Option, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone)] +#[serde(default)] +pub struct NoticeTranslation { + pub key: String, + pub value: String, +} + +impl PackageMetadata { + pub fn from_packages_file() -> PackageMetadata { + let packages_json_file = PackageMetadata::path_to_packages_file(); + if packages_json_file.exists() { + info!("packages_json_file exists, reading"); + match fs::read_to_string(packages_json_file) { + Ok(s) => match serde_json::from_str::(&s) { + Ok(config) => config, + Err(err) => { + error!("error parsing packages_json_file: {:?}", err); + Default::default() + } + }, + Err(err) => { + error!("error reading packages_json_file: {:?}", err); + Default::default() + } + } + } else { + info!("packages_json_file does not exist"); + Default::default() + } + } + + pub fn find_game_by_app_id(&self, app_id: &str) -> Option { + self.games.iter().find(|x| x.app_id == app_id).cloned() + } + + pub fn find_engine_by_name(&self, name: &str) -> Option { + self.engines.iter().find(|x| x.engine_name == name).cloned() + } + + pub fn find_notice_translation_by_key(&self, key: &str) -> Option { + self.notice_translation + .iter() + .find(|x| x.key == key) + .cloned() + } + + pub fn convert_notice_to_str(&self, notice_item: &Notice) -> String { + let mut notice = String::new(); + + if let Some(label) = ¬ice_item.label { + notice = label.to_string(); + } else if let Some(value) = ¬ice_item.value { + if let Some(notice_translation) = self.find_notice_translation_by_key(value) { + notice = notice_translation.value; + } else { + notice = value.to_string(); + } + } else if let Some(key) = ¬ice_item.key { + if let Some(notice_translation) = self.find_notice_translation_by_key(key) { + notice = notice_translation.value; + } else { + notice = key.to_string(); + } + } + + notice + } + + pub fn update_packages_json() -> io::Result<()> { + let config = config::Config::from_config_file(); + if !config.should_do_update { + return Ok(()); + } + + let packages_json_file = PackageMetadata::path_to_packages_file(); + let mut should_download = true; + let mut remote_hash_str: String = String::new(); + + let remote_path = PACKAGE_METADATA_FILENAME; + + let remote_hash_url = std::format!("{0}/{1}.hash256", config.host_url, remote_path); + match PackageMetadata::get_remote_packages_hash(&remote_hash_url) { + Some(tmp_hash_str) => { + remote_hash_str = tmp_hash_str; + } + None => { + info!("update_packages_json in get_remote_packages_hash call. received none"); + should_download = false; + } + } + + if should_download { + if !Path::new(&packages_json_file).exists() { + should_download = true; + info!( + "update_packages_json. {:?} does not exist", + packages_json_file + ); + } else { + let hash_str = package::generate_hash_from_file_path(&packages_json_file)?; + info!("update_packages_json. found hash: {}", hash_str); + + info!( + "update_packages_json. found hash and remote hash: {0} {1}", + hash_str, remote_hash_str + ); + if hash_str != remote_hash_str { + info!("update_packages_json. hash does not match. downloading"); + should_download = true; + } else { + should_download = false; + } + } + } + + if should_download { + info!("update_packages_json. downloading new {}.json", remote_path); + + let remote_packages_url = std::format!("{0}/{1}.json", config.host_url, remote_path); + let mut download_complete = false; + let local_packages_temp_path = PackageMetadata::path_to_packages_file() + .with_file_name(std::format!("{}-temp.json", remote_path)); + + match reqwest::blocking::get(remote_packages_url) { + Ok(mut response) => { + let mut dest = fs::File::create(&local_packages_temp_path)?; + io::copy(&mut response, &mut dest)?; + download_complete = true; + } + Err(err) => { + error!("update_packages_json. download err: {:?}", err); + } + } + + if download_complete { + let new_hash_str = + package::generate_hash_from_file_path(&local_packages_temp_path)?; + if new_hash_str == remote_hash_str { + info!("update_packages_json. new downloaded hash matches"); + fs::rename(local_packages_temp_path, packages_json_file)?; + } else { + info!("update_packages_json. new downloaded hash does not match"); + fs::remove_file(local_packages_temp_path)?; + } + } + } + + Ok(()) + } + + pub fn get_remote_packages_hash(remote_hash_url: &str) -> Option { + let remote_hash_response = match reqwest::blocking::get(remote_hash_url) { + Ok(s) => s, + Err(err) => { + error!("get_remote_packages_hash error in get: {:?}", err); + return None; + } + }; + + let remote_hash_str = match remote_hash_response.text() { + Ok(s) => s, + Err(err) => { + error!("get_remote_packages_hash error in text: {:?}", err); + return None; + } + }; + + Some(remote_hash_str) + } + + fn path_to_packages_file() -> PathBuf { + let xdg_dirs = xdg::BaseDirectories::new().unwrap(); + let config_home = xdg_dirs.get_cache_home(); + let folder_path = config_home.join("luxtorpeda"); + package::create_dir_or_show_error(&folder_path); + folder_path.join(format!("{}.json", PACKAGE_METADATA_FILENAME)) + } +} + +impl Game { + pub fn choices_with_notices(&mut self) -> Vec { + let mut simple_choices: Vec = vec![]; + + if let Some(choices) = &self.choices { + let package_metadata = PackageMetadata::from_packages_file(); + + for choice in choices { + let mut choice_info = SimpleEngineChoice { + name: choice.name.clone(), + notices: Vec::new(), + }; + + let mut engine_name = &choice.name; + if let Some(choice_engine_name) = &choice.engine_name { + engine_name = choice_engine_name; + } + + if let Some(engine) = + PackageMetadata::find_engine_by_name(&package_metadata, engine_name) + { + if let Some(engine_notices) = engine.notices { + for entry in engine_notices { + choice_info + .notices + .push(PackageMetadata::convert_notice_to_str( + &package_metadata, + &entry, + )); + } + } + + let controller_not_supported = engine.controller_not_supported; + let controller_supported = engine.controller_supported; + let controller_supported_manual = engine.controller_supported_manual_game; + + if controller_not_supported { + choice_info + .notices + .push("Engine Does Not Have Native Controller Support".to_string()); + } else if controller_supported && self.controller_steam_default { + choice_info.notices.push( + "Engine Has Native Controller Support And Works Out of the Box" + .to_string(), + ); + } else if controller_supported_manual && self.controller_steam_default { + choice_info.notices.push( + "Engine Has Native Controller Support But Needs Manual In-Game Settings" + .to_string(), + ); + } else if controller_supported && !self.controller_steam_default { + choice_info.notices.push( + "Engine Has Native Controller Support But Needs Manual Steam Settings" + .to_string(), + ); + } + } + + if self.cloud_not_available { + choice_info + .notices + .push("Game Does Not Have Cloud Saves".to_string()); + } else if self.cloud_available && !self.cloud_supported { + choice_info + .notices + .push("Game Has Cloud Saves But Unknown Status".to_string()); + } else if self.cloud_available && self.cloud_supported { + choice_info + .notices + .push("Cloud Saves Supported".to_string()); + } else if self.cloud_available && self.cloud_issue { + choice_info + .notices + .push("Cloud Saves Not Supported".to_string()); + } + + if let Some(game_notices) = &self.notices { + for entry in game_notices { + choice_info + .notices + .push(PackageMetadata::convert_notice_to_str( + &package_metadata, + entry, + )); + } + } + + simple_choices.push(choice_info); + } + } + + simple_choices + } + + pub fn find_license_dialog_message(&self) -> Option { + let package_metadata = PackageMetadata::from_packages_file(); + if let Some(engine) = + PackageMetadata::find_engine_by_name(&package_metadata, &self.engine_name) + { + if let Some(engine_notices) = engine.notices { + for entry in engine_notices { + if let Some(key) = entry.key { + if key == "non_free" { + return Some(std::format!( + "This engine uses a non-free engine ({0}). Are you sure you want to continue?", + engine.license)); + } else if key == "closed_source" { + return Some("This engine uses assets from the closed source release. Are you sure you want to continue?".to_string()); + } + } + } + } + } + + None + } + + pub fn find_download_config_by_name(&self, name: &str) -> Option { + if let Some(download_config) = &self.download_config { + return download_config + .iter() + .find(|x| x.download_name == name) + .cloned(); + } + + None + } + + pub fn update_from_choice(&mut self, engine_choice: &EngineChoice) { + if let Some(engine_name) = &engine_choice.engine_name { + self.engine_name = engine_name.to_string(); + } + + if engine_choice.command.is_some() { + self.command = engine_choice.command.clone(); + } + + if !engine_choice.command_args.is_empty() { + self.command_args = engine_choice.command_args.clone(); + } + + if engine_choice.download_config.is_some() { + self.download_config = engine_choice.download_config.clone(); + } + + if engine_choice.commands.is_some() { + self.commands = engine_choice.commands.clone(); + } + + if engine_choice.setup.is_some() { + self.setup = engine_choice.setup.clone(); + } + } +}