From 151557182425cda849fe0f1fe28af83947d64a9e Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 16:51:58 +0200 Subject: [PATCH] Remove deprecated Athena Shield and HeliosLauncher code Deleted the dlAsync.js and landing.js files from both "ancien code" and "version code final" directories. This cleanup removes outdated functionality related to mod validation and launcher processes, streamlining the codebase. --- ajouts/ancien code HeliosLauncher/landing.js | 1030 ---------------- ajouts/ancien code athena shield/dlAsync.js | 305 ----- ajouts/version code final/dlAsync.js | 335 ----- ajouts/version code final/landing.js | 1163 ------------------ 4 files changed, 2833 deletions(-) delete mode 100644 ajouts/ancien code HeliosLauncher/landing.js delete mode 100644 ajouts/ancien code athena shield/dlAsync.js delete mode 100644 ajouts/version code final/dlAsync.js delete mode 100644 ajouts/version code final/landing.js 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 -}