diff --git a/src/among_us_version.rs b/src/among_us_version.rs new file mode 100644 index 0000000..0383474 --- /dev/null +++ b/src/among_us_version.rs @@ -0,0 +1,39 @@ +use std::fmt; + +#[derive(Ord, PartialOrd, Eq, PartialEq)] +pub struct AmongUsVersion { + year: i32, + month: i32, + day: i32, +} + +impl fmt::Display for AmongUsVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.{}.{}", self.year, self.month, self.day) + } +} + +impl From<&str> for AmongUsVersion { + fn from(s: &str) -> AmongUsVersion { + // Ignore a prepending "v" + let tmp_str = s.replace("v", ""); + + let v: Vec<&str> = tmp_str.split('.').collect(); + + AmongUsVersion { + year: v[0].parse().unwrap(), + month: v[1].parse().unwrap(), + day: v[2].parse().unwrap(), + } + } +} + +impl From<(i32, i32, i32)> for AmongUsVersion { + fn from(s: (i32, i32, i32)) -> AmongUsVersion { + AmongUsVersion { + year: s.0, + month: s.1, + day: s.2, + } + } +} diff --git a/src/main.rs b/src/main.rs index 1f8b7b7..1637d77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,61 +1,43 @@ -use std::cell::RefCell; -use std::path::{Path, PathBuf}; -use std::rc::Rc; -use std::str; -use std::*; +// Modules +mod among_us_version; + +// Uses +use among_us_version::*; + +use std::{cell::RefCell, fs, io, path::Path, path::PathBuf, process, rc::Rc, str}; -use druid::widget::*; -use druid::{ - commands, AppDelegate, AppLauncher, Application, Data, DelegateCtx, Env, FileDialogOptions, - Handled, Target, WidgetExt, WindowDesc, -}; use fs_extra::{copy_items, dir}; + use regex::Regex; +// GUI stuff +use druid::{ + commands, widget::*, AppDelegate, AppLauncher, Application, Data, DelegateCtx, Env, + FileDialogOptions, Handled, Target, WidgetExt, WindowDesc, +}; + struct Delegate; #[derive(Data, Clone)] struct AppData { pub among_us_path: Rc>, + pub installs_path: String, } -static AMONG_US_APPID: &'static str = "945360"; - -#[derive(Ord, PartialOrd, Eq, PartialEq)] -struct AmongUsVersion { - year: i32, - month: i32, - day: i32, -} - -impl fmt::Display for AmongUsVersion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}.{}.{}", self.year, self.month, self.day) - } -} - -impl From<&str> for AmongUsVersion { - fn from(s: &str) -> AmongUsVersion { - let v: Vec<&str> = s.split(".").collect(); - AmongUsVersion { - year: i32::from_str_radix(v[0], 10).unwrap(), - month: i32::from_str_radix(v[1], 10).unwrap(), - day: i32::from_str_radix(v[2], 10).unwrap(), - } - } -} +static AMONG_US_APPID: &str = "945360"; fn attempt_run_among_us(install_path: &Path) { - let executable_path: path::PathBuf = [install_path.to_str().unwrap(), "Among Us.exe"] + let executable_path: PathBuf = [install_path.to_str().unwrap(), "Among Us.exe"] .iter() .collect(); - std::process::Command::new(executable_path.to_str().unwrap()) + process::Command::new(executable_path.to_str().unwrap()) .spawn() .unwrap(); } fn unmod_among_us_folder(folder_path: &Path) { + println!("Unmodding Among Us..."); let doorstop_path: PathBuf = [folder_path.to_str().unwrap(), "doorstop_config.ini"] .iter() .collect(); @@ -65,6 +47,7 @@ fn unmod_among_us_folder(folder_path: &Path) { let winhttp_path: PathBuf = [folder_path.to_str().unwrap(), "winhttp.dll"] .iter() .collect(); + let bepinex_path: PathBuf = [folder_path.to_str().unwrap(), "BepInEx"].iter().collect(); let mono_path: PathBuf = [folder_path.to_str().unwrap(), "mono"].iter().collect(); fs::remove_file(doorstop_path).unwrap_or_default(); @@ -144,6 +127,7 @@ impl AppDelegate for Delegate { println!("{}", buf.to_str().unwrap()); println!("Handled!"); Application::global().quit(); + return Handled::Yes; } Handled::No @@ -157,7 +141,6 @@ async fn main() { println!("Updater Version: {}", version); get_latest_updater_version().await.unwrap(); - //let _version_check_thread_handle = thread::spawn(move || get_latest_updater_version()); // CREATE PROGRAM DIRECTORY let mut data_path = dirs::data_dir().unwrap(); @@ -168,25 +151,28 @@ 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 mut among_us_folder = path::PathBuf::new(); + let mut among_us_folder = PathBuf::new(); let mut existing_file_path = data_path.clone(); existing_file_path.push("existing_among_us_dir.txt"); let existing_found_folder = fs::read_to_string(existing_file_path.clone()); - if existing_found_folder.is_ok() { - among_us_folder.push(existing_found_folder.unwrap()); + if let Ok(existing_folder) = existing_found_folder { + among_us_folder.push(existing_folder); } else { let folder_opt = detect_among_us_folder(); if folder_opt.is_some() { among_us_folder.push(folder_opt.unwrap()); } else { - let path_rc = Rc::new(RefCell::new(String::from(""))); - let data: AppData = AppData { - among_us_path: path_rc.clone(), - }; let open_button = Button::new("Locate Among Us").fix_height(45.0).on_click( move |ctx, _: &mut AppData, _| { ctx.submit_command( @@ -194,183 +180,194 @@ async fn main() { ); }, ); - let flex: Flex = Flex::column().with_flex_child(open_button, 1.0); - let open_dialog = WindowDesc::new(|| flex) - .title("Among Us Not Found!") - .window_size((400.0, 400.0)); - AppLauncher::with_window(open_dialog) - .delegate(Delegate) - .launch(data) - .unwrap_or_default(); - among_us_folder = path::PathBuf::from(Rc::try_unwrap(path_rc).unwrap().take()); + // If we can't find Among Us, add the locate button. + // TODO: make this button then update the UI assuming it's valid - println!("{}", among_us_folder.to_str().unwrap()); - - // win.modal_msg(&ui, "Find Among Us", "On the following Open File dialog, navigate to your Among Us folder and select Among Us.exe"); - // let open_window = win.open_file(&ui); - // println!("{}", open_window.unwrap().to_str().unwrap()); - // among_us_folder = open_window.unwrap().clone(); + root_column.add_flex_child(open_button, 1.0); // Pop the selected file off the end of the path // among_us_folder.pop(); } - fs::write(existing_file_path, among_us_folder.to_str().unwrap()).unwrap(); + + if among_us_folder.exists() { + fs::write(existing_file_path, among_us_folder.to_str().unwrap()).unwrap(); + } } - println!("Among Us Folder: {}", among_us_folder.to_str().unwrap()); - - let among_us_version = - determine_among_us_version(String::from(among_us_folder.to_str().unwrap())).unwrap(); - 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.to_string().clone(), - ver_url.0.clone() - ); - let new_installed_path: path::PathBuf = - [installs_path.to_str().unwrap(), version_smash.as_str()] - .iter() - .collect(); - - if !path::Path::exists(&new_installed_path) { - println!("Copying Among Us to separate location..."); - copy_folder_to_target( - among_us_folder.to_str().unwrap(), - installs_path.to_str().unwrap(), - ); - - let among_us_path: path::PathBuf = [installs_path.to_str().unwrap(), "Among Us\\"] - .iter() - .collect(); - - if !among_us_path.to_str().unwrap().contains("Among Us") { - process::exit(0); + 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); + } else { + println!("Among Us Folder not automatically determined"); } + } - // 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("/").nth(0).unwrap(); - download_path.push(downloaded_filename.clone()); - - if !path::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() + 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(); - fs::write(download_path.clone(), zip).unwrap(); - } - let opened_zip = fs::File::open(download_path).unwrap(); - println!("Extracting..."); - let mut archive = zip::ZipArchive::new(opened_zip).unwrap(); - archive.extract(data_path.clone()).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(); - let mut root_folder_path = String::new(); - for i in archive.file_names() { - root_folder_path = String::from(i.split("/").nth(0).unwrap()); - break; - } - let extracted_path: path::PathBuf = - [data_path.to_str().unwrap(), root_folder_path.as_str(), "."] + 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(); - 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()); - let mut root_column: druid::widget::Flex = druid::widget::Flex::column(); - - 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()); + 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).unwrap(); + println!("Extracting mod zip file..."); + let mut archive = zip::ZipArchive::new(opened_zip).unwrap(); + archive.extract(data_path.clone()).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"); } - 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()); + // Find existing installs, make buttons for them + let install_iter = fs::read_dir(installs_path.clone()); - let mut index = 0; - for j in &auv_array { - if j == auv { - let mut clone = installs_path.clone(); - clone.push(existing_ver_smash.clone()); + if let Ok(iter) = install_iter { + let mut collection: Vec> = iter.collect(); + collection.reverse(); - let fybutton = druid::widget::Button::new(button_string.as_str()) - .fix_height(45.0) - .on_click(move |_, _: &mut u32, _| { - attempt_run_among_us(&clone); - }); + // Iterate first to find labels: - flexbox_array - .get_mut(index) - .unwrap() - .add_flex_child(fybutton, 1.0); + let mut flexbox_array: Vec> = Vec::new(); + let mut auv_array: Vec = Vec::new(); - println!("{}", existing_ver_smash.clone().to_str().unwrap()); + 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()); } - index += 1; } - } - for i in flexbox_array { - root_column.add_flex_child(i, 1.0); + + 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(); + // TODO: Auto launch latest sanctioned if the user has a setting like that + // Auto launch latest experimental as well + println!("Launching main window..."); let main_window = WindowDesc::new(|| root_column) @@ -378,8 +375,7 @@ async fn main() { .window_size((400.0, 400.0)); let app_launcher = AppLauncher::with_window(main_window); let _external_handler = app_launcher.get_external_handle(); - app_launcher.launch(1).unwrap(); - // AppLauncher::with_window(main_window).launch(1).unwrap(); + app_launcher.launch(app_data).unwrap(); // let mut install_folder_path = installs_path.clone(); // install_folder_path.push(version_smash); @@ -392,7 +388,7 @@ async fn main() { // .iter() // .collect(); - // std::process::Command::new(executable_path.to_str().unwrap()) + // process::Command::new(executable_path.to_str().unwrap()) // .spawn() // .unwrap(); } @@ -416,23 +412,6 @@ async fn determine_town_of_us_url(among_us_version: String) -> Option<(String, S println!("Sanctioned version cannot be determined, installing experimental latest..."); line = markdown.find("[Download]"); line_offset = 15; - // println!("At this point, there are two options:"); - // println!(" [1] Revert Among Us to public-previous in Steam THEN select this option."); - // println!(" [2] Attempt to use the latest version of Town of Us anyway"); - // let mut input = String::new(); - // io::stdin().read_line(&mut input).unwrap(); - // match input.trim() { - // "1" => { - // println!("Re-attempting..."); - // return None; - // } - // "2" => { - // println!("Continuing..."); - // line = markdown.find("[Download]"); - // line_offset = 15; - // } - // _ => return None, - // }; } // 100% scientific "-15" here to get the correct version since we find to [Download] above which is after the version number for that line let splits = markdown.split_at(line.unwrap() - line_offset); @@ -460,53 +439,38 @@ fn determine_among_us_version(folder_root: String) -> Option { let target_bytes: [u8; TARGET_BYTES_LEN] = [3, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0]; - let file_bytes = fs::read(asset_file).unwrap(); - //println!("{:?}", file_bytes); + if let Ok(file_bytes) = fs::read(asset_file) { + let mut bytes_str = String::new(); + let mut target_head = 0; + let mut file_index = 0; + for i in &file_bytes { + if target_head == TARGET_BYTES_LEN { + break; + } - let mut bytes_str = String::new(); - let mut target_head = 0; - let mut file_index = 0; - for i in &file_bytes { - if target_head == TARGET_BYTES_LEN { - break; + if *i == target_bytes[target_head] { + target_head += 1; + } else { + target_head = 0; + } + file_index += 1; + bytes_str += &format!("{i:x}"); } - if *i == target_bytes[target_head] { - target_head += 1; - } else { - target_head = 0; - } - file_index += 1; - bytes_str += &format!("{i:x}"); + let offset: usize = usize::from(file_bytes[file_index]); + file_index += 4; + + Some(AmongUsVersion::from( + str::from_utf8(file_bytes.get(file_index..file_index + offset).unwrap()).unwrap(), + )) + } else { + None } - let offset: usize = usize::from(file_bytes[file_index]); - file_index += 4; - - // let mut offset = 10; - - // if file_bytes[file_index + 9] == 0 { - // offset -= 1; - // } - // if file_bytes[file_index + 8] == 0 { - // offset -= 1; - // } - - // println!( - // "|{}|", - // str::from_utf8(file_bytes.get(file_index..file_index + offset).unwrap()).unwrap() - // ); - let ver = AmongUsVersion::from( - str::from_utf8(file_bytes.get(file_index..file_index + offset).unwrap()).unwrap(), - ); - println!("AmongUsVersion: {}", ver); - Some(ver) - // Some(String::from( - // str::from_utf8(file_bytes.get(file_index..file_index + offset).unwrap()).unwrap(), - // )) + //println!("{:?}", file_bytes); } -fn copy_folder_to_target>(source: T, dest: T) { +fn copy_folder_to_target>(source: T, dest: T) { let mut copy_opts = dir::CopyOptions::new(); copy_opts.overwrite = true; copy_opts.skip_exist = true; @@ -516,7 +480,7 @@ fn copy_folder_to_target>(source: T, dest: T) { copy_items(&from_paths, dest, ©_opts).unwrap(); } -fn copy_folder_contents_to_target>(source: T, dest: T) { +fn copy_folder_contents_to_target>(source: T, dest: T) { let mut copy_opts = dir::CopyOptions::new(); copy_opts.overwrite = true; copy_opts.skip_exist = true;