diff --git a/.gitignore b/.gitignore index f9136e5..6a40430 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -/target -/.fleet \ No newline at end of file +target +.fleet +Cargo.lock \ No newline at end of file diff --git a/bepinex_helpers/src/error.rs b/bepinex_helpers/src/error.rs new file mode 100644 index 0000000..6b0eca4 --- /dev/null +++ b/bepinex_helpers/src/error.rs @@ -0,0 +1,45 @@ +use std::{error, fmt::Display, io}; + +#[derive(Debug)] +pub enum ErrorCode { + Io(io::Error), + InvalidGameType, +} + +impl Display for ErrorCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ErrorCode::Io(e) => write!(f, "{}", e), + ErrorCode::InvalidGameType => write!(f, "Invalid game type"), + } + } +} + +#[derive(Debug)] +pub struct Error { + code: ErrorCode, +} + +impl Error { + pub fn invalid_game_type() -> Self { + Error { + code: ErrorCode::InvalidGameType, + } + } +} + +impl error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.code) + } +} + +impl From for Error { + fn from(e: io::Error) -> Self { + Error { + code: ErrorCode::Io(e), + } + } +} diff --git a/bepinex_helpers/src/game.rs b/bepinex_helpers/src/game.rs index 1aa7a19..9d37e38 100644 --- a/bepinex_helpers/src/game.rs +++ b/bepinex_helpers/src/game.rs @@ -2,29 +2,55 @@ use semver::Version; use std::{ error, fmt::Display, + fs, path::{Path, PathBuf}, + str::FromStr, }; use steamlocate::SteamDir; +use crate::error::Error; + #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum GameType { UnityMono, UnityIL2CPP, } +impl ToString for GameType { + fn to_string(&self) -> String { + match self { + Self::UnityMono => "UnityMono".into(), + Self::UnityIL2CPP => "UnityIL2CPP".into(), + } + } +} + +impl FromStr for GameType { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "UnityMono" => Ok(Self::UnityMono), + "Mono" => Ok(Self::UnityMono), + "UnityIL2CPP" => Ok(Self::UnityIL2CPP), + "IL2CPP" => Ok(Self::UnityIL2CPP), + _ => Err(Error::invalid_game_type()), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)] pub struct Game { pub name: String, pub arch: String, pub path: PathBuf, pub ty: Option, - pub bepinex_version: Option, + pub bepinex_version: Option, } impl Game { - pub fn set_bix(&mut self, bix: Option) -> Game { + pub fn set_bix(&mut self, bix: Option) { self.bepinex_version = bix; - self.to_owned() } pub fn set_arch(&mut self, arch: String) { @@ -35,7 +61,7 @@ impl Game { self.ty = ty; } - pub fn get_installed_bepinex_version(&self) -> Option { + pub fn get_installed_bepinex_version(&self) -> Option { let core_path = self.path.join("BepInEx").join("core"); match core_path.exists() { true => { @@ -49,6 +75,30 @@ impl Game { false => None, } } + + pub fn get_game_type(&self) -> Option { + let mono = "Managed"; + let il2cpp = "il2cpp_data"; + + match fs::read_dir(&self.path) { + Ok(dir) => { + let data_dir = dir.filter_map(Result::ok).find(|el| { + el.file_name().to_str().unwrap().ends_with("_Data") + && el.file_type().unwrap().is_dir() + }); + + let data_dir = data_dir.as_ref()?.path(); + if data_dir.join(mono).exists() { + Some(GameType::UnityMono) + } else if data_dir.join(il2cpp).exists() { + Some(GameType::UnityIL2CPP) + } else { + None + } + } + _ => None, + } + } } impl Default for Game { @@ -70,50 +120,58 @@ impl Display for Game { } pub fn get_unity_games() -> Result, Box> { - let mut unity_games: Vec = Vec::new(); - - let mut steamapps = SteamDir::locate().unwrap_or_default(); + let mut steamapps = SteamDir::locate().ok_or("Steam not found")?; let apps = steamapps.apps(); - apps.iter().for_each(|(_id, app)| match app { - Some(app) => { + let unity_games = apps + .iter() + .filter_map(|(_id, app)| app.as_ref()) + .filter_map(|app| { let path = Path::new(&app.path); - if path.join("UnityPlayer.dll").exists() { - let mut game = Game { - name: app.name.clone().unwrap_or_default(), - arch: "a".to_owned(), - path: app.path.to_owned(), - bepinex_version: None, - ty: None, - }; - let bix_ver = game.get_installed_bepinex_version(); - game.set_bix(bix_ver); - - unity_games.push(game); + match path.join("UnityPlayer.dll").exists() { + true => { + let mut game = Game { + name: app.name.clone().unwrap_or_default(), + arch: "a".to_owned(), + path: app.path.to_owned(), + bepinex_version: None, + ty: None, + }; + + let bix_ver = game.get_installed_bepinex_version(); + let game_type = game.get_game_type(); + game.set_bix(bix_ver); + game.set_ty(game_type); + + Some(game) + } + false => None, } - } - None => {} - }); + }) + .collect::>(); Ok(unity_games) } -pub fn get_dll_version(path: PathBuf) -> Result> { +pub fn get_dll_version(path: PathBuf) -> Result> { let file = pelite::FileMap::open(path.as_path())?; let img = pelite::PeFile::from_bytes(file.as_ref())?; let resources = img.resources()?; let version_info = resources.version_info()?; + let lang = version_info .translation() .get(0) .ok_or("Failed to get lang")?; + let strings = version_info.file_info().strings; let string = strings .get(lang) .ok_or("Failed to get strings for that lang")?; + let version = string .get("ProductVersion") .ok_or("Failed to get prod. version")?; - println!("{:?}", Version::parse(version)); - Ok(version.to_owned()) + // TODO: Do some proper handling of invalid semver that bix has in older versions 💀 + Ok(Version::parse(version).unwrap()) } diff --git a/bepinex_helpers/src/lib.rs b/bepinex_helpers/src/lib.rs index f7ee1cd..1817c3f 100644 --- a/bepinex_helpers/src/lib.rs +++ b/bepinex_helpers/src/lib.rs @@ -1 +1,2 @@ +pub mod error; pub mod game; diff --git a/bepinex_installer/src/error/mod.rs b/bepinex_installer/src/error/mod.rs deleted file mode 100644 index 7d88b67..0000000 --- a/bepinex_installer/src/error/mod.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::{error, fmt::Display, io}; - -use reqwest::StatusCode; - -#[derive(Debug)] -pub enum ErrorCode { - Http(StatusCode), - Reqwest(reqwest::Error), - Io(io::Error), - ZipError(zip::result::ZipError), - InvalidGameType, -} - -impl Display for ErrorCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ErrorCode::Reqwest(e) => write!(f, "{}", e), - ErrorCode::Io(e) => write!(f, "{}", e), - ErrorCode::Http(code) => write!(f, "HTTP Response code: {}", code), - ErrorCode::ZipError(e) => write!(f, "{}", e), - ErrorCode::InvalidGameType => write!(f, "Invalid game type"), - } - } -} - -#[derive(Debug)] -pub struct Error { - code: ErrorCode, -} - -impl Error { - pub fn http(code: StatusCode) -> Self { - Error { - code: ErrorCode::Http(code), - } - } - - pub fn zip_error(e: zip::result::ZipError) -> Self { - Error { - code: ErrorCode::ZipError(e), - } - } - - pub fn invalid_game_type() -> Self { - Error { - code: ErrorCode::InvalidGameType, - } - } -} - -impl error::Error for Error {} - -impl Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.code) - } -} - -impl From for Error { - fn from(e: reqwest::Error) -> Self { - Error { - code: ErrorCode::Reqwest(e), - } - } -} - -impl From for Error { - fn from(e: io::Error) -> Self { - Error { - code: ErrorCode::Io(e), - } - } -} - -impl From for Error { - fn from(e: zip::result::ZipError) -> Self { - Error { - code: ErrorCode::ZipError(e), - } - } -} diff --git a/bepinex_installer/src/installer.rs b/bepinex_installer/src/installer.rs index 0a0f561..7ba6036 100644 --- a/bepinex_installer/src/installer.rs +++ b/bepinex_installer/src/installer.rs @@ -70,7 +70,7 @@ impl App for Installer { CentralPanel::default().show(ctx, |ui| { StripBuilder::new(ui) .size(Size::exact(20.0)) - .size(Size::exact(350.0)) + .size(Size::exact(20.0)) .size(Size::remainder()) .vertical(|mut strip| { strip.cell(|ui| { @@ -79,22 +79,57 @@ impl App for Installer { strip.cell(|ui| { self.show_bix_select(ui); }); - strip.cell(|ui| { - ui.centered_and_justified(|ui| { - let install_btn = Button::new("Install"); - if let (Some(ref selected_game), Some(selected_bix)) = - (&self.selected_game, &self.selected_bix) - { - let enabled = (selected_game.ty == Some(GameType::UnityIL2CPP) - && selected_bix.version >= *MIN_IL2CPP_STABLE_VERSION) - || (selected_game.ty != Some(GameType::UnityIL2CPP)); - if ui.add_enabled(enabled, install_btn).clicked() { - println!("{:#?}", self.selected_game); - println!("{:#?}", self.selected_bix); + strip.strip(|builder| { + builder + .size(Size::remainder()) + .size(Size::exact(40.0)) + .vertical(|mut strip| { + if let (Some(selected_game), Some(selected_bix)) = + (&self.selected_game, &self.selected_bix) + { + let supported_ver = selected_game.ty + == Some(GameType::UnityIL2CPP) + && selected_bix.version >= *MIN_IL2CPP_STABLE_VERSION; + + let enabled = supported_ver + || (selected_game.ty != Some(GameType::UnityIL2CPP)); + + strip.cell(|ui| { + ui.group(|ui| { + ui.horizontal(|ui| { + ui.label("Game type: "); + match &selected_game.ty { + Some(ty) => ui.monospace(ty.to_string()), + None => ui.monospace("Not Mono or IL2CPP"), + } + }); + ui.horizontal(|ui| { + ui.label("Installed BepInEx: "); + match &selected_game.bepinex_version { + Some(bix) => ui.monospace(bix.to_string()), + None => ui.monospace("None"), + } + }); + }); + }); + strip.cell(|ui| { + ui.centered_and_justified(|ui| { + let install_btn = Button::new("Install").small(); + if ui.add_enabled(enabled, install_btn).clicked() { + todo!( + " + Implement install logic: + - Download correct zip (bix version, game type) + - Support file names from 5.4.11 + - Unzip it + " + ) + } + }); + }) } - } - }); + }); }) }); });