14 Commits
1.0.0 ... 2.0.0

Author SHA1 Message Date
38c975b44c Massive GUI code improvements!
Almost all data initialization and processing happens through druid

Tokio removed for now, web requests block again
2022-09-25 23:50:07 -07:00
762d96baee Added a semver class
Auto updater improvements, not yet ready

Registry and Epic Games auto-detection started
2022-09-03 18:56:03 -07:00
81e8eaad0f Merge branch 'feature-cleanup' 2022-08-31 12:53:29 -07:00
84d206a87b Merge branch 'master' of http://git.dormedas.com/dormedas/town-of-us-updater 2022-08-31 12:52:52 -07:00
e59c25af25 Cleaned up dead code comments
Locating the among us folder and launching the app are one in the same, need GUI work to allow it to update after location

More and changed log messages
2022-08-31 12:51:56 -07:00
cf75410860 Clippy fixes 2022-08-29 23:30:29 -07:00
35828e4b5b Clippy fixes 2022-08-29 22:59:53 -07:00
bac49bc766 Fix clippy compile error 2022-08-29 22:59:19 -07:00
9564c70a33 Remove comments, simplify 2022-08-29 19:17:47 -07:00
c9e9cbc66d Merge branch 'master' into feature-cleanup 2022-08-29 18:43:53 -07:00
88f8ca7031 Bump to 2.0.0 2022-08-29 18:43:40 -07:00
721e08cfc6 Fix warning 2022-08-29 18:43:22 -07:00
d6e3a7133b Move AmongUsVersion to its own file. Concatenate and reduce imports. 2022-08-29 18:41:03 -07:00
9e90a18c6a Update 'README.md' 2022-08-29 14:31:59 -07:00
6 changed files with 548 additions and 360 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "town-of-us-updater" name = "town-of-us-updater"
version = "1.0.0" version = "2.0.0"
edition = "2021" edition = "2021"
build = "src/build.rs" build = "src/build.rs"
@@ -16,6 +16,7 @@ iui = "0.3.0"
serde_json = "1.0" serde_json = "1.0"
tokio = {version="1", features=["full"]} tokio = {version="1", features=["full"]}
druid = "0.7.0" druid = "0.7.0"
registry = "1"
[build-dependencies] [build-dependencies]
embed-resource = "1.6" embed-resource = "1.6"

View File

@@ -1,2 +1,20 @@
# town-of-us-updater # Town of Us Updater
A tool to automatically install the **Town of Us R** mod for **Among Us**.
## Features
- Caches old builds to allow you to continue playing the mod in case of a breaking Among Us update
- GUI to select which build to play
- Auto-detection of Among Us install directory
# Contributing
Help. I have no standards. Just look at the code.
## How to Build
- [Install Rust](https://www.rust-lang.org/learn/get-started)
- Download repository
- Navigate to repository
- `cargo build`

View File

@@ -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<AppData, Flex<AppData>>,
}
impl AmongUsLauncherWidget {
pub fn build_widget(&mut self, data: &AppData) {
let mut flex: Flex<AppData> = 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<Result<fs::DirEntry, io::Error>> = 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<AppData> =
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<druid::widget::Flex<AppData>> = Vec::new();
let mut auv_array: Vec<String> = 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<AppData> = 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<AppData> = 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<AppData> 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!");
}
}

50
src/among_us_version.rs Normal file
View File

@@ -0,0 +1,50 @@
use druid::Data;
use std::fmt;
#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug, Data)]
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 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"
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,
}
}
}

View File

@@ -1,61 +1,58 @@
use std::cell::RefCell; // Modules
use std::path::{Path, PathBuf}; mod among_us_launcher_widget;
use std::rc::Rc; mod among_us_version;
use std::str; mod semver;
use std::*;
use druid::widget::*; // Uses
use druid::{ use among_us_launcher_widget::*;
commands, AppDelegate, AppLauncher, Application, Data, DelegateCtx, Env, FileDialogOptions, use among_us_version::*;
Handled, Target, WidgetExt, WindowDesc,
use semver::*;
use std::{
fs,
path::{Path, PathBuf},
process, str,
}; };
use fs_extra::{copy_items, dir}; use fs_extra::{copy_items, dir};
use regex::Regex; use regex::Regex;
use registry::Hive;
// GUI stuff
use druid::{
commands, widget::*, AppDelegate, AppLauncher, Data, DelegateCtx, Env, Handled, Target,
WidgetPod, WindowDesc, WindowId,
};
struct Delegate; struct Delegate;
#[derive(Data, Clone)] #[derive(Data, Clone, Debug)]
struct AppData { pub struct AppData {
pub among_us_path: Rc<RefCell<String>>, 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: &'static str = "945360"; static AMONG_US_APPID: &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(),
}
}
}
fn attempt_run_among_us(install_path: &Path) { 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() .iter()
.collect(); .collect();
std::process::Command::new(executable_path.to_str().unwrap()) process::Command::new(executable_path.to_str().unwrap())
.spawn() .spawn()
.unwrap(); .unwrap();
} }
fn unmod_among_us_folder(folder_path: &Path) { fn unmod_among_us_folder(folder_path: &Path) {
println!("Unmodding Among Us...");
let doorstop_path: PathBuf = [folder_path.to_str().unwrap(), "doorstop_config.ini"] let doorstop_path: PathBuf = [folder_path.to_str().unwrap(), "doorstop_config.ini"]
.iter() .iter()
.collect(); .collect();
@@ -65,6 +62,7 @@ fn unmod_among_us_folder(folder_path: &Path) {
let winhttp_path: PathBuf = [folder_path.to_str().unwrap(), "winhttp.dll"] let winhttp_path: PathBuf = [folder_path.to_str().unwrap(), "winhttp.dll"]
.iter() .iter()
.collect(); .collect();
let bepinex_path: PathBuf = [folder_path.to_str().unwrap(), "BepInEx"].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(); let mono_path: PathBuf = [folder_path.to_str().unwrap(), "mono"].iter().collect();
fs::remove_file(doorstop_path).unwrap_or_default(); fs::remove_file(doorstop_path).unwrap_or_default();
@@ -74,13 +72,10 @@ fn unmod_among_us_folder(folder_path: &Path) {
fs::remove_dir_all(mono_path).unwrap_or_default(); 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 version = env!("CARGO_PKG_VERSION");
let mut cur_vers = version.split('.'); let local_sem_ver = SemVer::from(version);
let major_ver = cur_vers.next().unwrap().parse::<i32>().unwrap();
let minor_ver = cur_vers.next().unwrap().parse::<i32>().unwrap();
let patch_ver = cur_vers.next().unwrap().parse::<i32>().unwrap();
let body = let body =
reqwest::get("https://git.dormedas.com/api/v1/repos/dormedas/town-of-us-updater/releases"); reqwest::get("https://git.dormedas.com/api/v1/repos/dormedas/town-of-us-updater/releases");
@@ -89,33 +84,33 @@ async fn get_latest_updater_version() -> Result<(String, String), reqwest::Error
for i in root.as_array().unwrap() { for i in root.as_array().unwrap() {
let tag_name = i["tag_name"].as_str().unwrap(); let tag_name = i["tag_name"].as_str().unwrap();
if let Some(trimmed_str) = tag_name.strip_prefix('v') { if let Some(trimmed_str) = tag_name.strip_prefix('v') {
let mut vers = trimmed_str.split('.'); let remote_sem_ver = SemVer::from(trimmed_str);
let tag_major_ver = vers.next().unwrap().parse::<i32>().unwrap(); if remote_sem_ver > local_sem_ver {
let tag_minor_ver = vers.next().unwrap().parse::<i32>().unwrap(); for j in i["assets"].as_array().unwrap() {
let tag_patch_ver = vers.next().unwrap().parse::<i32>().unwrap(); if j["name"].as_str().unwrap().contains(".exe") {
if tag_major_ver > major_ver // let url = j["browser_download_url"].as_str().unwrap();
|| (tag_major_ver == major_ver && tag_minor_ver > minor_ver) // let data = reqwest::get(url).await?.bytes().await?;
|| (tag_major_ver == major_ver // let mut exe_dir = std::env::current_dir().unwrap();
&& tag_minor_ver == minor_ver // exe_dir.push("town-of-us-updater-new.exe");
&& tag_patch_ver > patch_ver) // fs::write(exe_dir, data).unwrap();
{ }
println!("New version of the updater detected!"); }
println!("New version ({}) of the updater detected!", remote_sem_ver);
} }
println!("{}", trimmed_str);
} else { } else {
let mut vers = tag_name.split('.'); let remote_sem_ver = SemVer::from(tag_name);
let tag_major_ver = vers.next().unwrap().parse::<i32>().unwrap(); if remote_sem_ver > local_sem_ver {
let tag_minor_ver = vers.next().unwrap().parse::<i32>().unwrap(); for j in i["assets"].as_array().unwrap() {
let tag_patch_ver = vers.next().unwrap().parse::<i32>().unwrap(); if j["name"].as_str().unwrap().contains(".exe") {
if tag_major_ver > major_ver // let url = j["browser_download_url"].as_str().unwrap();
|| (tag_major_ver == major_ver && tag_minor_ver > minor_ver) // let data = reqwest::get(url).await?.bytes().await?;
|| (tag_major_ver == major_ver // let mut exe_dir = std::env::current_dir().unwrap();
&& tag_minor_ver == minor_ver // exe_dir.push("town-of-us-updater-new.exe");
&& tag_patch_ver > patch_ver) // fs::write(exe_dir, data).unwrap();
{ }
println!("New version of the updater detected!"); }
println!("New version ({}) of the updater detected!", remote_sem_ver);
} }
println!("{}", i["tag_name"]);
} }
} }
@@ -123,6 +118,14 @@ async fn get_latest_updater_version() -> Result<(String, String), reqwest::Error
} }
impl AppDelegate<AppData> for Delegate { impl AppDelegate<AppData> for Delegate {
fn window_added(
&mut self,
_id: WindowId,
_data: &mut AppData,
_env: &Env,
_ctx: &mut DelegateCtx,
) {
}
fn command( fn command(
&mut self, &mut self,
_ctx: &mut DelegateCtx, _ctx: &mut DelegateCtx,
@@ -131,122 +134,41 @@ impl AppDelegate<AppData> for Delegate {
data: &mut AppData, data: &mut AppData,
_env: &Env, _env: &Env,
) -> Handled { ) -> Handled {
// println!("Command!");
if let Some(file_info) = cmd.get(commands::OPEN_FILE) { if let Some(file_info) = cmd.get(commands::OPEN_FILE) {
println!("{:?}", file_info);
let among_us_folder = file_info.path(); let among_us_folder = file_info.path();
let mut buf = among_us_folder.to_path_buf(); let mut buf = among_us_folder.to_path_buf();
buf.pop(); buf.pop();
let mut borrow = data.among_us_path.borrow_mut(); data.among_us_path = String::from(buf.to_str().unwrap());
*borrow = String::from(buf.to_str().unwrap());
// Pop the selected file off the end of the path // Pop the selected file off the end of the path
// among_us_folder.pop(); // among_us_folder.pop();
println!("{}", buf.to_str().unwrap()); println!("{}", buf.to_str().unwrap());
println!("Handled!"); // println!("Handled!");
Application::global().quit(); //Application::global().quit();
return Handled::Yes; return Handled::Yes;
} } else if let Some(_a) = cmd.get(among_us_launcher_widget::ATTEMPT_INSTALL) {
Handled::No 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();
#[tokio::main] println!("AmongUsVersion: {}", among_us_version);
async 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();
//let _version_check_thread_handle = thread::spawn(move || get_latest_updater_version());
// CREATE PROGRAM DIRECTORY
let mut data_path = dirs::data_dir().unwrap();
data_path.push("town-of-us-updater");
fs::create_dir(data_path.clone()).unwrap_or(());
let mut installs_path = data_path.clone();
installs_path.push("installs");
fs::create_dir(installs_path.clone()).unwrap_or(());
// DETERMINE AMONG US VERSION
let mut among_us_folder = path::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());
} 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(
druid::commands::SHOW_OPEN_PANEL.with(FileDialogOptions::new()),
);
},
);
let flex: Flex<AppData> = 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());
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();
// 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();
}
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) = let ver_url: (String, String, bool) =
determine_town_of_us_url(among_us_version.to_string().clone()) determine_town_of_us_url(among_us_version.to_string().clone()).unwrap();
.await
.unwrap();
let version_smash = format!( let version_smash = format!("{}-{}", among_us_version, ver_url.0.clone());
"{}-{}", let new_installed_path: PathBuf =
among_us_version.to_string().clone(), [data.installs_path.as_str(), version_smash.as_str()]
ver_url.0.clone()
);
let new_installed_path: path::PathBuf =
[installs_path.to_str().unwrap(), version_smash.as_str()]
.iter() .iter()
.collect(); .collect();
if !path::Path::exists(&new_installed_path) { if !Path::exists(&new_installed_path) {
println!("Copying Among Us to separate location..."); println!("Copying Among Us to cache location...");
copy_folder_to_target( copy_folder_to_target(data.among_us_path.clone(), data.installs_path.clone());
among_us_folder.to_str().unwrap(),
installs_path.to_str().unwrap(),
);
let among_us_path: path::PathBuf = [installs_path.to_str().unwrap(), "Among Us\\"] let among_us_path: PathBuf =
.iter() [data.installs_path.as_str(), "Among Us\\"].iter().collect();
.collect();
if !among_us_path.to_str().unwrap().contains("Among Us") { if !among_us_path.to_str().unwrap().contains("Among Us") {
process::exit(0); process::exit(0);
@@ -266,34 +188,37 @@ async fn main() {
fs::set_permissions(among_us_path.clone(), perms).unwrap(); fs::set_permissions(among_us_path.clone(), perms).unwrap();
fs::rename(among_us_path, new_installed_path.clone()).unwrap(); fs::rename(among_us_path, new_installed_path.clone()).unwrap();
let mut download_path = data_path.clone(); let mut download_path: PathBuf = [data.data_path.as_str()].iter().collect();
let downloaded_filename = ver_url.1.rsplit("/").nth(0).unwrap(); let downloaded_filename: &str = ver_url.1.rsplit('/').next().unwrap();
download_path.push(downloaded_filename.clone()); download_path.push(downloaded_filename);
if !path::Path::exists(&download_path) { if !download_path.exists() {
// println!("{:?}", download_path); // println!("{:?}", download_path);
println!("Downloading Town of Us... [{}]", ver_url.1.clone()); println!(
let zip = reqwest::get(ver_url.1.clone()) "Downloading Town of Us... Please be patient! [{}]",
.await ver_url.1.clone()
);
let zip = reqwest::blocking::get(ver_url.1.clone())
.unwrap() .unwrap()
.bytes() .bytes()
.await
.unwrap(); .unwrap();
fs::write(download_path.clone(), zip).unwrap(); fs::write(download_path.clone(), zip).unwrap();
} }
let opened_zip = fs::File::open(download_path).unwrap(); let opened_zip = fs::File::open(download_path.clone()).unwrap();
println!("Extracting..."); println!("Extracting mod zip file...");
let mut archive = zip::ZipArchive::new(opened_zip).unwrap(); let mut archive = zip::ZipArchive::new(opened_zip).unwrap();
archive.extract(data_path.clone()).unwrap(); archive.extract(data.data_path.clone()).unwrap();
fs::remove_file(download_path).unwrap();
let mut root_folder_path = String::new(); let mut root_folder_path = String::new();
for i in archive.file_names() { for i in archive.file_names() {
root_folder_path = String::from(i.split("/").nth(0).unwrap()); root_folder_path = String::from(i.split('/').next().unwrap());
break; break;
} }
let extracted_path: path::PathBuf = let extracted_path: PathBuf =
[data_path.to_str().unwrap(), root_folder_path.as_str(), "."] [data.data_path.as_str(), root_folder_path.as_str(), "."]
.iter() .iter()
.collect(); .collect();
println!("{}", extracted_path.to_str().unwrap()); println!("{}", extracted_path.to_str().unwrap());
@@ -301,111 +226,131 @@ async fn main() {
extracted_path.to_str().unwrap(), extracted_path.to_str().unwrap(),
new_installed_path.to_str().unwrap(), new_installed_path.to_str().unwrap(),
); );
}
}
return Handled::Yes;
}
Handled::No
}
}
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();
// CREATE PROGRAM DIRECTORY
let mut data_path = dirs::data_dir().unwrap();
data_path.push("town-of-us-updater");
fs::create_dir(data_path.clone()).unwrap_or(());
let mut installs_path = data_path.clone();
installs_path.push("installs");
fs::create_dir(installs_path.clone()).unwrap_or(());
let version = env!("CARGO_PKG_VERSION");
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 let Ok(existing_folder) = existing_found_folder {
among_us_folder.push(existing_folder);
} else { } else {
println!("Modded install already found"); let folder_opt: Option<String> = detect_among_us_folder();
if folder_opt.is_some() {
among_us_folder.push(folder_opt.unwrap());
} }
// Find existing installs, make buttons for them if among_us_folder.exists() {
let install_iter = fs::read_dir(installs_path.clone()); fs::write(existing_file_path, among_us_folder.to_str().unwrap()).unwrap();
let mut root_column: druid::widget::Flex<u32> = druid::widget::Flex::column();
if let Ok(iter) = install_iter {
let mut collection: Vec<Result<fs::DirEntry, std::io::Error>> = iter.collect();
collection.reverse();
// Iterate first to find labels:
let mut flexbox_array: Vec<druid::widget::Flex<u32>> = Vec::new();
let mut auv_array: Vec<String> = 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<u32> = 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());
} }
} }
for i in collection { let app_data: AppData = AppData {
let existing_ver_smash = i.unwrap().file_name(); among_us_path: String::from(among_us_folder.clone().to_str().unwrap()),
let mut ver_smash_split = existing_ver_smash.to_str().unwrap().split("-"); installs_path: String::from(installs_path.clone().to_str().unwrap()),
let auv = ver_smash_split.next().unwrap(); version: SemVer::from(version),
let button_string: String = format!("Town of Us {}", ver_smash_split.next().unwrap()); initialized: false,
among_us_version: AmongUsVersion::default(),
data_path: String::from(data_path.clone().to_str().unwrap()),
};
let mut index = 0; let mut root_column: druid::widget::Flex<AppData> = druid::widget::Flex::column();
for j in &auv_array {
if j == auv {
let mut clone = installs_path.clone();
clone.push(existing_ver_smash.clone());
let fybutton = druid::widget::Button::new(button_string.as_str()) // DETERMINE AMONG US VERSION
.fix_height(45.0)
.on_click(move |_, _: &mut u32, _| {
attempt_run_among_us(&clone);
});
flexbox_array if let Some(among_us_folder_str) = among_us_folder.to_str() {
.get_mut(index) if among_us_folder_str.len() > 0 {
.unwrap() println!("Among Us Folder: {}", among_us_folder_str);
.add_flex_child(fybutton, 1.0); } else {
println!("Among Us Folder not automatically determined");
println!("{}", existing_ver_smash.clone().to_str().unwrap());
}
index += 1;
}
}
for i in flexbox_array {
root_column.add_flex_child(i, 1.0);
} }
} }
// println!("Checking for updater updates..."); // println!("Checking for updater updates...");
// let vals: (String, String) = version_check_thread_handle.join().unwrap(); // 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..."); println!("Launching main window...");
let _main_menu: druid::MenuDesc<AppData> =
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) let main_window = WindowDesc::new(|| root_column)
.title(title_string) .title(title_string)
// .menu(main_menu)
.window_size((400.0, 400.0)); .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(); let _external_handler = app_launcher.get_external_handle();
app_launcher.launch(1).unwrap(); app_launcher.launch(app_data).unwrap();
// AppLauncher::with_window(main_window).launch(1).unwrap(); }
// let mut install_folder_path = installs_path.clone(); fn launch_better_crewlink() -> std::result::Result<(), String> {
// install_folder_path.push(version_smash); let mut dir = dirs::data_local_dir().unwrap();
// fs::create_dir(install_folder_path.clone()).unwrap_or(()); dir.push("Programs");
dir.push("bettercrewlink");
// Copy Among Us Out dir.push("Better-CrewLink.exe");
// Create destination path println!("Attempting to launch Better Crew Link");
if dir.exists() {
// let executable_path: path::PathBuf = [new_installed_path.to_str().unwrap(), "Among Us.exe"] process::Command::new(dir.as_path().to_str().unwrap())
// .iter() .stdout(process::Stdio::null())
// .collect(); .stderr(process::Stdio::null())
.spawn()
// std::process::Command::new(executable_path.to_str().unwrap()) .unwrap();
// .spawn() Ok(())
// .unwrap(); } else {
Err("Better Crew Link not found".to_string())
}
} }
// Returns (Version, URL) // Returns (Version, URL)
async fn determine_town_of_us_url(among_us_version: String) -> Option<(String, String, bool)> { fn determine_town_of_us_url(among_us_version: String) -> Option<(String, String, bool)> {
let markdown = let markdown = reqwest::blocking::get(
reqwest::get("https://raw.githubusercontent.com/eDonnes124/Town-Of-Us-R/master/README.md") "https://raw.githubusercontent.com/eDonnes124/Town-Of-Us-R/master/README.md",
.await )
.unwrap() .unwrap()
.text() .text()
.await
.unwrap(); .unwrap();
let mut line = markdown.find(&among_us_version); let mut line = markdown.find(&among_us_version);
let mut line_offset = 0; let mut line_offset = 0;
let mut official_compatibility = false; let mut official_compatibility = false;
@@ -416,23 +361,6 @@ async fn determine_town_of_us_url(among_us_version: String) -> Option<(String, S
println!("Sanctioned version cannot be determined, installing experimental latest..."); println!("Sanctioned version cannot be determined, installing experimental latest...");
line = markdown.find("[Download]"); line = markdown.find("[Download]");
line_offset = 15; 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 // 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); let splits = markdown.split_at(line.unwrap() - line_offset);
@@ -460,9 +388,7 @@ fn determine_among_us_version(folder_root: String) -> Option<AmongUsVersion> {
let target_bytes: [u8; TARGET_BYTES_LEN] = [3, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0]; 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(); if let Ok(file_bytes) = fs::read(asset_file) {
//println!("{:?}", file_bytes);
let mut bytes_str = String::new(); let mut bytes_str = String::new();
let mut target_head = 0; let mut target_head = 0;
let mut file_index = 0; let mut file_index = 0;
@@ -483,30 +409,17 @@ fn determine_among_us_version(folder_root: String) -> Option<AmongUsVersion> {
let offset: usize = usize::from(file_bytes[file_index]); let offset: usize = usize::from(file_bytes[file_index]);
file_index += 4; file_index += 4;
// let mut offset = 10; Some(AmongUsVersion::from(
// 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(), str::from_utf8(file_bytes.get(file_index..file_index + offset).unwrap()).unwrap(),
); ))
println!("AmongUsVersion: {}", ver); } else {
Some(ver) None
// Some(String::from( }
// str::from_utf8(file_bytes.get(file_index..file_index + offset).unwrap()).unwrap(),
// )) //println!("{:?}", file_bytes);
} }
fn copy_folder_to_target<T: AsRef<path::Path>>(source: T, dest: T) { fn copy_folder_to_target<T: AsRef<Path>>(source: T, dest: T) {
let mut copy_opts = dir::CopyOptions::new(); let mut copy_opts = dir::CopyOptions::new();
copy_opts.overwrite = true; copy_opts.overwrite = true;
copy_opts.skip_exist = true; copy_opts.skip_exist = true;
@@ -516,7 +429,7 @@ fn copy_folder_to_target<T: AsRef<path::Path>>(source: T, dest: T) {
copy_items(&from_paths, dest, &copy_opts).unwrap(); copy_items(&from_paths, dest, &copy_opts).unwrap();
} }
fn copy_folder_contents_to_target<T: AsRef<path::Path>>(source: T, dest: T) { fn copy_folder_contents_to_target<T: AsRef<Path>>(source: T, dest: T) {
let mut copy_opts = dir::CopyOptions::new(); let mut copy_opts = dir::CopyOptions::new();
copy_opts.overwrite = true; copy_opts.overwrite = true;
copy_opts.skip_exist = true; copy_opts.skip_exist = true;
@@ -525,7 +438,25 @@ fn copy_folder_contents_to_target<T: AsRef<path::Path>>(source: T, dest: T) {
fs_extra::dir::copy(source, dest, &copy_opts).unwrap(); fs_extra::dir::copy(source, dest, &copy_opts).unwrap();
} }
fn _detect_steam() -> Option<String> {
None
}
fn _detect_epic() -> Option<String> {
let _default_folder = String::from("C:\\Program Files\\Epic Games\\AmongUs");
None
}
fn detect_among_us_folder() -> Option<String> { fn detect_among_us_folder() -> Option<String> {
if let Ok(steam_regkey) = Hive::LocalMachine.open(
r"SOFTWARE\WOW6432Node\Valve\Steam",
registry::Security::Read,
) {
if let Ok(steam_folder) = steam_regkey.value("InstallPath") {
println!("{:?}", steam_folder);
}
}
let library_folder = let library_folder =
fs::read_to_string("C:\\Program Files (x86)\\Steam\\steamapps\\libraryfolders.vdf"); fs::read_to_string("C:\\Program Files (x86)\\Steam\\steamapps\\libraryfolders.vdf");
@@ -539,20 +470,11 @@ fn detect_among_us_folder() -> Option<String> {
} else { } else {
library_folder_string.truncate(appid_index.unwrap()); library_folder_string.truncate(appid_index.unwrap());
} }
//println!("{}", library_folder_string);
let path_regex = Regex::new(r#"path"\s+"([Z-a\w\d\s\\\(\):]+)""#).unwrap(); let path_regex = Regex::new(r#"path"\s+"([Z-a\w\d\s\\\(\):]+)""#).unwrap();
let caps: regex::CaptureMatches = path_regex.captures_iter(&library_folder_string); let caps: regex::CaptureMatches = path_regex.captures_iter(&library_folder_string);
let last_path = caps.last().unwrap(); let last_path = caps.last().unwrap();
let start = last_path.get(last_path.len() - 1).unwrap(); let start = last_path.get(last_path.len() - 1).unwrap();
/*
println!(
"{}",
library_folder_string
.get(start.start()..start.end())
.unwrap()
);
*/
return Some(format!( return Some(format!(
"{}\\\\steamapps\\\\common\\\\Among Us\\\\", "{}\\\\steamapps\\\\common\\\\Among Us\\\\",

31
src/semver.rs Normal file
View File

@@ -0,0 +1,31 @@
//! # SemVer
//! A simple Semantic Versioning struct to handle comparisons and ordering
// Uses
use std::fmt;
#[derive(druid::Data, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct SemVer {
pub major: i32,
pub minor: i32,
pub patch: i32,
}
impl fmt::Display for SemVer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
impl From<&str> for SemVer {
fn from(s: &str) -> SemVer {
let v: Vec<&str> = s.split('.').collect();
SemVer {
major: v[0].parse().unwrap(),
minor: v[1].parse().unwrap(),
patch: v[2].parse().unwrap(),
}
}
}