const { BrowserWindow, app, net, dialog, ipcMain, nativeImage, session, shell } = require("electron") const msmc = require("msmc") const os = require("os") const hwid = require("./modules/hwid") const path = require("node:path") const serverPing = require("./modules/serverPing") const gameOptions = require("./modules/gameOptions") const launcherSettings = require("./modules/launcherSettings") const config = require("./config.json") const { Authenticator } = require("minecraft-launcher-core") let launcherWindow, auth async function createLauncherWindow() { gameOptions.initOptions(path.join(app.getPath("appData"), ".catboat", "options.txt")) launcherSettings.initSettings(path.join(app.getPath("appData"), ".catboat")) if (net.isOnline()) { let win_width = 1550 let win_height = parseInt(win_width / (16/9)) const isLauncherNotBanned = await checkIfIAmBanned() try { launcherWindow = new BrowserWindow({ frame: false, width: win_width, height: win_height, minWidth: win_width, minHeight: win_height, titleBarStyle: "hidden", autoHideMenuBar: true, roundedCorners: false, resizable: false, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, "modules", "preload.js"), webviewTag: true } }) if (os.platform() == "darwin") { app.dock.setIcon(nativeImage.createFromPath(path.join(__dirname, "app", "assets", "img", "icon.png"))) } launcherWindow.setIcon(path.join(__dirname, "app", "assets", "img", "icon.png")) if (isLauncherNotBanned.success) { launcherWindow.loadFile(path.join(__dirname, "app", "login.html")) session.defaultSession.webRequest.onBeforeRequest({ urls: [ "https://embed.twitch.tv/*channel=*" ] }, (details, callback) => { const url = details.url const urlParams = new URLSearchParams(url.replace("https://embed.twitch.tv/", "")) if (urlParams.get("parent")) { callback({}) return } urlParams.set("parent", "localhost") urlParams.set("referrer", "https://localhost/") const redirectUrl = `https://embed.twitch.tv/?${urlParams.toString()}` callback({ cancel: false, redirectURL: redirectUrl }) }) session.defaultSession.webRequest.onHeadersReceived({ urls: [ "https://www.twitch.tv/*", "https://player.twitch.tv/*", "https://embed.twitch.tv/*" ] }, (details, callback) => { const responseHeaders = details.responseHeaders delete responseHeaders["Content-Security-Policy"] callback({ cancel: false, responseHeaders }) }) launcherWindow.webContents.openDevTools() } else { launcherWindow.loadFile(path.join(__dirname, "app", "banned.html")) launcherWindow.webContents.executeJavaScript(` setBannedBy("${isLauncherNotBanned.banned_by}") setBannedAt("${isLauncherNotBanned.banned_at}") setBannedBecause("${isLauncherNotBanned.reason}") `) } } catch (error) { dialog.showErrorBox("Erreur", error) } } else { dialog.showErrorBox("Connexion internet", "Le launcher requiert une connexion internet.") } } async function checkIfIAmBanned() { if (net.isOnline()) { try { const reponse = await fetch(`${config.api.base}${config.api.endpoints.checkBanStatus}`, { method: "post", headers: { accept: "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ hwid: hwid.getHWID() }) }) const json = await reponse.json() return json } catch (error) { dialog.showErrorBox("Connexion à l'API", "Impossible de contacter l'API, fermeture du launcher.") console.error(error) app.exit() } } else { dialog.showErrorBox("Connexion internet", "Le launcher requiert une connexion internet.") } } app.whenReady().then(() => { createLauncherWindow() app.on("activate", async () => { if (BrowserWindow.getAllWindows().length === 0) await createLauncherWindow() }) }) app.on("window-all-closed", () => { app.quit() }) ipcMain.on("call", async (event, data) => { switch (data.method) { case "hardware::ramInformation": launcherWindow.webContents.send("Response", { totalRam: Math.round(os.totalmem() / 1024 / 1024 * 100) / 100, avaibleRam: Math.round(os.freemem() / 1024 / 1024 * 100) / 100, }) break case "server::ping": const status = await serverPing.fetchServerStatus() launcherWindow.webContents.send("Response", status) break case "window::close": launcherWindow.close() app.quit() break case "skin::set": const file = await dialog.showOpenDialog(launcherWindow, { filters: [ { name: "Images", extensions: ["png", "jpg", "jpeg"] } ], properties: ["openFile", "dontAddToRecent", "showHiddenFiles"], title: "Sélectionner l'image de votre skin", securityScopedBookmarks: true }) if (!file.canceled) { const confirmDialog = await dialog.showMessageBox(launcherWindow, { type: "question", message: "Êtes-vous sûr de vouloir changer votre skin ?", buttons: [ "Oui", "Annuler" ], title: "Confirmer le changement de skin" }) confirmDialog.response == 0 ? skinPath = file.filePaths[0] : skinPath = null } break case "auth::mojang": if (data.args.trim() != "") { auth = Authenticator.getAuth(data.args.username, data.args.password) await launcherWindow.loadFile(path.join(__dirname, "app", "logged.html")) } else { dialog.showErrorBox("Erreur", "Le mot de passe n'est pas défini. Les comptes crackés ne sont pas supporté par le launcher.") } break case "auth::microsoft": const authManager = new msmc.Auth("select_account") try { const xboxManager = await authManager.launch("raw") const token = await xboxManager.getMinecraft() auth = token.mclc() console.log(auth) await launcherWindow.loadFile(path.join(__dirname, "app", "logged.html")) } catch (error) { console.log(error) if (error == "error.gui.closed") { launcherWindow.webContents.send("Response") } } break case "auth::refresh": const user = data.args.user if (user.meta?.type == "msa") { try { const authManager = new msmc.Auth("none") const xboxManager = await authManager.refresh(user.meta.refresh) const minecraft = await xboxManager.getMinecraft() auth = minecraft.mclc() await launcherWindow.loadFile(path.join(__dirname, "app", "logged.html")) } catch (error) { dialog.showErrorBox("Erreur lors de la connexion via token", error) console.log(error) launcherWindow.webContents.send("Response") } } else { try { auth = Authenticator.refreshAuth(user.access_token, user.client_token) await launcherWindow.loadFile(path.join(__dirname, "app", "logged.html")) } catch (error) { dialog.showErrorBox("Erreur lors de la connexion via token", error) console.log(error) launcherWindow.webContents.send("Response") } } break case "shell::openExternal": shell.openExternal(data.args.url) break case "audio::mute": launcherWindow.webContents.setAudioMuted(true) break case "audio::unmute": launcherWindow.webContents.setAudioMuted(false) break case "player::profile": await launcherWindow.webContents.send("Response", auth) break case "game::parseOptions": const $gameOptions = gameOptions.parseOptions() const allowedKeys = ["renderDistance", "renderClouds", "graphicsMode", "gamma", "graphicsMode", "guiScale"] const filteredOptions = Object.fromEntries( Object.entries($gameOptions).filter(([key]) => allowedKeys.includes(key)) ) launcherWindow.webContents.send("Response", filteredOptions) break case "game::optionSet": const options = gameOptions.parseOptions() switch (data.args.key) { case "graphicsMode": options.graphicsMode = data.args.key == true ? 0 : 1 break case "gamma": options.gamma = data.args.key == false ? 0.50 : 1.0 break default: options[data.args.key] = data.args.value break } gameOptions.saveOptions(gameOptions.stringfyOptions(options)) break case "settings::read": const $launcherSettings = launcherSettings.readSettings() launcherWindow.webContents.send("Response", $launcherSettings) break } })