4 Commits

Author SHA1 Message Date
231e95792e update README
Some checks failed
AstralRinth App build / Build (x86_64-pc-windows-msvc, windows-latest) (push) Has been cancelled
AstralRinth App build / Build (x86_64-unknown-linux-gnu, ubuntu-latest) (push) Has been cancelled
2025-10-19 21:31:10 +03:00
905eae8403 cleanup patches 2025-10-19 21:08:52 +03:00
868fda1703 fix typo: remove shit ads after upstream 2025-10-19 21:08:02 +03:00
4b86c4ee8a refactor: minor changes in update.js 2025-10-19 21:06:03 +03:00
11 changed files with 110 additions and 7492 deletions

View File

@@ -82,7 +82,9 @@ Avoid using builds with these prefixes — they may be unstable or experimental:
- Built-in update alerts for new versions posted on Git Astralium.
- Automatic download and installation capabilities.
- Database migration fixes, when error occurred (Interactive Mode) (Modrinth issue)
- ElyBy skin system integration (AuthLib / Java)
- Ely.by full integration
- The official account skin system is managed by ely.by
- Offline accounts must install AuthLib through the instance settings
---
@@ -119,4 +121,5 @@ To begin using AstralRinth:
If you'd like to support development, you can donate via the following crypto wallets:
- TON (Telegram): UQCG0a2fm5YzKzIm6E8M3_CqBQ5Q1RT95MU60c9UPjKn_NEN
- Toncoin (TON): UQA5pGOJhIz9UAVEOh5t2ur1QVbNr_FC1eq9bOb3GwTgaiqk
- USDT (TON): UQA5pGOJhIz9UAVEOh5t2ur1QVbNr_FC1eq9bOb3GwTgaiqk

View File

@@ -865,16 +865,6 @@ provideAppUpdateDownloadProgress(appUpdateDownload) // [AR Note] If delete this
</div>
</div>
</div>
<template v-if="showAd">
<a
href="https://modrinth.plus?app"
class="absolute bottom-[250px] w-full flex justify-center items-center gap-1 px-4 py-3 text-purple font-medium hover:underline z-10"
target="_blank"
>
<ArrowBigUpDashIcon class="text-2xl" /> Upgrade to Modrinth+
</a>
<PromotionWrapper />
</template>
</div>
</div>
<URLConfirmModal ref="urlModal" />

View File

@@ -211,28 +211,34 @@ const messages = defineMessages({
<ModalWrapper ref="updateModalView" :has-to-type="false" header="Request to update the AstralRinth launcher">
<div class="space-y-4">
<div class="space-y-2">
<p>The new version of the AstralRinth launcher is available.</p>
<strong>The new version of the AstralRinth launcher is available!</strong>
<p>Your version is outdated. We recommend that you update to the latest version.</p>
<p><strong> Warning </strong></p>
<br/>
<br/>
<p><strong> Please, read this notice before initialize update process</strong></p>
<p>
Before updating, make sure that you have saved all running instances and made a backup copy of the instances
that are valuable to you. Remember that the authors of the product are not responsible for the breakdown of
your files, so you should always make copies of them and keep them in a safe place.
Before updating, make sure that you have saved and closed all running instances and made a backup copy of the launcher data such as
<code>%appdata%\Roaming\AstralRinthApp</code> on Windows or <code>~/Library/Application Support/AstralRinthApp</code> on macOS.
Remember that the authors of the product are not responsible for the breakdown of
your files, so you should always make back up copies of them and keep them in a safe place.
</p>
</div>
<div class="text-sm text-secondary space-y-1">
<a class="neon-text" href="https://me.astralium.su/get/ar" target="_blank"
rel="noopener noreferrer"><strong>Source:</strong> Git Astralium</a>
<p>
<strong>Version on remote server:</strong>
<span id="releaseData" class="neon-text"></span>
</p>
<p>
<strong>Version on local device:</strong>
<strong> Latest release tag:</strong>
<span id="releaseTag" class="neon-text"></span>
<br/>
<strong> Latest release title:</strong>
<span id="releaseTitle" class="neon-text"></span>
<br/>
<strong>💾 Installed & Running version:</strong>
<span class="neon-text">v{{ version }}</span>
</p>
</div>
<a class="neon-text" href="https://me.astralium.su/get/ar" target="_blank"
rel="noopener noreferrer">
Checkout our git repository
</a>
<div class="absolute bottom-4 right-4 flex items-center gap-4 neon-button neon">
<Button class="bordered" @click="updateModalView.hide()">Cancel</Button>
<Button class="bordered" @click="initDownload()">Download file</Button>
@@ -271,4 +277,11 @@ const messages = defineMessages({
@import '../../../../../../packages/assets/styles/neon-icon.scss';
@import '../../../../../../packages/assets/styles/neon-button.scss';
@import '../../../../../../packages/assets/styles/neon-text.scss';
code {
background: linear-gradient(90deg, #005eff, #00cfff);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
}
</style>

View File

@@ -1,96 +1,97 @@
import { ref } from 'vue'
import { getVersion } from '@tauri-apps/api/app'
import { initUpdateLauncher, getOS } from '@/helpers/utils.js'
import { ref } from 'vue'
import { getOS, initUpdateLauncher } from '@/helpers/utils.js'
export const allowState = ref(false)
export const installState = ref(false)
export const updateState = ref(false)
const currentOS = ref('')
const releaseLink = `https://git.astralium.su/api/v1/repos/didirus/AstralRinth/releases/latest`
const failedFetch = [`Failed to fetch remote releases:`, `Failed to fetch remote commits:`]
const currentOS = await getOS()
const api = `https://git.astralium.su/api/v1/repos/didirus/AstralRinth/releases/latest`
const osList = ['macos', 'windows', 'linux']
const macExtensionList = ['.dmg', '.pkg']
const windowsExtensionList = ['.exe', '.msi']
const systems = ['macos', 'windows', 'linux']
const macosExtensions = ['.dmg', '.pkg', '.app']
const windowsExtensions = ['.exe', '.msi']
const blacklistPrefixes = [
`dev`,
`nightly`,
`dirty`,
`dirty-dev`,
`dirty-nightly`,
`dirty_dev`,
`dirty_nightly`,
const blacklistBeginPrefixes = [
`dev`,
`nightly`,
`dirty`,
`dirty-dev`,
`dirty-nightly`,
`dirty_dev`,
`dirty_nightly`,
] // This is blacklisted builds for download. For example, file.startsWith('dev') is not allowed.
export async function getRemote(isDownloadState) {
var releaseData = null;
var result = false;
try {
const response = await fetch(releaseLink);
if (!response.ok) {
throw new Error(response.status);
}
const remoteData = await response.json();
currentOS.value = await getOS();
const remoteLatestReleaseTag = remoteData.tag_name;
releaseData = document.getElementById('releaseData');
const remoteVersion = releaseData ? (releaseData.textContent = remoteLatestReleaseTag) : remoteLatestReleaseTag;
if (osList.includes(currentOS.value.toLowerCase())) {
const localVersion = await getVersion();
const isUpdateAvailable = !remoteVersion.includes(localVersion);
updateState.value = isUpdateAvailable;
allowState.value = isUpdateAvailable;
} else {
updateState.value = false;
allowState.value = false;
}
if (isDownloadState) {
installState.value = true;
const builds = remoteData.assets;
const fileName = getInstaller(getExtension(), builds);
result = fileName ? await initUpdateLauncher(fileName[1], fileName[0], currentOS.value, true) : false;
installState.value = false;
}
console.log('Update available state is', updateState.value);
console.log('Remote version is', remoteVersion);
console.log('Local version is', await getVersion());
console.log('Operating System is', currentOS.value);
return result;
} catch (error) {
console.error(failedFetch[0], error);
if (!releaseData) {
const errorData = document.getElementById('releaseData');
if (errorData) {
errorData.textContent = `${error.message}`;
}
updateState.value = false;
allowState.value = false;
installState.value = false;
}
}
var releaseTag = null;
var releaseTitle = null;
var result = false;
try {
const response = await fetch(api);
if (!response.ok) {
throw new Error(response.status);
}
const remoteData = await response.json();
releaseTag = document.getElementById('releaseTag');
releaseTitle = document.getElementById('releaseTitle');
if (releaseTag && releaseTitle) {
releaseTag.textContent = remoteData.tag_name;
releaseTitle.textContent = remoteData.name;
}
if (systems.includes(currentOS.toLowerCase())) {
const localVersion = await getVersion();
const isUpdateAvailable = !remoteData.tag_name.includes(localVersion);
updateState.value = isUpdateAvailable;
allowState.value = isUpdateAvailable;
} else {
updateState.value = false;
allowState.value = false;
}
if (isDownloadState) {
try {
installState.value = true;
const builds = remoteData.assets;
const fileName = getInstaller(getExtension(), builds);
result = fileName ? await initUpdateLauncher(fileName[1], fileName[0], currentOS, true) : false;
installState.value = false;
} catch (err) {
installState.value = false;
}
}
console.log('Update available state is', updateState.value);
console.log('Remote version is', remoteData.tag_name);
console.log('Remote title is', remoteData.name);
console.log('Local version is', await getVersion());
console.log('Operating System is', currentOS);
return result;
} catch (error) {
console.error("Failed to fetch remote releases:", error);
if (!releaseTag) {
updateState.value = false;
allowState.value = false;
installState.value = false;
}
}
}
function getInstaller(osExtension, builds) {
console.log(osExtension, builds)
for (const build of builds) {
if (blacklistPrefixes.some(prefix => build.name.startsWith(prefix))) {
continue;
}
if (osExtension.some(ext => build.name.endsWith(ext))) {
console.log(build.name, build.browser_download_url);
return [build.name, build.browser_download_url];
}
}
return null;
console.log(osExtension, builds)
for (const build of builds) {
if (blacklistBeginPrefixes.some(prefix => build.name.startsWith(prefix))) {
continue;
}
if (osExtension.some(ext => build.name.endsWith(ext))) {
console.log(build.name, build.browser_download_url);
return [build.name, build.browser_download_url];
}
}
return null;
}
function getExtension() {
return osList.find(osName => osName === currentOS.value.toLowerCase())?.endsWith('macos')
? macExtensionList
: windowsExtensionList;
return systems.find(osName => osName === currentOS.toLowerCase())?.endsWith('macos')
? macosExtensions
: windowsExtensions;
}

View File

@@ -1,103 +0,0 @@
diff --git a/packages/app-lib/src/state/db.rs b/packages/app-lib/src/state/db.rs
index 14c53d81..607a345f 100644
--- a/packages/app-lib/src/state/db.rs
+++ b/packages/app-lib/src/state/db.rs
@@ -3,6 +3,7 @@ use sqlx::sqlite::{
SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions,
};
use sqlx::{Pool, Sqlite};
+use tokio::time::Instant;
use std::str::FromStr;
use std::time::Duration;
@@ -17,8 +18,10 @@ pub(crate) async fn connect() -> crate::Result<Pool<Sqlite>> {
crate::util::io::create_dir_all(&settings_dir).await?;
}
- let uri = format!("sqlite:{}", settings_dir.join("app.db").display());
+ 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)?
.busy_timeout(Duration::from_secs(30))
.journal_mode(SqliteJournalMode::Wal)
@@ -30,10 +33,16 @@ pub(crate) async fn connect() -> crate::Result<Pool<Sqlite>> {
.connect_with(conn_options)
.await?;
- fix_migration_20240711194701(&pool).await?; // Patch by AstralRinth - 08.07.2025
+ 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}"
@@ -66,8 +75,17 @@ async fn stale_data_cleanup(pool: &Pool<Sqlite>) -> crate::Result<()> {
}
/*
// Patch by AstralRinth - 08.07.2025
+Problem files:
+/packages/app-lib/migrations/20240711194701_init.sql !eol
+/packages/app-lib/migrations/20240813205023_drop-active-unique.sql !eol
+/packages/app-lib/migrations/20240930001852_disable-personalized-ads.sql !eol
+/packages/app-lib/migrations/20241222013857_feature-flags.sql !eol
*/
-async fn fix_migration_20240711194701(pool: &Pool<Sqlite>) -> crate::Result<()> {
+async fn fix_modrinth_issued_migrations(
+ pool: &Pool<Sqlite>,
+) -> crate::Result<()> {
+ let started = Instant::now();
+ tracing::info!("Fixing modrinth issued migrations");
sqlx::query(
r#"
UPDATE "_sqlx_migrations"
@@ -77,5 +95,41 @@ async fn fix_migration_20240711194701(pool: &Pool<Sqlite>) -> crate::Result<()>
)
.execute(pool)
.await?;
+ tracing::info!("⚙️ Fixed first migration");
+ sqlx::query(
+ r#"
+ UPDATE "_sqlx_migrations"
+ SET checksum = X'5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206'
+ WHERE version = '20240813205023';
+ "#,
+ )
+ .execute(pool)
+ .await?;
+ tracing::info!("⚙️ Fixed second migration");
+ sqlx::query(
+ r#"
+ UPDATE "_sqlx_migrations"
+ SET checksum = X'c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57'
+ WHERE version = '20240930001852';
+ "#,
+ )
+ .execute(pool)
+ .await?;
+ tracing::info!("⚙️ Fixed third migration");
+ sqlx::query(
+ r#"
+ UPDATE "_sqlx_migrations"
+ SET checksum = X'c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704'
+ WHERE version = '20241222013857';
+ "#,
+ )
+ .execute(pool)
+ .await?;
+ tracing::info!("⚙️ Fixed fourth migration");
+ let elapsed = started.elapsed();
+ tracing::info!(
+ "✅ Fixed all known modrinth-issued migrations in {:.2?}",
+ elapsed
+ );
Ok(())
}

View File

@@ -1,675 +0,0 @@
diff --git a/apps/app-frontend/src/components/ui/AccountsCard.vue b/apps/app-frontend/src/components/ui/AccountsCard.vue
index 7b03e1f39..69ee0e01e 100644
--- a/apps/app-frontend/src/components/ui/AccountsCard.vue
+++ b/apps/app-frontend/src/components/ui/AccountsCard.vue
@@ -50,9 +50,8 @@
color="primary"
@click="login()"
>
- <LogInIcon v-if="!loginDisabled" />
+ <MicrosoftIcon v-if="!loginDisabled"/>
<SpinnerIcon v-else class="animate-spin" />
- <MicrosoftIcon/>
</Button>
<Button v-tooltip="'Add offline'" icon-only @click="tryOfflineLogin()">
<PirateIcon />
diff --git a/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue
index 7810581a3..ff16faadb 100644
--- a/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue
+++ b/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue
@@ -26,6 +26,7 @@ import {
type Version,
} from '@modrinth/utils'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
+import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { get_project, get_version_many } from '@/helpers/cache'
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
import dayjs from 'dayjs'
@@ -35,6 +36,11 @@ import type {
Manifest,
} from '../../../helpers/types'
+import { initAuthlibPatching } from '@/helpers/utils.js'
+const authLibPatchingModal = ref(null)
+const isAuthLibPatchedSuccess = ref(false)
+const isAuthLibPatching = ref(false)
+
const { formatMessage } = useVIntl()
const repairConfirmModal = ref()
@@ -447,9 +453,43 @@ const messages = defineMessages({
defaultMessage: 'reinstall',
},
})
+
+async function handleInitAuthLibPatching(ismojang: boolean) {
+ isAuthLibPatching.value = true
+ let state = false
+ let instance_path = props.instance.loader_version != null ? props.instance.game_version + "-" + props.instance.loader_version : props.instance.game_version
+ try {
+ state = await initAuthlibPatching(instance_path, ismojang)
+ } catch (err) {
+ console.error(err)
+ }
+ isAuthLibPatching.value = false
+ isAuthLibPatchedSuccess.value = state
+ authLibPatchingModal.value.show()
+}
</script>
<template>
+ <ModalWrapper
+ ref="authLibPatchingModal"
+ :header="'AuthLib installation report'"
+ :closable="true"
+ @close="authLibPatchingModal.hide()"
+ >
+ <div class="modal-body">
+ <h2 class="text-lg font-bold text-contrast space-y-2">
+ <p class="flex items-center gap-2 neon-text">
+ <span v-if="isAuthLibPatchedSuccess" class="neon-text">
+ AuthLib installation completed successfully! Now you can log in and play!
+ </span>
+ <span v-else class="neon-text">
+ Failed to install AuthLib. It's possible that no compatible AuthLib version was found for the selected game and/or mod loader version.
+ There may also be a problem with accessing resources behind CloudFlare.
+ </span>
+ </p>
+ </h2>
+ </div>
+ </ModalWrapper>
<ConfirmModalWrapper
ref="repairConfirmModal"
:title="formatMessage(messages.repairConfirmTitle)"
@@ -720,6 +760,24 @@ const messages = defineMessages({
</button>
</ButtonStyled>
</div>
+ <h2 class="m-0 mt-4 text-lg font-extrabold text-contrast block">
+ <div v-if="isAuthLibPatching" class="w-6 h-6 cursor-pointer hover:brightness-75 neon-icon pulse">
+ <SpinnerIcon class="size-4 animate-spin" />
+ </div>
+ Auth system (Skins) <span class="text-sm font-bold px-2 bg-brand-highlight text-brand rounded-full">Beta</span>
+ </h2>
+ <div class="mt-4 flex gap-2">
+ <ButtonStyled class="neon-button neon">
+ <button :disabled="isAuthLibPatching" @click="handleInitAuthLibPatching(true)">
+ Install Microsoft
+ </button>
+ </ButtonStyled>
+ <ButtonStyled class="neon-button neon">
+ <button :disabled="isAuthLibPatching" @click="handleInitAuthLibPatching(false) ">
+ Install Ely.By
+ </button>
+ </ButtonStyled>
+ </div>
</template>
<template v-else>
<template v-if="instance.linked_data && instance.linked_data.locked">
@@ -787,3 +845,9 @@ const messages = defineMessages({
</template>
</div>
</template>
+
+<style lang="scss" scoped>
+@import '../../../../../../packages/assets/styles/neon-button.scss';
+@import '../../../../../../packages/assets/styles/neon-text.scss';
+@import '../../../../../../packages/assets/styles/neon-icon.scss';
+</style>
\ No newline at end of file
diff --git a/apps/app-frontend/src/helpers/update.js b/apps/app-frontend/src/helpers/update.js
index cea6c6bd4..f7033b7ac 100644
--- a/apps/app-frontend/src/helpers/update.js
+++ b/apps/app-frontend/src/helpers/update.js
@@ -1,6 +1,6 @@
import { ref } from 'vue'
import { getVersion } from '@tauri-apps/api/app'
-import { getArtifact, getOS } from '@/helpers/utils.js'
+import { initUpdateLauncher, getOS } from '@/helpers/utils.js'
export const allowState = ref(false)
export const installState = ref(false)
@@ -52,7 +52,7 @@ export async function getRemote(isDownloadState) {
installState.value = true;
const builds = remoteData.assets;
const fileName = getInstaller(getExtension(), builds);
- result = fileName ? await getArtifact(fileName[1], fileName[0], currentOS.value, true) : false;
+ result = fileName ? await initUpdateLauncher(fileName[1], fileName[0], currentOS.value, true) : false;
installState.value = false;
}
diff --git a/apps/app-frontend/src/helpers/utils.js b/apps/app-frontend/src/helpers/utils.js
index ac950e175..99cbde38f 100644
--- a/apps/app-frontend/src/helpers/utils.js
+++ b/apps/app-frontend/src/helpers/utils.js
@@ -11,9 +11,9 @@ export async function getOS() {
}
// [AR] Feature
-export async function getArtifact(downloadurl, filename, ostype, autoupdatesupported) {
+export async function initUpdateLauncher(downloadurl, filename, ostype, autoupdatesupported) {
console.log('Downloading build', downloadurl, filename, ostype, autoupdatesupported)
- return await invoke('plugin:utils|get_artifact', { downloadurl, filename, ostype, autoupdatesupported })
+ return await invoke('plugin:utils|init_update_launcher', { downloadurl, filename, ostype, autoupdatesupported })
}
// [AR] Patch fix
@@ -21,6 +21,11 @@ export async function applyMigrationFix(eol) {
return await invoke('plugin:utils|apply_migration_fix', { eol })
}
+// [AR] Feature
+export async function initAuthlibPatching(minecraftversion, ismojang) {
+ return await invoke('plugin:utils|init_authlib_patching', { minecraftversion, ismojang })
+}
+
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 4942497aa..5a35c88df 100644
--- a/apps/app/build.rs
+++ b/apps/app/build.rs
@@ -218,8 +218,9 @@ fn main() {
"utils",
InlinedPlugin::new()
.commands(&[
+ "init_authlib_patching",
"apply_migration_fix",
- "get_artifact",
+ "init_update_launcher",
"get_os",
"should_disable_mouseover",
"highlight_in_folder",
diff --git a/apps/app/src/api/utils.rs b/apps/app/src/api/utils.rs
index 452079157..951affa63 100644
--- a/apps/app/src/api/utils.rs
+++ b/apps/app/src/api/utils.rs
@@ -10,14 +10,15 @@ use crate::api::{Result, TheseusSerializableError};
use dashmap::DashMap;
use std::path::{Path, PathBuf};
use theseus::prelude::canonicalize;
-use url::Url;
use theseus::util::utils;
+use url::Url;
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
tauri::plugin::Builder::new("utils")
.invoke_handler(tauri::generate_handler![
+ init_authlib_patching,
apply_migration_fix,
- get_artifact,
+ init_update_launcher,
get_os,
should_disable_mouseover,
highlight_in_folder,
@@ -29,6 +30,17 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
.build()
}
+/// [AR] Feature
+#[tauri::command]
+pub async fn init_authlib_patching(
+ minecraftversion: &str,
+ ismojang: bool,
+) -> Result<bool> {
+ let result =
+ utils::init_authlib_patching(minecraftversion, ismojang).await?;
+ Ok(result)
+}
+
/// [AR] Patch fix
#[tauri::command]
pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
@@ -38,8 +50,19 @@ pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
/// [AR] Feature
#[tauri::command]
-pub async fn get_artifact(downloadurl: &str, filename: &str, ostype: &str, autoupdatesupported: bool) -> Result<()> {
- let _ = utils::init_download(downloadurl, filename, ostype, autoupdatesupported).await;
+pub async fn init_update_launcher(
+ downloadurl: &str,
+ filename: &str,
+ ostype: &str,
+ autoupdatesupported: bool,
+) -> Result<()> {
+ let _ = utils::init_update_launcher(
+ downloadurl,
+ filename,
+ ostype,
+ autoupdatesupported,
+ )
+ .await;
Ok(())
}
diff --git a/apps/app/tauri.conf.json b/apps/app/tauri.conf.json
index 4ebb2aa5f..cfc271565 100644
--- a/apps/app/tauri.conf.json
+++ b/apps/app/tauri.conf.json
@@ -41,7 +41,7 @@
]
},
"productName": "AstralRinth App",
- "version": "0.10.303",
+ "version": "0.10.304",
"mainBinaryName": "AstralRinth App",
"identifier": "AstralRinthApp",
"plugins": {
diff --git a/packages/app-lib/src/error.rs b/packages/app-lib/src/error.rs
index 75c144f55..360984efd 100644
--- a/packages/app-lib/src/error.rs
+++ b/packages/app-lib/src/error.rs
@@ -151,6 +151,41 @@ pub enum ErrorKind {
"A skin texture must have a dimension of either 64x64 or 64x32 pixels"
)]
InvalidSkinTexture,
+
+ #[error(
+ "[AR] Target minecraft {minecraft_version} version doesn't exist."
+ )]
+ InvalidMinecraftVersion {
+ minecraft_version: String,
+ },
+
+ #[error(
+ "[AR] Target metadata not found for minecraft version {minecraft_version}."
+ )]
+ MinecraftMetadataNotFound {
+ minecraft_version: String,
+ },
+
+ #[error(
+ "[AR] Network error: {error}"
+ )]
+ NetworkErrorOccurred {
+ error: String,
+ },
+
+ #[error(
+ "[AR] IO error: {error}"
+ )]
+ IOErrorOccurred {
+ error: String,
+ },
+
+ #[error(
+ "[AR] Parse error: {reason}"
+ )]
+ ParseError {
+ reason: String,
+ },
}
#[derive(Debug)]
diff --git a/packages/app-lib/src/launcher/mod.rs b/packages/app-lib/src/launcher/mod.rs
index f1b5deb85..a7d0708e3 100644
--- a/packages/app-lib/src/launcher/mod.rs
+++ b/packages/app-lib/src/launcher/mod.rs
@@ -633,6 +633,7 @@ pub async fn launch_minecraft(
command.arg("--add-opens=jdk.internal/jdk.internal.misc=ALL-UNNAMED");
}
+ // FIXME: Fix ElyBy integration with this patch.
// [AR] Patch
if credentials.access_token == "null" && credentials.refresh_token == "null" {
if version_jar == "1.16.4" || version_jar == "1.16.5" {
diff --git a/packages/app-lib/src/util/utils.rs b/packages/app-lib/src/util/utils.rs
index adebc3a67..e4d9a2ab1 100644
--- a/packages/app-lib/src/util/utils.rs
+++ b/packages/app-lib/src/util/utils.rs
@@ -1,12 +1,12 @@
-///
-/// [AR] Feature
-///
-use crate::Result;
use crate::api::update;
use crate::state::db;
+///
+/// [AR] Feature Utils
+///
+use crate::{Result, State};
use serde::{Deserialize, Serialize};
use std::process;
-use tokio::io;
+use tokio::{fs, io};
const PACKAGE_JSON_CONTENT: &str =
// include_str!("../../../../apps/app-frontend/package.json");
@@ -17,13 +17,311 @@ pub struct Launcher {
pub version: String,
}
+#[derive(Debug, Deserialize)]
+struct Artifact {
+ path: Option<String>,
+ sha1: Option<String>,
+ url: Option<String>,
+}
+
+#[derive(Debug, Deserialize)]
+struct Downloads {
+ artifact: Option<Artifact>,
+}
+
+#[derive(Debug, Deserialize)]
+struct Library {
+ name: String,
+ downloads: Option<Downloads>,
+}
+
+#[derive(Debug, Deserialize)]
+struct VersionJson {
+ libraries: Vec<Library>,
+}
+
+/// Deserialize the content of package.json into a Launcher struct
pub fn read_package_json() -> io::Result<Launcher> {
- // Deserialize the content of package.json into a Launcher struct
let launcher: Launcher = serde_json::from_str(PACKAGE_JSON_CONTENT)?;
-
Ok(launcher)
}
+/// ### AR • Universal Write (IO) Function
+/// Saves the downloaded bytes to the `libraries` directory using the given relative path.
+async fn write_file_to_libraries(
+ relative_path: &str,
+ bytes: &bytes::Bytes,
+) -> Result<()> {
+ let state = State::get().await?;
+ let output_path = state.directories.libraries_dir().join(relative_path);
+
+ fs::write(&output_path, bytes).await.map_err(|e| {
+ tracing::error!("[AR] • Failed to save file: {:?}", e);
+ crate::ErrorKind::IOErrorOccurred {
+ error: format!("Failed to save file: {e}"),
+ }
+ .as_error()
+ })
+}
+
+/// ### AR • AuthLib (Ely By)
+/// Initializes the AuthLib patching process.
+///
+/// Returns `true` if the authlib patched successfully.
+pub async fn init_authlib_patching(
+ minecraft_version: &str,
+ is_mojang: bool,
+) -> Result<bool> {
+ let minecraft_library_metadata = get_minecraft_library_metadata(minecraft_version).await?;
+ // Parses the AuthLib version from string
+ // Example output: "com.mojang:authlib:6.0.58" -> "6.0.58"
+ let authlib_version = minecraft_library_metadata.name.split(':').nth(2).unwrap_or("unknown");
+
+ tracing::info!(
+ "[AR] • Attempting to download AuthLib {}.",
+ authlib_version
+ );
+
+ download_authlib(
+ &minecraft_library_metadata,
+ authlib_version,
+ minecraft_version,
+ is_mojang,
+ )
+ .await
+}
+
+/// ### AR • AuthLib (Ely By)
+/// Downloads the AuthLib file from Mojang libraries or Git Astralium services.
+async fn download_authlib(
+ minecraft_library_metadata: &Library,
+ authlib_version: &str,
+ minecraft_version: &str,
+ is_mojang: bool,
+) -> Result<bool> {
+ let state = State::get().await?;
+ let (url, path) = extract_download_info(minecraft_library_metadata, minecraft_version)?;
+ let mut download_url = url.to_string();
+ let full_path = state.directories.libraries_dir().join(path);
+
+ if !is_mojang {
+ tracing::info!(
+ "[AR] • Attempting to download AuthLib from Git Astralium"
+ );
+ download_url = extract_ely_authlib_url(authlib_version).await?;
+ }
+ tracing::info!("[AR] • Downloading AuthLib from URL: {}", download_url);
+ let bytes = fetch_bytes_from_url(&download_url).await?;
+ tracing::info!("[AR] • Will save to path: {}", full_path.to_str().unwrap());
+ write_file_to_libraries(full_path.to_str().unwrap(), &bytes).await?;
+ tracing::info!("[AR] • Successfully saved AuthLib to {:?}", full_path);
+ Ok(true)
+}
+
+/// ### AR • AuthLib (Ely By)
+/// Parses the ElyIntegration release JSON and returns the download URL for the given AuthLib version.
+async fn extract_ely_authlib_url(authlib_version: &str) -> Result<String> {
+ let url = "https://git.astralium.su/api/v1/repos/didirus/ElyIntegration/releases/latest";
+
+ let response = reqwest::get(url).await.map_err(|e| {
+ tracing::error!(
+ "[AR] • Failed to fetch ElyIntegration release JSON: {:?}",
+ e
+ );
+ crate::ErrorKind::NetworkErrorOccurred {
+ error: format!("Failed to fetch ElyIntegration release JSON: {}", e),
+ }
+ .as_error()
+ })?;
+
+ let json: serde_json::Value = response.json().await.map_err(|e| {
+ tracing::error!("[AR] • Failed to parse ElyIntegration JSON: {:?}", e);
+ crate::ErrorKind::ParseError {
+ reason: format!("Failed to parse ElyIntegration JSON: {}", e),
+ }
+ .as_error()
+ })?;
+
+ let assets =
+ json.get("assets")
+ .and_then(|v| v.as_array())
+ .ok_or_else(|| {
+ crate::ErrorKind::ParseError {
+ reason: "Missing 'assets' array".into(),
+ }
+ .as_error()
+ })?;
+
+ let asset = assets
+ .iter()
+ .find(|a| {
+ a.get("name")
+ .and_then(|n| n.as_str())
+ .map(|n| n.contains(authlib_version))
+ .unwrap_or(false)
+ })
+ .ok_or_else(|| {
+ crate::ErrorKind::ParseError {
+ reason: format!(
+ "No matching asset for authlib-{}.jar",
+ authlib_version
+ ),
+ }
+ .as_error()
+ })?;
+
+ let download_url = asset
+ .get("browser_download_url")
+ .and_then(|u| u.as_str())
+ .ok_or_else(|| {
+ crate::ErrorKind::ParseError {
+ reason: "Missing 'browser_download_url'".into(),
+ }
+ .as_error()
+ })?;
+
+ Ok(download_url.to_string())
+}
+
+/// ### AR • AuthLib (Ely By)
+/// Extracts the artifact URL and Path from the library structure.
+///
+/// Returns a tuple of references to the URL and path strings,
+/// or an error if the required metadata is missing.
+fn extract_download_info<'a>(
+ minecraft_library_metadata: &'a Library,
+ minecraft_version: &str,
+) -> Result<(&'a str, &'a str)> {
+ let artifact = minecraft_library_metadata
+ .downloads
+ .as_ref()
+ .and_then(|d| d.artifact.as_ref())
+ .ok_or_else(|| {
+ crate::ErrorKind::MinecraftMetadataNotFound {
+ minecraft_version: minecraft_version.to_string(),
+ }
+ .as_error()
+ })?;
+
+ let url = artifact.url.as_deref().ok_or_else(|| {
+ crate::ErrorKind::MinecraftMetadataNotFound {
+ minecraft_version: minecraft_version.to_string(),
+ }
+ .as_error()
+ })?;
+
+ let path = artifact.path.as_deref().ok_or_else(|| {
+ crate::ErrorKind::MinecraftMetadataNotFound {
+ minecraft_version: minecraft_version.to_string(),
+ }
+ .as_error()
+ })?;
+
+ Ok((url, path))
+}
+
+/// ### AR • AuthLib (Ely By)
+/// Downloads bytes from the provided URL with a 15 second timeout.
+async fn fetch_bytes_from_url(url: &str) -> Result<bytes::Bytes> {
+ // Create client instance with request timeout.
+ let client = reqwest::Client::new();
+ const TIMEOUT_SECONDS: u64 = 15;
+
+ let response = tokio::time::timeout(
+ std::time::Duration::from_secs(TIMEOUT_SECONDS),
+ client.get(url).send(),
+ )
+ .await
+ .map_err(|_| {
+ tracing::error!("[AR] • Download timed out after {} seconds", TIMEOUT_SECONDS);
+ crate::ErrorKind::NetworkErrorOccurred {
+ error: format!("Download timed out after {TIMEOUT_SECONDS} seconds").to_string(),
+ }
+ .as_error()
+ })?
+ .map_err(|e| {
+ tracing::error!("[AR] • Request error: {:?}", e);
+ crate::ErrorKind::NetworkErrorOccurred {
+ error: format!("Request error: {e}"),
+ }
+ .as_error()
+ })?;
+
+ if !response.status().is_success() {
+ let status = response.status().to_string();
+ tracing::error!("[AR] • Failed to download authlib: HTTP {}", status);
+ return Err(crate::ErrorKind::NetworkErrorOccurred {
+ error: format!("Failed to download authlib: HTTP {status}"),
+ }
+ .as_error());
+ }
+
+ response.bytes().await.map_err(|e| {
+ tracing::error!("[AR] • Failed to read response bytes: {:?}", e);
+ crate::ErrorKind::NetworkErrorOccurred {
+ error: format!("Failed to read response bytes: {e}"),
+ }
+ .as_error()
+ })
+}
+
+/// ### AR • AuthLib (Ely By)
+/// Gets the Minecraft library metadata from the local libraries directory.
+async fn get_minecraft_library_metadata(minecraft_version: &str) -> Result<Library> {
+ let state = State::get().await?;
+
+ let path = state
+ .directories
+ .version_dir(minecraft_version)
+ .join(format!("{}.json", minecraft_version));
+ if !path.exists() {
+ tracing::error!("[AR] • File not found: {:#?}", path);
+ return Err(crate::ErrorKind::InvalidMinecraftVersion {
+ minecraft_version: minecraft_version.to_string(),
+ }
+ .as_error());
+ }
+
+ let content = fs::read_to_string(&path).await?;
+ let version_data: VersionJson = serde_json::from_str(&content)?;
+
+ for lib in version_data.libraries {
+ if lib.name.contains("com.mojang:authlib") {
+ if let Some(downloads) = &lib.downloads {
+ if let Some(artifact) = &downloads.artifact {
+ if artifact.path.is_some()
+ && artifact.url.is_some()
+ && artifact.sha1.is_some()
+ {
+ tracing::info!("[AR] • Found AuthLib: {}", lib.name);
+ tracing::info!(
+ "[AR] • Path: {}",
+ artifact.path.as_ref().unwrap()
+ );
+ tracing::info!(
+ "[AR] • URL: {}",
+ artifact.url.as_ref().unwrap()
+ );
+ tracing::info!(
+ "[AR] • SHA1: {}",
+ artifact.sha1.as_ref().unwrap()
+ );
+
+ return Ok(lib);
+ }
+ }
+ }
+ }
+ }
+
+ Err(crate::ErrorKind::MinecraftMetadataNotFound {
+ minecraft_version: minecraft_version.to_string(),
+ }
+ .as_error())
+}
+
+/// ### AR • Migration
+/// Applying migration fix for SQLite database.
pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
tracing::info!("[AR] • Attempting to apply migration fix");
let patched = db::apply_migration_fix(eol).await?;
@@ -35,14 +333,19 @@ pub async fn apply_migration_fix(eol: &str) -> Result<bool> {
Ok(patched)
}
-pub async fn init_download(
+/// ### AR • Updater
+/// Initialize the update launcher.
+pub async fn init_update_launcher(
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);
+ tracing::info!("[AR] • Initialize downloading from • {:?}", download_url);
+ tracing::info!("[AR] • Save local file name • {:?}", local_filename);
+ tracing::info!("[AR] • OS type • {}", os_type);
+ tracing::info!("[AR] • Auto update supported • {}", auto_update_supported);
+
if let Err(e) = update::get_resource(
download_url,
local_filename,

View File

@@ -1,26 +0,0 @@
diff --git a/apps/app-frontend/src/components/ui/InstanceCreationModal.vue b/apps/app-frontend/src/components/ui/InstanceCreationModal.vue
index 9333a75a..e439c203 100644
--- a/apps/app-frontend/src/components/ui/InstanceCreationModal.vue
+++ b/apps/app-frontend/src/components/ui/InstanceCreationModal.vue
@@ -379,7 +379,7 @@ const upload_icon = async () => {
],
})
- icon.value = res ? res.path : null
+ icon.value = res ? res : null
if (!icon.value) return
display_icon.value = convertFileSrc(icon.value)
diff --git a/apps/app-frontend/src/pages/instance/Options.vue b/apps/app-frontend/src/pages/instance/Options.vue
index e4166eab..9ca84390 100644
--- a/apps/app-frontend/src/pages/instance/Options.vue
+++ b/apps/app-frontend/src/pages/instance/Options.vue
@@ -581,7 +581,7 @@ async function setIcon() {
if (!value) return
- icon.value = value.path
+ icon.value = value
await edit_icon(props.instance.path, icon.value).catch(handleError)
trackEvent('InstanceSetIcon')

File diff suppressed because one or more lines are too long

View File

@@ -1,22 +0,0 @@
diff --git a/packages/app-lib/src/api/profile/mod.rs b/packages/app-lib/src/api/profile/mod.rs
index aaa2864a..7addca68 100644
--- a/packages/app-lib/src/api/profile/mod.rs
+++ b/packages/app-lib/src/api/profile/mod.rs
@@ -845,7 +845,7 @@ pub async fn create_mrpack_json(
)
.await?;
- let files = projects
+ let mut files = projects
.into_iter()
.filter_map(|(path, version_id)| {
if let Some(version) = versions.iter().find(|x| x.id == version_id)
@@ -890,6 +890,8 @@ pub async fn create_mrpack_json(
})
.collect::<crate::Result<Vec<PackFile>>>()?;
+ files.sort_by(|a, b| a.path.cmp(&b.path));
+
Ok(PackFormat {
game: "minecraft".to_string(),
format_version: 1,

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +0,0 @@
diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs
index 6d429ffb..f261561f 100644
--- a/apps/app/src/main.rs
+++ b/apps/app/src/main.rs
@@ -188,7 +188,20 @@ fn main() {
.plugin(tauri_plugin_shell::init())
.plugin(
tauri_plugin_window_state::Builder::default()
- .with_filename("app-window-state.json")
+ .with_filename(
+ if std::env::current_dir()
+ .ok()
+ .map(|dir| dir.join("portable.txt").exists())
+ .unwrap_or(false)
+ {
+ std::env::current_dir()
+ .ok()
+ .map(|dir| dir.join("UserData/app-window-state.json").to_string_lossy().into_owned())
+ .unwrap()
+ } else {
+ "app-window-state.json".to_string()
+ },
+ )
.build(),
)
.setup(|app| {
diff --git a/packages/app-lib/src/state/dirs.rs b/packages/app-lib/src/state/dirs.rs
index 96148a91..038313d6 100644
--- a/packages/app-lib/src/state/dirs.rs
+++ b/packages/app-lib/src/state/dirs.rs
@@ -23,8 +23,13 @@ impl DirectoryInfo {
// Get the settings directory
// init() is not needed for this function
pub fn get_initial_settings_dir() -> Option<PathBuf> {
- Self::env_path("THESEUS_CONFIG_DIR")
- .or_else(|| Some(dirs::data_dir()?.join("AstralRinthApp")))
+ Self::env_path("THESEUS_CONFIG_DIR").or_else(|| {
+ if std::env::current_dir().ok()?.join("portable.txt").exists() {
+ Some(std::path::Path::new("UserData").to_path_buf())
+ } else {
+ Some(dirs::data_dir()?.join("AstralRinthApp"))
+ }
+ })
}
/// Get all paths needed for Theseus to operate properly