Compare commits

..

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

22 changed files with 113 additions and 647 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 }}

Binary file not shown.

Binary file not shown.

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;
} }
@ -106,7 +92,7 @@ img.logo {
img.mascot { img.mascot {
width: 50%; width: 50%;
float: right; float: right;
margin-top: calc(300px - (50% + 32px)); margin-top: calc(300px - (50% + 12px));
} }
fieldset { fieldset {
@ -386,63 +372,7 @@ div.checkboxes > input[type="checkbox"] {
font-size: small; font-size: small;
} }
div.loader {
overflow: hidden;
position: absolute;
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;
}
/* 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% {
transform: translateX(-100%);
}
100% {
transform: translateX(200%);
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 846 KiB

View File

@ -7,7 +7,7 @@ const audioPourcentageLabel = document.querySelector("label[for='audioVolume']")
const audio = new Audio() const audio = new Audio()
function startAudio() { function startAudio() {
audio.src = "./assets/audio/main_menu.mp3" audio.src = "./assets/audio/Golden Hill (Radio Edit).mp3"
audio.loop = true audio.loop = true
audio.play() audio.play()
audio.onended = () => { audio.onended = () => {
@ -58,39 +58,25 @@ 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}%`
} }
function logout() { function logout() {
system.call("auth::reset") localStorage.removeItem("user")
document.location.href = './login.html' document.location.href = './login.html'
} }
@ -98,82 +84,30 @@ system.result("server::ping", pong => {
playersStatus.innerText = `${pong.players.online}/${pong.players.max}` playersStatus.innerText = `${pong.players.online}/${pong.players.max}`
}) })
system.result("player::profile", playerProfile => {
if (!localStorage.getItem("user")) {
localStorage.setItem("user", JSON.stringify(playerProfile))
}
})
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())}`)
if (span) {
span.innerText = Math.floor(value)
}
} }
function handleSettingsChanges(key, value) { function handleSettingsChanges(key, value) {
system.call("settings::set", { key, value }) system.call("settings::set", { key, value })
const span = document.querySelector(`span#${key == "ram" ? "currentRam" : key}`)
if (span) {
span.innerText = key == "ram" ? Math.floor(value.max / 1024) + " G" : 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,52 +118,16 @@ 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 => {
console.log(playerProfile)
})
system.result("oculus::getdefaultshaderset", 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")
system.call("server::ping") system.call("server::ping")
system.call("player::profile") system.call("player::profile")
system.call("settings::read") system.call("settings::read")
system.call("oculus::getdefaultshaderset")
hideLoadingBar()
startAudio() startAudio()
} }

View File

@ -18,4 +18,13 @@ system.result("auth::microsoft", () => {
system.result("auth::refresh", () => { system.result("auth::refresh", () => {
selectLoginType("select") selectLoginType("select")
}) })
window.onload = () => {
if (localStorage.getItem("user")) {
system.call("auth::refresh", {
user: JSON.parse(localStorage.getItem("user"))
})
selectLoginType("token")
}
}

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>
@ -50,10 +46,10 @@
Ram alloué Ram alloué
</label> </label>
<div class="ranges"> <div class="ranges">
<span id="currentRam"> <span>
0.5GB 0.5GB
</span> </span>
<input type="range" name="ram" min="0" max="2048" id="ram" onchange="handleSettingsChanges('ram', { max: this.value })"> <input type="range" name="ram" min="0" max="2048" id="ram" onchange="handleSettingsChanges(this.name, this.value)">
<span id="maxRam"> <span id="maxRam">
MAX MAX
</span> </span>
@ -63,7 +59,7 @@
Distance de rendu Distance de rendu
</label> </label>
<div class="ranges"> <div class="ranges">
<span id="currentRenderDistance"> <span>
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>
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)">
@ -104,12 +100,12 @@
Luminsoité max Luminsoité max
</label> </label>
</div> </div>
<div> <!-- <div>
<input type="checkbox" name="sildurs_shader" id="sildurs_shader" onclick="system.call('oculus::defaultshaderset', { boolean: this.checked })"> <input type="checkbox" name="sildurs_shader" id="sildurs_shader">
<label for="sildurs_shader"> <label for="sildurs_shader">
Sildur's Shader Sildur's Shader
</label> </label>
</div> </div> -->
</div> </div>
</article> </article>
</details> </details>
@ -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>
@ -215,23 +205,16 @@
<section class="center"> <section class="center">
<img class="logo" src="./assets/img/logo.png" alt=""> <img class="logo" src="./assets/img/logo.png" alt="">
<br> <br>
<button class="play load" name="play" onclick="system.call('game::launch')"> <button class="play" name="play" onclick="system.call('game::launch')">
Jouer Jouer
</button> </button>
</section> </section>
<section class="right"> <section class="right">
<img class="mascot" src="./assets/img/sulli.png" alt=""> <img class="mascot" src="./assets/img/alcaz_mascote.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")
@ -43,7 +43,7 @@ async function buildApp() {
"!dist", "!dist",
"!.env" "!.env"
], ],
buildNumber: 2, buildNumber: 1,
buildVersion: `${package.version}`, buildVersion: `${package.version}`,
releaseInfo: { releaseInfo: {
releaseDate: getFormattedProductionDate(), releaseDate: getFormattedProductionDate(),

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": {

219
main.js
View File

@ -1,5 +1,4 @@
const { BrowserWindow, app, net, dialog, ipcMain, nativeImage, session, shell } = require("electron") const { BrowserWindow, app, net, dialog, ipcMain, nativeImage, session, shell } = require("electron")
const properties = require("js-java-properties")
const msmc = require("msmc") const msmc = require("msmc")
const os = require("node:os") const os = require("node:os")
const fs = require("node:fs") const fs = require("node:fs")
@ -14,16 +13,12 @@ const fileManager = require("./modules/fileManager")
const launcher = new Client() 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 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()
async function createLauncherWindow() { async function createLauncherWindow() {
gameOptions.initOptions(path.join(app.getPath("appData"), ".catboat", "options.txt")) gameOptions.initOptions(path.join(app.getPath("appData"), ".catboat", "options.txt"))
@ -109,63 +104,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 +129,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 +156,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: [
@ -267,19 +181,8 @@ ipcMain.on("call", async (event, data) => {
case "auth::mojang": case "auth::mojang":
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 }) await fetch(`${config.api.base}${config.api.endpoints.telemetry}/${hwid.getHWID()}/${(await auth).uuid}`)
if (await checkCoutdown((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.")
} }
@ -287,23 +190,11 @@ ipcMain.on("call", async (event, data) => {
case "auth::microsoft": case "auth::microsoft":
const authManager = new msmc.Auth("select_account") const authManager = new msmc.Auth("select_account")
try { try {
const xboxManager = await authManager.launch("electron") const xboxManager = await authManager.launch("raw")
const token = await xboxManager.getMinecraft() const token = await xboxManager.getMinecraft()
auth = token.mclc() auth = token.mclc()
console.log(auth.meta) await fetch(`${config.api.base}${config.api.endpoints.telemetry}/${hwid.getHWID()}/${auth.uuid}`)
launcherSettings.set("auth", { token: auth.meta, type: "msa", clientToken: auth.client_token }) await launcherWindow.loadFile(path.join(__dirname, "app", "logged.html"))
if (await checkCoutdown(auth.uuid)) {
launcherWindow.loadFile(path.join(__dirname, "app", "logged.html"))
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") {
@ -311,11 +202,32 @@ ipcMain.on("call", async (event, data) => {
} }
} }
break break
case "auth::reset": case "auth::refresh":
launcherSettings.set("auth", { token: "", type: "msa", clientToken: "" }) const user = data.args.user
break if (user.meta?.type == "msa") {
case "settings::set": try {
launcherSettings.set(data.args.key, data.args.value) 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"))
await fetch(`${config.api.base}${config.api.endpoints.telemetry}/${hwid.getHWID()}/${auth.uuid}`)
} catch (error) {
dialog.showErrorBox("Erreur lors de la connexion via token", error)
console.error(error)
launcherWindow.webContents.send("Response<auth::refresh>")
}
} else {
try {
auth = Authenticator.refreshAuth(user.access_token, user.client_token)
await launcherWindow.loadFile(path.join(__dirname, "app", "logged.html"))
await fetch(`${config.api.base}${config.api.endpoints.telemetry}/${hwid.getHWID()}/${(await auth).uuid}`)
} catch (error) {
dialog.showErrorBox("Erreur lors de la connexion via token", error)
console.error(error)
launcherWindow.webContents.send("Response<auth::refresh>")
}
}
break break
case "shell::openExternal": case "shell::openExternal":
shell.openExternal(data.args.url) shell.openExternal(data.args.url)
@ -362,39 +274,17 @@ ipcMain.on("call", async (event, data) => {
case "app::devtools": case "app::devtools":
launcherWindow.webContents.openDevTools() launcherWindow.webContents.openDevTools()
break break
case "oculus::defaultshaderset":
const filePath = path.join(app.getPath("appData"), ".catboat", "config", "oculus.properties")
if (fs.existsSync(filePath)) {
writeOculusConfigFile(filePath)
} else {
fs.copyFileSync(path.join(__dirname, "oculus.properties"), filePath)
writeOculusConfigFile(filePath)
}
break
case "oculus::getdefaultshaderset":
const $filePath = path.join(app.getPath("appData"), ".catboat", "config", "oculus.properties")
if (fs.existsSync($filePath)) {
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
} }
}) })
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 })
if (launcherWindow instanceof BrowserWindow) { launcherWindow.setProgressBar(10, {
launcherWindow.webContents.send("Response<progress::update>", { type: "landing" }) mode: "indeterminate"
} })
for (const remoteFile of remoteFiles) { for (const remoteFile of remoteFiles) {
try { try {
const localFile = localFiles.find(file => file === remoteFile) const localFile = localFiles.find(file => file === remoteFile)
@ -423,34 +313,32 @@ async function launchGame(restartGame) {
const url = `${config.api.base}${config.api.endpoints.downloadFile}/${new String(item).replace(/\\/g, "/")}` const url = `${config.api.base}${config.api.endpoints.downloadFile}/${new String(item).replace(/\\/g, "/")}`
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) { launcherWindow.setProgressBar(((downloadQueue.indexOf(item) + 1) / downloadQueue.length) * 100, {
launcherWindow.webContents.send("Response<izitoast::info>", { title: "Lancement du jeu", message: "Ce processus peut être plus ou moins long selon votre configuration." }) mode: "normal"
launcherWindow.webContents.send("Response<progress::update>", { type: "continuous" }) })
}
} catch (error) { } catch (error) {
if (launcherWindow instanceof BrowserWindow) { launcherWindow.setProgressBar(((downloadQueue.indexOf(item) + 1) / downloadQueue.length) * 100, {
launcherWindow.webContents.send("Response<progress::hide>") mode: "error"
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
} }
} }
launcherWindow.setProgressBar(0, {
mode: "none"
})
dialog.showMessageBox(launcherWindow, {
title: "Téléchargement des fichiers",
message: "Téléchargement fini."
})
gamePlayable = true gamePlayable = true
launcher.on("close", () => { launcher.on("close", () => {
launcherWindow.webContents.send("Response<game::launch>", { disablePlayButton: true }) launcherSettings.webContents.send("Response<game::launch>", { disablePlayButton: true })
gamePlayable = true gamePlayable = true
}) })
launcher.on("debug", (log) => { launcher.on("debug", (log) => {
console.log(log) console.log(log)
}) })
launcher.on("data", (log) => {
if (sendToRenderer) {
launcherWindow.webContents.send("Response<game::launched>")
sendToRenderer = false
}
console.log(log)
})
launcher.on("data", (log) => { launcher.on("data", (log) => {
console.log(log) console.log(log)
}) })
@ -463,7 +351,6 @@ 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")),
memory: { memory: {
min: 512, min: 512,
max: launcherSettings.get("ram").max max: launcherSettings.get("ram").max
@ -481,7 +368,5 @@ socket.on("force-game-update", async () => {
}) })
launchProcess.kill() launchProcess.kill()
await launchGame(true) await launchGame(true)
} else {
await launchGame(false)
} }
}) })

View File

@ -1,95 +0,0 @@
const { execSync } = require("child_process")
const fs = require("fs")
const path = require("path")
const os = require("os")
const download = require("download")
const AdmZip = require("adm-zip")
const JAVA_DOWNLOAD_URL = {
win32: "https://download.oracle.com/java/17/archive/jdk-17.0.12_windows-x64_bin.zip",
darwin: "https://download.oracle.com/java/17/archive/jdk-17.0.12_macos-x64_bin.tar.gz",
linux: "https://download.oracle.com/java/17/archive/jdk-17.0.12_linux-aarch64_bin.tar.gz",
}
function checkJavaVersion(requiredVersion = "17") {
try {
const javaVersionOutput = execSync("java -version", {
encoding: "utf8",
stdio: "pipe"
})
const versionMatch = javaVersionOutput.match(/"(\d+\.\d+)(\.\d+)?_\d+"/)
if (versionMatch) {
const installedVersion = versionMatch[1]
console.log(`Java is installed. Version: ${installedVersion}`)
if (parseFloat(installedVersion) >= parseFloat(requiredVersion)) {
console.log("Required Java version is already installed.")
return true
} else {
console.log(`Installed Java version (${installedVersion}) is less than required version (${requiredVersion}).`)
return false
}
} else {
console.log("Java is installed but version could not be determined.")
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) {
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 downloadAndInstallJava(customExtractPath || path.join(__dirname, "..", "..", "java"))
}
} else {
console.log("No further action is required.")
}
}
async function getPath(customExtractPath) {
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

@ -5,27 +5,23 @@ let launcherDataPath = path.join(__dirname, ".catboat")
const baseSettings = { const baseSettings = {
ram: { ram: {
max: 2048 max: 1024
},
auth: {
token: "",
type: "msa",
clientToken: ""
} }
} }
function initSettings($launcherDataPath) { function initSettings($launcherDataPath) {
launcherDataPath = $launcherDataPath launcherDataPath = $launcherDataPath
if (!fs.existsSync(launcherDataPath)) { const gameDir = path.parse($launcherDataPath).dir
if (!fs.existsSync(gameDir)) {
try { try {
fs.mkdirSync(launcherDataPath) fs.mkdirSync(gameDir)
} catch (error) { } catch (error) {
throw error throw error
} }
} }
if (!fs.existsSync(path.join(launcherDataPath, "launcher_settings.json"))) { if (!fs.existsSync($launcherDataPath)) {
try { try {
fs.writeFileSync(path.join(launcherDataPath, "launcher_settings.json"), JSON.stringify(baseSettings, null, 4)) fs.writeFileSync($launcherDataPath, JSON.stringify(baseSettings, null, 4))
} catch (error) { } catch (error) {
throw error throw error
} }
@ -41,8 +37,7 @@ function writeSettings(settings) {
fs.writeFileSync(path.join(launcherDataPath, "launcher_settings.json"), JSON.stringify(settings, null, 4)) fs.writeFileSync(path.join(launcherDataPath, "launcher_settings.json"), JSON.stringify(settings, null, 4))
return return
} catch (error) { } catch (error) {
clean() throw error
return writeSettings(settings)
} }
} }
@ -63,8 +58,8 @@ function get(key) {
return value return value
} catch (error) { } catch (error) {
clean() console.error("Erreur lors de l'accès aux paramètres :", error.message)
return get(key) throw error
} }
} }
@ -86,10 +81,10 @@ function set(key, newValue) {
obj[keys[keys.length - 1]] = newValue obj[keys[keys.length - 1]] = newValue
fs.writeFileSync(filePath, JSON.stringify(settings, null, 4)) fs.writeFileSync(filePath, JSON.stringify(settings, null, 2))
} catch (error) { } catch (error) {
clean() console.error("Erreur lors de la mise à jour des paramètres :", error.message)
return set(ke, value) throw error
} }
} }
@ -106,8 +101,7 @@ function readSettings() {
try { try {
return JSON.parse(fs.readFileSync(path.join(launcherDataPath, "launcher_settings.json"))) return JSON.parse(fs.readFileSync(path.join(launcherDataPath, "launcher_settings.json")))
} catch (error) { } catch (error) {
clean() throw error
return readSettings()
} }
} }

View File

@ -1,27 +0,0 @@
const os = require("os")
function arch() {
switch (os.arch()) {
case "x32":
case "ia32":
case "x86":
case "mips":
case "ppc":
case "s390":
return "x86"
case "x64":
case "arm64":
return "arm64"
case "mipsel":
case "ppc64":
case "riscv64":
case "s390x":
case "loong64":
return "x64"
default:
return "unknown"
}
}
module.exports.arch = arch

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

23
package-lock.json generated
View File

@ -1,21 +1,19 @@
{ {
"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",
"download": "^8.0.0", "download": "^8.0.0",
"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",
@ -4220,15 +4218,6 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/js-java-properties": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/js-java-properties/-/js-java-properties-1.0.3.tgz",
"integrity": "sha512-KFvPPxguCIv4T/Z45tk+eDkD2UiPglaarN8qyrY3RsJDnhz2LMlHp52WFYRYjub5F4SlMKv2u0Z6F/yR1eZ5Jg==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@ -6597,12 +6586,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,11 @@
"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",
"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"
} }
} }