diff --git a/.github/workflows/astralrinth-build.yml b/.github/workflows/astralrinth-build.yml index b5ede000..c5a263d7 100644 --- a/.github/workflows/astralrinth-build.yml +++ b/.github/workflows/astralrinth-build.yml @@ -16,6 +16,7 @@ on: - 'packages/assets/**' - 'packages/ui/**' - 'packages/utils/**' + workflow_dispatch: jobs: build: @@ -24,12 +25,12 @@ jobs: fail-fast: false matrix: # platform: [macos-latest, windows-latest, ubuntu-latest] - platform: [ubuntu-latest] + platform: [windows-latest, ubuntu-latest] include: # - platform: macos-latest # artifact-target-name: universal-apple-darwin - # - platform: windows-latest - # artifact-target-name: x86_64-pc-windows-msvc + - platform: windows-latest + artifact-target-name: x86_64-pc-windows-msvc - platform: ubuntu-latest artifact-target-name: x86_64-unknown-linux-gnu @@ -41,6 +42,35 @@ jobs: with: fetch-depth: 2 + - name: 🔍 Validate Git config does not introduce CRLF + shell: bash + run: | + echo "🔍 Checking Git config for CRLF settings..." + + autocrlf=$(git config --get core.autocrlf || echo "unset") + eol_setting=$(git config --get core.eol || echo "unset") + + echo "core.autocrlf = $autocrlf" + echo "core.eol = $eol_setting" + + if [ "$autocrlf" = "true" ]; then + echo "⚠️ WARNING: core.autocrlf is set to 'true'. Consider setting it to 'input' or 'false'." + fi + + if [ "$eol_setting" = "crlf" ]; then + echo "⚠️ WARNING: core.eol is set to 'crlf'. Consider unsetting it or setting to 'lf'." + fi + + - name: 🔍 Check migration files line endings (LF only) + shell: bash + run: | + echo "🔍 Scanning migration SQL files for CR characters (\\r)..." + if grep -Iq $'\r' packages/app-lib/migrations/*.sql; then + echo "❌ ERROR: Some migration files contain CR (\\r) characters — expected only LF line endings." + exit 1 + fi + echo "✅ All migration files use LF line endings" + - name: 🧰 Setup Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -73,11 +103,11 @@ jobs: - name: 🧰 Install dependencies run: pnpm install - # - name: ✍️ Set up Windows code signing (jsign) - # if: matrix.platform == 'windows-latest' && env.SIGN_WINDOWS_BINARIES == 'true' - # shell: bash - # run: | - # choco install jsign --ignore-dependencies + - name: ✍️ Set up Windows code signing (jsign) + if: matrix.platform == 'windows-latest' && env.SIGN_WINDOWS_BINARIES == 'true' + shell: bash + run: | + choco install jsign --ignore-dependencies - name: 🗑️ Clean up cached bundles shell: bash @@ -99,15 +129,15 @@ jobs: TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} - # - name: 🔨 Build Windows app - # if: matrix.platform == 'windows-latest' - # shell: pwsh - # run: | - # $env:JAVA_HOME = "$env:JAVA_HOME_11_X64" - # pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json --verbose --bundles 'nsis' - # env: - # TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} - # TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} + - name: 🔨 Build Windows app + if: matrix.platform == 'windows-latest' + shell: pwsh + run: | + $env:JAVA_HOME = "$env:JAVA_HOME_11_X64" + pnpm --filter=@modrinth/app run tauri build --config tauri-release.conf.json --verbose --bundles 'nsis' + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} - name: 📤 Upload app bundles uses: actions/upload-artifact@v3 diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index a8a76db2..c837744c 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -42,7 +42,7 @@ import ModrinthLoadingIndicator from '@/components/LoadingIndicatorBar.vue' import { handleError, useNotifications } from '@/store/notifications.js' import { command_listener, warning_listener } from '@/helpers/events.js' import { type } from '@tauri-apps/plugin-os' -import { getOS, isDev, restartApp } from '@/helpers/utils.js' +import { getOS, isDev } from '@/helpers/utils.js' import { debugAnalytics, initAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics' import { getCurrentWindow } from '@tauri-apps/api/window' import { getVersion } from '@tauri-apps/api/app' @@ -72,6 +72,9 @@ import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue' import { get_available_capes, get_available_skins } from './helpers/skins' import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer' +// [AR] Feature +import { getRemote, updateState } from '@/helpers/update.js' + const themeStore = useTheming() const news = ref([]) @@ -99,6 +102,7 @@ const isMaximized = ref(false) onMounted(async () => { await useCheckDisableMouseover() + await getRemote(false) // [AR] Check for updates document.querySelector('body').addEventListener('click', handleClick) document.querySelector('body').addEventListener('auxclick', handleAuxClick) @@ -161,11 +165,11 @@ async function setupApp() { initAnalytics() if (!telemetry) { - console.info("[AR] Telemetry disabled by default (Hard patched).") + console.info("[AR] • Telemetry disabled by default (Hard patched).") optOutAnalytics() } if (!personalized_ads) { - console.info("[AR] Personalized ads disabled by default (Hard patched).") + console.info("[AR] • Personalized ads disabled by default (Hard patched).") } if (dev) debugAnalytics() @@ -188,7 +192,7 @@ async function setupApp() { }), ) - // Patched by AstralRinth + /// [AR] Patch // useFetch( // `https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`, // 'criticalAnnouncements', @@ -465,12 +469,20 @@ function handleAuxClick(e) {
- + + + + +@import '../../../packages/assets/styles/neon-icon.scss'; +@import '../../../packages/assets/styles/neon-text.scss'; + .window-controls { z-index: 20; display: none; diff --git a/apps/app-frontend/src/components/ui/AccountsCard.vue b/apps/app-frontend/src/components/ui/AccountsCard.vue index f352c6d5..7b03e1f3 100644 --- a/apps/app-frontend/src/components/ui/AccountsCard.vue +++ b/apps/app-frontend/src/components/ui/AccountsCard.vue @@ -153,11 +153,11 @@ const loginErrorModal = ref(null) const unexpectedErrorModal = ref(null) const playerName = ref('') -async function tryOfflineLogin() { // Patched by AstralRinth +async function tryOfflineLogin() { // [AR] Feature loginOfflineModal.value.show() } -async function offlineLoginFinally() { // Patched by AstralRinth +async function offlineLoginFinally() { // [AR] Feature const name = playerName.value if (name.length > 1 && name.length < 20 && name !== '') { const loggedIn = await offline_login(name).catch(handleError) @@ -176,12 +176,12 @@ async function offlineLoginFinally() { // Patched by AstralRinth } } -function retryOfflineLogin() { // Patched by AstralRinth +function retryOfflineLogin() { // [AR] Feature loginErrorModal.value.hide() tryOfflineLogin() } -function getAccountType(account) { // Patched by AstralRinth +function getAccountType(account) { // [AR] Feature if (account.access_token != "null" && account.access_token != null && account.access_token != "") { return License } else { diff --git a/apps/app-frontend/src/components/ui/ErrorModal.vue b/apps/app-frontend/src/components/ui/ErrorModal.vue index bae91908..808a101e 100644 --- a/apps/app-frontend/src/components/ui/ErrorModal.vue +++ b/apps/app-frontend/src/components/ui/ErrorModal.vue @@ -18,11 +18,16 @@ import { cancel_directory_change } from '@/helpers/settings.ts' import { install } from '@/helpers/profile.js' import { trackEvent } from '@/helpers/analytics' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' +import { applyMigrationFix } from '@/helpers/utils.js' +import { restartApp } from '@/helpers/utils.js' const errorModal = ref() const error = ref() const closable = ref(true) const errorCollapsed = ref(false) +const language = ref('en') +const migrationFixSuccess = ref(null) // null | true | false +const migrationFixCallbackModel = ref() const title = ref('An error occurred') const errorType = ref('unknown') @@ -148,6 +153,30 @@ async function copyToClipboard(text) { copied.value = false }, 3000) } + +function toggleLanguage() { + language.value = language.value === 'en' ? 'ru' : 'en' +} + +async function onApplyMigrationFix(eol) { + console.log(`[AR] • Attempting to apply migration ${eol.toUpperCase()} fix`) + try { + const result = await applyMigrationFix(eol) + migrationFixSuccess.value = result === true + console.log(`[AR] • Successfully applied migration ${eol.toUpperCase()} fix`, result) + } catch (err) { + console.error(`[AR] • Failed to apply migration fix:`, err) + migrationFixSuccess.value = false + } finally { + migrationFixCallbackModel.value?.show?.() + if (migrationFixSuccess.value === true) { + setTimeout(async () => { + await restartApp() + }, 3000) + } + } +} + \ No newline at end of file diff --git a/apps/app-frontend/src/components/ui/settings/PrivacySettings.vue b/apps/app-frontend/src/components/ui/settings/PrivacySettings.vue index fffe8b02..13f5991c 100644 --- a/apps/app-frontend/src/components/ui/settings/PrivacySettings.vue +++ b/apps/app-frontend/src/components/ui/settings/PrivacySettings.vue @@ -30,7 +30,7 @@ watch( option, you opt out and ads will no longer be shown based on your interests.

- + @@ -43,7 +43,7 @@ watch( longer be collected.

- + diff --git a/apps/app-frontend/src/helpers/update.js b/apps/app-frontend/src/helpers/update.js index 6f3d87a1..cea6c6bd 100644 --- a/apps/app-frontend/src/helpers/update.js +++ b/apps/app-frontend/src/helpers/update.js @@ -11,7 +11,7 @@ const releaseLink = `https://git.astralium.su/api/v1/repos/didirus/AstralRinth/r const failedFetch = [`Failed to fetch remote releases:`, `Failed to fetch remote commits:`] const osList = ['macos', 'windows', 'linux'] -const macExtensionList = ['.app', '.dmg'] +const macExtensionList = ['.dmg', '.pkg'] const windowsExtensionList = ['.exe', '.msi'] const blacklistPrefixes = [ diff --git a/apps/app-frontend/src/helpers/utils.js b/apps/app-frontend/src/helpers/utils.js index a6353504..ac950e17 100644 --- a/apps/app-frontend/src/helpers/utils.js +++ b/apps/app-frontend/src/helpers/utils.js @@ -10,11 +10,17 @@ export async function getOS() { return await invoke('plugin:utils|get_os') } +// [AR] Feature export async function getArtifact(downloadurl, filename, ostype, autoupdatesupported) { console.log('Downloading build', downloadurl, filename, ostype, autoupdatesupported) return await invoke('plugin:utils|get_artifact', { downloadurl, filename, ostype, autoupdatesupported }) } +// [AR] Patch fix +export async function applyMigrationFix(eol) { + return await invoke('plugin:utils|apply_migration_fix', { eol }) +} + export async function openPath(path) { return await invoke('plugin:utils|open_path', { path }) } diff --git a/apps/app/build.rs b/apps/app/build.rs index 59f78131..4942497a 100644 --- a/apps/app/build.rs +++ b/apps/app/build.rs @@ -218,6 +218,7 @@ fn main() { "utils", InlinedPlugin::new() .commands(&[ + "apply_migration_fix", "get_artifact", "get_os", "should_disable_mouseover", diff --git a/apps/app/src/api/utils.rs b/apps/app/src/api/utils.rs index dfe64509..45207915 100644 --- a/apps/app/src/api/utils.rs +++ b/apps/app/src/api/utils.rs @@ -11,10 +11,12 @@ use dashmap::DashMap; use std::path::{Path, PathBuf}; use theseus::prelude::canonicalize; use url::Url; +use theseus::util::utils; pub fn init() -> tauri::plugin::TauriPlugin { tauri::plugin::Builder::new("utils") .invoke_handler(tauri::generate_handler![ + apply_migration_fix, get_artifact, get_os, should_disable_mouseover, @@ -27,9 +29,17 @@ pub fn init() -> tauri::plugin::TauriPlugin { .build() } +/// [AR] Patch fix +#[tauri::command] +pub async fn apply_migration_fix(eol: &str) -> Result { + let result = utils::apply_migration_fix(eol).await?; + Ok(result) +} + +/// [AR] Feature #[tauri::command] pub async fn get_artifact(downloadurl: &str, filename: &str, ostype: &str, autoupdatesupported: bool) -> Result<()> { - theseus::download::init_download(downloadurl, filename, ostype, autoupdatesupported).await; + let _ = utils::init_download(downloadurl, filename, ostype, autoupdatesupported).await; Ok(()) } diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs index dc3ee39e..94427776 100644 --- a/apps/app/src/main.rs +++ b/apps/app/src/main.rs @@ -157,7 +157,7 @@ fn main() { */ let _log_guard = theseus::start_logger(); - tracing::info!("Initialized tracing subscriber. Loading Modrinth App!"); + tracing::info!("Initialized tracing subscriber. Loading AstralRinth App!"); let mut builder = tauri::Builder::default(); diff --git a/apps/app/tauri.conf.json b/apps/app/tauri.conf.json index 26021c70..4ebb2aa5 100644 --- a/apps/app/tauri.conf.json +++ b/apps/app/tauri.conf.json @@ -41,7 +41,7 @@ ] }, "productName": "AstralRinth App", - "version": "0.10.302", + "version": "0.10.303", "mainBinaryName": "AstralRinth App", "identifier": "AstralRinthApp", "plugins": { diff --git a/packages/app-lib/src/api/download.rs b/packages/app-lib/src/api/download.rs deleted file mode 100644 index bac7cdc5..00000000 --- a/packages/app-lib/src/api/download.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::process::exit; - -use reqwest; -use tokio::fs::File as AsyncFile; -use tokio::io::AsyncWriteExt; -use tokio::process::Command; - -async fn download_file(download_url: &str, local_filename: &str, os_type: &str, auto_update_supported: bool) -> Result<(), Box> { - let download_dir = dirs::download_dir().ok_or("[download_file] • Failed to determine download directory")?; - let full_path = download_dir.join(local_filename); - let response = reqwest::get(download_url).await?; - let bytes = response.bytes().await?; - let mut dest_file = AsyncFile::create(&full_path).await?; - dest_file.write_all(&bytes).await?; - println!("[download_file] • File downloaded to: {:?}", full_path); - if auto_update_supported { - let status; - if os_type.to_lowercase() == "Windows".to_lowercase() { - status = Command::new("explorer") - .arg(download_dir.display().to_string()) - .status() - .await - .expect("[download_file] • Failed to open downloads folder"); - } else if os_type.to_lowercase() == "MacOS".to_lowercase() { - status = Command::new("open") - .arg(full_path.to_str().unwrap_or_default()) - .status() - .await - .expect("[download_file] • Failed to execute command"); - } else { - status = Command::new(".") - .arg(full_path.to_str().unwrap_or_default()) - .status() - .await - .expect("[download_file] • Failed to execute command"); - } - if status.success() { - println!("[download_file] • File opened successfully!"); - } else { - eprintln!("[download_file] • Failed to open the file. Exit code: {:?}", status.code()); - } - } - Ok(()) -} - -pub async fn init_download(download_url: &str, local_filename: &str, os_type: &str, auto_update_supported: bool) { - println!("[init_download] • Initialize downloading from • {:?}", download_url); - println!("[init_download] • Save local file name • {:?}", local_filename); - if let Err(e) = download_file(download_url, local_filename, os_type, auto_update_supported).await { - eprintln!("[init_download] • An error occurred! Failed to download the file: {}", e); - } else { - println!("[init_download] • Code finishes without errors."); - exit(0) - } -} \ No newline at end of file diff --git a/packages/app-lib/src/api/mod.rs b/packages/app-lib/src/api/mod.rs index 5c37b003..2623bba1 100644 --- a/packages/app-lib/src/api/mod.rs +++ b/packages/app-lib/src/api/mod.rs @@ -12,8 +12,8 @@ pub mod pack; pub mod process; pub mod profile; pub mod settings; +pub mod update; // [AR] Feature pub mod tags; -pub mod download; // AstralRinth pub mod worlds; pub mod data { diff --git a/packages/app-lib/src/api/update.rs b/packages/app-lib/src/api/update.rs new file mode 100644 index 00000000..3ca0cebd --- /dev/null +++ b/packages/app-lib/src/api/update.rs @@ -0,0 +1,117 @@ +use reqwest; +use std::path::PathBuf; +use tokio::fs::File as AsyncFile; +use tokio::io::AsyncWriteExt; +use tokio::process::Command; + +pub(crate) async fn get_resource( + download_url: &str, + local_filename: &str, + os_type: &str, + auto_update_supported: bool, +) -> Result<(), Box> { + let download_dir = dirs::download_dir() + .ok_or("[AR] • Failed to determine download directory")?; + let full_path = download_dir.join(local_filename); + + let response = reqwest::get(download_url).await?; + let bytes = response.bytes().await?; + let mut dest_file = AsyncFile::create(&full_path).await?; + dest_file.write_all(&bytes).await?; + tracing::info!("[AR] • File downloaded to: {:?}", full_path); + + if auto_update_supported { + let result = match os_type.to_lowercase().as_str() { + "windows" => handle_windows_file(&full_path).await, + "macos" => open_macos_file(&full_path).await, + _ => open_default(&full_path).await, + }; + + match result { + Ok(_) => tracing::info!("[AR] • File opened successfully!"), + Err(e) => tracing::info!("[AR] • Failed to open file: {e}"), + } + } + + Ok(()) +} + +async fn handle_windows_file(path: &PathBuf) -> Result<(), Box> { + let filename = path + .file_name() + .and_then(|f| f.to_str()) + .unwrap_or_default() + .to_lowercase(); + + if filename.ends_with(".exe") || filename.ends_with(".msi") { + tracing::info!("[AR] • Detected installer: {}", filename); + run_windows_installer(path).await + } else { + open_windows_folder(path).await + } +} + +async fn run_windows_installer(path: &PathBuf) -> Result<(), Box> { + let installer_path = path.to_str().unwrap_or_default(); + + let status = if installer_path.ends_with(".msi") { + Command::new("msiexec") + .args(&["/i", installer_path, "/quiet"]) + .status() + .await? + } else { + Command::new("cmd") + .args(&["/C", installer_path]) + .status() + .await? + }; + + if status.success() { + tracing::info!("[AR] • Installer started successfully."); + Ok(()) + } else { + tracing::error!("Installer failed. Exit code: {:?}", status.code()); + tracing::info!("[AR] • Trying to open folder..."); + open_windows_folder(path).await + } +} + +async fn open_windows_folder(path: &PathBuf) -> Result<(), Box> { + let folder = path.parent().unwrap_or(path); + let status = Command::new("explorer") + .arg(folder.display().to_string()) + .status() + .await?; + + if !status.success() { + Err(format!("Exit code: {:?}", status.code()).into()) + } else { + Ok(()) + } +} + +async fn open_macos_file(path: &PathBuf) -> Result<(), Box> { + let status = Command::new("open") + .arg(path.to_str().unwrap_or_default()) + .status() + .await?; + + if !status.success() { + Err(format!("Exit code: {:?}", status.code()).into()) + } else { + Ok(()) + } +} + +async fn open_default(path: &PathBuf) -> Result<(), Box> { + let status = Command::new(".") + .arg(path.to_str().unwrap_or_default()) + .status() + .await?; + + if !status.success() { + Err(format!("Exit code: {:?}", status.code()).into()) + } else { + Ok(()) + } +} diff --git a/packages/app-lib/src/launcher/mod.rs b/packages/app-lib/src/launcher/mod.rs index a62deec7..f1b5deb8 100644 --- a/packages/app-lib/src/launcher/mod.rs +++ b/packages/app-lib/src/launcher/mod.rs @@ -14,7 +14,7 @@ use chrono::Utc; use daedalus as d; use daedalus::minecraft::{LoggingSide, RuleAction, VersionInfo}; use daedalus::modded::LoaderVersion; -use rand::seq::SliceRandom; // AstralRinth +use rand::seq::SliceRandom; // [AR] Feature use regex::Regex; use serde::Deserialize; use st::Profile; @@ -633,7 +633,7 @@ pub async fn launch_minecraft( command.arg("--add-opens=jdk.internal/jdk.internal.misc=ALL-UNNAMED"); } - // Patched by AstralRinth + // [AR] Patch if credentials.access_token == "null" && credentials.refresh_token == "null" { if version_jar == "1.16.4" || version_jar == "1.16.5" { let invalid_url = "https://invalid.invalid"; @@ -743,6 +743,7 @@ pub async fn launch_minecraft( } } + // [AR] Feature let selected_phrase = ACTIVE_STATE.choose(&mut rand::thread_rng()).unwrap(); let _ = state .discord_rpc diff --git a/packages/app-lib/src/lib.rs b/packages/app-lib/src/lib.rs index 55f4459a..376f77e6 100644 --- a/packages/app-lib/src/lib.rs +++ b/packages/app-lib/src/lib.rs @@ -8,7 +8,7 @@ and launching Modrinth mod packs #![deny(unused_must_use)] #[macro_use] -mod util; +pub mod util; // [AR] Refactor mod api; mod config; diff --git a/packages/app-lib/src/state/db.rs b/packages/app-lib/src/state/db.rs index 331d9cb4..c939fd64 100644 --- a/packages/app-lib/src/state/db.rs +++ b/packages/app-lib/src/state/db.rs @@ -1,18 +1,32 @@ +use crate::ErrorKind; use crate::state::DirectoryInfo; use sqlx::sqlite::{ SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, }; use sqlx::{Pool, Sqlite}; -use std::env; -use tokio::time::Instant; +use std::collections::HashMap; use std::str::FromStr; use std::time::Duration; +use tokio::time::Instant; pub(crate) async fn connect() -> crate::Result> { + let pool = connect_without_migrate().await?; + + sqlx::migrate!().run(&pool).await?; + + if let Err(err) = stale_data_cleanup(&pool).await { + tracing::warn!( + "Failed to clean up stale data from state database: {err}" + ); + } + + Ok(pool) +} + +// [AR] Feature. Implement SQLite3 connection without SQLx migrations. +async fn connect_without_migrate() -> crate::Result> { let settings_dir = DirectoryInfo::get_initial_settings_dir().ok_or( - crate::ErrorKind::FSError( - "Could not find valid config dir".to_string(), - ), + ErrorKind::FSError("Could not find valid config dir".to_string()), )?; if !settings_dir.exists() { @@ -20,7 +34,6 @@ pub(crate) async fn connect() -> crate::Result> { } let db_path = settings_dir.join("app.db"); - let db_exists = db_path.exists(); let uri = format!("sqlite:{}", db_path.display()); let conn_options = SqliteConnectOptions::from_str(&uri)? @@ -34,22 +47,6 @@ pub(crate) async fn connect() -> crate::Result> { .connect_with(conn_options) .await?; - if db_exists { - fix_modrinth_issued_migrations(&pool).await?; - } - - sqlx::migrate!().run(&pool).await?; - - if !db_exists { - fix_modrinth_issued_migrations(&pool).await?; - } - - if let Err(err) = stale_data_cleanup(&pool).await { - tracing::warn!( - "Failed to clean up stale data from state database: {err}" - ); - } - Ok(pool) } @@ -75,72 +72,103 @@ async fn stale_data_cleanup(pool: &Pool) -> crate::Result<()> { Ok(()) } /* -// Patch by AstralRinth - 08.07.2025 -Problem files: +// [AR] Patch fix +Problem files, view detailed information in .gitattributes: /packages/app-lib/migrations/20240711194701_init.sql !eol +CRLF -> 4c47e326f16f2b1efca548076ce638d4c90dd610172fe48c47d6de9bc46ef1c5abeadfdea05041ddd72c3819fa10c040 +LF -> e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21 /packages/app-lib/migrations/20240813205023_drop-active-unique.sql !eol +CRLF -> C8FD2EFE72E66E394732599EA8D93CE1ED337F098697B3ADAD40DD37CC6367893E199A8D7113B44A3D0FFB537692F91D +LF -> 5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206 /packages/app-lib/migrations/20240930001852_disable-personalized-ads.sql !eol +CRLF -> C0DE804F171B5530010EDAE087A6E75645C0E90177E28365F935C9FDD9A5C68E24850B8C1498E386A379D525D520BC57 +LF -> c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57 /packages/app-lib/migrations/20241222013857_feature-flags.sql !eol +CRLF -> 6B6F097E5BB45A397C96C3F1DC9C2A18433564E81DB264FE08A4775198CCEAC03C9E63C3605994ECB19C281C37D8F6AE +LF -> c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704 */ -async fn fix_modrinth_issued_migrations( - pool: &Pool, -) -> crate::Result<()> { - let arch = env::consts::ARCH; - let os = env::consts::OS; +pub(crate) async fn apply_migration_fix(eol: &str) -> crate::Result { + let started = Instant::now(); - tracing::info!("Running on OS: {}, ARCH: {}", os, arch); + // Create connection to the database without migrations + let pool = connect_without_migrate().await?; + tracing::info!( + "⚙️ Patching Modrinth corrupted migration checksums using EOL standard: {eol}" + ); - if os == "windows" && arch == "x86_64" { - tracing::warn!("🛑 Skipping migration checksum fix on Windows x86_64 (runtime-detected)"); - return Ok(()); + // validate EOL input + if eol != "lf" && eol != "crlf" { + return Ok(false); } - let started = Instant::now(); - tracing::info!("Fixing modrinth issued migrations"); - sqlx::query( + // [eol][version] -> checksum + let checksums: HashMap<(&str, &str), &str> = HashMap::from([ + ( + ("lf", "20240711194701"), + "e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21", + ), + ( + ("crlf", "20240711194701"), + "4c47e326f16f2b1efca548076ce638d4c90dd610172fe48c47d6de9bc46ef1c5abeadfdea05041ddd72c3819fa10c040", + ), + ( + ("lf", "20240813205023"), + "5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206", + ), + ( + ("crlf", "20240813205023"), + "C8FD2EFE72E66E394732599EA8D93CE1ED337F098697B3ADAD40DD37CC6367893E199A8D7113B44A3D0FFB537692F91D", + ), + ( + ("lf", "20240930001852"), + "c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57", + ), + ( + ("crlf", "20240930001852"), + "C0DE804F171B5530010EDAE087A6E75645C0E90177E28365F935C9FDD9A5C68E24850B8C1498E386A379D525D520BC57", + ), + ( + ("lf", "20241222013857"), + "c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704", + ), + ( + ("crlf", "20241222013857"), + "6B6F097E5BB45A397C96C3F1DC9C2A18433564E81DB264FE08A4775198CCEAC03C9E63C3605994ECB19C281C37D8F6AE", + ), + ]); + + let mut changed = false; + + for ((eol_key, version), checksum) in checksums.iter() { + if *eol_key != eol { + continue; + } + + tracing::info!( + "⏳ Patching checksum for migration {version} ({})", + eol.to_uppercase() + ); + + let result = sqlx::query(&format!( r#" UPDATE "_sqlx_migrations" - SET checksum = X'e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21' - WHERE version = '20240711194701'; - "#, - ) - .execute(pool) + SET checksum = X'{checksum}' + WHERE version = '{version}'; + "# + )) + .execute(&pool) .await?; - tracing::info!("⚙️ Fixed checksum for first migration"); - sqlx::query( - r#" - UPDATE "_sqlx_migrations" - SET checksum = X'5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206' - WHERE version = '20240813205023'; - "#, - ) - .execute(pool) - .await?; - tracing::info!("⚙️ Fixed checksum for second migration"); - sqlx::query( - r#" - UPDATE "_sqlx_migrations" - SET checksum = X'c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57' - WHERE version = '20240930001852'; - "#, - ) - .execute(pool) - .await?; - tracing::info!("⚙️ Fixed checksum for third migration"); - sqlx::query( - r#" - UPDATE "_sqlx_migrations" - SET checksum = X'c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704' - WHERE version = '20241222013857'; - "#, - ) - .execute(pool) - .await?; - tracing::info!("⚙️ Fixed checksum for fourth migration"); - let elapsed = started.elapsed(); + + if result.rows_affected() > 0 { + changed = true; + } + } + tracing::info!( - "✅ Fixed all known Modrinth checksums for migrations in {:.2?}", - elapsed + "✅ Checksum patching completed in {:.2?} (changes: {})", + started.elapsed(), + changed ); - Ok(()) + + Ok(changed) } diff --git a/packages/app-lib/src/state/dirs.rs b/packages/app-lib/src/state/dirs.rs index 4dce9681..cabd33ad 100644 --- a/packages/app-lib/src/state/dirs.rs +++ b/packages/app-lib/src/state/dirs.rs @@ -22,6 +22,7 @@ pub struct DirectoryInfo { impl DirectoryInfo { // Get the settings directory // init() is not needed for this function + // [AR] Patch fix. From PR. pub fn get_initial_settings_dir() -> Option { Self::env_path("THESEUS_CONFIG_DIR").or_else(|| { if std::env::current_dir().ok()?.join("portable.txt").exists() { diff --git a/packages/app-lib/src/state/discord.rs b/packages/app-lib/src/state/discord.rs index 39a8eaf0..b6135b66 100644 --- a/packages/app-lib/src/state/discord.rs +++ b/packages/app-lib/src/state/discord.rs @@ -1,16 +1,17 @@ +// [AR] Feature use std::{ sync::{atomic::AtomicBool, Arc}, - time::{SystemTime, UNIX_EPOCH}, // AstralRinth + time::{SystemTime, UNIX_EPOCH}, }; use discord_rich_presence::{ - activity::{Activity, Assets, Timestamps}, // AstralRinth + activity::{Activity, Assets, Timestamps}, // [AR] Feature DiscordIpc, DiscordIpcClient, }; -use rand::seq::SliceRandom; // AstralRinth +use rand::seq::SliceRandom; // [AR] Feature use tokio::sync::RwLock; -use crate::util::utils; // AstralRinth +use crate::util::utils; // [AR] Feature use crate::State; pub struct DiscordGuard { diff --git a/packages/app-lib/src/state/minecraft_auth.rs b/packages/app-lib/src/state/minecraft_auth.rs index fdd21b76..834120b1 100644 --- a/packages/app-lib/src/state/minecraft_auth.rs +++ b/packages/app-lib/src/state/minecraft_auth.rs @@ -213,7 +213,7 @@ pub async fn login_finish( Ok(credentials) } -// Patched by AstralRinth +// [AR] Feature #[tracing::instrument] pub async fn offline_auth( name: &str, @@ -790,7 +790,7 @@ const MICROSOFT_CLIENT_ID: &str = "00000000402b5328"; const REDIRECT_URL: &str = "https://login.live.com/oauth20_desktop.srf"; const REQUESTED_SCOPES: &str = "service::user.auth.xboxlive.com::MBI_SSL"; -/* AstralRinth +/* [AR] Fix * Weird visibility issue that didn't reproduce before * Had to make DeviceToken and RequestWithDate pub(crate) to fix compilation error */ diff --git a/packages/app-lib/src/util/mod.rs b/packages/app-lib/src/util/mod.rs index 1296e43a..8a946021 100644 --- a/packages/app-lib/src/util/mod.rs +++ b/packages/app-lib/src/util/mod.rs @@ -3,5 +3,5 @@ pub mod fetch; pub mod io; pub mod jre; pub mod platform; -pub mod utils; // AstralRinth +pub mod utils; // [AR] Feature pub mod server_ping; diff --git a/packages/app-lib/src/util/utils.rs b/packages/app-lib/src/util/utils.rs index 0f8fd7af..adebc3a6 100644 --- a/packages/app-lib/src/util/utils.rs +++ b/packages/app-lib/src/util/utils.rs @@ -1,16 +1,20 @@ +/// +/// [AR] Feature +/// +use crate::Result; +use crate::api::update; +use crate::state::db; use serde::{Deserialize, Serialize}; +use std::process; use tokio::io; -/* -AstralRinth Utils -*/ const PACKAGE_JSON_CONTENT: &str = // include_str!("../../../../apps/app-frontend/package.json"); include_str!("../../../../apps/app/tauri.conf.json"); #[derive(Serialize, Deserialize)] pub struct Launcher { - pub version: String + pub version: String, } pub fn read_package_json() -> io::Result { @@ -19,3 +23,41 @@ pub fn read_package_json() -> io::Result { Ok(launcher) } + +pub async fn apply_migration_fix(eol: &str) -> Result { + tracing::info!("[AR] • Attempting to apply migration fix"); + let patched = db::apply_migration_fix(eol).await?; + if patched { + tracing::info!("[AR] • Successfully applied migration fix"); + } else { + tracing::error!("[AR] • Failed to apply migration fix"); + } + Ok(patched) +} + +pub async fn init_download( + download_url: &str, + local_filename: &str, + os_type: &str, + auto_update_supported: bool, +) -> Result<()> { + println!("[AR] • Initialize downloading from • {:?}", download_url); + println!("[AR] • Save local file name • {:?}", local_filename); + if let Err(e) = update::get_resource( + download_url, + local_filename, + os_type, + auto_update_supported, + ) + .await + { + eprintln!( + "[AR] • An error occurred! Failed to download the file: {}", + e + ); + } else { + println!("[AR] • Code finishes without errors."); + process::exit(0) + } + Ok(()) +} diff --git a/packages/assets/index.ts b/packages/assets/index.ts index 237535cc..57a98db4 100644 --- a/packages/assets/index.ts +++ b/packages/assets/index.ts @@ -83,14 +83,14 @@ export const TwitterIcon = _TwitterIcon export const WindowsIcon = _WindowsIcon export const YouTubeIcon = _YouTubeIcon -// AstralRinth Icons +// [AR] Feature. Icons import _PirateIcon from './icons/pirate.svg?component' import _MicrosoftIcon from './icons/microsoft.svg?component' import _PirateShipIcon from './icons/pirate-ship.svg?component' import _AstralRinthLogo from './icons/astralrinth-logo.svg?component' -// AstralRinth Exports +// [AR] Feature. Exports export const PirateIcon = _PirateIcon export const MicrosoftIcon = _MicrosoftIcon diff --git a/packages/assets/styles/neon-button.scss b/packages/assets/styles/neon-button.scss new file mode 100644 index 00000000..91e201f3 --- /dev/null +++ b/packages/assets/styles/neon-button.scss @@ -0,0 +1,39 @@ +// [AR] Feature +.neon-button.neon :deep(:is(button, a, .button-like)), +.neon-button.neon :slotted(:is(button, a, .button-like)), +.neon-button.neon :slotted(*) :is(button, a, .button-like) { + cursor: pointer; + background-color: transparent; + border: 1px solid #3e8cde; + color: #3e8cde; + text-shadow: + 0 0 4px rgba(79, 173, 255, 0.5), + 0 0 8px rgba(14, 98, 204, 0.5), + 0 0 12px rgba(122, 31, 199, 0.5); + transition: + color 0.25s ease, + box-shadow 0.3s ease, + transform 0.15s ease; + box-shadow: 0 0 4px rgba(79, 173, 255, 0.5); +} + +.bordered { + border-radius: 12px; +} + +/* Hover */ +.neon-button.neon + :deep(:is(button, a, .button-like):hover):not([disabled]):not(.disabled), +.neon-button.neon + :slotted(:is(button, a, .button-like):hover):not([disabled]):not(.disabled), +.neon-button.neon + :slotted(*) :is(button, a, .button-like):hover:not([disabled]):not(.disabled) { + color: #10fae5; + transform: scale(1.02); + box-shadow: + 0 0 4px rgba(16, 250, 229, 0.3), + 0 0 8px rgba(16, 250, 229, 0.2); + text-shadow: + 0 0 2px rgba(16, 250, 229, 0.4), + 0 0 4px rgba(16, 250, 229, 0.25); +} \ No newline at end of file diff --git a/packages/assets/styles/neon-icon.scss b/packages/assets/styles/neon-icon.scss new file mode 100644 index 00000000..a0e2fb62 --- /dev/null +++ b/packages/assets/styles/neon-icon.scss @@ -0,0 +1,37 @@ +// [AR] Feature +.neon-icon { + background-color: transparent; + color: #3e8cde; + text-shadow: + 0 0 4px rgba(79, 173, 255, 0.5), + 0 0 8px rgba(14, 98, 204, 0.5), + 0 0 12px rgba(122, 31, 199, 0.5); + transition: transform 0.25s ease, color 0.25s ease, text-shadow 0.25s ease; + cursor: pointer; + display: inline-block; +} + +/* Hover */ +.neon-icon:hover { + color: #10fae5; + transform: scale(1.05); + text-shadow: + 0 0 2px rgba(16, 250, 229, 0.4), + 0 0 4px rgba(16, 250, 229, 0.25); +} + +.neon-icon.pulse { + position: relative; + animation: neon-pulse 1s ease-in-out infinite; + filter: drop-shadow(0 0 6px #10fae5); + box-shadow: none; +} + +@keyframes neon-pulse { + 0%, 100% { + filter: drop-shadow(0 0 4px #10fae5); + } + 50% { + filter: drop-shadow(0 0 12px #10fae5); + } +} \ No newline at end of file diff --git a/packages/assets/styles/neon-text.scss b/packages/assets/styles/neon-text.scss new file mode 100644 index 00000000..1899f2b6 --- /dev/null +++ b/packages/assets/styles/neon-text.scss @@ -0,0 +1,28 @@ +// [AR] Feature +.neon-text { + background-color: transparent; + color: #3e8cde; + text-shadow: + 0 0 4px rgba(79, 173, 255, 0.5), + 0 0 8px rgba(14, 98, 204, 0.5), + 0 0 12px rgba(122, 31, 199, 0.5); + transition: + color 0.25s ease, + box-shadow 0.3s ease, + transform 0.15s ease; + + white-space: normal; + word-wrap: break-word; + overflow-wrap: break-word; + max-width: 100%; + display: inline-block; + padding: 4px 8px; +} + +/* Hover */ +.neon-text:hover:not([disabled]):not(.disabled) { + color: #10fae5; + text-shadow: + 0 0 2px rgba(16, 250, 229, 0.4), + 0 0 4px rgba(16, 250, 229, 0.25); +}