diff --git a/.gitignore b/.gitignore index dbd4f2bf..452f9542 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,4 @@ last-packet Leafish.code-workspace # this is part of compiling the installer (but we don't want to upload it) -install/resources/* \ No newline at end of file +install/resources/wrapper.jar \ No newline at end of file diff --git a/install/src/icon.txt b/install/resources/icon.txt similarity index 100% rename from install/src/icon.txt rename to install/resources/icon.txt diff --git a/install/resources/leafish-icon.png b/install/resources/leafish-icon.png new file mode 100644 index 00000000..ccffeab6 Binary files /dev/null and b/install/resources/leafish-icon.png differ diff --git a/install/src/install.rs b/install/src/install.rs index 723eb47f..fbc6a5e7 100644 --- a/install/src/install.rs +++ b/install/src/install.rs @@ -1,188 +1,422 @@ -use std::{ - collections::HashMap, - fs::{self, File, OpenOptions}, - io::{Read, Seek, Write}, - path::Path, -}; - -use chrono::Utc; -use serde_derive::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; - -// all of these are expected to be prepended with ".minecraft" -const DIR_PATH: &str = "/versions/Leafish/"; -const DESC_JSON_PATH: &str = "/versions/Leafish/Leafish.json"; -const JAR_PATH: &str = "/versions/Leafish/Leafish.jar"; -const PROFILES_JSON_PATH: &str = "/launcher_profiles.json"; -const LIBRARY_DIR_PATH: &str = "/libraries/leafish/Leafish/Jar/"; -const LIBRARY_PATH: &str = "/libraries/leafish/Leafish/Jar/Leafish-Jar.jar"; - -// FIXME: add cli that allows reinstalling, uninstalling, installing and getting info - -#[derive(Serialize, Deserialize)] -struct Description { - id: String, - #[serde(rename = "inheritsFrom")] - inherits_from: String, - time: String, - #[serde(rename = "releaseTime")] - release_time: String, - #[serde(rename = "type")] - ty: String, - libraries: Vec, - #[serde(rename = "mainClass")] - main_class: String, - #[serde(rename = "minecraftArguments")] - minecraft_arguments: String, -} +use std::{fs, path::Path}; -#[derive(Serialize, Deserialize)] -struct Library { - name: String, -} +pub mod mojang { -#[derive(Serialize, Deserialize)] -struct Profiles { - profiles: HashMap, - settings: Settings, - version: usize, -} + use std::{ + collections::HashMap, + fs::{self, File, OpenOptions}, + io::{Read, Seek, Write}, + path::Path, + }; -#[skip_serializing_none] -#[derive(Serialize, Deserialize)] -struct Profile { - created: Option, - icon: String, - #[serde(rename = "lastUsed")] - last_used: String, - #[serde(rename = "lastVersionId")] - last_version_id: Option, - name: String, - #[serde(rename = "type")] - ty: String, -} + use chrono::Utc; + use serde_derive::{Deserialize, Serialize}; + use serde_with::skip_serializing_none; -#[derive(Serialize, Deserialize)] -struct Settings { - #[serde(rename = "crashAssistance")] - crash_assistance: bool, - #[serde(rename = "enableAdvanced")] - advanced: bool, - #[serde(rename = "enableAnalytics")] - analytics: bool, - #[serde(rename = "enableHistorical")] - historical: bool, - #[serde(rename = "enableReleases")] - releases: bool, - #[serde(rename = "enableSnapshots")] - snapshots: bool, - #[serde(rename = "keepLauncherOpen")] - keep_launcher_open: bool, - #[serde(rename = "profileSorting")] - profile_sorting: String, - #[serde(rename = "showGameLog")] - show_game_log: bool, - #[serde(rename = "showMenu")] - show_menu: bool, - #[serde(rename = "soundOn")] - sound_on: bool, -} + use crate::install::mk_dir; + + // all of these are expected to be prepended with ".minecraft" + const DIR_PATH: &str = "/versions/Leafish/"; + const DESC_JSON_PATH: &str = "/versions/Leafish/Leafish.json"; + const JAR_PATH: &str = "/versions/Leafish/Leafish.jar"; + const PROFILES_JSON_PATH: &str = "/launcher_profiles.json"; + const LIBRARY_DIR_PATH: &str = "/libraries/leafish/Leafish/Jar/"; + const LIBRARY_PATH: &str = "/libraries/leafish/Leafish/Jar/Leafish-Jar.jar"; + + // FIXME: add cli that allows reinstalling, uninstalling, installing and getting info + + #[derive(Serialize, Deserialize)] + struct Description { + id: String, + #[serde(rename = "inheritsFrom")] + inherits_from: String, + time: String, + #[serde(rename = "releaseTime")] + release_time: String, + #[serde(rename = "type")] + ty: String, + libraries: Vec, + #[serde(rename = "mainClass")] + main_class: String, + #[serde(rename = "minecraftArguments")] + minecraft_arguments: String, + } + + #[derive(Serialize, Deserialize)] + struct Library { + name: String, + } + + #[derive(Serialize, Deserialize)] + struct Profiles { + profiles: HashMap, + settings: Settings, + version: usize, + } + + #[skip_serializing_none] + #[derive(Serialize, Deserialize)] + struct Profile { + created: Option, + icon: String, + #[serde(rename = "lastUsed")] + last_used: String, + #[serde(rename = "lastVersionId")] + last_version_id: Option, + name: String, + #[serde(rename = "type")] + ty: String, + } + + #[derive(Serialize, Deserialize)] + struct Settings { + #[serde(rename = "crashAssistance")] + crash_assistance: bool, + #[serde(rename = "enableAdvanced")] + advanced: bool, + #[serde(rename = "enableAnalytics")] + analytics: bool, + #[serde(rename = "enableHistorical")] + historical: bool, + #[serde(rename = "enableReleases")] + releases: bool, + #[serde(rename = "enableSnapshots")] + snapshots: bool, + #[serde(rename = "keepLauncherOpen")] + keep_launcher_open: bool, + #[serde(rename = "profileSorting")] + profile_sorting: String, + #[serde(rename = "showGameLog")] + show_game_log: bool, + #[serde(rename = "showMenu")] + show_menu: bool, + #[serde(rename = "soundOn")] + sound_on: bool, + } -pub fn setup_launcher_wrapper(prefix: &str) -> anyhow::Result { - if !Path::new(prefix).exists() { - println!("[INFO] Couldn't find .minecraft directory"); - return Ok(false); - } - - let json_path = format!("{}{}", prefix, DESC_JSON_PATH); - let jar_path = format!("{}{}", prefix, JAR_PATH); - if Path::new(&json_path).exists() && Path::new(&jar_path).exists() { - println!("[INFO] Leafish is already installed"); - return Ok(false); - } - // cleanup old files - if Path::new(&json_path).exists() { - println!("[INFO] Removing old json..."); - fs::remove_file(&json_path)?; - } - if Path::new(&jar_path).exists() { - println!("[INFO] Removing old jar..."); - fs::remove_file(&jar_path)?; - } - - let dir_path = format!("{}{}", prefix, DIR_PATH); - if !Path::new(&dir_path).exists() { - println!("[INFO] Creating version directory..."); - // create directory if necessary - fs::create_dir(&dir_path)?; - } - - // write files - println!("[INFO] Creating json..."); - let mut json = File::create_new(&json_path)?; - json.write_all(serde_json::to_string_pretty(&Description { - id: "Leafish".to_string(), - inherits_from: "1.8.9".to_string(), // FIXME: is this a good default? - time: "2020-01-01T00:00:00+02:00".to_string(), // FIXME: use some sensible time - release_time: "2020-01-01T00:00:00+02:00".to_string(), - ty: "release".to_string(), - libraries: vec![Library { name: "leafish:Leafish:Jar".to_string() }], // we need nobody, but ourselves ;) - main_class: "de.leafish.Main".to_string(), - minecraft_arguments: "--username ${auth_player_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userProperties ${user_properties} --userType ${user_type}".to_string(), - })?.as_bytes())?; - - // FIXME: download the latest jar from github - let raw_jar = include_bytes!("../resources/wrapper.jar"); - - println!("[INFO] Copying version jar..."); - let mut jar = File::create_new(&jar_path)?; - jar.write_all(raw_jar)?; - - let lib_dir = format!("{}{}", prefix, LIBRARY_DIR_PATH); - if !Path::new(&lib_dir).exists() { - println!("[INFO] Copying library..."); - fs::create_dir_all(&lib_dir)?; - let mut file = File::create_new(format!("{}{}", prefix, LIBRARY_PATH))?; - file.write_all(raw_jar)?; - } - - let profiles_path = format!("{}{}", prefix, PROFILES_JSON_PATH); - if !Path::new(&profiles_path).exists() { - println!( - "[WARN] Couldn't create profile as the file {} doesn't exist", - profiles_path + pub fn setup(prefix: &str) -> anyhow::Result { + if !Path::new(prefix).exists() { + println!("[Info] Couldn't find .minecraft directory"); + return Ok(false); + } + + let json_path = format!("{}{}", prefix, DESC_JSON_PATH); + let jar_path = format!("{}{}", prefix, JAR_PATH); + if Path::new(&json_path).exists() && Path::new(&jar_path).exists() { + println!("[Info] Leafish is already installed"); + return Ok(false); + } + // cleanup old files + if Path::new(&json_path).exists() { + println!("[Info] Removing old json..."); + fs::remove_file(&json_path)?; + } + if Path::new(&jar_path).exists() { + println!("[Info] Removing old jar..."); + fs::remove_file(&jar_path)?; + } + + let dir_path = format!("{}{}", prefix, DIR_PATH); + // create version directory if necessary + mk_dir(&dir_path, "[Info] Creating version directory...")?; + + // write files + println!("[Info] Creating json..."); + let mut json = File::create_new(&json_path)?; + json.write_all(serde_json::to_string_pretty(&Description { + id: "Leafish".to_string(), + inherits_from: "1.8.9".to_string(), // FIXME: is this a good default? + time: "2020-01-01T00:00:00+02:00".to_string(), // FIXME: use some sensible time + release_time: "2020-01-01T00:00:00+02:00".to_string(), + ty: "release".to_string(), + libraries: vec![Library { name: "leafish:Leafish:Jar".to_string() }], // we need nobody, but ourselves ;) + main_class: "de.leafish.Main".to_string(), + minecraft_arguments: "--username ${auth_player_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userProperties ${user_properties} --userType ${user_type} --path ./versions/Leafish/".to_string(), + })?.as_bytes())?; + + // FIXME: download the latest jar from github + let raw_jar = include_bytes!("../resources/wrapper.jar"); + + println!("[Info] Copying version jar..."); + let mut jar = File::create_new(&jar_path)?; + jar.write_all(raw_jar)?; + + let lib_dir = format!("{}{}", prefix, LIBRARY_DIR_PATH); + if !Path::new(&lib_dir).exists() { + println!("[Info] Copying library..."); + fs::create_dir_all(&lib_dir)?; + let mut file = File::create_new(format!("{}{}", prefix, LIBRARY_PATH))?; + file.write_all(raw_jar)?; + } + + let profiles_path = format!("{}{}", prefix, PROFILES_JSON_PATH); + if !Path::new(&profiles_path).exists() { + println!( + "[Warn] Couldn't create profile as the file {} doesn't exist", + profiles_path + ); + return Ok(true); + } + println!("[Info] Installing profile..."); + let mut profiles_json_file = OpenOptions::new() + .append(false) + .write(true) + .read(true) + .open(&profiles_path)?; + let mut profiles = String::new(); + profiles_json_file.read_to_string(&mut profiles)?; + let mut profiles: Profiles = serde_json::from_str(&profiles)?; + let now = Utc::now(); + let now = now.format("%Y-%m-%dT%H:%M:%S.000Z"); + + profiles.profiles.insert( + "Leafish".to_string(), + Profile { + created: Some(now.to_string()), + icon: include_str!("../resources/icon.txt").to_string(), + last_used: now.to_string(), + last_version_id: Some("Leafish".to_string()), + name: "Leafish".to_string(), + ty: "custom".to_string(), + }, ); - return Ok(true); - } - println!("[INFO] Installing profile..."); - let mut profiles_json_file = OpenOptions::new() - .append(false) - .write(true) - .read(true) - .open(&profiles_path)?; - let mut profiles = String::new(); - profiles_json_file.read_to_string(&mut profiles)?; - let mut profiles: Profiles = serde_json::from_str(&profiles)?; - let now = Utc::now(); - let now = now.format("%Y-%m-%dT%H:%M:%S.000Z"); - - profiles.profiles.insert( - "Leafish".to_string(), - Profile { - created: Some(now.to_string()), - icon: include_str!("./icon.txt").to_string(), - last_used: now.to_string(), - last_version_id: Some("Leafish".to_string()), + + profiles_json_file.set_len(0)?; + profiles_json_file.seek(std::io::SeekFrom::Start(0))?; + profiles_json_file.write_all(serde_json::to_string_pretty(&profiles)?.as_bytes())?; + + println!("[Info] Installation into .minecraft directory successful"); + + Ok(true) + } +} + +pub mod prism { + use std::{fs, path::Path}; + + use chrono::Utc; + use serde_derive::Serialize; + use serde_with::skip_serializing_none; + + use crate::install::mk_dir; + + const ICONS_DIR_PATH: &str = "/icons"; + const ICON_PATH: &str = "/icons/leafish.png"; + const INSTANCE_DIR_PATH: &str = "/instances/Leafish"; + const CFG_PATH: &str = "/instances/Leafish/instance.cfg"; + const PACK_PATH: &str = "/instances/Leafish/mmc-pack.json"; + const META_DIR_PATH: &str = "/meta/de.leafish"; + const META_PATH: &str = "/meta/de.leafish/Leafish.json"; + const LIB_DIR_PATH: &str = "/libraries/de/leafish"; + const LIB_PATH: &str = "/libraries/de/leafish/Leafish.jar"; + + const DEFAULT_CFG: &str = "[General] + ConfigVersion=1.2 + iconKey=leafish + name=Leafish + InstanceType=OneSix"; + + pub fn setup(prefix: &str) -> anyhow::Result { + if !Path::new(prefix).exists() { + // FIXME: this will print twice, fix this + println!("[Info] [PrismLauncher] Couldn't find PrismLauncher directory"); + return Ok(false); + } + + println!("[Info] [PrismLauncher] Found PrismLauncher directory"); + + let dir_path = format!("{}{}", prefix, INSTANCE_DIR_PATH); + if Path::new(&dir_path).exists() { + println!("[Info] [PrismLauncher] Profile already exists"); + return Ok(false); + } + mk_dir( + &dir_path, + "[Info] [PrismLauncher] Creating instance directory...", + )?; + + let dir_path = format!("{}{}", prefix, ICONS_DIR_PATH); + mk_dir( + &dir_path, + "[Info] [PrismLauncher] Creating icons directory...", + )?; + + let icon_path = format!("{}{}", prefix, ICON_PATH); + if !Path::new(&icon_path).exists() { + println!("[Info] [PrismLauncher] Copying icon..."); + fs::write(&icon_path, include_bytes!("../resources/leafish-icon.png"))?; + } + + let cfg_path = format!("{}{}", prefix, CFG_PATH); + fs::write(&cfg_path, DEFAULT_CFG.as_bytes())?; + + let pack_path = format!("{}{}", prefix, PACK_PATH); + fs::write( + &pack_path, + serde_json::to_string_pretty(&PackDesc { + components: vec![Component { + important: Some(true), + uid: "de.leafish".to_string(), + version: "Leafish".to_string(), + // cached_name: Some("Leafish".to_string()), + cached_name: None, + cached_requires: None, + // cached_version: Some("Leafish".to_string()), + cached_version: None, + cached_volatile: None, + dependency_only: None, + }], + format_version: 1, + })?, + )?; + let dir_path = format!("{}{}", prefix, LIB_DIR_PATH); + mk_dir(&dir_path, "[Info] [PrismLauncher] Creating library directory...")?; + let lib_path = format!("{}{}", prefix, LIB_PATH); + fs::write(&lib_path, include_bytes!("../resources/wrapper.jar"))?; + let dir_path = format!("{}{}", prefix, META_DIR_PATH); + mk_dir(&dir_path, "[Info] [PrismLauncher] Creating meta directory...")?; + let meta_path = format!("{}{}", prefix, META_PATH); + + let now = Utc::now(); + let now = now.format("%Y-%m-%dT%H:%M:%S"); + + fs::write(&meta_path, serde_json::to_string_pretty(&VersionMeta { + traits: None, + asset_index: AssetIndex { // FIXME: don't choose one version statically! + id: "12".to_string(), + sha1: "518a69b460cb49a5547cea4290d343116a5d2eb8".to_string(), + size: 436400, + total_size: 627004279, // FIXME: does this not depend on the client jar? + url: "https://piston-meta.mojang.com/v1/packages/518a69b460cb49a5547cea4290d343116a5d2eb8/12.json".to_string(), + }, + compatible_java_majors: vec![8,9,10,11,12,13,14,15,16,17], + format_version: 1, + libraries: vec![], + main_class: "de.leafish.Main".to_string(), + main_jar: MainJar { + downloads: Download { + artifact: Some(Artifact { + sha1: "fdfbab865254c28e67a6c4e7448583147db3a7f2".to_string(), + size: 5118199, + url: "https://github.com/Lea-fish/Releases/releases/download/alpha/bootstrap.jar".to_string(), + }), + }, + name: "de.leafish:Leafish:v1.0.0".to_string(), + }, + minecraft_arguments: "--username ${auth_player_name} --gameDir ${game_directory} --assetsDir ${assets_root} --assetIndex ${assets_index_name} --uuid ${auth_uuid} --accessToken ${auth_access_token} --userProperties ${user_properties} --userType ${user_type} --path ../".to_string(), name: "Leafish".to_string(), - ty: "custom".to_string(), - }, - ); + order: 0, + release_time: now.to_string(), + requires: vec![], + ty: "release".to_string(), + uid: "net.minecraft".to_string(), + version: "1.20.4".to_string(), + })?)?; + + Ok(true) + } + + #[derive(Serialize)] + struct PackDesc { + components: Vec, + #[serde(rename = "formatVersion")] + format_version: usize, + } + + #[skip_serializing_none] + #[derive(Serialize)] + struct Component { + #[serde(rename = "cachedName")] + cached_name: Option, + #[serde(rename = "cachedRequires")] + cached_requires: Option, + #[serde(rename = "cachedVersion")] + cached_version: Option, + #[serde(rename = "cachedVolatile")] + cached_volatile: Option, + #[serde(rename = "dependencyOnly")] + dependency_only: Option, + important: Option, + uid: String, + version: String, + } + + #[skip_serializing_none] + #[derive(Serialize)] + struct VersionMeta { + #[serde(rename = "+traits")] + traits: Option>, + #[serde(rename = "assetIndex")] + asset_index: AssetIndex, + #[serde(rename = "compatibleJavaMajors")] + compatible_java_majors: Vec, + #[serde(rename = "formatVersion")] + format_version: usize, + libraries: Vec, + #[serde(rename = "mainClass")] + main_class: String, + #[serde(rename = "mainJar")] + main_jar: MainJar, + #[serde(rename = "minecraftArguments")] + minecraft_arguments: String, + name: String, + order: isize, + #[serde(rename = "releaseTime")] + release_time: String, + requires: Vec, + #[serde(rename = "type")] + ty: String, + uid: String, + version: String, + } + + #[derive(Serialize)] + struct MainJar { + downloads: Download, + name: String, + } + + #[derive(Serialize)] + struct AssetIndex { + id: String, + sha1: String, + size: usize, + #[serde(rename = "totalSize")] + total_size: usize, + url: String, + } + + #[derive(Serialize)] + struct Library { + downloads: Vec, + name: String, + } + + #[skip_serializing_none] + #[derive(Serialize)] + struct Download { + artifact: Option, + } + + #[derive(Serialize)] + struct Artifact { + sha1: String, + size: usize, + url: String, + } - profiles_json_file.set_len(0)?; - profiles_json_file.seek(std::io::SeekFrom::Start(0))?; - profiles_json_file.write_all(serde_json::to_string_pretty(&profiles)?.as_bytes())?; + #[derive(Serialize)] + struct Required { + suggests: String, + uid: String, + } - println!("[INFO] Installation successful"); +} - Ok(true) +fn mk_dir(path: &str, msg: &str) -> anyhow::Result<()> { + if !Path::new(path).exists() { + println!("{}", msg); + fs::create_dir_all(path)?; + } + Ok(()) } diff --git a/install/src/main.rs b/install/src/main.rs index 63ab7f82..54313b17 100644 --- a/install/src/main.rs +++ b/install/src/main.rs @@ -1,14 +1,17 @@ -use install::setup_launcher_wrapper; +use install::{mojang, prism}; mod install; fn main() { - let dir = mc_dir(); - setup_launcher_wrapper(&dir).unwrap(); + let mojang_dir = mojang_dir(); + mojang::setup(&mojang_dir).unwrap(); + for dir in prism_dirs() { + prism::setup(&dir).unwrap(); + } } #[cfg(any(target_os = "windows", target_os = "macos"))] -fn mc_dir() -> String { +fn mojang_dir() -> String { platform_dirs::AppDirs::new(Some(".minecraft"), false) .unwrap() .config_dir @@ -18,7 +21,7 @@ fn mc_dir() -> String { } #[cfg(target_os = "linux")] -fn mc_dir() -> String { +fn mojang_dir() -> String { let state = platform_dirs::AppDirs::new(None, false).unwrap(); let full = state.config_dir.to_str().unwrap(); format!( @@ -27,3 +30,35 @@ fn mc_dir() -> String { ".minecraft" ) } + +#[cfg(any(target_os = "windows", target_os = "macos"))] +fn prism_dirs() -> Vec { + vec![platform_dirs::AppDirs::new(Some("PrismLauncher"), false) + .unwrap() + .config_dir + .to_str() + .unwrap() + .to_string()] +} + +#[cfg(target_os = "linux")] +fn prism_dirs() -> Vec { + let flatpak = { + let state = platform_dirs::AppDirs::new(None, false).unwrap(); + let full = state.config_dir.to_str().unwrap(); + format!( + "{}{}", + &full[0..(full.len() - ".config".len())], + ".var/app/org.prismlauncher.PrismLauncher/data/PrismLauncher" + ) + }; + vec![ + platform_dirs::AppDirs::new(None, false) + .unwrap() + .data_dir + .to_str() + .unwrap() + .to_string(), + flatpak, + ] +} diff --git a/src/main.rs b/src/main.rs index 359a882d..7b3d2123 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,19 +32,21 @@ use glutin::surface::GlSurface; use glutin::surface::SwapInterval; use glutin_winit::DisplayBuilder; use glutin_winit::GlWindow; -#[cfg(target_os = "linux")] -use instant::{Duration, Instant}; +use instant::Duration; use leafish_protocol::protocol::login::AccountType; use log::{debug, error, info, warn}; use raw_window_handle::HasRawWindowHandle; use shared::Version; use std::fs; use std::num::NonZeroU32; +use std::time::Instant; use winit::keyboard::Key; use winit::keyboard::ModifiersKeyState; use winit::keyboard::NamedKey; use winit::keyboard::SmolStr; +#[cfg(target_os = "linux")] use winit::raw_window_handle::HasDisplayHandle; +#[cfg(target_os = "linux")] use winit::raw_window_handle::RawDisplayHandle; use winit::window::Icon; extern crate leafish_shared as shared; diff --git a/src/screen/launcher.rs b/src/screen/launcher.rs index c49462cf..1cef6173 100644 --- a/src/screen/launcher.rs +++ b/src/screen/launcher.rs @@ -216,7 +216,6 @@ impl super::Screen for Launcher { back.add_click_func(move |_, game| { let accounts = accounts.clone(); let account_type = account_type.clone(); - let idx = idx; let mut client_token = game.vars.get(auth::AUTH_CLIENT_TOKEN).clone(); if client_token.is_empty() { client_token = std::iter::repeat(()) @@ -333,7 +332,6 @@ impl super::Screen for Launcher { let accounts = self.accounts.clone(); btn.add_click_func(move |_, game| { let accounts = accounts.clone(); - let idx = idx; game.screen_sys.clone().add_screen(Box::new( super::confirm_box::ConfirmBox::new( String::from("Do you want to delete this account?"), @@ -371,7 +369,6 @@ impl super::Screen for Launcher { btn.add_click_func(move |_, game| { let accounts = accounts.clone(); let account_type = account_type.clone(); - let idx = idx; game.screen_sys.clone().add_screen(Box::new( super::edit_account::EditAccountEntry::new( Some((aname.clone(), apw.clone())),