4
0
mirror of https://github.com/AltarikMC/Launcher synced 2024-11-21 06:09:51 +01:00

Merge pull request #359 from AltarikMC/dev

2.1.1
This commit is contained in:
Quentin Legot 2024-01-14 18:07:46 +01:00 committed by GitHub
commit c62847163a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 2056 additions and 924 deletions

29
.eslintrc.cjs Normal file
View File

@ -0,0 +1,29 @@
module.exports = {
env: {
browser: true,
es2021: true
},
extends: [
'standard'
],
overrides: [
{
env: {
node: true
},
files: [
'.eslintrc.{js,cjs}'
],
parserOptions: {
sourceType: 'script'
}
}
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: [],
rules: {
}
}

View File

@ -1,49 +1,49 @@
const path = require('path'); const path = require('path');
const pkg = require('./package.json') const pkg = require('./package.json')
module.exports = { module.exports = {
packagerConfig: { packagerConfig: {
packageName: "altarik-launcher", packageName: "altarik-launcher",
name: "Altarik Launcher", name: "Altarik Launcher",
productName: "altarik-launcher", productName: "altarik-launcher",
icon: path.resolve(__dirname, 'icon.ico'), icon: path.resolve(__dirname, 'icon.ico'),
asar: true, asar: true,
}, },
plugins: [ plugins: [
{ {
name: '@electron-forge/plugin-auto-unpack-natives', name: '@electron-forge/plugin-auto-unpack-natives',
config: {} config: {}
} }
], ],
makers: [ makers: [
{ {
name: "@electron-forge/maker-squirrel", name: "@electron-forge/maker-squirrel",
platforms: ['darwin', 'win32'], platforms: ['darwin', 'win32'],
config: { config: {
name: pkg.name, name: pkg.name,
iconUrl: path.resolve(__dirname, 'icon.ico'), iconUrl: path.resolve(__dirname, 'icon.ico'),
//loadingGif: path.resolve(__dirname, 'src/assets/loading.gif'), //loadingGif: path.resolve(__dirname, 'src/assets/loading.gif'),
setupIcon: path.resolve(__dirname, 'icon.ico'), setupIcon: path.resolve(__dirname, 'icon.ico'),
setupExe: `${pkg.name}-${pkg.version}-win32-x64.exe` setupExe: `${pkg.name}-${pkg.version}-win32-x64.exe`
} }
}, },
{ {
name: '@electron-forge/maker-zip', name: '@electron-forge/maker-zip',
platforms: ['linux'] platforms: ['linux']
} }
], ],
publishers: [ publishers: [
{ {
name: '@electron-forge/publisher-github', name: '@electron-forge/publisher-github',
config: { config: {
repository: { repository: {
owner: 'AltarikMC', owner: 'AltarikMC',
name: 'Launcher' name: 'Launcher'
}, },
preRelease: false, preRelease: false,
draft: true, draft: true,
tagPrefix: '' tagPrefix: ''
} }
} }
] ]
} }

View File

@ -1,9 +1,10 @@
{ {
"name": "altarik-launcher", "name": "altarik-launcher",
"author": "Altarik", "author": "Altarik",
"version": "2.1.0", "version": "2.1.1",
"description": "Altarik Launcher", "description": "Altarik Launcher",
"main": "src/server/main.js", "main": "src/server/main.js",
"type": "module",
"homepage": "https://altarik.fr/", "homepage": "https://altarik.fr/",
"private": true, "private": true,
"repository": "AltarikMC/Launcher", "repository": "AltarikMC/Launcher",
@ -29,27 +30,32 @@
"publish": "electron-forge publish" "publish": "electron-forge publish"
}, },
"devDependencies": { "devDependencies": {
"@electron-forge/cli": "^6.4.2", "@electron-forge/cli": "^7.1.0",
"@electron-forge/maker-squirrel": "^6.4.2", "@electron-forge/maker-squirrel": "^7.1.0",
"@electron-forge/maker-zip": "^6.4.2", "@electron-forge/maker-zip": "^7.2.0",
"@electron-forge/plugin-auto-unpack-natives": "^6.4.2", "@electron-forge/plugin-auto-unpack-natives": "^7.2.0",
"@electron-forge/publisher-github": "^6.4.2", "@electron-forge/publisher-github": "^7.2.0",
"electron": "^27.0.4" "electron": "28.1.3",
"eslint": "^8.56.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-promise": "^6.0.0"
}, },
"dependencies": { "dependencies": {
"decompress": "^4.2.1", "decompress": "^4.2.1",
"electron-is-dev": "^2.0.0", "electron-is-dev": "^2.0.0",
"electron-log": "^5.0.0", "electron-log": "^5.0.3",
"electron-squirrel-startup": "^1.0.0", "electron-squirrel-startup": "^1.0.0",
"extract-zip": "^2.0.1", "extract-zip": "^2.0.1",
"hasha": "^5.2.2", "hasha": "^6.0.0",
"izitoast": "^1.4.0", "izitoast": "^1.4.0",
"minecraft-launcher-core": "^3.17.3", "minecraft-launcher-core": "^3.17.3",
"msmc": "^4.1.0", "msmc": "^4.1.0",
"node-fetch": "^2.7.0", "node-fetch": "^3.0.0",
"vue": "^3.3.8" "vue": "^3.4.11"
}, },
"config": { "config": {
"forge": "./config.forge.js" "forge": "./config.forge.cjs"
} }
} }

View File

@ -1,51 +1,47 @@
function handleSquirrelEvent(app) { export default function handleSquirrelEvent (app) {
if (process.argv.length === 1) { if (process.argv.length === 1) {
return false; return false
}
const ChildProcess = require('child_process');
const path = require('path');
const appFolder = path.resolve(process.execPath, '..');
const rootAtomFolder = path.resolve(appFolder, '..');
const updateDotExe = path.resolve(path.join(rootAtomFolder, 'Update.exe'));
const exeName = path.basename(process.execPath);
const spawn = function(command, args) {
let spawnedProcess;
try {
spawnedProcess = ChildProcess.spawn(command, args, {detached: true});
} catch (error) {}
return spawnedProcess;
};
const spawnUpdate = function(args) {
return spawn(updateDotExe, args);
};
const squirrelEvent = process.argv[1];
switch (squirrelEvent) {
case '--squirrel-install':
case '--squirrel-updated':
spawnUpdate(['--createShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-uninstall':
spawnUpdate(['--removeShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-obsolete':
app.quit();
return true;
}
} }
module.exports = { const ChildProcess = require('child_process')
handleSquirrelEvent const path = require('path')
}
const appFolder = path.resolve(process.execPath, '..')
const rootAtomFolder = path.resolve(appFolder, '..')
const updateDotExe = path.resolve(path.join(rootAtomFolder, 'Update.exe'))
const exeName = path.basename(process.execPath)
const spawn = function (command, args) {
let spawnedProcess
try {
spawnedProcess = ChildProcess.spawn(command, args, { detached: true })
} catch (error) {}
return spawnedProcess
}
const spawnUpdate = function (args) {
return spawn(updateDotExe, args)
}
const squirrelEvent = process.argv[1]
switch (squirrelEvent) {
case '--squirrel-install':
case '--squirrel-updated':
spawnUpdate(['--createShortcut', exeName])
setTimeout(app.quit, 1000)
return true
case '--squirrel-uninstall':
spawnUpdate(['--removeShortcut', exeName])
setTimeout(app.quit, 1000)
return true
case '--squirrel-obsolete':
app.quit()
return true
}
}

View File

@ -1,98 +1,112 @@
const { app, BrowserWindow, Menu, ipcMain, autoUpdater, dialog } = require('electron') import { app, BrowserWindow, Menu, ipcMain, autoUpdater, dialog } from 'electron'
const logger = require('electron-log') import isDev from 'electron-is-dev'
const { join } = require('path') import logger from 'electron-log'
const updater = require('./updater.js') import { join, dirname } from 'path'
let updaterInstance = new updater.Updater(app, autoUpdater, dialog, logger, showNotification) import Updater from './updater.js'
import electronStartup from 'electron-squirrel-startup'
import install from './install.js'
import Mc from './minecraft.js'
import { minimizeWindow, closeWindow } from './menubar.js'
import { fileURLToPath } from 'url'
const updaterInstance = new Updater(app, autoUpdater, dialog, logger, showNotification)
updaterInstance.configUpdater() updaterInstance.configUpdater()
if (require('electron-squirrel-startup')) { const minecraft = new Mc()
require("./install.js").handleSquirrelEvent(app)
app.quit()
return
}
const minecraft = require('./minecraft.js')
minecraft.showNotification = showNotification minecraft.showNotification = showNotification
const iconPath = join(__dirname, "icon.ico") const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const iconPath = join(__dirname, 'icon.ico')
let win = null let win = null
function createWindow () { function createWindow () {
win = new BrowserWindow({ win = new BrowserWindow({
width: 1000, width: 1000,
height: 600, height: 600,
resizable: false, resizable: false,
icon: iconPath, icon: iconPath,
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false contextIsolation: false
}, },
frame: false frame: false
}) })
//Menu.setApplicationMenu(null) if (!isDev) {
win.loadFile('src/client/checkingUpdate.html') Menu.setApplicationMenu(null)
win.on("close", () => { }
app.quit()
}) win.loadFile('src/client/checkingUpdate.html')
win.on('close', () => {
app.quit()
})
} }
const { setWindow, minimizeWindow, closeWindow } = require("./menubar.js"); function showNotification (title, body = '', clazz = 'info') {
win.webContents.send('notification', { title, body, class: clazz })
}
app.whenReady().then(() => { ipcMain.on('disconnect', () => {
minecraft.auth = null
win.loadFile('src/client/login.html').then(() => showNotification('Déconnecté', 'Vous avez été déconnecté de votre compte', 'success'))
})
ipcMain.on('pageReady', (event) => {
event.sender.send('nick', { name: minecraft.auth.name })
minecraft.getModsInformations(event)
})
ipcMain.on('checking-update', () => {
updaterInstance.checkForUpdates(win, showNotification)
})
function main () {
if (electronStartup) {
install.handleSquirrelEvent(app)
app.quit()
return
}
app.whenReady().then(() => {
createWindow() createWindow()
setWindow(win) })
})
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
app.quit() app.quit()
} }
}) })
ipcMain.on('minimizeWindow', () => { ipcMain.on('minimizeWindow', () => {
minimizeWindow(win) minimizeWindow(win)
}) })
ipcMain.on('closeWindow', () => { ipcMain.on('closeWindow', () => {
closeWindow(win) closeWindow(win)
}) })
app.on('activate', () => { app.on('activate', () => {
if (win === null){ if (win === null) {
createWindow() createWindow()
} }
}) })
ipcMain.on("login", (event, args) => { ipcMain.on('login', (event, args) => {
minecraft.login(event, win, args.user, args.pass) minecraft.login(event, win, args.user, args.pass)
}) })
ipcMain.on("microsoft-login", (event) => { ipcMain.on('microsoft-login', (event) => {
minecraft.microsoftLogin(event, win) minecraft.microsoftLogin(event, win)
}) })
ipcMain.on("invalidateData", event => { ipcMain.on('invalidateData', event => {
minecraft.invalidateData(event) minecraft.invalidateData(event)
}) })
ipcMain.on("launch", (event, args) => { ipcMain.on('launch', (event, args) => {
minecraft.launch(event, args) minecraft.launch(event, args)
}) })
function showNotification(title, body="", clazz="info") {
win.webContents.send('notification', {title: title, body: body, class: clazz})
} }
ipcMain.on("disconnect", () => { main()
minecraft.auth = null
win.loadFile('src/client/login.html').then(() => showNotification("Déconnecté", "Vous avez été déconnecté de votre compte", "success"))
})
ipcMain.on("pageReady", (event) => {
event.sender.send("nick", { name: minecraft.auth.name })
minecraft.getModsInformations(event)
})
ipcMain.on("checking-update", () => {
updaterInstance.checkForUpdates(win, showNotification)
})

View File

@ -1,21 +1,9 @@
let win; export function minimizeWindow (browserWindow) {
if (browserWindow.minimizable) {
function setWindow(browserWindow) { browserWindow.minimize()
win = browserWindow; }
} }
function minimizeWindow(browserWindow = win) { export function closeWindow (browserWindow) {
if(browserWindow.minimizable) { browserWindow.close()
browserWindow.minimize()
}
} }
function closeWindow(browserWindow = win) {
browserWindow.close()
}
module.exports = {
setWindow,
minimizeWindow,
closeWindow
}

View File

@ -1,385 +1,375 @@
const isDev = require("electron-is-dev") import isDev from 'electron-is-dev'
const { Authenticator, Client } = require("minecraft-launcher-core") import mlc from 'minecraft-launcher-core'
const fetch = require("node-fetch").default import fetch from 'node-fetch'
const hasha = require("hasha") import { hashFile } from 'hasha'
const fs = require("fs") import fs from 'fs'
const { join } = require("path") import { join } from 'path'
const constants = require("constants") import zip from 'extract-zip'
const zip = require("extract-zip") import logger from 'electron-log'
const logger = require("electron-log") import { Auth, lst } from 'msmc'
const { Auth, lst } = require("msmc") import decompress from 'decompress'
const decompress = require("decompress") import decompressTar from 'decompress-targz'
const decompressTar = require("decompress-targz")
const { Authenticator, Client } = mlc
class Minecraft { export default class Minecraft {
appdata = process.env.APPDATA || (process.platform === 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + '/.local/share')
localappdata = process.env.LOCALAPPDATA || (process.platform === 'darwin' ? process.env.HOME + '/Library/Application Support/' : process.env.HOME + '/.config')
minecraftpath = join(this.appdata, '.altarik')
launcher = new Client()
auth = null
modsList = undefined
showNotification = undefined
modsInformationsEndpoint = 'https://launcher.altarik.fr'
appdata = process.env.APPDATA || (process.platform === "darwin" ? process.env.HOME + "/Library/Preferences" : process.env.HOME + "/.local/share") setShowNotification (showNotification) {
localappdata = process.env.LOCALAPPDATA || (process.platform === "darwin" ? process.env.HOME + "/Library/Application Support/" : process.env.HOME + "/.config") this.showNotification = showNotification
minecraftpath = join(this.appdata, ".altarik") }
launcher = new Client()
auth = null
modsList = undefined
showNotification = undefined
modsInformationsEndpoint = "https://launcher.altarik.fr"
setShowNotification(showNotification) { /**
this.showNotification = showNotification
}
/**
* @deprecated Mojang removed this method of authentification * @deprecated Mojang removed this method of authentification
* 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) {
const authManager = new Auth("select_account") const authManager = new Auth('select_account')
authManager.launch("electron").then(async xboxManager => { authManager.launch('electron').then(async xboxManager => {
xboxManager.getMinecraft().then(async token => { xboxManager.getMinecraft().then(async token => {
if(!token.isDemo()) { if (!token.isDemo()) {
this.auth = token.mclc() this.auth = token.mclc()
logger.info("[MS login] User has been connected successfully to them account") logger.info('[MS login] User has been connected successfully to them account')
win.loadFile("src/client/index.html") win.loadFile('src/client/index.html')
} else {
event.sender.send("loginError")
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")
}
}).catch(err => {
event.sender.send("loginError")
logger.error("[MS login] " + lst(err))
this.showNotification("Erreur de connexion à Mojang", lst(err), "error")
})
}).catch(err => {
event.sender.send("loginError")
if(err != "error.gui.closed") {
logger.error("[MS login] " + lst(err))
this.showNotification("Une erreur de connexion à Xbox est survenue", lst(err), "error")
}
})
}
launch(event, args) {
this.extractJava(Number(args.chapter), event).then((javaPath) => {
this.extractMods(Number(args.chapter), event).then((chapter) => {
this.launcher.launch({
authorization: this.auth,
root: this.minecraftpath,
javaPath: javaPath,
version: {
number: chapter.minecraftVersion,
type: chapter.type | "release",
custom: chapter.customVersion
},
memory: {
max: args.maxMem,
min: args.minMem
}
}).then(v => {
if(v === null) {
this.close(event, -1)
}
})
this.launcher.on("debug", (e) => logger.info(`debug: ${e}`));
this.launcher.on("data", (e) => logger.info(`data: ${e}`));
this.launcher.on("progress", (e) => {
event.sender.send("progress", e)
logger.info(`progress ${e.type} :${e.task} / ${e.total}`)
})
this.launcher.on("arguments", (e) => {
event.sender.send("launch", e)
logger.info("launching the game")
logger.info(e)
})
this.launcher.on("close", (e) => {
this.close(event, e)
})
}).catch((err) => {
this.showNotification("Impossible de lancer le jeu", "Erreur inconnue", "error")
event.sender.send("close", 1)
logger.error("Unable to launch the game")
logger.error(err)
})
}).catch(err => {
this.showNotification("Impossible de lancer le jeu", "Impossible d'installer Java pour votre configuration", "error")
event.sender.send("close", 1)
logger.warn("Unable to install java")
logger.warn(err)
})
}
close(event, code) {
event.sender.send("close", code)
if(code !== 0) {
logger.warn("Minecraft didn't close properly")
logger.warn(code)
this.showNotification("Une erreur est survenue", "Minecraft ne s'est pas fermé correctement", "error")
}
}
getModsInformations(event) {
fetch(this.modsInformationsEndpoint).then(response => {
if(response.ok) {
response.json().then(data => {
let folder = join(this.localappdata, "altarik-launcher", "data")
if(!fs.existsSync(folder))
fs.mkdirSync(folder, {recursive: true})
let file = join(folder, "launcher.json")
if(fs.existsSync(file))
fs.rmSync(file)
fs.writeFileSync(file, JSON.stringify(data))
event.sender.send("modsInformations", this.extractModsInformations(data))
}).catch(err => {
event.sender.send("modsInformations", this.extractModsFromFileSystem())
logger.warn(err)
logger.warn("An error occured while trying to connect to server")
})
} else {
logger.warn("Unable to connect to server")
logger.warn(err)
event.sender.send("modsInformations", this.extractModsFromFileSystem())
}
}).catch(err => {
logger.warn("Unable to connect to server")
logger.warn(err)
event.sender.send("modsInformations", this.extractModsFromFileSystem())
})
}
extractModsFromFileSystem() {
let filepath = join(this.localappdata, "altarik-launcher/data/launcher.json")
if(fs.existsSync(filepath)) {
let content = fs.readFileSync(filepath)
if(content !== 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")
return null
}
} else { } else {
return null; event.sender.send('loginError')
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')
} }
} }).catch(err => {
event.sender.send('loginError')
logger.error('[MS login] ' + lst(err))
this.showNotification('Erreur de connexion à Mojang', lst(err), 'error')
})
}).catch(err => {
event.sender.send('loginError')
if (err !== 'error.gui.closed') {
logger.error('[MS login] ' + lst(err))
this.showNotification('Une erreur de connexion à Xbox est survenue', lst(err), 'error')
}
})
}
extractModsInformations(json) { launch (event, args) {
this.modsList = json.chapters this.extractJava(Number(args.chapter), event).then((javaPath) => {
return this.modsList this.extractMods(Number(args.chapter), event).then((chapter) => {
} this.launcher.launch({
authorization: this.auth,
async extractMods(chapterId, event) { root: this.minecraftpath,
return new Promise(async (resolve, reject) => { javaPath,
const modsFolder = join(this.minecraftpath, "mods") version: {
const shaderFolder = join(this.minecraftpath, "shaderpacks") number: chapter.minecraftVersion,
if(fs.existsSync(modsFolder)) type: chapter.type | 'release',
fs.rmSync(modsFolder, { recursive: true }) custom: chapter.customVersion
if(fs.existsSync(shaderFolder)) },
fs.rmSync(shaderFolder, { recursive: true }) memory: {
for(const i in this.modsList) { max: args.maxMem,
if(Number(i) === chapterId) { min: args.minMem
const chapter = this.modsList[i] }
for(let j in chapter.modspack.mods) { }).then(v => {
event.sender.send("progress", {type: "mods", task: 0, total: chapter.modspack.mods.length }) if (v === null) {
let modpackFolder = join(this.minecraftpath, "modpack", chapter.title) this.close(event, -1)
if(!fs.existsSync(modpackFolder)) }
fs.mkdirSync(modpackFolder, { recursive: true })
const path = join(modpackFolder, `modpack${j}.zip`)
try {
fs.accessSync(path, constants.W_OK)
let sha1 = await hasha.fromFile(path, {algorithm: "sha1"})
if(sha1 === chapter.modspack.sha1sum[j]) {
await this.unzipMods(path).catch(err => {
reject(err)
})
} else {
logger.warn(`sha1sum ${sha1} don't correspond to ${chapter.modspack.sha1sum[j]} of mods ${path}`)
await this.downloadAndExtractMods(chapter.modspack.mods[j], path).catch(err => {
reject(err)
})
}
event.sender.send("progress", {type: "mods", task: Number(j)+1, total: chapter.modspack.mods.length })
} catch (err) {
try {
await this.downloadAndExtractMods(chapter.modspack.mods[j], path)
} catch(e) {
reject({ err, e })
return
}
}
}
resolve(chapter)
return
}
}
reject("didn't found the correct chapter" + chapter)
}) })
} this.launcher.on('debug', (e) => logger.info(`debug: ${e}`))
downloadMods(link, path) { this.launcher.on('data', (e) => logger.info(`data: ${e}`))
return new Promise((resolve, reject) => { this.launcher.on('progress', (e) => {
fetch(link).then(response => { event.sender.send('progress', e)
if(response.ok) { logger.info(`progress ${e.type} :${e.task} / ${e.total}`)
if(fs.existsSync(path))
fs.rmSync(path)
const dest = fs.createWriteStream(path)
response.body.pipe(dest)
response.body.on("end", () => {
logger.log("download completed");
resolve("download completed")
})
dest.on("error", () => {
reject("An error appenned when using stream")
});
} else {
reject(response.status)
}
}).catch(err => {
reject(err)
})
}) })
} this.launcher.on('arguments', (e) => {
event.sender.send('launch', e)
async unzipMods(zipLocation, outLocation=this.minecraftpath) { logger.info('launching the game')
return new Promise(async (resolve, reject) => { logger.info(e)
logger.info(`unzipping ${zipLocation} file to ${outLocation}`)
zip(zipLocation, { dir: outLocation }).then(() => {
resolve()
}).catch(err => {
logger.error(`failed to unzip file`)
reject(err)
})
}) })
this.launcher.on('close', (e) => {
} this.close(event, e)
async extractTar(tarLocation, outLocation=this.microsoftpath) {
return new Promise(async (resolve, reject) => {
logger.info(`Extracting targz ${tarLocation} file to ${outLocation}`)
decompress(tarLocation, outLocation, {
plugins: [
decompressTar()
]
}).then(() => {
resolve()
}).catch((e) => {
logger.error(`Failed to extract targz file`)
reject(e)
})
}) })
} }).catch((err) => {
this.showNotification('Impossible de lancer le jeu', 'Erreur inconnue', 'error')
event.sender.send('close', 1)
logger.error('Unable to launch the game')
logger.error(err)
})
}).catch(err => {
this.showNotification('Impossible de lancer le jeu', "Impossible d'installer Java pour votre configuration", 'error')
event.sender.send('close', 1)
logger.warn('Unable to install java')
logger.warn(err)
})
}
async downloadAndExtractMods(link, path) { close (event, code) {
return new Promise(async (resolve, reject) => { event.sender.send('close', code)
this.downloadMods(link, path).then(() => { if (code !== 0) {
this.unzipMods(path).then(() => { logger.warn("Minecraft didn't close properly")
resolve() logger.warn(code)
}).catch(err => { this.showNotification('Une erreur est survenue', "Minecraft ne s'est pas fermé correctement", 'error')
}
}
getModsInformations (event) {
fetch(this.modsInformationsEndpoint).then(response => {
if (response.ok) {
response.json().then(data => {
const folder = join(this.localappdata, 'altarik-launcher', 'data')
if (!fs.existsSync(folder)) { fs.mkdirSync(folder, { recursive: true }) }
const file = join(folder, 'launcher.json')
if (fs.existsSync(file)) { fs.rmSync(file) }
fs.writeFileSync(file, JSON.stringify(data))
event.sender.send('modsInformations', this.extractModsInformations(data))
}).catch(err => {
event.sender.send('modsInformations', this.extractModsFromFileSystem())
logger.warn(err)
logger.warn('An error occured while trying to connect to server')
})
} else {
logger.warn('Unable to connect to server')
logger.warn(response.status)
event.sender.send('modsInformations', this.extractModsFromFileSystem())
}
}).catch(err => {
logger.warn('Unable to connect to server')
logger.warn(err)
event.sender.send('modsInformations', this.extractModsFromFileSystem())
})
}
extractModsFromFileSystem () {
const filepath = join(this.localappdata, 'altarik-launcher/data/launcher.json')
if (fs.existsSync(filepath)) {
const content = fs.readFileSync(filepath)
if (content !== 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')
return null
}
} else {
return null
}
}
extractModsInformations (json) {
this.modsList = json.chapters
return this.modsList
}
async extractMods (chapterId, event) {
return new Promise((resolve, reject) => {
const modsFolder = join(this.minecraftpath, 'mods')
// const shaderFolder = join(this.minecraftpath, 'shaderpacks')
if (fs.existsSync(modsFolder)) { fs.rmSync(modsFolder, { recursive: true }) }
// if (fs.existsSync(shaderFolder)) { fs.rmSync(shaderFolder, { recursive: true }) }
for (const i in this.modsList) {
if (Number(i) === chapterId) {
const chapter = this.modsList[i]
for (const j in chapter.modspack.mods) {
event.sender.send('progress', { type: 'mods', task: 0, total: chapter.modspack.mods.length })
const modpackFolder = join(this.minecraftpath, 'modpack', chapter.title)
if (!fs.existsSync(modpackFolder)) { fs.mkdirSync(modpackFolder, { recursive: true }) }
const path = join(modpackFolder, `modpack${j}.zip`)
try {
fs.accessSync(path, fs.W_OK)
hashFile(path, { algorithm: 'sha1' }).then(sha1 => {
if (sha1 === chapter.modspack.sha1sum[j]) {
this.unzipMods(path).catch(err => {
reject(err) reject(err)
}) })
}).catch(err => {
reject(err)
})
})
}
async extractJava(chapterId, event) {
return new Promise(async (resolve, reject) => {
const runtime = join(this.minecraftpath, "runtime")
if(this.modsList[chapterId].java.platform[process.platform] !== undefined
&& 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`)
if(fs.existsSync(jre))
fs.rmSync(jre, { recursive: true })
if(!fs.existsSync(downloadFolder))
fs.mkdirSync(downloadFolder, { recursive: true })
if(fs.existsSync(downloadFile)) {
let sha1 = await hasha.fromFile(downloadFile, {algorithm: "sha256"})
if(sha1 === infos.sha256sum) {
await this.extractJavaArchive(downloadFile, runtime)
let filename = process.platform == "win32" ? "java.exe" : "java"
resolve(join(jre, "bin", filename))
} else {
logger.warn(`java sha256sum ${sha1} don't correspond to ${infos.sha256sum}`)
await this.downloadAndExtractJava(infos, downloadFolder, runtime).then(() => resolve(join(jre, "bin", process.platform === "win32" ? "java.exe" : "java"))).catch(err => reject(err))
}
} else { } else {
await this.downloadAndExtractJava(infos, downloadFolder, runtime).then(() => resolve(join(jre, "bin", process.platform === "win32" ? "java.exe" : "java"))).catch(err => reject(err)) logger.warn(`sha1sum ${sha1} don't correspond to ${chapter.modspack.sha1sum[j]} of mods ${path}`)
} this.downloadAndExtractMods(chapter.modspack.mods[j], path).catch(err => {
event.sender.send("progress", {type: "java", task: 1, total: 1 })
} else {
reject("There is not available version for this system")
}
})
}
async downloadAndExtractJava(infos, downloadFolder, runtimeFolder) {
return new Promise((resolve, reject) => {
logger.info(`Downloading ${infos.name}`)
this.downloadMods(infos.link, join(downloadFolder, `${infos.name}.zip`)).then(() => {
logger.info(`download completed`)
this.extractJavaArchive(join(downloadFolder, `${infos.name}.zip`), runtimeFolder).then(() => {
logger.info(`File unzipped`)
resolve()
}).catch(err => {
let join_s = join(downloadFolder, `${infos.name}.zip`)
logger.info(`Failed to unzip ${join_s}`)
reject(err) reject(err)
}) })
}).catch(err => { }
logger.err(`Failed to download ${infos.link} to ${infos.name}.zip`) }).catch(err => {
reject(new Error('Can obtain md5 hash of file ' + path + ': ' + err))
})
event.sender.send('progress', { type: 'mods', task: Number(j) + 1, total: chapter.modspack.mods.length })
} catch (err) {
this.downloadAndExtractMods(chapter.modspack.mods[j], path).catch(err => {
reject(err) reject(err)
}) })
}) }
} }
resolve(chapter)
return
}
}
reject(new Error("didn't found the correct chapter" + chapterId))
})
}
async extractJavaArchive(zipLocation, outLocation) { downloadMods (link, path) {
if(process.platform === "win32") { return new Promise((resolve, reject) => {
await this.unzipMods(zipLocation, outLocation) fetch(link).then(response => {
if (response.ok) {
if (fs.existsSync(path)) { fs.rmSync(path) }
const dest = fs.createWriteStream(path)
response.body.pipe(dest)
response.body.on('end', () => {
logger.log('download completed')
resolve('download completed')
})
dest.on('error', () => {
reject(new Error('An error appenned when using stream'))
})
} else { } else {
await this.extractTar(zipLocation, outLocation) reject(response.status)
} }
} }).catch(err => {
reject(err)
})
})
}
invalidateData(event) { async unzipMods (zipLocation, outLocation = this.minecraftpath) {
logger.info("invalidate game data...") return new Promise((resolve, reject) => {
const assets = join(this.minecraftpath, "assets") logger.info(`unzipping ${zipLocation} file to ${outLocation}`)
const librairies = join(this.minecraftpath,"libraries") zip(zipLocation, { dir: outLocation }).then(() => {
const natives = join(this.minecraftpath, "natives") resolve()
const versions = join(this.minecraftpath, "versions") }).catch(err => {
if(fs.existsSync(assets)) logger.error('failed to unzip file')
fs.rmSync(assets, { recursive: true }) reject(err)
if(fs.existsSync(librairies)) })
fs.rmSync(librairies, { recursive: true }) })
if(fs.existsSync(natives)) }
fs.rmSync(natives, { recursive: true })
if(fs.existsSync(versions)) async extractTar (tarLocation, outLocation = this.microsoftpath) {
fs.rmSync(versions, { recursive: true }) return new Promise((resolve, reject) => {
logger.info("Game data invalidated") logger.info(`Extracting targz ${tarLocation} file to ${outLocation}`)
event.sender.send("invalidated") decompress(tarLocation, outLocation, {
plugins: [
decompressTar()
]
}).then(() => {
resolve()
}).catch((e) => {
logger.error('Failed to extract targz file')
reject(e)
})
})
}
async downloadAndExtractMods (link, path) {
return new Promise((resolve, reject) => {
this.downloadMods(link, path).then(() => {
this.unzipMods(path).then(() => {
resolve()
}).catch(err => {
reject(err)
})
}).catch(err => {
reject(err)
})
})
}
async extractJava (chapterId, event) {
return new Promise((resolve, reject) => {
const runtime = join(this.minecraftpath, 'runtime')
if (this.modsList[chapterId].java.platform[process.platform] !== undefined &&
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`)
if (fs.existsSync(jre)) { fs.rmSync(jre, { recursive: true }) }
if (!fs.existsSync(downloadFolder)) { fs.mkdirSync(downloadFolder, { recursive: true }) }
if (fs.existsSync(downloadFile)) {
hashFile(downloadFile, { algorithm: 'sha256' }).then(sha1 => {
if (sha1 === infos.sha256sum) {
this.extractJavaArchive(downloadFile, runtime).then(() => {
const filename = process.platform === 'win32' ? 'java.exe' : 'java'
resolve(join(jre, 'bin', filename))
}).catch(err => {
reject(err)
})
} else {
logger.warn(`java sha256sum ${sha1} don't correspond to ${infos.sha256sum}`)
this.downloadAndExtractJava(infos, downloadFolder, runtime).then(() => resolve(join(jre, 'bin', process.platform === 'win32' ? 'java.exe' : 'java'))).catch(err => reject(err))
}
}).catch(err => {
reject(err)
})
} else {
this.downloadAndExtractJava(infos, downloadFolder, runtime).then(() => resolve(join(jre, 'bin', process.platform === 'win32' ? 'java.exe' : 'java'))).catch(err => reject(err))
}
event.sender.send('progress', { type: 'java', task: 1, total: 1 })
} else {
reject(new Error('There is not available version for this system'))
}
})
}
async downloadAndExtractJava (infos, downloadFolder, runtimeFolder) {
return new Promise((resolve, reject) => {
logger.info(`Downloading ${infos.name}`)
this.downloadMods(infos.link, join(downloadFolder, `${infos.name}.zip`)).then(() => {
logger.info('download completed')
this.extractJavaArchive(join(downloadFolder, `${infos.name}.zip`), runtimeFolder).then(() => {
logger.info('File unzipped')
resolve()
}).catch(err => {
const joinS = join(downloadFolder, `${infos.name}.zip`)
logger.info(`Failed to unzip ${joinS}`)
reject(err)
})
}).catch(err => {
logger.err(`Failed to download ${infos.link} to ${infos.name}.zip`)
reject(err)
})
})
}
async extractJavaArchive (zipLocation, outLocation) {
if (process.platform === 'win32') {
await this.unzipMods(zipLocation, outLocation)
} else {
await this.extractTar(zipLocation, outLocation)
} }
}
invalidateData (event) {
logger.info('invalidate game data...')
const assets = join(this.minecraftpath, 'assets')
const librairies = join(this.minecraftpath, 'libraries')
const natives = join(this.minecraftpath, 'natives')
const versions = join(this.minecraftpath, 'versions')
if (fs.existsSync(assets)) { fs.rmSync(assets, { recursive: true }) }
if (fs.existsSync(librairies)) { fs.rmSync(librairies, { recursive: true }) }
if (fs.existsSync(natives)) { fs.rmSync(natives, { recursive: true }) }
if (fs.existsSync(versions)) { fs.rmSync(versions, { recursive: true }) }
logger.info('Game data invalidated')
event.sender.send('invalidated')
}
} }
module.exports = new Minecraft

View File

@ -1,101 +1,94 @@
const isDev = require('electron-is-dev') import isDev from 'electron-is-dev'
const fetch = require('node-fetch').default import fetch from 'node-fetch'
const pkg = require('../../package.json') import pkg from '../../package.json' assert {type: 'json'}
const server = 'https://update.electronjs.org' const server = 'https://update.electronjs.org'
class Updater { export default class Updater {
constructor (app, autoUpdater, dialog, logger, ipcMain) {
this.app = app
this.autoUpdater = autoUpdater
this.dialog = dialog
this.logger = logger
this.ipcMain = ipcMain
}
constructor(app, autoUpdater, dialog, logger, ipcMain) { configUpdater () {
this.app = app this.logger.info(`electron version: ${process.versions.electron}`)
this.autoUpdater = autoUpdater this.logger.info(`chrome version: ${process.versions.chrome}`)
this.dialog = dialog this.logger.info(`Node version: ${process.versions.node}`)
this.logger = logger this.logger.info(`platform: ${process.platform}`)
this.ipcMain = ipcMain this.logger.info(`arch: ${process.arch}`)
if (isDev) {
this.logger.info(`developpement version ${this.app.getVersion()}`)
return
} }
this.logger.info(`production version ${this.app.getVersion()}`)
// TODO : replace dialog by automatic restart
this.autoUpdater.on('update-downloaded', (_event, releaseNotes, releaseName) => {
this.logger.info(`update downloaded ${releaseName}`)
this.logger.info('Leaving application to install update...')
this.autoUpdater.quitAndInstall()
})
}
configUpdater() { checkForUpdates (win, showNotification) {
this.logger.info(`electron version: ${process.versions['electron']}`) if (isDev) {
this.logger.info(`chrome version: ${process.versions['chrome']}`) win.loadFile('src/client/login.html')
this.logger.info(`Node version: ${process.versions['node']}`) return
this.logger.info(`platform: ${process.platform}`)
this.logger.info(`arch: ${process.arch}`)
if(isDev) {
this.logger.info(`developpement version ${this.app.getVersion()}`)
return
}
this.logger.info(`production version ${this.app.getVersion()}`)
// TODO : replace dialog by automatic restart
this.autoUpdater.on('update-downloaded', (_event, releaseNotes, releaseName) => {
this.logger.info(`update downloaded ${releaseName}`)
this.logger.info("Leaving application to install update...")
this.autoUpdater.quitAndInstall()
})
} }
this.logger.info('Checking for update...')
const feed = `${server}/${pkg.repository}/${process.platform}-${process.arch}/${this.app.getVersion()}`
if (process.platform !== 'linux') {
this.autoUpdater.setFeedURL(feed)
this.autoUpdater.on('error', message => {
this.displayError(win, showNotification, message)
})
checkForUpdates(win, showNotification) { this.autoUpdater.on('update-available', () => {
if(isDev) { this.logger.info('update available, downloading...')
win.loadFile('src/client/login.html') win.webContents.send('update-available')
return; })
} this.autoUpdater.on('update-not-available', () => {
this.logger.info("Checking for update...") this.logger.info('update not available')
const feed = `${server}/${pkg.repository}/${process.platform}-${process.arch}/${this.app.getVersion()}` win.loadFile('src/client/login.html')
if(process.platform != 'linux') { })
this.autoUpdater.setFeedURL(feed) this.autoUpdater.checkForUpdates()
this.autoUpdater.on('error', message => { } else {
this.displayError(win, showNotification, message) this.searchUpdateLinux(win, showNotification)
})
this.autoUpdater.on('update-available', () => {
this.logger.info("update available, downloading...")
win.webContents.send("update-available")
})
this.autoUpdater.on("update-not-available", () => {
this.logger.info("update not available")
win.loadFile('src/client/login.html')
})
this.autoUpdater.checkForUpdates()
} else {
this.searchUpdateLinux(win, showNotification)
}
} }
}
searchUpdateLinux(win, showNotification) { searchUpdateLinux (win, showNotification) {
const url = 'https://api.github.com/repos/AltarikMc/Launcher/releases/latest' const url = 'https://api.github.com/repos/AltarikMc/Launcher/releases/latest'
fetch(url).then(response => { fetch(url).then(response => {
if(response.status === 200) { if (response.status === 200) {
response.json().then(json => { response.json().then(json => {
if(json.tag_name !== pkg.version) { if (json.tag_name !== pkg.version) {
let asset = json.assets.filter(el => el.browser_download_url.includes(".zip")) const asset = json.assets.filter(el => el.browser_download_url.includes('.zip'))
if(asset.length === 1) { if (asset.length === 1) {
let downloadUrl = asset[0].browser_download_url const downloadUrl = asset[0].browser_download_url
win.webContents.send("please-download-update", { url: downloadUrl} ) win.webContents.send('please-download-update', { url: downloadUrl })
this.logger.info("update available, please download") this.logger.info('update available, please download')
} else {
this.displayError(win, showNotification, "Can't find right asset in last update")
}
} else {
this.logger.info("update not available")
win.loadFile('src/client/login.html')
}
}).catch(err => this.displayError(win, showNotification, err))
} else { } else {
this.displayError(win, showNotification, "Server unavailable") this.displayError(win, showNotification, "Can't find right asset in last update")
} }
}).catch(err => this.displayError(win, showNotification, err)) } else {
} this.logger.info('update not available')
win.loadFile('src/client/login.html')
displayError(win, showNotification, errorMessage) { }
this.logger.error('There was a problem updating the application') }).catch(err => this.displayError(win, showNotification, err))
this.logger.error(errorMessage) } else {
win.loadFile('src/client/login.html').then(() => { this.displayError(win, showNotification, 'Server unavailable')
showNotification("Une erreur est survenue lors de la vérification de la mise à jour", "Veuillez vérifier votre connexion internet et réessayer", "error") }
}) }).catch(err => this.displayError(win, showNotification, err))
} }
} displayError (win, showNotification, errorMessage) {
this.logger.error('There was a problem updating the application')
module.exports = { this.logger.error(errorMessage)
Updater win.loadFile('src/client/login.html').then(() => {
showNotification('Une erreur est survenue lors de la vérification de la mise à jour', 'Veuillez vérifier votre connexion internet et réessayer', 'error')
})
}
} }

1682
yarn.lock

File diff suppressed because it is too large Load Diff