Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d0deed3
comment guy
Raicuparta Jul 19, 2025
1238c42
downloading configs somewhat
Raicuparta Jul 19, 2025
923c2af
add todo
Raicuparta Jul 19, 2025
fafa7d8
update deps
Raicuparta Jul 19, 2025
a670933
dont overwrite configs unless manually downloading config
Raicuparta Jul 19, 2025
0e7423a
Merge branch 'main' into remote-configs
Raicuparta Jul 20, 2025
9ce5f3e
remote config text, tooltip adjustments
Raicuparta Jul 20, 2025
4159cf0
cleanup
Raicuparta Jul 20, 2025
2aa8024
move more config logic to generic loader
Raicuparta Jul 20, 2025
337ef7a
cleanup
Raicuparta Jul 20, 2025
ebcb9d4
cleaner still shiny sparkly
Raicuparta Jul 20, 2025
495d4f2
extract folder configs
Raicuparta Jul 20, 2025
a5c9377
abzû normalized to abzu
Raicuparta Jul 20, 2025
cdb005f
parse runnable parameters
Raicuparta Jul 20, 2025
85c7cb8
use config setting for editing configs locally too
Raicuparta Jul 20, 2025
6d0bc7c
http client why not
Raicuparta Jul 20, 2025
43f3e17
make folder type work
Raicuparta Jul 20, 2025
2ecd774
saving config stuff to manifest, wip
Raicuparta Jul 26, 2025
7a09880
auto update mod manifests
Raicuparta Jul 26, 2025
bbe497a
button for config folder too
Raicuparta Jul 26, 2025
e306ddd
update translations
Raicuparta Jul 26, 2025
447da28
sturdy boy
Raicuparta Jul 26, 2025
021de96
Bump version 0.17.0
Raicuparta Jul 26, 2025
a6e06cd
Fix publish script version parsing
Raicuparta Jul 26, 2025
edeefa3
Clean up publish scripts for easier testing
Raicuparta Jul 26, 2025
71111bc
cleanup
Raicuparta Jul 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: setup node
uses: actions/setup-node@v4
with:
cache: 'npm'
cache: "npm"

- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
Expand All @@ -31,7 +31,7 @@ jobs:
- name: Build
id: build
run: |
$output = ./publish.ps1
$output = ./scripts/create-release-build.ps1
$version = $output | Select-Object -Last 1
"version=$version" >> $env:GITHUB_OUTPUT
env:
Expand Down
16 changes: 13 additions & 3 deletions 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
Expand Up @@ -3,7 +3,7 @@ members = ["proc-macros", "core", "tauri-app"]
resolver = "2"

[workspace.package]
version = "0.16.1"
version = "0.17.0"
authors = ["Raicuparta"]
license = "GPL-3.0-or-later"
repository = "https://github.com/Raicuparta/rai-pal"
Expand Down
1 change: 1 addition & 0 deletions backend/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ reqwest = { version = "0.12.22", features = [
] }
serde_urlencoded = "0.7.1"
steamlocate = "2.0.1"
unicode-normalization = "0.1.24"
uuid = { version = "1.17.0", features = ["v4"] }
zip = { version = "4.3.0", default-features = false, features = ["deflate"] }
paths-as-strings = "0.1.1"
Expand Down
9 changes: 9 additions & 0 deletions backend/core/src/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::{
provider::ProviderId,
provider_command::{ProviderCommand, ProviderCommandAction},
},
remote_config::{self, RemoteConfigs},
result::{Error, Result},
};

Expand Down Expand Up @@ -220,4 +221,12 @@ impl DbGame {
}
Ok(self)
}

pub async fn get_remote_configs(&self) -> Result<Option<RemoteConfigs>> {
if let Some(exe_path) = self.exe_path.as_ref() {
remote_config::get_remote_configs(&exe_path.0).await
} else {
Err(Error::GameNotInstalled(self.display_title.clone()))
}
}
}
8 changes: 7 additions & 1 deletion backend/core/src/game_title.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashSet;

use lazy_regex::{regex, regex_replace_all};
use unicode_normalization::UnicodeNormalization;

static DEMO_REGEX: &lazy_regex::Lazy<regex::Regex> = regex!(r"(?i)[\((\s+)]demo\)?$");
static BRACKETS_REGEX: &lazy_regex::Lazy<regex::Regex> = regex!(r"\[.*?\]|\(.*?\)|\{.*?\}|<.*?>");
Expand Down Expand Up @@ -34,7 +35,12 @@ pub fn get_normalized_titles(title: &str) -> Vec<String> {
}

fn normalize_title(title: &str) -> String {
regex_replace_all!(r"\W+", title, "").to_lowercase()
let normalized = title
.nfd()
.filter(|c| !unicode_normalization::char::is_combining_mark(*c))
.collect::<String>();

regex_replace_all!(r"\W+", &normalized, "").to_lowercase()
}

pub fn is_probably_demo(title: &str) -> bool {
Expand Down
23 changes: 23 additions & 0 deletions backend/core/src/http_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use std::sync::OnceLock;

use reqwest::{Client, redirect::Policy};

static HTTP_CLIENT: OnceLock<Client> = OnceLock::new();
static HEAD_CLIENT: OnceLock<Client> = OnceLock::new();

pub fn get_client() -> &'static Client {
HTTP_CLIENT.get_or_init(|| {
Client::builder()
.build()
.expect("Failed to create HTTP client")
})
}

pub fn get_client_no_redirect() -> &'static Client {
HEAD_CLIENT.get_or_init(|| {
Client::builder()
.redirect(Policy::none())
.build()
.expect("Failed to create HEAD HTTP client")
})
}
2 changes: 2 additions & 0 deletions backend/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ pub mod game_mod;
pub mod game_tag;
pub mod game_title;
pub mod games_query;
pub mod http_client;
pub mod local_database;
pub mod local_mod;
pub mod maps;
pub mod mod_loaders;
pub mod mod_manifest;
pub mod paths;
pub mod providers;
pub mod remote_config;
pub mod remote_game;
pub mod remote_mod;
pub mod result;
Expand Down
1 change: 0 additions & 1 deletion backend/core/src/local_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,6 @@ pub fn create() -> Result<DbMutex> {
"#,
)?;

// TODO: Too slow to do on startup.
attach_remote_database(&connection, &remote_game::get_database_file_path()?)?;

instant.log_next("Created local database!");
Expand Down
23 changes: 13 additions & 10 deletions backend/core/src/mod_loaders/bepinex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ use crate::{
game_engines::{game_engine::EngineBrand, unity::UnityBackend},
game_mod::CommonModData,
local_mod::{LocalMod, ModKind},
mod_loaders::mod_loader::{ModLoaderActions, ModLoaderData},
mod_loaders::{
mod_database::ModConfigs,
mod_loader::{ModLoaderActions, ModLoaderData},
},
paths,
result::{Error, Result},
};
Expand Down Expand Up @@ -154,15 +157,6 @@ impl ModLoaderActions for BepInEx {
Ok(())
}

fn configure_mod(&self, game: &DbGame, _local_mod: &LocalMod) -> Result {
let game_data_folder = game.get_installed_mods_folder()?;
let mod_config_path = game_data_folder.join("BepInEx").join("config");

// TODO: actually open the specific config file somehow. Probably needs to be in the remote mod manifest.

Ok(open::that_detached(mod_config_path)?)
}

async fn run_without_game(&self, local_mod: &LocalMod) -> Result {
Err(Error::CantRunNonRunnable(local_mod.common.id.clone()))
}
Expand Down Expand Up @@ -199,6 +193,15 @@ impl ModLoaderActions for BepInEx {

Ok(local_mods)
}

fn get_config_path(&self, game: &DbGame, mod_configs: &ModConfigs) -> Result<PathBuf> {
let destination_path = game
.get_installed_mods_folder()?
.join("BepInEx")
.join(&mod_configs.destination_path);

Ok(destination_path)
}
}

fn is_legacy(game: &DbGame) -> bool {
Expand Down
51 changes: 31 additions & 20 deletions backend/core/src/mod_loaders/mod_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use std::path::PathBuf;

use lazy_regex::regex_captures;
use log::error;
use rai_pal_proc_macros::serializable_struct;
use reqwest::redirect::Policy;
use rai_pal_proc_macros::{serializable_enum, serializable_struct};

use crate::{
game_engines::{game_engine::EngineBrand, unity::UnityBackend},
game_mod::EngineVersionRange,
http_client,
result::Result,
};

Expand All @@ -33,6 +33,7 @@ pub struct DatabaseEntry {
pub github: Option<ModGithubInfo>,
pub redownload_id: Option<i32>,
pub deprecated: Option<bool>,
pub configs: Option<ModConfigs>,
}

#[serializable_struct]
Expand All @@ -54,6 +55,19 @@ pub struct ModDownload {
pub runnable: Option<RunnableModData>,
}

#[serializable_struct]
pub struct ModConfigs {
pub destination_path: String,
pub destination_type: ModConfigDestinationType,
pub mod_id_override: Option<String>,
}

#[serializable_enum]
pub enum ModConfigDestinationType {
File,
Folder,
}

#[serializable_struct]
pub struct ModGithubInfo {
pub user: String,
Expand All @@ -64,12 +78,15 @@ pub struct ModGithubInfo {
}

pub async fn get(mod_loader_id: &str) -> Result<ModDatabase> {
Ok(reqwest::get(format!(
"{URL_BASE}/{DATABASE_VERSION}/{mod_loader_id}.json"
))
.await?
.json::<ModDatabase>()
.await?)
let client = http_client::get_client();
Ok(client
.get(format!(
"{URL_BASE}/{DATABASE_VERSION}/{mod_loader_id}.json"
))
.send()
.await?
.json::<ModDatabase>()
.await?)
}

impl DatabaseEntry {
Expand Down Expand Up @@ -106,13 +123,17 @@ impl ModGithubInfo {
async fn get_latest_tag(&self) -> Option<String> {
let url = format!("{}/latest", self.get_releases_url());

let response = (match request_head(&url).await {
let response = match http_client::get_client_no_redirect()
.head(&url)
.send()
.await
{
Ok(response) => Some(response),
Err(err) => {
error!("Failed to request head for url `{url}`. Error: {err}");
None
}
})?;
}?;

if response.status().is_redirection() {
let location_header = response
Expand Down Expand Up @@ -168,13 +189,3 @@ impl ModGithubInfo {
format!("https://github.com/{}/{}/releases", self.user, self.repo)
}
}

async fn request_head(url: &str) -> Result<reqwest::Response> {
let client = reqwest::Client::builder()
// Don't follow redirects. We don't need to actually download the final page,
// we just want to look at where it wants to redirect.
.redirect(Policy::none())
.build()?;

Ok(client.head(url).send().await?)
}
Loading
Loading