diff --git a/src/among_us_launcher_widget.rs b/src/among_us_launcher_widget.rs new file mode 100644 index 0000000..393b881 --- /dev/null +++ b/src/among_us_launcher_widget.rs @@ -0,0 +1,166 @@ +use crate::AppData; +use druid::{ + widget::*, BoxConstraints, Env, Event, EventCtx, FileDialogOptions, LayoutCtx, LifeCycle, + LifeCycleCtx, PaintCtx, Selector, Size, UpdateCtx, WidgetExt, WidgetPod, +}; +use std::{fs, io, path::PathBuf}; + +pub const ATTEMPT_INSTALL: Selector = Selector::new("town-of-us-updater.attempt_install"); + +pub struct AmongUsLauncherWidget { + pub root: WidgetPod>, +} + +impl AmongUsLauncherWidget { + pub fn build_widget(&mut self, data: &AppData) { + let mut flex: Flex = Flex::column(); + + // Find existing installs, make buttons for them + let install_iter = fs::read_dir(data.installs_path.clone()); + + if let Ok(iter) = install_iter { + let mut collection: Vec> = iter.collect(); + collection.reverse(); + + if collection.is_empty() { + if data.among_us_path.is_empty() { + // We did not find an install path, let's add a button to prompt the user + // to find their install path + let open_button = Button::new("Locate Among Us") + .fix_height(45.0) + .on_click(move |ctx, _: &mut AppData, _| { + ctx.submit_command( + druid::commands::SHOW_OPEN_PANEL.with(FileDialogOptions::new()), + ); + }) + .center(); + flex.add_flex_child(open_button, 1.0); + } else { + let label: druid::widget::Label = + druid::widget::Label::new("We found Among Us but you have no installs"); + flex.add_flex_child(label, 1.0); + } + } else { + // Iterate first to find labels: + + let mut flexbox_array: Vec> = Vec::new(); + let mut auv_array: Vec = Vec::new(); + + for i in &collection { + let existing_ver_smash = i.as_ref().unwrap().file_name(); + let mut ver_smash_split = existing_ver_smash.to_str().unwrap().split('-'); + let auv = ver_smash_split.next().unwrap(); + if !auv_array.contains(&auv.to_string()) { + let label_text = format!("Among Us {}", auv); + let flex: druid::widget::Flex = druid::widget::Flex::column() + .with_flex_child( + druid::widget::Label::new(label_text.as_str()).with_text_size(24.0), + 1.0, + ) + .with_default_spacer(); + flexbox_array.push(flex); + auv_array.push(auv.to_string()); + } + } + + println!("Installs list:"); + + for i in collection { + let existing_ver_smash = i.unwrap().file_name(); + let mut ver_smash_split = existing_ver_smash.to_str().unwrap().split('-'); + let auv = ver_smash_split.next().unwrap(); + let button_string: String = + format!("Town of Us {}", ver_smash_split.next().unwrap()); + + for (index, j) in auv_array.iter().enumerate() { + if j == auv { + let mut clone: PathBuf = PathBuf::from(data.installs_path.clone()); + clone.push(existing_ver_smash.clone()); + + let mut button_row: Flex = Flex::row(); + + let fybutton = druid::widget::Button::new(button_string.as_str()) + .fix_height(45.0) + .center() + .on_click(move |_, _: &mut AppData, _| { + crate::attempt_run_among_us(&clone); + // crate::launch_better_crewlink().unwrap(); + }); + + button_row.add_flex_child(fybutton, 1.0); + + // TODO: Uncomment + // let delete_button = druid::widget::Button::new("Delete") + // .fix_height(45.0) + // .on_click(move |_, _: &mut AppData, _| {}); + + // button_row.add_flex_child(delete_button, 1.0); + + flexbox_array + .get_mut(index) + .unwrap() + .add_flex_child(button_row, 1.0); + + println!("- {}", existing_ver_smash.clone().to_str().unwrap()); + } + } + } + + for i in flexbox_array { + flex.add_flex_child(i, 1.0); + } + } + } + self.root = WidgetPod::new(flex); + } +} + +impl Widget for AmongUsLauncherWidget { + fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut AppData, env: &Env) { + // println!("{:?}", event); + // println!("{:?}", data); + match event { + Event::MouseDown(_a) => { + // println!("Mouse Down!"); + self.root.event(ctx, event, data, env); + } + Event::WindowConnected => { + self.build_widget(data); + ctx.children_changed(); + } + _ => { + self.root.event(ctx, event, data, env); + } + } + } + fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &AppData, env: &Env) { + self.root.lifecycle(ctx, event, data, env); + // println!("Lifecycle!") + } + fn update(&mut self, ctx: &mut UpdateCtx, old_data: &AppData, data: &AppData, env: &Env) { + self.root.update(ctx, data, env); + if old_data.among_us_path.is_empty() && !data.among_us_path.is_empty() { + ctx.submit_command(ATTEMPT_INSTALL); + println!("Detect Stuff"); + } + // println!("Update!"); + self.build_widget(data); + ctx.children_changed(); + } + fn layout( + &mut self, + ctx: &mut LayoutCtx, + bc: &BoxConstraints, + data: &AppData, + env: &Env, + ) -> Size { + self.root.layout(ctx, bc, data, env); + // println!("Layout!"); + bc.constrain((400.0, 400.0)) + } + + fn paint(&mut self, ctx: &mut PaintCtx, data: &AppData, env: &Env) { + self.root.paint(ctx, data, env); + // println!("Paint!"); + } +} diff --git a/src/among_us_version.rs b/src/among_us_version.rs index 0383474..8ebb5f2 100644 --- a/src/among_us_version.rs +++ b/src/among_us_version.rs @@ -1,6 +1,7 @@ +use druid::Data; use std::fmt; -#[derive(Ord, PartialOrd, Eq, PartialEq)] +#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug, Data)] pub struct AmongUsVersion { year: i32, month: i32, @@ -13,6 +14,16 @@ impl fmt::Display for AmongUsVersion { } } +impl Default for AmongUsVersion { + fn default() -> Self { + AmongUsVersion { + year: 0, + month: 0, + day: 0, + } + } +} + impl From<&str> for AmongUsVersion { fn from(s: &str) -> AmongUsVersion { // Ignore a prepending "v" diff --git a/src/main.rs b/src/main.rs index 11fa434..edc76d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,18 @@ // Modules +mod among_us_launcher_widget; mod among_us_version; mod semver; // Uses +use among_us_launcher_widget::*; use among_us_version::*; use semver::*; use std::{ - cell::RefCell, - fs, io, + fs, path::{Path, PathBuf}, - process, - rc::Rc, - str, + process, str, }; use fs_extra::{copy_items, dir}; @@ -24,16 +23,20 @@ use registry::Hive; // GUI stuff use druid::{ - commands, widget::*, AppDelegate, AppLauncher, Application, Data, DelegateCtx, Env, - FileDialogOptions, Handled, Target, WidgetExt, WindowDesc, + commands, widget::*, AppDelegate, AppLauncher, Data, DelegateCtx, Env, Handled, Target, + WidgetPod, WindowDesc, WindowId, }; struct Delegate; -#[derive(Data, Clone)] -struct AppData { - pub among_us_path: Rc>, +#[derive(Data, Clone, Debug)] +pub struct AppData { + pub among_us_path: String, pub installs_path: String, + pub version: SemVer, + pub initialized: bool, + pub among_us_version: AmongUsVersion, + pub data_path: String, } static AMONG_US_APPID: &str = "945360"; @@ -69,7 +72,7 @@ fn unmod_among_us_folder(folder_path: &Path) { fs::remove_dir_all(mono_path).unwrap_or_default(); } -async fn get_latest_updater_version() -> Result<(String, String), reqwest::Error> { +async fn _get_latest_updater_version() -> Result<(String, String), reqwest::Error> { let version = env!("CARGO_PKG_VERSION"); let local_sem_ver = SemVer::from(version); @@ -115,6 +118,14 @@ async fn get_latest_updater_version() -> Result<(String, String), reqwest::Error } impl AppDelegate for Delegate { + fn window_added( + &mut self, + _id: WindowId, + _data: &mut AppData, + _env: &Env, + _ctx: &mut DelegateCtx, + ) { + } fn command( &mut self, _ctx: &mut DelegateCtx, @@ -123,33 +134,114 @@ impl AppDelegate for Delegate { data: &mut AppData, _env: &Env, ) -> Handled { + // println!("Command!"); if let Some(file_info) = cmd.get(commands::OPEN_FILE) { - println!("{:?}", file_info); let among_us_folder = file_info.path(); let mut buf = among_us_folder.to_path_buf(); buf.pop(); - let mut borrow = data.among_us_path.borrow_mut(); - *borrow = String::from(buf.to_str().unwrap()); + data.among_us_path = String::from(buf.to_str().unwrap()); // Pop the selected file off the end of the path // among_us_folder.pop(); println!("{}", buf.to_str().unwrap()); - println!("Handled!"); - Application::global().quit(); + // println!("Handled!"); + //Application::global().quit(); + + return Handled::Yes; + } else if let Some(_a) = cmd.get(among_us_launcher_widget::ATTEMPT_INSTALL) { + if let Some(among_us_version) = + determine_among_us_version(String::from(data.among_us_path.clone())) + { + data.among_us_version = among_us_version.clone(); + println!("AmongUsVersion: {}", among_us_version); + let ver_url: (String, String, bool) = + determine_town_of_us_url(among_us_version.to_string().clone()).unwrap(); + + let version_smash = format!("{}-{}", among_us_version, ver_url.0.clone()); + let new_installed_path: PathBuf = + [data.installs_path.as_str(), version_smash.as_str()] + .iter() + .collect(); + + if !Path::exists(&new_installed_path) { + println!("Copying Among Us to cache location..."); + copy_folder_to_target(data.among_us_path.clone(), data.installs_path.clone()); + + let among_us_path: PathBuf = + [data.installs_path.as_str(), "Among Us\\"].iter().collect(); + + if !among_us_path.to_str().unwrap().contains("Among Us") { + process::exit(0); + } + + // Un-mod whatever we found if it's modded + unmod_among_us_folder(&among_us_path); + + println!( + "Renaming {} to {}", + among_us_path.to_str().unwrap(), + new_installed_path.to_str().unwrap() + ); + + let mut perms = fs::metadata(among_us_path.clone()).unwrap().permissions(); + perms.set_readonly(false); + fs::set_permissions(among_us_path.clone(), perms).unwrap(); + + fs::rename(among_us_path, new_installed_path.clone()).unwrap(); + let mut download_path: PathBuf = [data.data_path.as_str()].iter().collect(); + let downloaded_filename: &str = ver_url.1.rsplit('/').next().unwrap(); + download_path.push(downloaded_filename); + + if !download_path.exists() { + // println!("{:?}", download_path); + println!( + "Downloading Town of Us... Please be patient! [{}]", + ver_url.1.clone() + ); + let zip = reqwest::blocking::get(ver_url.1.clone()) + .unwrap() + .bytes() + .unwrap(); + fs::write(download_path.clone(), zip).unwrap(); + } + + let opened_zip = fs::File::open(download_path.clone()).unwrap(); + println!("Extracting mod zip file..."); + let mut archive = zip::ZipArchive::new(opened_zip).unwrap(); + archive.extract(data.data_path.clone()).unwrap(); + + fs::remove_file(download_path).unwrap(); + + let mut root_folder_path = String::new(); + for i in archive.file_names() { + root_folder_path = String::from(i.split('/').next().unwrap()); + break; + } + let extracted_path: PathBuf = + [data.data_path.as_str(), root_folder_path.as_str(), "."] + .iter() + .collect(); + println!("{}", extracted_path.to_str().unwrap()); + copy_folder_contents_to_target( + extracted_path.to_str().unwrap(), + new_installed_path.to_str().unwrap(), + ); + } + } return Handled::Yes; } + Handled::No } } -#[tokio::main] -async fn main() { +fn main() { let version = env!("CARGO_PKG_VERSION"); let title_string: String = format!("Town of Us Updater - {}", version); - println!("Updater Version: {}", version); - get_latest_updater_version().await.unwrap(); + // println!("Updater Version: {}", version); + // get_latest_updater_version().await.unwrap(); // CREATE PROGRAM DIRECTORY let mut data_path = dirs::data_dir().unwrap(); @@ -160,14 +252,7 @@ async fn main() { installs_path.push("installs"); fs::create_dir(installs_path.clone()).unwrap_or(()); - let app_data: AppData = AppData { - among_us_path: Rc::new(RefCell::new(String::from(""))), - installs_path: String::from(""), - }; - - let mut root_column: druid::widget::Flex = druid::widget::Flex::column(); - - // DETERMINE AMONG US VERSION + let version = env!("CARGO_PKG_VERSION"); let mut among_us_folder = PathBuf::new(); @@ -178,25 +263,9 @@ async fn main() { if let Ok(existing_folder) = existing_found_folder { among_us_folder.push(existing_folder); } else { - let folder_opt = detect_among_us_folder(); + let folder_opt: Option = detect_among_us_folder(); if folder_opt.is_some() { among_us_folder.push(folder_opt.unwrap()); - } else { - let open_button = Button::new("Locate Among Us").fix_height(45.0).on_click( - move |ctx, _: &mut AppData, _| { - ctx.submit_command( - druid::commands::SHOW_OPEN_PANEL.with(FileDialogOptions::new()), - ); - }, - ); - - // If we can't find Among Us, add the locate button. - // TODO: make this button then update the UI assuming it's valid - - root_column.add_flex_child(open_button, 1.0); - - // Pop the selected file off the end of the path - // among_us_folder.pop(); } if among_us_folder.exists() { @@ -204,6 +273,19 @@ async fn main() { } } + let app_data: AppData = AppData { + among_us_path: String::from(among_us_folder.clone().to_str().unwrap()), + installs_path: String::from(installs_path.clone().to_str().unwrap()), + version: SemVer::from(version), + initialized: false, + among_us_version: AmongUsVersion::default(), + data_path: String::from(data_path.clone().to_str().unwrap()), + }; + + let mut root_column: druid::widget::Flex = druid::widget::Flex::column(); + + // DETERMINE AMONG US VERSION + if let Some(among_us_folder_str) = among_us_folder.to_str() { if among_us_folder_str.len() > 0 { println!("Among Us Folder: {}", among_us_folder_str); @@ -212,167 +294,6 @@ async fn main() { } } - if let Some(among_us_version) = - determine_among_us_version(String::from(among_us_folder.to_str().unwrap())) - { - println!("AmongUsVersion: {}", among_us_version); - let ver_url: (String, String, bool) = - determine_town_of_us_url(among_us_version.to_string().clone()) - .await - .unwrap(); - - let version_smash = format!("{}-{}", among_us_version, ver_url.0.clone()); - let new_installed_path: PathBuf = [installs_path.to_str().unwrap(), version_smash.as_str()] - .iter() - .collect(); - - if !Path::exists(&new_installed_path) { - println!("Copying Among Us to cache location..."); - copy_folder_to_target( - among_us_folder.to_str().unwrap(), - installs_path.to_str().unwrap(), - ); - - let among_us_path: PathBuf = [installs_path.to_str().unwrap(), "Among Us\\"] - .iter() - .collect(); - - if !among_us_path.to_str().unwrap().contains("Among Us") { - process::exit(0); - } - - // Un-mod whatever we found if it's modded - unmod_among_us_folder(&among_us_path); - - println!( - "Renaming {} to {}", - among_us_path.to_str().unwrap(), - new_installed_path.to_str().unwrap() - ); - - let mut perms = fs::metadata(among_us_path.clone()).unwrap().permissions(); - perms.set_readonly(false); - fs::set_permissions(among_us_path.clone(), perms).unwrap(); - - fs::rename(among_us_path, new_installed_path.clone()).unwrap(); - let mut download_path = data_path.clone(); - let downloaded_filename = ver_url.1.rsplit('/').next().unwrap(); - download_path.push(downloaded_filename.clone()); - - if !Path::exists(&download_path) { - // println!("{:?}", download_path); - println!("Downloading Town of Us... [{}]", ver_url.1.clone()); - let zip = reqwest::get(ver_url.1.clone()) - .await - .unwrap() - .bytes() - .await - .unwrap(); - fs::write(download_path.clone(), zip).unwrap(); - } - - let opened_zip = fs::File::open(download_path.clone()).unwrap(); - println!("Extracting mod zip file..."); - let mut archive = zip::ZipArchive::new(opened_zip).unwrap(); - archive.extract(data_path.clone()).unwrap(); - - fs::remove_file(download_path).unwrap(); - - let mut root_folder_path = String::new(); - for i in archive.file_names() { - root_folder_path = String::from(i.split('/').next().unwrap()); - break; - } - let extracted_path: PathBuf = - [data_path.to_str().unwrap(), root_folder_path.as_str(), "."] - .iter() - .collect(); - println!("{}", extracted_path.to_str().unwrap()); - copy_folder_contents_to_target( - extracted_path.to_str().unwrap(), - new_installed_path.to_str().unwrap(), - ); - } else { - println!("Modded install already found"); - } - - // Find existing installs, make buttons for them - let install_iter = fs::read_dir(installs_path.clone()); - - if let Ok(iter) = install_iter { - let mut collection: Vec> = iter.collect(); - collection.reverse(); - - // Iterate first to find labels: - - let mut flexbox_array: Vec> = Vec::new(); - let mut auv_array: Vec = Vec::new(); - - for i in &collection { - let existing_ver_smash = i.as_ref().unwrap().file_name(); - let mut ver_smash_split = existing_ver_smash.to_str().unwrap().split('-'); - let auv = ver_smash_split.next().unwrap(); - if !auv_array.contains(&auv.to_string()) { - let label_text = format!("Among Us {}", auv); - let flex: druid::widget::Flex = druid::widget::Flex::column() - .with_flex_child( - druid::widget::Label::new(label_text.as_str()).with_text_size(24.0), - 1.0, - ) - .with_default_spacer(); - flexbox_array.push(flex); - auv_array.push(auv.to_string()); - } - } - - println!("Installs list:"); - - for i in collection { - let existing_ver_smash = i.unwrap().file_name(); - let mut ver_smash_split = existing_ver_smash.to_str().unwrap().split('-'); - let auv = ver_smash_split.next().unwrap(); - let button_string: String = - format!("Town of Us {}", ver_smash_split.next().unwrap()); - - for (index, j) in auv_array.iter().enumerate() { - if j == auv { - let mut clone = installs_path.clone(); - clone.push(existing_ver_smash.clone()); - - let mut button_row: Flex = Flex::row(); - - let fybutton = druid::widget::Button::new(button_string.as_str()) - .fix_height(45.0) - .center() - .on_click(move |_, _: &mut AppData, _| { - attempt_run_among_us(&clone); - }); - - button_row.add_flex_child(fybutton, 1.0); - - // TODO: Uncomment - // let delete_button = druid::widget::Button::new("Delete") - // .fix_height(45.0) - // .on_click(move |_, _: &mut AppData, _| {}); - - // button_row.add_flex_child(delete_button, 1.0); - - flexbox_array - .get_mut(index) - .unwrap() - .add_flex_child(button_row, 1.0); - - println!("- {}", existing_ver_smash.clone().to_str().unwrap()); - } - } - } - - for i in flexbox_array { - root_column.add_flex_child(i, 1.0); - } - } - } - // println!("Checking for updater updates..."); // let vals: (String, String) = version_check_thread_handle.join().unwrap(); @@ -381,38 +302,55 @@ async fn main() { println!("Launching main window..."); + let _main_menu: druid::MenuDesc = + druid::MenuDesc::empty().append(druid::MenuItem::new( + druid::LocalizedString::new("File"), + druid::Command::new(druid::Selector::new("test"), 0, Target::Auto), + )); + + let widget: AmongUsLauncherWidget = AmongUsLauncherWidget { + root: WidgetPod::new(Flex::column()), + }; + + root_column.add_flex_child(widget, 1.0); + launch_better_crewlink().unwrap_or(()); + let main_window = WindowDesc::new(|| root_column) .title(title_string) + // .menu(main_menu) .window_size((400.0, 400.0)); - let app_launcher = AppLauncher::with_window(main_window); + let app_launcher = AppLauncher::with_window(main_window).delegate(Delegate {}); let _external_handler = app_launcher.get_external_handle(); app_launcher.launch(app_data).unwrap(); +} - // let mut install_folder_path = installs_path.clone(); - // install_folder_path.push(version_smash); - // fs::create_dir(install_folder_path.clone()).unwrap_or(()); - - // Copy Among Us Out - // Create destination path - - // let executable_path: path::PathBuf = [new_installed_path.to_str().unwrap(), "Among Us.exe"] - // .iter() - // .collect(); - - // process::Command::new(executable_path.to_str().unwrap()) - // .spawn() - // .unwrap(); +fn launch_better_crewlink() -> std::result::Result<(), String> { + let mut dir = dirs::data_local_dir().unwrap(); + dir.push("Programs"); + dir.push("bettercrewlink"); + dir.push("Better-CrewLink.exe"); + println!("Attempting to launch Better Crew Link"); + if dir.exists() { + process::Command::new(dir.as_path().to_str().unwrap()) + .stdout(process::Stdio::null()) + .stderr(process::Stdio::null()) + .spawn() + .unwrap(); + Ok(()) + } else { + Err("Better Crew Link not found".to_string()) + } } // Returns (Version, URL) -async fn determine_town_of_us_url(among_us_version: String) -> Option<(String, String, bool)> { - let markdown = - reqwest::get("https://raw.githubusercontent.com/eDonnes124/Town-Of-Us-R/master/README.md") - .await - .unwrap() - .text() - .await - .unwrap(); +fn determine_town_of_us_url(among_us_version: String) -> Option<(String, String, bool)> { + let markdown = reqwest::blocking::get( + "https://raw.githubusercontent.com/eDonnes124/Town-Of-Us-R/master/README.md", + ) + .unwrap() + .text() + .unwrap(); + let mut line = markdown.find(&among_us_version); let mut line_offset = 0; let mut official_compatibility = false; @@ -500,12 +438,12 @@ fn copy_folder_contents_to_target>(source: T, dest: T) { fs_extra::dir::copy(source, dest, ©_opts).unwrap(); } -fn detect_steam() -> Option { +fn _detect_steam() -> Option { None } -fn detect_epic() -> Option { - let default_folder = String::from("C:\\Program Files\\Epic Games\\AmongUs"); +fn _detect_epic() -> Option { + let _default_folder = String::from("C:\\Program Files\\Epic Games\\AmongUs"); None } diff --git a/src/semver.rs b/src/semver.rs index 95eedbe..eedbd9f 100644 --- a/src/semver.rs +++ b/src/semver.rs @@ -5,7 +5,7 @@ // Uses use std::fmt; -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)] +#[derive(druid::Data, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] pub struct SemVer { pub major: i32, pub minor: i32,