Compare commits
166 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
258cd0d421 | ||
|
f65eb2f2d6 | ||
|
fb1cb7b415 | ||
|
9b898cc033 | ||
|
5a6217467a | ||
|
3d470d9a32 | ||
|
cf7fd2f411 | ||
|
16790ca416 | ||
|
7e95771957 | ||
|
ab7e3c301c | ||
|
b019f40802 | ||
|
9d80d3b1d5 | ||
|
92f2aab2a1 | ||
|
06ba2ebe69 | ||
|
e6cf76b436 | ||
|
5de3acb96a | ||
|
d89b270c20 | ||
|
9e26b288fc | ||
|
22d2e064f3 | ||
|
ab3d4e9499 | ||
|
3295430c54 | ||
|
e4ddf898f9 | ||
|
22233831dc | ||
|
9c9f70a7ca | ||
|
11f5501a25 | ||
|
e96231579e | ||
|
776c46d7e1 | ||
|
47378d63d2 | ||
|
ac8724c868 | ||
|
ba265af4e9 | ||
|
aa9a03c7b1 | ||
|
ee96980dee | ||
|
2f27cdbaf4 | ||
|
c9aeb0e0aa | ||
|
2abe214005 | ||
|
f3c1e42a03 | ||
|
e639061fa8 | ||
|
e3af7669d8 | ||
|
a731fa90ea | ||
|
3ef5fabb35 | ||
|
12a84c1cce | ||
|
a1837aa20e | ||
|
16ad59685e | ||
|
15f7560916 | ||
|
e314599d99 | ||
|
28c9c65220 | ||
|
43b26ef1b9 | ||
|
e9a5f80a36 | ||
|
9a4129c11a | ||
|
b32857e7de | ||
|
a22bd32cb1 | ||
|
45630c068c | ||
|
fb586cca69 | ||
|
ecfe0de0fb | ||
|
1c598e7980 | ||
|
248937c22d | ||
|
5d44cc3408 | ||
|
9224531b62 | ||
|
e3ee03ef73 | ||
|
190bb4cf85 | ||
|
fa47c9c42c | ||
|
e7dd171cea | ||
|
2e1ab3c266 | ||
|
0a80a3b073 | ||
|
ee61ea4979 | ||
|
4e2c9ce3ec | ||
|
0bc74d9c66 | ||
|
d7d0803661 | ||
|
15fc12b625 | ||
|
ccf099a5cf | ||
|
b092722488 | ||
|
fc289262a8 | ||
|
a8c92ab272 | ||
|
f6b787c526 | ||
|
78a4f7bbf3 | ||
|
58e68c116c | ||
|
ad47617cd0 | ||
|
2fdb217e64 | ||
|
c1d36d2b03 | ||
|
9c6d75f812 | ||
|
a2168da999 | ||
|
430e840514 | ||
|
f9e4fd8561 | ||
|
54e6572754 | ||
|
79135f310d | ||
|
31a51b8e7f | ||
|
84c13e6972 | ||
|
1b48ad58e6 | ||
|
2c487f71ad | ||
|
61dbabdba3 | ||
|
cd1ca7edf5 | ||
|
be4a42b50a | ||
|
48d0c9e549 | ||
|
a9fa7c4ff9 | ||
|
cb8d1bb00f | ||
|
2743585b12 | ||
|
a6ca2e800c | ||
|
779a9a54ec | ||
|
8723a192b4 | ||
|
c93d4922a6 | ||
|
d48abf444c | ||
|
3ea41b42e5 | ||
|
faa5f5267b | ||
|
c0776dcf61 | ||
|
25e7e5aa55 | ||
|
17e36fa5a2 | ||
|
2156099ea8 | ||
|
9a2c1fd9b9 | ||
|
cc86f2a257 | ||
|
e6897dad2c | ||
|
bd19b16530 | ||
|
46853157ec | ||
|
0203295e7c | ||
|
6b755fef15 | ||
|
bace2e12e1 | ||
|
eeaa2e98d0 | ||
|
bec1385c55 | ||
|
f795b28d23 | ||
|
ef8f36b519 | ||
|
b09cd2ef28 | ||
|
1bdb413ab5 | ||
|
71b25d3e5c | ||
|
7f821f36d7 | ||
|
1430d0faa2 | ||
|
8726638a23 | ||
|
b1abe07aeb | ||
|
b3f8ff9595 | ||
|
64dfc541dc | ||
|
c151744ff9 | ||
|
9d1aa497a7 | ||
|
141a753893 | ||
|
8b5a7bb02e | ||
|
1a2e9f3be4 | ||
|
5fc6be444a | ||
|
96db607912 | ||
|
1110119df0 | ||
|
4f504cd470 | ||
|
19ee187f10 | ||
|
9761c1d9d0 | ||
|
8b04f476ee | ||
|
90cd2c4614 | ||
|
da706813ba | ||
|
d087ca5a60 | ||
|
28631610ab | ||
|
cbc8b07467 | ||
|
590cefc5d4 | ||
|
0194c2b6f1 | ||
|
d94365f535 | ||
|
555cb0125c | ||
|
68362021f5 | ||
|
b54b206b9a | ||
|
ef112cef3a | ||
|
887bb67ab0 | ||
|
c141475404 | ||
|
a0520a9458 | ||
|
448440a604 | ||
|
90818245f3 | ||
|
644a32de37 | ||
|
5c0a293390 | ||
|
1fc118ee8c | ||
|
e08c3a903a | ||
|
81367bc619 | ||
|
e8e7f85c64 | ||
|
c834ca971a | ||
|
6b96770c98 | ||
|
46f8b195d6 |
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
dist
|
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"env": {
|
"env": {
|
||||||
"es6": true,
|
"es2022": true,
|
||||||
"node": true
|
"node": true
|
||||||
},
|
},
|
||||||
"extends": "eslint:recommended",
|
"extends": "eslint:recommended",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2017,
|
"ecmaVersion": 2022,
|
||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
@ -45,6 +45,9 @@
|
|||||||
"ignoreRestSiblings": false,
|
"ignoreRestSiblings": false,
|
||||||
"argsIgnorePattern": "reject"
|
"argsIgnorePattern": "reject"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"no-async-promise-executor": [
|
||||||
|
0
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
|
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
github: dscalzi
|
||||||
|
patreon: dscalzi
|
||||||
|
custom: ['https://www.paypal.me/dscalzi']
|
38
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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: 18
|
||||||
|
|
||||||
|
- 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
|
44
.travis.yml
@ -1,44 +0,0 @@
|
|||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: osx
|
|
||||||
osx_image: xcode10
|
|
||||||
language: node_js
|
|
||||||
node_js: "10"
|
|
||||||
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
|
|
||||||
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
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017-2022 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.
|
85
README.md
@ -1,18 +1,21 @@
|
|||||||
<p align="center"><img src="./app/assets/images/WesterosSealCircle.png" width="150px" height="150px" alt="westeroscraft"></p>
|
<p align="center"><img src="./app/assets/images/SealCircle.png" width="150px" height="150px" alt="aventium softworks"></p>
|
||||||
|
|
||||||
<h1 align="center"> WesterosCraft Launcher</h1>
|
<h1 align="center">Helios Launcher</h1>
|
||||||
|
|
||||||
[<p align="center"><img src="https://img.shields.io/travis/WesterosCraftCode/ElectronLauncher.svg?style=for-the-badge" alt="travis">](https://travis-ci.org/WesterosCraftCode/ElectronLauncher) [<img src="https://img.shields.io/github/downloads/WesterosCraftCode/ElectronLauncher/total.svg?style=for-the-badge" alt="downloads">](https://github.com/WesterosCraftCode/ElectronLauncher/releases) <img src="https://forthebadge.com/images/badges/winter-is-coming.svg" height="28px" alt="stark"></p>
|
<em><h5 align="center">(formerly Electron Launcher)</h5></em>
|
||||||
|
|
||||||
<p align="center">Join WesterosCraft without worrying about installing Java, Forge, or other mods. We'll handle that for you.</p>
|
[<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>
|
||||||
|
|
||||||
![Screenshot 1](https://i.imgur.com/M8HVW9H.jpg)
|
<p align="center">Join modded servers without worrying about installing Java, Forge, or other mods. We'll handle that for you.</p>
|
||||||
![Screenshot 2](https://i.imgur.com/zDiSoq4.jpg)
|
|
||||||
|
![Screenshot 1](https://i.imgur.com/6o7SmH6.png)
|
||||||
|
![Screenshot 2](https://i.imgur.com/x3B34n1.png)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* 🔒 Full account management.
|
* 🔒 Full account management.
|
||||||
* Add multiple accounts and easily switch between them.
|
* 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.
|
* Credentials are never stored and transmitted directly to Mojang.
|
||||||
* 📂 Efficient asset management.
|
* 📂 Efficient asset management.
|
||||||
* Receive client updates as soon as we release them.
|
* Receive client updates as soon as we release them.
|
||||||
@ -36,24 +39,25 @@ This is not an exhaustive list. Download and install the launcher to gauge all i
|
|||||||
|
|
||||||
## Downloads
|
## Downloads
|
||||||
|
|
||||||
You can download from [GitHub Releases](https://github.com/WesterosCraftCode/ElectronLauncher/releases) or [WesterosCraft.com/launcher](https://westeroscraft.com/launcher)
|
You can download from [GitHub Releases](https://github.com/dscalzi/HeliosLauncher/releases)
|
||||||
|
|
||||||
#### Latest Release
|
#### Latest Release
|
||||||
|
|
||||||
[![](https://img.shields.io/github/release/WesterosCraftCode/ElectronLauncher.svg?style=flat-square)](https://github.com/WesterosCraftCode/ElectronLauncher/releases/latest)
|
[![](https://img.shields.io/github/release/dscalzi/HeliosLauncher.svg?style=flat-square)](https://github.com/dscalzi/HeliosLauncher/releases/latest)
|
||||||
|
|
||||||
#### Latest Pre-Release
|
#### Latest Pre-Release
|
||||||
[![](https://img.shields.io/github/release/WesterosCraftCode/ElectronLauncher/all.svg?style=flat-square)](https://github.com/WesterosCraftCode/ElectronLauncher/releases)
|
[![](https://img.shields.io/github/release/dscalzi/HeliosLauncher/all.svg?style=flat-square)](https://github.com/dscalzi/HeliosLauncher/releases)
|
||||||
|
|
||||||
**Supported Platforms**
|
**Supported Platforms**
|
||||||
|
|
||||||
If you download from the [Releases](https://github.com/WesterosCraftCode/ElectronLauncher/releases) tab, select the installer for your system.
|
If you download from the [Releases](https://github.com/dscalzi/HeliosLauncher/releases) tab, select the installer for your system.
|
||||||
|
|
||||||
| Platform | File |
|
| Platform | File |
|
||||||
| -------- | ---- |
|
| -------- | ---- |
|
||||||
| Windows x64 | `westeroscraftlauncher-setup-VERSION.exe` |
|
| Windows x64 | `Helios-Launcher-setup-VERSION.exe` |
|
||||||
| macOS | `westeroscraftlauncher-VERSION.dmg` |
|
| macOS x64 | `Helios-Launcher-setup-VERSION-x64.dmg` |
|
||||||
| Linux x64 | `westeroscraftlauncher-VERSION-x86_64.AppImage` |
|
| macOS arm64 | `Helios-Launcher-setup-VERSION-arm64.dmg` |
|
||||||
|
| Linux x64 | `Helios-Launcher-setup-VERSION.AppImage` |
|
||||||
|
|
||||||
## Console
|
## Console
|
||||||
|
|
||||||
@ -69,24 +73,26 @@ Ensure that you have the console tab selected. Do not paste anything into the co
|
|||||||
|
|
||||||
If you want to export the console output, simply right click anywhere on the console and click **Save as..**
|
If you want to export the console output, simply right click anywhere on the console and click **Save as..**
|
||||||
|
|
||||||
![console example](https://i.imgur.com/HazXrgT.png)
|
![console example](https://i.imgur.com/T5e73jP.png)
|
||||||
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
This section details the setup of a basic developmentment environment.
|
||||||
|
|
||||||
### Getting Started
|
### Getting Started
|
||||||
|
|
||||||
**System Requirements**
|
**System Requirements**
|
||||||
|
|
||||||
* [Node.js][nodejs] v10.x.x
|
* [Node.js][nodejs] v18
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Clone and Install Dependencies**
|
**Clone and Install Dependencies**
|
||||||
|
|
||||||
```console
|
```console
|
||||||
> git clone https://github.com/WesterosCraftCode/ElectronLauncher.git
|
> git clone https://github.com/dscalzi/HeliosLauncher.git
|
||||||
> cd ElectronLauncher
|
> cd HeliosLauncher
|
||||||
> npm install
|
> npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -134,28 +140,24 @@ Paste the following into `.vscode/launch.json`
|
|||||||
"name": "Debug Main Process",
|
"name": "Debug Main Process",
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceFolder}",
|
||||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
|
"program": "${workspaceFolder}/node_modules/electron/cli.js",
|
||||||
"windows": {
|
"args" : ["."],
|
||||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
|
"outputCapture": "std"
|
||||||
},
|
|
||||||
"args": ["."],
|
|
||||||
"console": "integratedTerminal",
|
|
||||||
"protocol": "inspector"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Debug Renderer Process",
|
"name": "Debug Renderer Process",
|
||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
|
||||||
"windows": {
|
"windows": {
|
||||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
||||||
},
|
},
|
||||||
"runtimeArgs": [
|
"runtimeArgs": [
|
||||||
"${workspaceRoot}/.",
|
"${workspaceFolder}/.",
|
||||||
"--remote-debugging-port=9222"
|
"--remote-debugging-port=9222"
|
||||||
],
|
],
|
||||||
"webRoot": "${workspaceRoot}"
|
"webRoot": "${workspaceFolder}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -177,30 +179,25 @@ Note that you **cannot** open the DevTools window while using this debug configu
|
|||||||
|
|
||||||
### Note on Third-Party Usage
|
### Note on Third-Party Usage
|
||||||
|
|
||||||
You may use this software in your own project so long as the following conditions are met.
|
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.
|
||||||
|
|
||||||
* Credit is expressly given to the original authors (Daniel Scalzi, WesterosCraft).
|
For instructions on setting up Microsoft Authentication, see https://github.com/dscalzi/HeliosLauncher/blob/master/docs/MicrosoftAuth.md.
|
||||||
* 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
|
## Resources
|
||||||
|
|
||||||
* [WesterosCraft][westeroscraft]
|
|
||||||
* [Support Forum][supportforum]
|
|
||||||
* [Wiki][wiki]
|
* [Wiki][wiki]
|
||||||
|
* [Nebula (Create Distribution.json)][nebula]
|
||||||
|
* [v2 Rewrite Branch (Inactive)][v2branch]
|
||||||
|
|
||||||
The best way to contact the developers is on Discord.
|
The best way to contact the developers is on Discord.
|
||||||
|
|
||||||
[![discord](https://discordapp.com/api/guilds/98469309352775680/embed.png?style=banner2)][discord]
|
[![discord](https://discordapp.com/api/guilds/211524927831015424/embed.png?style=banner3)][discord]
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### See you in Westeros.
|
### See you ingame.
|
||||||
|
|
||||||
|
|
||||||
[nodejs]: https://nodejs.org/en/ 'Node.js'
|
[nodejs]: https://nodejs.org/en/ 'Node.js'
|
||||||
@ -208,7 +205,7 @@ The best way to contact the developers is on Discord.
|
|||||||
[mainprocess]: https://electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes 'Main Process'
|
[mainprocess]: https://electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes 'Main Process'
|
||||||
[rendererprocess]: https://electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes 'Renderer Process'
|
[rendererprocess]: https://electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes 'Renderer Process'
|
||||||
[chromedebugger]: https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome 'Debugger for Chrome'
|
[chromedebugger]: https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome 'Debugger for Chrome'
|
||||||
[westeroscraft]: https://westeroscraft.com/ 'WesterosCraft.com'
|
[discord]: https://discord.gg/zNWUXdt 'Discord'
|
||||||
[supportforum]: https://westeroscraft.com/forum/support.40/ 'Support Forum'
|
[wiki]: https://github.com/dscalzi/HeliosLauncher/wiki 'wiki'
|
||||||
[discord]: https://discord.gg/hqdjs3m 'Discord'
|
[nebula]: https://github.com/dscalzi/Nebula 'dscalzi/Nebula'
|
||||||
[wiki]: https://github.com/WesterosCraftCode/ElectronLauncher/wiki 'wiki'
|
[v2branch]: https://github.com/dscalzi/HeliosLauncher/tree/ts-refactor 'v2 branch'
|
||||||
|
22
app/app.ejs
@ -1,7 +1,7 @@
|
|||||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" http-equiv="Content-Security-Policy" content="script-src 'self'"/>
|
<meta charset="utf-8" http-equiv="Content-Security-Policy" content="script-src 'self' 'sha256-In6B8teKZQll5heMl9bS7CESTbGvuAt3VVV86BUQBDk='"/>
|
||||||
<title>Westeroscraft Launcher</title>
|
<title><%= lang('app.title') %></title>
|
||||||
<script src="./assets/js/scripts/uicore.js"></script>
|
<script src="./assets/js/scripts/uicore.js"></script>
|
||||||
<script src="./assets/js/scripts/uibinder.js"></script>
|
<script src="./assets/js/scripts/uibinder.js"></script>
|
||||||
<link type="text/css" rel="stylesheet" href="./assets/css/launcher.css">
|
<link type="text/css" rel="stylesheet" href="./assets/css/launcher.css">
|
||||||
@ -27,19 +27,21 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body bkid="<%=bkid%>">
|
<body bkid="<%=bkid%>">
|
||||||
<% include frame.ejs %>
|
<%- include('frame') %>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<% include welcome.ejs %>
|
<%- include('welcome') %>
|
||||||
<% include login.ejs %>
|
<%- include('login') %>
|
||||||
<% include settings.ejs %>
|
<%- include('waiting') %>
|
||||||
<% include landing.ejs %>
|
<%- include('loginOptions') %>
|
||||||
|
<%- include('settings') %>
|
||||||
|
<%- include('landing') %>
|
||||||
</div>
|
</div>
|
||||||
<% include overlay.ejs %>
|
<%- include('overlay') %>
|
||||||
<div id="loadingContainer">
|
<div id="loadingContainer">
|
||||||
<div id="loadingContent">
|
<div id="loadingContent">
|
||||||
<div id="loadSpinnerContainer">
|
<div id="loadSpinnerContainer">
|
||||||
<img id="loadCenterImage" src="assets/images/westeroscraftlogo1.png">
|
<img id="loadCenterImage" src="assets/images/LoadingSeal.png">
|
||||||
<img id="loadSpinnerImage" class="rotating" src="assets/images/westeroscraftlogo2.png">
|
<img id="loadSpinnerImage" class="rotating" src="assets/images/LoadingText.png">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -105,16 +105,13 @@ body, button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Frame logo (windows only). */
|
/* Frame logo (windows only). */
|
||||||
#frameImageDock {
|
#frameTitleDock {
|
||||||
width: 100px;
|
padding: 0px 10px;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
#frameImage {
|
#frameTitleText {
|
||||||
height: 15px;
|
font-size: 14px;
|
||||||
filter: grayscale(100%);
|
font-family: 'Avenir Medium';
|
||||||
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Windows frame button dock. */
|
/* Windows frame button dock. */
|
||||||
@ -225,6 +222,7 @@ body, button {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.50);
|
||||||
}
|
}
|
||||||
|
|
||||||
#welcomeContent {
|
#welcomeContent {
|
||||||
@ -875,6 +873,175 @@ 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) *
|
* Settings View (sttings.ejs) *
|
||||||
@ -1061,6 +1228,59 @@ body, button {
|
|||||||
font-size: 12px;
|
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. */
|
/* Remove spin button from number inputs. */
|
||||||
#settingsContainer input[type=number]::-webkit-inner-spin-button {
|
#settingsContainer input[type=number]::-webkit-inner-spin-button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
@ -1236,34 +1456,26 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* File input for file selection. */
|
/* File selection button. */
|
||||||
.settingsFileSelSel {
|
.settingsFileSelButton {
|
||||||
width: 0px;
|
border: 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;
|
border-radius: 0px 3px 3px 0px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 0px 5px;
|
padding: 0px 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background: rgba(126, 126, 126, 0.57);
|
background: rgba(126, 126, 126, 0.57);
|
||||||
transition: 0.25s ease;
|
transition: 0.25s ease;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
.settingsFileSelLabel:hover,
|
.settingsFileSelButton:hover,
|
||||||
.settingsFileSelLabel:focus,
|
.settingsFileSelButton:focus {
|
||||||
.settingsFileSelSel:focus ~ #settingsJavaExecLabel {
|
|
||||||
text-shadow: 0px 0px 20px white;
|
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. */
|
/* Description for the file selector. */
|
||||||
.settingsFileSelDesc {
|
.settingsFileSelDesc {
|
||||||
@ -1280,45 +1492,65 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
* Settings View (Account Tab)
|
* Settings View (Account Tab)
|
||||||
* * */
|
* * */
|
||||||
|
|
||||||
/* Add account button styles. */
|
.settingsAuthAccountTypeContainer {
|
||||||
#settingsAddAccount {
|
display: flex;
|
||||||
background: rgba(0, 0, 0, 0.25);
|
|
||||||
border: 1px solid rgba(126, 126, 126, 0.57);
|
|
||||||
border-radius: 3px;
|
|
||||||
height: 50px;
|
|
||||||
width: 75%;
|
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;
|
text-align: left;
|
||||||
padding: 0px 50px;
|
padding: 2px 0px;
|
||||||
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: 0.25s ease;
|
transition: 0.25s ease;
|
||||||
}
|
}
|
||||||
#settingsAddAccount:hover,
|
.settingsAddAuthAccount:hover,
|
||||||
#settingsAddAccount:focus {
|
.settingsAddAuthAccount:focus {
|
||||||
background: rgba(54, 54, 54, 0.25);
|
text-shadow: 0px 0px 20px white, 0px 0px 20px white, 0px 0px 20px white;
|
||||||
text-shadow: 0px 0px 20px white;
|
|
||||||
}
|
}
|
||||||
|
.settingsAddAuthAccount:active {
|
||||||
/* Settings auth accounts header. */
|
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);
|
||||||
#settingsCurrentAccountsHeader {
|
color: rgba(255, 255, 255, 0.75);
|
||||||
margin: 20px 0px;
|
}
|
||||||
|
.settingsAddAuthAccount:disabled {
|
||||||
|
color: rgba(255, 255, 255, 0.75);
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Auth account list container styles. */
|
/* Auth account list container styles. */
|
||||||
#settingsCurrentAccounts {
|
.settingsCurrentAccounts {
|
||||||
margin-bottom: 5%;
|
margin-bottom: 5%;
|
||||||
}
|
}
|
||||||
#settingsCurrentAccounts > .settingsAuthAccount:not(:last-child) {
|
.settingsCurrentAccounts > .settingsAuthAccount:not(:last-child) {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
#settingsCurrentAccounts > .settingsAuthAccount:not(:first-child) {
|
.settingsCurrentAccounts > .settingsAuthAccount:not(:first-child) {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Auth account shared styles. */
|
/* Auth account shared styles. */
|
||||||
.settingsAuthAccount {
|
.settingsAuthAccount {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 75%;
|
|
||||||
background: rgba(0, 0, 0, 0.25);
|
background: rgba(0, 0, 0, 0.25);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 1px solid rgba(126, 126, 126, 0.57);
|
border: 1px solid rgba(126, 126, 126, 0.57);
|
||||||
@ -1459,59 +1691,6 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
* Settings View (Mods Tab)
|
* 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. */
|
/* Main content container for the mod elements. */
|
||||||
#settingsModsContainer {
|
#settingsModsContainer {
|
||||||
width: 75%;
|
width: 75%;
|
||||||
@ -3593,6 +3772,7 @@ input:checked + .toggleSwitchSlider:before {
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
line-height: 10px;
|
line-height: 10px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Content container for the server listing's information. */
|
/* Content container for the server listing's information. */
|
||||||
|
BIN
app/assets/images/LoadingSeal.png
Normal file
After Width: | Height: | Size: 244 KiB |
BIN
app/assets/images/LoadingText.png
Normal file
After Width: | Height: | Size: 124 KiB |
BIN
app/assets/images/SealCircle.ico
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
app/assets/images/SealCircle.png
Normal file
After Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 807 KiB |
Before Width: | Height: | Size: 361 KiB |
Before Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 94 KiB |
7
app/assets/images/icons/microsoft.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 303 B |
5
app/assets/images/icons/mojang.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 664 B |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 606 KiB |
Before Width: | Height: | Size: 227 KiB |
@ -1,48 +0,0 @@
|
|||||||
const { AssetGuard } = require('./assetguard')
|
|
||||||
|
|
||||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
|
||||||
|
|
||||||
const tracker = new AssetGuard(process.argv[2], process.argv[3])
|
|
||||||
console.log('AssetExec Started')
|
|
||||||
|
|
||||||
// Temporary for debug purposes.
|
|
||||||
process.on('unhandledRejection', r => console.log(r))
|
|
||||||
|
|
||||||
tracker.on('validate', (data) => {
|
|
||||||
process.send({context: 'validate', data})
|
|
||||||
})
|
|
||||||
tracker.on('progress', (data, acc, total) => {
|
|
||||||
process.send({context: 'progress', data, value: acc, total, percent: parseInt((acc/total)*100)})
|
|
||||||
})
|
|
||||||
tracker.on('complete', (data, ...args) => {
|
|
||||||
process.send({context: 'complete', data, args})
|
|
||||||
})
|
|
||||||
tracker.on('error', (data, error) => {
|
|
||||||
process.send({context: 'error', data, error})
|
|
||||||
})
|
|
||||||
|
|
||||||
process.on('message', (msg) => {
|
|
||||||
if(msg.task === 'execute'){
|
|
||||||
const func = msg.function
|
|
||||||
let nS = tracker[func]
|
|
||||||
let iS = AssetGuard[func]
|
|
||||||
if(typeof nS === 'function' || typeof iS === 'function'){
|
|
||||||
const f = typeof nS === 'function' ? nS : iS
|
|
||||||
const res = f.apply(f === nS ? tracker : null, msg.argsArr)
|
|
||||||
if(res instanceof Promise){
|
|
||||||
res.then((v) => {
|
|
||||||
process.send({result: v, context: func})
|
|
||||||
}).catch((err) => {
|
|
||||||
process.send({result: err.message, context: func})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
process.send({result: res, context: func})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
process.on('disconnect', () => {
|
|
||||||
console.log('AssetExec Disconnected')
|
|
||||||
process.exit(0)
|
|
||||||
})
|
|
@ -9,16 +9,19 @@
|
|||||||
* @module authmanager
|
* @module authmanager
|
||||||
*/
|
*/
|
||||||
// Requirements
|
// Requirements
|
||||||
const ConfigManager = require('./configmanager')
|
const ConfigManager = require('./configmanager')
|
||||||
const LoggerUtil = require('./loggerutil')
|
const { LoggerUtil } = require('helios-core')
|
||||||
const Mojang = require('./mojang')
|
const { RestResponseStatus } = require('helios-core/common')
|
||||||
const logger = LoggerUtil('%c[AuthManager]', 'color: #a02d2a; font-weight: bold')
|
const { MojangRestAPI, mojangErrorDisplayable, MojangErrorCode } = require('helios-core/mojang')
|
||||||
const loggerSuccess = LoggerUtil('%c[AuthManager]', 'color: #209b07; font-weight: bold')
|
const { MicrosoftAuth, microsoftErrorDisplayable, MicrosoftErrorCode } = require('helios-core/microsoft')
|
||||||
|
const { AZURE_CLIENT_ID } = require('./ipcconstants')
|
||||||
|
|
||||||
|
const log = LoggerUtil.getLogger('AuthManager')
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an account. This will authenticate the given credentials with Mojang's
|
* 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
|
* authserver. The resultant data will be stored as an auth account in the
|
||||||
* configuration database.
|
* configuration database.
|
||||||
*
|
*
|
||||||
@ -26,40 +29,172 @@ const loggerSuccess = LoggerUtil('%c[AuthManager]', 'color: #209b07; font-weight
|
|||||||
* @param {string} password The account password.
|
* @param {string} password The account password.
|
||||||
* @returns {Promise.<Object>} Promise which resolves the resolved authenticated account object.
|
* @returns {Promise.<Object>} Promise which resolves the resolved authenticated account object.
|
||||||
*/
|
*/
|
||||||
exports.addAccount = async function(username, password){
|
exports.addMojangAccount = async function(username, password) {
|
||||||
try {
|
try {
|
||||||
const session = await Mojang.authenticate(username, password, ConfigManager.getClientToken())
|
const response = await MojangRestAPI.authenticate(username, password, ConfigManager.getClientToken())
|
||||||
if(session.selectedProfile != null){
|
console.log(response)
|
||||||
const ret = ConfigManager.addAuthAccount(session.selectedProfile.id, session.accessToken, username, session.selectedProfile.name)
|
if(response.responseStatus === RestResponseStatus.SUCCESS) {
|
||||||
if(ConfigManager.getClientToken() == null){
|
|
||||||
ConfigManager.setClientToken(session.clientToken)
|
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))
|
||||||
}
|
}
|
||||||
ConfigManager.save()
|
|
||||||
return ret
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('NotPaidAccount')
|
return Promise.reject(mojangErrorDisplayable(response.mojangErrorCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err){
|
} catch (err){
|
||||||
return Promise.reject(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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove an account. This will invalidate the access token associated
|
* 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.
|
* with the account and then remove it from the database.
|
||||||
*
|
*
|
||||||
* @param {string} uuid The UUID of the account to be removed.
|
* @param {string} uuid The UUID of the account to be removed.
|
||||||
* @returns {Promise.<void>} Promise which resolves to void when the action is complete.
|
* @returns {Promise.<void>} Promise which resolves to void when the action is complete.
|
||||||
*/
|
*/
|
||||||
exports.removeAccount = async function(uuid){
|
exports.removeMojangAccount = async function(uuid){
|
||||||
try {
|
try {
|
||||||
const authAcc = ConfigManager.getAuthAccount(uuid)
|
const authAcc = ConfigManager.getAuthAccount(uuid)
|
||||||
await Mojang.invalidate(authAcc.accessToken, ConfigManager.getClientToken())
|
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.removeAuthAccount(uuid)
|
||||||
ConfigManager.save()
|
ConfigManager.save()
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
} catch (err){
|
} catch (err){
|
||||||
|
log.error('Error while removing account', err)
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,31 +204,112 @@ exports.removeAccount = async function(uuid){
|
|||||||
* we will attempt to refresh the access token and update that value. If that fails, a
|
* we will attempt to refresh the access token and update that value. If that fails, a
|
||||||
* new login will be required.
|
* new login will be required.
|
||||||
*
|
*
|
||||||
* **Function is WIP**
|
* @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,
|
* @returns {Promise.<boolean>} Promise which resolves to true if the access token is valid,
|
||||||
* otherwise false.
|
* otherwise false.
|
||||||
*/
|
*/
|
||||||
exports.validateSelected = async function(){
|
exports.validateSelected = async function(){
|
||||||
const current = ConfigManager.getSelectedAccount()
|
const current = ConfigManager.getSelectedAccount()
|
||||||
const isValid = await Mojang.validate(current.accessToken, ConfigManager.getClientToken())
|
|
||||||
if(!isValid){
|
if(current.type === 'microsoft') {
|
||||||
try {
|
return await validateSelectedMicrosoftAccount()
|
||||||
const session = await Mojang.refresh(current.accessToken, ConfigManager.getClientToken())
|
|
||||||
ConfigManager.updateAuthAccount(current.uuid, session.accessToken)
|
|
||||||
ConfigManager.save()
|
|
||||||
} catch(err) {
|
|
||||||
logger.debug('Error while validating selected profile:', err)
|
|
||||||
if(err && err.error === 'ForbiddenOperationException'){
|
|
||||||
// What do we do?
|
|
||||||
}
|
|
||||||
logger.log('Account access token is invalid.')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
loggerSuccess.log('Account access token validated.')
|
|
||||||
return true
|
|
||||||
} else {
|
} else {
|
||||||
loggerSuccess.log('Account access token validated.')
|
return await validateSelectedMojangAccount()
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,14 +1,15 @@
|
|||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
|
const { LoggerUtil } = require('helios-core')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const logger = require('./loggerutil')('%c[ConfigManager]', 'color: #a02d2a; font-weight: bold')
|
const logger = LoggerUtil.getLogger('ConfigManager')
|
||||||
|
|
||||||
const sysRoot = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME)
|
const sysRoot = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME)
|
||||||
const dataPath = path.join(sysRoot, '.westeroscraft')
|
|
||||||
|
|
||||||
// Forked processes do not have access to electron, so we have this workaround.
|
const dataPath = path.join(sysRoot, '.helioslauncher')
|
||||||
const launcherDir = process.env.CONFIG_DIRECT_PATH || require('electron').remote.app.getPath('userData')
|
|
||||||
|
const launcherDir = require('@electron/remote').app.getPath('userData')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the absolute path of the launcher directory.
|
* Retrieve the absolute path of the launcher directory.
|
||||||
@ -42,24 +43,30 @@ const configPath = path.join(exports.getLauncherDirectory(), 'config.json')
|
|||||||
const configPathLEGACY = path.join(dataPath, 'config.json')
|
const configPathLEGACY = path.join(dataPath, 'config.json')
|
||||||
const firstLaunch = !fs.existsSync(configPath) && !fs.existsSync(configPathLEGACY)
|
const firstLaunch = !fs.existsSync(configPath) && !fs.existsSync(configPathLEGACY)
|
||||||
|
|
||||||
exports.getAbsoluteMinRAM = function(){
|
exports.getAbsoluteMinRAM = function(ram){
|
||||||
const mem = os.totalmem()
|
if(ram?.minimum != null) {
|
||||||
return mem >= 6000000000 ? 3 : 2
|
return ram.minimum/1024
|
||||||
|
} else {
|
||||||
|
// Legacy behavior
|
||||||
|
const mem = os.totalmem()
|
||||||
|
return mem >= (6*1073741824) ? 3 : 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getAbsoluteMaxRAM = function(){
|
exports.getAbsoluteMaxRAM = function(ram){
|
||||||
const mem = os.totalmem()
|
const mem = os.totalmem()
|
||||||
const gT16 = mem-16000000000
|
const gT16 = mem-(16*1073741824)
|
||||||
return Math.floor((mem-1000000000-(gT16 > 0 ? (Number.parseInt(gT16/8) + 16000000000/4) : mem/4))/1000000000)
|
return Math.floor((mem-(gT16 > 0 ? (Number.parseInt(gT16/8) + (16*1073741824)/4) : mem/4))/1073741824)
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveMaxRAM(){
|
function resolveSelectedRAM(ram) {
|
||||||
const mem = os.totalmem()
|
if(ram?.recommended != null) {
|
||||||
return mem >= 8000000000 ? '4G' : (mem >= 6000000000 ? '3G' : '2G')
|
return `${ram.recommended}M`
|
||||||
}
|
} else {
|
||||||
|
// Legacy behavior
|
||||||
function resolveMinRAM(){
|
const mem = os.totalmem()
|
||||||
return resolveMaxRAM()
|
return mem >= (8*1073741824) ? '4G' : (mem >= (6*1073741824) ? '3G' : '2G')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,17 +77,6 @@ function resolveMinRAM(){
|
|||||||
*/
|
*/
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
settings: {
|
settings: {
|
||||||
java: {
|
|
||||||
minRAM: resolveMinRAM(),
|
|
||||||
maxRAM: resolveMaxRAM(), // Dynamic
|
|
||||||
executable: null,
|
|
||||||
jvmOptions: [
|
|
||||||
'-XX:+UseConcMarkSweepGC',
|
|
||||||
'-XX:+CMSIncrementalMode',
|
|
||||||
'-XX:-UseAdaptiveSizePolicy',
|
|
||||||
'-Xmn128M'
|
|
||||||
],
|
|
||||||
},
|
|
||||||
game: {
|
game: {
|
||||||
resWidth: 1280,
|
resWidth: 1280,
|
||||||
resHeight: 720,
|
resHeight: 720,
|
||||||
@ -102,7 +98,8 @@ const DEFAULT_CONFIG = {
|
|||||||
selectedServer: null, // Resolved
|
selectedServer: null, // Resolved
|
||||||
selectedAccount: null,
|
selectedAccount: null,
|
||||||
authenticationDatabase: {},
|
authenticationDatabase: {},
|
||||||
modConfigurations: []
|
modConfigurations: [],
|
||||||
|
javaConfig: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = null
|
let config = null
|
||||||
@ -143,8 +140,8 @@ exports.load = function(){
|
|||||||
doValidate = true
|
doValidate = true
|
||||||
} catch (err){
|
} catch (err){
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
logger.log('Configuration file contains malformed JSON or is corrupt.')
|
logger.info('Configuration file contains malformed JSON or is corrupt.')
|
||||||
logger.log('Generating a new configuration file.')
|
logger.info('Generating a new configuration file.')
|
||||||
fs.ensureDirSync(path.join(configPath, '..'))
|
fs.ensureDirSync(path.join(configPath, '..'))
|
||||||
config = DEFAULT_CONFIG
|
config = DEFAULT_CONFIG
|
||||||
exports.save()
|
exports.save()
|
||||||
@ -154,7 +151,7 @@ exports.load = function(){
|
|||||||
exports.save()
|
exports.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.log('Successfully Loaded')
|
logger.info('Successfully Loaded')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,7 +173,7 @@ function validateKeySet(srcObj, destObj){
|
|||||||
if(srcObj == null){
|
if(srcObj == null){
|
||||||
srcObj = {}
|
srcObj = {}
|
||||||
}
|
}
|
||||||
const validationBlacklist = ['authenticationDatabase']
|
const validationBlacklist = ['authenticationDatabase', 'javaConfig']
|
||||||
const keys = Object.keys(srcObj)
|
const keys = Object.keys(srcObj)
|
||||||
for(let i=0; i<keys.length; i++){
|
for(let i=0; i<keys.length; i++){
|
||||||
if(typeof destObj[keys[i]] === 'undefined'){
|
if(typeof destObj[keys[i]] === 'undefined'){
|
||||||
@ -317,20 +314,21 @@ exports.getAuthAccount = function(uuid){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the access token of an authenticated account.
|
* Update the access token of an authenticated mojang account.
|
||||||
*
|
*
|
||||||
* @param {string} uuid The uuid of the authenticated account.
|
* @param {string} uuid The uuid of the authenticated account.
|
||||||
* @param {string} accessToken The new Access Token.
|
* @param {string} accessToken The new Access Token.
|
||||||
*
|
*
|
||||||
* @returns {Object} The authenticated account object created by this action.
|
* @returns {Object} The authenticated account object created by this action.
|
||||||
*/
|
*/
|
||||||
exports.updateAuthAccount = function(uuid, accessToken){
|
exports.updateMojangAuthAccount = function(uuid, accessToken){
|
||||||
config.authenticationDatabase[uuid].accessToken = accessToken
|
config.authenticationDatabase[uuid].accessToken = accessToken
|
||||||
|
config.authenticationDatabase[uuid].type = 'mojang' // For gradual conversion.
|
||||||
return config.authenticationDatabase[uuid]
|
return config.authenticationDatabase[uuid]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an authenticated account to the database to be stored.
|
* Adds an authenticated mojang account to the database to be stored.
|
||||||
*
|
*
|
||||||
* @param {string} uuid The uuid of the authenticated account.
|
* @param {string} uuid The uuid of the authenticated account.
|
||||||
* @param {string} accessToken The accessToken of the authenticated account.
|
* @param {string} accessToken The accessToken of the authenticated account.
|
||||||
@ -339,9 +337,10 @@ exports.updateAuthAccount = function(uuid, accessToken){
|
|||||||
*
|
*
|
||||||
* @returns {Object} The authenticated account object created by this action.
|
* @returns {Object} The authenticated account object created by this action.
|
||||||
*/
|
*/
|
||||||
exports.addAuthAccount = function(uuid, accessToken, username, displayName){
|
exports.addMojangAuthAccount = function(uuid, accessToken, username, displayName){
|
||||||
config.selectedAccount = uuid
|
config.selectedAccount = uuid
|
||||||
config.authenticationDatabase[uuid] = {
|
config.authenticationDatabase[uuid] = {
|
||||||
|
type: 'mojang',
|
||||||
accessToken,
|
accessToken,
|
||||||
username: username.trim(),
|
username: username.trim(),
|
||||||
uuid: uuid.trim(),
|
uuid: uuid.trim(),
|
||||||
@ -350,6 +349,58 @@ exports.addAuthAccount = function(uuid, accessToken, username, displayName){
|
|||||||
return config.authenticationDatabase[uuid]
|
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
|
* Remove an authenticated account from the database. If the account
|
||||||
* was also the selected account, a new one will be selected. If there
|
* was also the selected account, a new one will be selected. If there
|
||||||
@ -456,16 +507,66 @@ exports.setModConfiguration = function(serverid, configuration){
|
|||||||
|
|
||||||
// Java 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
|
* Retrieve the minimum amount of memory for JVM initialization. This value
|
||||||
* contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
* contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
||||||
* 1024 MegaBytes, etc.
|
* 1024 MegaBytes, etc.
|
||||||
*
|
*
|
||||||
* @param {boolean} def Optional. If true, the default value will be returned.
|
* @param {string} serverid The server id.
|
||||||
* @returns {string} The minimum amount of memory for JVM initialization.
|
* @returns {string} The minimum amount of memory for JVM initialization.
|
||||||
*/
|
*/
|
||||||
exports.getMinRAM = function(def = false){
|
exports.getMinRAM = function(serverid){
|
||||||
return !def ? config.settings.java.minRAM : DEFAULT_CONFIG.settings.java.minRAM
|
return config.javaConfig[serverid].minRAM
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -473,10 +574,11 @@ exports.getMinRAM = function(def = false){
|
|||||||
* contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
* contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
||||||
* 1024 MegaBytes, etc.
|
* 1024 MegaBytes, etc.
|
||||||
*
|
*
|
||||||
|
* @param {string} serverid The server id.
|
||||||
* @param {string} minRAM The new minimum amount of memory for JVM initialization.
|
* @param {string} minRAM The new minimum amount of memory for JVM initialization.
|
||||||
*/
|
*/
|
||||||
exports.setMinRAM = function(minRAM){
|
exports.setMinRAM = function(serverid, minRAM){
|
||||||
config.settings.java.minRAM = minRAM
|
config.javaConfig[serverid].minRAM = minRAM
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -484,11 +586,11 @@ exports.setMinRAM = function(minRAM){
|
|||||||
* contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
* contains the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
||||||
* 1024 MegaBytes, etc.
|
* 1024 MegaBytes, etc.
|
||||||
*
|
*
|
||||||
* @param {boolean} def Optional. If true, the default value will be returned.
|
* @param {string} serverid The server id.
|
||||||
* @returns {string} The maximum amount of memory for JVM initialization.
|
* @returns {string} The maximum amount of memory for JVM initialization.
|
||||||
*/
|
*/
|
||||||
exports.getMaxRAM = function(def = false){
|
exports.getMaxRAM = function(serverid){
|
||||||
return !def ? config.settings.java.maxRAM : resolveMaxRAM()
|
return config.javaConfig[serverid].maxRAM
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -496,10 +598,11 @@ exports.getMaxRAM = function(def = false){
|
|||||||
* contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
* contain the units of memory. For example, '5G' = 5 GigaBytes, '1024M' =
|
||||||
* 1024 MegaBytes, etc.
|
* 1024 MegaBytes, etc.
|
||||||
*
|
*
|
||||||
|
* @param {string} serverid The server id.
|
||||||
* @param {string} maxRAM The new maximum amount of memory for JVM initialization.
|
* @param {string} maxRAM The new maximum amount of memory for JVM initialization.
|
||||||
*/
|
*/
|
||||||
exports.setMaxRAM = function(maxRAM){
|
exports.setMaxRAM = function(serverid, maxRAM){
|
||||||
config.settings.java.maxRAM = maxRAM
|
config.javaConfig[serverid].maxRAM = maxRAM
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -507,19 +610,21 @@ exports.setMaxRAM = function(maxRAM){
|
|||||||
*
|
*
|
||||||
* This is a resolved configuration value and defaults to null until externally assigned.
|
* 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.
|
* @returns {string} The path of the Java Executable.
|
||||||
*/
|
*/
|
||||||
exports.getJavaExecutable = function(){
|
exports.getJavaExecutable = function(serverid){
|
||||||
return config.settings.java.executable
|
return config.javaConfig[serverid].executable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the path of the Java Executable.
|
* Set the path of the Java Executable.
|
||||||
*
|
*
|
||||||
|
* @param {string} serverid The server id.
|
||||||
* @param {string} executable The new path of the Java Executable.
|
* @param {string} executable The new path of the Java Executable.
|
||||||
*/
|
*/
|
||||||
exports.setJavaExecutable = function(executable){
|
exports.setJavaExecutable = function(serverid, executable){
|
||||||
config.settings.java.executable = executable
|
config.javaConfig[serverid].executable = executable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -527,11 +632,11 @@ exports.setJavaExecutable = function(executable){
|
|||||||
* such as memory allocation, will be dynamically resolved and will not be included
|
* such as memory allocation, will be dynamically resolved and will not be included
|
||||||
* in this value.
|
* in this value.
|
||||||
*
|
*
|
||||||
* @param {boolean} def Optional. If true, the default value will be returned.
|
* @param {string} serverid The server id.
|
||||||
* @returns {Array.<string>} An array of the additional arguments for JVM initialization.
|
* @returns {Array.<string>} An array of the additional arguments for JVM initialization.
|
||||||
*/
|
*/
|
||||||
exports.getJVMOptions = function(def = false){
|
exports.getJVMOptions = function(serverid){
|
||||||
return !def ? config.settings.java.jvmOptions : DEFAULT_CONFIG.settings.java.jvmOptions
|
return config.javaConfig[serverid].jvmOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -539,11 +644,12 @@ exports.getJVMOptions = function(def = false){
|
|||||||
* such as memory allocation, will be dynamically resolved and should not be
|
* such as memory allocation, will be dynamically resolved and should not be
|
||||||
* included in this value.
|
* included in this value.
|
||||||
*
|
*
|
||||||
|
* @param {string} serverid The server id.
|
||||||
* @param {Array.<string>} jvmOptions An array of the new additional arguments for JVM
|
* @param {Array.<string>} jvmOptions An array of the new additional arguments for JVM
|
||||||
* initialization.
|
* initialization.
|
||||||
*/
|
*/
|
||||||
exports.setJVMOptions = function(jvmOptions){
|
exports.setJVMOptions = function(serverid, jvmOptions){
|
||||||
config.settings.java.jvmOptions = jvmOptions
|
config.javaConfig[serverid].jvmOptions = jvmOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// Game Settings
|
// Game Settings
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
// Work in progress
|
// Work in progress
|
||||||
const logger = require('./loggerutil')('%c[DiscordWrapper]', 'color: #7289da; font-weight: bold')
|
const { LoggerUtil } = require('helios-core')
|
||||||
|
|
||||||
const {Client} = require('discord-rpc')
|
const logger = LoggerUtil.getLogger('DiscordWrapper')
|
||||||
|
|
||||||
|
const { Client } = require('discord-rpc-patch')
|
||||||
|
|
||||||
|
const Lang = require('./langloader')
|
||||||
|
|
||||||
let client
|
let client
|
||||||
let activity
|
let activity
|
||||||
|
|
||||||
exports.initRPC = function(genSettings, servSettings, initialDetails = 'Waiting for Client..'){
|
exports.initRPC = function(genSettings, servSettings, initialDetails = Lang.queryJS('discord.waiting')){
|
||||||
client = new Client({ transport: 'ipc' })
|
client = new Client({ transport: 'ipc' })
|
||||||
|
|
||||||
activity = {
|
activity = {
|
||||||
details: initialDetails,
|
details: initialDetails,
|
||||||
state: 'Server: ' + servSettings.shortId,
|
state: Lang.queryJS('discord.state', {shortId: servSettings.shortId}),
|
||||||
largeImageKey: servSettings.largeImageKey,
|
largeImageKey: servSettings.largeImageKey,
|
||||||
largeImageText: servSettings.largeImageText,
|
largeImageText: servSettings.largeImageText,
|
||||||
smallImageKey: genSettings.smallImageKey,
|
smallImageKey: genSettings.smallImageKey,
|
||||||
@ -21,15 +25,15 @@ exports.initRPC = function(genSettings, servSettings, initialDetails = 'Waiting
|
|||||||
}
|
}
|
||||||
|
|
||||||
client.on('ready', () => {
|
client.on('ready', () => {
|
||||||
logger.log('Discord RPC Connected')
|
logger.info('Discord RPC Connected')
|
||||||
client.setActivity(activity)
|
client.setActivity(activity)
|
||||||
})
|
})
|
||||||
|
|
||||||
client.login({clientId: genSettings.clientId}).catch(error => {
|
client.login({clientId: genSettings.clientId}).catch(error => {
|
||||||
if(error.message.includes('ENOENT')) {
|
if(error.message.includes('ENOENT')) {
|
||||||
logger.log('Unable to initialize Discord Rich Presence, no client detected.')
|
logger.info('Unable to initialize Discord Rich Presence, no client detected.')
|
||||||
} else {
|
} else {
|
||||||
logger.log('Unable to initialize Discord Rich Presence: ' + error.message, error)
|
logger.info('Unable to initialize Discord Rich Presence: ' + error.message, error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,593 +1,17 @@
|
|||||||
const fs = require('fs')
|
const { DistributionAPI } = require('helios-core/common')
|
||||||
const path = require('path')
|
|
||||||
const request = require('request')
|
|
||||||
|
|
||||||
const ConfigManager = require('./configmanager')
|
const ConfigManager = require('./configmanager')
|
||||||
const logger = require('./loggerutil')('%c[DistroManager]', 'color: #a02d2a; font-weight: bold')
|
|
||||||
|
|
||||||
/**
|
// Old WesterosCraft url.
|
||||||
* Represents the download information
|
// exports.REMOTE_DISTRO_URL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json'
|
||||||
* for a specific module.
|
exports.REMOTE_DISTRO_URL = 'https://helios-files.geekcorner.eu.org/distribution.json'
|
||||||
*/
|
|
||||||
class Artifact {
|
|
||||||
|
|
||||||
/**
|
const api = new DistributionAPI(
|
||||||
* Parse a JSON object into an Artifact.
|
ConfigManager.getLauncherDirectory(),
|
||||||
*
|
null, // Injected forcefully by the preloader.
|
||||||
* @param {Object} json A JSON object representing an Artifact.
|
null, // Injected forcefully by the preloader.
|
||||||
*
|
exports.REMOTE_DISTRO_URL,
|
||||||
* @returns {Artifact} The parsed Artifact.
|
false
|
||||||
*/
|
)
|
||||||
static fromJSON(json){
|
|
||||||
return Object.assign(new Artifact(), json)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
exports.DistroAPI = api
|
||||||
* Get the MD5 hash of the artifact. This value may
|
|
||||||
* be undefined for artifacts which are not to be
|
|
||||||
* validated and updated.
|
|
||||||
*
|
|
||||||
* @returns {string} The MD5 hash of the Artifact or undefined.
|
|
||||||
*/
|
|
||||||
getHash(){
|
|
||||||
return this.MD5
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {number} The download size of the artifact.
|
|
||||||
*/
|
|
||||||
getSize(){
|
|
||||||
return this.size
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The download url of the artifact.
|
|
||||||
*/
|
|
||||||
getURL(){
|
|
||||||
return this.url
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The artifact's destination path.
|
|
||||||
*/
|
|
||||||
getPath(){
|
|
||||||
return this.path
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
exports.Artifact
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a the requirement status
|
|
||||||
* of a module.
|
|
||||||
*/
|
|
||||||
class Required {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a JSON object into a Required object.
|
|
||||||
*
|
|
||||||
* @param {Object} json A JSON object representing a Required object.
|
|
||||||
*
|
|
||||||
* @returns {Required} The parsed Required object.
|
|
||||||
*/
|
|
||||||
static fromJSON(json){
|
|
||||||
if(json == null){
|
|
||||||
return new Required(true, true)
|
|
||||||
} else {
|
|
||||||
return new Required(json.value == null ? true : json.value, json.def == null ? true : json.def)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(value, def){
|
|
||||||
this.value = value
|
|
||||||
this.default = def
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the default value for a required object. If a module
|
|
||||||
* is not required, this value determines whether or not
|
|
||||||
* it is enabled by default.
|
|
||||||
*
|
|
||||||
* @returns {boolean} The default enabled value.
|
|
||||||
*/
|
|
||||||
isDefault(){
|
|
||||||
return this.default
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {boolean} Whether or not the module is required.
|
|
||||||
*/
|
|
||||||
isRequired(){
|
|
||||||
return this.value
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
exports.Required
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a module.
|
|
||||||
*/
|
|
||||||
class Module {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a JSON object into a Module.
|
|
||||||
*
|
|
||||||
* @param {Object} json A JSON object representing a Module.
|
|
||||||
* @param {string} serverid The ID of the server to which this module belongs.
|
|
||||||
*
|
|
||||||
* @returns {Module} The parsed Module.
|
|
||||||
*/
|
|
||||||
static fromJSON(json, serverid){
|
|
||||||
return new Module(json.id, json.name, json.type, json.required, json.artifact, json.subModules, serverid)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the default extension for a specific module type.
|
|
||||||
*
|
|
||||||
* @param {string} type The type of the module.
|
|
||||||
*
|
|
||||||
* @return {string} The default extension for the given type.
|
|
||||||
*/
|
|
||||||
static _resolveDefaultExtension(type){
|
|
||||||
switch (type) {
|
|
||||||
case exports.Types.Library:
|
|
||||||
case exports.Types.ForgeHosted:
|
|
||||||
case exports.Types.LiteLoader:
|
|
||||||
case exports.Types.ForgeMod:
|
|
||||||
return 'jar'
|
|
||||||
case exports.Types.LiteMod:
|
|
||||||
return 'litemod'
|
|
||||||
case exports.Types.File:
|
|
||||||
default:
|
|
||||||
return 'jar' // There is no default extension really.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(id, name, type, required, artifact, subModules, serverid) {
|
|
||||||
this.identifier = id
|
|
||||||
this.type = type
|
|
||||||
this._resolveMetaData()
|
|
||||||
this.name = name
|
|
||||||
this.required = Required.fromJSON(required)
|
|
||||||
this.artifact = Artifact.fromJSON(artifact)
|
|
||||||
this._resolveArtifactPath(artifact.path, serverid)
|
|
||||||
this._resolveSubModules(subModules, serverid)
|
|
||||||
}
|
|
||||||
|
|
||||||
_resolveMetaData(){
|
|
||||||
try {
|
|
||||||
|
|
||||||
const m0 = this.identifier.split('@')
|
|
||||||
|
|
||||||
this.artifactExt = m0[1] || Module._resolveDefaultExtension(this.type)
|
|
||||||
|
|
||||||
const m1 = m0[0].split(':')
|
|
||||||
|
|
||||||
this.artifactVersion = m1[2] || '???'
|
|
||||||
this.artifactID = m1[1] || '???'
|
|
||||||
this.artifactGroup = m1[0] || '???'
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
// Improper identifier
|
|
||||||
logger.error('Improper ID for module', this.identifier, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_resolveArtifactPath(artifactPath, serverid){
|
|
||||||
const pth = artifactPath == null ? path.join(...this.getGroup().split('.'), this.getID(), this.getVersion(), `${this.getID()}-${this.getVersion()}.${this.getExtension()}`) : artifactPath
|
|
||||||
|
|
||||||
switch (this.type){
|
|
||||||
case exports.Types.Library:
|
|
||||||
case exports.Types.ForgeHosted:
|
|
||||||
case exports.Types.LiteLoader:
|
|
||||||
this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'libraries', pth)
|
|
||||||
break
|
|
||||||
case exports.Types.ForgeMod:
|
|
||||||
case exports.Types.LiteMod:
|
|
||||||
this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'modstore', pth)
|
|
||||||
break
|
|
||||||
case exports.Types.File:
|
|
||||||
default:
|
|
||||||
this.artifact.path = path.join(ConfigManager.getInstanceDirectory(), serverid, pth)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_resolveSubModules(json, serverid){
|
|
||||||
const arr = []
|
|
||||||
if(json != null){
|
|
||||||
for(let sm of json){
|
|
||||||
arr.push(Module.fromJSON(sm, serverid))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.subModules = arr.length > 0 ? arr : null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The full, unparsed module identifier.
|
|
||||||
*/
|
|
||||||
getIdentifier(){
|
|
||||||
return this.identifier
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The name of the module.
|
|
||||||
*/
|
|
||||||
getName(){
|
|
||||||
return this.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Required} The required object declared by this module.
|
|
||||||
*/
|
|
||||||
getRequired(){
|
|
||||||
return this.required
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Artifact} The artifact declared by this module.
|
|
||||||
*/
|
|
||||||
getArtifact(){
|
|
||||||
return this.artifact
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The maven identifier of this module's artifact.
|
|
||||||
*/
|
|
||||||
getID(){
|
|
||||||
return this.artifactID
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The maven group of this module's artifact.
|
|
||||||
*/
|
|
||||||
getGroup(){
|
|
||||||
return this.artifactGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The identifier without he version or extension.
|
|
||||||
*/
|
|
||||||
getVersionlessID(){
|
|
||||||
return this.getGroup() + ':' + this.getID()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The identifier without the extension.
|
|
||||||
*/
|
|
||||||
getExtensionlessID(){
|
|
||||||
return this.getIdentifier().split('@')[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The version of this module's artifact.
|
|
||||||
*/
|
|
||||||
getVersion(){
|
|
||||||
return this.artifactVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The extension of this module's artifact.
|
|
||||||
*/
|
|
||||||
getExtension(){
|
|
||||||
return this.artifactExt
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {boolean} Whether or not this module has sub modules.
|
|
||||||
*/
|
|
||||||
hasSubModules(){
|
|
||||||
return this.subModules != null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Array.<Module>} An array of sub modules.
|
|
||||||
*/
|
|
||||||
getSubModules(){
|
|
||||||
return this.subModules
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The type of the module.
|
|
||||||
*/
|
|
||||||
getType(){
|
|
||||||
return this.type
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
exports.Module
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a server configuration.
|
|
||||||
*/
|
|
||||||
class Server {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a JSON object into a Server.
|
|
||||||
*
|
|
||||||
* @param {Object} json A JSON object representing a Server.
|
|
||||||
*
|
|
||||||
* @returns {Server} The parsed Server object.
|
|
||||||
*/
|
|
||||||
static fromJSON(json){
|
|
||||||
|
|
||||||
const mdls = json.modules
|
|
||||||
json.modules = []
|
|
||||||
|
|
||||||
const serv = Object.assign(new Server(), json)
|
|
||||||
serv._resolveModules(mdls)
|
|
||||||
|
|
||||||
return serv
|
|
||||||
}
|
|
||||||
|
|
||||||
_resolveModules(json){
|
|
||||||
const arr = []
|
|
||||||
for(let m of json){
|
|
||||||
arr.push(Module.fromJSON(m, this.getID()))
|
|
||||||
}
|
|
||||||
this.modules = arr
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The ID of the server.
|
|
||||||
*/
|
|
||||||
getID(){
|
|
||||||
return this.id
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The name of the server.
|
|
||||||
*/
|
|
||||||
getName(){
|
|
||||||
return this.name
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The description of the server.
|
|
||||||
*/
|
|
||||||
getDescription(){
|
|
||||||
return this.description
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The URL of the server's icon.
|
|
||||||
*/
|
|
||||||
getIcon(){
|
|
||||||
return this.icon
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The version of the server configuration.
|
|
||||||
*/
|
|
||||||
getVersion(){
|
|
||||||
return this.version
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The IP address of the server.
|
|
||||||
*/
|
|
||||||
getAddress(){
|
|
||||||
return this.address
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The minecraft version of the server.
|
|
||||||
*/
|
|
||||||
getMinecraftVersion(){
|
|
||||||
return this.minecraftVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {boolean} Whether or not this server is the main
|
|
||||||
* server. The main server is selected by the launcher when
|
|
||||||
* no valid server is selected.
|
|
||||||
*/
|
|
||||||
isMainServer(){
|
|
||||||
return this.mainServer
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {boolean} Whether or not the server is autoconnect.
|
|
||||||
* by default.
|
|
||||||
*/
|
|
||||||
isAutoConnect(){
|
|
||||||
return this.autoconnect
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Array.<Module>} An array of modules for this server.
|
|
||||||
*/
|
|
||||||
getModules(){
|
|
||||||
return this.modules
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
exports.Server
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the Distribution Index.
|
|
||||||
*/
|
|
||||||
class DistroIndex {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a JSON object into a DistroIndex.
|
|
||||||
*
|
|
||||||
* @param {Object} json A JSON object representing a DistroIndex.
|
|
||||||
*
|
|
||||||
* @returns {DistroIndex} The parsed Server object.
|
|
||||||
*/
|
|
||||||
static fromJSON(json){
|
|
||||||
|
|
||||||
const servers = json.servers
|
|
||||||
json.servers = []
|
|
||||||
|
|
||||||
const distro = Object.assign(new DistroIndex(), json)
|
|
||||||
distro._resolveServers(servers)
|
|
||||||
distro._resolveMainServer()
|
|
||||||
|
|
||||||
return distro
|
|
||||||
}
|
|
||||||
|
|
||||||
_resolveServers(json){
|
|
||||||
const arr = []
|
|
||||||
for(let s of json){
|
|
||||||
arr.push(Server.fromJSON(s))
|
|
||||||
}
|
|
||||||
this.servers = arr
|
|
||||||
}
|
|
||||||
|
|
||||||
_resolveMainServer(){
|
|
||||||
|
|
||||||
for(let serv of this.servers){
|
|
||||||
if(serv.mainServer){
|
|
||||||
this.mainServer = serv.id
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no server declares default_selected, default to the first one declared.
|
|
||||||
this.mainServer = (this.servers.length > 0) ? this.servers[0].getID() : null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The version of the distribution index.
|
|
||||||
*/
|
|
||||||
getVersion(){
|
|
||||||
return this.version
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} The URL to the news RSS feed.
|
|
||||||
*/
|
|
||||||
getRSS(){
|
|
||||||
return this.rss
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Array.<Server>} An array of declared server configurations.
|
|
||||||
*/
|
|
||||||
getServers(){
|
|
||||||
return this.servers
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a server configuration by its ID. If it does not
|
|
||||||
* exist, null will be returned.
|
|
||||||
*
|
|
||||||
* @param {string} id The ID of the server.
|
|
||||||
*
|
|
||||||
* @returns {Server} The server configuration with the given ID or null.
|
|
||||||
*/
|
|
||||||
getServer(id){
|
|
||||||
for(let serv of this.servers){
|
|
||||||
if(serv.id === id){
|
|
||||||
return serv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the main server.
|
|
||||||
*
|
|
||||||
* @returns {Server} The main server.
|
|
||||||
*/
|
|
||||||
getMainServer(){
|
|
||||||
return this.mainServer != null ? this.getServer(this.mainServer) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
exports.DistroIndex
|
|
||||||
|
|
||||||
exports.Types = {
|
|
||||||
Library: 'Library',
|
|
||||||
ForgeHosted: 'ForgeHosted',
|
|
||||||
Forge: 'Forge', // Unimplemented
|
|
||||||
LiteLoader: 'LiteLoader',
|
|
||||||
ForgeMod: 'ForgeMod',
|
|
||||||
LiteMod: 'LiteMod',
|
|
||||||
File: 'File'
|
|
||||||
}
|
|
||||||
|
|
||||||
let DEV_MODE = false
|
|
||||||
|
|
||||||
const DISTRO_PATH = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json')
|
|
||||||
const DEV_PATH = path.join(ConfigManager.getLauncherDirectory(), 'dev_distribution.json')
|
|
||||||
|
|
||||||
let data = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Promise.<DistroIndex>}
|
|
||||||
*/
|
|
||||||
exports.pullRemote = function(){
|
|
||||||
if(DEV_MODE){
|
|
||||||
return exports.pullLocal()
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const distroURL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json'
|
|
||||||
//const distroURL = 'https://gist.githubusercontent.com/dscalzi/53b1ba7a11d26a5c353f9d5ae484b71b/raw/'
|
|
||||||
const opts = {
|
|
||||||
url: distroURL,
|
|
||||||
timeout: 2500
|
|
||||||
}
|
|
||||||
const distroDest = path.join(ConfigManager.getLauncherDirectory(), 'distribution.json')
|
|
||||||
request(opts, (error, resp, body) => {
|
|
||||||
if(!error){
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = DistroIndex.fromJSON(JSON.parse(body))
|
|
||||||
} catch (e) {
|
|
||||||
reject(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFile(distroDest, body, 'utf-8', (err) => {
|
|
||||||
if(!err){
|
|
||||||
resolve(data)
|
|
||||||
} else {
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Promise.<DistroIndex>}
|
|
||||||
*/
|
|
||||||
exports.pullLocal = function(){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fs.readFile(DEV_MODE ? DEV_PATH : DISTRO_PATH, 'utf-8', (err, d) => {
|
|
||||||
if(!err){
|
|
||||||
data = DistroIndex.fromJSON(JSON.parse(d))
|
|
||||||
resolve(data)
|
|
||||||
} else {
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.setDevMode = function(value){
|
|
||||||
if(value){
|
|
||||||
logger.log('Developer mode enabled.')
|
|
||||||
logger.log('If you don\'t know what that means, revert immediately.')
|
|
||||||
} else {
|
|
||||||
logger.log('Developer mode disabled.')
|
|
||||||
}
|
|
||||||
DEV_MODE = value
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.isDevMode = function(){
|
|
||||||
return DEV_MODE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {DistroIndex}
|
|
||||||
*/
|
|
||||||
exports.getDistribution = function(){
|
|
||||||
return data
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { shell } = require('electron')
|
const { ipcRenderer, shell } = require('electron')
|
||||||
|
const { SHELL_OPCODE } = require('./ipcconstants')
|
||||||
|
|
||||||
// Group #1: File Name (without .disabled, if any)
|
// Group #1: File Name (without .disabled, if any)
|
||||||
// Group #2: File Extension (jar, zip, or litemod)
|
// Group #2: File Extension (jar, zip, or litemod)
|
||||||
@ -92,14 +93,19 @@ exports.addDropinMods = function(files, modsdir) {
|
|||||||
* @param {string} modsDir The path to the mods directory.
|
* @param {string} modsDir The path to the mods directory.
|
||||||
* @param {string} fullName The fullName of the discovered mod to delete.
|
* @param {string} fullName The fullName of the discovered mod to delete.
|
||||||
*
|
*
|
||||||
* @returns {boolean} True if the mod was deleted, otherwise false.
|
* @returns {Promise.<boolean>} True if the mod was deleted, otherwise false.
|
||||||
*/
|
*/
|
||||||
exports.deleteDropinMod = function(modsDir, fullName){
|
exports.deleteDropinMod = async function(modsDir, fullName){
|
||||||
const res = shell.moveItemToTrash(path.join(modsDir, fullName))
|
|
||||||
if(!res){
|
const res = await ipcRenderer.invoke(SHELL_OPCODE.TRASH_ITEM, path.join(modsDir, fullName))
|
||||||
|
|
||||||
|
if(!res.result) {
|
||||||
shell.beep()
|
shell.beep()
|
||||||
|
console.error('Error deleting drop-in mod.', res.error)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return res
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
28
app/assets/js/ipcconstants.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// 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 = '1ce6e35a-126f-48fd-97fb-54d143ac6d45'
|
||||||
|
// 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'
|
||||||
|
}
|
43
app/assets/js/langloader.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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,32 +0,0 @@
|
|||||||
class LoggerUtil {
|
|
||||||
|
|
||||||
constructor(prefix, style){
|
|
||||||
this.prefix = prefix
|
|
||||||
this.style = style
|
|
||||||
}
|
|
||||||
|
|
||||||
log(){
|
|
||||||
console.log.apply(null, [this.prefix, this.style, ...arguments])
|
|
||||||
}
|
|
||||||
|
|
||||||
info(){
|
|
||||||
console.info.apply(null, [this.prefix, this.style, ...arguments])
|
|
||||||
}
|
|
||||||
|
|
||||||
warn(){
|
|
||||||
console.warn.apply(null, [this.prefix, this.style, ...arguments])
|
|
||||||
}
|
|
||||||
|
|
||||||
debug(){
|
|
||||||
console.debug.apply(null, [this.prefix, this.style, ...arguments])
|
|
||||||
}
|
|
||||||
|
|
||||||
error(){
|
|
||||||
console.error.apply(null, [this.prefix, this.style, ...arguments])
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function (prefix, style){
|
|
||||||
return new LoggerUtil(prefix, style)
|
|
||||||
}
|
|
@ -1,271 +0,0 @@
|
|||||||
/**
|
|
||||||
* Mojang
|
|
||||||
*
|
|
||||||
* This module serves as a minimal wrapper for Mojang's REST api.
|
|
||||||
*
|
|
||||||
* @module mojang
|
|
||||||
*/
|
|
||||||
// Requirements
|
|
||||||
const request = require('request')
|
|
||||||
const logger = require('./loggerutil')('%c[Mojang]', 'color: #a02d2a; font-weight: bold')
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
const minecraftAgent = {
|
|
||||||
name: 'Minecraft',
|
|
||||||
version: 1
|
|
||||||
}
|
|
||||||
const authpath = 'https://authserver.mojang.com'
|
|
||||||
const statuses = [
|
|
||||||
{
|
|
||||||
service: 'sessionserver.mojang.com',
|
|
||||||
status: 'grey',
|
|
||||||
name: 'Multiplayer Session Service',
|
|
||||||
essential: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: 'authserver.mojang.com',
|
|
||||||
status: 'grey',
|
|
||||||
name: 'Authentication Service',
|
|
||||||
essential: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: 'textures.minecraft.net',
|
|
||||||
status: 'grey',
|
|
||||||
name: 'Minecraft Skins',
|
|
||||||
essential: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: 'api.mojang.com',
|
|
||||||
status: 'grey',
|
|
||||||
name: 'Public API',
|
|
||||||
essential: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: 'minecraft.net',
|
|
||||||
status: 'grey',
|
|
||||||
name: 'Minecraft.net',
|
|
||||||
essential: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
service: 'account.mojang.com',
|
|
||||||
status: 'grey',
|
|
||||||
name: 'Mojang Accounts Website',
|
|
||||||
essential: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// Functions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a Mojang status color to a hex value. Valid statuses
|
|
||||||
* are 'green', 'yellow', 'red', and 'grey'. Grey is a custom status
|
|
||||||
* to our project which represents an unknown status.
|
|
||||||
*
|
|
||||||
* @param {string} status A valid status code.
|
|
||||||
* @returns {string} The hex color of the status code.
|
|
||||||
*/
|
|
||||||
exports.statusToHex = function(status){
|
|
||||||
switch(status.toLowerCase()){
|
|
||||||
case 'green':
|
|
||||||
return '#a5c325'
|
|
||||||
case 'yellow':
|
|
||||||
return '#eac918'
|
|
||||||
case 'red':
|
|
||||||
return '#c32625'
|
|
||||||
case 'grey':
|
|
||||||
default:
|
|
||||||
return '#848484'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the status of Mojang's services.
|
|
||||||
* The response is condensed into a single object. Each service is
|
|
||||||
* a key, where the value is an object containing a status and name
|
|
||||||
* property.
|
|
||||||
*
|
|
||||||
* @see http://wiki.vg/Mojang_API#API_Status
|
|
||||||
*/
|
|
||||||
exports.status = function(){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
request.get('https://status.mojang.com/check',
|
|
||||||
{
|
|
||||||
json: true,
|
|
||||||
timeout: 2500
|
|
||||||
},
|
|
||||||
function(error, response, body){
|
|
||||||
|
|
||||||
if(error || response.statusCode !== 200){
|
|
||||||
logger.warn('Unable to retrieve Mojang status.')
|
|
||||||
logger.debug('Error while retrieving Mojang statuses:', error)
|
|
||||||
//reject(error || response.statusCode)
|
|
||||||
for(let i=0; i<statuses.length; i++){
|
|
||||||
statuses[i].status = 'grey'
|
|
||||||
}
|
|
||||||
resolve(statuses)
|
|
||||||
} else {
|
|
||||||
for(let i=0; i<body.length; i++){
|
|
||||||
const key = Object.keys(body[i])[0]
|
|
||||||
inner:
|
|
||||||
for(let j=0; j<statuses.length; j++){
|
|
||||||
if(statuses[j].service === key) {
|
|
||||||
statuses[j].status = body[i][key]
|
|
||||||
break inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve(statuses)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authenticate a user with their Mojang credentials.
|
|
||||||
*
|
|
||||||
* @param {string} username The user's username, this is often an email.
|
|
||||||
* @param {string} password The user's password.
|
|
||||||
* @param {string} clientToken The launcher's Client Token.
|
|
||||||
* @param {boolean} requestUser Optional. Adds user object to the reponse.
|
|
||||||
* @param {Object} agent Optional. Provided by default. Adds user info to the response.
|
|
||||||
*
|
|
||||||
* @see http://wiki.vg/Authentication#Authenticate
|
|
||||||
*/
|
|
||||||
exports.authenticate = function(username, password, clientToken, requestUser = true, agent = minecraftAgent){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
agent,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
requestUser
|
|
||||||
}
|
|
||||||
if(clientToken != null){
|
|
||||||
body.clientToken = clientToken
|
|
||||||
}
|
|
||||||
|
|
||||||
request.post(authpath + '/authenticate',
|
|
||||||
{
|
|
||||||
json: true,
|
|
||||||
body
|
|
||||||
},
|
|
||||||
function(error, response, body){
|
|
||||||
if(error){
|
|
||||||
logger.error('Error during authentication.', error)
|
|
||||||
reject(error)
|
|
||||||
} else {
|
|
||||||
if(response.statusCode === 200){
|
|
||||||
resolve(body)
|
|
||||||
} else {
|
|
||||||
reject(body || {code: 'ENOTFOUND'})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate an access token. This should always be done before launching.
|
|
||||||
* The client token should match the one used to create the access token.
|
|
||||||
*
|
|
||||||
* @param {string} accessToken The access token to validate.
|
|
||||||
* @param {string} clientToken The launcher's client token.
|
|
||||||
*
|
|
||||||
* @see http://wiki.vg/Authentication#Validate
|
|
||||||
*/
|
|
||||||
exports.validate = function(accessToken, clientToken){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
request.post(authpath + '/validate',
|
|
||||||
{
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
accessToken,
|
|
||||||
clientToken
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function(error, response, body){
|
|
||||||
if(error){
|
|
||||||
logger.error('Error during validation.', error)
|
|
||||||
reject(error)
|
|
||||||
} else {
|
|
||||||
if(response.statusCode === 403){
|
|
||||||
resolve(false)
|
|
||||||
} else {
|
|
||||||
// 204 if valid
|
|
||||||
resolve(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalidates an access token. The clientToken must match the
|
|
||||||
* token used to create the provided accessToken.
|
|
||||||
*
|
|
||||||
* @param {string} accessToken The access token to invalidate.
|
|
||||||
* @param {string} clientToken The launcher's client token.
|
|
||||||
*
|
|
||||||
* @see http://wiki.vg/Authentication#Invalidate
|
|
||||||
*/
|
|
||||||
exports.invalidate = function(accessToken, clientToken){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
request.post(authpath + '/invalidate',
|
|
||||||
{
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
accessToken,
|
|
||||||
clientToken
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function(error, response, body){
|
|
||||||
if(error){
|
|
||||||
logger.error('Error during invalidation.', error)
|
|
||||||
reject(error)
|
|
||||||
} else {
|
|
||||||
if(response.statusCode === 204){
|
|
||||||
resolve()
|
|
||||||
} else {
|
|
||||||
reject(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh a user's authentication. This should be used to keep a user logged
|
|
||||||
* in without asking them for their credentials again. A new access token will
|
|
||||||
* be generated using a recent invalid access token. See Wiki for more info.
|
|
||||||
*
|
|
||||||
* @param {string} accessToken The old access token.
|
|
||||||
* @param {string} clientToken The launcher's client token.
|
|
||||||
* @param {boolean} requestUser Optional. Adds user object to the reponse.
|
|
||||||
*
|
|
||||||
* @see http://wiki.vg/Authentication#Refresh
|
|
||||||
*/
|
|
||||||
exports.refresh = function(accessToken, clientToken, requestUser = true){
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
request.post(authpath + '/refresh',
|
|
||||||
{
|
|
||||||
json: true,
|
|
||||||
body: {
|
|
||||||
accessToken,
|
|
||||||
clientToken,
|
|
||||||
requestUser
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function(error, response, body){
|
|
||||||
if(error){
|
|
||||||
logger.error('Error during refresh.', error)
|
|
||||||
reject(error)
|
|
||||||
} else {
|
|
||||||
if(response.statusCode === 200){
|
|
||||||
resolve(body)
|
|
||||||
} else {
|
|
||||||
reject(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,24 +1,41 @@
|
|||||||
const {ipcRenderer} = require('electron')
|
const {ipcRenderer} = require('electron')
|
||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
const ConfigManager = require('./configmanager')
|
const ConfigManager = require('./configmanager')
|
||||||
const DistroManager = require('./distromanager')
|
const { DistroAPI } = require('./distromanager')
|
||||||
const logger = require('./loggerutil')('%c[Preloader]', 'color: #a02d2a; font-weight: bold')
|
const LangLoader = require('./langloader')
|
||||||
|
const { LoggerUtil } = require('helios-core')
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const { HeliosDistribution } = require('helios-core/common')
|
||||||
|
|
||||||
logger.log('Loading..')
|
const logger = LoggerUtil.getLogger('Preloader')
|
||||||
|
|
||||||
|
logger.info('Loading..')
|
||||||
|
|
||||||
// Load ConfigManager
|
// Load ConfigManager
|
||||||
ConfigManager.load()
|
ConfigManager.load()
|
||||||
|
|
||||||
|
// Yuck!
|
||||||
|
// TODO Fix this
|
||||||
|
DistroAPI['commonDir'] = ConfigManager.getCommonDirectory()
|
||||||
|
DistroAPI['instanceDir'] = ConfigManager.getInstanceDirectory()
|
||||||
|
|
||||||
|
// Load Strings
|
||||||
|
LangLoader.setupLanguage()
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {HeliosDistribution} data
|
||||||
|
*/
|
||||||
function onDistroLoad(data){
|
function onDistroLoad(data){
|
||||||
if(data != null){
|
if(data != null){
|
||||||
|
|
||||||
// Resolve the selected server if its value has yet to be set.
|
// Resolve the selected server if its value has yet to be set.
|
||||||
if(ConfigManager.getSelectedServer() == null || data.getServer(ConfigManager.getSelectedServer()) == null){
|
if(ConfigManager.getSelectedServer() == null || data.getServerById(ConfigManager.getSelectedServer()) == null){
|
||||||
logger.log('Determining default selected server..')
|
logger.info('Determining default selected server..')
|
||||||
ConfigManager.setSelectedServer(data.getMainServer().getID())
|
ConfigManager.setSelectedServer(data.getMainServer().rawServer.id)
|
||||||
ConfigManager.save()
|
ConfigManager.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -26,40 +43,25 @@ function onDistroLoad(data){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure Distribution is downloaded and cached.
|
// Ensure Distribution is downloaded and cached.
|
||||||
DistroManager.pullRemote().then((data) => {
|
DistroAPI.getDistribution()
|
||||||
logger.log('Loaded distribution index.')
|
.then(heliosDistro => {
|
||||||
|
logger.info('Loaded distribution index.')
|
||||||
|
|
||||||
onDistroLoad(data)
|
onDistroLoad(heliosDistro)
|
||||||
|
})
|
||||||
}).catch((err) => {
|
.catch(err => {
|
||||||
logger.log('Failed to load distribution index.')
|
logger.info('Failed to load an older version of the distribution index.')
|
||||||
logger.error(err)
|
logger.info('Application cannot run.')
|
||||||
|
|
||||||
logger.log('Attempting to load an older version of the distribution index.')
|
|
||||||
// Try getting a local copy, better than nothing.
|
|
||||||
DistroManager.pullLocal().then((data) => {
|
|
||||||
logger.log('Successfully loaded an older version of the distribution index.')
|
|
||||||
|
|
||||||
onDistroLoad(data)
|
|
||||||
|
|
||||||
|
|
||||||
}).catch((err) => {
|
|
||||||
|
|
||||||
logger.log('Failed to load an older version of the distribution index.')
|
|
||||||
logger.log('Application cannot run.')
|
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
|
|
||||||
onDistroLoad(null)
|
onDistroLoad(null)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// Clean up temp dir incase previous launches ended unexpectedly.
|
// Clean up temp dir incase previous launches ended unexpectedly.
|
||||||
fs.remove(path.join(os.tmpdir(), ConfigManager.getTempNativeFolder()), (err) => {
|
fs.remove(path.join(os.tmpdir(), ConfigManager.getTempNativeFolder()), (err) => {
|
||||||
if(err){
|
if(err){
|
||||||
logger.warn('Error while cleaning natives directory', err)
|
logger.warn('Error while cleaning natives directory', err)
|
||||||
} else {
|
} else {
|
||||||
logger.log('Cleaned natives directory.')
|
logger.info('Cleaned natives directory.')
|
||||||
}
|
}
|
||||||
})
|
})
|
@ -2,31 +2,42 @@ const AdmZip = require('adm-zip')
|
|||||||
const child_process = require('child_process')
|
const child_process = require('child_process')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
|
const { LoggerUtil } = require('helios-core')
|
||||||
|
const { getMojangOS, isLibraryCompatible, mcVersionAtLeast } = require('helios-core/common')
|
||||||
|
const { Type } = require('helios-distribution-types')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const {URL} = require('url')
|
|
||||||
|
|
||||||
const { Library } = require('./assetguard')
|
const ConfigManager = require('./configmanager')
|
||||||
const ConfigManager = require('./configmanager')
|
|
||||||
const DistroManager = require('./distromanager')
|
|
||||||
const LoggerUtil = require('./loggerutil')
|
|
||||||
|
|
||||||
const logger = LoggerUtil('%c[ProcessBuilder]', 'color: #003996; font-weight: bold')
|
const logger = LoggerUtil.getLogger('ProcessBuilder')
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only forge and fabric are top level mod loaders.
|
||||||
|
*
|
||||||
|
* Forge 1.13+ launch logic is similar to fabrics, for now using usingFabricLoader flag to
|
||||||
|
* change minor details when needed.
|
||||||
|
*
|
||||||
|
* Rewrite of this module may be needed in the future.
|
||||||
|
*/
|
||||||
class ProcessBuilder {
|
class ProcessBuilder {
|
||||||
|
|
||||||
constructor(distroServer, versionData, forgeData, authUser){
|
constructor(distroServer, vanillaManifest, modManifest, authUser, launcherVersion){
|
||||||
this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.getID())
|
this.gameDir = path.join(ConfigManager.getInstanceDirectory(), distroServer.rawServer.id)
|
||||||
this.commonDir = ConfigManager.getCommonDirectory()
|
this.commonDir = ConfigManager.getCommonDirectory()
|
||||||
this.server = distroServer
|
this.server = distroServer
|
||||||
this.versionData = versionData
|
this.vanillaManifest = vanillaManifest
|
||||||
this.forgeData = forgeData
|
this.modManifest = modManifest
|
||||||
this.authUser = authUser
|
this.authUser = authUser
|
||||||
|
this.launcherVersion = launcherVersion
|
||||||
|
this.forgeModListFile = path.join(this.gameDir, 'forgeMods.list') // 1.13+
|
||||||
this.fmlDir = path.join(this.gameDir, 'forgeModList.json')
|
this.fmlDir = path.join(this.gameDir, 'forgeModList.json')
|
||||||
this.llDir = path.join(this.gameDir, 'liteloaderModList.json')
|
this.llDir = path.join(this.gameDir, 'liteloaderModList.json')
|
||||||
this.libPath = path.join(this.commonDir, 'libraries')
|
this.libPath = path.join(this.commonDir, 'libraries')
|
||||||
|
|
||||||
this.usingLiteLoader = false
|
this.usingLiteLoader = false
|
||||||
|
this.usingFabricLoader = false
|
||||||
this.llPath = null
|
this.llPath = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,18 +49,31 @@ class ProcessBuilder {
|
|||||||
const tempNativePath = path.join(os.tmpdir(), ConfigManager.getTempNativeFolder(), crypto.pseudoRandomBytes(16).toString('hex'))
|
const tempNativePath = path.join(os.tmpdir(), ConfigManager.getTempNativeFolder(), crypto.pseudoRandomBytes(16).toString('hex'))
|
||||||
process.throwDeprecation = true
|
process.throwDeprecation = true
|
||||||
this.setupLiteLoader()
|
this.setupLiteLoader()
|
||||||
logger.log('Using liteloader:', this.usingLiteLoader)
|
logger.info('Using liteloader:', this.usingLiteLoader)
|
||||||
const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.server.getID()).mods, this.server.getModules())
|
this.usingFabricLoader = this.server.modules.some(mdl => mdl.rawModule.type === Type.Fabric)
|
||||||
this.constructModList('forge', modObj.fMods, true)
|
logger.info('Using fabric loader:', this.usingFabricLoader)
|
||||||
if(this.usingLiteLoader){
|
const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.server.rawServer.id).mods, this.server.modules)
|
||||||
this.constructModList('liteloader', modObj.lMods, true)
|
|
||||||
|
// Mod list below 1.13
|
||||||
|
// Fabric only supports 1.14+
|
||||||
|
if(!mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){
|
||||||
|
this.constructJSONModList('forge', modObj.fMods, true)
|
||||||
|
if(this.usingLiteLoader){
|
||||||
|
this.constructJSONModList('liteloader', modObj.lMods, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const uberModArr = modObj.fMods.concat(modObj.lMods)
|
const uberModArr = modObj.fMods.concat(modObj.lMods)
|
||||||
const args = this.constructJVMArguments(uberModArr, tempNativePath)
|
let args = this.constructJVMArguments(uberModArr, tempNativePath)
|
||||||
|
|
||||||
logger.log('Launch Arguments:', args)
|
if(mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){
|
||||||
|
//args = args.concat(this.constructModArguments(modObj.fMods))
|
||||||
|
args = args.concat(this.constructModList(modObj.fMods))
|
||||||
|
}
|
||||||
|
|
||||||
const child = child_process.spawn(ConfigManager.getJavaExecutable(), args, {
|
logger.info('Launch Arguments:', args)
|
||||||
|
|
||||||
|
const child = child_process.spawn(ConfigManager.getJavaExecutable(this.server.rawServer.id), args, {
|
||||||
cwd: this.gameDir,
|
cwd: this.gameDir,
|
||||||
detached: ConfigManager.getLaunchDetached()
|
detached: ConfigManager.getLaunchDetached()
|
||||||
})
|
})
|
||||||
@ -61,22 +85,20 @@ class ProcessBuilder {
|
|||||||
child.stdout.setEncoding('utf8')
|
child.stdout.setEncoding('utf8')
|
||||||
child.stderr.setEncoding('utf8')
|
child.stderr.setEncoding('utf8')
|
||||||
|
|
||||||
const loggerMCstdout = LoggerUtil('%c[Minecraft]', 'color: #36b030; font-weight: bold')
|
|
||||||
const loggerMCstderr = LoggerUtil('%c[Minecraft]', 'color: #b03030; font-weight: bold')
|
|
||||||
|
|
||||||
child.stdout.on('data', (data) => {
|
child.stdout.on('data', (data) => {
|
||||||
loggerMCstdout.log(data)
|
data.trim().split('\n').forEach(x => console.log(`\x1b[32m[Minecraft]\x1b[0m ${x}`))
|
||||||
|
|
||||||
})
|
})
|
||||||
child.stderr.on('data', (data) => {
|
child.stderr.on('data', (data) => {
|
||||||
loggerMCstderr.log(data)
|
data.trim().split('\n').forEach(x => console.log(`\x1b[31m[Minecraft]\x1b[0m ${x}`))
|
||||||
})
|
})
|
||||||
child.on('close', (code, signal) => {
|
child.on('close', (code, signal) => {
|
||||||
logger.log('Exited with code', code)
|
logger.info('Exited with code', code)
|
||||||
fs.remove(tempNativePath, (err) => {
|
fs.remove(tempNativePath, (err) => {
|
||||||
if(err){
|
if(err){
|
||||||
logger.warn('Error while deleting temp dir', err)
|
logger.warn('Error while deleting temp dir', err)
|
||||||
} else {
|
} else {
|
||||||
logger.log('Temp dir deleted successfully.')
|
logger.info('Temp dir deleted successfully.')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -84,6 +106,16 @@ class ProcessBuilder {
|
|||||||
return child
|
return child
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the platform specific classpath separator. On windows, this is a semicolon.
|
||||||
|
* On Unix, this is a colon.
|
||||||
|
*
|
||||||
|
* @returns {string} The classpath separator for the current operating system.
|
||||||
|
*/
|
||||||
|
static getClasspathSeparator() {
|
||||||
|
return process.platform === 'win32' ? ';' : ':'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if an optional mod is enabled from its configuration value. If the
|
* Determine if an optional mod is enabled from its configuration value. If the
|
||||||
* configuration value is null, the required object will be used to
|
* configuration value is null, the required object will be used to
|
||||||
@ -102,7 +134,7 @@ class ProcessBuilder {
|
|||||||
* @returns {boolean} True if the mod is enabled, false otherwise.
|
* @returns {boolean} True if the mod is enabled, false otherwise.
|
||||||
*/
|
*/
|
||||||
static isModEnabled(modCfg, required = null){
|
static isModEnabled(modCfg, required = null){
|
||||||
return modCfg != null ? ((typeof modCfg === 'boolean' && modCfg) || (typeof modCfg === 'object' && (typeof modCfg.value !== 'undefined' ? modCfg.value : true))) : required != null ? required.isDefault() : true
|
return modCfg != null ? ((typeof modCfg === 'boolean' && modCfg) || (typeof modCfg === 'object' && (typeof modCfg.value !== 'undefined' ? modCfg.value : true))) : required != null ? required.def : true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,20 +144,20 @@ class ProcessBuilder {
|
|||||||
* mod. It must not be declared as a submodule.
|
* mod. It must not be declared as a submodule.
|
||||||
*/
|
*/
|
||||||
setupLiteLoader(){
|
setupLiteLoader(){
|
||||||
for(let ll of this.server.getModules()){
|
for(let ll of this.server.modules){
|
||||||
if(ll.getType() === DistroManager.Types.LiteLoader){
|
if(ll.rawModule.type === Type.LiteLoader){
|
||||||
if(!ll.getRequired().isRequired()){
|
if(!ll.getRequired().value){
|
||||||
const modCfg = ConfigManager.getModConfiguration(this.server.getID()).mods
|
const modCfg = ConfigManager.getModConfiguration(this.server.rawServer.id).mods
|
||||||
if(ProcessBuilder.isModEnabled(modCfg[ll.getVersionlessID()], ll.getRequired())){
|
if(ProcessBuilder.isModEnabled(modCfg[ll.getVersionlessMavenIdentifier()], ll.getRequired())){
|
||||||
if(fs.existsSync(ll.getArtifact().getPath())){
|
if(fs.existsSync(ll.getPath())){
|
||||||
this.usingLiteLoader = true
|
this.usingLiteLoader = true
|
||||||
this.llPath = ll.getArtifact().getPath()
|
this.llPath = ll.getPath()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(fs.existsSync(ll.getArtifact().getPath())){
|
if(fs.existsSync(ll.getPath())){
|
||||||
this.usingLiteLoader = true
|
this.usingLiteLoader = true
|
||||||
this.llPath = ll.getArtifact().getPath()
|
this.llPath = ll.getPath()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,20 +178,20 @@ class ProcessBuilder {
|
|||||||
let lMods = []
|
let lMods = []
|
||||||
|
|
||||||
for(let mdl of mdls){
|
for(let mdl of mdls){
|
||||||
const type = mdl.getType()
|
const type = mdl.rawModule.type
|
||||||
if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){
|
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader || type === Type.FabricMod){
|
||||||
const o = !mdl.getRequired().isRequired()
|
const o = !mdl.getRequired().value
|
||||||
const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessID()], mdl.getRequired())
|
const e = ProcessBuilder.isModEnabled(modCfg[mdl.getVersionlessMavenIdentifier()], mdl.getRequired())
|
||||||
if(!o || (o && e)){
|
if(!o || (o && e)){
|
||||||
if(mdl.hasSubModules()){
|
if(mdl.subModules.length > 0){
|
||||||
const v = this.resolveModConfiguration(modCfg[mdl.getVersionlessID()].mods, mdl.getSubModules())
|
const v = this.resolveModConfiguration(modCfg[mdl.getVersionlessMavenIdentifier()].mods, mdl.subModules)
|
||||||
fMods = fMods.concat(v.fMods)
|
fMods = fMods.concat(v.fMods)
|
||||||
lMods = lMods.concat(v.lMods)
|
lMods = lMods.concat(v.lMods)
|
||||||
if(mdl.type === DistroManager.Types.LiteLoader){
|
if(type === Type.LiteLoader){
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(mdl.type === DistroManager.Types.ForgeMod){
|
if(type === Type.ForgeMod || type === Type.FabricMod){
|
||||||
fMods.push(mdl)
|
fMods.push(mdl)
|
||||||
} else {
|
} else {
|
||||||
lMods.push(mdl)
|
lMods.push(mdl)
|
||||||
@ -174,13 +206,20 @@ class ProcessBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_lteMinorVersion(version) {
|
||||||
|
return Number(this.modManifest.id.split('-')[0].split('.')[1]) <= Number(version)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test to see if this version of forge requires the absolute: prefix
|
* Test to see if this version of forge requires the absolute: prefix
|
||||||
* on the modListFile repository field.
|
* on the modListFile repository field.
|
||||||
*/
|
*/
|
||||||
_requiresAbsolute(){
|
_requiresAbsolute(){
|
||||||
try {
|
try {
|
||||||
const ver = this.forgeData.id.split('-')[2]
|
if(this._lteMinorVersion(9)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const ver = this.modManifest.id.split('-')[2]
|
||||||
const pts = ver.split('.')
|
const pts = ver.split('.')
|
||||||
const min = [14, 23, 3, 2655]
|
const min = [14, 23, 3, 2655]
|
||||||
for(let i=0; i<pts.length; i++){
|
for(let i=0; i<pts.length; i++){
|
||||||
@ -207,7 +246,7 @@ class ProcessBuilder {
|
|||||||
* @param {Array.<Object>} mods An array of mods to add to the mod list.
|
* @param {Array.<Object>} mods An array of mods to add to the mod list.
|
||||||
* @param {boolean} save Optional. Whether or not we should save the mod list file.
|
* @param {boolean} save Optional. Whether or not we should save the mod list file.
|
||||||
*/
|
*/
|
||||||
constructModList(type, mods, save = false){
|
constructJSONModList(type, mods, save = false){
|
||||||
const modList = {
|
const modList = {
|
||||||
repositoryRoot: ((type === 'forge' && this._requiresAbsolute()) ? 'absolute:' : '') + path.join(this.commonDir, 'modstore')
|
repositoryRoot: ((type === 'forge' && this._requiresAbsolute()) ? 'absolute:' : '') + path.join(this.commonDir, 'modstore')
|
||||||
}
|
}
|
||||||
@ -215,11 +254,11 @@ class ProcessBuilder {
|
|||||||
const ids = []
|
const ids = []
|
||||||
if(type === 'forge'){
|
if(type === 'forge'){
|
||||||
for(let mod of mods){
|
for(let mod of mods){
|
||||||
ids.push(mod.getExtensionlessID())
|
ids.push(mod.getExtensionlessMavenIdentifier())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for(let mod of mods){
|
for(let mod of mods){
|
||||||
ids.push(mod.getExtensionlessID() + '@' + mod.getExtension())
|
ids.push(mod.getMavenIdentifier())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
modList.modRef = ids
|
modList.modRef = ids
|
||||||
@ -232,6 +271,70 @@ class ProcessBuilder {
|
|||||||
return modList
|
return modList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Construct the mod argument list for forge 1.13
|
||||||
|
// *
|
||||||
|
// * @param {Array.<Object>} mods An array of mods to add to the mod list.
|
||||||
|
// */
|
||||||
|
// constructModArguments(mods){
|
||||||
|
// const argStr = mods.map(mod => {
|
||||||
|
// return mod.getExtensionlessMavenIdentifier()
|
||||||
|
// }).join(',')
|
||||||
|
|
||||||
|
// if(argStr){
|
||||||
|
// return [
|
||||||
|
// '--fml.mavenRoots',
|
||||||
|
// path.join('..', '..', 'common', 'modstore'),
|
||||||
|
// '--fml.mods',
|
||||||
|
// argStr
|
||||||
|
// ]
|
||||||
|
// } else {
|
||||||
|
// return []
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the mod argument list for forge 1.13 and Fabric
|
||||||
|
*
|
||||||
|
* @param {Array.<Object>} mods An array of mods to add to the mod list.
|
||||||
|
*/
|
||||||
|
constructModList(mods) {
|
||||||
|
const writeBuffer = mods.map(mod => {
|
||||||
|
return this.usingFabricLoader ? mod.getPath() : mod.getExtensionlessMavenIdentifier()
|
||||||
|
}).join('\n')
|
||||||
|
|
||||||
|
if(writeBuffer) {
|
||||||
|
fs.writeFileSync(this.forgeModListFile, writeBuffer, 'UTF-8')
|
||||||
|
return this.usingFabricLoader ? [
|
||||||
|
'--fabric.addMods',
|
||||||
|
`@${this.forgeModListFile}`
|
||||||
|
] : [
|
||||||
|
'--fml.mavenRoots',
|
||||||
|
path.join('..', '..', 'common', 'modstore'),
|
||||||
|
'--fml.modLists',
|
||||||
|
this.forgeModListFile
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_processAutoConnectArg(args){
|
||||||
|
if(ConfigManager.getAutoConnect() && this.server.rawServer.autoconnect){
|
||||||
|
if(mcVersionAtLeast('1.20', this.server.rawServer.minecraftVersion)){
|
||||||
|
args.push('--quickPlayMultiplayer')
|
||||||
|
args.push(`${this.server.hostname}:${this.server.port}`)
|
||||||
|
} else {
|
||||||
|
args.push('--server')
|
||||||
|
args.push(this.server.hostname)
|
||||||
|
args.push('--port')
|
||||||
|
args.push(this.server.port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct the argument array that will be passed to the JVM process.
|
* Construct the argument array that will be passed to the JVM process.
|
||||||
*
|
*
|
||||||
@ -240,22 +343,211 @@ class ProcessBuilder {
|
|||||||
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
||||||
*/
|
*/
|
||||||
constructJVMArguments(mods, tempNativePath){
|
constructJVMArguments(mods, tempNativePath){
|
||||||
|
if(mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){
|
||||||
|
return this._constructJVMArguments113(mods, tempNativePath)
|
||||||
|
} else {
|
||||||
|
return this._constructJVMArguments112(mods, tempNativePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let args = ['-Xmx' + ConfigManager.getMaxRAM(),
|
/**
|
||||||
'-Xms' + ConfigManager.getMinRAM(),
|
* Construct the argument array that will be passed to the JVM process.
|
||||||
'-Djava.library.path=' + tempNativePath,
|
* This function is for 1.12 and below.
|
||||||
'-cp',
|
*
|
||||||
this.classpathArg(mods, tempNativePath).join(process.platform === 'win32' ? ';' : ':'),
|
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
||||||
this.forgeData.mainClass]
|
* @param {string} tempNativePath The path to store the native libraries.
|
||||||
|
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
||||||
|
*/
|
||||||
|
_constructJVMArguments112(mods, tempNativePath){
|
||||||
|
|
||||||
|
let args = []
|
||||||
|
|
||||||
|
// Classpath Argument
|
||||||
|
args.push('-cp')
|
||||||
|
args.push(this.classpathArg(mods, tempNativePath).join(ProcessBuilder.getClasspathSeparator()))
|
||||||
|
|
||||||
|
// Java Arguments
|
||||||
if(process.platform === 'darwin'){
|
if(process.platform === 'darwin'){
|
||||||
args.unshift('-Xdock:name=WesterosCraft')
|
args.push('-Xdock:name=HeliosLauncher')
|
||||||
args.unshift('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
|
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
|
||||||
|
}
|
||||||
|
args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.rawServer.id))
|
||||||
|
args.push('-Xms' + ConfigManager.getMinRAM(this.server.rawServer.id))
|
||||||
|
args = args.concat(ConfigManager.getJVMOptions(this.server.rawServer.id))
|
||||||
|
args.push('-Djava.library.path=' + tempNativePath)
|
||||||
|
|
||||||
|
// Main Java Class
|
||||||
|
args.push(this.modManifest.mainClass)
|
||||||
|
|
||||||
|
// Forge Arguments
|
||||||
|
args = args.concat(this._resolveForgeArgs())
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the argument array that will be passed to the JVM process.
|
||||||
|
* This function is for 1.13+
|
||||||
|
*
|
||||||
|
* Note: Required Libs https://github.com/MinecraftForge/MinecraftForge/blob/af98088d04186452cb364280340124dfd4766a5c/src/fmllauncher/java/net/minecraftforge/fml/loading/LibraryFinder.java#L82
|
||||||
|
*
|
||||||
|
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
||||||
|
* @param {string} tempNativePath The path to store the native libraries.
|
||||||
|
* @returns {Array.<string>} An array containing the full JVM arguments for this process.
|
||||||
|
*/
|
||||||
|
_constructJVMArguments113(mods, tempNativePath){
|
||||||
|
|
||||||
|
const argDiscovery = /\${*(.*)}/
|
||||||
|
|
||||||
|
// JVM Arguments First
|
||||||
|
let args = this.vanillaManifest.arguments.jvm
|
||||||
|
|
||||||
|
// Debug securejarhandler
|
||||||
|
// args.push('-Dbsl.debug=true')
|
||||||
|
|
||||||
|
if(this.modManifest.arguments.jvm != null) {
|
||||||
|
for(const argStr of this.modManifest.arguments.jvm) {
|
||||||
|
args.push(argStr
|
||||||
|
.replaceAll('${library_directory}', this.libPath)
|
||||||
|
.replaceAll('${classpath_separator}', ProcessBuilder.getClasspathSeparator())
|
||||||
|
.replaceAll('${version_name}', this.modManifest.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
args.splice(2, 0, ...ConfigManager.getJVMOptions())
|
//args.push('-Dlog4j.configurationFile=D:\\WesterosCraft\\game\\common\\assets\\log_configs\\client-1.12.xml')
|
||||||
|
|
||||||
args = args.concat(this._resolveForgeArgs())
|
// Java Arguments
|
||||||
|
if(process.platform === 'darwin'){
|
||||||
|
args.push('-Xdock:name=HeliosLauncher')
|
||||||
|
args.push('-Xdock:icon=' + path.join(__dirname, '..', 'images', 'minecraft.icns'))
|
||||||
|
}
|
||||||
|
args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.rawServer.id))
|
||||||
|
args.push('-Xms' + ConfigManager.getMinRAM(this.server.rawServer.id))
|
||||||
|
args = args.concat(ConfigManager.getJVMOptions(this.server.rawServer.id))
|
||||||
|
|
||||||
|
// Main Java Class
|
||||||
|
args.push(this.modManifest.mainClass)
|
||||||
|
|
||||||
|
// Vanilla Arguments
|
||||||
|
args = args.concat(this.vanillaManifest.arguments.game)
|
||||||
|
|
||||||
|
for(let i=0; i<args.length; i++){
|
||||||
|
if(typeof args[i] === 'object' && args[i].rules != null){
|
||||||
|
|
||||||
|
let checksum = 0
|
||||||
|
for(let rule of args[i].rules){
|
||||||
|
if(rule.os != null){
|
||||||
|
if(rule.os.name === getMojangOS()
|
||||||
|
&& (rule.os.version == null || new RegExp(rule.os.version).test(os.release))){
|
||||||
|
if(rule.action === 'allow'){
|
||||||
|
checksum++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(rule.action === 'disallow'){
|
||||||
|
checksum++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(rule.features != null){
|
||||||
|
// We don't have many 'features' in the index at the moment.
|
||||||
|
// This should be fine for a while.
|
||||||
|
if(rule.features.has_custom_resolution != null && rule.features.has_custom_resolution === true){
|
||||||
|
if(ConfigManager.getFullscreen()){
|
||||||
|
args[i].value = [
|
||||||
|
'--fullscreen',
|
||||||
|
'true'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
checksum++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO splice not push
|
||||||
|
if(checksum === args[i].rules.length){
|
||||||
|
if(typeof args[i].value === 'string'){
|
||||||
|
args[i] = args[i].value
|
||||||
|
} else if(typeof args[i].value === 'object'){
|
||||||
|
//args = args.concat(args[i].value)
|
||||||
|
args.splice(i, 1, ...args[i].value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement i to reprocess the resolved value
|
||||||
|
i--
|
||||||
|
} else {
|
||||||
|
args[i] = null
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if(typeof args[i] === 'string'){
|
||||||
|
if(argDiscovery.test(args[i])){
|
||||||
|
const identifier = args[i].match(argDiscovery)[1]
|
||||||
|
let val = null
|
||||||
|
switch(identifier){
|
||||||
|
case 'auth_player_name':
|
||||||
|
val = this.authUser.displayName.trim()
|
||||||
|
break
|
||||||
|
case 'version_name':
|
||||||
|
//val = vanillaManifest.id
|
||||||
|
val = this.server.rawServer.id
|
||||||
|
break
|
||||||
|
case 'game_directory':
|
||||||
|
val = this.gameDir
|
||||||
|
break
|
||||||
|
case 'assets_root':
|
||||||
|
val = path.join(this.commonDir, 'assets')
|
||||||
|
break
|
||||||
|
case 'assets_index_name':
|
||||||
|
val = this.vanillaManifest.assets
|
||||||
|
break
|
||||||
|
case 'auth_uuid':
|
||||||
|
val = this.authUser.uuid.trim()
|
||||||
|
break
|
||||||
|
case 'auth_access_token':
|
||||||
|
val = this.authUser.accessToken
|
||||||
|
break
|
||||||
|
case 'user_type':
|
||||||
|
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang'
|
||||||
|
break
|
||||||
|
case 'version_type':
|
||||||
|
val = this.vanillaManifest.type
|
||||||
|
break
|
||||||
|
case 'resolution_width':
|
||||||
|
val = ConfigManager.getGameWidth()
|
||||||
|
break
|
||||||
|
case 'resolution_height':
|
||||||
|
val = ConfigManager.getGameHeight()
|
||||||
|
break
|
||||||
|
case 'natives_directory':
|
||||||
|
val = args[i].replace(argDiscovery, tempNativePath)
|
||||||
|
break
|
||||||
|
case 'launcher_name':
|
||||||
|
val = args[i].replace(argDiscovery, 'Helios-Launcher')
|
||||||
|
break
|
||||||
|
case 'launcher_version':
|
||||||
|
val = args[i].replace(argDiscovery, this.launcherVersion)
|
||||||
|
break
|
||||||
|
case 'classpath':
|
||||||
|
val = this.classpathArg(mods, tempNativePath).join(ProcessBuilder.getClasspathSeparator())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if(val != null){
|
||||||
|
args[i] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Autoconnect
|
||||||
|
this._processAutoConnectArg(args)
|
||||||
|
|
||||||
|
|
||||||
|
// Forge Specific Arguments
|
||||||
|
args = args.concat(this.modManifest.arguments.game)
|
||||||
|
|
||||||
|
// Filter null values
|
||||||
|
args = args.filter(arg => {
|
||||||
|
return arg != null
|
||||||
|
})
|
||||||
|
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
@ -266,7 +558,7 @@ class ProcessBuilder {
|
|||||||
* @returns {Array.<string>} An array containing the arguments required by forge.
|
* @returns {Array.<string>} An array containing the arguments required by forge.
|
||||||
*/
|
*/
|
||||||
_resolveForgeArgs(){
|
_resolveForgeArgs(){
|
||||||
const mcArgs = this.forgeData.minecraftArguments.split(' ')
|
const mcArgs = this.modManifest.minecraftArguments.split(' ')
|
||||||
const argDiscovery = /\${*(.*)}/
|
const argDiscovery = /\${*(.*)}/
|
||||||
|
|
||||||
// Replace the declared variables with their proper values.
|
// Replace the declared variables with their proper values.
|
||||||
@ -279,8 +571,8 @@ class ProcessBuilder {
|
|||||||
val = this.authUser.displayName.trim()
|
val = this.authUser.displayName.trim()
|
||||||
break
|
break
|
||||||
case 'version_name':
|
case 'version_name':
|
||||||
//val = versionData.id
|
//val = vanillaManifest.id
|
||||||
val = this.server.getID()
|
val = this.server.rawServer.id
|
||||||
break
|
break
|
||||||
case 'game_directory':
|
case 'game_directory':
|
||||||
val = this.gameDir
|
val = this.gameDir
|
||||||
@ -289,7 +581,7 @@ class ProcessBuilder {
|
|||||||
val = path.join(this.commonDir, 'assets')
|
val = path.join(this.commonDir, 'assets')
|
||||||
break
|
break
|
||||||
case 'assets_index_name':
|
case 'assets_index_name':
|
||||||
val = this.versionData.assets
|
val = this.vanillaManifest.assets
|
||||||
break
|
break
|
||||||
case 'auth_uuid':
|
case 'auth_uuid':
|
||||||
val = this.authUser.uuid.trim()
|
val = this.authUser.uuid.trim()
|
||||||
@ -298,10 +590,13 @@ class ProcessBuilder {
|
|||||||
val = this.authUser.accessToken
|
val = this.authUser.accessToken
|
||||||
break
|
break
|
||||||
case 'user_type':
|
case 'user_type':
|
||||||
val = 'MOJANG'
|
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang'
|
||||||
|
break
|
||||||
|
case 'user_properties': // 1.8.9 and below.
|
||||||
|
val = '{}'
|
||||||
break
|
break
|
||||||
case 'version_type':
|
case 'version_type':
|
||||||
val = this.versionData.type
|
val = this.vanillaManifest.type
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if(val != null){
|
if(val != null){
|
||||||
@ -309,39 +604,59 @@ class ProcessBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mcArgs.push('--modListFile')
|
|
||||||
mcArgs.push('absolute:' + this.fmlDir)
|
|
||||||
|
|
||||||
|
// Autoconnect to the selected server.
|
||||||
|
this._processAutoConnectArg(mcArgs)
|
||||||
|
|
||||||
|
// Prepare game resolution
|
||||||
|
if(ConfigManager.getFullscreen()){
|
||||||
|
mcArgs.push('--fullscreen')
|
||||||
|
mcArgs.push(true)
|
||||||
|
} else {
|
||||||
|
mcArgs.push('--width')
|
||||||
|
mcArgs.push(ConfigManager.getGameWidth())
|
||||||
|
mcArgs.push('--height')
|
||||||
|
mcArgs.push(ConfigManager.getGameHeight())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mod List File Argument
|
||||||
|
mcArgs.push('--modListFile')
|
||||||
|
if(this._lteMinorVersion(9)) {
|
||||||
|
mcArgs.push(path.basename(this.fmlDir))
|
||||||
|
} else {
|
||||||
|
mcArgs.push('absolute:' + this.fmlDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// LiteLoader
|
||||||
if(this.usingLiteLoader){
|
if(this.usingLiteLoader){
|
||||||
mcArgs.push('--modRepo')
|
mcArgs.push('--modRepo')
|
||||||
mcArgs.push(this.llDir)
|
mcArgs.push(this.llDir)
|
||||||
|
|
||||||
|
// Set first arg to liteloader tweak class
|
||||||
mcArgs.unshift('com.mumfrey.liteloader.launch.LiteLoaderTweaker')
|
mcArgs.unshift('com.mumfrey.liteloader.launch.LiteLoaderTweaker')
|
||||||
mcArgs.unshift('--tweakClass')
|
mcArgs.unshift('--tweakClass')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare game resolution
|
return mcArgs
|
||||||
if(ConfigManager.getFullscreen()){
|
}
|
||||||
mcArgs.unshift('--fullscreen')
|
|
||||||
} else {
|
|
||||||
mcArgs.unshift(ConfigManager.getGameWidth())
|
|
||||||
mcArgs.unshift('--width')
|
|
||||||
mcArgs.unshift(ConfigManager.getGameHeight())
|
|
||||||
mcArgs.unshift('--height')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare autoconnect
|
/**
|
||||||
if(ConfigManager.getAutoConnect() && this.server.isAutoConnect()){
|
* Ensure that the classpath entries all point to jar files.
|
||||||
const serverURL = new URL('my://' + this.server.getAddress())
|
*
|
||||||
mcArgs.unshift(serverURL.hostname)
|
* @param {Array.<String>} list Array of classpath entries.
|
||||||
mcArgs.unshift('--server')
|
*/
|
||||||
if(serverURL.port){
|
_processClassPathList(list) {
|
||||||
mcArgs.unshift(serverURL.port)
|
|
||||||
mcArgs.unshift('--port')
|
const ext = '.jar'
|
||||||
|
const extLen = ext.length
|
||||||
|
for(let i=0; i<list.length; i++) {
|
||||||
|
const extIndex = list[i].indexOf(ext)
|
||||||
|
if(extIndex > -1 && extIndex !== list[i].length - extLen) {
|
||||||
|
list[i] = list[i].substring(0, extIndex + extLen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mcArgs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -356,9 +671,13 @@ class ProcessBuilder {
|
|||||||
classpathArg(mods, tempNativePath){
|
classpathArg(mods, tempNativePath){
|
||||||
let cpArgs = []
|
let cpArgs = []
|
||||||
|
|
||||||
// Add the version.jar to the classpath.
|
if(!mcVersionAtLeast('1.17', this.server.rawServer.minecraftVersion) || this.usingFabricLoader) {
|
||||||
const version = this.versionData.id
|
// Add the version.jar to the classpath.
|
||||||
cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar'))
|
// Must not be added to the classpath for Forge 1.17+.
|
||||||
|
const version = this.vanillaManifest.id
|
||||||
|
cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar'))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if(this.usingLiteLoader){
|
if(this.usingLiteLoader){
|
||||||
cpArgs.push(this.llPath)
|
cpArgs.push(this.llPath)
|
||||||
@ -366,11 +685,17 @@ class ProcessBuilder {
|
|||||||
|
|
||||||
// Resolve the Mojang declared libraries.
|
// Resolve the Mojang declared libraries.
|
||||||
const mojangLibs = this._resolveMojangLibraries(tempNativePath)
|
const mojangLibs = this._resolveMojangLibraries(tempNativePath)
|
||||||
cpArgs = cpArgs.concat(mojangLibs)
|
|
||||||
|
|
||||||
// Resolve the server declared libraries.
|
// Resolve the server declared libraries.
|
||||||
const servLibs = this._resolveServerLibraries(mods)
|
const servLibs = this._resolveServerLibraries(mods)
|
||||||
cpArgs = cpArgs.concat(servLibs)
|
|
||||||
|
// Merge libraries, server libs with the same
|
||||||
|
// maven identifier will override the mojang ones.
|
||||||
|
// Ex. 1.7.10 forge overrides mojang's guava with newer version.
|
||||||
|
const finalLibs = {...mojangLibs, ...servLibs}
|
||||||
|
cpArgs = cpArgs.concat(Object.values(finalLibs))
|
||||||
|
|
||||||
|
this._processClassPathList(cpArgs)
|
||||||
|
|
||||||
return cpArgs
|
return cpArgs
|
||||||
}
|
}
|
||||||
@ -382,26 +707,23 @@ class ProcessBuilder {
|
|||||||
* TODO - clean up function
|
* TODO - clean up function
|
||||||
*
|
*
|
||||||
* @param {string} tempNativePath The path to store the native libraries.
|
* @param {string} tempNativePath The path to store the native libraries.
|
||||||
* @returns {Array.<string>} An array containing the paths of each library mojang declares.
|
* @returns {{[id: string]: string}} An object containing the paths of each library mojang declares.
|
||||||
*/
|
*/
|
||||||
_resolveMojangLibraries(tempNativePath){
|
_resolveMojangLibraries(tempNativePath){
|
||||||
const libs = []
|
const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/
|
||||||
|
const libs = {}
|
||||||
|
|
||||||
const libArr = this.versionData.libraries
|
const libArr = this.vanillaManifest.libraries
|
||||||
fs.ensureDirSync(tempNativePath)
|
fs.ensureDirSync(tempNativePath)
|
||||||
for(let i=0; i<libArr.length; i++){
|
for(let i=0; i<libArr.length; i++){
|
||||||
const lib = libArr[i]
|
const lib = libArr[i]
|
||||||
if(Library.validateRules(lib.rules, lib.natives)){
|
if(isLibraryCompatible(lib.rules, lib.natives)){
|
||||||
if(lib.natives == null){
|
|
||||||
const dlInfo = lib.downloads
|
// Pre-1.19 has a natives object.
|
||||||
const artifact = dlInfo.artifact
|
if(lib.natives != null) {
|
||||||
const to = path.join(this.libPath, artifact.path)
|
|
||||||
libs.push(to)
|
|
||||||
} else {
|
|
||||||
// Extract the native library.
|
// Extract the native library.
|
||||||
const extractInst = lib.extract
|
const exclusionArr = lib.extract != null ? lib.extract.exclude : ['META-INF/']
|
||||||
const exclusionArr = extractInst.exclude
|
const artifact = lib.downloads.classifiers[lib.natives[getMojangOS()].replace('${arch}', process.arch.replace('x', ''))]
|
||||||
const artifact = lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))]
|
|
||||||
|
|
||||||
// Location of native zip.
|
// Location of native zip.
|
||||||
const to = path.join(this.libPath, artifact.path)
|
const to = path.join(this.libPath, artifact.path)
|
||||||
@ -433,6 +755,65 @@ class ProcessBuilder {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 1.19+ logic
|
||||||
|
else if(lib.name.includes('natives-')) {
|
||||||
|
|
||||||
|
const regexTest = nativesRegex.exec(lib.name)
|
||||||
|
// const os = regexTest[1]
|
||||||
|
const arch = regexTest[2] ?? 'x64'
|
||||||
|
|
||||||
|
if(arch != process.arch) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the native library.
|
||||||
|
const exclusionArr = lib.extract != null ? lib.extract.exclude : ['META-INF/', '.git', '.sha1']
|
||||||
|
const artifact = lib.downloads.artifact
|
||||||
|
|
||||||
|
// Location of native zip.
|
||||||
|
const to = path.join(this.libPath, artifact.path)
|
||||||
|
|
||||||
|
let zip = new AdmZip(to)
|
||||||
|
let zipEntries = zip.getEntries()
|
||||||
|
|
||||||
|
// Unzip the native zip.
|
||||||
|
for(let i=0; i<zipEntries.length; i++){
|
||||||
|
if(zipEntries[i].isDirectory) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = zipEntries[i].entryName
|
||||||
|
|
||||||
|
let shouldExclude = false
|
||||||
|
|
||||||
|
// Exclude noted files.
|
||||||
|
exclusionArr.forEach(function(exclusion){
|
||||||
|
if(fileName.indexOf(exclusion) > -1){
|
||||||
|
shouldExclude = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const extractName = fileName.includes('/') ? fileName.substring(fileName.lastIndexOf('/')) : fileName
|
||||||
|
|
||||||
|
// Extract the file.
|
||||||
|
if(!shouldExclude){
|
||||||
|
fs.writeFile(path.join(tempNativePath, extractName), zipEntries[i].getData(), (err) => {
|
||||||
|
if(err){
|
||||||
|
logger.error('Error while extracting native library:', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No natives
|
||||||
|
else {
|
||||||
|
const dlInfo = lib.downloads
|
||||||
|
const artifact = dlInfo.artifact
|
||||||
|
const to = path.join(this.libPath, artifact.path)
|
||||||
|
const versionIndependentId = lib.name.substring(0, lib.name.lastIndexOf(':'))
|
||||||
|
libs[versionIndependentId] = to
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,21 +826,21 @@ class ProcessBuilder {
|
|||||||
* declare libraries.
|
* declare libraries.
|
||||||
*
|
*
|
||||||
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
* @param {Array.<Object>} mods An array of enabled mods which will be launched with this process.
|
||||||
* @returns {Array.<string>} An array containing the paths of each library this server requires.
|
* @returns {{[id: string]: string}} An object containing the paths of each library this server requires.
|
||||||
*/
|
*/
|
||||||
_resolveServerLibraries(mods){
|
_resolveServerLibraries(mods){
|
||||||
const mdls = this.server.getModules()
|
const mdls = this.server.modules
|
||||||
let libs = []
|
let libs = {}
|
||||||
|
|
||||||
// Locate Forge/Libraries
|
// Locate Forge/Fabric/Libraries
|
||||||
for(let mdl of mdls){
|
for(let mdl of mdls){
|
||||||
const type = mdl.getType()
|
const type = mdl.rawModule.type
|
||||||
if(type === DistroManager.Types.ForgeHosted || type === DistroManager.Types.Library){
|
if(type === Type.ForgeHosted || type === Type.Fabric || type === Type.Library){
|
||||||
libs.push(mdl.getArtifact().getPath())
|
libs[mdl.getVersionlessMavenIdentifier()] = mdl.getPath()
|
||||||
if(mdl.hasSubModules()){
|
if(mdl.subModules.length > 0){
|
||||||
const res = this._resolveModuleLibraries(mdl)
|
const res = this._resolveModuleLibraries(mdl)
|
||||||
if(res.length > 0){
|
if(res.length > 0){
|
||||||
libs = libs.concat(res)
|
libs = {...libs, ...res}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -470,7 +851,7 @@ class ProcessBuilder {
|
|||||||
if(mods.sub_modules != null){
|
if(mods.sub_modules != null){
|
||||||
const res = this._resolveModuleLibraries(mods[i])
|
const res = this._resolveModuleLibraries(mods[i])
|
||||||
if(res.length > 0){
|
if(res.length > 0){
|
||||||
libs = libs.concat(res)
|
libs = {...libs, ...res}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -485,17 +866,20 @@ class ProcessBuilder {
|
|||||||
* @returns {Array.<string>} An array containing the paths of each library this module requires.
|
* @returns {Array.<string>} An array containing the paths of each library this module requires.
|
||||||
*/
|
*/
|
||||||
_resolveModuleLibraries(mdl){
|
_resolveModuleLibraries(mdl){
|
||||||
if(!mdl.hasSubModules()){
|
if(!mdl.subModules.length > 0){
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
let libs = []
|
let libs = []
|
||||||
for(let sm of mdl.getSubModules()){
|
for(let sm of mdl.subModules){
|
||||||
if(sm.getType() === DistroManager.Types.Library){
|
if(sm.rawModule.type === Type.Library){
|
||||||
libs.push(sm.getArtifact().getPath())
|
|
||||||
|
if(sm.rawModule.classpath ?? true) {
|
||||||
|
libs.push(sm.getPath())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If this module has submodules, we need to resolve the libraries for those.
|
// If this module has submodules, we need to resolve the libraries for those.
|
||||||
// To avoid unnecessary recursive calls, base case is checked here.
|
// To avoid unnecessary recursive calls, base case is checked here.
|
||||||
if(mdl.hasSubModules()){
|
if(mdl.subModules.length > 0){
|
||||||
const res = this._resolveModuleLibraries(sm)
|
const res = this._resolveModuleLibraries(sm)
|
||||||
if(res.length > 0){
|
if(res.length > 0){
|
||||||
libs = libs.concat(res)
|
libs = libs.concat(res)
|
||||||
@ -504,6 +888,7 @@ class ProcessBuilder {
|
|||||||
}
|
}
|
||||||
return libs
|
return libs
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ProcessBuilder
|
module.exports = ProcessBuilder
|
@ -21,8 +21,6 @@ const loginForm = document.getElementById('loginForm')
|
|||||||
// Control variables.
|
// Control variables.
|
||||||
let lu = false, lp = false
|
let lu = false, lp = false
|
||||||
|
|
||||||
const loggerLogin = LoggerUtil('%c[Login]', 'color: #000668; font-weight: bold')
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a login error.
|
* Show a login error.
|
||||||
@ -56,7 +54,7 @@ function shakeError(element){
|
|||||||
function validateEmail(value){
|
function validateEmail(value){
|
||||||
if(value){
|
if(value){
|
||||||
if(!basicEmail.test(value) && !validUsername.test(value)){
|
if(!basicEmail.test(value) && !validUsername.test(value)){
|
||||||
showError(loginEmailError, '* Invalid Value')
|
showError(loginEmailError, Lang.queryJS('login.error.invalidValue'))
|
||||||
loginDisabled(true)
|
loginDisabled(true)
|
||||||
lu = false
|
lu = false
|
||||||
} else {
|
} else {
|
||||||
@ -68,7 +66,7 @@ function validateEmail(value){
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lu = false
|
lu = false
|
||||||
showError(loginEmailError, '* Required')
|
showError(loginEmailError, Lang.queryJS('login.error.requiredValue'))
|
||||||
loginDisabled(true)
|
loginDisabled(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,7 +85,7 @@ function validatePassword(value){
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lp = false
|
lp = false
|
||||||
showError(loginPasswordError, '* Required')
|
showError(loginPasswordError, Lang.queryJS('login.error.invalidValue'))
|
||||||
loginDisabled(true)
|
loginDisabled(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,10 +127,10 @@ function loginDisabled(v){
|
|||||||
function loginLoading(v){
|
function loginLoading(v){
|
||||||
if(v){
|
if(v){
|
||||||
loginButton.setAttribute('loading', v)
|
loginButton.setAttribute('loading', v)
|
||||||
loginButton.innerHTML = loginButton.innerHTML.replace('LOGIN', 'LOGGING IN')
|
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.login'), Lang.queryJS('login.loggingIn'))
|
||||||
} else {
|
} else {
|
||||||
loginButton.removeAttribute('loading')
|
loginButton.removeAttribute('loading')
|
||||||
loginButton.innerHTML = loginButton.innerHTML.replace('LOGGING IN', 'LOGIN')
|
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.login'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,79 +152,6 @@ function formDisabled(v){
|
|||||||
loginRememberOption.disabled = 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: '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.'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(err.error != null){
|
|
||||||
if(err.error === 'ForbiddenOperationException'){
|
|
||||||
if(err.errorMessage != null){
|
|
||||||
if(err.errorMessage === 'Invalid credentials. Invalid username or password.'){
|
|
||||||
return {
|
|
||||||
title: 'Error During Login:<br>Invalid Credentials',
|
|
||||||
desc: 'The email or password you\'ve entered is incorrect. Please try again.'
|
|
||||||
}
|
|
||||||
} else if(err.errorMessage === 'Invalid credentials.'){
|
|
||||||
return {
|
|
||||||
title: 'Error During Login:<br>Too Many Attempts',
|
|
||||||
desc: 'There have been too many login attempts with this account recently. Please try again later.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Request errors (from Node).
|
|
||||||
if(err.code != null){
|
|
||||||
if(err.code === 'ENOENT'){
|
|
||||||
// No Internet.
|
|
||||||
return {
|
|
||||||
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.'
|
|
||||||
}
|
|
||||||
} else if(err.code === 'ENOTFOUND'){
|
|
||||||
// Could not reach server.
|
|
||||||
return {
|
|
||||||
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>.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(err.message != null){
|
|
||||||
if(err.message === 'NotPaidAccount'){
|
|
||||||
return {
|
|
||||||
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>'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unknown error with request.
|
|
||||||
return {
|
|
||||||
title: 'Error During Login:<br>Unknown Error',
|
|
||||||
desc: err.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Unknown Mojang error.
|
|
||||||
return {
|
|
||||||
title: err.error,
|
|
||||||
desc: err.errorMessage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let loginViewOnSuccess = VIEWS.landing
|
let loginViewOnSuccess = VIEWS.landing
|
||||||
let loginViewOnCancel = VIEWS.settings
|
let loginViewOnCancel = VIEWS.settings
|
||||||
let loginViewCancelHandler
|
let loginViewCancelHandler
|
||||||
@ -262,16 +187,16 @@ loginButton.addEventListener('click', () => {
|
|||||||
// Show loading stuff.
|
// Show loading stuff.
|
||||||
loginLoading(true)
|
loginLoading(true)
|
||||||
|
|
||||||
AuthManager.addAccount(loginUsername.value, loginPassword.value).then((value) => {
|
AuthManager.addMojangAccount(loginUsername.value, loginPassword.value).then((value) => {
|
||||||
updateSelectedAccount(value)
|
updateSelectedAccount(value)
|
||||||
loginButton.innerHTML = loginButton.innerHTML.replace('LOGGING IN', 'SUCCESS')
|
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.loggingIn'), Lang.queryJS('login.success'))
|
||||||
$('.circle-loader').toggleClass('load-complete')
|
$('.circle-loader').toggleClass('load-complete')
|
||||||
$('.checkmark').toggle()
|
$('.checkmark').toggle()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
switchView(VIEWS.login, loginViewOnSuccess, 500, 500, () => {
|
switchView(VIEWS.login, loginViewOnSuccess, 500, 500, async () => {
|
||||||
// Temporary workaround
|
// Temporary workaround
|
||||||
if(loginViewOnSuccess === VIEWS.settings){
|
if(loginViewOnSuccess === VIEWS.settings){
|
||||||
prepareSettings()
|
await prepareSettings()
|
||||||
}
|
}
|
||||||
loginViewOnSuccess = VIEWS.landing // Reset this for good measure.
|
loginViewOnSuccess = VIEWS.landing // Reset this for good measure.
|
||||||
loginCancelEnabled(false) // Reset this for good measure.
|
loginCancelEnabled(false) // Reset this for good measure.
|
||||||
@ -281,20 +206,29 @@ loginButton.addEventListener('click', () => {
|
|||||||
$('.circle-loader').toggleClass('load-complete')
|
$('.circle-loader').toggleClass('load-complete')
|
||||||
$('.checkmark').toggle()
|
$('.checkmark').toggle()
|
||||||
loginLoading(false)
|
loginLoading(false)
|
||||||
loginButton.innerHTML = loginButton.innerHTML.replace('SUCCESS', 'LOGIN')
|
loginButton.innerHTML = loginButton.innerHTML.replace(Lang.queryJS('login.success'), Lang.queryJS('login.login'))
|
||||||
formDisabled(false)
|
formDisabled(false)
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}).catch((err) => {
|
}).catch((displayableError) => {
|
||||||
loginLoading(false)
|
loginLoading(false)
|
||||||
const errF = resolveError(err)
|
|
||||||
setOverlayContent(errF.title, errF.desc, 'Try Again')
|
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'))
|
||||||
setOverlayHandler(() => {
|
setOverlayHandler(() => {
|
||||||
formDisabled(false)
|
formDisabled(false)
|
||||||
toggleOverlay(false)
|
toggleOverlay(false)
|
||||||
})
|
})
|
||||||
toggleOverlay(true)
|
toggleOverlay(true)
|
||||||
loggerLogin.log('Error while logging in.', err)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
50
app/assets/js/scripts/loginOptions.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
const loginOptionsCancelContainer = document.getElementById('loginOptionCancelContainer')
|
||||||
|
const loginOptionMicrosoft = document.getElementById('loginOptionMicrosoft')
|
||||||
|
const loginOptionMojang = document.getElementById('loginOptionMojang')
|
||||||
|
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
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loginOptionMojang.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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -117,8 +117,8 @@ function toggleOverlay(toggleState, dismissable = false, content = 'overlayConte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleServerSelection(toggleState){
|
async function toggleServerSelection(toggleState){
|
||||||
prepareServerSelectionList()
|
await prepareServerSelectionList()
|
||||||
toggleOverlay(toggleState, true, 'serverSelectContent')
|
toggleOverlay(toggleState, true, 'serverSelectContent')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ function toggleServerSelection(toggleState){
|
|||||||
* @param {string} acknowledge Acknowledge button text.
|
* @param {string} acknowledge Acknowledge button text.
|
||||||
* @param {string} dismiss Dismiss button text.
|
* @param {string} dismiss Dismiss button text.
|
||||||
*/
|
*/
|
||||||
function setOverlayContent(title, description, acknowledge, dismiss = 'Dismiss'){
|
function setOverlayContent(title, description, acknowledge, dismiss = Lang.queryJS('overlay.dismiss')){
|
||||||
document.getElementById('overlayTitle').innerHTML = title
|
document.getElementById('overlayTitle').innerHTML = title
|
||||||
document.getElementById('overlayDesc').innerHTML = description
|
document.getElementById('overlayDesc').innerHTML = description
|
||||||
document.getElementById('overlayAcknowledge').innerHTML = acknowledge
|
document.getElementById('overlayAcknowledge').innerHTML = acknowledge
|
||||||
@ -171,11 +171,11 @@ function setDismissHandler(handler){
|
|||||||
|
|
||||||
/* Server Select View */
|
/* Server Select View */
|
||||||
|
|
||||||
document.getElementById('serverSelectConfirm').addEventListener('click', () => {
|
document.getElementById('serverSelectConfirm').addEventListener('click', async () => {
|
||||||
const listings = document.getElementsByClassName('serverListing')
|
const listings = document.getElementsByClassName('serverListing')
|
||||||
for(let i=0; i<listings.length; i++){
|
for(let i=0; i<listings.length; i++){
|
||||||
if(listings[i].hasAttribute('selected')){
|
if(listings[i].hasAttribute('selected')){
|
||||||
const serv = DistroManager.getDistribution().getServer(listings[i].getAttribute('servid'))
|
const serv = (await DistroAPI.getDistribution()).getServerById(listings[i].getAttribute('servid'))
|
||||||
updateSelectedServer(serv)
|
updateSelectedServer(serv)
|
||||||
refreshServerStatus(true)
|
refreshServerStatus(true)
|
||||||
toggleOverlay(false)
|
toggleOverlay(false)
|
||||||
@ -184,19 +184,22 @@ document.getElementById('serverSelectConfirm').addEventListener('click', () => {
|
|||||||
}
|
}
|
||||||
// None are selected? Not possible right? Meh, handle it.
|
// None are selected? Not possible right? Meh, handle it.
|
||||||
if(listings.length > 0){
|
if(listings.length > 0){
|
||||||
const serv = DistroManager.getDistribution().getServer(listings[i].getAttribute('servid'))
|
const serv = (await DistroAPI.getDistribution()).getServerById(listings[i].getAttribute('servid'))
|
||||||
updateSelectedServer(serv)
|
updateSelectedServer(serv)
|
||||||
toggleOverlay(false)
|
toggleOverlay(false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
document.getElementById('accountSelectConfirm').addEventListener('click', () => {
|
document.getElementById('accountSelectConfirm').addEventListener('click', async () => {
|
||||||
const listings = document.getElementsByClassName('accountListing')
|
const listings = document.getElementsByClassName('accountListing')
|
||||||
for(let i=0; i<listings.length; i++){
|
for(let i=0; i<listings.length; i++){
|
||||||
if(listings[i].hasAttribute('selected')){
|
if(listings[i].hasAttribute('selected')){
|
||||||
const authAcc = ConfigManager.setSelectedAccount(listings[i].getAttribute('uuid'))
|
const authAcc = ConfigManager.setSelectedAccount(listings[i].getAttribute('uuid'))
|
||||||
ConfigManager.save()
|
ConfigManager.save()
|
||||||
updateSelectedAccount(authAcc)
|
updateSelectedAccount(authAcc)
|
||||||
|
if(getCurrentView() === VIEWS.settings) {
|
||||||
|
await prepareSettings()
|
||||||
|
}
|
||||||
toggleOverlay(false)
|
toggleOverlay(false)
|
||||||
validateSelectedAccount()
|
validateSelectedAccount()
|
||||||
return
|
return
|
||||||
@ -207,6 +210,9 @@ document.getElementById('accountSelectConfirm').addEventListener('click', () =>
|
|||||||
const authAcc = ConfigManager.setSelectedAccount(listings[0].getAttribute('uuid'))
|
const authAcc = ConfigManager.setSelectedAccount(listings[0].getAttribute('uuid'))
|
||||||
ConfigManager.save()
|
ConfigManager.save()
|
||||||
updateSelectedAccount(authAcc)
|
updateSelectedAccount(authAcc)
|
||||||
|
if(getCurrentView() === VIEWS.settings) {
|
||||||
|
await prepareSettings()
|
||||||
|
}
|
||||||
toggleOverlay(false)
|
toggleOverlay(false)
|
||||||
validateSelectedAccount()
|
validateSelectedAccount()
|
||||||
}
|
}
|
||||||
@ -261,21 +267,21 @@ function setAccountListingHandlers(){
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateServerListings(){
|
async function populateServerListings(){
|
||||||
const distro = DistroManager.getDistribution()
|
const distro = await DistroAPI.getDistribution()
|
||||||
const giaSel = ConfigManager.getSelectedServer()
|
const giaSel = ConfigManager.getSelectedServer()
|
||||||
const servers = distro.getServers()
|
const servers = distro.servers
|
||||||
let htmlString = ''
|
let htmlString = ''
|
||||||
for(const serv of servers){
|
for(const serv of servers){
|
||||||
htmlString += `<button class="serverListing" servid="${serv.getID()}" ${serv.getID() === giaSel ? 'selected' : ''}>
|
htmlString += `<button class="serverListing" servid="${serv.rawServer.id}" ${serv.rawServer.id === giaSel ? 'selected' : ''}>
|
||||||
<img class="serverListingImg" src="${serv.getIcon()}"/>
|
<img class="serverListingImg" src="${serv.rawServer.icon}"/>
|
||||||
<div class="serverListingDetails">
|
<div class="serverListingDetails">
|
||||||
<span class="serverListingName">${serv.getName()}</span>
|
<span class="serverListingName">${serv.rawServer.name}</span>
|
||||||
<span class="serverListingDescription">${serv.getDescription()}</span>
|
<span class="serverListingDescription">${serv.rawServer.description}</span>
|
||||||
<div class="serverListingInfo">
|
<div class="serverListingInfo">
|
||||||
<div class="serverListingVersion">${serv.getMinecraftVersion()}</div>
|
<div class="serverListingVersion">${serv.rawServer.minecraftVersion}</div>
|
||||||
<div class="serverListingRevision">${serv.getVersion()}</div>
|
<div class="serverListingRevision">${serv.rawServer.version}</div>
|
||||||
${serv.isMainServer() ? `<div class="serverListingStarWrapper">
|
${serv.rawServer.mainServer ? `<div class="serverListingStarWrapper">
|
||||||
<svg id="Layer_1" viewBox="0 0 107.45 104.74" width="20px" height="20px">
|
<svg id="Layer_1" viewBox="0 0 107.45 104.74" width="20px" height="20px">
|
||||||
<defs>
|
<defs>
|
||||||
<style>.cls-1{fill:#fff;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style>
|
<style>.cls-1{fill:#fff;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;}</style>
|
||||||
@ -283,7 +289,7 @@ 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"/>
|
<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"/>
|
<circle class="cls-2" cx="53.73" cy="53.9" r="38"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="serverListingStarTooltip">Main Server</span>
|
<span class="serverListingStarTooltip">${Lang.queryJS('settings.serverListing.mainServer')}</span>
|
||||||
</div>` : ''}
|
</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -299,7 +305,7 @@ function populateAccountListings(){
|
|||||||
let htmlString = ''
|
let htmlString = ''
|
||||||
for(let i=0; i<accounts.length; i++){
|
for(let i=0; i<accounts.length; i++){
|
||||||
htmlString += `<button class="accountListing" uuid="${accounts[i].uuid}" ${i===0 ? 'selected' : ''}>
|
htmlString += `<button class="accountListing" uuid="${accounts[i].uuid}" ${i===0 ? 'selected' : ''}>
|
||||||
<img src="https://crafatar.com/renders/head/${accounts[i].uuid}?scale=2&default=MHF_Steve&overlay">
|
<img src="https://mc-heads.net/head/${accounts[i].uuid}/40">
|
||||||
<div class="accountListingName">${accounts[i].displayName}</div>
|
<div class="accountListingName">${accounts[i].displayName}</div>
|
||||||
</button>`
|
</button>`
|
||||||
}
|
}
|
||||||
@ -307,8 +313,8 @@ function populateAccountListings(){
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareServerSelectionList(){
|
async function prepareServerSelectionList(){
|
||||||
populateServerListings()
|
await populateServerListings()
|
||||||
setServerListingHandlers()
|
setServerListingHandlers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
// Requirements
|
// Requirements
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const { Type } = require('helios-distribution-types')
|
||||||
|
|
||||||
const AuthManager = require('./assets/js/authmanager')
|
const AuthManager = require('./assets/js/authmanager')
|
||||||
const ConfigManager = require('./assets/js/configmanager')
|
const ConfigManager = require('./assets/js/configmanager')
|
||||||
const DistroManager = require('./assets/js/distromanager')
|
const { DistroAPI } = require('./assets/js/distromanager')
|
||||||
|
|
||||||
let rscShouldLoad = false
|
let rscShouldLoad = false
|
||||||
let fatalStartupError = false
|
let fatalStartupError = false
|
||||||
@ -15,9 +16,11 @@ let fatalStartupError = false
|
|||||||
// Mapping of each view to their container IDs.
|
// Mapping of each view to their container IDs.
|
||||||
const VIEWS = {
|
const VIEWS = {
|
||||||
landing: '#landingContainer',
|
landing: '#landingContainer',
|
||||||
|
loginOptions: '#loginOptionsContainer',
|
||||||
login: '#loginContainer',
|
login: '#loginContainer',
|
||||||
settings: '#settingsContainer',
|
settings: '#settingsContainer',
|
||||||
welcome: '#welcomeContainer'
|
welcome: '#welcomeContainer',
|
||||||
|
waiting: '#waitingContainer'
|
||||||
}
|
}
|
||||||
|
|
||||||
// The currently shown view container.
|
// The currently shown view container.
|
||||||
@ -37,10 +40,10 @@ let currentView
|
|||||||
*/
|
*/
|
||||||
function switchView(current, next, currentFadeTime = 500, nextFadeTime = 500, onCurrentFade = () => {}, onNextFade = () => {}){
|
function switchView(current, next, currentFadeTime = 500, nextFadeTime = 500, onCurrentFade = () => {}, onNextFade = () => {}){
|
||||||
currentView = next
|
currentView = next
|
||||||
$(`${current}`).fadeOut(currentFadeTime, () => {
|
$(`${current}`).fadeOut(currentFadeTime, async () => {
|
||||||
onCurrentFade()
|
await onCurrentFade()
|
||||||
$(`${next}`).fadeIn(nextFadeTime, () => {
|
$(`${next}`).fadeIn(nextFadeTime, async () => {
|
||||||
onNextFade()
|
await onNextFade()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -54,14 +57,15 @@ function getCurrentView(){
|
|||||||
return currentView
|
return currentView
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMainUI(data){
|
async function showMainUI(data){
|
||||||
|
|
||||||
if(!isDev){
|
if(!isDev){
|
||||||
loggerAutoUpdater.log('Initializing..')
|
loggerAutoUpdater.info('Initializing..')
|
||||||
ipcRenderer.send('autoUpdateAction', 'initAutoUpdater', ConfigManager.getAllowPrerelease())
|
ipcRenderer.send('autoUpdateAction', 'initAutoUpdater', ConfigManager.getAllowPrerelease())
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectedServer(data.getServer(ConfigManager.getSelectedServer()))
|
await prepareSettings(true)
|
||||||
|
updateSelectedServer(data.getServerById(ConfigManager.getSelectedServer()))
|
||||||
refreshServerStatus()
|
refreshServerStatus()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.getElementById('frameBar').style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
|
document.getElementById('frameBar').style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
|
||||||
@ -84,8 +88,11 @@ function showMainUI(data){
|
|||||||
currentView = VIEWS.landing
|
currentView = VIEWS.landing
|
||||||
$(VIEWS.landing).fadeIn(1000)
|
$(VIEWS.landing).fadeIn(1000)
|
||||||
} else {
|
} else {
|
||||||
currentView = VIEWS.login
|
loginOptionsCancelEnabled(false)
|
||||||
$(VIEWS.login).fadeIn(1000)
|
loginOptionsViewOnLoginSuccess = VIEWS.landing
|
||||||
|
loginOptionsViewOnLoginCancel = VIEWS.loginOptions
|
||||||
|
currentView = VIEWS.loginOptions
|
||||||
|
$(VIEWS.loginOptions).fadeIn(1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,9 +114,9 @@ function showFatalStartupError(){
|
|||||||
$('#loadingContainer').fadeOut(250, () => {
|
$('#loadingContainer').fadeOut(250, () => {
|
||||||
document.getElementById('overlayContainer').style.background = 'none'
|
document.getElementById('overlayContainer').style.background = 'none'
|
||||||
setOverlayContent(
|
setOverlayContent(
|
||||||
'Fatal Error: Unable to Load Distribution Index',
|
Lang.queryJS('uibinder.startup.fatalErrorTitle'),
|
||||||
'A connection could not be established to our servers to download the distribution index. No local copies were available to load. <br><br>The distribution index is an essential file which provides the latest server information. The launcher is unable to start without it. Ensure you are connected to the internet and relaunch the application.',
|
Lang.queryJS('uibinder.startup.fatalErrorMessage'),
|
||||||
'Close'
|
Lang.queryJS('uibinder.startup.closeButton')
|
||||||
)
|
)
|
||||||
setOverlayHandler(() => {
|
setOverlayHandler(() => {
|
||||||
const window = remote.getCurrentWindow()
|
const window = remote.getCurrentWindow()
|
||||||
@ -126,10 +133,11 @@ function showFatalStartupError(){
|
|||||||
* @param {Object} data The distro index object.
|
* @param {Object} data The distro index object.
|
||||||
*/
|
*/
|
||||||
function onDistroRefresh(data){
|
function onDistroRefresh(data){
|
||||||
updateSelectedServer(data.getServer(ConfigManager.getSelectedServer()))
|
updateSelectedServer(data.getServerById(ConfigManager.getSelectedServer()))
|
||||||
refreshServerStatus()
|
refreshServerStatus()
|
||||||
initNews()
|
initNews()
|
||||||
syncModConfigurations(data)
|
syncModConfigurations(data)
|
||||||
|
ensureJavaSettings(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -141,10 +149,10 @@ function syncModConfigurations(data){
|
|||||||
|
|
||||||
const syncedCfgs = []
|
const syncedCfgs = []
|
||||||
|
|
||||||
for(let serv of data.getServers()){
|
for(let serv of data.servers){
|
||||||
|
|
||||||
const id = serv.getID()
|
const id = serv.rawServer.id
|
||||||
const mdls = serv.getModules()
|
const mdls = serv.modules
|
||||||
const cfg = ConfigManager.getModConfiguration(id)
|
const cfg = ConfigManager.getModConfiguration(id)
|
||||||
|
|
||||||
if(cfg != null){
|
if(cfg != null){
|
||||||
@ -153,20 +161,20 @@ function syncModConfigurations(data){
|
|||||||
const mods = {}
|
const mods = {}
|
||||||
|
|
||||||
for(let mdl of mdls){
|
for(let mdl of mdls){
|
||||||
const type = mdl.getType()
|
const type = mdl.rawModule.type
|
||||||
|
|
||||||
if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){
|
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader || type === Type.FabricMod){
|
||||||
if(!mdl.getRequired().isRequired()){
|
if(!mdl.getRequired().value){
|
||||||
const mdlID = mdl.getVersionlessID()
|
const mdlID = mdl.getVersionlessMavenIdentifier()
|
||||||
if(modsOld[mdlID] == null){
|
if(modsOld[mdlID] == null){
|
||||||
mods[mdlID] = scanOptionalSubModules(mdl.getSubModules(), mdl)
|
mods[mdlID] = scanOptionalSubModules(mdl.subModules, mdl)
|
||||||
} else {
|
} else {
|
||||||
mods[mdlID] = mergeModConfiguration(modsOld[mdlID], scanOptionalSubModules(mdl.getSubModules(), mdl), false)
|
mods[mdlID] = mergeModConfiguration(modsOld[mdlID], scanOptionalSubModules(mdl.subModules, mdl), false)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(mdl.hasSubModules()){
|
if(mdl.subModules.length > 0){
|
||||||
const mdlID = mdl.getVersionlessID()
|
const mdlID = mdl.getVersionlessMavenIdentifier()
|
||||||
const v = scanOptionalSubModules(mdl.getSubModules(), mdl)
|
const v = scanOptionalSubModules(mdl.subModules, mdl)
|
||||||
if(typeof v === 'object'){
|
if(typeof v === 'object'){
|
||||||
if(modsOld[mdlID] == null){
|
if(modsOld[mdlID] == null){
|
||||||
mods[mdlID] = v
|
mods[mdlID] = v
|
||||||
@ -189,15 +197,15 @@ function syncModConfigurations(data){
|
|||||||
const mods = {}
|
const mods = {}
|
||||||
|
|
||||||
for(let mdl of mdls){
|
for(let mdl of mdls){
|
||||||
const type = mdl.getType()
|
const type = mdl.rawModule.type
|
||||||
if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){
|
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader || type === Type.FabricMod){
|
||||||
if(!mdl.getRequired().isRequired()){
|
if(!mdl.getRequired().value){
|
||||||
mods[mdl.getVersionlessID()] = scanOptionalSubModules(mdl.getSubModules(), mdl)
|
mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl)
|
||||||
} else {
|
} else {
|
||||||
if(mdl.hasSubModules()){
|
if(mdl.subModules.length > 0){
|
||||||
const v = scanOptionalSubModules(mdl.getSubModules(), mdl)
|
const v = scanOptionalSubModules(mdl.subModules, mdl)
|
||||||
if(typeof v === 'object'){
|
if(typeof v === 'object'){
|
||||||
mods[mdl.getVersionlessID()] = v
|
mods[mdl.getVersionlessMavenIdentifier()] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,6 +224,21 @@ function syncModConfigurations(data){
|
|||||||
ConfigManager.save()
|
ConfigManager.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure java configurations are present for the available servers.
|
||||||
|
*
|
||||||
|
* @param {Object} data The distro index object.
|
||||||
|
*/
|
||||||
|
function ensureJavaSettings(data) {
|
||||||
|
|
||||||
|
// Nothing too fancy for now.
|
||||||
|
for(const serv of data.servers){
|
||||||
|
ConfigManager.ensureJavaConfig(serv.rawServer.id, serv.effectiveJavaOptions, serv.rawServer.javaOptions?.ram)
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigManager.save()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively scan for optional sub modules. If none are found,
|
* Recursively scan for optional sub modules. If none are found,
|
||||||
* this function returns a boolean. If optional sub modules do exist,
|
* this function returns a boolean. If optional sub modules do exist,
|
||||||
@ -228,17 +251,17 @@ function scanOptionalSubModules(mdls, origin){
|
|||||||
const mods = {}
|
const mods = {}
|
||||||
|
|
||||||
for(let mdl of mdls){
|
for(let mdl of mdls){
|
||||||
const type = mdl.getType()
|
const type = mdl.rawModule.type
|
||||||
// Optional types.
|
// Optional types.
|
||||||
if(type === DistroManager.Types.ForgeMod || type === DistroManager.Types.LiteMod || type === DistroManager.Types.LiteLoader){
|
if(type === Type.ForgeMod || type === Type.LiteMod || type === Type.LiteLoader || type === Type.FabricMod){
|
||||||
// It is optional.
|
// It is optional.
|
||||||
if(!mdl.getRequired().isRequired()){
|
if(!mdl.getRequired().value){
|
||||||
mods[mdl.getVersionlessID()] = scanOptionalSubModules(mdl.getSubModules(), mdl)
|
mods[mdl.getVersionlessMavenIdentifier()] = scanOptionalSubModules(mdl.subModules, mdl)
|
||||||
} else {
|
} else {
|
||||||
if(mdl.hasSubModules()){
|
if(mdl.hasSubModules()){
|
||||||
const v = scanOptionalSubModules(mdl.getSubModules(), mdl)
|
const v = scanOptionalSubModules(mdl.subModules, mdl)
|
||||||
if(typeof v === 'object'){
|
if(typeof v === 'object'){
|
||||||
mods[mdl.getVersionlessID()] = v
|
mods[mdl.getVersionlessMavenIdentifier()] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,13 +272,13 @@ function scanOptionalSubModules(mdls, origin){
|
|||||||
const ret = {
|
const ret = {
|
||||||
mods
|
mods
|
||||||
}
|
}
|
||||||
if(!origin.getRequired().isRequired()){
|
if(!origin.getRequired().value){
|
||||||
ret.value = origin.getRequired().isDefault()
|
ret.value = origin.getRequired().def
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return origin.getRequired().isDefault()
|
return origin.getRequired().def
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -300,18 +323,6 @@ function mergeModConfiguration(o, n, nReq = false){
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshDistributionIndex(remote, onSuccess, onError){
|
|
||||||
if(remote){
|
|
||||||
DistroManager.pullRemote()
|
|
||||||
.then(onSuccess)
|
|
||||||
.catch(onError)
|
|
||||||
} else {
|
|
||||||
DistroManager.pullLocal()
|
|
||||||
.then(onSuccess)
|
|
||||||
.catch(onError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validateSelectedAccount(){
|
async function validateSelectedAccount(){
|
||||||
const selectedAcc = ConfigManager.getSelectedAccount()
|
const selectedAcc = ConfigManager.getSelectedAccount()
|
||||||
if(selectedAcc != null){
|
if(selectedAcc != null){
|
||||||
@ -321,26 +332,54 @@ async function validateSelectedAccount(){
|
|||||||
ConfigManager.save()
|
ConfigManager.save()
|
||||||
const accLen = Object.keys(ConfigManager.getAuthAccounts()).length
|
const accLen = Object.keys(ConfigManager.getAuthAccounts()).length
|
||||||
setOverlayContent(
|
setOverlayContent(
|
||||||
'Failed to Refresh Login',
|
Lang.queryJS('uibinder.validateAccount.failedMessageTitle'),
|
||||||
`We were unable to refresh the login for <strong>${selectedAcc.displayName}</strong>. Please ${accLen > 0 ? 'select another account or ' : ''} login again.`,
|
accLen > 0
|
||||||
'Login',
|
? Lang.queryJS('uibinder.validateAccount.failedMessage', { 'account': selectedAcc.displayName })
|
||||||
'Select Another Account'
|
: Lang.queryJS('uibinder.validateAccount.failedMessageSelectAnotherAccount', { 'account': selectedAcc.displayName }),
|
||||||
|
Lang.queryJS('uibinder.validateAccount.loginButton'),
|
||||||
|
Lang.queryJS('uibinder.validateAccount.selectAnotherAccountButton')
|
||||||
)
|
)
|
||||||
setOverlayHandler(() => {
|
setOverlayHandler(() => {
|
||||||
document.getElementById('loginUsername').value = selectedAcc.username
|
|
||||||
validateEmail(selectedAcc.username)
|
const isMicrosoft = selectedAcc.type === 'microsoft'
|
||||||
loginViewOnSuccess = getCurrentView()
|
|
||||||
loginViewOnCancel = getCurrentView()
|
if(isMicrosoft) {
|
||||||
if(accLen > 0){
|
// Empty for now
|
||||||
loginViewCancelHandler = () => {
|
} else {
|
||||||
ConfigManager.addAuthAccount(selectedAcc.uuid, selectedAcc.accessToken, selectedAcc.username, selectedAcc.displayName)
|
// Mojang
|
||||||
|
// For convenience, pre-populate the username of the account.
|
||||||
|
document.getElementById('loginUsername').value = selectedAcc.username
|
||||||
|
validateEmail(selectedAcc.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
loginOptionsViewOnLoginSuccess = getCurrentView()
|
||||||
|
loginOptionsViewOnLoginCancel = VIEWS.loginOptions
|
||||||
|
|
||||||
|
if(accLen > 0) {
|
||||||
|
loginOptionsViewOnCancel = getCurrentView()
|
||||||
|
loginOptionsViewCancelHandler = () => {
|
||||||
|
if(isMicrosoft) {
|
||||||
|
ConfigManager.addMicrosoftAuthAccount(
|
||||||
|
selectedAcc.uuid,
|
||||||
|
selectedAcc.accessToken,
|
||||||
|
selectedAcc.username,
|
||||||
|
selectedAcc.expiresAt,
|
||||||
|
selectedAcc.microsoft.access_token,
|
||||||
|
selectedAcc.microsoft.refresh_token,
|
||||||
|
selectedAcc.microsoft.expires_at
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ConfigManager.addMojangAuthAccount(selectedAcc.uuid, selectedAcc.accessToken, selectedAcc.username, selectedAcc.displayName)
|
||||||
|
}
|
||||||
ConfigManager.save()
|
ConfigManager.save()
|
||||||
validateSelectedAccount()
|
validateSelectedAccount()
|
||||||
}
|
}
|
||||||
loginCancelEnabled(true)
|
loginOptionsCancelEnabled(true)
|
||||||
|
} else {
|
||||||
|
loginOptionsCancelEnabled(false)
|
||||||
}
|
}
|
||||||
toggleOverlay(false)
|
toggleOverlay(false)
|
||||||
switchView(getCurrentView(), VIEWS.login)
|
switchView(getCurrentView(), VIEWS.loginOptions)
|
||||||
})
|
})
|
||||||
setDismissHandler(() => {
|
setDismissHandler(() => {
|
||||||
if(accLen > 1){
|
if(accLen > 1){
|
||||||
@ -380,14 +419,14 @@ function setSelectedAccount(uuid){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Synchronous Listener
|
// Synchronous Listener
|
||||||
document.addEventListener('readystatechange', function(){
|
document.addEventListener('readystatechange', async () => {
|
||||||
|
|
||||||
if (document.readyState === 'interactive' || document.readyState === 'complete'){
|
if (document.readyState === 'interactive' || document.readyState === 'complete'){
|
||||||
if(rscShouldLoad){
|
if(rscShouldLoad){
|
||||||
rscShouldLoad = false
|
rscShouldLoad = false
|
||||||
if(!fatalStartupError){
|
if(!fatalStartupError){
|
||||||
const data = DistroManager.getDistribution()
|
const data = await DistroAPI.getDistribution()
|
||||||
showMainUI(data)
|
await showMainUI(data)
|
||||||
} else {
|
} else {
|
||||||
showFatalStartupError()
|
showFatalStartupError()
|
||||||
}
|
}
|
||||||
@ -397,12 +436,13 @@ document.addEventListener('readystatechange', function(){
|
|||||||
}, false)
|
}, false)
|
||||||
|
|
||||||
// Actions that must be performed after the distribution index is downloaded.
|
// Actions that must be performed after the distribution index is downloaded.
|
||||||
ipcRenderer.on('distributionIndexDone', (event, res) => {
|
ipcRenderer.on('distributionIndexDone', async (event, res) => {
|
||||||
if(res) {
|
if(res) {
|
||||||
const data = DistroManager.getDistribution()
|
const data = await DistroAPI.getDistribution()
|
||||||
syncModConfigurations(data)
|
syncModConfigurations(data)
|
||||||
|
ensureJavaSettings(data)
|
||||||
if(document.readyState === 'interactive' || document.readyState === 'complete'){
|
if(document.readyState === 'interactive' || document.readyState === 'complete'){
|
||||||
showMainUI(data)
|
await showMainUI(data)
|
||||||
} else {
|
} else {
|
||||||
rscShouldLoad = true
|
rscShouldLoad = true
|
||||||
}
|
}
|
||||||
@ -415,3 +455,12 @@ ipcRenderer.on('distributionIndexDone', (event, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Util for development
|
||||||
|
async function devModeToggle() {
|
||||||
|
DistroAPI.toggleDevMode(true)
|
||||||
|
const data = await DistroAPI.refreshDistributionOrFallback()
|
||||||
|
ensureJavaSettings(data)
|
||||||
|
updateSelectedServer(data.servers[0])
|
||||||
|
syncModConfigurations(data)
|
||||||
|
}
|
||||||
|
@ -5,14 +5,15 @@
|
|||||||
* modules, excluding dependencies.
|
* modules, excluding dependencies.
|
||||||
*/
|
*/
|
||||||
// Requirements
|
// Requirements
|
||||||
const $ = require('jquery')
|
const $ = require('jquery')
|
||||||
const {ipcRenderer, remote, shell, webFrame} = require('electron')
|
const {ipcRenderer, shell, webFrame} = require('electron')
|
||||||
const isDev = require('./assets/js/isdev')
|
const remote = require('@electron/remote')
|
||||||
const LoggerUtil = require('./assets/js/loggerutil')
|
const isDev = require('./assets/js/isdev')
|
||||||
|
const { LoggerUtil } = require('helios-core')
|
||||||
|
const Lang = require('./assets/js/langloader')
|
||||||
|
|
||||||
const loggerUICore = LoggerUtil('%c[UICore]', 'color: #000668; font-weight: bold')
|
const loggerUICore = LoggerUtil.getLogger('UICore')
|
||||||
const loggerAutoUpdater = LoggerUtil('%c[AutoUpdater]', 'color: #000668; font-weight: bold')
|
const loggerAutoUpdater = LoggerUtil.getLogger('AutoUpdater')
|
||||||
const loggerAutoUpdaterSuccess = LoggerUtil('%c[AutoUpdater]', 'color: #209b07; font-weight: bold')
|
|
||||||
|
|
||||||
// Log deprecation and process warnings.
|
// Log deprecation and process warnings.
|
||||||
process.traceProcessWarnings = true
|
process.traceProcessWarnings = true
|
||||||
@ -34,7 +35,6 @@ remote.getCurrentWebContents().on('devtools-opened', () => {
|
|||||||
// Disable zoom, needed for darwin.
|
// Disable zoom, needed for darwin.
|
||||||
webFrame.setZoomLevel(0)
|
webFrame.setZoomLevel(0)
|
||||||
webFrame.setVisualZoomLevelLimits(1, 1)
|
webFrame.setVisualZoomLevelLimits(1, 1)
|
||||||
webFrame.setLayoutZoomLevelLimits(0, 0)
|
|
||||||
|
|
||||||
// Initialize auto updates in production environments.
|
// Initialize auto updates in production environments.
|
||||||
let updateCheckListener
|
let updateCheckListener
|
||||||
@ -42,22 +42,22 @@ if(!isDev){
|
|||||||
ipcRenderer.on('autoUpdateNotification', (event, arg, info) => {
|
ipcRenderer.on('autoUpdateNotification', (event, arg, info) => {
|
||||||
switch(arg){
|
switch(arg){
|
||||||
case 'checking-for-update':
|
case 'checking-for-update':
|
||||||
loggerAutoUpdater.log('Checking for update..')
|
loggerAutoUpdater.info('Checking for update..')
|
||||||
settingsUpdateButtonStatus('Checking for Updates..', true)
|
settingsUpdateButtonStatus(Lang.queryJS('uicore.autoUpdate.checkingForUpdateButton'), true)
|
||||||
break
|
break
|
||||||
case 'update-available':
|
case 'update-available':
|
||||||
loggerAutoUpdaterSuccess.log('New update available', info.version)
|
loggerAutoUpdater.info('New update available', info.version)
|
||||||
|
|
||||||
if(process.platform === 'darwin'){
|
if(process.platform === 'darwin'){
|
||||||
info.darwindownload = `https://github.com/WesterosCraftCode/ElectronLauncher/releases/download/v${info.version}/westeroscraftlauncher-${info.version}.dmg`
|
info.darwindownload = `https://github.com/dscalzi/HeliosLauncher/releases/download/v${info.version}/Helios-Launcher-setup-${info.version}${process.arch === 'arm64' ? '-arm64' : '-x64'}.dmg`
|
||||||
showUpdateUI(info)
|
showUpdateUI(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
populateSettingsUpdateInformation(info)
|
populateSettingsUpdateInformation(info)
|
||||||
break
|
break
|
||||||
case 'update-downloaded':
|
case 'update-downloaded':
|
||||||
loggerAutoUpdaterSuccess.log('Update ' + info.version + ' ready to be installed.')
|
loggerAutoUpdater.info('Update ' + info.version + ' ready to be installed.')
|
||||||
settingsUpdateButtonStatus('Install Now', false, () => {
|
settingsUpdateButtonStatus(Lang.queryJS('uicore.autoUpdate.installNowButton'), false, () => {
|
||||||
if(!isDev){
|
if(!isDev){
|
||||||
ipcRenderer.send('autoUpdateAction', 'installUpdateNow')
|
ipcRenderer.send('autoUpdateAction', 'installUpdateNow')
|
||||||
}
|
}
|
||||||
@ -65,8 +65,8 @@ if(!isDev){
|
|||||||
showUpdateUI(info)
|
showUpdateUI(info)
|
||||||
break
|
break
|
||||||
case 'update-not-available':
|
case 'update-not-available':
|
||||||
loggerAutoUpdater.log('No new update found.')
|
loggerAutoUpdater.info('No new update found.')
|
||||||
settingsUpdateButtonStatus('Check for Updates')
|
settingsUpdateButtonStatus(Lang.queryJS('uicore.autoUpdate.checkForUpdatesButton'))
|
||||||
break
|
break
|
||||||
case 'ready':
|
case 'ready':
|
||||||
updateCheckListener = setInterval(() => {
|
updateCheckListener = setInterval(() => {
|
||||||
@ -77,9 +77,9 @@ if(!isDev){
|
|||||||
case 'realerror':
|
case 'realerror':
|
||||||
if(info != null && info.code != null){
|
if(info != null && info.code != null){
|
||||||
if(info.code === 'ERR_UPDATER_INVALID_RELEASE_FEED'){
|
if(info.code === 'ERR_UPDATER_INVALID_RELEASE_FEED'){
|
||||||
loggerAutoUpdater.log('No suitable releases found.')
|
loggerAutoUpdater.info('No suitable releases found.')
|
||||||
} else if(info.code === 'ERR_XML_MISSED_ELEMENT'){
|
} else if(info.code === 'ERR_XML_MISSED_ELEMENT'){
|
||||||
loggerAutoUpdater.log('No releases found.')
|
loggerAutoUpdater.info('No releases found.')
|
||||||
} else {
|
} else {
|
||||||
loggerAutoUpdater.error('Error during update check..', info)
|
loggerAutoUpdater.error('Error during update check..', info)
|
||||||
loggerAutoUpdater.debug('Error Code:', info.code)
|
loggerAutoUpdater.debug('Error Code:', info.code)
|
||||||
@ -87,7 +87,7 @@ if(!isDev){
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
loggerAutoUpdater.log('Unknown argument', arg)
|
loggerAutoUpdater.info('Unknown argument', arg)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -130,12 +130,12 @@ function showUpdateUI(info){
|
|||||||
|
|
||||||
/* jQuery Example
|
/* jQuery Example
|
||||||
$(function(){
|
$(function(){
|
||||||
loggerUICore.log('UICore Initialized');
|
loggerUICore.info('UICore Initialized');
|
||||||
})*/
|
})*/
|
||||||
|
|
||||||
document.addEventListener('readystatechange', function () {
|
document.addEventListener('readystatechange', function () {
|
||||||
if (document.readyState === 'interactive'){
|
if (document.readyState === 'interactive'){
|
||||||
loggerUICore.log('UICore Initializing..')
|
loggerUICore.info('UICore Initializing..')
|
||||||
|
|
||||||
// Bind close button.
|
// Bind close button.
|
||||||
Array.from(document.getElementsByClassName('fCb')).map((val) => {
|
Array.from(document.getElementsByClassName('fCb')).map((val) => {
|
||||||
|
@ -2,5 +2,8 @@
|
|||||||
* Script for welcome.ejs
|
* Script for welcome.ejs
|
||||||
*/
|
*/
|
||||||
document.getElementById('welcomeButton').addEventListener('click', e => {
|
document.getElementById('welcomeButton').addEventListener('click', e => {
|
||||||
switchView(VIEWS.welcome, VIEWS.login)
|
loginOptionsCancelEnabled(false) // False by default, be explicit.
|
||||||
|
loginOptionsViewOnLoginSuccess = VIEWS.landing
|
||||||
|
loginOptionsViewOnLoginCancel = VIEWS.loginOptions
|
||||||
|
switchView(VIEWS.welcome, VIEWS.loginOptions)
|
||||||
})
|
})
|
20
app/assets/lang/_custom.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Custom Language File for Launcher Customizer
|
||||||
|
|
||||||
|
[ejs.app]
|
||||||
|
title = "Helios Launcher"
|
||||||
|
|
||||||
|
[ejs.landing]
|
||||||
|
mediaGitHubURL = "https://github.com/dscalzi/HeliosLauncher"
|
||||||
|
mediaTwitterURL = "#"
|
||||||
|
mediaInstagramURL = "#"
|
||||||
|
mediaYouTubeURL = "#"
|
||||||
|
mediaDiscordURL = "https://discord.gg/zNWUXdt"
|
||||||
|
|
||||||
|
[ejs.settings]
|
||||||
|
sourceGithubLink = "https://github.com/dscalZi/HeliosLauncher"
|
||||||
|
supportLink = "https://github.com/dscalZi/HeliosLauncher/issues"
|
||||||
|
|
||||||
|
[ejs.welcome]
|
||||||
|
welcomeHeader = "WELCOME TO WESTEROSCRAFT"
|
||||||
|
welcomeDescription = "Our mission is to recreate the universe imagined by author George RR Martin in his fantasy series, A Song of Ice and Fire. Through the collaborative effort of thousands of community members, we have sought to create Westeros as accurately and precisely as possible within Minecraft. The world we are creating is yours to explore. Journey from Dorne to Castle Black, and if you aren’t afraid, beyond the Wall itself, but best not delay. As the words of House Stark ominously warn: Winter is Coming."
|
||||||
|
welcomeDescCTA = "You are just a few clicks away from Westeros."
|
297
app/assets/lang/en_US.toml
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
[ejs.landing]
|
||||||
|
updateAvailableTooltip = "Update Available"
|
||||||
|
usernamePlaceholder = "Username"
|
||||||
|
usernameEditButton = "Edit"
|
||||||
|
settingsTooltip = "Settings"
|
||||||
|
serverStatus = "SERVER"
|
||||||
|
serverStatusPlaceholder = "OFFLINE"
|
||||||
|
mojangStatus = "MOJANG STATUS"
|
||||||
|
mojangStatusTooltipTitle = "Services"
|
||||||
|
mojangStatusNETitle = "Non Essential"
|
||||||
|
newsButton = "NEWS"
|
||||||
|
launchButton = "PLAY"
|
||||||
|
launchButtonPlaceholder = "• No Server Selected"
|
||||||
|
launchDetails = "Please wait.."
|
||||||
|
newsNavigationStatus = "{currentPage} of {totalPages}"
|
||||||
|
newsErrorLoadSpan = "Checking for News.."
|
||||||
|
newsErrorFailedSpan = "Failed to Load News"
|
||||||
|
newsErrorRetryButton = "Try Again"
|
||||||
|
newsErrorNoneSpan = "No News"
|
||||||
|
|
||||||
|
[ejs.login]
|
||||||
|
loginCancelText = "Cancel"
|
||||||
|
loginSubheader = "MINECRAFT LOGIN"
|
||||||
|
loginEmailError = "* Invalid Value"
|
||||||
|
loginEmailPlaceholder = "EMAIL OR USERNAME"
|
||||||
|
loginPasswordError = "* Required"
|
||||||
|
loginPasswordPlaceholder = "PASSWORD"
|
||||||
|
loginForgotPasswordLink = "https://minecraft.net/password/forgot/"
|
||||||
|
loginForgotPasswordText = "forgot password?"
|
||||||
|
loginRememberMeText = "remember me?"
|
||||||
|
loginButtonText = "LOGIN"
|
||||||
|
loginNeedAccountLink = "https://minecraft.net/store/minecraft-java-edition/"
|
||||||
|
loginNeedAccountText = "Need an Account?"
|
||||||
|
loginPasswordDisclaimer1 = "Your password is sent directly to mojang and never stored."
|
||||||
|
loginPasswordDisclaimer2 = "{appName} is not affiliated with Mojang AB."
|
||||||
|
|
||||||
|
[ejs.loginOptions]
|
||||||
|
loginOptionsTitle = "Login Options"
|
||||||
|
loginWithMicrosoft = "Login with Microsoft"
|
||||||
|
loginWithMojang = "Login with Mojang"
|
||||||
|
cancelButton = "Cancel"
|
||||||
|
|
||||||
|
[ejs.overlay]
|
||||||
|
serverSelectHeader = "Available Servers"
|
||||||
|
serverSelectConfirm = "Select"
|
||||||
|
serverSelectCancel = "Cancel"
|
||||||
|
accountSelectHeader = "Select an Account"
|
||||||
|
accountSelectConfirm = "Select"
|
||||||
|
accountSelectCancel = "Cancel"
|
||||||
|
|
||||||
|
[ejs.settings]
|
||||||
|
navHeaderText = "Settings"
|
||||||
|
navAccount = "Account"
|
||||||
|
navMinecraft = "Minecraft"
|
||||||
|
navMods = "Mods"
|
||||||
|
navJava = "Java"
|
||||||
|
navLauncher = "Launcher"
|
||||||
|
navAbout = "About"
|
||||||
|
navUpdates = "Updates"
|
||||||
|
navDone = "Done"
|
||||||
|
tabAccountHeaderText = "Account Settings"
|
||||||
|
tabAccountHeaderDesc = "Add new accounts or manage existing ones."
|
||||||
|
microsoftAccount = "Microsoft"
|
||||||
|
addMicrosoftAccount = "+ Add Microsoft Account"
|
||||||
|
mojangAccount = "Mojang"
|
||||||
|
addMojangAccount = "+ Add Mojang Account"
|
||||||
|
minecraftTabHeaderText = "Minecraft Settings"
|
||||||
|
minecraftTabHeaderDesc = "Options related to game launch."
|
||||||
|
gameResolutionTitle = "Game Resolution"
|
||||||
|
launchFullscreenTitle = "Launch in fullscreen."
|
||||||
|
autoConnectTitle = "Automatically connect to the server on launch."
|
||||||
|
launchDetachedTitle = "Launch game process detached from launcher."
|
||||||
|
launchDetachedDesc = "If the game is not detached, closing the launcher will also close the game."
|
||||||
|
tabModsHeaderText = "Mod Settings"
|
||||||
|
tabModsHeaderDesc = "Enable or disable mods."
|
||||||
|
switchServerButton = "Switch"
|
||||||
|
requiredMods = "Required Mods"
|
||||||
|
optionalMods = "Optional Mods"
|
||||||
|
dropinMods = "Drop-in Mods"
|
||||||
|
addMods = "Add Mods"
|
||||||
|
dropinRefreshNote = "(F5 to Refresh)"
|
||||||
|
shaderpacks = "Shaderpacks"
|
||||||
|
shaderpackDesc = "Enable or disable shaders. Please note, shaders will only run smoothly on powerful setups. You may add custom packs here."
|
||||||
|
selectShaderpack = "Select Shaderpack"
|
||||||
|
tabJavaHeaderText = "Java Settings"
|
||||||
|
tabJavaHeaderDesc = "Manage the Java configuration (advanced)."
|
||||||
|
memoryTitle = "Memory"
|
||||||
|
maxRAM = "Maximum RAM"
|
||||||
|
minRAM = "Minimum RAM"
|
||||||
|
memoryDesc = "The recommended minimum RAM is 3 gigabytes. Setting the minimum and maximum values to the same value may reduce lag."
|
||||||
|
memoryTotalTitle = "Total"
|
||||||
|
memoryAvailableTitle = "Available"
|
||||||
|
javaExecutableTitle = "Java Executable"
|
||||||
|
javaExecSelDialogTitle = "Select Java Executable"
|
||||||
|
javaExecSelButtonText = "Choose File"
|
||||||
|
javaExecDesc = "The Java executable is validated before game launch."
|
||||||
|
javaPathDesc = "The path should end with <strong>{pathSuffix}</strong>."
|
||||||
|
jvmOptsTitle = "Additional JVM Options"
|
||||||
|
jvmOptsDesc = "Options to be provided to the JVM at runtime. <em>-Xms</em> and <em>-Xmx</em> should not be included."
|
||||||
|
launcherTabHeaderText = "Launcher Settings"
|
||||||
|
launcherTabHeaderDesc = "Options related to the launcher itself."
|
||||||
|
allowPrereleaseTitle = "Allow Pre-Release Updates."
|
||||||
|
allowPrereleaseDesc = "Pre-Releases include new features which may have not been fully tested or integrated.<br>This will always be true if you are using a pre-release version."
|
||||||
|
dataDirectoryTitle = "Data Directory"
|
||||||
|
selectDataDirectory = "Select Data Directory"
|
||||||
|
chooseFolder = "Choose Folder"
|
||||||
|
dataDirectoryDesc = "All game files and local Java installations will be stored in the data directory.<br>Screenshots and world saves are stored in the instance folder for the corresponding server configuration."
|
||||||
|
aboutTabHeaderText = "About"
|
||||||
|
aboutTabHeaderDesc = "View information and release notes for the current version."
|
||||||
|
aboutTitle = "{appName}"
|
||||||
|
stableRelease = "Stable Release"
|
||||||
|
versionText = "Version "
|
||||||
|
sourceGithub = "Source (GitHub)"
|
||||||
|
support = "Support"
|
||||||
|
devToolsConsole = "DevTools Console"
|
||||||
|
releaseNotes = "Release Notes"
|
||||||
|
changelog = "Changelog"
|
||||||
|
noReleaseNotes = "No Release Notes"
|
||||||
|
viewReleaseNotes = "View Release Notes on GitHub"
|
||||||
|
launcherUpdatesHeaderText = "Launcher Updates"
|
||||||
|
launcherUpdatesHeaderDesc = "Download, install, and review updates for the launcher."
|
||||||
|
checkForUpdates = "Check for Updates"
|
||||||
|
whatsNew = "What's New"
|
||||||
|
updateReleaseNotes = "Update Release Notes"
|
||||||
|
|
||||||
|
[ejs.waiting]
|
||||||
|
waitingText = "Waiting for Microsoft.."
|
||||||
|
|
||||||
|
[ejs.welcome]
|
||||||
|
continueButton = "CONTINUE"
|
||||||
|
|
||||||
|
|
||||||
|
[js.discord]
|
||||||
|
waiting = "Waiting for Client.."
|
||||||
|
state = "Server: {shortId}"
|
||||||
|
|
||||||
|
[js.index]
|
||||||
|
microsoftLoginTitle = "Microsoft Login"
|
||||||
|
microsoftLogoutTitle = "Microsoft Logout"
|
||||||
|
|
||||||
|
[js.login]
|
||||||
|
login = "LOGIN"
|
||||||
|
loggingIn = "LOGGING IN"
|
||||||
|
success = "SUCCESS"
|
||||||
|
tryAgain = "Try Again"
|
||||||
|
|
||||||
|
[js.login.error]
|
||||||
|
invalidValue = "* Invalid Value"
|
||||||
|
requiredValue = "* Required"
|
||||||
|
|
||||||
|
[js.login.error.unknown]
|
||||||
|
title = "Unknown Error During Login"
|
||||||
|
desc = "An unknown error has occurred. Please see the console for details."
|
||||||
|
|
||||||
|
[js.landing.launch]
|
||||||
|
pleaseWait = "Please wait.."
|
||||||
|
failureTitle = "Error During Launch"
|
||||||
|
failureText = "See console (CTRL + Shift + i) for more details."
|
||||||
|
okay = "Okay"
|
||||||
|
|
||||||
|
[js.landing.selectedAccount]
|
||||||
|
noAccountSelected = "No Account Selected"
|
||||||
|
|
||||||
|
[js.landing.selectedServer]
|
||||||
|
noSelection = "No Server Selected"
|
||||||
|
loading = "Loading.."
|
||||||
|
|
||||||
|
[js.landing.serverStatus]
|
||||||
|
server = "SERVER"
|
||||||
|
offline = "OFFLINE"
|
||||||
|
players = "PLAYERS"
|
||||||
|
|
||||||
|
[js.landing.systemScan]
|
||||||
|
checking = "Checking system info.."
|
||||||
|
noCompatibleJava = "No Compatible<br>Java Installation Found"
|
||||||
|
installJavaMessage = "In order to launch Minecraft, you need a 64-bit installation of Java {major}. Would you like us to install a copy?"
|
||||||
|
installJava = "Install Java"
|
||||||
|
installJavaManually = "Install Manually"
|
||||||
|
javaDownloadPrepare = "Preparing Java Download.."
|
||||||
|
javaDownloadFailureTitle = "Error During Java Download"
|
||||||
|
javaDownloadFailureText = "See console (CTRL + Shift + i) for more details."
|
||||||
|
javaRequired = "Java is Required<br>to Launch"
|
||||||
|
javaRequiredMessage = 'A valid x64 installation of Java {major} is required to launch.<br><br>Please refer to our <a href="https://github.com/dscalzi/HeliosLauncher/wiki/Java-Management#manually-installing-a-valid-version-of-java">Java Management Guide</a> for instructions on how to manually install Java.'
|
||||||
|
javaRequiredDismiss = "I Understand"
|
||||||
|
javaRequiredCancel = "Go Back"
|
||||||
|
|
||||||
|
[js.landing.downloadJava]
|
||||||
|
findJdkFailure = "Failed to find OpenJDK distribution."
|
||||||
|
javaDownloadCorruptedError = "Downloaded JDK has a bad hash, the file may be corrupted."
|
||||||
|
extractingJava = "Extracting Java"
|
||||||
|
javaInstalled = "Java Installed!"
|
||||||
|
|
||||||
|
[js.landing.dlAsync]
|
||||||
|
loadingServerInfo = "Loading server information.."
|
||||||
|
fatalError = "Fatal Error"
|
||||||
|
unableToLoadDistributionIndex = "Could not load a copy of the distribution index. See the console (CTRL + Shift + i) for more details."
|
||||||
|
pleaseWait = "Please wait.."
|
||||||
|
errorDuringLaunchTitle = "Error During Launch"
|
||||||
|
seeConsoleForDetails = "See console (CTRL + Shift + i) for more details."
|
||||||
|
validatingFileIntegrity = "Validating file integrity.."
|
||||||
|
errorDuringFileVerificationTitle = "Error During File Verification"
|
||||||
|
downloadingFiles = "Downloading files.."
|
||||||
|
errorDuringFileDownloadTitle = "Error During File Download"
|
||||||
|
preparingToLaunch = "Preparing to launch.."
|
||||||
|
launchingGame = "Launching game.."
|
||||||
|
launchWrapperNotDownloaded = "The main file, LaunchWrapper, failed to download properly. As a result, the game cannot launch.<br><br>To fix this issue, temporarily turn off your antivirus software and launch the game again.<br><br>If you have time, please <a href=\"https://github.com/dscalzi/HeliosLauncher/issues\">submit an issue</a> and let us know what antivirus software you use. We'll contact them and try to straighten things out."
|
||||||
|
doneEnjoyServer = "Done. Enjoy the server!"
|
||||||
|
checkConsoleForDetails = "Please check the console (CTRL + Shift + i) for more details."
|
||||||
|
|
||||||
|
[js.landing.news]
|
||||||
|
checking = "Checking for News"
|
||||||
|
|
||||||
|
[js.landing.discord]
|
||||||
|
loading = "Loading game.."
|
||||||
|
joining = "Sailing to Westeros!"
|
||||||
|
joined = "Exploring the Realm!"
|
||||||
|
|
||||||
|
[js.overlay]
|
||||||
|
dismiss = "Dismiss"
|
||||||
|
|
||||||
|
[js.settings.fileSelectors]
|
||||||
|
executables = "Executables"
|
||||||
|
allFiles = "All Files"
|
||||||
|
|
||||||
|
[js.settings.mstfLogin]
|
||||||
|
errorTitle = "Something Went Wrong"
|
||||||
|
errorMessage = "Microsoft authentication failed. Please try again."
|
||||||
|
okButton = "OK"
|
||||||
|
|
||||||
|
[js.settings.mstfLogout]
|
||||||
|
errorTitle = "Something Went Wrong"
|
||||||
|
errorMessage = "Microsoft logout failed. Please try again."
|
||||||
|
okButton = "OK"
|
||||||
|
|
||||||
|
[js.settings.authAccountSelect]
|
||||||
|
selectButton = "Select Account"
|
||||||
|
selectedButton = "Selected Account ✔"
|
||||||
|
|
||||||
|
[js.settings.authAccountLogout]
|
||||||
|
lastAccountWarningTitle = "Warning<br>This is Your Last Account"
|
||||||
|
lastAccountWarningMessage = "In order to use the launcher you must be logged into at least one account. You will need to login again after.<br><br>Are you sure you want to log out?"
|
||||||
|
confirmButton = "I'm Sure"
|
||||||
|
cancelButton = "Cancel"
|
||||||
|
|
||||||
|
[js.settings.authAccountPopulate]
|
||||||
|
username = "Username"
|
||||||
|
uuid = "UUID"
|
||||||
|
selectAccount = "Select Account"
|
||||||
|
selectedAccount = "Selected Account ✓"
|
||||||
|
logout = "Log Out"
|
||||||
|
|
||||||
|
[js.settings.dropinMods]
|
||||||
|
removeButton = "Remove"
|
||||||
|
deleteFailedTitle = "Failed to Delete<br>Drop-in Mod {fullName}"
|
||||||
|
deleteFailedMessage = "Make sure the file is not in use and try again."
|
||||||
|
failedToggleTitle = "Failed to Toggle<br>One or More Drop-in Mods"
|
||||||
|
okButton = "Okay"
|
||||||
|
|
||||||
|
[js.settings.serverListing]
|
||||||
|
mainServer = "Main Server"
|
||||||
|
|
||||||
|
[js.settings.java]
|
||||||
|
selectedJava = "Selected: Java {version} ({vendor})"
|
||||||
|
invalidSelection = "Invalid Selection"
|
||||||
|
requiresJava = "Requires Java {major} x64."
|
||||||
|
availableOptions = "Available Options for Java {major} (HotSpot VM)"
|
||||||
|
|
||||||
|
[js.settings.about]
|
||||||
|
preReleaseTitle = "Pre-release"
|
||||||
|
stableReleaseTitle = "Stable Release"
|
||||||
|
releaseNotesFailed = "Failed to load release notes."
|
||||||
|
|
||||||
|
[js.settings.updates]
|
||||||
|
newReleaseTitle = "New Release Available"
|
||||||
|
newPreReleaseTitle = "New Pre-release Available"
|
||||||
|
downloadingButton = "Downloading.."
|
||||||
|
downloadButton = 'Download from GitHub<span style="font-size: 10px;color: gray;text-shadow: none !important;">Close the launcher and run the dmg to update.</span>'
|
||||||
|
latestVersionTitle = "You Are Running the Latest Version"
|
||||||
|
checkForUpdatesButton = "Check for Updates"
|
||||||
|
checkingForUpdatesButton = "Checking for Updates.."
|
||||||
|
|
||||||
|
[js.uibinder.startup]
|
||||||
|
fatalErrorTitle = "Fatal Error: Unable to Load Distribution Index"
|
||||||
|
fatalErrorMessage = "A connection could not be established to our servers to download the distribution index. No local copies were available to load. <br><br>The distribution index is an essential file which provides the latest server information. The launcher is unable to start without it. Ensure you are connected to the internet and relaunch the application."
|
||||||
|
closeButton = "Close"
|
||||||
|
|
||||||
|
[js.uibinder.validateAccount]
|
||||||
|
failedMessageTitle = "Failed to Refresh Login"
|
||||||
|
failedMessage = "We were unable to refresh the login for <strong>{account}</strong>. Please select another account or login again."
|
||||||
|
failedMessageSelectAnotherAccount = "We were unable to refresh the login for <strong>{account}</strong>. Please login again."
|
||||||
|
loginButton = "Login"
|
||||||
|
selectAnotherAccountButton = "Select Another Account"
|
||||||
|
|
||||||
|
[js.uicore.autoUpdate]
|
||||||
|
checkingForUpdateButton = "Checking for Updates..."
|
||||||
|
installNowButton = "Install Now"
|
||||||
|
checkForUpdatesButton = "Check for Updates"
|
@ -12,8 +12,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<% } else{ %>
|
<% } else{ %>
|
||||||
<div id="frameContentWin">
|
<div id="frameContentWin">
|
||||||
<div id="frameImageDock">
|
<div id="frameTitleDock">
|
||||||
<img id= "frameImage" src="./assets/images/WCTextCrop.png" />
|
<span id="frameTitleText"><%= lang('app.title') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="frameButtonDockWin">
|
<div id="frameButtonDockWin">
|
||||||
<button class="frameButton fMb" id="frameButton_minimize" tabIndex="-1">
|
<button class="frameButton fMb" id="frameButton_minimize" tabIndex="-1">
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
<div id="upper">
|
<div id="upper">
|
||||||
<div id="left">
|
<div id="left">
|
||||||
<div id="image_seal_container">
|
<div id="image_seal_container">
|
||||||
<img id="image_seal" src="assets/images/WesterosSealCircle.png"/>
|
<img id="image_seal" src="assets/images/SealCircle.png"/>
|
||||||
<div id="updateAvailableTooltip">Update Available</div>
|
<div id="updateAvailableTooltip"><%- lang('landing.updateAvailableTooltip') %></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
@ -11,9 +11,9 @@
|
|||||||
<div id="right">
|
<div id="right">
|
||||||
<div id="rightContainer">
|
<div id="rightContainer">
|
||||||
<div id="user_content">
|
<div id="user_content">
|
||||||
<span id="user_text">Username</span>
|
<span id="user_text"><%- lang('landing.usernamePlaceholder') %></span>
|
||||||
<div id="avatarContainer">
|
<div id="avatarContainer">
|
||||||
<button id="avatarOverlay">Edit</button>
|
<button id="avatarOverlay"><%- lang('landing.usernameEditButton') %></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="mediaContent">
|
<div id="mediaContent">
|
||||||
@ -23,14 +23,14 @@
|
|||||||
<svg id="settingsSVG" class="mediaSVG" viewBox="0 0 141.36 137.43">
|
<svg id="settingsSVG" class="mediaSVG" viewBox="0 0 141.36 137.43">
|
||||||
<path d="M70.70475616319865,83.36934004916053 a15.320781354859122,15.320781354859122 0 1 1 14.454501310561755,-15.296030496450625 A14.850515045097694,14.850515045097694 0 0 1 70.70475616319865,83.36934004916053 M123.25082856443602,55.425620905968366 h-12.375429204248078 A45.54157947163293,45.54157947163293 0 0 0 107.21227231573047,46.243052436416285 l8.613298726156664,-9.108315894326587 a9.727087354538993,9.727087354538993 0 0 0 0,-13.167456673319956 l-3.465120177189462,-3.6631270444574313 a8.489544434114185,8.489544434114185 0 0 0 -12.375429204248078,0 l-8.613298726156664,9.108315894326587 A40.442902639482725,40.442902639482725 0 0 0 81.99114759747292,25.427580514871032 V12.532383284044531 a9.108315894326587,9.108315894326587 0 0 0 -8.811305593424633,-9.306322761594556 h-4.950171681699231 a9.108315894326587,9.108315894326587 0 0 0 -8.811305593424633,9.306322761594556 v12.895197230826497 a40.17064319698927,40.17064319698927 0 0 0 -9.331073620003052,4.0591407789933704 l-8.613298726156664,-9.108315894326587 a8.489544434114185,8.489544434114185 0 0 0 -12.375429204248078,0 L25.58394128451018,23.967279868769744 a9.727087354538993,9.727087354538993 0 0 0 0,13.167456673319956 L34.19724001066683,46.243052436416285 a45.07131316187151,45.07131316187151 0 0 0 -3.6631270444574313,9.083565035918088 h-12.375429204248078 a9.083565035918088,9.083565035918088 0 0 0 -8.811305593424633,9.306322761594556 v5.197680265784193 a9.108315894326587,9.108315894326587 0 0 0 8.811305593424633,9.306322761594556 h11.979415469712139 a45.69008462208391,45.69008462208391 0 0 0 4.0591407789933704,10.642869115653347 l-8.613298726156664,9.108315894326587 a9.727087354538993,9.727087354538993 0 0 0 0,13.167456673319956 l3.465120177189462,3.6631270444574313 a8.489544434114185,8.489544434114185 0 0 0 12.375429204248078,0 l8.613298726156664,-9.108315894326587 a40.49240435629971,40.49240435629971 0 0 0 9.331073620003052,4.0591407789933704 v12.895197230826497 a9.083565035918088,9.083565035918088 0 0 0 8.811305593424633,9.306322761594556 h4.950171681699231 A9.083565035918088,9.083565035918088 0 0 0 81.99114759747292,123.68848839660077 V110.79329116577425 a40.78941465720167,40.78941465720167 0 0 0 9.331073620003052,-4.0591407789933704 l8.613298726156664,9.108315894326587 a8.489544434114185,8.489544434114185 0 0 0 12.375429204248078,0 l3.465120177189462,-3.6631270444574313 a9.727087354538993,9.727087354538993 0 0 0 0,-13.167456673319956 l-8.613298726156664,-9.108315894326587 a45.665333763675406,45.665333763675406 0 0 0 4.034389920584874,-10.642869115653347 h12.004166328120636 a9.108315894326587,9.108315894326587 0 0 0 8.811305593424633,-9.306322761594556 v-5.197680265784193 a9.083565035918088,9.083565035918088 0 0 0 -8.811305593424633,-9.306322761594556 " id="svg_3" class=""/>
|
<path d="M70.70475616319865,83.36934004916053 a15.320781354859122,15.320781354859122 0 1 1 14.454501310561755,-15.296030496450625 A14.850515045097694,14.850515045097694 0 0 1 70.70475616319865,83.36934004916053 M123.25082856443602,55.425620905968366 h-12.375429204248078 A45.54157947163293,45.54157947163293 0 0 0 107.21227231573047,46.243052436416285 l8.613298726156664,-9.108315894326587 a9.727087354538993,9.727087354538993 0 0 0 0,-13.167456673319956 l-3.465120177189462,-3.6631270444574313 a8.489544434114185,8.489544434114185 0 0 0 -12.375429204248078,0 l-8.613298726156664,9.108315894326587 A40.442902639482725,40.442902639482725 0 0 0 81.99114759747292,25.427580514871032 V12.532383284044531 a9.108315894326587,9.108315894326587 0 0 0 -8.811305593424633,-9.306322761594556 h-4.950171681699231 a9.108315894326587,9.108315894326587 0 0 0 -8.811305593424633,9.306322761594556 v12.895197230826497 a40.17064319698927,40.17064319698927 0 0 0 -9.331073620003052,4.0591407789933704 l-8.613298726156664,-9.108315894326587 a8.489544434114185,8.489544434114185 0 0 0 -12.375429204248078,0 L25.58394128451018,23.967279868769744 a9.727087354538993,9.727087354538993 0 0 0 0,13.167456673319956 L34.19724001066683,46.243052436416285 a45.07131316187151,45.07131316187151 0 0 0 -3.6631270444574313,9.083565035918088 h-12.375429204248078 a9.083565035918088,9.083565035918088 0 0 0 -8.811305593424633,9.306322761594556 v5.197680265784193 a9.108315894326587,9.108315894326587 0 0 0 8.811305593424633,9.306322761594556 h11.979415469712139 a45.69008462208391,45.69008462208391 0 0 0 4.0591407789933704,10.642869115653347 l-8.613298726156664,9.108315894326587 a9.727087354538993,9.727087354538993 0 0 0 0,13.167456673319956 l3.465120177189462,3.6631270444574313 a8.489544434114185,8.489544434114185 0 0 0 12.375429204248078,0 l8.613298726156664,-9.108315894326587 a40.49240435629971,40.49240435629971 0 0 0 9.331073620003052,4.0591407789933704 v12.895197230826497 a9.083565035918088,9.083565035918088 0 0 0 8.811305593424633,9.306322761594556 h4.950171681699231 A9.083565035918088,9.083565035918088 0 0 0 81.99114759747292,123.68848839660077 V110.79329116577425 a40.78941465720167,40.78941465720167 0 0 0 9.331073620003052,-4.0591407789933704 l8.613298726156664,9.108315894326587 a8.489544434114185,8.489544434114185 0 0 0 12.375429204248078,0 l3.465120177189462,-3.6631270444574313 a9.727087354538993,9.727087354538993 0 0 0 0,-13.167456673319956 l-8.613298726156664,-9.108315894326587 a45.665333763675406,45.665333763675406 0 0 0 4.034389920584874,-10.642869115653347 h12.004166328120636 a9.108315894326587,9.108315894326587 0 0 0 8.811305593424633,-9.306322761594556 v-5.197680265784193 a9.083565035918088,9.083565035918088 0 0 0 -8.811305593424633,-9.306322761594556 " id="svg_3" class=""/>
|
||||||
</svg>
|
</svg>
|
||||||
<div id="settingsTooltip">Settings</div>
|
<div id="settingsTooltip"><%- lang('landing.settingsTooltip') %></div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mediaDivider"></div>
|
<div class="mediaDivider"></div>
|
||||||
<div id="externalMedia">
|
<div id="externalMedia">
|
||||||
<div class="mediaContainer">
|
<div class="mediaContainer">
|
||||||
<a href="http://www.westeroscraft.com" class="mediaURL" id="linkURL">
|
<a href="<%- lang('landing.mediaGitHubURL') %>" class="mediaURL" id="linkURL">
|
||||||
<svg id="linkSVG" class="mediaSVG" viewBox="35.34 34.3575 70.68 68.71500">
|
<svg id="linkSVG" class="mediaSVG" viewBox="35.34 34.3575 70.68 68.71500">
|
||||||
<g>
|
<g>
|
||||||
<path d="M75.37,65.51a3.85,3.85,0,0,0-1.73.42,8.22,8.22,0,0,1,.94,3.76A8.36,8.36,0,0,1,66.23,78H46.37a8.35,8.35,0,1,1,0-16.7h9.18a21.51,21.51,0,0,1,6.65-8.72H46.37a17.07,17.07,0,1,0,0,34.15H66.23A17,17,0,0,0,82.77,65.51Z"/>
|
<path d="M75.37,65.51a3.85,3.85,0,0,0-1.73.42,8.22,8.22,0,0,1,.94,3.76A8.36,8.36,0,0,1,66.23,78H46.37a8.35,8.35,0,1,1,0-16.7h9.18a21.51,21.51,0,0,1,6.65-8.72H46.37a17.07,17.07,0,1,0,0,34.15H66.23A17,17,0,0,0,82.77,65.51Z"/>
|
||||||
@ -40,7 +40,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="mediaContainer">
|
<div class="mediaContainer">
|
||||||
<a href="https://twitter.com/westeroscraft" class="mediaURL" id="twitterURL">
|
<a href="<%- lang('landing.mediaTwitterURL') %>" class="mediaURL" id="twitterURL">
|
||||||
<svg id="twitterSVG" class="mediaSVG" viewBox="0 0 5000 4060" preserveAspectRatio="xMidYMid meet">
|
<svg id="twitterSVG" class="mediaSVG" viewBox="0 0 5000 4060" preserveAspectRatio="xMidYMid meet">
|
||||||
<g>
|
<g>
|
||||||
<path d="M1210 4048 c-350 -30 -780 -175 -1124 -378 -56 -33 -86 -57 -86 -68 0 -16 7 -17 83 -9 114 12 349 1 493 -22 295 -49 620 -180 843 -341 l54 -38 -49 -7 c-367 -49 -660 -256 -821 -582 -30 -61 -53 -120 -51 -130 3 -16 12 -17 73 -13 97 7 199 5 270 -4 l60 -9 -65 -22 c-341 -117 -609 -419 -681 -769 -18 -88 -26 -226 -13 -239 4 -3 32 7 63 22 68 35 198 77 266 86 28 4 58 9 68 12 10 2 -22 -34 -72 -82 -240 -232 -353 -532 -321 -852 15 -149 79 -347 133 -418 16 -20 17 -19 49 20 377 455 913 795 1491 945 160 41 346 74 485 86 l82 7 -7 -59 c-5 -33 -7 -117 -6 -189 2 -163 31 -286 103 -430 141 -285 422 -504 708 -550 112 -19 333 -19 442 0 180 30 335 108 477 239 l58 54 95 -24 c143 -36 286 -89 427 -160 70 -35 131 -60 135 -56 19 19 -74 209 -151 312 -50 66 -161 178 -216 217 l-30 22 73 -14 c111 -21 257 -63 353 -101 99 -39 99 -39 99 -19 0 57 -237 326 -412 468 l-88 71 6 51 c4 28 1 130 -5 226 -30 440 -131 806 -333 1202 -380 745 -1036 1277 -1823 1477 -243 62 -430 81 -786 78 -134 0 -291 -5 -349 -10z"/>
|
<path d="M1210 4048 c-350 -30 -780 -175 -1124 -378 -56 -33 -86 -57 -86 -68 0 -16 7 -17 83 -9 114 12 349 1 493 -22 295 -49 620 -180 843 -341 l54 -38 -49 -7 c-367 -49 -660 -256 -821 -582 -30 -61 -53 -120 -51 -130 3 -16 12 -17 73 -13 97 7 199 5 270 -4 l60 -9 -65 -22 c-341 -117 -609 -419 -681 -769 -18 -88 -26 -226 -13 -239 4 -3 32 7 63 22 68 35 198 77 266 86 28 4 58 9 68 12 10 2 -22 -34 -72 -82 -240 -232 -353 -532 -321 -852 15 -149 79 -347 133 -418 16 -20 17 -19 49 20 377 455 913 795 1491 945 160 41 346 74 485 86 l82 7 -7 -59 c-5 -33 -7 -117 -6 -189 2 -163 31 -286 103 -430 141 -285 422 -504 708 -550 112 -19 333 -19 442 0 180 30 335 108 477 239 l58 54 95 -24 c143 -36 286 -89 427 -160 70 -35 131 -60 135 -56 19 19 -74 209 -151 312 -50 66 -161 178 -216 217 l-30 22 73 -14 c111 -21 257 -63 353 -101 99 -39 99 -39 99 -19 0 57 -237 326 -412 468 l-88 71 6 51 c4 28 1 130 -5 226 -30 440 -131 806 -333 1202 -380 745 -1036 1277 -1823 1477 -243 62 -430 81 -786 78 -134 0 -291 -5 -349 -10z"/>
|
||||||
@ -49,7 +49,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="mediaContainer">
|
<div class="mediaContainer">
|
||||||
<a href="https://www.instagram.com/westeroscraft/" class="mediaURL" id="instagramURL">
|
<a href="<%- lang('landing.mediaInstagramURL') %>" class="mediaURL" id="instagramURL">
|
||||||
<svg id="instagramSVG" class="mediaSVG" viewBox="0 0 5040 5040">
|
<svg id="instagramSVG" class="mediaSVG" viewBox="0 0 5040 5040">
|
||||||
<defs>
|
<defs>
|
||||||
<radialGradient id="instaFill" cx="30%" cy="107%" r="150%">
|
<radialGradient id="instaFill" cx="30%" cy="107%" r="150%">
|
||||||
@ -69,7 +69,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="mediaContainer">
|
<div class="mediaContainer">
|
||||||
<a href="https://www.youtube.com/user/WesterosCraft" class="mediaURL" id="youtubeURL">
|
<a href="<%- lang('landing.mediaYouTubeURL') %>" class="mediaURL" id="youtubeURL">
|
||||||
<svg id="youtubeSVG" class="mediaSVG" viewBox="35.34 34.3575 70.68 68.71500">
|
<svg id="youtubeSVG" class="mediaSVG" viewBox="35.34 34.3575 70.68 68.71500">
|
||||||
<g>
|
<g>
|
||||||
<path d="M84.8,69.52,65.88,79.76V59.27Zm23.65.59c0-5.14-.79-17.63-3.94-20.57S99,45.86,73.37,45.86s-28,.73-31.14,3.68S38.29,65,38.29,70.11s.79,17.63,3.94,20.57,5.52,3.68,31.14,3.68,28-.74,31.14-3.68,3.94-15.42,3.94-20.57"/>
|
<path d="M84.8,69.52,65.88,79.76V59.27Zm23.65.59c0-5.14-.79-17.63-3.94-20.57S99,45.86,73.37,45.86s-28,.73-31.14,3.68S38.29,65,38.29,70.11s.79,17.63,3.94,20.57,5.52,3.68,31.14,3.68,28-.74,31.14-3.68,3.94-15.42,3.94-20.57"/>
|
||||||
@ -78,7 +78,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="mediaContainer">
|
<div class="mediaContainer">
|
||||||
<a href="https://discord.gg/hqdjs3m" class="mediaURL" id="discordURL">
|
<a href="<%- lang('landing.mediaDiscordURL') %>" class="mediaURL" id="discordURL">
|
||||||
<svg id="discordSVG" class="mediaSVG" viewBox="35.34 34.3575 70.68 68.71500">
|
<svg id="discordSVG" class="mediaSVG" viewBox="35.34 34.3575 70.68 68.71500">
|
||||||
<g>
|
<g>
|
||||||
<path d="M81.23,78.48a6.14,6.14,0,1,1,6.14-6.14,6.14,6.14,0,0,1-6.14,6.14M60,78.48a6.14,6.14,0,1,1,6.14-6.14A6.14,6.14,0,0,1,60,78.48M104.41,73c-.92-7.7-8.24-22.9-8.24-22.9A43,43,0,0,0,88,45.59a17.88,17.88,0,0,0-8.38-1.27l-.13,1.06a23.52,23.52,0,0,1,5.8,1.95,87.59,87.59,0,0,1,8.17,4.87s-10.32-5.63-22.27-5.63a51.32,51.32,0,0,0-23.2,5.63,87.84,87.84,0,0,1,8.17-4.87,23.57,23.57,0,0,1,5.8-1.95l-.13-1.06a17.88,17.88,0,0,0-8.38,1.27,42.84,42.84,0,0,0-8.21,4.56S37.87,65.35,37,73s-.37,11.54-.37,11.54,4.22,5.68,9.9,7.14,7.7,1.47,7.7,1.47l3.75-4.68a21.22,21.22,0,0,1-4.65-2A24.47,24.47,0,0,1,47.93,82S61.16,88.4,70.68,88.4c10,0,22.75-6.44,22.75-6.44a24.56,24.56,0,0,1-5.35,4.56,21.22,21.22,0,0,1-4.65,2l3.75,4.68s2,0,7.7-1.47,9.89-7.14,9.89-7.14.55-3.85-.37-11.54"/>
|
<path d="M81.23,78.48a6.14,6.14,0,1,1,6.14-6.14,6.14,6.14,0,0,1-6.14,6.14M60,78.48a6.14,6.14,0,1,1,6.14-6.14A6.14,6.14,0,0,1,60,78.48M104.41,73c-.92-7.7-8.24-22.9-8.24-22.9A43,43,0,0,0,88,45.59a17.88,17.88,0,0,0-8.38-1.27l-.13,1.06a23.52,23.52,0,0,1,5.8,1.95,87.59,87.59,0,0,1,8.17,4.87s-10.32-5.63-22.27-5.63a51.32,51.32,0,0,0-23.2,5.63,87.84,87.84,0,0,1,8.17-4.87,23.57,23.57,0,0,1,5.8-1.95l-.13-1.06a17.88,17.88,0,0,0-8.38,1.27,42.84,42.84,0,0,0-8.21,4.56S37.87,65.35,37,73s-.37,11.54-.37,11.54,4.22,5.68,9.9,7.14,7.7,1.47,7.7,1.47l3.75-4.68a21.22,21.22,0,0,1-4.65-2A24.47,24.47,0,0,1,47.93,82S61.16,88.4,70.68,88.4c10,0,22.75-6.44,22.75-6.44a24.56,24.56,0,0,1-5.35,4.56,21.22,21.22,0,0,1-4.65,2l3.75,4.68s2,0,7.7-1.47,9.89-7.14,9.89-7.14.55-3.85-.37-11.54"/>
|
||||||
@ -96,21 +96,21 @@
|
|||||||
<div class="bot_wrapper">
|
<div class="bot_wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div id="server_status_wrapper">
|
<div id="server_status_wrapper">
|
||||||
<span class="bot_label" id="landingPlayerLabel">SERVER</span>
|
<span class="bot_label" id="landingPlayerLabel"><%- lang('landing.serverStatus') %></span>
|
||||||
<span id="player_count">OFFLINE</span>
|
<span id="player_count"><%- lang('landing.serverStatusPlaceholder') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="bot_divider"></div>
|
<div class="bot_divider"></div>
|
||||||
<div id="mojangStatusWrapper">
|
<div id="mojangStatusWrapper">
|
||||||
<span class="bot_label">MOJANG STATUS</span>
|
<span class="bot_label"><%- lang('landing.mojangStatus') %></span>
|
||||||
<span id="mojang_status_icon">•</span>
|
<span id="mojang_status_icon">•</span>
|
||||||
<div id="mojangStatusTooltip">
|
<div id="mojangStatusTooltip">
|
||||||
<div id="mojangStatusTooltipTitle">Services</div>
|
<div id="mojangStatusTooltipTitle"><%- lang('landing.mojangStatusTooltipTitle') %></div>
|
||||||
<div id="mojangStatusEssentialContainer">
|
<div id="mojangStatusEssentialContainer">
|
||||||
<!-- Essential Mojang services are populated here. -->
|
<!-- Essential Mojang services are populated here. -->
|
||||||
</div>
|
</div>
|
||||||
<div id="mojangStatusNEContainer">
|
<div id="mojangStatusNEContainer">
|
||||||
<div class="mojangStatusNEBar"></div>
|
<div class="mojangStatusNEBar"></div>
|
||||||
<div id="mojangStatusNETitle">Non Essential</div>
|
<div id="mojangStatusNETitle"><%- lang('landing.mojangStatusNETitle') %></div>
|
||||||
<div class="mojangStatusNEBar"></div>
|
<div class="mojangStatusNEBar"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="mojangStatusNonEssentialContainer">
|
<div id="mojangStatusNonEssentialContainer">
|
||||||
@ -133,7 +133,7 @@
|
|||||||
</defs>
|
</defs>
|
||||||
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span id="newsButtonText">NEWS</span>
|
<span id="newsButtonText"><%- lang('landing.newsButton') %></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -141,9 +141,9 @@
|
|||||||
<div id="right">
|
<div id="right">
|
||||||
<div class="bot_wrapper">
|
<div class="bot_wrapper">
|
||||||
<div id="launch_content">
|
<div id="launch_content">
|
||||||
<button id="launch_button">PLAY</button>
|
<button id="launch_button"><%- lang('landing.launchButton') %></button>
|
||||||
<div class="bot_divider"></div>
|
<div class="bot_divider"></div>
|
||||||
<button id="server_selection_button" class="bot_label">• No Server Selected</button>
|
<button id="server_selection_button" class="bot_label"><%- lang('landing.launchButtonPlaceholder') %></button>
|
||||||
</div>
|
</div>
|
||||||
<div id="launch_details">
|
<div id="launch_details">
|
||||||
<div id="launch_details_left">
|
<div id="launch_details_left">
|
||||||
@ -152,7 +152,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="launch_details_right">
|
<div id="launch_details_right">
|
||||||
<progress id="launch_progress" value="22" max="100"></progress>
|
<progress id="launch_progress" value="22" max="100"></progress>
|
||||||
<span id="launch_details_text" class="bot_label">Please wait..</span>
|
<span id="launch_details_text" class="bot_label"><%- lang('landing.launchDetails') %></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -184,7 +184,7 @@
|
|||||||
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
<polyline class="arrowLine" points="0.71 13.26 12.56 1.41 24.16 13.02"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<span id="newsNavigationStatus">1 of 1</span>
|
<span id="newsNavigationStatus"><%- lang('landing.newsNavigationStatus', { currentPage: 1, totalPages: 1 }) %></span>
|
||||||
<button id="newsNavigateRight">
|
<button id="newsNavigateRight">
|
||||||
<svg id="newsNavigationRightSVG" viewBox="0 0 24.87 13.97">
|
<svg id="newsNavigationRightSVG" viewBox="0 0 24.87 13.97">
|
||||||
<defs>
|
<defs>
|
||||||
@ -205,14 +205,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="newsErrorContainer">
|
<div id="newsErrorContainer">
|
||||||
<div id="newsErrorLoading">
|
<div id="newsErrorLoading">
|
||||||
<span id="nELoadSpan" class="newsErrorContent">Checking for News..</span>
|
<span id="nELoadSpan" class="newsErrorContent"><%- lang('landing.newsErrorLoadSpan') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="newsErrorFailed" style="display: none;">
|
<div id="newsErrorFailed" style="display: none;">
|
||||||
<span id="nEFailedSpan" class="newsErrorContent">Failed to Load News</span>
|
<span id="nEFailedSpan" class="newsErrorContent"><%- lang('landing.newsErrorFailedSpan') %></span>
|
||||||
<button id="newsErrorRetry">Try Again</button>
|
<button id="newsErrorRetry"><%- lang('landing.newsErrorRetryButton') %></button>
|
||||||
</div>
|
</div>
|
||||||
<div id="newsErrorNone" style="display: none;">
|
<div id="newsErrorNone" style="display: none;">
|
||||||
<span id="nENoneSpan" class="newsErrorContent">No News</span>
|
<span id="nENoneSpan" class="newsErrorContent"><%- lang('landing.newsErrorNoneSpan') %></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,21 +2,21 @@
|
|||||||
<div id="loginCancelContainer" style="display: none;">
|
<div id="loginCancelContainer" style="display: none;">
|
||||||
<button id="loginCancelButton">
|
<button id="loginCancelButton">
|
||||||
<div id="loginCancelIcon">X</div>
|
<div id="loginCancelIcon">X</div>
|
||||||
<span id="loginCancelText">Cancel</span>
|
<span id="loginCancelText"><%- lang('login.loginCancelText') %></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="loginContent">
|
<div id="loginContent">
|
||||||
<form id="loginForm">
|
<form id="loginForm">
|
||||||
<img id="loginImageSeal" src="assets/images/WesterosSealCircle.png"/>
|
<img id="loginImageSeal" src="assets/images/SealCircle.png"/>
|
||||||
<span id="loginSubheader">MINECRAFT LOGIN</span>
|
<span id="loginSubheader"><%- lang('login.loginSubheader') %></span>
|
||||||
<div class="loginFieldContainer">
|
<div class="loginFieldContainer">
|
||||||
<svg id="profileSVG" class="loginSVG" viewBox="40 37 65.36 61.43">
|
<svg id="profileSVG" class="loginSVG" viewBox="40 37 65.36 61.43">
|
||||||
<g>
|
<g>
|
||||||
<path d="M86.77,58.12A13.79,13.79,0,1,0,73,71.91,13.79,13.79,0,0,0,86.77,58.12M97,103.67a3.41,3.41,0,0,0,3.39-3.84,27.57,27.57,0,0,0-54.61,0,3.41,3.41,0,0,0,3.39,3.84Z"/>
|
<path d="M86.77,58.12A13.79,13.79,0,1,0,73,71.91,13.79,13.79,0,0,0,86.77,58.12M97,103.67a3.41,3.41,0,0,0,3.39-3.84,27.57,27.57,0,0,0-54.61,0,3.41,3.41,0,0,0,3.39,3.84Z"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="loginErrorSpan" id="loginEmailError">* Invalid Value</span>
|
<span class="loginErrorSpan" id="loginEmailError"><%- lang('login.loginEmailError') %></span>
|
||||||
<input id="loginUsername" class="loginField" type="text" placeholder="EMAIL OR USERNAME"/>
|
<input id="loginUsername" class="loginField" type="text" placeholder="<%- lang('login.loginEmailPlaceholder') %>"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="loginFieldContainer">
|
<div class="loginFieldContainer">
|
||||||
<svg id="lockSVG" class="loginSVG" viewBox="40 32 60.36 70.43">
|
<svg id="lockSVG" class="loginSVG" viewBox="40 32 60.36 70.43">
|
||||||
@ -24,22 +24,22 @@
|
|||||||
<path d="M86.16,54a16.38,16.38,0,1,0-32,0H44V102.7H96V54Zm-25.9-3.39a9.89,9.89,0,1,1,19.77,0A9.78,9.78,0,0,1,79.39,54H60.89A9.78,9.78,0,0,1,60.26,50.59ZM70,96.2a6.5,6.5,0,0,1-6.5-6.5,6.39,6.39,0,0,1,3.1-5.4V67h6.5V84.11a6.42,6.42,0,0,1,3.39,5.6A6.5,6.5,0,0,1,70,96.2Z"/>
|
<path d="M86.16,54a16.38,16.38,0,1,0-32,0H44V102.7H96V54Zm-25.9-3.39a9.89,9.89,0,1,1,19.77,0A9.78,9.78,0,0,1,79.39,54H60.89A9.78,9.78,0,0,1,60.26,50.59ZM70,96.2a6.5,6.5,0,0,1-6.5-6.5,6.39,6.39,0,0,1,3.1-5.4V67h6.5V84.11a6.42,6.42,0,0,1,3.39,5.6A6.5,6.5,0,0,1,70,96.2Z"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="loginErrorSpan" id="loginPasswordError">* Required</span>
|
<span class="loginErrorSpan" id="loginPasswordError"><%- lang('login.loginPasswordError') %></span>
|
||||||
<input id="loginPassword" class="loginField" type="password" placeholder="PASSWORD"/>
|
<input id="loginPassword" class="loginField" type="password" placeholder="<%- lang('login.loginPasswordPlaceholder') %>"/>
|
||||||
</div>
|
</div>
|
||||||
<div id="loginOptions">
|
<div id="loginOptions">
|
||||||
<span class="loginSpanDim">
|
<span class="loginSpanDim">
|
||||||
<a href="https://help.mojang.com/customer/en/portal/articles/329524-change-or-forgot-password">forgot password?</a>
|
<a href="<%- lang('login.loginForgotPasswordLink') %>"><%- lang('login.loginForgotPasswordText') %></a>
|
||||||
</span>
|
</span>
|
||||||
<label id="checkmarkContainer">
|
<label id="checkmarkContainer">
|
||||||
<input id="loginRememberOption" type="checkbox" checked>
|
<input id="loginRememberOption" type="checkbox" checked>
|
||||||
<span id="loginRememberText" class="loginSpanDim">remember me?</span>
|
<span id="loginRememberText" class="loginSpanDim"><%- lang('login.loginRememberMeText') %></span>
|
||||||
<span class="loginCheckmark"></span>
|
<span class="loginCheckmark"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button id="loginButton" disabled>
|
<button id="loginButton" disabled>
|
||||||
<div id="loginButtonContent">
|
<div id="loginButtonContent">
|
||||||
LOGIN
|
<%- lang('login.loginButtonText') %>
|
||||||
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
<svg id="loginSVG" viewBox="0 0 24.87 13.97">
|
||||||
<defs>
|
<defs>
|
||||||
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
|
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
|
||||||
@ -54,10 +54,10 @@
|
|||||||
</button>
|
</button>
|
||||||
<div id="loginDisclaimer">
|
<div id="loginDisclaimer">
|
||||||
<span class="loginSpanDim" id="loginRegisterSpan">
|
<span class="loginSpanDim" id="loginRegisterSpan">
|
||||||
<a href="https://minecraft.net/en-us/store/minecraft/">Need an Account?</a>
|
<a href="<%- lang('login.loginNeedAccountLink') %>"><%- lang('login.loginNeedAccountText') %></a>
|
||||||
</span>
|
</span>
|
||||||
<p class="loginDisclaimerText">Your password is sent directly to mojang and never stored.</p>
|
<p class="loginDisclaimerText"><%- lang('login.loginPasswordDisclaimer1') %></p>
|
||||||
<p class="loginDisclaimerText">WesterosCraft is not affiliated with Mojang AB.</p>
|
<p class="loginDisclaimerText"><%- lang('login.loginPasswordDisclaimer2', { appName: lang('app.title') }) %></p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
34
app/loginOptions.ejs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<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="loginOptionMojang" class="loginOptionButton">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" 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>
|
||||||
|
<span><%- lang('loginOptions.loginWithMojang') %></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,29 +1,29 @@
|
|||||||
<div id="overlayContainer" style="display: none;">
|
<div id="overlayContainer" style="display: none;">
|
||||||
<div id="serverSelectContent" style="display: none;">
|
<div id="serverSelectContent" style="display: none;">
|
||||||
<span id="serverSelectHeader">Available Servers</span>
|
<span id="serverSelectHeader"><%- lang('overlay.serverSelectHeader') %></span>
|
||||||
<div id="serverSelectList">
|
<div id="serverSelectList">
|
||||||
<div id="serverSelectListScrollable">
|
<div id="serverSelectListScrollable">
|
||||||
<!-- Server listings populated here. -->
|
<!-- Server listings populated here. -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="serverSelectActions">
|
<div id="serverSelectActions">
|
||||||
<button id="serverSelectConfirm" class="overlayKeybindEnter" type="submit">Select</button>
|
<button id="serverSelectConfirm" class="overlayKeybindEnter" type="submit"><%- lang('overlay.serverSelectConfirm') %></button>
|
||||||
<div id="serverSelectCancelWrapper">
|
<div id="serverSelectCancelWrapper">
|
||||||
<button id="serverSelectCancel" class="overlayKeybindEsc">Cancel</button>
|
<button id="serverSelectCancel" class="overlayKeybindEsc"><%- lang('overlay.serverSelectCancel') %></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="accountSelectContent" style="display: none;">
|
<div id="accountSelectContent" style="display: none;">
|
||||||
<span id="accountSelectHeader">Select an Account</span>
|
<span id="accountSelectHeader"><%- lang('overlay.accountSelectHeader') %></span>
|
||||||
<div id="accountSelectList">
|
<div id="accountSelectList">
|
||||||
<div id="accountSelectListScrollable">
|
<div id="accountSelectListScrollable">
|
||||||
<!-- Accounts populated here. -->
|
<!-- Accounts populated here. -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="accountSelectActions">
|
<div id="accountSelectActions">
|
||||||
<button id="accountSelectConfirm" class="overlayKeybindEnter" type="submit">Select</button>
|
<button id="accountSelectConfirm" class="overlayKeybindEnter" type="submit"><%- lang('overlay.accountSelectConfirm') %></button>
|
||||||
<div id="accountSelectCancelWrapper">
|
<div id="accountSelectCancelWrapper">
|
||||||
<button id="accountSelectCancel" class="overlayKeybindEsc">Cancel</button>
|
<button id="accountSelectCancel" class="overlayKeybindEsc"><%- lang('overlay.accountSelectCancel') %></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
221
app/settings.ejs
@ -2,21 +2,21 @@
|
|||||||
<div id="settingsContainerLeft">
|
<div id="settingsContainerLeft">
|
||||||
<div id="settingsNavContainer">
|
<div id="settingsNavContainer">
|
||||||
<div id="settingsNavHeader">
|
<div id="settingsNavHeader">
|
||||||
<span id="settingsNavHeaderText">Settings</span>
|
<span id="settingsNavHeaderText"><%- lang('settings.navHeaderText') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsNavItemsContainer">
|
<div id="settingsNavItemsContainer">
|
||||||
<div id="settingsNavItemsContent">
|
<div id="settingsNavItemsContent">
|
||||||
<button class="settingsNavItem" rSc="settingsTabAccount" id="settingsNavAccount" selected>Account</button>
|
<button class="settingsNavItem" rSc="settingsTabAccount" id="settingsNavAccount" selected><%- lang('settings.navAccount') %></button>
|
||||||
<button class="settingsNavItem" rSc="settingsTabMinecraft">Minecraft</button>
|
<button class="settingsNavItem" rSc="settingsTabMinecraft"><%- lang('settings.navMinecraft') %></button>
|
||||||
<button class="settingsNavItem" rSc="settingsTabMods">Mods</button>
|
<button class="settingsNavItem" rSc="settingsTabMods"><%- lang('settings.navMods') %></button>
|
||||||
<button class="settingsNavItem" rSc="settingsTabJava">Java</button>
|
<button class="settingsNavItem" rSc="settingsTabJava"><%- lang('settings.navJava') %></button>
|
||||||
<button class="settingsNavItem" rSc="settingsTabLauncher">Launcher</button>
|
<button class="settingsNavItem" rSc="settingsTabLauncher"><%- lang('settings.navLauncher') %></button>
|
||||||
<div class="settingsNavSpacer"></div>
|
<div class="settingsNavSpacer"></div>
|
||||||
<button class="settingsNavItem" rSc="settingsTabAbout">About</button>
|
<button class="settingsNavItem" rSc="settingsTabAbout"><%- lang('settings.navAbout') %></button>
|
||||||
<button class="settingsNavItem" rSc="settingsTabUpdate" id="settingsNavUpdate">Updates</button>
|
<button class="settingsNavItem" rSc="settingsTabUpdate" id="settingsNavUpdate"><%- lang('settings.navUpdates') %></button>
|
||||||
<div id="settingsNavContentBottom">
|
<div id="settingsNavContentBottom">
|
||||||
<div class="settingsNavDivider"></div>
|
<div class="settingsNavDivider"></div>
|
||||||
<button id="settingsNavDone">Done</button>
|
<button id="settingsNavDone"><%- lang('settings.navDone') %></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -25,28 +25,57 @@
|
|||||||
<div id="settingsContainerRight">
|
<div id="settingsContainerRight">
|
||||||
<div id="settingsTabAccount" class="settingsTab">
|
<div id="settingsTabAccount" class="settingsTab">
|
||||||
<div class="settingsTabHeader">
|
<div class="settingsTabHeader">
|
||||||
<span class="settingsTabHeaderText">Account Settings</span>
|
<span class="settingsTabHeaderText"><%- lang('settings.tabAccountHeaderText') %></span>
|
||||||
<span class="settingsTabHeaderDesc">Add new accounts or manage existing ones.</span>
|
<span class="settingsTabHeaderDesc"><%- lang('settings.tabAccountHeaderDesc') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsAddAccountContainer">
|
<div class="settingsAuthAccountTypeContainer">
|
||||||
<button id="settingsAddAccount">
|
<div class="settingsAuthAccountTypeHeader">
|
||||||
<span id="settingsAddAccountText">+ Add Account</span>
|
<div class="settingsAuthAccountTypeHeaderLeft">
|
||||||
</button>
|
<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('settings.microsoftAccount') %></span>
|
||||||
|
</div>
|
||||||
|
<div class="settingsAuthAccountTypeHeaderRight">
|
||||||
|
<button class="settingsAddAuthAccount" id="settingsAddMicrosoftAccount"><%- lang('settings.addMicrosoftAccount') %></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settingsCurrentAccounts" id="settingsCurrentMicrosoftAccounts">
|
||||||
|
<!-- Microsoft auth accounts populated here. -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsCurrentAccountsHeader">
|
|
||||||
<span class="settingsFieldTitle">Current Accounts</span>
|
<div class="settingsAuthAccountTypeContainer">
|
||||||
</div>
|
<div class="settingsAuthAccountTypeHeader">
|
||||||
<div id="settingsCurrentAccounts">
|
<div class="settingsAuthAccountTypeHeaderLeft">
|
||||||
<!-- Auth accounts populated here. -->
|
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" 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>
|
||||||
|
<span><%- lang('settings.mojangAccount') %></span>
|
||||||
|
</div>
|
||||||
|
<div class="settingsAuthAccountTypeHeaderRight">
|
||||||
|
<button class="settingsAddAuthAccount" id="settingsAddMojangAccount"><%- lang('settings.addMojangAccount') %></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settingsCurrentAccounts" id="settingsCurrentMojangAccounts">
|
||||||
|
<!-- Mojang auth accounts populated here. -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsTabMinecraft" class="settingsTab" style="display: none;">
|
<div id="settingsTabMinecraft" class="settingsTab" style="display: none;">
|
||||||
<div class="settingsTabHeader">
|
<div class="settingsTabHeader">
|
||||||
<span class="settingsTabHeaderText">Minecraft Settings</span>
|
<span class="settingsTabHeaderText"><%- lang('settings.minecraftTabHeaderText') %></span>
|
||||||
<span class="settingsTabHeaderDesc">Options related to game launch.</span>
|
<span class="settingsTabHeaderDesc"><%- lang('settings.minecraftTabHeaderDesc') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsGameResolutionContainer">
|
<div id="settingsGameResolutionContainer">
|
||||||
<span class="settingsFieldTitle">Game Resolution</span>
|
<span class="settingsFieldTitle"><%- lang('settings.gameResolutionTitle') %></span>
|
||||||
<div id="settingsGameResolutionContent">
|
<div id="settingsGameResolutionContent">
|
||||||
<input type="number" id="settingsGameWidth" min="0" cValue="GameWidth">
|
<input type="number" id="settingsGameWidth" min="0" cValue="GameWidth">
|
||||||
<div id="settingsGameResolutionCross">✖</div>
|
<div id="settingsGameResolutionCross">✖</div>
|
||||||
@ -55,7 +84,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="settingsFieldContainer">
|
<div class="settingsFieldContainer">
|
||||||
<div class="settingsFieldLeft">
|
<div class="settingsFieldLeft">
|
||||||
<span class="settingsFieldTitle">Launch in fullscreen.</span>
|
<span class="settingsFieldTitle"><%- lang('settings.launchFullscreenTitle') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsFieldRight">
|
<div class="settingsFieldRight">
|
||||||
<label class="toggleSwitch">
|
<label class="toggleSwitch">
|
||||||
@ -66,7 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="settingsFieldContainer">
|
<div class="settingsFieldContainer">
|
||||||
<div class="settingsFieldLeft">
|
<div class="settingsFieldLeft">
|
||||||
<span class="settingsFieldTitle">Automatically connect to the server on launch.</span>
|
<span class="settingsFieldTitle"><%- lang('settings.autoConnectTitle') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsFieldRight">
|
<div class="settingsFieldRight">
|
||||||
<label class="toggleSwitch">
|
<label class="toggleSwitch">
|
||||||
@ -77,8 +106,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="settingsFieldContainer">
|
<div class="settingsFieldContainer">
|
||||||
<div class="settingsFieldLeft">
|
<div class="settingsFieldLeft">
|
||||||
<span class="settingsFieldTitle">Launch game process detached from launcher.</span>
|
<span class="settingsFieldTitle"><%- lang('settings.launchDetachedTitle') %></span>
|
||||||
<span class="settingsFieldDesc">If the game is not detached, closing the launcher will also close the game.</span>
|
<span class="settingsFieldDesc"><%- lang('settings.launchDetachedDesc') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsFieldRight">
|
<div class="settingsFieldRight">
|
||||||
<label class="toggleSwitch">
|
<label class="toggleSwitch">
|
||||||
@ -90,46 +119,46 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="settingsTabMods" class="settingsTab" style="display: none;">
|
<div id="settingsTabMods" class="settingsTab" style="display: none;">
|
||||||
<div class="settingsTabHeader">
|
<div class="settingsTabHeader">
|
||||||
<span class="settingsTabHeaderText">Mod Settings</span>
|
<span class="settingsTabHeaderText"><%- lang('settings.tabModsHeaderText') %></span>
|
||||||
<span class="settingsTabHeaderDesc">Enable or disable mods.</span>
|
<span class="settingsTabHeaderDesc"><%- lang('settings.tabModsHeaderDesc') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsSelServContainer">
|
<div class="settingsSelServContainer">
|
||||||
<div id="settingsSelServContent">
|
<div class="settingsSelServContent">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsSwitchServerContainer">
|
<div class="settingsSwitchServerContainer">
|
||||||
<div id="settingsSwitchServerContent">
|
<div class="settingsSwitchServerContent">
|
||||||
<button id="settingsSwitchServerButton">Switch</button>
|
<button class="settingsSwitchServerButton"><%- lang('settings.switchServerButton') %></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsModsContainer">
|
<div id="settingsModsContainer">
|
||||||
<div id="settingsReqModsContainer">
|
<div id="settingsReqModsContainer">
|
||||||
<div class="settingsModsHeader">Required Mods</div>
|
<div class="settingsModsHeader"><%- lang('settings.requiredMods') %></div>
|
||||||
<div id="settingsReqModsContent">
|
<div id="settingsReqModsContent">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsOptModsContainer">
|
<div id="settingsOptModsContainer">
|
||||||
<div class="settingsModsHeader">Optional Mods</div>
|
<div class="settingsModsHeader"><%- lang('settings.optionalMods') %></div>
|
||||||
<div id="settingsOptModsContent">
|
<div id="settingsOptModsContent">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsDropinModsContainer">
|
<div id="settingsDropinModsContainer">
|
||||||
<div class="settingsModsHeader">Drop-in Mods</div>
|
<div class="settingsModsHeader"><%- lang('settings.dropinMods') %></div>
|
||||||
<button id="settingsDropinFileSystemButton">+ Add Mods <span id="settingsDropinRefreshNote">(F5 to Refresh)</span></button>
|
<button id="settingsDropinFileSystemButton"><%- lang('settings.addMods') %> <span id="settingsDropinRefreshNote"><%- lang('settings.dropinRefreshNote') %></span></button>
|
||||||
<div id="settingsDropinModsContent">
|
<div id="settingsDropinModsContent">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsShadersContainer">
|
<div id="settingsShadersContainer">
|
||||||
<div class="settingsModsHeader">Shaderpacks</div>
|
<div class="settingsModsHeader"><%- lang('settings.shaderpacks') %></div>
|
||||||
<div id="settingsShaderpackDesc">Enable or disable shaders. Please note, shaders will only run smoothly on powerful setups. You may add custom packs here.</div>
|
<div id="settingsShaderpackDesc"><%- lang('settings.shaderpackDesc') %></div>
|
||||||
<div id="settingsShaderpackWrapper">
|
<div id="settingsShaderpackWrapper">
|
||||||
<button id="settingsShaderpackButton"> + </button>
|
<button id="settingsShaderpackButton"> + </button>
|
||||||
<div class="settingsSelectContainer">
|
<div class="settingsSelectContainer">
|
||||||
<div class="settingsSelectSelected" id="settingsShadersSelected">Select Shaderpack</div>
|
<div class="settingsSelectSelected" id="settingsShadersSelected"><%- lang('settings.selectShaderpack') %></div>
|
||||||
<div class="settingsSelectOptions" id="settingsShadersOptions" hidden>
|
<div class="settingsSelectOptions" id="settingsShadersOptions" hidden>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -140,17 +169,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="settingsTabJava" class="settingsTab" style="display: none;">
|
<div id="settingsTabJava" class="settingsTab" style="display: none;">
|
||||||
<div class="settingsTabHeader">
|
<div class="settingsTabHeader">
|
||||||
<span class="settingsTabHeaderText">Java Settings</span>
|
<span class="settingsTabHeaderText"><%- lang('settings.tabJavaHeaderText') %></span>
|
||||||
<span class="settingsTabHeaderDesc">Manage the Java configuration (advanced).</span>
|
<span class="settingsTabHeaderDesc"><%- lang('settings.tabJavaHeaderDesc') %></span>
|
||||||
|
</div>
|
||||||
|
<div class="settingsSelServContainer">
|
||||||
|
<div class="settingsSelServContent">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="settingsSwitchServerContainer">
|
||||||
|
<div class="settingsSwitchServerContent">
|
||||||
|
<button class="settingsSwitchServerButton"><%- lang('settings.switchServerButton') %></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsMemoryContainer">
|
<div id="settingsMemoryContainer">
|
||||||
<div id="settingsMemoryTitle">Memory</div>
|
<div id="settingsMemoryTitle"><%- lang('settings.memoryTitle') %></div>
|
||||||
<div id="settingsMemoryContent">
|
<div id="settingsMemoryContent">
|
||||||
<div id="settingsMemoryContentLeft">
|
<div id="settingsMemoryContentLeft">
|
||||||
<div class="settingsMemoryContentItem">
|
<div class="settingsMemoryContentItem">
|
||||||
<span class="settingsMemoryHeader">Maximum RAM</span>
|
<span class="settingsMemoryHeader"><%- lang('settings.maxRAM') %></span>
|
||||||
<div class="settingsMemoryActionContainer">
|
<div class="settingsMemoryActionContainer">
|
||||||
<div id="settingsMaxRAMRange" class="rangeSlider" cValue="MaxRAM" min="3" max="8" value="3" step="0.5">
|
<div id="settingsMaxRAMRange" class="rangeSlider" cValue="MaxRAM" serverDependent min="3" max="8" value="3" step="0.5">
|
||||||
<div class="rangeSliderBar"></div>
|
<div class="rangeSliderBar"></div>
|
||||||
<div class="rangeSliderTrack"></div>
|
<div class="rangeSliderTrack"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -158,25 +197,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsMemoryContentItem">
|
<div class="settingsMemoryContentItem">
|
||||||
<span class="settingsMemoryHeader">Minimum RAM</span>
|
<span class="settingsMemoryHeader"><%- lang('settings.minRAM') %></span>
|
||||||
<div class="settingsMemoryActionContainer">
|
<div class="settingsMemoryActionContainer">
|
||||||
<div id="settingsMinRAMRange" class="rangeSlider" cValue="MinRAM" min="3" max="8" value="3" step="0.5">
|
<div id="settingsMinRAMRange" class="rangeSlider" cValue="MinRAM" serverDependent min="3" max="8" value="3" step="0.5">
|
||||||
<div class="rangeSliderBar"></div>
|
<div class="rangeSliderBar"></div>
|
||||||
<div class="rangeSliderTrack"></div>
|
<div class="rangeSliderTrack"></div>
|
||||||
</div>
|
</div>
|
||||||
<span id="settingsMinRAMLabel" class="settingsMemoryLabel">3G</span>
|
<span id="settingsMinRAMLabel" class="settingsMemoryLabel">3G</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsMemoryDesc">The recommended minimum RAM is 3 gigabytes. Setting the minimum and maximum values to the same value may reduce lag.</div>
|
<div id="settingsMemoryDesc"><%- lang('settings.memoryDesc') %></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsMemoryContentRight">
|
<div id="settingsMemoryContentRight">
|
||||||
<div id="settingsMemoryStatus">
|
<div id="settingsMemoryStatus">
|
||||||
<div class="settingsMemoryStatusContainer">
|
<div class="settingsMemoryStatusContainer">
|
||||||
<span class="settingsMemoryStatusTitle">Total</span>
|
<span class="settingsMemoryStatusTitle"><%- lang('settings.memoryTotalTitle') %></span>
|
||||||
<span id="settingsMemoryTotal" class="settingsMemoryStatusValue">16G</span>
|
<span id="settingsMemoryTotal" class="settingsMemoryStatusValue">16G</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsMemoryStatusContainer">
|
<div class="settingsMemoryStatusContainer">
|
||||||
<span class="settingsMemoryStatusTitle">Available</span>
|
<span class="settingsMemoryStatusTitle"><%- lang('settings.memoryAvailableTitle') %></span>
|
||||||
<span id="settingsMemoryAvail" class="settingsMemoryStatusValue">7.3G</span>
|
<span id="settingsMemoryAvail" class="settingsMemoryStatusValue">7.3G</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -184,9 +223,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsFileSelContainer">
|
<div class="settingsFileSelContainer">
|
||||||
<div class="settingsFileSelTitle">Java Executable</div>
|
<div class="settingsFileSelTitle"><%- lang('settings.javaExecutableTitle') %></div>
|
||||||
<div class="settingsFileSelContent">
|
<div class="settingsFileSelContent">
|
||||||
<div id="settingsJavaExecDetails">Selected: Java 8 Update 172 (x64)</div>
|
<div id="settingsJavaExecDetails"><!-- Invalid Selection --></div>
|
||||||
<div class="settingsFileSelActions">
|
<div class="settingsFileSelActions">
|
||||||
<div class="settingsFileSelIcon">
|
<div class="settingsFileSelIcon">
|
||||||
<svg class="settingsFileSelSVG" x="0px" y="0px" viewBox="0 0 305.001 305.001">
|
<svg class="settingsFileSelSVG" x="0px" y="0px" viewBox="0 0 305.001 305.001">
|
||||||
@ -202,15 +241,14 @@
|
|||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<input class="settingsFileSelVal" id="settingsJavaExecVal" type="text" value="null" cValue="JavaExecutable" disabled>
|
<input class="settingsFileSelVal" id="settingsJavaExecVal" type="text" value="null" cValue="JavaExecutable" serverDependent disabled>
|
||||||
<input class="settingsFileSelSel" id="settingsJavaExecSel" type="file" <%= process.platform === 'win32' ? 'accept=.exe' : '' %>>
|
<button class="settingsFileSelButton" id="settingsJavaExecSel" dialogTitle="<%- lang('settings.javaExecSelDialogTitle') %>" dialogDirectory="false"><%- lang('settings.javaExecSelButtonText') %></button>
|
||||||
<label class="settingsFileSelLabel" for="settingsJavaExecSel">Choose File</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsFileSelDesc">The Java executable is validated before game launch. <strong>Requires Java 8 x64.</strong><br>The path should end with <strong>bin<%= process.platform === 'win32' ? '\\javaw.exe' : '/java' %></strong>.</div>
|
<div class="settingsFileSelDesc"><%- lang('settings.javaExecDesc') %> <strong id="settingsJavaReqDesc"><!-- Requires Java 8 x64. --></strong><br><%- lang('settings.javaPathDesc', {'pathSuffix': `bin${process.platform === 'win32' ? '\\javaw.exe' : '/java'}`}) %></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsJVMOptsContainer">
|
<div id="settingsJVMOptsContainer">
|
||||||
<div id="settingsJVMOptsTitle">Additional JVM Options</div>
|
<div id="settingsJVMOptsTitle"><%- lang('settings.jvmOptsTitle') %></div>
|
||||||
<div id="settingsJVMOptsContent">
|
<div id="settingsJVMOptsContent">
|
||||||
<div class="settingsFileSelIcon">
|
<div class="settingsFileSelIcon">
|
||||||
<svg class="settingsFileSelSVG" x="0px" y="0px" viewBox="0 0 305.001 305.001">
|
<svg class="settingsFileSelSVG" x="0px" y="0px" viewBox="0 0 305.001 305.001">
|
||||||
@ -226,20 +264,20 @@
|
|||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<input id="settingsJVMOptsVal" cValue="JVMOptions" type="text">
|
<input id="settingsJVMOptsVal" cValue="JVMOptions" serverDependent type="text">
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsJVMOptsDesc">Options to be provided to the JVM at runtime. <em>-Xms</em> and <em>-Xmx</em> should not be included.<br><a href="https://docs.oracle.com/javase/8/docs/technotes/tools/<%= process.platform === 'win32' ? 'windows' : 'unix' %>/java.html">Available Options for Java 8</a>.</div>
|
<div id="settingsJVMOptsDesc"><%- lang('settings.jvmOptsDesc') %><br><a href="#" id="settingsJvmOptsLink"><!-- Available Options --></a></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsTabLauncher" class="settingsTab" style="display: none;">
|
<div id="settingsTabLauncher" class="settingsTab" style="display: none;">
|
||||||
<div class="settingsTabHeader">
|
<div class="settingsTabHeader">
|
||||||
<span class="settingsTabHeaderText">Launcher Settings</span>
|
<span class="settingsTabHeaderText"><%- lang('settings.launcherTabHeaderText') %></span>
|
||||||
<span class="settingsTabHeaderDesc">Options related to the launcher itself.</span>
|
<span class="settingsTabHeaderDesc"><%- lang('settings.launcherTabHeaderDesc') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsFieldContainer">
|
<div class="settingsFieldContainer">
|
||||||
<div class="settingsFieldLeft">
|
<div class="settingsFieldLeft">
|
||||||
<span class="settingsFieldTitle">Allow Pre-Release Updates.</span>
|
<span class="settingsFieldTitle"><%- lang('settings.allowPrereleaseTitle') %></span>
|
||||||
<span class="settingsFieldDesc">Pre-Releases include new features which may have not been fully tested or integrated.<br>This will always be true if you are using a pre-release version.</span>
|
<span class="settingsFieldDesc"><%- lang('settings.allowPrereleaseDesc') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsFieldRight">
|
<div class="settingsFieldRight">
|
||||||
<label class="toggleSwitch">
|
<label class="toggleSwitch">
|
||||||
@ -250,7 +288,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="settingsFileSelContainer">
|
<div class="settingsFileSelContainer">
|
||||||
<div class="settingsFileSelContent">
|
<div class="settingsFileSelContent">
|
||||||
<div class="settingsFieldTitle" id="settingsDataDirTitle">Data Directory</div>
|
<div class="settingsFieldTitle" id="settingsDataDirTitle"><%- lang('settings.dataDirectoryTitle') %></div>
|
||||||
<div class="settingsFileSelActions">
|
<div class="settingsFileSelActions">
|
||||||
<div class="settingsFileSelIcon">
|
<div class="settingsFileSelIcon">
|
||||||
<svg class="settingsFileSelSVG">
|
<svg class="settingsFileSelSVG">
|
||||||
@ -262,91 +300,90 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<input class="settingsFileSelVal" type="text" value="null" cValue="DataDirectory" disabled>
|
<input class="settingsFileSelVal" type="text" value="null" cValue="DataDirectory" disabled>
|
||||||
<input class="settingsFileSelSel" id="settingsDataDirSel" type="file" webkitdirectory>
|
<button class="settingsFileSelButton" dialogTitle="<%- lang('settings.selectDataDirectory') %>" dialogDirectory="true"><%- lang('settings.chooseFolder') %></button>
|
||||||
<label class="settingsFileSelLabel" for="settingsDataDirSel">Choose Folder</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsFileSelDesc">All game files and local Java installations will be stored in the data directory.<br>Screenshots and world saves are stored in the instance folder for the corresponding server configuration.</div>
|
<div class="settingsFileSelDesc"><%- lang('settings.dataDirectoryDesc') %></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsTabAbout" class="settingsTab" style="display: none;">
|
<div id="settingsTabAbout" class="settingsTab" style="display: none;">
|
||||||
<div class="settingsTabHeader">
|
<div class="settingsTabHeader">
|
||||||
<span class="settingsTabHeaderText">About</span>
|
<span class="settingsTabHeaderText"><%- lang('settings.aboutTabHeaderText') %></span>
|
||||||
<span class="settingsTabHeaderDesc">View information and release notes for the current version.</span>
|
<span class="settingsTabHeaderDesc"><%- lang('settings.aboutTabHeaderDesc') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsAboutCurrentContainer">
|
<div id="settingsAboutCurrentContainer">
|
||||||
<div id="settingsAboutCurrentContent">
|
<div id="settingsAboutCurrentContent">
|
||||||
<div id="settingsAboutCurrentHeadline">
|
<div id="settingsAboutCurrentHeadline">
|
||||||
<img id="settingsAboutLogo" src="./assets/images/WesterosSealSquare.png">
|
<img id="settingsAboutLogo" src="./assets/images/SealCircle.png">
|
||||||
<span id="settingsAboutTitle">WesterosCraft Launcher</span>
|
<span id="settingsAboutTitle"><%- lang('settings.aboutTitle', { appName: lang('app.title') }) %></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsAboutCurrentVersion">
|
<div id="settingsAboutCurrentVersion">
|
||||||
<div id="settingsAboutCurrentVersionCheck">✓</div>
|
<div id="settingsAboutCurrentVersionCheck">✓</div>
|
||||||
<div id="settingsAboutCurrentVersionDetails">
|
<div id="settingsAboutCurrentVersionDetails">
|
||||||
<span id="settingsAboutCurrentVersionTitle">Stable Release</span>
|
<span id="settingsAboutCurrentVersionTitle"><%- lang('settings.stableRelease') %></span>
|
||||||
<div id="settingsAboutCurrentVersionLine">
|
<div id="settingsAboutCurrentVersionLine">
|
||||||
<span id="settingsAboutCurrentVersionText">Version </span>
|
<span id="settingsAboutCurrentVersionText"><%- lang('settings.versionText') %></span>
|
||||||
<span id="settingsAboutCurrentVersionValue">0.0.1-alpha.12</span>
|
<span id="settingsAboutCurrentVersionValue">0.0.1-alpha.18</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsAboutButtons">
|
<div id="settingsAboutButtons">
|
||||||
<a href="https://github.com/WesterosCraftCode/ElectronLauncher" id="settingsAboutSourceButton" class="settingsAboutButton">Source (GitHub)</a>
|
<a href="<%- lang('settings.sourceGithubLink') %>" id="settingsAboutSourceButton" class="settingsAboutButton"><%- lang('settings.sourceGithub') %></a>
|
||||||
<!-- The following must be included in third-party usage. -->
|
<!-- The following must be included in third-party usage. -->
|
||||||
<!-- <a href="https://github.com/WesterosCraftCode/ElectronLauncher" id="settingsAboutSourceButton" class="settingsAboutButton">Original Source</a> -->
|
<!-- <a href="https://github.com/dscalzi/HeliosLauncher" id="settingsAboutSourceButton" class="settingsAboutButton">Original Source</a> -->
|
||||||
<a href="http://www.westeroscraft.com/forum/support.40/" id="settingsAboutSupportButton" class="settingsAboutButton">Support</a>
|
<a href="<%- lang('settings.supportLink') %>" id="settingsAboutSupportButton" class="settingsAboutButton"><%- lang('settings.support') %></a>
|
||||||
<a href="#" id="settingsAboutDevToolsButton" class="settingsAboutButton">DevTools Console</a>
|
<a href="#" id="settingsAboutDevToolsButton" class="settingsAboutButton"><%- lang('settings.devToolsConsole') %></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsChangelogContainer">
|
<div class="settingsChangelogContainer">
|
||||||
<div class="settingsChangelogContent">
|
<div class="settingsChangelogContent">
|
||||||
<div class="settingsChangelogHeadline">
|
<div class="settingsChangelogHeadline">
|
||||||
<div class="settingsChangelogLabel">Release Notes</div>
|
<div class="settingsChangelogLabel"><%- lang('settings.releaseNotes') %></div>
|
||||||
<div class="settingsChangelogTitle">Changelog</div>
|
<div class="settingsChangelogTitle"><%- lang('settings.changelog') %></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsChangelogText">
|
<div class="settingsChangelogText">
|
||||||
No Release Notes
|
<%- lang('settings.noReleaseNotes') %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsChangelogActions">
|
<div class="settingsChangelogActions">
|
||||||
<a class="settingsChangelogButton settingsAboutButton" href="#">View Release Notes on GitHub</a>
|
<a class="settingsChangelogButton settingsAboutButton" href="#"><%- lang('settings.viewReleaseNotes') %></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsTabUpdate" class="settingsTab" style="display: none;">
|
<div id="settingsTabUpdate" class="settingsTab" style="display: none;">
|
||||||
<div class="settingsTabHeader">
|
<div class="settingsTabHeader">
|
||||||
<span class="settingsTabHeaderText">Launcher Updates</span>
|
<span class="settingsTabHeaderText"><%- lang('settings.launcherUpdatesHeaderText') %></span>
|
||||||
<span class="settingsTabHeaderDesc">Download, install, and review updates for the launcher.</span>
|
<span class="settingsTabHeaderDesc"><%- lang('settings.launcherUpdatesHeaderDesc') %></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsUpdateStatusContainer">
|
<div id="settingsUpdateStatusContainer">
|
||||||
<div id="settingsUpdateStatusContent">
|
<div id="settingsUpdateStatusContent">
|
||||||
<div id="settingsUpdateStatusHeadline">
|
<div id="settingsUpdateStatusHeadline">
|
||||||
<span id="settingsUpdateTitle">You Are Running the Latest Version</span>
|
<span id="settingsUpdateTitle"><!-- You Are Running the Latest Version --></span>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsUpdateVersion">
|
<div id="settingsUpdateVersion">
|
||||||
<div id="settingsUpdateVersionCheck">✓</div>
|
<div id="settingsUpdateVersionCheck">✓</div>
|
||||||
<div id="settingsUpdateVersionDetails">
|
<div id="settingsUpdateVersionDetails">
|
||||||
<span id="settingsUpdateVersionTitle">Stable Release</span>
|
<span id="settingsUpdateVersionTitle"><%- lang('settings.stableRelease') %></span>
|
||||||
<div id="settingsUpdateVersionLine">
|
<div id="settingsUpdateVersionLine">
|
||||||
<span id="settingsUpdateVersionText">Version </span>
|
<span id="settingsUpdateVersionText"><%- lang('settings.versionText') %> </span>
|
||||||
<span id="settingsUpdateVersionValue">0.0.1-alpha.18</span>
|
<span id="settingsUpdateVersionValue">0.0.1-alpha.18</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="settingsUpdateActionContainer">
|
<div id="settingsUpdateActionContainer">
|
||||||
<button id="settingsUpdateActionButton">Check for Updates</button>
|
<button id="settingsUpdateActionButton"><%- lang('settings.checkForUpdates') %></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsChangelogContainer">
|
<div class="settingsChangelogContainer">
|
||||||
<div class="settingsChangelogContent">
|
<div class="settingsChangelogContent">
|
||||||
<div class="settingsChangelogHeadline">
|
<div class="settingsChangelogHeadline">
|
||||||
<div class="settingsChangelogLabel">What's New</div>
|
<div class="settingsChangelogLabel"><%- lang('settings.whatsNew') %></div>
|
||||||
<div class="settingsChangelogTitle">Update Release Notes</div>
|
<div class="settingsChangelogTitle"><%- lang('settings.updateReleaseNotes') %></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settingsChangelogText">
|
<div class="settingsChangelogText">
|
||||||
No Release Notes
|
<%- lang('settings.noReleaseNotes') %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
8
app/waiting.ejs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<div id="waitingContainer" style="display: none;">
|
||||||
|
<div id="waitingContent">
|
||||||
|
<div class="waitingSpinner"></div>
|
||||||
|
<div id="waitingTextContainer">
|
||||||
|
<h2><%- lang('waiting.waitingText') %></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -4,14 +4,14 @@
|
|||||||
<div class="cloudBottom"></div>
|
<div class="cloudBottom"></div>
|
||||||
</div>-->
|
</div>-->
|
||||||
<div id="welcomeContent">
|
<div id="welcomeContent">
|
||||||
<img id="welcomeImageSeal" src="assets/images/WesterosSealCircle.png"/>
|
<img id="welcomeImageSeal" src="assets/images/SealCircle.png"/>
|
||||||
<span id="welcomeHeader">WELCOME TO WESTEROSCRAFT</span>
|
<span id="welcomeHeader"><%- lang('welcome.welcomeHeader') %></span>
|
||||||
<span id="welcomeDescription">Our mission is to recreate the universe imagined by author George RR Martin in his fantasy series, A Song of Ice and Fire. Through the collaborative effort of thousands of community members, we have sought to create Westeros as accurately and precisely as possible within Minecraft. The world we are creating is yours to explore. Journey from Dorne to Castle Black, and if you aren’t afraid, beyond the Wall itself, but best not delay. As the words of House Stark ominously warn: Winter is Coming.</span>
|
<span id="welcomeDescription"><%- lang('welcome.welcomeDescription') %></span>
|
||||||
<br>
|
<br>
|
||||||
<span id="welcomeDescCTA">You are just a few clicks away from Westeros.</span>
|
<span id="welcomeDescCTA"><%- lang('welcome.welcomeDescCTA') %></span>
|
||||||
<button id="welcomeButton">
|
<button id="welcomeButton">
|
||||||
<div id="welcomeButtonContent">
|
<div id="welcomeButtonContent">
|
||||||
CONTINUE
|
<%- lang('welcome.continueButton') %>
|
||||||
<svg id="welcomeSVG" viewBox="0 0 24.87 13.97">
|
<svg id="welcomeSVG" viewBox="0 0 24.87 13.97">
|
||||||
<defs>
|
<defs>
|
||||||
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
|
<style>.arrowLine{fill:none;stroke:#FFF;stroke-width:2px;transition: 0.25s ease;}</style>
|
||||||
|
BIN
build/icon.icns
BIN
build/icon.ico
Before Width: | Height: | Size: 361 KiB |
BIN
build/icon.png
Normal file
After Width: | Height: | Size: 142 KiB |
@ -1,3 +1,3 @@
|
|||||||
owner: WesterosCraftCode
|
owner: dscalzi
|
||||||
repo: ElectronLauncher
|
repo: HeliosLauncher
|
||||||
provider: github
|
provider: github
|
||||||
|
52
docs/MicrosoftAuth.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Microsoft Authentication
|
||||||
|
|
||||||
|
Authenticating with Microsoft is fully supported by Helios Launcher.
|
||||||
|
|
||||||
|
## Acquiring an Azure Client ID
|
||||||
|
|
||||||
|
1. Navigate to https://portal.azure.com
|
||||||
|
2. In the search bar, search for **Azure Active Directory**.
|
||||||
|
3. In Azure Active Directory, go to **App Registrations** on the left pane (Under *Manage*).
|
||||||
|
4. Click **New Registration**.
|
||||||
|
- Set **Name** to be your launcher's name.
|
||||||
|
- Set **Supported account types** to *Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)*
|
||||||
|
- Leave **Redirect URI** blank.
|
||||||
|
- Register the application.
|
||||||
|
5. You should be on the application's management page. If not, Navigate back to **App Registrations**. Select the application you just registered.
|
||||||
|
6. Click **Authentication** on the left pane (Under *Manage*).
|
||||||
|
7. Click **Add Platform**.
|
||||||
|
- Select **Mobile and desktop applications**.
|
||||||
|
- Choose `https://login.microsoftonline.com/common/oauth2/nativeclient` as the **Redirect URI**.
|
||||||
|
- Select **Configure** to finish adding the platform.
|
||||||
|
8. Go to **Credentials & secrets**.
|
||||||
|
- Select **Client secrets**.
|
||||||
|
- Click **New client secret**.
|
||||||
|
- Set a description.
|
||||||
|
- Click **Add**.
|
||||||
|
- Don't copy the client secret, adding one is just a requirement from Microsoft.
|
||||||
|
8. Navigate back to **Overview**.
|
||||||
|
9. Copy **Application (client) ID**.
|
||||||
|
|
||||||
|
|
||||||
|
## Adding the Azure Client ID to Helios Launcher.
|
||||||
|
|
||||||
|
In `app/assets/js/ipcconstants.js` you'll find **`AZURE_CLIENT_ID`**. Set it to your application's id.
|
||||||
|
|
||||||
|
Note: Azure Client ID is NOT a secret value and **can** be stored in git. Reference: https://stackoverflow.com/questions/57306964/are-azure-active-directorys-tenantid-and-clientid-considered-secrets
|
||||||
|
|
||||||
|
Then relaunch your app, and login. You'll be greeted with an error message, because the app isn't whitelisted yet. Microsoft needs some activity on the app before whitelisting it. __Trying to log in before requesting whitelist is mandatory.__
|
||||||
|
|
||||||
|
## Requesting whitelisting from Microsoft
|
||||||
|
|
||||||
|
1. Ensure you have completed every step of this doc page.
|
||||||
|
2. Fill [this form](https://aka.ms/mce-reviewappid) with the required information. Remember this is a new appID for approval. You can find both the Client ID and the Tenant ID on the overview page in the Azure Portal.
|
||||||
|
3. Give Microsoft some time to review your app.
|
||||||
|
4. Once you have received Microsoft's approval, allow up to 24 hours for the changes to apply.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
You can now authenticate with Microsoft through the launcher.
|
||||||
|
|
||||||
|
References:
|
||||||
|
- https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app
|
||||||
|
- https://help.minecraft.net/hc/en-us/articles/16254801392141
|
158
docs/distro.md
@ -1,5 +1,9 @@
|
|||||||
# Distribution Index
|
# Distribution Index
|
||||||
|
|
||||||
|
You can use [Nebula](https://github.com/dscalzi/Nebula) to automate the generation of a distribution index.
|
||||||
|
|
||||||
|
The most up to date and accurate descriptions of the distribution spec can be viewed in [helios-distribution-types](https://github.com/dscalzi/helios-distribution-types).
|
||||||
|
|
||||||
The distribution index is written in JSON. The general format of the index is as posted below.
|
The distribution index is written in JSON. The general format of the index is as posted below.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@ -141,12 +145,122 @@ Only one server in the array should have the `mainServer` property enabled. This
|
|||||||
|
|
||||||
Whether or not the server can be autoconnected to. If false, the server will not be autoconnected to even when the user has the autoconnect setting enabled.
|
Whether or not the server can be autoconnected to. If false, the server will not be autoconnected to even when the user has the autoconnect setting enabled.
|
||||||
|
|
||||||
|
### `Server.javaOptions: JavaOptions`
|
||||||
|
|
||||||
|
**OPTIONAL**
|
||||||
|
|
||||||
|
Sever-specific Java options. If not provided, defaults are used by the client.
|
||||||
|
|
||||||
### `Server.modules: Module[]`
|
### `Server.modules: Module[]`
|
||||||
|
|
||||||
An array of module objects.
|
An array of module objects.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## JavaOptions Object
|
||||||
|
|
||||||
|
Server-specific Java options.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"supported": ">=17",
|
||||||
|
"suggestedMajor": 17,
|
||||||
|
"platformOptions": [
|
||||||
|
{
|
||||||
|
"platform": "darwin",
|
||||||
|
"architecture": "arm64",
|
||||||
|
"distribution": "CORRETTO"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ram": {
|
||||||
|
"recommended": 3072,
|
||||||
|
"minimum": 2048
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `JavaOptions.platformOptions: JavaPlatformOptions[]`
|
||||||
|
|
||||||
|
**OPTIONAL**
|
||||||
|
|
||||||
|
Platform-specific java rules for this server configuration. Validation rules will be delegated to the client for any undefined properties. Java validation can be configured for specific platforms and architectures. The most specific ruleset will be applied.
|
||||||
|
|
||||||
|
Maxtrix Precedence (Highest - Lowest)
|
||||||
|
- Current platform, current architecture (ex. win32 x64).
|
||||||
|
- Current platform, any architecture (ex. win32).
|
||||||
|
- Java Options base properties.
|
||||||
|
- Client logic (default logic in the client).
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
|
||||||
|
- `platformOptions.platform: string` - The platform that this validation matrix applies to.
|
||||||
|
- `platformOptions.architecture: string` - Optional. The architecture that this validation matrix applies to. If omitted, applies to all architectures.
|
||||||
|
- `platformOptions.distribution: string` - Optional. See `JavaOptions.distribution`.
|
||||||
|
- `platformOptions.supported: string` - Optional. See `JavaOptions.supported`.
|
||||||
|
- `platformOptions.suggestedMajor: number` - Optional. See `JavaOptions.suggestedMajor`.
|
||||||
|
|
||||||
|
### `JavaOptions.ram: object`
|
||||||
|
|
||||||
|
**OPTIONAL**
|
||||||
|
|
||||||
|
This allows you to require a minimum and recommended amount of RAM per server instance. The minimum is the smallest value the user can select in the settings slider. The recommended value will be the default value selected for that server. These values are specified in megabytes and must be an interval of 512. This allows configuration in intervals of half gigabytes. In the above example, the recommended ram value is 3 GB (3072 MB) and the minimum is 2 GB (2048 MB).
|
||||||
|
|
||||||
|
- `ram.recommended: number` - The recommended amount of RAM in megabytes. Must be an interval of 512.
|
||||||
|
- `ram.minimum: number` - The absolute minimum amount of RAM in megabytes. Must be an interval of 512.
|
||||||
|
|
||||||
|
### `JavaOptions.distribution: string`
|
||||||
|
|
||||||
|
**OPTIONAL**
|
||||||
|
|
||||||
|
Preferred JDK distribution to download if no applicable installation could be found. If omitted, the client will decide (decision may be platform-specific).
|
||||||
|
|
||||||
|
### `JavaOptions.supported: string`
|
||||||
|
|
||||||
|
**OPTIONAL**
|
||||||
|
|
||||||
|
A semver range of supported JDK versions.
|
||||||
|
|
||||||
|
Java version syntax is platform dependent.
|
||||||
|
|
||||||
|
JDK 8 and prior
|
||||||
|
```
|
||||||
|
1.{major}.{minor}_{patch}-b{build}
|
||||||
|
Ex. 1.8.0_152-b16
|
||||||
|
```
|
||||||
|
|
||||||
|
JDK 9+
|
||||||
|
```
|
||||||
|
{major}.{minor}.{patch}+{build}
|
||||||
|
Ex. 11.0.12+7
|
||||||
|
```
|
||||||
|
|
||||||
|
For processing, all versions will be translated into a semver compliant string. JDK 9+ is already semver. For versions 8 and below, `1.{major}.{minor}_{patch}-b{build}` will be translated to `{major}.{minor}.{patch}+{build}`.
|
||||||
|
|
||||||
|
If specified, you must also specify suggestedMajor.
|
||||||
|
|
||||||
|
If omitted, the client will decide based on the game version.
|
||||||
|
|
||||||
|
### `JavaOptions.suggestedMajor: number`
|
||||||
|
|
||||||
|
**OPTIONAL**
|
||||||
|
|
||||||
|
The suggested major Java version. The suggested major should comply with the version range specified by supported, if defined. This will be used in messages displayed to the end user, and to automatically fetch a Java version.
|
||||||
|
|
||||||
|
NOTE If supported is specified, suggestedMajor must be set. The launcher's default value may not comply with your custom major supported range.
|
||||||
|
|
||||||
|
Common use case:
|
||||||
|
- supported: '>=17.x'
|
||||||
|
- suggestedMajor: 17
|
||||||
|
|
||||||
|
More involved:
|
||||||
|
- supported: '>=16 <20'
|
||||||
|
- suggestedMajor: 17
|
||||||
|
|
||||||
|
Given a wider support range, it becomes necessary to specify which major version in the range is the suggested.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Module Object
|
## Module Object
|
||||||
|
|
||||||
A module is a generic representation of a file required to run the minecraft client.
|
A module is a generic representation of a file required to run the minecraft client.
|
||||||
@ -206,6 +320,12 @@ The name of the module. Used on the UI.
|
|||||||
|
|
||||||
The type of the module.
|
The type of the module.
|
||||||
|
|
||||||
|
### `Module.classpath: boolean`
|
||||||
|
|
||||||
|
**OPTIONAL**
|
||||||
|
|
||||||
|
If the module is of type `Library`, whether the library should be added to the classpath. Defaults to true.
|
||||||
|
|
||||||
### `Module.required: Required`
|
### `Module.required: Required`
|
||||||
|
|
||||||
**OPTIONAL**
|
**OPTIONAL**
|
||||||
@ -240,10 +360,12 @@ The resolved/provided paths are appended to a base path depending on the module'
|
|||||||
| Type | Path |
|
| Type | Path |
|
||||||
| ---- | ---- |
|
| ---- | ---- |
|
||||||
| `ForgeHosted` | ({`commonDirectory`}/libraries/{`path` OR resolved}) |
|
| `ForgeHosted` | ({`commonDirectory`}/libraries/{`path` OR resolved}) |
|
||||||
|
| `Fabric` | ({`commonDirectory`}/libraries/{`path` OR resolved}) |
|
||||||
| `LiteLoader` | ({`commonDirectory`}/libraries/{`path` OR resolved}) |
|
| `LiteLoader` | ({`commonDirectory`}/libraries/{`path` OR resolved}) |
|
||||||
| `Library` | ({`commonDirectory`}/libraries/{`path` OR resolved}) |
|
| `Library` | ({`commonDirectory`}/libraries/{`path` OR resolved}) |
|
||||||
| `ForgeMod` | ({`commonDirectory`}/modstore/{`path` OR resolved}) |
|
| `ForgeMod` | ({`commonDirectory`}/modstore/{`path` OR resolved}) |
|
||||||
| `LiteMod` | ({`commonDirectory`}/modstore/{`path` OR resolved}) |
|
| `LiteMod` | ({`commonDirectory`}/modstore/{`path` OR resolved}) |
|
||||||
|
| `FabricMod` | ({`commonDirectory`}/mods/fabric/{`path` OR resolved}) |
|
||||||
| `File` | ({`instanceDirectory`}/{`Server.id`}/{`path` OR resolved}) |
|
| `File` | ({`instanceDirectory`}/{`Server.id`}/{`path` OR resolved}) |
|
||||||
|
|
||||||
The `commonDirectory` and `instanceDirectory` values are stored in the launcher's config.json.
|
The `commonDirectory` and `instanceDirectory` values are stored in the launcher's config.json.
|
||||||
@ -288,7 +410,7 @@ If the module is enabled by default. Has no effect unless `Required.value` is fa
|
|||||||
|
|
||||||
### ForgeHosted
|
### ForgeHosted
|
||||||
|
|
||||||
The module type `ForgeHosted` represents forge itself. Currently, the launcher only supports forge servers, as vanilla servers can be connected to via the mojang launcher. The `Hosted` part is key, this means that the forge module must declare its required libraries as submodules.
|
The module type `ForgeHosted` represents forge itself. Currently, the launcher only supports modded servers, as vanilla servers can be connected to via the mojang launcher. The `Hosted` part is key, this means that the forge module must declare its required libraries as submodules.
|
||||||
|
|
||||||
Ex.
|
Ex.
|
||||||
|
|
||||||
@ -323,6 +445,40 @@ There were plans to add a `Forge` type, in which the required libraries would be
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Fabric
|
||||||
|
|
||||||
|
The module type `Fabric` represents the fabric mod loader. Currently, the launcher only supports modded servers, as vanilla servers can be connected to via the mojang launcher.
|
||||||
|
|
||||||
|
Ex.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "net.fabricmc:fabric-loader:0.15.0",
|
||||||
|
"name": "Fabric (fabric-loader)",
|
||||||
|
"type": "Fabric",
|
||||||
|
"artifact": {
|
||||||
|
"size": 1196222,
|
||||||
|
"MD5": "a43d5a142246801343b6cedef1c102c4",
|
||||||
|
"url": "http://localhost:8080/repo/lib/net/fabricmc/fabric-loader/0.15.0/fabric-loader-0.15.0.jar"
|
||||||
|
},
|
||||||
|
"subModules": [
|
||||||
|
{
|
||||||
|
"id": "1.20.1-fabric-0.15.0",
|
||||||
|
"name": "Fabric (version.json)",
|
||||||
|
"type": "VersionManifest",
|
||||||
|
"artifact": {
|
||||||
|
"size": 2847,
|
||||||
|
"MD5": "69a2bd43452325ba1bc882fa0904e054",
|
||||||
|
"url": "http://localhost:8080/repo/versions/1.20.1-fabric-0.15.0/1.20.1-fabric-0.15.0.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Fabric works similarly to Forge 1.13+.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### LiteLoader
|
### LiteLoader
|
||||||
|
|
||||||
The module type `LiteLoader` represents liteloader. It is handled as a library and added to the classpath at runtime. Special launch conditions are executed when liteloader is present and enabled. This module can be optional and toggled similarly to `ForgeMod` and `Litemod` modules.
|
The module type `LiteLoader` represents liteloader. It is handled as a library and added to the classpath at runtime. Special launch conditions are executed when liteloader is present and enabled. This module can be optional and toggled similarly to `ForgeMod` and `Litemod` modules.
|
||||||
|
@ -8,14 +8,14 @@
|
|||||||
"java": {
|
"java": {
|
||||||
"oracle": "http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html"
|
"oracle": "http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html"
|
||||||
},
|
},
|
||||||
"rss": "https://westeroscraft.com/articles/index.rss",
|
"rss": "https://forum.westeroscraft.com/forum/news.2/index.rss",
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"id": "WesterosCraft-1.11.2",
|
"id": "WesterosCraft-1.11.2",
|
||||||
"name": "WesterosCraft Production Server",
|
"name": "WesterosCraft Production Server",
|
||||||
"description": "Main WesterosCraft server. Connect to enter the Realm.",
|
"description": "Main WesterosCraft server. Connect to enter the Realm.",
|
||||||
"icon": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/server-prod.png",
|
"icon": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/server-prod.png",
|
||||||
"version": "3.9.0",
|
"version": "3.9.4",
|
||||||
"address": "mc.westeroscraft.com",
|
"address": "mc.westeroscraft.com",
|
||||||
"minecraftVersion": "1.11.2",
|
"minecraftVersion": "1.11.2",
|
||||||
"discord": {
|
"discord": {
|
||||||
@ -219,22 +219,22 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "com.westeroscraft:westerosblocks:3.1.0-alpha-2-138",
|
"id": "com.westeroscraft:westerosblocks:3.2.0-beta-1-190",
|
||||||
"name": "WesterosBlocks",
|
"name": "WesterosBlocks",
|
||||||
"type": "ForgeMod",
|
"type": "ForgeMod",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 17352677,
|
"size": 17376788,
|
||||||
"MD5": "b5409aa925a47f67158c8141e71f723f",
|
"MD5": "370f4f1804c93f498f31af8dac509605",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/WesterosBlocks.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/WesterosBlocks.jar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "com.westeroscraft:westeroscraftrp:2018-07-21",
|
"id": "com.westeroscraft:westeroscraftrp:2019-01-21",
|
||||||
"name": "WesterosCraft Resource Pack",
|
"name": "WesterosCraft Resource Pack",
|
||||||
"type": "File",
|
"type": "File",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 46942221,
|
"size": 46999843,
|
||||||
"MD5": "26e3e63a5778691eb3a9db11f449fdf1",
|
"MD5": "309eb9e5296e9f55cfecb7d4058245a4",
|
||||||
"path": "resourcepacks/WesterosCraft.zip",
|
"path": "resourcepacks/WesterosCraft.zip",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/resourcepacks/WesterosCraft.zip"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/resourcepacks/WesterosCraft.zip"
|
||||||
}
|
}
|
||||||
@ -260,15 +260,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "org.blockartistry:dynsurround:1.11.2-3.4.6.2",
|
"id": "org.blockartistry:dynsurround:1.11.2-3.4.9.3",
|
||||||
"name": "DynamicSurroundings",
|
"name": "DynamicSurroundings",
|
||||||
"type": "ForgeMod",
|
"type": "ForgeMod",
|
||||||
"required": {
|
"required": {
|
||||||
"value": false
|
"value": false
|
||||||
},
|
},
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 21853035,
|
"size": 22291385,
|
||||||
"MD5": "82a6aac5420151960b8dd709deee5423",
|
"MD5": "65403c66d8b3655b372f58047941d206",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/DynamicSurroundings.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/DynamicSurroundings.jar"
|
||||||
},
|
},
|
||||||
"subModules": [
|
"subModules": [
|
||||||
@ -278,7 +278,6 @@
|
|||||||
"type": "File",
|
"type": "File",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 20258,
|
"size": 20258,
|
||||||
"MD5": "3df81248db151750b7d0a0193b327b47",
|
|
||||||
"path": "/config/dsurround/dsurround.cfg",
|
"path": "/config/dsurround/dsurround.cfg",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/config/dsurround/dsurround.cfg"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/config/dsurround/dsurround.cfg"
|
||||||
}
|
}
|
||||||
@ -297,15 +296,15 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "octarine-noise:betterfoliage:1.11.2-2.1.8",
|
"id": "octarine-noise:betterfoliage:1.11.2-2.1.10",
|
||||||
"name": "BetterFoliage",
|
"name": "BetterFoliage",
|
||||||
"type": "ForgeMod",
|
"type": "ForgeMod",
|
||||||
"required": {
|
"required": {
|
||||||
"value": false
|
"value": false
|
||||||
},
|
},
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 4676029,
|
"size": 4675903,
|
||||||
"MD5": "b2dd47e42da56fb49a07a0d38df91bc4",
|
"MD5": "522fdf73b6e4343cb6243872fb7b4b6c",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/BetterFoliage.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/prod-1.11.2/mods/BetterFoliage.jar"
|
||||||
},
|
},
|
||||||
"subModules": [
|
"subModules": [
|
||||||
@ -430,7 +429,7 @@
|
|||||||
"name": "WesterosCraft Test Server",
|
"name": "WesterosCraft Test Server",
|
||||||
"description": "Main testing server. Experimental changes are live here.",
|
"description": "Main testing server. Experimental changes are live here.",
|
||||||
"icon": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/server-test.png",
|
"icon": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/server-test.png",
|
||||||
"version": "3.8.3",
|
"version": "3.9.4",
|
||||||
"address": "mc.westeroscraft.com:4444",
|
"address": "mc.westeroscraft.com:4444",
|
||||||
"minecraftVersion": "1.11.2",
|
"minecraftVersion": "1.11.2",
|
||||||
"discord": {
|
"discord": {
|
||||||
@ -634,22 +633,22 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "com.westeroscraft:westerosblocks:3.1.0-alpha-2-138",
|
"id": "com.westeroscraft:westerosblocks:3.2.0-beta-1-190",
|
||||||
"name": "WesterosBlocks",
|
"name": "WesterosBlocks",
|
||||||
"type": "ForgeMod",
|
"type": "ForgeMod",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 17352677,
|
"size": 17376788,
|
||||||
"MD5": "b5409aa925a47f67158c8141e71f723f",
|
"MD5": "370f4f1804c93f498f31af8dac509605",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/mods/WesterosBlocks.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/mods/WesterosBlocks.jar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "com.westeroscraft:westeroscraftrp:2018-07-21",
|
"id": "com.westeroscraft:westeroscraftrp:2019-01-21",
|
||||||
"name": "WesterosCraft Resource Pack",
|
"name": "WesterosCraft Resource Pack",
|
||||||
"type": "File",
|
"type": "File",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 46942221,
|
"size": 46999843,
|
||||||
"MD5": "26e3e63a5778691eb3a9db11f449fdf1",
|
"MD5": "309eb9e5296e9f55cfecb7d4058245a4",
|
||||||
"path": "resourcepacks/WesterosCraft.zip",
|
"path": "resourcepacks/WesterosCraft.zip",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/resourcepacks/WesterosCraft.zip"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/resourcepacks/WesterosCraft.zip"
|
||||||
}
|
}
|
||||||
@ -693,7 +692,6 @@
|
|||||||
"type": "File",
|
"type": "File",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 20849,
|
"size": 20849,
|
||||||
"MD5": "8d6c08c158aa846162e2a179d6228181",
|
|
||||||
"path": "/config/dsurround/dsurround.cfg",
|
"path": "/config/dsurround/dsurround.cfg",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/config/dsurround/dsurround.cfg"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.11.2/config/dsurround/dsurround.cfg"
|
||||||
}
|
}
|
||||||
@ -857,13 +855,13 @@
|
|||||||
"autoconnect": true,
|
"autoconnect": true,
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"id": "net.minecraftforge:forge:1.12.2-14.23.2.2651",
|
"id": "net.minecraftforge:forge:1.12.2-14.23.5.2797",
|
||||||
"name": "Minecraft Forge",
|
"name": "Minecraft Forge",
|
||||||
"type": "ForgeHosted",
|
"type": "ForgeHosted",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 4823957,
|
"size": 4937218,
|
||||||
"MD5": "42d3aec7cdd6e4e49b4ff77a1db7c3a9",
|
"MD5": "5320593704af58b3906e7106d27e41c8",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/forge-1.12.2-14.23.2.2651-universal.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/forge-1.12.2-14.23.5.2797-universal.jar"
|
||||||
},
|
},
|
||||||
"subModules": [
|
"subModules": [
|
||||||
{
|
{
|
||||||
@ -1046,6 +1044,16 @@
|
|||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/jopt-simple-5.0.3.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/jopt-simple-5.0.3.jar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "org.apache.maven:maven-artifact:3.5.3",
|
||||||
|
"name": "maven-artifact",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 54961,
|
||||||
|
"MD5": "7741ebf29690ee7d9dde9cf4376347fc",
|
||||||
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/1.12.2/maven-artifact-3.5.3.jar"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "net.minecraftforge:MercuriusUpdater:1.12.2",
|
"id": "net.minecraftforge:MercuriusUpdater:1.12.2",
|
||||||
"name": "MercuriusUpdater",
|
"name": "MercuriusUpdater",
|
||||||
@ -1059,56 +1067,56 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "com.westeroscraft:westerosblocks:3.1.0-alpha-2-10",
|
"id": "com.westeroscraft:westerosblocks:4.0.0-beta-1-66",
|
||||||
"name": "WesterosBlocks",
|
"name": "WesterosBlocks",
|
||||||
"type": "ForgeMod",
|
"type": "ForgeMod",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 17261877,
|
"size": 17286330,
|
||||||
"MD5": "f23568619e6fc2bf0cdbdcc05b6a8af9",
|
"MD5": "e5c38ef42e9cc4b957122207dcf95f4f",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/WesterosBlocks.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/WesterosBlocks.jar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "com.westeroscraft:westeroscraftrp:2018-07-21",
|
"id": "com.westeroscraft:westeroscraftrp:2019-01-21",
|
||||||
"name": "WesterosCraft Resource Pack",
|
"name": "WesterosCraft Resource Pack",
|
||||||
"type": "File",
|
"type": "File",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 46944689,
|
"size": 47002312,
|
||||||
"MD5": "caa806fb84e56c6c230e56b17670f2bc",
|
"MD5": "86b169611ca0e51fdc47384d8423c402",
|
||||||
"path": "resourcepacks/WesterosCraft.zip",
|
"path": "resourcepacks/WesterosCraft.zip",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/resourcepacks/WesterosCraft.zip"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/resourcepacks/WesterosCraft.zip"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "net.optifine:optifine:1.12.2_HD_U_D1",
|
"id": "net.optifine:optifine:1.12.2_HD_U_E3",
|
||||||
"name": "Optifine",
|
"name": "Optifine",
|
||||||
"type": "ForgeMod",
|
"type": "ForgeMod",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 2372821,
|
"size": 2444057,
|
||||||
"MD5": "4bfc1c95dde8ec08568e01bfaa61e7c5",
|
"MD5": "7ec95c57ac1a224d6eb93bd370e4ac37",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/OptiFine.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/OptiFine.jar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "mezz:jei:1.12.2-4.8.5.151",
|
"id": "mezz:jei:1.12.2-4.14.3.242",
|
||||||
"name": "JustEnoughItems",
|
"name": "JustEnoughItems",
|
||||||
"type": "ForgeMod",
|
"type": "ForgeMod",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 545366,
|
"size": 620682,
|
||||||
"MD5": "7194b7b1f1ea6ad20013c596319db5b0",
|
"MD5": "ae6d0e6e873ef6c20f41097dc7fee8c6",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/jei.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/jei.jar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "org.blockartistry:dynsurround:1.12.2-3.4.9.3",
|
"id": "org.blockartistry:dynsurround:1.12.2-3.4.10.5",
|
||||||
"name": "DynamicSurroundings",
|
"name": "DynamicSurroundings",
|
||||||
"type": "ForgeMod",
|
"type": "ForgeMod",
|
||||||
"required": {
|
"required": {
|
||||||
"value": false
|
"value": false
|
||||||
},
|
},
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 22298474,
|
"size": 22507134,
|
||||||
"MD5": "115baf8e5f4e7d9757a2a85fb3507789",
|
"MD5": "3d75602c66b7ccfc23c342e8d5e07469",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/DynamicSurroundings.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/DynamicSurroundings.jar"
|
||||||
},
|
},
|
||||||
"subModules": [
|
"subModules": [
|
||||||
@ -1117,8 +1125,7 @@
|
|||||||
"name": "DynamicSurroundings General Configuration File",
|
"name": "DynamicSurroundings General Configuration File",
|
||||||
"type": "File",
|
"type": "File",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 21195,
|
"size": 22179,
|
||||||
"MD5": "850f1103765f45698954b4e3b0b0369d",
|
|
||||||
"path": "/config/dsurround/dsurround.cfg",
|
"path": "/config/dsurround/dsurround.cfg",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/config/dsurround/dsurround.cfg"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/config/dsurround/dsurround.cfg"
|
||||||
}
|
}
|
||||||
@ -1137,15 +1144,15 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "octarine-noise:betterfoliage:1.12-2.1.10",
|
"id": "octarine-noise:betterfoliage:1.12-2.2.0",
|
||||||
"name": "BetterFoliage",
|
"name": "BetterFoliage",
|
||||||
"type": "ForgeMod",
|
"type": "ForgeMod",
|
||||||
"required": {
|
"required": {
|
||||||
"value": false
|
"value": false
|
||||||
},
|
},
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 4671397,
|
"size": 926081,
|
||||||
"MD5": "06714eb2c13f59df5e3c92cec7370e11",
|
"MD5": "e05a720c5900b9bac4e01179abe8eef6",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/BetterFoliage.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/BetterFoliage.jar"
|
||||||
},
|
},
|
||||||
"subModules": [
|
"subModules": [
|
||||||
@ -1163,28 +1170,38 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "techbrew:journeymap:1.12.2-5.5.2",
|
"id": "forgelin:forgelin:1.8.2",
|
||||||
|
"name": "Forgelin",
|
||||||
|
"type": "ForgeMod",
|
||||||
|
"artifact": {
|
||||||
|
"size": 5124663,
|
||||||
|
"MD5": "59035365f7af35f599d1c4baade64d8b",
|
||||||
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/Forgelin.jar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "techbrew:journeymap:1.12.2-5.5.3",
|
||||||
"name": "JourneyMap",
|
"name": "JourneyMap",
|
||||||
"type": "ForgeMod",
|
"type": "ForgeMod",
|
||||||
"required": {
|
"required": {
|
||||||
"value": false
|
"value": false
|
||||||
},
|
},
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 1822850,
|
"size": 1831161,
|
||||||
"MD5": "b743562dac1b5334c20ac87b54c0b518",
|
"MD5": "d3b5a672d2393f9fe63903598d50c769",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/journeymap.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/journeymap.jar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "com.github.hexomod:worldeditcuife2:2.1.2-mf-1.12.2-14.23.0.2487",
|
"id": "com.github.hexomod:worldeditcuife2:2.2.0-mf-1.12.2-14.23.5.2768",
|
||||||
"name": "WorldEditCUI",
|
"name": "WorldEditCUI",
|
||||||
"type": "ForgeMod",
|
"type": "ForgeMod",
|
||||||
"required": {
|
"required": {
|
||||||
"value": false
|
"value": false
|
||||||
},
|
},
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 461808,
|
"size": 459294,
|
||||||
"MD5": "44b1b1031c25f04955bfd7ed734bd467",
|
"MD5": "2b8c1c3bc48c2d80b71daa658f656edb",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/worldeditcuife.jar"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/mods/worldeditcuife.jar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1219,14 +1236,14 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "com.sonicether:seus:11.0",
|
"id": "com.sonicether:seus-renewed:1.0.0",
|
||||||
"name": "Sonic Ether's Unbelievable Shaders",
|
"name": "Sonic Ether's Unbelievable Shaders Renewed",
|
||||||
"type": "File",
|
"type": "File",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 175159,
|
"size": 7062491,
|
||||||
"MD5": "bfa8c31d1da8131b59917bb2460205b1",
|
"MD5": "e68cc9f8ffc8fad66583b9b2cc05356f",
|
||||||
"path": "shaderpacks/SEUS v11.0.zip",
|
"path": "shaderpacks/SEUS-Renewed.zip",
|
||||||
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/shaderpacks/SEUS.zip"
|
"url": "http://mc.westeroscraft.com/WesterosCraftLauncher/test-1.12.2/shaderpacks/SEUS-Renewed.zip"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1251,6 +1268,317 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "WesterosCraftTest-1.13.2",
|
||||||
|
"name": "WesterosCraft 1.13.2 Test Server",
|
||||||
|
"description": "Tests for our version change to 1.13.2 are live here.",
|
||||||
|
"icon": "http://mc.westeroscraft.com/WesterosCraftLauncher/files/server-test.png",
|
||||||
|
"version": "5.0.0",
|
||||||
|
"address": "mc.westeroscraft.com:4445",
|
||||||
|
"minecraftVersion": "1.13.2",
|
||||||
|
"discord": {
|
||||||
|
"shortId": "1.13.2 Test Server",
|
||||||
|
"largeImageText": "WesterosCraft 1.13.2 Test Server",
|
||||||
|
"largeImageKey": "server-test"
|
||||||
|
},
|
||||||
|
"mainServer": false,
|
||||||
|
"autoconnect": false,
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"id": "net.minecraftforge:forge:1.13.2-25.0.219",
|
||||||
|
"name": "Minecraft Forge (base jar)",
|
||||||
|
"type": "ForgeHosted",
|
||||||
|
"artifact": {
|
||||||
|
"size": 159918,
|
||||||
|
"MD5": "c5a9c40711feb2b4ab0329c48256e30d",
|
||||||
|
"url": ""
|
||||||
|
},
|
||||||
|
"subModules": [
|
||||||
|
{
|
||||||
|
"id": "net.minecraftforge:forge:1.13.2-25.0.219:universal",
|
||||||
|
"name": "Minecraft Forge (universal jar)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 2343272 ,
|
||||||
|
"MD5": "338c495d266da44ac1955caf91cc14c4",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "net.minecraftforge:forge:1.13.2-25.0.219:client",
|
||||||
|
"name": "Minecraft Forge (client jar)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 3065519,
|
||||||
|
"MD5": "df63f7288a8118c9b8836a3e53827aac",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "net.minecraft:client:1.13.2:extra",
|
||||||
|
"name": "Minecraft Forge (client extra)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 9622361,
|
||||||
|
"MD5": "dc66e2219e3de6e7ca05847fd3c7746d",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "net.minecraft:client:1.13.2-20190213.203750:srg",
|
||||||
|
"name": "Minecraft Forge (client srg)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 8198877,
|
||||||
|
"MD5": "80f355cffe270d2c2943301b96d98fa6",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1.13.2-forge-25.0.219",
|
||||||
|
"name": "Minecraft Forge (version.json)",
|
||||||
|
"type": "VersionManifest",
|
||||||
|
"artifact": {
|
||||||
|
"size": 11511,
|
||||||
|
"MD5": "f13f9e20324d0a55c646d5fb8876b2bb",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "com.paulscode:soundsystem:201809301515",
|
||||||
|
"name": "Minecraft Forge (Paul's Code Soundsystem)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 66607,
|
||||||
|
"MD5": "bf43e7b9f628534614b3fc5e4e69d209",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "org.ow2.asm:asm:6.2",
|
||||||
|
"name": "Minecraft Forge (asm)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 111214,
|
||||||
|
"MD5": "7abdce94068615d690495f45eb6eb980",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "org.ow2.asm:asm-commons:6.2",
|
||||||
|
"name": "Minecraft Forge (asm-commons)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 78919,
|
||||||
|
"MD5": "a031c9a32770c02c2f91d2bcbeceabcd",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "org.ow2.asm:asm-tree:6.2",
|
||||||
|
"name": "Minecraft Forge (asm-tree)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 50370,
|
||||||
|
"MD5": "e7279981c6764dcd73a99705acf5c9a6",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cpw.mods:modlauncher:2.1.1",
|
||||||
|
"name": "Minecraft Forge (modlauncher)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 101586,
|
||||||
|
"MD5": "089ae3cf5afe10e96bb930b70dd99402",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cpw.mods:grossjava9hacks:1.1.0",
|
||||||
|
"name": "Minecraft Forge (Gross Java 9 Hacks)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 1759,
|
||||||
|
"MD5": "1adde27730734a786f461ed048d440a0",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "net.minecraftforge:accesstransformers:0.16.0-shadowed",
|
||||||
|
"name": "Minecraft Forge (Access Transformers)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 444428,
|
||||||
|
"MD5": "dbe346d662c7bdff0988f941342932e6",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "net.minecraftforge:eventbus:0.9.2-service",
|
||||||
|
"name": "Minecraft Forge (EventBus)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 38908,
|
||||||
|
"MD5": "77c6c079914a8369a185e11ae1a7a878",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "net.minecraftforge:forgespi:0.13.0",
|
||||||
|
"name": "Minecraft Forge (Forge SPI)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 16607,
|
||||||
|
"MD5": "5634ba7001f8501a2b553430e2068751",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "net.minecraftforge:coremods:0.5.0",
|
||||||
|
"name": "Minecraft Forge (coremods)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 12284,
|
||||||
|
"MD5": "d3da5879965cf6f37413d6d1a80d92a7",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "net.minecraftforge:unsafe:0.2.0",
|
||||||
|
"name": "Minecraft Forge (unsafe)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 2834,
|
||||||
|
"MD5": "2d1016ebe4c1a63dd50a59d26bd12db1",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "com.electronwill.night-config:core:3.6.0",
|
||||||
|
"name": "Minecraft Forge (night-config core)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 199763,
|
||||||
|
"MD5": "841adeca8f5e5e092c7bafb412dcb615",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "com.electronwill.night-config:toml:3.6.0",
|
||||||
|
"name": "Minecraft Forge (night-config toml)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 31256,
|
||||||
|
"MD5": "2d1d5f1fa4735b4e654613b493512432",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "org.jline:jline:3.9.0",
|
||||||
|
"name": "Minecraft Forge (jline)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 707273,
|
||||||
|
"MD5": "2db9aae140f810a72fc4a5cb5aa5cf9b",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "org.apache.maven:maven-artifact:3.6.0",
|
||||||
|
"name": "Minecraft Forge (maven-artifact)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 55051,
|
||||||
|
"MD5": "89e95013b11f347e48c0525965600404",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "net.jodah:typetools:0.6.0",
|
||||||
|
"name": "Minecraft Forge (typetools)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 14734,
|
||||||
|
"MD5": "9f65b6e90eb986fe25afc39b8ed3f43b",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "java3d:vecmath:1.5.2",
|
||||||
|
"name": "Minecraft Forge (vecmath)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 318956,
|
||||||
|
"MD5": "e5d2b7f46c4800a32f62ce75676a5710",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "org.apache.logging.log4j:log4j-api:2.11.2",
|
||||||
|
"name": "Minecraft Forge (log4j-api)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 266283,
|
||||||
|
"MD5": "3f7ee51e3dd0830de799dae0b90243dd",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "org.apache.logging.log4j:log4j-core:2.11.2",
|
||||||
|
"name": "Minecraft Forge (log4j-core)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 1629585,
|
||||||
|
"MD5": "c8bd8b5c5aaaa07a3dcbf57de01c9266",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "net.minecrell:terminalconsoleappender:1.1.1",
|
||||||
|
"name": "Minecraft Forge (terminalconsoleappender)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 15240,
|
||||||
|
"MD5": "a190e88073a41dfa1b1d47854c41230b",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "net.sf.jopt-simple:jopt-simple:5.0.4",
|
||||||
|
"name": "Minecraft Forge (jopt-simple)",
|
||||||
|
"type": "Library",
|
||||||
|
"artifact": {
|
||||||
|
"size": 78146,
|
||||||
|
"MD5": "eb0d9dffe9b0eddead68fe678be76c49",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "squeek:appleskin:1.0.11",
|
||||||
|
"name": "AppleSkin (test mod)",
|
||||||
|
"type": "ForgeMod",
|
||||||
|
"artifact": {
|
||||||
|
"size": 27382,
|
||||||
|
"MD5": "a1c0cfed77b2cb7b6699e59ba0252a50",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "com.blamejared:controlling:4.0.1",
|
||||||
|
"name": "Controlling (Test Mod)",
|
||||||
|
"type": "ForgeMod",
|
||||||
|
"artifact": {
|
||||||
|
"size": 34914,
|
||||||
|
"MD5": "b4dc10b9039518ce00845ca962e6a136",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
51
electron-builder.yml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
appId: 'helioslauncher'
|
||||||
|
productName: 'Helios Launcher'
|
||||||
|
artifactName: '${productName}-setup-${version}.${ext}'
|
||||||
|
|
||||||
|
copyright: 'Copyright © 2018-2022 Daniel Scalzi'
|
||||||
|
|
||||||
|
asar: true
|
||||||
|
compression: 'maximum'
|
||||||
|
|
||||||
|
files:
|
||||||
|
- '!{dist,.gitignore,.vscode,docs,dev-app-update.yml,.nvmrc,.eslintrc.json}'
|
||||||
|
|
||||||
|
extraResources:
|
||||||
|
- 'libraries'
|
||||||
|
|
||||||
|
# Windows Configuration
|
||||||
|
win:
|
||||||
|
target:
|
||||||
|
- target: 'nsis'
|
||||||
|
arch: 'x64'
|
||||||
|
|
||||||
|
# Windows Installer Configuration
|
||||||
|
nsis:
|
||||||
|
oneClick: false
|
||||||
|
perMachine: false
|
||||||
|
allowElevation: true
|
||||||
|
allowToChangeInstallationDirectory: true
|
||||||
|
|
||||||
|
# macOS Configuration
|
||||||
|
mac:
|
||||||
|
target:
|
||||||
|
- target: 'dmg'
|
||||||
|
arch:
|
||||||
|
- 'x64'
|
||||||
|
- 'arm64'
|
||||||
|
artifactName: '${productName}-setup-${version}-${arch}.${ext}'
|
||||||
|
category: 'public.app-category.games'
|
||||||
|
|
||||||
|
# Linux Configuration
|
||||||
|
linux:
|
||||||
|
target: 'AppImage'
|
||||||
|
maintainer: 'Daniel Scalzi'
|
||||||
|
vendor: 'Daniel Scalzi'
|
||||||
|
synopsis: 'Modded Minecraft Launcher'
|
||||||
|
description: 'Custom launcher which allows users to join modded servers. All mods, configurations, and updates are handled automatically.'
|
||||||
|
category: 'Game'
|
||||||
|
|
||||||
|
|
||||||
|
directories:
|
||||||
|
buildResources: 'build'
|
||||||
|
output: 'dist'
|
190
index.js
@ -1,13 +1,20 @@
|
|||||||
|
const remoteMain = require('@electron/remote/main')
|
||||||
|
remoteMain.initialize()
|
||||||
|
|
||||||
// Requirements
|
// Requirements
|
||||||
const {app, BrowserWindow, ipcMain} = require('electron')
|
const { app, BrowserWindow, ipcMain, Menu, shell } = require('electron')
|
||||||
const Menu = require('electron').Menu
|
const autoUpdater = require('electron-updater').autoUpdater
|
||||||
const autoUpdater = require('electron-updater').autoUpdater
|
const ejse = require('ejs-electron')
|
||||||
const ejse = require('ejs-electron')
|
const fs = require('fs')
|
||||||
const fs = require('fs')
|
const isDev = require('./app/assets/js/isdev')
|
||||||
const isDev = require('./app/assets/js/isdev')
|
const path = require('path')
|
||||||
const path = require('path')
|
const semver = require('semver')
|
||||||
const semver = require('semver')
|
const { pathToFileURL } = require('url')
|
||||||
const url = require('url')
|
const { AZURE_CLIENT_ID, MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR, SHELL_OPCODE } = require('./app/assets/js/ipcconstants')
|
||||||
|
const LangLoader = require('./app/assets/js/langloader')
|
||||||
|
|
||||||
|
// Setup Lang
|
||||||
|
LangLoader.setupLanguage()
|
||||||
|
|
||||||
// Setup auto updater.
|
// Setup auto updater.
|
||||||
function initAutoUpdater(event, data) {
|
function initAutoUpdater(event, data) {
|
||||||
@ -82,10 +89,137 @@ ipcMain.on('distributionIndexDone', (event, res) => {
|
|||||||
event.sender.send('distributionIndexDone', res)
|
event.sender.send('distributionIndexDone', res)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Handle trash item.
|
||||||
|
ipcMain.handle(SHELL_OPCODE.TRASH_ITEM, async (event, ...args) => {
|
||||||
|
try {
|
||||||
|
await shell.trashItem(args[0])
|
||||||
|
return {
|
||||||
|
result: true
|
||||||
|
}
|
||||||
|
} catch(error) {
|
||||||
|
return {
|
||||||
|
result: false,
|
||||||
|
error: error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Disable hardware acceleration.
|
// Disable hardware acceleration.
|
||||||
// https://electronjs.org/docs/tutorial/offscreen-rendering
|
// https://electronjs.org/docs/tutorial/offscreen-rendering
|
||||||
app.disableHardwareAcceleration()
|
app.disableHardwareAcceleration()
|
||||||
|
|
||||||
|
|
||||||
|
const REDIRECT_URI_PREFIX = 'https://login.microsoftonline.com/common/oauth2/nativeclient?'
|
||||||
|
|
||||||
|
// Microsoft Auth Login
|
||||||
|
let msftAuthWindow
|
||||||
|
let msftAuthSuccess
|
||||||
|
let msftAuthViewSuccess
|
||||||
|
let msftAuthViewOnClose
|
||||||
|
ipcMain.on(MSFT_OPCODE.OPEN_LOGIN, (ipcEvent, ...arguments_) => {
|
||||||
|
if (msftAuthWindow) {
|
||||||
|
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.ALREADY_OPEN, msftAuthViewOnClose)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msftAuthSuccess = false
|
||||||
|
msftAuthViewSuccess = arguments_[0]
|
||||||
|
msftAuthViewOnClose = arguments_[1]
|
||||||
|
msftAuthWindow = new BrowserWindow({
|
||||||
|
title: LangLoader.queryJS('index.microsoftLoginTitle'),
|
||||||
|
backgroundColor: '#222222',
|
||||||
|
width: 520,
|
||||||
|
height: 600,
|
||||||
|
frame: true,
|
||||||
|
icon: getPlatformIcon('SealCircle')
|
||||||
|
})
|
||||||
|
|
||||||
|
msftAuthWindow.on('closed', () => {
|
||||||
|
msftAuthWindow = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
msftAuthWindow.on('close', () => {
|
||||||
|
if(!msftAuthSuccess) {
|
||||||
|
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.NOT_FINISHED, msftAuthViewOnClose)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
msftAuthWindow.webContents.on('did-navigate', (_, uri) => {
|
||||||
|
if (uri.startsWith(REDIRECT_URI_PREFIX)) {
|
||||||
|
let queries = uri.substring(REDIRECT_URI_PREFIX.length).split('#', 1).toString().split('&')
|
||||||
|
let queryMap = {}
|
||||||
|
|
||||||
|
queries.forEach(query => {
|
||||||
|
const [name, value] = query.split('=')
|
||||||
|
queryMap[name] = decodeURI(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGIN, MSFT_REPLY_TYPE.SUCCESS, queryMap, msftAuthViewSuccess)
|
||||||
|
|
||||||
|
msftAuthSuccess = true
|
||||||
|
msftAuthWindow.close()
|
||||||
|
msftAuthWindow = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
msftAuthWindow.removeMenu()
|
||||||
|
msftAuthWindow.loadURL(`https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?prompt=select_account&client_id=${AZURE_CLIENT_ID}&response_type=code&scope=XboxLive.signin%20offline_access&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Microsoft Auth Logout
|
||||||
|
let msftLogoutWindow
|
||||||
|
let msftLogoutSuccess
|
||||||
|
let msftLogoutSuccessSent
|
||||||
|
ipcMain.on(MSFT_OPCODE.OPEN_LOGOUT, (ipcEvent, uuid, isLastAccount) => {
|
||||||
|
if (msftLogoutWindow) {
|
||||||
|
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.ALREADY_OPEN)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msftLogoutSuccess = false
|
||||||
|
msftLogoutSuccessSent = false
|
||||||
|
msftLogoutWindow = new BrowserWindow({
|
||||||
|
title: LangLoader.queryJS('index.microsoftLogoutTitle'),
|
||||||
|
backgroundColor: '#222222',
|
||||||
|
width: 520,
|
||||||
|
height: 600,
|
||||||
|
frame: true,
|
||||||
|
icon: getPlatformIcon('SealCircle')
|
||||||
|
})
|
||||||
|
|
||||||
|
msftLogoutWindow.on('closed', () => {
|
||||||
|
msftLogoutWindow = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
msftLogoutWindow.on('close', () => {
|
||||||
|
if(!msftLogoutSuccess) {
|
||||||
|
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.ERROR, MSFT_ERROR.NOT_FINISHED)
|
||||||
|
} else if(!msftLogoutSuccessSent) {
|
||||||
|
msftLogoutSuccessSent = true
|
||||||
|
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.SUCCESS, uuid, isLastAccount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
msftLogoutWindow.webContents.on('did-navigate', (_, uri) => {
|
||||||
|
if(uri.startsWith('https://login.microsoftonline.com/common/oauth2/v2.0/logoutsession')) {
|
||||||
|
msftLogoutSuccess = true
|
||||||
|
setTimeout(() => {
|
||||||
|
if(!msftLogoutSuccessSent) {
|
||||||
|
msftLogoutSuccessSent = true
|
||||||
|
ipcEvent.reply(MSFT_OPCODE.REPLY_LOGOUT, MSFT_REPLY_TYPE.SUCCESS, uuid, isLastAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msftLogoutWindow) {
|
||||||
|
msftLogoutWindow.close()
|
||||||
|
msftLogoutWindow = null
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
msftLogoutWindow.removeMenu()
|
||||||
|
msftLogoutWindow.loadURL('https://login.microsoftonline.com/common/oauth2/v2.0/logout')
|
||||||
|
})
|
||||||
|
|
||||||
// Keep a global reference of the window object, if you don't, the window will
|
// Keep a global reference of the window object, if you don't, the window will
|
||||||
// be closed automatically when the JavaScript object is garbage collected.
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
let win
|
let win
|
||||||
@ -95,7 +229,7 @@ function createWindow() {
|
|||||||
win = new BrowserWindow({
|
win = new BrowserWindow({
|
||||||
width: 980,
|
width: 980,
|
||||||
height: 552,
|
height: 552,
|
||||||
icon: getPlatformIcon('WesterosSealSquare'),
|
icon: getPlatformIcon('SealCircle'),
|
||||||
frame: false,
|
frame: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
preload: path.join(__dirname, 'app', 'assets', 'js', 'preloader.js'),
|
preload: path.join(__dirname, 'app', 'assets', 'js', 'preloader.js'),
|
||||||
@ -104,22 +238,23 @@ function createWindow() {
|
|||||||
},
|
},
|
||||||
backgroundColor: '#171614'
|
backgroundColor: '#171614'
|
||||||
})
|
})
|
||||||
|
remoteMain.enable(win.webContents)
|
||||||
|
|
||||||
ejse.data('bkid', Math.floor((Math.random() * fs.readdirSync(path.join(__dirname, 'app', 'assets', 'images', 'backgrounds')).length)))
|
const data = {
|
||||||
|
bkid: Math.floor((Math.random() * fs.readdirSync(path.join(__dirname, 'app', 'assets', 'images', 'backgrounds')).length)),
|
||||||
|
lang: (str, placeHolders) => LangLoader.queryEJS(str, placeHolders)
|
||||||
|
}
|
||||||
|
Object.entries(data).forEach(([key, val]) => ejse.data(key, val))
|
||||||
|
|
||||||
win.loadURL(url.format({
|
win.loadURL(pathToFileURL(path.join(__dirname, 'app', 'app.ejs')).toString())
|
||||||
pathname: path.join(__dirname, 'app', 'app.ejs'),
|
|
||||||
protocol: 'file:',
|
|
||||||
slashes: true
|
|
||||||
}))
|
|
||||||
|
|
||||||
/*win.once('ready-to-show', () => {
|
/*win.once('ready-to-show', () => {
|
||||||
win.show()
|
win.show()
|
||||||
})*/
|
})*/
|
||||||
|
|
||||||
win.setMenu(null)
|
win.removeMenu()
|
||||||
|
|
||||||
win.setResizable(true)
|
win.resizable = true
|
||||||
|
|
||||||
win.on('closed', () => {
|
win.on('closed', () => {
|
||||||
win = null
|
win = null
|
||||||
@ -191,16 +326,19 @@ function createMenu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPlatformIcon(filename){
|
function getPlatformIcon(filename){
|
||||||
const opSys = process.platform
|
let ext
|
||||||
if (opSys === 'darwin') {
|
switch(process.platform) {
|
||||||
filename = filename + '.icns'
|
case 'win32':
|
||||||
} else if (opSys === 'win32') {
|
ext = 'ico'
|
||||||
filename = filename + '.ico'
|
break
|
||||||
} else {
|
case 'darwin':
|
||||||
filename = filename + '.png'
|
case 'linux':
|
||||||
|
default:
|
||||||
|
ext = 'png'
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return path.join(__dirname, 'app', 'assets', 'images', filename)
|
return path.join(__dirname, 'app', 'assets', 'images', `${filename}.${ext}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.on('ready', createWindow)
|
app.on('ready', createWindow)
|
||||||
|
7272
package-lock.json
generated
110
package.json
@ -1,105 +1,51 @@
|
|||||||
{
|
{
|
||||||
"name": "westeroscraftlauncher",
|
"name": "helioslauncher",
|
||||||
"version": "1.5.1",
|
"version": "2.1.0",
|
||||||
"productName": "WesterosCraft Launcher",
|
"productName": "Helios Launcher",
|
||||||
"description": "Modded Minecraft Launcher",
|
"description": "Modded Minecraft Launcher",
|
||||||
"author": "Daniel Scalzi (https://github.com/dscalzi/)",
|
"author": "Daniel Scalzi (https://github.com/dscalzi/)",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"homepage": "https://github.com/WesterosCraftCode/ElectronLauncher",
|
"homepage": "https://github.com/dscalzi/HeliosLauncher",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/WesterosCraftCode/ElectronLauncher/issues"
|
"url": "https://github.com/dscalzi/HeliosLauncher/issues"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"cilinux": "electron-builder --linux --win",
|
"dist": "electron-builder build",
|
||||||
"cidarwin": "electron-builder --mac",
|
"dist:win": "npm run dist -- -w",
|
||||||
"dist": "cross-env ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES=true electron-builder",
|
"dist:mac": "npm run dist -- -m",
|
||||||
"dist:win": "npm run dist -- --win --x64",
|
"dist:linux": "npm run dist -- -l",
|
||||||
"dist:mac": "npm run dist -- --mac",
|
|
||||||
"dist:linux": "npm run dist -- --linux --x64",
|
|
||||||
"lint": "eslint --config .eslintrc.json ."
|
"lint": "eslint --config .eslintrc.json ."
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "10.x.x"
|
"node": "18.x.x"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.4.13",
|
"@electron/remote": "^2.1.0",
|
||||||
"async": "^2.6.1",
|
"adm-zip": "^0.5.9",
|
||||||
"discord-rpc": "github:discordjs/RPC",
|
"discord-rpc-patch": "^4.0.1",
|
||||||
"ejs": "^2.6.1",
|
"ejs": "^3.1.9",
|
||||||
"ejs-electron": "^2.0.3",
|
"ejs-electron": "^2.1.1",
|
||||||
"electron-updater": "^4.0.6",
|
"electron-updater": "^6.1.7",
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^11.1.1",
|
||||||
"github-syntax-dark": "^0.5.0",
|
"github-syntax-dark": "^0.5.0",
|
||||||
"jquery": "^3.3.1",
|
"got": "^11.8.5",
|
||||||
"request": "^2.88.0",
|
"helios-core": "~2.1.0",
|
||||||
"semver": "^5.6.0",
|
"helios-distribution-types": "^1.3.0",
|
||||||
"tar-fs": "^2.0.0",
|
"jquery": "^3.7.1",
|
||||||
"winreg": "^1.2.4"
|
"lodash.merge": "^4.6.2",
|
||||||
|
"semver": "^7.5.4",
|
||||||
|
"toml": "^3.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cross-env": "^5.2.0",
|
"electron": "^27.1.3",
|
||||||
"electron": "^4.0.4",
|
"electron-builder": "^24.9.1",
|
||||||
"electron-builder": "^20.38.5",
|
"eslint": "^8.55.0"
|
||||||
"eslint": "^5.13.0"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/WesterosCraftCode/ElectronLauncher.git"
|
"url": "git+https://github.com/dscalzi/HeliosLauncher.git"
|
||||||
},
|
|
||||||
"build": {
|
|
||||||
"appId": "westeroscraftlauncher",
|
|
||||||
"productName": "WesterosCraft Launcher",
|
|
||||||
"artifactName": "${productName}.${ext}",
|
|
||||||
"copyright": "Copyright © 2018 WesterosCraft",
|
|
||||||
"directories": {
|
|
||||||
"buildResources": "build",
|
|
||||||
"output": "dist"
|
|
||||||
},
|
|
||||||
"win": {
|
|
||||||
"target": [
|
|
||||||
{
|
|
||||||
"target": "nsis",
|
|
||||||
"arch": "x64"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"icon": "build/icon.ico"
|
|
||||||
},
|
|
||||||
"nsis": {
|
|
||||||
"oneClick": false,
|
|
||||||
"perMachine": true,
|
|
||||||
"allowElevation": true,
|
|
||||||
"installerIcon": "build/icon.ico",
|
|
||||||
"uninstallerIcon": "build/icon.ico",
|
|
||||||
"allowToChangeInstallationDirectory": true
|
|
||||||
},
|
|
||||||
"mac": {
|
|
||||||
"target": "dmg",
|
|
||||||
"category": "public.app-category.games",
|
|
||||||
"icon": "build/icon.icns"
|
|
||||||
},
|
|
||||||
"linux": {
|
|
||||||
"target": "AppImage",
|
|
||||||
"maintainer": "Daniel Scalzi",
|
|
||||||
"vendor": "WesterosCraft",
|
|
||||||
"synopsis": "Custom Launcher for WesterosCraft",
|
|
||||||
"description": "Custom launcher which allows users to join WesterosCraft. All mods, configurations, and updates are handled automatically.",
|
|
||||||
"category": "Game"
|
|
||||||
},
|
|
||||||
"deb": {
|
|
||||||
"compression": "xz",
|
|
||||||
"packageCategory": "Games",
|
|
||||||
"priority": "optional"
|
|
||||||
},
|
|
||||||
"compression": "maximum",
|
|
||||||
"files": [
|
|
||||||
"!{dist,.gitignore,.vscode,docs,dev-app-update.yml,.travis.yml,.nvmrc,.eslintrc.json}"
|
|
||||||
],
|
|
||||||
"extraResources": [
|
|
||||||
"libraries"
|
|
||||||
],
|
|
||||||
"asar": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|