27 Commits

Author SHA1 Message Date
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
5 changed files with 686 additions and 401 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "town-of-us-updater"
version = "1.0.0"
version = "4.0.0"
edition = "2021"
build = "src/build.rs"
@@ -12,10 +12,13 @@ regex = "1.5"
fs_extra = "1.2.0"
dirs = "4.0.0"
reqwest = {version = "0.11.11", features = ["blocking"]}
iui = "0.3.0"
serde_json = "1.0"
md-5 = "0.10.5"
tokio = {version="1", features=["full"]}
druid = "0.7.0"
registry = "1"
egui = "0.20.1"
eframe = "0.20.1"
rfd = "0.10"
[build-dependencies]
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
- GUI to select which build to play
- Auto-detection of Among Us install directory
- Auto-launch of BetterCrewLink if installed
# 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);
}

View File

@@ -1,61 +1,224 @@
use std::cell::RefCell;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::str;
use std::*;
// Modules
mod among_us_version;
mod semver;
use druid::widget::*;
use druid::{
commands, AppDelegate, AppLauncher, Application, Data, DelegateCtx, Env, FileDialogOptions,
Handled, Target, WidgetExt, WindowDesc,
// Uses
use among_us_version::*;
use md5::{Digest, Md5};
use semver::*;
use std::{
fs, io,
path::{Path, PathBuf},
process, str,
};
use fs_extra::{copy_items, dir};
use regex::Regex;
struct Delegate;
#[cfg(target_os = "windows")]
use registry::Hive;
#[derive(Data, Clone)]
struct AppData {
pub among_us_path: Rc<RefCell<String>>,
// GUI stuff
use eframe::egui;
use reqwest::StatusCode;
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum InitializingState {
StartingGUI,
DeterminingVersion,
CopyingAmongUs,
UnmoddingAmongUs,
DownloadingZip,
Unzipping,
CopyingZipIn,
}
static AMONG_US_APPID: &'static str = "945360";
#[derive(Ord, PartialOrd, Eq, PartialEq)]
struct AmongUsVersion {
year: i32,
month: i32,
day: i32,
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum GlobalAppState {
Initializing(InitializingState),
Initialized,
Installing,
Playing,
}
impl fmt::Display for AmongUsVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.year, self.month, self.day)
}
#[derive(Clone, Debug)]
pub struct AppData {
pub among_us_path: String,
pub installs_path: String,
pub version: SemVer,
pub initialized: bool,
pub among_us_version: AmongUsVersion,
pub data_path: String,
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>,
}
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(),
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,
},
initialized: 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(),
}
}
}
impl AppData {
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);
if ui.button(button_text).clicked() {
crate::attempt_run_among_us(&clone);
}
} else {
let button_text = egui::RichText::new(button_string.clone()).size(25.0);
if ui.button(button_text).clicked() {
crate::attempt_run_among_us(&clone);
}
}
if !self.initialized {
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) {
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.initialized, "DELETE MODE");
});
egui::CentralPanel::default().show(ctx, |ui| {
self.draw_layout(ui);
});
}
}
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 attempt_delete_among_us(install_path: &Path) {
fs::remove_dir_all(install_path).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 +228,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();
@@ -74,13 +238,10 @@ fn unmod_among_us_folder(folder_path: &Path) {
fs::remove_dir_all(mono_path).unwrap_or_default();
}
async fn get_latest_updater_version() -> Result<(String, String), reqwest::Error> {
async fn _get_latest_updater_version() -> Result<(String, String), reqwest::Error> {
let version = env!("CARGO_PKG_VERSION");
let mut cur_vers = version.split('.');
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 local_sem_ver = SemVer::from(version);
let body =
reqwest::get("https://git.dormedas.com/api/v1/repos/dormedas/town-of-us-updater/releases");
@@ -89,164 +250,62 @@ async fn get_latest_updater_version() -> Result<(String, String), reqwest::Error
for i in root.as_array().unwrap() {
let tag_name = i["tag_name"].as_str().unwrap();
if let Some(trimmed_str) = tag_name.strip_prefix('v') {
let mut vers = trimmed_str.split('.');
let tag_major_ver = vers.next().unwrap().parse::<i32>().unwrap();
let tag_minor_ver = vers.next().unwrap().parse::<i32>().unwrap();
let tag_patch_ver = vers.next().unwrap().parse::<i32>().unwrap();
if tag_major_ver > major_ver
|| (tag_major_ver == major_ver && tag_minor_ver > minor_ver)
|| (tag_major_ver == major_ver
&& tag_minor_ver == minor_ver
&& tag_patch_ver > patch_ver)
{
println!("New version of the updater detected!");
let remote_sem_ver = SemVer::from(trimmed_str);
if remote_sem_ver > local_sem_ver {
for j in i["assets"].as_array().unwrap() {
if j["name"].as_str().unwrap().contains(".exe") {
// let url = j["browser_download_url"].as_str().unwrap();
// let data = reqwest::get(url).await?.bytes().await?;
// let mut exe_dir = std::env::current_dir().unwrap();
// exe_dir.push("town-of-us-updater-new.exe");
// fs::write(exe_dir, data).unwrap();
}
}
println!("New version ({}) of the updater detected!", remote_sem_ver);
}
println!("{}", trimmed_str);
} else {
let mut vers = tag_name.split('.');
let tag_major_ver = vers.next().unwrap().parse::<i32>().unwrap();
let tag_minor_ver = vers.next().unwrap().parse::<i32>().unwrap();
let tag_patch_ver = vers.next().unwrap().parse::<i32>().unwrap();
if tag_major_ver > major_ver
|| (tag_major_ver == major_ver && tag_minor_ver > minor_ver)
|| (tag_major_ver == major_ver
&& tag_minor_ver == minor_ver
&& tag_patch_ver > patch_ver)
{
println!("New version of the updater detected!");
let remote_sem_ver = SemVer::from(tag_name);
if remote_sem_ver > local_sem_ver {
for j in i["assets"].as_array().unwrap() {
if j["name"].as_str().unwrap().contains(".exe") {
// let url = j["browser_download_url"].as_str().unwrap();
// let data = reqwest::get(url).await?.bytes().await?;
// let mut exe_dir = std::env::current_dir().unwrap();
// exe_dir.push("town-of-us-updater-new.exe");
// fs::write(exe_dir, data).unwrap();
}
}
println!("New version ({}) of the updater detected!", remote_sem_ver);
}
println!("{}", i["tag_name"]);
}
}
Ok((String::from("no"), String::from("yes")))
}
impl AppDelegate<AppData> for Delegate {
fn command(
&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 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();
impl AppData {
fn on_path_added(&mut self) {
if let Some(among_us_version) =
determine_among_us_version(String::from(self.among_us_path.clone()))
{
self.among_us_version = among_us_version.clone();
println!("AmongUsVersion: {}", among_us_version);
}
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();
self.app_state = GlobalAppState::Initializing(InitializingState::DeterminingVersion);
let ver_url: (String, String, bool) =
determine_town_of_us_url(among_us_version.to_string().clone())
.await
.unwrap();
determine_town_of_us_url(self.among_us_version.to_string().clone()).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()]
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::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(),
);
if !Path::exists(&new_installed_path) {
println!("Copying Among Us to cache location...");
copy_folder_to_target(self.among_us_path.clone(), self.installs_path.clone());
let among_us_path: path::PathBuf = [installs_path.to_str().unwrap(), "Among Us\\"]
.iter()
.collect();
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);
@@ -266,34 +325,38 @@ async fn main() {
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());
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 !path::Path::exists(&download_path) {
if !download_path.exists() {
// println!("{:?}", download_path);
println!("Downloading Town of Us... [{}]", ver_url.1.clone());
let zip = reqwest::get(ver_url.1.clone())
.await
.unwrap()
.bytes()
.await
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).unwrap();
println!("Extracting...");
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();
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("/").nth(0).unwrap());
root_folder_path = String::from(i.split('/').next().unwrap());
break;
}
let extracted_path: path::PathBuf =
[data_path.to_str().unwrap(), root_folder_path.as_str(), "."]
let extracted_path: PathBuf = [self.data_path.as_str(), root_folder_path.as_str(), "."]
.iter()
.collect();
println!("{}", extracted_path.to_str().unwrap());
@@ -301,111 +364,223 @@ async fn main() {
extracted_path.to_str().unwrap(),
new_installed_path.to_str().unwrap(),
);
}
self.initialized = !self.initialized;
self.app_state = GlobalAppState::Initialized;
self.update_installs_list = true;
}
}
fn main() {
let version = env!("CARGO_PKG_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();
let blessed_version: String = match response.status() {
StatusCode::OK => response.text().unwrap(),
_ => String::from("0.0.0-v0.0.0"),
};
println!("Blessed Version: {}", blessed_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(());
let mut true_env = false;
for argument in std::env::args() {
if argument == "true" {
true_env = true;
}
}
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!("Modded install already found");
println!("Failed to get hash. Running local version.")
}
// 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 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 let Ok(iter) = install_iter {
let mut collection: Vec<Result<fs::DirEntry, std::io::Error>> = iter.collect();
collection.reverse();
if !our_hash.is_empty() {
println!("{} vs {}", our_hash, updated_exe_hash);
} else {
println!("Local executable didn't exist");
}
// Iterate first to find labels:
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 mut flexbox_array: Vec<druid::widget::Flex<u32>> = Vec::new();
let mut auv_array: Vec<String> = Vec::new();
let updated_exe_data = client.execute(updated_exe_data_req);
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());
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");
}
};
}
}
}
}
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());
// 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();
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;
process::exit(0);
}
}
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();
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 {
let folder_opt: Option<String> = detect_among_us_folder();
if folder_opt.is_some() {
among_us_folder.push(folder_opt.unwrap());
}
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()),
installs_path: String::from(installs_path.clone().to_str().unwrap()),
version: SemVer::from(version),
initialized: false,
among_us_version: AmongUsVersion::default(),
data_path: String::from(data_path.clone().to_str().unwrap()),
app_state: GlobalAppState::Initializing(InitializingState::StartingGUI),
blessed_version,
update_installs_list: true,
installs_last_update_time: std::time::Instant::now(),
installs_list: Vec::new(),
};
// 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 {
println!("Among Us Folder not automatically determined");
}
}
// 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)
.title(title_string)
.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();
launch_better_crewlink().unwrap_or(());
// let mut install_folder_path = installs_path.clone();
// install_folder_path.push(version_smash);
// fs::create_dir(install_folder_path.clone()).unwrap_or(());
// let app_launcher = AppLauncher::with_window(main_window).delegate(Delegate {});
// app_launcher.launch(app_data).unwrap();
let mut native_options = eframe::NativeOptions::default();
native_options.min_window_size = Some(egui::vec2(425.0, 400.0));
native_options.initial_window_size = Some(egui::vec2(425.0, 400.0));
app_data.detect_installs(); // Initial check since we only update every 10s
eframe::run_native(
title_string.as_str(),
native_options,
Box::new(|_cc| Box::new(app_data)),
);
}
// Copy Among Us Out
// Create destination path
// let executable_path: path::PathBuf = [new_installed_path.to_str().unwrap(), "Among Us.exe"]
// .iter()
// .collect();
// std::process::Command::new(executable_path.to_str().unwrap())
// .spawn()
// .unwrap();
fn launch_better_crewlink() -> std::result::Result<(), String> {
let mut dir = dirs::data_local_dir().unwrap();
dir.push("Programs");
dir.push("bettercrewlink");
dir.push("Better-CrewLink.exe");
println!("Attempting to launch Better Crew Link");
if dir.exists() {
process::Command::new(dir.as_path().to_str().unwrap())
.stdout(process::Stdio::null())
.stderr(process::Stdio::null())
.spawn()
.unwrap();
Ok(())
} else {
Err("Better Crew Link not found".to_string())
}
}
// Returns (Version, URL)
async fn determine_town_of_us_url(among_us_version: String) -> Option<(String, String, bool)> {
let markdown =
reqwest::get("https://raw.githubusercontent.com/eDonnes124/Town-Of-Us-R/master/README.md")
.await
fn determine_town_of_us_url(among_us_version: String) -> Option<(String, String, bool)> {
let markdown = reqwest::blocking::get(
"https://raw.githubusercontent.com/eDonnes124/Town-Of-Us-R/master/README.md",
)
.unwrap()
.text()
.await
.unwrap();
let mut line = markdown.find(&among_us_version);
let mut line_offset = 0;
let mut official_compatibility = false;
@@ -416,23 +591,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);
@@ -454,15 +612,13 @@ async fn determine_town_of_us_url(among_us_version: String) -> Option<(String, S
}
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;
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;
@@ -483,30 +639,17 @@ fn determine_among_us_version(folder_root: String) -> Option<AmongUsVersion> {
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(
Some(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(),
// ))
))
} else {
None
}
//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();
copy_opts.overwrite = true;
copy_opts.skip_exist = true;
@@ -516,7 +659,7 @@ fn copy_folder_to_target<T: AsRef<path::Path>>(source: T, dest: T) {
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();
copy_opts.overwrite = true;
copy_opts.skip_exist = true;
@@ -525,12 +668,35 @@ fn copy_folder_contents_to_target<T: AsRef<path::Path>>(source: T, dest: T) {
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> {
if cfg!(windows) {
let mut library_folder_path: PathBuf =
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("C:\\Program Files (x86)\\Steam\\steamapps\\libraryfolders.vdf");
fs::read_to_string(library_folder_path.to_str().expect("Path invalid"));
if library_folder.is_ok() {
println!("Steam is on C:\\ drive!");
let mut library_folder_string: String = library_folder.unwrap();
let appid_index = library_folder_string.find(AMONG_US_APPID);
if appid_index.is_none() {
@@ -539,27 +705,20 @@ fn detect_among_us_folder() -> Option<String> {
} else {
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 caps: regex::CaptureMatches = path_regex.captures_iter(&library_folder_string);
let last_path = caps.last().unwrap();
let start = last_path.get(last_path.len() - 1).unwrap();
/*
println!(
"{}",
library_folder_string
.get(start.start()..start.end())
.unwrap()
);
*/
return Some(format!(
"{}\\\\steamapps\\\\common\\\\Among Us\\\\",
r"{}\\steamapps\\common\\Among Us\\",
library_folder_string
.get(start.start()..start.end())
.unwrap()
));
}
} 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(),
}
}
}