31 Commits

Author SHA1 Message Date
63b58bbe05 Added config file
Added Steam and Among Us process detection

Added ability to auto-run the blessed build
2023-02-03 15:34:33 -08:00
399b975aaf Version 4.1.0
Split AppData logic to its own, large, file

Add Steam detection on Windows

Added asynchronous installation of mod to make GUI responsive
2023-01-31 22:18:17 -08:00
ed50979397 Version 4.0.2
Fixed delete mode being on by default
2023-01-29 16:27:10 -08:00
762b2f61aa Version 4.0.1
Fix updater not installing new versions
2023-01-28 23:57:51 -08:00
3bd05ba8dd Bump to version 4.0.0
Switch from druid to egui

Support deletion of builds

Auto-updater!

Add a test
2023-01-28 23:29:20 -08:00
ff311475dc Add blessed build text coloring 2023-01-15 14:31:55 -08:00
8e61c8a2f2 Merge branch 'hotfix-2.0.0' 2022-11-28 19:37:10 -08:00
97cdf4829a push to 2.3 2022-11-28 19:23:30 -08:00
7244c24fe4 Fix sorting 2022-11-28 19:20:05 -08:00
fbc4c747a5 Merge branch 'hotfix-2.0.0' 2022-11-14 18:37:53 -08:00
7f1a6c6bc1 Bump to 2.2.0 2022-11-14 18:20:32 -08:00
856cffd740 No timeouts while downloading 2022-11-14 18:15:24 -08:00
9468194718 Remove windows-only components
Attempt to divide initialization
2022-10-11 21:03:33 -07:00
69f67d303e Merge branch 'master' of http://git.dormedas.com/dormedas/town-of-us-updater 2022-09-26 19:08:26 -07:00
6d05440d98 Merge branch 'hotfix-2.0.0' 2022-09-26 19:07:54 -07:00
686d24f7b5 Hotfix: Fix initialization of mod if Among Us path is known 2022-09-26 19:02:17 -07:00
972c310673 Update 'README.md' 2022-09-26 14:47:01 -07:00
90b703ee30 Bump to 3.0.0 2022-09-26 08:15:17 -07:00
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
7 changed files with 1131 additions and 396 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "town-of-us-updater" name = "town-of-us-updater"
version = "1.0.0" version = "4.1.0"
edition = "2021" edition = "2021"
build = "src/build.rs" build = "src/build.rs"
@@ -12,10 +12,15 @@ regex = "1.5"
fs_extra = "1.2.0" fs_extra = "1.2.0"
dirs = "4.0.0" dirs = "4.0.0"
reqwest = {version = "0.11.11", features = ["blocking"]} reqwest = {version = "0.11.11", features = ["blocking"]}
iui = "0.3.0" serde = {version= "1.0.143", features = ["derive"]}
serde_json = "1.0" serde_json = "1.0"
md-5 = "0.10.5"
tokio = {version="1", features=["full"]} tokio = {version="1", features=["full"]}
druid = "0.7.0" registry = "1"
egui = "0.20.1"
eframe = "0.20.1"
rfd = "0.10"
tasklist = "0.2.12"
[build-dependencies] [build-dependencies]
embed-resource = "1.6" embed-resource = "1.6"

View File

@@ -7,6 +7,7 @@ A tool to automatically install the **Town of Us R** mod for **Among Us**.
- Caches old builds to allow you to continue playing the mod in case of a breaking Among Us update - 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 - GUI to select which build to play
- Auto-detection of Among Us install directory - Auto-detection of Among Us install directory
- Auto-launch of BetterCrewLink if installed
# Contributing # Contributing

91
src/among_us_version.rs Normal file
View File

@@ -0,0 +1,91 @@
use std::fmt;
#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug)]
pub struct AmongUsVersion {
year: i32,
month: i32,
day: i32,
}
impl AmongUsVersion {
pub fn as_american_string(&self) -> String {
format!("{}-{}-{}", self.month, self.day, self.year)
}
}
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,
}
}
}
#[test]
fn ordering_test() {
let mut vec = Vec::new();
vec.push(AmongUsVersion {
year: 2019,
month: 1,
day: 25,
});
vec.push(AmongUsVersion {
year: 2022,
month: 12,
day: 25,
});
vec.push(AmongUsVersion {
year: 2022,
month: 12,
day: 14,
});
vec.push(AmongUsVersion {
year: 2022,
month: 12,
day: 8,
});
vec.sort();
assert_eq!(vec[0].year, 2019);
assert_eq!(vec[1].year, 2022);
assert_eq!(vec[1].day, 8);
assert_eq!(vec[2].year, 2022);
assert_eq!(vec[2].day, 14);
assert_eq!(vec[3].year, 2022);
assert_eq!(vec[3].day, 25);
}

636
src/app_data.rs Normal file
View File

@@ -0,0 +1,636 @@
// Crate imports
use crate::among_us_version::*;
use crate::config;
use crate::config::*;
use crate::semver::*;
// Other imports
use eframe::egui;
use std::thread::JoinHandle;
use std::{
fs,
path::{Path, PathBuf},
process, thread,
};
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum InitializingState {
StartingGUI,
FindingAmongUs,
DeterminingVersion,
DeterminingToUVersion,
CopyingAmongUs,
UnmoddingAmongUs,
DownloadingZip,
Unzipping,
ModdingAmongUs,
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum GlobalAppState {
Initializing(InitializingState),
Initialized,
Installing,
Playing,
}
#[derive(Debug)]
pub struct AppData {
pub among_us_path: String,
pub installs_path: String,
pub version: SemVer,
pub delete_mode: bool,
pub among_us_version: AmongUsVersion,
pub data_path: String,
pub app_state: GlobalAppState,
pub blessed_version: String,
pub update_installs_list: bool,
pub installs_last_update_time: std::time::Instant,
pub installs_list: Vec<PathBuf>,
pub init_install_path: Option<PathBuf>,
pub init_download_url: Option<String>,
pub launcher_running: bool,
pub config: AppConfig,
pub among_us_launched: bool,
pub among_us_running: bool,
// Thread handles
pub find_among_us_handle: Option<JoinHandle<Option<(String, LauncherType)>>>,
pub determine_au_version_handle: Option<JoinHandle<Option<AmongUsVersion>>>,
pub determine_tou_version_handle: Option<JoinHandle<Option<(String, String, bool)>>>,
pub copy_au_handle: Option<JoinHandle<()>>,
pub download_tou_handle: Option<JoinHandle<()>>,
}
impl Default for AppData {
fn default() -> AppData {
AppData {
among_us_path: String::new(),
installs_path: String::new(),
version: SemVer {
major: 0,
minor: 0,
patch: 0,
},
delete_mode: false,
among_us_version: AmongUsVersion::default(),
data_path: String::new(),
app_state: GlobalAppState::Initialized,
blessed_version: String::new(),
update_installs_list: true,
installs_last_update_time: std::time::Instant::now(),
installs_list: Vec::new(),
init_install_path: None,
init_download_url: None,
launcher_running: false,
config: AppConfig::default(),
among_us_launched: false,
among_us_running: false,
find_among_us_handle: None,
determine_au_version_handle: None,
determine_tou_version_handle: None,
copy_au_handle: None,
download_tou_handle: None,
}
}
}
impl AppData {
pub fn detect_running_stores(&mut self) {
if cfg!(windows) {
unsafe {
let tl = tasklist::Tasklist::new();
self.launcher_running = false;
for i in tl {
if i.get_pname() == "steam.exe" {
self.launcher_running = true;
// println!("Steam running!");
} else if i.get_pname() == "EpicGamesLauncher.exe" {
// println!("Epic is running!");
} else {
}
}
}
}
}
pub fn detect_running_among_us(&mut self) {
if cfg!(windows) {
unsafe {
let tl = tasklist::Tasklist::new();
self.among_us_running = false;
for i in tl {
if i.get_pname() == "Among Us.exe" {
self.among_us_running = true;
}
}
}
}
}
pub fn on_path_added(&mut self) {
if self.among_us_path.is_empty() {
return;
}
if let Some(among_us_version) =
crate::determine_among_us_version(String::from(self.among_us_path.clone()))
{
self.among_us_version = among_us_version.clone();
println!("AmongUsVersion: {}", among_us_version);
}
self.app_state = GlobalAppState::Initializing(InitializingState::DeterminingVersion);
let ver_url: (String, String, bool) =
crate::determine_town_of_us_url(self.among_us_version.to_string().clone()).unwrap();
let version_smash = format!("{}-{}", self.among_us_version, ver_url.0.clone());
let new_installed_path: PathBuf = [self.installs_path.as_str(), version_smash.as_str()]
.iter()
.collect();
if !Path::exists(&new_installed_path) {
println!("Copying Among Us to cache location...");
crate::copy_folder_to_target(self.among_us_path.clone(), self.installs_path.clone());
let among_us_path: PathBuf = [self.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
crate::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 = [self.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 client = reqwest::blocking::Client::builder()
.timeout(None)
.build()
.unwrap();
let zip_request = client.get(ver_url.1.clone()).build().unwrap();
let zip = client.execute(zip_request).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(self.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 = [self.data_path.as_str(), root_folder_path.as_str(), "."]
.iter()
.collect();
println!("{}", extracted_path.to_str().unwrap());
crate::copy_folder_contents_to_target(
extracted_path.to_str().unwrap(),
new_installed_path.to_str().unwrap(),
);
}
self.app_state = GlobalAppState::Initialized;
self.update_installs_list = true;
}
pub fn detect_installs(&mut self) {
let install_iter = fs::read_dir(self.installs_path.clone());
if let Ok(iter) = install_iter {
self.installs_list = iter
.map(|elem| PathBuf::from(elem.unwrap().file_name()))
.collect();
} else {
self.installs_list.clear();
}
self.installs_last_update_time = std::time::Instant::now();
}
fn draw_layout(&mut self, ui: &mut egui::Ui) {
let now = std::time::Instant::now();
if now.duration_since(self.installs_last_update_time) > std::time::Duration::from_secs(10) {
self.detect_installs();
}
let mut collection = self.installs_list.clone();
if collection.is_empty() {
if ui.button("Locate your Among Us Directory").clicked() {
if let Some(mut path) = rfd::FileDialog::new().pick_file() {
path.pop();
self.among_us_path = path.display().to_string();
self.on_path_added();
}
}
return;
}
collection.reverse();
let mut auv_array: Vec<AmongUsVersion> = Vec::new();
for i in &collection {
let existing_ver_smash = i;
let mut ver_smash_split = existing_ver_smash.to_str().unwrap().split('-');
let among_us_version = AmongUsVersion::from(ver_smash_split.next().unwrap());
if !auv_array.contains(&among_us_version) {
auv_array.push(among_us_version);
}
}
auv_array.sort();
auv_array.reverse();
ui.separator();
for i in &auv_array {
ui.label(
egui::RichText::new(format!("Among Us {}", i.as_american_string())).size(35.0),
);
for j in &collection {
let existing_ver_smash = j;
let mut ver_smash_split = existing_ver_smash.to_str().unwrap().split('-');
let mut blessed_split = self.blessed_version.as_str().split('-');
let among_us_version = AmongUsVersion::from(ver_smash_split.next().unwrap());
let tou_version = ver_smash_split.next().unwrap();
let blessed_version = AmongUsVersion::from(blessed_split.next().unwrap());
let blessed_tou_version = blessed_split.next().unwrap();
let button_string: String = format!("Town of Us {}", tou_version);
if i == &among_us_version {
let mut clone: PathBuf = PathBuf::from(self.installs_path.clone());
clone.push(existing_ver_smash.clone());
ui.horizontal(|ui| {
if i == &blessed_version && tou_version == blessed_tou_version {
let button_text = egui::RichText::new(button_string.clone())
.size(25.0)
.color(egui::Color32::GREEN);
let auto_run = self.config.auto_run_blessed
&& !self.among_us_launched
&& self.launcher_running;
if ui
.add_enabled(!self.among_us_running, egui::Button::new(button_text))
.clicked()
|| auto_run
{
crate::attempt_run_among_us(&clone);
self.among_us_launched = true;
}
} else {
let button_text = egui::RichText::new(button_string.clone()).size(25.0);
if ui
.add_enabled(!self.among_us_running, egui::Button::new(button_text))
.clicked()
{
crate::attempt_run_among_us(&clone);
self.among_us_launched = true;
}
}
ui.set_enabled(true);
if !self.delete_mode {
ui.set_visible(false);
}
let button_text = egui::RichText::new("DELETE")
.size(25.0)
.color(egui::Color32::RED);
if ui.button(button_text).clicked() {
if i != &blessed_version || tou_version != blessed_tou_version {
crate::attempt_delete_among_us(&clone);
self.update_installs_list = true;
}
}
ui.set_visible(true);
});
}
}
ui.separator();
}
}
}
impl eframe::App for AppData {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
match &mut self.app_state {
GlobalAppState::Initializing(sub_state) => match sub_state {
InitializingState::StartingGUI => {
if !self.among_us_path.is_empty() {
// If we pulled in a cached location, skip straight to ToU version check
self.app_state =
GlobalAppState::Initializing(InitializingState::DeterminingVersion);
} else {
self.app_state =
GlobalAppState::Initializing(InitializingState::FindingAmongUs);
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Initializing...");
});
ctx.request_repaint();
}
InitializingState::FindingAmongUs => {
// if !self.among_us_path.is_empty() {
// self.app_state =
// GlobalAppState::Initializing(InitializingState::DeterminingVersion);
// }
if self.find_among_us_handle.as_ref().is_some() {
if self.find_among_us_handle.as_ref().unwrap().is_finished() {
if let Some(handle) = self.find_among_us_handle.take() {
println!("Thread done, retrieving data...");
let (path, launcher_type) = handle.join().unwrap().unwrap();
self.among_us_path = path.clone();
self.config.among_us_path = Some(PathBuf::from(path));
self.config.launcher_type = launcher_type;
config::save_config(&self.config);
self.app_state = GlobalAppState::Initializing(
InitializingState::DeterminingVersion,
);
}
}
} else {
println!("Spawning thread...");
self.find_among_us_handle = Some(thread::spawn(|| {
return crate::detect_among_us_folder();
}));
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Finding Among Us...");
});
ctx.request_repaint();
}
InitializingState::DeterminingVersion => {
if self.among_us_version != AmongUsVersion::default() {
self.app_state =
GlobalAppState::Initializing(InitializingState::DeterminingToUVersion);
} else {
if self.determine_au_version_handle.as_ref().is_some() {
if self
.determine_au_version_handle
.as_ref()
.unwrap()
.is_finished()
{
if let Some(handle) = self.determine_au_version_handle.take() {
println!("Thread done, retrieving data...");
self.among_us_version = handle.join().unwrap().unwrap();
}
}
} else {
println!("Spawning thread...");
let path = self.among_us_path.clone();
self.determine_au_version_handle = Some(thread::spawn(move || {
return crate::determine_among_us_version(path);
}));
}
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Determining Among Us Version...");
});
ctx.request_repaint();
}
InitializingState::DeterminingToUVersion => {
if self.determine_tou_version_handle.as_ref().is_some() {
if self
.determine_tou_version_handle
.as_ref()
.unwrap()
.is_finished()
{
if let Some(handle) = self.determine_tou_version_handle.take() {
println!("Thread done, retrieving data...");
let result = handle.join().unwrap().unwrap();
let version_smash =
format!("{}-{}", self.among_us_version, result.0.clone());
let new_path: PathBuf =
[self.installs_path.as_str(), version_smash.as_str()]
.iter()
.collect();
self.init_install_path = Some(new_path.clone());
self.init_download_url = Some(result.1.clone());
if !Path::exists(&new_path) {
self.app_state = GlobalAppState::Initializing(
InitializingState::CopyingAmongUs,
);
} else {
self.app_state = GlobalAppState::Initialized;
}
}
}
} else {
println!("Spawning thread...");
let ver = self.among_us_version.clone();
self.determine_tou_version_handle = Some(thread::spawn(move || {
return crate::determine_town_of_us_url(ver.to_string());
}));
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Determining Among Us Version...");
});
ctx.request_repaint();
}
InitializingState::CopyingAmongUs => {
if self.copy_au_handle.as_ref().is_some() {
if self.copy_au_handle.as_ref().unwrap().is_finished() {
self.copy_au_handle = None;
self.app_state =
GlobalAppState::Initializing(InitializingState::UnmoddingAmongUs);
}
} else {
println!("Spawning thread...");
let among_us_path = self.among_us_path.clone();
let installs_path = self.installs_path.clone();
self.copy_au_handle = Some(thread::spawn(move || {
println!("Copying Among Us to cache location...");
crate::copy_folder_to_target(among_us_path, installs_path);
}));
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Copying Among Us...");
});
ctx.request_repaint();
}
InitializingState::UnmoddingAmongUs => {
if self.copy_au_handle.as_ref().is_some() {
if self.copy_au_handle.as_ref().unwrap().is_finished() {
self.copy_au_handle = None;
self.app_state =
GlobalAppState::Initializing(InitializingState::DownloadingZip);
}
} else {
// TODO: This sucks
let new_installed_path = self.init_install_path.as_ref().unwrap().clone();
let installs_path = self.installs_path.clone();
self.copy_au_handle = Some(thread::spawn(move || {
let among_us_path: PathBuf =
[installs_path.as_str(), "Among Us"].iter().collect();
// Un-mod whatever we found if it's modded
crate::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();
}));
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Unmodding Among Us...");
});
ctx.request_repaint();
}
InitializingState::DownloadingZip => {
if self.download_tou_handle.as_ref().is_some() {
if self.download_tou_handle.as_ref().unwrap().is_finished() {
self.download_tou_handle = None;
self.app_state =
GlobalAppState::Initializing(InitializingState::Unzipping);
}
} else {
let data_path = self.data_path.clone();
let download_url = self.init_download_url.as_ref().unwrap().clone();
self.download_tou_handle = Some(thread::spawn(move || {
let mut download_path: PathBuf = [data_path.as_str()].iter().collect();
let downloaded_filename: &str =
download_url.rsplit('/').next().unwrap();
download_path.push(downloaded_filename);
if !download_path.exists() {
println!(
"Downloading Town of Us... Please be patient! [{}]",
download_url.clone()
);
let client = reqwest::blocking::Client::builder()
.timeout(None)
.build()
.unwrap();
let zip_request = client.get(download_url.clone()).build().unwrap();
let zip = client.execute(zip_request).unwrap().bytes().unwrap();
fs::write(download_path.clone(), zip).unwrap();
}
}));
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Downloading Town of Us...");
});
ctx.request_repaint();
}
InitializingState::Unzipping => {
if self.download_tou_handle.as_ref().is_some() {
if self.download_tou_handle.as_ref().unwrap().is_finished() {
self.download_tou_handle = None;
self.app_state = GlobalAppState::Initialized;
}
} else {
let data_path = self.data_path.clone();
let download_url = self.init_download_url.as_ref().unwrap().clone();
let new_installed_path = self.init_install_path.as_ref().unwrap().clone();
self.download_tou_handle = Some(thread::spawn(move || {
let mut download_path: PathBuf = [data_path.as_str()].iter().collect();
let downloaded_filename: &str =
download_url.rsplit('/').next().unwrap();
download_path.push(downloaded_filename);
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.as_str(), root_folder_path.as_str(), "."]
.iter()
.collect();
println!("{}", extracted_path.to_str().unwrap());
crate::copy_folder_contents_to_target(
extracted_path.to_str().unwrap(),
new_installed_path.to_str().unwrap(),
);
}));
}
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Extracting Town of Us...");
ui.heading("Modding Among Us...");
});
ctx.request_repaint();
}
_ => {}
},
GlobalAppState::Initialized => {
self.detect_running_stores();
self.detect_running_among_us();
if self.update_installs_list {
self.detect_installs();
self.update_installs_list = false;
}
egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| {
ui.checkbox(&mut self.delete_mode, "DELETE MODE");
if ui
.checkbox(&mut self.config.auto_run_blessed, "Auto run blessed build")
.clicked()
{
config::save_config(&self.config);
};
});
egui::CentralPanel::default().show(ctx, |ui| {
if !self.launcher_running {
let warning_string = match self.config.launcher_type {
LauncherType::Steam => "STEAM IS NOT RUNNING",
LauncherType::Epic => "EPIC IS NOT RUNNING",
_ => "UNKNOWN LAUNCHER USED",
};
let warning_text = egui::RichText::new(warning_string)
// .size(25.0)
.color(egui::Color32::RED);
ui.heading(warning_text);
ctx.request_repaint();
}
self.draw_layout(ui);
});
}
_ => {}
}
// app_data.on_path_added();
// app_data.detect_installs(); // Initial check since we only update every 10s
}
}

39
src/config.rs Normal file
View File

@@ -0,0 +1,39 @@
use serde::{Deserialize, Serialize};
use std::{fs, path::PathBuf};
#[derive(Serialize, Deserialize, Default, Debug)]
pub enum LauncherType {
#[default]
None,
Steam,
Epic,
}
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct AppConfig {
pub launcher_type: LauncherType,
pub among_us_path: Option<PathBuf>,
pub auto_run_blessed: bool,
}
pub fn load_config() -> Option<AppConfig> {
let mut config_path = dirs::data_dir().unwrap();
config_path.push("town-of-us-updater");
config_path.push("settings.json");
if let Ok(string) = fs::read_to_string(config_path) {
let app_config: AppConfig = serde_json::from_str(string.as_str()).unwrap();
return Some(app_config);
} else {
None
}
}
pub fn save_config(app_data: &AppConfig) {
println!("Saving config");
let mut config_path = dirs::data_dir().unwrap();
config_path.push("town-of-us-updater");
config_path.push("settings.json");
let json = serde_json::to_string_pretty(app_data).unwrap();
fs::write(config_path, json).unwrap();
}

View File

@@ -1,61 +1,57 @@
use std::cell::RefCell; // Module definitions
use std::path::{Path, PathBuf}; mod among_us_version;
use std::rc::Rc; mod app_data;
use std::str; mod config;
use std::*; mod semver;
use druid::widget::*; // Crate imports
use druid::{ use among_us_version::*;
commands, AppDelegate, AppLauncher, Application, Data, DelegateCtx, Env, FileDialogOptions, use app_data::*;
Handled, Target, WidgetExt, WindowDesc, use config::LauncherType;
// Other imports
use md5::{Digest, Md5};
use semver::*;
use std::{
fs, io,
path::{Path, PathBuf},
process, str, thread,
}; };
use fs_extra::{copy_items, dir}; use fs_extra::{copy_items, dir};
use regex::Regex; use regex::Regex;
struct Delegate; #[cfg(target_os = "windows")]
use registry::Hive;
#[derive(Data, Clone)] // GUI stuff
struct AppData {
pub among_us_path: Rc<RefCell<String>>,
}
static AMONG_US_APPID: &'static str = "945360"; use eframe::egui;
#[derive(Ord, PartialOrd, Eq, PartialEq)] use reqwest::StatusCode;
struct AmongUsVersion {
year: i32,
month: i32,
day: i32,
}
impl fmt::Display for AmongUsVersion { struct CombinedVersion(AmongUsVersion, SemVer);
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.year, self.month, self.day)
}
}
impl From<&str> for AmongUsVersion { static AMONG_US_APPID: &str = "945360";
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 attempt_delete_among_us(install_path: &Path) {
fs::remove_dir_all(install_path).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 +61,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 +71,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,75 +83,52 @@ 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"]);
} }
} }
Ok((String::from("no"), String::from("yes"))) Ok((String::from("no"), String::from("yes")))
} }
impl AppDelegate<AppData> for Delegate { fn main() {
fn command( let app_config = config::load_config().unwrap_or_default();
&mut self,
_ctx: &mut DelegateCtx,
_target: Target,
cmd: &druid::Command,
data: &mut AppData,
_env: &Env,
) -> Handled {
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());
// Pop the selected file off the end of the path
// among_us_folder.pop();
println!("{}", buf.to_str().unwrap());
println!("Handled!");
Application::global().quit();
return Handled::Yes;
}
Handled::No
}
}
#[tokio::main]
async fn main() {
let version = env!("CARGO_PKG_VERSION"); let version = env!("CARGO_PKG_VERSION");
let title_string: String = format!("Town of Us Updater - {}", version); let title_string: String = format!("Town of Us Updater - {}", version);
let blessed_body = reqwest::blocking::get("https://tou.dormedas.com/blessed");
let response = blessed_body.unwrap();
println!("Updater Version: {}", version); let blessed_version: String = match response.status() {
get_latest_updater_version().await.unwrap(); StatusCode::OK => response.text().unwrap(),
//let _version_check_thread_handle = thread::spawn(move || get_latest_updater_version()); _ => String::from("0.0.0-v0.0.0"),
};
println!("Blessed Version: {}", blessed_version);
// CREATE PROGRAM DIRECTORY // CREATE PROGRAM DIRECTORY
let mut data_path = dirs::data_dir().unwrap(); let mut data_path = dirs::data_dir().unwrap();
@@ -168,244 +139,209 @@ async fn main() {
installs_path.push("installs"); installs_path.push("installs");
fs::create_dir(installs_path.clone()).unwrap_or(()); fs::create_dir(installs_path.clone()).unwrap_or(());
// DETERMINE AMONG US VERSION let mut true_env = false;
for argument in std::env::args() {
if argument == "true" {
true_env = true;
}
}
let mut among_us_folder = path::PathBuf::new(); if !true_env {
let mut exe_path = data_path.clone();
if cfg!(windows) {
exe_path.push("town-of-us-updater.exe");
} else if cfg!(unix) {
exe_path.push("town-of-us-updater");
}
let mut updated_exe_hash = String::new();
if let Ok(response) = reqwest::blocking::get("https://tou.dormedas.com/release/hash") {
if let Ok(text) = response.text() {
updated_exe_hash = text;
}
} else {
println!("Failed to get hash. Running local version.")
}
// If we heard back, try to update, otherwise run normally
if !updated_exe_hash.is_empty() {
let mut our_hash = String::new();
let mut try_continue = false;
match fs::read(&exe_path) {
Ok(bytes) => {
let mut hasher = Md5::new();
hasher.update(bytes);
let finalized = hasher.finalize();
our_hash = format!("{finalized:x}");
try_continue = true;
}
Err(e) => match e.kind() {
io::ErrorKind::NotFound => try_continue = true,
_ => {
println!("{}", e.to_string());
}
},
}
if !our_hash.is_empty() {
println!("{} vs {}", our_hash, updated_exe_hash);
} else {
println!("Local executable didn't exist");
}
if try_continue {
if our_hash != updated_exe_hash {
println!("Newer version detected, downloading...");
let client = reqwest::blocking::Client::builder()
.timeout(None)
.build()
.unwrap();
let updated_exe_data_req = client
.get("https://tou.dormedas.com/release")
.build()
.unwrap();
let updated_exe_data = client.execute(updated_exe_data_req);
if let Ok(response) = updated_exe_data {
if response.status() == StatusCode::OK {
if let Ok(file_bytes) = response.bytes() {
match fs::write(&exe_path, file_bytes) {
Ok(_) => {
println!("Successfully updated executable");
}
_ => {
println!("Failed to update executable");
}
};
}
}
}
}
// We can fail to download a good binary, just continue with our current executable
if exe_path.exists() {
process::Command::new(&exe_path)
.arg("true")
.stdout(process::Stdio::null())
.stderr(process::Stdio::null())
.spawn()
.unwrap();
process::exit(0);
}
}
}
}
let version = env!("CARGO_PKG_VERSION");
let mut among_us_folder = PathBuf::new();
let mut existing_file_path = data_path.clone(); let mut existing_file_path = data_path.clone();
existing_file_path.push("existing_among_us_dir.txt"); existing_file_path.push("existing_among_us_dir.txt");
let existing_found_folder = fs::read_to_string(existing_file_path.clone()); let existing_found_folder = fs::read_to_string(existing_file_path.clone());
if existing_found_folder.is_ok() { if let Ok(existing_folder) = existing_found_folder {
among_us_folder.push(existing_found_folder.unwrap()); among_us_folder.push(existing_folder);
} else { } else {
let folder_opt = detect_among_us_folder(); let folder_opt: Option<(String, LauncherType)> = detect_among_us_folder();
if folder_opt.is_some() { if folder_opt.is_some() {
among_us_folder.push(folder_opt.unwrap()); among_us_folder.push(folder_opt.unwrap().0);
}
if among_us_folder.exists() {
fs::write(existing_file_path, among_us_folder.to_str().unwrap()).unwrap();
}
}
let mut app_data: AppData = AppData {
// among_us_path: String::from(among_us_folder.clone().to_str().unwrap()),
among_us_path: String::new(),
installs_path: String::from(installs_path.clone().to_str().unwrap()),
version: SemVer::from(version),
delete_mode: false,
among_us_version: AmongUsVersion::default(),
data_path: String::from(data_path.clone().to_str().unwrap()),
app_state: GlobalAppState::Initializing(InitializingState::StartingGUI),
blessed_version,
update_installs_list: true,
installs_last_update_time: std::time::Instant::now(),
installs_list: Vec::new(),
init_install_path: None,
init_download_url: None,
launcher_running: false,
config: app_config,
among_us_launched: false,
among_us_running: false,
find_among_us_handle: None,
determine_au_version_handle: None,
determine_tou_version_handle: None,
copy_au_handle: None,
download_tou_handle: None,
};
// 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);
} else { } else {
let path_rc = Rc::new(RefCell::new(String::from(""))); println!("Among Us Folder not automatically determined");
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) =
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);
}
// 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()
.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 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(), "."]
.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<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 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());
let mut index = 0;
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())
.fix_height(45.0)
.on_click(move |_, _: &mut u32, _| {
attempt_run_among_us(&clone);
});
flexbox_array
.get_mut(index)
.unwrap()
.add_flex_child(fybutton, 1.0);
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..."); // TODO: Auto launch latest sanctioned if the user has a setting like that
// let vals: (String, String) = version_check_thread_handle.join().unwrap(); // Auto launch latest experimental as well
println!("Launching main window..."); println!("Launching main window...");
let main_window = WindowDesc::new(|| root_column) // Tell better crewlink to launch somewhere else
.title(title_string) thread::spawn(|| {
.window_size((400.0, 400.0)); launch_better_crewlink().unwrap_or(());
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();
// let mut install_folder_path = installs_path.clone(); let mut native_options = eframe::NativeOptions::default();
// install_folder_path.push(version_smash); native_options.min_window_size = Some(egui::vec2(425.0, 400.0));
// fs::create_dir(install_folder_path.clone()).unwrap_or(()); native_options.initial_window_size = Some(egui::vec2(425.0, 400.0));
// Copy Among Us Out app_data.detect_running_stores();
// Create destination path
// let executable_path: path::PathBuf = [new_installed_path.to_str().unwrap(), "Among Us.exe"] eframe::run_native(
// .iter() title_string.as_str(),
// .collect(); native_options,
Box::new(|_cc| Box::new(app_data)),
);
}
// std::process::Command::new(executable_path.to_str().unwrap()) fn launch_better_crewlink() -> std::result::Result<(), String> {
// .spawn() let mut dir = dirs::data_local_dir().unwrap();
// .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) // 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 +352,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);
@@ -454,59 +373,44 @@ async fn determine_town_of_us_url(among_us_version: String) -> Option<(String, S
} }
fn determine_among_us_version(folder_root: String) -> Option<AmongUsVersion> { fn determine_among_us_version(folder_root: String) -> Option<AmongUsVersion> {
let asset_file = format!("{}\\Among Us_Data\\globalgamemanagers", folder_root); let asset_file = format!("{}/Among Us_Data/globalgamemanagers", folder_root);
const TARGET_BYTES_LEN: usize = 16; const TARGET_BYTES_LEN: usize = 16;
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 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(); if *i == target_bytes[target_head] {
let mut target_head = 0; target_head += 1;
let mut file_index = 0; } else {
for i in &file_bytes { target_head = 0;
if target_head == TARGET_BYTES_LEN { }
break; file_index += 1;
bytes_str += &format!("{i:x}");
} }
if *i == target_bytes[target_head] { let offset: usize = usize::from(file_bytes[file_index]);
target_head += 1; file_index += 4;
} else {
target_head = 0; Some(AmongUsVersion::from(
} str::from_utf8(file_bytes.get(file_index..file_index + offset).unwrap()).unwrap(),
file_index += 1; ))
bytes_str += &format!("{i:x}"); } else {
None
} }
let offset: usize = usize::from(file_bytes[file_index]); //println!("{:?}", file_bytes);
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(),
// ))
} }
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 +420,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,12 +429,24 @@ 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_among_us_folder() -> Option<String> { fn detect_steam() -> Option<(String, LauncherType)> {
let library_folder = let mut library_folder_path: PathBuf =
fs::read_to_string("C:\\Program Files (x86)\\Steam\\steamapps\\libraryfolders.vdf"); PathBuf::from("C:\\Program Files (x86)\\Steam\\steamapps\\libraryfolders.vdf");
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") {
library_folder_path = PathBuf::from(steam_folder.to_string());
library_folder_path.push("steamapps\\libraryfolders.vdf");
println!("{:?}", steam_folder);
println!("{:?}", library_folder_path.to_str());
}
}
let library_folder = fs::read_to_string(library_folder_path.to_str().expect("Path invalid"));
if library_folder.is_ok() { if library_folder.is_ok() {
println!("Steam is on C:\\ drive!");
let mut library_folder_string: String = library_folder.unwrap(); let mut library_folder_string: String = library_folder.unwrap();
let appid_index = library_folder_string.find(AMONG_US_APPID); let appid_index = library_folder_string.find(AMONG_US_APPID);
if appid_index.is_none() { if appid_index.is_none() {
@@ -539,27 +455,43 @@ 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((
"{}\\\\steamapps\\\\common\\\\Among Us\\\\", format!(
library_folder_string r"{}\\steamapps\\common\\Among Us\\",
.get(start.start()..start.end()) library_folder_string
.unwrap() .get(start.start()..start.end())
.unwrap()
),
LauncherType::Steam,
)); ));
} }
None None
} }
fn detect_epic() -> Option<(String, LauncherType)> {
let default_folder = Path::new("C:\\Program Files\\Epic Games\\AmongUs");
if default_folder.exists() {
return Some((default_folder.to_string_lossy().into(), LauncherType::Epic));
}
None
}
fn detect_among_us_folder() -> Option<(String, LauncherType)> {
if cfg!(windows) {
// Default to Steam
let return_val = detect_steam();
if return_val.is_none() {
return detect_epic();
} else {
return return_val;
}
} else {
}
None
}

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(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(),
}
}
}