4
0
mirror of https://github.com/AltarikMC/Launcher synced 2024-11-22 06:19:50 +01:00

Fix launcher still consider downloading assets when download failed (caused by corrupted files)

This commit is contained in:
Quentin Legot 2022-06-22 22:44:33 +02:00
parent 38fd3ad906
commit 73e4666659

View File

@ -1,351 +1,359 @@
const isDev = require('electron-is-dev') const isDev = require('electron-is-dev')
const { Client, Authenticator } = require('minecraft-launcher-core') const { Client, Authenticator } = require('minecraft-launcher-core')
const fetch = require('node-fetch').default const fetch = require('node-fetch').default
const hasha = require('hasha') const hasha = require('hasha')
const fs = require('fs') const fs = require('fs')
const { join } = require('path') const { join } = require('path')
const constants = require("constants") const constants = require("constants")
const zip = require('extract-zip') const zip = require('extract-zip')
const logger = require('electron-log') const logger = require('electron-log')
const msmc = require('msmc') const msmc = require('msmc')
class Minecraft { class Minecraft {
appdata = process.env.APPDATA || (process.platform === 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share") appdata = process.env.APPDATA || (process.platform === 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share")
minecraftpath = join(this.appdata, ".altarik") minecraftpath = join(this.appdata, ".altarik")
launcher = new Client() launcher = new Client()
auth = null auth = null
modsList = undefined modsList = undefined
showNotification = undefined showNotification = undefined
setShowNotification(showNotification) { setShowNotification(showNotification) {
this.showNotification = showNotification this.showNotification = showNotification
} }
/** /**
* Used to login through Mojang account * Used to login through Mojang account
*/ */
login(event, win, username, password) { login(event, win, username, password) {
this.auth = null this.auth = null
if(isDev || password.trim() !== "") { if(isDev || password.trim() !== "") {
this.auth = Authenticator.getAuth(username, password) this.auth = Authenticator.getAuth(username, password)
this.auth.then(v => { this.auth.then(v => {
win.loadFile('src/client/index.html') win.loadFile('src/client/index.html')
}).catch(() => { }).catch(() => {
event.sender.send("loginError") event.sender.send("loginError")
logger.error("[MJ login] User haven't purchase the game") logger.error("[MJ login] User haven't purchase the game")
this.showNotification("Erreur de connexion", "Vous ne possèdez pas de licence Minecraft sur ce compte", "error") this.showNotification("Erreur de connexion", "Vous ne possèdez pas de licence Minecraft sur ce compte", "error")
}) })
} else { } else {
this.showNotification("Erreur de connexion", "Veuillez renseignez un mot de passe", "warning") this.showNotification("Erreur de connexion", "Veuillez renseignez un mot de passe", "warning")
} }
} }
/** /**
* Used to login through a Microsoft account * Used to login through a Microsoft account
*/ */
microsoftLogin(event, win) { microsoftLogin(event, win) {
msmc.fastLaunch("electron", msmc.fastLaunch("electron",
(update) => { (update) => {
switch (update.type) { switch (update.type) {
case "Error": case "Error":
event.sender.send("loginError") event.sender.send("loginError")
this.showNotification("Une erreur est survenue", update.data, "error") this.showNotification("Une erreur est survenue", update.data, "error")
logger.error("MC-Account error:", update.data); logger.error("MC-Account error:", update.data);
break; break;
} }
}).then(result => { }).then(result => {
if(msmc.errorCheck(result)) { if(msmc.errorCheck(result)) {
event.sender.send("loginError") event.sender.send("loginError")
logger.error(result.reason) logger.error(result.reason)
this.showNotification("Erreur de connexion", result.reason, "error") this.showNotification("Erreur de connexion", result.reason, "error")
} else { } else {
if(!msmc.isDemoUser(result)) { if(!msmc.isDemoUser(result)) {
this.auth = msmc.getMCLC().getAuth(result) this.auth = msmc.getMCLC().getAuth(result)
win.loadFile('src/client/index.html') win.loadFile('src/client/index.html')
} else { } else {
event.sender.send("loginError") event.sender.send("loginError")
logger.error("[MS login] User haven't purchase the game") logger.error("[MS login] User haven't purchase the game")
this.showNotification("Erreur de connexion", "Vous ne possèdez pas de licence Minecraft sur ce compte", "error") this.showNotification("Erreur de connexion", "Vous ne possèdez pas de licence Minecraft sur ce compte", "error")
} }
} }
}).catch(reason => { }).catch(reason => {
event.sender.send("loginError") event.sender.send("loginError")
logger.error(reason) logger.error(reason)
this.showNotification("Erreur de connexion", "Erreur inconnue", "error") this.showNotification("Erreur de connexion", "Erreur inconnue", "error")
}) })
} }
launch(event, args) { launch(event, args) {
this.extractJava(Number(args.chapter), event).then((javaPath) => { this.extractJava(Number(args.chapter), event).then((javaPath) => {
this.extractMods(Number(args.chapter), event).then((chapter) => { this.extractMods(Number(args.chapter), event).then((chapter) => {
this.launcher.launch({ this.launcher.launch({
authorization: this.auth, authorization: this.auth,
root: this.minecraftpath, root: this.minecraftpath,
javaPath: javaPath, javaPath: javaPath,
version: { version: {
number: chapter.minecraftVersion, number: chapter.minecraftVersion,
type: chapter.type | "release", type: chapter.type | "release",
custom: chapter.customVersion custom: chapter.customVersion
}, },
memory: { memory: {
max: args.maxMem, max: args.maxMem,
min: args.minMem min: args.minMem
} }
}) }).then(v => {
this.launcher.on('debug', (e) => logger.info(`debug: ${e}`)); if(v === null) {
this.launcher.on('data', (e) => logger.info(`data: ${e}`)); this.close(event, -1)
this.launcher.on('progress', (e) => { }
event.sender.send("progress", e) })
logger.info(`progress ${e.type} :${e.task} / ${e.total}`) this.launcher.on('debug', (e) => logger.info(`debug: ${e}`));
}) this.launcher.on('data', (e) => logger.info(`data: ${e}`));
this.launcher.on('arguments', (e) => { this.launcher.on('progress', (e) => {
event.sender.send("launch", e) event.sender.send("progress", e)
logger.info("launching the game") logger.info(`progress ${e.type} :${e.task} / ${e.total}`)
logger.info(e) })
}) this.launcher.on('arguments', (e) => {
this.launcher.on('close', (e) => { event.sender.send("launch", e)
event.sender.send("close", e) logger.info("launching the game")
if(e !== 0) { logger.info(e)
logger.warn("Minecraft didn't close properly") })
logger.warn(e) this.launcher.on('close', (e) => {
this.showNotification("Une erreur est survenue", "Minecraft ne s'est pas fermé correctement", "error") this.close(event, e)
} })
}) }).catch((err) => {
}).catch((err) => { this.showNotification("Impossible de lancer le jeu", "Erreur inconnue", "error")
this.showNotification("Impossible de lancer le jeu", "Erreur inconnue", "error") event.sender.send("close", 1)
event.sender.send("close", 1) logger.error('Unable to launch the game')
logger.error('Unable to launch the game') logger.error(err)
logger.error(err) })
}) }).catch(err => {
}).catch(err => { this.showNotification("Impossible de lancer le jeu", "Impossible d'installer Java pour votre configuration", "error")
this.showNotification("Impossible de lancer le jeu", "Impossible d'installer Java pour votre configuration", "error") event.sender.send("close", 1)
event.sender.send("close", 1) logger.warn("Unable to install java")
logger.warn("Unable to install java") logger.warn(err)
logger.warn(err) })
})
}
}
close(event, code) {
getModsInformations(event) { event.sender.send("close", code)
fetch("https://altarik.fr/launcher.json").then(response => { if(code !== 0) {
if(response.ok) { logger.warn("Minecraft didn't close properly")
response.json().then(data => { logger.warn(code)
let folder = join(process.env.LOCALAPPDATA, "altarik-launcher", "data") this.showNotification("Une erreur est survenue", "Minecraft ne s'est pas fermé correctement", "error")
if(!fs.existsSync(folder)) }
fs.mkdirSync(folder, {recursive: true}) }
let file = join(folder, "launcher.json")
if(fs.existsSync(file)) getModsInformations(event) {
fs.rmSync(file) fetch("https://altarik.fr/launcher.json").then(response => {
fs.writeFileSync(file, JSON.stringify(data)) if(response.ok) {
event.sender.send('modsInformations', this.extractModsInformations(data)) response.json().then(data => {
}).catch(err => { let folder = join(process.env.LOCALAPPDATA, "altarik-launcher", "data")
event.sender.send('modsInformations', this.extractModsFromFileSystem()) if(!fs.existsSync(folder))
logger.warn(err) fs.mkdirSync(folder, {recursive: true})
logger.warn("An error occured while trying to connect to server") let file = join(folder, "launcher.json")
}) if(fs.existsSync(file))
} else { fs.rmSync(file)
logger.warn("Unable to connect to server") fs.writeFileSync(file, JSON.stringify(data))
logger.warn(err) event.sender.send('modsInformations', this.extractModsInformations(data))
event.sender.send('modsInformations', this.extractModsFromFileSystem()) }).catch(err => {
} event.sender.send('modsInformations', this.extractModsFromFileSystem())
}).catch(err => { logger.warn(err)
logger.warn("Unable to connect to server") logger.warn("An error occured while trying to connect to server")
logger.warn(err) })
event.sender.send('modsInformations', this.extractModsFromFileSystem()) } else {
}) logger.warn("Unable to connect to server")
} logger.warn(err)
event.sender.send('modsInformations', this.extractModsFromFileSystem())
extractModsFromFileSystem() { }
let filepath = join(process.env.LOCALAPPDATA, "altarik-launcher/data/launcher.json") }).catch(err => {
if(fs.existsSync(filepath)) { logger.warn("Unable to connect to server")
let content = fs.readFileSync(filepath) logger.warn(err)
if(content !== null) { event.sender.send('modsInformations', this.extractModsFromFileSystem())
this.showNotification("Impossible de récupérer certaines informations en ligne", "utilisation des dernières données récupérées", "warning") })
return this.extractModsInformations(JSON.parse(content)) }
} else {
this.showNotification("Impossible de récupérer certaines informations en ligne", "Veuillez réessayez en cliquant sur le bouton", "warning") extractModsFromFileSystem() {
logger.error("Unable to get chapters informations from server or filesystem") let filepath = join(process.env.LOCALAPPDATA, "altarik-launcher/data/launcher.json")
return null if(fs.existsSync(filepath)) {
} let content = fs.readFileSync(filepath)
} else { if(content !== null) {
return null; this.showNotification("Impossible de récupérer certaines informations en ligne", "utilisation des dernières données récupérées", "warning")
} return this.extractModsInformations(JSON.parse(content))
} else {
} this.showNotification("Impossible de récupérer certaines informations en ligne", "Veuillez réessayez en cliquant sur le bouton", "warning")
logger.error("Unable to get chapters informations from server or filesystem")
extractModsInformations(json) { return null
this.modsList = json.chapters }
return this.modsList } else {
} return null;
}
async extractMods(chapterId, event) {
return new Promise(async (resolve, reject) => { }
const modsFolder = join(this.minecraftpath, "mods")
const shaderFolder = join(this.minecraftpath, "shaderpacks") extractModsInformations(json) {
if(fs.existsSync(modsFolder)) this.modsList = json.chapters
fs.rmSync(modsFolder, { recursive: true }) return this.modsList
if(fs.existsSync(shaderFolder)) }
fs.rmSync(shaderFolder, { recursive: true })
for(const i in this.modsList) { async extractMods(chapterId, event) {
if(Number(i) === chapterId) { return new Promise(async (resolve, reject) => {
const chapter = this.modsList[i] const modsFolder = join(this.minecraftpath, "mods")
for(let j in chapter.modspack.mods) { const shaderFolder = join(this.minecraftpath, "shaderpacks")
event.sender.send("progress", {type: "mods", task: 0, total: chapter.modspack.mods.length }) if(fs.existsSync(modsFolder))
let modpackFolder = join(this.minecraftpath, "modpack", chapter.title) fs.rmSync(modsFolder, { recursive: true })
if(!fs.existsSync(modpackFolder)) if(fs.existsSync(shaderFolder))
fs.mkdirSync(modpackFolder, { recursive: true }) fs.rmSync(shaderFolder, { recursive: true })
const path = join(modpackFolder, `modpack${j}.zip`) for(const i in this.modsList) {
try { if(Number(i) === chapterId) {
fs.accessSync(path, constants.W_OK) const chapter = this.modsList[i]
let sha1 = await hasha.fromFile(path, {algorithm: 'sha1'}) for(let j in chapter.modspack.mods) {
if(sha1 === chapter.modspack.sha1sum[j]) { event.sender.send("progress", {type: "mods", task: 0, total: chapter.modspack.mods.length })
await this.unzipMods(path).catch(err => { let modpackFolder = join(this.minecraftpath, "modpack", chapter.title)
reject(err) if(!fs.existsSync(modpackFolder))
}) fs.mkdirSync(modpackFolder, { recursive: true })
} else { const path = join(modpackFolder, `modpack${j}.zip`)
logger.warn(`sha1sum ${sha1} don't correspond to ${chapter.modspack.sha1sum[j]} of mods ${path}`) try {
await this.downloadAndExtractMods(chapter.modspack.mods[j], path).catch(err => { fs.accessSync(path, constants.W_OK)
reject(err) let sha1 = await hasha.fromFile(path, {algorithm: 'sha1'})
}) if(sha1 === chapter.modspack.sha1sum[j]) {
} await this.unzipMods(path).catch(err => {
event.sender.send("progress", {type: "mods", task: Number(j)+1, total: chapter.modspack.mods.length }) reject(err)
} catch (err) { })
try { } else {
await this.downloadAndExtractMods(chapter.modspack.mods[j], path) logger.warn(`sha1sum ${sha1} don't correspond to ${chapter.modspack.sha1sum[j]} of mods ${path}`)
} catch(e) { await this.downloadAndExtractMods(chapter.modspack.mods[j], path).catch(err => {
reject({ err, e }) reject(err)
return })
} }
} event.sender.send("progress", {type: "mods", task: Number(j)+1, total: chapter.modspack.mods.length })
} } catch (err) {
resolve(chapter) try {
return await this.downloadAndExtractMods(chapter.modspack.mods[j], path)
} catch(e) {
} reject({ err, e })
} return
reject("didn't found the correct chapter" + chapter) }
}) }
} }
resolve(chapter)
downloadMods(link, path) { return
return new Promise((resolve, reject) => {
fetch(link).then(response => { }
if(response.ok) { }
if(fs.existsSync(path)) reject("didn't found the correct chapter" + chapter)
fs.rmSync(path) })
const dest = fs.createWriteStream(path) }
response.body.pipe(dest)
response.body.on("end", () => { downloadMods(link, path) {
logger.log("download completed"); return new Promise((resolve, reject) => {
resolve("download completed") fetch(link).then(response => {
}) if(response.ok) {
dest.on("error", () => { if(fs.existsSync(path))
reject("An error appenned when using stream") fs.rmSync(path)
}); const dest = fs.createWriteStream(path)
} else { response.body.pipe(dest)
reject(response.status) response.body.on("end", () => {
} logger.log("download completed");
}).catch(err => { resolve("download completed")
reject(err) })
}) dest.on("error", () => {
}) reject("An error appenned when using stream")
} });
} else {
async unzipMods(zipLocation, outLocation=this.minecraftpath) { reject(response.status)
return new Promise(async (resolve, reject) => { }
logger.info(`unzipping ${zipLocation} file to ${outLocation}`) }).catch(err => {
zip(zipLocation, { dir: outLocation }).then(() => { reject(err)
resolve() })
}).catch(err => { })
logger.err(`failed to unzip file`) }
reject(err)
}) async unzipMods(zipLocation, outLocation=this.minecraftpath) {
return new Promise(async (resolve, reject) => {
}) logger.info(`unzipping ${zipLocation} file to ${outLocation}`)
zip(zipLocation, { dir: outLocation }).then(() => {
} resolve()
}).catch(err => {
async downloadAndExtractMods(link, path) { logger.err(`failed to unzip file`)
return new Promise(async (resolve, reject) => { reject(err)
this.downloadMods(link, path).then(() => { })
this.unzipMods(path).then(() => {
resolve() })
}).catch(err => {
reject(err) }
})
}).catch(err => { async downloadAndExtractMods(link, path) {
reject(err) return new Promise(async (resolve, reject) => {
}) this.downloadMods(link, path).then(() => {
this.unzipMods(path).then(() => {
}) resolve()
} }).catch(err => {
reject(err)
async extractJava(chapterId, event) { })
return new Promise(async (resolve, reject) => { }).catch(err => {
const runtime = join(this.minecraftpath, "runtime") reject(err)
if(this.modsList[chapterId].java.platform[process.platform][process.arch] !== undefined) { })
event.sender.send("progress", {type: "java", task: 0, total: 1 })
const infos = this.modsList[chapterId].java.platform[process.platform][process.arch] })
const jre = join(runtime, infos.name) }
const downloadFolder = join(runtime, "download")
const downloadFile = join(downloadFolder, `${infos.name}.zip`) async extractJava(chapterId, event) {
if(fs.existsSync(jre)) return new Promise(async (resolve, reject) => {
fs.rmSync(jre, { recursive: true }) const runtime = join(this.minecraftpath, "runtime")
if(!fs.existsSync(downloadFolder)) if(this.modsList[chapterId].java.platform[process.platform][process.arch] !== undefined) {
fs.mkdirSync(downloadFolder, { recursive: true }) event.sender.send("progress", {type: "java", task: 0, total: 1 })
if(fs.existsSync(downloadFile)) { const infos = this.modsList[chapterId].java.platform[process.platform][process.arch]
let sha1 = await hasha.fromFile(downloadFile, {algorithm: 'sha256'}) const jre = join(runtime, infos.name)
if(sha1 === infos.sha256sum) { const downloadFolder = join(runtime, "download")
await this.unzipMods(downloadFile, runtime) const downloadFile = join(downloadFolder, `${infos.name}.zip`)
resolve(join(jre, 'bin', 'java.exe')) if(fs.existsSync(jre))
} else { fs.rmSync(jre, { recursive: true })
logger.warn(`java sha256sum ${sha1} don't correspond to ${infos.sha256sum}`) if(!fs.existsSync(downloadFolder))
await this.downloadAndExtractJava(infos, downloadFolder, runtime).then(() => resolve(join(jre, 'bin', 'java.exe'))).catch(err => reject(err)) fs.mkdirSync(downloadFolder, { recursive: true })
} if(fs.existsSync(downloadFile)) {
} else { let sha1 = await hasha.fromFile(downloadFile, {algorithm: 'sha256'})
await this.downloadAndExtractJava(infos, downloadFolder, runtime).then(() => resolve(join(jre, 'bin', 'java.exe'))).catch(err => reject(err)) if(sha1 === infos.sha256sum) {
} await this.unzipMods(downloadFile, runtime)
event.sender.send("progress", {type: "java", task: 1, total: 1 }) resolve(join(jre, 'bin', 'java.exe'))
} else { } else {
reject("There is not available version for this system") logger.warn(`java sha256sum ${sha1} don't correspond to ${infos.sha256sum}`)
} await this.downloadAndExtractJava(infos, downloadFolder, runtime).then(() => resolve(join(jre, 'bin', 'java.exe'))).catch(err => reject(err))
}) }
} } else {
await this.downloadAndExtractJava(infos, downloadFolder, runtime).then(() => resolve(join(jre, 'bin', 'java.exe'))).catch(err => reject(err))
async downloadAndExtractJava(infos, downloadFolder, runtimeFolder) { }
return new Promise((resolve, reject) => { event.sender.send("progress", {type: "java", task: 1, total: 1 })
logger.info(`Downloading ${infos.name}`) } else {
this.downloadMods(infos.link, join(downloadFolder, `${infos.name}.zip`)).then(() => { reject("There is not available version for this system")
logger.info(`download completed`) }
this.unzipMods(join(downloadFolder, `${infos.name}.zip`), runtimeFolder).then(() => { })
logger.info(`File unzipped`) }
resolve()
}).catch(err => { async downloadAndExtractJava(infos, downloadFolder, runtimeFolder) {
logger.info(`Failed to unzip ${join(downloadFolder, `${infos.name}.zip`)}`) return new Promise((resolve, reject) => {
reject(err) logger.info(`Downloading ${infos.name}`)
}) this.downloadMods(infos.link, join(downloadFolder, `${infos.name}.zip`)).then(() => {
}).catch(err => { logger.info(`download completed`)
logger.err(`Failed to download ${infos.link} to ${infos.name}.zip`) this.unzipMods(join(downloadFolder, `${infos.name}.zip`), runtimeFolder).then(() => {
reject(err) logger.info(`File unzipped`)
}) resolve()
}) }).catch(err => {
} logger.info(`Failed to unzip ${join(downloadFolder, `${infos.name}.zip`)}`)
reject(err)
invalidateData(event) { })
logger.info("invalidate game data...") }).catch(err => {
const assets = join(this.minecraftpath, 'assets') logger.err(`Failed to download ${infos.link} to ${infos.name}.zip`)
const librairies = join(this.minecraftpath,'libraries') reject(err)
const natives = join(this.minecraftpath, 'natives') })
if(fs.existsSync(assets)) })
fs.rmdirSync(assets, { recursive: true }) }
if(fs.existsSync(librairies))
fs.rmdirSync(librairies, { recursive: true }) invalidateData(event) {
if(fs.existsSync(natives)) logger.info("invalidate game data...")
fs.rmdirSync(natives, { recursive: true }) const assets = join(this.minecraftpath, 'assets')
logger.info("Game data invalidated") const librairies = join(this.minecraftpath,'libraries')
event.sender.send("invalidated") const natives = join(this.minecraftpath, 'natives')
} if(fs.existsSync(assets))
} fs.rmdirSync(assets, { recursive: true })
if(fs.existsSync(librairies))
fs.rmdirSync(librairies, { recursive: true })
if(fs.existsSync(natives))
fs.rmdirSync(natives, { recursive: true })
logger.info("Game data invalidated")
event.sender.send("invalidated")
}
}
module.exports = new Minecraft module.exports = new Minecraft