Skip to content

Commit

Permalink
0.12.0 (#217)
Browse files Browse the repository at this point in the history
- Fetching mods from new version of the database, which means new UUVR
versions now available, plus a new Configuration Manager mod for Unity
BepInEx mods.
- Mod loaders now support passing a few extra parameters (thanks keton,
and sorry for the delay).
- Use more recent version of BepInEx for IL2CPP games.
- Add new "App Type" filter, lets you hide demos. For now, it only uses
reported information from Steam, but for other providers I'm just
guessing from the game name (if it ends in "Demo", it's probably a
demo).
- Delete all mod files (but not configs) from a mod's folder when that
mod is updated. Helps prevent issues with conflicts when updating mods
or installing mods with the same IDs.
  • Loading branch information
Raicuparta authored Jun 23, 2024
2 parents c3b4407 + 47a4e6b commit 876b0a8
Show file tree
Hide file tree
Showing 29 changed files with 713,051 additions and 60 deletions.
2 changes: 1 addition & 1 deletion backend/Cargo.lock

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

2 changes: 1 addition & 1 deletion backend/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rai-pal"
version = "0.11.1"
version = "0.12.0"
authors = ["Raicuparta"]
license = "GPL-3.0-or-later"
repository = "https://github.com/Raicuparta/rai-pal"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4.3.0
Binary file not shown.
Binary file modified backend/resources/bepinex/Il2Cpp/Windows/X64/mod-loader.zip
Binary file not shown.
67 changes: 49 additions & 18 deletions backend/resources/bepinex/Il2Cpp/doorstop_config.ini
Original file line number Diff line number Diff line change
@@ -1,22 +1,53 @@
# {{ MOD_FILES_PATH }} will be used by Rai Pal. This token gets replaced by the path where BepInEx is installed.

[UnityDoorstop]
# Specifies whether assembly executing is enabled
enabled=true
# Specifies the path (absolute, or relative to the game's exe) to the DLL/EXE that should be executed by Doorstop
targetAssembly={{MOD_FILES_PATH}}/BepInEx/core/BepInEx.IL2CPP.dll
# Specifies whether Unity's output log should be redirected to <current folder>\output_log.txt
redirectOutputLog=false
# General options for Unity Doorstop
[General]

# Enable Doorstop?
enabled = true

# Path to the assembly to load and execute
# NOTE: The entrypoint must be of format `static void Doorstop.Entrypoint.Start()`
target_assembly = {{MOD_FILES_PATH}}\BepInEx\core\BepInEx.Unity.IL2CPP.dll

# If true, Unity's output log is redirected to <current folder>\output_log.txt
redirect_output_log = false

# Overrides the default boot.config file path
boot_config_override =

# If enabled, DOORSTOP_DISABLE env var value is ignored
# USE THIS ONLY WHEN ASKED TO OR YOU KNOW WHAT THIS MEANS
ignoreDisableSwitch=true
[MonoBackend]
runtimeLib={{MOD_FILES_PATH}}/mono/MonoBleedingEdge/EmbedRuntime/mono-2.0-sgen.dll
configDir={{MOD_FILES_PATH}}/mono/MonoBleedingEdge/etc
corlibDir={{MOD_FILES_PATH}}/mono/Managed
# Specifies whether the mono soft debugger is enabled
debugEnabled=false
# Specifies whether the mono soft debugger should suspend the process and wait for the remote debugger
debugSuspend=false
# Specifies the listening address the soft debugger
debugAddress=127.0.0.1:10000
ignore_disable_switch = true

# Options specific to running under Unity Mono runtime
[UnityMono]

# Overrides default Mono DLL search path
# Sometimes it is needed to instruct Mono to seek its assemblies from a different path
# (e.g. mscorlib is stripped in original game)
# This option causes Mono to seek mscorlib and core libraries from a different folder before Managed
# Original Managed folder is added as a secondary folder in the search path
dll_search_path_override =

# If true, Mono debugger server will be enabled
debug_enabled = false

# When debug_enabled is true, this option specifies whether Doorstop should initialize the debugger server
# If you experience crashes when starting the debugger on debug UnityPlayer builds, try setting this to false
debug_start_server = true

# When debug_enabled is true, specifies the address to use for the debugger server
debug_address = 127.0.0.1:10000

# If true and debug_enabled is true, Mono debugger server will suspend the game execution until a debugger is attached
debug_suspend = false

# Options sepcific to running under Il2Cpp runtime
[Il2Cpp]

# Path to coreclr.dll that contains the CoreCLR runtime
coreclr_path = {{MOD_FILES_PATH}}\dotnet\coreclr.dll

# Path to the directory containing the managed core libraries for CoreCLR (mscorlib, System, etc.)
corlib_dir = {{MOD_FILES_PATH}}\dotnet
3 changes: 3 additions & 0 deletions backend/src/app_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
use crate::serializable_enum;

serializable_enum!(AppType { Game, Demo });
23 changes: 0 additions & 23 deletions backend/src/installed_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,29 +149,6 @@ impl InstalledGame {
Ok(open::that_detached(&self.executable.path)?)
}

pub fn uninstall_mod(&self, mod_id: &str) -> Result {
// TODO this should be handled by each mod loader.
let installed_mods_folder = self.get_installed_mods_folder()?;
let bepinex_folder = installed_mods_folder.join("BepInEx");

let plugins_folder = bepinex_folder.join("plugins").join(mod_id);
if plugins_folder.is_dir() {
fs::remove_dir_all(plugins_folder)?;
}

let patchers_folder = bepinex_folder.join("patchers").join(mod_id);
if patchers_folder.is_dir() {
fs::remove_dir_all(patchers_folder)?;
}

let manifest_path = self.get_installed_mod_manifest_path(mod_id)?;
if manifest_path.is_file() {
fs::remove_file(manifest_path)?;
}

Ok(())
}

pub fn get_manifest_paths(&self) -> Vec<PathBuf> {
match self.get_installed_mod_manifest_path("*") {
Ok(manifests_path) => glob_path(&manifests_path),
Expand Down
14 changes: 13 additions & 1 deletion backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use tauri_plugin_log::LogTarget;

mod analytics;
mod app_state;
mod app_type;
mod debug;
mod events;
mod files;
Expand Down Expand Up @@ -239,6 +240,9 @@ async fn install_mod(game_id: &str, mod_id: &str, handle: AppHandle) -> Result {

let mod_loader = mod_loaders.try_get(&local_mod.common.loader_id)?;

// Uninstall mod if it already exists, in case there are conflicting leftover files when updating.
mod_loader.uninstall_mod(game, &local_mod).await?;

mod_loader.install_mod(game, &local_mod).await?;

refresh_game_mods_and_exe(&game.id, &handle)?;
Expand Down Expand Up @@ -330,7 +334,15 @@ async fn uninstall_mod(game_id: &str, mod_id: &str, handle: AppHandle) -> Result
let state = handle.app_state();
let mut installed_games = state.installed_games.get_data()?;
let game = installed_games.try_get_mut(game_id)?;
game.uninstall_mod(mod_id)?;

let mod_loaders = state.mod_loaders.get_data()?;

let local_mod = refresh_and_get_local_mod(mod_id, &mod_loaders, &handle).await?;

let mod_loader = mod_loaders.try_get(&local_mod.common.loader_id)?;

// Uninstall mod if it already exists, in case there are conflicting leftover files when updating.
mod_loader.uninstall_mod(game, &local_mod).await?;

refresh_game_mods_and_exe(&game.id, &handle)?;

Expand Down
22 changes: 22 additions & 0 deletions backend/src/mod_loaders/bepinex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,28 @@ impl ModLoaderActions for BepInEx {
Ok(())
}

async fn uninstall_mod(&self, game: &InstalledGame, local_mod: &LocalMod) -> Result {
let installed_mods_folder = game.get_installed_mods_folder()?;
let bepinex_folder = installed_mods_folder.join("BepInEx");

let plugins_folder = bepinex_folder.join("plugins").join(&local_mod.common.id);
if plugins_folder.is_dir() {
fs::remove_dir_all(plugins_folder)?;
}

let patchers_folder = bepinex_folder.join("patchers").join(&local_mod.common.id);
if patchers_folder.is_dir() {
fs::remove_dir_all(patchers_folder)?;
}

let manifest_path = game.get_installed_mod_manifest_path(&local_mod.common.id)?;
if manifest_path.is_file() {
fs::remove_file(manifest_path)?;
}

Ok(())
}

fn configure_mod(&self, game: &InstalledGame, _local_mod: &LocalMod) -> Result {
let game_data_folder = game.get_installed_mods_folder()?;
let mod_config_path = game_data_folder.join("BepInEx").join("config");
Expand Down
10 changes: 8 additions & 2 deletions backend/src/mod_loaders/mod_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ use crate::{
Result,
};

const URL_BASE: &str = "https://raicuparta.github.io/rai-pal-db";
const URL_BASE: &str = "https://raicuparta.github.io/rai-pal-db/mod-db";

// The repository over at github.com/Raicuparta/rai-pal-db can have multiple versions of the database.
// This way we prevent old versions of Rai Pal from breaking unless we want them to.
// So when you need to change the database in a backwards-incompatible way,
// you would create a new folder in the database repository and change this number to match the folder.
const DATABASE_VERSION: i32 = 0;

serializable_struct!(DatabaseEntry {
pub id: String,
Expand Down Expand Up @@ -59,7 +65,7 @@ pub async fn get(mod_loader_id: &str) -> Result<ModDatabase> {
let random = rand::random::<u32>();

Ok(reqwest::get(format!(
"{URL_BASE}/{mod_loader_id}.json?cache_avoider={random}"
"{URL_BASE}/{DATABASE_VERSION}/{mod_loader_id}.json?cache_avoider={random}"
))
.await?
.json::<ModDatabase>()
Expand Down
1 change: 1 addition & 0 deletions backend/src/mod_loaders/mod_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub enum ModLoader {
pub trait ModLoaderActions {
fn install(&self, game: &InstalledGame) -> Result;
async fn install_mod_inner(&self, game: &InstalledGame, local_mod: &LocalMod) -> Result;
async fn uninstall_mod(&self, game: &InstalledGame, local_mod: &LocalMod) -> Result;
async fn run_without_game(&self, local_mod: &LocalMod) -> Result;
fn configure_mod(&self, game: &InstalledGame, local_mod: &LocalMod) -> Result;
fn open_installed_mod_folder(&self, game: &InstalledGame, local_mod: &LocalMod) -> Result;
Expand Down
29 changes: 28 additions & 1 deletion backend/src/mod_loaders/runnable_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::{
},
mod_manifest,
paths::glob_path,
providers::provider_command::ProviderCommand,
result::Error,
serializable_enum,
serializable_struct,
Expand All @@ -38,6 +39,8 @@ serializable_enum!(RunnableParameter {
ExecutableName,
ExecutablePath,
GameJson,
StartCommand,
StartCommandArgs,
});

impl ModLoaderStatic for RunnableLoader {
Expand Down Expand Up @@ -91,6 +94,24 @@ fn replace_parameters(argument: &str, game: &InstalledGame) -> String {
result = replace_parameter_value(&result, RunnableParameter::GameJson, || {
Ok(serde_json::to_string(&game)?)
});
result = replace_parameter_value(&result, RunnableParameter::StartCommand, || {
game.start_command.as_ref().map_or_else(
|| Ok(game.executable.path.to_string_lossy().to_string()),
|provider_command| match provider_command {
ProviderCommand::String(s) => Ok(s.to_string()),
ProviderCommand::Path(exe_path, _) => Ok(exe_path.to_string_lossy().to_string()),
},
)
});
result = replace_parameter_value(&result, RunnableParameter::StartCommandArgs, || {
game.start_command.as_ref().map_or_else(
|| Ok(String::new()),
|provider_command| match provider_command {
ProviderCommand::Path(_, args) => Ok(args.join(" ")),
ProviderCommand::String(_) => Ok(String::new()),
},
)
});

result
}
Expand Down Expand Up @@ -129,6 +150,12 @@ impl ModLoaderActions for RunnableLoader {
Ok(())
}

async fn uninstall_mod(&self, _game: &InstalledGame, _local_mod: &LocalMod) -> Result {
// There's nothing to uninstall for runnables.

Ok(())
}

async fn run_without_game(&self, local_mod: &LocalMod) -> Result {
let mod_folder = self.get_mod_path(&local_mod.common)?;

Expand All @@ -147,7 +174,7 @@ impl ModLoaderActions for RunnableLoader {
}

fn configure_mod(&self, game: &InstalledGame, local_mod: &LocalMod) -> Result {
// TODO: make it actually open the config file / folder as per manifest.
// TODO: make it actually open the config file / folder (would need extra info in database / manifest).
self.open_installed_mod_folder(game, local_mod)
}

Expand Down
17 changes: 17 additions & 0 deletions backend/src/owned_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::collections::{
};

use crate::{
app_type::AppType,
game_executable::OperatingSystem,
game_mode::GameMode,
providers::{
Expand All @@ -24,6 +25,7 @@ serializable_struct!(OwnedGame {
pub release_date: Option<i64>,
pub thumbnail_url: Option<String>,
pub game_mode: Option<GameMode>,
pub app_type: Option<AppType>,

// TODO: the keys for this map should be ProviderCommandAction, but tauri-specta doesn't support that.
pub provider_commands: HashMap<String, ProviderCommand>,
Expand All @@ -40,6 +42,7 @@ impl OwnedGame {
release_date: None,
thumbnail_url: None,
game_mode: None,
app_type: None,
}
}

Expand All @@ -63,6 +66,20 @@ impl OwnedGame {
self
}

pub fn set_app_type(&mut self, app_type: AppType) -> &mut Self {
self.app_type = Some(app_type);
self
}

pub fn guess_app_type(&mut self) -> &mut Self {
self.app_type = Some(if self.name.to_lowercase().ends_with(" demo") {
AppType::Demo
} else {
AppType::Game
});
self
}

pub fn add_provider_command(
&mut self,
command_action: ProviderCommandAction,
Expand Down
3 changes: 2 additions & 1 deletion backend/src/providers/epic_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ impl ProviderActions for Epic {
])
.ok()?,
)),
);
)
.guess_app_type();

if let Some(thumbnail_url) = catalog_item.get_thumbnail_url() {
game.set_thumbnail_url(&thumbnail_url);
Expand Down
3 changes: 2 additions & 1 deletion backend/src/providers/gog_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ impl ProviderActions for Gog {
]
.to_vec(),
),
);
)
.guess_app_type();

if let Some(thumbnail_url) = db_entry.image_url.clone() {
game.set_thumbnail_url(&thumbnail_url);
Expand Down
3 changes: 2 additions & 1 deletion backend/src/providers/itch_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ impl ProviderActions for Itch {
.add_provider_command(
ProviderCommandAction::Install,
ProviderCommand::String(format!("itch://install?game_id={}", row.id)),
);
)
.guess_app_type();

game
})
Expand Down
13 changes: 13 additions & 0 deletions backend/src/providers/steam_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use super::{
},
};
use crate::{
app_type::AppType,
game_engines::game_engine::GameEngine,
game_executable::OperatingSystem,
game_mode::GameMode,
Expand Down Expand Up @@ -268,6 +269,18 @@ impl ProviderActions for Steam {
game.set_release_date(release_date.into());
}

if let Some(app_type) = &app_info.app_type {
if app_type == "Game" {
game.set_app_type(AppType::Game);
} else if app_type == "Demo" {
game.set_app_type(AppType::Demo);
}
} else {
// We only try to guess the app type if couldn't read it from appinfo.
// For instance, something marked as Tool or Application shouldn't be marked as a game.
game.guess_app_type();
}

Some(game)
})
.collect();
Expand Down
Loading

0 comments on commit 876b0a8

Please sign in to comment.