You've already forked AstralRinth
forked from didirus/AstralRinth
* initial migration * barebones profiles * Finish profiles * Add back file watcher * UI support progress * Finish most of cache * Fix options page * Fix forge, finish modrinth auth * Accounts, process cache * Run SQLX prepare * Finish * Run lint + actions * Fix version to be compat with windows * fix lint * actually fix lint * actually fix lint again
514 lines
18 KiB
Rust
514 lines
18 KiB
Rust
use crate::data::DirectoryInfo;
|
|
use crate::jre::check_jre;
|
|
use crate::prelude::ModLoader;
|
|
use crate::state;
|
|
use crate::state::{
|
|
Credentials, DefaultPage, DeviceToken, DeviceTokenKey, DeviceTokenPair,
|
|
Hooks, LinkedData, MemorySettings, ModrinthCredentials, Profile,
|
|
ProfileInstallStage, Theme, WindowSize,
|
|
};
|
|
use crate::util::fetch::{read_json, IoSemaphore};
|
|
use chrono::{DateTime, Utc};
|
|
use p256::ecdsa::SigningKey;
|
|
use p256::pkcs8::DecodePrivateKey;
|
|
use serde::Deserialize;
|
|
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
use tokio::sync::Semaphore;
|
|
use uuid::Uuid;
|
|
|
|
pub async fn migrate_legacy_data<'a, E>(exec: E) -> crate::Result<()>
|
|
where
|
|
E: sqlx::Executor<'a, Database = sqlx::Sqlite> + Copy,
|
|
{
|
|
let mut settings = state::Settings::get(exec).await?;
|
|
|
|
if settings.migrated {
|
|
return Ok(());
|
|
};
|
|
|
|
let old_launcher_root = if let Some(dir) = default_settings_dir() {
|
|
dir
|
|
} else {
|
|
return Ok(());
|
|
};
|
|
let old_launcher_root_str = old_launcher_root.to_string_lossy().to_string();
|
|
|
|
let new_launcher_root = DirectoryInfo::get_initial_settings_dir().ok_or(
|
|
crate::ErrorKind::FSError(
|
|
"Could not find valid config dir".to_string(),
|
|
),
|
|
)?;
|
|
let new_launcher_root_str = new_launcher_root
|
|
.to_string_lossy()
|
|
.to_string()
|
|
.trim_end_matches('/')
|
|
.trim_end_matches('\\')
|
|
.to_string();
|
|
|
|
let io_semaphore = IoSemaphore(Semaphore::new(10));
|
|
let settings_path = old_launcher_root.join("settings.json");
|
|
|
|
if let Ok(legacy_settings) =
|
|
read_json::<LegacySettings>(&settings_path, &io_semaphore).await
|
|
{
|
|
settings.max_concurrent_writes = legacy_settings.max_concurrent_writes;
|
|
settings.max_concurrent_downloads =
|
|
legacy_settings.max_concurrent_downloads;
|
|
settings.theme = match legacy_settings.theme {
|
|
LegacyTheme::Dark => Theme::Dark,
|
|
LegacyTheme::Light => Theme::Light,
|
|
LegacyTheme::Oled => Theme::Oled,
|
|
};
|
|
settings.default_page = match legacy_settings.default_page {
|
|
LegacyDefaultPage::Home => DefaultPage::Home,
|
|
LegacyDefaultPage::Library => DefaultPage::Library,
|
|
};
|
|
settings.collapsed_navigation = legacy_settings.collapsed_navigation;
|
|
settings.advanced_rendering = legacy_settings.advanced_rendering;
|
|
settings.native_decorations = legacy_settings.native_decorations;
|
|
settings.telemetry = !legacy_settings.opt_out_analytics;
|
|
settings.discord_rpc = !legacy_settings.disable_discord_rpc;
|
|
settings.developer_mode = legacy_settings.developer_mode;
|
|
settings.onboarded = legacy_settings.fully_onboarded;
|
|
settings.extra_launch_args = legacy_settings.custom_java_args;
|
|
settings.custom_env_vars = legacy_settings.custom_env_args;
|
|
settings.memory.maximum = legacy_settings.memory.maximum;
|
|
settings.force_fullscreen = legacy_settings.force_fullscreen;
|
|
settings.game_resolution.0 = legacy_settings.game_resolution.0;
|
|
settings.game_resolution.1 = legacy_settings.game_resolution.1;
|
|
settings.hide_on_process_start = legacy_settings.hide_on_process;
|
|
settings.hooks.pre_launch = legacy_settings.hooks.pre_launch;
|
|
settings.hooks.wrapper = legacy_settings.hooks.wrapper;
|
|
settings.hooks.post_exit = legacy_settings.hooks.post_exit;
|
|
|
|
if let Some(path) = legacy_settings
|
|
.loaded_config_dir
|
|
.clone()
|
|
.and_then(|x| x.to_str().map(|x| x.to_string()))
|
|
{
|
|
if path != old_launcher_root_str {
|
|
settings.custom_dir = Some(path);
|
|
}
|
|
}
|
|
|
|
settings.prev_custom_dir = Some(old_launcher_root_str.clone());
|
|
|
|
for (_, legacy_version) in legacy_settings.java_globals.0 {
|
|
if let Ok(Some(mut java_version)) =
|
|
check_jre(PathBuf::from(legacy_version.path)).await
|
|
{
|
|
java_version.path = java_version
|
|
.path
|
|
.replace(&old_launcher_root_str, &new_launcher_root_str);
|
|
|
|
java_version.upsert(exec).await?;
|
|
}
|
|
}
|
|
|
|
let modrinth_auth_path =
|
|
old_launcher_root.join("caches/metadata/auth.json");
|
|
if let Ok(creds) = read_json::<LegacyModrinthCredentials>(
|
|
&modrinth_auth_path,
|
|
&io_semaphore,
|
|
)
|
|
.await
|
|
{
|
|
ModrinthCredentials {
|
|
session: creds.session,
|
|
expires: creds.expires_at,
|
|
user_id: creds.user.id,
|
|
active: true,
|
|
}
|
|
.upsert(exec)
|
|
.await?;
|
|
}
|
|
|
|
let minecraft_auth_path =
|
|
old_launcher_root.join("caches/metadata/minecraft_auth.json");
|
|
if let Ok(minecraft_auth) = read_json::<LegacyMinecraftAuthStore>(
|
|
&minecraft_auth_path,
|
|
&io_semaphore,
|
|
)
|
|
.await
|
|
{
|
|
let minecraft_users_len = minecraft_auth.users.len();
|
|
for (uuid, credential) in minecraft_auth.users {
|
|
Credentials {
|
|
id: credential.id,
|
|
username: credential.username,
|
|
access_token: credential.access_token,
|
|
refresh_token: credential.refresh_token,
|
|
expires: credential.expires,
|
|
active: minecraft_auth.default_user == Some(uuid)
|
|
|| minecraft_users_len == 1,
|
|
}
|
|
.upsert(exec)
|
|
.await?;
|
|
}
|
|
|
|
if let Some(device_token) = minecraft_auth.token {
|
|
if let Ok(private_key) =
|
|
SigningKey::from_pkcs8_pem(&device_token.private_key)
|
|
{
|
|
if let Ok(uuid) = Uuid::parse_str(&device_token.id) {
|
|
DeviceTokenPair {
|
|
token: DeviceToken {
|
|
issue_instant: device_token.token.issue_instant,
|
|
not_after: device_token.token.not_after,
|
|
token: device_token.token.token,
|
|
display_claims: device_token
|
|
.token
|
|
.display_claims,
|
|
},
|
|
key: DeviceTokenKey {
|
|
id: uuid,
|
|
key: private_key,
|
|
x: device_token.x,
|
|
y: device_token.y,
|
|
},
|
|
}
|
|
.upsert(exec)
|
|
.await?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Ok(profiles_dir) = std::fs::read_dir(
|
|
&legacy_settings
|
|
.loaded_config_dir
|
|
.unwrap_or(old_launcher_root)
|
|
.join("profiles"),
|
|
) {
|
|
for entry in profiles_dir.flatten() {
|
|
if entry.path().is_dir() {
|
|
let profile_path = entry.path().join("profile.json");
|
|
|
|
if let Ok(profile) =
|
|
read_json::<LegacyProfile>(&profile_path, &io_semaphore)
|
|
.await
|
|
{
|
|
Profile {
|
|
path: profile.path,
|
|
install_stage: match profile.install_stage {
|
|
LegacyProfileInstallStage::Installed => {
|
|
ProfileInstallStage::Installed
|
|
}
|
|
LegacyProfileInstallStage::Installing => {
|
|
ProfileInstallStage::Installing
|
|
}
|
|
LegacyProfileInstallStage::PackInstalling => {
|
|
ProfileInstallStage::PackInstalling
|
|
}
|
|
LegacyProfileInstallStage::NotInstalled => {
|
|
ProfileInstallStage::NotInstalled
|
|
}
|
|
},
|
|
name: profile.metadata.name,
|
|
icon_path: profile.metadata.icon.map(|x| {
|
|
x.replace(
|
|
&old_launcher_root_str,
|
|
&new_launcher_root_str,
|
|
)
|
|
}),
|
|
game_version: profile.metadata.game_version,
|
|
loader: match profile.metadata.loader {
|
|
LegacyModLoader::Vanilla => ModLoader::Vanilla,
|
|
LegacyModLoader::Forge => ModLoader::Forge,
|
|
LegacyModLoader::Fabric => ModLoader::Fabric,
|
|
LegacyModLoader::Quilt => ModLoader::Quilt,
|
|
LegacyModLoader::NeoForge => {
|
|
ModLoader::NeoForge
|
|
}
|
|
},
|
|
loader_version: profile
|
|
.metadata
|
|
.loader_version
|
|
.map(|x| x.id),
|
|
groups: profile.metadata.groups,
|
|
linked_data: profile.metadata.linked_data.and_then(
|
|
|x| {
|
|
if let Some(project_id) = x.project_id {
|
|
if let Some(version_id) = x.version_id {
|
|
if let Some(locked) = x.locked {
|
|
return Some(LinkedData {
|
|
project_id,
|
|
version_id,
|
|
locked,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
},
|
|
),
|
|
created: profile.metadata.date_created,
|
|
modified: profile.metadata.date_modified,
|
|
last_played: profile.metadata.last_played,
|
|
submitted_time_played: profile
|
|
.metadata
|
|
.submitted_time_played,
|
|
recent_time_played: profile
|
|
.metadata
|
|
.recent_time_played,
|
|
java_path: profile.java.as_ref().and_then(|x| {
|
|
x.override_version.clone().map(|x| {
|
|
x.path.replace(
|
|
&old_launcher_root_str,
|
|
&new_launcher_root_str,
|
|
)
|
|
})
|
|
}),
|
|
extra_launch_args: profile
|
|
.java
|
|
.as_ref()
|
|
.and_then(|x| x.extra_arguments.clone()),
|
|
custom_env_vars: profile
|
|
.java
|
|
.and_then(|x| x.custom_env_args),
|
|
memory: profile
|
|
.memory
|
|
.map(|x| MemorySettings { maximum: x.maximum }),
|
|
force_fullscreen: profile.fullscreen,
|
|
game_resolution: profile
|
|
.resolution
|
|
.map(|x| WindowSize(x.0, x.1)),
|
|
hooks: Hooks {
|
|
pre_launch: profile
|
|
.hooks
|
|
.as_ref()
|
|
.and_then(|x| x.pre_launch.clone()),
|
|
wrapper: profile
|
|
.hooks
|
|
.as_ref()
|
|
.and_then(|x| x.wrapper.clone()),
|
|
post_exit: profile
|
|
.hooks
|
|
.and_then(|x| x.post_exit),
|
|
},
|
|
}
|
|
.upsert(exec)
|
|
.await?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
settings.migrated = true;
|
|
settings.update(exec).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Deserialize, Debug, Clone)]
|
|
struct LegacySettings {
|
|
pub theme: LegacyTheme,
|
|
pub memory: LegacyMemorySettings,
|
|
#[serde(default)]
|
|
pub force_fullscreen: bool,
|
|
pub game_resolution: LegacyWindowSize,
|
|
pub custom_java_args: Vec<String>,
|
|
pub custom_env_args: Vec<(String, String)>,
|
|
pub java_globals: LegacyJavaGlobals,
|
|
pub hooks: LegacyHooks,
|
|
pub max_concurrent_downloads: usize,
|
|
pub max_concurrent_writes: usize,
|
|
pub collapsed_navigation: bool,
|
|
#[serde(default)]
|
|
pub disable_discord_rpc: bool,
|
|
#[serde(default)]
|
|
pub hide_on_process: bool,
|
|
#[serde(default)]
|
|
pub native_decorations: bool,
|
|
#[serde(default)]
|
|
pub default_page: LegacyDefaultPage,
|
|
#[serde(default)]
|
|
pub developer_mode: bool,
|
|
#[serde(default)]
|
|
pub opt_out_analytics: bool,
|
|
#[serde(default)]
|
|
pub advanced_rendering: bool,
|
|
#[serde(default)]
|
|
pub fully_onboarded: bool,
|
|
#[serde(default = "default_settings_dir")]
|
|
pub loaded_config_dir: Option<PathBuf>,
|
|
}
|
|
|
|
fn default_settings_dir() -> Option<PathBuf> {
|
|
Some(dirs::config_dir()?.join("com.modrinth.theseus"))
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum LegacyTheme {
|
|
Dark,
|
|
Light,
|
|
Oled,
|
|
}
|
|
|
|
#[derive(Deserialize, Default, Debug, Clone, Copy)]
|
|
enum LegacyDefaultPage {
|
|
#[default]
|
|
Home,
|
|
Library,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug, Clone)]
|
|
struct LegacyHooks {
|
|
pub pre_launch: Option<String>,
|
|
pub wrapper: Option<String>,
|
|
pub post_exit: Option<String>,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug, Clone, Copy)]
|
|
struct LegacyMemorySettings {
|
|
pub maximum: u32,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug, Clone, Copy)]
|
|
struct LegacyWindowSize(pub u16, pub u16);
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
struct LegacyJavaGlobals(HashMap<String, LegacyJavaVersion>);
|
|
|
|
#[derive(Debug, PartialEq, Eq, Hash, Deserialize, Clone)]
|
|
struct LegacyJavaVersion {
|
|
pub path: String,
|
|
pub version: String,
|
|
pub architecture: String,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
struct LegacyModrinthUser {
|
|
pub id: String,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
struct LegacyModrinthCredentials {
|
|
pub session: String,
|
|
pub expires_at: DateTime<Utc>,
|
|
pub user: LegacyModrinthUser,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
struct LegacyMinecraftAuthStore {
|
|
pub users: HashMap<Uuid, LegacyCredentials>,
|
|
pub token: Option<LegacySaveDeviceToken>,
|
|
pub default_user: Option<Uuid>,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
struct LegacyCredentials {
|
|
pub id: Uuid,
|
|
pub username: String,
|
|
pub access_token: String,
|
|
pub refresh_token: String,
|
|
pub expires: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
struct LegacySaveDeviceToken {
|
|
pub id: String,
|
|
pub private_key: String,
|
|
pub x: String,
|
|
pub y: String,
|
|
pub token: LegacyDeviceToken,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
#[serde(rename_all = "PascalCase")]
|
|
struct LegacyDeviceToken {
|
|
pub issue_instant: DateTime<Utc>,
|
|
pub not_after: DateTime<Utc>,
|
|
pub token: String,
|
|
pub display_claims: HashMap<String, serde_json::Value>,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
struct LegacyProfile {
|
|
#[serde(default)]
|
|
pub install_stage: LegacyProfileInstallStage,
|
|
#[serde(default)]
|
|
pub path: String,
|
|
pub metadata: LegacyProfileMetadata,
|
|
pub java: Option<LegacyJavaSettings>,
|
|
pub memory: Option<LegacyMemorySettings>,
|
|
pub resolution: Option<LegacyWindowSize>,
|
|
pub fullscreen: Option<bool>,
|
|
pub hooks: Option<LegacyHooks>,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
struct LegacyProfileMetadata {
|
|
pub name: String,
|
|
pub icon: Option<String>,
|
|
#[serde(default)]
|
|
pub groups: Vec<String>,
|
|
|
|
pub game_version: String,
|
|
#[serde(default)]
|
|
pub loader: LegacyModLoader,
|
|
pub loader_version: Option<LegacyLoaderVersion>,
|
|
|
|
pub linked_data: Option<LegacyLinkedData>,
|
|
|
|
#[serde(default)]
|
|
pub date_created: DateTime<Utc>,
|
|
#[serde(default)]
|
|
pub date_modified: DateTime<Utc>,
|
|
pub last_played: Option<DateTime<Utc>>,
|
|
#[serde(default)]
|
|
pub submitted_time_played: u64,
|
|
#[serde(default)]
|
|
pub recent_time_played: u64,
|
|
}
|
|
|
|
#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Default)]
|
|
#[serde(rename_all = "lowercase")]
|
|
enum LegacyModLoader {
|
|
#[default]
|
|
Vanilla,
|
|
Forge,
|
|
Fabric,
|
|
Quilt,
|
|
NeoForge,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
struct LegacyLinkedData {
|
|
pub project_id: Option<String>,
|
|
pub version_id: Option<String>,
|
|
|
|
#[serde(default = "default_locked")]
|
|
pub locked: Option<bool>,
|
|
}
|
|
|
|
fn default_locked() -> Option<bool> {
|
|
Some(true)
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
struct LegacyJavaSettings {
|
|
pub override_version: Option<LegacyJavaVersion>,
|
|
pub extra_arguments: Option<Vec<String>>,
|
|
pub custom_env_args: Option<Vec<(String, String)>>,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
struct LegacyLoaderVersion {
|
|
pub id: String,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
#[serde(rename_all = "snake_case")]
|
|
enum LegacyProfileInstallStage {
|
|
Installed,
|
|
Installing,
|
|
PackInstalling,
|
|
#[default]
|
|
NotInstalled,
|
|
}
|