Move renderer to Vite + React
@ -1,29 +1,9 @@
|
||||
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: {
|
||||
}
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'@electron-toolkit',
|
||||
'@electron-toolkit/eslint-config-prettier'
|
||||
]
|
||||
}
|
||||
|
1
.gitignore
vendored
@ -4,3 +4,4 @@ out/
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
.idea
|
||||
dist
|
6
.prettierignore
Normal file
@ -0,0 +1,6 @@
|
||||
out
|
||||
dist
|
||||
pnpm-lock.yaml
|
||||
LICENSE.md
|
||||
tsconfig.json
|
||||
tsconfig.*.json
|
5
.prettierrc.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
singleQuote: true
|
||||
semi: false
|
||||
printWidth: 100
|
||||
trailingComma: none
|
||||
endOfLine: auto
|
@ -1,49 +1,53 @@
|
||||
const path = require('path');
|
||||
const path = require('path')
|
||||
const pkg = require('./package.json')
|
||||
|
||||
module.exports = {
|
||||
packagerConfig: {
|
||||
packageName: "altarik-launcher",
|
||||
name: "Altarik Launcher",
|
||||
productName: "altarik-launcher",
|
||||
icon: path.resolve(__dirname, 'icon.ico'),
|
||||
asar: true,
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: '@electron-forge/plugin-auto-unpack-natives',
|
||||
config: {}
|
||||
}
|
||||
],
|
||||
makers: [
|
||||
{
|
||||
name: "@electron-forge/maker-squirrel",
|
||||
platforms: ['darwin', 'win32'],
|
||||
config: {
|
||||
name: pkg.name,
|
||||
iconUrl: path.resolve(__dirname, 'icon.ico'),
|
||||
//loadingGif: path.resolve(__dirname, 'src/assets/loading.gif'),
|
||||
setupIcon: path.resolve(__dirname, 'icon.ico'),
|
||||
setupExe: `${pkg.name}-${pkg.version}-win32-x64.exe`
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-zip',
|
||||
platforms: ['linux']
|
||||
}
|
||||
],
|
||||
publishers: [
|
||||
{
|
||||
name: '@electron-forge/publisher-github',
|
||||
config: {
|
||||
repository: {
|
||||
owner: 'AltarikMC',
|
||||
name: 'Launcher'
|
||||
},
|
||||
preRelease: false,
|
||||
draft: true,
|
||||
tagPrefix: ''
|
||||
}
|
||||
}
|
||||
packagerConfig: {
|
||||
packageName: 'altarik-launcher',
|
||||
name: 'Altarik Launcher',
|
||||
productName: 'altarik-launcher',
|
||||
icon: path.resolve(__dirname, 'icon.ico'),
|
||||
asar: true,
|
||||
ignore: [
|
||||
/^\/src/,
|
||||
/(.eslintrc.json)|(.gitignore)|(electron.vite.config.ts)|(forge.config.cjs)|(tsconfig.*)/
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: '@electron-forge/plugin-auto-unpack-natives',
|
||||
config: {}
|
||||
}
|
||||
],
|
||||
makers: [
|
||||
{
|
||||
name: '@electron-forge/maker-squirrel',
|
||||
platforms: ['darwin', 'win32'],
|
||||
config: {
|
||||
name: pkg.name,
|
||||
iconUrl: path.resolve(__dirname, 'icon.ico'),
|
||||
// loadingGif: path.resolve(__dirname, 'src/assets/loading.gif'),
|
||||
setupIcon: path.resolve(__dirname, 'icon.ico'),
|
||||
setupExe: `${pkg.name}-${pkg.version}-win32-x64.exe`
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '@electron-forge/maker-zip',
|
||||
platforms: ['linux']
|
||||
}
|
||||
],
|
||||
publishers: [
|
||||
{
|
||||
name: '@electron-forge/publisher-github',
|
||||
config: {
|
||||
repository: {
|
||||
owner: 'AltarikMC',
|
||||
name: 'Launcher'
|
||||
},
|
||||
preRelease: false,
|
||||
draft: true,
|
||||
tagPrefix: ''
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
31
package.json
@ -3,7 +3,7 @@
|
||||
"author": "Altarik",
|
||||
"version": "2.1.4",
|
||||
"description": "Altarik Launcher",
|
||||
"main": "src/server/main.js",
|
||||
"main": "./dist/main/index.js",
|
||||
"type": "module",
|
||||
"homepage": "https://altarik.fr/",
|
||||
"private": true,
|
||||
@ -23,11 +23,11 @@
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"scripts": {
|
||||
"start": "electron-forge start",
|
||||
"test": "electron-forge start --enable-logging --inspect-electron",
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"publish": "electron-forge publish"
|
||||
"start": "electron-vite preview --outDir=dist",
|
||||
"dev": "electron-vite dev --outDir=dist",
|
||||
"package": "electron-vite build --outDir=dist && electron-forge package",
|
||||
"make": "electron-vite build --outDir=dist && electron-forge make",
|
||||
"publish": "electron-vite build --outDir=dist && electron-forge publish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^7.1.0",
|
||||
@ -35,25 +35,38 @@
|
||||
"@electron-forge/maker-zip": "^7.2.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "^7.2.0",
|
||||
"@electron-forge/publisher-github": "^7.2.0",
|
||||
"@electron-toolkit/eslint-config": "^1.0.2",
|
||||
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"cssnano": "^6.0.3",
|
||||
"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"
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"postcss": "^8.4.33",
|
||||
"tailwindcss": "^3.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron-toolkit/preload": "^3.0.0",
|
||||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"decompress": "^4.2.1",
|
||||
"electron-is-dev": "^3.0.1",
|
||||
"electron-log": "^5.0.3",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"electron-vite": "^2.0.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"extract-zip": "^2.0.1",
|
||||
"hasha": "^6.0.0",
|
||||
"izitoast": "^1.4.0",
|
||||
"minecraft-launcher-core": "^3.17.3",
|
||||
"msmc": "^4.1.0",
|
||||
"node-fetch": "^3.0.0",
|
||||
"vue": "^3.4.11"
|
||||
"prettier": "^3.2.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"vite": "^5.0.12"
|
||||
},
|
||||
"config": {
|
||||
"forge": "./config.forge.cjs"
|
||||
|
7
postcss.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
const vue = require('vue/dist/vue.cjs.js')
|
||||
app = vue.createApp({
|
||||
data () {
|
||||
return {
|
||||
displayFullscreen: 'block',
|
||||
fullscreenText: 'Recherche de mise à jour...',
|
||||
downloadLink: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.sendCheckingUpdate()
|
||||
},
|
||||
methods: {
|
||||
sendCheckingUpdate () {
|
||||
ipcRenderer.send('checking-update')
|
||||
},
|
||||
openLinkExternal () {
|
||||
shell.openExternal(this.downloadLink)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const root = app.mount('#vue')
|
||||
|
||||
ipcRenderer.on('update-available', (event, arg) => {
|
||||
root.fullscreenText = 'Mise à jour disponible, téléchargement...'
|
||||
})
|
||||
|
||||
ipcRenderer.on('please-download-update', (event, args) => {
|
||||
root.fullscreenText = 'Veuillez télécharger la mise à jour en cliquant sur le lien suivant :'
|
||||
root.downloadLink = `${args.url}`
|
||||
})
|
@ -1,177 +0,0 @@
|
||||
const os = require('os')
|
||||
const totalMem = os.totalmem() / (1.049 * Math.pow(10, 6))
|
||||
const vue = require('vue/dist/vue.cjs.js')
|
||||
|
||||
app = vue.createApp({
|
||||
data () {
|
||||
return {
|
||||
minMemValue: localStorage.getItem('minMem') != null ? localStorage.getItem('minMem') : 1024,
|
||||
maxMemValue: localStorage.getItem('maxMem') != null ? localStorage.getItem('maxMem') : 2048,
|
||||
memStep: 128,
|
||||
memMax: totalMem,
|
||||
invalidateButtonText: 'Supprimer et retélécharger les bibliothèques',
|
||||
invalidateButtonDisabled: false,
|
||||
displayFullscreen: 'none',
|
||||
displaySettings: 'none',
|
||||
displayCredits: 'none',
|
||||
nick: 'Chargement',
|
||||
launchBtnText: 'Selectionnez un chapitre',
|
||||
launchBtnDisable: true,
|
||||
launchBtnHidden: false,
|
||||
loadingMessageHidden: true,
|
||||
loadingMessageText: 'Téléchargement de Minecraft en cours...',
|
||||
fullprogressbarHidden: true,
|
||||
progressbarWidth: 0,
|
||||
sidebarContent: '<hr><p>Chargement en cours</p>',
|
||||
modsInformations: [],
|
||||
modsInformationsLoaded: true,
|
||||
selectedChapter: -1,
|
||||
gameLaunching: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
iziToast.settings({
|
||||
close: false,
|
||||
closeOnClick: true,
|
||||
timeout: 5000,
|
||||
position: 'topRight',
|
||||
resetOnHover: true
|
||||
})
|
||||
setInterval(() => {
|
||||
ipcRenderer.send('pageReady')
|
||||
}, 500)
|
||||
},
|
||||
methods: {
|
||||
invalidateData () {
|
||||
this.invalidateButtonDisabled = true
|
||||
this.invalidateButtonText = 'Opération en cours'
|
||||
this.showInfo('Opération en cours', 'Suppression des données du jeu en cours')
|
||||
ipcRenderer.send('invalidateData')
|
||||
},
|
||||
launchBtnClick () {
|
||||
this.launchBtnHidden = true
|
||||
this.fullprogressbarHidden = false
|
||||
this.loadingMessageHidden = false
|
||||
if (Number(this.minMemValue) <= Number(this.maxMemValue)) {
|
||||
ipcRenderer.send('launch', {
|
||||
minMem: this.minMemValue + 'M',
|
||||
maxMem: this.maxMemValue + 'M',
|
||||
chapter: this.selectedChapter
|
||||
})
|
||||
this.launchBtnDisable = true
|
||||
localStorage.setItem('minMem', this.minMemValue)
|
||||
localStorage.setItem('maxMem', this.maxMemValue)
|
||||
this.gameLaunching = true
|
||||
} else {
|
||||
this.showError('Erreur de lancement', 'La mémoire minimale doit être inférieure ou égale à la mémoire maximale.')
|
||||
}
|
||||
},
|
||||
changeSelectedChapter (index) {
|
||||
this.selectedChapter = parseInt(index)
|
||||
root.launchBtnText = 'JOUER'
|
||||
root.launchBtnDisable = false
|
||||
},
|
||||
disconnectBtn () {
|
||||
ipcRenderer.send('disconnect')
|
||||
},
|
||||
options () {
|
||||
if (!this.gameLaunching) {
|
||||
this.displayFullscreen = 'block'
|
||||
this.displaySettings = 'block'
|
||||
this.displayCredits = 'none'
|
||||
}
|
||||
},
|
||||
discord () {
|
||||
shell.openExternal('https://discord.gg/p3EnE6Jumg')
|
||||
},
|
||||
web () {
|
||||
shell.openExternal('https://altarik.fr')
|
||||
},
|
||||
closeFullscreen () {
|
||||
this.displayFullscreen = 'none'
|
||||
this.displaySettings = 'none'
|
||||
this.displayCredits = 'none'
|
||||
},
|
||||
credits () {
|
||||
this.displayFullscreen = 'block'
|
||||
this.displaySettings = 'none'
|
||||
this.displayCredits = 'block'
|
||||
},
|
||||
updateModsInformations (content) {
|
||||
this.modsInformations = content
|
||||
},
|
||||
getModsInformations () {
|
||||
return this.modsInformations
|
||||
},
|
||||
showInfo (title, body) {
|
||||
iziToast.info({
|
||||
title,
|
||||
message: body,
|
||||
color: 'blue'
|
||||
})
|
||||
},
|
||||
showError (title, body) {
|
||||
iziToast.error({
|
||||
title,
|
||||
message: body,
|
||||
color: 'red'
|
||||
})
|
||||
},
|
||||
showWarning (title, body) {
|
||||
iziToast.warning({
|
||||
title,
|
||||
message: body,
|
||||
color: 'yellow'
|
||||
})
|
||||
},
|
||||
showSuccess (title, body) {
|
||||
iziToast.success({
|
||||
title,
|
||||
message: body,
|
||||
color: 'green'
|
||||
})
|
||||
},
|
||||
isSelected (index) {
|
||||
return this.selectedChapter === index
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const root = app.mount('#vue')
|
||||
|
||||
ipcRenderer.on('invalidated', () => {
|
||||
root.invalidateButtonDisabled = false
|
||||
root.invalidateButtonText = 'Supprimer et retélécharger les bibliothèques'
|
||||
root.showSuccess('Opération terminée', 'Les données du jeu ont été supprimé avec succès')
|
||||
})
|
||||
|
||||
ipcRenderer.on('progress', (e, args) => {
|
||||
root.progressbarWidth = (args.task / Math.max(args.total, args.task)) * 100
|
||||
root.loadingMessageText = 'Téléchargement de ' + args.type + ': ' + args.task + ' sur ' + Math.max(args.total, args.task)
|
||||
})
|
||||
|
||||
ipcRenderer.on('close', (_e, _args) => {
|
||||
root.launchBtnHidden = false
|
||||
root.fullprogressbarHidden = true
|
||||
root.loadingMessageHidden = true
|
||||
root.loadingMessageText = 'Chargement de Minecraft en cours...'
|
||||
root.progressbarWidth = 0
|
||||
root.launchBtnDisable = false
|
||||
root.gameLaunching = false
|
||||
})
|
||||
|
||||
ipcRenderer.on('launch', (_e, _args) => {
|
||||
root.fullprogressbarHidden = true
|
||||
root.loadingMessageHidden = true
|
||||
})
|
||||
|
||||
ipcRenderer.on('modsInformations', (_e, args) => {
|
||||
if (args === null) {
|
||||
root.modsInformationsLoaded = false
|
||||
} else {
|
||||
root.modsInformationsLoaded = true
|
||||
}
|
||||
root.updateModsInformations(args)
|
||||
})
|
||||
|
||||
ipcRenderer.on('nick', (_e, args) => root.nick = args.name)
|
@ -1,87 +0,0 @@
|
||||
const vue = require('vue/dist/vue.cjs.js')
|
||||
app = vue.createApp({
|
||||
data () {
|
||||
return {
|
||||
login: 'Connexion',
|
||||
email: 'Email',
|
||||
password: 'Mot de passe',
|
||||
send_credentials: 'Se connecter',
|
||||
microsoft_button: 'Connexion avec un compte Microsoft'
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
iziToast.settings({
|
||||
close: false,
|
||||
closeOnClick: true,
|
||||
timeout: 5000,
|
||||
position: 'topRight',
|
||||
resetOnHover: true
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
formSubmit (e) {
|
||||
e.preventDefault()
|
||||
if (!microsoftButton.disabled) {
|
||||
form.disabled = true
|
||||
if (user.value) {
|
||||
ipcRenderer.send('login', {
|
||||
user: user.value,
|
||||
pass: password.value
|
||||
})
|
||||
} else {
|
||||
this.showWarning('Erreur de connexion', 'Veuillez entrer des identifiants')
|
||||
form.disabled = false
|
||||
}
|
||||
}
|
||||
},
|
||||
microsoftButton (e) {
|
||||
e.preventDefault()
|
||||
if (!form.disabled) {
|
||||
microsoftButton.disabled = true
|
||||
form.disabled = true
|
||||
ipcRenderer.send('microsoft-login')
|
||||
}
|
||||
},
|
||||
showInfo (title, body) {
|
||||
iziToast.info({
|
||||
title,
|
||||
message: body,
|
||||
color: 'blue'
|
||||
})
|
||||
},
|
||||
showError (title, body) {
|
||||
iziToast.error({
|
||||
title,
|
||||
message: body,
|
||||
color: 'red'
|
||||
})
|
||||
},
|
||||
showWarning (title, body) {
|
||||
iziToast.warning({
|
||||
title,
|
||||
message: body,
|
||||
color: 'yellow'
|
||||
})
|
||||
},
|
||||
showSuccess (title, body) {
|
||||
iziToast.success({
|
||||
title,
|
||||
message: body,
|
||||
color: 'green'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.mount('#vue')
|
||||
|
||||
// theirs const are declared after vue cause vue modify them when declaring new vue instance
|
||||
const form = document.querySelector('#login-form')
|
||||
const user = document.querySelector('#nickname')
|
||||
const password = document.querySelector('#password')
|
||||
const microsoftButton = document.querySelector('#microsoft-button')
|
||||
|
||||
ipcRenderer.on('loginError', () => {
|
||||
form.disabled = false
|
||||
microsoftButton.disabled = false
|
||||
})
|
@ -1,29 +0,0 @@
|
||||
'use strict'
|
||||
const { ipcRenderer, shell } = require('electron')
|
||||
let app
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const minimizeButton = document.getElementById('minimize-btn')
|
||||
const closeButton = document.getElementById('close-btn')
|
||||
|
||||
minimizeButton.addEventListener('click', () => ipcRenderer.send('minimizeWindow'))
|
||||
|
||||
closeButton.addEventListener('click', () => ipcRenderer.send('closeWindow'))
|
||||
})
|
||||
|
||||
ipcRenderer.on('notification', (_e, args) => {
|
||||
app.notificationTitle = args.title
|
||||
app.notificationMessage = args.body
|
||||
switch (args.class) {
|
||||
case 'success':
|
||||
app._component.methods.showSuccess(args.title, args.body)
|
||||
break
|
||||
case 'warning':
|
||||
app._component.methods.showWarning(args.title, args.body)
|
||||
break
|
||||
case 'error':
|
||||
app._component.methods.showError(args.title, args.body)
|
||||
break
|
||||
case 'info':default:
|
||||
app._component.methods.showInfo(args.title, args.body)
|
||||
}
|
||||
})
|
@ -1,35 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Altarik Launcher</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval';" />
|
||||
<link rel="stylesheet" href="../../node_modules/izitoast/dist/css/iziToast.min.css">
|
||||
<link href="assets/css/fonts.css" rel="stylesheet" />
|
||||
<link href="assets/css/checkingUpdate.css" rel="stylesheet" />
|
||||
<link href="assets/css/menubar.css" rel="stylesheet" />
|
||||
<!-- <link rel="shortcut icon" type="image/png" href="assets/images/icon.png"/> -->
|
||||
</head>
|
||||
<body>
|
||||
<div id="vue">
|
||||
<div id="menubar">
|
||||
<ul class="left">
|
||||
<img src="../../icon.ico">
|
||||
</ul>
|
||||
<ul class="right">
|
||||
<!-- Mettre ce code en ligne pour éviter que chrome ne met un espace automatiquement entre les éléments -->
|
||||
<li id="minimize-btn"><i class="material-icons">minimize</i></li><!--<li id="max-unmax-btn"><i class="material-icons">crop_square</i></li>--><li id="close-btn"><i class="material-icons">close</i></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="fullscreen" :style="{ display: displayFullscreen }">
|
||||
<div id="fullscreen-content">
|
||||
<p>{{ fullscreenText }} <br /><a @click="openLinkExternal" v-if="downloadLink !== null" href="#">{{ downloadLink }}</a></p>
|
||||
<div class="dots-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../../node_modules/izitoast/dist/js/iziToast.js" type="text/javascript"></script>
|
||||
<script src="assets/js/preload.js"></script>
|
||||
<script src="assets/js/checkingUpdate.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,112 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Altarik Launcher</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval';" />
|
||||
<link rel="stylesheet" href="../../node_modules/izitoast/dist/css/iziToast.min.css">
|
||||
<link href="assets/css/fonts.css" rel="stylesheet" />
|
||||
<link href="assets/css/index.css" rel="stylesheet" />
|
||||
<link href="assets/css/menubar.css" rel="stylesheet" />
|
||||
<!--<link rel="shortcut icon" type="image/png" href="assets/images/icon.png"/> -->
|
||||
</head>
|
||||
<body>
|
||||
<div id="vue">
|
||||
<div id="menubar">
|
||||
<ul class="left">
|
||||
<img src="../../icon.ico">
|
||||
</ul>
|
||||
<ul class="right">
|
||||
<!-- Mettre ce code en ligne pour éviter que chrome ne met un espace automatiquement entre les éléments -->
|
||||
<li id="minimize-btn"><i class="material-icons">minimize</i></li><!--<li id="max-unmax-btn"><i class="material-icons">crop_square</i></li>--><li id="close-btn"><i class="material-icons">close</i></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="fullscreen" :style="{ display: displayFullscreen }">
|
||||
<div @click="closeFullscreen" id="close"><i class="material-icons">close</i></div>
|
||||
<div id="settings" :style="{ display: displaySettings }">
|
||||
<h2>Paramètres</h2>
|
||||
<span href="" id="disconnect-btn" @click="disconnectBtn">Se déconnecter</span>
|
||||
<h4>Allocation mémoire</h4>
|
||||
<label for="minMem">mémoire minimale : <span id="outputMinMem">{{ minMemValue }}</span></label><br />
|
||||
<input type="number" min="1024" :max="memMax" :step="memStep" v-model="minMemValue" class="slider" id="minMem"><br />
|
||||
<label for="maxMem">mémoire maximale : <span id="outputMaxMem">{{ maxMemValue }}</span></label><br />
|
||||
<input type="number" min="1024" :max="memMax" :step="memStep" v-model="maxMemValue" class="slider" id="maxMem"><br />
|
||||
<h4>Au secours, mon jeu ne démarre pas</h4>
|
||||
<button @click="invalidateData" :disabled="invalidateButtonDisabled">{{ invalidateButtonText }}</button><br />
|
||||
<span @click="credits">Voir crédits</span>
|
||||
</div>
|
||||
<div id="credits" :style="{ display: displayCredits }">
|
||||
<div class="content">
|
||||
<p>BSD 3-Clause License</p>
|
||||
|
||||
<p>Copyright (c) 2021, Altarik<br />
|
||||
All rights reserved.</p>
|
||||
|
||||
<p>Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:</p>
|
||||
|
||||
<ol>
|
||||
<li>Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.</li>
|
||||
<li>Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.</li>
|
||||
<li>Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.</li>
|
||||
</ol>
|
||||
|
||||
<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="content">
|
||||
<div id="sidebar">
|
||||
<h2>Chapitres</h2>
|
||||
<div id="sidebar-content" @change="modsInformations">
|
||||
<div v-if="modsInformationsLoaded === false">Une erreur est survenue lors de la récupération des informations, vérifiez votre connexion internet puis cliquez sur réessayez</div>
|
||||
<div v-for="(item, index) in modsInformations" v-else-if="modsInformations.length !== 0" v-on:click="changeSelectedChapter(index)" :class="{ selected: isSelected(index) }">
|
||||
<h3>{{ item.title }}</h3>
|
||||
<p>{{ item.description}}</p>
|
||||
</div>
|
||||
<div v-else>Chargement en cours</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="media">
|
||||
<div @click="options" title="Paramètres">
|
||||
<img src="assets/images/settings.png">
|
||||
</div>
|
||||
<div @click="discord" title="Rejoingnez notre Discord">
|
||||
<img src="assets/images/discord.png">
|
||||
</div>
|
||||
<div @click="web"title="Visitez notre site web">
|
||||
<img src="assets/images/web.png">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="main">
|
||||
<div id="account">
|
||||
<div id="nick">{{ nick }}</div><!-- <img src=""> Head du joueur -->
|
||||
</div>
|
||||
<button @click="launchBtnClick" id="launch-btn" :disabled="launchBtnDisable">
|
||||
<div id="launch-text" :class="[{hidden: launchBtnHidden}]">{{ launchBtnText }}</div>
|
||||
<div id="loading-message" :class="[{hidden: loadingMessageHidden}]">{{ loadingMessageText }}</div>
|
||||
<div id="fullprogressbar" :class="[{hidden: fullprogressbarHidden}]"><div id="progressbar" :style="{ width: progressbarWidth + '%' }"></div></div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../../node_modules/izitoast/dist/js/iziToast.js" type="text/javascript"></script>
|
||||
<script src="assets/js/preload.js"></script>
|
||||
<script src="assets/js/index.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,44 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Altarik Launcher</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval';" />
|
||||
<link rel="stylesheet" href="../../node_modules/izitoast/dist/css/iziToast.min.css">
|
||||
<link href="assets/css/fonts.css" rel="stylesheet" />
|
||||
<link href="assets/css/login.css" rel="stylesheet" />
|
||||
<link href="assets/css/menubar.css" rel="stylesheet" />
|
||||
<!-- <link rel="shortcut icon" type="image/png" href="assets/images/icon.png"/> -->
|
||||
</head>
|
||||
<body>
|
||||
<div id="vue">
|
||||
<div id="menubar">
|
||||
<ul class="left">
|
||||
<img src="../../icon.ico">
|
||||
</ul>
|
||||
<ul class="right">
|
||||
<!-- Mettre ce code en ligne pour éviter que chrome ne met un espace automatiquement entre les éléments -->
|
||||
<li id="minimize-btn"><i class="material-icons">minimize</i></li><!--<li id="max-unmax-btn"><i class="material-icons">crop_square</i></li>--><li id="close-btn"><i class="material-icons">close</i></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="content">
|
||||
<div id="login">
|
||||
<h3>{{ login }}</h3>
|
||||
<hr>
|
||||
<form v-on:submit="formSubmit" id="login-form">
|
||||
<label for="nickname">{{ email }}:</label><br />
|
||||
<input type="text" name="nickname" id="nickname"><br />
|
||||
<label for="password">{{ password }}:</label><br />
|
||||
<input type="password" name="password" id="password"><br />
|
||||
<input type="submit" v-bind:value="send_credentials">
|
||||
</form>
|
||||
</div>
|
||||
<button id="microsoft-button" v-on:click="microsoftButton">{{ microsoft_button }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="../../node_modules/izitoast/dist/js/iziToast.js" type="text/javascript"></script>
|
||||
<script src="assets/js/preload.js"></script>
|
||||
<script src="assets/js/login.js"></script>
|
||||
</body>
|
||||
</html>
|
107
src/main/index.js
Normal file
@ -0,0 +1,107 @@
|
||||
import { app, shell, BrowserWindow, autoUpdater, dialog, Menu, ipcMain } from 'electron'
|
||||
import { dirname, join } from 'path'
|
||||
import { optimizer, is } from '@electron-toolkit/utils'
|
||||
import logger from 'electron-log'
|
||||
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 icon from '../../resources/icon.png?asset'
|
||||
|
||||
const updaterInstance = new Updater(app, autoUpdater, dialog, logger, showNotification)
|
||||
updaterInstance.configUpdater()
|
||||
|
||||
const minecraft = new Mc()
|
||||
minecraft.showNotification = showNotification
|
||||
|
||||
const __dirname = dirname(__filename)
|
||||
const iconPath = join(__dirname, 'icon.ico')
|
||||
|
||||
let mainWindow
|
||||
|
||||
function createWindow() {
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1000,
|
||||
height: 600,
|
||||
resizable: false,
|
||||
autoHideMenuBar: true,
|
||||
icon: iconPath,
|
||||
// ...(process.platform === 'linux' ? { icon } : {}),
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.mjs'),
|
||||
sandbox: false,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
},
|
||||
frame: false
|
||||
})
|
||||
if (!is.dev) {
|
||||
Menu.setApplicationMenu(null)
|
||||
}
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
mainWindow.on('close', () => {
|
||||
app.quit()
|
||||
})
|
||||
|
||||
if (is.dev && process.env.ELECTRON_RENDERER_URL) {
|
||||
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL)
|
||||
} else {
|
||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
}
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
function showNotification(title, body = '', clazz = 'info') {
|
||||
mainWindow.webContents.send('notification', { title, body, class: clazz })
|
||||
}
|
||||
|
||||
ipcMain.on('minimizeWindow', () => {
|
||||
minimizeWindow(mainWindow)
|
||||
})
|
||||
|
||||
ipcMain.on('closeWindow', () => {
|
||||
closeWindow(mainWindow)
|
||||
})
|
||||
|
||||
ipcMain.on('microsoft-login', (event) => {
|
||||
minecraft.microsoftLogin(event, mainWindow)
|
||||
})
|
||||
|
||||
ipcMain.on('invalidateData', (event) => {
|
||||
minecraft.invalidateData(event)
|
||||
})
|
||||
|
||||
ipcMain.on('launch', (event, args) => {
|
||||
minecraft.launch(event, args)
|
||||
})
|
||||
|
||||
function main() {
|
||||
if (electronStartup) {
|
||||
install(app)
|
||||
app.quit()
|
||||
return null
|
||||
}
|
||||
app.whenReady().then(() => {
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
mainWindow = createWindow()
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
@ -1,375 +1,375 @@
|
||||
import isDev from 'electron-is-dev'
|
||||
import mlc from 'minecraft-launcher-core'
|
||||
import fetch from 'node-fetch'
|
||||
import { hashFile } from 'hasha'
|
||||
import fs from 'fs'
|
||||
import { join } from 'path'
|
||||
import zip from 'extract-zip'
|
||||
import logger from 'electron-log'
|
||||
import { Auth, lst } from 'msmc'
|
||||
import decompress from 'decompress'
|
||||
import decompressTar from 'decompress-targz'
|
||||
|
||||
const { Authenticator, Client } = mlc
|
||||
|
||||
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'
|
||||
|
||||
setShowNotification (showNotification) {
|
||||
this.showNotification = showNotification
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Mojang removed this method of authentification
|
||||
* Used to login through Mojang account
|
||||
*/
|
||||
login (event, win, username, password) {
|
||||
this.auth = null
|
||||
if (isDev || password.trim() !== '') {
|
||||
this.auth = Authenticator.getAuth(username, password)
|
||||
this.auth.then(v => {
|
||||
win.loadFile('src/client/index.html')
|
||||
}).catch(() => {
|
||||
event.sender.send('loginError')
|
||||
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')
|
||||
})
|
||||
} else {
|
||||
this.showNotification('Erreur de connexion', 'Veuillez renseignez un mot de passe', 'warning')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to login through a Microsoft account
|
||||
*/
|
||||
microsoftLogin (event, win) {
|
||||
const authManager = new Auth('select_account')
|
||||
authManager.launch('electron').then(async xboxManager => {
|
||||
xboxManager.getMinecraft().then(async token => {
|
||||
if (!token.isDemo()) {
|
||||
this.auth = token.mclc()
|
||||
logger.info('[MS login] User has been connected successfully to them account')
|
||||
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')
|
||||
this.showNotification('Erreur de connexion à Mojang', err, 'error')
|
||||
logger.error('[MS login] ' + lst(err))
|
||||
})
|
||||
}).catch(err => {
|
||||
event.sender.send('loginError')
|
||||
if (err !== 'error.gui.closed') {
|
||||
this.showNotification('Une erreur de connexion à Xbox est survenue', err, 'error')
|
||||
logger.error('[MS login] ' + lst(err))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
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 => {
|
||||
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)
|
||||
})
|
||||
} else {
|
||||
logger.warn(`sha1sum ${sha1} don't correspond to ${chapter.modspack.sha1sum[j]} of mods ${path}`)
|
||||
this.downloadAndExtractMods(chapter.modspack.mods[j], path).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
}
|
||||
}).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)
|
||||
})
|
||||
}
|
||||
}
|
||||
resolve(chapter)
|
||||
return
|
||||
}
|
||||
}
|
||||
reject(new Error("didn't found the correct chapter" + chapterId))
|
||||
})
|
||||
}
|
||||
|
||||
downloadMods (link, path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
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 {
|
||||
reject(response.status)
|
||||
}
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async unzipMods (zipLocation, outLocation = this.minecraftpath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.info(`unzipping ${zipLocation} file to ${outLocation}`)
|
||||
zip(zipLocation, { dir: outLocation }).then(() => {
|
||||
resolve()
|
||||
}).catch(err => {
|
||||
logger.error('failed to unzip file')
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async extractTar (tarLocation, outLocation = this.microsoftpath) {
|
||||
return new Promise((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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
||||
import { is } from '@electron-toolkit/utils'
|
||||
import mlc from 'minecraft-launcher-core'
|
||||
import fetch from 'node-fetch'
|
||||
import { hashFile } from 'hasha'
|
||||
import fs from 'fs'
|
||||
import { join } from 'path'
|
||||
import zip from 'extract-zip'
|
||||
import logger from 'electron-log'
|
||||
import { Auth, lst } from 'msmc'
|
||||
import decompress from 'decompress'
|
||||
import decompressTar from 'decompress-targz'
|
||||
|
||||
const { Authenticator, Client } = mlc
|
||||
|
||||
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'
|
||||
|
||||
setShowNotification (showNotification) {
|
||||
this.showNotification = showNotification
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Mojang removed this method of authentification
|
||||
* Used to login through Mojang account
|
||||
*/
|
||||
login (event, win, username, password) {
|
||||
this.auth = null
|
||||
if (is.dev || password.trim() !== '') {
|
||||
this.auth = Authenticator.getAuth(username, password)
|
||||
this.auth.then(v => {
|
||||
win.loadFile('src/client/index.html')
|
||||
}).catch(() => {
|
||||
event.sender.send('loginError')
|
||||
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')
|
||||
})
|
||||
} else {
|
||||
this.showNotification('Erreur de connexion', 'Veuillez renseignez un mot de passe', 'warning')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to login through a Microsoft account
|
||||
*/
|
||||
microsoftLogin (event, win) {
|
||||
const authManager = new Auth('select_account')
|
||||
authManager.launch('electron').then(async xboxManager => {
|
||||
xboxManager.getMinecraft().then(async token => {
|
||||
if (!token.isDemo()) {
|
||||
this.auth = token.mclc()
|
||||
logger.info('[MS login] User has been connected successfully to them account')
|
||||
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')
|
||||
this.showNotification('Erreur de connexion à Mojang', err, 'error')
|
||||
logger.error('[MS login] ' + lst(err))
|
||||
})
|
||||
}).catch(err => {
|
||||
event.sender.send('loginError')
|
||||
if (err !== 'error.gui.closed') {
|
||||
this.showNotification('Une erreur de connexion à Xbox est survenue', err, 'error')
|
||||
logger.error('[MS login] ' + lst(err))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
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 => {
|
||||
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)
|
||||
})
|
||||
} else {
|
||||
logger.warn(`sha1sum ${sha1} don't correspond to ${chapter.modspack.sha1sum[j]} of mods ${path}`)
|
||||
this.downloadAndExtractMods(chapter.modspack.mods[j], path).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
}
|
||||
}).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)
|
||||
})
|
||||
}
|
||||
}
|
||||
resolve(chapter)
|
||||
return
|
||||
}
|
||||
}
|
||||
reject(new Error("didn't found the correct chapter" + chapterId))
|
||||
})
|
||||
}
|
||||
|
||||
downloadMods (link, path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
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 {
|
||||
reject(response.status)
|
||||
}
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async unzipMods (zipLocation, outLocation = this.minecraftpath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.info(`unzipping ${zipLocation} file to ${outLocation}`)
|
||||
zip(zipLocation, { dir: outLocation }).then(() => {
|
||||
resolve()
|
||||
}).catch(err => {
|
||||
logger.error('failed to unzip file')
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async extractTar (tarLocation, outLocation = this.microsoftpath) {
|
||||
return new Promise((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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import isDev from 'electron-is-dev'
|
||||
import { is } from '@electron-toolkit/utils'
|
||||
import fetch from 'node-fetch'
|
||||
import pkg from '../../package.json' assert {type: 'json'}
|
||||
|
||||
@ -19,7 +19,7 @@ export default class Updater {
|
||||
this.logger.info(`Node version: ${process.versions.node}`)
|
||||
this.logger.info(`platform: ${process.platform}`)
|
||||
this.logger.info(`arch: ${process.arch}`)
|
||||
if (isDev) {
|
||||
if (is.dev) {
|
||||
this.logger.info(`developpement version ${this.app.getVersion()}`)
|
||||
return
|
||||
}
|
||||
@ -33,7 +33,7 @@ export default class Updater {
|
||||
}
|
||||
|
||||
checkForUpdates (win, showNotification) {
|
||||
if (isDev) {
|
||||
if (is.dev) {
|
||||
win.loadFile('src/client/login.html')
|
||||
return
|
||||
}
|
20
src/preload/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { contextBridge } from 'electron'
|
||||
import { electronAPI } from '@electron-toolkit/preload'
|
||||
|
||||
// Custom APIs for renderer
|
||||
const api = {}
|
||||
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
// renderer only if context isolation is enabled, otherwise
|
||||
// just add to the DOM global.
|
||||
if (process.contextIsolated) {
|
||||
try {
|
||||
contextBridge.exposeInMainWorld('electron', electronAPI)
|
||||
contextBridge.exposeInMainWorld('api', api)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
} else {
|
||||
window.electron = electronAPI
|
||||
window.api = api
|
||||
}
|
17
src/renderer/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Electron</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
17
src/renderer/src/App.jsx
Normal file
@ -0,0 +1,17 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React, { useState } from 'react'
|
||||
import CheckingUpdate from './components/CheckingUpdate'
|
||||
import Login from './components/Login'
|
||||
import Main from './components/Main'
|
||||
import Menubar from './components/Menubar'
|
||||
|
||||
export default function App() {
|
||||
const [checkingUpdate, setCheckingUpdate] = useState(true)
|
||||
const [login, setLogin] = useState(false)
|
||||
return (
|
||||
<>
|
||||
<Menubar />
|
||||
{checkingUpdate ? <CheckingUpdate /> : login ? <Login /> : <Main />}
|
||||
</>
|
||||
)
|
||||
}
|
@ -32,3 +32,8 @@
|
||||
font-family: "French-Press";
|
||||
src: url("../fonts/Frenchpress.otf") format("OpenType");
|
||||
}
|
||||
|
||||
.material-icons{
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 178 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 375 KiB After Width: | Height: | Size: 375 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
3
src/renderer/src/assets/index.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
6
src/renderer/src/components/CheckingUpdate.jsx
Normal file
@ -0,0 +1,6 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React, { useState } from 'react'
|
||||
|
||||
export default function CheckingUpdate() {
|
||||
return <>Bonjour</>
|
||||
}
|
6
src/renderer/src/components/Fullscreen.jsx
Normal file
@ -0,0 +1,6 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React from "react"
|
||||
|
||||
export default function Fullscreen() {
|
||||
return <></>
|
||||
}
|
6
src/renderer/src/components/Login.jsx
Normal file
@ -0,0 +1,6 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React from 'react'
|
||||
|
||||
export default function Login() {
|
||||
return <></>
|
||||
}
|
6
src/renderer/src/components/Main.jsx
Normal file
@ -0,0 +1,6 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React from 'react'
|
||||
|
||||
export default function Main() {
|
||||
return <></>
|
||||
}
|
24
src/renderer/src/components/Menubar.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React from 'react'
|
||||
import icon from '../../../../icon.ico'
|
||||
|
||||
export default function Menubar() {
|
||||
return (
|
||||
<div
|
||||
className="fixed top-0 left-0 w-full h-7 bg-slate-900 text-white cursor-move select-none z-50 *:m-0 *:p-0"
|
||||
style={{ 'webkit-app-region': 'drag' }}
|
||||
>
|
||||
<ul className="float-left">
|
||||
<img src={icon} className="pl-4 h-7 w-auto" />
|
||||
</ul>
|
||||
<ul className="float-right *:inline-block *:h-7 *:hover:cursor-pointer *:hover:transition-all *:ease-in *:duration-300">
|
||||
<li className="hover:bg-slate-800">
|
||||
<i className="material-icons">minimize</i>
|
||||
</li>
|
||||
<li className="hover:bg-red-600">
|
||||
<i className="material-icons">close</i>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
12
src/renderer/src/main.jsx
Normal file
@ -0,0 +1,12 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import './assets/index.css'
|
||||
import './assets/css/fonts.css'
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
)
|
@ -1,112 +0,0 @@
|
||||
import { app, BrowserWindow, Menu, ipcMain, autoUpdater, dialog } from 'electron'
|
||||
import isDev from 'electron-is-dev'
|
||||
import logger from 'electron-log'
|
||||
import { join, dirname } from 'path'
|
||||
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()
|
||||
|
||||
const minecraft = new Mc()
|
||||
minecraft.showNotification = showNotification
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
const iconPath = join(__dirname, 'icon.ico')
|
||||
|
||||
let win = null
|
||||
|
||||
function createWindow () {
|
||||
win = new BrowserWindow({
|
||||
width: 1000,
|
||||
height: 600,
|
||||
resizable: false,
|
||||
icon: iconPath,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
},
|
||||
frame: false
|
||||
})
|
||||
if (!isDev) {
|
||||
Menu.setApplicationMenu(null)
|
||||
}
|
||||
|
||||
win.loadFile('src/client/checkingUpdate.html')
|
||||
win.on('close', () => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
|
||||
function showNotification (title, body = '', clazz = 'info') {
|
||||
win.webContents.send('notification', { title, body, class: clazz })
|
||||
}
|
||||
|
||||
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(app)
|
||||
app.quit()
|
||||
return
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('minimizeWindow', () => {
|
||||
minimizeWindow(win)
|
||||
})
|
||||
|
||||
ipcMain.on('closeWindow', () => {
|
||||
closeWindow(win)
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (win === null) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('login', (event, args) => {
|
||||
minecraft.login(event, win, args.user, args.pass)
|
||||
})
|
||||
|
||||
ipcMain.on('microsoft-login', (event) => {
|
||||
minecraft.microsoftLogin(event, win)
|
||||
})
|
||||
|
||||
ipcMain.on('invalidateData', event => {
|
||||
minecraft.invalidateData(event)
|
||||
})
|
||||
|
||||
ipcMain.on('launch', (event, args) => {
|
||||
minecraft.launch(event, args)
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
9
tailwind.config.js
Normal file
@ -0,0 +1,9 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: [],
|
||||
darkMode: 'class'
|
||||
}
|