Compare commits

..

No commits in common. "main" and "0.0.1-alpha2" have entirely different histories.

15 changed files with 218 additions and 508 deletions

View File

@ -1,32 +0,0 @@
name: Release app
on:
workflow_dispatch: null
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- windows-latest
- macos-latest
- macos-latest
- ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@4.2.2
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@4.4.0
with:
node-version: 20
- name: Config git user
run: |
git config --global user.email "gilleslazure04@gmail.com"
git config --global user.name "Gilles Lazure <azures04>"
- name: Install app dependencies
run: |
npm i
- name: Build app
run: |
node build
env:
GITHUB_TOKEN: ${{ github.token }}

View File

@ -11,14 +11,6 @@ body {
background-color: #262626; background-color: #262626;
} }
main {
app-region: drag;
}
main > * {
app-region: no-drag;
}
main > article > section > img { main > article > section > img {
width: 150px; width: 150px;
} }
@ -41,14 +33,6 @@ main > article > section.informations {
font-family: "Roboto", sans-serif; font-family: "Roboto", sans-serif;
} }
main > article > section.logo > h2 {
color: #ffffff;
padding: 13px 13px 13px 13px;
font-weight: bolder;
text-align: center;
font-family: "Roboto", sans-serif;
}
main > article > section.informations > h2 { main > article > section.informations > h2 {
margin-bottom: 10px; margin-bottom: 10px;
} }

View File

@ -4,8 +4,6 @@
* { * {
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
outline: none;
user-select: none;
box-sizing: border-box; box-sizing: border-box;
font-family: "Roboto", sans-serif; font-family: "Roboto", sans-serif;
} }
@ -18,11 +16,6 @@ body {
background-attachment: fixed; background-attachment: fixed;
} }
img {
app-region: drag;
pointer-events: none;
}
main { main {
position: absolute; position: absolute;
top: 0px; top: 0px;
@ -31,19 +24,12 @@ main {
bottom: 0px; bottom: 0px;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden;
app-region: drag;
}
main > * {
app-region: no-drag;
} }
main > nav { main > nav {
margin-left: 1rem; margin-left: 1rem;
width: 20%; width: 20%;
height: 100%; height: 100%;
overflow-y: auto;
background-color: #3e3e3ee6; background-color: #3e3e3ee6;
} }
@ -386,63 +372,25 @@ div.checkboxes > input[type="checkbox"] {
font-size: small; font-size: small;
} }
div.loader { button.load {
overflow: hidden; content: "";
position: absolute; transition: background-color 0.3s;
left: 0px;
right: 0px;
bottom: 0px;
width: 100%;
height: 10px;
} }
div.loader > div.full {
height: 10px;
width: 100%;
background-color: #3e3e3ee6;
}
div.loader > div.full > div.progress,
div.loader > div.full > div.loading {
height: 10px;
width: 50%;
transition: 1s width;
background-color: #39aa6d;
}
div.loader > div.full > div.loading {
animation: animateLoadingEffect 1s linear infinite;
}
[hidden] { [hidden] {
display: none; display: none;
visibility: hidden; visibility: hidden;
} }
@keyframes backgroundAnimation {
/* Chrome-specific scrollbar styling */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1); /* Slightly visible track for better contrast */
}
::-webkit-scrollbar-thumb {
background: #2E8B57;
border-radius: 4px; /* Rounded edges for a modern look */
}
::-webkit-scrollbar-thumb:hover {
background: #39aa6d;
}
@keyframes animateLoadingEffect {
0% { 0% {
transform: translateX(-100%); background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
} }
100% { 100% {
transform: translateX(200%); background-position: 0% 50%;
} }
} }

View File

@ -58,32 +58,18 @@ function toggleAudio(element) {
function toggleMusic(element) { function toggleMusic(element) {
if (element.getAttribute("state") == 0) { if (element.getAttribute("state") == 0) {
audio.pause() system.call("audio::mute")
element.setAttribute("state", 1) element.setAttribute("state", 1)
element.children[0].classList.replace("fa-pause", "fa-play") element.children[0].classList.replace("fa-pause", "fa-play")
element.children[1].innerText = "Reprendre" element.children[1].innerText = "Reprendre"
} else { } else {
audio.play() system.call("audio::unmute")
element.setAttribute("state", 0) element.setAttribute("state", 0)
element.children[0].classList.replace("fa-play", "fa-pause") element.children[0].classList.replace("fa-play", "fa-pause")
element.children[1].innerText = "Pause" element.children[1].innerText = "Pause"
} }
} }
function toggleMusicVolume(element) {
if (element.getAttribute("state") == 0) {
system.call("audio::mute")
element.setAttribute("state", 1)
element.children[0].classList.replace("fa-volume", "fa-volume-slash")
element.children[1].innerText = "Activer le son"
} else {
system.call("audio::unmute")
element.setAttribute("state", 0)
element.children[0].classList.replace("fa-volume-slash", "fa-volume")
element.children[1].innerText = "Couper le son"
}
}
function updateVolume(value) { function updateVolume(value) {
audio.volume = value / 100 audio.volume = value / 100
audioPourcentageLabel.innerText = `${value}%` audioPourcentageLabel.innerText = `${value}%`
@ -100,7 +86,7 @@ system.result("server::ping", pong => {
function handleOptionsChanges(key, value) { function handleOptionsChanges(key, value) {
system.call("game::optionSet", { key, value }) system.call("game::optionSet", { key, value })
const span = document.querySelector(`span#current${key.replace(/./, c => c.toUpperCase())}`) const span = document.querySelector(`span#${key}`)
if (span) { if (span) {
span.innerText = Math.floor(value) span.innerText = Math.floor(value)
} }
@ -114,66 +100,16 @@ function handleSettingsChanges(key, value) {
} }
} }
function setLoadingType(type) {
const loader = document.querySelector(".loader")
switch (type) {
case "continuous":
loader.children[0].children[0].classList.remove("progress")
loader.children[0].children[0].classList.add("loading")
break
case "progress":
loader.children[0].children[0].classList.remove("loading")
loader.children[0].children[0].classList.add("progress")
break
default:
break
}
}
function setLoadingProgress(pourcentage) {
const loader = document.querySelector(".loader")
const progress = loader.querySelector(".progress")
if (progress) {
progress.setAttribute("style", `width: ${pourcentage}%`)
}
}
function toggleLoadingBar() {
const loader = document.querySelector(".loader")
if (loader.hasAttribute("hidden")) {
loader.removeAttribute("hidden")
} else {
loader.setAttribute("hidden", "")
}
}
function showLoadingBar() {
const loader = document.querySelector(".loader")
if (loader.hasAttribute("hidden")) {
loader.removeAttribute("hidden")
}
}
function hideLoadingBar() {
const loader = document.querySelector(".loader")
if (!loader.hasAttribute("hidden")) {
loader.setAttribute("hidden", "")
}
}
system.result("game::parseOptions", options => { system.result("game::parseOptions", options => {
gamma.checked = options.gamma == 1 ? true : false gamma.checked = options.gamma == 1 ? true : false
renderClouds.checked = options.renderrenderClouds == false ? false : true renderClouds.checked = options.renderrenderClouds == false ? false : true
guiScale.value = options.guiScale guiScale.value = options.guiScale
currentGuiScale.innerText = options.guiScale
graphicsMode.checked = options.graphicsMode == 0 ? true : false graphicsMode.checked = options.graphicsMode == 0 ? true : false
renderDistance.value = options.renderDistance renderDistance.value = options.renderDistance
currentRenderDistance.innerText = options.renderDistance
}) })
system.result("settings::read", settings => { system.result("settings::read", settings => {
ram.value = settings.ram.max ram.value = settings.ram.max
currentRam.innerText = `${Math.floor(settings.ram.max / 1024)} G`
}) })
system.result("hardware::ramInformation", $ram => { system.result("hardware::ramInformation", $ram => {
@ -184,17 +120,11 @@ system.result("hardware::ramInformation", $ram => {
system.result("game::launch", info => { system.result("game::launch", info => {
if (info.disablePlayButton) { if (info.disablePlayButton) {
playButton.removeAttribute("hidden") playButton.removeAttribute("hidden")
hideLoadingBar()
} else { } else {
playButton.setAttribute("hidden", "") playButton.setAttribute("hidden", "")
} }
}) })
system.result("game::launched", info => {
muteAudio()
hideLoadingBar()
})
system.result("player::profile", playerProfile => { system.result("player::profile", playerProfile => {
console.log(playerProfile) console.log(playerProfile)
}) })
@ -203,26 +133,6 @@ system.result("oculus::getdefaultshaderset", boolean => {
sildurs_shader.checked = boolean sildurs_shader.checked = boolean
}) })
system.result("progress::update", info => {
showLoadingBar()
setLoadingType(info.type)
if (info.type == "progress" && typeof info.pourcentage == "number") {
setLoadingProgress(info.pourcentage)
}
})
system.result("progress::hide", () => {
hideLoadingBar()
})
system.result("izitoast::error", info => {
iziToast.error(info)
})
system.result("progress::info", info => {
iziToast.error(info)
})
window.onload = () => { window.onload = () => {
system.call("hardware::ramInformation") system.call("hardware::ramInformation")
system.call("game::parseOptions") system.call("game::parseOptions")
@ -230,6 +140,5 @@ window.onload = () => {
system.call("player::profile") system.call("player::profile")
system.call("settings::read") system.call("settings::read")
system.call("oculus::getdefaultshaderset") system.call("oculus::getdefaultshaderset")
hideLoadingBar()
startAudio() startAudio()
} }

View File

@ -1,28 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./assets/css/banned.css">
<title>Launcher banni</title>
</head>
<body>
<main>
<button class="close" onclick="system.call('window::close')">
<i class="fas fa-times"></i>
</button>
<article>
<section class="logo">
<img src="./assets/img/icon.png" alt="">
<h2>
Veuillez patienter
</h2>
</section>
</article>
</main>
<script src="./assets/js/banned.js"></script>
</body>
</html>

View File

@ -3,7 +3,6 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/izitoast/1.4.0/css/iziToast.css">
<link rel="stylesheet" href="./assets/css/index.css"> <link rel="stylesheet" href="./assets/css/index.css">
<title>NyanLauncher</title> <title>NyanLauncher</title>
</head> </head>
@ -12,9 +11,6 @@
<button class="close" onclick="system.call('window::close')"> <button class="close" onclick="system.call('window::close')">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
<button class="close" onclick="system.call('window::reduce')">
<i class="fas fa-minus"></i>
</button>
<nav hidden> <nav hidden>
<button class="close" onclick="hideNavBar()"> <button class="close" onclick="hideNavBar()">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
@ -63,7 +59,7 @@
Distance de rendu Distance de rendu
</label> </label>
<div class="ranges"> <div class="ranges">
<span id="currentRenderDistance"> <span id="renderDistance">
4 4
</span> </span>
<input type="range" name="renderDistance" min="4" max="32" id="renderDistance" onchange="handleOptionsChanges(this.name, this.value)"> <input type="range" name="renderDistance" min="4" max="32" id="renderDistance" onchange="handleOptionsChanges(this.name, this.value)">
@ -76,7 +72,7 @@
Taille de l'interface Taille de l'interface
</label> </label>
<div class="ranges"> <div class="ranges">
<span id="currentGuiScale"> <span id="guiScale">
1 1
</span> </span>
<input type="range" name="guiScale" min="1" max="4" id="guiScale" onchange="handleOptionsChanges(this.name, this.value)"> <input type="range" name="guiScale" min="1" max="4" id="guiScale" onchange="handleOptionsChanges(this.name, this.value)">
@ -129,12 +125,6 @@
100% 100%
</label> </label>
</div> </div>
<button class="classic" onclick="toggleMusicVolume(this)" state="0">
<i class="fas fa-volume"></i>
<span>
Couper le son
</span>
</button>
<button class="classic" onclick="toggleMusic(this)" state="0"> <button class="classic" onclick="toggleMusic(this)" state="0">
<i class="fas fa-pause"></i> <i class="fas fa-pause"></i>
<span> <span>
@ -223,15 +213,8 @@
<img class="mascot" src="./assets/img/sulli.png" alt=""> <img class="mascot" src="./assets/img/sulli.png" alt="">
</section> </section>
</footer> </footer>
<div class="loader">
<div class="full">
<div class="loading">
</div>
</div>
</div>
</main> </main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/izitoast/1.4.0/js/iziToast.min.js"></script>
<script src="./assets/js/index.js"></script> <script src="./assets/js/index.js"></script>
</body> </body>

View File

@ -12,9 +12,6 @@
<button class="close" onclick="system.call('window::close')"> <button class="close" onclick="system.call('window::close')">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
<button class="close" onclick="system.call('window::reduce')">
<i class="fas fa-minus"></i>
</button>
<article class="loginchoice"> <article class="loginchoice">
<div frame="select"> <div frame="select">
<h2> <h2>

View File

@ -1,4 +1,4 @@
require("v8-compile-cache") const fs = require("node:fs")
const path = require("node:path") const path = require("node:path")
const builder = require("electron-builder") const builder = require("electron-builder")

View File

@ -6,8 +6,7 @@
"checkBanStatus": "/api/v1/ban/iam", "checkBanStatus": "/api/v1/ban/iam",
"telemetry": "/api/v1/telemetry", "telemetry": "/api/v1/telemetry",
"gameFiles": "/api/v1/file/game", "gameFiles": "/api/v1/file/game",
"downloadFile": "/api/v1/file/game/download", "downloadFile": "/api/v1/file/game/download"
"countdown": "/api/v1/countdown"
}, },
"websockets": { "websockets": {
"base": { "base": {

153
main.js
View File

@ -15,13 +15,12 @@ const launcher = new Client()
const { io } = require("socket.io-client") const { io } = require("socket.io-client")
const download = require("download") const download = require("download")
const rpc = require("./modules/rpc") const rpc = require("./modules/rpc")
const java = require("./modules/java")
const socket = io({ const socket = io({
host: config.api.websockets.base.host, host: config.api.websockets.base.host,
port: config.api.websockets.base.port port: config.api.websockets.base.port
}) })
let launcherWindow, defaultWindow, auth, gamePlayable = false, launchProcess let launcherWindow, auth, gamePlayable = false, launchProcess
rpc.startRichPresence() rpc.startRichPresence()
@ -109,63 +108,6 @@ async function createLauncherWindow() {
} }
} }
async function createDefaultWindow() {
gameOptions.initOptions(path.join(app.getPath("appData"), ".catboat", "options.txt"))
launcherSettings.initSettings(path.join(app.getPath("appData"), ".catboat"))
if (net.isOnline()) {
const isLauncherNotBanned = await checkIfIAmBanned()
try {
defaultWindow = new BrowserWindow({
frame: false,
width: 300,
height: 400,
minWidth: 300,
minHeight: 400,
titleBarStyle: "hidden",
autoHideMenuBar: true,
roundedCorners: false,
resizable: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, "modules", "preload.js"),
webviewTag: true,
devTools: false
}
})
if (os.platform() == "darwin") {
app.dock.setIcon(nativeImage.createFromPath(path.join(__dirname, "app", "assets", "img", "icon.png")))
}
defaultWindow.setIcon(path.join(__dirname, "app", "assets", "img", "icon.png"))
if (isLauncherNotBanned.success) {
defaultWindow.loadFile(path.join(__dirname, "app", "loading.html"))
defaultWindow.webContents.openDevTools()
try {
await java.main(path.join(app.getPath("appData"), ".catboat", "jre"))
await launchGame(false)
defaultWindow.hide()
createLauncherWindow()
} catch (error) {
console.log(error)
defaultWindow.hide()
createLauncherWindow()
}
} else {
launcherWindow.loadFile(path.join(__dirname, "app", "banned.html"))
defaultWindow.webContents.executeJavaScript(`
setBannedBy("${isLauncherNotBanned.banned_by}")
setBannedAt("${isLauncherNotBanned.banned_at}")
setBannedBecause("${isLauncherNotBanned.reason}")
`)
}
} catch (error) {
dialog.showErrorBox("Erreur", error.toString())
}
} else {
dialog.showErrorBox("Connexion internet", "Le launcher requiert une connexion internet.")
}
}
async function checkIfIAmBanned() { async function checkIfIAmBanned() {
if (net.isOnline()) { if (net.isOnline()) {
try { try {
@ -191,31 +133,10 @@ async function checkIfIAmBanned() {
} }
} }
async function checkCoutdown(uuid) {
try {
const response = await fetch(`${config.api.base}${config.api.endpoints.countdown}/${uuid}`)
const json = await response.json()
console.log(json)
return json.success
} catch (error) {
return false
}
}
function writeOculusConfigFile(filePath) {
const oculusProperties = properties.parse(fs.readFileSync(filePath).toString())
if (data.args.boolean) {
properties.setProperty(oculusProperties, "shaderPack", "Sildurs_Vibrant_Shaders_v1.50_Medium.zip")
} else {
properties.setProperty(oculusProperties, "shaderPack", "")
}
fs.writeFileSync(filePath, properties.stringify(oculusProperties))
}
app.whenReady().then(() => { app.whenReady().then(() => {
createDefaultWindow() createLauncherWindow()
app.on("activate", async () => { app.on("activate", async () => {
if (BrowserWindow.getAllWindows().length === 0) await createDefaultWindow() if (BrowserWindow.getAllWindows().length === 0) await createLauncherWindow()
}) })
}) })
@ -239,9 +160,6 @@ ipcMain.on("call", async (event, data) => {
launcherWindow.close() launcherWindow.close()
app.quit() app.quit()
break break
case "window::reduce":
launcherWindow.minimize()
break
case "skin::set": case "skin::set":
const file = await dialog.showOpenDialog(launcherWindow, { const file = await dialog.showOpenDialog(launcherWindow, {
filters: [ filters: [
@ -268,18 +186,8 @@ ipcMain.on("call", async (event, data) => {
if (data.args.trim() != "") { if (data.args.trim() != "") {
auth = Authenticator.getAuth(data.args.username, data.args.password) auth = Authenticator.getAuth(data.args.username, data.args.password)
launcherSettings.set("auth", { token: (await auth).access_token, type: "mojang", clientToken: (await auth).client_token }) launcherSettings.set("auth", { token: (await auth).access_token, type: "mojang", clientToken: (await auth).client_token })
if (await checkCoutdown((await auth).uuid)) { await fetch(`${config.api.base}${config.api.endpoints.telemetry}/${hwid.getHWID()}/${(await auth).uuid}`)
await launcherWindow.loadFile(path.join(__dirname, "app", "logged.html")) await launcherWindow.loadFile(path.join(__dirname, "app", "logged.html"))
await fetch(`${config.api.base}${config.api.endpoints.telemetry}/${hwid.getHWID()}/${(await auth).uuid}`)
} else {
await launcherWindow.loadURL("https://nyancraft.catboat.fr")
await launcherWindow.webContents.insertCSS("a.download-button { display: none; } #return-button { app-region: no-drag } ")
await launcherWindow.webContents.executeJavaScript(`
const returnButton = document.querySelector(\"#return-button\")
returnButton.innerText = "Fermer le launcher"
returnButton.onclick = () => system.call("window::close")
`)
}
} else { } else {
dialog.showErrorBox("Erreur", "Le mot de passe n'est pas défini. Les comptes crackés ne sont pas supporté par le launcher.") dialog.showErrorBox("Erreur", "Le mot de passe n'est pas défini. Les comptes crackés ne sont pas supporté par le launcher.")
} }
@ -292,18 +200,8 @@ ipcMain.on("call", async (event, data) => {
auth = token.mclc() auth = token.mclc()
console.log(auth.meta) console.log(auth.meta)
launcherSettings.set("auth", { token: auth.meta, type: "msa", clientToken: auth.client_token }) launcherSettings.set("auth", { token: auth.meta, type: "msa", clientToken: auth.client_token })
if (await checkCoutdown(auth.uuid)) { launcherWindow.loadFile(path.join(__dirname, "app", "logged.html"))
launcherWindow.loadFile(path.join(__dirname, "app", "logged.html")) fetch(`${config.api.base}${config.api.endpoints.telemetry}/${hwid.getHWID()}/${auth.uuid}`)
fetch(`${config.api.base}${config.api.endpoints.telemetry}/${hwid.getHWID()}/${auth.uuid}`)
} else {
await launcherWindow.loadURL("https://nyancraft.catboat.fr")
await launcherWindow.webContents.insertCSS("a.download-button { display: none; } #return-button { app-region: no-drag } ")
await launcherWindow.webContents.executeJavaScript(`
const returnButton = document.querySelector(\"#return-button\")
returnButton.innerText = "Fermer le launcher"
returnButton.onclick = () => system.call("window::close")
`)
}
} catch (error) { } catch (error) {
console.error(error) console.error(error)
if (error == "error.gui.closed") { if (error == "error.gui.closed") {
@ -363,32 +261,23 @@ ipcMain.on("call", async (event, data) => {
launcherWindow.webContents.openDevTools() launcherWindow.webContents.openDevTools()
break break
case "oculus::defaultshaderset": case "oculus::defaultshaderset":
const filePath = path.join(app.getPath("appData"), ".catboat", "config", "oculus.properties") const oculusProperties = properties.parse(fs.readFileSync(path.join(app.getPath("appData"), ".catboat", "config", "oculus.properties")).toString())
if (fs.existsSync(filePath)) { if (data.args.boolean) {
writeOculusConfigFile(filePath) properties.setProperty(oculusProperties, "shaderPack", "Sildur%27s+Vibrant+Shaders+v1.50+Medium.zip")
} else { } else {
fs.copyFileSync(path.join(__dirname, "oculus.properties"), filePath) properties.setProperty(oculusProperties, "shaderPack", "")
writeOculusConfigFile(filePath)
} }
fs.writeFileSync(path.join(app.getPath("appData"), ".catboat", "config", "oculus.properties"), properties.stringify(oculusProperties))
break break
case "oculus::getdefaultshaderset": case "oculus::getdefaultshaderset":
const $filePath = path.join(app.getPath("appData"), ".catboat", "config", "oculus.properties") const $oculusProperties = properties.parse(fs.readFileSync(path.join(app.getPath("appData"), ".catboat", "config", "oculus.properties")).toString())
if (fs.existsSync($filePath)) { launcherWindow.webContents.send("Response<oculus::getdefaultshaderset>", properties.getProperty($oculusProperties, "shaderPack") == "Sildur%27s+Vibrant+Shaders+v1.50+Medium.zip" ? true : false)
const $oculusProperties = properties.parse(fs.readFileSync($filePath).toString())
launcherWindow.webContents.send("Response<oculus::getdefaultshaderset>", properties.getProperty($oculusProperties, "shaderPack") == "Sildurs_Vibrant_Shaders_v1.50_Medium.zip" ? true : false) } else {
const $oculusProperties = properties.parse(fs.readFileSync($filePath).toString())
launcherWindow.webContents.send("Response<oculus::getdefaultshaderset>", properties.getProperty($oculusProperties, "shaderPack") == "Sildurs_Vibrant_Shaders_v1.50_Medium.zip" ? true : false)
}
break break
} }
}) })
async function launchGame(restartGame) { async function launchGame(restartGame) {
let sendToRenderer = false launcherWindow.webContents.send("Response<game::launch>", { disablePlayButton: false })
if (launcherWindow instanceof BrowserWindow) {
sendToRenderer = true
launcherWindow.webContents.send("Response<game::launch>", { disablePlayButton: false })
}
const downloadQueue = [] const downloadQueue = []
const remoteFiles = await fileManager.getRemoteFiles() const remoteFiles = await fileManager.getRemoteFiles()
const localFiles = fs.readdirSync(path.join(app.getPath("appData"), ".catboat"), { recursive: true }) const localFiles = fs.readdirSync(path.join(app.getPath("appData"), ".catboat"), { recursive: true })
@ -424,13 +313,11 @@ async function launchGame(restartGame) {
try { try {
await download(url, path.join(app.getPath("appData"), ".catboat", path.dirname(item))) await download(url, path.join(app.getPath("appData"), ".catboat", path.dirname(item)))
if (launcherWindow instanceof BrowserWindow) { if (launcherWindow instanceof BrowserWindow) {
launcherWindow.webContents.send("Response<izitoast::info>", { title: "Lancement du jeu", message: "Ce processus peut être plus ou moins long selon votre configuration." }) launcherWindow.webContents.send("Response<progress::update>", { type: "landing" })
launcherWindow.webContents.send("Response<progress::update>", { type: "continuous" })
} }
} catch (error) { } catch (error) {
if (launcherWindow instanceof BrowserWindow) { if (launcherWindow instanceof BrowserWindow) {
launcherWindow.webContents.send("Response<progress::hide>") launcherWindow.webContents.send("Response<progress::error>", { type: "landing" })
launcherWindow.webContents.send("Response<izitoast::error>", { title: "Erreur", message: new String(error) })
} }
dialog.showErrorBox("Erreur lors du téléchargement des fichiers", error.toString()) dialog.showErrorBox("Erreur lors du téléchargement des fichiers", error.toString())
continue continue
@ -445,10 +332,6 @@ async function launchGame(restartGame) {
console.log(log) console.log(log)
}) })
launcher.on("data", (log) => { launcher.on("data", (log) => {
if (sendToRenderer) {
launcherWindow.webContents.send("Response<game::launched>")
sendToRenderer = false
}
console.log(log) console.log(log)
}) })
launcher.on("data", (log) => { launcher.on("data", (log) => {
@ -463,7 +346,7 @@ async function launchGame(restartGame) {
type: "release" type: "release"
}, },
forge: path.join(app.getPath("appData"), ".catboat", "forge-1.16.5.jar"), forge: path.join(app.getPath("appData"), ".catboat", "forge-1.16.5.jar"),
javaPath: await java.getPath(path.join(app.getPath("appData"), ".catboat", "jre")), javaPath: "C:\\Program Files\\Java\\jdk-17\\bin\\java.exe",
memory: { memory: {
min: 512, min: 512,
max: launcherSettings.get("ram").max max: launcherSettings.get("ram").max

View File

@ -1,95 +1,182 @@
const { execSync } = require("child_process")
const fs = require("fs")
const path = require("path")
const os = require("os") const os = require("os")
const download = require("download") const path = require("path")
const osmeta = require("./osmeta")
const { exec } = require("child_process")
const files = require("./files")
const config = require("../config.json")
const settings = require("./settings")
const { dialog, BrowserWindow, shell } = require("electron")
const AdmZip = require("adm-zip") const AdmZip = require("adm-zip")
const JAVA_DOWNLOAD_URL = { function isJavaInstalled() {
win32: "https://download.oracle.com/java/17/archive/jdk-17.0.12_windows-x64_bin.zip", return new Promise((resolve, reject) => {
darwin: "https://download.oracle.com/java/17/archive/jdk-17.0.12_macos-x64_bin.tar.gz", try {
linux: "https://download.oracle.com/java/17/archive/jdk-17.0.12_linux-aarch64_bin.tar.gz", exec(`"${settings.get("javaPath") == "" ? "java" : settings.get("javaPath")}" -version`, (error, stdout, stderr) => {
if (error) {
reject({
java: false
})
}
const output = `${stdout}\n${stderr}`
const lines = output.split("\n")
const version = lines.filter(line => line.includes("version \""))[0]
try {
let matchRegEx
if (version.includes("_")) {
matchRegEx = /(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)\_(?<update>\d+)/
} else {
matchRegEx = /(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)/
}
const versionMatch = version.match(matchRegEx)
const { major, minor, patch, update } = versionMatch.groups
resolve({
java: true,
version: {
major: parseInt(major),
minor: parseInt(minor),
patch: parseInt(patch),
update: update ? parseInt(update) : null
}
})
} catch (error) {
reject({
java: false,
error
})
}
})
} catch (error) {
reject({
java: false,
error
})
}
})
} }
function checkJavaVersion(requiredVersion = "17") { async function showJavaErrorBox() {
try { const javaErrorBox = await dialog.showMessageBox(BrowserWindow.getFocusedWindow(), {
const javaVersionOutput = execSync("java -version", { title: "Mauvaise version de Java",
encoding: "utf8", message: "Mauvaise version de Java",
stdio: "pipe" detail: "La version de java installé sur votre ordinateur n'est pas compatible avec la version de minecraft utilisé",
}) icon: path.join(__dirname, "code/app/assets/img/java.png"),
const versionMatch = javaVersionOutput.match(/"(\d+\.\d+)(\.\d+)?_\d+"/) buttons: [
if (versionMatch) { "Fermer",
const installedVersion = versionMatch[1] "Installer automatiquement java",
console.log(`Java is installed. Version: ${installedVersion}`) "Installer manuellement java",
if (parseFloat(installedVersion) >= parseFloat(requiredVersion)) { "Utilise un chemin d'accès personnalié",
console.log("Required Java version is already installed.") ],
return true cancelId: 0,
defaultId: 1
})
return javaErrorBox
}
function installJava(dest) {
return new Promise(async (resolve, reject) => {
const response = await fetch(`${config.apiServicesURL}/api/java`)
const java = await response.json()
const arch = osmeta.arch() == "unknow" ? "x64" : osmeta.arch()
if (java.downloads[os.platform()]) {
if (java.downloads[os.platform()][arch]) {
files.downloadFile(config.apiServicesURL + java.downloads[os.platform()][arch].url, path.join(dest, "java.zip"), (error, file) => {
if (error) throw error
try {
const zip = new AdmZip(path.join(dest, "java.zip"))
zip.extractAllTo(path.join(dest, "java"), true)
files.removeFile(path.join(dest, "java.zip"))
if (os.platform() == "darwin") {
exec(`chmod +x "${path.join(dest, "java", "jre-1.8.0_411.jre", "Contents", "Home", "bin", "java")}"`, (error) => {
if (error) reject(error)
resolve(path.join(dest, "java", "jre-1.8.0_411.jre", "Contents", "Home", "bin", "java"))
})
} else {
exec(`chmod +x "${path.join(dest, "java", "bin", "java")}"`, (error) => {
if (error) reject(error)
resolve(path.join(dest, "java", "bin", "java"))
})
}
} catch (err) {
reject(err)
}
})
} else { } else {
console.log(`Installed Java version (${installedVersion}) is less than required version (${requiredVersion}).`) reject(new Error("Java are not supported for your OS arch"))
return false
} }
} else { } else {
console.log("Java is installed but version could not be determined.") reject(new Error("Java are not supported for your OS"))
return false
} }
} catch (error) {
console.log("Java is not installed.")
return false
}
}
async function downloadAndInstallJava(extractPath = null) {
const platform = os.platform()
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "java-installer-"))
const downloadUrl = JAVA_DOWNLOAD_URL[platform]
if (!downloadUrl) {
console.error("Unsupported platform for Java installation.")
return
}
const zipPath = path.join(tempDir, "java-package.zip")
await download(downloadUrl, tempDir, {
filename: "java-package.zip"
}) })
const extractionDir = extractPath || tempDir
const zip = new AdmZip(zipPath)
zip.extractAllTo(extractionDir, true)
fs.unlinkSync(zipPath)
} }
async function main(customExtractPath) { async function main(requiredJavaVersionMinor, dest) {
const requiredVersion = "17" return new Promise(async (resolve, reject) => {
const isJavaInstalled = checkJavaVersion(requiredVersion) try {
const java = await isJavaInstalled()
if (!isJavaInstalled) { if (java.version.minor < parseInt(requiredJavaVersionMinor)) {
const javaErrorBox = await showJavaErrorBox()
if (!fs.existsSync(path.join(customExtractPath, "jdk-17.0.12", "bin", os.platform() == "win32" ? "java.exe" : "java"))) { switch (javaErrorBox.response) {
await downloadAndInstallJava(customExtractPath || path.join(__dirname, "..", "..", "java")) case 0:
resolve()
break
case 1:
installJava(dest).then(javaPath => {
resolve(javaPath)
})
break
case 2:
shell.openExternal(`https://www.java.com/fr/download/manual.jsp`)
resolve()
break
case 3:
const javaBinPathSelect = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), {
filters: [
{
name: "java",
}
]
})
if (javaBinPathSelect.canceled) {
showJavaErrorBox()
} else {
resolve(javaBinPathSelect.filePaths[0])
}
break
}
} else {
resolve()
}
} catch (error) {
const javaErrorBox = await showJavaErrorBox()
switch (javaErrorBox.response) {
case 0:
resolve()
break
case 1:
installJava(dest).then(javaPath => {
resolve(javaPath)
})
break
case 2:
shell.openExternal(`https://www.java.com/fr/download/manual.jsp`)
resolve()
break
case 3:
const javaBinPathSelect = await dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), {
filters: [
{
name: "java",
}
]
})
if (javaBinPathSelect.canceled) {
showJavaErrorBox()
} else {
resolve(javaBinPathSelect.filePaths[0])
}
break
}
} }
} else { })
console.log("No further action is required.")
}
} }
async function getPath(customExtractPath) { module.exports.java = main
const requiredVersion = "17"
const isJavaInstalled = checkJavaVersion(requiredVersion)
if (!isJavaInstalled) {
if (!fs.existsSync(path.join(customExtractPath, "jdk-17.0.12", "bin", os.platform() == "win32" ? "java.exe" : "java"))) {
await main(customExtractPath)
} else {
return path.join(customExtractPath, "jdk-17.0.12", "bin", os.platform() == "win32" ? "java.exe" : "java")
}
} else {
console.log("No further action is required.")
}
}
module.exports = {
main,
getPath
}

View File

@ -3,22 +3,19 @@ const RPC = require("discord-rpc")
class DiscordRPC { class DiscordRPC {
constructor() { constructor() {
readonly: this.activity = { readonly: this.activity = {
details: "Officiel | Solva x Alcaz", details: "Actif dans le launcher",
timestamps : { start: Date.now() },
assets: { assets: {
large_image: "rpc_catboat_large", large_image: "logo",
large_text: "CatBoat Launcher", large_text: "CatBoat Minecraft Launcher"
small_image : "alflamme_comm_legoshi",
small_text: "by TheAlfiTeam",
}, },
buttons: [ buttons: [
{ {
"label": "Discord", label: "Discord",
"url": "https://discord.gg/catboat" url: "https://discord.com/invite/catboat"
}, },
{ {
"label": "Download Launcher", label: "CatBoat",
"url": "https://catboat.thealfigame.fr" url: "https://catboat.fr/"
} }
], ],
instance: true instance: true
@ -33,12 +30,12 @@ class DiscordRPC {
this.client.request("SET_ACTIVITY", { pid: process.pid, activity: this.activity }) this.client.request("SET_ACTIVITY", { pid: process.pid, activity: this.activity })
console.log("The Discord Rich Presence has been set successfully.") console.log("The Discord Rich Presence has been set successfully.")
}) })
this.client.login({ clientId: "1259291027148115988" }).catch(e => { this.client.login({ clientId: "1365563093157154868" }).catch(e => {
console.log("Silent client detected: the activity status has been disabled.")
console.log(e) console.log(e)
console.log("Silent client detected: the activity status has been disabled.")
this.isEnabled = false this.isEnabled = false
}).then(r => this.isEnabled = true) }).then(r => this.isEnabled = true)
.catch(err => console.log(err)) .catch(err => console.log)
} }
} }

View File

@ -1,8 +0,0 @@
#This file stores configuration options for Oculus, such as the currently active shaderpack
#Sun May 11 07:39:36 CEST 2025
colorSpace=SRGB
disableUpdateMessage=false
enableDebugOptions=false
maxShadowRenderDistance=32
shaderPack=Sildurs_Vibrant_Shaders_v1.50_Medium.zip
enableShaders=true

13
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "catboat-launcher", "name": "catboat-launcher",
"version": "0.0.9-alpha", "version": "0.0.1-alpha",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "catboat-launcher", "name": "catboat-launcher",
"version": "0.0.9-alpha", "version": "0.0.1-alpha",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
@ -14,8 +14,7 @@
"js-java-properties": "^1.0.3", "js-java-properties": "^1.0.3",
"minecraft-launcher-core": "^3.18.2", "minecraft-launcher-core": "^3.18.2",
"msmc": "^5.0.5", "msmc": "^5.0.5",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1"
"v8-compile-cache": "^2.4.0"
}, },
"devDependencies": { "devDependencies": {
"electron": "^35.2.1", "electron": "^35.2.1",
@ -6597,12 +6596,6 @@
"uuid": "dist/bin/uuid" "uuid": "dist/bin/uuid"
} }
}, },
"node_modules/v8-compile-cache": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz",
"integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==",
"license": "MIT"
},
"node_modules/verror": { "node_modules/verror": {
"version": "1.10.1", "version": "1.10.1",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "catboat-launcher", "name": "catboat-launcher",
"version": "0.1.0-alpha", "version": "0.0.1-alpha",
"description": "a simple minecraft launcher for catboat", "description": "a simple minecraft launcher for catboat",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
@ -22,14 +22,12 @@
"electron-builder": "^26.0.12", "electron-builder": "^26.0.12",
"electronmon": "^2.0.3" "electronmon": "^2.0.3"
}, },
"homepage": "https://nyancraft.catboat.fr",
"dependencies": { "dependencies": {
"discord-rpc": "^4.0.1", "discord-rpc": "^4.0.1",
"download": "^8.0.0", "download": "^8.0.0",
"js-java-properties": "^1.0.3", "js-java-properties": "^1.0.3",
"minecraft-launcher-core": "^3.18.2", "minecraft-launcher-core": "^3.18.2",
"msmc": "^5.0.5", "msmc": "^5.0.5",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1"
"v8-compile-cache": "^2.4.0"
} }
} }