You've already forked AstralRinth
Compare commits
23 Commits
14bf06e4bd
...
AR-0.10.30
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d5d747202 | |||
| 7516ff9e47 | |||
| df9bbe3ba0 | |||
| 362fd7f32a | |||
| adf831dab9 | |||
| efeac22d14 | |||
| 591d98a9eb | |||
| 77472d9a09 | |||
| 789d666515 | |||
| d917bff6ef | |||
| 4e69cd8bde | |||
| b71e4cc6f9 | |||
| a56ab6adb9 | |||
| f1b67c9584 | |||
| 3d32640b83 | |||
| 6dfb599e14 | |||
| 332a543f66 | |||
| 1ef96c447e | |||
| 1ec92b5f97 | |||
| f0a4532051 | |||
| 14f6450cf4 | |||
| 97bd18c7b3 | |||
| 34d85a03b2 |
64
.github/workflows/astralrinth-build.yml
vendored
64
.github/workflows/astralrinth-build.yml
vendored
@@ -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
|
||||
|
||||
159
README.md
159
README.md
@@ -1,76 +1,123 @@
|
||||
# Navigation in this README
|
||||
- [Install instructions](#install-instructions)
|
||||
- [Features](#features)
|
||||
- [Getting started](#getting-started)
|
||||
- [Disclaimer](#disclaimer)
|
||||
- [Donate](#support-our-project-crypto-wallets)
|
||||
# 📘 Navigation
|
||||
|
||||
- [🔧 Install Instructions](#install-instructions)
|
||||
- [✨ Features](#features)
|
||||
- [🚀 Getting Started](#getting-started)
|
||||
- [⚠️ Disclaimer](#disclaimer)
|
||||
- [💰 Donate](#support-our-project-crypto-wallets)
|
||||
|
||||
## Other languages
|
||||
> [Русский](readme/ru_ru/README.md)
|
||||
|
||||
## Support channel
|
||||
> [Telegram](https://me.astralium.su/ref/telegram_channel)
|
||||
|
||||
---
|
||||
|
||||
# About Project
|
||||
|
||||
## AstralRinth • Empowering Your Minecraft Adventure
|
||||
Welcome to AR • Fork of Modrinth, the ultimate game launcher designed to enhance your Minecraft experience through the Modrinth platform and their API. Whether you're a graphical interface enthusiast, or a developer integrating Modrinth projects, Theseus core is your gateway to a new level of Minecraft gaming.
|
||||
## **AstralRinth • Empowering Your Minecraft Adventure**
|
||||
|
||||
## About Software
|
||||
Introducing AstralRinth, a specialized variant of Theseus dedicated to implementing offline authorization for an even more flexible and user-centric Minecraft Modrinth experience. Roam the Minecraft realms without the constraints of online authentication, thanks to AstralRinth.
|
||||
Welcome to **AstralRinth (AR)** — a powerful fork of Modrinth, reimagined to enhance your Minecraft journey. Whether you're a GUI enthusiast or a developer building with Modrinth’s API, **Theseus Core** is your launchpad into a new era of Minecraft gameplay.
|
||||
|
||||
## AR • Unlocking Minecraft's Boundless Horizon
|
||||
Dive into the extraordinary world of AstralRinth, a fork of the original project with a unique focus on providing a free trial experience for Minecraft, all without the need for a license. Currently boasting:
|
||||
- *Recently, improved integration with the Git Astralium API has been added.*
|
||||
|
||||
# Install instructions
|
||||
- To install our application, you need to download a file for your operating system from our available releases or development builds • [Download variants here](https://git.astralium.su/didirus/AstralRinth/releases)
|
||||
- After you have downloaded the required executable file or archive, then open it
|
||||
## **About the Software**
|
||||
|
||||
### Downloadable file extensions
|
||||
- `.msi` format for Windows OS system _(Supported popular latest versions of Microsoft Windows)_
|
||||
- `.dmg` format for MacOS system _(Works on Macos Ventura / Sonoma / Sequoia, but it should be works on older OS builds)_
|
||||
- `.deb` format for Linux OS systems _(Since there are quite a few distributions, we do not guarantee
|
||||
**AstralRinth** is a dedicated branch of the Theseus project, focused on **offline authentication**, offering you more flexibility and control. Play Minecraft without the need for constant online verification — a user-first approach to modern modded gaming.
|
||||
|
||||
### Installation subjects
|
||||
- Builds in releases that are signed with the following prefixes are not recommended for installation and may contain errors:
|
||||
- `dev`
|
||||
- `nightly`
|
||||
- `dirty`
|
||||
- `dirty-dev`
|
||||
- `dirty-nightly`
|
||||
- `dirty_dev`
|
||||
- `dirty_nightly`
|
||||
- Auto-updating takes place through parsing special versions from releases, so we also distribute clean types of `.msi, .dmg and .deb`
|
||||
## **AR • Unlocking Minecraft's Boundless Horizon**
|
||||
|
||||
This unique fork introduces a **free trial Minecraft experience**, bypassing license checks while maintaining rich functionality. Currently includes:
|
||||
|
||||
---
|
||||
|
||||
# Install Instructions
|
||||
|
||||
To install the launcher:
|
||||
|
||||
1. Visit the [releases page](https://git.astralium.su/didirus/AstralRinth/releases) to download the correct version for your system.
|
||||
2. Run the downloaded file or extract and launch it, depending on the format.
|
||||
|
||||
### Downloadable File Extensions
|
||||
|
||||
| Extension | OS | Notes |
|
||||
| --------- | ------- | --------------------------------------------------------------------- |
|
||||
| `.msi` | Windows | Supported on all recent Windows versions |
|
||||
| `.dmg` | macOS | Works on Ventura, Sonoma, Sequoia _(may also support older versions)_ |
|
||||
| `.deb` | Linux | Basic support; compatibility may vary by distribution |
|
||||
|
||||
### Installation Warnings
|
||||
|
||||
Avoid using builds with these prefixes — they may be unstable or experimental:
|
||||
|
||||
- `dev`
|
||||
- `nightly`
|
||||
- `dirty`
|
||||
- `dirty-dev`
|
||||
- `dirty-nightly`
|
||||
- `dirty_dev`
|
||||
- `dirty_nightly`
|
||||
|
||||
---
|
||||
|
||||
# Features
|
||||
|
||||
### Featured enhancement in AR
|
||||
- AstralRinth offers a range of authorization options, giving users the flexibility to log in with valid licenses or even a pirate account without auth credentials breaks (_Unlike MultiMC Cracked and similar software_). Experience Minecraft on your terms, breaking free from traditional licensing constraints (_Popular in Russian Federation_).
|
||||
> _The launcher provides an opportunity to use the well-known Modrinth, but with an improved user experience._
|
||||
|
||||
### Easy to use
|
||||
- Using the launcher is intuitive, any user can figure it out.
|
||||
## Included exclusive features
|
||||
|
||||
### Update notifies
|
||||
- We have implemented notifications about the release of new updates on our Git. The launcher can also download them for you and try to install them.
|
||||
- No ads in the entire launcher.
|
||||
- Custom `.svg` vector icons for a distinct UI.
|
||||
- Improved compatibility with both licensed and pirate accounts.
|
||||
- Use **official microsoft accounts** or **offline/pirate accounts** — login won't break.
|
||||
- Supports license-free access for testing or personal use.
|
||||
- No dependence on official authentication services.
|
||||
- Discord Rich Presence integration:
|
||||
- Dynamic status messages.
|
||||
- In-game timer and AFK counter.
|
||||
- Strict disabling of statistics and other Modrinth metrics.
|
||||
- Optimized archive/package size.
|
||||
- Integrated update fetcher for seamless version management.
|
||||
- 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)
|
||||
|
||||
### Enhancements
|
||||
- Custom .SVG vectors for a personalized touch.
|
||||
- Improved compatibility for both pirate and licensed accounts.
|
||||
- Beautiful Discord RPC with random messages while playing, along with an in-game timer and AFK counter.
|
||||
- Forced disabling of statistics collection (modrinch metrics) with a hard patch from AstralRinth, ensuring it remains deactivated regardless of the configuration setting.
|
||||
- Removal of advertisements from all launcher views.
|
||||
- Optimization of packages (archives).
|
||||
- Integrated update fetching feature
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
To begin your AstralRinth adventure, follow these steps:
|
||||
1. **Download Your OS Version**: Head over to our [releases page](https://git.astralium.su/didirus/AstralRinth/releases/) to find the right file for your operating system.
|
||||
- **Choosing the Correct File**: Ensure you select the file that matches your OS requirements.
|
||||
- [**How select file**](#downloadable-file-extensions)
|
||||
- [**How select release**](#installation-subjects)
|
||||
2. **Authentication**: Log in with a valid license or, for testing, try using a pirate account to see AstralRinth in action.
|
||||
3. **Launch Minecraft**: Start your journey by launching Minecraft through AstralRinth and enjoy the adventures that await.
|
||||
- **Choosing java installation**: The launcher will try to automatically detect the recommended JVM version for running the game, but you can configure everything in the launcher settings.
|
||||
|
||||
To begin using AstralRinth:
|
||||
|
||||
1. **Download Your OS Version**
|
||||
|
||||
- Go to the [releases page](https://git.astralium.su/didirus/AstralRinth/releases)
|
||||
- [How to choose a file](#downloadable-file-extensions)
|
||||
- [How to choose a release](#installation-warnings)
|
||||
|
||||
2. **Log In**
|
||||
|
||||
- Use your official Mojang/Microsoft account, or test using a non-licensed account.
|
||||
|
||||
3. **Launch Minecraft**
|
||||
- Start Minecraft from the launcher.
|
||||
- The launcher will auto-detect the recommended JVM version.
|
||||
- You can also configure Java manually in the settings.
|
||||
|
||||
---
|
||||
|
||||
# Disclaimer
|
||||
- AstralRinth is a project intended for experimentation and educational purposes only. It does not endorse or support piracy, and users are encouraged to obtain valid licenses for a fully-supported Minecraft experience.
|
||||
- Users are reminded to respect licensing agreements and support the developers of Minecraft.
|
||||
|
||||
# Support our Project (Crypto Wallets)
|
||||
- **AstralRinth** is intended **solely for educational and experimental use**.
|
||||
- We **do not condone piracy** — users are encouraged to purchase a legitimate Minecraft license.
|
||||
- Respect all relevant licensing agreements and support Minecraft developers.
|
||||
|
||||
---
|
||||
|
||||
# Support Our Project (Crypto Wallets)
|
||||
|
||||
If you'd like to support development, you can donate via the following crypto wallets:
|
||||
|
||||
- BTC (Telegram): 14g6asNYzcUoaQtB8B2QGKabgEvn55wfLj
|
||||
- USDT TRC20 (Telegram): TMSmv1D5Fdf4fipUpwBCdh16WevrV45vGr
|
||||
- TONCOIN (Telegram): UQAqUJ2_hVBI6k_gPyfp_jd-1K0OS61nIFPZuJWN9BwGAvKe
|
||||
- TONCOIN (Telegram): UQAqUJ2_hVBI6k_gPyfp_jd-1K0OS61nIFPZuJWN9BwGAvKe
|
||||
|
||||
@@ -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) {
|
||||
<PlusIcon />
|
||||
</NavButton>
|
||||
<div class="flex flex-grow"></div>
|
||||
<NavButton v-if="updateAvailable" v-tooltip.right="'Install update'" :to="() => restartApp()">
|
||||
<!-- [AR] TODO -->
|
||||
<!-- <NavButton v-if="updateAvailable" v-tooltip.right="'Install update'" :to="() => restartApp()">
|
||||
<DownloadIcon />
|
||||
</NavButton>
|
||||
<NavButton v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
|
||||
<SettingsIcon />
|
||||
</NavButton>
|
||||
</NavButton> -->
|
||||
<template v-if="updateState">
|
||||
<NavButton class="neon-icon pulse" v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
|
||||
<SettingsIcon />
|
||||
</NavButton>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NavButton v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
|
||||
<SettingsIcon />
|
||||
</NavButton>
|
||||
</template>
|
||||
<ButtonStyled v-if="credentials" type="transparent" circular>
|
||||
<OverflowMenu
|
||||
:options="[
|
||||
@@ -659,6 +671,9 @@ function handleAuxClick(e) {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../packages/assets/styles/neon-icon.scss';
|
||||
@import '../../../packages/assets/styles/neon-text.scss';
|
||||
|
||||
.window-controls {
|
||||
z-index: 20;
|
||||
display: none;
|
||||
|
||||
@@ -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 />
|
||||
@@ -153,11 +152,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 +175,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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -298,10 +327,20 @@ async function copyToClipboard(text) {
|
||||
<template v-if="copied"> <CheckIcon class="text-green" /> Copied! </template>
|
||||
<template v-else> <CopyIcon /> Copy debug info </template>
|
||||
</button>
|
||||
<ButtonStyled class="neon-button neon">
|
||||
<a href="https://me.astralium.su/get/ar/help" target="_blank" rel="noopener noreferrer">
|
||||
Get AstralRinth support
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled class="neon-button neon" >
|
||||
<a href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer">
|
||||
Checkout latest releases
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<template v-if="hasDebugInfo">
|
||||
<div class="bg-button-bg rounded-xl mt-2 overflow-clip">
|
||||
<div class="bg-button-bg rounded-xl mt-2 overflow-hidden">
|
||||
<button
|
||||
class="flex items-center justify-between w-full bg-transparent border-0 px-4 py-3 cursor-pointer"
|
||||
@click="errorCollapsed = !errorCollapsed"
|
||||
@@ -313,12 +352,123 @@ async function copyToClipboard(text) {
|
||||
/>
|
||||
</button>
|
||||
<Collapsible :collapsed="errorCollapsed">
|
||||
<pre class="m-0 px-4 py-3 bg-bg rounded-none">{{ debugInfo }}</pre>
|
||||
<pre
|
||||
class="m-0 px-4 py-3 bg-bg rounded-none whitespace-pre-wrap break-words overflow-x-auto max-w-full"
|
||||
>{{ debugInfo }}</pre>
|
||||
</Collapsible>
|
||||
</div>
|
||||
<template v-if="errorType === 'state_init'">
|
||||
<div class="notice">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 v-if="language === 'en'" class="notice__title">⚠️ Migration Issue • Important Notice ⚠️</h3>
|
||||
<h3 v-if="language === 'ru'" class="notice__title">⚠️ Проблема миграции • Важное уведомление ⚠️</h3>
|
||||
<ButtonStyled>
|
||||
<button @click="toggleLanguage">
|
||||
{{ language === 'en' ? '📖 Русский' : '📖 English' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p v-if="language === 'en'" class="notice__text">
|
||||
We're experiencing an issue with our database migration system due to differences in how different operating systems handle line endings. This might cause problems with our app's functionality.
|
||||
</p>
|
||||
<p v-if="language === 'en'" class="notice__text">
|
||||
<strong>What's happening?</strong> When we build our app, we use a system that checks the integrity of our database migrations. However, this system can get confused when it encounters different line endings (like CRLF vs LF) used by different operating systems. This can lead to errors and make our app unusable.
|
||||
</p>
|
||||
<p v-if="language === 'en'" class="notice__text">
|
||||
<strong>Why is this happening?</strong> This issue is caused by a combination of factors, including different operating systems handling line endings differently, Git's line ending conversion settings, and our app's build process.
|
||||
</p>
|
||||
<p v-if="language === 'en'" class="notice__text">
|
||||
<strong>What are we doing about it?</strong> We're working to resolve this issue and ensure that our app works smoothly for all users. In the meantime, we apologize for any inconvenience this might cause and appreciate your patience and understanding.
|
||||
</p>
|
||||
<p v-if="language === 'ru'" class="notice__text">
|
||||
Мы сталкиваемся с проблемой в нашей системе миграции базы данных из-за различий в том, как разные операционные системы обрабатывают окончания строк. Это может вызвать проблемы с функциональностью нашего приложения.
|
||||
</p>
|
||||
<p v-if="language === 'ru'" class="notice__text">
|
||||
<strong>Что происходит?</strong> Когда мы строим наше приложение, мы используем систему, которая проверяет целостность наших миграций базы данных. Однако эта система может сбиваться, когда сталкивается с различными окончаниями строк (например, CRLF против LF), используемыми разными операционными системами. Это может привести к ошибкам и сделать наше приложение неработоспособным.
|
||||
</p>
|
||||
<p v-if="language === 'ru'" class="notice__text">
|
||||
<strong>Почему это происходит?</strong> Эта проблема вызвана сочетанием факторов, включая различную обработку окончаний строк разными операционными системами, настройки преобразования окончаний строк в Git и процесс сборки нашего приложения.
|
||||
</p>
|
||||
<p v-if="language === 'ru'" class="notice__text">
|
||||
<strong>Что мы с этим делаем?</strong> Мы работаем над решением этой проблемы и обеспечением бесперебойной работы нашего приложения для всех пользователей. В это время мы извиняемся за возможные неудобства и благодарим вас за терпение и понимание.
|
||||
</p>
|
||||
</div>
|
||||
<h2 class="text-lg font-bold text-contrast">
|
||||
<template v-if="language === 'en'">Possible fix in real time:</template>
|
||||
<template v-if="language === 'ru'">Возможное исправление в реальном времени:</template>
|
||||
</h2>
|
||||
<div class="flex justify-between">
|
||||
<ol class="flex flex-col gap-3">
|
||||
<li>
|
||||
<ButtonStyled class="neon-button neon">
|
||||
<button
|
||||
:title="language === 'en'
|
||||
? 'Convert all line endings in migration files to LF (Unix-style: \\n)'
|
||||
: 'Преобразовать все окончания строк в файлах миграций в LF (Unix-стиль: \\n)'"
|
||||
aria-label="LF"
|
||||
@click="onApplyMigrationFix('lf')"
|
||||
>
|
||||
{{ language === 'en' ? 'Apply LF Migration Fix' : 'Применить исправление миграции LF' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</li>
|
||||
<li>
|
||||
<ButtonStyled class="neon-button neon">
|
||||
<button
|
||||
:title="language === 'en'
|
||||
? 'Convert all line endings in migration files to CRLF (Windows-style: \\r\\n)'
|
||||
: 'Преобразовать все окончания строк в файлах миграций в CRLF (Windows-стиль: \\r\\n)'"
|
||||
aria-label="CRLF"
|
||||
@click="onApplyMigrationFix('crlf')"
|
||||
>
|
||||
{{ language === 'en' ? 'Apply CRLF Migration Fix' : 'Применить исправление миграции CRLF' }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
<ModalWrapper
|
||||
ref="migrationFixCallbackModel"
|
||||
:header="language === 'en'
|
||||
? '💡 Migration fix report'
|
||||
: '💡 Отчет об исправлении миграции'"
|
||||
:closable="closable">
|
||||
<div class="modal-body">
|
||||
<h2 class="text-lg font-bold text-contrast space-y-2">
|
||||
<template v-if="migrationFixSuccess === true">
|
||||
<p class="flex items-center gap-2 neon-text">
|
||||
✅
|
||||
{{ language === 'en'
|
||||
? 'The migration fix has been applied successfully. Please restart the launcher and try to log in to the game :)'
|
||||
: 'Исправление миграции успешно применено. Пожалуйста, перезапустите лаунчер и попробуйте снова авторизоваться в игре :)' }}
|
||||
</p>
|
||||
<p class="mt-2 text-sm neon-text">
|
||||
{{ language === 'en'
|
||||
? 'If the problem persists, please try the other fix.'
|
||||
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="migrationFixSuccess === false">
|
||||
<p class="flex items-center gap-2 neon-text">
|
||||
❌
|
||||
{{ language === 'en'
|
||||
? 'The migration fix failed or had no effect.'
|
||||
: 'Исправление миграции не было успешно применено или не имело эффекта.' }}
|
||||
</p>
|
||||
<p class="mt-2 text-sm neon-text">
|
||||
{{ language === 'en'
|
||||
? 'If the problem persists, please try the other fix.'
|
||||
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
|
||||
</p>
|
||||
</template>
|
||||
</h2>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@@ -333,6 +483,9 @@ async function copyToClipboard(text) {
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../../../../../packages/assets/styles/neon-button.scss';
|
||||
@import '../../../../../packages/assets/styles/neon-text.scss';
|
||||
|
||||
.cta-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -36,60 +36,6 @@
|
||||
<span class="circle stopped" />
|
||||
<span class="running-text"> No instances running </span>
|
||||
</div>
|
||||
<div v-if="updateState">
|
||||
<a>
|
||||
<Button class="download" :disabled="installState" @click="initUpdateModal(), getRemote(false)">
|
||||
<DownloadIcon />
|
||||
{{
|
||||
installState
|
||||
? "Downloading new update..."
|
||||
: "Download new update"
|
||||
}}
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
<ModalWrapper ref="updateModalView" :has-to-type="false" header="Request to update the AstralRinth launcher">
|
||||
<div class="modal-body">
|
||||
<div class="markdown-body">
|
||||
<p>The new version of the AstralRinth launcher is available.</p>
|
||||
<p>Your version is outdated. We recommend that you update to the latest version.</p>
|
||||
<p><strong>⚠️ Warning ⚠️</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.
|
||||
</p>
|
||||
</div>
|
||||
<span>Source • Git Astralium</span>
|
||||
<span>Version on remote server • <p id="releaseData" class="cosmic inline-fix"></p></span>
|
||||
<span>Version on local device •
|
||||
<p class="cosmic inline-fix">v{{ version }}</p>
|
||||
</span>
|
||||
<div class="button-group push-right">
|
||||
<Button class="updater-modal" @click="updateModalView.hide()">
|
||||
Cancel</Button>
|
||||
<Button class="updater-modal" @click="initDownload()">
|
||||
Download file
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
<ModalWrapper ref="updateRequestFailView" :has-to-type="false" header="Failed to request a file from the server :(">
|
||||
<div class="modal-body">
|
||||
<div class="markdown-body">
|
||||
<p><strong>Error occurred</strong></p>
|
||||
<p>Unfortunately, the program was unable to download the file from our servers.</p>
|
||||
<p>Please try downloading it yourself from <a href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer">Git Astralium</a> if there are any updates available.</p>
|
||||
</div>
|
||||
<span>Local AstralRinth •
|
||||
<p class="cosmic inline-fix">v{{ version }}</p>
|
||||
</span>
|
||||
</div>
|
||||
<div class="button-group push-right">
|
||||
<Button class="updater-modal" @click="updateRequestFailView.hide()">
|
||||
Close</Button>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
</div>
|
||||
<transition name="download">
|
||||
<Card v-if="showCard === true && currentLoadingBars.length > 0" ref="card" class="info-card">
|
||||
@@ -138,29 +84,6 @@ import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { get_many } from '@/helpers/profile.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
|
||||
const version = await getVersion()
|
||||
|
||||
import { installState, getRemote, updateState } from '@/helpers/update.js'
|
||||
import ModalWrapper from './modal/ModalWrapper.vue'
|
||||
|
||||
const updateModalView = ref(null)
|
||||
const updateRequestFailView = ref(null)
|
||||
|
||||
const initUpdateModal = async () => {
|
||||
updateModalView.value.show()
|
||||
}
|
||||
|
||||
const initDownload = async () => {
|
||||
updateModalView.value.hide()
|
||||
const result = await getRemote(true);
|
||||
if (!result) {
|
||||
updateRequestFailView.value.show()
|
||||
}
|
||||
}
|
||||
|
||||
await getRemote(false)
|
||||
|
||||
const router = useRouter()
|
||||
const card = ref(null)
|
||||
@@ -318,101 +241,6 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.inline-fix {
|
||||
display: inline-flex;
|
||||
margin-top: -2rem;
|
||||
margin-bottom: -2rem;
|
||||
//margin-left: 0.3rem;
|
||||
}
|
||||
|
||||
.cosmic {
|
||||
color: #3e8cde;
|
||||
text-decoration: none;
|
||||
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.35s ease;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
:deep(table) {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
:deep(hr),
|
||||
:deep(h1),
|
||||
:deep(h2) {
|
||||
max-width: max(60rem, 90%);
|
||||
}
|
||||
|
||||
:deep(ul),
|
||||
:deep(ol) {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding: var(--gap-lg);
|
||||
text-align: left;
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.download {
|
||||
color: #3e8cde;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-button-bg);
|
||||
// padding: var(--gap-sm) var(--gap-lg);
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
text-decoration: none;
|
||||
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.35s ease;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.download:hover,
|
||||
.download:focus,
|
||||
.download:active {
|
||||
color: #10fae5;
|
||||
text-shadow: #26065e;
|
||||
}
|
||||
|
||||
.updater-modal {
|
||||
color: #3e8cde;
|
||||
padding: var(--gap-sm) var(--gap-lg);
|
||||
text-decoration: none;
|
||||
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.35s ease;
|
||||
}
|
||||
|
||||
.updater-modal:hover,
|
||||
.updater-modal:focus,
|
||||
.updater-modal:active {
|
||||
color: #10fae5;
|
||||
text-shadow: #26065e;
|
||||
}
|
||||
|
||||
.action-groups {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -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>
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
PaintbrushIcon,
|
||||
GameIcon,
|
||||
CoffeeIcon,
|
||||
DownloadIcon,
|
||||
SpinnerIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { TabbedModal } from '@modrinth/ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
@@ -23,6 +25,23 @@ import { useTheming } from '@/store/state'
|
||||
import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { get, set } from '@/helpers/settings.ts'
|
||||
// [AR] Imports
|
||||
import { installState, getRemote, updateState } from '@/helpers/update.js'
|
||||
|
||||
const updateModalView = ref(null)
|
||||
const updateRequestFailView = ref(null)
|
||||
|
||||
const initUpdateModal = async () => {
|
||||
updateModalView.value.show()
|
||||
}
|
||||
|
||||
const initDownload = async () => {
|
||||
updateModalView.value.hide()
|
||||
const result = await getRemote(true);
|
||||
if (!result) {
|
||||
updateRequestFailView.value.show()
|
||||
}
|
||||
}
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -138,11 +157,10 @@ function devModeCount() {
|
||||
{{ formatMessage(developerModeEnabled) }}
|
||||
</p>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
<button
|
||||
class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation"
|
||||
:class="{ 'text-brand': themeStore.devMode, 'text-secondary': !themeStore.devMode }"
|
||||
@click="devModeCount"
|
||||
>
|
||||
@click="devModeCount">
|
||||
<AstralRinthLogo class="w-6 h-6" />
|
||||
</button>
|
||||
<div>
|
||||
@@ -153,9 +171,80 @@ function devModeCount() {
|
||||
{{ osVersion }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="updateState" class="w-8 h-8 cursor-pointer hover:brightness-75 neon-icon pulse">
|
||||
<template v-if="installState">
|
||||
<SpinnerIcon class="size-6 animate-spin" v-tooltip.bottom="'Installing in process...'" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<DownloadIcon class="size-6" v-tooltip.bottom="'View update info'" @click="!installState && (initUpdateModal(), getRemote(false))" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</TabbedModal>
|
||||
<!-- [AR] Feature -->
|
||||
<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>
|
||||
<p>Your version is outdated. We recommend that you update to the latest version.</p>
|
||||
<p><strong>⚠️ Warning ⚠️</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.
|
||||
</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>
|
||||
<span class="neon-text">v{{ version }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
<ModalWrapper ref="updateRequestFailView" :has-to-type="false" header="Failed to request a file from the server :(">
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<p><strong>Error occurred</strong></p>
|
||||
<p>Unfortunately, the program was unable to download the file from our servers.</p>
|
||||
<p>
|
||||
Please try downloading it yourself from
|
||||
<a class="neon-text" href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer">Git
|
||||
Astralium</a>
|
||||
if there are any updates available.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-secondary">
|
||||
<p>
|
||||
<strong>Local AstralRinth:</strong>
|
||||
<span class="neon-text">v{{ version }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-4 right-4 flex items-center gap-4 neon-button neon">
|
||||
<Button class="bordered" @click="updateRequestFailView.hide()">Close</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../../../../packages/assets/styles/neon-icon.scss';
|
||||
@import '../../../../../../packages/assets/styles/neon-button.scss';
|
||||
@import '../../../../../../packages/assets/styles/neon-text.scss';
|
||||
</style>
|
||||
@@ -30,7 +30,7 @@ watch(
|
||||
option, you opt out and ads will no longer be shown based on your interests.
|
||||
</p>
|
||||
</div>
|
||||
<!-- AstralRinth disabled element by default -->
|
||||
<!-- [AR] Patch. Disabled element by default -->
|
||||
<Toggle id="personalized-ads" v-model="settings.personalized_ads" :disabled="!settings.personalized_ads" />
|
||||
</div>
|
||||
|
||||
@@ -43,7 +43,7 @@ watch(
|
||||
longer be collected.
|
||||
</p>
|
||||
</div>
|
||||
<!-- AstralRinth disabled element by default -->
|
||||
<!-- [AR] Patch. Disabled element by default -->
|
||||
<Toggle id="opt-out-analytics" v-model="settings.telemetry" :disabled="!settings.telemetry" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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 = [
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,20 @@ export async function getOS() {
|
||||
return await invoke('plugin:utils|get_os')
|
||||
}
|
||||
|
||||
export async function getArtifact(downloadurl, filename, ostype, autoupdatesupported) {
|
||||
// [AR] Feature
|
||||
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
|
||||
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) {
|
||||
|
||||
@@ -218,7 +218,9 @@ fn main() {
|
||||
"utils",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"get_artifact",
|
||||
"init_authlib_patching",
|
||||
"apply_migration_fix",
|
||||
"init_update_launcher",
|
||||
"get_os",
|
||||
"should_disable_mouseover",
|
||||
"highlight_in_folder",
|
||||
|
||||
@@ -10,12 +10,15 @@ use crate::api::{Result, TheseusSerializableError};
|
||||
use dashmap::DashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use theseus::prelude::canonicalize;
|
||||
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![
|
||||
get_artifact,
|
||||
init_authlib_patching,
|
||||
apply_migration_fix,
|
||||
init_update_launcher,
|
||||
get_os,
|
||||
should_disable_mouseover,
|
||||
highlight_in_folder,
|
||||
@@ -27,9 +30,39 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
.build()
|
||||
}
|
||||
|
||||
/// [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;
|
||||
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> {
|
||||
let result = utils::apply_migration_fix(eol).await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// [AR] Feature
|
||||
#[tauri::command]
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
]
|
||||
},
|
||||
"productName": "AstralRinth App",
|
||||
"version": "0.10.302",
|
||||
"version": "0.10.304",
|
||||
"mainBinaryName": "AstralRinth App",
|
||||
"identifier": "AstralRinthApp",
|
||||
"plugins": {
|
||||
|
||||
@@ -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<dyn std::error::Error>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
117
packages/app-lib/src/api/update.rs
Normal file
117
packages/app-lib/src/api/update.rs
Normal file
@@ -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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -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)]
|
||||
|
||||
@@ -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,8 @@ pub async fn launch_minecraft(
|
||||
command.arg("--add-opens=jdk.internal/jdk.internal.misc=ALL-UNNAMED");
|
||||
}
|
||||
|
||||
// Patched by AstralRinth
|
||||
// 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" {
|
||||
let invalid_url = "https://invalid.invalid";
|
||||
@@ -743,6 +744,7 @@ pub async fn launch_minecraft(
|
||||
}
|
||||
}
|
||||
|
||||
// [AR] Feature
|
||||
let selected_phrase = ACTIVE_STATE.choose(&mut rand::thread_rng()).unwrap();
|
||||
let _ = state
|
||||
.discord_rpc
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Pool<Sqlite>> {
|
||||
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<Pool<Sqlite>> {
|
||||
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<Pool<Sqlite>> {
|
||||
}
|
||||
|
||||
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<Pool<Sqlite>> {
|
||||
.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<Sqlite>) -> 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<Sqlite>,
|
||||
) -> crate::Result<()> {
|
||||
let arch = env::consts::ARCH;
|
||||
let os = env::consts::OS;
|
||||
pub(crate) async fn apply_migration_fix(eol: &str) -> crate::Result<bool> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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<PathBuf> {
|
||||
Self::env_path("THESEUS_CONFIG_DIR").or_else(|| {
|
||||
if std::env::current_dir().ok()?.join("portable.txt").exists() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,21 +1,366 @@
|
||||
use crate::api::update;
|
||||
use crate::state::db;
|
||||
///
|
||||
/// [AR] Feature Utils
|
||||
///
|
||||
use crate::{Result, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io;
|
||||
use std::process;
|
||||
use tokio::{fs, 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<Launcher> {
|
||||
// Deserialize the content of package.json into a Launcher struct
|
||||
let launcher: Launcher = serde_json::from_str(PACKAGE_JSON_CONTENT)?;
|
||||
#[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> {
|
||||
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?;
|
||||
if patched {
|
||||
tracing::info!("[AR] • Successfully applied migration fix");
|
||||
} else {
|
||||
tracing::error!("[AR] • Failed to apply migration fix");
|
||||
}
|
||||
Ok(patched)
|
||||
}
|
||||
|
||||
/// ### 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<()> {
|
||||
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,
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
39
packages/assets/styles/neon-button.scss
Normal file
39
packages/assets/styles/neon-button.scss
Normal file
@@ -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);
|
||||
}
|
||||
37
packages/assets/styles/neon-icon.scss
Normal file
37
packages/assets/styles/neon-icon.scss
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
28
packages/assets/styles/neon-text.scss
Normal file
28
packages/assets/styles/neon-text.scss
Normal file
@@ -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);
|
||||
}
|
||||
675
patches/elyby-integration.patch
Normal file
675
patches/elyby-integration.patch
Normal file
@@ -0,0 +1,675 @@
|
||||
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,
|
||||
122
readme/ru_ru/README.md
Normal file
122
readme/ru_ru/README.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# 📘 Навигация
|
||||
|
||||
- [🔧 Установка](#установка)
|
||||
- [✨ Возможности](#возможности)
|
||||
- [🚀 Начало работы](#начало-работы)
|
||||
- [⚠️ Отказ от ответственности](#отказ-от-ответственности)
|
||||
- [💰 Поддержать](#поддержать-проект-крипто-кошельки)
|
||||
|
||||
## Другие языки
|
||||
> [English](../../README.md)
|
||||
|
||||
## Канал поддержки
|
||||
> [Telegram](https://me.astralium.su/ref/telegram_channel)
|
||||
|
||||
---
|
||||
|
||||
# О проекте
|
||||
|
||||
## **AstralRinth • Раскрой потенциал своих приключений в Minecraft**
|
||||
|
||||
Добро пожаловать в **AstralRinth (AR)** — мощный форк Modrinth, переосмысленный для улучшения твоего опыта в Minecraft. Независимо от того, являешься ли ты поклонником GUI или разработчиком, использующим API Modrinth, **Theseus Core** — это твоя стартовая платформа для новой эры игрового процесса в Minecraft.
|
||||
|
||||
- *С недавнего времени имеется улучшенная интеграция с Git Astralium API*
|
||||
|
||||
## **О программе**
|
||||
|
||||
**AstralRinth** — это специализированная ветка проекта Theseus, сосредоточенная на **офлайн-аутентификации**, предоставляя ещё большую гибкость и ориентированность на пользователя. Путешествуй по мирам Minecraft без необходимости онлайн-авторизации — благодаря AstralRinth.
|
||||
|
||||
## **AR • Открой безграничные горизонты Minecraft**
|
||||
|
||||
Уникальный форк с фокусом на предоставление **пробного игрового опыта Minecraft** — без необходимости лицензии. В настоящее время включает:
|
||||
|
||||
---
|
||||
|
||||
# Установка
|
||||
|
||||
Чтобы установить лаунчер:
|
||||
|
||||
1. Перейдите на [страницу релизов](https://git.astralium.su/didirus/AstralRinth/releases), чтобы скачать нужную версию для вашей системы.
|
||||
2. После загрузки нужного исполняемого файла или архива — откройте его.
|
||||
|
||||
### Поддерживаемые расширения файлов
|
||||
|
||||
| Расширение | ОС | Примечания |
|
||||
| ---------- | ------- | --------------------------------------------------------------------- |
|
||||
| `.msi` | Windows | Поддерживаются актуальные версии Microsoft Windows |
|
||||
| `.dmg` | macOS | Работает на MacOS Ventura / Sonoma / Sequoia, возможно и на старых |
|
||||
| `.deb` | Linux | Так как дистрибутивов много, совместимость не гарантируется полностью |
|
||||
|
||||
### Особенности установки
|
||||
|
||||
Сборки с префиксами ниже **не рекомендуются к установке** и могут содержать ошибки:
|
||||
|
||||
- `dev`
|
||||
- `nightly`
|
||||
- `dirty`
|
||||
- `dirty-dev`
|
||||
- `dirty-nightly`
|
||||
- `dirty_dev`
|
||||
- `dirty_nightly`
|
||||
|
||||
---
|
||||
|
||||
# Возможности
|
||||
|
||||
> _Лаунчер предоставляет возможность использовать знакомый Modrinth с улучшенным пользовательским интерфейсом._
|
||||
|
||||
## Уникальные функции
|
||||
|
||||
- Отсутствие рекламы во всём лаунчере.
|
||||
- Кастомные `.svg` векторы для персонализированного интерфейса.
|
||||
- Улучшенная совместимость с лицензионными и пиратскими аккаунтами.
|
||||
- Используйте **официальные аккаунты Microsoft** или **пиратские аккаунты** — без сбоев в авторизации.
|
||||
- Поддержка безлицензионного доступа для тестирования или личного использования.
|
||||
- Нет зависимости от официальных сервисов авторизации.
|
||||
- Интеграция Discord Rich Presence:
|
||||
- Случайные статусные сообщения.
|
||||
- Таймер игры и счётчик AFK.
|
||||
- Жёсткое отключение сбора статистики (метрик Modrinth) через патч AstralRinth — работает независимо от конфигурации.
|
||||
- Удаление рекламы из всех окон лаунчера.
|
||||
- Оптимизация архивов (пакетов).
|
||||
- Встроенная система получения обновлений:
|
||||
- Уведомления о новых версиях на Git.
|
||||
- Возможность автозагрузки и автоустановки.
|
||||
- Исправления миграции базы данных при ошибках (интерактивный режим, проблема Modrinth).
|
||||
- Интеграция системы скинов ElyBy (AuthLib / Java).
|
||||
|
||||
---
|
||||
|
||||
# Начало работы
|
||||
|
||||
Чтобы начать приключение с AstralRinth, выполните следующие шаги:
|
||||
|
||||
1. **Скачайте нужную версию под вашу ОС**
|
||||
|
||||
- Перейдите на [страницу релизов](https://git.astralium.su/didirus/AstralRinth/releases)
|
||||
- [**Как выбрать файл**](#поддерживаемые-расширения-файлов)
|
||||
- [**Как выбрать релиз**](#особенности-установки)
|
||||
|
||||
2. **Авторизация**
|
||||
|
||||
- Войдите с действующей лицензией или используйте пиратский аккаунт для теста AstralRinth.
|
||||
|
||||
3. **Запуск Minecraft**
|
||||
- Запустите Minecraft через AstralRinth и наслаждайтесь.
|
||||
- Лаунчер попытается автоматически определить рекомендованную JVM для запуска игры, но вы можете всё настроить вручную в настройках.
|
||||
|
||||
---
|
||||
|
||||
# Отказ от ответственности
|
||||
|
||||
- **AstralRinth** предназначен только для экспериментов и образовательных целей. Мы не поддерживаем пиратство и рекомендуем приобретать официальную лицензию Minecraft для полноценного игрового опыта.
|
||||
- Пользователям напоминается соблюдать лицензионные соглашения и поддерживать разработчиков Minecraft.
|
||||
|
||||
---
|
||||
|
||||
# Поддержать проект (крипто-кошельки)
|
||||
|
||||
Если вы хотите поддержать разработку, вы можете отправить донат на следующие кошельки:
|
||||
|
||||
- BTC (Telegram): 14g6asNYzcUoaQtB8B2QGKabgEvn55wfLj
|
||||
- TONCOIN (Telegram): UQAqUJ2_hVBI6k_gPyfp_jd-1K0OS61nIFPZuJWN9BwGAvKe
|
||||
Reference in New Issue
Block a user