Skip to content

Commit

Permalink
0.8.0 (#183)
Browse files Browse the repository at this point in the history
- Add itch.io provider (requires having the itch.io app installed). Note
that this does not detect games from bundles unless you've specifically
added them to your library.
- Separate handling of data based on local files vs data from remote
sources. This means Rai Pal will show local information more quickly,
while it waits for the remote data to arrive.
- Handle more processing in parallel, to make it faster for you to see
results.
- Change filters menu, to make it possible to toggle each value
individually, instead of only being able to select one value at a time.
This is particularly useful if you mean to hide a specific value.
- Fix a problem where window scrolling would go whacko funko mode and
cause mouse events to be offset inside modals (wtf).
- Make most buttons smaller to allow for higher density in the UI.
- Fix missing thumbnails for some games in their respective game page.
- Fix broken cache for some providers.
  • Loading branch information
Raicuparta authored Jan 21, 2024
2 parents 40553b3 + 91203c4 commit fe2c858
Show file tree
Hide file tree
Showing 67 changed files with 1,708 additions and 1,081 deletions.
4 changes: 3 additions & 1 deletion backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions backend/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rai-pal"
version = "0.7.1"
version = "0.8.0"
authors = ["Raicuparta"]
license = "GPL-3.0-or-later"
repository = "https://github.com/Raicuparta/rai-pal"
Expand Down Expand Up @@ -41,20 +41,22 @@ serde_json = "1.0.108"
lazy_static = "1.4.0"
uuid = "1.6.1"
rand = "0.8.5"
winreg = "0.52.0"
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1", features = ["colored"] }
log = "0.4.20"
tauri-runtime = "0.14.1"
base64 = "0.21.5"
chrono = "0.4.31"
rusqlite = { version = "0.30.0", features = ["bundled"] }
tokio = "1.35.1"
serde_urlencoded = "0.7.1"

[target.'cfg(target_os = "linux")'.dependencies]
webkit2gtk = "0.18.2"
webview2-com = "0.27.0" # Needed for getting the webview window on linux

[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3.9", features = ["shellapi", "winuser"] }
winreg = "0.52.0"

[features]
# this feature is used for production builds or when `devPath` points to the filesystem
Expand Down Expand Up @@ -168,7 +170,7 @@ single_match_else = "warn"
stable_sort_primitive = "warn"
string_add_assign = "warn"
struct_excessive_bools = "warn"
too_many_lines = "warn"
too_many_lines = "allow"
transmute_ptr_to_ptr = "warn"
trivially_copy_pass_by_ref = "warn"
unchecked_duration_subtraction = "warn"
Expand Down
2 changes: 2 additions & 0 deletions backend/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
maps::TryGettable,
mod_loaders::mod_loader,
owned_game,
remote_game,
remote_mod,
Error,
Result,
Expand All @@ -25,6 +26,7 @@ pub struct AppState {
pub mod_loaders: Mutex<Option<mod_loader::Map>>,
pub local_mods: Mutex<Option<local_mod::Map>>,
pub remote_mods: Mutex<Option<remote_mod::Map>>,
pub remote_games: Mutex<Option<remote_game::Map>>,
}

type TauriState<'a> = tauri::State<'a, AppState>;
Expand Down
1 change: 1 addition & 0 deletions backend/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::serializable_enum;
serializable_enum!(AppEvent {
SyncInstalledGames,
SyncOwnedGames,
SyncRemoteGames,
SyncModLoaders,
SyncLocalMods,
SyncRemoteMods,
Expand Down
22 changes: 9 additions & 13 deletions backend/src/game_engines/unity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,10 @@ fn get_alt_architecture(game_path: &Path) -> Option<Architecture> {
// This would usually be UnityPlayer.dll, steam_api.dll, etc.
// Here the guessing can go wrong, since it's possible a top level dll is actual x86,
// when the actual game is x64.
if let Ok(mut top_level_dlls) = glob_path(&game_folder.join("*.dll")) {
if let Some(Ok(first_dll)) = top_level_dlls.next() {
if let Ok(file) = fs::read(first_dll) {
if let Ok((_, arch)) = read_windows_binary(&file) {
return arch;
}
if let Some(first_dll) = glob_path(&game_folder.join("*.dll")).first() {
if let Ok(file) = fs::read(first_dll) {
if let Ok((_, arch)) = read_windows_binary(&file) {
return arch;
}
}
}
Expand All @@ -159,14 +157,12 @@ fn get_alt_architecture(game_path: &Path) -> Option<Architecture> {
// so I'm leaving it for last. (mostly because my own UUVR mod drops both the
// x86 and x64 dlls in the folder so Unity picks the right one)
if let Ok(unity_data_path) = get_unity_data_path(game_path) {
if let Ok(mut plugin_dlls) =
glob_path(&unity_data_path.join("Plugins").join("**").join("*.dll"))
if let Some(first_dll) =
glob_path(&unity_data_path.join("Plugins").join("**").join("*.dll")).first()
{
if let Some(Ok(first_dll)) = plugin_dlls.next() {
if let Ok(file) = fs::read(first_dll) {
if let Ok((_, arch)) = read_windows_binary(&file) {
return arch;
}
if let Ok(file) = fs::read(first_dll) {
if let Ok((_, arch)) = read_windows_binary(&file) {
return arch;
}
}
}
Expand Down
40 changes: 16 additions & 24 deletions backend/src/game_engines/unreal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,8 @@ fn get_shipping_exe(game_exe_path: &Path) -> PathBuf {
return game_exe_path.to_path_buf();
}

if let Some(Ok(sibling_shipping_exe)) = glob_path(&parent.join("*Shipping.exe"))
.ok()
.and_then(|mut paths| paths.next())
if let Some(sibling_shipping_exe) =
glob_path(&parent.join("*Shipping.exe")).first().cloned()
{
// Case where given exe is a sibling of the shipping exe.
return sibling_shipping_exe;
Expand All @@ -212,7 +211,7 @@ fn get_shipping_exe(game_exe_path: &Path) -> PathBuf {

// From here, we start presuming that the given exe is a launcher at the root level,
// and we need to dig down to find the shipping exe.
if let Ok(globbed_paths) = glob_path(
let globbed_paths = glob_path(
&parent
// This portion of the path would usually be the game's name, but no way to guess that.
// We know it's not "Engine", but can't exclude with the rust glob crate (we filter it below).
Expand All @@ -224,29 +223,22 @@ fn get_shipping_exe(game_exe_path: &Path) -> PathBuf {
.join("Win*")
// The file name may or may not end with Shipping.exe, so we don't test for that yet.
.join("*.exe"),
) {
let mut suitable_paths = globbed_paths.filter_map(|path_result| {
let path = path_result.ok()?;
);

// Filter for the correct Win* folders, since the glob couldn't do it above.
if path.parent().is_some_and(is_valid_win_folder)
let mut suitable_paths = globbed_paths.iter().filter(|path| {
// Filter for the correct Win* folders, since the glob couldn't do it above.
path.parent().is_some_and(is_valid_win_folder)
// The Engine folder can have similar structure, but it's not the one we want.
&& !path.starts_with(parent.join("Engine"))
{
Some(path)
} else {
None
}
});

let first_path = suitable_paths.next();
if let Some(best_path) = suitable_paths
// Exe that looks like a shipping exe takes priority.
.find(|path| is_shipping_exe(path))
.or(first_path)
{
return best_path;
}
});

let first_path = suitable_paths.next();
if let Some(best_path) = suitable_paths
// Exe that looks like a shipping exe takes priority.
.find(|path| is_shipping_exe(path))
.or(first_path)
{
return best_path.clone();
}
}

Expand Down
32 changes: 0 additions & 32 deletions backend/src/game_mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
use std::collections::{
HashMap,
HashSet,
};

use crate::{
game_engines::{
game_engine::GameEngineBrand,
unity::UnityScriptingBackend,
},
local_mod::{self,},
remote_mod,
serializable_struct,
};

Expand All @@ -19,28 +12,3 @@ serializable_struct!(CommonModData {
pub unity_backend: Option<UnityScriptingBackend>,
pub loader_id: String, // TODO make enum
});

pub type CommonDataMap = HashMap<String, CommonModData>;

pub fn get_common_data_map(
local_mods: &local_mod::Map,
remote_mods: &remote_mod::Map,
) -> CommonDataMap {
let keys: HashSet<_> = remote_mods
.keys()
.chain(local_mods.keys())
.cloned()
.collect();

keys.iter()
.filter_map(|key| {
Some((
key.clone(),
local_mods.get(key).map_or_else(
|| remote_mods.get(key).map(|local| local.common.clone()),
|remote| Some(remote.common.clone()),
)?,
))
})
.collect()
}
91 changes: 44 additions & 47 deletions backend/src/installed_game.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
use std::{
collections::HashMap,
fs::{
self,
File,
},
fs::{self,},
path::{
Path,
PathBuf,
},
};

use log::error;

use crate::{
game_executable::GameExecutable,
game_mod,
mod_manifest,
owned_game,
paths::{
self,
glob_path,
hash_path,
},
providers::{
Expand All @@ -41,7 +40,7 @@ serializable_struct!(InstalledGame {
});

pub type Map = HashMap<String, InstalledGame>;
type InstalledModVersions = HashMap<String, Option<String>>;
type InstalledModVersions = HashMap<String, String>;

impl InstalledGame {
pub fn new(path: &Path, name: &str, provider_id: ProviderId) -> Option<Self> {
Expand All @@ -68,8 +67,10 @@ impl InstalledGame {

let executable = GameExecutable::new(path)?;

Some(Self {
id: hash_path(&executable.path),
let game_id = hash_path(&executable.path);

let mut installed_game = Self {
id: game_id,
name: name.to_string(),
provider: provider_id,
installed_mod_versions: HashMap::default(),
Expand All @@ -78,7 +79,11 @@ impl InstalledGame {
thumbnail_url: None,
start_command: None,
owned_game_id: None,
})
};

installed_game.refresh_installed_mods();

Some(installed_game)
}

pub fn set_discriminator(&mut self, discriminator: &str) -> &Self {
Expand Down Expand Up @@ -116,8 +121,8 @@ impl InstalledGame {
Ok(())
}

pub fn update_available_mods(&mut self, data_map: &game_mod::CommonDataMap) {
self.installed_mod_versions = self.get_available_mods(data_map);
pub fn refresh_installed_mods(&mut self) {
self.installed_mod_versions = self.get_available_mods();
}

pub fn open_game_folder(&self) -> Result {
Expand Down Expand Up @@ -163,17 +168,31 @@ impl InstalledGame {
Ok(())
}

pub fn refresh_mods(&mut self, data_map: &game_mod::CommonDataMap) {
self.installed_mod_versions = self.get_available_mods(data_map);
pub fn get_manifest_paths(&self) -> Vec<PathBuf> {
match self.get_installed_mod_manifest_path("*") {
Ok(manifests_path) => glob_path(&manifests_path),
Err(err) => {
error!(
"Failed to get mod manifests glob path for game {}. Error: {}",
self.id, err
);
Vec::default()
}
}
}

pub fn get_installed_mods_folder(&self) -> Result<PathBuf> {
let installed_mods_folder = paths::app_data_path()?
.join("installed-mods")
.join(&self.id);
fs::create_dir_all(&installed_mods_folder)?;
pub fn get_available_mods(&self) -> InstalledModVersions {
self.get_manifest_paths()
.iter()
.filter_map(|manifest_path| {
let manifest = mod_manifest::get(manifest_path)?;

Ok(installed_mods_folder)
Some((
manifest_path.file_stem()?.to_str()?.to_string(),
manifest.version,
))
})
.collect()
}

pub fn get_installed_mod_manifest_path(&self, mod_id: &str) -> Result<PathBuf> {
Expand All @@ -183,34 +202,12 @@ impl InstalledGame {
.join(format!("{mod_id}.json")))
}

pub fn get_installed_mod_version(&self, mod_id: &str) -> Option<String> {
let manifest_path = self.get_installed_mod_manifest_path(mod_id).ok()?;
let manifest_file = File::open(manifest_path).ok()?;
let manifest: mod_manifest::Manifest = serde_json::from_reader(manifest_file).ok()?;
Some(manifest.version)
}

pub fn get_available_mods(&self, data_map: &game_mod::CommonDataMap) -> InstalledModVersions {
data_map
.iter()
.filter_map(|(mod_id, mod_data)| {
if equal_or_none(
mod_data.engine,
self.executable.engine.as_ref().map(|engine| engine.brand),
) && equal_or_none(mod_data.unity_backend, self.executable.scripting_backend)
{
Some((mod_id.clone(), self.get_installed_mod_version(mod_id)))
} else {
None
}
})
.collect()
}
}
pub fn get_installed_mods_folder(&self) -> Result<PathBuf> {
let installed_mods_folder = paths::app_data_path()?
.join("installed-mods")
.join(&self.id);
fs::create_dir_all(&installed_mods_folder)?;

fn equal_or_none<T: PartialEq>(a: Option<T>, b: Option<T>) -> bool {
match (a, b) {
(Some(value_a), Some(value_b)) => value_a == value_b,
_ => true,
Ok(installed_mods_folder)
}
}
2 changes: 1 addition & 1 deletion backend/src/local_mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ serializable_struct!(LocalMod {
});

pub fn get_manifest_path(mod_path: &Path) -> PathBuf {
mod_path.join("rai-pal-manifest.json")
mod_path.join(mod_manifest::Manifest::FILE_NAME)
}

impl LocalMod {
Expand Down
Loading

0 comments on commit fe2c858

Please sign in to comment.