mirror of
https://github.com/dscalzi/HeliosLauncher.git
synced 2024-12-21 19:22:13 -08:00
Merge afb461dbc3
into 6aaeeff9a4
This commit is contained in:
commit
5500d45ae6
76
app/assets/extraverif/extraverif.js
Normal file
76
app/assets/extraverif/extraverif.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const readline = require('readline')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const configPath = path.join(__dirname, 'variables.json')
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
const rawData = fs.readFileSync(configPath)
|
||||||
|
return JSON.parse(rawData.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConfig(config) {
|
||||||
|
const data = JSON.stringify(config, null, 2)
|
||||||
|
fs.writeFileSync(configPath, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
rl.question('Would you like to activate debug mode? (yes/no): ', (debugAnswer) => {
|
||||||
|
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 activated. Menu hidden.')
|
||||||
|
} else if (menuAnswer.toLowerCase() === 'block') {
|
||||||
|
config.menuVisibility = 'blocked'
|
||||||
|
console.log('Extra file verification activated. Menu blocked.')
|
||||||
|
} else {
|
||||||
|
console.log('Invalid option for the menu.')
|
||||||
|
rl.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
saveConfig(config)
|
||||||
|
rl.close()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else if (answer.toLowerCase() === 'no') {
|
||||||
|
console.log('Extra file verification not activated. Closing the CLI.')
|
||||||
|
config.extraFileVerifActivated = false
|
||||||
|
config.menuVisibility = 'visible'
|
||||||
|
config.debug = false
|
||||||
|
|
||||||
|
saveConfig(config)
|
||||||
|
rl.close()
|
||||||
|
} else {
|
||||||
|
console.log('Invalid response.')
|
||||||
|
rl.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
startCLI()
|
30
app/assets/extraverif/parserExtraverif.js
Normal file
30
app/assets/extraverif/parserExtraverif.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const configPath = path.join(__dirname, 'variables.json')
|
||||||
|
|
||||||
|
class ExtraFileVerification {
|
||||||
|
constructor() {
|
||||||
|
this.config = this.loadConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
loadConfig() {
|
||||||
|
const rawData = fs.readFileSync(configPath)
|
||||||
|
return JSON.parse(rawData.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return this.config.extraFileVerifActivated
|
||||||
|
}
|
||||||
|
|
||||||
|
get type() {
|
||||||
|
return this.config.menuVisibility
|
||||||
|
}
|
||||||
|
|
||||||
|
get debug() {
|
||||||
|
return this.config.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExtraFileVerificationInstance = new ExtraFileVerification()
|
||||||
|
module.exports = ExtraFileVerificationInstance
|
5
app/assets/extraverif/variables.json
Normal file
5
app/assets/extraverif/variables.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extraFileVerifActivated": false,
|
||||||
|
"menuVisibility": "visible",
|
||||||
|
"debug": false
|
||||||
|
}
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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')
|
||||||
@ -46,7 +48,7 @@ const loggerLanding = LoggerUtil.getLogger('Landing')
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Show/hide the loading area.
|
* Show/hide the loading area.
|
||||||
*
|
*
|
||||||
* @param {boolean} loading True if the loading area should be shown, otherwise false.
|
* @param {boolean} loading True if the loading area should be shown, otherwise false.
|
||||||
*/
|
*/
|
||||||
function toggleLaunchArea(loading){
|
function toggleLaunchArea(loading){
|
||||||
@ -61,7 +63,7 @@ function toggleLaunchArea(loading){
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the details text of the loading area.
|
* Set the details text of the loading area.
|
||||||
*
|
*
|
||||||
* @param {string} details The new text for the loading details.
|
* @param {string} details The new text for the loading details.
|
||||||
*/
|
*/
|
||||||
function setLaunchDetails(details){
|
function setLaunchDetails(details){
|
||||||
@ -70,7 +72,7 @@ function setLaunchDetails(details){
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the value of the loading progress bar and display that value.
|
* Set the value of the loading progress bar and display that value.
|
||||||
*
|
*
|
||||||
* @param {number} percent Percentage (0-100)
|
* @param {number} percent Percentage (0-100)
|
||||||
*/
|
*/
|
||||||
function setLaunchPercentage(percent){
|
function setLaunchPercentage(percent){
|
||||||
@ -81,7 +83,7 @@ function setLaunchPercentage(percent){
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the value of the OS progress bar and display that on the UI.
|
* Set the value of the OS progress bar and display that on the UI.
|
||||||
*
|
*
|
||||||
* @param {number} percent Percentage (0-100)
|
* @param {number} percent Percentage (0-100)
|
||||||
*/
|
*/
|
||||||
function setDownloadPercentage(percent){
|
function setDownloadPercentage(percent){
|
||||||
@ -91,7 +93,7 @@ function setDownloadPercentage(percent){
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable or disable the launch button.
|
* Enable or disable the launch button.
|
||||||
*
|
*
|
||||||
* @param {boolean} val True to enable, false to disable.
|
* @param {boolean} val True to enable, false to disable.
|
||||||
*/
|
*/
|
||||||
function setLaunchEnabled(val){
|
function setLaunchEnabled(val){
|
||||||
@ -192,7 +194,7 @@ const refreshMojangStatuses = async function(){
|
|||||||
loggerLanding.warn('Unable to refresh Mojang service status.')
|
loggerLanding.warn('Unable to refresh Mojang service status.')
|
||||||
statuses = MojangRestAPI.getDefaultStatuses()
|
statuses = MojangRestAPI.getDefaultStatuses()
|
||||||
}
|
}
|
||||||
|
|
||||||
greenCount = 0
|
greenCount = 0
|
||||||
greyCount = 0
|
greyCount = 0
|
||||||
|
|
||||||
@ -229,7 +231,7 @@ const refreshMojangStatuses = async function(){
|
|||||||
status = 'green'
|
status = 'green'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('mojangStatusEssentialContainer').innerHTML = tooltipEssentialHTML
|
document.getElementById('mojangStatusEssentialContainer').innerHTML = tooltipEssentialHTML
|
||||||
document.getElementById('mojangStatusNonEssentialContainer').innerHTML = tooltipNonEssentialHTML
|
document.getElementById('mojangStatusNonEssentialContainer').innerHTML = tooltipNonEssentialHTML
|
||||||
document.getElementById('mojang_status_icon').style.color = MojangRestAPI.statusToHex(status)
|
document.getElementById('mojang_status_icon').style.color = MojangRestAPI.statusToHex(status)
|
||||||
@ -263,7 +265,7 @@ const refreshServerStatus = async (fade = false) => {
|
|||||||
document.getElementById('landingPlayerLabel').innerHTML = pLabel
|
document.getElementById('landingPlayerLabel').innerHTML = pLabel
|
||||||
document.getElementById('player_count').innerHTML = pVal
|
document.getElementById('player_count').innerHTML = pVal
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshMojangStatuses()
|
refreshMojangStatuses()
|
||||||
@ -276,7 +278,7 @@ let serverStatusListener = setInterval(() => refreshServerStatus(true), 300000)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows an error overlay, toggles off the launch area.
|
* Shows an error overlay, toggles off the launch area.
|
||||||
*
|
*
|
||||||
* @param {string} title The overlay title.
|
* @param {string} title The overlay title.
|
||||||
* @param {string} desc The overlay description.
|
* @param {string} desc The overlay description.
|
||||||
*/
|
*/
|
||||||
@ -295,8 +297,8 @@ function showLaunchFailure(title, desc){
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously scan the system for valid Java installations.
|
* 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){
|
async function asyncSystemScan(effectiveJavaOptions, launchAfter = true){
|
||||||
|
|
||||||
@ -321,7 +323,7 @@ async function asyncSystemScan(effectiveJavaOptions, launchAfter = true){
|
|||||||
setOverlayHandler(() => {
|
setOverlayHandler(() => {
|
||||||
setLaunchDetails(Lang.queryJS('landing.systemScan.javaDownloadPrepare'))
|
setLaunchDetails(Lang.queryJS('landing.systemScan.javaDownloadPrepare'))
|
||||||
toggleOverlay(false)
|
toggleOverlay(false)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
downloadJava(effectiveJavaOptions, launchAfter)
|
downloadJava(effectiveJavaOptions, launchAfter)
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
@ -445,13 +447,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('dlAsync')
|
||||||
setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo'))
|
setLaunchDetails(Lang.queryJS('landing.dlAsync.loadingServerInfo'))
|
||||||
|
|
||||||
let distro
|
let distro
|
||||||
@ -459,21 +465,137 @@ 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 (extraFileVerif.status) {
|
||||||
|
loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.usingExtraFileVerif'))
|
||||||
|
|
||||||
|
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 (extraFileVerif.debug) {
|
||||||
|
loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.distributionIdentityError', {
|
||||||
|
'moduleName': mdl.rawModule.name,
|
||||||
|
'moduleIdentity': modIdentity
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
distroMods[modPath] = modIdentity
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Function to extract mod identity from the jar file
|
||||||
|
const extractModIdentity = (filePath) => {
|
||||||
|
if (extraFileVerif.debug) {
|
||||||
|
loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.modIdentityExtraction', {'filePath': filePath}))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (extraFileVerif.debug) {
|
||||||
|
loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.identityNotFoundUsingHash', {
|
||||||
|
'filePath': filePath,
|
||||||
|
'hash': hash
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate mods function
|
||||||
|
const validateMods = () => {
|
||||||
|
loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.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.extraFileVerif.modValidationBypassed', {'mod': mod}))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedIdentity = distroMods[modPath]
|
||||||
|
loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.validatingMod', {'mod': mod}))
|
||||||
|
|
||||||
|
if (expectedIdentity) {
|
||||||
|
const modIdentity = extractModIdentity(modPath)
|
||||||
|
if (extraFileVerif.debug) {
|
||||||
|
loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.expectedAndCalculatedIdentity', {
|
||||||
|
'expectedIdentity': expectedIdentity,
|
||||||
|
'mod': mod,
|
||||||
|
'modIdentity': modIdentity
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modIdentity !== expectedIdentity) {
|
||||||
|
if (extraFileVerif.debug) {
|
||||||
|
loggerLanding.error(Lang.queryJS('landing.dlAsync.extraFileVerif.modIdentityMismatchError', {
|
||||||
|
'mod': mod,
|
||||||
|
'expectedIdentity': expectedIdentity,
|
||||||
|
'modIdentity': modIdentity
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
valid = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loggerLanding.warn(Lang.queryJS('landing.dlAsync.extraFileVerif.expectedIdentityNotFound', {'mod': mod}))
|
||||||
|
valid = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loggerLanding.info(Lang.queryJS('landing.dlAsync.extraFileVerif.modValidationCompleted'))
|
||||||
|
return valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform mod validation before proceeding
|
||||||
|
if (!validateMods()) {
|
||||||
|
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.extraFileVerif.notUsingExtraFileVerif'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------- 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 +611,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,11 +630,10 @@ 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.')
|
||||||
@ -524,12 +645,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.notUsingExtraFileVerif.downloadingFiles'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove download bar.
|
// Remove download bar.
|
||||||
@ -577,9 +698,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 +708,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 +729,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 +748,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'))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -656,8 +774,8 @@ let newsGlideCount = 0
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the news UI via a slide animation.
|
* 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){
|
function slide_(up){
|
||||||
const lCUpper = document.querySelector('#landingContainer > #upper')
|
const lCUpper = document.querySelector('#landingContainer > #upper')
|
||||||
@ -731,7 +849,7 @@ let newsLoadingListener = null
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the news loading animation.
|
* Set the news loading animation.
|
||||||
*
|
*
|
||||||
* @param {boolean} val True to set loading animation, otherwise false.
|
* @param {boolean} val True to set loading animation, otherwise false.
|
||||||
*/
|
*/
|
||||||
function setNewsLoading(val){
|
function setNewsLoading(val){
|
||||||
@ -773,7 +891,7 @@ newsArticleContentScrollable.onscroll = (e) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reload the news without restarting.
|
* Reload the news without restarting.
|
||||||
*
|
*
|
||||||
* @returns {Promise.<void>} A promise which resolves when the news
|
* @returns {Promise.<void>} A promise which resolves when the news
|
||||||
* content has finished loading and transitioning.
|
* content has finished loading and transitioning.
|
||||||
*/
|
*/
|
||||||
@ -811,7 +929,7 @@ async function digestMessage(str) {
|
|||||||
/**
|
/**
|
||||||
* Initialize News UI. This will load the news and prepare
|
* Initialize News UI. This will load the news and prepare
|
||||||
* the UI accordingly.
|
* the UI accordingly.
|
||||||
*
|
*
|
||||||
* @returns {Promise.<void>} A promise which resolves when the news
|
* @returns {Promise.<void>} A promise which resolves when the news
|
||||||
* content has finished loading and transitioning.
|
* content has finished loading and transitioning.
|
||||||
*/
|
*/
|
||||||
@ -890,7 +1008,7 @@ async function initNews(){
|
|||||||
const switchHandler = (forward) => {
|
const switchHandler = (forward) => {
|
||||||
let cArt = parseInt(newsContent.getAttribute('article'))
|
let cArt = parseInt(newsContent.getAttribute('article'))
|
||||||
let nxtArt = forward ? (cArt >= newsArr.length-1 ? 0 : cArt + 1) : (cArt <= 0 ? newsArr.length-1 : cArt - 1)
|
let nxtArt = forward ? (cArt >= newsArr.length-1 ? 0 : cArt + 1) : (cArt <= 0 ? newsArr.length-1 : cArt - 1)
|
||||||
|
|
||||||
displayArticle(newsArr[nxtArt], nxtArt+1)
|
displayArticle(newsArr[nxtArt], nxtArt+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -930,7 +1048,7 @@ document.addEventListener('keydown', (e) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a news article on the UI.
|
* Display a news article on the UI.
|
||||||
*
|
*
|
||||||
* @param {Object} articleObject The article meta object.
|
* @param {Object} articleObject The article meta object.
|
||||||
* @param {number} index The article index.
|
* @param {number} index The article index.
|
||||||
*/
|
*/
|
||||||
@ -965,7 +1083,7 @@ async function loadNews(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
|
||||||
const newsFeed = distroData.rawDistribution.rss
|
const newsFeed = distroData.rawDistribution.rss
|
||||||
const newsHost = new URL(newsFeed).origin + '/'
|
const newsHost = new URL(newsFeed).origin + '/'
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -975,7 +1093,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.
|
||||||
|
@ -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 extraFileVerif = require('./assets/extraverif/parserExtraverif')
|
||||||
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 `extraFileVerif`.
|
||||||
|
*
|
||||||
|
* The function performs the following:
|
||||||
|
* - If `extraFileVerif.type` is 'hidden': hides the Mods button entirely.
|
||||||
|
* - If `extraFileVerif.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 (extraFileVerif.type === 'hidden') {
|
||||||
|
// Hide the Mods navigation button
|
||||||
|
modsButton.style.display = 'none'
|
||||||
|
} else if (extraFileVerif.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()
|
||||||
|
@ -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.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}"
|
||||||
|
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 = "Extra file verification has detected invalid mods. Please delete the {folder} folder and restart the launcher."
|
||||||
|
downloadingFiles = "No invalid files, skipping download."
|
||||||
|
usingExtraFileVerif = "Extra file verification activated, Resource usage..."
|
||||||
|
notUsingExtraFileVerif = "Extra file verification deactivated, Not using resources..."
|
||||||
|
|
||||||
[js.landing.news]
|
[js.landing.news]
|
||||||
checking = "Checking for News"
|
checking = "Checking for News"
|
||||||
|
48
docs/ExtraFileVerification.md
Normal file
48
docs/ExtraFileVerification.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
**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 ExtraFileVerification.
|
||||||
|
|
||||||
|
### 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, 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.
|
||||||
|
|
||||||
|
**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:**
|
||||||
|
|
||||||
|
- 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:**
|
||||||
|
|
||||||
|
- 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, 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
|
||||||
|
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 ExtraFileVerification, feel free to contact me.
|
||||||
|
|
||||||
|
**The only way to bypass ExtraFileVerification would be through advanced cryptography knowledge, involving signature copying or hash modification.**
|
||||||
|
|
||||||
|
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)**
|
@ -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 .",
|
||||||
|
"extraverif": "node app/assets/extraverif/extraverif.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "20.x.x"
|
"node": "20.x.x"
|
||||||
|
Loading…
Reference in New Issue
Block a user