Compare commits
2 Commits
master
...
typescript
Author | SHA1 | Date | |
---|---|---|---|
|
b9536ed014 | ||
|
9cb10b70af |
@ -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
@ -1,3 +0,0 @@
|
||||
github: dscalzi
|
||||
patreon: dscalzi
|
||||
custom: ['https://www.paypal.me/dscalzi']
|
38
.github/workflows/build.yml
vendored
@ -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
@ -3,4 +3,5 @@
|
||||
/.vscode/
|
||||
/target/
|
||||
/logs/
|
||||
/dist/
|
||||
/dist/
|
||||
/out/
|
45
.travis.yml
Normal 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+$/"
|
21
LICENSE.txt
@ -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.
|
49
README.md
@ -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'
|
||||
|
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 604 KiB |
Before Width: | Height: | Size: 781 KiB |
Before Width: | Height: | Size: 311 KiB |
Before Width: | Height: | Size: 155 KiB |
Before Width: | Height: | Size: 343 KiB |
Before Width: | Height: | Size: 500 KiB |
Before Width: | Height: | Size: 580 KiB |
Before Width: | Height: | Size: 312 KiB |
@ -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 |
@ -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 |
Before Width: | Height: | Size: 30 KiB |
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
@ -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'
|
||||
}
|
@ -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))
|
@ -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')
|
||||
}
|
@ -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.')
|
||||
}
|
||||
})
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
@ -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)
|
||||
})
|
@ -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 = "Пара нажатий и ты на сервере"
|
@ -1,317 +0,0 @@
|
||||
[ejs.landing]
|
||||
updateAvailableTooltip = "Доступно обновление"
|
||||
usernamePlaceholder = "Имя пользователя"
|
||||
usernameEditButton = "Редактировать"
|
||||
settingsTooltip = "Настройки"
|
||||
serverStatus = "СЕРВЕР"
|
||||
serverStatusPlaceholder = "ОФФЛАЙН"
|
||||
mojangStatus = "СТАТУС MOJANG"
|
||||
mojangStatusTooltipTitle = "Сервисы"
|
||||
mojangStatusNETitle = "Необязательно"
|
||||
newsButton = "НОВОСТИ"
|
||||
launchButton = "ИГРАТЬ"
|
||||
launchButtonPlaceholder = "• Сервер не выбран"
|
||||
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 = "Выбранный аккаунт ✔"
|
||||
|
||||
[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 = "Произошла неизвестная ошибка. Подробности можно посмотреть в консоли."
|
||||
|
||||
|
@ -1,363 +0,0 @@
|
||||
[ejs.landing]
|
||||
updateAvailableTooltip = "Доступно обновление"
|
||||
usernamePlaceholder = "Имя пользователя"
|
||||
usernameEditButton = "Редактировать"
|
||||
settingsTooltip = "Настройки"
|
||||
serverStatus = "СЕРВЕР"
|
||||
serverStatusPlaceholder = "ОФФЛАЙН"
|
||||
mojangStatus = "СТАТУС MOJANG"
|
||||
mojangStatusTooltipTitle = "Сервисы"
|
||||
mojangStatusNETitle = "Необязательно"
|
||||
newsButton = "НОВОСТИ"
|
||||
launchButton = "ИГРАТЬ"
|
||||
launchButtonPlaceholder = "• Сервер не выбран"
|
||||
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 = "Выбранный аккаунт ✔"
|
||||
|
||||
[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 = "Произошла неизвестная ошибка. Подробности можно посмотреть в консоли."
|
||||
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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. */
|
BIN
assets/fonts/Avenir-Black.ttf
Normal file
BIN
assets/fonts/Avenir-BlackOblique.ttf
Normal file
BIN
assets/fonts/Avenir-Book.ttf
Normal file
BIN
assets/fonts/Avenir-BookOblique.ttf
Normal file
BIN
assets/fonts/Avenir-Heavy.ttf
Normal file
BIN
assets/fonts/Avenir-HeavyOblique.ttf
Normal file
BIN
assets/fonts/Avenir-Light.ttf
Normal file
BIN
assets/fonts/Avenir-LightOblique.ttf
Normal file
BIN
assets/fonts/Avenir-Medium.ttf
Normal file
BIN
assets/fonts/Avenir-MediumOblique.ttf
Normal file
BIN
assets/fonts/Avenir-Oblique.ttf
Normal file
BIN
assets/fonts/Avenir-Roman.ttf
Normal file
BIN
assets/fonts/ringbearer.ttf
Normal file
BIN
assets/images/LoadingSeal.png
Normal file
After Width: | Height: | Size: 244 KiB |
BIN
assets/images/LoadingText.png
Normal file
After Width: | Height: | Size: 124 KiB |
BIN
assets/images/SealCircle.ico
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
assets/images/SealCircle.png
Normal file
After Width: | Height: | Size: 142 KiB |
BIN
assets/images/backgrounds/0.jpg
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
assets/images/backgrounds/1.jpg
Normal file
After Width: | Height: | Size: 181 KiB |
BIN
assets/images/backgrounds/2.jpg
Normal file
After Width: | Height: | Size: 502 KiB |
BIN
assets/images/backgrounds/3.jpg
Normal file
After Width: | Height: | Size: 1.0 MiB |
BIN
assets/images/backgrounds/4.jpg
Normal file
After Width: | Height: | Size: 268 KiB |
BIN
assets/images/backgrounds/5.jpg
Normal file
After Width: | Height: | Size: 456 KiB |
BIN
assets/images/backgrounds/6.jpg
Normal file
After Width: | Height: | Size: 2.6 MiB |
BIN
assets/images/backgrounds/7.jpg
Normal file
After Width: | Height: | Size: 5.0 MiB |
Before Width: | Height: | Size: 298 B After Width: | Height: | Size: 298 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 875 B After Width: | Height: | Size: 875 B |
Before Width: | Height: | Size: 756 B After Width: | Height: | Size: 756 B |
Before Width: | Height: | Size: 959 B After Width: | Height: | Size: 959 B |
Before Width: | Height: | Size: 602 B After Width: | Height: | Size: 602 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 809 B After Width: | Height: | Size: 809 B |
Before Width: | Height: | Size: 932 B After Width: | Height: | Size: 932 B |
Before Width: | Height: | Size: 822 B After Width: | Height: | Size: 822 B |
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 1018 B |
Before Width: | Height: | Size: 907 B After Width: | Height: | Size: 907 B |
Before Width: | Height: | Size: 700 B After Width: | Height: | Size: 700 B |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 654 B After Width: | Height: | Size: 654 B |
49
assets/lang/en_US.json
Normal 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
@ -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)
|
||||
})
|
||||
|
||||
})
|
@ -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()
|
||||
}
|
||||
|