Compare commits

..

2 Commits

Author SHA1 Message Date
Daniel Scalzi
b9536ed014
Initial work on react.
Far from done.
Far from working.
Requires rewrite of UI logic using react patterns.
2020-03-08 20:40:37 -04:00
Daniel Scalzi
9cb10b70af
Working on typescript conversion, not functional yet. 2020-01-26 01:12:48 -05:00
160 changed files with 18874 additions and 10923 deletions

View File

@ -1,11 +1,11 @@
{
"env": {
"es2022": true,
"es2017": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2022,
"ecmaVersion": 2019,
"sourceType": "module"
},
"rules": {
@ -52,7 +52,7 @@
},
"overrides": [
{
"files": [ "app/assets/js/scripts/*.js" ],
"files": [ "src/scripts/*.js" ],
"rules": {
"no-unused-vars": [
0

3
.github/FUNDING.yml vendored
View File

@ -1,3 +0,0 @@
github: dscalzi
patreon: dscalzi
custom: ['https://www.paypal.me/dscalzi']

View File

@ -1,38 +0,0 @@
name: Build
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
permissions:
contents: write
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
node-version: 20
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.x
- name: Install Dependencies
run: npm ci
shell: bash
- name: Build
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm run dist
shell: bash

3
.gitignore vendored
View File

@ -3,4 +3,5 @@
/.vscode/
/target/
/logs/
/dist/
/dist/
/out/

2
.nvmrc
View File

@ -1 +1 @@
20
12

45
.travis.yml Normal file
View File

@ -0,0 +1,45 @@
matrix:
include:
- os: osx
osx_image: xcode11.3
language: node_js
node_js: "12"
env:
- ELECTRON_CACHE=$HOME/.cache/electron
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
- ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=true
- CSC_IDENTITY_AUTO_DISCOVERY=false
- os: linux
services: docker
language: generic
node_js: "12"
env:
- ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=true
cache:
directories:
- node_modules
- $HOME/.cache/electron
- $HOME/.cache/electron-builder
script:
- |
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
ENVS=`env | grep -iE '(DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_)' | sed -n '/^[^\t]/s/=.*//p' | sed '/^$/d' | sed 's/^/-e /g' | tr '\n' ' '`
docker run $ENVS --rm \
-v ${PWD}:/project \
-v ~/.cache/electron:/root/.cache/electron \
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
electronuserland/builder:wine \
/bin/bash -c "node -v && npm ci && npm run cilinux"
else
npm run cidarwin
fi
before_cache:
- rm -rf $HOME/.cache/electron-builder/wine
branches:
except:
- "/^v\\d+\\.\\d+\\.\\d+$/"

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017-2024 Daniel D. Scalzi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,10 +1,10 @@
<p align="center"><img src="./app/assets/images/SealCircle.png" width="150px" height="150px" alt="aventium softworks"></p>
<h1 align="center">ONIMAI.RU MC Launcher</h1>
<h1 align="center">Helios Launcher</h1>
<em><h5 align="center">(formerly Electron Launcher)</h5></em>
[<p align="center"><img src="https://img.shields.io/github/actions/workflow/status/dscalzi/HeliosLauncher/build.yml?branch=master&style=for-the-badge" alt="gh actions">](https://github.com/dscalzi/HeliosLauncher/actions) [<img src="https://img.shields.io/github/downloads/dscalzi/HeliosLauncher/total.svg?style=for-the-badge" alt="downloads">](https://github.com/dscalzi/HeliosLauncher/releases) <img src="https://forthebadge.com/images/badges/winter-is-coming.svg" height="28px" alt="winter-is-coming"></p>
[<p align="center"><img src="https://img.shields.io/travis/dscalzi/HeliosLauncher.svg?style=for-the-badge" alt="travis">](https://travis-ci.org/dscalzi/HeliosLauncher) [<img src="https://img.shields.io/github/downloads/dscalzi/HeliosLauncher/total.svg?style=for-the-badge" alt="downloads">](https://github.com/dscalzi/HeliosLauncher/releases) <img src="https://forthebadge.com/images/badges/winter-is-coming.svg" height="28px" alt="stark"></p>
<p align="center">Join modded servers without worrying about installing Java, Forge, or other mods. We'll handle that for you.</p>
@ -15,7 +15,6 @@
* 🔒 Full account management.
* Add multiple accounts and easily switch between them.
* Microsoft (OAuth 2.0) + Mojang (Yggdrasil) authentication fully supported.
* Credentials are never stored and transmitted directly to Mojang.
* 📂 Efficient asset management.
* Receive client updates as soon as we release them.
@ -54,10 +53,9 @@ If you download from the [Releases](https://github.com/dscalzi/HeliosLauncher/re
| Platform | File |
| -------- | ---- |
| Windows x64 | `Helios-Launcher-setup-VERSION.exe` |
| macOS x64 | `Helios-Launcher-setup-VERSION-x64.dmg` |
| macOS arm64 | `Helios-Launcher-setup-VERSION-arm64.dmg` |
| Linux x64 | `Helios-Launcher-setup-VERSION.AppImage` |
| Windows x64 | `helioslauncher-setup-VERSION.exe` |
| macOS | `helioslauncher-VERSION.dmg` |
| Linux x64 | `helioslauncher-VERSION-x86_64.AppImage` |
## Console
@ -78,13 +76,11 @@ If you want to export the console output, simply right click anywhere on the con
## Development
This section details the setup of a basic developmentment environment.
### Getting Started
**System Requirements**
* [Node.js][nodejs] v20
* [Node.js][nodejs] v12
---
@ -140,24 +136,28 @@ Paste the following into `.vscode/launch.json`
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/node_modules/electron/cli.js",
"args" : ["."],
"outputCapture": "std"
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"args": ["."],
"console": "integratedTerminal",
"protocol": "inspector"
},
{
"name": "Debug Renderer Process",
"type": "chrome",
"request": "launch",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
},
"runtimeArgs": [
"${workspaceFolder}/.",
"${workspaceRoot}/.",
"--remote-debugging-port=9222"
],
"webRoot": "${workspaceFolder}"
"webRoot": "${workspaceRoot}"
}
]
}
@ -179,17 +179,20 @@ Note that you **cannot** open the DevTools window while using this debug configu
### Note on Third-Party Usage
Please give credit to the original author and provide a link to the original source. This is free software, please do at least this much.
You may use this software in your own project so long as the following conditions are met.
For instructions on setting up Microsoft Authentication, see https://github.com/dscalzi/HeliosLauncher/blob/master/docs/MicrosoftAuth.md.
* Credit is expressly given to the original authors (Daniel Scalzi).
* Include a link to the original source on the launcher's About page.
* Credit the authors and provide a link to the original source in any publications or download pages.
* The source code remain **public** as a fork of this repository.
We reserve the right to update these conditions at any time, please check back periodically.
---
## Resources
* [Wiki][wiki]
* [Nebula (Create Distribution.json)][nebula]
* [v2 Rewrite Branch (Inactive)][v2branch]
The best way to contact the developers is on Discord.
@ -207,5 +210,3 @@ The best way to contact the developers is on Discord.
[chromedebugger]: https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome 'Debugger for Chrome'
[discord]: https://discord.gg/zNWUXdt 'Discord'
[wiki]: https://github.com/dscalzi/HeliosLauncher/wiki 'wiki'
[nebula]: https://github.com/dscalzi/Nebula 'dscalzi/Nebula'
[v2branch]: https://github.com/dscalzi/HeliosLauncher/tree/ts-refactor 'v2 branch'

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 580 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 KiB

View File

@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23 23">
<path fill="#f3f3f3" d="M0 0h23v23H0z" />
<path fill="#f35325" d="M1 1h10v10H1z" />
<path fill="#81bc06" d="M12 1h10v10H12z" />
<path fill="#05a6f0" d="M1 12h10v10H1z" />
<path fill="#ffba08" d="M12 12h10v10H12z" />
</svg>

Before

Width:  |  Height:  |  Size: 303 B

View File

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 9.677 9.667">
<path d="M-26.332-12.098h2.715c-1.357.18-2.574 1.23-2.715 2.633z" fill="#fff" />
<path d="M2.598.022h7.07L9.665 7c-.003 1.334-1.113 2.46-2.402 2.654H0V2.542C.134 1.2 1.3.195 2.598.022z" fill="#db2331" />
<path d="M1.54 2.844c.314-.76 1.31-.46 1.954-.528.785-.083 1.503.272 2.1.758l.164-.9c.327.345.587.756.964 1.052.28.254.655-.342.86-.013.42.864.408 1.86.54 2.795l-.788-.373C6.9 4.17 5.126 3.052 3.656 3.685c-1.294.592-1.156 2.65.06 3.255 1.354.703 2.953.51 4.405.292-.07.42-.34.87-.834.816l-4.95.002c-.5.055-.886-.413-.838-.89l.04-4.315z" fill="#fff" />
</svg>

Before

Width:  |  Height:  |  Size: 664 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,447 +0,0 @@
/**
* AuthManager
*
* This module aims to abstract login procedures. Results from Mojang's REST api
* are retrieved through our Mojang module. These results are processed and stored,
* if applicable, in the config using the ConfigManager. All login procedures should
* be made through this module.
*
* @module authmanager
*/
// Requirements
const ConfigManager = require('./configmanager')
const { LoggerUtil } = require('helios-core')
const { RestResponseStatus } = require('helios-core/common')
const { MojangRestAPI, MojangErrorCode } = require('helios-core/mojang')
const { MicrosoftAuth, MicrosoftErrorCode } = require('helios-core/microsoft')
const { AZURE_CLIENT_ID } = require('./ipcconstants')
const Lang = require('./langloader')
const log = LoggerUtil.getLogger('AuthManager')
// Error messages
function microsoftErrorDisplayable(errorCode) {
switch (errorCode) {
case MicrosoftErrorCode.NO_PROFILE:
return {
title: Lang.queryJS('auth.microsoft.error.noProfileTitle'),
desc: Lang.queryJS('auth.microsoft.error.noProfileDesc')
}
case MicrosoftErrorCode.NO_XBOX_ACCOUNT:
return {
title: Lang.queryJS('auth.microsoft.error.noXboxAccountTitle'),
desc: Lang.queryJS('auth.microsoft.error.noXboxAccountDesc')
}
case MicrosoftErrorCode.XBL_BANNED:
return {
title: Lang.queryJS('auth.microsoft.error.xblBannedTitle'),
desc: Lang.queryJS('auth.microsoft.error.xblBannedDesc')
}
case MicrosoftErrorCode.UNDER_18:
return {
title: Lang.queryJS('auth.microsoft.error.under18Title'),
desc: Lang.queryJS('auth.microsoft.error.under18Desc')
}
case MicrosoftErrorCode.UNKNOWN:
return {
title: Lang.queryJS('auth.microsoft.error.unknownTitle'),
desc: Lang.queryJS('auth.microsoft.error.unknownDesc')
}
}
}
function mojangErrorDisplayable(errorCode) {
switch(errorCode) {
case MojangErrorCode.ERROR_METHOD_NOT_ALLOWED:
return {
title: Lang.queryJS('auth.mojang.error.methodNotAllowedTitle'),
desc: Lang.queryJS('auth.mojang.error.methodNotAllowedDesc')
}
case MojangErrorCode.ERROR_NOT_FOUND:
return {
title: Lang.queryJS('auth.mojang.error.notFoundTitle'),
desc: Lang.queryJS('auth.mojang.error.notFoundDesc')
}
case MojangErrorCode.ERROR_USER_MIGRATED:
return {
title: Lang.queryJS('auth.mojang.error.accountMigratedTitle'),
desc: Lang.queryJS('auth.mojang.error.accountMigratedDesc')
}
case MojangErrorCode.ERROR_INVALID_CREDENTIALS:
return {
title: Lang.queryJS('auth.mojang.error.invalidCredentialsTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidCredentialsDesc')
}
case MojangErrorCode.ERROR_RATELIMIT:
return {
title: Lang.queryJS('auth.mojang.error.tooManyAttemptsTitle'),
desc: Lang.queryJS('auth.mojang.error.tooManyAttemptsDesc')
}
case MojangErrorCode.ERROR_INVALID_TOKEN:
return {
title: Lang.queryJS('auth.mojang.error.invalidTokenTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidTokenDesc')
}
case MojangErrorCode.ERROR_ACCESS_TOKEN_HAS_PROFILE:
return {
title: Lang.queryJS('auth.mojang.error.tokenHasProfileTitle'),
desc: Lang.queryJS('auth.mojang.error.tokenHasProfileDesc')
}
case MojangErrorCode.ERROR_CREDENTIALS_MISSING:
return {
title: Lang.queryJS('auth.mojang.error.credentialsMissingTitle'),
desc: Lang.queryJS('auth.mojang.error.credentialsMissingDesc')
}
case MojangErrorCode.ERROR_INVALID_SALT_VERSION:
return {
title: Lang.queryJS('auth.mojang.error.invalidSaltVersionTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidSaltVersionDesc')
}
case MojangErrorCode.ERROR_UNSUPPORTED_MEDIA_TYPE:
return {
title: Lang.queryJS('auth.mojang.error.unsupportedMediaTypeTitle'),
desc: Lang.queryJS('auth.mojang.error.unsupportedMediaTypeDesc')
}
case MojangErrorCode.ERROR_GONE:
return {
title: Lang.queryJS('auth.mojang.error.accountGoneTitle'),
desc: Lang.queryJS('auth.mojang.error.accountGoneDesc')
}
case MojangErrorCode.ERROR_UNREACHABLE:
return {
title: Lang.queryJS('auth.mojang.error.unreachableTitle'),
desc: Lang.queryJS('auth.mojang.error.unreachableDesc')
}
case MojangErrorCode.ERROR_NOT_PAID:
return {
title: Lang.queryJS('auth.mojang.error.gameNotPurchasedTitle'),
desc: Lang.queryJS('auth.mojang.error.gameNotPurchasedDesc')
}
case MojangErrorCode.UNKNOWN:
return {
title: Lang.queryJS('auth.mojang.error.unknownErrorTitle'),
desc: Lang.queryJS('auth.mojang.error.unknownErrorDesc')
}
default:
throw new Error(`Unknown error code: ${errorCode}`)
}
}
// Functions
/**
* Add a Mojang account. This will authenticate the given credentials with Mojang's
* authserver. The resultant data will be stored as an auth account in the
* configuration database.
*
* @param {string} username The account username (email if migrated).
* @param {string} password The account password.
* @returns {Promise.<Object>} Promise which resolves the resolved authenticated account object.
*/
exports.addMojangAccount = async function(username, password) {
try {
const response = await MojangRestAPI.authenticate(username, password, ConfigManager.getClientToken())
console.log(response)
if(response.responseStatus === RestResponseStatus.SUCCESS) {
const session = response.data
if(session.selectedProfile != null){
const ret = ConfigManager.addMojangAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name)
if(ConfigManager.getClientToken() == null){
ConfigManager.setClientToken(session.clientToken)
}
ConfigManager.save()
return ret
} else {
return Promise.reject(mojangErrorDisplayable(MojangErrorCode.ERROR_NOT_PAID))
}
} else {
return Promise.reject(mojangErrorDisplayable(response.mojangErrorCode))
}
} catch (err){
log.error(err)
return Promise.reject(mojangErrorDisplayable(MojangErrorCode.UNKNOWN))
}
}
exports.addOfflineAccount = async function(username) {
try {
const ret = ConfigManager.addOfflineAccount(username)
ConfigManager.save()
return ret
} catch (err){
log.error(err)
return Promise.reject(mojangErrorDisplayable(MojangErrorCode.UNKNOWN))
}
}
const AUTH_MODE = { FULL: 0, MS_REFRESH: 1, MC_REFRESH: 2 }
/**
* Perform the full MS Auth flow in a given mode.
*
* AUTH_MODE.FULL = Full authorization for a new account.
* AUTH_MODE.MS_REFRESH = Full refresh authorization.
* AUTH_MODE.MC_REFRESH = Refresh of the MC token, reusing the MS token.
*
* @param {string} entryCode FULL-AuthCode. MS_REFRESH=refreshToken, MC_REFRESH=accessToken
* @param {*} authMode The auth mode.
* @returns An object with all auth data. AccessToken object will be null when mode is MC_REFRESH.
*/
async function fullMicrosoftAuthFlow(entryCode, authMode) {
try {
let accessTokenRaw
let accessToken
if(authMode !== AUTH_MODE.MC_REFRESH) {
const accessTokenResponse = await MicrosoftAuth.getAccessToken(entryCode, authMode === AUTH_MODE.MS_REFRESH, AZURE_CLIENT_ID)
if(accessTokenResponse.responseStatus === RestResponseStatus.ERROR) {
return Promise.reject(microsoftErrorDisplayable(accessTokenResponse.microsoftErrorCode))
}
accessToken = accessTokenResponse.data
accessTokenRaw = accessToken.access_token
} else {
accessTokenRaw = entryCode
}
const xblResponse = await MicrosoftAuth.getXBLToken(accessTokenRaw)
if(xblResponse.responseStatus === RestResponseStatus.ERROR) {
return Promise.reject(microsoftErrorDisplayable(xblResponse.microsoftErrorCode))
}
const xstsResonse = await MicrosoftAuth.getXSTSToken(xblResponse.data)
if(xstsResonse.responseStatus === RestResponseStatus.ERROR) {
return Promise.reject(microsoftErrorDisplayable(xstsResonse.microsoftErrorCode))
}
const mcTokenResponse = await MicrosoftAuth.getMCAccessToken(xstsResonse.data)
if(mcTokenResponse.responseStatus === RestResponseStatus.ERROR) {
return Promise.reject(microsoftErrorDisplayable(mcTokenResponse.microsoftErrorCode))
}
const mcProfileResponse = await MicrosoftAuth.getMCProfile(mcTokenResponse.data.access_token)
if(mcProfileResponse.responseStatus === RestResponseStatus.ERROR) {
return Promise.reject(microsoftErrorDisplayable(mcProfileResponse.microsoftErrorCode))
}
return {
accessToken,
accessTokenRaw,
xbl: xblResponse.data,
xsts: xstsResonse.data,
mcToken: mcTokenResponse.data,
mcProfile: mcProfileResponse.data
}
} catch(err) {
log.error(err)
return Promise.reject(microsoftErrorDisplayable(MicrosoftErrorCode.UNKNOWN))
}
}
/**
* Calculate the expiry date. Advance the expiry time by 10 seconds
* to reduce the liklihood of working with an expired token.
*
* @param {number} nowMs Current time milliseconds.
* @param {number} epiresInS Expires in (seconds)
* @returns
*/
function calculateExpiryDate(nowMs, epiresInS) {
return nowMs + ((epiresInS-10)*1000)
}
/**
* Add a Microsoft account. This will pass the provided auth code to Mojang's OAuth2.0 flow.
* The resultant data will be stored as an auth account in the configuration database.
*
* @param {string} authCode The authCode obtained from microsoft.
* @returns {Promise.<Object>} Promise which resolves the resolved authenticated account object.
*/
exports.addMicrosoftAccount = async function(authCode) {
const fullAuth = await fullMicrosoftAuthFlow(authCode, AUTH_MODE.FULL)
// Advance expiry by 10 seconds to avoid close calls.
const now = new Date().getTime()
const ret = ConfigManager.addMicrosoftAuthAccount(
fullAuth.mcProfile.id,
fullAuth.mcToken.access_token,
fullAuth.mcProfile.name,
calculateExpiryDate(now, fullAuth.mcToken.expires_in),
fullAuth.accessToken.access_token,
fullAuth.accessToken.refresh_token,
calculateExpiryDate(now, fullAuth.accessToken.expires_in)
)
ConfigManager.save()
return ret
}
/**
* Remove a Mojang account. This will invalidate the access token associated
* with the account and then remove it from the database.
*
* @param {string} uuid The UUID of the account to be removed.
* @returns {Promise.<void>} Promise which resolves to void when the action is complete.
*/
exports.removeMojangAccount = async function(uuid){
try {
const authAcc = ConfigManager.getAuthAccount(uuid)
const response = await MojangRestAPI.invalidate(authAcc.accessToken, ConfigManager.getClientToken())
if(response.responseStatus === RestResponseStatus.SUCCESS) {
ConfigManager.removeAuthAccount(uuid)
ConfigManager.save()
return Promise.resolve()
} else {
log.error('Error while removing account', response.error)
return Promise.reject(response.error)
}
} catch (err){
log.error('Error while removing account', err)
return Promise.reject(err)
}
}
/**
* Remove a Microsoft account. It is expected that the caller will invoke the OAuth logout
* through the ipc renderer.
*
* @param {string} uuid The UUID of the account to be removed.
* @returns {Promise.<void>} Promise which resolves to void when the action is complete.
*/
exports.removeMicrosoftAccount = async function(uuid){
try {
ConfigManager.removeAuthAccount(uuid)
ConfigManager.save()
return Promise.resolve()
} catch (err){
log.error('Error while removing account', err)
return Promise.reject(err)
}
}
exports.removeOfflineAccount = async function(uuid){
try {
ConfigManager.removeAuthAccount(uuid)
ConfigManager.save()
return Promise.resolve()
} catch (err){
log.error('Error while removing account', err)
return Promise.reject(err)
}
}
/**
* Validate the selected account with Mojang's authserver. If the account is not valid,
* we will attempt to refresh the access token and update that value. If that fails, a
* new login will be required.
*
* @returns {Promise.<boolean>} Promise which resolves to true if the access token is valid,
* otherwise false.
*/
async function validateSelectedMojangAccount(){
const current = ConfigManager.getSelectedAccount()
const response = await MojangRestAPI.validate(current.accessToken, ConfigManager.getClientToken())
if(response.responseStatus === RestResponseStatus.SUCCESS) {
const isValid = response.data
if(!isValid){
const refreshResponse = await MojangRestAPI.refresh(current.accessToken, ConfigManager.getClientToken())
if(refreshResponse.responseStatus === RestResponseStatus.SUCCESS) {
const session = refreshResponse.data
ConfigManager.updateMojangAuthAccount(current.uuid, session.accessToken)
ConfigManager.save()
} else {
log.error('Error while validating selected profile:', refreshResponse.error)
log.info('Account access token is invalid.')
return false
}
log.info('Account access token validated.')
return true
} else {
log.info('Account access token validated.')
return true
}
}
}
/**
* Validate the selected account with Microsoft's authserver. If the account is not valid,
* we will attempt to refresh the access token and update that value. If that fails, a
* new login will be required.
*
* @returns {Promise.<boolean>} Promise which resolves to true if the access token is valid,
* otherwise false.
*/
async function validateSelectedMicrosoftAccount(){
const current = ConfigManager.getSelectedAccount()
const now = new Date().getTime()
const mcExpiresAt = current.expiresAt
const mcExpired = now >= mcExpiresAt
if(!mcExpired) {
return true
}
// MC token expired. Check MS token.
const msExpiresAt = current.microsoft.expires_at
const msExpired = now >= msExpiresAt
if(msExpired) {
// MS expired, do full refresh.
try {
const res = await fullMicrosoftAuthFlow(current.microsoft.refresh_token, AUTH_MODE.MS_REFRESH)
ConfigManager.updateMicrosoftAuthAccount(
current.uuid,
res.mcToken.access_token,
res.accessToken.access_token,
res.accessToken.refresh_token,
calculateExpiryDate(now, res.accessToken.expires_in),
calculateExpiryDate(now, res.mcToken.expires_in)
)
ConfigManager.save()
return true
} catch(err) {
return false
}
} else {
// Only MC expired, use existing MS token.
try {
const res = await fullMicrosoftAuthFlow(current.microsoft.access_token, AUTH_MODE.MC_REFRESH)
ConfigManager.updateMicrosoftAuthAccount(
current.uuid,
res.mcToken.access_token,
current.microsoft.access_token,
current.microsoft.refresh_token,
current.microsoft.expires_at,
calculateExpiryDate(now, res.mcToken.expires_in)
)
ConfigManager.save()
return true
}
catch(err) {
return false
}
}
}
/**
* Validate the selected auth account.
*
* @returns {Promise.<boolean>} Promise which resolves to true if the access token is valid,
* otherwise false.
*/
exports.validateSelected = async function(){
const current = ConfigManager.getSelectedAccount()
if(current.type === 'microsoft') {
return await validateSelectedMicrosoftAccount()
} else {
return await validateSelectedMojangAccount()
}
}

View File

@ -1,808 +0,0 @@
const fs = require('fs-extra')
const { LoggerUtil } = require('helios-core')
const { randomUUID } = require('crypto')
const os = require('os')
const path = require('path')
const logger = LoggerUtil.getLogger('ConfigManager')
const sysRoot = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME)
const dataPath = path.join(sysRoot, '.helioslauncher')
const launcherDir = require('@electron/remote').app.getPath('userData')
/**
* Retrieve the absolute path of the launcher directory.
*
* @returns {string} The absolute path of the launcher directory.
*/
exports.getLauncherDirectory = function(){
return launcherDir
}
/**
* Get the launcher's data directory. This is where all files related
* to game launch are installed (common, instances, java, etc).
*
* @returns {string} The absolute path of the launcher's data directory.
*/
exports.getDataDirectory = function(def = false){
return !def ? config.settings.launcher.dataDirectory : DEFAULT_CONFIG.settings.launcher.dataDirectory
}
/**
* Set the new data directory.
*
* @param {string} dataDirectory The new data directory.
*/
exports.setDataDirectory = function(dataDirectory){
config.settings.launcher.dataDirectory = dataDirectory
}
const configPath = path.join(exports.getLauncherDirectory(), 'config.json')
const configPathLEGACY = path.join(dataPath, 'config.json')
const firstLaunch = !fs.existsSync(configPath) && !fs.existsSync(configPathLEGACY)
exports.getAbsoluteMinRAM = function(ram){
if(ram?.minimum != null) {
return ram.minimum/1024
} else {
// Legacy behavior
const mem = os.totalmem()
return mem >= (6*1073741824) ? 3 : 2
}
}
exports.getAbsoluteMaxRAM = function(ram){
const mem = os.totalmem()
const gT16 = mem-(16*1073741824)
return Math.floor((mem-(gT16 > 0 ? (Number.parseInt(gT16/8) + (16*1073741824)/4) : mem/4))/1073741824)
}
function resolveSelectedRAM(ram) {
if(ram?.recommended != null) {
return `${ram.recommended}M`
} else {
// Legacy behavior
const mem = os.totalmem()
return mem >= (8*1073741824) ? '4G' : (mem >= (6*1073741824) ? '3G' : '2G')
}
}
/**
* Three types of values:
* Static = Explicitly declared.
* Dynamic = Calculated by a private function.
* Resolved = Resolved externally, defaults to null.
*/
const DEFAULT_CONFIG = {
settings: {
game: {
resWidth: 1280,
resHeight: 720,
fullscreen: false,
autoConnect: true,
launchDetached: true
},
launcher: {
allowPrerelease: false,
dataDirectory: dataPath
}
},
newsCache: {
date: null,
content: null,
dismissed: false
},
clientToken: null,
selectedServer: null, // Resolved
selectedAccount: null,
authenticationDatabase: {},
modConfigurations: [],
javaConfig: {}
}
let config = null
// Persistance Utility Functions
/**
* Save the current configuration to a file.
*/
exports.save = function(){
fs.writeFileSync(configPath, JSON.stringify(config, null, 4), 'UTF-8')
}
/**
* Load the configuration into memory. If a configuration file exists,
* that will be read and saved. Otherwise, a default configuration will
* be generated. Note that "resolved" values default to null and will
* need to be externally assigned.
*/
exports.load = function(){
let doLoad = true
if(!fs.existsSync(configPath)){
// Create all parent directories.
fs.ensureDirSync(path.join(configPath, '..'))
if(fs.existsSync(configPathLEGACY)){
fs.moveSync(configPathLEGACY, configPath)
} else {
doLoad = false
config = DEFAULT_CONFIG
exports.save()
}
}
if(doLoad){
let doValidate = false
try {
config = JSON.parse(fs.readFileSync(configPath, 'UTF-8'))
doValidate = true
} catch (err){
logger.error(err)
logger.info('Configuration file contains malformed JSON or is corrupt.')
logger.info('Generating a new configuration file.')
fs.ensureDirSync(path.join(configPath, '..'))
config = DEFAULT_CONFIG
exports.save()
}
if(doValidate){
config = validateKeySet(DEFAULT_CONFIG, config)
exports.save()
}
}
logger.info('Successfully Loaded')
}
/**
* @returns {boolean} Whether or not the manager has been loaded.
*/
exports.isLoaded = function(){
return config != null
}
/**
* Validate that the destination object has at least every field
* present in the source object. Assign a default value otherwise.
*
* @param {Object} srcObj The source object to reference against.
* @param {Object} destObj The destination object.
* @returns {Object} A validated destination object.
*/
function validateKeySet(srcObj, destObj){
if(srcObj == null){
srcObj = {}
}
const validationBlacklist = ['authenticationDatabase', 'javaConfig']
const keys = Object.keys(srcObj)
for(let i=0; i<keys.length; i++){
if(typeof destObj[keys[i]] === 'undefined'){
destObj[keys[i]] = srcObj[keys[i]]
} else if(typeof srcObj[keys[i]] === 'object' && srcObj[keys[i]] != null && !(srcObj[keys[i]] instanceof Array) && validationBlacklist.indexOf(keys[i]) === -1){
destObj[keys[i]] = validateKeySet(srcObj[keys[i]], destObj[keys[i]])
}
}
return destObj
}
/**
* Check to see if this is the first time the user has launched the
* application. This is determined by the existance of the data path.
*
* @returns {boolean} True if this is the first launch, otherwise false.
*/
exports.isFirstLaunch = function(){
return firstLaunch
}
/**
* Returns the name of the folder in the OS temp directory which we
* will use to extract and store native dependencies for game launch.
*
* @returns {string} The name of the folder.
*/
exports.getTempNativeFolder = function(){
return 'WCNatives'
}
// System Settings (Unconfigurable on UI)
/**
* Retrieve the news cache to determine
* whether or not there is newer news.
*
* @returns {Object} The news cache object.
*/
exports.getNewsCache = function(){
return config.newsCache
}
/**
* Set the new news cache object.
*
* @param {Object} newsCache The new news cache object.
*/
exports.setNewsCache = function(newsCache){
config.newsCache = newsCache
}
/**
* Set whether or not the news has been dismissed (checked)
*
* @param {boolean} dismissed Whether or not the news has been dismissed (checked).
*/
exports.setNewsCacheDismissed = function(dismissed){
config.newsCache.dismissed = dismissed
}
/**
* Retrieve the common directory for shared
* game files (assets, libraries, etc).
*
* @returns {string} The launcher's common directory.
*/
exports.getCommonDirectory = function(){
return path.join(exports.getDataDirectory(), 'common')
}
/**
* Retrieve the instance directory for the per
* server game directories.
*
* @returns {string} The launcher's instance directory.
*/
exports.getInstanceDirectory = function(){
return path.join(exports.getDataDirectory(), 'instances')
}
/**
* Retrieve the launcher's Client Token.
* There is no default client token.
*
* @returns {string} The launcher's Client Token.
*/
exports.getClientToken = function(){
return config.clientToken
}
/**
* Set the launcher's Client Token.
*
* @param {string} clientToken The launcher's new Client Token.
*/
exports.setClientToken = function(clientToken){
config.clientToken = clientToken
}
/**
* Retrieve the ID of the selected serverpack.
*
* @param {boolean} def Optional. If true, the default value will be returned.
* @returns {string} The ID of the selected serverpack.
*/
exports.getSelectedServer = function(def = false){
return !def ? config.selectedServer : DEFAULT_CONFIG.clientToken
}
/**
* Set the ID of the selected serverpack.
*
* @param {string} serverID The ID of the new selected serverpack.
*/
exports.setSelectedServer = function(serverID){
config.selectedServer = serverID
}
/**
* Get an array of each account currently authenticated by the launcher.
*
* @returns {Array.<Object>} An array of each stored authenticated account.
*/
exports.getAuthAccounts = function(){
return config.authenticationDatabase
}
/**
* Returns the authenticated account with the given uuid. Value may
* be null.
*
* @param {string} uuid The uuid of the authenticated account.
* @returns {Object} The authenticated account with the given uuid.
*/
exports.getAuthAccount = function(uuid){
return config.authenticationDatabase[uuid]
}
/**
* Update the access token of an authenticated mojang account.
*
* @param {string} uuid The uuid of the authenticated account.
* @param {string} accessToken The new Access Token.
*
* @returns {Object} The authenticated account object created by this action.
*/
exports.updateMojangAuthAccount = function(uuid, accessToken){
config.authenticationDatabase[uuid].accessToken = accessToken
config.authenticationDatabase[uuid].type = 'mojang' // For gradual conversion.
return config.authenticationDatabase[uuid]
}
/**
* Adds an authenticated mojang account to the database to be stored.
*
* @param {string} uuid The uuid of the authenticated account.
* @param {string} accessToken The accessToken of the authenticated account.
* @param {string} username The username (usually email) of the authenticated account.
* @param {string} displayName The in game name of the authenticated account.
*
* @returns {Object} The authenticated account object created by this action.
*/
exports.addMojangAuthAccount = function(uuid, accessToken, username, displayName){
config.selectedAccount = uuid
config.authenticationDatabase[uuid] = {
type: 'mojang',
accessToken,
username: username.trim(),
uuid: uuid.trim(),
displayName: displayName.trim()
}
return config.authenticationDatabase[uuid]
}
exports.addOfflineAccount = function(username){
console.log("Yeah I try to create new offline account")
uuid = randomUUID()
config.selectedAccount = uuid
config.authenticationDatabase[uuid] = {
type: 'offline',
password: '',
username: username.trim(),
uuid: uuid.trim(),
displayName: username.trim()
}
return config.authenticationDatabase[uuid]
}
/**
* Update the tokens of an authenticated microsoft account.
*
* @param {string} uuid The uuid of the authenticated account.
* @param {string} accessToken The new Access Token.
* @param {string} msAccessToken The new Microsoft Access Token
* @param {string} msRefreshToken The new Microsoft Refresh Token
* @param {date} msExpires The date when the microsoft access token expires
* @param {date} mcExpires The date when the mojang access token expires
*
* @returns {Object} The authenticated account object created by this action.
*/
exports.updateMicrosoftAuthAccount = function(uuid, accessToken, msAccessToken, msRefreshToken, msExpires, mcExpires) {
config.authenticationDatabase[uuid].accessToken = accessToken
config.authenticationDatabase[uuid].expiresAt = mcExpires
config.authenticationDatabase[uuid].microsoft.access_token = msAccessToken
config.authenticationDatabase[uuid].microsoft.refresh_token = msRefreshToken
config.authenticationDatabase[uuid].microsoft.expires_at = msExpires
return config.authenticationDatabase[uuid]
}
/**
* Adds an authenticated microsoft account to the database to be stored.
*
* @param {string} uuid The uuid of the authenticated account.
* @param {string} accessToken The accessToken of the authenticated account.
* @param {string} name The in game name of the authenticated account.
* @param {date} mcExpires The date when the mojang access token expires
* @param {string} msAccessToken The microsoft access token
* @param {string} msRefreshToken The microsoft refresh token
* @param {date} msExpires The date when the microsoft access token expires
*
* @returns {Object} The authenticated account object created by this action.
*/
exports.addMicrosoftAuthAccount = function(uuid, accessToken, name, mcExpires, msAccessToken, msRefreshToken, msExpires) {
config.selectedAccount = uuid
config.authenticationDatabase[uuid] = {
type: 'microsoft',
accessToken,
username: name.trim(),
uuid: uuid.trim(),
displayName: name.trim(),
expiresAt: mcExpires,
microsoft: {
access_token: msAccessToken,
refresh_token: msRefreshToken,
expires_at: msExpires
}
}
return config.authenticationDatabase[uuid]
}
/**
* Remove an authenticated account from the database. If the account
* was also the selected account, a new one will be selected. If there
* are no accounts, the selected account will be null.
*
* @param {string} uuid The uuid of the authenticated account.
*
* @returns {boolean} True if the account was removed, false if it never existed.
*/
exports.removeAuthAccount = function(uuid){
if(config.authenticationDatabase[uuid] != null){
delete config.authenticationDatabase[uuid]
if(config.selectedAccount === uuid){
const keys = Object.keys(config.authenticationDatabase)
if(keys.length > 0){
config.selectedAccount = keys[0]
} else {
config.selectedAccount = null
config.clientToken = null
}
}
return true
}
return false
}
/**
* Get the currently selected authenticated account.
*
* @returns {Object} The selected authenticated account.
*/
exports.getSelectedAccount = function(){
return config.authenticationDatabase[config.selectedAccount]
}
/**
* Set the selected authenticated account.
*
* @param {string} uuid The UUID of the account which is to be set
* as the selected account.
*
* @returns {Object} The selected authenticated account.
*/
exports.setSelectedAccount = function(uuid){
const authAcc = config.authenticationDatabase[uuid]
if(authAcc != null) {
config.selectedAccount = uuid
}
return authAcc
}
/**
* Get an array of each mod configuration currently stored.
*
* @returns {Array.<Object>} An array of each stored mod configuration.
*/
exports.getModConfigurations = function(){
return config.modConfigurations
}
/**
* Set the array of stored mod configurations.
*
* @param {Array.<Object>} configurations An array of mod configurations.
*/
exports.setModConfigurations = function(configurations){
config.modConfigurations = configurations
}
/**
* Get the mod configuration for a specific server.
*
* @param {string} serverid The id of the server.
* @returns {Object} The mod configuration for the given server.
*/
exports.getModConfiguration = function(serverid){
const cfgs = config.modConfigurations
for(let i=0; i<cfgs.length; i++){
if(cfgs[i].id === serverid){
return cfgs[i]
}
}
return null
}
/**
* Set the mod configuration for a specific server. This overrides any existing value.
*
* @param {string} serverid The id of the server for the given mod configuration.
* @param {Object} configuration The mod configuration for the given server.
*/
exports.setModConfiguration = function(serverid, configuration){
const cfgs = config.modConfigurations
for(let i=0; i<cfgs.length; i++){
if(cfgs[i].id === serverid){
cfgs[i] = configuration
return
}
}
cfgs.push(configuration)
}
// User Configurable Settings
// Java Settings
function defaultJavaConfig(effectiveJavaOptions, ram) {
if(effectiveJavaOptions.suggestedMajor > 8) {
return defaultJavaConfig17(ram)
} else {
return defaultJavaConfig8(ram)
}
}
function defaultJavaConfig8(ram) {
return {
minRAM: resolveSelectedRAM(ram),
maxRAM: resolveSelectedRAM(ram),
executable: null,
jvmOptions: [
'-XX:+UseConcMarkSweepGC',
'-XX:+CMSIncrementalMode',
'-XX:-UseAdaptiveSizePolicy',
'-Xmn128M'
],
}
}
function defaultJavaConfig17(ram) {
return {
minRAM: resolveSelectedRAM(ram),
maxRAM: resolveSelectedRAM(ram),
executable: null,
jvmOptions: [
'-XX:+UnlockExperimentalVMOptions',
'-XX:+UseG1GC',
'-XX:G1NewSizePercent=20',
'-XX:G1ReservePercent=20',
'-XX:MaxGCPauseMillis=50',
'-XX:G1HeapRegionSize=32M'
],
}
}
/**
* Ensure a java config property is set for the given server.
*
* @param {string} serverid The server id.
* @param {*} mcVersion The minecraft version of the server.
*/
exports.ensureJavaConfig = function(serverid, effectiveJavaOptions, ram) {
if(!Object.prototype.hasOwnProperty.call(config.javaConfig, serverid)) {
config.javaConfig[serverid] = defaultJavaConfig(effectiveJavaOptions, ram)
}
}
/**
* Retrieve the minimum amount of memory for JVM initialization. This value
* contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
* 1024 MegaBytes, etc.
*
* @param {string} serverid The server id.
* @returns {string} The minimum amount of memory for JVM initialization.
*/
exports.getMinRAM = function(serverid){
return config.javaConfig[serverid].minRAM
}
/**
* Set the minimum amount of memory for JVM initialization. This value should
* contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
* 1024 MegaBytes, etc.
*
* @param {string} serverid The server id.
* @param {string} minRAM The new minimum amount of memory for JVM initialization.
*/
exports.setMinRAM = function(serverid, minRAM){
config.javaConfig[serverid].minRAM = minRAM
}
/**
* Retrieve the maximum amount of memory for JVM initialization. This value
* contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
* 1024 MegaBytes, etc.
*
* @param {string} serverid The server id.
* @returns {string} The maximum amount of memory for JVM initialization.
*/
exports.getMaxRAM = function(serverid){
return config.javaConfig[serverid].maxRAM
}
/**
* Set the maximum amount of memory for JVM initialization. This value should
* contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
* 1024 MegaBytes, etc.
*
* @param {string} serverid The server id.
* @param {string} maxRAM The new maximum amount of memory for JVM initialization.
*/
exports.setMaxRAM = function(serverid, maxRAM){
config.javaConfig[serverid].maxRAM = maxRAM
}
/**
* Retrieve the path of the Java Executable.
*
* This is a resolved configuration value and defaults to null until externally assigned.
*
* @param {string} serverid The server id.
* @returns {string} The path of the Java Executable.
*/
exports.getJavaExecutable = function(serverid){
return config.javaConfig[serverid].executable
}
/**
* Set the path of the Java Executable.
*
* @param {string} serverid The server id.
* @param {string} executable The new path of the Java Executable.
*/
exports.setJavaExecutable = function(serverid, executable){
config.javaConfig[serverid].executable = executable
}
/**
* Retrieve the additional arguments for JVM initialization. Required arguments,
* such as memory allocation, will be dynamically resolved and will not be included
* in this value.
*
* @param {string} serverid The server id.
* @returns {Array.<string>} An array of the additional arguments for JVM initialization.
*/
exports.getJVMOptions = function(serverid){
return config.javaConfig[serverid].jvmOptions
}
/**
* Set the additional arguments for JVM initialization. Required arguments,
* such as memory allocation, will be dynamically resolved and should not be
* included in this value.
*
* @param {string} serverid The server id.
* @param {Array.<string>} jvmOptions An array of the new additional arguments for JVM
* initialization.
*/
exports.setJVMOptions = function(serverid, jvmOptions){
config.javaConfig[serverid].jvmOptions = jvmOptions
}
// Game Settings
/**
* Retrieve the width of the game window.
*
* @param {boolean} def Optional. If true, the default value will be returned.
* @returns {number} The width of the game window.
*/
exports.getGameWidth = function(def = false){
return !def ? config.settings.game.resWidth : DEFAULT_CONFIG.settings.game.resWidth
}
/**
* Set the width of the game window.
*
* @param {number} resWidth The new width of the game window.
*/
exports.setGameWidth = function(resWidth){
config.settings.game.resWidth = Number.parseInt(resWidth)
}
/**
* Validate a potential new width value.
*
* @param {number} resWidth The width value to validate.
* @returns {boolean} Whether or not the value is valid.
*/
exports.validateGameWidth = function(resWidth){
const nVal = Number.parseInt(resWidth)
return Number.isInteger(nVal) && nVal >= 0
}
/**
* Retrieve the height of the game window.
*
* @param {boolean} def Optional. If true, the default value will be returned.
* @returns {number} The height of the game window.
*/
exports.getGameHeight = function(def = false){
return !def ? config.settings.game.resHeight : DEFAULT_CONFIG.settings.game.resHeight
}
/**
* Set the height of the game window.
*
* @param {number} resHeight The new height of the game window.
*/
exports.setGameHeight = function(resHeight){
config.settings.game.resHeight = Number.parseInt(resHeight)
}
/**
* Validate a potential new height value.
*
* @param {number} resHeight The height value to validate.
* @returns {boolean} Whether or not the value is valid.
*/
exports.validateGameHeight = function(resHeight){
const nVal = Number.parseInt(resHeight)
return Number.isInteger(nVal) && nVal >= 0
}
/**
* Check if the game should be launched in fullscreen mode.
*
* @param {boolean} def Optional. If true, the default value will be returned.
* @returns {boolean} Whether or not the game is set to launch in fullscreen mode.
*/
exports.getFullscreen = function(def = false){
return !def ? config.settings.game.fullscreen : DEFAULT_CONFIG.settings.game.fullscreen
}
/**
* Change the status of if the game should be launched in fullscreen mode.
*
* @param {boolean} fullscreen Whether or not the game should launch in fullscreen mode.
*/
exports.setFullscreen = function(fullscreen){
config.settings.game.fullscreen = fullscreen
}
/**
* Check if the game should auto connect to servers.
*
* @param {boolean} def Optional. If true, the default value will be returned.
* @returns {boolean} Whether or not the game should auto connect to servers.
*/
exports.getAutoConnect = function(def = false){
return !def ? config.settings.game.autoConnect : DEFAULT_CONFIG.settings.game.autoConnect
}
/**
* Change the status of whether or not the game should auto connect to servers.
*
* @param {boolean} autoConnect Whether or not the game should auto connect to servers.
*/
exports.setAutoConnect = function(autoConnect){
config.settings.game.autoConnect = autoConnect
}
/**
* Check if the game should launch as a detached process.
*
* @param {boolean} def Optional. If true, the default value will be returned.
* @returns {boolean} Whether or not the game will launch as a detached process.
*/
exports.getLaunchDetached = function(def = false){
return !def ? config.settings.game.launchDetached : DEFAULT_CONFIG.settings.game.launchDetached
}
/**
* Change the status of whether or not the game should launch as a detached process.
*
* @param {boolean} launchDetached Whether or not the game should launch as a detached process.
*/
exports.setLaunchDetached = function(launchDetached){
config.settings.game.launchDetached = launchDetached
}
// Launcher Settings
/**
* Check if the launcher should download prerelease versions.
*
* @param {boolean} def Optional. If true, the default value will be returned.
* @returns {boolean} Whether or not the launcher should download prerelease versions.
*/
exports.getAllowPrerelease = function(def = false){
return !def ? config.settings.launcher.allowPrerelease : DEFAULT_CONFIG.settings.launcher.allowPrerelease
}
/**
* Change the status of Whether or not the launcher should download prerelease versions.
*
* @param {boolean} launchDetached Whether or not the launcher should download prerelease versions.
*/
exports.setAllowPrerelease = function(allowPrerelease){
config.settings.launcher.allowPrerelease = allowPrerelease
}

View File

@ -1,52 +0,0 @@
// Work in progress
const { LoggerUtil } = require('helios-core')
const logger = LoggerUtil.getLogger('DiscordWrapper')
const { Client } = require('discord-rpc-patch')
const Lang = require('./langloader')
let client
let activity
exports.initRPC = function(genSettings, servSettings, initialDetails = Lang.queryJS('discord.waiting')){
client = new Client({ transport: 'ipc' })
activity = {
details: initialDetails,
state: Lang.queryJS('discord.state', {shortId: servSettings.shortId}),
largeImageKey: servSettings.largeImageKey,
largeImageText: servSettings.largeImageText,
smallImageKey: genSettings.smallImageKey,
smallImageText: genSettings.smallImageText,
startTimestamp: new Date().getTime(),
instance: false
}
client.on('ready', () => {
logger.info('Discord RPC Connected')
client.setActivity(activity)
})
client.login({clientId: genSettings.clientId}).catch(error => {
if(error.message.includes('ENOENT')) {
logger.info('Unable to initialize Discord Rich Presence, no client detected.')
} else {
logger.info('Unable to initialize Discord Rich Presence: ' + error.message, error)
}
})
}
exports.updateDetails = function(details){
activity.details = details
client.setActivity(activity)
}
exports.shutdownRPC = function(){
if(!client) return
client.clearActivity()
client.destroy()
client = null
activity = null
}

View File

@ -1,15 +0,0 @@
const { DistributionAPI } = require('helios-core/common')
const ConfigManager = require('./configmanager')
exports.REMOTE_DISTRO_URL = 'https://git.onimai.ru/ONIMAI-SMP/distribution/raw/branch/main/distribution.json'
const api = new DistributionAPI(
ConfigManager.getLauncherDirectory(),
null, // Injected forcefully by the preloader.
null, // Injected forcefully by the preloader.
exports.REMOTE_DISTRO_URL,
false
)
exports.DistroAPI = api

View File

@ -1,28 +0,0 @@
// NOTE FOR THIRD-PARTY
// REPLACE THIS CLIENT ID WITH YOUR APPLICATION ID.
// SEE https://github.com/dscalzi/HeliosLauncher/blob/master/docs/MicrosoftAuth.md
exports.AZURE_CLIENT_ID = '58f2320b-3644-4194-9174-796e17617dd0'
// SEE NOTE ABOVE.
// Opcodes
exports.MSFT_OPCODE = {
OPEN_LOGIN: 'MSFT_AUTH_OPEN_LOGIN',
OPEN_LOGOUT: 'MSFT_AUTH_OPEN_LOGOUT',
REPLY_LOGIN: 'MSFT_AUTH_REPLY_LOGIN',
REPLY_LOGOUT: 'MSFT_AUTH_REPLY_LOGOUT'
}
// Reply types for REPLY opcode.
exports.MSFT_REPLY_TYPE = {
SUCCESS: 'MSFT_AUTH_REPLY_SUCCESS',
ERROR: 'MSFT_AUTH_REPLY_ERROR'
}
// Error types for ERROR reply.
exports.MSFT_ERROR = {
ALREADY_OPEN: 'MSFT_AUTH_ERR_ALREADY_OPEN',
NOT_FINISHED: 'MSFT_AUTH_ERR_NOT_FINISHED'
}
exports.SHELL_OPCODE = {
TRASH_ITEM: 'TRASH_ITEM'
}

View File

@ -1,5 +0,0 @@
'use strict'
const getFromEnv = parseInt(process.env.ELECTRON_IS_DEV, 10) === 1
const isEnvSet = 'ELECTRON_IS_DEV' in process.env
module.exports = isEnvSet ? getFromEnv : (process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath))

View File

@ -1,43 +0,0 @@
const fs = require('fs-extra')
const path = require('path')
const toml = require('toml')
const merge = require('lodash.merge')
let lang
exports.loadLanguage = function(id){
lang = merge(lang || {}, toml.parse(fs.readFileSync(path.join(__dirname, '..', 'lang', `${id}.toml`))) || {})
}
exports.query = function(id, placeHolders){
let query = id.split('.')
let res = lang
for(let q of query){
res = res[q]
}
let text = res === lang ? '' : res
if (placeHolders) {
Object.entries(placeHolders).forEach(([key, value]) => {
text = text.replace(`{${key}}`, value)
})
}
return text
}
exports.queryJS = function(id, placeHolders){
return exports.query(`js.${id}`, placeHolders)
}
exports.queryEJS = function(id, placeHolders){
return exports.query(`ejs.${id}`, placeHolders)
}
exports.setupLanguage = function(){
// Load Language Files
exports.loadLanguage('en_US')
// Uncomment this when translations are ready
//exports.loadLanguage('xx_XX')
// Load Custom Language File for Launcher Customizer
exports.loadLanguage('_custom')
}

View File

@ -1,67 +0,0 @@
const {ipcRenderer} = require('electron')
const fs = require('fs-extra')
const os = require('os')
const path = require('path')
const ConfigManager = require('./configmanager')
const { DistroAPI } = require('./distromanager')
const LangLoader = require('./langloader')
const { LoggerUtil } = require('helios-core')
// eslint-disable-next-line no-unused-vars
const { HeliosDistribution } = require('helios-core/common')
const logger = LoggerUtil.getLogger('Preloader')
logger.info('Loading..')
// Load ConfigManager
ConfigManager.load()
// Yuck!
// TODO Fix this
DistroAPI['commonDir'] = ConfigManager.getCommonDirectory()
DistroAPI['instanceDir'] = ConfigManager.getInstanceDirectory()
// Load Strings
LangLoader.setupLanguage()
/**
*
* @param {HeliosDistribution} data
*/
function onDistroLoad(data){
if(data != null){
// Resolve the selected server if its value has yet to be set.
if(ConfigManager.getSelectedServer() == null || data.getServerById(ConfigManager.getSelectedServer()) == null){
logger.info('Determining default selected server..')
ConfigManager.setSelectedServer(data.getMainServer().rawServer.id)
ConfigManager.save()
}
}
ipcRenderer.send('distributionIndexDone', data != null)
}
// Ensure Distribution is downloaded and cached.
DistroAPI.getDistribution()
.then(heliosDistro => {
logger.info('Loaded distribution index.')
onDistroLoad(heliosDistro)
})
.catch(err => {
logger.info('Failed to load an older version of the distribution index.')
logger.info('Application cannot run.')
logger.error(err)
onDistroLoad(null)
})
// Clean up temp dir incase previous launches ended unexpectedly.
fs.remove(path.join(os.tmpdir(), ConfigManager.getTempNativeFolder()), (err) => {
if(err){
logger.warn('Error while cleaning natives directory', err)
} else {
logger.info('Cleaned natives directory.')
}
})

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +0,0 @@
const loginOptionsCancelContainer = document.getElementById('loginOptionCancelContainer')
const loginOptionMicrosoft = document.getElementById('loginOptionMicrosoft')
//const loginOptionMojang = document.getElementById('loginOptionMojang')
const loginOptionOffline = document.getElementById('loginOptionOffline')
const loginOptionsCancelButton = document.getElementById('loginOptionCancelButton')
let loginOptionsCancellable = false
let loginOptionsViewOnLoginSuccess
let loginOptionsViewOnLoginCancel
let loginOptionsViewOnCancel
let loginOptionsViewCancelHandler
function loginOptionsCancelEnabled(val){
if(val){
$(loginOptionsCancelContainer).show()
} else {
$(loginOptionsCancelContainer).hide()
}
}
loginOptionMicrosoft.onclick = (e) => {
switchView(getCurrentView(), VIEWS.waiting, 500, 500, () => {
ipcRenderer.send(
MSFT_OPCODE.OPEN_LOGIN,
loginOptionsViewOnLoginSuccess,
loginOptionsViewOnLoginCancel
)
})
}
loginOptionOffline.onclick = (e) => {
switchView(getCurrentView(), VIEWS.login, 500, 500, () => {
loginViewOnSuccess = loginOptionsViewOnLoginSuccess
loginViewOnCancel = loginOptionsViewOnLoginCancel
loginCancelEnabled(true)
})
}
loginOptionsCancelButton.onclick = (e) => {
switchView(getCurrentView(), loginOptionsViewOnCancel, 500, 500, () => {
// Clear login values (Mojang login)
// No cleanup needed for Microsoft.
loginUsername.value = ''
loginPassword.value = ''
if(loginOptionsViewCancelHandler != null){
loginOptionsViewCancelHandler()
loginOptionsViewCancelHandler = null
}
})
}

View File

@ -1,9 +0,0 @@
/**
* Script for welcome.ejs
*/
document.getElementById('welcomeButton').addEventListener('click', e => {
loginOptionsCancelEnabled(false) // False by default, be explicit.
loginOptionsViewOnLoginSuccess = VIEWS.landing
loginOptionsViewOnLoginCancel = VIEWS.loginOptions
switchView(VIEWS.welcome, VIEWS.loginOptions)
})

View File

@ -1,20 +0,0 @@
# Custom Language File for Launcher Customizer
[ejs.app]
title = "ONIMAI.RU MC Launcher"
[ejs.landing]
mediaGitHubURL = "https://onimai.ru"
mediaTwitterURL = "#"
mediaInstagramURL = "#"
mediaYouTubeURL = "#"
mediaDiscordURL = "https://ds.onimai.ru"
[ejs.settings]
sourceGithubLink = "https://git.onimai.ru/ONIMAI-SMP/Launcher"
supportLink = "https://git.onimai.ru/ONIMAI-SMP/Launcher/issues"
[ejs.welcome]
welcomeHeader = "ДОБРО ПОЖАЛОВАТЬ!"
welcomeDescription = "Спасибо что скачали наш лаунчер! В первую очередь он предназначен для удобной игры на наших серверах. С данным лаунчером вы сможете не ебаться с ручным обновлением, а обновлять сборку одним нажатием кнопки. Удачной игры"
welcomeDescCTA = "Пара нажатий и ты на сервере"

View File

@ -1,317 +0,0 @@
[ejs.landing]
updateAvailableTooltip = "Доступно обновление"
usernamePlaceholder = "Имя пользователя"
usernameEditButton = "Редактировать"
settingsTooltip = "Настройки"
serverStatus = "СЕРВЕР"
serverStatusPlaceholder = "ОФФЛАЙН"
mojangStatus = "СТАТУС MOJANG"
mojangStatusTooltipTitle = "Сервисы"
mojangStatusNETitle = "Необязательно"
newsButton = "НОВОСТИ"
launchButton = "ИГРАТЬ"
launchButtonPlaceholder = "&#8226; Сервер не выбран"
launchDetails = "Пожалуйста, подождите.."
newsNavigationStatus = "{currentPage} из {totalPages}"
newsErrorLoadSpan = "Проверка новостей.."
newsErrorFailedSpan = "Не удалось загрузить новости"
newsErrorRetryButton = "Попробовать снова"
newsErrorNoneSpan = "Новостей нет"
[ejs.login]
loginCancelText = "Отмена"
loginSubheader = "ВХОД В MINECRAFT"
loginEmailError = "* Неверное значение"
loginEmailPlaceholder = "EMAIL ИЛИ ИМЯ ПОЛЬЗОВАТЕЛЯ"
loginPasswordError = "* Обязательное поле"
loginPasswordPlaceholder = "ПАРОЛЬ"
loginForgotPasswordLink = "https://minecraft.net/password/forgot/"
loginForgotPasswordText = "забыли пароль?"
loginRememberMeText = "запомнить меня?"
loginButtonText = "ВОЙТИ"
loginNeedAccountLink = "https://minecraft.net/store/minecraft-java-edition/"
loginNeedAccountText = "Нужен аккаунт?"
loginPasswordDisclaimer1 = "Ваш пароль отправляется напрямую в Mojang и никогда не сохраняется."
loginPasswordDisclaimer2 = "{appName} не связан с Mojang AB."
[ejs.loginOptions]
loginOptionsTitle = "Опции входа"
loginWithMicrosoft = "Войти через Microsoft"
loginWithMojang = "Войти через Mojang"
loginWithOffline = "Вход через Пиратку"
cancelButton = "Отмена"
[ejs.overlay]
serverSelectHeader = "Доступные серверы"
serverSelectConfirm = "Выбрать"
serverSelectCancel = "Отмена"
accountSelectHeader = "Выберите аккаунт"
accountSelectConfirm = "Выбрать"
accountSelectCancel = "Отмена"
[ejs.settings]
navHeaderText = "Настройки"
navAccount = "Аккаунт"
navMinecraft = "Minecraft"
navMods = "Моды"
navJava = "Java"
navLauncher = "Лаунчер"
navAbout = "О программе"
navUpdates = "Обновления"
navDone = "Готово"
tabAccountHeaderText = "Настройки аккаунта"
tabAccountHeaderDesc = "Добавляйте новые аккаунты или управляйте существующими."
microsoftAccount = "Microsoft"
addMicrosoftAccount = "+ Добавить аккаунт Microsoft"
mojangAccount = "Mojang"
offlineAccount = "Аккаунт Пиратка"
addMojangAccount = "+ Добавить аккаунт Mojang"
minecraftTabHeaderText = "Настройки Minecraft"
minecraftTabHeaderDesc = "Параметры, связанные с запуском игры."
gameResolutionTitle = "Разрешение игры"
launchFullscreenTitle = "Запуск в полноэкранном режиме."
autoConnectTitle = "Автоматически подключаться к серверу при запуске."
launchDetachedTitle = "Запускать игру отдельно от лаунчера."
launchDetachedDesc = "Если игра не запущена отдельно, закрытие лаунчера приведет к закрытию игры."
tabModsHeaderText = "Настройки модов"
tabModsHeaderDesc = "Включайте или отключайте моды."
switchServerButton = "Сменить сервер"
requiredMods = "Обязательные моды"
optionalMods = "Дополнительные моды"
dropinMods = "Установленные моды"
addMods = "Добавить моды"
dropinRefreshNote = "(Нажмите F5 для обновления)"
shaderpacks = "Шейдерпаки"
shaderpackDesc = "Включайте или отключайте шейдеры. Учтите, что они требуют мощного компьютера. Вы можете добавлять собственные шейдерпаки здесь."
selectShaderpack = "Выбрать шейдерпак"
tabJavaHeaderText = "Настройки Java"
tabJavaHeaderDesc = "Управление конфигурацией Java (для продвинутых пользователей)."
memoryTitle = "Память"
maxRAM = "Максимальный объем RAM"
minRAM = "Минимальный объем RAM"
memoryDesc = "Рекомендуемый минимум RAM — 3 ГБ. Установка одинаковых значений для минимума и максимума может снизить лаги."
memoryTotalTitle = "Всего"
memoryAvailableTitle = "Доступно"
javaExecutableTitle = "Исполняемый файл Java"
javaExecSelDialogTitle = "Выберите исполняемый файл Java"
javaExecSelButtonText = "Выбрать файл"
javaExecDesc = "Исполняемый файл Java проверяется перед запуском игры."
javaPathDesc = "Путь должен заканчиваться на <strong>{pathSuffix}</strong>."
jvmOptsTitle = "Дополнительные параметры JVM"
jvmOptsDesc = "Опции, передаваемые JVM во время выполнения. <em>-Xms</em> и <em>-Xmx</em> не должны включаться."
launcherTabHeaderText = "Настройки лаунчера"
launcherTabHeaderDesc = "Параметры, связанные с самим лаунчером."
allowPrereleaseTitle = "Разрешить обновления предварительных версий."
allowPrereleaseDesc = "Предварительные версии включают новые функции, которые могут быть недостаточно протестированы.<br>Этот параметр всегда включен, если вы используете предварительную версию."
dataDirectoryTitle = "Директория данных"
selectDataDirectory = "Выбрать директорию данных"
chooseFolder = "Выбрать папку"
dataDirectoryDesc = "Все игровые файлы и локальные установки Java будут храниться в этой директории.<br>Скриншоты и сохранения миров хранятся в папке экземпляра соответствующей конфигурации сервера."
aboutTabHeaderText = "О программе"
aboutTabHeaderDesc = "Просмотр информации и заметок о текущей версии."
aboutTitle = "{appName}"
stableRelease = "Стабильный релиз"
versionText = "Версия "
sourceGithub = "Исходный код (GitHub)"
support = "Поддержка"
devToolsConsole = "Консоль DevTools"
releaseNotes = "Примечания к выпуску"
changelog = "История изменений"
noReleaseNotes = "Нет примечаний к выпуску"
viewReleaseNotes = "Просмотреть примечания к выпуску на GitHub"
launcherUpdatesHeaderText = "Обновления лаунчера"
launcherUpdatesHeaderDesc = "Загрузка, установка и просмотр обновлений лаунчера."
checkForUpdates = "Проверить обновления"
whatsNew = "Что нового"
updateReleaseNotes = "Примечания к обновлению"
[ejs.waiting]
waitingText = "Ожидание Microsoft.."
[ejs.welcome]
continueButton = "ПРОДОЛЖИТЬ"
[js.discord]
waiting = "Ожидание клиента.."
state = "Сервер: {shortId}"
[js.index]
microsoftLoginTitle = "Вход через Microsoft"
microsoftLogoutTitle = "Выход из Microsoft"
[js.login]
login = "ВОЙТИ"
loggingIn = "ВХОД В СИСТЕМУ"
success = "УСПЕШНО"
tryAgain = "Попробовать снова"
[js.login.error]
invalidValue = "* Неверное значение"
requiredValue = "* Обязательное поле"
[js.login.error.unknown]
title = "Неизвестная ошибка при входе"
desc = "Произошла неизвестная ошибка. Подробности можно посмотреть в консоли."
[js.landing.launch]
pleaseWait = "Пожалуйста, подождите.."
failureTitle = "Ошибка при запуске"
failureText = "Смотрите консоль (CTRL + Shift + I) для подробностей."
okay = "ОК"
[js.landing.selectedAccount]
noAccountSelected = "Аккаунт не выбран"
[js.landing.selectedServer]
noSelection = "Сервер не выбран"
loading = "Загрузка.."
[js.landing.serverStatus]
server = "СЕРВЕР"
offline = "ОФФЛАЙН"
players = "ИГРОКИ"
[js.landing.systemScan]
checking = "Проверка информации о системе.."
noCompatibleJava = "Совместимая<br>установка Java не найдена"
installJavaMessage = "Для запуска Minecraft требуется 64-битная версия Java {major}. Установить её сейчас?"
installJava = "Установить Java"
installJavaManually = "Установить вручную"
javaDownloadPrepare = "Подготовка загрузки Java.."
javaDownloadFailureTitle = "Ошибка загрузки Java"
javaDownloadFailureText = "Смотрите консоль (CTRL + Shift + I) для подробностей."
javaRequired = "Для запуска требуется Java"
javaRequiredMessage = "Для запуска необходима действительная 64-битная установка Java {major}.<br><br>Пожалуйста, ознакомьтесь с нашим <a href=\"https://github.com/dscalzi/HeliosLauncher/wiki/Java-Management#manually-installing-a-valid-version-of-java\">Руководством по управлению Java</a>, чтобы установить её вручную."
javaRequiredDismiss = "Понял"
javaRequiredCancel = "Назад"
[js.landing.downloadJava]
findJdkFailure = "Не удалось найти дистрибутив OpenJDK."
javaDownloadCorruptedError = "Загруженный JDK поврежден, файл может быть испорчен."
extractingJava = "Распаковка Java"
javaInstalled = "Java установлена!"
[js.landing.dlAsync]
loadingServerInfo = "Загрузка информации о сервере.."
fatalError = "Критическая ошибка"
unableToLoadDistributionIndex = "Не удалось загрузить индекс дистрибуции. Смотрите консоль (CTRL + Shift + I) для подробностей."
pleaseWait = "Пожалуйста, подождите.."
errorDuringLaunchTitle = "Ошибка при запуске"
seeConsoleForDetails = "Смотрите консоль (CTRL + Shift + I) для подробностей."
validatingFileIntegrity = "Проверка целостности файлов.."
errorDuringFileVerificationTitle = "Ошибка при проверке файлов"
downloadingFiles = "Загрузка файлов.."
errorDuringFileDownloadTitle = "Ошибка при загрузке файлов"
preparingToLaunch = "Подготовка к запуску.."
launchingGame = "Запуск игры.."
launchWrapperNotDownloaded = "Основной файл, LaunchWrapper, не был загружен должным образом. В результате игра не может быть запущена.<br><br>Чтобы исправить эту проблему, временно отключите антивирусное ПО и попробуйте снова.<br><br>Если у вас есть время, пожалуйста, <a href=\"https://github.com/dscalzi/HeliosLauncher/issues\">сообщите об этой проблеме</a> и укажите, каким антивирусом вы пользуетесь. Мы постараемся связаться с разработчиками антивируса и решить проблему."
doneEnjoyServer = "Готово. Наслаждайтесь сервером!"
checkConsoleForDetails = "Пожалуйста, проверьте консоль (CTRL + Shift + I) для подробностей."
[js.landing.news]
checking = "Проверка новостей"
[js.landing.discord]
loading = "Загрузка игры.."
joining = "Путешествие в Вестерос!"
joined = "Исследование мира!"
[js.overlay]
dismiss = "Закрыть"
[js.settings.fileSelectors]
executables = "Исполняемые файлы"
allFiles = "Все файлы"
[js.settings.mstfLogin]
errorTitle = "Что-то пошло не так"
errorMessage = "Ошибка аутентификации Microsoft. Попробуйте еще раз."
okButton = "ОК"
[js.settings.mstfLogout]
errorTitle = "Что-то пошло не так"
errorMessage = "Ошибка выхода из Microsoft. Попробуйте еще раз."
okButton = "ОК"
[js.settings.dropinMods]
removeButton = "Удалить"
deleteFailedTitle = "Ошибка удаления<br>мода {fullName}"
deleteFailedMessage = "Убедитесь, что файл не используется, и попробуйте снова."
failedToggleTitle = "Ошибка переключения<br>одного или нескольких установленных модов"
okButton = "ОК"
[js.settings.serverListing]
mainServer = "Основной сервер"
[js.settings.java]
selectedJava = "Выбрано: Java {version} ({vendor})"
invalidSelection = "Некорректный выбор"
requiresJava = "Требуется Java {major} x64."
availableOptions = "Доступные версии Java {major} (HotSpot VM)"
[js.settings.about]
preReleaseTitle = "Предварительная версия"
stableReleaseTitle = "Стабильная версия"
releaseNotesFailed = "Не удалось загрузить примечания к выпуску."
[js.settings.updates]
newReleaseTitle = "Доступно новое обновление"
newPreReleaseTitle = "Доступна новая предварительная версия"
downloadingButton = "Загрузка.."
downloadButton = 'Загрузить с GitHub<span style="font-size: 10px;color: gray;text-shadow: none !important;">Закройте лаунчер и запустите установщик для обновления.</span>'
latestVersionTitle = "У вас установлена последняя версия"
checkForUpdatesButton = "Проверить обновления"
checkingForUpdatesButton = "Проверка обновлений.."
[js.settings.msftLogin]
errorTitle = "Ошибка входа в Microsoft"
errorMessage = "Не удалось выполнить аутентификацию в Microsoft. Попробуйте снова."
okButton = "ОК"
[js.settings.authAccountSelect]
selectButton = "Выбрать аккаунт"
selectedButton = "Выбранный аккаунт &#10004;"
[js.settings.authAccountLogout]
lastAccountWarningTitle = "Внимание<br>Это ваш последний аккаунт"
lastAccountWarningMessage = "Для использования лаунчера необходимо быть авторизованным хотя бы в одном аккаунте. После выхода вам придется войти снова.<br><br>Вы уверены, что хотите выйти?"
confirmButton = "Я уверен"
cancelButton = "Отмена"
[js.settings.authAccountPopulate]
username = "Имя пользователя"
uuid = "UUID"
selectAccount = "Выбрать аккаунт"
selectedAccount = "Выбранный аккаунт ✓"
logout = "Выйти"
[js.uibinder.startup]
fatalErrorTitle = "Критическая ошибка: невозможно загрузить индекс дистрибуции"
fatalErrorMessage = "Не удалось установить соединение с нашими серверами для загрузки индекса дистрибуции. Локальные копии отсутствуют.<br><br>Индекс дистрибуции — это важный файл, который содержит актуальную информацию о сервере. Лаунчер не сможет запуститься без него. Убедитесь, что у вас есть подключение к интернету, и перезапустите приложение."
closeButton = "Закрыть"
[js.uibinder.validateAccount]
failedMessageTitle = "Ошибка обновления входа"
failedMessage = "Не удалось обновить вход для <strong>{account}</strong>. Пожалуйста, выберите другой аккаунт или войдите снова."
failedMessageSelectAnotherAccount = "Не удалось обновить вход для <strong>{account}</strong>. Пожалуйста, войдите снова."
loginButton = "Войти"
selectAnotherAccountButton = "Выбрать другой аккаунт"
[js.uicore.autoUpdate]
checkingForUpdateButton = "Проверка обновлений..."
installNowButton = "Установить сейчас"
checkForUpdatesButton = "Проверить обновления"
[js.auth.microsoft.error]
noProfileTitle = "Ошибка входа:<br>Профиль не настроен"
noProfileDesc = "Ваш аккаунт Microsoft еще не имеет профиля Minecraft. Если вы недавно купили игру или активировали ее через Xbox Game Pass, вам нужно настроить профиль на <a href=\"https://minecraft.net/\">Minecraft.net</a>.<br><br>Если вы еще не купили игру, вы можете сделать это на <a href=\"https://minecraft.net/\">Minecraft.net</a>."
noXboxAccountTitle = "Ошибка входа:<br>Нет аккаунта Xbox"
noXboxAccountDesc = "У вашей учетной записи Microsoft нет привязанного аккаунта Xbox."
xblBannedTitle = "Ошибка входа:<br>Xbox Live недоступен"
xblBannedDesc = "Ваш аккаунт Microsoft зарегистрирован в стране, где Xbox Live недоступен или заблокирован."
under18Title = "Ошибка входа:<br>Требуется родительское разрешение"
under18Desc = "Аккаунты пользователей младше 18 лет должны быть добавлены в «Семью» взрослым."
unknownTitle = "Неизвестная ошибка входа"
unknownDesc = "Произошла неизвестная ошибка. Подробности можно посмотреть в консоли."

View File

@ -1,363 +0,0 @@
[ejs.landing]
updateAvailableTooltip = "Доступно обновление"
usernamePlaceholder = "Имя пользователя"
usernameEditButton = "Редактировать"
settingsTooltip = "Настройки"
serverStatus = "СЕРВЕР"
serverStatusPlaceholder = "ОФФЛАЙН"
mojangStatus = "СТАТУС MOJANG"
mojangStatusTooltipTitle = "Сервисы"
mojangStatusNETitle = "Необязательно"
newsButton = "НОВОСТИ"
launchButton = "ИГРАТЬ"
launchButtonPlaceholder = "&#8226; Сервер не выбран"
launchDetails = "Пожалуйста, подождите.."
newsNavigationStatus = "{currentPage} из {totalPages}"
newsErrorLoadSpan = "Проверка новостей.."
newsErrorFailedSpan = "Не удалось загрузить новости"
newsErrorRetryButton = "Попробовать снова"
newsErrorNoneSpan = "Новостей нет"
[ejs.login]
loginCancelText = "Отмена"
loginSubheader = "ВХОД В MINECRAFT"
loginEmailError = "* Неверное значение"
loginEmailPlaceholder = "EMAIL ИЛИ ИМЯ ПОЛЬЗОВАТЕЛЯ"
loginPasswordError = "* Обязательное поле"
loginPasswordPlaceholder = "ПАРОЛЬ"
loginForgotPasswordLink = "https://minecraft.net/password/forgot/"
loginForgotPasswordText = "забыли пароль?"
loginRememberMeText = "запомнить меня?"
loginButtonText = "ВОЙТИ"
loginNeedAccountLink = "https://minecraft.net/store/minecraft-java-edition/"
loginNeedAccountText = "Нужен аккаунт?"
loginPasswordDisclaimer1 = "Ваш пароль отправляется напрямую в Mojang и никогда не сохраняется."
loginPasswordDisclaimer2 = "{appName} не связан с Mojang AB."
[ejs.loginOptions]
loginOptionsTitle = "Опции входа"
loginWithMicrosoft = "Войти через Microsoft"
loginWithMojang = "Войти через Mojang"
cancelButton = "Отмена"
[ejs.overlay]
serverSelectHeader = "Доступные серверы"
serverSelectConfirm = "Выбрать"
serverSelectCancel = "Отмена"
accountSelectHeader = "Выберите аккаунт"
accountSelectConfirm = "Выбрать"
accountSelectCancel = "Отмена"
[ejs.settings]
navHeaderText = "Настройки"
navAccount = "Аккаунт"
navMinecraft = "Minecraft"
navMods = "Моды"
navJava = "Java"
navLauncher = "Лаунчер"
navAbout = "О программе"
navUpdates = "Обновления"
navDone = "Готово"
tabAccountHeaderText = "Настройки аккаунта"
tabAccountHeaderDesc = "Добавляйте новые аккаунты или управляйте существующими."
microsoftAccount = "Microsoft"
addMicrosoftAccount = "+ Добавить аккаунт Microsoft"
mojangAccount = "Mojang"
addMojangAccount = "+ Добавить аккаунт Mojang"
minecraftTabHeaderText = "Настройки Minecraft"
minecraftTabHeaderDesc = "Параметры, связанные с запуском игры."
gameResolutionTitle = "Разрешение игры"
launchFullscreenTitle = "Запуск в полноэкранном режиме."
autoConnectTitle = "Автоматически подключаться к серверу при запуске."
launchDetachedTitle = "Запускать игру отдельно от лаунчера."
launchDetachedDesc = "Если игра не запущена отдельно, закрытие лаунчера приведет к закрытию игры."
tabModsHeaderText = "Настройки модов"
tabModsHeaderDesc = "Включайте или отключайте моды."
switchServerButton = "Сменить сервер"
requiredMods = "Обязательные моды"
optionalMods = "Дополнительные моды"
dropinMods = "Установленные моды"
addMods = "Добавить моды"
dropinRefreshNote = "(Нажмите F5 для обновления)"
shaderpacks = "Шейдерпаки"
shaderpackDesc = "Включайте или отключайте шейдеры. Учтите, что они требуют мощного компьютера. Вы можете добавлять собственные шейдерпаки здесь."
selectShaderpack = "Выбрать шейдерпак"
tabJavaHeaderText = "Настройки Java"
tabJavaHeaderDesc = "Управление конфигурацией Java (для продвинутых пользователей)."
memoryTitle = "Память"
maxRAM = "Максимальный объем RAM"
minRAM = "Минимальный объем RAM"
memoryDesc = "Рекомендуемый минимум RAM — 3 ГБ. Установка одинаковых значений для минимума и максимума может снизить лаги."
memoryTotalTitle = "Всего"
memoryAvailableTitle = "Доступно"
javaExecutableTitle = "Исполняемый файл Java"
javaExecSelDialogTitle = "Выберите исполняемый файл Java"
javaExecSelButtonText = "Выбрать файл"
javaExecDesc = "Исполняемый файл Java проверяется перед запуском игры."
javaPathDesc = "Путь должен заканчиваться на <strong>{pathSuffix}</strong>."
jvmOptsTitle = "Дополнительные параметры JVM"
jvmOptsDesc = "Опции, передаваемые JVM во время выполнения. <em>-Xms</em> и <em>-Xmx</em> не должны включаться."
launcherTabHeaderText = "Настройки лаунчера"
launcherTabHeaderDesc = "Параметры, связанные с самим лаунчером."
allowPrereleaseTitle = "Разрешить обновления предварительных версий."
allowPrereleaseDesc = "Предварительные версии включают новые функции, которые могут быть недостаточно протестированы.<br>Этот параметр всегда включен, если вы используете предварительную версию."
dataDirectoryTitle = "Директория данных"
selectDataDirectory = "Выбрать директорию данных"
chooseFolder = "Выбрать папку"
dataDirectoryDesc = "Все игровые файлы и локальные установки Java будут храниться в этой директории.<br>Скриншоты и сохранения миров хранятся в папке экземпляра соответствующей конфигурации сервера."
aboutTabHeaderText = "О программе"
aboutTabHeaderDesc = "Просмотр информации и заметок о текущей версии."
aboutTitle = "{appName}"
stableRelease = "Стабильный релиз"
versionText = "Версия "
sourceGithub = "Исходный код (GitHub)"
support = "Поддержка"
devToolsConsole = "Консоль DevTools"
releaseNotes = "Примечания к выпуску"
changelog = "История изменений"
noReleaseNotes = "Нет примечаний к выпуску"
viewReleaseNotes = "Просмотреть примечания к выпуску на GitHub"
launcherUpdatesHeaderText = "Обновления лаунчера"
launcherUpdatesHeaderDesc = "Загрузка, установка и просмотр обновлений лаунчера."
checkForUpdates = "Проверить обновления"
whatsNew = "Что нового"
updateReleaseNotes = "Примечания к обновлению"
[ejs.waiting]
waitingText = "Ожидание Microsoft.."
[ejs.welcome]
continueButton = "ПРОДОЛЖИТЬ"
[js.discord]
waiting = "Ожидание клиента.."
state = "Сервер: {shortId}"
[js.index]
microsoftLoginTitle = "Вход через Microsoft"
microsoftLogoutTitle = "Выход из Microsoft"
[js.login]
login = "ВОЙТИ"
loggingIn = "ВХОД В СИСТЕМУ"
success = "УСПЕШНО"
tryAgain = "Попробовать снова"
[js.login.error]
invalidValue = "* Неверное значение"
requiredValue = "* Обязательное поле"
[js.login.error.unknown]
title = "Неизвестная ошибка при входе"
desc = "Произошла неизвестная ошибка. Подробности можно посмотреть в консоли."
[js.landing.launch]
pleaseWait = "Пожалуйста, подождите.."
failureTitle = "Ошибка при запуске"
failureText = "Смотрите консоль (CTRL + Shift + I) для подробностей."
okay = "ОК"
[js.landing.selectedAccount]
noAccountSelected = "Аккаунт не выбран"
[js.landing.selectedServer]
noSelection = "Сервер не выбран"
loading = "Загрузка.."
[js.landing.serverStatus]
server = "СЕРВЕР"
offline = "ОФФЛАЙН"
players = "ИГРОКИ"
[js.landing.systemScan]
checking = "Проверка информации о системе.."
noCompatibleJava = "Совместимая<br>установка Java не найдена"
installJavaMessage = "Для запуска Minecraft требуется 64-битная версия Java {major}. Установить её сейчас?"
installJava = "Установить Java"
installJavaManually = "Установить вручную"
javaDownloadPrepare = "Подготовка загрузки Java.."
javaDownloadFailureTitle = "Ошибка загрузки Java"
javaDownloadFailureText = "Смотрите консоль (CTRL + Shift + I) для подробностей."
javaRequired = "Для запуска требуется Java"
javaRequiredMessage = "Для запуска необходима действительная 64-битная установка Java {major}.<br><br>Пожалуйста, ознакомьтесь с нашим <a href=\"https://github.com/dscalzi/HeliosLauncher/wiki/Java-Management#manually-installing-a-valid-version-of-java\">Руководством по управлению Java</a>, чтобы установить её вручную."
javaRequiredDismiss = "Понял"
javaRequiredCancel = "Назад"
[js.landing.downloadJava]
findJdkFailure = "Не удалось найти дистрибутив OpenJDK."
javaDownloadCorruptedError = "Загруженный JDK поврежден, файл может быть испорчен."
extractingJava = "Распаковка Java"
javaInstalled = "Java установлена!"
[js.landing.dlAsync]
loadingServerInfo = "Загрузка информации о сервере.."
fatalError = "Критическая ошибка"
unableToLoadDistributionIndex = "Не удалось загрузить индекс дистрибуции. Смотрите консоль (CTRL + Shift + I) для подробностей."
pleaseWait = "Пожалуйста, подождите.."
errorDuringLaunchTitle = "Ошибка при запуске"
seeConsoleForDetails = "Смотрите консоль (CTRL + Shift + I) для подробностей."
validatingFileIntegrity = "Проверка целостности файлов.."
errorDuringFileVerificationTitle = "Ошибка при проверке файлов"
downloadingFiles = "Загрузка файлов.."
errorDuringFileDownloadTitle = "Ошибка при загрузке файлов"
preparingToLaunch = "Подготовка к запуску.."
launchingGame = "Запуск игры.."
launchWrapperNotDownloaded = "Основной файл, LaunchWrapper, не был загружен должным образом. В результате игра не может быть запущена.<br><br>Чтобы исправить эту проблему, временно отключите антивирусное ПО и попробуйте снова.<br><br>Если у вас есть время, пожалуйста, <a href=\"https://github.com/dscalzi/HeliosLauncher/issues\">сообщите об этой проблеме</a> и укажите, каким антивирусом вы пользуетесь. Мы постараемся связаться с разработчиками антивируса и решить проблему."
doneEnjoyServer = "Готово. Наслаждайтесь сервером!"
checkConsoleForDetails = "Пожалуйста, проверьте консоль (CTRL + Shift + I) для подробностей."
[js.landing.news]
checking = "Проверка новостей"
[js.landing.discord]
loading = "Загрузка игры.."
joining = "Путешествие в Вестерос!"
joined = "Исследование мира!"
[js.overlay]
dismiss = "Закрыть"
[js.settings.fileSelectors]
executables = "Исполняемые файлы"
allFiles = "Все файлы"
[js.settings.mstfLogin]
errorTitle = "Что-то пошло не так"
errorMessage = "Ошибка аутентификации Microsoft. Попробуйте еще раз."
okButton = "ОК"
[js.settings.mstfLogout]
errorTitle = "Что-то пошло не так"
errorMessage = "Ошибка выхода из Microsoft. Попробуйте еще раз."
okButton = "ОК"
[js.settings.authAccountLogout]
lastAccountWarningTitle = "Внимание<br>Это ваш последний аккаунт"
lastAccountWarningMessage = "Для использования лаунчера необходимо быть авторизованным хотя бы в одном аккаунте. После выхода вам придется войти снова.<br><br>Вы уверены, что хотите выйти?"
confirmButton = "Я уверен"
cancelButton = "Отмена"
[js.settings.authAccountPopulate]
username = "Имя пользователя"
uuid = "UUID"
selectAccount = "Выбрать аккаунт"
selectedAccount = "Выбранный аккаунт ✓"
logout = "Выйти"
[js.settings.dropinMods]
removeButton = "Удалить"
deleteFailedTitle = "Ошибка удаления<br>мода {fullName}"
deleteFailedMessage = "Убедитесь, что файл не используется, и попробуйте снова."
failedToggleTitle = "Ошибка переключения<br>одного или нескольких установленных модов"
okButton = "ОК"
[js.settings.serverListing]
mainServer = "Основной сервер"
[js.settings.java]
selectedJava = "Выбрано: Java {version} ({vendor})"
invalidSelection = "Некорректный выбор"
requiresJava = "Требуется Java {major} x64."
availableOptions = "Доступные версии Java {major} (HotSpot VM)"
[js.settings.about]
preReleaseTitle = "Предварительная версия"
stableReleaseTitle = "Стабильная версия"
releaseNotesFailed = "Не удалось загрузить примечания к выпуску."
[js.settings.updates]
newReleaseTitle = "Доступно новое обновление"
newPreReleaseTitle = "Доступна новая предварительная версия"
downloadingButton = "Загрузка.."
downloadButton = 'Загрузить с GitHub<span style="font-size: 10px;color: gray;text-shadow: none !important;">Закройте лаунчер и запустите установщик для обновления.</span>'
latestVersionTitle = "У вас установлена последняя версия"
checkForUpdatesButton = "Проверить обновления"
checkingForUpdatesButton = "Проверка обновлений.."
[js.settings.msftLogin]
errorTitle = "Ошибка входа в Microsoft"
errorMessage = "Не удалось выполнить аутентификацию в Microsoft. Попробуйте снова."
okButton = "ОК"
[js.settings.authAccountSelect]
selectButton = "Выбрать аккаунт"
selectedButton = "Выбранный аккаунт &#10004;"
[js.settings.authAccountLogout]
lastAccountWarningTitle = "Внимание<br>Это ваш последний аккаунт"
lastAccountWarningMessage = "Для использования лаунчера необходимо быть авторизованным хотя бы в одном аккаунте. После выхода вам придется войти снова.<br><br>Вы уверены, что хотите выйти?"
confirmButton = "Я уверен"
cancelButton = "Отмена"
[js.settings.authAccountPopulate]
username = "Имя пользователя"
uuid = "UUID"
selectAccount = "Выбрать аккаунт"
selectedAccount = "Выбранный аккаунт ✓"
logout = "Выйти"
[js.settings.dropinMods]
removeButton = "Удалить"
deleteFailedTitle = "Ошибка удаления<br>мода {fullName}"
deleteFailedMessage = "Убедитесь, что файл не используется, и попробуйте снова."
failedToggleTitle = "Ошибка переключения<br>одного или нескольких установленных модов"
okButton = "ОК"
[js.settings.serverListing]
mainServer = "Основной сервер"
[js.settings.java]
selectedJava = "Выбрано: Java {version} ({vendor})"
invalidSelection = "Некорректный выбор"
requiresJava = "Требуется Java {major} x64."
availableOptions = "Доступные версии Java {major} (HotSpot VM)"
[js.settings.about]
preReleaseTitle = "Предварительная версия"
stableReleaseTitle = "Стабильная версия"
releaseNotesFailed = "Не удалось загрузить примечания к выпуску."
[js.settings.updates]
newReleaseTitle = "Доступно новое обновление"
newPreReleaseTitle = "Доступна новая предварительная версия"
downloadingButton = "Загрузка.."
downloadButton = 'Загрузить с GitHub<span style="font-size: 10px;color: gray;text-shadow: none !important;">Закройте лаунчер и запустите установщик для обновления.</span>'
latestVersionTitle = "У вас установлена последняя версия"
checkForUpdatesButton = "Проверить обновления"
checkingForUpdatesButton = "Проверка обновлений.."
[js.settings.msftLogin]
errorTitle = "Ошибка входа в Microsoft"
errorMessage = "Не удалось выполнить аутентификацию в Microsoft. Попробуйте снова."
okButton = "ОК"
[js.uibinder.startup]
fatalErrorTitle = "Критическая ошибка: невозможно загрузить индекс дистрибуции"
fatalErrorMessage = "Не удалось установить соединение с нашими серверами для загрузки индекса дистрибуции. Локальные копии отсутствуют.<br><br>Индекс дистрибуции — это важный файл, который содержит актуальную информацию о сервере. Лаунчер не сможет запуститься без него. Убедитесь, что у вас есть подключение к интернету, и перезапустите приложение."
closeButton = "Закрыть"
[js.uibinder.validateAccount]
failedMessageTitle = "Ошибка обновления входа"
failedMessage = "Не удалось обновить вход для <strong>{account}</strong>. Пожалуйста, выберите другой аккаунт или войдите снова."
failedMessageSelectAnotherAccount = "Не удалось обновить вход для <strong>{account}</strong>. Пожалуйста, войдите снова."
loginButton = "Войти"
selectAnotherAccountButton = "Выбрать другой аккаунт"
[js.uicore.autoUpdate]
checkingForUpdateButton = "Проверка обновлений..."
installNowButton = "Установить сейчас"
checkForUpdatesButton = "Проверить обновления"
[js.auth.microsoft.error]
noProfileTitle = "Ошибка входа:<br>Профиль не настроен"
noProfileDesc = "Ваш аккаунт Microsoft еще не имеет профиля Minecraft. Если вы недавно купили игру или активировали ее через Xbox Game Pass, вам нужно настроить профиль на <a href=\"https://minecraft.net/\">Minecraft.net</a>.<br><br>Если вы еще не купили игру, вы можете сделать это на <a href=\"https://minecraft.net/\">Minecraft.net</a>."
noXboxAccountTitle = "Ошибка входа:<br>Нет аккаунта Xbox"
noXboxAccountDesc = "У вашей учетной записи Microsoft нет привязанного аккаунта Xbox."
xblBannedTitle = "Ошибка входа:<br>Xbox Live недоступен"
xblBannedDesc = "Ваш аккаунт Microsoft зарегистрирован в стране, где Xbox Live недоступен или заблокирован."
under18Title = "Ошибка входа:<br>Требуется родительское разрешение"
under18Desc = "Аккаунты пользователей младше 18 лет должны быть добавлены в «Семью» взрослым."
unknownTitle = "Неизвестная ошибка входа"
unknownDesc = "Произошла неизвестная ошибка. Подробности можно посмотреть в консоли."

View File

@ -1,29 +0,0 @@
<div id="loginOptionsContainer" style="display: none;">
<div id="loginOptionsContent">
<div class="loginOptionsMainContent">
<h2><%- lang('loginOptions.loginOptionsTitle') %></h2>
<div class="loginOptionActions">
<div class="loginOptionButtonContainer">
<button id="loginOptionMicrosoft" class="loginOptionButton">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 23 23">
<path fill="#f35325" d="M1 1h10v10H1z" />
<path fill="#81bc06" d="M12 1h10v10H12z" />
<path fill="#05a6f0" d="M1 12h10v10H1z" />
<path fill="#ffba08" d="M12 12h10v10H12z" />
</svg>
<span><%- lang('loginOptions.loginWithMicrosoft') %></span>
</button>
</div>
<div class="loginOptionButtonContainer">
<button id="loginOptionOffline" class="loginOptionButton">
<span><%- lang('loginOptions.loginWithOffline') %></span>
</button>
</div>
</div>
<div id="loginOptionCancelContainer" style="display: none;">
<button id="loginOptionCancelButton"><%- lang('loginOptions.cancelButton') %></button>
</div>
</div>
</div>
<script src="./assets/js/scripts/loginOptions.js"></script>
</div>

View File

@ -1,8 +0,0 @@
<div id="waitingContainer" style="display: none;">
<div id="waitingContent">
<div class="waitingSpinner"></div>
<div id="waitingTextContainer">
<h2><%- lang('waiting.waitingText') %></h2>
</div>
</div>
</div>

View File

@ -1,25 +0,0 @@
<div id="welcomeContainer" style="display: none;">
<!--<div class="cloudDiv">
<div class="cloudTop"></div>
<div class="cloudBottom"></div>
</div>-->
<div id="welcomeContent">
<img id="welcomeImageSeal" src="assets/images/SealCircle.png"/>
<span id="welcomeHeader"><%- lang('welcome.welcomeHeader') %></span>
<span id="welcomeDescription"><%- lang('welcome.welcomeDescription') %></span>
<br>
<span id="welcomeDescCTA"><%- lang('welcome.welcomeDescCTA') %></span>
<button id="welcomeButton">
<div id="welcomeButtonContent">
<%- lang('welcome.continueButton') %>
<svg id="welcomeSVG" viewBox="0 0 24.87 13.97">
<defs>
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
</defs>
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
</svg>
</div>
</button>
</div>
<script src="./assets/js/scripts/welcome.js"></script>
</div>

View File

@ -1,5 +1,5 @@
/* Github Code Highlighting. */
@import "../../../node_modules/github-syntax-dark/lib/github-dark.css";
@import "../../node_modules/github-syntax-dark/lib/github-dark.css";
/*******************************************************************************
* *
@ -222,7 +222,6 @@ body, button {
align-items: center;
height: 100%;
width: 100%;
background: rgba(0, 0, 0, 0.50);
}
#welcomeContent {
@ -388,17 +387,6 @@ body, button {
background: rgba(0, 0, 0, 0.50);
}
#loginOfflineContainer {
position: relative;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
transition: filter 0.25s ease;
background: rgba(0, 0, 0, 0.50);
}
/* Login cancel button styles. */
#loginCancelContainer {
position: absolute;
@ -884,175 +872,6 @@ body, button {
}
*/
/*******************************************************************************
* *
* Waiting View (waiting.ejs) *
* *
******************************************************************************/
#waitingContainer {
position: relative;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
transition: filter 0.25s ease;
background: rgba(0, 0, 0, 0.50);
}
#waitingContent {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 50%;
top: -10%;
position: relative;
}
.waitingSpinner:before {
transform: rotateX(60deg) rotateY(45deg) rotateZ(45deg);
animation: 750ms rotateBefore infinite linear reverse;
}
.waitingSpinner:after {
transform: rotateX(240deg) rotateY(45deg) rotateZ(45deg);
animation: 750ms rotateAfter infinite linear;
}
.waitingSpinner:before,
.waitingSpinner:after {
box-sizing: border-box;
content: '';
display: block;
position: fixed;
top: calc(50% - 5em);
/* left: 50%; */
margin-top: -5em;
margin-left: -5em;
width: 10em;
height: 10em;
transform-style: preserve-3d;
transform-origin: 50%;
transform: rotateY(50%);
perspective-origin: 50% 50%;
perspective: 340px;
background-size: 10em 10em;
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjI2NnB4IiBoZWlnaHQ9IjI5N3B4IiB2aWV3Qm94PSIwIDAgMjY2IDI5NyIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczpza2V0Y2g9Imh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaC9ucyI+CiAgICA8dGl0bGU+c3Bpbm5lcjwvdGl0bGU+CiAgICA8ZGVzY3JpcHRpb24+Q3JlYXRlZCB3aXRoIFNrZXRjaCAoaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoKTwvZGVzY3JpcHRpb24+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4KICAgICAgICA8cGF0aCBkPSJNMTcxLjUwNzgxMywzLjI1MDAwMDM4IEMyMjYuMjA4MTgzLDEyLjg1NzcxMTEgMjk3LjExMjcyMiw3MS40OTEyODIzIDI1MC44OTU1OTksMTA4LjQxMDE1NSBDMjE2LjU4MjAyNCwxMzUuODIwMzEgMTg2LjUyODQwNSw5Ny4wNjI0OTY0IDE1Ni44MDA3NzQsODUuNzczNDM0NiBDMTI3LjA3MzE0Myw3NC40ODQzNzIxIDc2Ljg4ODQ2MzIsODQuMjE2MTQ2MiA2MC4xMjg5MDY1LDEwOC40MTAxNTMgQy0xNS45ODA0Njg1LDIxOC4yODEyNDcgMTQ1LjI3NzM0NCwyOTYuNjY3OTY4IDE0NS4yNzczNDQsMjk2LjY2Nzk2OCBDMTQ1LjI3NzM0NCwyOTYuNjY3OTY4IC0yNS40NDkyMTg3LDI1Ny4yNDIxOTggMy4zOTg0Mzc1LDEwOC40MTAxNTUgQzE2LjMwNzA2NjEsNDEuODExNDE3NCA4NC43Mjc1ODI5LC0xMS45OTIyOTg1IDE3MS41MDc4MTMsMy4yNTAwMDAzOCBaIiBpZD0iUGF0aC0xIiBmaWxsPSIjZmZmZmZmIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICA8L2c+Cjwvc3ZnPg==);
}
#waitingTextContainer {
position: fixed;
top: 50%;
}
@keyframes rotateBefore {
from {
transform: rotateX(60deg) rotateY(45deg) rotateZ(0deg);
}
to {
transform: rotateX(60deg) rotateY(45deg) rotateZ(-360deg);
}
}
@keyframes rotateAfter {
from {
transform: rotateX(240deg) rotateY(45deg) rotateZ(0deg);
}
to {
transform: rotateX(240deg) rotateY(45deg) rotateZ(360deg);
}
}
/*******************************************************************************
* *
* Login Options View (loginOptions.ejs) *
* *
******************************************************************************/
#loginOptionsContainer {
position: relative;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
transition: filter 0.25s ease;
background: rgba(0, 0, 0, 0.50);
}
#loginOptionsContent {
border-radius: 3px;
position: relative;
top: -5%;
}
.loginOptionsMainContent {
display: flex;
flex-direction: column;
align-items: center;
}
.loginOptionActions {
display: flex;
flex-direction: column;
row-gap: 10px;
}
.loginOptionButtonContainer {
width: 16em;
}
.loginOptionButton {
background: rgba(0, 0, 0, 0.25);
border: 1px solid rgba(126, 126, 126, 0.57);
border-radius: 3px;
height: 50px;
width: 100%;
text-align: left;
padding: 0px 25px;
cursor: pointer;
outline: none;
transition: 0.25s ease;
display: flex;
align-items: center;
column-gap: 5px;
}
.loginOptionButton:hover,
.loginOptionButton:focus {
background: rgba(54, 54, 54, 0.25);
text-shadow: 0px 0px 20px white;
}
#loginOptionCancelContainer {
position: absolute;
bottom: -100px;
}
#loginOptionCancelButton {
background: none;
border: none;
padding: 2px 0px;
font-size: 16px;
font-weight: bold;
color: lightgrey;
cursor: pointer;
outline: none;
transition: 0.25s ease;
}
#loginOptionCancelButton:hover,
#loginOptionCancelButton:focus {
text-shadow: 0px 0px 20px lightgrey;
}
#loginOptionCancelButton:active {
text-shadow: 0px 0px 20px rgba(211, 211, 211, 0.75);
color: rgba(211, 211, 211, 0.75);
}
#loginOptionCancelButton:disabled {
color: rgba(211, 211, 211, 0.75);
pointer-events: none;
}
/*******************************************************************************
* *
* Settings View (sttings.ejs) *
@ -1239,59 +1058,6 @@ body, button {
font-size: 12px;
}
/* Selected server content container */
.settingsSelServContainer {
background: rgba(0, 0, 0, 0.25);
width: 75%;
border-radius: 3px;
display: flex;
justify-content: space-between;
margin: 15px 0px;
}
/* Div which will be populated with the selected server's information. */
.settingsSelServContent {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 5px 0px;
}
/* Wrapper container for the switch server button. */
.settingsSwitchServerContainer {
display: flex;
align-items: center;
padding: 15px;
}
/* Button to switch server configurations on the mods tab. */
.settingsSwitchServerButton {
opacity: 0;
border: 1px solid rgb(255, 255, 255);
color: rgb(255, 255, 255);
background: none;
font-size: 12px;
border-radius: 3px;
font-family: 'Avenir Medium';
transition: 0.25s ease;
cursor: pointer;
outline: none;
}
.settingsSwitchServerButton:hover,
.settingsSwitchServerButton:focus {
box-shadow: 0px 0px 20px rgb(255, 255, 255);
background: rgba(255, 255, 255, 0.25);
}
.settingsSwitchServerButton:active {
box-shadow: 0px 0px 20px rgb(187, 187, 187);
background: rgba(187, 187, 187, 0.25);
border: 1px solid rgb(187, 187, 187);
color: rgb(187, 187, 187);
}
.settingsSelServContainer:hover .settingsSwitchServerButton {
opacity: 1;
}
/* Remove spin button from number inputs. */
#settingsContainer input[type=number]::-webkit-inner-spin-button {
-webkit-appearance: none;
@ -1467,26 +1233,34 @@ input:checked + .toggleSwitchSlider:before {
height: 30px;
}
/* File selection button. */
.settingsFileSelButton {
border: 0px;
/* File input for file selection. */
.settingsFileSelSel {
width: 0px;
height: 0px;
opacity: 0;
}
.settingsFileSelSel::-webkit-file-upload-button {
display: none;
}
/* Wrapper label to add a custom style to the file input. */
.settingsFileSelLabel {
border-left: 0px;
border-radius: 0px 3px 3px 0px;
font-size: 12px;
padding: 0px 5px;
cursor: pointer;
display: flex;
align-items: center;
background: rgba(126, 126, 126, 0.57);
transition: 0.25s ease;
white-space: nowrap;
outline: none;
}
.settingsFileSelButton:hover,
.settingsFileSelButton:focus {
.settingsFileSelLabel:hover,
.settingsFileSelLabel:focus,
.settingsFileSelSel:focus ~ #settingsJavaExecLabel {
text-shadow: 0px 0px 20px white;
}
.settingsFileSelButton:active {
text-shadow: 0px 0px 20px rgba(255, 255, 255, 0.75);
color: rgba(255, 255, 255, 0.75);
}
/* Description for the file selector. */
.settingsFileSelDesc {
@ -1503,65 +1277,45 @@ input:checked + .toggleSwitchSlider:before {
* Settings View (Account Tab)
* * */
.settingsAuthAccountTypeContainer {
display: flex;
/* Add account button styles. */
#settingsAddAccount {
background: rgba(0, 0, 0, 0.25);
border: 1px solid rgba(126, 126, 126, 0.57);
border-radius: 3px;
height: 50px;
width: 75%;
flex-direction: column;
}
.settingsAuthAccountTypeHeader {
display: flex;
align-items: center;
width: 100%;
justify-content: space-between;
padding: 10px 0px;
border-bottom: 1px solid #ffffff85;
margin-bottom: 30px;
}
.settingsAuthAccountTypeHeaderLeft {
display: flex;
column-gap: 5px;
}
/* Settings add account button styles. */
.settingsAddAuthAccount {
background: none;
border: none;
text-align: left;
padding: 2px 0px;
color: white;
padding: 0px 50px;
cursor: pointer;
outline: none;
transition: 0.25s ease;
}
.settingsAddAuthAccount:hover,
.settingsAddAuthAccount:focus {
text-shadow: 0px 0px 20px white, 0px 0px 20px white, 0px 0px 20px white;
#settingsAddAccount:hover,
#settingsAddAccount:focus {
background: rgba(54, 54, 54, 0.25);
text-shadow: 0px 0px 20px white;
}
.settingsAddAuthAccount:active {
text-shadow: 0px 0px 20px rgba(255, 255, 255, 0.75), 0px 0px 20px rgba(255, 255, 255, 0.75), 0px 0px 20px rgba(255, 255, 255, 0.75);
color: rgba(255, 255, 255, 0.75);
}
.settingsAddAuthAccount:disabled {
color: rgba(255, 255, 255, 0.75);
pointer-events: none;
/* Settings auth accounts header. */
#settingsCurrentAccountsHeader {
margin: 20px 0px;
}
/* Auth account list container styles. */
.settingsCurrentAccounts {
#settingsCurrentAccounts {
margin-bottom: 5%;
}
.settingsCurrentAccounts > .settingsAuthAccount:not(:last-child) {
#settingsCurrentAccounts > .settingsAuthAccount:not(:last-child) {
margin-bottom: 10px;
}
.settingsCurrentAccounts > .settingsAuthAccount:not(:first-child) {
#settingsCurrentAccounts > .settingsAuthAccount:not(:first-child) {
margin-top: 10px;
}
/* Auth account shared styles. */
.settingsAuthAccount {
display: flex;
width: 75%;
background: rgba(0, 0, 0, 0.25);
border-radius: 3px;
border: 1px solid rgba(126, 126, 126, 0.57);
@ -1702,6 +1456,59 @@ input:checked + .toggleSwitchSlider:before {
* Settings View (Mods Tab)
* * */
/* Selected server content container */
#settingsSelServContainer {
background: rgba(0, 0, 0, 0.25);
width: 75%;
border-radius: 3px;
display: flex;
justify-content: space-between;
margin: 15px 0px;
}
/* Div which will be populated with the selected server's information. */
#settingsSelServContent {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 5px 0px;
}
/* Wrapper container for the switch server button. */
#settingsSwitchServerContainer {
display: flex;
align-items: center;
padding: 15px;
}
/* Button to switch server configurations on the mods tab. */
#settingsSwitchServerButton {
opacity: 0;
border: 1px solid rgb(255, 255, 255);
color: rgb(255, 255, 255);
background: none;
font-size: 12px;
border-radius: 3px;
font-family: 'Avenir Medium';
transition: 0.25s ease;
cursor: pointer;
outline: none;
}
#settingsSwitchServerButton:hover,
#settingsSwitchServerButton:focus {
box-shadow: 0px 0px 20px rgb(255, 255, 255);
background: rgba(255, 255, 255, 0.25);
}
#settingsSwitchServerButton:active {
box-shadow: 0px 0px 20px rgb(187, 187, 187);
background: rgba(187, 187, 187, 0.25);
border: 1px solid rgb(187, 187, 187);
color: rgb(187, 187, 187);
}
#settingsSelServContainer:hover #settingsSwitchServerButton {
opacity: 1;
}
/* Main content container for the mod elements. */
#settingsModsContainer {
width: 75%;
@ -3783,7 +3590,6 @@ input:checked + .toggleSwitchSlider:before {
font-size: 10px;
line-height: 10px;
font-weight: bold;
text-align: left;
}
/* Content container for the server listing's information. */

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/fonts/ringbearer.ttf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

View File

Before

Width:  |  Height:  |  Size: 298 B

After

Width:  |  Height:  |  Size: 298 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 875 B

After

Width:  |  Height:  |  Size: 875 B

View File

Before

Width:  |  Height:  |  Size: 756 B

After

Width:  |  Height:  |  Size: 756 B

View File

Before

Width:  |  Height:  |  Size: 959 B

After

Width:  |  Height:  |  Size: 959 B

View File

Before

Width:  |  Height:  |  Size: 602 B

After

Width:  |  Height:  |  Size: 602 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 809 B

After

Width:  |  Height:  |  Size: 809 B

View File

Before

Width:  |  Height:  |  Size: 932 B

After

Width:  |  Height:  |  Size: 932 B

View File

Before

Width:  |  Height:  |  Size: 822 B

After

Width:  |  Height:  |  Size: 822 B

View File

Before

Width:  |  Height:  |  Size: 1018 B

After

Width:  |  Height:  |  Size: 1018 B

View File

Before

Width:  |  Height:  |  Size: 907 B

After

Width:  |  Height:  |  Size: 907 B

View File

Before

Width:  |  Height:  |  Size: 700 B

After

Width:  |  Height:  |  Size: 700 B

View File

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Before

Width:  |  Height:  |  Size: 654 B

After

Width:  |  Height:  |  Size: 654 B

49
assets/lang/en_US.json Normal file
View File

@ -0,0 +1,49 @@
{
"html": {
"avatarOverlay": "Edit"
},
"js": {
"login": {
"error": {
"invalidValue": "* Invalid Value",
"requiredValue": "* Required",
"userMigrated": {
"title": "Error During Login:<br>Invalid Credentials",
"desc": "You've attempted to login with a migrated account. Try again using the account email as the username."
},
"invalidCredentials": {
"title": "Error During Login:<br>Invalid Credentials",
"desc": "The email or password you've entered is incorrect. Please try again."
},
"rateLimit": {
"title": "Error During Login:<br>Too Many Attempts",
"desc": "There have been too many login attempts with this account recently. Please try again later."
},
"noInternet": {
"title": "Error During Login:<br>No Internet Connection",
"desc": "You must be connected to the internet in order to login. Please connect and try again."
},
"authDown": {
"title": "Error During Login:<br>Authentication Server Offline",
"desc": "Mojang's authentication server is currently offline or unreachable. Please wait a bit and try again. You can check the status of the server on <a href=\"https://help.mojang.com/\">Mojang's help portal</a>."
},
"notPaid": {
"title": "Error During Login:<br>Game Not Purchased",
"desc": "The account you are trying to login with has not purchased a copy of Minecraft.<br>You may purchase a copy on <a href=\"https://minecraft.net/\">Minecraft.net</a>"
},
"unknown": {
"title": "Error During Login:<br>Unknown Error"
}
},
"login": "LOGIN",
"loggingIn": "LOGGING IN",
"success": "SUCCESS",
"tryAgain": "Try Again"
},
"landing": {
"launch": {
"pleaseWait": "Please wait.."
}
}
}
}

1134
assets/scripts/landing.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,8 @@ const loginCancelContainer = document.getElementById('loginCancelContainer')
const loginCancelButton = document.getElementById('loginCancelButton')
const loginEmailError = document.getElementById('loginEmailError')
const loginUsername = document.getElementById('loginUsername')
const loginPasswordError = document.getElementById('loginPasswordError')
const loginPassword = document.getElementById('loginPassword')
const checkmarkContainer = document.getElementById('checkmarkContainer')
const loginRememberOption = document.getElementById('loginRememberOption')
const loginButton = document.getElementById('loginButton')
@ -19,6 +21,8 @@ const loginForm = document.getElementById('loginForm')
// Control variables.
let lu = false, lp = false
const loggerLogin = new LoggerUtil('%c[Login]', 'color: #000668; font-weight: bold')
/**
* Show a login error.
@ -75,7 +79,17 @@ function validateEmail(value){
* @param {string} value The password value.
*/
function validatePassword(value){
lp = true;
if(value){
loginPasswordError.style.opacity = 0
lp = true
if(lu){
loginDisabled(false)
}
} else {
lp = false
showError(loginPasswordError, Lang.queryJS('login.error.invalidValue'))
loginDisabled(true)
}
}
// Emphasize errors with shake when focus is lost.
@ -83,10 +97,17 @@ loginUsername.addEventListener('focusout', (e) => {
validateEmail(e.target.value)
shakeError(loginEmailError)
})
loginPassword.addEventListener('focusout', (e) => {
validatePassword(e.target.value)
shakeError(loginPasswordError)
})
// Validate input for each field.
loginUsername.addEventListener('input', (e) => {
loginDisabled(false)
validateEmail(e.target.value)
})
loginPassword.addEventListener('input', (e) => {
validatePassword(e.target.value)
})
/**
@ -124,6 +145,7 @@ function formDisabled(v){
loginDisabled(v)
loginCancelButton.disabled = v
loginUsername.disabled = v
loginPassword.disabled = v
if(v){
checkmarkContainer.setAttribute('disabled', v)
} else {
@ -132,6 +154,79 @@ function formDisabled(v){
loginRememberOption.disabled = v
}
/**
* Parses an error and returns a user-friendly title and description
* for our error overlay.
*
* @param {Error | {cause: string, error: string, errorMessage: string}} err A Node.js
* error or Mojang error response.
*/
function resolveError(err){
// Mojang Response => err.cause | err.error | err.errorMessage
// Node error => err.code | err.message
if(err.cause != null && err.cause === 'UserMigratedException') {
return {
title: Lang.queryJS('login.error.userMigrated.title'),
desc: Lang.queryJS('login.error.userMigrated.desc')
}
} else {
if(err.error != null){
if(err.error === 'ForbiddenOperationException'){
if(err.errorMessage != null){
if(err.errorMessage === 'Invalid credentials. Invalid username or password.'){
return {
title: Lang.queryJS('login.error.invalidCredentials.title'),
desc: Lang.queryJS('login.error.invalidCredentials.desc')
}
} else if(err.errorMessage === 'Invalid credentials.'){
return {
title: Lang.queryJS('login.error.rateLimit.title'),
desc: Lang.queryJS('login.error.rateLimit.desc')
}
}
}
}
} else {
// Request errors (from Node).
if(err.code != null){
if(err.code === 'ENOENT'){
// No Internet.
return {
title: Lang.queryJS('login.error.noInternet.title'),
desc: Lang.queryJS('login.error.noInternet.desc')
}
} else if(err.code === 'ENOTFOUND'){
// Could not reach server.
return {
title: Lang.queryJS('login.error.authDown.title'),
desc: Lang.queryJS('login.error.authDown.desc')
}
}
}
}
}
if(err.message != null){
if(err.message === 'NotPaidAccount'){
return {
title: Lang.queryJS('login.error.notPaid.title'),
desc: Lang.queryJS('login.error.notPaid.desc')
}
} else {
// Unknown error with request.
return {
title: Lang.queryJS('login.error.unknown.title'),
desc: err.message
}
}
} else {
// Unknown Mojang error.
return {
title: err.error,
desc: err.errorMessage
}
}
}
let loginViewOnSuccess = VIEWS.landing
let loginViewOnCancel = VIEWS.settings
let loginViewCancelHandler
@ -167,21 +262,22 @@ loginButton.addEventListener('click', () => {
// Show loading stuff.
loginLoading(true)
AuthManager.addOfflineAccount(loginUsername.value).then((value) => {
AuthManager.addAccount(loginUsername.value, loginPassword.value).then((value) => {
updateSelectedAccount(value)
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.success'))
$('.circle-loader').toggleClass('load-complete')
$('.checkmark').toggle()
setTimeout(() => {
switchView(VIEWS.login, loginViewOnSuccess, 500, 500, async () => {
switchView(VIEWS.login, loginViewOnSuccess, 500, 500, () => {
// Temporary workaround
if(loginViewOnSuccess === VIEWS.settings){
await prepareSettings()
prepareSettings()
}
loginViewOnSuccess = VIEWS.landing // Reset this for good measure.
loginCancelEnabled(false) // Reset this for good measure.
loginViewCancelHandler = null // Reset this for good measure.
loginUsername.value = ''
loginPassword.value = ''
$('.circle-loader').toggleClass('load-complete')
$('.checkmark').toggle()
loginLoading(false)
@ -189,25 +285,16 @@ loginButton.addEventListener('click', () => {
formDisabled(false)
})
}, 1000)
}).catch((displayableError) => {
}).catch((err) => {
loginLoading(false)
let actualDisplayableError
if(isDisplayableError(displayableError)) {
msftLoginLogger.error('Error while logging in.', displayableError)
actualDisplayableError = displayableError
} else {
// Uh oh.
msftLoginLogger.error('Unhandled error during login.', displayableError)
actualDisplayableError = Lang.queryJS('login.error.unknown')
}
setOverlayContent(actualDisplayableError.title, actualDisplayableError.desc, Lang.queryJS('login.tryAgain'))
const errF = resolveError(err)
setOverlayContent(errF.title, errF.desc, Lang.queryJS('login.tryAgain'))
setOverlayHandler(() => {
formDisabled(false)
toggleOverlay(false)
})
toggleOverlay(true)
loggerLogin.log('Error while logging in.', err)
})
})

View File

@ -117,8 +117,8 @@ function toggleOverlay(toggleState, dismissable = false, content = 'overlayConte
}
}
async function toggleServerSelection(toggleState){
await prepareServerSelectionList()
function toggleServerSelection(toggleState){
prepareServerSelectionList()
toggleOverlay(toggleState, true, 'serverSelectContent')
}
@ -130,7 +130,7 @@ async function toggleServerSelection(toggleState){
* @param {string} acknowledge Acknowledge button text.
* @param {string} dismiss Dismiss button text.
*/
function setOverlayContent(title, description, acknowledge, dismiss = Lang.queryJS('overlay.dismiss')){
function setOverlayContent(title, description, acknowledge, dismiss = 'Dismiss'){
document.getElementById('overlayTitle').innerHTML = title
document.getElementById('overlayDesc').innerHTML = description
document.getElementById('overlayAcknowledge').innerHTML = acknowledge
@ -171,11 +171,11 @@ function setDismissHandler(handler){
/* Server Select View */
document.getElementById('serverSelectConfirm').addEventListener('click', async () => {
document.getElementById('serverSelectConfirm').addEventListener('click', () => {
const listings = document.getElementsByClassName('serverListing')
for(let i=0; i<listings.length; i++){
if(listings[i].hasAttribute('selected')){
const serv = (await DistroAPI.getDistribution()).getServerById(listings[i].getAttribute('servid'))
const serv = DistroManager.getDistribution().getServer(listings[i].getAttribute('servid'))
updateSelectedServer(serv)
refreshServerStatus(true)
toggleOverlay(false)
@ -184,22 +184,19 @@ document.getElementById('serverSelectConfirm').addEventListener('click', async (
}
// None are selected? Not possible right? Meh, handle it.
if(listings.length > 0){
const serv = (await DistroAPI.getDistribution()).getServerById(listings[i].getAttribute('servid'))
const serv = DistroManager.getDistribution().getServer(listings[i].getAttribute('servid'))
updateSelectedServer(serv)
toggleOverlay(false)
}
})
document.getElementById('accountSelectConfirm').addEventListener('click', async () => {
document.getElementById('accountSelectConfirm').addEventListener('click', () => {
const listings = document.getElementsByClassName('accountListing')
for(let i=0; i<listings.length; i++){
if(listings[i].hasAttribute('selected')){
const authAcc = ConfigManager.setSelectedAccount(listings[i].getAttribute('uuid'))
ConfigManager.save()
updateSelectedAccount(authAcc)
if(getCurrentView() === VIEWS.settings) {
await prepareSettings()
}
toggleOverlay(false)
validateSelectedAccount()
return
@ -210,9 +207,6 @@ document.getElementById('accountSelectConfirm').addEventListener('click', async
const authAcc = ConfigManager.setSelectedAccount(listings[0].getAttribute('uuid'))
ConfigManager.save()
updateSelectedAccount(authAcc)
if(getCurrentView() === VIEWS.settings) {
await prepareSettings()
}
toggleOverlay(false)
validateSelectedAccount()
}
@ -267,21 +261,21 @@ function setAccountListingHandlers(){
})
}
async function populateServerListings(){
const distro = await DistroAPI.getDistribution()
function populateServerListings(){
const distro = DistroManager.getDistribution()
const giaSel = ConfigManager.getSelectedServer()
const servers = distro.servers
const servers = distro.getServers()
let htmlString = ''
for(const serv of servers){
htmlString += `<button class="serverListing" servid="${serv.rawServer.id}" ${serv.rawServer.id === giaSel ? 'selected' : ''}>
<img class="serverListingImg" src="${serv.rawServer.icon}"/>
htmlString += `<button class="serverListing" servid="${serv.getID()}" ${serv.getID() === giaSel ? 'selected' : ''}>
<img class="serverListingImg" src="${serv.getIcon()}"/>
<div class="serverListingDetails">
<span class="serverListingName">${serv.rawServer.name}</span>
<span class="serverListingDescription">${serv.rawServer.description}</span>
<span class="serverListingName">${serv.getName()}</span>
<span class="serverListingDescription">${serv.getDescription()}</span>
<div class="serverListingInfo">
<div class="serverListingVersion">${serv.rawServer.minecraftVersion}</div>
<div class="serverListingRevision">${serv.rawServer.version}</div>
${serv.rawServer.mainServer ? `<div class="serverListingStarWrapper">
<div class="serverListingVersion">${serv.getMinecraftVersion()}</div>
<div class="serverListingRevision">${serv.getVersion()}</div>
${serv.isMainServer() ? `<div class="serverListingStarWrapper">
<svg id="Layer_1" viewBox="0 0 107.45 104.74" width="20px" height="20px">
<defs>
<style>.cls-1{fill:#fff;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style>
@ -289,7 +283,7 @@ async function populateServerListings(){
<path class="cls-1" d="M100.93,65.54C89,62,68.18,55.65,63.54,52.13c2.7-5.23,18.8-19.2,28-27.55C81.36,31.74,63.74,43.87,58.09,45.3c-2.41-5.37-3.61-26.52-4.37-39-.77,12.46-2,33.64-4.36,39-5.7-1.46-23.3-13.57-33.49-20.72,9.26,8.37,25.39,22.36,28,27.55C39.21,55.68,18.47,62,6.52,65.55c12.32-2,33.63-6.06,39.34-4.9-.16,5.87-8.41,26.16-13.11,37.69,6.1-10.89,16.52-30.16,21-33.9,4.5,3.79,14.93,23.09,21,34C70,86.84,61.73,66.48,61.59,60.65,67.36,59.49,88.64,63.52,100.93,65.54Z"/>
<circle class="cls-2" cx="53.73" cy="53.9" r="38"/>
</svg>
<span class="serverListingStarTooltip">${Lang.queryJS('settings.serverListing.mainServer')}</span>
<span class="serverListingStarTooltip">Main Server</span>
</div>` : ''}
</div>
</div>
@ -305,7 +299,7 @@ function populateAccountListings(){
let htmlString = ''
for(let i=0; i<accounts.length; i++){
htmlString += `<button class="accountListing" uuid="${accounts[i].uuid}" ${i===0 ? 'selected' : ''}>
<img src="https://mc-heads.net/head/${accounts[i].uuid}/40">
<img src="https://crafatar.com/renders/head/${accounts[i].uuid}?scale=2&default=MHF_Steve&overlay">
<div class="accountListingName">${accounts[i].displayName}</div>
</button>`
}
@ -313,8 +307,8 @@ function populateAccountListings(){
}
async function prepareServerSelectionList(){
await populateServerListings()
function prepareServerSelectionList(){
populateServerListings()
setServerListingHandlers()
}

Some files were not shown because too many files have changed in this diff Show More