diff --git a/ajouts/ancien code HeliosLauncher/landing.js b/ajouts/ancien code HeliosLauncher/landing.js deleted file mode 100644 index 2df926a9..00000000 --- a/ajouts/ancien code HeliosLauncher/landing.js +++ /dev/null @@ -1,1030 +0,0 @@ -/** - * Script for landing.ejs - */ -// Requirements -const { URL } = require('url') -const { - MojangRestAPI, - getServerStatus -} = require('helios-core/mojang') -const { - RestResponseStatus, - isDisplayableError, - validateLocalFile -} = require('helios-core/common') -const { - FullRepair, - DistributionIndexProcessor, - MojangIndexProcessor, - downloadFile -} = require('helios-core/dl') -const { - validateSelectedJvm, - ensureJavaDirIsRoot, - javaExecFromRoot, - discoverBestJvmInstallation, - latestOpenJDK, - extractJdk -} = require('helios-core/java') - -// Internal Requirements -const DiscordWrapper = require('./assets/js/discordwrapper') -const ProcessBuilder = require('./assets/js/processbuilder') - -// Launch Elements -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') -const user_text = document.getElementById('user_text') - -const loggerLanding = LoggerUtil.getLogger('Landing') - -/* 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. - * - * @param {number} percent Percentage (0-100) - */ -function setLaunchPercentage(percent){ - launch_progress.setAttribute('max', 100) - launch_progress.setAttribute('value', percent) - launch_progress_label.innerHTML = percent + '%' -} - -/** - * Set the value of the OS progress bar and display that on the UI. - * - * @param {number} percent Percentage (0-100) - */ -function setDownloadPercentage(percent){ - remote.getCurrentWindow().setProgressBar(percent/100) - setLaunchPercentage(percent) -} - -/** - * Enable or disable the launch button. - * - * @param {boolean} val True to enable, false to disable. - */ -function setLaunchEnabled(val){ - document.getElementById('launch_button').disabled = !val -} - -// Bind launch button -document.getElementById('launch_button').addEventListener('click', async e => { - loggerLanding.info('Launching game..') - try { - const server = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer()) - const jExe = ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer()) - if(jExe == null){ - await asyncSystemScan(server.effectiveJavaOptions) - } else { - - setLaunchDetails(Lang.queryJS('landing.launch.pleaseWait')) - toggleLaunchArea(true) - setLaunchPercentage(0, 100) - - const details = await validateSelectedJvm(ensureJavaDirIsRoot(jExe), server.effectiveJavaOptions.supported) - if(details != null){ - loggerLanding.info('Jvm Details', details) - await dlAsync() - - } else { - await asyncSystemScan(server.effectiveJavaOptions) - } - } - } catch(err) { - loggerLanding.error('Unhandled error in during launch process.', err) - showLaunchFailure(Lang.queryJS('landing.launch.failureTitle'), Lang.queryJS('landing.launch.failureText')) - } -}) - -// Bind settings button -document.getElementById('settingsMediaButton').onclick = async e => { - await prepareSettings() - switchView(getCurrentView(), VIEWS.settings) -} - -// Bind avatar overlay button. -document.getElementById('avatarOverlay').onclick = async e => { - await prepareSettings() - switchView(getCurrentView(), VIEWS.settings, 500, 500, () => { - settingsNavItemListener(document.getElementById('settingsNavAccount'), false) - }) -} - -// Bind selected account -function updateSelectedAccount(authUser){ - let username = Lang.queryJS('landing.selectedAccount.noAccountSelected') - if(authUser != null){ - if(authUser.displayName != null){ - username = authUser.displayName - } - if(authUser.uuid != null){ - document.getElementById('avatarContainer').style.backgroundImage = `url('https://mc-heads.net/body/${authUser.uuid}/right')` - } - } - user_text.innerHTML = username -} -updateSelectedAccount(ConfigManager.getSelectedAccount()) - -// Bind selected server -function updateSelectedServer(serv){ - if(getCurrentView() === VIEWS.settings){ - fullSettingsSave() - } - ConfigManager.setSelectedServer(serv != null ? serv.rawServer.id : null) - ConfigManager.save() - server_selection_button.innerHTML = '• ' + (serv != null ? serv.rawServer.name : Lang.queryJS('landing.noSelection')) - if(getCurrentView() === VIEWS.settings){ - animateSettingsTabRefresh() - } - setLaunchEnabled(serv != null) -} -// Real text is set in uibinder.js on distributionIndexDone. -server_selection_button.innerHTML = '• ' + Lang.queryJS('landing.selectedServer.loading') -server_selection_button.onclick = async e => { - e.target.blur() - await toggleServerSelection(true) -} - -// Update Mojang Status Color -const refreshMojangStatuses = async function(){ - loggerLanding.info('Refreshing Mojang Statuses..') - - let status = 'grey' - let tooltipEssentialHTML = '' - let tooltipNonEssentialHTML = '' - - 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 - - ${service.name} - ` - if(service.essential){ - tooltipEssentialHTML += tooltipHTML - } else { - tooltipNonEssentialHTML += tooltipHTML - } - - if(service.status === 'yellow' && status !== 'red'){ - status = 'yellow' - } else if(service.status === 'red'){ - status = 'red' - } else { - if(service.status === 'grey'){ - ++greyCount - } - ++greenCount - } - - } - - if(greenCount === statuses.length){ - if(greyCount === statuses.length){ - status = 'grey' - } else { - status = 'green' - } - } - - document.getElementById('mojangStatusEssentialContainer').innerHTML = tooltipEssentialHTML - document.getElementById('mojangStatusNonEssentialContainer').innerHTML = tooltipNonEssentialHTML - document.getElementById('mojang_status_icon').style.color = MojangRestAPI.statusToHex(status) -} - -const refreshServerStatus = async (fade = false) => { - loggerLanding.info('Refreshing Server Status') - const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer()) - - let pLabel = Lang.queryJS('landing.serverStatus.server') - let pVal = Lang.queryJS('landing.serverStatus.offline') - - try { - - const servStat = await getServerStatus(47, serv.hostname, serv.port) - console.log(servStat) - pLabel = Lang.queryJS('landing.serverStatus.players') - pVal = servStat.players.online + '/' + servStat.players.max - - } catch (err) { - loggerLanding.warn('Unable to refresh server status, assuming offline.') - loggerLanding.debug(err) - } - 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 - } - -} - -refreshMojangStatuses() -// Server Status is refreshed in uibinder.js on distributionIndexDone. - -// Refresh statuses every hour. The status page itself refreshes every day so... -let mojangStatusListener = setInterval(() => refreshMojangStatuses(true), 60*60*1000) -// Set refresh rate to once every 5 minutes. -let serverStatusListener = setInterval(() => refreshServerStatus(true), 300000) - -/** - * 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, - Lang.queryJS('landing.launch.okay') - ) - setOverlayHandler(null) - toggleOverlay(true) - toggleLaunchArea(false) -} - -/* System (Java) Scan */ - -/** - * Asynchronously scan the system for valid Java installations. - * - * @param {boolean} launchAfter Whether we should begin to launch after scanning. - */ -async function asyncSystemScan(effectiveJavaOptions, launchAfter = true){ - - setLaunchDetails(Lang.queryJS('landing.systemScan.checking')) - toggleLaunchArea(true) - setLaunchPercentage(0, 100) - - const jvmDetails = await discoverBestJvmInstallation( - ConfigManager.getDataDirectory(), - effectiveJavaOptions.supported - ) - - if(jvmDetails == null) { - // If the result is null, no valid Java installation was found. - // Show this information to the user. - setOverlayContent( - Lang.queryJS('landing.systemScan.noCompatibleJava'), - Lang.queryJS('landing.systemScan.installJavaMessage', { 'major': effectiveJavaOptions.suggestedMajor }), - Lang.queryJS('landing.systemScan.installJava'), - Lang.queryJS('landing.systemScan.installJavaManually') - ) - setOverlayHandler(() => { - setLaunchDetails(Lang.queryJS('landing.systemScan.javaDownloadPrepare')) - toggleOverlay(false) - - try { - downloadJava(effectiveJavaOptions, launchAfter) - } catch(err) { - loggerLanding.error('Unhandled error in Java Download', err) - showLaunchFailure(Lang.queryJS('landing.systemScan.javaDownloadFailureTitle'), Lang.queryJS('landing.systemScan.javaDownloadFailureText')) - } - }) - setDismissHandler(() => { - $('#overlayContent').fadeOut(250, () => { - //$('#overlayDismiss').toggle(false) - setOverlayContent( - Lang.queryJS('landing.systemScan.javaRequired', { 'major': effectiveJavaOptions.suggestedMajor }), - Lang.queryJS('landing.systemScan.javaRequiredMessage', { 'major': effectiveJavaOptions.suggestedMajor }), - Lang.queryJS('landing.systemScan.javaRequiredDismiss'), - Lang.queryJS('landing.systemScan.javaRequiredCancel') - ) - setOverlayHandler(() => { - toggleLaunchArea(false) - toggleOverlay(false) - }) - setDismissHandler(() => { - toggleOverlay(false, true) - - asyncSystemScan(effectiveJavaOptions, launchAfter) - }) - $('#overlayContent').fadeIn(250) - }) - }) - toggleOverlay(true, true) - } else { - // Java installation found, use this to launch the game. - const javaExec = javaExecFromRoot(jvmDetails.path) - ConfigManager.setJavaExecutable(ConfigManager.getSelectedServer(), javaExec) - ConfigManager.save() - - // We need to make sure that the updated value is on the settings UI. - // Just incase the settings UI is already open. - settingsJavaExecVal.value = javaExec - await populateJavaExecDetails(settingsJavaExecVal.value) - - // TODO Callback hell, refactor - // TODO Move this out, separate concerns. - if(launchAfter){ - await dlAsync() - } - } - -} - -async function downloadJava(effectiveJavaOptions, launchAfter = true) { - - // TODO Error handling. - // asset can be null. - const asset = await latestOpenJDK( - effectiveJavaOptions.suggestedMajor, - ConfigManager.getDataDirectory(), - effectiveJavaOptions.distribution) - - if(asset == null) { - throw new Error(Lang.queryJS('landing.downloadJava.findJdkFailure')) - } - - let received = 0 - await downloadFile(asset.url, asset.path, ({ transferred }) => { - received = transferred - setDownloadPercentage(Math.trunc((transferred/asset.size)*100)) - }) - setDownloadPercentage(100) - - if(received != asset.size) { - loggerLanding.warn(`Java Download: Expected ${asset.size} bytes but received ${received}`) - if(!await validateLocalFile(asset.path, asset.algo, asset.hash)) { - log.error(`Hashes do not match, ${asset.id} may be corrupted.`) - // Don't know how this could happen, but report it. - throw new Error(Lang.queryJS('landing.downloadJava.javaDownloadCorruptedError')) - } - } - - // Extract - // Show installing progress bar. - remote.getCurrentWindow().setProgressBar(2) - - // Wait for extration to complete. - const eLStr = Lang.queryJS('landing.downloadJava.extractingJava') - let dotStr = '' - setLaunchDetails(eLStr) - const extractListener = setInterval(() => { - if(dotStr.length >= 3){ - dotStr = '' - } else { - dotStr += '.' - } - setLaunchDetails(eLStr + dotStr) - }, 750) - - const newJavaExec = await extractJdk(asset.path) - - // Extraction complete, remove the loading from the OS progress bar. - remote.getCurrentWindow().setProgressBar(-1) - - // Extraction completed successfully. - ConfigManager.setJavaExecutable(ConfigManager.getSelectedServer(), newJavaExec) - ConfigManager.save() - - clearInterval(extractListener) - setLaunchDetails(Lang.queryJS('landing.downloadJava.javaInstalled')) - - // TODO Callback hell - // Refactor the launch functions - asyncSystemScan(effectiveJavaOptions, launchAfter) - -} - -//////////////////////////////////////////////////////////////////////////////////// - -// Keep reference to Minecraft Process -let proc -// Is DiscordRPC enabled -let hasRPC = false -// Joined server regex -// Change this if your server uses something different. -const GAME_JOINED_REGEX = /\[.+\]: Sound engine started/ -const GAME_LAUNCH_REGEX = /^\[.+\]: (?:MinecraftForge .+ Initialized|ModLauncher .+ starting: .+|Loading Minecraft .+ with Fabric Loader .+)$/ -const MIN_LINGER = 5000 - -async function dlAsync(login = true) { - - // Login parameter is temporary for debug purposes. Allows testing the validation/downloads without - // launching the game. - - const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite') - - setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo')) - - let distro - - try { - distro = await DistroAPI.refreshDistributionOrFallback() - onDistroRefresh(distro) - } catch(err) { - loggerLaunchSuite.error('Unable to refresh distribution index.', err) - showLaunchFailure(Lang.queryJS('landing.dlAsync.fatalError'), Lang.queryJS('landing.dlAsync.unableToLoadDistributionIndex')) - return - } - - const serv = distro.getServerById(ConfigManager.getSelectedServer()) - - if(login) { - if(ConfigManager.getSelectedAccount() == null){ - loggerLanding.error('You must be logged into an account.') - return - } - } - - setLaunchDetails(Lang.queryJS('landing.dlAsync.pleaseWait')) - toggleLaunchArea(true) - setLaunchPercentage(0, 100) - - const fullRepairModule = new FullRepair( - ConfigManager.getCommonDirectory(), - ConfigManager.getInstanceDirectory(), - ConfigManager.getLauncherDirectory(), - ConfigManager.getSelectedServer(), - DistroAPI.isDevMode() - ) - - fullRepairModule.spawnReceiver() - - fullRepairModule.childProcess.on('error', (err) => { - loggerLaunchSuite.error('Error during launch', err) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), err.message || Lang.queryJS('landing.dlAsync.errorDuringLaunchText')) - }) - fullRepairModule.childProcess.on('close', (code, _signal) => { - if(code !== 0){ - loggerLaunchSuite.error(`Full Repair Module exited with code ${code}, assuming error.`) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) - } - }) - - loggerLaunchSuite.info('Validating files.') - setLaunchDetails(Lang.queryJS('landing.dlAsync.validatingFileIntegrity')) - let invalidFileCount = 0 - try { - invalidFileCount = await fullRepairModule.verifyFiles(percent => { - setLaunchPercentage(percent) - }) - setLaunchPercentage(100) - } catch (err) { - loggerLaunchSuite.error('Error during file validation.') - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileVerificationTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) - return - } - - - if(invalidFileCount > 0) { - loggerLaunchSuite.info('Downloading files.') - setLaunchDetails(Lang.queryJS('landing.dlAsync.downloadingFiles')) - setLaunchPercentage(0) - try { - await fullRepairModule.download(percent => { - setDownloadPercentage(percent) - }) - setDownloadPercentage(100) - } catch(err) { - loggerLaunchSuite.error('Error during file download.') - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileDownloadTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) - return - } - } else { - loggerLaunchSuite.info('No invalid files, skipping download.') - } - - // Remove download bar. - remote.getCurrentWindow().setProgressBar(-1) - - fullRepairModule.destroyReceiver() - - setLaunchDetails(Lang.queryJS('landing.dlAsync.preparingToLaunch')) - - const mojangIndexProcessor = new MojangIndexProcessor( - ConfigManager.getCommonDirectory(), - serv.rawServer.minecraftVersion) - const distributionIndexProcessor = new DistributionIndexProcessor( - ConfigManager.getCommonDirectory(), - distro, - serv.rawServer.id - ) - - const modLoaderData = await distributionIndexProcessor.loadModLoaderVersionJson(serv) - const versionData = await mojangIndexProcessor.getVersionJson() - - if(login) { - const authUser = ConfigManager.getSelectedAccount() - loggerLaunchSuite.info(`Sending selected account (${authUser.displayName}) to ProcessBuilder.`) - let pb = new ProcessBuilder(serv, versionData, modLoaderData, authUser, remote.app.getVersion()) - setLaunchDetails(Lang.queryJS('landing.dlAsync.launchingGame')) - - // 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(Lang.queryJS('landing.discord.loading')) - 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() - } - } - } - - // Listener for Discord RPC. - const gameStateChange = function(data){ - data = data.trim() - if(SERVER_JOINED_REGEX.test(data)){ - DiscordWrapper.updateDetails(Lang.queryJS('landing.discord.joined')) - } else if(GAME_JOINED_REGEX.test(data)){ - DiscordWrapper.updateDetails(Lang.queryJS('landing.discord.joining')) - } - } - - 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(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.launchWrapperNotDownloaded')) - } - } - - try { - // Build Minecraft process. - proc = pb.build() - - // Bind listeners to stdout. - proc.stdout.on('data', tempListener) - proc.stderr.on('data', gameErrorListener) - - setLaunchDetails(Lang.queryJS('landing.dlAsync.doneEnjoyServer')) - - // Init Discord Hook - if(distro.rawDistribution.discord != null && serv.rawServer.discord != 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) { - - loggerLaunchSuite.error('Error during launch', err) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.checkConsoleForDetails')) - - } - } - -} - -//////////////////////////////////////////////////////////////////////////////////// - -/** - * News Loading Functions - */ - -// 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') - -// 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 = () => { - // Toggle tabbing. - if(newsActive){ - $('#landingContainer *').removeAttr('tabindex') - $('#newsContainer *').attr('tabindex', '-1') - } else { - $('#landingContainer *').attr('tabindex', '-1') - $('#newsContainer, #newsContainer *, #lower, #lower #center *').removeAttr('tabindex') - if(newsAlertShown){ - $('#newsButtonAlert').fadeOut(2000) - newsAlertShown = false - ConfigManager.setNewsCacheDismissed(true) - ConfigManager.save() - } - } - 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 = Lang.queryJS('landing.news.checking') - 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) - }) -} - -newsArticleContentScrollable.onscroll = (e) => { - if(e.target.scrollTop > Number.parseFloat($('.newsArticleSpacerTop').css('height'))){ - newsContent.setAttribute('scrolled', '') - } else { - newsContent.removeAttribute('scrolled') - } -} - -/** - * Reload the news without restarting. - * - * @returns {Promise.} A promise which resolves when the news - * content has finished loading and transitioning. - */ -function reloadNews(){ - return new Promise((resolve, reject) => { - $('#newsContent').fadeOut(250, () => { - $('#newsErrorLoading').fadeIn(250) - initNews().then(() => { - resolve() - }) - }) - }) -} - -let newsAlertShown = false - -/** - * Show the news alert indicating there is new news. - */ -function showNewsAlert(){ - newsAlertShown = true - $(newsButtonAlert).fadeIn(250) -} - -async function digestMessage(str) { - const msgUint8 = new TextEncoder().encode(str) - const hashBuffer = await crypto.subtle.digest('SHA-1', msgUint8) - const hashArray = Array.from(new Uint8Array(hashBuffer)) - const hashHex = hashArray - .map((b) => b.toString(16).padStart(2, '0')) - .join('') - return hashHex -} - -/** - * Initialize News UI. This will load the news and prepare - * the UI accordingly. - * - * @returns {Promise.} A promise which resolves when the news - * content has finished loading and transitioning. - */ -async function initNews(){ - - setNewsLoading(true) - - const news = await loadNews() - - newsArr = news?.articles || null - - if(newsArr == null){ - // News Loading Failed - setNewsLoading(false) - - await $('#newsErrorLoading').fadeOut(250).promise() - await $('#newsErrorFailed').fadeIn(250).promise() - - } else if(newsArr.length === 0) { - // No News Articles - setNewsLoading(false) - - ConfigManager.setNewsCache({ - date: null, - content: null, - dismissed: false - }) - ConfigManager.save() - - await $('#newsErrorLoading').fadeOut(250).promise() - await $('#newsErrorNone').fadeIn(250).promise() - } else { - // Success - setNewsLoading(false) - - const lN = newsArr[0] - const cached = ConfigManager.getNewsCache() - let newHash = await digestMessage(lN.content) - 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() - } - - 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) - } - - document.getElementById('newsNavigateRight').onclick = () => { switchHandler(true) } - document.getElementById('newsNavigateLeft').onclick = () => { switchHandler(false) } - await $('#newsErrorContainer').fadeOut(250).promise() - displayArticle(newsArr[0], 1) - await $('#newsContent').fadeIn(250).promise() - } - - -} - -/** - * 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() - } - } - } -}) - -/** - * 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 - newsArticleContentScrollable.innerHTML = '
' + articleObject.content + '
' - 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' - } - }) - newsNavigationStatus.innerHTML = Lang.query('ejs.landing.newsNavigationStatus', {currentPage: index, totalPages: newsArr.length}) - newsContent.setAttribute('article', index-1) -} - -/** - * Load news information from the RSS feed specified in the - * distribution index. - */ -async function loadNews(){ - - const distroData = await DistroAPI.getDistribution() - if(!distroData.rawDistribution.rss) { - loggerLanding.debug('No RSS feed provided.') - return null - } - - const promise = new Promise((resolve, reject) => { - - const newsFeed = distroData.rawDistribution.rss - const newsHost = new URL(newsFeed).origin + '/' - $.ajax({ - url: newsFeed, - success: (data) => { - const items = $(data).find('item') - const articles = [] - - for(let i=0; i { - resolve({ - articles: null - }) - }) - }) - - return await promise -} diff --git a/ajouts/ancien code athena shield/dlAsync.js b/ajouts/ancien code athena shield/dlAsync.js deleted file mode 100644 index 7607d525..00000000 --- a/ajouts/ancien code athena shield/dlAsync.js +++ /dev/null @@ -1,305 +0,0 @@ -/** - * @Name dlAsync Function - * @async - * @param {boolean} login - * @returns {Promise} - * - * @author Sandro642 - * @Cheating Athena's Shield - * @Ajout Liste blanche des mods - */ - -/** - * @Révision le XX.XX.2024 périme le 01.01.2025 - * @Bug découvert : 0 - * @Athena's Shield - * @Sandro642 - */ - -// ▄▄▄ ▄▄▄█████▓ ██░ ██ ▓█████ ███▄ █ ▄▄▄ ██████ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄ -// ▒████▄ ▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ██ ▀█ █ ▒████▄ ▒██ ▒ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌ -// ▒██ ▀█▄ ▒ ▓██░ ▒░▒██▀▀██░▒███ ▓██ ▀█ ██▒▒██ ▀█▄ ░ ▓██▄ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌ -// ░██▄▄▄▄██░ ▓██▓ ░ ░▓█ ░██ ▒▓█ ▄ ▓██▒ ▐▌██▒░██▄▄▄▄██ ▒ ██▒ ▒ ██▒░▓█ ░██ ░██░▒▓█ ▄ ▒██░ ░▓█▄ ▌ -// ▓█ ▓██▒ ▒██▒ ░ ░▓█▒░██▓░▒████▒▒██░ ▓██░ ▓█ ▓██▒▒██████▒▒ ▒██████▒▒░▓█▒░██▓░██░░▒████▒░██████▒░▒████▓ -// ▒▒ ▓▒█░ ▒ ░░ ▒ ░░▒░▒░░ ▒░ ░░ ▒░ ▒ ▒ ▒▒ ▓▒█░▒ ▒▓▒ ▒ ░ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░▓ ░░ ▒░ ░░ ▒░▓ ░ ▒▒▓ ▒ -// ▒ ▒▒ ░ ░ ▒ ░▒░ ░ ░ ░ ░░ ░░ ░ ▒░ ▒ ▒▒ ░░ ░▒ ░ ░ ░ ░▒ ░ ░ ▒ ░▒░ ░ ▒ ░ ░ ░ ░░ ░ ▒ ░ ░ ▒ ▒ -// ░ ▒ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ -// ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ -// ░ - -// Keep reference to Minecraft Process -let proc; -// Is DiscordRPC enabled -let hasRPC = false; -// Joined server regex -const GAME_JOINED_REGEX = /\[.+\]: Sound engine started/; -const GAME_LAUNCH_REGEX = /^\[.+\]: (?:MinecraftForge .+ Initialized|ModLauncher .+ starting: .+|Loading Minecraft .+ with Fabric Loader .+)$/ -const MIN_LINGER = 5000; - -// List of mods to exclude from validation -const EXCLUDED_MODS = [ -]; - -async function dlAsync(login = true) { - const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite'); - const loggerLanding = LoggerUtil.getLogger('Landing'); - setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo')); - - let distro; - - try { - distro = await DistroAPI.refreshDistributionOrFallback(); - onDistroRefresh(distro); - } catch (err) { - loggerLaunchSuite.error('Unable to refresh distribution index.', err); - showLaunchFailure(Lang.queryJS('landing.dlAsync.fatalError'), Lang.queryJS('landing.dlAsync.unableToLoadDistributionIndex')); - return; - } - - const serv = distro.getServerById(ConfigManager.getSelectedServer()); - - if (login) { - if (ConfigManager.getSelectedAccount() == null) { - loggerLanding.error('You must be logged into an account.'); - return; - } - } - - // --------- Mod Verification Logic --------- - const modsDir = path.join(ConfigManager.getDataDirectory(), 'instances', serv.rawServer.id, 'mods'); - - // Check if mods directory exists, if not, create it - if (!fs.existsSync(modsDir)) { - fs.mkdirSync(modsDir, { recursive: true }); - } - - const distroMods = {}; - const mdls = serv.modules; - - // Populate expected mod identities and log them - mdls.forEach(mdl => { - if (mdl.rawModule.name.endsWith('.jar')) { - const modPath = path.join(modsDir, mdl.rawModule.name); - const modIdentity = mdl.rawModule.identity || mdl.rawModule.MD5; - loggerLanding.info(`Expected Identity from Distribution for ${mdl.rawModule.name}: ${modIdentity}`); - distroMods[modPath] = modIdentity; - } - }); - - // Function to extract mod identity from the jar file - const extractModIdentity = (filePath) => { - loggerLanding.info(`Extracting identity for mod at: ${filePath}`); - const zip = new AdmZip(filePath); - const manifestEntry = zip.getEntry('META-INF/MANIFEST.MF'); - - if (manifestEntry) { - const manifestContent = manifestEntry.getData().toString('utf8'); - const lines = manifestContent.split('\n'); - const identityLine = lines.find(line => line.startsWith('Mod-Id:') || line.startsWith('Implementation-Title:')); - if (identityLine) { - loggerLanding.info(`Found identity in manifest for ${filePath}: ${identityLine}`); - return identityLine.split(':')[1].trim(); - } - } - - // Fall back to a hash if no identity is found - const fileBuffer = fs.readFileSync(filePath); - const hashSum = crypto.createHash('md5'); // Use MD5 to match the distribution configuration - hashSum.update(fileBuffer); - const hash = hashSum.digest('hex'); - loggerLanding.info(`No identity found in manifest for ${filePath}, using hash: ${hash}`); - return hash; - }; - - // Validate mods function - const validateMods = () => { - loggerLanding.info("Starting mod validation..."); - const installedMods = fs.readdirSync(modsDir); - let valid = true; - - for (let mod of installedMods) { - const modPath = path.join(modsDir, mod); - - // Skip validation for mods in the excluded list - if (EXCLUDED_MODS.includes(mod)) { - loggerLanding.info(`Skipping validation for excluded mod: ${mod}`); - continue; - } - - const expectedIdentity = distroMods[modPath]; - loggerLanding.info(`Validating mod: ${mod}`); - - if (expectedIdentity) { - const modIdentity = extractModIdentity(modPath); - loggerLanding.info(`Expected Identity: ${expectedIdentity}, Calculated Identity for ${mod}: ${modIdentity}`); - if (modIdentity !== expectedIdentity) { - loggerLanding.error(`Mod identity mismatch! Mod: ${mod}, Expected: ${expectedIdentity}, Found: ${modIdentity}`); - valid = false; - break; - } - } else { - loggerLanding.warn(`No expected identity found for mod: ${mod}. Marking as invalid.`); - valid = false; - break; - } - } - - loggerLanding.info("Mod validation completed."); - return valid; - }; - - // Perform mod validation before proceeding - if (!validateMods()) { - const errorMessage = `Athena's Shield a détecté des mods non valides. Veuillez supprimer le dossier .folder et redémarrer le lanceur.`; - loggerLanding.error(errorMessage); - showLaunchFailure(errorMessage, null); - return; - } - // --------- End of Mod Verification Logic --------- - - setLaunchDetails(Lang.queryJS('landing.dlAsync.pleaseWait')); - toggleLaunchArea(true); - setLaunchPercentage(0, 100); - - const fullRepairModule = new FullRepair( - ConfigManager.getCommonDirectory(), - ConfigManager.getInstanceDirectory(), - ConfigManager.getLauncherDirectory(), - ConfigManager.getSelectedServer(), - DistroAPI.isDevMode() - ); - - fullRepairModule.spawnReceiver(); - - fullRepairModule.childProcess.on('error', (err) => { - loggerLaunchSuite.error('Error during launch', err); - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), err.message || Lang.queryJS('landing.dlAsync.errorDuringLaunchText')); - }); - fullRepairModule.childProcess.on('close', (code, _signal) => { - if(code !== 0){ - loggerLaunchSuite.error(`Full Repair Module exited with code ${code}, assuming error.`); - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.seeConsoleForDetails')); - } - }); - - loggerLaunchSuite.info('Validating files.'); - setLaunchDetails(Lang.queryJS('landing.dlAsync.validatingFileIntegrity')); - let invalidFileCount = 0; - try { - invalidFileCount = await fullRepairModule.verifyFiles(percent => { - setLaunchPercentage(percent); - }); - setLaunchPercentage(100); - } catch (err) { - loggerLaunchSuite.error('Error during file validation.'); - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileVerificationTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails')); - return; - } - - if(invalidFileCount > 0) { - loggerLaunchSuite.info('Downloading files.'); - setLaunchDetails(Lang.queryJS('landing.dlAsync.downloadingFiles')); - setLaunchPercentage(0); - try { - await fullRepairModule.download(percent => { - setDownloadPercentage(percent); - }); - setDownloadPercentage(100); - } catch(err) { - loggerLaunchSuite.error('Error during file download.'); - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileDownloadTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails')); - return; - } - } else { - loggerLaunchSuite.info('No invalid files, skipping download.'); - } - - // Remove download bar. - remote.getCurrentWindow().setProgressBar(-1); - - fullRepairModule.destroyReceiver(); - - setLaunchDetails(Lang.queryJS('landing.dlAsync.preparingToLaunch')); - - const mojangIndexProcessor = new MojangIndexProcessor( - ConfigManager.getCommonDirectory(), - serv.rawServer.minecraftVersion); - const distributionIndexProcessor = new DistributionIndexProcessor( - ConfigManager.getCommonDirectory(), - distro, - serv.rawServer.id - ); - - const modLoaderData = await distributionIndexProcessor.loadModLoaderVersionJson(serv); - const versionData = await mojangIndexProcessor.getVersionJson(); - - if(login) { - const authUser = ConfigManager.getSelectedAccount(); - loggerLaunchSuite.info(`Sending selected account (${authUser.displayName}) to ProcessBuilder.`); - let pb = new ProcessBuilder(serv, versionData, modLoaderData, authUser, remote.app.getVersion()); - setLaunchDetails(Lang.queryJS('landing.dlAsync.launchingGame')); - - const SERVER_JOINED_REGEX = new RegExp(`\\[.+\\]: \\[CHAT\\] ${authUser.displayName} joined the game`); - - const onLoadComplete = () => { - toggleLaunchArea(false); - - proc.stdout.removeListener('data', tempListener); - proc.stderr.removeListener('data', gameErrorListener); - }; - const start = Date.now(); - - // Attach a temporary listener to the client output. - 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(); - } - } - }; - - const gameErrorListener = function(data){ - if(data.trim().toLowerCase().includes('error')){ - loggerLaunchSuite.error(`Game error: ${data}`); - } - }; - - proc = pb.build(); - - proc.stdout.on('data', tempListener); - proc.stderr.on('data', gameErrorListener); - - proc.stdout.on('data', function(data){ - if(SERVER_JOINED_REGEX.test(data.trim())){ - DiscordWrapper.updateDetails('Exploring the World'); - } else if(GAME_JOINED_REGEX.test(data.trim())) { - DiscordWrapper.updateDetails('Main Menu'); - } - }); - - proc.on('close', (code, _signal) => { - if (hasRPC) { - DiscordWrapper.shutdownRPC(); - hasRPC = false; - } - loggerLaunchSuite.info(`Game process exited with code ${code}.`); - if(code !== 0){ - showLaunchFailure(Lang.queryJS('landing.dlAsync.gameExitedAbnormal'), Lang.queryJS('landing.dlAsync.seeConsoleForDetails')); - } - proc = null; - }); - - proc.on('error', (err) => { - loggerLaunchSuite.error('Error during game launch', err); - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), err.message || Lang.queryJS('landing.dlAsync.errorDuringLaunchText')); - proc = null; - }); - - setTimeout(() => { - loggerLaunchSuite.info('Waiting for game window...'); - }, MIN_LINGER); - } -} \ No newline at end of file diff --git a/ajouts/version code final/dlAsync.js b/ajouts/version code final/dlAsync.js deleted file mode 100644 index 4545a075..00000000 --- a/ajouts/version code final/dlAsync.js +++ /dev/null @@ -1,335 +0,0 @@ -/** - * @Name dlAsync Function - * @returns {Promise} - * - * @author Sandro642 - * @Cheating Athena's Shield - * - * @Added whitelist for mods - * @Added support for the new HeliosLauncher version - */ - -import fs from 'fs' -import crypto from 'crypto' -import {DistributionIndexProcessor, FullRepair, MojangIndexProcessor} from 'helios-core/dl' - -/** - * @Reviewed on XX.XX.2024 expires on 01.01.2025 - * @Bugs discovereds: 0 - * @Athena's Shield - * @Sandro642 - */ - - -// ▄▄▄ ▄▄▄█████▓ ██░ ██ ▓█████ ███▄ █ ▄▄▄ ██████ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄ -// ▒████▄ ▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ██ ▀█ █ ▒████▄ ▒██ ▒ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌ -// ▒██ ▀█▄ ▒ ▓██░ ▒░▒██▀▀██░▒███ ▓██ ▀█ ██▒▒██ ▀█▄ ░ ▓██▄ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌ -// ░██▄▄▄▄██░ ▓██▓ ░ ░▓█ ░██ ▒▓█ ▄ ▓██▒ ▐▌██▒░██▄▄▄▄██ ▒ ██▒ ▒ ██▒░▓█ ░██ ░██░▒▓█ ▄ ▒██░ ░▓█▄ ▌ -// ▓█ ▓██▒ ▒██▒ ░ ░▓█▒░██▓░▒████▒▒██░ ▓██░ ▓█ ▓██▒▒██████▒▒ ▒██████▒▒░▓█▒░██▓░██░░▒████▒░██████▒░▒████▓ -// ▒▒ ▓▒█░ ▒ ░░ ▒ ░░▒░▒░░ ▒░ ░░ ▒░ ▒ ▒ ▒▒ ▓▒█░▒ ▒▓▒ ▒ ░ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░▓ ░░ ▒░ ░░ ▒░▓ ░ ▒▒▓ ▒ -// ▒ ▒▒ ░ ░ ▒ ░▒░ ░ ░ ░ ░░ ░░ ░ ▒░ ▒ ▒▒ ░░ ░▒ ░ ░ ░ ░▒ ░ ░ ▒ ░▒░ ░ ▒ ░ ░ ░ ░░ ░ ▒ ░ ░ ▒ ▒ -// ░ ▒ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ -// ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ -// ░ - -// Keep reference to Minecraft Process -let proc -// Is DiscordRPC enabled -let hasRPC = false -// Joined server regex -const GAME_JOINED_REGEX = /\[.+\]: Sound engine started/ -const GAME_LAUNCH_REGEX = /^\[.+\]: (?:MinecraftForge .+ Initialized|ModLauncher .+ starting: .+|Loading Minecraft .+ with Fabric Loader .+)$/ -const MIN_LINGER = 5000 - -// List of mods to exclude from validation -const EXCLUDED_MODS = [ -] - -async function dlAsync(login = true) { - const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite') - const loggerLanding = LoggerUtil.getLogger('Landing') - setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo')) - - let distro - - try { - distro = await DistroAPI.refreshDistributionOrFallback() - onDistroRefresh(distro) - } catch (err) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.unableToLoadDistributionIndex')) - showLaunchFailure(Lang.queryJS('landing.dlAsync.fatalError'), Lang.queryJS('landing.dlAsync.unableToLoadDistributionIndex')) - return - } - - const serv = distro.getServerById(ConfigManager.getSelectedServer()) - - if (login) { - if (ConfigManager.getSelectedAccount() == null) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.accountLoginNeeded')) - return - } - } - - // --------- Mod Verification Logic --------- - - if (athShield.status) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.usingAthShield')) - - const modsDir = path.join(ConfigManager.getDataDirectory(), 'instances', serv.rawServer.id, 'mods') - - // Check if mods directory exists, if not, create it - if (!fs.existsSync(modsDir)) { - fs.mkdirSync(modsDir, {recursive: true}) - } - - const distroMods = {} - const mdls = serv.modules - - // Populate expected mod identities and log them - mdls.forEach(mdl => { - if (mdl.rawModule.name.endsWith('.jar')) { - const modPath = path.join(modsDir, mdl.rawModule.name) - const modIdentity = mdl.rawModule.identity || mdl.rawModule.artifact.MD5 - if (athShield.debug) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.distributionIdentityError', { - 'moduleName': mdl.rawModule.name, - 'moduleIdentity': modIdentity - })) - } - - distroMods[modPath] = modIdentity - } - }) - - // Function to extract mod identity from the jar file - const extractModIdentity = (filePath) => { - if (athShield.debug) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': filePath})) - } - - // Fall back to a hash if no identity is found - const fileBuffer = fs.readFileSync(filePath) - const hashSum = crypto.createHash('md5') // Use MD5 to match the distribution configuration - hashSum.update(fileBuffer) - const hash = hashSum.digest('hex') - if (athShield.debug) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundUsingHash', { - 'filePath': filePath, - 'hash': hash - })) - } - - return hash - } - - // Validate mods function - const validateMods = () => { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.startingModValidation')) - const installedMods = fs.readdirSync(modsDir) - let valid = true - - for (let mod of installedMods) { - const modPath = path.join(modsDir, mod) - - // Skip validation for mods in the excluded list - if (EXCLUDED_MODS.includes(mod)) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modValidationBypassed', {'mod': mod})) - continue - } - - const expectedIdentity = distroMods[modPath] - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.validatingMod', {'mod': mod})) - - if (expectedIdentity) { - const modIdentity = extractModIdentity(modPath) - if (athShield.debug) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', { - 'expectedIdentity': expectedIdentity, - 'mod': mod, - 'modIdentity': modIdentity - })) - } - - if (modIdentity !== expectedIdentity) { - if (athShield.debug) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.AthShield.modIdentityMismatchError', { - 'mod': mod, - 'expectedIdentity': expectedIdentity, - 'modIdentity': modIdentity - })) - } - - valid = false - break - } - } else { - loggerLanding.warn(Lang.queryJS('landing.dlAsync.AthShield.expectedIdentityNotFound', {'mod': mod})) - valid = false - break - } - } - - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modValidationCompleted')) - return valid - } - - // Perform mod validation before proceeding - if (!validateMods()) { - const errorMessage = Lang.queryJS('landing.dlAsync.AthShield.invalidModsDetectedMessage', {'folder': ConfigManager.getNameDataPath()}) - loggerLanding.error(errorMessage) - showLaunchFailure(errorMessage, null) - return - } - - } else { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.notUsingAthShield')) - } - - // --------- End of Mod Verification Logic --------- - - setLaunchDetails(Lang.queryJS('landing.dlAsync.pleaseWait')) - toggleLaunchArea(true) - setLaunchPercentage(0, 100) - - const fullRepairModule = new FullRepair( - ConfigManager.getCommonDirectory(), - ConfigManager.getInstanceDirectory(), - ConfigManager.getLauncherDirectory(), - ConfigManager.getSelectedServer(), - DistroAPI.isDevMode() - ) - - fullRepairModule.spawnReceiver() - - fullRepairModule.childProcess.on('error', (err) => { - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.errorDuringLaunchText') + err) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), err.message || Lang.queryJS('landing.dlAsync.errorDuringLaunchText')) - }) - fullRepairModule.childProcess.on('close', (code, _signal) => { - if(code !== 0){ - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.fullRepairMode', {'code': code})) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) - } - }) - - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.validatingFileIntegrity')) - setLaunchDetails(Lang.queryJS('landing.dlAsync.validatingFileIntegrity')) - let invalidFileCount = 0 - try { - invalidFileCount = await fullRepairModule.verifyFiles(percent => { - setLaunchPercentage(percent) - }) - setLaunchPercentage(100) - } catch (err) { - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.errFileVerification')) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileVerificationTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) - return - } - - if(invalidFileCount > 0) { - loggerLaunchSuite.info('Downloading files.') - setLaunchDetails(Lang.queryJS('landing.dlAsync.downloadingFiles')) - setLaunchPercentage(0) - try { - await fullRepairModule.download(percent => { - setDownloadPercentage(percent) - }) - setDownloadPercentage(100) - } catch(err) { - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.errorDuringFileDownloadTitle')) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileDownloadTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) - return - } - } else { - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.AthShield.downloadingFiles')) - } - - // Remove download bar. - remote.getCurrentWindow().setProgressBar(-1) - - fullRepairModule.destroyReceiver() - - setLaunchDetails(Lang.queryJS('landing.dlAsync.preparingToLaunch')) - - const mojangIndexProcessor = new MojangIndexProcessor( - ConfigManager.getCommonDirectory(), - serv.rawServer.minecraftVersion) - const distributionIndexProcessor = new DistributionIndexProcessor( - ConfigManager.getCommonDirectory(), - distro, - serv.rawServer.id - ) - - const modLoaderData = await distributionIndexProcessor.loadModLoaderVersionJson(serv) - const versionData = await mojangIndexProcessor.getVersionJson() - - if(login) { - const authUser = ConfigManager.getSelectedAccount() - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.accountToProcessBuilder', {'userDisplayName': authUser.displayName})) - let pb = new ProcessBuilder(serv, versionData, modLoaderData, authUser, remote.app.getVersion()) - setLaunchDetails(Lang.queryJS('landing.dlAsync.launchingGame')) - - const SERVER_JOINED_REGEX = new RegExp(`\\[.+\\]: \\[CHAT\\] ${authUser.displayName} joined the game`) - - const onLoadComplete = () => { - toggleLaunchArea(false) - - proc.stdout.removeListener('data', tempListener) - proc.stderr.removeListener('data', gameErrorListener) - } - const start = Date.now() - - // Attach a temporary listener to the client output. - 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() - } - } - } - - const gameErrorListener = function(data){ - if(data.trim().toLowerCase().includes('error')){ - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.gameError', {'data': data})) - } - } - - proc = pb.build() - - proc.stdout.on('data', tempListener) - proc.stderr.on('data', gameErrorListener) - - proc.stdout.on('data', function(data){ - if(SERVER_JOINED_REGEX.test(data.trim())){ - DiscordWrapper.updateDetails('Exploring the World') - } else if(GAME_JOINED_REGEX.test(data.trim())) { - DiscordWrapper.updateDetails('Main Menu') - } - }) - - proc.on('close', (code, _signal) => { - if (hasRPC) { - DiscordWrapper.shutdownRPC() - hasRPC = false - } - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.gameExited', {'code': code})) - if(code !== 0){ - showLaunchFailure(Lang.queryJS('landing.dlAsync.gameExitedAbnormal'), Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) - } - proc = null - }) - - proc.on('error', (err) => { - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.gameErrorDuringLaunch', {'error': err})) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), err.message || Lang.queryJS('landing.dlAsync.errorDuringLaunchText')) - proc = null - }) - - setTimeout(() => { - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.waintingLaunchingGame')) - }, MIN_LINGER) - } -} \ No newline at end of file diff --git a/ajouts/version code final/landing.js b/ajouts/version code final/landing.js deleted file mode 100644 index 7637c9ab..00000000 --- a/ajouts/version code final/landing.js +++ /dev/null @@ -1,1163 +0,0 @@ -/** - * Script for landing.ejs - */ -// Requirements -const { URL } = require('url') -const { - MojangRestAPI, - getServerStatus -} = require('helios-core/mojang') -const { - RestResponseStatus, - isDisplayableError, - validateLocalFile -} = require('helios-core/common') -const { - FullRepair, - DistributionIndexProcessor, - MojangIndexProcessor, - downloadFile -} = require('helios-core/dl') -const { - validateSelectedJvm, - ensureJavaDirIsRoot, - javaExecFromRoot, - discoverBestJvmInstallation, - latestOpenJDK, - extractJdk -} = require('helios-core/java') - -// Internal Requirements -const DiscordWrapper = require('./assets/js/discordwrapper') -const ProcessBuilder = require('./assets/js/processbuilder') -const crypto = require('crypto') -const fs = require('fs') - -// Launch Elements -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') -const user_text = document.getElementById('user_text') - -const loggerLanding = LoggerUtil.getLogger('Landing') - -/* 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. - * - * @param {number} percent Percentage (0-100) - */ -function setLaunchPercentage(percent){ - launch_progress.setAttribute('max', 100) - launch_progress.setAttribute('value', percent) - launch_progress_label.innerHTML = percent + '%' -} - -/** - * Set the value of the OS progress bar and display that on the UI. - * - * @param {number} percent Percentage (0-100) - */ -function setDownloadPercentage(percent){ - remote.getCurrentWindow().setProgressBar(percent/100) - setLaunchPercentage(percent) -} - -/** - * Enable or disable the launch button. - * - * @param {boolean} val True to enable, false to disable. - */ -function setLaunchEnabled(val){ - document.getElementById('launch_button').disabled = !val -} - -// Bind launch button -document.getElementById('launch_button').addEventListener('click', async e => { - loggerLanding.info('Launching game..') - try { - const server = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer()) - const jExe = ConfigManager.getJavaExecutable(ConfigManager.getSelectedServer()) - if(jExe == null){ - await asyncSystemScan(server.effectiveJavaOptions) - } else { - - setLaunchDetails(Lang.queryJS('landing.launch.pleaseWait')) - toggleLaunchArea(true) - setLaunchPercentage(0, 100) - - const details = await validateSelectedJvm(ensureJavaDirIsRoot(jExe), server.effectiveJavaOptions.supported) - if(details != null){ - loggerLanding.info('Jvm Details', details) - await dlAsync() - - } else { - await asyncSystemScan(server.effectiveJavaOptions) - } - } - } catch(err) { - loggerLanding.error('Unhandled error in during launch process.', err) - showLaunchFailure(Lang.queryJS('landing.launch.failureTitle'), Lang.queryJS('landing.launch.failureText')) - } -}) - -// Bind settings button -document.getElementById('settingsMediaButton').onclick = async e => { - await prepareSettings() - switchView(getCurrentView(), VIEWS.settings) -} - -// Bind avatar overlay button. -document.getElementById('avatarOverlay').onclick = async e => { - await prepareSettings() - switchView(getCurrentView(), VIEWS.settings, 500, 500, () => { - settingsNavItemListener(document.getElementById('settingsNavAccount'), false) - }) -} - -// Bind selected account -function updateSelectedAccount(authUser){ - let username = Lang.queryJS('landing.selectedAccount.noAccountSelected') - if(authUser != null){ - if(authUser.displayName != null){ - username = authUser.displayName - } - if(authUser.uuid != null){ - document.getElementById('avatarContainer').style.backgroundImage = `url('https://mc-heads.net/body/${authUser.uuid}/right')` - } - } - user_text.innerHTML = username -} -updateSelectedAccount(ConfigManager.getSelectedAccount()) - -// Bind selected server -function updateSelectedServer(serv){ - if(getCurrentView() === VIEWS.settings){ - fullSettingsSave() - } - ConfigManager.setSelectedServer(serv != null ? serv.rawServer.id : null) - ConfigManager.save() - server_selection_button.innerHTML = '• ' + (serv != null ? serv.rawServer.name : Lang.queryJS('landing.noSelection')) - if(getCurrentView() === VIEWS.settings){ - animateSettingsTabRefresh() - } - setLaunchEnabled(serv != null) -} -// Real text is set in uibinder.js on distributionIndexDone. -server_selection_button.innerHTML = '• ' + Lang.queryJS('landing.selectedServer.loading') -server_selection_button.onclick = async e => { - e.target.blur() - await toggleServerSelection(true) -} - -// Update Mojang Status Color -const refreshMojangStatuses = async function(){ - loggerLanding.info('Refreshing Mojang Statuses..') - - let status = 'grey' - let tooltipEssentialHTML = '' - let tooltipNonEssentialHTML = '' - - 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 - - ${service.name} - ` - if(service.essential){ - tooltipEssentialHTML += tooltipHTML - } else { - tooltipNonEssentialHTML += tooltipHTML - } - - if(service.status === 'yellow' && status !== 'red'){ - status = 'yellow' - } else if(service.status === 'red'){ - status = 'red' - } else { - if(service.status === 'grey'){ - ++greyCount - } - ++greenCount - } - - } - - if(greenCount === statuses.length){ - if(greyCount === statuses.length){ - status = 'grey' - } else { - status = 'green' - } - } - - document.getElementById('mojangStatusEssentialContainer').innerHTML = tooltipEssentialHTML - document.getElementById('mojangStatusNonEssentialContainer').innerHTML = tooltipNonEssentialHTML - document.getElementById('mojang_status_icon').style.color = MojangRestAPI.statusToHex(status) -} - -const refreshServerStatus = async (fade = false) => { - loggerLanding.info('Refreshing Server Status') - const serv = (await DistroAPI.getDistribution()).getServerById(ConfigManager.getSelectedServer()) - - let pLabel = Lang.queryJS('landing.serverStatus.server') - let pVal = Lang.queryJS('landing.serverStatus.offline') - - try { - - const servStat = await getServerStatus(47, serv.hostname, serv.port) - console.log(servStat) - pLabel = Lang.queryJS('landing.serverStatus.players') - pVal = servStat.players.online + '/' + servStat.players.max - - } catch (err) { - loggerLanding.warn('Unable to refresh server status, assuming offline.') - loggerLanding.debug(err) - } - 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 - } - -} - -refreshMojangStatuses() -// Server Status is refreshed in uibinder.js on distributionIndexDone. - -// Refresh statuses every hour. The status page itself refreshes every day so... -let mojangStatusListener = setInterval(() => refreshMojangStatuses(true), 60*60*1000) -// Set refresh rate to once every 5 minutes. -let serverStatusListener = setInterval(() => refreshServerStatus(true), 300000) - -/** - * 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, - Lang.queryJS('landing.launch.okay') - ) - setOverlayHandler(null) - toggleOverlay(true) - toggleLaunchArea(false) -} - -/* System (Java) Scan */ - -/** - * Asynchronously scan the system for valid Java installations. - * - * @param {boolean} launchAfter Whether we should begin to launch after scanning. - */ -async function asyncSystemScan(effectiveJavaOptions, launchAfter = true){ - - setLaunchDetails(Lang.queryJS('landing.systemScan.checking')) - toggleLaunchArea(true) - setLaunchPercentage(0, 100) - - const jvmDetails = await discoverBestJvmInstallation( - ConfigManager.getDataDirectory(), - effectiveJavaOptions.supported - ) - - if(jvmDetails == null) { - // If the result is null, no valid Java installation was found. - // Show this information to the user. - setOverlayContent( - Lang.queryJS('landing.systemScan.noCompatibleJava'), - Lang.queryJS('landing.systemScan.installJavaMessage', { 'major': effectiveJavaOptions.suggestedMajor }), - Lang.queryJS('landing.systemScan.installJava'), - Lang.queryJS('landing.systemScan.installJavaManually') - ) - setOverlayHandler(() => { - setLaunchDetails(Lang.queryJS('landing.systemScan.javaDownloadPrepare')) - toggleOverlay(false) - - try { - downloadJava(effectiveJavaOptions, launchAfter) - } catch(err) { - loggerLanding.error('Unhandled error in Java Download', err) - showLaunchFailure(Lang.queryJS('landing.systemScan.javaDownloadFailureTitle'), Lang.queryJS('landing.systemScan.javaDownloadFailureText')) - } - }) - setDismissHandler(() => { - $('#overlayContent').fadeOut(250, () => { - //$('#overlayDismiss').toggle(false) - setOverlayContent( - Lang.queryJS('landing.systemScan.javaRequired', { 'major': effectiveJavaOptions.suggestedMajor }), - Lang.queryJS('landing.systemScan.javaRequiredMessage', { 'major': effectiveJavaOptions.suggestedMajor }), - Lang.queryJS('landing.systemScan.javaRequiredDismiss'), - Lang.queryJS('landing.systemScan.javaRequiredCancel') - ) - setOverlayHandler(() => { - toggleLaunchArea(false) - toggleOverlay(false) - }) - setDismissHandler(() => { - toggleOverlay(false, true) - - asyncSystemScan(effectiveJavaOptions, launchAfter) - }) - $('#overlayContent').fadeIn(250) - }) - }) - toggleOverlay(true, true) - } else { - // Java installation found, use this to launch the game. - const javaExec = javaExecFromRoot(jvmDetails.path) - ConfigManager.setJavaExecutable(ConfigManager.getSelectedServer(), javaExec) - ConfigManager.save() - - // We need to make sure that the updated value is on the settings UI. - // Just incase the settings UI is already open. - settingsJavaExecVal.value = javaExec - await populateJavaExecDetails(settingsJavaExecVal.value) - - // TODO Callback hell, refactor - // TODO Move this out, separate concerns. - if(launchAfter){ - await dlAsync() - } - } - -} - -async function downloadJava(effectiveJavaOptions, launchAfter = true) { - - // TODO Error handling. - // asset can be null. - const asset = await latestOpenJDK( - effectiveJavaOptions.suggestedMajor, - ConfigManager.getDataDirectory(), - effectiveJavaOptions.distribution) - - if(asset == null) { - throw new Error(Lang.queryJS('landing.downloadJava.findJdkFailure')) - } - - let received = 0 - await downloadFile(asset.url, asset.path, ({ transferred }) => { - received = transferred - setDownloadPercentage(Math.trunc((transferred/asset.size)*100)) - }) - setDownloadPercentage(100) - - if(received != asset.size) { - loggerLanding.warn(`Java Download: Expected ${asset.size} bytes but received ${received}`) - if(!await validateLocalFile(asset.path, asset.algo, asset.hash)) { - log.error(`Hashes do not match, ${asset.id} may be corrupted.`) - // Don't know how this could happen, but report it. - throw new Error(Lang.queryJS('landing.downloadJava.javaDownloadCorruptedError')) - } - } - - // Extract - // Show installing progress bar. - remote.getCurrentWindow().setProgressBar(2) - - // Wait for extration to complete. - const eLStr = Lang.queryJS('landing.downloadJava.extractingJava') - let dotStr = '' - setLaunchDetails(eLStr) - const extractListener = setInterval(() => { - if(dotStr.length >= 3){ - dotStr = '' - } else { - dotStr += '.' - } - setLaunchDetails(eLStr + dotStr) - }, 750) - - const newJavaExec = await extractJdk(asset.path) - - // Extraction complete, remove the loading from the OS progress bar. - remote.getCurrentWindow().setProgressBar(-1) - - // Extraction completed successfully. - ConfigManager.setJavaExecutable(ConfigManager.getSelectedServer(), newJavaExec) - ConfigManager.save() - - clearInterval(extractListener) - setLaunchDetails(Lang.queryJS('landing.downloadJava.javaInstalled')) - - // TODO Callback hell - // Refactor the launch functions - asyncSystemScan(effectiveJavaOptions, launchAfter) - -} - -//////////////////////////////////////////////////////////////////////////////////// - -/** - * @Name dlAsync Function - * @returns {Promise} - * - * @author Sandro642 - * @Cheating Athena's Shield - * - * @Added whitelist for mods - * @Added support for the new HeliosLauncher version - */ - -/** - * @Reviewed on XX.XX.2024 expires on 01.01.2025 - * @Bugs discovereds: 0 - * @Athena's Shield - * @Sandro642 - */ - - -// ▄▄▄ ▄▄▄█████▓ ██░ ██ ▓█████ ███▄ █ ▄▄▄ ██████ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄ -// ▒████▄ ▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ██ ▀█ █ ▒████▄ ▒██ ▒ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌ -// ▒██ ▀█▄ ▒ ▓██░ ▒░▒██▀▀██░▒███ ▓██ ▀█ ██▒▒██ ▀█▄ ░ ▓██▄ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌ -// ░██▄▄▄▄██░ ▓██▓ ░ ░▓█ ░██ ▒▓█ ▄ ▓██▒ ▐▌██▒░██▄▄▄▄██ ▒ ██▒ ▒ ██▒░▓█ ░██ ░██░▒▓█ ▄ ▒██░ ░▓█▄ ▌ -// ▓█ ▓██▒ ▒██▒ ░ ░▓█▒░██▓░▒████▒▒██░ ▓██░ ▓█ ▓██▒▒██████▒▒ ▒██████▒▒░▓█▒░██▓░██░░▒████▒░██████▒░▒████▓ -// ▒▒ ▓▒█░ ▒ ░░ ▒ ░░▒░▒░░ ▒░ ░░ ▒░ ▒ ▒ ▒▒ ▓▒█░▒ ▒▓▒ ▒ ░ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░▓ ░░ ▒░ ░░ ▒░▓ ░ ▒▒▓ ▒ -// ▒ ▒▒ ░ ░ ▒ ░▒░ ░ ░ ░ ░░ ░░ ░ ▒░ ▒ ▒▒ ░░ ░▒ ░ ░ ░ ░▒ ░ ░ ▒ ░▒░ ░ ▒ ░ ░ ░ ░░ ░ ▒ ░ ░ ▒ ▒ -// ░ ▒ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ -// ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ -// ░ - -// Keep reference to Minecraft Process -let proc -// Is DiscordRPC enabled -let hasRPC = false -// Joined server regex -const GAME_JOINED_REGEX = /\[.+\]: Sound engine started/ -const GAME_LAUNCH_REGEX = /^\[.+\]: (?:MinecraftForge .+ Initialized|ModLauncher .+ starting: .+|Loading Minecraft .+ with Fabric Loader .+)$/ -const MIN_LINGER = 5000 - -// List of mods to exclude from validation -const EXCLUDED_MODS = [ -] - -async function dlAsync(login = true) { - const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite') - const loggerLanding = LoggerUtil.getLogger('Landing') - setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo')) - - let distro - - try { - distro = await DistroAPI.refreshDistributionOrFallback() - onDistroRefresh(distro) - } catch (err) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.unableToLoadDistributionIndex')) - showLaunchFailure(Lang.queryJS('landing.dlAsync.fatalError'), Lang.queryJS('landing.dlAsync.unableToLoadDistributionIndex')) - return - } - - const serv = distro.getServerById(ConfigManager.getSelectedServer()) - - if (login) { - if (ConfigManager.getSelectedAccount() == null) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.accountLoginNeeded')) - return - } - } - - // --------- Mod Verification Logic --------- - - if (athShield.status) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.usingAthShield')) - - const modsDir = path.join(ConfigManager.getDataDirectory(), 'instances', serv.rawServer.id, 'mods') - - // Check if mods directory exists, if not, create it - if (!fs.existsSync(modsDir)) { - fs.mkdirSync(modsDir, {recursive: true}) - } - - const distroMods = {} - const mdls = serv.modules - - // Populate expected mod identities and log them - mdls.forEach(mdl => { - if (mdl.rawModule.name.endsWith('.jar')) { - const modPath = path.join(modsDir, mdl.rawModule.name) - const modIdentity = mdl.rawModule.identity || mdl.rawModule.artifact.MD5 - if (athShield.debug) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.distributionIdentityError', { - 'moduleName': mdl.rawModule.name, - 'moduleIdentity': modIdentity - })) - } - - distroMods[modPath] = modIdentity - } - }) - - // Function to extract mod identity from the jar file - const extractModIdentity = (filePath) => { - if (athShield.debug) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': filePath})) - } - - // Fall back to a hash if no identity is found - const fileBuffer = fs.readFileSync(filePath) - const hashSum = crypto.createHash('md5') // Use MD5 to match the distribution configuration - hashSum.update(fileBuffer) - const hash = hashSum.digest('hex') - if (athShield.debug) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundUsingHash', { - 'filePath': filePath, - 'hash': hash - })) - } - - return hash - } - - // Validate mods function - const validateMods = () => { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.startingModValidation')) - const installedMods = fs.readdirSync(modsDir) - let valid = true - - for (let mod of installedMods) { - const modPath = path.join(modsDir, mod) - - // Skip validation for mods in the excluded list - if (EXCLUDED_MODS.includes(mod)) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modValidationBypassed', {'mod': mod})) - continue - } - - const expectedIdentity = distroMods[modPath] - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.validatingMod', {'mod': mod})) - - if (expectedIdentity) { - const modIdentity = extractModIdentity(modPath) - if (athShield.debug) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', { - 'expectedIdentity': expectedIdentity, - 'mod': mod, - 'modIdentity': modIdentity - })) - } - - if (modIdentity !== expectedIdentity) { - if (athShield.debug) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.AthShield.modIdentityMismatchError', { - 'mod': mod, - 'expectedIdentity': expectedIdentity, - 'modIdentity': modIdentity - })) - } - - valid = false - break - } - } else { - loggerLanding.warn(Lang.queryJS('landing.dlAsync.AthShield.expectedIdentityNotFound', {'mod': mod})) - valid = false - break - } - } - - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modValidationCompleted')) - return valid - } - - // Perform mod validation before proceeding - if (!validateMods()) { - const errorMessage = Lang.queryJS('landing.dlAsync.AthShield.invalidModsDetectedMessage', {'folder': ConfigManager.getNameDataPath()}) - loggerLanding.error(errorMessage) - showLaunchFailure(errorMessage, null) - return - } - - } else { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.notUsingAthShield')) - } - - // --------- End of Mod Verification Logic --------- - - setLaunchDetails(Lang.queryJS('landing.dlAsync.pleaseWait')) - toggleLaunchArea(true) - setLaunchPercentage(0, 100) - - const fullRepairModule = new FullRepair( - ConfigManager.getCommonDirectory(), - ConfigManager.getInstanceDirectory(), - ConfigManager.getLauncherDirectory(), - ConfigManager.getSelectedServer(), - DistroAPI.isDevMode() - ) - - fullRepairModule.spawnReceiver() - - fullRepairModule.childProcess.on('error', (err) => { - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.errorDuringLaunchText') + err) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), err.message || Lang.queryJS('landing.dlAsync.errorDuringLaunchText')) - }) - fullRepairModule.childProcess.on('close', (code, _signal) => { - if(code !== 0){ - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.fullRepairMode', {'code': code})) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) - } - }) - - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.validatingFileIntegrity')) - setLaunchDetails(Lang.queryJS('landing.dlAsync.validatingFileIntegrity')) - let invalidFileCount = 0 - try { - invalidFileCount = await fullRepairModule.verifyFiles(percent => { - setLaunchPercentage(percent) - }) - setLaunchPercentage(100) - } catch (err) { - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.errFileVerification')) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileVerificationTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) - return - } - - if(invalidFileCount > 0) { - loggerLaunchSuite.info('Downloading files.') - setLaunchDetails(Lang.queryJS('landing.dlAsync.downloadingFiles')) - setLaunchPercentage(0) - try { - await fullRepairModule.download(percent => { - setDownloadPercentage(percent) - }) - setDownloadPercentage(100) - } catch(err) { - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.errorDuringFileDownloadTitle')) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileDownloadTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) - return - } - } else { - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.AthShield.downloadingFiles')) - } - - // Remove download bar. - remote.getCurrentWindow().setProgressBar(-1) - - fullRepairModule.destroyReceiver() - - setLaunchDetails(Lang.queryJS('landing.dlAsync.preparingToLaunch')) - - const mojangIndexProcessor = new MojangIndexProcessor( - ConfigManager.getCommonDirectory(), - serv.rawServer.minecraftVersion) - const distributionIndexProcessor = new DistributionIndexProcessor( - ConfigManager.getCommonDirectory(), - distro, - serv.rawServer.id - ) - - const modLoaderData = await distributionIndexProcessor.loadModLoaderVersionJson(serv) - const versionData = await mojangIndexProcessor.getVersionJson() - - if(login) { - const authUser = ConfigManager.getSelectedAccount() - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.accountToProcessBuilder', {'userDisplayName': authUser.displayName})) - let pb = new ProcessBuilder(serv, versionData, modLoaderData, authUser, remote.app.getVersion()) - setLaunchDetails(Lang.queryJS('landing.dlAsync.launchingGame')) - - const SERVER_JOINED_REGEX = new RegExp(`\\[.+\\]: \\[CHAT\\] ${authUser.displayName} joined the game`) - - const onLoadComplete = () => { - toggleLaunchArea(false) - - proc.stdout.removeListener('data', tempListener) - proc.stderr.removeListener('data', gameErrorListener) - } - const start = Date.now() - - // Attach a temporary listener to the client output. - 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() - } - } - } - - const gameErrorListener = function(data){ - if(data.trim().toLowerCase().includes('error')){ - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.gameError', {'data': data})) - } - } - - proc = pb.build() - - proc.stdout.on('data', tempListener) - proc.stderr.on('data', gameErrorListener) - - proc.stdout.on('data', function(data){ - if(SERVER_JOINED_REGEX.test(data.trim())){ - DiscordWrapper.updateDetails('Exploring the World') - } else if(GAME_JOINED_REGEX.test(data.trim())) { - DiscordWrapper.updateDetails('Main Menu') - } - }) - - proc.on('close', (code, _signal) => { - if (hasRPC) { - DiscordWrapper.shutdownRPC() - hasRPC = false - } - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.gameExited', {'code': code})) - if(code !== 0){ - showLaunchFailure(Lang.queryJS('landing.dlAsync.gameExitedAbnormal'), Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) - } - proc = null - }) - - proc.on('error', (err) => { - loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.gameErrorDuringLaunch', {'error': err})) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), err.message || Lang.queryJS('landing.dlAsync.errorDuringLaunchText')) - proc = null - }) - - setTimeout(() => { - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.waintingLaunchingGame')) - }, MIN_LINGER) - } -} - -//////////////////////////////////////////////////////////////////////////////////// - -/** - * News Loading Functions - */ - -// 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') - -// 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 = () => { - // Toggle tabbing. - if(newsActive){ - $('#landingContainer *').removeAttr('tabindex') - $('#newsContainer *').attr('tabindex', '-1') - } else { - $('#landingContainer *').attr('tabindex', '-1') - $('#newsContainer, #newsContainer *, #lower, #lower #center *').removeAttr('tabindex') - if(newsAlertShown){ - $('#newsButtonAlert').fadeOut(2000) - newsAlertShown = false - ConfigManager.setNewsCacheDismissed(true) - ConfigManager.save() - } - } - 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 = Lang.queryJS('landing.news.checking') - 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) - }) -} - -newsArticleContentScrollable.onscroll = (e) => { - if(e.target.scrollTop > Number.parseFloat($('.newsArticleSpacerTop').css('height'))){ - newsContent.setAttribute('scrolled', '') - } else { - newsContent.removeAttribute('scrolled') - } -} - -/** - * Reload the news without restarting. - * - * @returns {Promise.} A promise which resolves when the news - * content has finished loading and transitioning. - */ -function reloadNews(){ - return new Promise((resolve, reject) => { - $('#newsContent').fadeOut(250, () => { - $('#newsErrorLoading').fadeIn(250) - initNews().then(() => { - resolve() - }) - }) - }) -} - -let newsAlertShown = false - -/** - * Show the news alert indicating there is new news. - */ -function showNewsAlert(){ - newsAlertShown = true - $(newsButtonAlert).fadeIn(250) -} - -async function digestMessage(str) { - const msgUint8 = new TextEncoder().encode(str) - const hashBuffer = await crypto.subtle.digest('SHA-1', msgUint8) - const hashArray = Array.from(new Uint8Array(hashBuffer)) - const hashHex = hashArray - .map((b) => b.toString(16).padStart(2, '0')) - .join('') - return hashHex -} - -/** - * Initialize News UI. This will load the news and prepare - * the UI accordingly. - * - * @returns {Promise.} A promise which resolves when the news - * content has finished loading and transitioning. - */ -async function initNews(){ - - setNewsLoading(true) - - const news = await loadNews() - - newsArr = news?.articles || null - - if(newsArr == null){ - // News Loading Failed - setNewsLoading(false) - - await $('#newsErrorLoading').fadeOut(250).promise() - await $('#newsErrorFailed').fadeIn(250).promise() - - } else if(newsArr.length === 0) { - // No News Articles - setNewsLoading(false) - - ConfigManager.setNewsCache({ - date: null, - content: null, - dismissed: false - }) - ConfigManager.save() - - await $('#newsErrorLoading').fadeOut(250).promise() - await $('#newsErrorNone').fadeIn(250).promise() - } else { - // Success - setNewsLoading(false) - - const lN = newsArr[0] - const cached = ConfigManager.getNewsCache() - let newHash = await digestMessage(lN.content) - 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() - } - - 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) - } - - document.getElementById('newsNavigateRight').onclick = () => { switchHandler(true) } - document.getElementById('newsNavigateLeft').onclick = () => { switchHandler(false) } - await $('#newsErrorContainer').fadeOut(250).promise() - displayArticle(newsArr[0], 1) - await $('#newsContent').fadeIn(250).promise() - } - - -} - -/** - * 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() - } - } - } -}) - -/** - * 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 - newsArticleContentScrollable.innerHTML = '
' + articleObject.content + '
' - 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' - } - }) - newsNavigationStatus.innerHTML = Lang.query('ejs.landing.newsNavigationStatus', {currentPage: index, totalPages: newsArr.length}) - newsContent.setAttribute('article', index-1) -} - -/** - * Load news information from the RSS feed specified in the - * distribution index. - */ -async function loadNews(){ - - const distroData = await DistroAPI.getDistribution() - if(!distroData.rawDistribution.rss) { - loggerLanding.debug('No RSS feed provided.') - return null - } - - const promise = new Promise((resolve, reject) => { - - const newsFeed = distroData.rawDistribution.rss - const newsHost = new URL(newsFeed).origin + '/' - $.ajax({ - url: newsFeed, - success: (data) => { - const items = $(data).find('item') - const articles = [] - - for(let i=0; i { - resolve({ - articles: null - }) - }) - }) - - return await promise -}