This commit is contained in:
Sandro Soria 2024-10-26 16:48:08 +00:00 committed by GitHub
commit 7513a1202d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 474 additions and 51 deletions

View File

@ -126,6 +126,42 @@ Builds for macOS may not work on Windows/Linux and vice-versa.
--- ---
### Athena's Shield
The Extra File Verification System
Athenas 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 ### Visual Studio Code
All development of the launcher should be done using [Visual Studio Code][vscode]. All development of the launcher should be done using [Visual Studio Code][vscode].

View File

@ -0,0 +1,84 @@
const fs = require('fs')
const readline = require('readline')
const path = require('path')
// Path to the configuration file
const configPath = path.join(__dirname, 'variables.athshield')
// Load the variables from the file
function loadConfig() {
const rawData = fs.readFileSync(configPath)
return JSON.parse(rawData.toString()) // Convert Buffer to string
}
// 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.close()
return
}
if (answer.toLowerCase() === 'yes') {
config.athenaShieldActivated = true
rl.question('Would you like to activate debug mode? (yes/no): ', (debugAnswer) => {
config.debug = debugAnswer.toLowerCase() === 'yes' // Set debug to true or false
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.')
} 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
config.debug = 'false'
// Save the modified configuration
saveConfig(config)
rl.close()
} else {
console.log('Invalid response.')
rl.close()
}
})
}
// Launch the CLI
startCLI()

View File

@ -0,0 +1,37 @@
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 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

View File

@ -0,0 +1,5 @@
{
"athenaShieldActivated": false,
"menuVisibility": "visible",
"debug": "false"
}

View File

@ -7,10 +7,23 @@ const logger = LoggerUtil.getLogger('ConfigManager')
const sysRoot = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME) const sysRoot = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME)
const dataPath = path.join(sysRoot, '.helioslauncher') const nameDataPath = '.helioslauncher'
const dataPath = path.join(sysRoot, nameDataPath)
const launcherDir = require('@electron/remote').app.getPath('userData') 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.getNameDataPath = function(){
return nameDataPath
}
/** /**
* Retrieve the absolute path of the launcher directory. * Retrieve the absolute path of the launcher directory.
* *

View File

@ -30,6 +30,8 @@ const {
// Internal Requirements // Internal Requirements
const DiscordWrapper = require('./assets/js/discordwrapper') const DiscordWrapper = require('./assets/js/discordwrapper')
const ProcessBuilder = require('./assets/js/processbuilder') const ProcessBuilder = require('./assets/js/processbuilder')
const crypto = require('crypto')
const fs = require('fs')
// Launch Elements // Launch Elements
const launch_content = document.getElementById('launch_content') const launch_content = document.getElementById('launch_content')
@ -435,6 +437,36 @@ async function downloadJava(effectiveJavaOptions, launchAfter = true) {
} }
/**
* @Name dlAsync Function
* @returns {Promise<void>}
*
* @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 // Keep reference to Minecraft Process
let proc let proc
// Is DiscordRPC enabled // Is DiscordRPC enabled
@ -445,13 +477,17 @@ const GAME_JOINED_REGEX = /\[.+\]: Sound engine started/
const GAME_LAUNCH_REGEX = /^\[.+\]: (?:MinecraftForge .+ Initialized|ModLauncher .+ starting: .+|Loading Minecraft .+ with Fabric Loader .+)$/ const GAME_LAUNCH_REGEX = /^\[.+\]: (?:MinecraftForge .+ Initialized|ModLauncher .+ starting: .+|Loading Minecraft .+ with Fabric Loader .+)$/
const MIN_LINGER = 5000 const MIN_LINGER = 5000
// List of mods to exclude from validation
const EXCLUDED_MODS = [
]
async function dlAsync(login = true) { async function dlAsync(login = true) {
// Login parameter is temporary for debug purposes. Allows testing the validation/downloads without // Login parameter is temporary for debug purposes. Allows testing the validation/downloads without
// launching the game. // launching the game.
const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite') const loggerLaunchSuite = LoggerUtil.getLogger('LaunchSuite')
const loggerLanding = LoggerUtil.getLogger('Landing')
setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo')) setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo'))
let distro let distro
@ -459,21 +495,138 @@ async function dlAsync(login = true) {
try { try {
distro = await DistroAPI.refreshDistributionOrFallback() distro = await DistroAPI.refreshDistributionOrFallback()
onDistroRefresh(distro) onDistroRefresh(distro)
} catch(err) { } catch (err) {
loggerLaunchSuite.error('Unable to refresh distribution index.', err) loggerLanding.error(Lang.queryJS('landing.dlAsync.unableToLoadDistributionIndex'))
showLaunchFailure(Lang.queryJS('landing.dlAsync.fatalError'), Lang.queryJS('landing.dlAsync.unableToLoadDistributionIndex')) showLaunchFailure(Lang.queryJS('landing.dlAsync.fatalError'), Lang.queryJS('landing.dlAsync.unableToLoadDistributionIndex'))
return return
} }
const serv = distro.getServerById(ConfigManager.getSelectedServer()) const serv = distro.getServerById(ConfigManager.getSelectedServer())
if(login) { if (login) {
if(ConfigManager.getSelectedAccount() == null){ if (ConfigManager.getSelectedAccount() == null) {
loggerLanding.error('You must be logged into an account.') loggerLanding.error(Lang.queryJS('landing.dlAsync.accountLoginNeeded'))
return 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')) setLaunchDetails(Lang.queryJS('landing.dlAsync.pleaseWait'))
toggleLaunchArea(true) toggleLaunchArea(true)
setLaunchPercentage(0, 100) setLaunchPercentage(0, 100)
@ -489,17 +642,17 @@ async function dlAsync(login = true) {
fullRepairModule.spawnReceiver() fullRepairModule.spawnReceiver()
fullRepairModule.childProcess.on('error', (err) => { 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')) showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), err.message || Lang.queryJS('landing.dlAsync.errorDuringLaunchText'))
}) })
fullRepairModule.childProcess.on('close', (code, _signal) => { fullRepairModule.childProcess.on('close', (code, _signal) => {
if(code !== 0){ 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')) 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')) setLaunchDetails(Lang.queryJS('landing.dlAsync.validatingFileIntegrity'))
let invalidFileCount = 0 let invalidFileCount = 0
try { try {
@ -508,12 +661,11 @@ async function dlAsync(login = true) {
}) })
setLaunchPercentage(100) setLaunchPercentage(100)
} catch (err) { } 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')) showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileVerificationTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails'))
return return
} }
if(invalidFileCount > 0) { if(invalidFileCount > 0) {
loggerLaunchSuite.info('Downloading files.') loggerLaunchSuite.info('Downloading files.')
setLaunchDetails(Lang.queryJS('landing.dlAsync.downloadingFiles')) setLaunchDetails(Lang.queryJS('landing.dlAsync.downloadingFiles'))
@ -524,12 +676,12 @@ async function dlAsync(login = true) {
}) })
setDownloadPercentage(100) setDownloadPercentage(100)
} catch(err) { } 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')) showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringFileDownloadTitle'), err.displayable || Lang.queryJS('landing.dlAsync.seeConsoleForDetails'))
return return
} }
} else { } else {
loggerLaunchSuite.info('No invalid files, skipping download.') loggerLaunchSuite.info(Lang.queryJS('landing.dlAsync.AthShield.downloadingFiles'))
} }
// Remove download bar. // Remove download bar.
@ -577,9 +729,9 @@ async function dlAsync(login = true) {
// the progress bar stuff. // the progress bar stuff.
const tempListener = function(data){ const tempListener = function(data){
if(GAME_LAUNCH_REGEX.test(data.trim())){ if(GAME_LAUNCH_REGEX.test(data.trim())){
const diff = Date.now()-start const diff = Date.now() - start
if(diff < MIN_LINGER) { if(diff < MIN_LINGER) {
setTimeout(onLoadComplete, MIN_LINGER-diff) setTimeout(onLoadComplete, MIN_LINGER - diff)
} else { } else {
onLoadComplete() onLoadComplete()
} }
@ -587,18 +739,18 @@ async function dlAsync(login = true) {
} }
// Listener for Discord RPC. // Listener for Discord RPC.
const gameStateChange = function(data){ const gameStateChange = function(data) {
data = data.trim() data = data.trim()
if(SERVER_JOINED_REGEX.test(data)){ if(SERVER_JOINED_REGEX.test(data)) {
DiscordWrapper.updateDetails(Lang.queryJS('landing.discord.joined')) DiscordWrapper.updateDetails(Lang.queryJS('landing.discord.joined'))
} else if(GAME_JOINED_REGEX.test(data)){ } else if(GAME_JOINED_REGEX.test(data)) {
DiscordWrapper.updateDetails(Lang.queryJS('landing.discord.joining')) DiscordWrapper.updateDetails(Lang.queryJS('landing.discord.joining'))
} }
} }
const gameErrorListener = function(data){ const gameErrorListener = function(data) {
data = data.trim() data = data.trim()
if(data.indexOf('Could not find or load main class net.minecraft.launchwrapper.Launch') > -1){ 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.') loggerLaunchSuite.error('Game launch failed, LaunchWrapper was not downloaded properly.')
showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.launchWrapperNotDownloaded')) showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.launchWrapperNotDownloaded'))
} }
@ -608,14 +760,14 @@ async function dlAsync(login = true) {
// Build Minecraft process. // Build Minecraft process.
proc = pb.build() proc = pb.build()
// Bind listeners to stdout. // Bind listeners to stdout and stderr.
proc.stdout.on('data', tempListener) proc.stdout.on('data', tempListener)
proc.stderr.on('data', gameErrorListener) proc.stderr.on('data', gameErrorListener)
setLaunchDetails(Lang.queryJS('landing.dlAsync.doneEnjoyServer')) setLaunchDetails(Lang.queryJS('landing.dlAsync.doneEnjoyServer'))
// Init Discord Hook // Init Discord Hook
if(distro.rawDistribution.discord != null && serv.rawServer.discord != null){ if(distro.rawDistribution.discord != null && serv.rawServer.discord != null) {
DiscordWrapper.initRPC(distro.rawDistribution.discord, serv.rawServer.discord) DiscordWrapper.initRPC(distro.rawDistribution.discord, serv.rawServer.discord)
hasRPC = true hasRPC = true
proc.on('close', (code, signal) => { proc.on('close', (code, signal) => {
@ -627,13 +779,10 @@ async function dlAsync(login = true) {
} }
} catch(err) { } catch(err) {
loggerLaunchSuite.error('Error during launch', err) loggerLaunchSuite.error('Error during launch', err)
showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.checkConsoleForDetails')) showLaunchFailure(Lang.queryJS('landing.dlAsync.errorDuringLaunchTitle'), Lang.queryJS('landing.dlAsync.checkConsoleForDetails'))
} }
} }
} }
/** /**
@ -975,7 +1124,7 @@ async function loadNews(){
const articles = [] const articles = []
for(let i=0; i<items.length; i++){ for(let i=0; i<items.length; i++){
// JQuery Element // JQuery Element
const el = $(items[i]) const el = $(items[i])
// Resolve date. // Resolve date.

View File

@ -1,8 +1,9 @@
// Requirements // Requirements
const os = require('os') const os = require('os')
const semver = require('semver') 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 { MSFT_OPCODE, MSFT_REPLY_TYPE, MSFT_ERROR } = require('./assets/js/ipcconstants')
const settingsState = { const settingsState = {
@ -707,6 +708,29 @@ document.getElementById('settingsGameHeight').addEventListener('keydown', (e) =>
const settingsModsContainer = document.getElementById('settingsModsContainer') 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. * Resolve and update the mods on the UI.
*/ */
@ -1133,6 +1157,7 @@ async function prepareModsTab(first){
await resolveModsForUI() await resolveModsForUI()
await resolveDropinModsForUI() await resolveDropinModsForUI()
await resolveShaderpacksForUI() await resolveShaderpacksForUI()
manageModCategory()
bindDropinModsRemoveButton() bindDropinModsRemoveButton()
bindDropinModFileSystemButton() bindDropinModFileSystemButton()
bindShaderpackButton() bindShaderpackButton()

View File

@ -206,6 +206,30 @@ launchingGame = "Launching game.."
launchWrapperNotDownloaded = "The main file, LaunchWrapper, failed to download properly. As a result, the game cannot launch.<br><br>To fix this issue, temporarily turn off your antivirus software and launch the game again.<br><br>If you have time, please <a href=\"https://github.com/dscalzi/HeliosLauncher/issues\">submit an issue</a> and let us know what antivirus software you use. We'll contact them and try to straighten things out." launchWrapperNotDownloaded = "The main file, LaunchWrapper, failed to download properly. As a result, the game cannot launch.<br><br>To fix this issue, temporarily turn off your antivirus software and launch the game again.<br><br>If you have time, please <a href=\"https://github.com/dscalzi/HeliosLauncher/issues\">submit an issue</a> and let us know what antivirus software you use. We'll contact them and try to straighten things out."
doneEnjoyServer = "Done. Enjoy the server!" doneEnjoyServer = "Done. Enjoy the server!"
checkConsoleForDetails = "Please check the console (CTRL + Shift + i) for more details." 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}."
waintingLaunchingGame = "Waiting for game window..."
[js.landing.dlAsync.AthShield]
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}"
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."
usingAthShield = "Athena's Shield activated, Resource usage..."
notUsingAthShield = "Athena's Shield deactivated, Not using resources..."
[js.landing.news] [js.landing.news]
checking = "Checking for News" checking = "Checking for News"

48
docs/Athena's Shield.md Normal file
View File

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

Binary file not shown.

View File

@ -17,7 +17,8 @@
"dist:win": "npm run dist -- -w", "dist:win": "npm run dist -- -w",
"dist:mac": "npm run dist -- -m", "dist:mac": "npm run dist -- -m",
"dist:linux": "npm run dist -- -l", "dist:linux": "npm run dist -- -l",
"lint": "eslint --config .eslintrc.json ." "lint": "eslint --config .eslintrc.json .",
"athshield": "node app/assets/athshield/athshield.js"
}, },
"engines": { "engines": {
"node": "20.x.x" "node": "20.x.x"
@ -25,6 +26,7 @@
"dependencies": { "dependencies": {
"@electron/remote": "^2.1.2", "@electron/remote": "^2.1.2",
"adm-zip": "^0.5.16", "adm-zip": "^0.5.16",
"crypto": "^1.0.1",
"discord-rpc-patch": "^4.0.1", "discord-rpc-patch": "^4.0.1",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"ejs-electron": "^3.0.0", "ejs-electron": "^3.0.0",