From 3ee4c737905cbdf09354682b814b9dba167af063 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Wed, 23 Oct 2024 16:23:32 +0200 Subject: [PATCH 01/52] Add documentation for Athena's Shield feature --- Athena's Shield.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Athena's Shield.md diff --git a/Athena's Shield.md b/Athena's Shield.md new file mode 100644 index 00000000..e12ca984 --- /dev/null +++ b/Athena's Shield.md @@ -0,0 +1,44 @@ +# Documentation : Athena's Shield pour HeliosLauncher + +## Introduction + +**HeliosLauncher** est un lanceur de jeu pour **Minecraft** qui est développé par Daniel Scalzi pour garantir une expérience de jeu sécurisée et optimisée. Pour maintenir l'intégrité du jeu et empêcher l'introduction de mods non autorisés ou altérés, j'ai mis en place un nouveau système de sécurité appelé **Athena's Shield**. + +## Objectif d'Athena's Shield + +Le principal objectif d'Athena's Shield est d'assurer que seuls les mods autorisés et vérifiés sont utilisés avec HeliosLauncher. Ce système vérifie l'intégrité des mods installés pour empêcher toute modification ou ajout de mods malveillants ou non approuvés. + +## Fonctionnalités Clés + +1. **Validation des Mods Installés** : + - Avant de lancer le jeu, Athena's Shield vérifie les mods présents dans le dossier des mods de HeliosLauncher. + - Chaque mod est validé en comparant son nom et son hash (empreinte numérique) avec les données attendues dans la distribution. + - Si un mod ne correspond pas aux critères de validation, le lancement du jeu est arrêté, et un message d'erreur est affiché. + +2. **Vérification au Premier Lancement** : + - Lors du premier lancement de HeliosLauncher, si le dossier des mods est vide, le jeu est lancé sans vérification des mods. + - Si des mods sont détectés lors du premier lancement, leur validité est vérifiée immédiatement pour éviter tout problème. + +3. **Gestion des Modifications** : + - Athena's Shield vérifie également les changements dans les mods. Par exemple, si un mod est supprimé ou remplacé, ou si son nom est modifié, cela sera détecté. + - La vérification des hashs garantit que les mods n'ont pas été modifiés depuis leur téléchargement initial. + +4. **Message d'Erreur et Instructions** : + - En cas de détection de mods non valides ou de modifications non autorisées, le système affiche un message d'erreur clair. + - Les utilisateurs reçoivent des instructions spécifiques pour résoudre les problèmes, telles que supprimer le dossier de mods et redémarrer le lanceur. + +## Avantages pour les Utilisateurs + +- **Sécurité Renforcée** : En empêchant les mods non autorisés et en vérifiant leur intégrité, Athena's Shield protège les utilisateurs contre les mods malveillants. +- **Expérience de Jeu Fiable** : Assure que les mods utilisés sont ceux qui ont été testés et validés, garantissant une expérience de jeu stable et sans problèmes. +- **Simplicité d'Utilisation** : Les utilisateurs sont guidés avec des messages clairs et des instructions en cas de problème, facilitant la résolution des éventuels conflits. + +## Conclusion + +Athena's Shield est une étape importante pour améliorer la sécurité et l'intégrité de HeliosLauncher. En intégrant cette solution, je m'assure que chaque utilisateur de Minecraft profite d'une expérience de jeu sûre et fiable, sans compromis sur la qualité ou la sécurité. + +Pour toute question ou besoin de clarification supplémentaire sur Athena's Shield, n'hésitez pas à me contacter. + +Le seul moyen de passer Athena's Shield est d'avoir fait des études de cryptographie, la copie de signature, modification du Hash. + +> La création et la vérification d'Athena's Shield sont encore en cours d'acheminement vers leur point d'arrivée. \ No newline at end of file From 9813802b1c1529dd18881562d30bddaedcada0f2 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Wed, 23 Oct 2024 17:33:39 +0200 Subject: [PATCH 02/52] Add Athena's Shield configuration and CLI Introduce AthenaShield class to manage configuration, CLI for user setup, and update package.json with new script. This enhances the application's configurability and user interaction. --- athshield/athshield.js | 75 +++++++++++++++++++++++++++++++++++ athshield/parserAthShield.js | 32 +++++++++++++++ athshield/variables.athshield | 4 ++ package.json | 3 +- 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 athshield/athshield.js create mode 100644 athshield/parserAthShield.js create mode 100644 athshield/variables.athshield diff --git a/athshield/athshield.js b/athshield/athshield.js new file mode 100644 index 00000000..429fd900 --- /dev/null +++ b/athshield/athshield.js @@ -0,0 +1,75 @@ +const fs = require('fs') +const readline = require('readline') +const path = require('path') + +// Chemin vers le fichier de configuration +const configPath = path.join(__dirname, 'variables.athshield') + +// Charger les variables depuis le fichier +function loadConfig() { + const rawData = fs.readFileSync(configPath) + return JSON.parse(rawData.toString()) // Convertir Buffer en string +} + +// Sauvegarder les variables dans le fichier +function saveConfig(config) { + const data = JSON.stringify(config, null, 2) + fs.writeFileSync(configPath, data) +} + +// Création de l'interface readline +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}) + +// Fonction pour poser les questions à l'utilisateur +function startCLI() { + const config = loadConfig() + + rl.question('Voulez-vous activer Athena\'s Shield ? (oui/non) : ', (answer) => { + if (answer.trim().startsWith('//')) { + console.log('Ceci est un commentaire, la ligne est ignorée.') + rl.close() + return + } + + if (answer.toLowerCase() === 'oui') { + config.athenaShieldActivated = true + + rl.question('Voulez-vous cacher ou bloquer le menu ? (cacher/bloquer) : ', (menuAnswer) => { + if (menuAnswer.trim().startsWith('//')) { + console.log('Ceci est un commentaire, la ligne est ignorée.') + rl.close() + return + } + + if (menuAnswer.toLowerCase() === 'cacher' || menuAnswer.toLowerCase() === 'bloquer') { + config.menuVisibility = menuAnswer.toLowerCase() + console.log(`Athena's Shield activé. Menu ${config.menuVisibility === 'cacher' ? 'caché' : 'bloqué'}.`) + + // Sauvegarder la configuration modifiée + saveConfig(config) + rl.close() + } else { + console.log('Option non valide pour le menu.') + rl.close() + } + }) + } else if (answer.toLowerCase() === 'non') { + console.log('Athena\'s Shield non activé. Fermeture du CLI.') + config.athenaShieldActivated = false + config.menuVisibility = 'visible' // Remettre la valeur par défaut + + // Sauvegarder la configuration modifiée + saveConfig(config) + rl.close() + } else { + console.log('Réponse non valide.') + rl.close() + } + }) +} + +// Lancer le CLI +startCLI() diff --git a/athshield/parserAthShield.js b/athshield/parserAthShield.js new file mode 100644 index 00000000..8a166733 --- /dev/null +++ b/athshield/parserAthShield.js @@ -0,0 +1,32 @@ +const fs = require('fs') +const path = require('path') + +// Chemin vers le fichier de configuration +const configPath = path.join(__dirname, 'variables.athshield') + +// Classe pour gérer Athena's Shield +class AthenaShield { + constructor() { + this.config = this.loadConfig() + } + + // Charger les variables depuis le fichier + loadConfig() { + const rawData = fs.readFileSync(configPath) + return JSON.parse(rawData.toString()) + } + + // Récupérer le statut d'Athena's Shield + get status() { + return this.config.athenaShieldActivated + } + + // Récupérer la visibilité du menu + get view() { + return this.config.menuVisibility + } +} + +// Exporter une instance de la classe +const athenaShieldInstance = new AthenaShield() +module.exports = athenaShieldInstance diff --git a/athshield/variables.athshield b/athshield/variables.athshield new file mode 100644 index 00000000..19610b1f --- /dev/null +++ b/athshield/variables.athshield @@ -0,0 +1,4 @@ +{ + "athenaShieldActivated": false, + "menuVisibility": "visible" +} \ No newline at end of file diff --git a/package.json b/package.json index 6ce1fd61..79c080d4 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "dist:win": "npm run dist -- -w", "dist:mac": "npm run dist -- -m", "dist:linux": "npm run dist -- -l", - "lint": "eslint --config .eslintrc.json ." + "lint": "eslint --config .eslintrc.json .", + "athshield": "node ./athshield/athshield.js" }, "engines": { "node": "20.x.x" From 5f3e2293605309d836232b8127291fe698a27181 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Wed, 23 Oct 2024 19:17:23 +0200 Subject: [PATCH 03/52] Add new dlAsync function and mod validation messages Implemented a new `dlAsync.js` file and updated the `en_US.toml` with new mod validation messages. This includes the logic for mod verification and error handling, ensuring the integrity of mods before launching the game. Ajout prochainement de l'utilitaire npm run athshield --- ajouts/ancien code HeliosLauncher/landing.js | 1030 ++++++++++++++++ ajouts/ancien code athena shield/dlAsync.js | 305 +++++ ajouts/version code final/dlAsync.js | 306 +++++ ajouts/version code final/landing.js | 1139 ++++++++++++++++++ app/assets/js/configmanager.js | 9 + app/assets/js/scripts/landing.js | 4 + app/assets/lang/en_US.toml | 23 + 7 files changed, 2816 insertions(+) create mode 100644 ajouts/ancien code HeliosLauncher/landing.js create mode 100644 ajouts/ancien code athena shield/dlAsync.js create mode 100644 ajouts/version code final/dlAsync.js create mode 100644 ajouts/version code final/landing.js diff --git a/ajouts/ancien code HeliosLauncher/landing.js b/ajouts/ancien code HeliosLauncher/landing.js new file mode 100644 index 00000000..2df926a9 --- /dev/null +++ b/ajouts/ancien code HeliosLauncher/landing.js @@ -0,0 +1,1030 @@ +/** + * 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 new file mode 100644 index 00000000..7607d525 --- /dev/null +++ b/ajouts/ancien code athena shield/dlAsync.js @@ -0,0 +1,305 @@ +/** + * @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 new file mode 100644 index 00000000..283db1db --- /dev/null +++ b/ajouts/version code final/dlAsync.js @@ -0,0 +1,306 @@ +/** + * @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) { + 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 --------- + 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(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) => { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': 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(Lang.queryJS('landing.dlAsync.AthShield.manifestIdentityFound', {'filePath': filePath, 'identityLine': 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(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundInManifest', {'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) + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', {'expectedIdentity': expectedIdentity, 'mod': mod, 'modIdentity': modIdentity})) + + if (modIdentity !== expectedIdentity) { + 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': dataPath}) + 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(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.launchingGame')) + }, MIN_LINGER) + } +} \ No newline at end of file diff --git a/ajouts/version code final/landing.js b/ajouts/version code final/landing.js new file mode 100644 index 00000000..faea5e02 --- /dev/null +++ b/ajouts/version code final/landing.js @@ -0,0 +1,1139 @@ +/** + * 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 Lang = require('./app/assets/js/langloader') +const dataPath = require('./app/assets/configmanager') +const path = require('path') + +// 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 + * @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) { + 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 --------- + 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(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) => { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': 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(Lang.queryJS('landing.dlAsync.AthShield.manifestIdentityFound', {'filePath': filePath, 'identityLine': 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(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundInManifest', {'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) + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', {'expectedIdentity': expectedIdentity, 'mod': mod, 'modIdentity': modIdentity})) + + if (modIdentity !== expectedIdentity) { + 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': dataPath}) + 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(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.launchingGame')) + }, 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 +} diff --git a/app/assets/js/configmanager.js b/app/assets/js/configmanager.js index 38f864fe..ff1026bf 100644 --- a/app/assets/js/configmanager.js +++ b/app/assets/js/configmanager.js @@ -11,6 +11,15 @@ const dataPath = path.join(sysRoot, '.helioslauncher') const launcherDir = require('@electron/remote').app.getPath('userData') +/** + * The path to the data directory used by the application. + * This variable can be used to retrieve or set the location + * where the application's data files are stored. + * + * @type {string} + */ +exports.dataPath = dataPath + /** * Retrieve the absolute path of the launcher directory. * diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 1a1c1768..f1835240 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -435,6 +435,8 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { } +//////////////////////////////////////////////////////////////////////////////////// + // Keep reference to Minecraft Process let proc // Is DiscordRPC enabled @@ -636,6 +638,8 @@ async function dlAsync(login = true) { } +//////////////////////////////////////////////////////////////////////////////////// + /** * News Loading Functions */ diff --git a/app/assets/lang/en_US.toml b/app/assets/lang/en_US.toml index b9f64cfb..d6251fca 100644 --- a/app/assets/lang/en_US.toml +++ b/app/assets/lang/en_US.toml @@ -206,6 +206,29 @@ launchingGame = "Launching game.." launchWrapperNotDownloaded = "The main file, LaunchWrapper, failed to download properly. As a result, the game cannot launch.

To fix this issue, temporarily turn off your antivirus software and launch the game again.

If you have time, please submit an issue and let us know what antivirus software you use. We'll contact them and try to straighten things out." doneEnjoyServer = "Done. Enjoy the server!" checkConsoleForDetails = "Please check the console (CTRL + Shift + i) for more details." +accountLoginNeeded = "You must be logged into an account." +fullRepairMode = "Full Repair Module exited with code {code}, assuming error." +errFileVerification = "Error during file validation." +accountToProcessBuilder = "Sending selected account ({userDisplayName}) to ProcessBuilder." +gameError = "Game error: {data}" +gameExited = "Game process exited with code {code}." +gameErrorDuringLaunch = "Error during game launch {error}." +launchingGame = "Waiting for game window..." + +[js.landing.dlAsync.AthShield] +distributionIdentityError = "Expected Identity from Distribution for {moduleName}: {moduleIdentity}." +modIdentityExtraction = "Extracting identity for mod at: {filePath}." +manifestIdentityFound = "Found identity in manifest for {filePath}: {identityLine}" +identityNotFoundInManifest = "No identity found in manifest for {filePath}, using hash: {hash}" +startingModValidation = "Starting mod validation..." +modValidationBypassed = "Skipping validation for excluded mod: {mod}" +validatingMod = "Validating mod: {mod}" +expectedAndCalculatedIdentity = "Expected Identity: {expectedIdentity}, Calculated Identity for {mod}: {modIdentity}" +modIdentityMismatchError = "Mod identity mismatch! Mod: {mod}, Expected: {expectedIdentity}, Found: {modIdentity}" +expectedIdentityNotFound = "No expected identity found for mod: {mod}. Marking as invalid." +modValidationCompleted = "Mod validation completed." +invalidModsDetectedMessage = "Athena's Shield has detected invalid mods. Please delete the {folder} folder and restart the launcher." +downloadingFiles = "No invalid files, skipping download." [js.landing.news] checking = "Checking for News" From 3790b52d0d67934586bd7bdd299aa54c9d1eca15 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 16:05:21 +0200 Subject: [PATCH 04/52] Update landing.js dependencies and add new features Replaced 'path' with 'fs' module and removed unused 'Lang'. Added support for the new HeliosLauncher version and implemented a whitelist for mods. Fixed a typo in the logging message. --- ajouts/version code final/landing.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ajouts/version code final/landing.js b/ajouts/version code final/landing.js index faea5e02..9ddc8447 100644 --- a/ajouts/version code final/landing.js +++ b/ajouts/version code final/landing.js @@ -30,9 +30,8 @@ const { // Internal Requirements const DiscordWrapper = require('./assets/js/discordwrapper') const ProcessBuilder = require('./assets/js/processbuilder') -const Lang = require('./app/assets/js/langloader') const dataPath = require('./app/assets/configmanager') -const path = require('path') +const fs = require('fs') // Launch Elements const launch_content = document.getElementById('launch_content') @@ -442,22 +441,23 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { /** * @Name dlAsync Function - * @async - * @param {boolean} login * @returns {Promise} * * @author Sandro642 * @Cheating Athena's Shield - * @Ajout Liste blanche des mods + * + * @Added whitelist for mods + * @Added support for the new HeliosLauncher version */ /** - * @Révision le XX.XX.2024 périme le 01.01.2025 - * @Bug découvert : 0 + * @Reviewed on XX.XX.2024 expires on 01.01.2025 + * @Bugs discovered: 0 * @Athena's Shield * @Sandro642 */ + // ▄▄▄ ▄▄▄█████▓ ██░ ██ ▓█████ ███▄ █ ▄▄▄ ██████ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄ // ▒████▄ ▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ██ ▀█ █ ▒████▄ ▒██ ▒ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌ // ▒██ ▀█▄ ▒ ▓██░ ▒░▒██▀▀██░▒███ ▓██ ▀█ ██▒▒██ ▀█▄ ░ ▓██▄ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌ @@ -742,7 +742,7 @@ async function dlAsync(login = true) { }) setTimeout(() => { - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.launchingGame')) + loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.waintingLaunchingGame')) }, MIN_LINGER) } } From 85cb73363aa1e78b31fc7cb3a1e26c33ed451233 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 16:08:01 +0200 Subject: [PATCH 05/52] Fix typo in game launch message Corrected the typo from "launchingGame" to "waintingLaunchingGame" in the en_US language file. This ensures the message displayed to users is accurate and free of errors. --- app/assets/lang/en_US.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/lang/en_US.toml b/app/assets/lang/en_US.toml index d6251fca..db4e9c71 100644 --- a/app/assets/lang/en_US.toml +++ b/app/assets/lang/en_US.toml @@ -213,7 +213,7 @@ accountToProcessBuilder = "Sending selected account ({userDisplayName}) to Proce gameError = "Game error: {data}" gameExited = "Game process exited with code {code}." gameErrorDuringLaunch = "Error during game launch {error}." -launchingGame = "Waiting for game window..." +waintingLaunchingGame = "Waiting for game window..." [js.landing.dlAsync.AthShield] distributionIdentityError = "Expected Identity from Distribution for {moduleName}: {moduleIdentity}." From 7073c2744b63b347b1fbb3f7a5a2703a55f0af26 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 16:08:15 +0200 Subject: [PATCH 06/52] Add mod whitelist and new HeliosLauncher support Implemented a whitelist for mods and added support for the new version of HeliosLauncher. Also corrected a language key for launch information logging. --- ajouts/version code final/dlAsync.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ajouts/version code final/dlAsync.js b/ajouts/version code final/dlAsync.js index 283db1db..ae2a2cfb 100644 --- a/ajouts/version code final/dlAsync.js +++ b/ajouts/version code final/dlAsync.js @@ -1,21 +1,22 @@ /** * @Name dlAsync Function - * @async - * @param {boolean} login * @returns {Promise} * * @author Sandro642 * @Cheating Athena's Shield - * @Ajout Liste blanche des mods + * + * @Added whitelist for mods + * @Added support for the new HeliosLauncher version */ /** - * @Révision le XX.XX.2024 périme le 01.01.2025 - * @Bug découvert : 0 + * @Reviewed on XX.XX.2024 expires on 01.01.2025 + * @Bugs discovered: 0 * @Athena's Shield * @Sandro642 */ + // ▄▄▄ ▄▄▄█████▓ ██░ ██ ▓█████ ███▄ █ ▄▄▄ ██████ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄ // ▒████▄ ▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ██ ▀█ █ ▒████▄ ▒██ ▒ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌ // ▒██ ▀█▄ ▒ ▓██░ ▒░▒██▀▀██░▒███ ▓██ ▀█ ██▒▒██ ▀█▄ ░ ▓██▄ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌ @@ -300,7 +301,7 @@ async function dlAsync(login = true) { }) setTimeout(() => { - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.launchingGame')) + loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.waintingLaunchingGame')) }, MIN_LINGER) } } \ No newline at end of file From 886b29e3565ddcf2ed1e73016b24685eff3d5ccf Mon Sep 17 00:00:00 2001 From: Sandro Soria Date: Thu, 24 Oct 2024 16:10:14 +0200 Subject: [PATCH 07/52] Use AthShield for mod verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented mod verification using AthShield when enabled. Added detailed mod identity extraction and validation logic for better integrity checks. Added logs for each verification step and fallback to hash-based identity if the manifest is missing. Grande ligne : quand tu actives ath shield alors il utilise le système Athena's Shield. --- ajouts/version code final/dlAsync.js | 166 ++++++++++++++++----------- 1 file changed, 96 insertions(+), 70 deletions(-) diff --git a/ajouts/version code final/dlAsync.js b/ajouts/version code final/dlAsync.js index 283db1db..bec57824 100644 --- a/ajouts/version code final/dlAsync.js +++ b/ajouts/version code final/dlAsync.js @@ -66,96 +66,122 @@ async function dlAsync(login = true) { } // --------- 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 }) - } + if (athShield.status) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.usingAthShield')) - const distroMods = {} - const mdls = serv.modules + const modsDir = path.join(ConfigManager.getDataDirectory(), 'instances', serv.rawServer.id, 'mods') - // 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(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) => { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': 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(Lang.queryJS('landing.dlAsync.AthShield.manifestIdentityFound', {'filePath': filePath, 'identityLine': identityLine})) - return identityLine.split(':')[1].trim() - } + // Check if mods directory exists, if not, create it + if (!fs.existsSync(modsDir)) { + fs.mkdirSync(modsDir, {recursive: true}) } - // 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(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundInManifest', {'filePath': filePath, 'hash': hash})) - return hash - } + const distroMods = {} + const mdls = serv.modules - // Validate mods function - const validateMods = () => { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.startingModValidation')) - const installedMods = fs.readdirSync(modsDir) - let valid = true + // 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(Lang.queryJS('landing.dlAsync.AthShield.distributionIdentityError', { + 'moduleName': mdl.rawModule.name, + 'moduleIdentity': modIdentity + })) + distroMods[modPath] = modIdentity + } + }) - for (let mod of installedMods) { - const modPath = path.join(modsDir, mod) + // Function to extract mod identity from the jar file + const extractModIdentity = (filePath) => { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': filePath})) + const zip = new AdmZip(filePath) + const manifestEntry = zip.getEntry('META-INF/MANIFEST.MF') - // Skip validation for mods in the excluded list - if (EXCLUDED_MODS.includes(mod)) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modValidationBypassed', {'mod': mod})) - continue + 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(Lang.queryJS('landing.dlAsync.AthShield.manifestIdentityFound', { + 'filePath': filePath, + 'identityLine': identityLine + })) + return identityLine.split(':')[1].trim() + } } - const expectedIdentity = distroMods[modPath] - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.validatingMod', {'mod': mod})) + // 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(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundInManifest', { + 'filePath': filePath, + 'hash': hash + })) + return hash + } - if (expectedIdentity) { - const modIdentity = extractModIdentity(modPath) - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', {'expectedIdentity': expectedIdentity, 'mod': mod, 'modIdentity': modIdentity})) + // Validate mods function + const validateMods = () => { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.startingModValidation')) + const installedMods = fs.readdirSync(modsDir) + let valid = true - if (modIdentity !== expectedIdentity) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.AthShield.modIdentityMismatchError', {'mod': mod, 'expectedIdentity': expectedIdentity, 'modIdentity': modIdentity})) + 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) + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', { + 'expectedIdentity': expectedIdentity, + 'mod': mod, + 'modIdentity': modIdentity + })) + + if (modIdentity !== expectedIdentity) { + 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 } - } else { - loggerLanding.warn(Lang.queryJS('landing.dlAsync.AthShield.expectedIdentityNotFound', {'mod': mod})) - valid = false - break } + + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modValidationCompleted')) + return valid } - 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': dataPath}) + loggerLanding.error(errorMessage) + showLaunchFailure(errorMessage, null) + return + } + + } else { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.notUsingAthShield')) } - // Perform mod validation before proceeding - if (!validateMods()) { - const errorMessage = Lang.queryJS('landing.dlAsync.AthShield.invalidModsDetectedMessage', {'folder': dataPath}) - loggerLanding.error(errorMessage) - showLaunchFailure(errorMessage, null) - return - } // --------- End of Mod Verification Logic --------- setLaunchDetails(Lang.queryJS('landing.dlAsync.pleaseWait')) From 2fc9c3ec3550cd7f72807b42e25e5a8ab72f1b28 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 16:27:37 +0200 Subject: [PATCH 08/52] Add mod verification logic using Athena's Shield Integrated Athena's Shield for mod verification, including mod identity extraction and validation against expected identities. Added exclusion list for mods, and implemented fallbacks for missing mod identities using MD5 hashes. Adjusted logging and error reporting to provide clearer feedback. --- ajouts/version code final/landing.js | 171 ++++++++------- app/assets/js/scripts/landing.js | 306 +++++++++++++++++++-------- 2 files changed, 320 insertions(+), 157 deletions(-) diff --git a/ajouts/version code final/landing.js b/ajouts/version code final/landing.js index 9ddc8447..058d1bd8 100644 --- a/ajouts/version code final/landing.js +++ b/ajouts/version code final/landing.js @@ -30,8 +30,9 @@ const { // Internal Requirements const DiscordWrapper = require('./assets/js/discordwrapper') const ProcessBuilder = require('./assets/js/processbuilder') -const dataPath = require('./app/assets/configmanager') -const fs = require('fs') +const dataPath = require('./assets/js/configmanager') +const athShield = require('./assets/athshield/parserAthShield') +const fs = require('fs') // Launch Elements const launch_content = document.getElementById('launch_content') @@ -508,96 +509,122 @@ async function dlAsync(login = true) { } // --------- 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 }) - } + if (athShield.status) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.usingAthShield')) - const distroMods = {} - const mdls = serv.modules + const modsDir = path.join(ConfigManager.getDataDirectory(), 'instances', serv.rawServer.id, 'mods') - // 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(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) => { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': 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(Lang.queryJS('landing.dlAsync.AthShield.manifestIdentityFound', {'filePath': filePath, 'identityLine': identityLine})) - return identityLine.split(':')[1].trim() - } + // Check if mods directory exists, if not, create it + if (!fs.existsSync(modsDir)) { + fs.mkdirSync(modsDir, {recursive: true}) } - // 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(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundInManifest', {'filePath': filePath, 'hash': hash})) - return hash - } + const distroMods = {} + const mdls = serv.modules - // Validate mods function - const validateMods = () => { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.startingModValidation')) - const installedMods = fs.readdirSync(modsDir) - let valid = true + // 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(Lang.queryJS('landing.dlAsync.AthShield.distributionIdentityError', { + 'moduleName': mdl.rawModule.name, + 'moduleIdentity': modIdentity + })) + distroMods[modPath] = modIdentity + } + }) - for (let mod of installedMods) { - const modPath = path.join(modsDir, mod) + // Function to extract mod identity from the jar file + const extractModIdentity = (filePath) => { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': filePath})) + const zip = new AdmZip(filePath) + const manifestEntry = zip.getEntry('META-INF/MANIFEST.MF') - // Skip validation for mods in the excluded list - if (EXCLUDED_MODS.includes(mod)) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modValidationBypassed', {'mod': mod})) - continue + 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(Lang.queryJS('landing.dlAsync.AthShield.manifestIdentityFound', { + 'filePath': filePath, + 'identityLine': identityLine + })) + return identityLine.split(':')[1].trim() + } } - const expectedIdentity = distroMods[modPath] - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.validatingMod', {'mod': mod})) + // 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(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundInManifest', { + 'filePath': filePath, + 'hash': hash + })) + return hash + } - if (expectedIdentity) { - const modIdentity = extractModIdentity(modPath) - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', {'expectedIdentity': expectedIdentity, 'mod': mod, 'modIdentity': modIdentity})) + // Validate mods function + const validateMods = () => { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.startingModValidation')) + const installedMods = fs.readdirSync(modsDir) + let valid = true - if (modIdentity !== expectedIdentity) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.AthShield.modIdentityMismatchError', {'mod': mod, 'expectedIdentity': expectedIdentity, 'modIdentity': modIdentity})) + 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) + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', { + 'expectedIdentity': expectedIdentity, + 'mod': mod, + 'modIdentity': modIdentity + })) + + if (modIdentity !== expectedIdentity) { + 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 } - } else { - loggerLanding.warn(Lang.queryJS('landing.dlAsync.AthShield.expectedIdentityNotFound', {'mod': mod})) - valid = false - break } + + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modValidationCompleted')) + return valid } - 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': dataPath}) + loggerLanding.error(errorMessage) + showLaunchFailure(errorMessage, null) + return + } + + } else { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.notUsingAthShield')) } - // Perform mod validation before proceeding - if (!validateMods()) { - const errorMessage = Lang.queryJS('landing.dlAsync.AthShield.invalidModsDetectedMessage', {'folder': dataPath}) - loggerLanding.error(errorMessage) - showLaunchFailure(errorMessage, null) - return - } // --------- End of Mod Verification Logic --------- setLaunchDetails(Lang.queryJS('landing.dlAsync.pleaseWait')) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index f1835240..058d1bd8 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -30,6 +30,9 @@ const { // Internal Requirements const DiscordWrapper = require('./assets/js/discordwrapper') const ProcessBuilder = require('./assets/js/processbuilder') +const dataPath = require('./assets/js/configmanager') +const athShield = require('./assets/athshield/parserAthShield') +const fs = require('fs') // Launch Elements const launch_content = document.getElementById('launch_content') @@ -46,7 +49,7 @@ const loggerLanding = LoggerUtil.getLogger('Landing') /** * Show/hide the loading area. - * + * * @param {boolean} loading True if the loading area should be shown, otherwise false. */ function toggleLaunchArea(loading){ @@ -61,7 +64,7 @@ function toggleLaunchArea(loading){ /** * Set the details text of the loading area. - * + * * @param {string} details The new text for the loading details. */ function setLaunchDetails(details){ @@ -70,7 +73,7 @@ function setLaunchDetails(details){ /** * Set the value of the loading progress bar and display that value. - * + * * @param {number} percent Percentage (0-100) */ function setLaunchPercentage(percent){ @@ -81,7 +84,7 @@ function setLaunchPercentage(percent){ /** * Set the value of the OS progress bar and display that on the UI. - * + * * @param {number} percent Percentage (0-100) */ function setDownloadPercentage(percent){ @@ -91,7 +94,7 @@ function setDownloadPercentage(percent){ /** * Enable or disable the launch button. - * + * * @param {boolean} val True to enable, false to disable. */ function setLaunchEnabled(val){ @@ -192,7 +195,7 @@ const refreshMojangStatuses = async function(){ loggerLanding.warn('Unable to refresh Mojang service status.') statuses = MojangRestAPI.getDefaultStatuses() } - + greenCount = 0 greyCount = 0 @@ -229,7 +232,7 @@ const refreshMojangStatuses = async function(){ status = 'green' } } - + document.getElementById('mojangStatusEssentialContainer').innerHTML = tooltipEssentialHTML document.getElementById('mojangStatusNonEssentialContainer').innerHTML = tooltipNonEssentialHTML document.getElementById('mojang_status_icon').style.color = MojangRestAPI.statusToHex(status) @@ -263,7 +266,7 @@ const refreshServerStatus = async (fade = false) => { document.getElementById('landingPlayerLabel').innerHTML = pLabel document.getElementById('player_count').innerHTML = pVal } - + } refreshMojangStatuses() @@ -276,7 +279,7 @@ 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. */ @@ -295,8 +298,8 @@ function showLaunchFailure(title, desc){ /** * Asynchronously scan the system for valid Java installations. - * - * @param {boolean} launchAfter Whether we should begin to launch after scanning. + * + * @param {boolean} launchAfter Whether we should begin to launch after scanning. */ async function asyncSystemScan(effectiveJavaOptions, launchAfter = true){ @@ -321,7 +324,7 @@ async function asyncSystemScan(effectiveJavaOptions, launchAfter = true){ setOverlayHandler(() => { setLaunchDetails(Lang.queryJS('landing.systemScan.javaDownloadPrepare')) toggleOverlay(false) - + try { downloadJava(effectiveJavaOptions, launchAfter) } catch(err) { @@ -437,23 +440,52 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { //////////////////////////////////////////////////////////////////////////////////// +/** + * @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 discovered: 0 + * @Athena's Shield + * @Sandro642 + */ + + +// ▄▄▄ ▄▄▄█████▓ ██░ ██ ▓█████ ███▄ █ ▄▄▄ ██████ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄ +// ▒████▄ ▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ██ ▀█ █ ▒████▄ ▒██ ▒ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌ +// ▒██ ▀█▄ ▒ ▓██░ ▒░▒██▀▀██░▒███ ▓██ ▀█ ██▒▒██ ▀█▄ ░ ▓██▄ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌ +// ░██▄▄▄▄██░ ▓██▓ ░ ░▓█ ░██ ▒▓█ ▄ ▓██▒ ▐▌██▒░██▄▄▄▄██ ▒ ██▒ ▒ ██▒░▓█ ░██ ░██░▒▓█ ▄ ▒██░ ░▓█▄ ▌ +// ▓█ ▓██▒ ▒██▒ ░ ░▓█▒░██▓░▒████▒▒██░ ▓██░ ▓█ ▓██▒▒██████▒▒ ▒██████▒▒░▓█▒░██▓░██░░▒████▒░██████▒░▒████▓ +// ▒▒ ▓▒█░ ▒ ░░ ▒ ░░▒░▒░░ ▒░ ░░ ▒░ ▒ ▒ ▒▒ ▓▒█░▒ ▒▓▒ ▒ ░ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░▓ ░░ ▒░ ░░ ▒░▓ ░ ▒▒▓ ▒ +// ▒ ▒▒ ░ ░ ▒ ░▒░ ░ ░ ░ ░░ ░░ ░ ▒░ ▒ ▒▒ ░░ ░▒ ░ ░ ░ ░▒ ░ ░ ▒ ░▒░ ░ ▒ ░ ░ ░ ░░ ░ ▒ ░ ░ ▒ ▒ +// ░ ▒ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ +// ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ +// ░ + // 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 +// List of mods to exclude from validation +const EXCLUDED_MODS = [ +] + 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') - + const loggerLanding = LoggerUtil.getLogger('Landing') setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo')) let distro @@ -461,21 +493,140 @@ async function dlAsync(login = true) { try { distro = await DistroAPI.refreshDistributionOrFallback() onDistroRefresh(distro) - } catch(err) { - loggerLaunchSuite.error('Unable to refresh distribution index.', err) + } 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('You must be logged into an account.') + 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.MD5 + 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) => { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': 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(Lang.queryJS('landing.dlAsync.AthShield.manifestIdentityFound', { + 'filePath': filePath, + 'identityLine': 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(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundInManifest', { + '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) + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', { + 'expectedIdentity': expectedIdentity, + 'mod': mod, + 'modIdentity': modIdentity + })) + + if (modIdentity !== expectedIdentity) { + 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': dataPath}) + 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) @@ -491,17 +642,17 @@ async function dlAsync(login = true) { fullRepairModule.spawnReceiver() fullRepairModule.childProcess.on('error', (err) => { - loggerLaunchSuite.error('Error during launch', 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(`Full Repair Module exited with code ${code}, assuming error.`) + loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.fullRepairMode', {'code': code})) showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) } }) - loggerLaunchSuite.info('Validating files.') + loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.validatingFileIntegrity')) setLaunchDetails(Lang.queryJS('landing.dlAsync.validatingFileIntegrity')) let invalidFileCount = 0 try { @@ -510,11 +661,10 @@ async function dlAsync(login = true) { }) setLaunchPercentage(100) } catch (err) { - loggerLaunchSuite.error('Error during file validation.') + 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.') @@ -526,12 +676,12 @@ async function dlAsync(login = true) { }) setDownloadPercentage(100) } catch(err) { - loggerLaunchSuite.error('Error during file download.') + loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.errorDuringFileDownloadTitle')) showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileDownloadTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails')) return } } else { - loggerLaunchSuite.info('No invalid files, skipping download.') + loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.AthShield.downloadingFiles')) } // Remove download bar. @@ -555,28 +705,21 @@ async function dlAsync(login = true) { if(login) { const authUser = ConfigManager.getSelectedAccount() - loggerLaunchSuite.info(`Sending selected account (${authUser.displayName}) to ProcessBuilder.`) + 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 = /\[.+\]: \[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 @@ -588,54 +731,47 @@ async function dlAsync(login = true) { } } - // 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')) + if(data.trim().toLowerCase().includes('error')){ + loggerLaunchSuite.error(Lang.queryJS('landing.dlAsync.gameError', {'data': data})) } } - try { - // Build Minecraft process. - proc = pb.build() + proc = pb.build() - // Bind listeners to stdout. - proc.stdout.on('data', tempListener) - proc.stderr.on('data', gameErrorListener) + 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 - }) + 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') } + }) - } catch(err) { + 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 + }) - loggerLaunchSuite.error('Error during launch', err) - showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.checkConsoleForDetails')) + 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) } - } //////////////////////////////////////////////////////////////////////////////////// @@ -660,8 +796,8 @@ let newsGlideCount = 0 /** * Show the news UI via a slide animation. - * - * @param {boolean} up True to slide up, otherwise false. + * + * @param {boolean} up True to slide up, otherwise false. */ function slide_(up){ const lCUpper = document.querySelector('#landingContainer > #upper') @@ -735,7 +871,7 @@ let newsLoadingListener = null /** * Set the news loading animation. - * + * * @param {boolean} val True to set loading animation, otherwise false. */ function setNewsLoading(val){ @@ -777,7 +913,7 @@ newsArticleContentScrollable.onscroll = (e) => { /** * Reload the news without restarting. - * + * * @returns {Promise.} A promise which resolves when the news * content has finished loading and transitioning. */ @@ -815,7 +951,7 @@ async function digestMessage(str) { /** * 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. */ @@ -894,7 +1030,7 @@ async function initNews(){ 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) } @@ -934,7 +1070,7 @@ document.addEventListener('keydown', (e) => { /** * Display a news article on the UI. - * + * * @param {Object} articleObject The article meta object. * @param {number} index The article index. */ @@ -969,7 +1105,7 @@ async function loadNews(){ } const promise = new Promise((resolve, reject) => { - + const newsFeed = distroData.rawDistribution.rss const newsHost = new URL(newsFeed).origin + '/' $.ajax({ @@ -979,7 +1115,7 @@ async function loadNews(){ const articles = [] for(let i=0; i Date: Thu, 24 Oct 2024 16:27:56 +0200 Subject: [PATCH 09/52] Refactor project structure by moving athshield files Relocated athshield files to app/assets/athshield for better organization and maintainability. This change improves the project's folder structure and simplifies file management. --- {athshield => app/assets/athshield}/athshield.js | 0 {athshield => app/assets/athshield}/parserAthShield.js | 0 {athshield => app/assets/athshield}/variables.athshield | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {athshield => app/assets/athshield}/athshield.js (100%) rename {athshield => app/assets/athshield}/parserAthShield.js (100%) rename {athshield => app/assets/athshield}/variables.athshield (100%) diff --git a/athshield/athshield.js b/app/assets/athshield/athshield.js similarity index 100% rename from athshield/athshield.js rename to app/assets/athshield/athshield.js diff --git a/athshield/parserAthShield.js b/app/assets/athshield/parserAthShield.js similarity index 100% rename from athshield/parserAthShield.js rename to app/assets/athshield/parserAthShield.js diff --git a/athshield/variables.athshield b/app/assets/athshield/variables.athshield similarity index 100% rename from athshield/variables.athshield rename to app/assets/athshield/variables.athshield From e3c6a185ed734df2c73a133e89ba0850c61e440d Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 16:28:05 +0200 Subject: [PATCH 10/52] Add Athena's Shield activation messages Include messages for when Athena's Shield is activated and deactivated in the English localization file. This will provide users with clear notifications on the status of Athena's Shield. --- app/assets/lang/en_US.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/lang/en_US.toml b/app/assets/lang/en_US.toml index db4e9c71..33a618c9 100644 --- a/app/assets/lang/en_US.toml +++ b/app/assets/lang/en_US.toml @@ -229,6 +229,8 @@ expectedIdentityNotFound = "No expected identity found for mod: {mod}. Marking a modValidationCompleted = "Mod validation completed." invalidModsDetectedMessage = "Athena's Shield has detected invalid mods. Please delete the {folder} folder and restart the launcher." downloadingFiles = "No invalid files, skipping download." +usingAthShield = "Athena's Shield activated, Resource usage..." +notUsingAthShield = "Athena's Shield deactivated, Not using resources..." [js.landing.news] checking = "Checking for News" From ae9e7b54b0bcc99da89eaf0a361068f775aed2d0 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 16:28:14 +0200 Subject: [PATCH 11/52] Update athshield script path in package.json Relocated the athshield.js script from ./athshield/ to app/assets/athshield/. This change ensures better organization of script files within the project structure. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 79c080d4..d5271cf1 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "dist:mac": "npm run dist -- -m", "dist:linux": "npm run dist -- -l", "lint": "eslint --config .eslintrc.json .", - "athshield": "node ./athshield/athshield.js" + "athshield": "node app/assets/athshield/athshield.js" }, "engines": { "node": "20.x.x" From fc5a2d4b8d04cd29e52cbeb8da77602229ac268f Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 18:00:00 +0200 Subject: [PATCH 12/52] Rename view method to type and integrate athShield in settings Renamed the `view` method to `type` in parserAthShield.js to better reflect its purpose. Removed unused `athShield` import from landing.js and added it to settings.js. Added a manageModCategory function in settings.js to manage the display and interaction state of the Mods tab based on the type of `athShield`. --- app/assets/athshield/parserAthShield.js | 2 +- app/assets/js/scripts/landing.js | 1 - app/assets/js/scripts/settings.js | 31 ++++++++++++++++++++++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/app/assets/athshield/parserAthShield.js b/app/assets/athshield/parserAthShield.js index 8a166733..de3c9a6c 100644 --- a/app/assets/athshield/parserAthShield.js +++ b/app/assets/athshield/parserAthShield.js @@ -22,7 +22,7 @@ class AthenaShield { } // Récupérer la visibilité du menu - get view() { + get type() { return this.config.menuVisibility } } diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 058d1bd8..e23b0c43 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -31,7 +31,6 @@ const { const DiscordWrapper = require('./assets/js/discordwrapper') const ProcessBuilder = require('./assets/js/processbuilder') const dataPath = require('./assets/js/configmanager') -const athShield = require('./assets/athshield/parserAthShield') const fs = require('fs') // Launch Elements diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index 81a65a70..96d956f9 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -1,8 +1,9 @@ // Requirements -const os = require('os') -const semver = require('semver') +const os = require('os') +const semver = require('semver') -const DropinModUtil = require('./assets/js/dropinmodutil') +const DropinModUtil = require('./assets/js/dropinmodutil') +const athShield = require('./assets/athshield/parserAthShield') const { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants') const settingsState = { @@ -707,6 +708,29 @@ document.getElementById('settingsGameHeight').addEventListener('keydown', (e) => const settingsModsContainer = document.getElementById('settingsModsContainer') +/** + * Manages the display and interaction state of the Mods tab and its buttons based on the type of `athShield`. + * + * The function performs the following: + * - If `athShield.type` is 'hidden': hides the Mods button entirely. + * - If `athShield.type` is 'blocked': shows the Mods button, displays the Mods tab, and disables all buttons within the Mods tab. + * - Otherwise: shows and enables all components (Mods button and buttons within the Mods tab) normally. + * + * @return {void} + */ +function manageModCategory() { + const modsButton = document.querySelector('button[rSc="settingsTabMods"]') + const dropInMods = document.getElementById('settingsDropinModsContainer') + + if (athShield.type === 'hidden') { + // Hide the Mods navigation button + modsButton.style.display = 'none' + } else if (athShield.type === 'blocked') { + // Hide the drop-in mods elements + dropInMods.style.display = 'none' + } +} + /** * Resolve and update the mods on the UI. */ @@ -1130,6 +1154,7 @@ function animateSettingsTabRefresh(){ * Prepare the Mods tab for display. */ async function prepareModsTab(first){ + manageModCategory() await resolveModsForUI() await resolveDropinModsForUI() await resolveShaderpacksForUI() From 96e0ca12f493ae61ed4d8ea4fb2d652c1e48629e Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 18:17:46 +0200 Subject: [PATCH 13/52] Refactor menu visibility logic in athshield.js Separate logic for 'cacher' and 'bloquer' options and update corresponding values to 'hidden' and 'blocked' respectively. Ensure configuration is saved after setting the menu visibility. --- app/assets/athshield/athshield.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/assets/athshield/athshield.js b/app/assets/athshield/athshield.js index 429fd900..f2a3d0a6 100644 --- a/app/assets/athshield/athshield.js +++ b/app/assets/athshield/athshield.js @@ -44,17 +44,21 @@ function startCLI() { return } - if (menuAnswer.toLowerCase() === 'cacher' || menuAnswer.toLowerCase() === 'bloquer') { - config.menuVisibility = menuAnswer.toLowerCase() - console.log(`Athena's Shield activé. Menu ${config.menuVisibility === 'cacher' ? 'caché' : 'bloqué'}.`) - - // Sauvegarder la configuration modifiée - saveConfig(config) - rl.close() + if (menuAnswer.toLowerCase() === 'cacher') { + config.menuVisibility = 'hidden' // Change to 'hidden' + console.log(`Athena's Shield activé. Menu caché.`) + } else if (menuAnswer.toLowerCase() === 'bloquer') { + config.menuVisibility = 'blocked' // Change to 'blocked' + console.log(`Athena's Shield activé. Menu bloqué.`) } else { console.log('Option non valide pour le menu.') rl.close() + return } + + // Sauvegarder la configuration modifiée + saveConfig(config) + rl.close() }) } else if (answer.toLowerCase() === 'non') { console.log('Athena\'s Shield non activé. Fermeture du CLI.') From 297a3a099c03227fa3362f7d49c65ebcc442305f Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 18:18:52 +0200 Subject: [PATCH 14/52] Refactor comments and questions from French to English Updated all comments and user prompts in athshield.js from French to English for better code readability and broader usability. No functional changes were made. --- app/assets/athshield/athshield.js | 46 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/app/assets/athshield/athshield.js b/app/assets/athshield/athshield.js index f2a3d0a6..63d20ac3 100644 --- a/app/assets/athshield/athshield.js +++ b/app/assets/athshield/athshield.js @@ -2,78 +2,78 @@ const fs = require('fs') const readline = require('readline') const path = require('path') -// Chemin vers le fichier de configuration +// Path to the configuration file const configPath = path.join(__dirname, 'variables.athshield') -// Charger les variables depuis le fichier +// Load the variables from the file function loadConfig() { const rawData = fs.readFileSync(configPath) - return JSON.parse(rawData.toString()) // Convertir Buffer en string + return JSON.parse(rawData.toString()) // Convert Buffer to string } -// Sauvegarder les variables dans le fichier +// Save the variables to the file function saveConfig(config) { const data = JSON.stringify(config, null, 2) fs.writeFileSync(configPath, data) } -// Création de l'interface readline +// Create the readline interface const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) -// Fonction pour poser les questions à l'utilisateur +// Function to ask questions to the user function startCLI() { const config = loadConfig() - rl.question('Voulez-vous activer Athena\'s Shield ? (oui/non) : ', (answer) => { + rl.question('Would you like to activate Athena\'s Shield? (yes/no): ', (answer) => { if (answer.trim().startsWith('//')) { - console.log('Ceci est un commentaire, la ligne est ignorée.') + console.log('This is a comment; the line is ignored.') rl.close() return } - if (answer.toLowerCase() === 'oui') { + if (answer.toLowerCase() === 'yes') { config.athenaShieldActivated = true - rl.question('Voulez-vous cacher ou bloquer le menu ? (cacher/bloquer) : ', (menuAnswer) => { + rl.question('Would you like to hide or block the menu? (hide/block): ', (menuAnswer) => { if (menuAnswer.trim().startsWith('//')) { - console.log('Ceci est un commentaire, la ligne est ignorée.') + console.log('This is a comment; the line is ignored.') rl.close() return } - if (menuAnswer.toLowerCase() === 'cacher') { + if (menuAnswer.toLowerCase() === 'hide') { config.menuVisibility = 'hidden' // Change to 'hidden' - console.log(`Athena's Shield activé. Menu caché.`) - } else if (menuAnswer.toLowerCase() === 'bloquer') { + console.log(`Athena's Shield activated. Menu hidden.`) + } else if (menuAnswer.toLowerCase() === 'block') { config.menuVisibility = 'blocked' // Change to 'blocked' - console.log(`Athena's Shield activé. Menu bloqué.`) + console.log(`Athena's Shield activated. Menu blocked.`) } else { - console.log('Option non valide pour le menu.') + console.log('Invalid option for the menu.') rl.close() return } - // Sauvegarder la configuration modifiée + // Save the modified configuration saveConfig(config) rl.close() }) - } else if (answer.toLowerCase() === 'non') { - console.log('Athena\'s Shield non activé. Fermeture du CLI.') + } else if (answer.toLowerCase() === 'no') { + console.log('Athena\'s Shield not activated. Closing the CLI.') config.athenaShieldActivated = false - config.menuVisibility = 'visible' // Remettre la valeur par défaut + config.menuVisibility = 'visible' // Reset to default value - // Sauvegarder la configuration modifiée + // Save the modified configuration saveConfig(config) rl.close() } else { - console.log('Réponse non valide.') + console.log('Invalid response.') rl.close() } }) } -// Lancer le CLI +// Launch the CLI startCLI() From 9d4c8cc399190609bd6f24520915e1ca681ff497 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 18:20:36 +0200 Subject: [PATCH 15/52] Fix escape character handling in console log messages Updated console log messages in athshield.js to use single quotes for consistent escape character handling. This adjustment ensures better compatibility and readability of string literals in the code. --- app/assets/athshield/athshield.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/athshield/athshield.js b/app/assets/athshield/athshield.js index 63d20ac3..e6dc1475 100644 --- a/app/assets/athshield/athshield.js +++ b/app/assets/athshield/athshield.js @@ -46,10 +46,10 @@ function startCLI() { if (menuAnswer.toLowerCase() === 'hide') { config.menuVisibility = 'hidden' // Change to 'hidden' - console.log(`Athena's Shield activated. Menu hidden.`) + console.log('Athena\'s Shield activated. Menu hidden.') } else if (menuAnswer.toLowerCase() === 'block') { config.menuVisibility = 'blocked' // Change to 'blocked' - console.log(`Athena's Shield activated. Menu blocked.`) + console.log('Athena\'s Shield activated. Menu blocked.') } else { console.log('Invalid option for the menu.') rl.close() From 48350019f238fec22ad252cb627f5ee19135d38e Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 23:39:45 +0200 Subject: [PATCH 16/52] Refactor dataPath assignment Replaced direct assignment of `dataPath` with variable `nameDataPath` for consistent naming. Introduced `getNameDataPath` function to return the launcher directory name string. --- app/assets/js/configmanager.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/js/configmanager.js b/app/assets/js/configmanager.js index ff1026bf..dbb87a12 100644 --- a/app/assets/js/configmanager.js +++ b/app/assets/js/configmanager.js @@ -7,7 +7,9 @@ const logger = LoggerUtil.getLogger('ConfigManager') const sysRoot = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME) -const dataPath = path.join(sysRoot, '.helioslauncher') +const nameDataPath = '.helioslauncher' + +const dataPath = path.join(sysRoot, nameDataPath) const launcherDir = require('@electron/remote').app.getPath('userData') @@ -18,7 +20,9 @@ const launcherDir = require('@electron/remote').app.getPath('userData') * * @type {string} */ -exports.dataPath = dataPath +exports.getNameDataPath = function(){ + return nameDataPath +} /** * Retrieve the absolute path of the launcher directory. From 1f4df6b8ff057df7f69e513ff7af321753982b79 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Thu, 24 Oct 2024 23:40:14 +0200 Subject: [PATCH 17/52] Refactor config path retrieval in landing.js Remove unused dataPath constant and replace its usage in mod validation error message with ConfigManager.getNameDataPath(). This ensures the config path is retrieved dynamically. --- app/assets/js/scripts/landing.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index e23b0c43..df70a5f9 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -30,7 +30,6 @@ const { // Internal Requirements const DiscordWrapper = require('./assets/js/discordwrapper') const ProcessBuilder = require('./assets/js/processbuilder') -const dataPath = require('./assets/js/configmanager') const fs = require('fs') // Launch Elements @@ -614,7 +613,7 @@ async function dlAsync(login = true) { // Perform mod validation before proceeding if (!validateMods()) { - const errorMessage = Lang.queryJS('landing.dlAsync.AthShield.invalidModsDetectedMessage', {'folder': dataPath}) + const errorMessage = Lang.queryJS('landing.dlAsync.AthShield.invalidModsDetectedMessage', {'folder': ConfigManager.getNameDataPath()}) loggerLanding.error(errorMessage) showLaunchFailure(errorMessage, null) return From 980c836d9da92482a6efab6cacbe949b23b1ad81 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Fri, 25 Oct 2024 21:16:50 +0200 Subject: [PATCH 18/52] Make package public Changed "private" field in package.json from true to false. This will make the package accessible on npm and allow others to install it. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d5271cf1..6f0a35ee 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "bugs": { "url": "https://github.com/dscalzi/HeliosLauncher/issues" }, - "private": true, + "private": false, "main": "index.js", "scripts": { "start": "electron .", From 09646484f1122d5557ba386ee533b191bfc0fa8c Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 14:30:35 +0200 Subject: [PATCH 19/52] Add project configuration and settings files Added .gitignore to exclude IDE-specific files, set up project code style configuration, included game and launcher settings in config.json, and created minimal discord and distribution JSON files for project setup. --- .idea/.gitignore | 8 ++ .idea/HeliosLauncher.iml | 9 ++ .idea/codeStyles/Project.xml | 14 +++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/discord.xml | 7 ++ .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/libraries/PackXZExtract.xml | 9 ++ .idea/misc.xml | 6 + .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + fr/config.json | 110 +++++++++++++++++++ fr/distribution.json | 1 + 12 files changed, 189 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/HeliosLauncher.iml create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/discord.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/libraries/PackXZExtract.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 fr/config.json create mode 100644 fr/distribution.json diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/HeliosLauncher.iml b/.idea/HeliosLauncher.iml new file mode 100644 index 00000000..d6ebd480 --- /dev/null +++ b/.idea/HeliosLauncher.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..089b600c --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 00000000..d8e95616 --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..03d9549e --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/libraries/PackXZExtract.xml b/.idea/libraries/PackXZExtract.xml new file mode 100644 index 00000000..7d226702 --- /dev/null +++ b/.idea/libraries/PackXZExtract.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..e6be3f13 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..c65f1d19 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/fr/config.json b/fr/config.json new file mode 100644 index 00000000..0bb27764 --- /dev/null +++ b/fr/config.json @@ -0,0 +1,110 @@ +{ + "settings": { + "game": { + "resWidth": 1280, + "resHeight": 720, + "fullscreen": false, + "autoConnect": true, + "launchDetached": true + }, + "launcher": { + "allowPrerelease": false, + "dataDirectory": "C:\\Users\\Shadow\\AppData\\Roaming\\.helioslauncher" + } + }, + "newsCache": { + "date": 1678502940000, + "content": "2ce148c5c087f5cc1cd167cb323424b222441a2f", + "dismissed": false + }, + "clientToken": null, + "selectedServer": "Demo-1.19.4", + "selectedAccount": "92d2a2d6900648a3b0e64ecf7875eb49", + "authenticationDatabase": { + "92d2a2d6900648a3b0e64ecf7875eb49": { + "type": "microsoft", + "accessToken": "eyJraWQiOiJhYzg0YSIsImFsZyI6IkhTMjU2In0.eyJ4dWlkIjoiMjUzNTQyNjM4Mjc4MTc5OCIsImFnZyI6IkFkdWx0Iiwic3ViIjoiYjc0MTAwM2ItNWI1Yy00Y2U5LWJkZmItZGIzZjM5NDIxNGIyIiwiYXV0aCI6IlhCT1giLCJucyI6ImRlZmF1bHQiLCJwc25pZCI6Ijc3MzU2NzY5ODEzMDM2OTMyMCIsInJvbGVzIjpbXSwiaXNzIjoiYXV0aGVudGljYXRpb24iLCJmbGFncyI6WyJ0d29mYWN0b3JhdXRoIiwibXNhbWlncmF0aW9uX3N0YWdlNCIsIm9yZGVyc18yMDIyIiwibXVsdGlwbGF5ZXIiXSwicHJvZmlsZXMiOnsibWMiOiI5MmQyYTJkNi05MDA2LTQ4YTMtYjBlNi00ZWNmNzg3NWViNDkifSwicGxhdGZvcm0iOiJVTktOT1dOIiwieXVpZCI6IjVlZTFlODI1YjZiYjVlMDk5YWJhZWE1YzhkMDdiMWVkIiwibmJmIjoxNzI5ODA1NDk3LCJleHAiOjE3Mjk4OTE4OTcsImlhdCI6MTcyOTgwNTQ5N30.CVKm31_LJ1YaxeFF7VLNuwS-RKidZEX6U7T-gygL91Y", + "username": "Sandro642", + "uuid": "92d2a2d6900648a3b0e64ecf7875eb49", + "displayName": "Sandro642", + "expiresAt": 1729891886954, + "microsoft": { + "access_token": "EwAoA+pvBAAUKods63Ys1fGlwiccIFJ+qE1hANsAAV9mds/QBtmjIyO4dXqXDT+Xki8mKEwRzO3c9bU1Jb90+JEufLws0yqpJe6sZC+BTHpZ3ih9MD0Ybdh7t3+nBaNsrxdCDAiZsS78j7h75+7uYX6b8RQ58NVAMfbYPnUmPRnhNvBgVYWlXQlPIejuut0OVP8nkOVkErIaj0qFVM8Gj1TXkMrgwWmQx88UVjUrvF6PuuTLAf0n5QQ6BdD93twrm44CYrUZuXZBxekOvF/2gNSm9Gc3w3gnfnC0/t1Xo85Nt68MuhaECfOlMUyRntgvdap0gKROCYJD9mh1PxDfPMNC2IguR19swXc00ZxTHtyKxHjvNUxGX7s8rAQhAkwQZgAAEKfjb7UAXRNGVUsGy07DR4/wAWetT5h9OiQ1ziXmB7nZKlhCPd49swbLXgqMlFb/JRmoYrNeommCuFCouD1Ch7CQD48ZkPIvvOlRVGujRb1ifpgDUFm5rj22zuZbm+I292MAocbDiw1+obaj/iA6latEicJdV1zxxgvOvYVilKo6dZzvtvgXLMuEPhuAb5tZ+H1zoz7igwP4Zw8H6FOyfmKMD5xv4faNXYnpKOocLDkM5vmkB4vJKt3tridBrsRdZgGeCh0B6OK8KSmWXIpm5DCvczus3hWEkPfbTwekayptLgZmsPf5obJ3N+2mp+uWfy9YrZ7k3j56/np61CtOO8+/ZbAau8/XIetmxnmb51Uh+PgVJBot5Qbc7L0JREdZnXZt7DAVsV9eQlSD9XTnReMY2VX70+6qOzddbVxoaZrKCQYt3MydDT90njzAyIkBdGqN0lPoTa3SKDUFh0JWOwRBY0YUjIRyHJRIo86/qsrl3J3UN+FK6VbfqlcO/svP5rkG7BcboOzijeMy/bGBjzVC64KaMO1T2h85LPYMpV0/RYEF/Mt+Yvp/02uaJF2uIdXHwSooNznnLD0EHPv5cWoY4wjr1U8wGJBs2jRrVdL1lSE9M3ABAisN2m717rbXnRzFDAHjOfk36CYgkC3x+sI1aeWK+3ZCcWM99ciHF5RFroQeAg==", + "refresh_token": "M.C538_BAY.0.U.-CqKBGlTb3jOsOybryiaxXM2tMhaz5MczCYAZ65EJNOWFP0jXWWhFHE39DgtpnDZGPCxhWY*aghV30KcMIx4t7kQC1pNHrtX!QyiW3*xXvpd0QEOSzb15bnTrqMV6V8L!*oEMr*xv5Q7*6xyiA4IBd3czHTzlsagHbqGn7nCXGMlEso0mi7gcHN2KHUJsrIGPkZINfz5X2f1m0XkBTxeO1aMEkmE*DLwrMcuw*S4b3QK6LpoWWNH2YKHz1a0n1M7EHnTqs*Y*22RmUkHR**yNyR*p5aOlA5tq6tTdo8tWKNZp1Ea11kY3v91adOLwSyc5aQ$$", + "expires_at": 1729809086954 + } + } + }, + "modConfigurations": [ + { + "id": "Demo-1.19.4", + "mods": {} + }, + { + "id": "Demo-1.20", + "mods": {} + }, + { + "id": "Fabric-Demo-1.20.4", + "mods": { + "com.terraformersmc:modmenu": true + } + }, + { + "id": "WesterosCraft-Demo-1.12.2", + "mods": {} + } + ], + "javaConfig": { + "Demo-1.19.4": { + "minRAM": "4G", + "maxRAM": "4G", + "executable": "C:\\Users\\Shadow\\AppData\\Roaming\\.helioslauncher\\runtime\\x64\\jdk-17.0.13+11\\bin\\javaw.exe", + "jvmOptions": [ + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseG1GC", + "-XX:G1NewSizePercent=20", + "-XX:G1ReservePercent=20", + "-XX:MaxGCPauseMillis=50", + "-XX:G1HeapRegionSize=32M" + ] + }, + "Demo-1.20": { + "minRAM": "4G", + "maxRAM": "4G", + "executable": null, + "jvmOptions": [ + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseG1GC", + "-XX:G1NewSizePercent=20", + "-XX:G1ReservePercent=20", + "-XX:MaxGCPauseMillis=50", + "-XX:G1HeapRegionSize=32M" + ] + }, + "Fabric-Demo-1.20.4": { + "minRAM": "4G", + "maxRAM": "4G", + "executable": null, + "jvmOptions": [ + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseG1GC", + "-XX:G1NewSizePercent=20", + "-XX:G1ReservePercent=20", + "-XX:MaxGCPauseMillis=50", + "-XX:G1HeapRegionSize=32M" + ] + }, + "WesterosCraft-Demo-1.12.2": { + "minRAM": "4G", + "maxRAM": "4G", + "executable": null, + "jvmOptions": [ + "-XX:+UseConcMarkSweepGC", + "-XX:+CMSIncrementalMode", + "-XX:-UseAdaptiveSizePolicy", + "-Xmn128M" + ] + } + } +} \ No newline at end of file diff --git a/fr/distribution.json b/fr/distribution.json new file mode 100644 index 00000000..98ad8f4f --- /dev/null +++ b/fr/distribution.json @@ -0,0 +1 @@ +{"version":"1.0.0","rss":"https://helios-files.geekcorner.eu.org/rss.xml","discord":{"clientId":"1086936373057040395","smallImageText":"Change me through Nebula","smallImageKey":"big"},"servers":[{"id":"Demo-1.19.4","name":"Demo (Minecraft 1.19.4)","description":"Demo Running Minecraft 1.19.4 (Forge v45.0.9)","icon":"https://helios-files.geekcorner.eu.org/servers/Demo-1.19.4/Demo-1.19.4.png","version":"1.0.0","address":"localhost:25565","minecraftVersion":"1.19.4","discord":{"shortId":"1.19.4 Demo","largeImageKey":"https://raw.githubusercontent.com/dscalzi/HeliosLauncher/master/build/icon.png","largeImageText":"forge-vanilla"},"mainServer":false,"autoconnect":false,"modules":[{"id":"net.minecraftforge:lowcodelanguage:1.19.4-45.0.9","name":"Minecraft Forge (lowcodelanguage)","type":"ForgeHosted","classpath":true,"artifact":{"size":7378,"MD5":"567726c42a1312962ffd96af646f227d","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/lowcodelanguage/1.19.4-45.0.9/lowcodelanguage-1.19.4-45.0.9.jar"},"subModules":[{"id":"1.19.4-45.0.9","name":"Minecraft Forge (version.json)","type":"VersionManifest","artifact":{"size":15497,"MD5":"97924bf1d3bf6f2bc4acc5662a7fecc0","url":"https://helios-files.geekcorner.eu.org/repo/versions/1.19.4-forge-45.0.9/1.19.4-forge-45.0.9.json"}},{"id":"net.minecraftforge:fmlcore:1.19.4-45.0.9","name":"Minecraft Forge (fmlcore)","type":"Library","classpath":true,"artifact":{"size":113254,"MD5":"087a7175f26ebce8dee27cc75ab706a6","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/fmlcore/1.19.4-45.0.9/fmlcore-1.19.4-45.0.9.jar"},"subModules":[]},{"id":"net.minecraftforge:javafmllanguage:1.19.4-45.0.9","name":"Minecraft Forge (javafmllanguage)","type":"Library","classpath":true,"artifact":{"size":16352,"MD5":"85f5a466356973b9a9199f5a5b1d1a25","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/javafmllanguage/1.19.4-45.0.9/javafmllanguage-1.19.4-45.0.9.jar"},"subModules":[]},{"id":"net.minecraftforge:mclanguage:1.19.4-45.0.9","name":"Minecraft Forge (mclanguage)","type":"Library","classpath":true,"artifact":{"size":4987,"MD5":"2afe8c6ba41599bbc139607e1f5bc90f","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/mclanguage/1.19.4-45.0.9/mclanguage-1.19.4-45.0.9.jar"},"subModules":[]},{"id":"net.minecraftforge:forge:1.19.4-45.0.9:universal","name":"Minecraft Forge (universal jar)","type":"Library","classpath":false,"artifact":{"size":2654813,"MD5":"75086169e9f5f6990cd399ae352590da","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forge/1.19.4-45.0.9/forge-1.19.4-45.0.9-universal.jar"},"subModules":[]},{"id":"net.minecraftforge:forge:1.19.4-45.0.9:client","name":"Minecraft Forge (client jar)","type":"Library","classpath":false,"artifact":{"size":5416831,"MD5":"db21cc95ee603f0dc4e7bce8af5d1f5c","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forge/1.19.4-45.0.9/forge-1.19.4-45.0.9-client.jar"},"subModules":[]},{"id":"net.minecraft:client:1.19.4-20230314.122934:srg","name":"Minecraft Forge (client srg)","type":"Library","classpath":false,"artifact":{"size":18578667,"MD5":"500a7127781d84a24d65f110479538f3","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.19.4-20230314.122934/client-1.19.4-20230314.122934-srg.jar"},"subModules":[]},{"id":"net.minecraft:client:1.19.4-20230314.122934:slim","name":"Minecraft Forge (client slim)","type":"Library","classpath":false,"artifact":{"size":12410103,"MD5":"764989b8230caa18e97f90799f902650","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.19.4-20230314.122934/client-1.19.4-20230314.122934-slim.jar"},"subModules":[]},{"id":"net.minecraft:client:1.19.4-20230314.122934:extra","name":"Minecraft Forge (client extra)","type":"Library","classpath":false,"artifact":{"size":11066539,"MD5":"e612fd0d646100ae60158ba37c5aa71a","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.19.4-20230314.122934/client-1.19.4-20230314.122934-extra.jar"},"subModules":[]},{"id":"cpw.mods:securejarhandler:2.1.6","name":"Minecraft Forge (securejarhandler)","type":"Library","artifact":{"size":87783,"MD5":"8a4e7e2770d7147d7551d1f3ad835baa","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/securejarhandler/2.1.6/securejarhandler-2.1.6.jar"}},{"id":"org.ow2.asm:asm:9.3","name":"Minecraft Forge (asm)","type":"Library","artifact":{"size":122176,"MD5":"e1c3b96035117ab516ffe0de9bd696e0","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm/9.3/asm-9.3.jar"}},{"id":"org.ow2.asm:asm-commons:9.3","name":"Minecraft Forge (asm-commons)","type":"Library","artifact":{"size":72716,"MD5":"16e6ac17d33ad97baa415c42e9d93d38","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-commons/9.3/asm-commons-9.3.jar"}},{"id":"org.ow2.asm:asm-tree:9.3","name":"Minecraft Forge (asm-tree)","type":"Library","artifact":{"size":52669,"MD5":"f087bfb911ff93957e44860de3e73c46","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-tree/9.3/asm-tree-9.3.jar"}},{"id":"org.ow2.asm:asm-util:9.3","name":"Minecraft Forge (asm-util)","type":"Library","artifact":{"size":85682,"MD5":"d6d0ac4e5c0be9a25a426c70c714b17b","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-util/9.3/asm-util-9.3.jar"}},{"id":"org.ow2.asm:asm-analysis:9.3","name":"Minecraft Forge (asm-analysis)","type":"Library","artifact":{"size":34276,"MD5":"11e43c0220b3f6b4791b8abf7b4cc7ef","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-analysis/9.3/asm-analysis-9.3.jar"}},{"id":"net.minecraftforge:accesstransformers:8.0.4","name":"Minecraft Forge (accesstransformers)","type":"Library","artifact":{"size":77756,"MD5":"f0d3533f9437ba4428eaee9f28f7ecd9","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/accesstransformers/8.0.4/accesstransformers-8.0.4.jar"}},{"id":"org.antlr:antlr4-runtime:4.9.1","name":"Minecraft Forge (antlr4-runtime)","type":"Library","artifact":{"size":337868,"MD5":"0dcc4b860d5d8d2852ab94d58c56ca2d","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/antlr/antlr4-runtime/4.9.1/antlr4-runtime-4.9.1.jar"}},{"id":"net.minecraftforge:eventbus:6.0.3","name":"Minecraft Forge (eventbus)","type":"Library","artifact":{"size":52624,"MD5":"7c93693a70962a1c1893e3c058dca5cf","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/eventbus/6.0.3/eventbus-6.0.3.jar"}},{"id":"net.minecraftforge:forgespi:6.0.0","name":"Minecraft Forge (forgespi)","type":"Library","artifact":{"size":32714,"MD5":"8c04b8c26ac1d55c281bfeaa71731414","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forgespi/6.0.0/forgespi-6.0.0.jar"}},{"id":"net.minecraftforge:coremods:5.0.1","name":"Minecraft Forge (coremods)","type":"Library","artifact":{"size":24199,"MD5":"8c590051c5d65e098ca7c2e3ddf803a8","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/coremods/5.0.1/coremods-5.0.1.jar"}},{"id":"cpw.mods:modlauncher:10.0.8","name":"Minecraft Forge (modlauncher)","type":"Library","artifact":{"size":130263,"MD5":"3e171edcc29e762d2ecafffabb9e9341","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/modlauncher/10.0.8/modlauncher-10.0.8.jar"}},{"id":"net.minecraftforge:unsafe:0.2.0","name":"Minecraft Forge (unsafe)","type":"Library","artifact":{"size":2834,"MD5":"2d1016ebe4c1a63dd50a59d26bd12db1","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/unsafe/0.2.0/unsafe-0.2.0.jar"}},{"id":"com.electronwill.night-config:core:3.6.4","name":"Minecraft Forge (core)","type":"Library","artifact":{"size":199834,"MD5":"d83ab07267e402131fb93d899a57f5cd","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/electronwill/night-config/core/3.6.4/core-3.6.4.jar"}},{"id":"com.electronwill.night-config:toml:3.6.4","name":"Minecraft Forge (toml)","type":"Library","artifact":{"size":31816,"MD5":"bc95d0709fff2164b01fd09fbc988be8","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/electronwill/night-config/toml/3.6.4/toml-3.6.4.jar"}},{"id":"org.apache.maven:maven-artifact:3.8.5","name":"Minecraft Forge (maven-artifact)","type":"Library","artifact":{"size":58077,"MD5":"ce473b0d9fbfd10fe147f03fe8707d67","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/maven/maven-artifact/3.8.5/maven-artifact-3.8.5.jar"}},{"id":"net.jodah:typetools:0.8.3","name":"Minecraft Forge (typetools)","type":"Library","artifact":{"size":15425,"MD5":"ee4596e1413cc1a5e080c971d6064753","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/jodah/typetools/0.8.3/typetools-0.8.3.jar"}},{"id":"net.minecrell:terminalconsoleappender:1.2.0","name":"Minecraft Forge (terminalconsoleappender)","type":"Library","artifact":{"size":15977,"MD5":"679363fa893293791e55a21f81342f87","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecrell/terminalconsoleappender/1.2.0/terminalconsoleappender-1.2.0.jar"}},{"id":"org.jline:jline-reader:3.12.1","name":"Minecraft Forge (jline-reader)","type":"Library","artifact":{"size":150765,"MD5":"a2e7b012cd9802f83321187409174a94","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/jline/jline-reader/3.12.1/jline-reader-3.12.1.jar"}},{"id":"org.jline:jline-terminal:3.12.1","name":"Minecraft Forge (jline-terminal)","type":"Library","artifact":{"size":211712,"MD5":"3c52be5ab5e3847be6e62269de924cb0","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/jline/jline-terminal/3.12.1/jline-terminal-3.12.1.jar"}},{"id":"org.spongepowered:mixin:0.8.5","name":"Minecraft Forge (mixin)","type":"Library","artifact":{"size":1089277,"MD5":"19b3a2ae9e445a6e626fd7d1648cfcb8","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar"}},{"id":"org.openjdk.nashorn:nashorn-core:15.3","name":"Minecraft Forge (nashorn-core)","type":"Library","artifact":{"size":2167288,"MD5":"91e98c20afa1090c344229ce28b4c53f","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/openjdk/nashorn/nashorn-core/15.3/nashorn-core-15.3.jar"}},{"id":"net.minecraftforge:JarJarSelector:0.3.19","name":"Minecraft Forge (JarJarSelector)","type":"Library","artifact":{"size":17374,"MD5":"2bb6cbe0e6c6fbcd8f92d2e6b1ea678e","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarSelector/0.3.19/JarJarSelector-0.3.19.jar"}},{"id":"net.minecraftforge:JarJarMetadata:0.3.19","name":"Minecraft Forge (JarJarMetadata)","type":"Library","artifact":{"size":15895,"MD5":"9633546d299d4282ca68d10582be1c8f","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarMetadata/0.3.19/JarJarMetadata-0.3.19.jar"}},{"id":"cpw.mods:bootstraplauncher:1.1.2","name":"Minecraft Forge (bootstraplauncher)","type":"Library","artifact":{"size":8284,"MD5":"48f5255aa337344c467c0150d347813d","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/bootstraplauncher/1.1.2/bootstraplauncher-1.1.2.jar"}},{"id":"net.minecraftforge:JarJarFileSystems:0.3.19","name":"Minecraft Forge (JarJarFileSystems)","type":"Library","artifact":{"size":32195,"MD5":"c2be1a88b63eb1b58b00ab6e498cd97d","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarFileSystems/0.3.19/JarJarFileSystems-0.3.19.jar"}},{"id":"net.minecraftforge:fmlloader:1.19.4-45.0.9","name":"Minecraft Forge (fmlloader)","type":"Library","artifact":{"size":261271,"MD5":"896da600a8508b6a41a4f804e1073b92","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/fmlloader/1.19.4-45.0.9/fmlloader-1.19.4-45.0.9.jar"}}]},{"id":"squeek.appleskin:appleskin:2.4.3+mc1.19.4@jar","name":"AppleSkin","type":"ForgeMod","artifact":{"size":46976,"url":"https://helios-files.geekcorner.eu.org/servers/Demo-1.19.4/forgemods/required/appleskin-forge-mc1.19.4-2.4.3.jar","MD5":"757b5e83f4c2ab0ab2a21289849edcdf"}},{"id":"hi.txt","name":"hi.txt","type":"File","artifact":{"size":147,"url":"https://helios-files.geekcorner.eu.org/servers/Demo-1.19.4/files/hi.txt","MD5":"e752096b159035a3ffaf396fb111b837","path":"hi.txt"}}]},{"id":"Demo-1.20","name":"Demo (Minecraft 1.20)","description":"Demo Running Minecraft 1.20 (Forge v46.0.2)","icon":"https://helios-files.geekcorner.eu.org/servers/Demo-1.20/Demo-1.20.png","version":"1.0.0","address":"localhost:25565","minecraftVersion":"1.20","discord":{"shortId":"","largeImageText":"","largeImageKey":""},"mainServer":false,"autoconnect":false,"modules":[{"id":"net.minecraftforge:lowcodelanguage:1.20-46.0.2","name":"Minecraft Forge (lowcodelanguage)","type":"ForgeHosted","classpath":true,"artifact":{"size":7400,"MD5":"4a69374eb645771ddb0387a9076b450f","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/lowcodelanguage/1.20-46.0.2/lowcodelanguage-1.20-46.0.2.jar"},"subModules":[{"id":"1.20-46.0.2","name":"Minecraft Forge (version.json)","type":"VersionManifest","artifact":{"size":15970,"MD5":"1125883344968dbbe14e74cf3ad2b5e8","url":"https://helios-files.geekcorner.eu.org/repo/versions/1.20-forge-46.0.2/1.20-forge-46.0.2.json"}},{"id":"net.minecraftforge:fmlcore:1.20-46.0.2","name":"Minecraft Forge (fmlcore)","type":"Library","classpath":true,"artifact":{"size":116123,"MD5":"bac94c804729e0952c84a2e863ad2269","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/fmlcore/1.20-46.0.2/fmlcore-1.20-46.0.2.jar"},"subModules":[]},{"id":"net.minecraftforge:javafmllanguage:1.20-46.0.2","name":"Minecraft Forge (javafmllanguage)","type":"Library","classpath":true,"artifact":{"size":16374,"MD5":"3b25d656c3a0fb057ed308a33ce43c29","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/javafmllanguage/1.20-46.0.2/javafmllanguage-1.20-46.0.2.jar"},"subModules":[]},{"id":"net.minecraftforge:mclanguage:1.20-46.0.2","name":"Minecraft Forge (mclanguage)","type":"Library","classpath":true,"artifact":{"size":5000,"MD5":"d0eb5fff9d92efcb43b8d708298cc483","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/mclanguage/1.20-46.0.2/mclanguage-1.20-46.0.2.jar"},"subModules":[]},{"id":"net.minecraftforge:forge:1.20-46.0.2:universal","name":"Minecraft Forge (universal jar)","type":"Library","classpath":false,"artifact":{"size":2612080,"MD5":"5dd29a134bd8f6135417bacb5e36408e","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forge/1.20-46.0.2/forge-1.20-46.0.2-universal.jar"},"subModules":[]},{"id":"net.minecraftforge:forge:1.20-46.0.2:client","name":"Minecraft Forge (client jar)","type":"Library","classpath":false,"artifact":{"size":4677757,"MD5":"55f309ca113761208206ce095c221a29","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forge/1.20-46.0.2/forge-1.20-46.0.2-client.jar"},"subModules":[]},{"id":"net.minecraft:client:1.20-20230608.053357:srg","name":"Minecraft Forge (client srg)","type":"Library","classpath":false,"artifact":{"size":18841206,"MD5":"d20dc7660df3488916d1cf24b61e08fb","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.20-20230608.053357/client-1.20-20230608.053357-srg.jar"},"subModules":[]},{"id":"net.minecraft:client:1.20-20230608.053357:slim","name":"Minecraft Forge (client slim)","type":"Library","classpath":false,"artifact":{"size":12591630,"MD5":"6fbeb6a4a0dc55cb90e9334885918053","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.20-20230608.053357/client-1.20-20230608.053357-slim.jar"},"subModules":[]},{"id":"net.minecraft:client:1.20-20230608.053357:extra","name":"Minecraft Forge (client extra)","type":"Library","classpath":false,"artifact":{"size":10436670,"MD5":"f4f8be138812304b01638fbf5c6ff81a","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.20-20230608.053357/client-1.20-20230608.053357-extra.jar"},"subModules":[]},{"id":"cpw.mods:securejarhandler:2.1.6","name":"Minecraft Forge (securejarhandler)","type":"Library","artifact":{"size":87783,"MD5":"8a4e7e2770d7147d7551d1f3ad835baa","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/securejarhandler/2.1.6/securejarhandler-2.1.6.jar"}},{"id":"org.ow2.asm:asm:9.5","name":"Minecraft Forge (asm)","type":"Library","artifact":{"size":121863,"MD5":"29721ee4b5eacf0a34b204c345c8bc69","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm/9.5/asm-9.5.jar"}},{"id":"org.ow2.asm:asm-commons:9.5","name":"Minecraft Forge (asm-commons)","type":"Library","artifact":{"size":72209,"MD5":"7d1fce986192f71722b19754e4cb9e61","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-commons/9.5/asm-commons-9.5.jar"}},{"id":"org.ow2.asm:asm-tree:9.5","name":"Minecraft Forge (asm-tree)","type":"Library","artifact":{"size":51944,"MD5":"44755681b7d6fa7143afbb438e55c20c","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-tree/9.5/asm-tree-9.5.jar"}},{"id":"org.ow2.asm:asm-util:9.5","name":"Minecraft Forge (asm-util)","type":"Library","artifact":{"size":91076,"MD5":"ad0016249fb68bb9196babefd47b80dc","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-util/9.5/asm-util-9.5.jar"}},{"id":"org.ow2.asm:asm-analysis:9.5","name":"Minecraft Forge (asm-analysis)","type":"Library","artifact":{"size":33978,"MD5":"4df0adafc78ebba404d4037987d36b61","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-analysis/9.5/asm-analysis-9.5.jar"}},{"id":"net.minecraftforge:accesstransformers:8.0.4","name":"Minecraft Forge (accesstransformers)","type":"Library","artifact":{"size":77756,"MD5":"f0d3533f9437ba4428eaee9f28f7ecd9","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/accesstransformers/8.0.4/accesstransformers-8.0.4.jar"}},{"id":"org.antlr:antlr4-runtime:4.9.1","name":"Minecraft Forge (antlr4-runtime)","type":"Library","artifact":{"size":337868,"MD5":"0dcc4b860d5d8d2852ab94d58c56ca2d","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/antlr/antlr4-runtime/4.9.1/antlr4-runtime-4.9.1.jar"}},{"id":"net.minecraftforge:eventbus:6.0.3","name":"Minecraft Forge (eventbus)","type":"Library","artifact":{"size":52624,"MD5":"7c93693a70962a1c1893e3c058dca5cf","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/eventbus/6.0.3/eventbus-6.0.3.jar"}},{"id":"net.minecraftforge:forgespi:7.0.0","name":"Minecraft Forge (forgespi)","type":"Library","artifact":{"size":30602,"MD5":"b1bf9c45a33a749ed58b15d679925243","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forgespi/7.0.0/forgespi-7.0.0.jar"}},{"id":"net.minecraftforge:coremods:5.0.1","name":"Minecraft Forge (coremods)","type":"Library","artifact":{"size":24199,"MD5":"8c590051c5d65e098ca7c2e3ddf803a8","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/coremods/5.0.1/coremods-5.0.1.jar"}},{"id":"cpw.mods:modlauncher:10.0.8","name":"Minecraft Forge (modlauncher)","type":"Library","artifact":{"size":130263,"MD5":"3e171edcc29e762d2ecafffabb9e9341","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/modlauncher/10.0.8/modlauncher-10.0.8.jar"}},{"id":"net.minecraftforge:unsafe:0.2.0","name":"Minecraft Forge (unsafe)","type":"Library","artifact":{"size":2834,"MD5":"2d1016ebe4c1a63dd50a59d26bd12db1","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/unsafe/0.2.0/unsafe-0.2.0.jar"}},{"id":"net.minecraftforge:mergetool:1.1.5:api","name":"Minecraft Forge (mergetool)","type":"Library","artifact":{"size":2572,"MD5":"8df9c5bf87d004ddb884eca99bc2a4b1","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/mergetool/1.1.5/mergetool-1.1.5-api.jar"}},{"id":"com.electronwill.night-config:core:3.6.4","name":"Minecraft Forge (core)","type":"Library","artifact":{"size":199834,"MD5":"d83ab07267e402131fb93d899a57f5cd","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/electronwill/night-config/core/3.6.4/core-3.6.4.jar"}},{"id":"com.electronwill.night-config:toml:3.6.4","name":"Minecraft Forge (toml)","type":"Library","artifact":{"size":31816,"MD5":"bc95d0709fff2164b01fd09fbc988be8","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/electronwill/night-config/toml/3.6.4/toml-3.6.4.jar"}},{"id":"org.apache.maven:maven-artifact:3.8.5","name":"Minecraft Forge (maven-artifact)","type":"Library","artifact":{"size":58077,"MD5":"ce473b0d9fbfd10fe147f03fe8707d67","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/maven/maven-artifact/3.8.5/maven-artifact-3.8.5.jar"}},{"id":"net.jodah:typetools:0.8.3","name":"Minecraft Forge (typetools)","type":"Library","artifact":{"size":15425,"MD5":"ee4596e1413cc1a5e080c971d6064753","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/jodah/typetools/0.8.3/typetools-0.8.3.jar"}},{"id":"net.minecrell:terminalconsoleappender:1.2.0","name":"Minecraft Forge (terminalconsoleappender)","type":"Library","artifact":{"size":15977,"MD5":"679363fa893293791e55a21f81342f87","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecrell/terminalconsoleappender/1.2.0/terminalconsoleappender-1.2.0.jar"}},{"id":"org.jline:jline-reader:3.12.1","name":"Minecraft Forge (jline-reader)","type":"Library","artifact":{"size":150765,"MD5":"a2e7b012cd9802f83321187409174a94","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/jline/jline-reader/3.12.1/jline-reader-3.12.1.jar"}},{"id":"org.jline:jline-terminal:3.12.1","name":"Minecraft Forge (jline-terminal)","type":"Library","artifact":{"size":211712,"MD5":"3c52be5ab5e3847be6e62269de924cb0","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/jline/jline-terminal/3.12.1/jline-terminal-3.12.1.jar"}},{"id":"org.spongepowered:mixin:0.8.5","name":"Minecraft Forge (mixin)","type":"Library","artifact":{"size":1089277,"MD5":"19b3a2ae9e445a6e626fd7d1648cfcb8","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar"}},{"id":"org.openjdk.nashorn:nashorn-core:15.3","name":"Minecraft Forge (nashorn-core)","type":"Library","artifact":{"size":2167288,"MD5":"91e98c20afa1090c344229ce28b4c53f","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/openjdk/nashorn/nashorn-core/15.3/nashorn-core-15.3.jar"}},{"id":"net.minecraftforge:JarJarSelector:0.3.19","name":"Minecraft Forge (JarJarSelector)","type":"Library","artifact":{"size":17374,"MD5":"2bb6cbe0e6c6fbcd8f92d2e6b1ea678e","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarSelector/0.3.19/JarJarSelector-0.3.19.jar"}},{"id":"net.minecraftforge:JarJarMetadata:0.3.19","name":"Minecraft Forge (JarJarMetadata)","type":"Library","artifact":{"size":15895,"MD5":"9633546d299d4282ca68d10582be1c8f","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarMetadata/0.3.19/JarJarMetadata-0.3.19.jar"}},{"id":"cpw.mods:bootstraplauncher:1.1.2","name":"Minecraft Forge (bootstraplauncher)","type":"Library","artifact":{"size":8284,"MD5":"48f5255aa337344c467c0150d347813d","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/bootstraplauncher/1.1.2/bootstraplauncher-1.1.2.jar"}},{"id":"net.minecraftforge:JarJarFileSystems:0.3.19","name":"Minecraft Forge (JarJarFileSystems)","type":"Library","artifact":{"size":32195,"MD5":"c2be1a88b63eb1b58b00ab6e498cd97d","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarFileSystems/0.3.19/JarJarFileSystems-0.3.19.jar"}},{"id":"net.minecraftforge:fmlloader:1.20-46.0.2","name":"Minecraft Forge (fmlloader)","type":"Library","artifact":{"size":261464,"MD5":"99825c1cc845cf542284efa122e1b96e","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/fmlloader/1.20-46.0.2/fmlloader-1.20-46.0.2.jar"}}]}]},{"id":"Fabric-Demo-1.20.4","name":"Fabric-Demo (Minecraft 1.20.4)","description":"Fabric-Demo Running Minecraft 1.20.4 (Fabric v0.15.6)","icon":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/Fabric-Demo-1.20.4.png","version":"1.0.0","address":"localhost:25565","minecraftVersion":"1.20.4","discord":{"shortId":"","largeImageText":"","largeImageKey":""},"mainServer":false,"autoconnect":false,"modules":[{"id":"net.fabricmc:fabric-loader:0.15.6","name":"Fabric (fabric-loader)","type":"Fabric","artifact":{"size":1321135,"MD5":"744dbc7c568d7e782e6616637a14f6b3","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/fabricmc/fabric-loader/0.15.6/fabric-loader-0.15.6.jar"},"subModules":[{"id":"1.20.4-fabric-0.15.6","name":"Fabric (version.json)","type":"VersionManifest","artifact":{"size":2847,"MD5":"6052ebb6acbba734c83fd3a09e67d31e","url":"https://helios-files.geekcorner.eu.org/repo/versions/1.20.4-fabric-0.15.6/1.20.4-fabric-0.15.6.json"}},{"id":"org.ow2.asm:asm:9.6","name":"Fabric (asm)","type":"Library","artifact":{"size":123598,"MD5":"6f8bccf756f170d4185bb24c8c2d2020","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm/9.6/asm-9.6.jar"}},{"id":"org.ow2.asm:asm-analysis:9.6","name":"Fabric (asm-analysis)","type":"Library","artifact":{"size":34041,"MD5":"31c84ef7cc893fb278952ae2d6a2674f","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-analysis/9.6/asm-analysis-9.6.jar"}},{"id":"org.ow2.asm:asm-commons:9.6","name":"Fabric (asm-commons)","type":"Library","artifact":{"size":72194,"MD5":"9e317c75534bd1da8c00a67c618ab288","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-commons/9.6/asm-commons-9.6.jar"}},{"id":"org.ow2.asm:asm-tree:9.6","name":"Fabric (asm-tree)","type":"Library","artifact":{"size":51935,"MD5":"6062608f1a98afe1e853d01fa1221a9e","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-tree/9.6/asm-tree-9.6.jar"}},{"id":"org.ow2.asm:asm-util:9.6","name":"Fabric (asm-util)","type":"Library","artifact":{"size":91131,"MD5":"bd3bc1c176a787373e9a031073c9574b","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-util/9.6/asm-util-9.6.jar"}},{"id":"net.fabricmc:sponge-mixin:0.12.5+mixin.0.8.5","name":"Fabric (sponge-mixin)","type":"Library","artifact":{"size":1451874,"MD5":"4cc3ff559cafdc70d9ed80e869d447f0","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/fabricmc/sponge-mixin/0.12.5+mixin.0.8.5/sponge-mixin-0.12.5+mixin.0.8.5.jar"}},{"id":"net.fabricmc:intermediary:1.20.4","name":"Fabric (intermediary)","type":"Library","artifact":{"size":608319,"MD5":"3a1cade1634c518db6147c9771686ea9","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/fabricmc/intermediary/1.20.4/intermediary-1.20.4.jar"}}]},{"id":"generated.fabricmod:iris:1.6.15@jar","name":"Iris","type":"FabricMod","artifact":{"size":2416339,"url":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/fabricmods/required/iris-mc1.20.4-1.6.15.jar","MD5":"a2f7578c1652222e8c703831b604150e"}},{"id":"me.jellysquid.mods:lithium:0.12.1@jar","name":"Lithium","type":"FabricMod","artifact":{"size":716022,"url":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/fabricmods/required/lithium-fabric-mc1.20.4-0.12.1.jar","MD5":"83da6729d5402dc42eca740a96f97346"}},{"id":"com.terraformersmc:modmenu:9.0.0@jar","name":"Mod Menu","type":"FabricMod","artifact":{"size":724484,"url":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/fabricmods/optionalon/modmenu-9.0.0.jar","MD5":"7eb06d459405ed6c87b16f4c5be4493d"},"required":{"value":false}},{"id":"me.jellysquid.mods:sodium:0.5.8+mc1.20.4@jar","name":"Sodium","type":"FabricMod","artifact":{"size":949085,"url":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/fabricmods/required/sodium-fabric-0.5.8+mc1.20.4.jar","MD5":"d7753a50ca37f50abb10465a9a425dac"}},{"id":"generated.library:fabric:api-0.95.4+1.20.4@jar","name":"fabric","type":"Library","artifact":{"size":2141107,"url":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/libraries/fabric-api-0.95.4+1.20.4.jar","MD5":"e16ba73c962937a8a11515ecfd47d163"}}]},{"id":"WesterosCraft-Demo-1.12.2","name":"WesterosCraft-Demo (Minecraft 1.12.2)","description":"WesterosCraft-Demo Running Minecraft 1.12.2 (Forge v14.23.5.2859)","icon":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/WesterosCraft-Demo-1.12.2.png","version":"2.1.6","address":"mc.westeroscraft.com:25565","minecraftVersion":"1.12.2","discord":{"shortId":"WesterosCraft Demo","largeImageKey":"westeroscraft","largeImageText":"Change me through Nebula"},"mainServer":false,"autoconnect":false,"modules":[{"id":"net.minecraftforge:forge:1.12.2-14.23.5.2859:universal","name":"Minecraft Forge","type":"ForgeHosted","artifact":{"size":4466108,"MD5":"fda01cd3cae80c2c6348ac3fc26e0af8","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forge/1.12.2-14.23.5.2859/forge-1.12.2-14.23.5.2859-universal.jar"},"subModules":[{"id":"1.12.2-14.23.5.2859","name":"Minecraft Forge (version.json)","type":"VersionManifest","artifact":{"size":12345,"MD5":"1959bb357e54a9666dd80d744b524639","url":"https://helios-files.geekcorner.eu.org/repo/versions/1.12.2-forge-14.23.5.2859/1.12.2-forge-14.23.5.2859.json"}},{"id":"org.ow2.asm:asm-debug-all:5.2@jar","name":"Minecraft Forge (asm-debug-all)","type":"Library","artifact":{"size":387903,"MD5":"fe5f20404ccdee9769ef05dc4b47ba98","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-debug-all/5.2/asm-debug-all-5.2.jar"}},{"id":"net.minecraft:launchwrapper:1.12@jar","name":"Minecraft Forge (launchwrapper)","type":"Library","artifact":{"size":32999,"MD5":"934b2d91c7c5be4a49577c9e6b40e8da","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/launchwrapper/1.12/launchwrapper-1.12.jar"}},{"id":"org.jline:jline:3.5.1@jar","name":"Minecraft Forge (jline)","type":"Library","artifact":{"size":614590,"MD5":"4c20d2879ed2bd75a0771ce29e89f6b0","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/jline/jline/3.5.1/jline-3.5.1.jar"}},{"id":"com.typesafe.akka:akka-actor_2.11:2.3.3@jar","name":"Minecraft Forge (akka-actor_2.11)","type":"Library","artifact":{"size":2514991,"MD5":"541440ca0819ebada47d6d1a8b3ee9e1","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/typesafe/akka/akka-actor_2.11/2.3.3/akka-actor_2.11-2.3.3.jar"}},{"id":"com.typesafe:config:1.2.1@jar","name":"Minecraft Forge (config)","type":"Library","artifact":{"size":219554,"MD5":"3aaf3c6e76a68e732c17d4a7e9877d81","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/typesafe/config/1.2.1/config-1.2.1.jar"}},{"id":"org.scala-lang:scala-actors-migration_2.11:1.1.0@jar","name":"Minecraft Forge (scala-actors-migration_2.11)","type":"Library","artifact":{"size":58018,"MD5":"f5e79398daa1806f8b17311a3c782723","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-actors-migration_2.11/1.1.0/scala-actors-migration_2.11-1.1.0.jar"}},{"id":"org.scala-lang:scala-compiler:2.11.1@jar","name":"Minecraft Forge (scala-compiler)","type":"Library","artifact":{"size":13449765,"MD5":"06030143bf86ca896fb6ccfd679b5760","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-compiler/2.11.1/scala-compiler-2.11.1.jar"}},{"id":"org.scala-lang.plugins:scala-continuations-library_2.11:1.0.2_mc@jar","name":"Minecraft Forge (scala-continuations-library_2.11)","type":"Library","artifact":{"size":25365,"MD5":"004d7007abbcee858d3ca2c3ccbcbaab","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/plugins/scala-continuations-library_2.11/1.0.2_mc/scala-continuations-library_2.11-1.0.2_mc.jar"}},{"id":"org.scala-lang.plugins:scala-continuations-plugin_2.11.1:1.0.2_mc@jar","name":"Minecraft Forge (scala-continuations-plugin_2.11.1)","type":"Library","artifact":{"size":206575,"MD5":"359c4a6743a082c689039482eed78670","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/plugins/scala-continuations-plugin_2.11.1/1.0.2_mc/scala-continuations-plugin_2.11.1-1.0.2_mc.jar"}},{"id":"org.scala-lang:scala-library:2.11.1@jar","name":"Minecraft Forge (scala-library)","type":"Library","artifact":{"size":5538130,"MD5":"1d88f665219e6006c5dd82d71c525c0f","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-library/2.11.1/scala-library-2.11.1.jar"}},{"id":"org.scala-lang:scala-parser-combinators_2.11:1.0.1@jar","name":"Minecraft Forge (scala-parser-combinators_2.11)","type":"Library","artifact":{"size":419701,"MD5":"4e694499c965af4a02599c99d4f0b196","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-parser-combinators_2.11/1.0.1/scala-parser-combinators_2.11-1.0.1.jar"}},{"id":"org.scala-lang:scala-reflect:2.11.1@jar","name":"Minecraft Forge (scala-reflect)","type":"Library","artifact":{"size":4372892,"MD5":"7878fac044e4e4b576bb35a77ccc34fc","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-reflect/2.11.1/scala-reflect-2.11.1.jar"}},{"id":"org.scala-lang:scala-swing_2.11:1.0.1@jar","name":"Minecraft Forge (scala-swing_2.11)","type":"Library","artifact":{"size":726500,"MD5":"1009d69e4948045383f2a7a334348af5","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-swing_2.11/1.0.1/scala-swing_2.11-1.0.1.jar"}},{"id":"org.scala-lang:scala-xml_2.11:1.0.2@jar","name":"Minecraft Forge (scala-xml_2.11)","type":"Library","artifact":{"size":648679,"MD5":"c2d7e66495afe14545c31b21e99879ef","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar"}},{"id":"lzma:lzma:0.0.1@jar","name":"Minecraft Forge (lzma)","type":"Library","artifact":{"size":5762,"MD5":"a3e3c3186e41c4a1a3027ba2bb23cdc6","url":"https://helios-files.geekcorner.eu.org/repo/lib/lzma/lzma/0.0.1/lzma-0.0.1.jar"}},{"id":"java3d:vecmath:1.5.2@jar","name":"Minecraft Forge (vecmath)","type":"Library","artifact":{"size":318956,"MD5":"e5d2b7f46c4800a32f62ce75676a5710","url":"https://helios-files.geekcorner.eu.org/repo/lib/java3d/vecmath/1.5.2/vecmath-1.5.2.jar"}},{"id":"net.sf.trove4j:trove4j:3.0.3@jar","name":"Minecraft Forge (trove4j)","type":"Library","artifact":{"size":2523218,"MD5":"8fc4d4e0129244f9fd39650c5f30feb2","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/sf/trove4j/trove4j/3.0.3/trove4j-3.0.3.jar"}},{"id":"org.apache.maven:maven-artifact:3.5.3@jar","name":"Minecraft Forge (maven-artifact)","type":"Library","artifact":{"size":54961,"MD5":"7741ebf29690ee7d9dde9cf4376347fc","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/maven/maven-artifact/3.5.3/maven-artifact-3.5.3.jar"}},{"id":"net.sf.jopt-simple:jopt-simple:5.0.3@jar","name":"Minecraft Forge (jopt-simple)","type":"Library","artifact":{"size":78175,"MD5":"0a5ec84e23df9d7cfb4063bc55f2744c","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/sf/jopt-simple/jopt-simple/5.0.3/jopt-simple-5.0.3.jar"}},{"id":"org.apache.logging.log4j:log4j-api:2.15.0@jar","name":"Minecraft Forge (log4j-api)","type":"Library","artifact":{"size":301804,"MD5":"a9ccfa7e3382dd2b9e0647a43d8286d7","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/logging/log4j/log4j-api/2.15.0/log4j-api-2.15.0.jar"}},{"id":"org.apache.logging.log4j:log4j-core:2.15.0@jar","name":"Minecraft Forge (log4j-core)","type":"Library","artifact":{"size":1789769,"MD5":"81e0433ae00602c0e4d00424d213b0ab","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/logging/log4j/log4j-core/2.15.0/log4j-core-2.15.0.jar"}},{"id":"org.apache.logging.log4j:log4j-slf4j18-impl:2.15.0@jar","name":"Minecraft Forge (log4j-slf4j18-impl)","type":"Library","artifact":{"size":21223,"MD5":"196442f1bdde4dbb0f576eed616e21b0","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/logging/log4j/log4j-slf4j18-impl/2.15.0/log4j-slf4j18-impl-2.15.0.jar"}}]},{"id":"com.creativemd:creativecore:1.10@jar","name":"CreativeCore","type":"ForgeMod","artifact":{"size":1290012,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/CreativeCore_v1.10.65_mc1.12.2.jar","MD5":"e6e4ad48ce7d4f0cd47a5d106dbad745"}},{"id":"org.orecruncher:dsurround:1.12.2-3.6.1.0@jar","name":"Dynamic Surroundings","type":"ForgeMod","artifact":{"size":15929762,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/DynamicSurroundings-1.12.2-3.6.1.0.jar","MD5":"1a0d50a34941865ff091a492e0612502"}},{"id":"sekelsta.horse_colors:horse_colors:1.12.2-1.2.6@jar","name":"Realistic Horse Genetics","type":"ForgeMod","artifact":{"size":711220,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/horse_colors-1.12.2-1.3.6.a.jar","MD5":"073030905d404994027f44839279cb3a"}},{"id":"journeymap:journeymap:1.12.2-5.7.1@jar","name":"JourneyMap","type":"ForgeMod","artifact":{"size":6990968,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/journeymap-1.12.2-5.7.1.jar","MD5":"8160b970fc14cff30dff2785bb9f964c"}},{"id":"net.optifine:optifine:1.12.2_HD_U_G5@jar","name":"OptiFine 1.12.2_HD_U_G5","type":"ForgeMod","artifact":{"size":2669107,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/OptiFine_1.12.2_HD_U_G5.jar","MD5":"54e561e441192cf009803ae95873c5d0"}},{"id":"org.orecruncher:orelib:1.12.2-3.6.0.1@jar","name":"OreLib Support Mod","type":"ForgeMod","artifact":{"size":341728,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/OreLib-1.12.2-3.6.0.1.jar","MD5":"00cce3b240365458adcad3e77782358a"}},{"id":"com.shynieke:statues:0.8.9.2@jar","name":"Statues mod","type":"ForgeMod","artifact":{"size":1811654,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/statues-1.12.X-0.8.9.2.jar","MD5":"a8c31d02a76d408ba19aabf832038087"}},{"id":"com.creativemd.opf:opframe:1.7.0@jar","name":"WCOnlinePictureFrame","type":"ForgeMod","artifact":{"size":64116,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/WCOnlinePictureFrame-1.7.0-beta-1-forge-1.12.2.jar","MD5":"1f9cd1d59f899ea895fd32aee42ed72f"}},{"id":"com.westeroscraft:westerosblocks:4.3.4-126@jar","name":"WesterosBlocks","type":"ForgeMod","artifact":{"size":19056312,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/WesterosBlocks-4.3.4-forge-1.12.2.jar","MD5":"fa85607024a470146e9ad088fe2c847c"}},{"id":"com.westeroscraft:westerosentities:1.12.2-1.3.0@jar","name":"WesterosEntities","type":"ForgeMod","artifact":{"size":287218,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/westerosentities-1.12.2-b6.jar","MD5":"a02be14d86cb9748892a92778b194f4c"}},{"id":"betterfoliage.cfg","name":"betterfoliage.cfg","type":"File","artifact":{"size":7085,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/betterfoliage.cfg","MD5":"99b9aa746613296250a4adc35a27d749","path":"config/betterfoliage.cfg"}},{"id":"creativecore-client.json","name":"creativecore-client.json","type":"File","artifact":{"size":221,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/creativecore-client.json","MD5":"057bbac7b2b20176faa1f70d4f1b2abf","path":"config/creativecore-client.json"}},{"id":"dsurround.cfg","name":"dsurround.cfg","type":"File","artifact":{"size":15280,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/dsurround/dsurround.cfg","MD5":"005809133dcac77ebfaba07c5b9fa334","path":"config/dsurround/dsurround.cfg"}},{"id":"forge.cfg","name":"forge.cfg","type":"File","artifact":{"size":3865,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/forge.cfg","MD5":"f171671570ac5e1c6ce1717ff70c8633","path":"config/forge.cfg"}},{"id":"forgeChunkLoading.cfg","name":"forgeChunkLoading.cfg","type":"File","artifact":{"size":2155,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/forgeChunkLoading.cfg","MD5":"255d6e9355788641e30db10e3c0b8551","path":"config/forgeChunkLoading.cfg"}},{"id":"horse_colors.cfg","name":"horse_colors.cfg","type":"File","artifact":{"size":4336,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/horse_colors.cfg","MD5":"a4976010ba4938857239ef37f9a491ed","path":"config/horse_colors.cfg"}},{"id":"journeymap_ModInfo.cfg","name":"journeymap_ModInfo.cfg","type":"File","artifact":{"size":370,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/journeymap_ModInfo.cfg","MD5":"70f47abc0084802ab02ff3a3d769e244","path":"config/journeymap_ModInfo.cfg"}},{"id":"journeymap_server.cfg","name":"journeymap_server.cfg","type":"File","artifact":{"size":615,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/journeymap_server.cfg","MD5":"704e8c040987675a9ac814abe9b7cdc6","path":"config/journeymap_server.cfg"}},{"id":"opframe-client.json","name":"opframe-client.json","type":"File","artifact":{"size":32,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/opframe-client.json","MD5":"012d93890dbdefeb4343f8af53dc3b91","path":"config/opframe-client.json"}},{"id":"orelib.cfg","name":"orelib.cfg","type":"File","artifact":{"size":604,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/orelib.cfg","MD5":"70f8e6b6d17a73fe970a92f72cfbf21c","path":"config/orelib.cfg"}},{"id":"splash.properties","name":"splash.properties","type":"File","artifact":{"size":358,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/splash.properties","MD5":"b5ac94dbd3b3529fbb233f9df0e14795","path":"config/splash.properties"}},{"id":"statues.cfg","name":"statues.cfg","type":"File","artifact":{"size":6931,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/statues.cfg","MD5":"3aae8ac0d253b88aedaaada1aa45a0c5","path":"config/statues.cfg"}},{"id":"westerosblocks.cfg","name":"westerosblocks.cfg","type":"File","artifact":{"size":113,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/westerosblocks.cfg","MD5":"a9dcb36d082866117ec7dc63ff689b85","path":"config/westerosblocks.cfg"}},{"id":"options.txt","name":"options.txt","type":"File","artifact":{"size":2788,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/options.txt","path":"options.txt"}},{"id":"WesterosCraft-v1.12.2-29.zip","name":"WesterosCraft-v1.12.2-29.zip","type":"File","artifact":{"size":55905310,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/resourcepacks/WesterosCraft-v1.12.2-29.zip","MD5":"eb3c794aafe44c7ea7d3407a10553bd4","path":"resourcepacks/WesterosCraft-v1.12.2-29.zip"}},{"id":"servers.dat","name":"servers.dat","type":"File","artifact":{"size":84,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/servers.dat","MD5":"71d99e229d7d2b8d2a6423e46832a4b8","path":"servers.dat"}},{"id":"Chocapic13_V9_Extreme.zip","name":"Chocapic13_V9_Extreme.zip","type":"File","artifact":{"size":3134623,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/shaderpacks/Chocapic13_V9_Extreme.zip","MD5":"2a5ec76ad7acc47005e32cc1e5da1e41","path":"shaderpacks/Chocapic13_V9_Extreme.zip"}},{"id":"SEUS-Renewed-v1.0.1.zip","name":"SEUS-Renewed-v1.0.1.zip","type":"File","artifact":{"size":7062638,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/shaderpacks/SEUS-Renewed-v1.0.1.zip","MD5":"8faeb04d9953c9b10b0794acafc4c1aa","path":"shaderpacks/SEUS-Renewed-v1.0.1.zip"}}]}]} From 3cdad516c9afaa90b055ab7bf114ede4bdc99191 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 16:26:54 +0200 Subject: [PATCH 20/52] Add debug mode option to Athena's Shield configuration Introduced an option to activate debug mode in Athena's Shield. Updated relevant JavaScript files and configuration to handle the debug mode setting. Added a new method for retrieving the debug status within the parserAthShield.js class. --- app/assets/athshield/athshield.js | 47 +++++++++++++----------- app/assets/athshield/parserAthShield.js | 5 +++ app/assets/athshield/variables.athshield | 3 +- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/app/assets/athshield/athshield.js b/app/assets/athshield/athshield.js index e6dc1475..15b7764c 100644 --- a/app/assets/athshield/athshield.js +++ b/app/assets/athshield/athshield.js @@ -37,33 +37,38 @@ function startCLI() { if (answer.toLowerCase() === 'yes') { config.athenaShieldActivated = true - rl.question('Would you like to hide or block the menu? (hide/block): ', (menuAnswer) => { - if (menuAnswer.trim().startsWith('//')) { - console.log('This is a comment; the line is ignored.') - rl.close() - return - } + rl.question('Would you like to activate debug mode? (yes/no): ', (debugAnswer) => { + config.debug = debugAnswer.toLowerCase() === 'yes' // Set debug to true or false - if (menuAnswer.toLowerCase() === 'hide') { - config.menuVisibility = 'hidden' // Change to 'hidden' - console.log('Athena\'s Shield activated. Menu hidden.') - } else if (menuAnswer.toLowerCase() === 'block') { - config.menuVisibility = 'blocked' // Change to 'blocked' - console.log('Athena\'s Shield activated. Menu blocked.') - } else { - console.log('Invalid option for the menu.') - rl.close() - return - } + rl.question('Would you like to hide or block the menu? (hide/block): ', (menuAnswer) => { + if (menuAnswer.trim().startsWith('//')) { + console.log('This is a comment; the line is ignored.') + rl.close() + return + } - // Save the modified configuration - saveConfig(config) - rl.close() + if (menuAnswer.toLowerCase() === 'hide') { + config.menuVisibility = 'hidden' // Set to 'hidden' + console.log('Athena\'s Shield activated. Menu hidden.') + } else if (menuAnswer.toLowerCase() === 'block') { + config.menuVisibility = 'blocked' // Set to 'blocked' + console.log('Athena\'s Shield activated. Menu blocked.') + } else { + console.log('Invalid option for the menu.') + rl.close() + return + } + + // Save the modified configuration + saveConfig(config) + rl.close() + }) }) } else if (answer.toLowerCase() === 'no') { console.log('Athena\'s Shield not activated. Closing the CLI.') config.athenaShieldActivated = false - config.menuVisibility = 'visible' // Reset to default value + config.menuVisibility = 'visible' // Reset to default + config.debug = 'false' // Save the modified configuration saveConfig(config) diff --git a/app/assets/athshield/parserAthShield.js b/app/assets/athshield/parserAthShield.js index de3c9a6c..336e0d4f 100644 --- a/app/assets/athshield/parserAthShield.js +++ b/app/assets/athshield/parserAthShield.js @@ -25,6 +25,11 @@ class AthenaShield { get type() { return this.config.menuVisibility } + + // Récupérer le mode debug + get debug() { + return this.config.debug + } } // Exporter une instance de la classe diff --git a/app/assets/athshield/variables.athshield b/app/assets/athshield/variables.athshield index 19610b1f..b7c29b1a 100644 --- a/app/assets/athshield/variables.athshield +++ b/app/assets/athshield/variables.athshield @@ -1,4 +1,5 @@ { "athenaShieldActivated": false, - "menuVisibility": "visible" + "menuVisibility": "visible", + "debug": "false" } \ No newline at end of file From 485facf1f18bad837f9faada1ace97020b8dfdc8 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 16:27:25 +0200 Subject: [PATCH 21/52] Enable debug logging conditionally for AthShield Wrapped several logging statements related to module identity extraction and validation with a conditional check on the athShield.debug flag. This ensures that detailed logging information is recorded only when debugging is enabled, optimizing performance and log clarity. --- app/assets/js/scripts/landing.js | 65 ++++++++++++++++---------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index df70a5f9..e4d95570 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -30,6 +30,7 @@ const { // Internal Requirements const DiscordWrapper = require('./assets/js/discordwrapper') const ProcessBuilder = require('./assets/js/processbuilder') +const crypto = require('crypto') const fs = require('fs') // Launch Elements @@ -525,32 +526,22 @@ async function dlAsync(login = true) { 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(Lang.queryJS('landing.dlAsync.AthShield.distributionIdentityError', { - 'moduleName': mdl.rawModule.name, - 'moduleIdentity': modIdentity - })) + 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) => { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': 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(Lang.queryJS('landing.dlAsync.AthShield.manifestIdentityFound', { - 'filePath': filePath, - 'identityLine': identityLine - })) - return identityLine.split(':')[1].trim() - } + if (athShield.debug) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': filePath})) } // Fall back to a hash if no identity is found @@ -558,10 +549,13 @@ async function dlAsync(login = true) { const hashSum = crypto.createHash('md5') // Use MD5 to match the distribution configuration hashSum.update(fileBuffer) const hash = hashSum.digest('hex') - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundInManifest', { - 'filePath': filePath, - 'hash': hash - })) + if (athShield.debug) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundUsingHash', { + 'filePath': filePath, + 'hash': hash + })) + } + return hash } @@ -585,18 +579,23 @@ async function dlAsync(login = true) { if (expectedIdentity) { const modIdentity = extractModIdentity(modPath) - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', { - 'expectedIdentity': expectedIdentity, - 'mod': mod, - 'modIdentity': modIdentity - })) - - if (modIdentity !== expectedIdentity) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.AthShield.modIdentityMismatchError', { - 'mod': mod, + 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 } From 33788d4086f9afafab9f779db6c31246419d3492 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 16:27:50 +0200 Subject: [PATCH 22/52] Update REMOTE_DISTRO_URL to new API endpoint Switched the distribution URL to a more reliable API endpoint to improve stability and performance. The new URL is 'https://api.skym-mc.fr/api/v1/servers/distro', which replaces the old one. --- app/assets/js/distromanager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/js/distromanager.js b/app/assets/js/distromanager.js index 8ae8ca0c..c8ef79af 100644 --- a/app/assets/js/distromanager.js +++ b/app/assets/js/distromanager.js @@ -4,7 +4,9 @@ const ConfigManager = require('./configmanager') // Old WesterosCraft url. // exports.REMOTE_DISTRO_URL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json' -exports.REMOTE_DISTRO_URL = 'https://helios-files.geekcorner.eu.org/distribution.json' +// https://helios-files.geekcorner.eu.org/distribution.json + +exports.REMOTE_DISTRO_URL = 'https://api.skym-mc.fr/api/v1/servers/distro' const api = new DistributionAPI( ConfigManager.getLauncherDirectory(), From 84b4ba91ed6c425f8c41271c83c8606635808fcc Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 16:28:10 +0200 Subject: [PATCH 23/52] Refactor identity check error message Simplify the log message for identity not found in the manifest by combining it with the hash usage statement. This improves readability and reduces redundancy in the code. --- app/assets/lang/en_US.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/lang/en_US.toml b/app/assets/lang/en_US.toml index 33a618c9..d1a8f062 100644 --- a/app/assets/lang/en_US.toml +++ b/app/assets/lang/en_US.toml @@ -218,8 +218,7 @@ waintingLaunchingGame = "Waiting for game window..." [js.landing.dlAsync.AthShield] distributionIdentityError = "Expected Identity from Distribution for {moduleName}: {moduleIdentity}." modIdentityExtraction = "Extracting identity for mod at: {filePath}." -manifestIdentityFound = "Found identity in manifest for {filePath}: {identityLine}" -identityNotFoundInManifest = "No identity found in manifest for {filePath}, using hash: {hash}" +identityNotFoundUsingHash = "No identity found in manifest for {filePath}, using hash: {hash}" startingModValidation = "Starting mod validation..." modValidationBypassed = "Skipping validation for excluded mod: {mod}" validatingMod = "Validating mod: {mod}" From f366b8b86a778e6c935eb277552083b25030fac3 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 16:28:41 +0200 Subject: [PATCH 24/52] Update distribution URL in distromanager.js Revert distribution URL to 'https://helios-files.geekcorner.eu.org/distribution.json'. This change ensures compatibility with the older distribution endpoint and corrects the previous, unintended URL. --- app/assets/js/distromanager.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/js/distromanager.js b/app/assets/js/distromanager.js index c8ef79af..8ae8ca0c 100644 --- a/app/assets/js/distromanager.js +++ b/app/assets/js/distromanager.js @@ -4,9 +4,7 @@ const ConfigManager = require('./configmanager') // Old WesterosCraft url. // exports.REMOTE_DISTRO_URL = 'http://mc.westeroscraft.com/WesterosCraftLauncher/distribution.json' -// https://helios-files.geekcorner.eu.org/distribution.json - -exports.REMOTE_DISTRO_URL = 'https://api.skym-mc.fr/api/v1/servers/distro' +exports.REMOTE_DISTRO_URL = 'https://helios-files.geekcorner.eu.org/distribution.json' const api = new DistributionAPI( ConfigManager.getLauncherDirectory(), From 50ad0e8a91854868c340ce10f4459ea570a3338b Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 16:46:05 +0200 Subject: [PATCH 25/52] Enable detailed debug logging for mod identity validation This commit refactors the mod identity extraction and validation process to include detailed debug logs, which are conditionally logged based on the `athShield.debug` flag. It also updates the import statements and correctly references the `ConfigManager.getNameDataPath` function for error messages. --- ajouts/version code final/dlAsync.js | 72 ++++++++++++++-------------- ajouts/version code final/landing.js | 71 +++++++++++++-------------- 2 files changed, 71 insertions(+), 72 deletions(-) diff --git a/ajouts/version code final/dlAsync.js b/ajouts/version code final/dlAsync.js index 4cd8457c..4545a075 100644 --- a/ajouts/version code final/dlAsync.js +++ b/ajouts/version code final/dlAsync.js @@ -9,9 +9,13 @@ * @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 discovered: 0 + * @Bugs discovereds: 0 * @Athena's Shield * @Sandro642 */ @@ -85,32 +89,22 @@ async function dlAsync(login = true) { 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(Lang.queryJS('landing.dlAsync.AthShield.distributionIdentityError', { - 'moduleName': mdl.rawModule.name, - 'moduleIdentity': modIdentity - })) + 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) => { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': 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(Lang.queryJS('landing.dlAsync.AthShield.manifestIdentityFound', { - 'filePath': filePath, - 'identityLine': identityLine - })) - return identityLine.split(':')[1].trim() - } + if (athShield.debug) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': filePath})) } // Fall back to a hash if no identity is found @@ -118,10 +112,13 @@ async function dlAsync(login = true) { const hashSum = crypto.createHash('md5') // Use MD5 to match the distribution configuration hashSum.update(fileBuffer) const hash = hashSum.digest('hex') - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundInManifest', { - 'filePath': filePath, - 'hash': hash - })) + if (athShield.debug) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundUsingHash', { + 'filePath': filePath, + 'hash': hash + })) + } + return hash } @@ -145,18 +142,23 @@ async function dlAsync(login = true) { if (expectedIdentity) { const modIdentity = extractModIdentity(modPath) - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', { - 'expectedIdentity': expectedIdentity, - 'mod': mod, - 'modIdentity': modIdentity - })) - - if (modIdentity !== expectedIdentity) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.AthShield.modIdentityMismatchError', { - 'mod': mod, + 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 } @@ -173,7 +175,7 @@ async function dlAsync(login = true) { // Perform mod validation before proceeding if (!validateMods()) { - const errorMessage = Lang.queryJS('landing.dlAsync.AthShield.invalidModsDetectedMessage', {'folder': dataPath}) + const errorMessage = Lang.queryJS('landing.dlAsync.AthShield.invalidModsDetectedMessage', {'folder': ConfigManager.getNameDataPath()}) loggerLanding.error(errorMessage) showLaunchFailure(errorMessage, null) return diff --git a/ajouts/version code final/landing.js b/ajouts/version code final/landing.js index 058d1bd8..7637c9ab 100644 --- a/ajouts/version code final/landing.js +++ b/ajouts/version code final/landing.js @@ -30,8 +30,7 @@ const { // Internal Requirements const DiscordWrapper = require('./assets/js/discordwrapper') const ProcessBuilder = require('./assets/js/processbuilder') -const dataPath = require('./assets/js/configmanager') -const athShield = require('./assets/athshield/parserAthShield') +const crypto = require('crypto') const fs = require('fs') // Launch Elements @@ -453,7 +452,7 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { /** * @Reviewed on XX.XX.2024 expires on 01.01.2025 - * @Bugs discovered: 0 + * @Bugs discovereds: 0 * @Athena's Shield * @Sandro642 */ @@ -527,32 +526,22 @@ async function dlAsync(login = true) { 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(Lang.queryJS('landing.dlAsync.AthShield.distributionIdentityError', { - 'moduleName': mdl.rawModule.name, - 'moduleIdentity': modIdentity - })) + 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) => { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': 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(Lang.queryJS('landing.dlAsync.AthShield.manifestIdentityFound', { - 'filePath': filePath, - 'identityLine': identityLine - })) - return identityLine.split(':')[1].trim() - } + if (athShield.debug) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modIdentityExtraction', {'filePath': filePath})) } // Fall back to a hash if no identity is found @@ -560,10 +549,13 @@ async function dlAsync(login = true) { const hashSum = crypto.createHash('md5') // Use MD5 to match the distribution configuration hashSum.update(fileBuffer) const hash = hashSum.digest('hex') - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundInManifest', { - 'filePath': filePath, - 'hash': hash - })) + if (athShield.debug) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.identityNotFoundUsingHash', { + 'filePath': filePath, + 'hash': hash + })) + } + return hash } @@ -587,18 +579,23 @@ async function dlAsync(login = true) { if (expectedIdentity) { const modIdentity = extractModIdentity(modPath) - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', { - 'expectedIdentity': expectedIdentity, - 'mod': mod, - 'modIdentity': modIdentity - })) - - if (modIdentity !== expectedIdentity) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.AthShield.modIdentityMismatchError', { - 'mod': mod, + 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 } @@ -615,7 +612,7 @@ async function dlAsync(login = true) { // Perform mod validation before proceeding if (!validateMods()) { - const errorMessage = Lang.queryJS('landing.dlAsync.AthShield.invalidModsDetectedMessage', {'folder': dataPath}) + const errorMessage = Lang.queryJS('landing.dlAsync.AthShield.invalidModsDetectedMessage', {'folder': ConfigManager.getNameDataPath()}) loggerLanding.error(errorMessage) showLaunchFailure(errorMessage, null) return From a94aa49402aa378a0b442a17cc276cf2b7a33d88 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 16:46:22 +0200 Subject: [PATCH 26/52] Fix typo in comment annotation Corrected the typo "discovereds" to "discovered" in the comment section of the landing.js file. This ensures accuracy and professionalism in the documentation. --- app/assets/js/scripts/landing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index e4d95570..7637c9ab 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -452,7 +452,7 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { /** * @Reviewed on XX.XX.2024 expires on 01.01.2025 - * @Bugs discovered: 0 + * @Bugs discovereds: 0 * @Athena's Shield * @Sandro642 */ From 151557182425cda849fe0f1fe28af83947d64a9e Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 16:51:58 +0200 Subject: [PATCH 27/52] 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 -} From db499844db6b59e965d07a62d54c246ea673ca70 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 17:03:15 +0200 Subject: [PATCH 28/52] Update documentation: Move Athena's Shield docs to /docs Renamed Athena's Shield documentation file and added detailed sections explaining its purpose, key features, and user benefits. Introduced a new security feature for HeliosLauncher, ensuring the integrity of installed mods. --- Athena's Shield.md | 44 ----------------------- docs/Athena's Shield.md | 48 +++++++++++++++++++++++++ docs/Athena_s-Shield-Documentation.pdf | Bin 0 -> 49121 bytes 3 files changed, 48 insertions(+), 44 deletions(-) delete mode 100644 Athena's Shield.md create mode 100644 docs/Athena's Shield.md create mode 100644 docs/Athena_s-Shield-Documentation.pdf diff --git a/Athena's Shield.md b/Athena's Shield.md deleted file mode 100644 index e12ca984..00000000 --- a/Athena's Shield.md +++ /dev/null @@ -1,44 +0,0 @@ -# Documentation : Athena's Shield pour HeliosLauncher - -## Introduction - -**HeliosLauncher** est un lanceur de jeu pour **Minecraft** qui est développé par Daniel Scalzi pour garantir une expérience de jeu sécurisée et optimisée. Pour maintenir l'intégrité du jeu et empêcher l'introduction de mods non autorisés ou altérés, j'ai mis en place un nouveau système de sécurité appelé **Athena's Shield**. - -## Objectif d'Athena's Shield - -Le principal objectif d'Athena's Shield est d'assurer que seuls les mods autorisés et vérifiés sont utilisés avec HeliosLauncher. Ce système vérifie l'intégrité des mods installés pour empêcher toute modification ou ajout de mods malveillants ou non approuvés. - -## Fonctionnalités Clés - -1. **Validation des Mods Installés** : - - Avant de lancer le jeu, Athena's Shield vérifie les mods présents dans le dossier des mods de HeliosLauncher. - - Chaque mod est validé en comparant son nom et son hash (empreinte numérique) avec les données attendues dans la distribution. - - Si un mod ne correspond pas aux critères de validation, le lancement du jeu est arrêté, et un message d'erreur est affiché. - -2. **Vérification au Premier Lancement** : - - Lors du premier lancement de HeliosLauncher, si le dossier des mods est vide, le jeu est lancé sans vérification des mods. - - Si des mods sont détectés lors du premier lancement, leur validité est vérifiée immédiatement pour éviter tout problème. - -3. **Gestion des Modifications** : - - Athena's Shield vérifie également les changements dans les mods. Par exemple, si un mod est supprimé ou remplacé, ou si son nom est modifié, cela sera détecté. - - La vérification des hashs garantit que les mods n'ont pas été modifiés depuis leur téléchargement initial. - -4. **Message d'Erreur et Instructions** : - - En cas de détection de mods non valides ou de modifications non autorisées, le système affiche un message d'erreur clair. - - Les utilisateurs reçoivent des instructions spécifiques pour résoudre les problèmes, telles que supprimer le dossier de mods et redémarrer le lanceur. - -## Avantages pour les Utilisateurs - -- **Sécurité Renforcée** : En empêchant les mods non autorisés et en vérifiant leur intégrité, Athena's Shield protège les utilisateurs contre les mods malveillants. -- **Expérience de Jeu Fiable** : Assure que les mods utilisés sont ceux qui ont été testés et validés, garantissant une expérience de jeu stable et sans problèmes. -- **Simplicité d'Utilisation** : Les utilisateurs sont guidés avec des messages clairs et des instructions en cas de problème, facilitant la résolution des éventuels conflits. - -## Conclusion - -Athena's Shield est une étape importante pour améliorer la sécurité et l'intégrité de HeliosLauncher. En intégrant cette solution, je m'assure que chaque utilisateur de Minecraft profite d'une expérience de jeu sûre et fiable, sans compromis sur la qualité ou la sécurité. - -Pour toute question ou besoin de clarification supplémentaire sur Athena's Shield, n'hésitez pas à me contacter. - -Le seul moyen de passer Athena's Shield est d'avoir fait des études de cryptographie, la copie de signature, modification du Hash. - -> La création et la vérification d'Athena's Shield sont encore en cours d'acheminement vers leur point d'arrivée. \ No newline at end of file diff --git a/docs/Athena's Shield.md b/docs/Athena's Shield.md new file mode 100644 index 00000000..fd19f411 --- /dev/null +++ b/docs/Athena's Shield.md @@ -0,0 +1,48 @@ +**Documentation: Athena's Shield for HeliosLauncher** + +### Introduction +HeliosLauncher is a game launcher for Minecraft developed by Daniel Scalzi, designed to provide a secure and optimized gaming experience. To maintain game integrity and prevent unauthorized or altered mods, I have implemented a new security system called Athena's Shield. + +### Purpose of Athena's Shield +The main purpose of Athena's Shield is to ensure that only authorized and verified mods are used with HeliosLauncher. This system checks the integrity of installed mods to prevent any modifications or additions of malicious or unapproved mods. + +### Key Features + +**Installed Mod Validation:** + +- Before launching the game, Athena's Shield verifies the mods present in the HeliosLauncher mods folder. +- Each mod is validated by comparing its name and hash (digital fingerprint) with expected data in the distribution. +- If a mod does not meet validation criteria, game launch is stopped, and an error message is displayed. + +**First Launch Verification:** + +- On the first launch of HeliosLauncher, if the mods folder is empty, the game is launched without mod verification. +- If mods are detected on the first launch, their validity is checked immediately to prevent issues. + +**Modification Management:** + +- Athena's Shield also verifies changes to mods. For instance, if a mod is deleted, replaced, or renamed, it will be detected. +- Hash verification ensures that mods have not been altered since their initial download. + +**Error Messages and Instructions:** + +- If any invalid mods or unauthorized modifications are detected, the system displays a clear error message. +- Users are provided with specific instructions to resolve issues, such as deleting the mods folder and restarting the launcher. + +### User Benefits + +- **Enhanced Security:** By preventing unauthorized mods and verifying integrity, Athena's Shield protects users from malicious mods. +- **Reliable Gaming Experience:** Ensures that only tested and validated mods are used, guaranteeing a stable and trouble-free gaming experience. +- **Ease of Use:** Users are guided with clear messages and instructions to help resolve any potential conflicts, simplifying problem-solving. + +### Conclusion +Athena's Shield is a significant step toward enhancing the security and integrity of HeliosLauncher. With this integration, I ensure that every Minecraft user enjoys a safe and reliable gaming experience, without compromising on quality or security. + +If you have any questions or need further clarification about Athena's Shield, feel free to contact me. + +**The only way to bypass Athena's Shield would be through advanced cryptography knowledge, involving signature copying or hash modification.** + +The creation and verification of Athena's Shield are currently complete, though additional improvements may be made in the future, as with any project. + +Respectfully, +**SORIA Sandro (Sandro642)** \ No newline at end of file diff --git a/docs/Athena_s-Shield-Documentation.pdf b/docs/Athena_s-Shield-Documentation.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0b8a218c04fb24f8faa9b2a430d7404d487e88e8 GIT binary patch literal 49121 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 Hcc1?kir#3e literal 0 HcmV?d00001 From 160898389e08b33bc34d7541ad93a6e1e3716d34 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 17:03:39 +0200 Subject: [PATCH 29/52] Add crypto package to dependencies The crypto package is added to the dependencies in package.json to support encryption-related functionalities. This addition helps in enhancing the security features of the application. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 6f0a35ee..4feaf639 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dependencies": { "@electron/remote": "^2.1.2", "adm-zip": "^0.5.16", + "crypto": "^1.0.1", "discord-rpc-patch": "^4.0.1", "ejs": "^3.1.10", "ejs-electron": "^3.0.0", From a9c81a15ffef32f3a5259e5da1371d5227f7adb6 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 17:05:45 +0200 Subject: [PATCH 30/52] Update review date in landing.js header comment Adjusted the review date to reflect the latest inspection on 10.26.2024. No other changes or bug fixes were included in this commit. --- app/assets/js/scripts/landing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 7637c9ab..13195793 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -451,7 +451,7 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { */ /** - * @Reviewed on XX.XX.2024 expires on 01.01.2025 + * @Reviewed on 10.26.2024 expires on XX.XX.2025 * @Bugs discovereds: 0 * @Athena's Shield * @Sandro642 From d0c5bf142ddbb43977a8f68110a317dce6700972 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 17:06:30 +0200 Subject: [PATCH 31/52] Set package to private Change the "private" field in package.json to true. This will prevent the package from being accidentally published to public repositories. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4feaf639..a9d4d5b3 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "bugs": { "url": "https://github.com/dscalzi/HeliosLauncher/issues" }, - "private": false, + "private": true, "main": "index.js", "scripts": { "start": "electron .", From 5ffa73275efcc8d3577bcbccac30b16038c0484a Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 17:08:43 +0200 Subject: [PATCH 32/52] Remove outdated .idea configuration files Removed various .idea project configuration files that are no longer needed. This cleanup helps avoid clutter and potential conflicts with differing local configurations. --- .idea/.gitignore | 8 -------- .idea/HeliosLauncher.iml | 9 --------- .idea/codeStyles/Project.xml | 14 -------------- .idea/codeStyles/codeStyleConfig.xml | 5 ----- .idea/discord.xml | 7 ------- .idea/inspectionProfiles/Project_Default.xml | 6 ------ .idea/libraries/PackXZExtract.xml | 9 --------- .idea/misc.xml | 6 ------ .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 10 files changed, 78 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/HeliosLauncher.iml delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/discord.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/libraries/PackXZExtract.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b81..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/HeliosLauncher.iml b/.idea/HeliosLauncher.iml deleted file mode 100644 index d6ebd480..00000000 --- a/.idea/HeliosLauncher.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 089b600c..00000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123c..00000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml deleted file mode 100644 index d8e95616..00000000 --- a/.idea/discord.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 03d9549e..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/libraries/PackXZExtract.xml b/.idea/libraries/PackXZExtract.xml deleted file mode 100644 index 7d226702..00000000 --- a/.idea/libraries/PackXZExtract.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index e6be3f13..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index c65f1d19..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddf..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 068ca6b0f0fb24d2a0279098011b269b3cc088f7 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 17:11:43 +0200 Subject: [PATCH 33/52] Remove French configuration and distribution files Deleted `config.json` and `distribution.json` from the `fr` directory. These files contained localized configuration and distribution settings which are no longer needed. --- fr/config.json | 110 ------------------------------------------- fr/distribution.json | 1 - 2 files changed, 111 deletions(-) delete mode 100644 fr/config.json delete mode 100644 fr/distribution.json diff --git a/fr/config.json b/fr/config.json deleted file mode 100644 index 0bb27764..00000000 --- a/fr/config.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "settings": { - "game": { - "resWidth": 1280, - "resHeight": 720, - "fullscreen": false, - "autoConnect": true, - "launchDetached": true - }, - "launcher": { - "allowPrerelease": false, - "dataDirectory": "C:\\Users\\Shadow\\AppData\\Roaming\\.helioslauncher" - } - }, - "newsCache": { - "date": 1678502940000, - "content": "2ce148c5c087f5cc1cd167cb323424b222441a2f", - "dismissed": false - }, - "clientToken": null, - "selectedServer": "Demo-1.19.4", - "selectedAccount": "92d2a2d6900648a3b0e64ecf7875eb49", - "authenticationDatabase": { - "92d2a2d6900648a3b0e64ecf7875eb49": { - "type": "microsoft", - "accessToken": "eyJraWQiOiJhYzg0YSIsImFsZyI6IkhTMjU2In0.eyJ4dWlkIjoiMjUzNTQyNjM4Mjc4MTc5OCIsImFnZyI6IkFkdWx0Iiwic3ViIjoiYjc0MTAwM2ItNWI1Yy00Y2U5LWJkZmItZGIzZjM5NDIxNGIyIiwiYXV0aCI6IlhCT1giLCJucyI6ImRlZmF1bHQiLCJwc25pZCI6Ijc3MzU2NzY5ODEzMDM2OTMyMCIsInJvbGVzIjpbXSwiaXNzIjoiYXV0aGVudGljYXRpb24iLCJmbGFncyI6WyJ0d29mYWN0b3JhdXRoIiwibXNhbWlncmF0aW9uX3N0YWdlNCIsIm9yZGVyc18yMDIyIiwibXVsdGlwbGF5ZXIiXSwicHJvZmlsZXMiOnsibWMiOiI5MmQyYTJkNi05MDA2LTQ4YTMtYjBlNi00ZWNmNzg3NWViNDkifSwicGxhdGZvcm0iOiJVTktOT1dOIiwieXVpZCI6IjVlZTFlODI1YjZiYjVlMDk5YWJhZWE1YzhkMDdiMWVkIiwibmJmIjoxNzI5ODA1NDk3LCJleHAiOjE3Mjk4OTE4OTcsImlhdCI6MTcyOTgwNTQ5N30.CVKm31_LJ1YaxeFF7VLNuwS-RKidZEX6U7T-gygL91Y", - "username": "Sandro642", - "uuid": "92d2a2d6900648a3b0e64ecf7875eb49", - "displayName": "Sandro642", - "expiresAt": 1729891886954, - "microsoft": { - "access_token": "EwAoA+pvBAAUKods63Ys1fGlwiccIFJ+qE1hANsAAV9mds/QBtmjIyO4dXqXDT+Xki8mKEwRzO3c9bU1Jb90+JEufLws0yqpJe6sZC+BTHpZ3ih9MD0Ybdh7t3+nBaNsrxdCDAiZsS78j7h75+7uYX6b8RQ58NVAMfbYPnUmPRnhNvBgVYWlXQlPIejuut0OVP8nkOVkErIaj0qFVM8Gj1TXkMrgwWmQx88UVjUrvF6PuuTLAf0n5QQ6BdD93twrm44CYrUZuXZBxekOvF/2gNSm9Gc3w3gnfnC0/t1Xo85Nt68MuhaECfOlMUyRntgvdap0gKROCYJD9mh1PxDfPMNC2IguR19swXc00ZxTHtyKxHjvNUxGX7s8rAQhAkwQZgAAEKfjb7UAXRNGVUsGy07DR4/wAWetT5h9OiQ1ziXmB7nZKlhCPd49swbLXgqMlFb/JRmoYrNeommCuFCouD1Ch7CQD48ZkPIvvOlRVGujRb1ifpgDUFm5rj22zuZbm+I292MAocbDiw1+obaj/iA6latEicJdV1zxxgvOvYVilKo6dZzvtvgXLMuEPhuAb5tZ+H1zoz7igwP4Zw8H6FOyfmKMD5xv4faNXYnpKOocLDkM5vmkB4vJKt3tridBrsRdZgGeCh0B6OK8KSmWXIpm5DCvczus3hWEkPfbTwekayptLgZmsPf5obJ3N+2mp+uWfy9YrZ7k3j56/np61CtOO8+/ZbAau8/XIetmxnmb51Uh+PgVJBot5Qbc7L0JREdZnXZt7DAVsV9eQlSD9XTnReMY2VX70+6qOzddbVxoaZrKCQYt3MydDT90njzAyIkBdGqN0lPoTa3SKDUFh0JWOwRBY0YUjIRyHJRIo86/qsrl3J3UN+FK6VbfqlcO/svP5rkG7BcboOzijeMy/bGBjzVC64KaMO1T2h85LPYMpV0/RYEF/Mt+Yvp/02uaJF2uIdXHwSooNznnLD0EHPv5cWoY4wjr1U8wGJBs2jRrVdL1lSE9M3ABAisN2m717rbXnRzFDAHjOfk36CYgkC3x+sI1aeWK+3ZCcWM99ciHF5RFroQeAg==", - "refresh_token": "M.C538_BAY.0.U.-CqKBGlTb3jOsOybryiaxXM2tMhaz5MczCYAZ65EJNOWFP0jXWWhFHE39DgtpnDZGPCxhWY*aghV30KcMIx4t7kQC1pNHrtX!QyiW3*xXvpd0QEOSzb15bnTrqMV6V8L!*oEMr*xv5Q7*6xyiA4IBd3czHTzlsagHbqGn7nCXGMlEso0mi7gcHN2KHUJsrIGPkZINfz5X2f1m0XkBTxeO1aMEkmE*DLwrMcuw*S4b3QK6LpoWWNH2YKHz1a0n1M7EHnTqs*Y*22RmUkHR**yNyR*p5aOlA5tq6tTdo8tWKNZp1Ea11kY3v91adOLwSyc5aQ$$", - "expires_at": 1729809086954 - } - } - }, - "modConfigurations": [ - { - "id": "Demo-1.19.4", - "mods": {} - }, - { - "id": "Demo-1.20", - "mods": {} - }, - { - "id": "Fabric-Demo-1.20.4", - "mods": { - "com.terraformersmc:modmenu": true - } - }, - { - "id": "WesterosCraft-Demo-1.12.2", - "mods": {} - } - ], - "javaConfig": { - "Demo-1.19.4": { - "minRAM": "4G", - "maxRAM": "4G", - "executable": "C:\\Users\\Shadow\\AppData\\Roaming\\.helioslauncher\\runtime\\x64\\jdk-17.0.13+11\\bin\\javaw.exe", - "jvmOptions": [ - "-XX:+UnlockExperimentalVMOptions", - "-XX:+UseG1GC", - "-XX:G1NewSizePercent=20", - "-XX:G1ReservePercent=20", - "-XX:MaxGCPauseMillis=50", - "-XX:G1HeapRegionSize=32M" - ] - }, - "Demo-1.20": { - "minRAM": "4G", - "maxRAM": "4G", - "executable": null, - "jvmOptions": [ - "-XX:+UnlockExperimentalVMOptions", - "-XX:+UseG1GC", - "-XX:G1NewSizePercent=20", - "-XX:G1ReservePercent=20", - "-XX:MaxGCPauseMillis=50", - "-XX:G1HeapRegionSize=32M" - ] - }, - "Fabric-Demo-1.20.4": { - "minRAM": "4G", - "maxRAM": "4G", - "executable": null, - "jvmOptions": [ - "-XX:+UnlockExperimentalVMOptions", - "-XX:+UseG1GC", - "-XX:G1NewSizePercent=20", - "-XX:G1ReservePercent=20", - "-XX:MaxGCPauseMillis=50", - "-XX:G1HeapRegionSize=32M" - ] - }, - "WesterosCraft-Demo-1.12.2": { - "minRAM": "4G", - "maxRAM": "4G", - "executable": null, - "jvmOptions": [ - "-XX:+UseConcMarkSweepGC", - "-XX:+CMSIncrementalMode", - "-XX:-UseAdaptiveSizePolicy", - "-Xmn128M" - ] - } - } -} \ No newline at end of file diff --git a/fr/distribution.json b/fr/distribution.json deleted file mode 100644 index 98ad8f4f..00000000 --- a/fr/distribution.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"1.0.0","rss":"https://helios-files.geekcorner.eu.org/rss.xml","discord":{"clientId":"1086936373057040395","smallImageText":"Change me through Nebula","smallImageKey":"big"},"servers":[{"id":"Demo-1.19.4","name":"Demo (Minecraft 1.19.4)","description":"Demo Running Minecraft 1.19.4 (Forge v45.0.9)","icon":"https://helios-files.geekcorner.eu.org/servers/Demo-1.19.4/Demo-1.19.4.png","version":"1.0.0","address":"localhost:25565","minecraftVersion":"1.19.4","discord":{"shortId":"1.19.4 Demo","largeImageKey":"https://raw.githubusercontent.com/dscalzi/HeliosLauncher/master/build/icon.png","largeImageText":"forge-vanilla"},"mainServer":false,"autoconnect":false,"modules":[{"id":"net.minecraftforge:lowcodelanguage:1.19.4-45.0.9","name":"Minecraft Forge (lowcodelanguage)","type":"ForgeHosted","classpath":true,"artifact":{"size":7378,"MD5":"567726c42a1312962ffd96af646f227d","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/lowcodelanguage/1.19.4-45.0.9/lowcodelanguage-1.19.4-45.0.9.jar"},"subModules":[{"id":"1.19.4-45.0.9","name":"Minecraft Forge (version.json)","type":"VersionManifest","artifact":{"size":15497,"MD5":"97924bf1d3bf6f2bc4acc5662a7fecc0","url":"https://helios-files.geekcorner.eu.org/repo/versions/1.19.4-forge-45.0.9/1.19.4-forge-45.0.9.json"}},{"id":"net.minecraftforge:fmlcore:1.19.4-45.0.9","name":"Minecraft Forge (fmlcore)","type":"Library","classpath":true,"artifact":{"size":113254,"MD5":"087a7175f26ebce8dee27cc75ab706a6","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/fmlcore/1.19.4-45.0.9/fmlcore-1.19.4-45.0.9.jar"},"subModules":[]},{"id":"net.minecraftforge:javafmllanguage:1.19.4-45.0.9","name":"Minecraft Forge (javafmllanguage)","type":"Library","classpath":true,"artifact":{"size":16352,"MD5":"85f5a466356973b9a9199f5a5b1d1a25","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/javafmllanguage/1.19.4-45.0.9/javafmllanguage-1.19.4-45.0.9.jar"},"subModules":[]},{"id":"net.minecraftforge:mclanguage:1.19.4-45.0.9","name":"Minecraft Forge (mclanguage)","type":"Library","classpath":true,"artifact":{"size":4987,"MD5":"2afe8c6ba41599bbc139607e1f5bc90f","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/mclanguage/1.19.4-45.0.9/mclanguage-1.19.4-45.0.9.jar"},"subModules":[]},{"id":"net.minecraftforge:forge:1.19.4-45.0.9:universal","name":"Minecraft Forge (universal jar)","type":"Library","classpath":false,"artifact":{"size":2654813,"MD5":"75086169e9f5f6990cd399ae352590da","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forge/1.19.4-45.0.9/forge-1.19.4-45.0.9-universal.jar"},"subModules":[]},{"id":"net.minecraftforge:forge:1.19.4-45.0.9:client","name":"Minecraft Forge (client jar)","type":"Library","classpath":false,"artifact":{"size":5416831,"MD5":"db21cc95ee603f0dc4e7bce8af5d1f5c","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forge/1.19.4-45.0.9/forge-1.19.4-45.0.9-client.jar"},"subModules":[]},{"id":"net.minecraft:client:1.19.4-20230314.122934:srg","name":"Minecraft Forge (client srg)","type":"Library","classpath":false,"artifact":{"size":18578667,"MD5":"500a7127781d84a24d65f110479538f3","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.19.4-20230314.122934/client-1.19.4-20230314.122934-srg.jar"},"subModules":[]},{"id":"net.minecraft:client:1.19.4-20230314.122934:slim","name":"Minecraft Forge (client slim)","type":"Library","classpath":false,"artifact":{"size":12410103,"MD5":"764989b8230caa18e97f90799f902650","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.19.4-20230314.122934/client-1.19.4-20230314.122934-slim.jar"},"subModules":[]},{"id":"net.minecraft:client:1.19.4-20230314.122934:extra","name":"Minecraft Forge (client extra)","type":"Library","classpath":false,"artifact":{"size":11066539,"MD5":"e612fd0d646100ae60158ba37c5aa71a","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.19.4-20230314.122934/client-1.19.4-20230314.122934-extra.jar"},"subModules":[]},{"id":"cpw.mods:securejarhandler:2.1.6","name":"Minecraft Forge (securejarhandler)","type":"Library","artifact":{"size":87783,"MD5":"8a4e7e2770d7147d7551d1f3ad835baa","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/securejarhandler/2.1.6/securejarhandler-2.1.6.jar"}},{"id":"org.ow2.asm:asm:9.3","name":"Minecraft Forge (asm)","type":"Library","artifact":{"size":122176,"MD5":"e1c3b96035117ab516ffe0de9bd696e0","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm/9.3/asm-9.3.jar"}},{"id":"org.ow2.asm:asm-commons:9.3","name":"Minecraft Forge (asm-commons)","type":"Library","artifact":{"size":72716,"MD5":"16e6ac17d33ad97baa415c42e9d93d38","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-commons/9.3/asm-commons-9.3.jar"}},{"id":"org.ow2.asm:asm-tree:9.3","name":"Minecraft Forge (asm-tree)","type":"Library","artifact":{"size":52669,"MD5":"f087bfb911ff93957e44860de3e73c46","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-tree/9.3/asm-tree-9.3.jar"}},{"id":"org.ow2.asm:asm-util:9.3","name":"Minecraft Forge (asm-util)","type":"Library","artifact":{"size":85682,"MD5":"d6d0ac4e5c0be9a25a426c70c714b17b","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-util/9.3/asm-util-9.3.jar"}},{"id":"org.ow2.asm:asm-analysis:9.3","name":"Minecraft Forge (asm-analysis)","type":"Library","artifact":{"size":34276,"MD5":"11e43c0220b3f6b4791b8abf7b4cc7ef","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-analysis/9.3/asm-analysis-9.3.jar"}},{"id":"net.minecraftforge:accesstransformers:8.0.4","name":"Minecraft Forge (accesstransformers)","type":"Library","artifact":{"size":77756,"MD5":"f0d3533f9437ba4428eaee9f28f7ecd9","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/accesstransformers/8.0.4/accesstransformers-8.0.4.jar"}},{"id":"org.antlr:antlr4-runtime:4.9.1","name":"Minecraft Forge (antlr4-runtime)","type":"Library","artifact":{"size":337868,"MD5":"0dcc4b860d5d8d2852ab94d58c56ca2d","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/antlr/antlr4-runtime/4.9.1/antlr4-runtime-4.9.1.jar"}},{"id":"net.minecraftforge:eventbus:6.0.3","name":"Minecraft Forge (eventbus)","type":"Library","artifact":{"size":52624,"MD5":"7c93693a70962a1c1893e3c058dca5cf","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/eventbus/6.0.3/eventbus-6.0.3.jar"}},{"id":"net.minecraftforge:forgespi:6.0.0","name":"Minecraft Forge (forgespi)","type":"Library","artifact":{"size":32714,"MD5":"8c04b8c26ac1d55c281bfeaa71731414","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forgespi/6.0.0/forgespi-6.0.0.jar"}},{"id":"net.minecraftforge:coremods:5.0.1","name":"Minecraft Forge (coremods)","type":"Library","artifact":{"size":24199,"MD5":"8c590051c5d65e098ca7c2e3ddf803a8","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/coremods/5.0.1/coremods-5.0.1.jar"}},{"id":"cpw.mods:modlauncher:10.0.8","name":"Minecraft Forge (modlauncher)","type":"Library","artifact":{"size":130263,"MD5":"3e171edcc29e762d2ecafffabb9e9341","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/modlauncher/10.0.8/modlauncher-10.0.8.jar"}},{"id":"net.minecraftforge:unsafe:0.2.0","name":"Minecraft Forge (unsafe)","type":"Library","artifact":{"size":2834,"MD5":"2d1016ebe4c1a63dd50a59d26bd12db1","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/unsafe/0.2.0/unsafe-0.2.0.jar"}},{"id":"com.electronwill.night-config:core:3.6.4","name":"Minecraft Forge (core)","type":"Library","artifact":{"size":199834,"MD5":"d83ab07267e402131fb93d899a57f5cd","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/electronwill/night-config/core/3.6.4/core-3.6.4.jar"}},{"id":"com.electronwill.night-config:toml:3.6.4","name":"Minecraft Forge (toml)","type":"Library","artifact":{"size":31816,"MD5":"bc95d0709fff2164b01fd09fbc988be8","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/electronwill/night-config/toml/3.6.4/toml-3.6.4.jar"}},{"id":"org.apache.maven:maven-artifact:3.8.5","name":"Minecraft Forge (maven-artifact)","type":"Library","artifact":{"size":58077,"MD5":"ce473b0d9fbfd10fe147f03fe8707d67","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/maven/maven-artifact/3.8.5/maven-artifact-3.8.5.jar"}},{"id":"net.jodah:typetools:0.8.3","name":"Minecraft Forge (typetools)","type":"Library","artifact":{"size":15425,"MD5":"ee4596e1413cc1a5e080c971d6064753","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/jodah/typetools/0.8.3/typetools-0.8.3.jar"}},{"id":"net.minecrell:terminalconsoleappender:1.2.0","name":"Minecraft Forge (terminalconsoleappender)","type":"Library","artifact":{"size":15977,"MD5":"679363fa893293791e55a21f81342f87","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecrell/terminalconsoleappender/1.2.0/terminalconsoleappender-1.2.0.jar"}},{"id":"org.jline:jline-reader:3.12.1","name":"Minecraft Forge (jline-reader)","type":"Library","artifact":{"size":150765,"MD5":"a2e7b012cd9802f83321187409174a94","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/jline/jline-reader/3.12.1/jline-reader-3.12.1.jar"}},{"id":"org.jline:jline-terminal:3.12.1","name":"Minecraft Forge (jline-terminal)","type":"Library","artifact":{"size":211712,"MD5":"3c52be5ab5e3847be6e62269de924cb0","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/jline/jline-terminal/3.12.1/jline-terminal-3.12.1.jar"}},{"id":"org.spongepowered:mixin:0.8.5","name":"Minecraft Forge (mixin)","type":"Library","artifact":{"size":1089277,"MD5":"19b3a2ae9e445a6e626fd7d1648cfcb8","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar"}},{"id":"org.openjdk.nashorn:nashorn-core:15.3","name":"Minecraft Forge (nashorn-core)","type":"Library","artifact":{"size":2167288,"MD5":"91e98c20afa1090c344229ce28b4c53f","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/openjdk/nashorn/nashorn-core/15.3/nashorn-core-15.3.jar"}},{"id":"net.minecraftforge:JarJarSelector:0.3.19","name":"Minecraft Forge (JarJarSelector)","type":"Library","artifact":{"size":17374,"MD5":"2bb6cbe0e6c6fbcd8f92d2e6b1ea678e","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarSelector/0.3.19/JarJarSelector-0.3.19.jar"}},{"id":"net.minecraftforge:JarJarMetadata:0.3.19","name":"Minecraft Forge (JarJarMetadata)","type":"Library","artifact":{"size":15895,"MD5":"9633546d299d4282ca68d10582be1c8f","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarMetadata/0.3.19/JarJarMetadata-0.3.19.jar"}},{"id":"cpw.mods:bootstraplauncher:1.1.2","name":"Minecraft Forge (bootstraplauncher)","type":"Library","artifact":{"size":8284,"MD5":"48f5255aa337344c467c0150d347813d","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/bootstraplauncher/1.1.2/bootstraplauncher-1.1.2.jar"}},{"id":"net.minecraftforge:JarJarFileSystems:0.3.19","name":"Minecraft Forge (JarJarFileSystems)","type":"Library","artifact":{"size":32195,"MD5":"c2be1a88b63eb1b58b00ab6e498cd97d","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarFileSystems/0.3.19/JarJarFileSystems-0.3.19.jar"}},{"id":"net.minecraftforge:fmlloader:1.19.4-45.0.9","name":"Minecraft Forge (fmlloader)","type":"Library","artifact":{"size":261271,"MD5":"896da600a8508b6a41a4f804e1073b92","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/fmlloader/1.19.4-45.0.9/fmlloader-1.19.4-45.0.9.jar"}}]},{"id":"squeek.appleskin:appleskin:2.4.3+mc1.19.4@jar","name":"AppleSkin","type":"ForgeMod","artifact":{"size":46976,"url":"https://helios-files.geekcorner.eu.org/servers/Demo-1.19.4/forgemods/required/appleskin-forge-mc1.19.4-2.4.3.jar","MD5":"757b5e83f4c2ab0ab2a21289849edcdf"}},{"id":"hi.txt","name":"hi.txt","type":"File","artifact":{"size":147,"url":"https://helios-files.geekcorner.eu.org/servers/Demo-1.19.4/files/hi.txt","MD5":"e752096b159035a3ffaf396fb111b837","path":"hi.txt"}}]},{"id":"Demo-1.20","name":"Demo (Minecraft 1.20)","description":"Demo Running Minecraft 1.20 (Forge v46.0.2)","icon":"https://helios-files.geekcorner.eu.org/servers/Demo-1.20/Demo-1.20.png","version":"1.0.0","address":"localhost:25565","minecraftVersion":"1.20","discord":{"shortId":"","largeImageText":"","largeImageKey":""},"mainServer":false,"autoconnect":false,"modules":[{"id":"net.minecraftforge:lowcodelanguage:1.20-46.0.2","name":"Minecraft Forge (lowcodelanguage)","type":"ForgeHosted","classpath":true,"artifact":{"size":7400,"MD5":"4a69374eb645771ddb0387a9076b450f","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/lowcodelanguage/1.20-46.0.2/lowcodelanguage-1.20-46.0.2.jar"},"subModules":[{"id":"1.20-46.0.2","name":"Minecraft Forge (version.json)","type":"VersionManifest","artifact":{"size":15970,"MD5":"1125883344968dbbe14e74cf3ad2b5e8","url":"https://helios-files.geekcorner.eu.org/repo/versions/1.20-forge-46.0.2/1.20-forge-46.0.2.json"}},{"id":"net.minecraftforge:fmlcore:1.20-46.0.2","name":"Minecraft Forge (fmlcore)","type":"Library","classpath":true,"artifact":{"size":116123,"MD5":"bac94c804729e0952c84a2e863ad2269","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/fmlcore/1.20-46.0.2/fmlcore-1.20-46.0.2.jar"},"subModules":[]},{"id":"net.minecraftforge:javafmllanguage:1.20-46.0.2","name":"Minecraft Forge (javafmllanguage)","type":"Library","classpath":true,"artifact":{"size":16374,"MD5":"3b25d656c3a0fb057ed308a33ce43c29","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/javafmllanguage/1.20-46.0.2/javafmllanguage-1.20-46.0.2.jar"},"subModules":[]},{"id":"net.minecraftforge:mclanguage:1.20-46.0.2","name":"Minecraft Forge (mclanguage)","type":"Library","classpath":true,"artifact":{"size":5000,"MD5":"d0eb5fff9d92efcb43b8d708298cc483","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/mclanguage/1.20-46.0.2/mclanguage-1.20-46.0.2.jar"},"subModules":[]},{"id":"net.minecraftforge:forge:1.20-46.0.2:universal","name":"Minecraft Forge (universal jar)","type":"Library","classpath":false,"artifact":{"size":2612080,"MD5":"5dd29a134bd8f6135417bacb5e36408e","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forge/1.20-46.0.2/forge-1.20-46.0.2-universal.jar"},"subModules":[]},{"id":"net.minecraftforge:forge:1.20-46.0.2:client","name":"Minecraft Forge (client jar)","type":"Library","classpath":false,"artifact":{"size":4677757,"MD5":"55f309ca113761208206ce095c221a29","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forge/1.20-46.0.2/forge-1.20-46.0.2-client.jar"},"subModules":[]},{"id":"net.minecraft:client:1.20-20230608.053357:srg","name":"Minecraft Forge (client srg)","type":"Library","classpath":false,"artifact":{"size":18841206,"MD5":"d20dc7660df3488916d1cf24b61e08fb","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.20-20230608.053357/client-1.20-20230608.053357-srg.jar"},"subModules":[]},{"id":"net.minecraft:client:1.20-20230608.053357:slim","name":"Minecraft Forge (client slim)","type":"Library","classpath":false,"artifact":{"size":12591630,"MD5":"6fbeb6a4a0dc55cb90e9334885918053","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.20-20230608.053357/client-1.20-20230608.053357-slim.jar"},"subModules":[]},{"id":"net.minecraft:client:1.20-20230608.053357:extra","name":"Minecraft Forge (client extra)","type":"Library","classpath":false,"artifact":{"size":10436670,"MD5":"f4f8be138812304b01638fbf5c6ff81a","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/client/1.20-20230608.053357/client-1.20-20230608.053357-extra.jar"},"subModules":[]},{"id":"cpw.mods:securejarhandler:2.1.6","name":"Minecraft Forge (securejarhandler)","type":"Library","artifact":{"size":87783,"MD5":"8a4e7e2770d7147d7551d1f3ad835baa","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/securejarhandler/2.1.6/securejarhandler-2.1.6.jar"}},{"id":"org.ow2.asm:asm:9.5","name":"Minecraft Forge (asm)","type":"Library","artifact":{"size":121863,"MD5":"29721ee4b5eacf0a34b204c345c8bc69","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm/9.5/asm-9.5.jar"}},{"id":"org.ow2.asm:asm-commons:9.5","name":"Minecraft Forge (asm-commons)","type":"Library","artifact":{"size":72209,"MD5":"7d1fce986192f71722b19754e4cb9e61","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-commons/9.5/asm-commons-9.5.jar"}},{"id":"org.ow2.asm:asm-tree:9.5","name":"Minecraft Forge (asm-tree)","type":"Library","artifact":{"size":51944,"MD5":"44755681b7d6fa7143afbb438e55c20c","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-tree/9.5/asm-tree-9.5.jar"}},{"id":"org.ow2.asm:asm-util:9.5","name":"Minecraft Forge (asm-util)","type":"Library","artifact":{"size":91076,"MD5":"ad0016249fb68bb9196babefd47b80dc","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-util/9.5/asm-util-9.5.jar"}},{"id":"org.ow2.asm:asm-analysis:9.5","name":"Minecraft Forge (asm-analysis)","type":"Library","artifact":{"size":33978,"MD5":"4df0adafc78ebba404d4037987d36b61","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-analysis/9.5/asm-analysis-9.5.jar"}},{"id":"net.minecraftforge:accesstransformers:8.0.4","name":"Minecraft Forge (accesstransformers)","type":"Library","artifact":{"size":77756,"MD5":"f0d3533f9437ba4428eaee9f28f7ecd9","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/accesstransformers/8.0.4/accesstransformers-8.0.4.jar"}},{"id":"org.antlr:antlr4-runtime:4.9.1","name":"Minecraft Forge (antlr4-runtime)","type":"Library","artifact":{"size":337868,"MD5":"0dcc4b860d5d8d2852ab94d58c56ca2d","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/antlr/antlr4-runtime/4.9.1/antlr4-runtime-4.9.1.jar"}},{"id":"net.minecraftforge:eventbus:6.0.3","name":"Minecraft Forge (eventbus)","type":"Library","artifact":{"size":52624,"MD5":"7c93693a70962a1c1893e3c058dca5cf","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/eventbus/6.0.3/eventbus-6.0.3.jar"}},{"id":"net.minecraftforge:forgespi:7.0.0","name":"Minecraft Forge (forgespi)","type":"Library","artifact":{"size":30602,"MD5":"b1bf9c45a33a749ed58b15d679925243","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forgespi/7.0.0/forgespi-7.0.0.jar"}},{"id":"net.minecraftforge:coremods:5.0.1","name":"Minecraft Forge (coremods)","type":"Library","artifact":{"size":24199,"MD5":"8c590051c5d65e098ca7c2e3ddf803a8","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/coremods/5.0.1/coremods-5.0.1.jar"}},{"id":"cpw.mods:modlauncher:10.0.8","name":"Minecraft Forge (modlauncher)","type":"Library","artifact":{"size":130263,"MD5":"3e171edcc29e762d2ecafffabb9e9341","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/modlauncher/10.0.8/modlauncher-10.0.8.jar"}},{"id":"net.minecraftforge:unsafe:0.2.0","name":"Minecraft Forge (unsafe)","type":"Library","artifact":{"size":2834,"MD5":"2d1016ebe4c1a63dd50a59d26bd12db1","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/unsafe/0.2.0/unsafe-0.2.0.jar"}},{"id":"net.minecraftforge:mergetool:1.1.5:api","name":"Minecraft Forge (mergetool)","type":"Library","artifact":{"size":2572,"MD5":"8df9c5bf87d004ddb884eca99bc2a4b1","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/mergetool/1.1.5/mergetool-1.1.5-api.jar"}},{"id":"com.electronwill.night-config:core:3.6.4","name":"Minecraft Forge (core)","type":"Library","artifact":{"size":199834,"MD5":"d83ab07267e402131fb93d899a57f5cd","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/electronwill/night-config/core/3.6.4/core-3.6.4.jar"}},{"id":"com.electronwill.night-config:toml:3.6.4","name":"Minecraft Forge (toml)","type":"Library","artifact":{"size":31816,"MD5":"bc95d0709fff2164b01fd09fbc988be8","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/electronwill/night-config/toml/3.6.4/toml-3.6.4.jar"}},{"id":"org.apache.maven:maven-artifact:3.8.5","name":"Minecraft Forge (maven-artifact)","type":"Library","artifact":{"size":58077,"MD5":"ce473b0d9fbfd10fe147f03fe8707d67","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/maven/maven-artifact/3.8.5/maven-artifact-3.8.5.jar"}},{"id":"net.jodah:typetools:0.8.3","name":"Minecraft Forge (typetools)","type":"Library","artifact":{"size":15425,"MD5":"ee4596e1413cc1a5e080c971d6064753","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/jodah/typetools/0.8.3/typetools-0.8.3.jar"}},{"id":"net.minecrell:terminalconsoleappender:1.2.0","name":"Minecraft Forge (terminalconsoleappender)","type":"Library","artifact":{"size":15977,"MD5":"679363fa893293791e55a21f81342f87","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecrell/terminalconsoleappender/1.2.0/terminalconsoleappender-1.2.0.jar"}},{"id":"org.jline:jline-reader:3.12.1","name":"Minecraft Forge (jline-reader)","type":"Library","artifact":{"size":150765,"MD5":"a2e7b012cd9802f83321187409174a94","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/jline/jline-reader/3.12.1/jline-reader-3.12.1.jar"}},{"id":"org.jline:jline-terminal:3.12.1","name":"Minecraft Forge (jline-terminal)","type":"Library","artifact":{"size":211712,"MD5":"3c52be5ab5e3847be6e62269de924cb0","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/jline/jline-terminal/3.12.1/jline-terminal-3.12.1.jar"}},{"id":"org.spongepowered:mixin:0.8.5","name":"Minecraft Forge (mixin)","type":"Library","artifact":{"size":1089277,"MD5":"19b3a2ae9e445a6e626fd7d1648cfcb8","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar"}},{"id":"org.openjdk.nashorn:nashorn-core:15.3","name":"Minecraft Forge (nashorn-core)","type":"Library","artifact":{"size":2167288,"MD5":"91e98c20afa1090c344229ce28b4c53f","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/openjdk/nashorn/nashorn-core/15.3/nashorn-core-15.3.jar"}},{"id":"net.minecraftforge:JarJarSelector:0.3.19","name":"Minecraft Forge (JarJarSelector)","type":"Library","artifact":{"size":17374,"MD5":"2bb6cbe0e6c6fbcd8f92d2e6b1ea678e","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarSelector/0.3.19/JarJarSelector-0.3.19.jar"}},{"id":"net.minecraftforge:JarJarMetadata:0.3.19","name":"Minecraft Forge (JarJarMetadata)","type":"Library","artifact":{"size":15895,"MD5":"9633546d299d4282ca68d10582be1c8f","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarMetadata/0.3.19/JarJarMetadata-0.3.19.jar"}},{"id":"cpw.mods:bootstraplauncher:1.1.2","name":"Minecraft Forge (bootstraplauncher)","type":"Library","artifact":{"size":8284,"MD5":"48f5255aa337344c467c0150d347813d","url":"https://helios-files.geekcorner.eu.org/repo/lib/cpw/mods/bootstraplauncher/1.1.2/bootstraplauncher-1.1.2.jar"}},{"id":"net.minecraftforge:JarJarFileSystems:0.3.19","name":"Minecraft Forge (JarJarFileSystems)","type":"Library","artifact":{"size":32195,"MD5":"c2be1a88b63eb1b58b00ab6e498cd97d","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/JarJarFileSystems/0.3.19/JarJarFileSystems-0.3.19.jar"}},{"id":"net.minecraftforge:fmlloader:1.20-46.0.2","name":"Minecraft Forge (fmlloader)","type":"Library","artifact":{"size":261464,"MD5":"99825c1cc845cf542284efa122e1b96e","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/fmlloader/1.20-46.0.2/fmlloader-1.20-46.0.2.jar"}}]}]},{"id":"Fabric-Demo-1.20.4","name":"Fabric-Demo (Minecraft 1.20.4)","description":"Fabric-Demo Running Minecraft 1.20.4 (Fabric v0.15.6)","icon":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/Fabric-Demo-1.20.4.png","version":"1.0.0","address":"localhost:25565","minecraftVersion":"1.20.4","discord":{"shortId":"","largeImageText":"","largeImageKey":""},"mainServer":false,"autoconnect":false,"modules":[{"id":"net.fabricmc:fabric-loader:0.15.6","name":"Fabric (fabric-loader)","type":"Fabric","artifact":{"size":1321135,"MD5":"744dbc7c568d7e782e6616637a14f6b3","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/fabricmc/fabric-loader/0.15.6/fabric-loader-0.15.6.jar"},"subModules":[{"id":"1.20.4-fabric-0.15.6","name":"Fabric (version.json)","type":"VersionManifest","artifact":{"size":2847,"MD5":"6052ebb6acbba734c83fd3a09e67d31e","url":"https://helios-files.geekcorner.eu.org/repo/versions/1.20.4-fabric-0.15.6/1.20.4-fabric-0.15.6.json"}},{"id":"org.ow2.asm:asm:9.6","name":"Fabric (asm)","type":"Library","artifact":{"size":123598,"MD5":"6f8bccf756f170d4185bb24c8c2d2020","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm/9.6/asm-9.6.jar"}},{"id":"org.ow2.asm:asm-analysis:9.6","name":"Fabric (asm-analysis)","type":"Library","artifact":{"size":34041,"MD5":"31c84ef7cc893fb278952ae2d6a2674f","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-analysis/9.6/asm-analysis-9.6.jar"}},{"id":"org.ow2.asm:asm-commons:9.6","name":"Fabric (asm-commons)","type":"Library","artifact":{"size":72194,"MD5":"9e317c75534bd1da8c00a67c618ab288","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-commons/9.6/asm-commons-9.6.jar"}},{"id":"org.ow2.asm:asm-tree:9.6","name":"Fabric (asm-tree)","type":"Library","artifact":{"size":51935,"MD5":"6062608f1a98afe1e853d01fa1221a9e","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-tree/9.6/asm-tree-9.6.jar"}},{"id":"org.ow2.asm:asm-util:9.6","name":"Fabric (asm-util)","type":"Library","artifact":{"size":91131,"MD5":"bd3bc1c176a787373e9a031073c9574b","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-util/9.6/asm-util-9.6.jar"}},{"id":"net.fabricmc:sponge-mixin:0.12.5+mixin.0.8.5","name":"Fabric (sponge-mixin)","type":"Library","artifact":{"size":1451874,"MD5":"4cc3ff559cafdc70d9ed80e869d447f0","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/fabricmc/sponge-mixin/0.12.5+mixin.0.8.5/sponge-mixin-0.12.5+mixin.0.8.5.jar"}},{"id":"net.fabricmc:intermediary:1.20.4","name":"Fabric (intermediary)","type":"Library","artifact":{"size":608319,"MD5":"3a1cade1634c518db6147c9771686ea9","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/fabricmc/intermediary/1.20.4/intermediary-1.20.4.jar"}}]},{"id":"generated.fabricmod:iris:1.6.15@jar","name":"Iris","type":"FabricMod","artifact":{"size":2416339,"url":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/fabricmods/required/iris-mc1.20.4-1.6.15.jar","MD5":"a2f7578c1652222e8c703831b604150e"}},{"id":"me.jellysquid.mods:lithium:0.12.1@jar","name":"Lithium","type":"FabricMod","artifact":{"size":716022,"url":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/fabricmods/required/lithium-fabric-mc1.20.4-0.12.1.jar","MD5":"83da6729d5402dc42eca740a96f97346"}},{"id":"com.terraformersmc:modmenu:9.0.0@jar","name":"Mod Menu","type":"FabricMod","artifact":{"size":724484,"url":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/fabricmods/optionalon/modmenu-9.0.0.jar","MD5":"7eb06d459405ed6c87b16f4c5be4493d"},"required":{"value":false}},{"id":"me.jellysquid.mods:sodium:0.5.8+mc1.20.4@jar","name":"Sodium","type":"FabricMod","artifact":{"size":949085,"url":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/fabricmods/required/sodium-fabric-0.5.8+mc1.20.4.jar","MD5":"d7753a50ca37f50abb10465a9a425dac"}},{"id":"generated.library:fabric:api-0.95.4+1.20.4@jar","name":"fabric","type":"Library","artifact":{"size":2141107,"url":"https://helios-files.geekcorner.eu.org/servers/Fabric-Demo-1.20.4/libraries/fabric-api-0.95.4+1.20.4.jar","MD5":"e16ba73c962937a8a11515ecfd47d163"}}]},{"id":"WesterosCraft-Demo-1.12.2","name":"WesterosCraft-Demo (Minecraft 1.12.2)","description":"WesterosCraft-Demo Running Minecraft 1.12.2 (Forge v14.23.5.2859)","icon":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/WesterosCraft-Demo-1.12.2.png","version":"2.1.6","address":"mc.westeroscraft.com:25565","minecraftVersion":"1.12.2","discord":{"shortId":"WesterosCraft Demo","largeImageKey":"westeroscraft","largeImageText":"Change me through Nebula"},"mainServer":false,"autoconnect":false,"modules":[{"id":"net.minecraftforge:forge:1.12.2-14.23.5.2859:universal","name":"Minecraft Forge","type":"ForgeHosted","artifact":{"size":4466108,"MD5":"fda01cd3cae80c2c6348ac3fc26e0af8","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraftforge/forge/1.12.2-14.23.5.2859/forge-1.12.2-14.23.5.2859-universal.jar"},"subModules":[{"id":"1.12.2-14.23.5.2859","name":"Minecraft Forge (version.json)","type":"VersionManifest","artifact":{"size":12345,"MD5":"1959bb357e54a9666dd80d744b524639","url":"https://helios-files.geekcorner.eu.org/repo/versions/1.12.2-forge-14.23.5.2859/1.12.2-forge-14.23.5.2859.json"}},{"id":"org.ow2.asm:asm-debug-all:5.2@jar","name":"Minecraft Forge (asm-debug-all)","type":"Library","artifact":{"size":387903,"MD5":"fe5f20404ccdee9769ef05dc4b47ba98","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/ow2/asm/asm-debug-all/5.2/asm-debug-all-5.2.jar"}},{"id":"net.minecraft:launchwrapper:1.12@jar","name":"Minecraft Forge (launchwrapper)","type":"Library","artifact":{"size":32999,"MD5":"934b2d91c7c5be4a49577c9e6b40e8da","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/minecraft/launchwrapper/1.12/launchwrapper-1.12.jar"}},{"id":"org.jline:jline:3.5.1@jar","name":"Minecraft Forge (jline)","type":"Library","artifact":{"size":614590,"MD5":"4c20d2879ed2bd75a0771ce29e89f6b0","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/jline/jline/3.5.1/jline-3.5.1.jar"}},{"id":"com.typesafe.akka:akka-actor_2.11:2.3.3@jar","name":"Minecraft Forge (akka-actor_2.11)","type":"Library","artifact":{"size":2514991,"MD5":"541440ca0819ebada47d6d1a8b3ee9e1","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/typesafe/akka/akka-actor_2.11/2.3.3/akka-actor_2.11-2.3.3.jar"}},{"id":"com.typesafe:config:1.2.1@jar","name":"Minecraft Forge (config)","type":"Library","artifact":{"size":219554,"MD5":"3aaf3c6e76a68e732c17d4a7e9877d81","url":"https://helios-files.geekcorner.eu.org/repo/lib/com/typesafe/config/1.2.1/config-1.2.1.jar"}},{"id":"org.scala-lang:scala-actors-migration_2.11:1.1.0@jar","name":"Minecraft Forge (scala-actors-migration_2.11)","type":"Library","artifact":{"size":58018,"MD5":"f5e79398daa1806f8b17311a3c782723","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-actors-migration_2.11/1.1.0/scala-actors-migration_2.11-1.1.0.jar"}},{"id":"org.scala-lang:scala-compiler:2.11.1@jar","name":"Minecraft Forge (scala-compiler)","type":"Library","artifact":{"size":13449765,"MD5":"06030143bf86ca896fb6ccfd679b5760","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-compiler/2.11.1/scala-compiler-2.11.1.jar"}},{"id":"org.scala-lang.plugins:scala-continuations-library_2.11:1.0.2_mc@jar","name":"Minecraft Forge (scala-continuations-library_2.11)","type":"Library","artifact":{"size":25365,"MD5":"004d7007abbcee858d3ca2c3ccbcbaab","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/plugins/scala-continuations-library_2.11/1.0.2_mc/scala-continuations-library_2.11-1.0.2_mc.jar"}},{"id":"org.scala-lang.plugins:scala-continuations-plugin_2.11.1:1.0.2_mc@jar","name":"Minecraft Forge (scala-continuations-plugin_2.11.1)","type":"Library","artifact":{"size":206575,"MD5":"359c4a6743a082c689039482eed78670","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/plugins/scala-continuations-plugin_2.11.1/1.0.2_mc/scala-continuations-plugin_2.11.1-1.0.2_mc.jar"}},{"id":"org.scala-lang:scala-library:2.11.1@jar","name":"Minecraft Forge (scala-library)","type":"Library","artifact":{"size":5538130,"MD5":"1d88f665219e6006c5dd82d71c525c0f","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-library/2.11.1/scala-library-2.11.1.jar"}},{"id":"org.scala-lang:scala-parser-combinators_2.11:1.0.1@jar","name":"Minecraft Forge (scala-parser-combinators_2.11)","type":"Library","artifact":{"size":419701,"MD5":"4e694499c965af4a02599c99d4f0b196","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-parser-combinators_2.11/1.0.1/scala-parser-combinators_2.11-1.0.1.jar"}},{"id":"org.scala-lang:scala-reflect:2.11.1@jar","name":"Minecraft Forge (scala-reflect)","type":"Library","artifact":{"size":4372892,"MD5":"7878fac044e4e4b576bb35a77ccc34fc","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-reflect/2.11.1/scala-reflect-2.11.1.jar"}},{"id":"org.scala-lang:scala-swing_2.11:1.0.1@jar","name":"Minecraft Forge (scala-swing_2.11)","type":"Library","artifact":{"size":726500,"MD5":"1009d69e4948045383f2a7a334348af5","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-swing_2.11/1.0.1/scala-swing_2.11-1.0.1.jar"}},{"id":"org.scala-lang:scala-xml_2.11:1.0.2@jar","name":"Minecraft Forge (scala-xml_2.11)","type":"Library","artifact":{"size":648679,"MD5":"c2d7e66495afe14545c31b21e99879ef","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/scala-lang/scala-xml_2.11/1.0.2/scala-xml_2.11-1.0.2.jar"}},{"id":"lzma:lzma:0.0.1@jar","name":"Minecraft Forge (lzma)","type":"Library","artifact":{"size":5762,"MD5":"a3e3c3186e41c4a1a3027ba2bb23cdc6","url":"https://helios-files.geekcorner.eu.org/repo/lib/lzma/lzma/0.0.1/lzma-0.0.1.jar"}},{"id":"java3d:vecmath:1.5.2@jar","name":"Minecraft Forge (vecmath)","type":"Library","artifact":{"size":318956,"MD5":"e5d2b7f46c4800a32f62ce75676a5710","url":"https://helios-files.geekcorner.eu.org/repo/lib/java3d/vecmath/1.5.2/vecmath-1.5.2.jar"}},{"id":"net.sf.trove4j:trove4j:3.0.3@jar","name":"Minecraft Forge (trove4j)","type":"Library","artifact":{"size":2523218,"MD5":"8fc4d4e0129244f9fd39650c5f30feb2","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/sf/trove4j/trove4j/3.0.3/trove4j-3.0.3.jar"}},{"id":"org.apache.maven:maven-artifact:3.5.3@jar","name":"Minecraft Forge (maven-artifact)","type":"Library","artifact":{"size":54961,"MD5":"7741ebf29690ee7d9dde9cf4376347fc","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/maven/maven-artifact/3.5.3/maven-artifact-3.5.3.jar"}},{"id":"net.sf.jopt-simple:jopt-simple:5.0.3@jar","name":"Minecraft Forge (jopt-simple)","type":"Library","artifact":{"size":78175,"MD5":"0a5ec84e23df9d7cfb4063bc55f2744c","url":"https://helios-files.geekcorner.eu.org/repo/lib/net/sf/jopt-simple/jopt-simple/5.0.3/jopt-simple-5.0.3.jar"}},{"id":"org.apache.logging.log4j:log4j-api:2.15.0@jar","name":"Minecraft Forge (log4j-api)","type":"Library","artifact":{"size":301804,"MD5":"a9ccfa7e3382dd2b9e0647a43d8286d7","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/logging/log4j/log4j-api/2.15.0/log4j-api-2.15.0.jar"}},{"id":"org.apache.logging.log4j:log4j-core:2.15.0@jar","name":"Minecraft Forge (log4j-core)","type":"Library","artifact":{"size":1789769,"MD5":"81e0433ae00602c0e4d00424d213b0ab","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/logging/log4j/log4j-core/2.15.0/log4j-core-2.15.0.jar"}},{"id":"org.apache.logging.log4j:log4j-slf4j18-impl:2.15.0@jar","name":"Minecraft Forge (log4j-slf4j18-impl)","type":"Library","artifact":{"size":21223,"MD5":"196442f1bdde4dbb0f576eed616e21b0","url":"https://helios-files.geekcorner.eu.org/repo/lib/org/apache/logging/log4j/log4j-slf4j18-impl/2.15.0/log4j-slf4j18-impl-2.15.0.jar"}}]},{"id":"com.creativemd:creativecore:1.10@jar","name":"CreativeCore","type":"ForgeMod","artifact":{"size":1290012,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/CreativeCore_v1.10.65_mc1.12.2.jar","MD5":"e6e4ad48ce7d4f0cd47a5d106dbad745"}},{"id":"org.orecruncher:dsurround:1.12.2-3.6.1.0@jar","name":"Dynamic Surroundings","type":"ForgeMod","artifact":{"size":15929762,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/DynamicSurroundings-1.12.2-3.6.1.0.jar","MD5":"1a0d50a34941865ff091a492e0612502"}},{"id":"sekelsta.horse_colors:horse_colors:1.12.2-1.2.6@jar","name":"Realistic Horse Genetics","type":"ForgeMod","artifact":{"size":711220,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/horse_colors-1.12.2-1.3.6.a.jar","MD5":"073030905d404994027f44839279cb3a"}},{"id":"journeymap:journeymap:1.12.2-5.7.1@jar","name":"JourneyMap","type":"ForgeMod","artifact":{"size":6990968,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/journeymap-1.12.2-5.7.1.jar","MD5":"8160b970fc14cff30dff2785bb9f964c"}},{"id":"net.optifine:optifine:1.12.2_HD_U_G5@jar","name":"OptiFine 1.12.2_HD_U_G5","type":"ForgeMod","artifact":{"size":2669107,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/OptiFine_1.12.2_HD_U_G5.jar","MD5":"54e561e441192cf009803ae95873c5d0"}},{"id":"org.orecruncher:orelib:1.12.2-3.6.0.1@jar","name":"OreLib Support Mod","type":"ForgeMod","artifact":{"size":341728,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/OreLib-1.12.2-3.6.0.1.jar","MD5":"00cce3b240365458adcad3e77782358a"}},{"id":"com.shynieke:statues:0.8.9.2@jar","name":"Statues mod","type":"ForgeMod","artifact":{"size":1811654,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/statues-1.12.X-0.8.9.2.jar","MD5":"a8c31d02a76d408ba19aabf832038087"}},{"id":"com.creativemd.opf:opframe:1.7.0@jar","name":"WCOnlinePictureFrame","type":"ForgeMod","artifact":{"size":64116,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/WCOnlinePictureFrame-1.7.0-beta-1-forge-1.12.2.jar","MD5":"1f9cd1d59f899ea895fd32aee42ed72f"}},{"id":"com.westeroscraft:westerosblocks:4.3.4-126@jar","name":"WesterosBlocks","type":"ForgeMod","artifact":{"size":19056312,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/WesterosBlocks-4.3.4-forge-1.12.2.jar","MD5":"fa85607024a470146e9ad088fe2c847c"}},{"id":"com.westeroscraft:westerosentities:1.12.2-1.3.0@jar","name":"WesterosEntities","type":"ForgeMod","artifact":{"size":287218,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/forgemods/required/westerosentities-1.12.2-b6.jar","MD5":"a02be14d86cb9748892a92778b194f4c"}},{"id":"betterfoliage.cfg","name":"betterfoliage.cfg","type":"File","artifact":{"size":7085,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/betterfoliage.cfg","MD5":"99b9aa746613296250a4adc35a27d749","path":"config/betterfoliage.cfg"}},{"id":"creativecore-client.json","name":"creativecore-client.json","type":"File","artifact":{"size":221,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/creativecore-client.json","MD5":"057bbac7b2b20176faa1f70d4f1b2abf","path":"config/creativecore-client.json"}},{"id":"dsurround.cfg","name":"dsurround.cfg","type":"File","artifact":{"size":15280,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/dsurround/dsurround.cfg","MD5":"005809133dcac77ebfaba07c5b9fa334","path":"config/dsurround/dsurround.cfg"}},{"id":"forge.cfg","name":"forge.cfg","type":"File","artifact":{"size":3865,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/forge.cfg","MD5":"f171671570ac5e1c6ce1717ff70c8633","path":"config/forge.cfg"}},{"id":"forgeChunkLoading.cfg","name":"forgeChunkLoading.cfg","type":"File","artifact":{"size":2155,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/forgeChunkLoading.cfg","MD5":"255d6e9355788641e30db10e3c0b8551","path":"config/forgeChunkLoading.cfg"}},{"id":"horse_colors.cfg","name":"horse_colors.cfg","type":"File","artifact":{"size":4336,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/horse_colors.cfg","MD5":"a4976010ba4938857239ef37f9a491ed","path":"config/horse_colors.cfg"}},{"id":"journeymap_ModInfo.cfg","name":"journeymap_ModInfo.cfg","type":"File","artifact":{"size":370,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/journeymap_ModInfo.cfg","MD5":"70f47abc0084802ab02ff3a3d769e244","path":"config/journeymap_ModInfo.cfg"}},{"id":"journeymap_server.cfg","name":"journeymap_server.cfg","type":"File","artifact":{"size":615,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/journeymap_server.cfg","MD5":"704e8c040987675a9ac814abe9b7cdc6","path":"config/journeymap_server.cfg"}},{"id":"opframe-client.json","name":"opframe-client.json","type":"File","artifact":{"size":32,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/opframe-client.json","MD5":"012d93890dbdefeb4343f8af53dc3b91","path":"config/opframe-client.json"}},{"id":"orelib.cfg","name":"orelib.cfg","type":"File","artifact":{"size":604,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/orelib.cfg","MD5":"70f8e6b6d17a73fe970a92f72cfbf21c","path":"config/orelib.cfg"}},{"id":"splash.properties","name":"splash.properties","type":"File","artifact":{"size":358,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/splash.properties","MD5":"b5ac94dbd3b3529fbb233f9df0e14795","path":"config/splash.properties"}},{"id":"statues.cfg","name":"statues.cfg","type":"File","artifact":{"size":6931,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/statues.cfg","MD5":"3aae8ac0d253b88aedaaada1aa45a0c5","path":"config/statues.cfg"}},{"id":"westerosblocks.cfg","name":"westerosblocks.cfg","type":"File","artifact":{"size":113,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/config/westerosblocks.cfg","MD5":"a9dcb36d082866117ec7dc63ff689b85","path":"config/westerosblocks.cfg"}},{"id":"options.txt","name":"options.txt","type":"File","artifact":{"size":2788,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/options.txt","path":"options.txt"}},{"id":"WesterosCraft-v1.12.2-29.zip","name":"WesterosCraft-v1.12.2-29.zip","type":"File","artifact":{"size":55905310,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/resourcepacks/WesterosCraft-v1.12.2-29.zip","MD5":"eb3c794aafe44c7ea7d3407a10553bd4","path":"resourcepacks/WesterosCraft-v1.12.2-29.zip"}},{"id":"servers.dat","name":"servers.dat","type":"File","artifact":{"size":84,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/servers.dat","MD5":"71d99e229d7d2b8d2a6423e46832a4b8","path":"servers.dat"}},{"id":"Chocapic13_V9_Extreme.zip","name":"Chocapic13_V9_Extreme.zip","type":"File","artifact":{"size":3134623,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/shaderpacks/Chocapic13_V9_Extreme.zip","MD5":"2a5ec76ad7acc47005e32cc1e5da1e41","path":"shaderpacks/Chocapic13_V9_Extreme.zip"}},{"id":"SEUS-Renewed-v1.0.1.zip","name":"SEUS-Renewed-v1.0.1.zip","type":"File","artifact":{"size":7062638,"url":"https://helios-files.geekcorner.eu.org/servers/WesterosCraft-Demo-1.12.2/files/shaderpacks/SEUS-Renewed-v1.0.1.zip","MD5":"8faeb04d9953c9b10b0794acafc4c1aa","path":"shaderpacks/SEUS-Renewed-v1.0.1.zip"}}]}]} From a8a00e46ba8b18a2a632919be5085a7f16a72918 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 17:26:45 +0200 Subject: [PATCH 34/52] Add Athena's Shield section to README Introduced instructions on activating Athena's Shield in HeliosLauncher. This includes steps for running the verification system and choosing various options to ensure mod integrity and a secure gaming experience. --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index ce117b3a..2bffa04d 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,42 @@ Builds for macOS may not work on Windows/Linux and vice-versa. --- +### Athena's Shield + +The Extra File Verification System + +Athena’s Shield prevents the use of unapproved mods in HeliosLauncher by verifying the integrity of each mod from the distribution, blocking unauthorized changes, and ensuring a secure, reliable gaming experience. + +**How to active this ?** + +```console +> npm run athshield +``` + +``` +Would you like to activate Athena's Shield? (yes/no): yes +Would you like to activate debug mode? (yes/no): yes +Would you like to hide or block the menu? (hide/block): block + +Athena's Shield activated. Menu blocked. +``` + +You can choose whether Athena's Shield hides the mod category or blocks interaction with the drop-in mod. + +```console +▄▄▄ ▄▄▄█████▓ ██░ ██ ▓█████ ███▄ █ ▄▄▄ ██████ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄ +▒████▄ ▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ██ ▀█ █ ▒████▄ ▒██ ▒ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌ +▒██ ▀█▄ ▒ ▓██░ ▒░▒██▀▀██░▒███ ▓██ ▀█ ██▒▒██ ▀█▄ ░ ▓██▄ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌ +░██▄▄▄▄██░ ▓██▓ ░ ░▓█ ░██ ▒▓█ ▄ ▓██▒ ▐▌██▒░██▄▄▄▄██ ▒ ██▒ ▒ ██▒░▓█ ░██ ░██░▒▓█ ▄ ▒██░ ░▓█▄ ▌ + ▓█ ▓██▒ ▒██▒ ░ ░▓█▒░██▓░▒████▒▒██░ ▓██░ ▓█ ▓██▒▒██████▒▒ ▒██████▒▒░▓█▒░██▓░██░░▒████▒░██████▒░▒████▓ + ▒▒ ▓▒█░ ▒ ░░ ▒ ░░▒░▒░░ ▒░ ░░ ▒░ ▒ ▒ ▒▒ ▓▒█░▒ ▒▓▒ ▒ ░ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░▓ ░░ ▒░ ░░ ▒░▓ ░ ▒▒▓ ▒ + ▒ ▒▒ ░ ░ ▒ ░▒░ ░ ░ ░ ░░ ░░ ░ ▒░ ▒ ▒▒ ░░ ░▒ ░ ░ ░ ░▒ ░ ░ ▒ ░▒░ ░ ▒ ░ ░ ░ ░░ ░ ▒ ░ ░ ▒ ▒ + ░ ▒ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ + ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ +``` + +--- + ### Visual Studio Code All development of the launcher should be done using [Visual Studio Code][vscode]. From aab9ff5c3ea018741c66675a99e359ab993be046 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 17:39:40 +0200 Subject: [PATCH 35/52] Update launch process with Discord RPC enhancements Refined the logic for initializing and shutting down Discord Rich Presence (RPC). Added more detailed logging and error handling to the game launch process, enhancing the user experience and debugging capabilities. --- app/assets/js/scripts/landing.js | 85 ++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 13195793..6720901e 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -702,72 +702,83 @@ async function dlAsync(login = true) { if(login) { const authUser = ConfigManager.getSelectedAccount() - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.accountToProcessBuilder', {'userDisplayName': authUser.displayName})) + 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 + const diff = Date.now() - start if(diff < MIN_LINGER) { - setTimeout(onLoadComplete, MIN_LINGER-diff) + 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})) + // 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')) } } - 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') + 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')) } - }) + } - proc.on('close', (code, _signal) => { - if (hasRPC) { - DiscordWrapper.shutdownRPC() - hasRPC = false + try { + // Build Minecraft process. + proc = pb.build() + + // Bind listeners to stdout and stderr. + 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 + }) } - 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) + } catch(err) { + loggerLaunchSuite.error('Error during launch', err) + showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.checkConsoleForDetails')) + } } } From 8744d3b8c5e9a599972201f21d713c53927045c0 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 17:41:08 +0200 Subject: [PATCH 36/52] Remove unnecessary comment separators Deleted redundant comment separators to clean up the code and improve readability. This does not affect the functionality but ensures the codebase remains easier to maintain. --- app/assets/js/scripts/landing.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 6720901e..0d00f5cf 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -437,8 +437,6 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { } -//////////////////////////////////////////////////////////////////////////////////// - /** * @Name dlAsync Function * @returns {Promise} @@ -782,8 +780,6 @@ async function dlAsync(login = true) { } } -//////////////////////////////////////////////////////////////////////////////////// - /** * News Loading Functions */ From 0893cf5a5ec075a790f7124a1d3324fa73ace9cd Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 17:45:03 +0200 Subject: [PATCH 37/52] Update landing script to improve debug capabilities Added comment about server regex customization and clarified the login parameter's role in the dlAsync function, simplifying testing and validation without game launch. --- app/assets/js/scripts/landing.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 0d00f5cf..163f24db 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -472,6 +472,7 @@ 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 @@ -481,6 +482,10 @@ const EXCLUDED_MODS = [ ] 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') const loggerLanding = LoggerUtil.getLogger('Landing') setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo')) From f0321e1f6d217d508d7e7b0d62d783ea2ea3128d Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sat, 26 Oct 2024 17:46:24 +0200 Subject: [PATCH 38/52] Reorder function calls in prepareModsTab. Moved the manageModCategory function call to the end within prepareModsTab. This ensures that category management happens after resolving the UI elements, maintaining the logical sequence of operations. --- app/assets/js/scripts/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index 96d956f9..599c45a6 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -1154,10 +1154,10 @@ function animateSettingsTabRefresh(){ * Prepare the Mods tab for display. */ async function prepareModsTab(first){ - manageModCategory() await resolveModsForUI() await resolveDropinModsForUI() await resolveShaderpacksForUI() + manageModCategory() bindDropinModsRemoveButton() bindDropinModFileSystemButton() bindShaderpackButton() From 0348e83ffe91c52b79ad651a0952c7e82414b58b Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 10:45:45 +0100 Subject: [PATCH 39/52] Refactor Athena's Shield configuration handling --- app/assets/athshield/variables.athshield | 5 --- .../athshield.js => extraverif/extraverif.js} | 40 ++++++------------- .../parserExtraverif.js} | 13 ++---- app/assets/extraverif/variables.json | 5 +++ 4 files changed, 21 insertions(+), 42 deletions(-) delete mode 100644 app/assets/athshield/variables.athshield rename app/assets/{athshield/athshield.js => extraverif/extraverif.js} (51%) rename app/assets/{athshield/parserAthShield.js => extraverif/parserExtraverif.js} (53%) create mode 100644 app/assets/extraverif/variables.json diff --git a/app/assets/athshield/variables.athshield b/app/assets/athshield/variables.athshield deleted file mode 100644 index b7c29b1a..00000000 --- a/app/assets/athshield/variables.athshield +++ /dev/null @@ -1,5 +0,0 @@ -{ - "athenaShieldActivated": false, - "menuVisibility": "visible", - "debug": "false" -} \ No newline at end of file diff --git a/app/assets/athshield/athshield.js b/app/assets/extraverif/extraverif.js similarity index 51% rename from app/assets/athshield/athshield.js rename to app/assets/extraverif/extraverif.js index 15b7764c..5f285b3b 100644 --- a/app/assets/athshield/athshield.js +++ b/app/assets/extraverif/extraverif.js @@ -2,75 +2,62 @@ const fs = require('fs') const readline = require('readline') const path = require('path') -// Path to the configuration file -const configPath = path.join(__dirname, 'variables.athshield') +const configPath = path.join(__dirname, 'variables.json') -// Load the variables from the file function loadConfig() { const rawData = fs.readFileSync(configPath) - return JSON.parse(rawData.toString()) // Convert Buffer to string + return JSON.parse(rawData.toString()) } -// Save the variables to the file function saveConfig(config) { const data = JSON.stringify(config, null, 2) fs.writeFileSync(configPath, data) } -// Create the readline interface const rl = readline.createInterface({ input: process.stdin, output: process.stdout }) -// Function to ask questions to the user function startCLI() { const config = loadConfig() - rl.question('Would you like to activate Athena\'s Shield? (yes/no): ', (answer) => { - if (answer.trim().startsWith('//')) { - console.log('This is a comment; the line is ignored.') + rl.question('Would you like to activate extra file verification? (yes/no): ', (answer) => { rl.close() return - } if (answer.toLowerCase() === 'yes') { - config.athenaShieldActivated = true + config.extraFileVerifActivated = true rl.question('Would you like to activate debug mode? (yes/no): ', (debugAnswer) => { - config.debug = debugAnswer.toLowerCase() === 'yes' // Set debug to true or false + config.debug = debugAnswer.toLowerCase() === 'yes' rl.question('Would you like to hide or block the menu? (hide/block): ', (menuAnswer) => { - if (menuAnswer.trim().startsWith('//')) { - console.log('This is a comment; the line is ignored.') rl.close() return - } if (menuAnswer.toLowerCase() === 'hide') { - config.menuVisibility = 'hidden' // Set to 'hidden' - console.log('Athena\'s Shield activated. Menu hidden.') + config.menuVisibility = 'hidden' + console.log('Extra file verification is activated. Menu hidden.') } else if (menuAnswer.toLowerCase() === 'block') { - config.menuVisibility = 'blocked' // Set to 'blocked' - console.log('Athena\'s Shield activated. Menu blocked.') + config.menuVisibility = 'blocked' + console.log('Extra file verification is activated. Menu blocked.') } else { console.log('Invalid option for the menu.') rl.close() return } - // Save the modified configuration saveConfig(config) rl.close() }) }) } else if (answer.toLowerCase() === 'no') { - console.log('Athena\'s Shield not activated. Closing the CLI.') - config.athenaShieldActivated = false - config.menuVisibility = 'visible' // Reset to default - config.debug = 'false' + console.log('Extra file verification is not activated. Closing the CLI.') + config.extraFileVerifActivated = false + config.menuVisibility = 'visible' + config.debug = false - // Save the modified configuration saveConfig(config) rl.close() } else { @@ -80,5 +67,4 @@ function startCLI() { }) } -// Launch the CLI startCLI() diff --git a/app/assets/athshield/parserAthShield.js b/app/assets/extraverif/parserExtraverif.js similarity index 53% rename from app/assets/athshield/parserAthShield.js rename to app/assets/extraverif/parserExtraverif.js index 336e0d4f..43921da3 100644 --- a/app/assets/athshield/parserAthShield.js +++ b/app/assets/extraverif/parserExtraverif.js @@ -1,37 +1,30 @@ const fs = require('fs') const path = require('path') -// Chemin vers le fichier de configuration -const configPath = path.join(__dirname, 'variables.athshield') +const configPath = path.join(__dirname, 'variables.json') -// Classe pour gérer Athena's Shield -class AthenaShield { +class ExtraFileVerification { constructor() { this.config = this.loadConfig() } - // Charger les variables depuis le fichier loadConfig() { const rawData = fs.readFileSync(configPath) return JSON.parse(rawData.toString()) } - // Récupérer le statut d'Athena's Shield get status() { - return this.config.athenaShieldActivated + return this.config.extraFileVerifActivated } - // Récupérer la visibilité du menu get type() { return this.config.menuVisibility } - // Récupérer le mode debug get debug() { return this.config.debug } } -// Exporter une instance de la classe const athenaShieldInstance = new AthenaShield() module.exports = athenaShieldInstance diff --git a/app/assets/extraverif/variables.json b/app/assets/extraverif/variables.json new file mode 100644 index 00000000..42cd8264 --- /dev/null +++ b/app/assets/extraverif/variables.json @@ -0,0 +1,5 @@ +{ + "extraFileVerifActivated": false, + "menuVisibility": "visible", + "debug": "false" +} From f68165c66f26f814d1a5e13b950b8aeb993a4d04 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 10:47:55 +0100 Subject: [PATCH 40/52] Refactor class name in parserExtraverif.js --- app/assets/extraverif/parserExtraverif.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/extraverif/parserExtraverif.js b/app/assets/extraverif/parserExtraverif.js index 43921da3..ee59892f 100644 --- a/app/assets/extraverif/parserExtraverif.js +++ b/app/assets/extraverif/parserExtraverif.js @@ -26,5 +26,5 @@ class ExtraFileVerification { } } -const athenaShieldInstance = new AthenaShield() -module.exports = athenaShieldInstance +const ExtraFileVerificationInstance = new ExtraFileVerification() +module.exports = ExtraFileVerificationInstance From 040ca971e8d3ad1ba0ae5752a80c9dac9bdc56e6 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 10:57:28 +0100 Subject: [PATCH 41/52] Refactor CLI activation logic and improve debug capabilities This commit refactors the code in extraverif.js to improve the activation logic for the extra file verification CLI. It now handles comment lines correctly and ignores them. Additionally, the debug capabilities have been enhanced. The landing script has also been updated to improve debug capabilities. Unnecessary comment separators have been removed. --- app/assets/extraverif/extraverif.js | 12 +++++++++--- app/assets/extraverif/variables.json | 8 ++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/assets/extraverif/extraverif.js b/app/assets/extraverif/extraverif.js index 5f285b3b..1c313bf0 100644 --- a/app/assets/extraverif/extraverif.js +++ b/app/assets/extraverif/extraverif.js @@ -23,8 +23,11 @@ function startCLI() { const config = loadConfig() rl.question('Would you like to activate extra file verification? (yes/no): ', (answer) => { + if (answer.trim().startsWith('//')) { + console.log('This is a comment; the line is ignored.') rl.close() return + } if (answer.toLowerCase() === 'yes') { config.extraFileVerifActivated = true @@ -33,15 +36,18 @@ function startCLI() { config.debug = debugAnswer.toLowerCase() === 'yes' rl.question('Would you like to hide or block the menu? (hide/block): ', (menuAnswer) => { + if (menuAnswer.trim().startsWith('//')) { + console.log('This is a comment; the line is ignored.') rl.close() return + } if (menuAnswer.toLowerCase() === 'hide') { config.menuVisibility = 'hidden' - console.log('Extra file verification is activated. Menu hidden.') + console.log('Extra file verification activated. Menu hidden.') } else if (menuAnswer.toLowerCase() === 'block') { config.menuVisibility = 'blocked' - console.log('Extra file verification is activated. Menu blocked.') + console.log('Extra file verification activated. Menu blocked.') } else { console.log('Invalid option for the menu.') rl.close() @@ -53,7 +59,7 @@ function startCLI() { }) }) } else if (answer.toLowerCase() === 'no') { - console.log('Extra file verification is not activated. Closing the CLI.') + console.log('Extra file verification not activated. Closing the CLI.') config.extraFileVerifActivated = false config.menuVisibility = 'visible' config.debug = false diff --git a/app/assets/extraverif/variables.json b/app/assets/extraverif/variables.json index 42cd8264..a8c88e0e 100644 --- a/app/assets/extraverif/variables.json +++ b/app/assets/extraverif/variables.json @@ -1,5 +1,5 @@ { - "extraFileVerifActivated": false, - "menuVisibility": "visible", - "debug": "false" -} + "extraFileVerifActivated": false, + "menuVisibility": "visible", + "debug": false +} \ No newline at end of file From aef5e02f1954d2aeb424d8031fb96c0940687c3d Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 10:57:37 +0100 Subject: [PATCH 42/52] Refactor mod verification logic and improve debug capabilities --- app/assets/js/scripts/landing.js | 71 +++++++++---------------------- app/assets/js/scripts/settings.js | 6 +-- 2 files changed, 24 insertions(+), 53 deletions(-) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 163f24db..43272a67 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -437,35 +437,7 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { } -/** - * @Name dlAsync Function - * @returns {Promise} - * - * @author Sandro642 - * @Cheating Athena's Shield - * - * @Added whitelist for mods - * @Added support for the new HeliosLauncher version - */ -/** - * @Reviewed on 10.26.2024 expires on XX.XX.2025 - * @Bugs discovereds: 0 - * @Athena's Shield - * @Sandro642 - */ - - -// ▄▄▄ ▄▄▄█████▓ ██░ ██ ▓█████ ███▄ █ ▄▄▄ ██████ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄ -// ▒████▄ ▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ██ ▀█ █ ▒████▄ ▒██ ▒ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌ -// ▒██ ▀█▄ ▒ ▓██░ ▒░▒██▀▀██░▒███ ▓██ ▀█ ██▒▒██ ▀█▄ ░ ▓██▄ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌ -// ░██▄▄▄▄██░ ▓██▓ ░ ░▓█ ░██ ▒▓█ ▄ ▓██▒ ▐▌██▒░██▄▄▄▄██ ▒ ██▒ ▒ ██▒░▓█ ░██ ░██░▒▓█ ▄ ▒██░ ░▓█▄ ▌ -// ▓█ ▓██▒ ▒██▒ ░ ░▓█▒░██▓░▒████▒▒██░ ▓██░ ▓█ ▓██▒▒██████▒▒ ▒██████▒▒░▓█▒░██▓░██░░▒████▒░██████▒░▒████▓ -// ▒▒ ▓▒█░ ▒ ░░ ▒ ░░▒░▒░░ ▒░ ░░ ▒░ ▒ ▒ ▒▒ ▓▒█░▒ ▒▓▒ ▒ ░ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░▓ ░░ ▒░ ░░ ▒░▓ ░ ▒▒▓ ▒ -// ▒ ▒▒ ░ ░ ▒ ░▒░ ░ ░ ░ ░░ ░░ ░ ▒░ ▒ ▒▒ ░░ ░▒ ░ ░ ░ ░▒ ░ ░ ▒ ░▒░ ░ ▒ ░ ░ ░ ░░ ░ ▒ ░ ░ ▒ ▒ -// ░ ▒ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ -// ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ -// ░ // Keep reference to Minecraft Process let proc @@ -512,8 +484,8 @@ async function dlAsync(login = true) { // --------- Mod Verification Logic --------- - if (athShield.status) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.usingAthShield')) + if (extraFileVerif.status) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.usingExtraFileVerif')) const modsDir = path.join(ConfigManager.getDataDirectory(), 'instances', serv.rawServer.id, 'mods') @@ -529,9 +501,9 @@ async function dlAsync(login = true) { 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', { + const modIdentity = mdl.rawModule || mdl.rawModule.artifact.MD5 + if (extraFileVerif.debug) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.distributionIdentityError', { 'moduleName': mdl.rawModule.name, 'moduleIdentity': modIdentity })) @@ -543,17 +515,16 @@ async function dlAsync(login = true) { // 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})) + if (extraFileVerif.debug) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.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', { + if (extraFileVerif.debug) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.identityNotFoundUsingHash', { 'filePath': filePath, 'hash': hash })) @@ -564,7 +535,7 @@ async function dlAsync(login = true) { // Validate mods function const validateMods = () => { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.startingModValidation')) + loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.startingModValidation')) const installedMods = fs.readdirSync(modsDir) let valid = true @@ -573,17 +544,17 @@ async function dlAsync(login = true) { // Skip validation for mods in the excluded list if (EXCLUDED_MODS.includes(mod)) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modValidationBypassed', {'mod': mod})) + loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.modValidationBypassed', {'mod': mod})) continue } const expectedIdentity = distroMods[modPath] - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.validatingMod', {'mod': mod})) + loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.validatingMod', {'mod': mod})) if (expectedIdentity) { const modIdentity = extractModIdentity(modPath) - if (athShield.debug) { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.expectedAndCalculatedIdentity', { + if (extraFileVerif.debug) { + loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.expectedAndCalculatedIdentity', { 'expectedIdentity': expectedIdentity, 'mod': mod, 'modIdentity': modIdentity @@ -591,8 +562,8 @@ async function dlAsync(login = true) { } if (modIdentity !== expectedIdentity) { - if (athShield.debug) { - loggerLanding.error(Lang.queryJS('landing.dlAsync.AthShield.modIdentityMismatchError', { + if (extraFileVerif.debug) { + loggerLanding.error(Lang.queryJS('landing.dlAsync.extraFileVerif.modIdentityMismatchError', { 'mod': mod, 'expectedIdentity': expectedIdentity, 'modIdentity': modIdentity @@ -603,26 +574,26 @@ async function dlAsync(login = true) { break } } else { - loggerLanding.warn(Lang.queryJS('landing.dlAsync.AthShield.expectedIdentityNotFound', {'mod': mod})) + loggerLanding.warn(Lang.queryJS('landing.dlAsync.extraFileVerif.expectedIdentityNotFound', {'mod': mod})) valid = false break } } - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.modValidationCompleted')) + loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.modValidationCompleted')) return valid } // Perform mod validation before proceeding if (!validateMods()) { - const errorMessage = Lang.queryJS('landing.dlAsync.AthShield.invalidModsDetectedMessage', {'folder': ConfigManager.getNameDataPath()}) + const errorMessage = Lang.queryJS('landing.dlAsync.extraFileVerif.invalidModsDetectedMessage', {'folder': ConfigManager.getNameDataPath()}) loggerLanding.error(errorMessage) showLaunchFailure(errorMessage, null) return } } else { - loggerLanding.info(Lang.queryJS('landing.dlAsync.AthShield.notUsingAthShield')) + loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.notUsingExtraFileVerif')) } // --------- End of Mod Verification Logic --------- @@ -681,7 +652,7 @@ async function dlAsync(login = true) { return } } else { - loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.AthShield.downloadingFiles')) + loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.notUsingExtraFileVerif.downloadingFiles')) } // Remove download bar. diff --git a/app/assets/js/scripts/settings.js b/app/assets/js/scripts/settings.js index 599c45a6..d14f71e6 100644 --- a/app/assets/js/scripts/settings.js +++ b/app/assets/js/scripts/settings.js @@ -3,7 +3,7 @@ const os = require('os') const semver = require('semver') const DropinModUtil = require('./assets/js/dropinmodutil') -const athShield = require('./assets/athshield/parserAthShield') +const extraFileVerif = require('./assets/extraverif/parserExtraverif') const { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants') const settingsState = { @@ -722,10 +722,10 @@ function manageModCategory() { const modsButton = document.querySelector('button[rSc="settingsTabMods"]') const dropInMods = document.getElementById('settingsDropinModsContainer') - if (athShield.type === 'hidden') { + if (extraFileVerif.type === 'hidden') { // Hide the Mods navigation button modsButton.style.display = 'none' - } else if (athShield.type === 'blocked') { + } else if (extraFileVerif.type === 'blocked') { // Hide the drop-in mods elements dropInMods.style.display = 'none' } From 1614b63ae1cfc4d1de3e34764b1a3113152561f6 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 10:57:46 +0100 Subject: [PATCH 43/52] Refactor language file and update mod verification messages --- app/assets/lang/en_US.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/lang/en_US.toml b/app/assets/lang/en_US.toml index d1a8f062..12ad1102 100644 --- a/app/assets/lang/en_US.toml +++ b/app/assets/lang/en_US.toml @@ -215,7 +215,7 @@ gameExited = "Game process exited with code {code}." gameErrorDuringLaunch = "Error during game launch {error}." waintingLaunchingGame = "Waiting for game window..." -[js.landing.dlAsync.AthShield] +[js.landing.dlAsync.extraFileVerif] distributionIdentityError = "Expected Identity from Distribution for {moduleName}: {moduleIdentity}." modIdentityExtraction = "Extracting identity for mod at: {filePath}." identityNotFoundUsingHash = "No identity found in manifest for {filePath}, using hash: {hash}" @@ -226,10 +226,10 @@ expectedAndCalculatedIdentity = "Expected Identity: {expectedIdentity}, Calculat modIdentityMismatchError = "Mod identity mismatch! Mod: {mod}, Expected: {expectedIdentity}, Found: {modIdentity}" expectedIdentityNotFound = "No expected identity found for mod: {mod}. Marking as invalid." modValidationCompleted = "Mod validation completed." -invalidModsDetectedMessage = "Athena's Shield has detected invalid mods. Please delete the {folder} folder and restart the launcher." +invalidModsDetectedMessage = "Extra file verification has detected invalid mods. Please delete the {folder} folder and restart the launcher." downloadingFiles = "No invalid files, skipping download." -usingAthShield = "Athena's Shield activated, Resource usage..." -notUsingAthShield = "Athena's Shield deactivated, Not using resources..." +usingExtraFileVerif = "Extra file verification activated, Resource usage..." +notUsingExtraFileVerif = "Extra file verification deactivated, Not using resources..." [js.landing.news] checking = "Checking for News" From 93bca16ebbcfd15be0760ceb112e4234f02291a8 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 11:00:30 +0100 Subject: [PATCH 44/52] Refactor documentation and file names for mod verification --- docs/Athena_s-Shield-Documentation.pdf | Bin 49121 -> 0 bytes ...a's Shield.md => ExtraFileVerification.md} | 24 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) delete mode 100644 docs/Athena_s-Shield-Documentation.pdf rename docs/{Athena's Shield.md => ExtraFileVerification.md} (51%) diff --git a/docs/Athena_s-Shield-Documentation.pdf b/docs/Athena_s-Shield-Documentation.pdf deleted file mode 100644 index 0b8a218c04fb24f8faa9b2a430d7404d487e88e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49121 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 Hcc1?kir#3e diff --git a/docs/Athena's Shield.md b/docs/ExtraFileVerification.md similarity index 51% rename from docs/Athena's Shield.md rename to docs/ExtraFileVerification.md index fd19f411..0d0f3cab 100644 --- a/docs/Athena's Shield.md +++ b/docs/ExtraFileVerification.md @@ -1,16 +1,16 @@ -**Documentation: Athena's Shield for HeliosLauncher** +**Documentation: ExtraFileVerification for HeliosLauncher** ### Introduction -HeliosLauncher is a game launcher for Minecraft developed by Daniel Scalzi, designed to provide a secure and optimized gaming experience. To maintain game integrity and prevent unauthorized or altered mods, I have implemented a new security system called Athena's Shield. +HeliosLauncher is a game launcher for Minecraft developed by Daniel Scalzi, designed to provide a secure and optimized gaming experience. To maintain game integrity and prevent unauthorized or altered mods, I have implemented a new security system called ExtraFileVerification. -### Purpose of Athena's Shield -The main purpose of Athena's Shield is to ensure that only authorized and verified mods are used with HeliosLauncher. This system checks the integrity of installed mods to prevent any modifications or additions of malicious or unapproved mods. +### Purpose of ExtraFileVerification +The main purpose of ExtraFileVerification is to ensure that only authorized and verified mods are used with HeliosLauncher. This system checks the integrity of installed mods to prevent any modifications or additions of malicious or unapproved mods. ### Key Features **Installed Mod Validation:** -- Before launching the game, Athena's Shield verifies the mods present in the HeliosLauncher mods folder. +- Before launching the game, ExtraFileVerification verifies the mods present in the HeliosLauncher mods folder. - Each mod is validated by comparing its name and hash (digital fingerprint) with expected data in the distribution. - If a mod does not meet validation criteria, game launch is stopped, and an error message is displayed. @@ -21,7 +21,7 @@ The main purpose of Athena's Shield is to ensure that only authorized and verifi **Modification Management:** -- Athena's Shield also verifies changes to mods. For instance, if a mod is deleted, replaced, or renamed, it will be detected. +- ExtraFileVerification also verifies changes to mods. For instance, if a mod is deleted, replaced, or renamed, it will be detected. - Hash verification ensures that mods have not been altered since their initial download. **Error Messages and Instructions:** @@ -31,18 +31,18 @@ The main purpose of Athena's Shield is to ensure that only authorized and verifi ### User Benefits -- **Enhanced Security:** By preventing unauthorized mods and verifying integrity, Athena's Shield protects users from malicious mods. +- **Enhanced Security:** By preventing unauthorized mods and verifying integrity, ExtraFileVerification protects users from malicious mods. - **Reliable Gaming Experience:** Ensures that only tested and validated mods are used, guaranteeing a stable and trouble-free gaming experience. - **Ease of Use:** Users are guided with clear messages and instructions to help resolve any potential conflicts, simplifying problem-solving. ### Conclusion -Athena's Shield is a significant step toward enhancing the security and integrity of HeliosLauncher. With this integration, I ensure that every Minecraft user enjoys a safe and reliable gaming experience, without compromising on quality or security. +ExtraFileVerification is a significant step toward enhancing the security and integrity of HeliosLauncher. With this integration, I ensure that every Minecraft user enjoys a safe and reliable gaming experience, without compromising on quality or security. -If you have any questions or need further clarification about Athena's Shield, feel free to contact me. +If you have any questions or need further clarification about ExtraFileVerification, feel free to contact me. -**The only way to bypass Athena's Shield would be through advanced cryptography knowledge, involving signature copying or hash modification.** +**The only way to bypass ExtraFileVerification would be through advanced cryptography knowledge, involving signature copying or hash modification.** -The creation and verification of Athena's Shield are currently complete, though additional improvements may be made in the future, as with any project. +The creation and verification of ExtraFileVerification are currently complete, though additional improvements may be made in the future, as with any project. Respectfully, -**SORIA Sandro (Sandro642)** \ No newline at end of file +**SORIA Sandro (Sandro642)** From bd6701521007b73a055ec3e9e38bad64ef3c57a3 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 11:00:35 +0100 Subject: [PATCH 45/52] Refactor package.json to update extraverif script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a9d4d5b3..5240f2d5 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "dist:mac": "npm run dist -- -m", "dist:linux": "npm run dist -- -l", "lint": "eslint --config .eslintrc.json .", - "athshield": "node app/assets/athshield/athshield.js" + "extraverif": "node app/assets/extraverif/extraverif.js" }, "engines": { "node": "20.x.x" From e51eefa61151734cc33b7911b6b23c7bb4e6064d Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 11:03:16 +0100 Subject: [PATCH 46/52] Refactor mod verification system and improve debug capabilities --- README.md | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 2bffa04d..6d68b47d 100644 --- a/README.md +++ b/README.md @@ -126,39 +126,27 @@ Builds for macOS may not work on Windows/Linux and vice-versa. --- -### Athena's Shield +### ExtraFileVerification The Extra File Verification System -Athena’s Shield prevents the use of unapproved mods in HeliosLauncher by verifying the integrity of each mod from the distribution, blocking unauthorized changes, and ensuring a secure, reliable gaming experience. +ExtraFileVerification prevents the use of unapproved mods in HeliosLauncher by verifying the integrity of each mod from the distribution, blocking unauthorized changes, and ensuring a secure, reliable gaming experience. **How to active this ?** ```console -> npm run athshield +> npm run extraverif ``` ``` -Would you like to activate Athena's Shield? (yes/no): yes +Would you like to activate extra file verification? (yes/no): yes Would you like to activate debug mode? (yes/no): yes Would you like to hide or block the menu? (hide/block): block -Athena's Shield activated. Menu blocked. +Extra file verification activated. Menu blocked. ``` -You can choose whether Athena's Shield hides the mod category or blocks interaction with the drop-in mod. - -```console -▄▄▄ ▄▄▄█████▓ ██░ ██ ▓█████ ███▄ █ ▄▄▄ ██████ ██████ ██░ ██ ██▓▓█████ ██▓ ▓█████▄ -▒████▄ ▓ ██▒ ▓▒▓██░ ██▒▓█ ▀ ██ ▀█ █ ▒████▄ ▒██ ▒ ▒██ ▒ ▓██░ ██▒▓██▒▓█ ▀ ▓██▒ ▒██▀ ██▌ -▒██ ▀█▄ ▒ ▓██░ ▒░▒██▀▀██░▒███ ▓██ ▀█ ██▒▒██ ▀█▄ ░ ▓██▄ ░ ▓██▄ ▒██▀▀██░▒██▒▒███ ▒██░ ░██ █▌ -░██▄▄▄▄██░ ▓██▓ ░ ░▓█ ░██ ▒▓█ ▄ ▓██▒ ▐▌██▒░██▄▄▄▄██ ▒ ██▒ ▒ ██▒░▓█ ░██ ░██░▒▓█ ▄ ▒██░ ░▓█▄ ▌ - ▓█ ▓██▒ ▒██▒ ░ ░▓█▒░██▓░▒████▒▒██░ ▓██░ ▓█ ▓██▒▒██████▒▒ ▒██████▒▒░▓█▒░██▓░██░░▒████▒░██████▒░▒████▓ - ▒▒ ▓▒█░ ▒ ░░ ▒ ░░▒░▒░░ ▒░ ░░ ▒░ ▒ ▒ ▒▒ ▓▒█░▒ ▒▓▒ ▒ ░ ▒ ▒▓▒ ▒ ░ ▒ ░░▒░▒░▓ ░░ ▒░ ░░ ▒░▓ ░ ▒▒▓ ▒ - ▒ ▒▒ ░ ░ ▒ ░▒░ ░ ░ ░ ░░ ░░ ░ ▒░ ▒ ▒▒ ░░ ░▒ ░ ░ ░ ░▒ ░ ░ ▒ ░▒░ ░ ▒ ░ ░ ░ ░░ ░ ▒ ░ ░ ▒ ▒ - ░ ▒ ░ ░ ░░ ░ ░ ░ ░ ░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ▒ ░ ░ ░ ░ ░ ░ ░ - ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ -``` +You can choose whether extra file verification hides the mod category or blocks interaction with the drop-in mod. --- From 745a11e5bb4165138aee403b077dd7c1dadc3238 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 11:04:30 +0100 Subject: [PATCH 47/52] Refactor landing.js: Remove unnecessary blank lines and comments --- app/assets/js/scripts/landing.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 43272a67..3e23c16d 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -437,8 +437,6 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) { } - - // Keep reference to Minecraft Process let proc // Is DiscordRPC enabled From 4732ee54f7f22a93e89a6d2f58271659891ffb10 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 11:05:21 +0100 Subject: [PATCH 48/52] Refactor landing.js: Update logger name in dlAsync function --- app/assets/js/scripts/landing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index 3e23c16d..e5c99a65 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -457,7 +457,7 @@ async function dlAsync(login = true) { // launching the game. const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite') - const loggerLanding = LoggerUtil.getLogger('Landing') + const loggerLanding = LoggerUtil.getLogger('dlAsync') setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo')) let distro From d659e0fa4186687792842202b5c2343876db79a2 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 11:08:57 +0100 Subject: [PATCH 49/52] Refactor README.md: Remove ExtraFileVerification section --- README.md | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/README.md b/README.md index 6d68b47d..ce117b3a 100644 --- a/README.md +++ b/README.md @@ -126,30 +126,6 @@ Builds for macOS may not work on Windows/Linux and vice-versa. --- -### ExtraFileVerification - -The Extra File Verification System - -ExtraFileVerification prevents the use of unapproved mods in HeliosLauncher by verifying the integrity of each mod from the distribution, blocking unauthorized changes, and ensuring a secure, reliable gaming experience. - -**How to active this ?** - -```console -> npm run extraverif -``` - -``` -Would you like to activate extra file verification? (yes/no): yes -Would you like to activate debug mode? (yes/no): yes -Would you like to hide or block the menu? (hide/block): block - -Extra file verification activated. Menu blocked. -``` - -You can choose whether extra file verification hides the mod category or blocks interaction with the drop-in mod. - ---- - ### Visual Studio Code All development of the launcher should be done using [Visual Studio Code][vscode]. From 07ed316f9f16426e485659b00db3a0506397b513 Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 11:47:01 +0100 Subject: [PATCH 50/52] Refactor package.json: Remove unused crypto dependency --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 5240f2d5..1d78de34 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "dependencies": { "@electron/remote": "^2.1.2", "adm-zip": "^0.5.16", - "crypto": "^1.0.1", "discord-rpc-patch": "^4.0.1", "ejs": "^3.1.10", "ejs-electron": "^3.0.0", From 7f33a1d2fa3f4369a0d8ff043fc087f4d367c75d Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 11:47:23 +0100 Subject: [PATCH 51/52] Refactor landing.js: Update modIdentity assignment in dlAsync function --- app/assets/js/scripts/landing.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/js/scripts/landing.js b/app/assets/js/scripts/landing.js index e5c99a65..3f82c89b 100644 --- a/app/assets/js/scripts/landing.js +++ b/app/assets/js/scripts/landing.js @@ -499,7 +499,7 @@ async function dlAsync(login = true) { mdls.forEach(mdl => { if (mdl.rawModule.name.endsWith('.jar')) { const modPath = path.join(modsDir, mdl.rawModule.name) - const modIdentity = mdl.rawModule || mdl.rawModule.artifact.MD5 + const modIdentity = mdl.rawModule.identity || mdl.rawModule.artifact.MD5 if (extraFileVerif.debug) { loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.distributionIdentityError', { 'moduleName': mdl.rawModule.name, From 2f0a054bfe242d3e09c65df2a4bd73b94f8c239f Mon Sep 17 00:00:00 2001 From: Sandro642 Date: Sun, 27 Oct 2024 11:53:20 +0100 Subject: [PATCH 52/52] Refactor variables.json: Add newline at end of file --- app/assets/extraverif/variables.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/extraverif/variables.json b/app/assets/extraverif/variables.json index a8c88e0e..a4101b00 100644 --- a/app/assets/extraverif/variables.json +++ b/app/assets/extraverif/variables.json @@ -2,4 +2,5 @@ "extraFileVerifActivated": false, "menuVisibility": "visible", "debug": false -} \ No newline at end of file + +}