You've already forked AstralRinth
Compare commits
16 Commits
f1b67c9584
...
AR-0.10.30
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d5d747202 | |||
| 7516ff9e47 | |||
| df9bbe3ba0 | |||
| 362fd7f32a | |||
| adf831dab9 | |||
| efeac22d14 | |||
| 591d98a9eb | |||
| 77472d9a09 | |||
| 789d666515 | |||
| d917bff6ef | |||
| 4e69cd8bde | |||
| b71e4cc6f9 | |||
| a56ab6adb9 | |||
| 14f6450cf4 | |||
| 97bd18c7b3 | |||
| 34d85a03b2 |
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)
|
||||
@@ -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 />
|
||||
|
||||
@@ -19,6 +19,7 @@ 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()
|
||||
@@ -168,6 +169,11 @@ async function onApplyMigrationFix(eol) {
|
||||
migrationFixSuccess.value = false
|
||||
} finally {
|
||||
migrationFixCallbackModel.value?.show?.()
|
||||
if (migrationFixSuccess.value === true) {
|
||||
setTimeout(async () => {
|
||||
await restartApp()
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,12 +327,12 @@ async function onApplyMigrationFix(eol) {
|
||||
<template v-if="copied"> <CheckIcon class="text-green" /> Copied! </template>
|
||||
<template v-else> <CopyIcon /> Copy debug info </template>
|
||||
</button>
|
||||
<ButtonStyled class="btn-wrapper neon">
|
||||
<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="btn-wrapper neon" >
|
||||
<ButtonStyled class="neon-button neon" >
|
||||
<a href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer">
|
||||
Checkout latest releases
|
||||
</a>
|
||||
@@ -334,7 +340,7 @@ async function onApplyMigrationFix(eol) {
|
||||
</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"
|
||||
@@ -346,7 +352,9 @@ async function onApplyMigrationFix(eol) {
|
||||
/>
|
||||
</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'">
|
||||
@@ -392,7 +400,7 @@ async function onApplyMigrationFix(eol) {
|
||||
<div class="flex justify-between">
|
||||
<ol class="flex flex-col gap-3">
|
||||
<li>
|
||||
<ButtonStyled class="btn-wrapper neon">
|
||||
<ButtonStyled class="neon-button neon">
|
||||
<button
|
||||
:title="language === 'en'
|
||||
? 'Convert all line endings in migration files to LF (Unix-style: \\n)'
|
||||
@@ -405,7 +413,7 @@ async function onApplyMigrationFix(eol) {
|
||||
</ButtonStyled>
|
||||
</li>
|
||||
<li>
|
||||
<ButtonStyled class="btn-wrapper neon">
|
||||
<ButtonStyled class="neon-button neon">
|
||||
<button
|
||||
:title="language === 'en'
|
||||
? 'Convert all line endings in migration files to CRLF (Windows-style: \\r\\n)'
|
||||
@@ -432,13 +440,13 @@ async function onApplyMigrationFix(eol) {
|
||||
<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 text-green-600">
|
||||
<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 text-gray-600">
|
||||
<p class="mt-2 text-sm neon-text">
|
||||
{{ language === 'en'
|
||||
? 'If the problem persists, please try the other fix.'
|
||||
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
|
||||
@@ -446,13 +454,13 @@ async function onApplyMigrationFix(eol) {
|
||||
</template>
|
||||
|
||||
<template v-else-if="migrationFixSuccess === false">
|
||||
<p class="flex items-center gap-2 text-red-600">
|
||||
<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 text-gray-600">
|
||||
<p class="mt-2 text-sm neon-text">
|
||||
{{ language === 'en'
|
||||
? 'If the problem persists, please try the other fix.'
|
||||
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
|
||||
@@ -476,6 +484,7 @@ async function onApplyMigrationFix(eol) {
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../../../../../packages/assets/styles/neon-button.scss';
|
||||
@import '../../../../../packages/assets/styles/neon-text.scss';
|
||||
|
||||
.cta-button {
|
||||
display: flex;
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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.303",
|
||||
"version": "0.10.304",
|
||||
"mainBinaryName": "AstralRinth App",
|
||||
"identifier": "AstralRinthApp",
|
||||
"plugins": {
|
||||
|
||||
@@ -1,42 +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 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("[AR] • Failed to determine download directory")?;
|
||||
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?;
|
||||
println!("[AR] • File downloaded to: {:?}", full_path);
|
||||
tracing::info!("[AR] • 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("[AR] • 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("[AR] • Failed to execute command");
|
||||
} else {
|
||||
status = Command::new(".")
|
||||
.arg(full_path.to_str().unwrap_or_default())
|
||||
.status()
|
||||
.await
|
||||
.expect("[AR] • Failed to execute command");
|
||||
}
|
||||
if status.success() {
|
||||
println!("[AR] • File opened successfully!");
|
||||
} else {
|
||||
eprintln!("[AR] • Failed to open the file. Exit code: {:?}", status.code());
|
||||
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)]
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::state::DirectoryInfo;
|
||||
use crate::ErrorKind;
|
||||
use crate::state::DirectoryInfo;
|
||||
use sqlx::sqlite::{
|
||||
SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions,
|
||||
};
|
||||
@@ -78,23 +78,23 @@ Problem files, view detailed information in .gitattributes:
|
||||
CRLF -> 4c47e326f16f2b1efca548076ce638d4c90dd610172fe48c47d6de9bc46ef1c5abeadfdea05041ddd72c3819fa10c040
|
||||
LF -> e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21
|
||||
/packages/app-lib/migrations/20240813205023_drop-active-unique.sql !eol
|
||||
CRLF -> 10f4a494df6fd791a093cc61401ecf3f9750fa6b97aa304ab06e29671e446586240910ffbf806f6ddc484a756770dde9
|
||||
CRLF -> C8FD2EFE72E66E394732599EA8D93CE1ED337F098697B3ADAD40DD37CC6367893E199A8D7113B44A3D0FFB537692F91D
|
||||
LF -> 5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206
|
||||
/packages/app-lib/migrations/20240930001852_disable-personalized-ads.sql !eol
|
||||
CRLF -> c8028ec3a2e61d15586e2f69ad6c6be5ac03b95918c2014cefb183ed6c254a52aad6f9ce98cda13ad545da3398574702
|
||||
CRLF -> C0DE804F171B5530010EDAE087A6E75645C0E90177E28365F935C9FDD9A5C68E24850B8C1498E386A379D525D520BC57
|
||||
LF -> c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57
|
||||
/packages/app-lib/migrations/20241222013857_feature-flags.sql !eol
|
||||
CRLF -> f8c55065e2563fa4738976eb13a052ae4c28da8d33143185550f6e1cee394a3243b1dca090b3e8bc50a93a8286a78c09
|
||||
CRLF -> 6B6F097E5BB45A397C96C3F1DC9C2A18433564E81DB264FE08A4775198CCEAC03C9E63C3605994ECB19C281C37D8F6AE
|
||||
LF -> c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704
|
||||
*/
|
||||
pub(crate) async fn fix_version_hash(
|
||||
eol: &str,
|
||||
) -> crate::Result<bool> {
|
||||
pub(crate) async fn apply_migration_fix(eol: &str) -> crate::Result<bool> {
|
||||
let started = Instant::now();
|
||||
|
||||
// Create connection to the database without migrations
|
||||
let pool = connect_without_migrate().await?;
|
||||
tracing::info!("⚙️ Patching Modrinth corrupted migration checksums using EOL standard: {eol}");
|
||||
tracing::info!(
|
||||
"⚙️ Patching Modrinth corrupted migration checksums using EOL standard: {eol}"
|
||||
);
|
||||
|
||||
// validate EOL input
|
||||
if eol != "lf" && eol != "crlf" {
|
||||
@@ -117,7 +117,7 @@ pub(crate) async fn fix_version_hash(
|
||||
),
|
||||
(
|
||||
("crlf", "20240813205023"),
|
||||
"10f4a494df6fd791a093cc61401ecf3f9750fa6b97aa304ab06e29671e446586240910ffbf806f6ddc484a756770dde9",
|
||||
"C8FD2EFE72E66E394732599EA8D93CE1ED337F098697B3ADAD40DD37CC6367893E199A8D7113B44A3D0FFB537692F91D",
|
||||
),
|
||||
(
|
||||
("lf", "20240930001852"),
|
||||
@@ -125,7 +125,7 @@ pub(crate) async fn fix_version_hash(
|
||||
),
|
||||
(
|
||||
("crlf", "20240930001852"),
|
||||
"c8028ec3a2e61d15586e2f69ad6c6be5ac03b95918c2014cefb183ed6c254a52aad6f9ce98cda13ad545da3398574702",
|
||||
"C0DE804F171B5530010EDAE087A6E75645C0E90177E28365F935C9FDD9A5C68E24850B8C1498E386A379D525D520BC57",
|
||||
),
|
||||
(
|
||||
("lf", "20241222013857"),
|
||||
@@ -133,7 +133,7 @@ pub(crate) async fn fix_version_hash(
|
||||
),
|
||||
(
|
||||
("crlf", "20241222013857"),
|
||||
"f8c55065e2563fa4738976eb13a052ae4c28da8d33143185550f6e1cee394a3243b1dca090b3e8bc50a93a8286a78c09",
|
||||
"6B6F097E5BB45A397C96C3F1DC9C2A18433564E81DB264FE08A4775198CCEAC03C9E63C3605994ECB19C281C37D8F6AE",
|
||||
),
|
||||
]);
|
||||
|
||||
|
||||
@@ -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,16 +17,314 @@ pub struct Launcher {
|
||||
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::fix_version_hash(eol).await?;
|
||||
let patched = db::apply_migration_fix(eol).await?;
|
||||
if patched {
|
||||
tracing::info!("[AR] • Successfully applied migration fix");
|
||||
} else {
|
||||
@@ -35,15 +333,20 @@ 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);
|
||||
if let Err(e) = update::download_file(
|
||||
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,
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
// [AR] Feature
|
||||
.btn-wrapper.neon :deep(:is(button, a, .button-like):first-child),
|
||||
.btn-wrapper.neon :slotted(:is(button, a, .button-like):first-child),
|
||||
.btn-wrapper.neon :slotted(*) > :is(button, a, .button-like):first-child,
|
||||
.btn-wrapper.neon :slotted(*) > *:first-child > :is(button, a, .button-like):first-child,
|
||||
.btn-wrapper.neon
|
||||
:slotted(*)
|
||||
> *:first-child
|
||||
> *:first-child
|
||||
> :is(button, a, .button-like):first-child {
|
||||
.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;
|
||||
@@ -22,20 +17,17 @@
|
||||
box-shadow: 0 0 4px rgba(79, 173, 255, 0.5);
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* Hover */
|
||||
.btn-wrapper.neon
|
||||
:deep(:is(button, a, .button-like):first-child):hover:not([disabled]):not(.disabled),
|
||||
.btn-wrapper.neon
|
||||
:slotted(:is(button, a, .button-like):first-child):hover:not([disabled]):not(.disabled),
|
||||
.btn-wrapper.neon
|
||||
:slotted(*) > :is(button, a, .button-like):first-child:hover:not([disabled]):not(.disabled),
|
||||
.btn-wrapper.neon
|
||||
:slotted(*) > *:first-child > :is(button, a, .button-like):first-child:hover:not([disabled]):not(.disabled),
|
||||
.btn-wrapper.neon
|
||||
:slotted(*)
|
||||
> *:first-child
|
||||
> *:first-child
|
||||
> :is(button, a, .button-like):first-child:hover:not([disabled]):not(.disabled) {
|
||||
.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:
|
||||
|
||||
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