2018-04-25 17:11:10 -07:00
/ * *
* Script for landing . ejs
* /
2018-04-25 14:40:46 -07:00
// Requirements
2018-04-25 23:39:47 -07:00
const cp = require ( 'child_process' )
2018-06-04 20:08:03 -07:00
const crypto = require ( 'crypto' )
2021-10-30 23:20:03 -07:00
const { URL } = require ( 'url' )
2023-02-25 00:09:22 -08:00
const {
MojangRestAPI ,
getServerStatus
} = require ( 'helios-core/mojang' )
const {
RestResponseStatus ,
isDisplayableError ,
mcVersionAtLeast
} = require ( 'helios-core/common' )
const {
FullRepair ,
DistributionIndexProcessor ,
MojangIndexProcessor
} = require ( 'helios-core/dl' )
2018-04-25 14:40:46 -07:00
// Internal Requirements
2018-07-22 08:40:15 -07:00
const DiscordWrapper = require ( './assets/js/discordwrapper' )
const ProcessBuilder = require ( './assets/js/processbuilder' )
2018-04-25 14:40:46 -07:00
// Launch Elements
2018-04-25 23:39:47 -07:00
const launch _content = document . getElementById ( 'launch_content' )
const launch _details = document . getElementById ( 'launch_details' )
const launch _progress = document . getElementById ( 'launch_progress' )
const launch _progress _label = document . getElementById ( 'launch_progress_label' )
const launch _details _text = document . getElementById ( 'launch_details_text' )
const server _selection _button = document . getElementById ( 'server_selection_button' )
2018-04-29 15:05:59 -07:00
const user _text = document . getElementById ( 'user_text' )
2018-04-25 14:40:46 -07:00
2022-12-24 19:17:30 -08:00
const loggerLanding = LoggerUtil . getLogger ( 'Landing' )
2018-08-22 11:21:49 -07:00
2018-04-25 17:11:10 -07:00
/* Launch Progress Wrapper Functions */
/ * *
* Show / hide the loading area .
*
* @ param { boolean } loading True if the loading area should be shown , otherwise false .
* /
function toggleLaunchArea ( loading ) {
if ( loading ) {
launch _details . style . display = 'flex'
launch _content . style . display = 'none'
} else {
launch _details . style . display = 'none'
launch _content . style . display = 'inline-flex'
}
}
/ * *
* Set the details text of the loading area .
*
* @ param { string } details The new text for the loading details .
* /
function setLaunchDetails ( details ) {
launch _details _text . innerHTML = details
}
/ * *
* Set the value of the loading progress bar and display that value .
*
2023-02-25 00:09:22 -08:00
* @ param { number } percent Percentage ( 0 - 100 )
2018-04-25 17:11:10 -07:00
* /
2023-02-25 00:09:22 -08:00
function setLaunchPercentage ( percent ) {
launch _progress . setAttribute ( 'max' , 100 )
launch _progress . setAttribute ( 'value' , percent )
2018-04-25 17:11:10 -07:00
launch _progress _label . innerHTML = percent + '%'
}
/ * *
* Set the value of the OS progress bar and display that on the UI .
*
2023-02-25 00:09:22 -08:00
* @ param { number } percent Percentage ( 0 - 100 )
2018-04-25 17:11:10 -07:00
* /
2023-02-25 00:09:22 -08:00
function setDownloadPercentage ( percent ) {
remote . getCurrentWindow ( ) . setProgressBar ( percent / 100 )
setLaunchPercentage ( percent )
2018-04-25 17:11:10 -07:00
}
2018-04-26 15:41:26 -07:00
/ * *
* Enable or disable the launch button .
*
* @ param { boolean } val True to enable , false to disable .
* /
function setLaunchEnabled ( val ) {
document . getElementById ( 'launch_button' ) . disabled = ! val
}
2018-04-25 14:40:46 -07:00
// Bind launch button
2023-02-24 18:02:18 -08:00
document . getElementById ( 'launch_button' ) . addEventListener ( 'click' , async ( e ) => {
2022-12-24 19:17:30 -08:00
loggerLanding . info ( 'Launching game..' )
2023-02-24 18:02:18 -08:00
const mcVersion = ( await DistroAPI . getDistribution ( ) ) . getServerById ( ConfigManager . getSelectedServer ( ) ) . rawServer . minecraftVersion
2022-11-27 15:13:50 -08:00
const jExe = ConfigManager . getJavaExecutable ( ConfigManager . getSelectedServer ( ) )
2018-04-25 14:40:46 -07:00
if ( jExe == null ) {
2018-12-31 07:39:27 -08:00
asyncSystemScan ( mcVersion )
2018-04-25 14:40:46 -07:00
} else {
2019-04-07 20:33:40 -07:00
setLaunchDetails ( Lang . queryJS ( 'landing.launch.pleaseWait' ) )
2018-04-25 14:40:46 -07:00
toggleLaunchArea ( true )
setLaunchPercentage ( 0 , 100 )
2019-03-05 20:05:01 -08:00
const jg = new JavaGuard ( mcVersion )
2023-02-25 00:09:22 -08:00
jg . _validateJavaBinary ( jExe ) . then ( async v => {
2022-12-24 19:17:30 -08:00
loggerLanding . info ( 'Java version meta' , v )
2018-05-15 03:07:28 -07:00
if ( v . valid ) {
2023-02-25 00:09:22 -08:00
await dlAsync ( )
2018-04-25 14:40:46 -07:00
} else {
2018-12-31 07:39:27 -08:00
asyncSystemScan ( mcVersion )
2018-04-25 14:40:46 -07:00
}
} )
}
} )
2018-05-30 13:00:07 -07:00
// Bind settings button
2023-02-24 18:02:18 -08:00
document . getElementById ( 'settingsMediaButton' ) . onclick = async e => {
await prepareSettings ( )
2018-05-30 13:00:07 -07:00
switchView ( getCurrentView ( ) , VIEWS . settings )
}
2018-06-21 09:38:21 -07:00
// Bind avatar overlay button.
2023-02-24 18:02:18 -08:00
document . getElementById ( 'avatarOverlay' ) . onclick = async e => {
await prepareSettings ( )
2018-06-21 09:38:21 -07:00
switchView ( getCurrentView ( ) , VIEWS . settings , 500 , 500 , ( ) => {
settingsNavItemListener ( document . getElementById ( 'settingsNavAccount' ) , false )
} )
}
2018-04-29 15:05:59 -07:00
// Bind selected account
function updateSelectedAccount ( authUser ) {
let username = 'No Account Selected'
2018-07-01 23:01:28 -07:00
if ( authUser != null ) {
if ( authUser . displayName != null ) {
username = authUser . displayName
}
if ( authUser . uuid != null ) {
2021-07-19 08:08:55 -07:00
document . getElementById ( 'avatarContainer' ) . style . backgroundImage = ` url('https://mc-heads.net/body/ ${ authUser . uuid } /right') `
2018-07-01 23:01:28 -07:00
}
2018-04-29 15:05:59 -07:00
}
user _text . innerHTML = username
}
updateSelectedAccount ( ConfigManager . getSelectedAccount ( ) )
2018-04-25 14:40:46 -07:00
// Bind selected server
2018-11-20 02:19:59 -08:00
function updateSelectedServer ( serv ) {
if ( getCurrentView ( ) === VIEWS . settings ) {
2022-11-27 15:13:50 -08:00
fullSettingsSave ( )
2018-04-26 15:41:26 -07:00
}
2023-02-24 18:02:18 -08:00
ConfigManager . setSelectedServer ( serv != null ? serv . rawServer . id : null )
2018-11-20 02:19:59 -08:00
ConfigManager . save ( )
2023-02-24 18:02:18 -08:00
server _selection _button . innerHTML = '\u2022 ' + ( serv != null ? serv . rawServer . name : 'No Server Selected' )
2018-08-06 21:58:32 -07:00
if ( getCurrentView ( ) === VIEWS . settings ) {
2022-11-27 15:13:50 -08:00
animateSettingsTabRefresh ( )
2018-08-06 21:58:32 -07:00
}
2018-11-20 02:19:59 -08:00
setLaunchEnabled ( serv != null )
2018-04-26 15:41:26 -07:00
}
2018-05-06 22:34:57 -07:00
// Real text is set in uibinder.js on distributionIndexDone.
2018-11-20 02:19:59 -08:00
server _selection _button . innerHTML = '\u2022 Loading..'
2023-02-24 18:02:18 -08:00
server _selection _button . onclick = async e => {
2018-04-25 23:39:47 -07:00
e . target . blur ( )
2023-02-24 18:02:18 -08:00
await toggleServerSelection ( true )
2018-08-22 07:54:09 -07:00
}
2018-04-25 14:40:46 -07:00
// Update Mojang Status Color
const refreshMojangStatuses = async function ( ) {
2022-12-24 19:17:30 -08:00
loggerLanding . info ( 'Refreshing Mojang Statuses..' )
2018-05-17 00:11:44 -07:00
2018-04-25 14:40:46 -07:00
let status = 'grey'
2018-07-22 10:31:15 -07:00
let tooltipEssentialHTML = ''
let tooltipNonEssentialHTML = ''
2018-05-17 00:11:44 -07:00
2022-02-06 15:23:44 -08:00
const response = await MojangRestAPI . status ( )
let statuses
if ( response . responseStatus === RestResponseStatus . SUCCESS ) {
statuses = response . data
} else {
loggerLanding . warn ( 'Unable to refresh Mojang service status.' )
statuses = MojangRestAPI . getDefaultStatuses ( )
}
greenCount = 0
greyCount = 0
for ( let i = 0 ; i < statuses . length ; i ++ ) {
const service = statuses [ i ]
if ( service . essential ) {
tooltipEssentialHTML += ` <div class="mojangStatusContainer">
< span class = "mojangStatusIcon" style = "color: ${MojangRestAPI.statusToHex(service.status)};" > & # 8226 ; < / s p a n >
< span class = "mojangStatusName" > $ { service . name } < / s p a n >
< / d i v > `
} else {
tooltipNonEssentialHTML += ` <div class="mojangStatusContainer">
< span class = "mojangStatusIcon" style = "color: ${MojangRestAPI.statusToHex(service.status)};" > & # 8226 ; < / s p a n >
< span class = "mojangStatusName" > $ { service . name } < / s p a n >
< / d i v > `
2018-04-25 14:40:46 -07:00
}
2018-05-17 00:11:44 -07:00
2022-02-06 15:23:44 -08:00
if ( service . status === 'yellow' && status !== 'red' ) {
status = 'yellow'
} else if ( service . status === 'red' ) {
status = 'red'
} else {
if ( service . status === 'grey' ) {
++ greyCount
2018-05-22 05:41:22 -07:00
}
2022-02-06 15:23:44 -08:00
++ greenCount
2018-04-25 14:40:46 -07:00
}
2018-05-17 00:11:44 -07:00
2022-02-06 15:23:44 -08:00
}
if ( greenCount === statuses . length ) {
if ( greyCount === statuses . length ) {
status = 'grey'
} else {
status = 'green'
}
2018-04-25 14:40:46 -07:00
}
2018-05-17 00:11:44 -07:00
document . getElementById ( 'mojangStatusEssentialContainer' ) . innerHTML = tooltipEssentialHTML
document . getElementById ( 'mojangStatusNonEssentialContainer' ) . innerHTML = tooltipNonEssentialHTML
2022-02-06 15:23:44 -08:00
document . getElementById ( 'mojang_status_icon' ) . style . color = MojangRestAPI . statusToHex ( status )
2018-04-25 14:40:46 -07:00
}
2023-02-24 18:02:18 -08:00
const refreshServerStatus = async ( fade = false ) => {
2022-12-24 19:17:30 -08:00
loggerLanding . info ( 'Refreshing Server Status' )
2023-02-24 18:02:18 -08:00
const serv = ( await DistroAPI . getDistribution ( ) ) . getServerById ( ConfigManager . getSelectedServer ( ) )
2018-04-25 14:40:46 -07:00
let pLabel = 'SERVER'
let pVal = 'OFFLINE'
try {
2021-10-30 23:20:03 -07:00
2023-02-24 18:02:18 -08:00
const servStat = await getServerStatus ( 47 , serv . hostname , serv . port )
2021-10-30 23:20:03 -07:00
console . log ( servStat )
pLabel = 'PLAYERS'
pVal = servStat . players . online + '/' + servStat . players . max
2018-04-25 14:40:46 -07:00
} catch ( err ) {
2018-08-22 11:21:49 -07:00
loggerLanding . warn ( 'Unable to refresh server status, assuming offline.' )
loggerLanding . debug ( err )
2018-04-25 14:40:46 -07:00
}
2018-04-26 15:41:26 -07:00
if ( fade ) {
$ ( '#server_status_wrapper' ) . fadeOut ( 250 , ( ) => {
document . getElementById ( 'landingPlayerLabel' ) . innerHTML = pLabel
document . getElementById ( 'player_count' ) . innerHTML = pVal
$ ( '#server_status_wrapper' ) . fadeIn ( 500 )
} )
} else {
document . getElementById ( 'landingPlayerLabel' ) . innerHTML = pLabel
document . getElementById ( 'player_count' ) . innerHTML = pVal
}
2018-04-25 14:40:46 -07:00
}
refreshMojangStatuses ( )
2018-05-06 22:34:57 -07:00
// Server Status is refreshed in uibinder.js on distributionIndexDone.
2018-04-25 14:40:46 -07:00
2022-11-06 19:38:15 -08:00
// Refresh statuses every hour. The status page itself refreshes every day so...
let mojangStatusListener = setInterval ( ( ) => refreshMojangStatuses ( true ) , 60 * 60 * 1000 )
2018-04-25 14:40:46 -07:00
// Set refresh rate to once every 5 minutes.
2018-04-26 15:41:26 -07:00
let serverStatusListener = setInterval ( ( ) => refreshServerStatus ( true ) , 300000 )
2018-04-25 14:40:46 -07:00
2019-01-20 03:55:13 -08:00
/ * *
* Shows an error overlay , toggles off the launch area .
*
* @ param { string } title The overlay title .
* @ param { string } desc The overlay description .
* /
function showLaunchFailure ( title , desc ) {
setOverlayContent (
title ,
desc ,
'Okay'
)
setOverlayHandler ( null )
toggleOverlay ( true )
toggleLaunchArea ( false )
}
2018-04-25 14:40:46 -07:00
/* System (Java) Scan */
let sysAEx
let scanAt
2018-04-28 20:34:23 -07:00
let extractListener
2018-12-31 07:39:27 -08:00
/ * *
* Asynchronously scan the system for valid Java installations .
*
* @ param { string } mcVersion The Minecraft version we are scanning for .
* @ param { boolean } launchAfter Whether we should begin to launch after scanning .
* /
function asyncSystemScan ( mcVersion , launchAfter = true ) {
2018-04-25 14:40:46 -07:00
setLaunchDetails ( 'Please wait..' )
toggleLaunchArea ( true )
setLaunchPercentage ( 0 , 100 )
2019-01-04 11:25:27 -08:00
const forkEnv = JSON . parse ( JSON . stringify ( process . env ) )
forkEnv . CONFIG _DIRECT _PATH = ConfigManager . getLauncherDirectory ( )
2018-04-25 14:40:46 -07:00
// Fork a process to run validations.
sysAEx = cp . fork ( path . join ( _ _dirname , 'assets' , 'js' , 'assetexec.js' ) , [
2019-03-05 20:05:01 -08:00
'JavaGuard' ,
mcVersion
2018-05-07 15:15:59 -07:00
] , {
2019-01-04 11:25:27 -08:00
env : forkEnv ,
2018-05-07 15:15:59 -07:00
stdio : 'pipe'
} )
// Stdout
2018-08-22 11:21:49 -07:00
sysAEx . stdio [ 1 ] . setEncoding ( 'utf8' )
2018-05-07 15:15:59 -07:00
sysAEx . stdio [ 1 ] . on ( 'data' , ( data ) => {
2022-12-24 19:17:30 -08:00
console . log ( ` \x 1b[32m[SysAEx] \x 1b[0m ${ data } ` )
2018-05-07 15:15:59 -07:00
} )
// Stderr
2018-08-22 11:21:49 -07:00
sysAEx . stdio [ 2 ] . setEncoding ( 'utf8' )
2018-05-07 15:15:59 -07:00
sysAEx . stdio [ 2 ] . on ( 'data' , ( data ) => {
2022-12-24 19:17:30 -08:00
console . log ( ` \x 1b[31m[SysAEx] \x 1b[0m ${ data } ` )
2018-05-07 15:15:59 -07:00
} )
2022-11-27 15:13:50 -08:00
2023-02-24 18:02:18 -08:00
const javaVer = mcVersionAtLeast ( '1.17' , mcVersion ) ? '17' : '8'
2018-04-25 14:40:46 -07:00
2023-02-24 18:02:18 -08:00
sysAEx . on ( 'message' , async ( m ) => {
2018-04-25 14:40:46 -07:00
2018-07-22 08:40:15 -07:00
if ( m . context === 'validateJava' ) {
2018-04-25 14:40:46 -07:00
if ( m . result == null ) {
// If the result is null, no valid Java installation was found.
// Show this information to the user.
setOverlayContent (
'No Compatible<br>Java Installation Found' ,
2022-11-27 15:13:50 -08:00
` In order to join WesterosCraft, you need a 64-bit installation of Java ${ javaVer } . Would you like us to install a copy? ` ,
2018-04-25 14:40:46 -07:00
'Install Java' ,
'Install Manually'
)
setOverlayHandler ( ( ) => {
setLaunchDetails ( 'Preparing Java Download..' )
2022-11-27 15:13:50 -08:00
sysAEx . send ( { task : 'changeContext' , class : 'AssetGuard' , args : [ ConfigManager . getCommonDirectory ( ) , ConfigManager . getJavaExecutable ( ConfigManager . getSelectedServer ( ) ) ] } )
sysAEx . send ( { task : 'execute' , function : '_enqueueOpenJDK' , argsArr : [ ConfigManager . getDataDirectory ( ) , mcVersion ] } )
2018-04-25 14:40:46 -07:00
toggleOverlay ( false )
} )
setDismissHandler ( ( ) => {
$ ( '#overlayContent' ) . fadeOut ( 250 , ( ) => {
//$('#overlayDismiss').toggle(false)
setOverlayContent (
2018-08-20 01:02:27 -07:00
'Java is Required<br>to Launch' ,
2022-11-27 15:13:50 -08:00
` A valid x64 installation of Java ${ javaVer } 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. ` ,
2018-04-25 14:40:46 -07:00
'I Understand' ,
'Go Back'
)
setOverlayHandler ( ( ) => {
toggleLaunchArea ( false )
toggleOverlay ( false )
} )
setDismissHandler ( ( ) => {
toggleOverlay ( false , true )
asyncSystemScan ( )
} )
$ ( '#overlayContent' ) . fadeIn ( 250 )
} )
} )
toggleOverlay ( true , true )
} else {
// Java installation found, use this to launch the game.
2022-11-27 15:13:50 -08:00
ConfigManager . setJavaExecutable ( ConfigManager . getSelectedServer ( ) , m . result )
2018-04-25 14:40:46 -07:00
ConfigManager . save ( )
2018-06-14 00:49:55 -07:00
// We need to make sure that the updated value is on the settings UI.
// Just incase the settings UI is already open.
settingsJavaExecVal . value = m . result
2023-02-24 18:02:18 -08:00
await populateJavaExecDetails ( settingsJavaExecVal . value )
2018-06-14 00:49:55 -07:00
2018-04-25 14:40:46 -07:00
if ( launchAfter ) {
2023-02-25 00:09:22 -08:00
await dlAsync ( )
2018-04-25 14:40:46 -07:00
}
sysAEx . disconnect ( )
}
2019-06-02 15:11:39 -07:00
} else if ( m . context === '_enqueueOpenJDK' ) {
2018-04-25 14:40:46 -07:00
if ( m . result === true ) {
// Oracle JRE enqueued successfully, begin download.
setLaunchDetails ( 'Downloading Java..' )
2018-07-22 08:40:15 -07:00
sysAEx . send ( { task : 'execute' , function : 'processDlQueues' , argsArr : [ [ { id : 'java' , limit : 1 } ] ] } )
2018-04-25 14:40:46 -07:00
} else {
// Oracle JRE enqueue failed. Probably due to a change in their website format.
// User will have to follow the guide to install Java.
setOverlayContent (
'Unexpected Issue:<br>Java Download Failed' ,
2019-09-05 13:08:47 -07:00
'Unfortunately we\'ve encountered an issue while attempting to install Java. You will need to manually install a copy. Please check out our <a href="https://github.com/dscalzi/HeliosLauncher/wiki">Troubleshooting Guide</a> for more details and instructions.' ,
2018-04-25 14:40:46 -07:00
'I Understand'
)
setOverlayHandler ( ( ) => {
toggleOverlay ( false )
toggleLaunchArea ( false )
} )
toggleOverlay ( true )
sysAEx . disconnect ( )
}
2018-07-22 08:40:15 -07:00
} else if ( m . context === 'progress' ) {
2018-04-25 14:40:46 -07:00
2018-07-22 08:40:15 -07:00
switch ( m . data ) {
case 'download' :
2019-06-02 15:11:39 -07:00
// Downloading..
2018-07-22 08:40:15 -07:00
setDownloadPercentage ( m . value , m . total , m . percent )
break
}
2018-04-25 14:40:46 -07:00
2018-07-22 08:40:15 -07:00
} else if ( m . context === 'complete' ) {
2018-07-01 23:24:13 -07:00
2018-07-22 08:40:15 -07:00
switch ( m . data ) {
2018-07-22 10:31:15 -07:00
case 'download' : {
2018-07-22 08:40:15 -07:00
// Show installing progress bar.
remote . getCurrentWindow ( ) . setProgressBar ( 2 )
2018-04-25 14:40:46 -07:00
2018-07-22 08:40:15 -07:00
// Wait for extration to complete.
const eLStr = 'Extracting'
let dotStr = ''
setLaunchDetails ( eLStr )
extractListener = setInterval ( ( ) => {
if ( dotStr . length >= 3 ) {
dotStr = ''
} else {
dotStr += '.'
}
setLaunchDetails ( eLStr + dotStr )
} , 750 )
break
2018-07-22 10:31:15 -07:00
}
2018-07-22 08:40:15 -07:00
case 'java' :
2018-07-22 10:31:15 -07:00
// Download & extraction complete, remove the loading from the OS progress bar.
2018-07-22 08:40:15 -07:00
remote . getCurrentWindow ( ) . setProgressBar ( - 1 )
// Extraction completed successfully.
2022-11-27 15:13:50 -08:00
ConfigManager . setJavaExecutable ( ConfigManager . getSelectedServer ( ) , m . args [ 0 ] )
2018-07-22 08:40:15 -07:00
ConfigManager . save ( )
2018-04-28 20:34:23 -07:00
2018-07-22 08:40:15 -07:00
if ( extractListener != null ) {
clearInterval ( extractListener )
extractListener = null
}
2018-04-25 14:40:46 -07:00
2018-07-22 08:40:15 -07:00
setLaunchDetails ( 'Java Installed!' )
2018-04-25 14:40:46 -07:00
2018-07-22 08:40:15 -07:00
if ( launchAfter ) {
2023-02-25 00:09:22 -08:00
await dlAsync ( )
2018-07-22 08:40:15 -07:00
}
sysAEx . disconnect ( )
break
2018-04-25 14:40:46 -07:00
}
2018-07-22 08:40:15 -07:00
2019-06-02 15:11:39 -07:00
} else if ( m . context === 'error' ) {
console . log ( m . error )
2018-04-25 14:40:46 -07:00
}
} )
// Begin system Java scan.
setLaunchDetails ( 'Checking system info..' )
2019-03-05 20:05:01 -08:00
sysAEx . send ( { task : 'execute' , function : 'validateJava' , argsArr : [ ConfigManager . getDataDirectory ( ) ] } )
2018-04-25 14:40:46 -07:00
}
// Keep reference to Minecraft Process
let proc
// Is DiscordRPC enabled
let hasRPC = false
// Joined server regex
2020-06-29 08:28:55 -07:00
// Change this if your server uses something different.
const GAME _JOINED _REGEX = /\[.+\]: Sound engine started/
2020-06-08 11:00:07 -07:00
const GAME _LAUNCH _REGEX = /^\[.+\]: (?:MinecraftForge .+ Initialized|ModLauncher .+ starting: .+)$/
const MIN _LINGER = 5000
2018-04-25 14:40:46 -07:00
2023-02-25 00:09:22 -08:00
async function dlAsync ( login = true ) {
2018-04-25 14:40:46 -07:00
// Login parameter is temporary for debug purposes. Allows testing the validation/downloads without
// launching the game.
2023-02-25 00:09:22 -08:00
const loggerLaunchSuite = LoggerUtil . getLogger ( 'LaunchSuite' )
setLaunchDetails ( 'Loading server information..' )
let distro
try {
distro = await DistroAPI . refreshDistributionOrFallback ( )
onDistroRefresh ( distro )
} catch ( err ) {
loggerLaunchSuite . error ( 'Unable to refresh distribution index.' , err )
showLaunchFailure ( 'Fatal Error' , 'Could not load a copy of the distribution index. See the console (CTRL + Shift + i) for more details.' )
return
}
const serv = distro . getServerById ( ConfigManager . getSelectedServer ( ) )
2018-04-25 14:40:46 -07:00
if ( login ) {
if ( ConfigManager . getSelectedAccount ( ) == null ) {
2018-08-22 11:21:49 -07:00
loggerLanding . error ( 'You must be logged into an account.' )
2018-04-25 14:40:46 -07:00
return
}
}
setLaunchDetails ( 'Please wait..' )
toggleLaunchArea ( true )
setLaunchPercentage ( 0 , 100 )
2023-02-25 00:09:22 -08:00
const fullRepairModule = new FullRepair (
ConfigManager . getCommonDirectory ( ) ,
2023-02-25 00:28:40 -08:00
ConfigManager . getInstanceDirectory ( ) ,
2023-02-25 00:09:22 -08:00
ConfigManager . getLauncherDirectory ( ) ,
ConfigManager . getSelectedServer ( ) ,
DistroAPI . isDevMode ( )
)
2018-08-22 11:21:49 -07:00
2023-02-25 00:09:22 -08:00
fullRepairModule . spawnReceiver ( )
2019-01-04 11:25:27 -08:00
2023-02-25 00:09:22 -08:00
fullRepairModule . childProcess . on ( 'error' , ( err ) => {
2019-01-20 03:55:13 -08:00
loggerLaunchSuite . error ( 'Error during launch' , err )
showLaunchFailure ( 'Error During Launch' , err . message || 'See console (CTRL + Shift + i) for more details.' )
} )
2023-02-25 00:09:22 -08:00
fullRepairModule . childProcess . on ( 'close' , ( code , _signal ) => {
2019-01-20 03:55:13 -08:00
if ( code !== 0 ) {
loggerLaunchSuite . error ( ` AssetExec exited with code ${ code } , assuming error. ` )
showLaunchFailure ( 'Error During Launch' , 'See console (CTRL + Shift + i) for more details.' )
}
} )
2018-04-25 14:40:46 -07:00
2023-02-25 00:09:22 -08:00
loggerLaunchSuite . info ( 'Validating files.' )
setLaunchDetails ( 'Validating file integrity..' )
const invalidFileCount = await fullRepairModule . verifyFiles ( percent => {
setLaunchPercentage ( percent )
} )
setLaunchPercentage ( 100 )
2018-04-25 14:40:46 -07:00
2023-02-25 00:09:22 -08:00
if ( invalidFileCount > 0 ) {
loggerLaunchSuite . info ( 'Downloading files.' )
setLaunchDetails ( 'Downloading files..' )
await fullRepairModule . download ( percent => {
setDownloadPercentage ( percent )
} )
setDownloadPercentage ( 100 )
} else {
loggerLaunchSuite . info ( 'No invalid files, skipping download.' )
}
2018-05-08 03:34:16 -07:00
2023-02-25 00:09:22 -08:00
// Remove download bar.
remote . getCurrentWindow ( ) . setProgressBar ( - 1 )
2018-04-25 14:40:46 -07:00
2023-02-25 00:09:22 -08:00
fullRepairModule . destroyReceiver ( )
2018-04-25 14:40:46 -07:00
2023-02-25 00:09:22 -08:00
setLaunchDetails ( 'Preparing to launch..' )
2019-02-18 03:31:01 -08:00
2023-02-25 00:09:22 -08:00
const mojangIndexProcessor = new MojangIndexProcessor (
ConfigManager . getCommonDirectory ( ) ,
serv . rawServer . minecraftVersion )
const distributionIndexProcessor = new DistributionIndexProcessor (
ConfigManager . getCommonDirectory ( ) ,
distro ,
serv . rawServer . id
)
2019-02-18 03:31:01 -08:00
2023-02-25 00:09:22 -08:00
// TODO need to load these.
const forgeData = await distributionIndexProcessor . loadForgeVersionJson ( serv )
const versionData = await mojangIndexProcessor . getVersionJson ( )
2019-02-18 03:31:01 -08:00
2023-02-25 00:09:22 -08:00
if ( login ) {
const authUser = ConfigManager . getSelectedAccount ( )
loggerLaunchSuite . info ( ` Sending selected account ( ${ authUser . displayName } ) to ProcessBuilder. ` )
let pb = new ProcessBuilder ( serv , versionData , forgeData , authUser , remote . app . getVersion ( ) )
setLaunchDetails ( 'Launching game..' )
// const SERVER_JOINED_REGEX = /\[.+\]: \[CHAT\] [a-zA-Z0-9_]{1,16} joined the game/
const SERVER _JOINED _REGEX = new RegExp ( ` \\ [.+ \\ ]: \\ [CHAT \\ ] ${ authUser . displayName } joined the game ` )
const onLoadComplete = ( ) => {
toggleLaunchArea ( false )
if ( hasRPC ) {
DiscordWrapper . updateDetails ( 'Loading game..' )
2018-07-25 19:30:47 -07:00
}
2023-02-25 00:09:22 -08:00
proc . stdout . on ( 'data' , gameStateChange )
proc . stdout . removeListener ( 'data' , tempListener )
proc . stderr . removeListener ( 'data' , gameErrorListener )
}
const start = Date . now ( )
// Attach a temporary listener to the client output.
// Will wait for a certain bit of text meaning that
// the client application has started, and we can hide
// the progress bar stuff.
const tempListener = function ( data ) {
if ( GAME _LAUNCH _REGEX . test ( data . trim ( ) ) ) {
const diff = Date . now ( ) - start
if ( diff < MIN _LINGER ) {
setTimeout ( onLoadComplete , MIN _LINGER - diff )
} else {
onLoadComplete ( )
2018-04-25 14:40:46 -07:00
}
}
2023-02-25 00:09:22 -08:00
}
2018-04-25 14:40:46 -07:00
2023-02-25 00:09:22 -08:00
// Listener for Discord RPC.
const gameStateChange = function ( data ) {
data = data . trim ( )
if ( SERVER _JOINED _REGEX . test ( data ) ) {
DiscordWrapper . updateDetails ( 'Exploring the Realm!' )
} else if ( GAME _JOINED _REGEX . test ( data ) ) {
DiscordWrapper . updateDetails ( 'Sailing to Westeros!' )
}
}
2018-04-25 14:40:46 -07:00
2023-02-25 00:09:22 -08:00
const gameErrorListener = function ( data ) {
data = data . trim ( )
if ( data . indexOf ( 'Could not find or load main class net.minecraft.launchwrapper.Launch' ) > - 1 ) {
loggerLaunchSuite . error ( 'Game launch failed, LaunchWrapper was not downloaded properly.' )
showLaunchFailure ( 'Error During Launch' , '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.' )
}
2018-04-25 14:40:46 -07:00
}
2023-02-25 00:09:22 -08:00
try {
// Build Minecraft process.
proc = pb . build ( )
// Bind listeners to stdout.
proc . stdout . on ( 'data' , tempListener )
proc . stderr . on ( 'data' , gameErrorListener )
setLaunchDetails ( 'Done. Enjoy the server!' )
// Init Discord Hook
if ( distro . rawDistribution . discord != null && serv . rawServerdiscord != null ) {
DiscordWrapper . initRPC ( distro . rawDistribution . discord , serv . rawServer . discord )
hasRPC = true
proc . on ( 'close' , ( code , signal ) => {
loggerLaunchSuite . info ( 'Shutting down Discord Rich Presence..' )
DiscordWrapper . shutdownRPC ( )
hasRPC = false
proc = null
} )
}
} catch ( err ) {
2018-04-25 14:40:46 -07:00
2023-02-25 00:09:22 -08:00
loggerLaunchSuite . error ( 'Error during launch' , err )
showLaunchFailure ( 'Error During Launch' , 'Please check the console (CTRL + Shift + i) for more details.' )
2018-05-08 17:10:46 -07:00
2023-02-25 00:09:22 -08:00
}
}
2018-05-08 17:10:46 -07:00
2018-05-06 18:45:20 -07:00
}
/ * *
* News Loading Functions
* /
2018-06-20 06:12:44 -07:00
// DOM Cache
const newsContent = document . getElementById ( 'newsContent' )
const newsArticleTitle = document . getElementById ( 'newsArticleTitle' )
const newsArticleDate = document . getElementById ( 'newsArticleDate' )
const newsArticleAuthor = document . getElementById ( 'newsArticleAuthor' )
const newsArticleComments = document . getElementById ( 'newsArticleComments' )
const newsNavigationStatus = document . getElementById ( 'newsNavigationStatus' )
const newsArticleContentScrollable = document . getElementById ( 'newsArticleContentScrollable' )
const nELoadSpan = document . getElementById ( 'nELoadSpan' )
2018-05-06 18:45:20 -07:00
// News slide caches.
let newsActive = false
let newsGlideCount = 0
/ * *
* Show the news UI via a slide animation .
*
* @ param { boolean } up True to slide up , otherwise false .
* /
function slide _ ( up ) {
const lCUpper = document . querySelector ( '#landingContainer > #upper' )
const lCLLeft = document . querySelector ( '#landingContainer > #lower > #left' )
const lCLCenter = document . querySelector ( '#landingContainer > #lower > #center' )
const lCLRight = document . querySelector ( '#landingContainer > #lower > #right' )
const newsBtn = document . querySelector ( '#landingContainer > #lower > #center #content' )
const landingContainer = document . getElementById ( 'landingContainer' )
const newsContainer = document . querySelector ( '#landingContainer > #newsContainer' )
newsGlideCount ++
if ( up ) {
lCUpper . style . top = '-200vh'
lCLLeft . style . top = '-200vh'
lCLCenter . style . top = '-200vh'
lCLRight . style . top = '-200vh'
newsBtn . style . top = '130vh'
newsContainer . style . top = '0px'
//date.toLocaleDateString('en-US', {month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric'})
//landingContainer.style.background = 'rgba(29, 29, 29, 0.55)'
landingContainer . style . background = 'rgba(0, 0, 0, 0.50)'
setTimeout ( ( ) => {
if ( newsGlideCount === 1 ) {
lCLCenter . style . transition = 'none'
newsBtn . style . transition = 'none'
}
newsGlideCount --
} , 2000 )
} else {
setTimeout ( ( ) => {
newsGlideCount --
} , 2000 )
landingContainer . style . background = null
lCLCenter . style . transition = null
newsBtn . style . transition = null
newsContainer . style . top = '100%'
lCUpper . style . top = '0px'
lCLLeft . style . top = '0px'
lCLCenter . style . top = '0px'
lCLRight . style . top = '0px'
newsBtn . style . top = '10px'
}
}
// Bind news button.
document . getElementById ( 'newsButton' ) . onclick = ( ) => {
2018-05-10 02:48:55 -07:00
// Toggle tabbing.
if ( newsActive ) {
2018-06-04 20:08:03 -07:00
$ ( '#landingContainer *' ) . removeAttr ( 'tabindex' )
$ ( '#newsContainer *' ) . attr ( 'tabindex' , '-1' )
2018-05-10 02:48:55 -07:00
} else {
2018-06-04 20:08:03 -07:00
$ ( '#landingContainer *' ) . attr ( 'tabindex' , '-1' )
$ ( '#newsContainer, #newsContainer *, #lower, #lower #center *' ) . removeAttr ( 'tabindex' )
if ( newsAlertShown ) {
$ ( '#newsButtonAlert' ) . fadeOut ( 2000 )
newsAlertShown = false
ConfigManager . setNewsCacheDismissed ( true )
ConfigManager . save ( )
}
2018-05-10 02:48:55 -07:00
}
2018-05-06 18:45:20 -07:00
slide _ ( ! newsActive )
newsActive = ! newsActive
}
// Array to store article meta.
let newsArr = null
// News load animation listener.
let newsLoadingListener = null
/ * *
* Set the news loading animation .
*
* @ param { boolean } val True to set loading animation , otherwise false .
* /
function setNewsLoading ( val ) {
if ( val ) {
const nLStr = 'Checking for News'
let dotStr = '..'
nELoadSpan . innerHTML = nLStr + dotStr
newsLoadingListener = setInterval ( ( ) => {
if ( dotStr . length >= 3 ) {
dotStr = ''
} else {
dotStr += '.'
}
nELoadSpan . innerHTML = nLStr + dotStr
} , 750 )
} else {
if ( newsLoadingListener != null ) {
clearInterval ( newsLoadingListener )
newsLoadingListener = null
}
}
}
// Bind retry button.
newsErrorRetry . onclick = ( ) => {
$ ( '#newsErrorFailed' ) . fadeOut ( 250 , ( ) => {
initNews ( )
$ ( '#newsErrorLoading' ) . fadeIn ( 250 )
} )
}
2018-06-20 06:12:44 -07:00
newsArticleContentScrollable . onscroll = ( e ) => {
if ( e . target . scrollTop > Number . parseFloat ( $ ( '.newsArticleSpacerTop' ) . css ( 'height' ) ) ) {
newsContent . setAttribute ( 'scrolled' , '' )
} else {
newsContent . removeAttribute ( 'scrolled' )
}
}
2018-05-06 18:45:20 -07:00
/ * *
* Reload the news without restarting .
2018-05-10 02:48:55 -07:00
*
* @ returns { Promise . < void > } A promise which resolves when the news
* content has finished loading and transitioning .
2018-05-06 18:45:20 -07:00
* /
2018-05-10 02:48:55 -07:00
function reloadNews ( ) {
return new Promise ( ( resolve , reject ) => {
$ ( '#newsContent' ) . fadeOut ( 250 , ( ) => {
$ ( '#newsErrorLoading' ) . fadeIn ( 250 )
initNews ( ) . then ( ( ) => {
resolve ( )
} )
} )
2018-05-06 18:45:20 -07:00
} )
}
2018-06-04 20:08:03 -07:00
let newsAlertShown = false
/ * *
* Show the news alert indicating there is new news .
* /
function showNewsAlert ( ) {
newsAlertShown = true
$ ( newsButtonAlert ) . fadeIn ( 250 )
}
2018-05-06 18:45:20 -07:00
/ * *
* Initialize News UI . This will load the news and prepare
* the UI accordingly .
2018-05-10 02:48:55 -07:00
*
* @ returns { Promise . < void > } A promise which resolves when the news
* content has finished loading and transitioning .
2018-05-06 18:45:20 -07:00
* /
2018-05-10 02:48:55 -07:00
function initNews ( ) {
2018-05-06 18:45:20 -07:00
2018-05-10 02:48:55 -07:00
return new Promise ( ( resolve , reject ) => {
setNewsLoading ( true )
2018-05-06 18:45:20 -07:00
2018-05-10 02:48:55 -07:00
let news = { }
loadNews ( ) . then ( news => {
2018-05-06 18:45:20 -07:00
2018-05-10 02:48:55 -07:00
newsArr = news . articles || null
2018-05-06 18:45:20 -07:00
2018-05-10 02:48:55 -07:00
if ( newsArr == null ) {
// News Loading Failed
setNewsLoading ( false )
2018-05-06 18:45:20 -07:00
2018-05-10 02:48:55 -07:00
$ ( '#newsErrorLoading' ) . fadeOut ( 250 , ( ) => {
$ ( '#newsErrorFailed' ) . fadeIn ( 250 , ( ) => {
resolve ( )
} )
} )
} else if ( newsArr . length === 0 ) {
// No News Articles
setNewsLoading ( false )
2018-05-06 18:45:20 -07:00
2018-06-04 20:08:03 -07:00
ConfigManager . setNewsCache ( {
date : null ,
content : null ,
dismissed : false
} )
ConfigManager . save ( )
2018-05-10 02:48:55 -07:00
$ ( '#newsErrorLoading' ) . fadeOut ( 250 , ( ) => {
$ ( '#newsErrorNone' ) . fadeIn ( 250 , ( ) => {
resolve ( )
} )
} )
} else {
// Success
setNewsLoading ( false )
2018-06-04 20:08:03 -07:00
const lN = newsArr [ 0 ]
const cached = ConfigManager . getNewsCache ( )
let newHash = crypto . createHash ( 'sha1' ) . update ( lN . content ) . digest ( 'hex' )
let newDate = new Date ( lN . date )
let isNew = false
if ( cached . date != null && cached . content != null ) {
if ( new Date ( cached . date ) >= newDate ) {
// Compare Content
if ( cached . content !== newHash ) {
isNew = true
showNewsAlert ( )
} else {
if ( ! cached . dismissed ) {
isNew = true
showNewsAlert ( )
}
}
} else {
isNew = true
showNewsAlert ( )
}
} else {
isNew = true
showNewsAlert ( )
}
if ( isNew ) {
ConfigManager . setNewsCache ( {
date : newDate . getTime ( ) ,
content : newHash ,
dismissed : false
} )
ConfigManager . save ( )
}
2018-05-10 02:48:55 -07:00
const switchHandler = ( forward ) => {
let cArt = parseInt ( newsContent . getAttribute ( 'article' ) )
let nxtArt = forward ? ( cArt >= newsArr . length - 1 ? 0 : cArt + 1 ) : ( cArt <= 0 ? newsArr . length - 1 : cArt - 1 )
displayArticle ( newsArr [ nxtArt ] , nxtArt + 1 )
}
2018-06-30 12:16:31 -07:00
2018-05-10 02:48:55 -07:00
document . getElementById ( 'newsNavigateRight' ) . onclick = ( ) => { switchHandler ( true ) }
document . getElementById ( 'newsNavigateLeft' ) . onclick = ( ) => { switchHandler ( false ) }
$ ( '#newsErrorContainer' ) . fadeOut ( 250 , ( ) => {
displayArticle ( newsArr [ 0 ] , 1 )
$ ( '#newsContent' ) . fadeIn ( 250 , ( ) => {
resolve ( )
} )
} )
}
2018-05-06 18:45:20 -07:00
} )
2018-05-10 02:48:55 -07:00
} )
2018-05-06 18:45:20 -07:00
}
2018-06-30 12:16:31 -07:00
/ * *
* Add keyboard controls to the news UI . Left and right arrows toggle
* between articles . If you are on the landing page , the up arrow will
* open the news UI .
* /
document . addEventListener ( 'keydown' , ( e ) => {
if ( newsActive ) {
if ( e . key === 'ArrowRight' || e . key === 'ArrowLeft' ) {
document . getElementById ( e . key === 'ArrowRight' ? 'newsNavigateRight' : 'newsNavigateLeft' ) . click ( )
}
// Interferes with scrolling an article using the down arrow.
// Not sure of a straight forward solution at this point.
// if(e.key === 'ArrowDown'){
// document.getElementById('newsButton').click()
// }
} else {
if ( getCurrentView ( ) === VIEWS . landing ) {
if ( e . key === 'ArrowUp' ) {
document . getElementById ( 'newsButton' ) . click ( )
}
}
}
} )
2018-05-06 18:45:20 -07:00
/ * *
* Display a news article on the UI .
*
* @ param { Object } articleObject The article meta object .
* @ param { number } index The article index .
* /
function displayArticle ( articleObject , index ) {
newsArticleTitle . innerHTML = articleObject . title
newsArticleTitle . href = articleObject . link
newsArticleAuthor . innerHTML = 'by ' + articleObject . author
newsArticleDate . innerHTML = articleObject . date
newsArticleComments . innerHTML = articleObject . comments
newsArticleComments . href = articleObject . commentsLink
2018-06-20 06:12:44 -07:00
newsArticleContentScrollable . innerHTML = '<div id="newsArticleContentWrapper"><div class="newsArticleSpacerTop"></div>' + articleObject . content + '<div class="newsArticleSpacerBot"></div></div>'
2018-09-04 23:30:26 -07:00
Array . from ( newsArticleContentScrollable . getElementsByClassName ( 'bbCodeSpoilerButton' ) ) . forEach ( v => {
v . onclick = ( ) => {
const text = v . parentElement . getElementsByClassName ( 'bbCodeSpoilerText' ) [ 0 ]
text . style . display = text . style . display === 'block' ? 'none' : 'block'
}
} )
2018-05-06 18:45:20 -07:00
newsNavigationStatus . innerHTML = index + ' of ' + newsArr . length
newsContent . setAttribute ( 'article' , index - 1 )
}
/ * *
* Load news information from the RSS feed specified in the
* distribution index .
* /
2023-02-24 18:02:18 -08:00
async function loadNews ( ) {
const distroData = await DistroAPI . getDistribution ( )
const promise = new Promise ( ( resolve , reject ) => {
const newsFeed = distroData . rawDistribution . rss
2018-05-08 17:10:46 -07:00
const newsHost = new URL ( newsFeed ) . origin + '/'
2020-03-18 16:54:46 -07:00
$ . ajax ( {
url : newsFeed ,
success : ( data ) => {
const items = $ ( data ) . find ( 'item' )
const articles = [ ]
for ( let i = 0 ; i < items . length ; i ++ ) {
// JQuery Element
const el = $ ( items [ i ] )
// Resolve date.
const date = new Date ( el . find ( 'pubDate' ) . text ( ) ) . toLocaleDateString ( 'en-US' , { month : 'short' , day : 'numeric' , year : 'numeric' , hour : 'numeric' , minute : 'numeric' } )
// Resolve comments.
let comments = el . find ( 'slash\\:comments' ) . text ( ) || '0'
comments = comments + ' Comment' + ( comments === '1' ? '' : 's' )
// Fix relative links in content.
let content = el . find ( 'content\\:encoded' ) . text ( )
let regex = /src="(?!http:\/\/|https:\/\/)(.+?)"/g
let matches
while ( ( matches = regex . exec ( content ) ) ) {
content = content . replace ( ` " ${ matches [ 1 ] } " ` , ` " ${ newsHost + matches [ 1 ] } " ` )
2018-07-22 10:31:15 -07:00
}
2020-03-18 16:54:46 -07:00
let link = el . find ( 'link' ) . text ( )
let title = el . find ( 'title' ) . text ( )
let author = el . find ( 'dc\\:creator' ) . text ( )
// Generate article.
articles . push (
{
link ,
title ,
date ,
author ,
content ,
comments ,
commentsLink : link + '#comments'
}
)
}
resolve ( {
articles
} )
} ,
timeout : 2500
} ) . catch ( err => {
2018-11-08 22:03:28 -08:00
resolve ( {
articles : null
2018-05-06 18:45:20 -07:00
} )
2018-11-08 22:03:28 -08:00
} )
2018-05-06 18:45:20 -07:00
} )
2023-02-24 18:02:18 -08:00
return await promise
2021-07-19 08:08:55 -07:00
}