Merge branch 'master' into pr/234

This commit is contained in:
IkyMax 2024-04-09 13:25:52 -06:00
commit 46da9fee1a
56 changed files with 16528 additions and 4860 deletions

View File

@ -6,21 +6,24 @@ jobs:
release: release:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions:
contents: write
strategy: strategy:
matrix: matrix:
os: [macos-latest, ubuntu-latest, windows-latest] os: [windows-latest]
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v1 uses: actions/checkout@v3
- name: Set up Node - name: Set up Node
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 20
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v4
with: with:
python-version: 3.x python-version: 3.x
@ -30,6 +33,6 @@ jobs:
- name: Build - name: Build
env: env:
GH_TOKEN: ${{ secrets.github_token }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm run dist run: npm run dist
shell: bash shell: bash

2
.nvmrc
View File

@ -1 +1 @@
18 20

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2017-2022 Daniel D. Scalzi Copyright (c) 2024 Iky Max
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

12706
LurkaryaSMP.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -84,7 +84,7 @@ This section details the setup of a basic developmentment environment.
**System Requirements** **System Requirements**
* [Node.js][nodejs] v18 * [Node.js][nodejs] v20
--- ---

View File

@ -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' 'sha256-In6B8teKZQll5heMl9bS7CESTbGvuAt3VVV86BUQBDk='"/> <meta charset="utf-8" http-equiv="Content-Security-Policy" content="script-src 'self' 'sha256-In6B8teKZQll5heMl9bS7CESTbGvuAt3VVV86BUQBDk='"/>
<title>Helios 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">
@ -45,11 +45,5 @@
</div> </div>
</div> </div>
</div> </div>
<script>
// Load language
for(let key of Object.keys(Lang.query('html'))){
document.getElementById(key).innerHTML = Lang.query(`html.${key}`)
}
</script>
</body> </body>
</html> </html>

View File

@ -3772,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. */

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 MiB

After

Width:  |  Height:  |  Size: 196 KiB

View 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

View File

@ -1,5 +1,46 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 9.677 9.667"> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<path d="M-26.332-12.098h2.715c-1.357.18-2.574 1.23-2.715 2.633z" fill="#fff" /> <!-- Created with Inkscape (http://www.inkscape.org/) -->
<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
</svg> width="1023.9999"
height="1023.9999"
viewBox="0 0 270.93333 270.93333"
version="1.1"
id="svg5"
xml:space="preserve"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="drawing.svg"
inkscape:export-filename="../public/logo.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.63895401"
inkscape:cx="477.34265"
inkscape:cy="486.733"
inkscape:window-width="1920"
inkscape:window-height="1056"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs
id="defs2" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
id="path8932"
style="fill:#ffffff;stroke-width:0.0715114"
d="M 134.21267,4.2600602 C 125.23724,4.3758509 120.0886,5.4813466 110.29201,9.3960126 93.27488,16.195963 77.776387,32.264168 68.884618,52.324813 62.265715,67.257658 58.723523,85.085427 58.812795,103.01853 l 0.01767,3.56838 v 0 c 1.389767,15.89728 3.684909,30.98331 8.065323,44.51911 0.04047,-0.0263 -0.0014,-0.001 0,0 0.02568,0.0257 0.41725,1.10929 0.870432,2.40722 6.108926,17.49604 16.632095,36.22208 28.486568,50.88866 13.221442,-6.80419 25.135912,-14.44158 34.500942,-23.94629 -7.03124,-8.32506 -14.13235,-19.42843 -18.91524,-29.74178 -2.5619,-5.5244 -6.28843,-15.2863 -6.56443,-17.19547 -0.002,-0.0142 -0.0136,0.0163 0,0 -4.14754,-14.30628 -5.21288,-29.13861 -3.98625,-42.331461 l 0.0184,-0.09391 c -0.0172,-0.193535 0.31727,-1.893447 0.61836,-3.688772 2.80368,-16.718487 9.62748,-29.384142 19.88086,-36.901326 2.10322,-1.541947 6.53715,-3.470181 9.54683,-4.151514 0.76199,-0.172484 2.38098,-0.258674 4.00103,-0.258792 1.61997,-1.17e-4 3.24131,0.08583 4.00843,0.258224 3.14386,0.706491 7.39719,2.558724 9.63575,4.195974 9.43695,6.902079 16.18451,18.744096 19.17175,33.398504 15.6112,3.012882 30.67721,8.408127 43.72855,15.0037 -0.16316,-4.645636 -0.59311,-10.151667 -1.1686,-14.265518 C 207.61514,62.424711 199.94063,44.022192 187.96006,30.088038 179.02804,19.699507 169.72405,12.883602 158.84816,8.7604439 149.20169,5.1033757 143.90155,4.1350776 134.21269,4.2600737 Z M 133.52179,88.02737 c -8.91018,0.08627 -17.29227,0.779465 -25.52413,2.096559 -0.59029,14.855551 0.18225,28.985851 3.82487,41.818971 7.60759,-1.34911 15.3315,-2.03896 23.04738,-2.0675 7.72797,-0.0286 15.44835,0.60517 23.03483,1.90218 3.78465,0.64706 7.60961,1.47129 7.55742,1.62857 -0.001,0.005 -0.0354,0.12029 -0.0393,0.13339 14.35951,3.59529 27.61358,10.0129 38.35033,17.64236 l -0.0111,0.0929 c 0.0841,-0.1968 0.13884,-0.25899 0.19893,-0.20691 0.0647,0.0561 0.66526,0.53049 1.33387,1.05397 13.50171,10.572 21.55838,22.81029 23.50682,35.70712 0.55135,3.64942 -0.0976,9.55982 -1.36863,12.46081 -2.01619,4.60176 -4.48377,7.49693 -9.05206,10.62247 -3.72363,2.54764 -9.72657,4.41792 -16.26858,5.06811 -6.78727,0.67459 -14.78442,-0.26451 -22.88378,-2.68653 -0.69873,-0.20895 -1.49852,-0.46453 -2.33198,-0.74218 -10.42054,12.00396 -22.65751,22.3449 -34.94841,30.36025 5.09018,2.7286 11.09025,5.49107 16.29196,7.43146 12.7374,4.75132 23.71802,7.12304 35.43408,7.65319 19.09428,0.86402 39.25372,-4.90942 51.41652,-14.72495 6.36162,-5.13398 8.59248,-7.24728 11.63654,-11.02434 2.13014,-2.64306 3.11754,-4.16549 6.56502,-10.11914 3.8399,-6.63139 6.34012,-15.147 7.32941,-24.9638 0.8637,-8.57047 -0.0564,-17.00095 -2.91112,-26.66704 -0.49368,-1.67158 -0.91435,-3.10383 -0.93486,-3.18248 -0.46135,-1.76553 -2.96953,-7.87374 -4.5511,-11.08419 -7.5165,-15.25769 -20.58567,-29.94489 -36.74912,-41.29796 -3.89837,-2.73809 -9.10569,-5.9562 -18.58699,-10.63431 -9.48128,-4.678131 -23.67244,-10.148842 -30.64136,-11.562707 -6.96892,-1.413855 -7.49314,-1.525726 -8.10278,-1.658069 -6.63464,-1.440366 -17.82492,-2.700618 -26.28056,-2.959585 -1.51327,-0.04634 -3.00952,-0.07641 -4.49011,-0.09059 -1.29554,-0.01245 -2.57937,-0.01233 -3.85225,0 z M 52.216432,110.39641 C 38.649057,118.70389 25.927419,130.09808 17.315905,141.78499 4.2298574,159.54437 -2.100977,180.8513 0.31767227,198.99413 1.9069293,210.91556 4.3854089,217.75692 10.689899,227.62321 c 5.733819,8.97319 16.037789,17.78908 26.640825,22.79371 9.272747,4.37667 21.59415,7.24683 32.628966,7.60074 20.500334,0.65753 43.91027,-5.89812 63.50224,-17.78315 1.00296,-0.60841 1.84565,-1.11846 1.87311,-1.13379 0.005,-0.002 0.0812,0.0471 0.0986,0.053 13.1604,-9.0775 25.05096,-18.61542 34.63489,-29.1552 l -0.21603,-0.0963 0.52385,-0.5911 c 12.37825,-13.97372 24.23134,-33.79183 31.10071,-51.69811 -12.55715,-7.96939 -25.169,-14.401 -38.0961,-17.70678 -5.36041,15.0597 -15.40558,31.91315 -26.28056,43.78498 -0.57646,0.6293 -1.2077,1.29903 -1.40284,1.48835 l -5.59207,5.71218 c -5.23693,5.36847 -17.24776,13.58618 -29.25317,19.01564 0.0262,0.0343 0,-0.005 0,0 0,0.0505 -1.495448,0.65823 -3.503959,1.42337 -6.756776,2.57394 -14.284581,4.29457 -20.631016,4.7164 -7.580415,0.50385 -14.655376,-0.58466 -20.901782,-3.21667 -2.613623,-1.10128 -5.354691,-3.02841 -8.085275,-5.68318 -1.541871,-1.49905 -3.426669,-4.44338 -4.545403,-7.1014 -0.904199,-2.1483 -1.714479,-7.86262 -1.472382,-10.3859 1.154209,-12.03022 7.818634,-23.6047 19.504074,-33.96 -5.234404,-14.98823 -8.139632,-30.70791 -9.00017,-45.30352 z"
sodipodi:nodetypes="csscccccscccsccccssssscccssscscssscccscssssscccssssssccccccssscsssssssccccccsccccssssssccsss" /></g></svg>

Before

Width:  |  Height:  |  Size: 664 B

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1,74 +0,0 @@
let target = require('./assetguard')[process.argv[2]]
if(target == null){
process.send({context: 'error', data: null, error: 'Invalid class name'})
console.error('Invalid class name passed to argv[2], cannot continue.')
process.exit(1)
}
let tracker = new target(...(process.argv.splice(3)))
const { LoggerUtil } = require('helios-core')
const logger = LoggerUtil.getLogger('AssetExec')
//const tracker = new AssetGuard(process.argv[2], process.argv[3])
logger.info('AssetExec Started')
// Temporary for debug purposes.
process.on('unhandledRejection', r => console.log(r))
let percent = 0
function assignListeners(){
tracker.on('validate', (data) => {
process.send({context: 'validate', data})
})
tracker.on('progress', (data, acc, total) => {
const currPercent = parseInt((acc/total) * 100)
if (currPercent !== percent) {
percent = currPercent
process.send({context: 'progress', data, value: acc, total, percent})
}
})
tracker.on('complete', (data, ...args) => {
process.send({context: 'complete', data, args})
})
tracker.on('error', (data, error) => {
process.send({context: 'error', data, error})
})
}
assignListeners()
process.on('message', (msg) => {
if(msg.task === 'execute'){
const func = msg.function
let nS = tracker[func] // Nonstatic context
let iS = target[func] // Static context
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 || err, context: func})
})
} else {
process.send({result: res, context: func})
}
} else {
process.send({context: 'error', data: null, error: `Function ${func} not found on ${process.argv[2]}`})
}
} else if(msg.task === 'changeContext'){
target = require('./assetguard')[msg.class]
if(target == null){
process.send({context: 'error', data: null, error: `Invalid class ${msg.class}`})
} else {
tracker = new target(...(msg.args))
assignListeners()
}
}
})
process.on('disconnect', () => {
logger.info('AssetExec Disconnected')
process.exit(0)
})

File diff suppressed because it is too large Load Diff

View File

@ -10,14 +10,124 @@
*/ */
// Requirements // Requirements
const ConfigManager = require('./configmanager') const ConfigManager = require('./configmanager')
const { LoggerUtil } = require('helios-core') const { LoggerUtil } = require('limbo-core')
const { RestResponseStatus } = require('helios-core/common') const { RestResponseStatus } = require('limbo-core/common')
const { MojangRestAPI, mojangErrorDisplayable, MojangErrorCode } = require('helios-core/mojang') const { MojangRestAPI, MojangErrorCode } = require('limbo-core/mojang')
const { MicrosoftAuth, microsoftErrorDisplayable, MicrosoftErrorCode } = require('helios-core/microsoft') const { MicrosoftAuth, MicrosoftErrorCode } = require('limbo-core/microsoft')
const { AZURE_CLIENT_ID } = require('./ipcconstants') const { AZURE_CLIENT_ID } = require('./ipcconstants')
const Lang = require('./langloader')
const log = LoggerUtil.getLogger('AuthManager') const log = LoggerUtil.getLogger('AuthManager')
// Error messages
function microsoftErrorDisplayable(errorCode) {
switch (errorCode) {
case MicrosoftErrorCode.NO_PROFILE:
return {
title: Lang.queryJS('auth.microsoft.error.noProfileTitle'),
desc: Lang.queryJS('auth.microsoft.error.noProfileDesc')
}
case MicrosoftErrorCode.NO_XBOX_ACCOUNT:
return {
title: Lang.queryJS('auth.microsoft.error.noXboxAccountTitle'),
desc: Lang.queryJS('auth.microsoft.error.noXboxAccountDesc')
}
case MicrosoftErrorCode.XBL_BANNED:
return {
title: Lang.queryJS('auth.microsoft.error.xblBannedTitle'),
desc: Lang.queryJS('auth.microsoft.error.xblBannedDesc')
}
case MicrosoftErrorCode.UNDER_18:
return {
title: Lang.queryJS('auth.microsoft.error.under18Title'),
desc: Lang.queryJS('auth.microsoft.error.under18Desc')
}
case MicrosoftErrorCode.UNKNOWN:
return {
title: Lang.queryJS('auth.microsoft.error.unknownTitle'),
desc: Lang.queryJS('auth.microsoft.error.unknownDesc')
}
}
}
function mojangErrorDisplayable(errorCode) {
switch(errorCode) {
case MojangErrorCode.ERROR_METHOD_NOT_ALLOWED:
return {
title: Lang.queryJS('auth.mojang.error.methodNotAllowedTitle'),
desc: Lang.queryJS('auth.mojang.error.methodNotAllowedDesc')
}
case MojangErrorCode.ERROR_NOT_FOUND:
return {
title: Lang.queryJS('auth.mojang.error.notFoundTitle'),
desc: Lang.queryJS('auth.mojang.error.notFoundDesc')
}
case MojangErrorCode.ERROR_USER_MIGRATED:
return {
title: Lang.queryJS('auth.mojang.error.accountMigratedTitle'),
desc: Lang.queryJS('auth.mojang.error.accountMigratedDesc')
}
case MojangErrorCode.ERROR_INVALID_CREDENTIALS:
return {
title: Lang.queryJS('auth.mojang.error.invalidCredentialsTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidCredentialsDesc')
}
case MojangErrorCode.ERROR_RATELIMIT:
return {
title: Lang.queryJS('auth.mojang.error.tooManyAttemptsTitle'),
desc: Lang.queryJS('auth.mojang.error.tooManyAttemptsDesc')
}
case MojangErrorCode.ERROR_INVALID_TOKEN:
return {
title: Lang.queryJS('auth.mojang.error.invalidTokenTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidTokenDesc')
}
case MojangErrorCode.ERROR_ACCESS_TOKEN_HAS_PROFILE:
return {
title: Lang.queryJS('auth.mojang.error.tokenHasProfileTitle'),
desc: Lang.queryJS('auth.mojang.error.tokenHasProfileDesc')
}
case MojangErrorCode.ERROR_CREDENTIALS_MISSING:
return {
title: Lang.queryJS('auth.mojang.error.credentialsMissingTitle'),
desc: Lang.queryJS('auth.mojang.error.credentialsMissingDesc')
}
case MojangErrorCode.ERROR_INVALID_SALT_VERSION:
return {
title: Lang.queryJS('auth.mojang.error.invalidSaltVersionTitle'),
desc: Lang.queryJS('auth.mojang.error.invalidSaltVersionDesc')
}
case MojangErrorCode.ERROR_UNSUPPORTED_MEDIA_TYPE:
return {
title: Lang.queryJS('auth.mojang.error.unsupportedMediaTypeTitle'),
desc: Lang.queryJS('auth.mojang.error.unsupportedMediaTypeDesc')
}
case MojangErrorCode.ERROR_GONE:
return {
title: Lang.queryJS('auth.mojang.error.accountGoneTitle'),
desc: Lang.queryJS('auth.mojang.error.accountGoneDesc')
}
case MojangErrorCode.ERROR_UNREACHABLE:
return {
title: Lang.queryJS('auth.mojang.error.unreachableTitle'),
desc: Lang.queryJS('auth.mojang.error.unreachableDesc')
}
case MojangErrorCode.ERROR_NOT_PAID:
return {
title: Lang.queryJS('auth.mojang.error.gameNotPurchasedTitle'),
desc: Lang.queryJS('auth.mojang.error.gameNotPurchasedDesc')
}
case MojangErrorCode.UNKNOWN:
return {
title: Lang.queryJS('auth.mojang.error.unknownErrorTitle'),
desc: Lang.queryJS('auth.mojang.error.unknownErrorDesc')
}
default:
throw new Error(`Unknown error code: ${errorCode}`)
}
}
// Functions // Functions
/** /**

View File

@ -1,16 +1,15 @@
const fs = require('fs-extra') const fs = require('fs-extra')
const { LoggerUtil } = require('helios-core') const { LoggerUtil } = require('limbo-core')
const os = require('os') const os = require('os')
const path = require('path') const path = require('path')
const logger = LoggerUtil.getLogger('ConfigManager') 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)
// TODO change
const dataPath = path.join(sysRoot, '.helioslauncher')
// Forked processes do not have access to electron, so we have this workaround. const dataPath = path.join(sysRoot, '.limbostudios')
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.
@ -44,45 +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
exports.getAbsoluteMaxRAM = function(){ const mem = os.totalmem()
const mem = os.totalmem() return mem >= (6*1073741824) ? 3 : 2
const gT16 = mem-16000000000 }
return Math.floor((mem-1000000000-(gT16 > 0 ? (Number.parseInt(gT16/8) + 16000000000/4) : mem/4))/1000000000) }
}
exports.getAbsoluteMaxRAM = function(ram){
function resolveMaxRAM(){ const mem = os.totalmem()
const mem = os.totalmem() const gT16 = mem-(16*1073741824)
return mem >= 8000000000 ? '4G' : (mem >= 6000000000 ? '3G' : '2G') return Math.floor((mem-(gT16 > 0 ? (Number.parseInt(gT16/8) + (16*1073741824)/4) : mem/4))/1073741824)
} }
function resolveMinRAM(){ function resolveSelectedRAM(ram) {
return resolveMaxRAM() if(ram?.recommended != null) {
} return `${ram.recommended}M`
} else {
/** // Legacy behavior
* TODO Copy pasted, should be in a utility file. const mem = os.totalmem()
* return mem >= (8*1073741824) ? '4G' : (mem >= (6*1073741824) ? '3G' : '2G')
* Returns true if the actual version is greater than
* or equal to the desired version.
*
* @param {string} desired The desired version.
* @param {string} actual The actual version.
*/
function mcVersionAtLeast(desired, actual){
const des = desired.split('.')
const act = actual.split('.')
for(let i=0; i<des.length; i++){
if(!(parseInt(act[i]) >= parseInt(des[i]))){
return false
}
} }
return true
} }
/** /**
@ -289,6 +273,10 @@ exports.getClientToken = function(){
exports.setClientToken = function(clientToken){ exports.setClientToken = function(clientToken){
config.clientToken = clientToken config.clientToken = clientToken
} }
/**
*
* @param {string} PlayerMeta
*/
/** /**
* Retrieve the ID of the selected serverpack. * Retrieve the ID of the selected serverpack.
@ -523,18 +511,18 @@ exports.setModConfiguration = function(serverid, configuration){
// Java Settings // Java Settings
function defaultJavaConfig(mcVersion) { function defaultJavaConfig(effectiveJavaOptions, ram) {
if(mcVersionAtLeast('1.17', mcVersion)) { if(effectiveJavaOptions.suggestedMajor > 8) {
return defaultJavaConfig117() return defaultJavaConfig17(ram)
} else { } else {
return defaultJavaConfigBelow117() return defaultJavaConfig8(ram)
} }
} }
function defaultJavaConfigBelow117() { function defaultJavaConfig8(ram) {
return { return {
minRAM: resolveMinRAM(), minRAM: resolveSelectedRAM(ram),
maxRAM: resolveMaxRAM(), // Dynamic maxRAM: resolveSelectedRAM(ram),
executable: null, executable: null,
jvmOptions: [ jvmOptions: [
'-XX:+UseConcMarkSweepGC', '-XX:+UseConcMarkSweepGC',
@ -545,10 +533,10 @@ function defaultJavaConfigBelow117() {
} }
} }
function defaultJavaConfig117() { function defaultJavaConfig17(ram) {
return { return {
minRAM: resolveMinRAM(), minRAM: resolveSelectedRAM(ram),
maxRAM: resolveMaxRAM(), // Dynamic maxRAM: resolveSelectedRAM(ram),
executable: null, executable: null,
jvmOptions: [ jvmOptions: [
'-XX:+UnlockExperimentalVMOptions', '-XX:+UnlockExperimentalVMOptions',
@ -567,9 +555,9 @@ function defaultJavaConfig117() {
* @param {string} serverid The server id. * @param {string} serverid The server id.
* @param {*} mcVersion The minecraft version of the server. * @param {*} mcVersion The minecraft version of the server.
*/ */
exports.ensureJavaConfig = function(serverid, mcVersion) { exports.ensureJavaConfig = function(serverid, effectiveJavaOptions, ram) {
if(!Object.prototype.hasOwnProperty.call(config.javaConfig, serverid)) { if(!Object.prototype.hasOwnProperty.call(config.javaConfig, serverid)) {
config.javaConfig[serverid] = defaultJavaConfig(mcVersion) config.javaConfig[serverid] = defaultJavaConfig(effectiveJavaOptions, ram)
} }
} }
@ -668,6 +656,7 @@ exports.setJVMOptions = function(serverid, jvmOptions){
config.javaConfig[serverid].jvmOptions = jvmOptions config.javaConfig[serverid].jvmOptions = jvmOptions
} }
// Game Settings // Game Settings
/** /**

View File

@ -1,19 +1,21 @@
// Work in progress // Work in progress
const { LoggerUtil } = require('helios-core') const { LoggerUtil } = require('limbo-core')
const logger = LoggerUtil.getLogger('DiscordWrapper') const logger = LoggerUtil.getLogger('DiscordWrapper')
const { Client } = require('discord-rpc-patch') 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,

View File

@ -1,621 +1,17 @@
const fs = require('fs') const { DistributionAPI } = require('limbo-core/common')
const path = require('path')
const request = require('request')
const { LoggerUtil } = require('helios-core')
const ConfigManager = require('./configmanager') const ConfigManager = require('./configmanager')
const logger = LoggerUtil.getLogger('DistroManager') // Old WesterosCraft url.
// exports.REMOTE_DISTRO_URL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json'
/** exports.REMOTE_DISTRO_URL = 'https://raw.githubusercontent.com/Limbo-Studios/LimboLauncher/master/LurkaryaSMP.json'
* Represents the download information
* for a specific module. const api = new DistributionAPI(
*/ ConfigManager.getLauncherDirectory(),
class Artifact { null, // Injected forcefully by the preloader.
null, // Injected forcefully by the preloader.
/** exports.REMOTE_DISTRO_URL,
* Parse a JSON object into an Artifact. false
* )
* @param {Object} json A JSON object representing an Artifact.
* exports.DistroAPI = api
* @returns {Artifact} The parsed Artifact.
*/
static fromJSON(json){
return Object.assign(new Artifact(), json)
}
/**
* 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.classpath, 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, classpath, required, artifact, subModules, serverid) {
this.identifier = id
this.type = type
this.classpath = classpath
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.artifactClassifier = m1[3] || undefined
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.artifactClassifier != undefined ? `-${this.artifactClassifier}` : ''}.${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.VersionManifest:
this.artifact.path = path.join(ConfigManager.getCommonDirectory(), 'versions', this.getIdentifier(), `${this.getIdentifier()}.json`)
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 classifier of this module's artifact
*/
getClassifier(){
return this.artifactClassifier
}
/**
* @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
}
/**
* @returns {boolean} Whether or not this library should be on the classpath.
*/
getClasspath(){
return this.classpath ?? true
}
}
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',
VersionManifest: 'VersionManifest'
}
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)
return
}
fs.writeFile(distroDest, body, 'utf-8', (err) => {
if(!err){
resolve(data)
return
} else {
reject(err)
return
}
})
} else {
reject(error)
return
}
})
})
}
/**
* @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)
return
} else {
reject(err)
return
}
})
})
}
exports.setDevMode = function(value){
if(value){
logger.info('Developer mode enabled.')
logger.info('If you don\'t know what that means, revert immediately.')
} else {
logger.info('Developer mode disabled.')
}
DEV_MODE = value
}
exports.isDevMode = function(){
return DEV_MODE
}
/**
* @returns {DistroIndex}
*/
exports.getDistribution = function(){
return data
}

View File

@ -1,7 +1,8 @@
// NOTE FOR THIRD-PARTY // NOTE FOR THIRD-PARTY
// REPLACE THIS CLIENT ID WITH YOUR APPLICATION ID. // REPLACE THIS CLIENT ID WITH YOUR APPLICATION ID.
// SEE https://github.com/dscalzi/HeliosLauncher/blob/master/docs/MicrosoftAuth.md // SEE https://github.com/dscalzi/HeliosLauncher/blob/master/docs/MicrosoftAuth.md
exports.AZURE_CLIENT_ID = '1ce6e35a-126f-48fd-97fb-54d143ac6d45' //exports.AZURE_CLIENT_ID = 'c8f958a0-3a28-44f4-88da-ae2bdcb64788'
exports.AZURE_CLIENT_ID = '52a394c2-2284-4a97-ad09-3d6b46bdf5e4'
// SEE NOTE ABOVE. // SEE NOTE ABOVE.

View File

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

View File

@ -4,9 +4,11 @@ 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 LangLoader = require('./langloader') const LangLoader = require('./langloader')
const { LoggerUtil } = require('helios-core') const { LoggerUtil } = require('limbo-core')
// eslint-disable-next-line no-unused-vars
const { HeliosDistribution } = require('limbo-core/common')
const logger = LoggerUtil.getLogger('Preloader') const logger = LoggerUtil.getLogger('Preloader')
@ -15,16 +17,25 @@ logger.info('Loading..')
// Load ConfigManager // Load ConfigManager
ConfigManager.load() ConfigManager.load()
// Load Strings // Yuck!
LangLoader.loadLanguage('en_US') // 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.info('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()
} }
} }
@ -32,35 +43,20 @@ function onDistroLoad(data){
} }
// Ensure Distribution is downloaded and cached. // Ensure Distribution is downloaded and cached.
DistroManager.pullRemote().then((data) => { DistroAPI.getDistribution()
logger.info('Loaded distribution index.') .then(heliosDistro => {
logger.info('Loaded distribution index.')
onDistroLoad(data)
}).catch((err) => {
logger.info('Failed to load distribution index.')
logger.error(err)
logger.info('Attempting to load an older version of the distribution index.')
// Try getting a local copy, better than nothing.
DistroManager.pullLocal().then((data) => {
logger.info('Successfully loaded an older version of the distribution index.')
onDistroLoad(data)
}).catch((err) => {
onDistroLoad(heliosDistro)
})
.catch(err => {
logger.info('Failed to load an older version of the distribution index.') logger.info('Failed to load an older version of the distribution index.')
logger.info('Application cannot run.') logger.info('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){

View File

@ -2,25 +2,34 @@ 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 { LoggerUtil } = require('limbo-core')
const { getMojangOS, isLibraryCompatible, mcVersionAtLeast } = require('limbo-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 { Util, Library } = require('./assetguard')
const ConfigManager = require('./configmanager') const ConfigManager = require('./configmanager')
const DistroManager = require('./distromanager')
const logger = LoggerUtil.getLogger('ProcessBuilder') 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, launcherVersion){ 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.launcherVersion = launcherVersion
this.forgeModListFile = path.join(this.gameDir, 'forgeMods.list') // 1.13+ this.forgeModListFile = path.join(this.gameDir, 'forgeMods.list') // 1.13+
@ -29,6 +38,7 @@ class ProcessBuilder {
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
} }
@ -41,10 +51,13 @@ class ProcessBuilder {
process.throwDeprecation = true process.throwDeprecation = true
this.setupLiteLoader() this.setupLiteLoader()
logger.info('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)
logger.info('Using fabric loader:', this.usingFabricLoader)
const modObj = this.resolveModConfiguration(ConfigManager.getModConfiguration(this.server.rawServer.id).mods, this.server.modules)
// Mod list below 1.13 // Mod list below 1.13
if(!Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){ // Fabric only supports 1.14+
if(!mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){
this.constructJSONModList('forge', modObj.fMods, true) this.constructJSONModList('forge', modObj.fMods, true)
if(this.usingLiteLoader){ if(this.usingLiteLoader){
this.constructJSONModList('liteloader', modObj.lMods, true) this.constructJSONModList('liteloader', modObj.lMods, true)
@ -54,14 +67,14 @@ class ProcessBuilder {
const uberModArr = modObj.fMods.concat(modObj.lMods) const uberModArr = modObj.fMods.concat(modObj.lMods)
let args = this.constructJVMArguments(uberModArr, tempNativePath) let args = this.constructJVMArguments(uberModArr, tempNativePath)
if(Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){ if(mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){
//args = args.concat(this.constructModArguments(modObj.fMods)) //args = args.concat(this.constructModArguments(modObj.fMods))
args = args.concat(this.constructModList(modObj.fMods)) args = args.concat(this.constructModList(modObj.fMods))
} }
logger.info('Launch Arguments:', args) logger.info('Launch Arguments:', args)
const child = child_process.spawn(ConfigManager.getJavaExecutable(this.server.getID()), 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()
}) })
@ -122,7 +135,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
} }
/** /**
@ -132,20 +145,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()
} }
} }
} }
@ -166,20 +179,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)
@ -195,7 +208,7 @@ class ProcessBuilder {
} }
_lteMinorVersion(version) { _lteMinorVersion(version) {
return Number(this.forgeData.id.split('-')[0].split('.')[1]) <= Number(version) return Number(this.modManifest.id.split('-')[0].split('.')[1]) <= Number(version)
} }
/** /**
@ -207,7 +220,7 @@ class ProcessBuilder {
if(this._lteMinorVersion(9)) { if(this._lteMinorVersion(9)) {
return false return false
} }
const ver = this.forgeData.id.split('-')[2] 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++){
@ -242,11 +255,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
@ -266,7 +279,7 @@ class ProcessBuilder {
// */ // */
// constructModArguments(mods){ // constructModArguments(mods){
// const argStr = mods.map(mod => { // const argStr = mods.map(mod => {
// return mod.getExtensionlessID() // return mod.getExtensionlessMavenIdentifier()
// }).join(',') // }).join(',')
// if(argStr){ // if(argStr){
@ -283,18 +296,21 @@ class ProcessBuilder {
// } // }
/** /**
* Construct the mod argument list for forge 1.13 * 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. * @param {Array.<Object>} mods An array of mods to add to the mod list.
*/ */
constructModList(mods) { constructModList(mods) {
const writeBuffer = mods.map(mod => { const writeBuffer = mods.map(mod => {
return mod.getExtensionlessID() return this.usingFabricLoader ? mod.getPath() : mod.getExtensionlessMavenIdentifier()
}).join('\n') }).join('\n')
if(writeBuffer) { if(writeBuffer) {
fs.writeFileSync(this.forgeModListFile, writeBuffer, 'UTF-8') fs.writeFileSync(this.forgeModListFile, writeBuffer, 'UTF-8')
return [ return this.usingFabricLoader ? [
'--fabric.addMods',
`@${this.forgeModListFile}`
] : [
'--fml.mavenRoots', '--fml.mavenRoots',
path.join('..', '..', 'common', 'modstore'), path.join('..', '..', 'common', 'modstore'),
'--fml.modLists', '--fml.modLists',
@ -307,13 +323,15 @@ class ProcessBuilder {
} }
_processAutoConnectArg(args){ _processAutoConnectArg(args){
if(ConfigManager.getAutoConnect() && this.server.isAutoConnect()){ if(ConfigManager.getAutoConnect() && this.server.rawServer.autoconnect){
const serverURL = new URL('my://' + this.server.getAddress()) if(mcVersionAtLeast('1.20', this.server.rawServer.minecraftVersion)){
args.push('--server') args.push('--quickPlayMultiplayer')
args.push(serverURL.hostname) args.push(`${this.server.hostname}:${this.server.port}`)
if(serverURL.port){ } else {
args.push('--server')
args.push(this.server.hostname)
args.push('--port') args.push('--port')
args.push(serverURL.port) args.push(this.server.port)
} }
} }
} }
@ -326,7 +344,7 @@ 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(Util.mcVersionAtLeast('1.13', this.server.getMinecraftVersion())){ if(mcVersionAtLeast('1.13', this.server.rawServer.minecraftVersion)){
return this._constructJVMArguments113(mods, tempNativePath) return this._constructJVMArguments113(mods, tempNativePath)
} else { } else {
return this._constructJVMArguments112(mods, tempNativePath) return this._constructJVMArguments112(mods, tempNativePath)
@ -351,16 +369,19 @@ class ProcessBuilder {
// Java Arguments // Java Arguments
if(process.platform === 'darwin'){ if(process.platform === 'darwin'){
args.push('-Xdock:name=HeliosLauncher') args.push('-Xdock:name=LimboLauncher')
args.push('-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.getID())) args.push('-Xmx' + ConfigManager.getMaxRAM(this.server.rawServer.id))
args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID())) args.push('-Xms' + ConfigManager.getMinRAM(this.server.rawServer.id))
args = args.concat(ConfigManager.getJVMOptions(this.server.getID())) args = args.concat(ConfigManager.getJVMOptions(this.server.rawServer.id))
args.push('-Djava.library.path=' + tempNativePath) args.push('-Djava.library.path=' + tempNativePath)
args.push(`-javaagent:${path.join(process.cwd(), 'resources', 'libraries', 'java', 'LimboAuth.jar')}=https://auth.lsmp.site/authlib-injector`)
args.push('-Dauthlibinjector.side=client')
args.push('-Dauthlibinjector.yggdrasil.prefetched=eyJtZXRhIjp7ImltcGxlbWVudGF0aW9uTmFtZSI6IkRyYXNsIiwiaW1wbGVtZW50YXRpb25WZXJzaW9uIjoiMS4wLjEiLCJsaW5rcyI6eyJob21lcGFnZSI6Imh0dHBzOi8vYXV0aC5sc21wLnNpdGUiLCJyZWdpc3RlciI6Imh0dHBzOi8vYXV0aC5sc21wLnNpdGUvZHJhc2wvcmVnaXN0cmF0aW9uIn0sInNlcnZlck5hbWUiOiJMaW1ibyIsImZlYXR1cmUuZW5hYmxlX3Byb2ZpbGVfa2V5Ijp0cnVlfSwic2lnbmF0dXJlUHVibGlja2V5IjoiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBeEpjeUkySlZ2YnoydVEzWGtuTm1cbmkzR1FhMHFVUmxxV2FsdGgzNEcreTMyeUJSWlFiVUVuM05HV0hFOVcrdGs3OG95WGQ2bHduWngyMlJJeVJjRlNcbnhPeFhDcW1DUTJpWmFWZi9KVE4vNG1VWmJnaWk4RU9DeVptT3dwZmp5RTJQZXFkQ1BaaHlVdExLYS82djhxVnlcbmRNSGZhMzV4Uml3UGtQdkdaQ3FJcmhRaktLcGxLaWJ1MzRzR1hDNy9hQXlBMXpqRXZoeFoyRjJLS0gwbzc5c2Jcbk16cmtML1N0alZDbnlnMVhCQUJ2ZDdBUENlTkViaytOamlTY0JLNExBTmpSZ2tqV2RQZjZRZ0lSRWJNN05TbTBcbk8yZGI3VmlWYVI3Q1FhMTlUa1Z2MHRTR3hCY1EyYmZiWk9teWtKK2ZtMDdBdUp2ek5qekRmMDZPNEdCckZVOUhcbjFkaWN4Q0wrM2grZzZvL0JiNUk3LzBGaVdId2xrUDNsRzhhQVk5clFhcm5OakxsZENrakN5Rmw4Y24zNENnWDBcbnNhNnN3Mkh2YUdqaEd1bldsazQvUjU0UUw4YVVVcGsrVzh2YzFaV2JOL3VONVRRNWZ6NDQyWmF5QnlJNkxqUlZcbjRXMXhnaS9adVNsc0dKZlgrdEdkTXNyTmNpbVZUakpNQUlIMUEwM2kyTWl3dmJsbmI1ZzRLM0hoZWtOakRUZ05cbmxKUXlGR1BQZmN6dkNwSFdITk5NODlaWlBweVhOZmZBbjltV21VaXFOTjM2WHR1SkxyTTFnTnJ5K3hwMnJic3dcbnduNEthUlpTcGFpencvUzd3cnlLN0lvNXpXb1R6aVZoM0RJblovUXZrb1NPMThMVWdBekpheVNvdWd5UFRWRnhcblNQY1ZoOWVEcit4aVoydit2RlFLMFJNQ0F3RUFBUT09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIsInNpZ25hdHVyZVB1YmxpY2tleXMiOlsiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBeEpjeUkySlZ2YnoydVEzWGtuTm1cbmkzR1FhMHFVUmxxV2FsdGgzNEcreTMyeUJSWlFiVUVuM05HV0hFOVcrdGs3OG95WGQ2bHduWngyMlJJeVJjRlNcbnhPeFhDcW1DUTJpWmFWZi9KVE4vNG1VWmJnaWk4RU9DeVptT3dwZmp5RTJQZXFkQ1BaaHlVdExLYS82djhxVnlcbmRNSGZhMzV4Uml3UGtQdkdaQ3FJcmhRaktLcGxLaWJ1MzRzR1hDNy9hQXlBMXpqRXZoeFoyRjJLS0gwbzc5c2Jcbk16cmtML1N0alZDbnlnMVhCQUJ2ZDdBUENlTkViaytOamlTY0JLNExBTmpSZ2tqV2RQZjZRZ0lSRWJNN05TbTBcbk8yZGI3VmlWYVI3Q1FhMTlUa1Z2MHRTR3hCY1EyYmZiWk9teWtKK2ZtMDdBdUp2ek5qekRmMDZPNEdCckZVOUhcbjFkaWN4Q0wrM2grZzZvL0JiNUk3LzBGaVdId2xrUDNsRzhhQVk5clFhcm5OakxsZENrakN5Rmw4Y24zNENnWDBcbnNhNnN3Mkh2YUdqaEd1bldsazQvUjU0UUw4YVVVcGsrVzh2YzFaV2JOL3VONVRRNWZ6NDQyWmF5QnlJNkxqUlZcbjRXMXhnaS9adVNsc0dKZlgrdEdkTXNyTmNpbVZUakpNQUlIMUEwM2kyTWl3dmJsbmI1ZzRLM0hoZWtOakRUZ05cbmxKUXlGR1BQZmN6dkNwSFdITk5NODlaWlBweVhOZmZBbjltV21VaXFOTjM2WHR1SkxyTTFnTnJ5K3hwMnJic3dcbnduNEthUlpTcGFpencvUzd3cnlLN0lvNXpXb1R6aVZoM0RJblovUXZrb1NPMThMVWdBekpheVNvdWd5UFRWRnhcblNQY1ZoOWVEcit4aVoydit2RlFLMFJNQ0F3RUFBUT09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIsIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NSUlDSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQWc4QU1JSUNDZ0tDQWdFQXlsQjRCNm01bHo3andyY0Z6NkZkXG4vZm5mVWhjdmx4c1RTbjVrSUsvMmFHRzFDM2tNeTRWamh3bHhGNkJGVVNuZnhoTnN3UGpoM1ppdGtCeEVBRlkyXG41dXprSkZSd0h3VkE5bWR3amFzaFhJTHRSNk9xZExYWEZWeVVQSVVSTE9TV3FHTkJ0YjA4RU41Zk1uRzhpRkxnXG5FSklCTXhzOUJ2RjNzMy9GaHVIeVBLaVZUWm1YWTBXWTRaeVlxdm9LUitYamFUUlBQdkJzRGE0V0kydTF6eFhNXG5lSGxvZFQzbG5DelZ2eU9ZQkxYTDZDSmdCeXVPeGNjSjhoblhmRjl5WTRGMGFlTDA4MEp6LzMrRUJORzhSTzRCXG55aHRCZjROeThOUTZzdFdzamZlVUl2SDdiVS80ekNZY1lPcTRXckluWEhxUzhxcnVEbUlsN1A1WFhHY2FidXpRXG5zdFBmL2gyQ1JBVXBQL1BsSFhjTWx2ZXdqbUdVNk1mREsrbGlmU2NOWXdqUHhSbzRuS1RHRlpmLzBhcUhDaC9FXG5Bc1F5TEtyT0lZUkUwbERHM2J6Qmg4b2dJTUxBdWdzQWZCYjZNM21xQ3FLYVRNQWYvVkFqaDVGRkpualMrN2JFXG4rYlpFVjBxd2F4MUNFb1BQSkwxZklRak9TOHpqMDg2Z2pwR1JDdFN5OStiVFBUZlRSL1NKK1ZVQjVHMkllQ0l0XG5rTkhwSlgyeWdvakZaOW41Rm5qN1I5Wm5PTStMOG55SWpQdTNhZVB2dGNyWGx5TGhIL2h2T2ZJT2pQeE9scVcrXG5PNVF3U0ZQNE9FY3lMQVVnRGRVZ3lXMzZaNW1CMjg1dUtXL2lnaHpac09UZXZWVUcyUXdESXRPYklWNmk4UkN4XG5GYk4yb0RIeVBhTzVqMXRUYUJOeVZ0OENBd0VBQVE9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iLCItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUFyYTRZMnd1M3JXRVc3Y0RURFJSZFxuNEl2VUQxNDBZMTJTYUczazRWM1V3VC9wRG5uWDVpdE9jWWlaQTBxZjRWQ3BKRHAyUGlmT0wrUHIvcGgvRzkvNlxuWm9JeGtCZUdFTm8rUzdpOUJxaXpKeTljbVpvY3B5eCtSa1phdzkrZnJDR05MdVlMcnh6aU5XaVhGQUNKU2cybVxuSEFDUjcrNk5rR044ZC8xNi8zUHhNbnZHU3lMVDdKS0dVZ3FqMVEzb1c3aytOTFhSOXN3Nm9SRUxPY25VdlpWYVxuMmJjZ2x2OHZsY3lQcXFuQmh5ZExmSEk4NVo1V25JWVp2aVozQmI0ZHY1Rm1lNzI2QkdPdEVZN2t6NDBSZml3alxuVDN4WUtZS1BKVVMzL2NyUFg2ZXVnbVd5cldkZGRLYWVQclc4OGJwMTdaNU5JU3RsSjVLSkprNGNvaGE4TytQN1xub25EcW1iSHdMcVBUZVI1MW5qa2daK0RKV1Q2Zno4a3U5T1dRbjZJL0Z4cU4xNGlZSWdoREppam1LdkV3c0k3RlxuSjVYMnR0UFhFdkJZTG1wajJqMGxRUWNVSXFIN2hraVorbUNXMEdZYXdKZ2JBZU5BcmFNOXNQKzc2TXlBR0lUdFxuQXNYdjFJUW1haCs3T2VESk9Ub0cyS2IxRGwwVmErSGlQOU1QcGNuTzdrYm42ZHFBeWhOdlJObUhuc1VPaUVjTFxuaFc5Ums3eHo4N0lCVi9jR0tiVURneHU4Y1lZMFA1MTJEV3Q1K0ptcjhXMTBGREZkTG1rSnQxdGFXeE54QXBNMlxuQ2lGUENpbWswMmtveUxaRFc5bnFwV053NnFTL1RPWVBkejQzOHFFdWFtdFlVSit1NldoQmpLOHhBSkVBdDVrM1xuZ0RLWCtubFRpRzNONnNlMDlENjJmUzhDQXdFQUFRPT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIl0sInNraW5Eb21haW5zIjpbImF1dGgubHNtcC5zaXRlIiwidGV4dHVyZXMubWluZWNyYWZ0Lm5ldCJdfQ==')
// Main Java Class // Main Java Class
args.push(this.forgeData.mainClass) args.push(this.modManifest.mainClass)
// Forge Arguments // Forge Arguments
args = args.concat(this._resolveForgeArgs()) args = args.concat(this._resolveForgeArgs())
@ -383,17 +404,17 @@ class ProcessBuilder {
const argDiscovery = /\${*(.*)}/ const argDiscovery = /\${*(.*)}/
// JVM Arguments First // JVM Arguments First
let args = this.versionData.arguments.jvm let args = this.vanillaManifest.arguments.jvm
// Debug securejarhandler // Debug securejarhandler
// args.push('-Dbsl.debug=true') // args.push('-Dbsl.debug=true')
if(this.forgeData.arguments.jvm != null) { if(this.modManifest.arguments.jvm != null) {
for(const argStr of this.forgeData.arguments.jvm) { for(const argStr of this.modManifest.arguments.jvm) {
args.push(argStr args.push(argStr
.replaceAll('${library_directory}', this.libPath) .replaceAll('${library_directory}', this.libPath)
.replaceAll('${classpath_separator}', ProcessBuilder.getClasspathSeparator()) .replaceAll('${classpath_separator}', ProcessBuilder.getClasspathSeparator())
.replaceAll('${version_name}', this.forgeData.id) .replaceAll('${version_name}', this.modManifest.id)
) )
} }
} }
@ -402,18 +423,34 @@ class ProcessBuilder {
// Java Arguments // Java Arguments
if(process.platform === 'darwin'){ if(process.platform === 'darwin'){
args.push('-Xdock:name=HeliosLauncher') args.push('-Xdock:name=LimboLauncher')
args.push('-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.getID()))
args.push('-Xms' + ConfigManager.getMinRAM(this.server.getID()))
args = args.concat(ConfigManager.getJVMOptions(this.server.getID()))
const current = ConfigManager.getSelectedAccount()
if (current.type === 'microsoft') {
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))
} else {
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('-Duser.language=es')
args.push('-Dminecraft.api.env=custom')
args.push('-Dminecraft.api.auth.host=https://auth.lsmp.site/authlib-injector/authserver')
args.push('-Dminecraft.api.account.host=https://auth.lsmp.site/authlib-injector/api')
args.push('-Dminecraft.api.session.host=https://auth.lsmp.site/authlib-injector/sessionserver')
args.push('-Dminecraft.api.services.host=https://auth.lsmp.site/authlib-injector/minecraftservices')
args.push(`-javaagent:${path.join(process.cwd(), 'resources', 'libraries', 'java', 'LimboAuth.jar')}=https://auth.lsmp.site/authlib-injector`)
args.push('-Dauthlibinjector.yggdrasil.prefetched=eyJtZXRhIjp7ImltcGxlbWVudGF0aW9uTmFtZSI6IkRyYXNsIiwiaW1wbGVtZW50YXRpb25WZXJzaW9uIjoiMS4wLjEiLCJsaW5rcyI6eyJob21lcGFnZSI6Imh0dHBzOi8vYXV0aC5sc21wLnNpdGUiLCJyZWdpc3RlciI6Imh0dHBzOi8vYXV0aC5sc21wLnNpdGUvZHJhc2wvcmVnaXN0cmF0aW9uIn0sInNlcnZlck5hbWUiOiJMaW1ibyIsImZlYXR1cmUuZW5hYmxlX3Byb2ZpbGVfa2V5Ijp0cnVlfSwic2lnbmF0dXJlUHVibGlja2V5IjoiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBeEpjeUkySlZ2YnoydVEzWGtuTm1cbmkzR1FhMHFVUmxxV2FsdGgzNEcreTMyeUJSWlFiVUVuM05HV0hFOVcrdGs3OG95WGQ2bHduWngyMlJJeVJjRlNcbnhPeFhDcW1DUTJpWmFWZi9KVE4vNG1VWmJnaWk4RU9DeVptT3dwZmp5RTJQZXFkQ1BaaHlVdExLYS82djhxVnlcbmRNSGZhMzV4Uml3UGtQdkdaQ3FJcmhRaktLcGxLaWJ1MzRzR1hDNy9hQXlBMXpqRXZoeFoyRjJLS0gwbzc5c2Jcbk16cmtML1N0alZDbnlnMVhCQUJ2ZDdBUENlTkViaytOamlTY0JLNExBTmpSZ2tqV2RQZjZRZ0lSRWJNN05TbTBcbk8yZGI3VmlWYVI3Q1FhMTlUa1Z2MHRTR3hCY1EyYmZiWk9teWtKK2ZtMDdBdUp2ek5qekRmMDZPNEdCckZVOUhcbjFkaWN4Q0wrM2grZzZvL0JiNUk3LzBGaVdId2xrUDNsRzhhQVk5clFhcm5OakxsZENrakN5Rmw4Y24zNENnWDBcbnNhNnN3Mkh2YUdqaEd1bldsazQvUjU0UUw4YVVVcGsrVzh2YzFaV2JOL3VONVRRNWZ6NDQyWmF5QnlJNkxqUlZcbjRXMXhnaS9adVNsc0dKZlgrdEdkTXNyTmNpbVZUakpNQUlIMUEwM2kyTWl3dmJsbmI1ZzRLM0hoZWtOakRUZ05cbmxKUXlGR1BQZmN6dkNwSFdITk5NODlaWlBweVhOZmZBbjltV21VaXFOTjM2WHR1SkxyTTFnTnJ5K3hwMnJic3dcbnduNEthUlpTcGFpencvUzd3cnlLN0lvNXpXb1R6aVZoM0RJblovUXZrb1NPMThMVWdBekpheVNvdWd5UFRWRnhcblNQY1ZoOWVEcit4aVoydit2RlFLMFJNQ0F3RUFBUT09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIsInNpZ25hdHVyZVB1YmxpY2tleXMiOlsiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBeEpjeUkySlZ2YnoydVEzWGtuTm1cbmkzR1FhMHFVUmxxV2FsdGgzNEcreTMyeUJSWlFiVUVuM05HV0hFOVcrdGs3OG95WGQ2bHduWngyMlJJeVJjRlNcbnhPeFhDcW1DUTJpWmFWZi9KVE4vNG1VWmJnaWk4RU9DeVptT3dwZmp5RTJQZXFkQ1BaaHlVdExLYS82djhxVnlcbmRNSGZhMzV4Uml3UGtQdkdaQ3FJcmhRaktLcGxLaWJ1MzRzR1hDNy9hQXlBMXpqRXZoeFoyRjJLS0gwbzc5c2Jcbk16cmtML1N0alZDbnlnMVhCQUJ2ZDdBUENlTkViaytOamlTY0JLNExBTmpSZ2tqV2RQZjZRZ0lSRWJNN05TbTBcbk8yZGI3VmlWYVI3Q1FhMTlUa1Z2MHRTR3hCY1EyYmZiWk9teWtKK2ZtMDdBdUp2ek5qekRmMDZPNEdCckZVOUhcbjFkaWN4Q0wrM2grZzZvL0JiNUk3LzBGaVdId2xrUDNsRzhhQVk5clFhcm5OakxsZENrakN5Rmw4Y24zNENnWDBcbnNhNnN3Mkh2YUdqaEd1bldsazQvUjU0UUw4YVVVcGsrVzh2YzFaV2JOL3VONVRRNWZ6NDQyWmF5QnlJNkxqUlZcbjRXMXhnaS9adVNsc0dKZlgrdEdkTXNyTmNpbVZUakpNQUlIMUEwM2kyTWl3dmJsbmI1ZzRLM0hoZWtOakRUZ05cbmxKUXlGR1BQZmN6dkNwSFdITk5NODlaWlBweVhOZmZBbjltV21VaXFOTjM2WHR1SkxyTTFnTnJ5K3hwMnJic3dcbnduNEthUlpTcGFpencvUzd3cnlLN0lvNXpXb1R6aVZoM0RJblovUXZrb1NPMThMVWdBekpheVNvdWd5UFRWRnhcblNQY1ZoOWVEcit4aVoydit2RlFLMFJNQ0F3RUFBUT09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIsIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NSUlDSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQWc4QU1JSUNDZ0tDQWdFQXlsQjRCNm01bHo3andyY0Z6NkZkXG4vZm5mVWhjdmx4c1RTbjVrSUsvMmFHRzFDM2tNeTRWamh3bHhGNkJGVVNuZnhoTnN3UGpoM1ppdGtCeEVBRlkyXG41dXprSkZSd0h3VkE5bWR3amFzaFhJTHRSNk9xZExYWEZWeVVQSVVSTE9TV3FHTkJ0YjA4RU41Zk1uRzhpRkxnXG5FSklCTXhzOUJ2RjNzMy9GaHVIeVBLaVZUWm1YWTBXWTRaeVlxdm9LUitYamFUUlBQdkJzRGE0V0kydTF6eFhNXG5lSGxvZFQzbG5DelZ2eU9ZQkxYTDZDSmdCeXVPeGNjSjhoblhmRjl5WTRGMGFlTDA4MEp6LzMrRUJORzhSTzRCXG55aHRCZjROeThOUTZzdFdzamZlVUl2SDdiVS80ekNZY1lPcTRXckluWEhxUzhxcnVEbUlsN1A1WFhHY2FidXpRXG5zdFBmL2gyQ1JBVXBQL1BsSFhjTWx2ZXdqbUdVNk1mREsrbGlmU2NOWXdqUHhSbzRuS1RHRlpmLzBhcUhDaC9FXG5Bc1F5TEtyT0lZUkUwbERHM2J6Qmg4b2dJTUxBdWdzQWZCYjZNM21xQ3FLYVRNQWYvVkFqaDVGRkpualMrN2JFXG4rYlpFVjBxd2F4MUNFb1BQSkwxZklRak9TOHpqMDg2Z2pwR1JDdFN5OStiVFBUZlRSL1NKK1ZVQjVHMkllQ0l0XG5rTkhwSlgyeWdvakZaOW41Rm5qN1I5Wm5PTStMOG55SWpQdTNhZVB2dGNyWGx5TGhIL2h2T2ZJT2pQeE9scVcrXG5PNVF3U0ZQNE9FY3lMQVVnRGRVZ3lXMzZaNW1CMjg1dUtXL2lnaHpac09UZXZWVUcyUXdESXRPYklWNmk4UkN4XG5GYk4yb0RIeVBhTzVqMXRUYUJOeVZ0OENBd0VBQVE9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iLCItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUFyYTRZMnd1M3JXRVc3Y0RURFJSZFxuNEl2VUQxNDBZMTJTYUczazRWM1V3VC9wRG5uWDVpdE9jWWlaQTBxZjRWQ3BKRHAyUGlmT0wrUHIvcGgvRzkvNlxuWm9JeGtCZUdFTm8rUzdpOUJxaXpKeTljbVpvY3B5eCtSa1phdzkrZnJDR05MdVlMcnh6aU5XaVhGQUNKU2cybVxuSEFDUjcrNk5rR044ZC8xNi8zUHhNbnZHU3lMVDdKS0dVZ3FqMVEzb1c3aytOTFhSOXN3Nm9SRUxPY25VdlpWYVxuMmJjZ2x2OHZsY3lQcXFuQmh5ZExmSEk4NVo1V25JWVp2aVozQmI0ZHY1Rm1lNzI2QkdPdEVZN2t6NDBSZml3alxuVDN4WUtZS1BKVVMzL2NyUFg2ZXVnbVd5cldkZGRLYWVQclc4OGJwMTdaNU5JU3RsSjVLSkprNGNvaGE4TytQN1xub25EcW1iSHdMcVBUZVI1MW5qa2daK0RKV1Q2Zno4a3U5T1dRbjZJL0Z4cU4xNGlZSWdoREppam1LdkV3c0k3RlxuSjVYMnR0UFhFdkJZTG1wajJqMGxRUWNVSXFIN2hraVorbUNXMEdZYXdKZ2JBZU5BcmFNOXNQKzc2TXlBR0lUdFxuQXNYdjFJUW1haCs3T2VESk9Ub0cyS2IxRGwwVmErSGlQOU1QcGNuTzdrYm42ZHFBeWhOdlJObUhuc1VPaUVjTFxuaFc5Ums3eHo4N0lCVi9jR0tiVURneHU4Y1lZMFA1MTJEV3Q1K0ptcjhXMTBGREZkTG1rSnQxdGFXeE54QXBNMlxuQ2lGUENpbWswMmtveUxaRFc5bnFwV053NnFTL1RPWVBkejQzOHFFdWFtdFlVSit1NldoQmpLOHhBSkVBdDVrM1xuZ0RLWCtubFRpRzNONnNlMDlENjJmUzhDQXdFQUFRPT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIl0sInNraW5Eb21haW5zIjpbImF1dGgubHNtcC5zaXRlIiwidGV4dHVyZXMubWluZWNyYWZ0Lm5ldCJdfQ==')
}
//estoy hasta los uebos, saquenme de aquí :3
// Main Java Class // Main Java Class
args.push(this.forgeData.mainClass) args.push(this.modManifest.mainClass)
// Vanilla Arguments // Vanilla Arguments
args = args.concat(this.versionData.arguments.game) args = args.concat(this.vanillaManifest.arguments.game)
for(let i=0; i<args.length; i++){ for(let i=0; i<args.length; i++){
if(typeof args[i] === 'object' && args[i].rules != null){ if(typeof args[i] === 'object' && args[i].rules != null){
@ -421,7 +458,7 @@ class ProcessBuilder {
let checksum = 0 let checksum = 0
for(let rule of args[i].rules){ for(let rule of args[i].rules){
if(rule.os != null){ if(rule.os != null){
if(rule.os.name === Library.mojangFriendlyOS() if(rule.os.name === getMojangOS()
&& (rule.os.version == null || new RegExp(rule.os.version).test(os.release))){ && (rule.os.version == null || new RegExp(rule.os.version).test(os.release))){
if(rule.action === 'allow'){ if(rule.action === 'allow'){
checksum++ checksum++
@ -470,8 +507,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
@ -480,7 +517,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()
@ -492,7 +529,7 @@ class ProcessBuilder {
val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang' val = this.authUser.type === 'microsoft' ? 'msa' : 'mojang'
break break
case 'version_type': case 'version_type':
val = this.versionData.type val = this.vanillaManifest.type
break break
case 'resolution_width': case 'resolution_width':
val = ConfigManager.getGameWidth() val = ConfigManager.getGameWidth()
@ -521,25 +558,11 @@ class ProcessBuilder {
} }
// Autoconnect // Autoconnect
let isAutoconnectBroken this._processAutoConnectArg(args)
try {
isAutoconnectBroken = Util.isAutoconnectBroken(this.forgeData.id.split('-')[2])
} catch(err) {
logger.error(err)
logger.error('Forge version format changed.. assuming autoconnect works.')
logger.debug('Forge version:', this.forgeData.id)
}
if(isAutoconnectBroken) {
logger.error('Server autoconnect disabled on Forge 1.15.2 for builds earlier than 31.2.15 due to OpenGL Stack Overflow issue.')
logger.error('Please upgrade your Forge version to at least 31.2.15!')
} else {
this._processAutoConnectArg(args)
}
// Forge Specific Arguments // Forge Specific Arguments
args = args.concat(this.forgeData.arguments.game) args = args.concat(this.modManifest.arguments.game)
// Filter null values // Filter null values
args = args.filter(arg => { args = args.filter(arg => {
@ -555,7 +578,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.
@ -568,8 +591,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
@ -578,7 +601,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()
@ -593,7 +616,7 @@ class ProcessBuilder {
val = '{}' 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){
@ -668,10 +691,10 @@ class ProcessBuilder {
classpathArg(mods, tempNativePath){ classpathArg(mods, tempNativePath){
let cpArgs = [] let cpArgs = []
if(!Util.mcVersionAtLeast('1.17', this.server.getMinecraftVersion())) { if(!mcVersionAtLeast('1.17', this.server.rawServer.minecraftVersion) || this.usingFabricLoader) {
// Add the version.jar to the classpath. // Add the version.jar to the classpath.
// Must not be added to the classpath for Forge 1.17+. // Must not be added to the classpath for Forge 1.17+.
const version = this.versionData.id const version = this.vanillaManifest.id
cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar')) cpArgs.push(path.join(this.commonDir, 'versions', version, version + '.jar'))
} }
@ -710,17 +733,17 @@ class ProcessBuilder {
const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/ const nativesRegex = /.+:natives-([^-]+)(?:-(.+))?/
const libs = {} 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)){
// Pre-1.19 has a natives object. // Pre-1.19 has a natives object.
if(lib.natives != null) { if(lib.natives != null) {
// Extract the native library. // Extract the native library.
const exclusionArr = lib.extract != null ? lib.extract.exclude : ['META-INF/'] const exclusionArr = lib.extract != null ? lib.extract.exclude : ['META-INF/']
const artifact = lib.downloads.classifiers[lib.natives[Library.mojangFriendlyOS()].replace('${arch}', process.arch.replace('x', ''))] const artifact = lib.downloads.classifiers[lib.natives[getMojangOS()].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)
@ -826,15 +849,15 @@ class ProcessBuilder {
* @returns {{[id: string]: string}} An object 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[mdl.getVersionlessID()] = 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, ...res} libs = {...libs, ...res}
@ -863,20 +886,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){
if(sm.getClasspath()) { if(sm.rawModule.classpath ?? true) {
libs.push(sm.getArtifact().getPath()) 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)

File diff suppressed because it is too large Load Diff

View File

@ -193,10 +193,10 @@ loginButton.addEventListener('click', () => {
$('.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.
@ -220,10 +220,7 @@ loginButton.addEventListener('click', () => {
} else { } else {
// Uh oh. // Uh oh.
msftLoginLogger.error('Unhandled error during login.', displayableError) msftLoginLogger.error('Unhandled error during login.', displayableError)
actualDisplayableError = { actualDisplayableError = Lang.queryJS('login.error.unknown')
title: 'Unknown Error During Login',
desc: 'An unknown error has occurred. Please see the console for details.'
}
} }
setOverlayContent(actualDisplayableError.title, actualDisplayableError.desc, Lang.queryJS('login.tryAgain')) setOverlayContent(actualDisplayableError.title, actualDisplayableError.desc, Lang.queryJS('login.tryAgain'))

View File

@ -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,13 +184,13 @@ 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')){
@ -198,7 +198,7 @@ document.getElementById('accountSelectConfirm').addEventListener('click', () =>
ConfigManager.save() ConfigManager.save()
updateSelectedAccount(authAcc) updateSelectedAccount(authAcc)
if(getCurrentView() === VIEWS.settings) { if(getCurrentView() === VIEWS.settings) {
prepareSettings() await prepareSettings()
} }
toggleOverlay(false) toggleOverlay(false)
validateSelectedAccount() validateSelectedAccount()
@ -211,7 +211,7 @@ document.getElementById('accountSelectConfirm').addEventListener('click', () =>
ConfigManager.save() ConfigManager.save()
updateSelectedAccount(authAcc) updateSelectedAccount(authAcc)
if(getCurrentView() === VIEWS.settings) { if(getCurrentView() === VIEWS.settings) {
prepareSettings() await prepareSettings()
} }
toggleOverlay(false) toggleOverlay(false)
validateSelectedAccount() validateSelectedAccount()
@ -267,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>
@ -289,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>
@ -313,8 +313,8 @@ function populateAccountListings(){
} }
function prepareServerSelectionList(){ async function prepareServerSelectionList(){
populateServerListings() await populateServerListings()
setServerListingHandlers() setServerListingHandlers()
} }

View File

@ -2,7 +2,6 @@
const os = require('os') const os = require('os')
const semver = require('semver') const semver = require('semver')
const { JavaGuard } = require('./assets/js/assetguard')
const DropinModUtil = require('./assets/js/dropinmodutil') const DropinModUtil = require('./assets/js/dropinmodutil')
const { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants') const { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants')
@ -60,8 +59,8 @@ function bindFileSelectors(){
if(isJavaExecSel && process.platform === 'win32') { if(isJavaExecSel && process.platform === 'win32') {
options.filters = [ options.filters = [
{ name: 'Executables', extensions: ['exe'] }, { name: Lang.queryJS('settings.fileSelectors.executables'), extensions: ['exe'] },
{ name: 'All Files', extensions: ['*'] } { name: Lang.queryJS('settings.fileSelectors.allFiles'), extensions: ['*'] }
] ]
} }
@ -69,7 +68,7 @@ function bindFileSelectors(){
if(!res.canceled) { if(!res.canceled) {
ele.previousElementSibling.value = res.filePaths[0] ele.previousElementSibling.value = res.filePaths[0]
if(isJavaExecSel) { if(isJavaExecSel) {
populateJavaExecDetails(ele.previousElementSibling.value) await populateJavaExecDetails(ele.previousElementSibling.value)
} }
} }
} }
@ -123,9 +122,10 @@ function initSettingsValidators(){
/** /**
* Load configuration values onto the UI. This is an automated process. * Load configuration values onto the UI. This is an automated process.
*/ */
function initSettingsValues(){ async function initSettingsValues(){
const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]') const sEls = document.getElementById('settingsContainer').querySelectorAll('[cValue]')
Array.from(sEls).map((v, index, arr) => {
for(const v of sEls) {
const cVal = v.getAttribute('cValue') const cVal = v.getAttribute('cValue')
const serverDependent = v.hasAttribute('serverDependent') // Means the first argument is the server id. const serverDependent = v.hasAttribute('serverDependent') // Means the first argument is the server id.
const gFn = ConfigManager['get' + cVal] const gFn = ConfigManager['get' + cVal]
@ -139,7 +139,7 @@ function initSettingsValues(){
// Special Conditions // Special Conditions
if(cVal === 'JavaExecutable'){ if(cVal === 'JavaExecutable'){
v.value = gFn.apply(null, gFnOpts) v.value = gFn.apply(null, gFnOpts)
populateJavaExecDetails(v.value) await populateJavaExecDetails(v.value)
} else if (cVal === 'DataDirectory'){ } else if (cVal === 'DataDirectory'){
v.value = gFn.apply(null, gFnOpts) v.value = gFn.apply(null, gFnOpts)
} else if(cVal === 'JVMOptions'){ } else if(cVal === 'JVMOptions'){
@ -156,7 +156,7 @@ function initSettingsValues(){
if(cVal === 'MinRAM' || cVal === 'MaxRAM'){ if(cVal === 'MinRAM' || cVal === 'MaxRAM'){
let val = gFn.apply(null, gFnOpts) let val = gFn.apply(null, gFnOpts)
if(val.endsWith('M')){ if(val.endsWith('M')){
val = Number(val.substring(0, val.length-1))/1000 val = Number(val.substring(0, val.length-1))/1024
} else { } else {
val = Number.parseFloat(val) val = Number.parseFloat(val)
} }
@ -168,8 +168,8 @@ function initSettingsValues(){
} }
} }
} }
}
})
} }
/** /**
@ -215,7 +215,7 @@ function saveSettingsValues(){
if(cVal === 'MinRAM' || cVal === 'MaxRAM'){ if(cVal === 'MinRAM' || cVal === 'MaxRAM'){
let val = Number(v.getAttribute('value')) let val = Number(v.getAttribute('value'))
if(val%1 > 0){ if(val%1 > 0){
val = val*1000 + 'M' val = val*1024 + 'M'
} else { } else {
val = val + 'G' val = val + 'G'
} }
@ -375,9 +375,9 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => {
// Unexpected error. // Unexpected error.
setOverlayContent( setOverlayContent(
'Something Went Wrong', Lang.queryJS('settings.msftLogin.errorTitle'),
'Microsoft authentication failed. Please try again.', Lang.queryJS('settings.msftLogin.errorMessage'),
'OK' Lang.queryJS('settings.msftLogin.okButton')
) )
setOverlayHandler(() => { setOverlayHandler(() => {
toggleOverlay(false) toggleOverlay(false)
@ -403,7 +403,7 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => {
setOverlayContent( setOverlayContent(
error, error,
errorDesc, errorDesc,
'OK' Lang.queryJS('settings.msftLogin.okButton')
) )
setOverlayHandler(() => { setOverlayHandler(() => {
toggleOverlay(false) toggleOverlay(false)
@ -419,8 +419,8 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => {
AuthManager.addMicrosoftAccount(authCode).then(value => { AuthManager.addMicrosoftAccount(authCode).then(value => {
document.getElementById("waitingText").innerHTML = "Finished" document.getElementById("waitingText").innerHTML = "Finished"
updateSelectedAccount(value) updateSelectedAccount(value)
switchView(getCurrentView(), viewOnClose, 500, 500, () => { switchView(getCurrentView(), viewOnClose, 500, 500, async () => {
prepareSettings() await prepareSettings()
}) })
}) })
.catch((displayableError) => { .catch((displayableError) => {
@ -432,10 +432,7 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGIN, (_, ...arguments_) => {
} else { } else {
// Uh oh. // Uh oh.
msftLoginLogger.error('Unhandled error during login.', displayableError) msftLoginLogger.error('Unhandled error during login.', displayableError)
actualDisplayableError = { actualDisplayableError = Lang.queryJS('login.error.unknown')
title: 'Unknown Error During Login',
desc: 'An unknown error has occurred. Please see the console for details.'
}
} }
switchView(getCurrentView(), viewOnClose, 500, 500, () => { switchView(getCurrentView(), viewOnClose, 500, 500, () => {
@ -464,11 +461,11 @@ function bindAuthAccountSelect(){
for(let i=0; i<selectBtns.length; i++){ for(let i=0; i<selectBtns.length; i++){
if(selectBtns[i].hasAttribute('selected')){ if(selectBtns[i].hasAttribute('selected')){
selectBtns[i].removeAttribute('selected') selectBtns[i].removeAttribute('selected')
selectBtns[i].innerHTML = 'Select Account' selectBtns[i].innerHTML = Lang.queryJS('settings.authAccountSelect.selectButton')
} }
} }
val.setAttribute('selected', '') val.setAttribute('selected', '')
val.innerHTML = 'Selected Account &#10004;' val.innerHTML = Lang.queryJS('settings.authAccountSelect.selectedButton')
setSelectedAccount(val.closest('.settingsAuthAccount').getAttribute('uuid')) setSelectedAccount(val.closest('.settingsAuthAccount').getAttribute('uuid'))
} }
}) })
@ -486,10 +483,10 @@ function bindAuthAccountLogOut(){
if(Object.keys(ConfigManager.getAuthAccounts()).length === 1){ if(Object.keys(ConfigManager.getAuthAccounts()).length === 1){
isLastAccount = true isLastAccount = true
setOverlayContent( setOverlayContent(
'Warning<br>This is Your Last Account', Lang.queryJS('settings.authAccountLogout.lastAccountWarningTitle'),
'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?', Lang.queryJS('settings.authAccountLogout.lastAccountWarningMessage'),
'I\'m Sure', Lang.queryJS('settings.authAccountLogout.confirmButton'),
'Cancel' Lang.queryJS('settings.authAccountLogout.cancelButton')
) )
setOverlayHandler(() => { setOverlayHandler(() => {
processLogOut(val, isLastAccount) processLogOut(val, isLastAccount)
@ -559,9 +556,9 @@ ipcRenderer.on(MSFT_OPCODE.REPLY_LOGOUT, (_, ...arguments_) => {
// Unexpected error. // Unexpected error.
setOverlayContent( setOverlayContent(
'Something Went Wrong', Lang.queryJS('settings.msftLogout.errorTitle'),
'Microsoft logout failed. Please try again.', Lang.queryJS('settings.msftLogout.errorMessage'),
'OK' Lang.queryJS('settings.msftLogout.okButton')
) )
setOverlayHandler(() => { setOverlayHandler(() => {
toggleOverlay(false) toggleOverlay(false)
@ -615,12 +612,12 @@ function refreshAuthAccountSelected(uuid){
const selBtn = val.getElementsByClassName('settingsAuthAccountSelect')[0] const selBtn = val.getElementsByClassName('settingsAuthAccountSelect')[0]
if(uuid === val.getAttribute('uuid')){ if(uuid === val.getAttribute('uuid')){
selBtn.setAttribute('selected', '') selBtn.setAttribute('selected', '')
selBtn.innerHTML = 'Selected Account &#10004;' selBtn.innerHTML = Lang.queryJS('settings.authAccountSelect.selectedButton')
} else { } else {
if(selBtn.hasAttribute('selected')){ if(selBtn.hasAttribute('selected')){
selBtn.removeAttribute('selected') selBtn.removeAttribute('selected')
} }
selBtn.innerHTML = 'Select Account' selBtn.innerHTML = Lang.queryJS('settings.authAccountSelect.selectButton')
} }
}) })
} }
@ -646,31 +643,55 @@ function populateAuthAccounts(){
const acc = authAccounts[val] const acc = authAccounts[val]
const accHtml = `<div class="settingsAuthAccount" uuid="${acc.uuid}"> const accHtml = `<div class="settingsAuthAccount" uuid="${acc.uuid}">
<div class="settingsAuthAccountLeft">
<img class="settingsAuthAccountImage" alt="${acc.displayName}" src="https://nmsr.lsmp.site/fullbodyiso/${acc.uuid}">
</div>
<div class="settingsAuthAccountRight">
<div class="settingsAuthAccountDetails">
<div class="settingsAuthAccountDetailPane">
<div class="settingsAuthAccountDetailTitle">${Lang.queryJS('settings.authAccountPopulate.username')}</div>
<div class="settingsAuthAccountDetailValue">${acc.displayName}</div>
</div>
<div class="settingsAuthAccountDetailPane">
<div class="settingsAuthAccountDetailTitle">${Lang.queryJS('settings.authAccountPopulate.uuid')}</div>
<div class="settingsAuthAccountDetailValue">${acc.uuid}</div>
</div>
</div>
<div class="settingsAuthAccountActions">
<button class="settingsAuthAccountSelect" ${selectedUUID === acc.uuid ? 'selected>' + Lang.queryJS('settings.authAccountPopulate.selectedAccount') : '>' + Lang.queryJS('settings.authAccountPopulate.selectAccount')}</button>
<div class="settingsAuthAccountWrapper">
<button class="settingsAuthAccountLogOut">${Lang.queryJS('settings.authAccountPopulate.logout')}</button>
</div>
</div>
</div>
</div>`
const accHtml2 = `<div class="settingsAuthAccount" uuid="${acc.uuid}">
<div class="settingsAuthAccountLeft"> <div class="settingsAuthAccountLeft">
<img class="settingsAuthAccountImage" alt="${acc.displayName}" src="https://mc-heads.net/body/${acc.uuid}/60"> <img class="settingsAuthAccountImage" alt="${acc.displayName}" src="https://mc-heads.net/body/${acc.uuid}/60">
</div> </div>
<div class="settingsAuthAccountRight"> <div class="settingsAuthAccountRight">
<div class="settingsAuthAccountDetails"> <div class="settingsAuthAccountDetails">
<div class="settingsAuthAccountDetailPane"> <div class="settingsAuthAccountDetailPane">
<div class="settingsAuthAccountDetailTitle">Username</div> <div class="settingsAuthAccountDetailTitle">${Lang.queryJS('settings.authAccountPopulate.username')}</div>
<div class="settingsAuthAccountDetailValue">${acc.displayName}</div> <div class="settingsAuthAccountDetailValue">${acc.displayName}</div>
</div> </div>
<div class="settingsAuthAccountDetailPane"> <div class="settingsAuthAccountDetailPane">
<div class="settingsAuthAccountDetailTitle">UUID</div> <div class="settingsAuthAccountDetailTitle">${Lang.queryJS('settings.authAccountPopulate.uuid')}</div>
<div class="settingsAuthAccountDetailValue">${acc.uuid}</div> <div class="settingsAuthAccountDetailValue">${acc.uuid}</div>
</div> </div>
</div> </div>
<div class="settingsAuthAccountActions"> <div class="settingsAuthAccountActions">
<button class="settingsAuthAccountSelect" ${selectedUUID === acc.uuid ? 'selected>Selected Account &#10004;' : '>Select Account'}</button> <button class="settingsAuthAccountSelect" ${selectedUUID === acc.uuid ? 'selected>' + Lang.queryJS('settings.authAccountPopulate.selectedAccount') : '>' + Lang.queryJS('settings.authAccountPopulate.selectAccount')}</button>
<div class="settingsAuthAccountWrapper"> <div class="settingsAuthAccountWrapper">
<button class="settingsAuthAccountLogOut">Log Out</button> <button class="settingsAuthAccountLogOut">${Lang.queryJS('settings.authAccountPopulate.logout')}</button>
</div> </div>
</div> </div>
</div> </div>
</div>` </div>`
if(acc.type === 'microsoft') { if(acc.type === 'microsoft') {
microsoftAuthAccountStr += accHtml microsoftAuthAccountStr += accHtml2
} else { } else {
mojangAuthAccountStr += accHtml mojangAuthAccountStr += accHtml
} }
@ -717,13 +738,13 @@ const settingsModsContainer = document.getElementById('settingsModsContainer')
/** /**
* Resolve and update the mods on the UI. * Resolve and update the mods on the UI.
*/ */
function resolveModsForUI(){ async function resolveModsForUI(){
const serv = ConfigManager.getSelectedServer() const serv = ConfigManager.getSelectedServer()
const distro = DistroManager.getDistribution() const distro = await DistroAPI.getDistribution()
const servConf = ConfigManager.getModConfiguration(serv) const servConf = ConfigManager.getModConfiguration(serv)
const modStr = parseModulesForUI(distro.getServer(serv).getModules(), false, servConf.mods) const modStr = parseModulesForUI(distro.getServerById(serv).modules, false, servConf.mods)
document.getElementById('settingsReqModsContent').innerHTML = modStr.reqMods document.getElementById('settingsReqModsContent').innerHTML = modStr.reqMods
document.getElementById('settingsOptModsContent').innerHTML = modStr.optMods document.getElementById('settingsOptModsContent').innerHTML = modStr.optMods
@ -743,17 +764,17 @@ function parseModulesForUI(mdls, submodules, servConf){
for(const mdl of mdls){ for(const mdl of mdls){
if(mdl.getType() === DistroManager.Types.ForgeMod || mdl.getType() === DistroManager.Types.LiteMod || mdl.getType() === DistroManager.Types.LiteLoader){ if(mdl.rawModule.type === Type.ForgeMod || mdl.rawModule.type === Type.LiteMod || mdl.rawModule.type === Type.LiteLoader || mdl.rawModule.type === Type.FabricMod){
if(mdl.getRequired().isRequired()){ if(mdl.getRequired().value){
reqMods += `<div id="${mdl.getVersionlessID()}" class="settingsBaseMod settings${submodules ? 'Sub' : ''}Mod" enabled> reqMods += `<div id="${mdl.getVersionlessMavenIdentifier()}" class="settingsBaseMod settings${submodules ? 'Sub' : ''}Mod" enabled>
<div class="settingsModContent"> <div class="settingsModContent">
<div class="settingsModMainWrapper"> <div class="settingsModMainWrapper">
<div class="settingsModStatus"></div> <div class="settingsModStatus"></div>
<div class="settingsModDetails"> <div class="settingsModDetails">
<span class="settingsModName">${mdl.getName()}</span> <span class="settingsModName">${mdl.rawModule.name}</span>
<span class="settingsModVersion">v${mdl.getVersion()}</span> <span class="settingsModVersion">v${mdl.mavenComponents.version}</span>
</div> </div>
</div> </div>
<label class="toggleSwitch" reqmod> <label class="toggleSwitch" reqmod>
@ -761,32 +782,32 @@ function parseModulesForUI(mdls, submodules, servConf){
<span class="toggleSwitchSlider"></span> <span class="toggleSwitchSlider"></span>
</label> </label>
</div> </div>
${mdl.hasSubModules() ? `<div class="settingsSubModContainer"> ${mdl.subModules.length > 0 ? `<div class="settingsSubModContainer">
${Object.values(parseModulesForUI(mdl.getSubModules(), true, servConf[mdl.getVersionlessID()])).join('')} ${Object.values(parseModulesForUI(mdl.subModules, true, servConf[mdl.getVersionlessMavenIdentifier()])).join('')}
</div>` : ''} </div>` : ''}
</div>` </div>`
} else { } else {
const conf = servConf[mdl.getVersionlessID()] const conf = servConf[mdl.getVersionlessMavenIdentifier()]
const val = typeof conf === 'object' ? conf.value : conf const val = typeof conf === 'object' ? conf.value : conf
optMods += `<div id="${mdl.getVersionlessID()}" class="settingsBaseMod settings${submodules ? 'Sub' : ''}Mod" ${val ? 'enabled' : ''}> optMods += `<div id="${mdl.getVersionlessMavenIdentifier()}" class="settingsBaseMod settings${submodules ? 'Sub' : ''}Mod" ${val ? 'enabled' : ''}>
<div class="settingsModContent"> <div class="settingsModContent">
<div class="settingsModMainWrapper"> <div class="settingsModMainWrapper">
<div class="settingsModStatus"></div> <div class="settingsModStatus"></div>
<div class="settingsModDetails"> <div class="settingsModDetails">
<span class="settingsModName">${mdl.getName()}</span> <span class="settingsModName">${mdl.rawModule.name}</span>
<span class="settingsModVersion">v${mdl.getVersion()}</span> <span class="settingsModVersion">v${mdl.mavenComponents.version}</span>
</div> </div>
</div> </div>
<label class="toggleSwitch"> <label class="toggleSwitch">
<input type="checkbox" formod="${mdl.getVersionlessID()}" ${val ? 'checked' : ''}> <input type="checkbox" formod="${mdl.getVersionlessMavenIdentifier()}" ${val ? 'checked' : ''}>
<span class="toggleSwitchSlider"></span> <span class="toggleSwitchSlider"></span>
</label> </label>
</div> </div>
${mdl.hasSubModules() ? `<div class="settingsSubModContainer"> ${mdl.subModules.length > 0 ? `<div class="settingsSubModContainer">
${Object.values(parseModulesForUI(mdl.getSubModules(), true, conf.mods)).join('')} ${Object.values(parseModulesForUI(mdl.subModules, true, conf.mods)).join('')}
</div>` : ''} </div>` : ''}
</div>` </div>`
@ -862,10 +883,10 @@ let CACHE_DROPIN_MODS
* Resolve any located drop-in mods for this server and * Resolve any located drop-in mods for this server and
* populate the results onto the UI. * populate the results onto the UI.
*/ */
function resolveDropinModsForUI(){ async function resolveDropinModsForUI(){
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer())
CACHE_SETTINGS_MODS_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.getID(), 'mods') CACHE_SETTINGS_MODS_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.rawServer.id, 'mods')
CACHE_DROPIN_MODS = DropinModUtil.scanForDropinMods(CACHE_SETTINGS_MODS_DIR, serv.getMinecraftVersion()) CACHE_DROPIN_MODS = DropinModUtil.scanForDropinMods(CACHE_SETTINGS_MODS_DIR, serv.rawServer.minecraftVersion)
let dropinMods = '' let dropinMods = ''
@ -877,7 +898,7 @@ function resolveDropinModsForUI(){
<div class="settingsModDetails"> <div class="settingsModDetails">
<span class="settingsModName">${dropin.name}</span> <span class="settingsModName">${dropin.name}</span>
<div class="settingsDropinRemoveWrapper"> <div class="settingsDropinRemoveWrapper">
<button class="settingsDropinRemoveButton" remmod="${dropin.fullName}">Remove</button> <button class="settingsDropinRemoveButton" remmod="${dropin.fullName}">${Lang.queryJS('settings.dropinMods.removeButton')}</button>
</div> </div>
</div> </div>
</div> </div>
@ -905,9 +926,9 @@ function bindDropinModsRemoveButton(){
document.getElementById(fullName).remove() document.getElementById(fullName).remove()
} else { } else {
setOverlayContent( setOverlayContent(
`Failed to Delete<br>Drop-in Mod ${fullName}`, Lang.queryJS('settings.dropinMods.deleteFailedTitle', { fullName }),
'Make sure the file is not in use and try again.', Lang.queryJS('settings.dropinMods.deleteFailedMessage'),
'Okay' Lang.queryJS('settings.dropinMods.okButton')
) )
setOverlayHandler(null) setOverlayHandler(null)
toggleOverlay(true) toggleOverlay(true)
@ -938,12 +959,12 @@ function bindDropinModFileSystemButton(){
fsBtn.removeAttribute('drag') fsBtn.removeAttribute('drag')
} }
fsBtn.ondrop = e => { fsBtn.ondrop = async e => {
fsBtn.removeAttribute('drag') fsBtn.removeAttribute('drag')
e.preventDefault() e.preventDefault()
DropinModUtil.addDropinMods(e.dataTransfer.files, CACHE_SETTINGS_MODS_DIR) DropinModUtil.addDropinMods(e.dataTransfer.files, CACHE_SETTINGS_MODS_DIR)
reloadDropinMods() await reloadDropinMods()
} }
} }
@ -960,9 +981,9 @@ function saveDropinModConfiguration(){
DropinModUtil.toggleDropinMod(CACHE_SETTINGS_MODS_DIR, dropin.fullName, dropinUIEnabled).catch(err => { DropinModUtil.toggleDropinMod(CACHE_SETTINGS_MODS_DIR, dropin.fullName, dropinUIEnabled).catch(err => {
if(!isOverlayVisible()){ if(!isOverlayVisible()){
setOverlayContent( setOverlayContent(
'Failed to Toggle<br>One or More Drop-in Mods', Lang.queryJS('settings.dropinMods.failedToggleTitle'),
err.message, err.message,
'Okay' Lang.queryJS('settings.dropinMods.okButton')
) )
setOverlayHandler(null) setOverlayHandler(null)
toggleOverlay(true) toggleOverlay(true)
@ -975,18 +996,18 @@ function saveDropinModConfiguration(){
// Refresh the drop-in mods when F5 is pressed. // Refresh the drop-in mods when F5 is pressed.
// Only active on the mods tab. // Only active on the mods tab.
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', async (e) => {
if(getCurrentView() === VIEWS.settings && selectedSettingsTab === 'settingsTabMods'){ if(getCurrentView() === VIEWS.settings && selectedSettingsTab === 'settingsTabMods'){
if(e.key === 'F5'){ if(e.key === 'F5'){
reloadDropinMods() await reloadDropinMods()
saveShaderpackSettings() saveShaderpackSettings()
resolveShaderpacksForUI() await resolveShaderpacksForUI()
} }
} }
}) })
function reloadDropinMods(){ async function reloadDropinMods(){
resolveDropinModsForUI() await resolveDropinModsForUI()
bindDropinModsRemoveButton() bindDropinModsRemoveButton()
bindDropinModFileSystemButton() bindDropinModFileSystemButton()
bindModsToggleSwitch() bindModsToggleSwitch()
@ -1001,9 +1022,9 @@ let CACHE_SELECTED_SHADERPACK
/** /**
* Load shaderpack information. * Load shaderpack information.
*/ */
function resolveShaderpacksForUI(){ async function resolveShaderpacksForUI(){
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer())
CACHE_SETTINGS_INSTANCE_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.getID()) CACHE_SETTINGS_INSTANCE_DIR = path.join(ConfigManager.getInstanceDirectory(), serv.rawServer.id)
CACHE_SHADERPACKS = DropinModUtil.scanForShaderpacks(CACHE_SETTINGS_INSTANCE_DIR) CACHE_SHADERPACKS = DropinModUtil.scanForShaderpacks(CACHE_SETTINGS_INSTANCE_DIR)
CACHE_SELECTED_SHADERPACK = DropinModUtil.getEnabledShaderpack(CACHE_SETTINGS_INSTANCE_DIR) CACHE_SELECTED_SHADERPACK = DropinModUtil.getEnabledShaderpack(CACHE_SETTINGS_INSTANCE_DIR)
@ -1062,13 +1083,13 @@ function bindShaderpackButton() {
spBtn.removeAttribute('drag') spBtn.removeAttribute('drag')
} }
spBtn.ondrop = e => { spBtn.ondrop = async e => {
spBtn.removeAttribute('drag') spBtn.removeAttribute('drag')
e.preventDefault() e.preventDefault()
DropinModUtil.addShaderpacks(e.dataTransfer.files, CACHE_SETTINGS_INSTANCE_DIR) DropinModUtil.addShaderpacks(e.dataTransfer.files, CACHE_SETTINGS_INSTANCE_DIR)
saveShaderpackSettings() saveShaderpackSettings()
resolveShaderpacksForUI() await resolveShaderpacksForUI()
} }
} }
@ -1077,19 +1098,19 @@ function bindShaderpackButton() {
/** /**
* Load the currently selected server information onto the mods tab. * Load the currently selected server information onto the mods tab.
*/ */
function loadSelectedServerOnModsTab(){ async function loadSelectedServerOnModsTab(){
const serv = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()) const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer())
for(const el of document.getElementsByClassName('settingsSelServContent')) { for(const el of document.getElementsByClassName('settingsSelServContent')) {
el.innerHTML = ` el.innerHTML = `
<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>
@ -1097,7 +1118,7 @@ function loadSelectedServerOnModsTab(){
<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>
@ -1107,9 +1128,9 @@ function loadSelectedServerOnModsTab(){
// Bind functionality to the server switch button. // Bind functionality to the server switch button.
Array.from(document.getElementsByClassName('settingsSwitchServerButton')).forEach(el => { Array.from(document.getElementsByClassName('settingsSwitchServerButton')).forEach(el => {
el.addEventListener('click', (e) => { el.addEventListener('click', async e => {
e.target.blur() e.target.blur()
toggleServerSelection(true) await toggleServerSelection(true)
}) })
}) })
@ -1127,8 +1148,8 @@ function saveAllModConfigurations(){
* server is changed. * server is changed.
*/ */
function animateSettingsTabRefresh(){ function animateSettingsTabRefresh(){
$(`#${selectedSettingsTab}`).fadeOut(500, () => { $(`#${selectedSettingsTab}`).fadeOut(500, async () => {
prepareSettings() await prepareSettings()
$(`#${selectedSettingsTab}`).fadeIn(500) $(`#${selectedSettingsTab}`).fadeIn(500)
}) })
} }
@ -1136,15 +1157,15 @@ function animateSettingsTabRefresh(){
/** /**
* Prepare the Mods tab for display. * Prepare the Mods tab for display.
*/ */
function prepareModsTab(first){ async function prepareModsTab(first){
resolveModsForUI() await resolveModsForUI()
resolveDropinModsForUI() await resolveDropinModsForUI()
resolveShaderpacksForUI() await resolveShaderpacksForUI()
bindDropinModsRemoveButton() bindDropinModsRemoveButton()
bindDropinModFileSystemButton() bindDropinModFileSystemButton()
bindShaderpackButton() bindShaderpackButton()
bindModsToggleSwitch() bindModsToggleSwitch()
loadSelectedServerOnModsTab() await loadSelectedServerOnModsTab()
} }
/** /**
@ -1162,16 +1183,6 @@ const settingsJavaExecDetails = document.getElementById('settingsJavaExecDetails
const settingsJavaReqDesc = document.getElementById('settingsJavaReqDesc') const settingsJavaReqDesc = document.getElementById('settingsJavaReqDesc')
const settingsJvmOptsLink = document.getElementById('settingsJvmOptsLink') const settingsJvmOptsLink = document.getElementById('settingsJvmOptsLink')
// Store maximum memory values.
const SETTINGS_MAX_MEMORY = ConfigManager.getAbsoluteMaxRAM()
const SETTINGS_MIN_MEMORY = ConfigManager.getAbsoluteMinRAM()
// Set the max and min values for the ranged sliders.
settingsMaxRAMRange.setAttribute('max', SETTINGS_MAX_MEMORY)
settingsMaxRAMRange.setAttribute('min', SETTINGS_MIN_MEMORY)
settingsMinRAMRange.setAttribute('max', SETTINGS_MAX_MEMORY)
settingsMinRAMRange.setAttribute('min', SETTINGS_MIN_MEMORY )
// Bind on change event for min memory container. // Bind on change event for min memory container.
settingsMinRAMRange.onchange = (e) => { settingsMinRAMRange.onchange = (e) => {
@ -1182,7 +1193,7 @@ settingsMinRAMRange.onchange = (e) => {
// Get reference to range bar. // Get reference to range bar.
const bar = e.target.getElementsByClassName('rangeSliderBar')[0] const bar = e.target.getElementsByClassName('rangeSliderBar')[0]
// Calculate effective total memory. // Calculate effective total memory.
const max = (os.totalmem()-1000000000)/1000000000 const max = os.totalmem()/1073741824
// Change range bar color based on the selected value. // Change range bar color based on the selected value.
if(sMinV >= max/2){ if(sMinV >= max/2){
@ -1214,7 +1225,7 @@ settingsMaxRAMRange.onchange = (e) => {
// Get reference to range bar. // Get reference to range bar.
const bar = e.target.getElementsByClassName('rangeSliderBar')[0] const bar = e.target.getElementsByClassName('rangeSliderBar')[0]
// Calculate effective total memory. // Calculate effective total memory.
const max = (os.totalmem()-1000000000)/1000000000 const max = os.totalmem()/1073741824
// Change range bar color based on the selected value. // Change range bar color based on the selected value.
if(sMaxV >= max/2){ if(sMaxV >= max/2){
@ -1342,8 +1353,8 @@ function updateRangedSlider(element, value, notch){
* Display the total and available RAM. * Display the total and available RAM.
*/ */
function populateMemoryStatus(){ function populateMemoryStatus(){
settingsMemoryTotal.innerHTML = Number((os.totalmem()-1000000000)/1000000000).toFixed(1) + 'G' settingsMemoryTotal.innerHTML = Number((os.totalmem()-1073741824)/1073741824).toFixed(1) + 'G'
settingsMemoryAvail.innerHTML = Number(os.freemem()/1000000000).toFixed(1) + 'G' settingsMemoryAvail.innerHTML = Number(os.freemem()/1073741824).toFixed(1) + 'G'
} }
/** /**
@ -1352,50 +1363,61 @@ function populateMemoryStatus(){
* *
* @param {string} execPath The executable path to populate against. * @param {string} execPath The executable path to populate against.
*/ */
function populateJavaExecDetails(execPath){ async function populateJavaExecDetails(execPath){
const jg = new JavaGuard(DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion()) const server = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer())
jg._validateJavaBinary(execPath).then(v => {
if(v.valid){
const vendor = v.vendor != null ? ` (${v.vendor})` : ''
if(v.version.major < 9) {
settingsJavaExecDetails.innerHTML = `Selected: Java ${v.version.major} Update ${v.version.update} (x${v.arch})${vendor}`
} else {
settingsJavaExecDetails.innerHTML = `Selected: Java ${v.version.major}.${v.version.minor}.${v.version.revision} (x${v.arch})${vendor}`
}
} else {
settingsJavaExecDetails.innerHTML = 'Invalid Selection'
}
})
}
function populateJavaReqDesc() { const details = await validateSelectedJvm(ensureJavaDirIsRoot(execPath), server.effectiveJavaOptions.supported)
const mcVer = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion()
if(Util.mcVersionAtLeast('1.17', mcVer)) { if(details != null) {
settingsJavaReqDesc.innerHTML = 'Requires Java 17 x64.' settingsJavaExecDetails.innerHTML = Lang.queryJS('settings.java.selectedJava', { version: details.semverStr, vendor: details.vendor })
} else { } else {
settingsJavaReqDesc.innerHTML = 'Requires Java 8 x64.' settingsJavaExecDetails.innerHTML = Lang.queryJS('settings.java.invalidSelection')
} }
} }
function populateJvmOptsLink() { function populateJavaReqDesc(server) {
const mcVer = DistroManager.getDistribution().getServer(ConfigManager.getSelectedServer()).getMinecraftVersion() settingsJavaReqDesc.innerHTML = Lang.queryJS('settings.java.requiresJava', { major: server.effectiveJavaOptions.suggestedMajor })
if(Util.mcVersionAtLeast('1.17', mcVer)) { }
settingsJvmOptsLink.innerHTML = 'Available Options for Java 17 (HotSpot VM)'
settingsJvmOptsLink.href = 'https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html#extra-options-for-java' function populateJvmOptsLink(server) {
} else { const major = server.effectiveJavaOptions.suggestedMajor
settingsJvmOptsLink.innerHTML = 'Available Options for Java 8 (HotSpot VM)' settingsJvmOptsLink.innerHTML = Lang.queryJS('settings.java.availableOptions', { major: major })
settingsJvmOptsLink.href = `https://docs.oracle.com/javase/8/docs/technotes/tools/${process.platform === 'win32' ? 'windows' : 'unix'}/java.html` if(major >= 12) {
settingsJvmOptsLink.href = `https://docs.oracle.com/en/java/javase/${major}/docs/specs/man/java.html#extra-options-for-java`
} }
else if(major >= 11) {
settingsJvmOptsLink.href = 'https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE'
}
else if(major >= 9) {
settingsJvmOptsLink.href = `https://docs.oracle.com/javase/${major}/tools/java.htm`
}
else {
settingsJvmOptsLink.href = `https://docs.oracle.com/javase/${major}/docs/technotes/tools/${process.platform === 'win32' ? 'windows' : 'unix'}/java.html`
}
}
function bindMinMaxRam(server) {
// Store maximum memory values.
const SETTINGS_MAX_MEMORY = ConfigManager.getAbsoluteMaxRAM(server.rawServer.javaOptions?.ram)
const SETTINGS_MIN_MEMORY = ConfigManager.getAbsoluteMinRAM(server.rawServer.javaOptions?.ram)
// Set the max and min values for the ranged sliders.
settingsMaxRAMRange.setAttribute('max', SETTINGS_MAX_MEMORY)
settingsMaxRAMRange.setAttribute('min', SETTINGS_MIN_MEMORY)
settingsMinRAMRange.setAttribute('max', SETTINGS_MAX_MEMORY)
settingsMinRAMRange.setAttribute('min', SETTINGS_MIN_MEMORY)
} }
/** /**
* Prepare the Java tab for display. * Prepare the Java tab for display.
*/ */
function prepareJavaTab(){ async function prepareJavaTab(){
bindRangeSlider() const server = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer())
bindMinMaxRam(server)
bindRangeSlider(server)
populateMemoryStatus() populateMemoryStatus()
populateJavaReqDesc() populateJavaReqDesc(server)
populateJvmOptsLink() populateJvmOptsLink(server)
} }
/** /**
@ -1436,11 +1458,11 @@ function isPrerelease(version){
function populateVersionInformation(version, valueElement, titleElement, checkElement){ function populateVersionInformation(version, valueElement, titleElement, checkElement){
valueElement.innerHTML = version valueElement.innerHTML = version
if(isPrerelease(version)){ if(isPrerelease(version)){
titleElement.innerHTML = 'Pre-release' titleElement.innerHTML = Lang.queryJS('settings.about.preReleaseTitle')
titleElement.style.color = '#ff886d' titleElement.style.color = '#ff886d'
checkElement.style.background = '#ff886d' checkElement.style.background = '#ff886d'
} else { } else {
titleElement.innerHTML = 'Stable Release' titleElement.innerHTML = Lang.queryJS('settings.about.stableReleaseTitle')
titleElement.style.color = null titleElement.style.color = null
checkElement.style.background = null checkElement.style.background = null
} }
@ -1479,7 +1501,7 @@ function populateReleaseNotes(){
}, },
timeout: 2500 timeout: 2500
}).catch(err => { }).catch(err => {
settingsAboutChangelogText.innerHTML = 'Failed to load release notes.' settingsAboutChangelogText.innerHTML = Lang.queryJS('settings.about.releaseNotesFailed')
}) })
} }
@ -1527,27 +1549,27 @@ function settingsUpdateButtonStatus(text, disabled = false, handler = null){
*/ */
function populateSettingsUpdateInformation(data){ function populateSettingsUpdateInformation(data){
if(data != null){ if(data != null){
settingsUpdateTitle.innerHTML = `New ${isPrerelease(data.version) ? 'Pre-release' : 'Release'} Available` settingsUpdateTitle.innerHTML = isPrerelease(data.version) ? Lang.queryJS('settings.updates.newPreReleaseTitle') : Lang.queryJS('settings.updates.newReleaseTitle')
settingsUpdateChangelogCont.style.display = null settingsUpdateChangelogCont.style.display = null
settingsUpdateChangelogTitle.innerHTML = data.releaseName settingsUpdateChangelogTitle.innerHTML = data.releaseName
settingsUpdateChangelogText.innerHTML = data.releaseNotes settingsUpdateChangelogText.innerHTML = data.releaseNotes
populateVersionInformation(data.version, settingsUpdateVersionValue, settingsUpdateVersionTitle, settingsUpdateVersionCheck) populateVersionInformation(data.version, settingsUpdateVersionValue, settingsUpdateVersionTitle, settingsUpdateVersionCheck)
if(process.platform === 'darwin'){ if(process.platform === 'darwin'){
settingsUpdateButtonStatus('Download from GitHub<span style="font-size: 10px;color: gray;text-shadow: none !important;">Close the launcher and run the dmg to update.</span>', false, () => { settingsUpdateButtonStatus(Lang.queryJS('settings.updates.downloadButton'), false, () => {
shell.openExternal(data.darwindownload) shell.openExternal(data.darwindownload)
}) })
} else { } else {
settingsUpdateButtonStatus('Downloading..', true) settingsUpdateButtonStatus(Lang.queryJS('settings.updates.downloadingButton'), true)
} }
} else { } else {
settingsUpdateTitle.innerHTML = 'You Are Running the Latest Version' settingsUpdateTitle.innerHTML = Lang.queryJS('settings.updates.latestVersionTitle')
settingsUpdateChangelogCont.style.display = 'none' settingsUpdateChangelogCont.style.display = 'none'
populateVersionInformation(remote.app.getVersion(), settingsUpdateVersionValue, settingsUpdateVersionTitle, settingsUpdateVersionCheck) populateVersionInformation(remote.app.getVersion(), settingsUpdateVersionValue, settingsUpdateVersionTitle, settingsUpdateVersionCheck)
settingsUpdateButtonStatus('Check for Updates', false, () => { settingsUpdateButtonStatus(Lang.queryJS('settings.updates.checkForUpdatesButton'), false, () => {
if(!isDev){ if(!isDev){
ipcRenderer.send('autoUpdateAction', 'checkForUpdate') ipcRenderer.send('autoUpdateAction', 'checkForUpdate')
settingsUpdateButtonStatus('Checking for Updates..', true) settingsUpdateButtonStatus(Lang.queryJS('settings.updates.checkingForUpdatesButton'), true)
} }
}) })
} }
@ -1571,17 +1593,17 @@ function prepareUpdateTab(data = null){
* *
* @param {boolean} first Whether or not it is the first load. * @param {boolean} first Whether or not it is the first load.
*/ */
function prepareSettings(first = false) { async function prepareSettings(first = false) {
if(first){ if(first){
setupSettingsTabs() setupSettingsTabs()
initSettingsValidators() initSettingsValidators()
prepareUpdateTab() prepareUpdateTab()
} else { } else {
prepareModsTab() await prepareModsTab()
} }
initSettingsValues() await initSettingsValues()
prepareAccountsTab() prepareAccountsTab()
prepareJavaTab() await prepareJavaTab()
prepareAboutTab() prepareAboutTab()
} }

View File

@ -4,11 +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')
const Lang = require('./assets/js/langloader')
let rscShouldLoad = false let rscShouldLoad = false
let fatalStartupError = false let fatalStartupError = false
@ -40,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()
}) })
}) })
} }
@ -57,15 +57,15 @@ function getCurrentView(){
return currentView return currentView
} }
function showMainUI(data){ async function showMainUI(data){
if(!isDev){ if(!isDev){
loggerAutoUpdater.info('Initializing..') loggerAutoUpdater.info('Initializing..')
ipcRenderer.send('autoUpdateAction', 'initAutoUpdater', ConfigManager.getAllowPrerelease()) ipcRenderer.send('autoUpdateAction', 'initAutoUpdater', ConfigManager.getAllowPrerelease())
} }
prepareSettings(true) await prepareSettings(true)
updateSelectedServer(data.getServer(ConfigManager.getSelectedServer())) 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)'
@ -114,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()
@ -133,7 +133,7 @@ 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)
@ -149,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){
@ -161,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
@ -197,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
} }
} }
} }
@ -232,8 +232,8 @@ function syncModConfigurations(data){
function ensureJavaSettings(data) { function ensureJavaSettings(data) {
// Nothing too fancy for now. // Nothing too fancy for now.
for(const serv of data.getServers()){ for(const serv of data.servers){
ConfigManager.ensureJavaConfig(serv.getID(), serv.getMinecraftVersion()) ConfigManager.ensureJavaConfig(serv.rawServer.id, serv.effectiveJavaOptions, serv.rawServer.javaOptions?.ram)
} }
ConfigManager.save() ConfigManager.save()
@ -251,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
} }
} }
} }
@ -272,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
} }
/** /**
@ -323,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){
@ -344,10 +332,12 @@ 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(() => {
@ -429,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()
} }
@ -446,13 +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) 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
} }
@ -467,11 +457,10 @@ ipcRenderer.on('distributionIndexDone', (event, res) => {
}) })
// Util for development // Util for development
function devModeToggle() { async function devModeToggle() {
DistroManager.setDevMode(true) DistroAPI.toggleDevMode(true)
DistroManager.pullLocal().then((data) => { const data = await DistroAPI.refreshDistributionOrFallback()
ensureJavaSettings(data) ensureJavaSettings(data)
updateSelectedServer(data.getServers()[0]) updateSelectedServer(data.servers[0])
syncModConfigurations(data) syncModConfigurations(data)
})
} }

View File

@ -9,7 +9,8 @@ const $ = require('jquery')
const {ipcRenderer, shell, webFrame} = require('electron') const {ipcRenderer, shell, webFrame} = require('electron')
const remote = require('@electron/remote') const remote = require('@electron/remote')
const isDev = require('./assets/js/isdev') const isDev = require('./assets/js/isdev')
const { LoggerUtil } = require('helios-core') const { LoggerUtil } = require('limbo-core')
const Lang = require('./assets/js/langloader')
const loggerUICore = LoggerUtil.getLogger('UICore') const loggerUICore = LoggerUtil.getLogger('UICore')
const loggerAutoUpdater = LoggerUtil.getLogger('AutoUpdater') const loggerAutoUpdater = LoggerUtil.getLogger('AutoUpdater')
@ -42,13 +43,13 @@ if(!isDev){
switch(arg){ switch(arg){
case 'checking-for-update': case 'checking-for-update':
loggerAutoUpdater.info('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':
loggerAutoUpdater.info('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/dscalzi/HeliosLauncher/releases/download/v${info.version}/Helios-Launcher-setup-${info.version}${process.arch === 'arm64' ? '-arm64' : '-x64'}.dmg` info.darwindownload = `https://github.com/Limbo-Studios/LimboLauncher/releases/download/v${info.version}/Limbo-Launcher-setup-${info.version}${process.arch === 'arm64' ? '-arm64' : '-x64'}.dmg`
showUpdateUI(info) showUpdateUI(info)
} }
@ -56,7 +57,7 @@ if(!isDev){
break break
case 'update-downloaded': case 'update-downloaded':
loggerAutoUpdater.info('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,7 +66,7 @@ if(!isDev){
break break
case 'update-not-available': case 'update-not-available':
loggerAutoUpdater.info('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(() => {

View File

@ -0,0 +1,19 @@
# Custom Language File for Launcher Customizer
[ejs.app]
title = "Limbo Launcher"
[ejs.landing]
mediaGitHubURL = "https://github.com/Limbo-Studios/LimboLauncher"
mediaTwitterURL = "#"
mediaInstagramURL = "#"
mediaYouTubeURL = "#"
mediaDiscordURL = "#"
[ejs.settings]
sourceGithubLink = "https://github.com/Limbo-Studios/LimboLauncher"
supportLink = "https://github.com/Limbo-Studios/LimboLauncher/issues"
[ejs.welcome]
welcomeHeader = "BIENVENIDO A LIMBO-STUDIOS"
welcomeDescription = "Hemos creado este launcher para que puedas disfrutar de nuestros proyectos, el proyecto el curso por ahora es Lurkarya, pero siempre podrán estar atentos a más! Lurkarya es un servidor survival multijugador con lore, ahora estamos en el segundo acto y estamos impacientes por ver que les depará el destino ;)"

View File

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

344
app/assets/lang/en_US.toml Normal file
View File

@ -0,0 +1,344 @@
[ejs.landing]
updateAvailableTooltip = "Actualización disponible"
usernamePlaceholder = "Usuario"
usernameEditButton = "Editar"
settingsTooltip = ""
serverStatus = "SERVIDOR"
serverStatusPlaceholder = "OFFLINE"
mojangStatus = "ESTADO DE LIMBOAUTH"
mojangStatusTooltipTitle = "Servicios"
mojangStatusNETitle = "Non&nbsp;Essential"
newsButton = "NOTICIAS"
launchButton = "JUGAR"
launchButtonPlaceholder = "&#8226; Ningún servidor seleccionado"
launchDetails = "Por favor, espere..."
newsNavigationStatus = "{currentPage} de {totalPages}"
newsErrorLoadSpan = "Buscando noticias..."
newsErrorFailedSpan = "Fallo al cargar noticias."
newsErrorRetryButton = "Intentar de nuevo"
newsErrorNoneSpan = "Sin noticias"
[ejs.login]
loginCancelText = "Cancelar"
loginSubheader = "LIMBO LOGIN"
loginEmailError = "* Valor invalido"
loginEmailPlaceholder = "USUARIO"
loginPasswordError = "* Requerido"
loginPasswordPlaceholder = "CONTRASEÑA"
loginForgotPasswordLink = "https://discord.com/channels/1088661129615642716/1095223903636426823"
loginForgotPasswordText = "Olvidaste la contraseña?"
loginRememberMeText = "remember me?"
loginButtonText = "LOGIN"
loginNeedAccountLink = "https://discord.com/channels/1088661129615642716/1095223903636426823"
loginNeedAccountText = "Necesitas una cuenta?"
loginPasswordDisclaimer1 = "Tu contraseña se encripta en un hash y no es almacenada directamente en el launcher ni en LimboAuth."
loginPasswordDisclaimer2 = "{appName} is not affiliated with Mojang AB."
[ejs.loginOptions]
loginOptionsTitle = "Opciones de Inicio de sesión"
loginWithMicrosoft = "Iniciar sesión con Microsoft"
loginWithMojang = "Iniciar sesión con LimboAuth"
cancelButton = "Cancel"
[ejs.overlay]
serverSelectHeader = "Servidores disponibles"
serverSelectConfirm = "Seleccionar"
serverSelectCancel = "Cancelar"
accountSelectHeader = "Selecciona una Cuenta"
accountSelectConfirm = "Seleccionar"
accountSelectCancel = "Cancelar"
[ejs.settings]
navHeaderText = "Ajustes"
navAccount = "Cuenta"
navMinecraft = "Minecraft"
navMods = "Mods"
navJava = "Java"
navLauncher = "Launcher"
navAbout = "Acerca de"
navUpdates = "Actualizaciones"
navDone = "Hecho"
tabAccountHeaderText = "Ajustes de la cuenta"
tabAccountHeaderDesc = "Añadir o administrar cuentas."
microsoftAccount = "Microsoft"
addMicrosoftAccount = "+ Añadir Cuenta de Microsoft"
mojangAccount = "Limbo"
addMojangAccount = "+ Añadir Cuenta de LimboAuth"
minecraftTabHeaderText = "Ajustes de Minecraft"
minecraftTabHeaderDesc = "Opciones relacionadas al abrir el juego."
gameResolutionTitle = "Resolución del juego"
launchFullscreenTitle = "Abrir en pantalla completa."
autoConnectTitle = "Automaticamente conectar al servidor al abrir."
launchDetachedTitle = "Launch game process detached from launcher."
launchDetachedDesc = "If the game is not detached, closing the launcher will also close the game."
tabModsHeaderText = "Ajustes de mod"
tabModsHeaderDesc = "Activar o desactivar mods."
switchServerButton = "Switch"
requiredMods = "Mods requeridos"
optionalMods = "Mods opcionales"
dropinMods = "Drop-in Mods"
addMods = "Añadir Mods"
dropinRefreshNote = "(F5 para actualizar)"
shaderpacks = "Paquetes de Shaders"
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 &#10004;"
[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.settings.msftLogin]
errorTitle = "Microsoft Login Failed"
errorMessage = "We were unable to authenticate your Microsoft account. Please try again."
okButton = "OK"
[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"
[js.auth.microsoft.error]
noProfileTitle = "Error During Login:<br>Profile Not Set Up"
noProfileDesc = "Your Microsoft account does not yet have a Minecraft profile set up. If you have recently purchased the game or redeemed it through Xbox Game Pass, you have to set up your profile on <a href=\"https://minecraft.net/\">Minecraft.net</a>.<br><br>If you have not yet purchased the game, you can also do that on <a href=\"https://minecraft.net/\">Minecraft.net</a>."
noXboxAccountTitle = "Error During Login:<br>No Xbox Account"
noXboxAccountDesc = "Your Microsoft account has no Xbox account associated with it."
xblBannedTitle = "Error During Login:<br>Xbox Live Unavailable"
xblBannedDesc = "Your Microsoft account is from a country where Xbox Live is not available or banned."
under18Title = "Error During Login:<br>Parental Approval Required"
under18Desc = "Accounts for users under the age of 18 must be added to a Family by an adult."
unknownTitle = "Unknown Error During Login"
unknownDesc = "An unknown error has occurred. Please see the console for details."
[js.auth.mojang.error]
methodNotAllowedTitle = "Internal Error:<br>Method Not Allowed"
methodNotAllowedDesc = "Method not allowed. Please report this error."
notFoundTitle = "Internal Error:<br>Not Found"
notFoundDesc = "The authentication endpoint was not found. Please report this issue."
accountMigratedTitle = "Error During Login:<br>Account Migrated"
accountMigratedDesc = "You've attempted to login with a migrated account. Try again using the account email as the username."
invalidCredentialsTitle = "Error During Login:<br>Invalid Credentials"
invalidCredentialsDesc = "The email or password you've entered is incorrect. Please try again."
tooManyAttemptsTitle = "Error During Login:<br>Too Many Attempts"
tooManyAttemptsDesc = "There have been too many login attempts with this account recently. Please try again later."
invalidTokenTitle = "Error During Login:<br>Invalid Token"
invalidTokenDesc = "The provided access token is invalid."
tokenHasProfileTitle = "Error During Login:<br>Token Has Profile"
tokenHasProfileDesc = "Access token already has a profile assigned. Selecting profiles is not implemented yet."
credentialsMissingTitle = "Error During Login:<br>Credentials Missing"
credentialsMissingDesc = "Username/password was not submitted or password is less than 3 characters."
invalidSaltVersionTitle = "Error During Login:<br>Invalid Salt Version"
invalidSaltVersionDesc = "Invalid salt version."
unsupportedMediaTypeTitle = "Internal Error:<br>Unsupported Media Type"
unsupportedMediaTypeDesc = "Unsupported media type. Please report this error."
accountGoneTitle = "Error During Login:<br>Account Migrated"
accountGoneDesc = "Account has been migrated to a Microsoft account. Please log in with Microsoft."
unreachableTitle = "Error During Login:<br>Unreachable"
unreachableDesc = "Unable to reach the authentication servers. Ensure that they are online and you are connected to the internet."
gameNotPurchasedTitle = "Error During Login:<br>Game Not Purchased"
gameNotPurchasedDesc = "The account you are trying to login with has not purchased a copy of Minecraft. You may purchase a copy on <a href=\"https://minecraft.net/\">Minecraft.net</a>"
unknownErrorTitle = "Unknown Error During Login"
unknownErrorDesc = "An unknown error has occurred. Please see the console for details."

298
app/assets/lang/es_ES.toml Normal file
View File

@ -0,0 +1,298 @@
# Translate by Luis Bazán (luisbazandev)
[ejs.landing]
updateAvailableTooltip = "Actualización disponible"
usernamePlaceholder = "Nombre de usuario"
usernameEditButton = "Editar"
settingsTooltip = "Configuraciónes"
serverStatus = "SERVIDOR"
serverStatusPlaceholder = "DESCONECTADO"
mojangStatus = "ESTADO DE MOJANG"
mojangStatusTooltipTitle = "Servicios"
mojangStatusNETitle = "Non&nbsp;Essential"
newsButton = "NOTICIAS"
launchButton = "JUGAR"
launchButtonPlaceholder = "&#8226; Ningún servidor seleccionado"
launchDetails = "Por favor espere.."
newsNavigationStatus = "{currentPage} de {totalPages}"
newsErrorLoadSpan = "Buscando nuevas noticias.."
newsErrorFailedSpan = "Fallo al cargar las noticias"
newsErrorRetryButton = "Intenta denuevo"
newsErrorNoneSpan = "No hay noticias"
[ejs.login]
loginCancelText = "Cancelar"
loginSubheader = "Inicio de sesión MINECRAFT"
loginEmailError = "* Valor invaido"
loginEmailPlaceholder = "CORREO O NOMBRE DE USUARIO"
loginPasswordError = "* Requerido"
loginPasswordPlaceholder = "CONTRASEÑA"
loginForgotPasswordLink = "https://minecraft.net/password/forgot/"
loginForgotPasswordText = "olvidaste tu contraseña?"
loginRememberMeText = "Recuerdame?"
loginButtonText = "Inicio de sesión"
loginNeedAccountLink = "https://minecraft.net/store/minecraft-java-edition/"
loginNeedAccountText = "Necesitas una cuenta?"
loginPasswordDisclaimer1 = "Tu contraseña se envía directamente a mojang y nunca se almacena."
loginPasswordDisclaimer2 = "{appName} no esta afiliado con Mojang AB."
[ejs.loginOptions]
loginOptionsTitle = "Opciones de inicio de sesión"
loginWithMicrosoft = "Inicio de sesión con Microsoft"
loginWithMojang = "Inicio de sesión con Mojang"
cancelButton = "Cancelar"
[ejs.overlay]
serverSelectHeader = "Servidores disponibles"
serverSelectConfirm = "Confirmar"
serverSelectCancel = "Cancelar"
accountSelectHeader = "Selecione una cuenta"
accountSelectConfirm = "Confirmar"
accountSelectCancel = "Cancelar"
[ejs.settings]
navHeaderText = "Configuración"
navAccount = "Cuenta"
navMinecraft = "Minecraft"
navMods = "Mods"
navJava = "Java"
navLauncher = "Launcher"
navAbout = "Acerca de"
navUpdates = "Actualizaciones"
navDone = "Listo"
tabAccountHeaderText = "Configuracion de Cuenta"
tabAccountHeaderDesc = "Agrega una nueva cuenta o administre las existentes"
microsoftAccount = "Microsoft"
addMicrosoftAccount = "+ Añadir cuenta de Microsoft"
mojangAccount = "Mojang"
addMojangAccount = "+ Añadir cuenta de Mojang"
minecraftTabHeaderText = "Configuraciones de Minecraft"
minecraftTabHeaderDesc = "Opciones relacionadas con el inicio del juego."
gameResolutionTitle = "Resolución del juego"
launchFullscreenTitle = "Iniciar en pantalla completa."
autoConnectTitle = "Conectarse automáticamente al servidor al iniciar."
launchDetachedTitle = "Proceso del juego separado al launcher."
launchDetachedDesc = "Si el launcher se cierra, cierra tambien el juego."
tabModsHeaderText = "Configuraciones de Mod"
tabModsHeaderDesc = "Activa o desactiva mods."
switchServerButton = "Cambiar"
requiredMods = "Mods requeridos"
optionalMods = "Mods opcionales"
dropinMods = "Suelta los Mods"
addMods = "Agregar Mods"
dropinRefreshNote = "(F5 para recargar)"
shaderpacks = "Shaderpacks"
shaderpackDesc = "Activar o desactivar shaders. Tenga en cuenta que los shaders funcionan sin problemas en configuraciones potentes. Puede agregar paquetes personalizados aquí."
selectShaderpack = "Seleciona un Shaderpack"
tabJavaHeaderText = "Configuraciones de Java"
tabJavaHeaderDesc = "Administra las configuraciones de Java (avanzado)."
memoryTitle = "Memoria"
maxRAM = "Máximo de RAM"
minRAM = "Mínimo de RAM"
memoryDesc = "La memoria RAM mínima recomendada es de 3 gigabytes. Configurar el mínimo y máximo en el mismo valor puede reducir el lag."
memoryTotalTitle = "Total"
memoryAvailableTitle = "Disponible"
javaExecutableTitle = "Ejecutable de Java"
javaExecSelDialogTitle = "Selecciona un ejecutable de Java"
javaExecSelButtonText = "Examinar"
javaExecDesc = "El ejecutable de Java se valida antes de iniciar el juego."
javaPathDesc = "La ruta debe terminar con <strong>{pathSuffix}</strong>."
jvmOptsTitle = "Opciones adicionales de la JVM"
jvmOptsDesc = "Options to be provided to the JVM at runtime. <em>-Xms</em> and <em>-Xmx</em> should not be included."
launcherTabHeaderText = "Opciones del Launcher"
launcherTabHeaderDesc = "Opciones relacionadas con el propio launcher."
allowPrereleaseTitle = "Permitir versiones de pre lanzamiento(Pre-Release)."
allowPrereleaseDesc = "Las versiones de pre lanzamiento incluyen nuevas características que talvez no estén del todo probadas o integradas.<br>Esto siempre será cierto si está utilizando una versión de pre lanzamiento."
dataDirectoryTitle = "Carpeta de almacenamiento"
selectDataDirectory = "Seleccionar carpeta de almacenamiento"
chooseFolder = "Examinar folder"
dataDirectoryDesc = "Todos los archivos del juego y las instalaciones locales de Java se almacenarán en la carpeta de almacenamiento.<br>Las capturas de pantalla y los mundos se almacenan en la carpeta de la instancia para la configuración del servidor correspondiente."
aboutTabHeaderText = "Acerca de"
aboutTabHeaderDesc = "Ver información y registros de cambios de esta versión."
aboutTitle = "{appName}"
stableRelease = "Versión estable"
versionText = "Versión "
sourceGithub = "Fuente (GitHub)"
support = "Soporte"
devToolsConsole = "DevTools Console"
releaseNotes = "Notas de versión"
changelog = "Registro de cambios"
noReleaseNotes = "Sin notas de versión"
viewReleaseNotes = "Ver notas de versión en GitHub"
launcherUpdatesHeaderText = "Actualizaciones del Launcher"
launcherUpdatesHeaderDesc = "Descargar, instalar y revisar las actualizaciones del launcher."
checkForUpdates = "Buscar actualizaciones"
whatsNew = "Que hay de nuevo?"
updateReleaseNotes = "Actualizar notas de versión"
[ejs.waiting]
waitingText = "Esperando a Microsoft.."
[ejs.welcome]
continueButton = "CONTINUAR"
[js.discord]
waiting = "Esperando al cliente.."
state = "Servidor: {shortId}"
[js.index]
microsoftLoginTitle = "Iniciar sesión con Microsoft"
microsoftLogoutTitle = "Cerrar sesión con Microsoft"
[js.login]
login = "Iniciar sesión"
loggingIn = "Iniciando sesión"
success = "Exitoso"
tryAgain = "Intentar denuevo"
[js.login.error]
invalidValue = "* Valor invalido"
requiredValue = "* Requerido"
[js.login.error.unknown]
title = "Error desconocido durante el inicio de sesión"
desc = "Un error desconocido a ocurrido. Consulte la consola para obtener más detalles."
[js.landing.launch]
pleaseWait = "Por favor espere.."
failureTitle = "Error durante el inicio"
failureText = "Ver la consola (CTRL + Shift + i) para más detalles."
okay = "Okay"
[js.landing.selectedAccount]
noAccountSelected = "Ninguna cuenta seleccionada"
[js.landing.selectedServer]
noSelection = "Ningun servidor selecionado"
loading = "Cargando.."
[js.landing.serverStatus]
server = "SERVIDOR"
offline = "DESCONECTADO"
players = "JUGADORES"
[js.landing.systemScan]
checking = "Verificando la información del sistema.."
noCompatibleJava = "No se encontró<br>una versión de Java compatible"
installJavaMessage = "In order to launch Minecraft, you need a 64-bit installation of Java {major}. Would you like us to install a copy?"
installJava = "Instalar Java"
installJavaManually = "Instalar manualmente"
javaDownloadPrepare = "Preparando descarga de Java.."
javaDownloadFailureTitle = "Error durante la descarga de Java"
javaDownloadFailureText = "Ver la consola (CTRL + Shift + i) para más detalles."
javaRequired = "Java es requerido<br>para iniciar"
javaRequiredMessage = 'Para iniciar se requiere una version de Java mayor a {major}.<br><br>Por favor diríjase a <a href="https://github.com/dscalzi/HeliosLauncher/wiki/Java-Management#manually-installing-a-valid-version-of-java">Guía de administración de Java</a> para instrucciónes de instalación manual.'
javaRequiredDismiss = "Entiendo"
javaRequiredCancel = "Regresar"
[js.landing.downloadJava]
findJdkFailure = "Fallo al buscar una distribución de OpenJDK."
javaDownloadCorruptedError = "El código hash del JDK descargado es erroneo, es probable que el archivo esté dañado."
extractingJava = "Extrayendo Java"
javaInstalled = "Java instalado!"
[js.landing.dlAsync]
loadingServerInfo = "Cargando información del servidor.."
fatalError = "Error fatal"
unableToLoadDistributionIndex = "No se pudo cargar una copia del índice de distribuciónes. Ver la consola (CTRL + Shift + i) para más detalles."
pleaseWait = "Por favor espere.."
errorDuringLaunchTitle = "Error durante el inicio"
seeConsoleForDetails = "Ver la consola (CTRL + Shift + i) para más detalles."
validatingFileIntegrity = "Validando integridad del archivo.."
errorDuringFileVerificationTitle = "Error durante la verificación del archivo"
downloadingFiles = "Descargando archivos.."
errorDuringFileDownloadTitle = "Error durante la descarga del archivo"
preparingToLaunch = "Preparando el inicio.."
launchingGame = "Iniciando el juego.."
launchWrapperNotDownloaded = "El archivo principal, LaunchWrapper, no se pudo descargar. Como resultado, el juego no se pudo iniciar.<br><br>Para solucionar este problema, apaga temporalmente tu software antivirus y vuelve a iniciar el juego.<br><br>Si tienes tiempo, por favor <a href=\"https://github.com/dscalzi/HeliosLauncher/issues\">envia un issue</a> y cuéntanos qué software antivirus utilizas. Nos pondremos en contacto con ellos e intentaremos arreglar las cosas."
doneEnjoyServer = "Hecho. Disfruta del servidor!"
checkConsoleForDetails = "Ver la consola (CTRL + Shift + i) para más detalles."
[js.landing.news]
checking = "Buscando noticias"
[js.landing.discord]
loading = "Cargando juego.."
joining = "Sailing to Westeros!" # IDK how to translate this xD
joined = "Explora el Realm!"
[js.overlay]
dismiss = "Dismiss"
[js.settings.fileSelectors]
executables = "Ejecutables"
allFiles = "Todos los archivos"
[js.settings.mstfLogin]
errorTitle = "Algo salió mal"
errorMessage = "La autenticación de Microsoft falló. Por favor inténtalo denuevo."
okButton = "OK"
[js.settings.mstfLogout]
errorTitle = "Algo salió mal"
errorMessage = "Cierre de sesión de Microsoft falló. Por favor inténtalo denuevo."
okButton = "OK"
[js.settings.authAccountSelect]
selectButton = "Selecionar cuenta"
selectedButton = "Selecionar cuenta &#10004;"
[js.settings.authAccountLogout]
lastAccountWarningTitle = "Advertencia<br>Está es su última cuenta"
lastAccountWarningMessage = "Para utilizar el launcher debe iniciar sesión en al menos una cuenta. Deberá iniciar sesión nuevamente después.<br><br>Está seguro de cerrar sesión?"
confirmButton = "Estoy seguro"
cancelButton = "Cancelar"
[js.settings.authAccountPopulate]
username = "Nombre de usuario"
uuid = "UUID"
selectAccount = "Selecionar cuenta"
selectedAccount = "Cuenta selecionada ✓"
logout = "Cerrar sesión"
[js.settings.dropinMods]
removeButton = "Remover"
deleteFailedTitle = "No se pudo borrar<br>el Mod {fullName}"
deleteFailedMessage = "Asegúrese de que el archivo no esté en uso y vuelva a intentarlo."
failedToggleTitle = "No se pudo alternar<br>Uno o más Mods"
okButton = "Okay"
[js.settings.serverListing]
mainServer = "Servidor principal"
[js.settings.java]
selectedJava = "Selecionado: Java {version} ({vendor})"
invalidSelection = "Seleción invalida"
requiresJava = "Requiere Java {major} x64."
availableOptions = "Opciones disponibles para Java {major} (HotSpot VM)"
[js.settings.about]
preReleaseTitle = "Versión de pre lanzamiento"
stableReleaseTitle = "Versión estable"
releaseNotesFailed = "No se pudieron cargar las notas de versión."
[js.settings.updates]
newReleaseTitle = "Nueva versión disponible"
newPreReleaseTitle = "Nueva versión de pre lanzamiento disponible"
downloadingButton = "Descargando.."
downloadButton = 'Descargar desde GitHub<span style="font-size: 10px;color: gray;text-shadow: none !important;">Cierra el launcher y Close ejecute el dmg para actualizar.</span>'
latestVersionTitle = "Actualmente tiene la última versión"
checkForUpdatesButton = "Buscar actualizaciones"
checkingForUpdatesButton = "Buscando actualizaciones.."
[js.uibinder.startup]
fatalErrorTitle = "Error fatal: No se puede cargar el índice de distribución"
fatalErrorMessage = "No se pudo establecer una conexión con nuestros servidores para descargar el índice de distribución. No había copias locales disponibles para cargar. <br><br>El índice de distribución es un archivo esencial que proporciona la información más reciente del servidor. El launcher no puede iniciarse sin él. Asegúrese de estar conectado a Internet y reinicie la aplicación."
closeButton = "Cerrar"
[js.uibinder.validateAccount]
failedMessageTitle = "No se pudo actualizar el inicio de sesión"
failedMessage = "No pudimos actualizar el inicio de sesión de <strong>{account}</strong>. Seleccione otra cuenta o inicie sesión nuevamente."
failedMessageSelectAnotherAccount = "No pudimos actualizar el inicio de sesión de <strong>{account}</strong>. Por favor inicie sesión nuevamente."
loginButton = "Iniciar sesión"
selectAnotherAccountButton = "Selecionar otra cuenta"
[js.uicore.autoUpdate]
checkingForUpdateButton = "Buscando actualizaciones..."
installNowButton = "Instalar ahora"
checkForUpdatesButton = "Buscar actualizaciones"

View File

@ -13,7 +13,7 @@
<% } else{ %> <% } else{ %>
<div id="frameContentWin"> <div id="frameContentWin">
<div id="frameTitleDock"> <div id="frameTitleDock">
<span id="frameTitleText">Helios Launcher</span> <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">

View File

@ -3,7 +3,7 @@
<div id="left"> <div id="left">
<div id="image_seal_container"> <div id="image_seal_container">
<img id="image_seal" src="assets/images/SealCircle.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="https://github.com/dscalzi/HeliosLauncher" 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="#" class="mediaURL" id="twitterURL" disabled> <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="#" class="mediaURL" id="instagramURL" disabled> <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="#" class="mediaURL" id="youtubeURL" disabled> <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/zNWUXdt" 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">&#8226;</span> <span id="mojang_status_icon">&#8226;</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&nbsp;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>
&#10;<span id="newsButtonText">NEWS</span> &#10;<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">&#8226; 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>

View File

@ -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/SealCircle.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://minecraft.net/password/forgot/">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/store/minecraft-java-edition/">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">Helios Launcher is not affiliated with Mojang AB.</p> <p class="loginDisclaimerText"><%- lang('login.loginPasswordDisclaimer2', { appName: lang('app.title') }) %></p>
</div> </div>
</form> </form>
</div> </div>

View File

@ -1,7 +1,7 @@
<div id="loginOptionsContainer" style="display: none;"> <div id="loginOptionsContainer" style="display: none;">
<div id="loginOptionsContent"> <div id="loginOptionsContent">
<div class="loginOptionsMainContent"> <div class="loginOptionsMainContent">
<h2>Login Options</h2> <h2><%- lang('loginOptions.loginOptionsTitle') %></h2>
<div class="loginOptionActions"> <div class="loginOptionActions">
<div class="loginOptionButtonContainer"> <div class="loginOptionButtonContainer">
<button id="loginOptionMicrosoft" class="loginOptionButton"> <button id="loginOptionMicrosoft" class="loginOptionButton">
@ -11,7 +11,7 @@
<path fill="#05a6f0" d="M1 12h10v10H1z" /> <path fill="#05a6f0" d="M1 12h10v10H1z" />
<path fill="#ffba08" d="M12 12h10v10H12z" /> <path fill="#ffba08" d="M12 12h10v10H12z" />
</svg> </svg>
<span>Login with Microsoft</span> <span><%- lang('loginOptions.loginWithMicrosoft') %></span>
</button> </button>
</div> </div>
<div class="loginOptionButtonContainer"> <div class="loginOptionButtonContainer">
@ -21,12 +21,12 @@
<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="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" /> <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> </svg>
<span>Login with Mojang</span> <span><%- lang('loginOptions.loginWithMojang') %></span>
</button> </button>
</div> </div>
</div> </div>
<div id="loginOptionCancelContainer" style="display: none;"> <div id="loginOptionCancelContainer" style="display: none;">
<button id="loginOptionCancelButton">Cancel</button> <button id="loginOptionCancelButton"><%- lang('loginOptions.cancelButton') %></button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -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>

View File

@ -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,8 +25,8 @@
<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 class="settingsAuthAccountTypeContainer"> <div class="settingsAuthAccountTypeContainer">
<div class="settingsAuthAccountTypeHeader"> <div class="settingsAuthAccountTypeHeader">
@ -37,10 +37,10 @@
<path fill="#05a6f0" d="M1 12h10v10H1z" /> <path fill="#05a6f0" d="M1 12h10v10H1z" />
<path fill="#ffba08" d="M12 12h10v10H12z" /> <path fill="#ffba08" d="M12 12h10v10H12z" />
</svg> </svg>
<span>Microsoft</span> <span><%- lang('settings.microsoftAccount') %></span>
</div> </div>
<div class="settingsAuthAccountTypeHeaderRight"> <div class="settingsAuthAccountTypeHeaderRight">
<button class="settingsAddAuthAccount" id="settingsAddMicrosoftAccount">+ Add Microsoft Account</button> <button class="settingsAddAuthAccount" id="settingsAddMicrosoftAccount"><%- lang('settings.addMicrosoftAccount') %></button>
</div> </div>
</div> </div>
@ -57,10 +57,10 @@
<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="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" /> <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> </svg>
<span>Mojang</span> <span><%- lang('settings.mojangAccount') %></span>
</div> </div>
<div class="settingsAuthAccountTypeHeaderRight"> <div class="settingsAuthAccountTypeHeaderRight">
<button class="settingsAddAuthAccount" id="settingsAddMojangAccount">+ Add Mojang Account</button> <button class="settingsAddAuthAccount" id="settingsAddMojangAccount"><%- lang('settings.addMojangAccount') %></button>
</div> </div>
</div> </div>
@ -71,11 +71,11 @@
</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">&#10006;</div> <div id="settingsGameResolutionCross">&#10006;</div>
@ -84,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">
@ -95,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">
@ -106,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">
@ -119,8 +119,8 @@
</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 class="settingsSelServContainer"> <div class="settingsSelServContainer">
<div class="settingsSelServContent"> <div class="settingsSelServContent">
@ -128,37 +128,37 @@
</div> </div>
<div class="settingsSwitchServerContainer"> <div class="settingsSwitchServerContainer">
<div class="settingsSwitchServerContent"> <div class="settingsSwitchServerContent">
<button class="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>
@ -169,8 +169,8 @@
</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>
<div class="settingsSelServContainer"> <div class="settingsSelServContainer">
<div class="settingsSelServContent"> <div class="settingsSelServContent">
@ -178,16 +178,16 @@
</div> </div>
<div class="settingsSwitchServerContainer"> <div class="settingsSwitchServerContainer">
<div class="settingsSwitchServerContent"> <div class="settingsSwitchServerContent">
<button class="settingsSwitchServerButton">Switch</button> <button class="settingsSwitchServerButton"><%- lang('settings.switchServerButton') %></button>
</div> </div>
</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" serverDependent 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>
@ -197,7 +197,7 @@
</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" serverDependent 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>
@ -206,16 +206,16 @@
<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>
@ -223,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">
@ -242,13 +242,13 @@
</svg> </svg>
</div> </div>
<input class="settingsFileSelVal" id="settingsJavaExecVal" type="text" value="null" cValue="JavaExecutable" serverDependent disabled> <input class="settingsFileSelVal" id="settingsJavaExecVal" type="text" value="null" cValue="JavaExecutable" serverDependent disabled>
<button class="settingsFileSelButton" id="settingsJavaExecSel" dialogTitle="Select Java Executable" dialogDirectory="false">Choose File</button> <button class="settingsFileSelButton" id="settingsJavaExecSel" dialogTitle="<%- lang('settings.javaExecSelDialogTitle') %>" dialogDirectory="false"><%- lang('settings.javaExecSelButtonText') %></button>
</div> </div>
</div> </div>
<div class="settingsFileSelDesc">The Java executable is validated before game launch. <strong id="settingsJavaReqDesc">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">
@ -266,18 +266,18 @@
</div> </div>
<input id="settingsJVMOptsVal" cValue="JVMOptions" serverDependent 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" id="settingsJvmOptsLink">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">
@ -288,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">
@ -300,90 +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>
<button class="settingsFileSelButton" dialogTitle="Select Data Directory" dialogDirectory="true">Choose Folder</button> <button class="settingsFileSelButton" dialogTitle="<%- lang('settings.selectDataDirectory') %>" dialogDirectory="true"><%- lang('settings.chooseFolder') %></button>
</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/SealCircle.png"> <img id="settingsAboutLogo" src="./assets/images/SealCircle.png">
<span id="settingsAboutTitle">Helios Launcher</span> <span id="settingsAboutTitle"><%- lang('settings.aboutTitle', { appName: lang('app.title') }) %></span>
</div> </div>
<div id="settingsAboutCurrentVersion"> <div id="settingsAboutCurrentVersion">
<div id="settingsAboutCurrentVersionCheck">&#10003;</div> <div id="settingsAboutCurrentVersionCheck">&#10003;</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/dscalZi/HeliosLauncher" 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/dscalzi/HeliosLauncher" id="settingsAboutSourceButton" class="settingsAboutButton">Original Source</a> --> <!-- <a href="https://github.com/dscalzi/HeliosLauncher" id="settingsAboutSourceButton" class="settingsAboutButton">Original Source</a> -->
<a href="https://github.com/dscalZi/HeliosLauncher/issues" 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">&#10003;</div> <div id="settingsUpdateVersionCheck">&#10003;</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>

View File

@ -2,7 +2,7 @@
<div id="waitingContent"> <div id="waitingContent">
<div class="waitingSpinner"></div> <div class="waitingSpinner"></div>
<div id="waitingTextContainer"> <div id="waitingTextContainer">
<h2 id="waitingText">Waiting for Microsoft..</h2> <h2><%- lang('waiting.waitingText') %></h2>
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,13 +5,13 @@
</div>--> </div>-->
<div id="welcomeContent"> <div id="welcomeContent">
<img id="welcomeImageSeal" src="assets/images/SealCircle.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 arent 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>

View File

@ -1,3 +1,3 @@
owner: dscalzi owner: Limbo-Studios
repo: HeliosLauncher repo: LimboLauncher
provider: github provider: github

View File

@ -18,18 +18,35 @@ Authenticating with Microsoft is fully supported by Helios Launcher.
- Select **Mobile and desktop applications**. - Select **Mobile and desktop applications**.
- Choose `https://login.microsoftonline.com/common/oauth2/nativeclient` as the **Redirect URI**. - Choose `https://login.microsoftonline.com/common/oauth2/nativeclient` as the **Redirect URI**.
- Select **Configure** to finish adding the platform. - 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**. 8. Navigate back to **Overview**.
9. Copy **Application (client) ID**. 9. Copy **Application (client) ID**.
Reference: https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app
## Adding the Azure Client ID to Helios Launcher. ## 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. 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 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. 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

View File

@ -2,6 +2,8 @@
You can use [Nebula](https://github.com/dscalzi/Nebula) to automate the generation of a 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
@ -143,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.
@ -248,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.
@ -296,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.
@ -331,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.

View File

@ -1,8 +1,8 @@
appId: 'helioslauncher' appId: 'LimboLauncher'
productName: 'Helios Launcher' productName: 'Limbo Launcher'
artifactName: '${productName}-setup-${version}.${ext}' artifactName: '${productName}-setup-${version}.${ext}'
copyright: 'Copyright © 2018-2022 Daniel Scalzi' copyright: 'Copyright © 2024 Limbo-Studios'
asar: true asar: true
compression: 'maximum' compression: 'maximum'
@ -39,10 +39,10 @@ mac:
# Linux Configuration # Linux Configuration
linux: linux:
target: 'AppImage' target: 'AppImage'
maintainer: 'Daniel Scalzi' maintainer: 'IkyMax'
vendor: 'Daniel Scalzi' vendor: 'IkyMax'
synopsis: 'Modded Minecraft Launcher' synopsis: 'Launcher de Limbo-studios para Lurkarya'
description: 'Custom launcher which allows users to join modded servers. All mods, configurations, and updates are handled automatically.' description: 'Custom launcher maded by IkyMax'
category: 'Game' category: 'Game'

118
index.js
View File

@ -11,6 +11,10 @@ const path = require('path')
const semver = require('semver') const semver = require('semver')
const { pathToFileURL } = require('url') const { pathToFileURL } = require('url')
const { AZURE_CLIENT_ID, MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR, SHELL_OPCODE } = require('./app/assets/js/ipcconstants') 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) {
@ -107,6 +111,114 @@ app.disableHardwareAcceleration()
// 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.
@ -128,7 +240,11 @@ function createWindow() {
}) })
remoteMain.enable(win.webContents) 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(pathToFileURL(path.join(__dirname, 'app', 'app.ejs')).toString()) win.loadURL(pathToFileURL(path.join(__dirname, 'app', 'app.ejs')).toString())

Binary file not shown.

2563
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
{ {
"name": "helioslauncher", "name": "Limbolauncher",
"version": "1.10.0", "version": "1.0.0",
"productName": "Helios Launcher", "productName": "Limbo Launcher",
"description": "Modded Minecraft Launcher", "description": "Launcher de Limbo-Studios",
"author": "Daniel Scalzi (https://github.com/dscalzi/)", "author": "IkyMax",
"license": "UNLICENSED", "license": "UNLICENSED",
"homepage": "https://github.com/dscalzi/HeliosLauncher", "homepage": "https://github.com/Limbo-Studios/LimboLauncher",
"bugs": { "bugs": {
"url": "https://github.com/dscalzi/HeliosLauncher/issues" "url": "https://github.com/Limbo-Studios/LimboLauncher/issues"
}, },
"private": true, "private": true,
"main": "index.js", "main": "index.js",
@ -20,35 +20,32 @@
"lint": "eslint --config .eslintrc.json ." "lint": "eslint --config .eslintrc.json ."
}, },
"engines": { "engines": {
"node": "18.x.x" "node": "20.x.x"
}, },
"dependencies": { "dependencies": {
"@electron/remote": "^2.0.8", "@electron/remote": "^2.1.2",
"adm-zip": "^0.5.9", "adm-zip": "^0.5.9",
"async": "^3.2.4",
"discord-rpc-patch": "^4.0.1", "discord-rpc-patch": "^4.0.1",
"ejs": "^3.1.8", "ejs": "^3.1.9",
"ejs-electron": "^2.1.1", "ejs-electron": "^3.0.0",
"electron-updater": "^5.3.0", "electron-updater": "^6.1.8",
"fs-extra": "^11.1.0", "fs-extra": "^11.1.1",
"github-syntax-dark": "^0.5.0", "github-syntax-dark": "^0.5.0",
"got": "^11.8.5", "got": "^11.8.5",
"helios-core": "~0.1.2", "limbo-core": "~2.2.0",
"jquery": "^3.6.1", "helios-distribution-types": "^1.3.0",
"node-disk-info": "^1.3.0", "jquery": "^3.7.1",
"node-stream-zip": "^1.15.0", "lodash.merge": "^4.6.2",
"request": "^2.88.2", "semver": "^7.6.0",
"semver": "^7.3.8", "toml": "^3.0.0"
"tar-fs": "^2.1.1",
"winreg": "^1.2.4"
}, },
"devDependencies": { "devDependencies": {
"electron": "^23.0.0", "electron": "^29.1.0",
"electron-builder": "^23.6.0", "electron-builder": "^24.13.3",
"eslint": "^8.34.0" "eslint": "^8.57.0"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/dscalzi/HeliosLauncher.git" "url": "git+https://github.com/Limbo-Studios/LimboLauncher.git"
} }
} }

Binary file not shown.

Binary file not shown.