This commit is contained in:
Aubrey 2024-05-24 18:08:08 +01:00
parent 0e7d983fd5
commit 0a3caacf80
34 changed files with 3809 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
Launcher/app.asar

5
Launcher/.forgeignore Normal file
View file

@ -0,0 +1,5 @@
precheck.txt
build/
build/*
build.js
temp.js

8
Launcher/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# big ass folders
.minecraft
.minecraft/
.minecraft/*
out/
node_modules
launcher.log
launcher.log

32
Launcher/.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,32 @@
{
"version": "0.2.0",
"compounds": [
{
"name": "Main + renderer",
"configurations": ["Main", "Renderer"],
"stopAll": true
}
],
"configurations": [
{
"name": "Renderer",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}"
},
{
"name": "Main",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": [".", "--remote-debugging-port=9222"],
"outputCapture": "std",
"console": "integratedTerminal"
}
]
}

3
Launcher/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"cloudcode.duetAI.inlineSuggestions.enableAuto": false
}

4
Launcher/README.md Normal file
View file

@ -0,0 +1,4 @@
# ToriLauncher
source code for tori launcher
built with electron, bundled with electron-forge

21
Launcher/build.js Normal file
View file

@ -0,0 +1,21 @@
const { app, BrowserWindow } = require('electron')
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 400,
height: 200
})
//use bytenode to convert js files to jsc
const bytenode = require("bytenode");
let compiledFilename = bytenode.compileFile({
filename: './temp.js',
output: './main.jsc'
});
//convert other Node.js files as required
}
app.whenReady().then(() => {
createWindow()
})

BIN
Launcher/build/install.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

12
Launcher/changelog.txt Normal file
View file

@ -0,0 +1,12 @@
[0.1.0]
Welcome new Tori users! This update addresses some smaller bugs and quality of life fixes that you as a community suggested, as well as some sponsor-related things.
If you find any issues or have any suggestions, please feel free to let us know in the Discord!
- Fixed launcher randomly logging you out on restart
- Fixed some mod manager issues
- Mod manager no longer allows you to edit mods while the game is running
- Added third party credits button to the Settings menu
- Fixed memory slider not saving it's state on launcher restart
- Made memory slider automatically set max RAM based off your computer's hardware
- Added Wepwawet and FFATL sponsors to the sidebar
- Cleanup some stuff internally

45
Launcher/english.json Normal file
View file

@ -0,0 +1,45 @@
{
"version": "4.0.0",
"name": "English",
"error": "An unknown error has occurred",
"error.auth": "An unknown authentication error has occurred",
"error.auth.microsoft": "Failed to login to Microsoft account",
"error.auth.xboxLive": "Failed to login to Xbox Live",
"error.auth.xsts": "Unknown error occurred when attempting to obtain an Xbox Live Security Token",
"error.auth.xsts.userNotFound": "The given Microsoft account doesn't have an Xbox account",
"error.auth.xsts.bannedCountry": "The given Microsoft account is from a country where Xbox Live is not available",
"error.auth.xsts.child": "The account is a child (under 18) and cannot proceed unless the account is added to a Family account by an adult",
"error.auth.xsts.child.SK": "South Korean Law: Go to the Xbox page and grant parental rights to continue logging in.",
"error.auth.minecraft": "Unknown error occurred when attempting to login to Minecraft",
"error.auth.minecraft.login": "Failed to authenticate with Mojang with given Xbox account",
"error.auth.minecraft.profile": "Failed to fetch minecraft profile",
"error.auth.minecraft.entitlements": "Failed to fetch player entitlements",
"error.gui": "An unknown gui framework error has occurred",
"error.gui.closed": "Gui closed by user",
"error.gui.raw.noBrowser": "No chromium browser was set, cannot continue!",
"error.state.invalid": "[Internal]: Method not implemented.",
"error.state.invalid.http": "[Internal]: Http server support not present in current environment .",
"error.state.invalid.gui": "[Internal]: Invalid gui framework.",
"error.state.invalid.redirect": "[Internal]: The token must have a redirect starting with 'http://localhost/' for this function to work!",
"error.state.invalid.electron": "[Internal]: It seems you're attempting to load electron on the frontend. A critical function is missing!",
"load": "Generic load event",
"load.auth": "Generic authentication load event",
"load.auth.microsoft": "Logging into Microsoft account",
"load.auth.xboxLive": "Logging into Xbox Live",
"load.auth.xboxLive.1": "Logging into Xbox Live",
"load.auth.xboxLive.2": "Authenticating with Xbox live",
"load.auth.xsts": "Generating Xbox Live Security Token",
"load.auth.minecraft": "Generic Minecraft login flow event",
"load.auth.minecraft.login": "Authenticating with Mojang's servers",
"load.auth.minecraft.profile": "Fetching player profile",
"load.auth.minecraft.gamepass": "[experimental!] Checking if a user has gamepass",
"gui": "Gui component",
"gui.title": "Sign in to your account",
"gui.market": "en-US"
}

44
Launcher/forge.config.js Normal file
View file

@ -0,0 +1,44 @@
module.exports = {
packagerConfig: {
name: "Tori Launcher",
icon: "resource/app-icon.ico",
asar: true,
"executableName": "torilauncher"
},
rebuildConfig: {},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {
name: "ToriLauncher",
loadingGif: 'build/install.gif',
setupIcon: 'resource/favicon.ico'
}
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin', 'linux'],
},
{
name: '@electron-forge/maker-deb',
config: {
options: {
bin: "ToriLauncher",
maintainer: "WifiRouter",
homepage: "https://toriclient.com/",
icon: 'resource/icon.png'
}
},
},
{
name: '@electron-forge/maker-rpm',
config: {},
},
],
plugins: [
{
name: '@electron-forge/plugin-auto-unpack-natives',
config: {},
},
],
};

284
Launcher/index.html Normal file
View file

@ -0,0 +1,284 @@
<!-- ToriLauncher by WifiRouter
Please do not redistribute my work or any decompiled source code without my permission.
I am a solo developer and I will be devastated 💔
thanks :3
can i get tori client early?
psalm 37:420 skibidi skibidi skibidi skibidi skibidi skibidi skibidi skibidi
ghost was here o/ -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Tori Launcher [BETA]</title>
<link rel="stylesheet" type="text/css" href="./style.css">
<script src="https://kit.fontawesome.com/b2ec349ebe.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" crossorigin="anonymous"></script>
<link rel="icon" type="image/x-icon" href="resource/app-icon.ico">
</head>
<body id="body"></body>
<div class="darken">
</div>
<div class="sponsor-container">
<div class="sponsor">
<i class="fa-solid fa-xmark close-button" onclick="closeSponsor();"></i>
<div class="wepwawet">
<div class="half1">
<p style="margin-top: 25px;">If you want a 24/7 uptime alternative to sharing your Tori world with your friends through World Host, Wepwawet is here to help!</p>
<p>Wepwawet hosting offers secure, fast and reliable 24/7 hosting for an affordable, low price. New users of Wepwawet hosting can get 20% off of their first billing cycle using code tori at checkout.<br><a onclick='window.link.open("https\:/\/wepwawet.net/p/toriclient/");' style="cursor: pointer;">https://wepwawet.net/p/toriclient/</a></p>
<h2 style="margin-bottom: 25px; line-height: 36px;">Use code <code>tori</code> for 20% off your first cycle!</h2>
</div>
<div class="half2">
<img src="resource/wepwawet.png" height="150px">
</div>
</div>
<div class="ffatl">
<div class="half1">
<p style="margin-top: 25px;">If you are interested in Minecraft PvP, FFA Tier List may be for you! FFATL allows you to be tested in the diamond sword PvP gamemode to determine your tier in PvP. This is done by fighting one of FFATL's Tier Testers. The first person to get to 10 kills wins the test, and the amount of kills you get and the way you get them determine your Tier.</p>
<p style="margin-bottom: 25px;">If you would like to be Tier Tested, head over to FFATL's Discord server at <a onclick='window.link.open("https\:/\/discord.gg/pvptiers");' style="cursor: pointer;">https://discord.gg/pvptiers</a> and open a test ticket to be put in to a queue for a Tier tester to be assigned to you. You must have a Discord account to do this.</p>
</div>
<div class="half2">
<img src="resource/ffatl.png" height="150px">
</div>
</div>
</div>
</div>
<div class="startup-overlay" id="startup-overlay">
<div class="loader-screen" id="loader-screen" style="display: flex; justify-content: center; align-items: center;">
<span class="loader-medium soverlay-spinner" id="soverlay-spinner" style="margin-right: 8px;"></span>
<p id="launch-progress" class="launch-progress">can i get tori client early?</p>
</div>
<button class="log-out-button-error" id="log-out-button-error" onclick="logOut();">Log Out</button>
<div class="whitelist-wallpaper" id="whitelist-wallpaper" style="margin-left: -2%; opacity: 0;"></div>
<div class="whitelist-container" id="whitelist-container" style="margin-right: -2%; opacity: 0;">
<div class="text">
<h1>Login to Tori Client</h1>
<p>Login with your Minecraft account to gain access to all the launcher features, including checking whitelist status, manage cosmetics, mods, and more!</p>
</div>
<button id="sign-in-with-microsoft" class="sign-in-with-microsoft enabled" onclick="altLogin();"><img src="resource/microsoft-logo.png" id="alt-login-icon"> Sign in with Microsoft<span class="alt-login-spinner" id="alt-login-spinner"></span></button>
<div class="not-whitelisted" id="not-whitelisted">
<i class="fa-solid fa-circle-xmark"></i>
<p>You're not whitelisted!</p>
</div>
<div class="button-list" id="button-list">
<button id="not-whitelisted-logout" onclick="$('#not-whitelisted-logout').text(''); $('#not-whitelisted-logout').append(`<span class='alt-login-spinner' id='alt-login-spinner' style='display: block !important;'></button>`); logOut();">Log Out</span></button>
<button id="not-whitelisted-retry" onclick="retryWhitelist();">Retry</button>
</div>
</div>
</div>
<div class="changelog-box" id="changelog-box">
<div class="container">
<p class="title">Changelog</p>
<div class="text">
<p class="changelog" id="changelog">Loading...</p>
</div>
<button class="close" onclick="$('#changelog-box').css('display', 'none')">Close</button>
</div>
</div>
<div class="mod-manager-container" id="mod-manager-container" style="display: none; opacity: 0;">
<div class="mod-manager" id="mod-manager" style="transform: scale(0.8);">
<p class="header">Mod Manager <i class="fa-solid fa-gears icon"></i></p>
<button class="button red red-button" id="closeModManager" onclick="closeModManager();">Close</button>
<button class="button blue-button" id="addMod" onclick="openFolder(true);" style="margin-right: 118px;">Open Mods</button>
<button class="button green-button" id="addMod" onclick="addMod();" style="margin-right: 252px;">Add Mods</button>
<div class="mod-list" id="mod-list">
<div class="loader-container" id="loader-container">
<span class="loader-small" id="01c23c0a-446c-4aa2-a065-a127d717c845"></span>
</div>
<div class="loader-container" id="loader-container-nomods">
<p>Nothing here! Perhaps you need to launch the game first?</p>
</div>
<div class="loader-container" id="loader-container-gamerunning">
<p>Game is running! We can't edit your mods while the game is running :(</p>
</div>
</div>
</div>
</div>
<div class="partner-overlay-creator" id="partner-overlay-creator" style="display: none;">
<div class="partner-details" id="partner-details">
<i class="fa-solid fa-xmark close" onclick="removePartnerOverlay('creator')"></i>
<img class="head" alt="head" src="https://mc-heads.net/avatar/3b22a598-34e5-4188-866b-e56b206b4629" id="partner-creator-head">
<div class="text">
<p class="username" id="partner-creator-username">PiyoFeather</p>
<p class="type" id="partner-creator-type">Creator</p>
</div>
<div class="social-media" id="partner-creator-sm">
<i class="fa-brands fa-youtube" style="color: #ff2100;" id="partner-creator-yt"></i>
<i class="fa-brands fa-discord" style="color: #5662f6;" id="partner-creator-discord"></i>
<i class="fa-brands fa-x-twitter" id="partner-creator-x"></i>
</div>
</div>
</div>
<div class="partner-overlay-server" id="partner-overlay-server" style="display: none;">
<div class="partner-details" id="partner-server-details">
<i class="fa-solid fa-xmark close" onclick="removePartnerOverlay('server')"></i>
<div class="player-info">
<img class="head" alt="head" src="https://media.discordapp.net/stickers/1103539625466806304.webp?size=160" id="partner-server-head">
<div class="text">
<p class="username" id="partner-server-username">[name]</p>
<p class="type" id="partner-server-type">[partner type]</p>
</div>
<div class="social-media" id="partner-server-sm">
<i class="fa-brands fa-youtube" style="color: #ff2100;" id="partner-server-yt"></i>
<i class="fa-brands fa-discord" style="color: #5662f6;" id="partner-server-discord"></i>
<i class="fa-brands fa-x-twitter" id="partner-server-x"></i>
</div>
</div>
</div>
</div>
<div class="sidebar">
<!-- <span class="selected"> -->
<img src="resource/logo.png" class="logo">
<div class="item border selected" onclick="switchPage('home')" id="button-home">
<i class="fa-solid fa-house"></i>
</div>
<div class="item border" onclick="openStore()" id="button-store">
<i class="fa-solid fa-cart-shopping"></i>
</div>
<div class="item border" onclick="openTwitter()">
<i class="fa-brands fa-x-twitter"></i>
</div>
<div class="item border" onclick="openDiscord()">
<i class="fa-brands fa-discord"></i>
</div>
<div class="item border" onclick="switchPage('customization')" id="button-customization">
<i class="fa-solid fa-wand-magic-sparkles"></i>
</div>
<div class="item border big float-bottom" onclick="openSponsorsPopups();" id="button-sponsors" style="margin-bottom: 75px; position: fixed; z-index: 15;">
<i class="fa-solid fa-handshake" id="sponsor-icon"></i>
<i class="fa-solid fa-circle-chevron-right" id="sponsor-icon-arrow" style="position: fixed; opacity: 0;"></i>
</div>
<div class="item border big float-bottom" onclick="switchPage('settings')" id="button-settings">
<i class="fa-solid fa-gear"></i>
</div>
<div class="sponsors-icons">
<div class="item border float-bottom big one" onclick="openSponsor(1)">
<i class="fa-solid fa-server"></i>
</div>
<div class="item border float-bottom big two" onclick="openSponsor(2)">
<i class="fa-solid fa-clipboard-list"></i>
</div>
</div>
</div>
<div class="content" id="page-home">
<div class="topbar">
<div class="featured-servers border-purple">
<div class="servers" onWheel="this.scrollLeft+=event.deltaY>0?100:-100" id="featured-servers-div">
<!-- featured servers auto populate here sometimes -->
</div>
</div>
<div class="accounts border-purple" onclick="signIn();">
<img src="https://mc-heads.net/head/ec669503-a227-4c7c-b27b-9d115f2daa45" class="head" style="display: none;" id="head">
<i class="fa-solid fa-triangle-exclamation warning" id="warning" style="display:none;"></i>
<span class="username" id="username" style="display:none;">Not logged in!</span>
<span class="loader-small" id="loader"></span>
</div>
<div class="mods border-purple" id="mods-manager">
<i class="fa-solid fa-gears icon"></i>
<span class="text">Manage Mods</span>
</div>
</div>
<div class="launch border-green">
<div class="overlay" onmouseover="showOverlay();" onmouseleave="hideOverlay();">
<div class="options" id="options">
<button class="launch-option" id="launch" onclick="launchGame();">LAUNCH 🚀</button>
<!-- <button class="launch-option" id="change-ver">Change Version</button> -->
</div>
<div class="launching center" id="launching">
<span class="loader-big"></span>
<p>Launching...</p>
</div>
<div class="launching center">
<div class="progressbox" id="launching-progress">
<p class="message" id="progresstext">Downloading mods...</p>
<progress value="1" max="0" id="progressbar"></progress>
<p class="txtprogress" id="txtprogress">0/0</p>
</div>
</div>
</div>
<p class="bird-client">鳥クライアント</p>
<p class="version">1.20.1</p>
</div>
<div class="flex">
<div class="news border-gray" id="news-div">
<!-- news auto populate here sometimes -->
</div>
<div class="partners border-gray">
<p class="heading">Partners</p>
<div class="partners-list" id="partners-div">
<!-- partners auto populate here sometimes -->
</div>
</div>
</div>
</div>
<div class="customization" id="page-customization">
<p>Customization page - Coming soon!</p>
</div>
<div class="settings" id="page-settings">
<div class="item">
<div class="text">
<p class="name">Open game folder</p>
<p class="description">Opens the .minecraft folder in your file browser.</p>
</div>
<button class="button" onclick="openFolder(false)">Open</button>
</div>
<div class="item">
<div class="text">
<p class="name">Upload log to support</p>
<p class="description">Uploads the launcher log to our support team for assistance.</p>
</div>
<button class="button" onclick="pastebin()" id="pastebinButton">Upload</button>
</div>
<div class="item">
<div class="text">
<p class="name">Adjust allocated game memory</p>
<p class="description">How much memory we should allocate to the game (Recommended: 6GB)</p>
</div>
<div class="info">
<input type="range" min="2" max="16" value="6" class="slider" id="ram-slider">
<div class="info-text">
<p class="min">2 GB</p>
<p class="current-allocation" id="mem-size">6 GB</p>
<p class="max">16 GB</p>
</div>
</div>
</div>
<div class="item">
<div class="text">
<p class="name">Usage and Licenses</p>
<p class="description">Opens the Tori Client Usage and Licenses.</p>
</div>
<button class="button" onclick="thirdParty();">Open</button>
</div>
<div class="item">
<div class="text">
<p class="name">Log out of account</p>
<p class="description">Logs you out of your Minecraft account. (Launcher will reload)</p>
</div>
<button class="button red red-button" onclick="logOut()" id="logoutButton">Log Out</button>
</div>
</div>
<div class="popup-container" onclick="hidePopup();">
<div class="popup green" id="popup">
<i class="fa-solid fa-circle-check" id="popup-icon"></i>
<p class="description" id="popup-description">hi chat</p>
</div>
</div>
<script src="renderer.js"></script>
</body>
</body>
</html>

3
Launcher/main-c.js Normal file
View file

@ -0,0 +1,3 @@
const bytenode = require('bytenode');
const main = require('./main.jsc');
main;

857
Launcher/main.js Normal file
View file

@ -0,0 +1,857 @@
const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron/main')
const { shell, dialog, globalShortcut } = require('electron');
const process = require('node:process');
if (require('electron-squirrel-startup')) process.exit();
const path = require('node:path')
const { Client } = require("minecraft-launcher-core");
const launcher = new Client();
const { Auth, assets, lst } = require("msmc");
const msmc = require('msmc');
const authManager = new Auth("select_account");
const fs = require('node:fs');
const https = require('https');
const axios = require('axios');
const os = require("os");
const { parse } = require('url')
const http = require('https')
const { basename } = require('path')
var exec = require('child_process').execSync;
const decompress = require('decompress');
const unzipper = require('unzipper');
const request = require("request");
const fetch = require('node-fetch');
const JDK_VER = "jdk-17.0.9+9";
const JDK_FILE = "jdk17.zip";
const JDK_DL = "https://cdn.toriclient.com/java/jdk17.zip";
var datafolder = path.resolve(process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share"), "torilauncher/");
var dotminecraft = path.resolve(datafolder, ".minecraft");
var configfile = path.resolve(datafolder, "config.json");
if(!fs.existsSync(datafolder + "/english.json")) {
fetch("https://raw.githubusercontent.com/Hanro50/MSMC/main/lexipacks/english.json", {method: "GET"})
.then(res => res.json())
.then((json) => {
fs.writeFileSync(datafolder + "/english.json", JSON.stringify(json));
})
.then(() => {
assets.loadLexiPack(datafolder + "/english.json");
});
}
console.log("All launcher output is piped to launcher.log. No information will be displayed in the console.")
var access = fs.createWriteStream('launcher.log');
process.stdout.write = process.stderr.write = access.write.bind(access);
process.on('uncaughtException', function(err) {
console.error((err && err.stack) ? err.stack : err);
});
var win = null;
var uploadedalready = false;
var gamerunning = false;
const createWindow = () => {
win = new BrowserWindow({
width: 1200,
height: 700,
minWidth: 1023,
minHeight: 563,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
//devTools: false
},
icon: path.join(__dirname, 'resource/favicon.ico')
})
console.log("[INFO] Loading main window...");
win.loadFile('index.html')
win.setMenuBarVisibility(false);
win.on('close', function (e) {
if(gamerunning) {
let response = dialog.showMessageBoxSync(this, {
type: 'warning',
buttons: ['Yes', 'No'],
title: 'Confirm close',
message: 'Your game is still running, are you sure you want to quit?\nClosing the launcher will close your game.'
});
if(response == 1) e.preventDefault();
}
});
}
ipcMain.handle("link:open", (event, url) => {
shell.openPath(url);
})
ipcMain.handle("socialmedia:twitter", () => {
console.log("[INFO] Opening Twitter...");
shell.openPath("https://twitter.com/ToriClient");
});
ipcMain.handle("socialmedia:discord", () => {
console.log("[INFO] Opening Discord...");
shell.openPath("https://discord.gg/4GvHfbZ84c");
});
ipcMain.handle("socialmedia:store", () => {
console.log("[INFO] Opening store...");
shell.openPath("https://toriclient.tebex.io/");
});
ipcMain.handle("settings:thirdparty", () => {
console.log("[INFO] Opening third party credits...");
shell.openPath("https://toriclient.com/thirdparty.txt");
});
ipcMain.handle("launcher:changelog", async () => {
if(fs.existsSync("changelog.txt")) {
return "no more :(";
}
const changelog_dl = fs.createWriteStream("changelog.txt");
await https.get("https://cdn.toriclient.com/launcher/changelog.txt", function(response) {
response.pipe(changelog_dl);
changelog_dl.on("finish", async () => {
changelog_dl.close();
});
});
});
ipcMain.handle("settings:logout", () => {
try {
if(fs.existsSync(datafolder + "/auth.key")){fs.unlinkSync(datafolder + "/auth.key");} else {return "not_logged_in"}
return "done";
} catch(err) {
console.log(err);
return err.message;
}
});
ipcMain.handle("settings:openfolder", (event, mods) => {
if(fs.existsSync(dotminecraft)) {
if(!mods) {
shell.openPath(path.normalize(dotminecraft), err => {if(err) {return err;}});
} else {
shell.openPath(path.normalize(dotminecraft + "/mods"), err => {if(err) {return err;}});
}
} else {
return ".minecraft folder not found! Please launch the game first.";
}
})
ipcMain.handle("modmanager:showinfolder", (event, filename) => {
if(!fs.existsSync(dotminecraft + "/mods/" + filename)) {
if(!fs.existsSync(dotminecraft + "/mods/" + filename + ".disabled")) {
let response = dialog.showMessageBoxSync(win, {
type: 'error',
buttons: ['OK'],
title: 'ToriLauncher - Error!',
message: `Something went wrong while trying to show file in folder. Are you sure it exists?\nFile: ${filename}\n\nIf you believe this is a bug, please report it to our development team!`
});
return;
}
shell.showItemInFolder(path.normalize(dotminecraft + "/mods/" + filename + ".disabled"));
return;
}
shell.showItemInFolder(path.normalize(dotminecraft + "/mods/" + filename));
});
ipcMain.handle("modmanager:deletemod", (event, filename) => {
if(essentialmods.includes(modlist[filename]["name"])) {
let response = dialog.showMessageBoxSync(win, {
type: 'warning',
buttons: ['OK'],
title: 'ToriLauncher - Action denied.',
message: `You cannot delete ${filename}. If you believe this is an error,\nplease contact the development team.`
});
return;
}
let response = dialog.showMessageBoxSync(win, {
type: 'warning',
buttons: ['Yes', 'No'],
title: 'Permanently delete mod?',
message: `Are you sure you want to delete ${filename}?\nThis cannot be undone!`
});
if(response == 1) {
// no
console.log("[INFO] Mod deletion cancelled for mod " + filename)
} else {
// yes
try {
fs.rmSync(dotminecraft + "/mods/" + filename);
console.log("[INFO] Successfully deleted mod " + filename);
} catch(err) {
try {
fs.rmSync(dotminecraft + "/mods/" + filename + ".disabled");
console.log("[INFO] Successfully deleted mod " + filename);
} catch(e) {
let response = dialog.showMessageBoxSync(win, {
type: 'error',
buttons: ['OK'],
title: 'ToriLauncher - Error!',
message: `Something went wrong while trying to delete that file. Are you sure it exists?\nFile: ${filename}\n\nIf you believe this is a bug, please report it to our development team!`
});
console.log(e);
}
}
// refresh list
win.webContents.send('sendMessage', {"message": "modmanager.addedmod"});
}
});
var essentialmodjar = null;
ipcMain.handle("game:resolvessentials", async () => {
let response = dialog.showMessageBoxSync(win, {
type: 'error',
buttons: ['Disable Essentials', 'Disable World Host', 'Ignore (Launch anyway)'],
title: 'ToriLauncher - Mod conflict!',
message: `It looks like you have Essentials installed, which is not compatible with one of our core mods, World Host. If you do not know what this means and you would like to use Essentials, disable World Host. If you choose to launch anyway, you will not get any support for any issues. What would you like to do?`
});
if(response === 0) {win.webContents.send('sendMessage', {"message": "conflict.essentials.resolved", "mod": "essential", "jar": essentialmodjar});}
if(response === 1) {win.webContents.send('sendMessage', {"message": "conflict.essentials.resolved", "mod": "worldhost"});}
if(response === 2) {win.webContents.send('sendMessage', {"message": "conflict.essentials.resolved", "mod": "ignore"});}
});
ipcMain.handle("modmanager:togglemod", (event, filename) => {
if(filename.endsWith(".jar")) {
fs.renameSync(dotminecraft + "/mods/" + filename, dotminecraft + "/mods/" + filename + ".disabled");
} else if(filename.endsWith(".jar.disabled")) {
fs.renameSync(dotminecraft + "/mods/" + filename, dotminecraft + "/mods/" + filename.slice(0, -9));
} else {
dialog.showMessageBoxSync(win, {
type: 'error',
buttons: ['OK'],
title: 'ToriLauncher - Error!',
message: `Failed to toggle the mod ${filename}.`
});
console.log("[ERROR] Failed to toggle the status of mod " + filename);
}
});
ipcMain.handle("modmanager:addmod", async () => {
dialog.showOpenDialog(null, {
title: "Select a Fabric mod (.jar)",
properties: ['openFile', 'multiSelections'],
filters: [{name: "Fabric mod", extensions: ["jar"]}]
}).then((files) => {
if (files && !files["canceled"]) {
console.log(files);
for(file in files["filePaths"]) {
fs.copyFileSync(files["filePaths"][file], dotminecraft + "/mods/" + basename(files["filePaths"][file]));
console.log("[SUCCESS] Added mod " + basename(files["filePaths"][file]));
}
win.webContents.send('sendMessage', {"message": "modmanager.addedmod"});
}
})
});
ipcMain.handle("game:checkwhitelist", async () => {
while(typeof username === 'undefined') {await sleep(100);}
const whitelist = await fetch("https://api.toriclient.com/launcher/whitelist").then(res => res.json());
const trimmeduuid = await fetch(`https://api.mojang.com/users/profiles/minecraft/${username}`).then(data => data.json()).then(player => player.id);
const sections = [trimmeduuid.slice(0, 8), trimmeduuid.slice(8, 12), trimmeduuid.slice(12, 16), trimmeduuid.slice(16, 20), trimmeduuid.slice(20, 32)];
const formattedUUID = `${sections[0]}-${sections[1]}-${sections[2]}-${sections[3]}-${sections[4]}`;
if(whitelist.includes(formattedUUID)) {
win.webContents.send('sendMessage', {"message": "whitelist.success"});
console.log("[INFO] Player " + username + " is whitelisted!")
} else {
win.webContents.send('sendMessage', {"message": "whitelist.fail"});
console.log("[WARN] Player " + username + " is not whitelisted!")
}
});
var modlist = {};
var essentialmods = null;
ipcMain.handle("modmanager:fetchmods", async () => {
// I HAVE NO FUCKING CLUE WHAT I WAS ON WHEN I WAS MAKING THIS
// BUT IT WORKS
// SO IM NOT GONNA TOUCH IT
// :D
console.log("[INFO] Scanning mod folder... this may take a moment...");
// get list of essential mods => mods you should not be able to disable
await fetch("https://api.toriclient.com/launcher/essential", {method: "GET"})
.then(res => res.json())
.then((json) => {
essentialmods = json;
});
if(fs.existsSync(dotminecraft + "/mods/tmp/")) {fs.rmSync(dotminecraft + "/mods/tmp/", { recursive: true, force: true });}
var count = 0;
var totalcount = -1;
modlist = {};
// make sure it aint empty first
if(!fs.existsSync(dotminecraft + "/mods/") || fs.readdirSync(dotminecraft + "/mods/").length === 0) {
console.log("[WARN] Mod folder empty! You may need to launch the game first.");
win.webContents.send('sendMessage', {"message": "modlist", "response": "empty"});
return;
}
fs.mkdirSync(dotminecraft + "/mods/tmp/");
fs.readdir(dotminecraft + "/mods/", (err, files) => {
files.forEach(file => {if (file.endsWith('.jar') || file.endsWith('.jar.disabled')) {totalcount++;}});
if (err) {
console.error('[ERROR] Error reading from mods folder: ', err);
return;
}
files.forEach(file => {
const filePath = path.join(dotminecraft + "/mods/", file);
if (file.endsWith('.jar') || file.endsWith('.jar.disabled')) {
fs.createReadStream(dotminecraft + "/mods/" + file)
.pipe(unzipper.Parse())
.on('entry', function (entry) {
if (entry.path === "fabric.mod.json") {
entry.pipe(fs.createWriteStream(dotminecraft + "/mods/tmp/" + file + ".json"));
} else {entry.autodrain();}
})
.on('close', () => {
try {
goofy = JSON.parse(fs.readFileSync(dotminecraft + "/mods/tmp/" + file + ".json", 'utf8'));
var essential = null;
if(essentialmods.includes(goofy['name'])) {essential = true;} else {essential = false;}
if(goofy['id'] === "essential-container") {essentialmodjar = file; goofy['name'] = goofy['custom']['modmenu'] ['parent']['name']; goofy['description'] = goofy['custom']['modmenu'] ['parent']['description'];}
if(file.endsWith(".jar")) {modlist[file] = {"name": goofy['name'], "desc": goofy['description'], "enabled": true, "id": goofy['id'], "essential": essential}} else {modlist[file] = {"name": goofy['name'], "desc": goofy['description'], "enabled": false, "essential": essential}}
if(goofy['id'] === "essential-container") {win.webContents.send('sendMessage', {"message": "essentialsjar", "response": essentialmodjar});}
delete goofy;
} catch(e) {
console.log("[WARN] Failed to read metadata from " + file + ". Skipping.");
var essential = null;
if(essentialmods.includes(file)) {essential = true;} else {essential = false;}
if(file.endsWith(".jar")) {modlist[file] = {"name": file, "desc": "", "enabled": true, "essential": essential}} else {modlist[file] = {"name": file, "desc": "", "enabled": false, "essential": essential}}
}
count++;
if(count > totalcount) {count = totalcount}
console.log("[INFO] Scanning mod " + file + "... (" + count + "/" + totalcount + ")");
});
}
});
});
// wait for everything to finish. cheeky but works lmao
while(totalcount === -1) {await sleep(10);}
while(count != totalcount) {await sleep(100);}
console.log("[INFO] Metadata scan finished. Scanned " + count + "/" + totalcount + " mods.");
console.log("[INFO] Starting icon scan... this may take a moment.");
var count = 0;
var totalcount = fs.readdirSync(dotminecraft + "/mods/tmp/").length;
// fs.readdir(dotminecraft + "/mods/tmp/", (err, files) => {if(err) {console.log("[ERROR] Failed to load icons: " + error); return;} totalcount = files.length});
fs.readdir(dotminecraft + "/mods/", (err, files) => {
//files.forEach(file => {if (file.endsWith('.jar') || file.endsWith('.jar.disabled')) {totalcount++;}});
if (err) {
console.error('[ERROR] Error reading from mods folder: ', err);
return;
}
files.forEach(file => {
if (file.endsWith('.jar') || file.endsWith('.jar.disabled')) {
const thesilly = dotminecraft + "/mods/tmp/" + file + ".json";
if(fs.existsSync(thesilly)) {
var thesillycontents = null;
try {
thesillycontents = JSON.parse(fs.readFileSync(dotminecraft + "/mods/tmp/" + file + ".json", 'utf8').replace(/\r?\n/g, ''));
} catch(err) {
console.log("[ERROR] Something went wrong while reading a mods data! It may not show up in the mod manager. Here's the JSON:");
console.log(fs.readFileSync(dotminecraft + "/mods/tmp/" + file + ".json", 'utf8'));
console.log(err);
}
const imgpath = thesillycontents["icon"];
fs.createReadStream(dotminecraft + "/mods/" + file)
.pipe(unzipper.Parse())
.on('entry', function (entry) {
if (entry.path === imgpath) {
entry.pipe(fs.createWriteStream(dotminecraft + "/mods/tmp/" + file + "-icon.png"));
} else {entry.autodrain();}
})
.on('close', () => {
if(fs.existsSync(dotminecraft + "/mods/tmp/" + file + "-icon.png")) {modlist[file]["icon"] = fs.readFileSync(dotminecraft + "/mods/tmp/" + file + "-icon.png", "base64");}
count++;
if(count > totalcount) {count = totalcount}
});
}
}
});
});
while(totalcount === -1) {await sleep(10);}
while(count != totalcount) {await sleep(100);}
console.log("[INFO] All done scanning!")
win.webContents.send('sendMessage', {"message": "modlist", "response": modlist});
fs.rmSync(dotminecraft + "/mods/tmp/", { recursive: true, force: true });
return true;
});
ipcMain.handle("settings:pastebin", async () => {
if(typeof username === 'undefined') username = "[NOT LOGGED IN]";
const options = {
method: "POST",
url: "https://api.toriclient.com/launcher/log/upload",
port: 443,
headers: {},
formData : {
"file": fs.createReadStream("launcher.log"),
"username": username
}
};
request(options, function (err, res, body) {
if(err) {console.log(err); win.webContents.send('sendMessage', {"message": "internal.log-response", "response": "general_fail"});}
console.log("[INFO] Printing raw response from API:")
console.log(body)
if(body.includes("413 Request Entity Too Large")) {
win.webContents.send('sendMessage', {"message": "internal.log-response", "response": {"status": "FAIL", "error": "413 Request Entity Too Large"}});
return;
}
parsedBody = JSON.parse(body);
if(parsedBody['status'] != "OK") {
console.log("[ERROR] Got a not good response from API: " + body);
win.webContents.send('sendMessage', {"message": "internal.log-response", "response": parsedBody});
} else {
console.log("[INFO] Uploaded log successfully.")
win.webContents.send('sendMessage', {"message": "internal.log-response", "response": parsedBody});
}
});
});
var loggedin = false;
ipcMain.handle('game:login', () => {
loggedin = false;
authManager.launch("raw").then(async xboxManager => {
//const token = await xboxManager.getMinecraft();
fs.writeFile(datafolder + "/auth.key", Buffer.from(JSON.stringify(xboxManager.save())).toString('base64'), err => {if (err) {console.error(err);}});
console.log("[INFO] Logged in successfully!")
loggedin = true;
}).catch(err => {
try {
console.error("[ERROR] Failed to get account: " + lst(err['ts']));
win.webContents.send('sendMessage', {"message": "internal.login-fail", "response": lst(err['ts'])});
console.log(err);
if(fs.existsSync(datafolder + "/auth.key")) {fs.rmSync(datafolder + "/auth.key");}
} catch (error) {
console.log(error);
}
});
win.webContents.send('sendMessage', {"message": "token.refresh"});
});
ipcMain.handle('game:checkstatus', () => {return loggedin});
var token = null;
ipcMain.handle('game:getaccount', async () => {
try {
if (!(fs.existsSync(datafolder + "/auth.key"))) {win.webContents.send('sendMessage', {"message": "internal.login-success", "response": "[NOT LOGGED IN]"}); return "no_account";} else {
let data = fs.readFileSync(datafolder + "/auth.key", "utf8");
token = Buffer.from(data, 'base64').toString('utf8');
if(typeof JSON.parse(token)['name'] === "string") {
console.log("[WARN] Detected old auth token, forcing relogin.");
fs.rmSync(datafolder + "/auth.key");
win.webContents.send('sendMessage', {"message": "internal.login-success", "response": "[NOT LOGGED IN]"});
return "no_account";
}
let auth = await new Auth().refresh(JSON.parse(token))
let mcobj = await auth.getMinecraft()
let mclc = (await mcobj).mclc();
token = mclc;
console.log("[INFO] Logged in as " + mclc['name']);
username = mclc['name'];
win.webContents.send('sendMessage', {"message": "internal.login-success", "response": username});
}
} catch (err) {
console.log(err);
win.webContents.send('sendMessage', {"message": "internal.login-fail", "response": lst(err['ts'])});
console.log("[ERROR] Something went wrong while logging in.")
return "err";
}
});
ipcMain.handle('launcher:update', async () => {return uploadedalready;});
ipcMain.handle('launcher:run', async () => {
win.webContents.send('sendMessage', {"message": "Checking for updates..."});
await new Promise(r => setTimeout(r, 500));
await axios({
method: 'GET',
url: "https://api.toriclient.com/launcher/version"
}).then(async function (response) {
var version = response['data'];
var pjson = require('./package.json');
var lversion = pjson.version;
console.log("[INFO] Current version: " + lversion + " | Latest version: " + version)
if(version != lversion) {
console.log("[INFO] Update required! Downloading update...");
win.webContents.send('sendMessage', {"message": "Downloading update..."});
const tempdir = os.tmpdir();
if(os.platform() === 'win32') {
const updater = fs.createWriteStream(tempdir + "\\update.exe");
await https.get("https://cdn.toriclient.com/launcher/ToriLauncher_latest.exe", function(response) {
response.pipe(updater);
updater.on("finish", async () => {
updater.close();
console.log("[INFO] Update downloaded. Executing update exe then quitting!");
win.webContents.send('sendMessage', {"message": "Verifying update..."});
await new Promise(r => setTimeout(r, 1500));
win.webContents.send('sendMessage', {"message": "Updating launcher..."});
await exec(tempdir + '\\update.exe', function(err, data) {
if(err) {console.log(err);}
});
fs.unlinkSync(tempdir + '\\update.exe');
process.exit();
});
});
}
} else {
console.log("[INFO] No update found, preparing launcher.");
win.webContents.send('sendMessage', {"message": "Preparing the launcher..."});
win.webContents.send('sendMessage', {"message": "ram.capacity", "totalram": totalmem, "allocated": JSON.parse(fs.readFileSync(configfile))['ramAllocatedGB']});
fetch("https://api.toriclient.com/launcher/servers", {method: "GET"})
.then(res => res.json())
.then((json) => {
win.webContents.send('featuredServers', json);
console.log("[INFO] Fetched featured servers from the API.");
}).catch(() => {
console.log("[ERROR] Failed to fetch featured servers from API.");
});
fetch("https://api.toriclient.com/launcher/news", {method: "GET"})
.then(res => res.json())
.then((json) => {
win.webContents.send('news', json);
console.log("[INFO] Fetched news from the API.");
}).catch(() => {
console.log("[ERROR] Failed to fetch news from API.");
});
fetch("https://api.toriclient.com/launcher/partners", {method: "GET"})
.then(res => res.json())
.then((json) => {
win.webContents.send('partners', json);
console.log("[INFO] Fetched partner list from the API.");
}).catch(() => {
console.log("[ERROR] Failed to fetch partners from API.");
});
if(uploadedalready) return "already";
}
}).catch(function (error) {
console.log("[ERROR] Failed to update!")
console.log(error);
win.webContents.send('sendMessage', {"message": "err.update_failed"});
if(uploadedalready) return "already";
});
});
ipcMain.handle('launcher:updateram', async (event, ram) => {
const file = require(configfile);
file.ramAllocatedGB = ram;
fs.writeFile(configfile, JSON.stringify(file, null, 4), function (err) {if(err) {console.log("[ERROR] Failed to write config file: " + err); return;}});
});
ipcMain.handle('game:launch', async (event, wam) => {
console.log("[INFO] User has allocated " + wam.toString() + "GB RAM to the game instance.");
if (!fs.existsSync(dotminecraft)){fs.mkdirSync(dotminecraft);}
if (!fs.existsSync(dotminecraft + "/versions/")){fs.mkdirSync(dotminecraft + "/versions/");}
if (!fs.existsSync(dotminecraft + "/versions/fabric/")){
fs.mkdirSync(dotminecraft + "/versions/fabric/");
const fabricjson = fs.createWriteStream(dotminecraft + "/versions/fabric/fabric.json");
https.get("https://cdn.toriclient.com/versions/fabric.json", function(response) {
response.pipe(fabricjson);
fabricjson.on("finish", () => {
fabricjson.close();
console.log("> Downloaded fabric.json");
});
});
} else {
/* var fabricjson = fs.readFileSync(dotminecraft + "/versions/fabric/fabric.json");
if(fabricjson.includes("fabric-loader-0.15.2-1.20.1")) {
fs.rmSync(dotminecraft + "/versions/fabric/fabric.json");
const fabricjson = fs.createWriteStream(dotminecraft + "/versions/fabric/fabric.json");
https.get("https://cdn.toriclient.com/versions/fabric.json", function(response) {
response.pipe(fabricjson);
fabricjson.on("finish", () => {
fabricjson.close();
console.log("> Downloaded fabric.json");
});
});
} */
fs.rmSync(dotminecraft + "/versions/fabric/fabric.json");
const fabricjson = fs.createWriteStream(dotminecraft + "/versions/fabric/fabric.json");
https.get("https://cdn.toriclient.com/versions/fabric.json", function(response) {
response.pipe(fabricjson);
fabricjson.on("finish", () => {
fabricjson.close();
console.log("> Downloaded fabric.json");
});
});
}
// rewrite to redownload fabric ver
if(!fs.existsSync(dotminecraft + "/mods/")) {fs.mkdirSync(dotminecraft + "/mods/")}
if(fs.existsSync(dotminecraft + "/mods/index.json")) {fs.rmSync(dotminecraft + "/mods/index.json")}
const modindex = fs.createWriteStream(dotminecraft + "/mods/index.json");
await https.get("https://cdn.toriclient.com/mods/index.json", function(response) {
response.pipe(modindex);
modindex.on("finish", () => {
modindex.close();
console.log("> Downloaded mod index");
continueLaunch(wam.toString());
});
});
});
var sleepSetTimeout_ctrl;
function sleep(ms) {
clearInterval(sleepSetTimeout_ctrl);
return new Promise(resolve => sleepSetTimeout_ctrl = setTimeout(resolve, ms));
}
async function continueLaunch(wam) {
var links = JSON.parse(fs.readFileSync(dotminecraft + "/mods/index.json"));
var keys = Object.keys(links);
const totallength = keys.length;
if(fs.existsSync(datafolder + "/lastindex.json")) {
var lastindex = JSON.parse(fs.readFileSync(datafolder + "/lastindex.json"));
} else {
fs.writeFileSync(datafolder + "/lastindex.json", "{}");
}
if(!fs.readFileSync(dotminecraft + "/mods/index.json").equals(fs.readFileSync(datafolder + "/lastindex.json"))) {
//if (fs.existsSync(dotminecraft + "/mods/")){await fs.rmSync(dotminecraft + "/mods", { recursive: true }); await fs.mkdirSync(dotminecraft + "/mods/");} else {fs.mkdirSync(dotminecraft + "/mods/");}
for (const key in lastindex) {
const filePath = `${dotminecraft}/mods/${lastindex[key].substring(lastindex[key].lastIndexOf('/') + 1)}`;
try {fs.rmSync(filePath);} catch(err) {try{if(err.code === "ENOENT") {fs.rmSync(filePath + ".disabled")}} catch (err) {if(err.code === "ENOENT") console.log("[WARN] Failed to delete file " + filePath + " because it doesn't exist.")}}
}
var i = 0;
for (const key in links) {
const url = links[key];
try {
const response = await axios({
method: 'GET',
url: url,
responseType: 'stream'
});
const fileName = url.substring(url.lastIndexOf('/') + 1)
const filePath = `${dotminecraft}/mods/${fileName}`;
const writer = fs.createWriteStream(filePath);
response.data.pipe(writer);
await new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
i++;
win.webContents.send('sendProgress', {"type": 'mods', "task": i, "total": totallength})
console.log(`[DL-INFO] Mod downloaded: ${url.substring(url.lastIndexOf('/') + 1)}`);
} catch (error) {
console.log(`[ERROR] Failed to download ${url}: ${error.message}`);
}
}
} else {
let missinglinks = [];
for (const key in links) {
const filePath = `${dotminecraft}/mods/${links[key].substring(links[key].lastIndexOf('/') + 1)}`;
if(!fs.existsSync(filePath) && !fs.existsSync(filePath + ".disabled")) {missinglinks.push(links[key]);}
}
if(missinglinks.length === 0) {
console.log("[INFO] All or most required mods already in mods folder.")
} else {
console.log("[INFO] Missing " + missinglinks.length + " mods, redownloading missing mods...")
var i = 0;
for(const key in missinglinks) {
const url = missinglinks[key];
try {
const response = await axios({
method: 'GET',
url: url,
responseType: 'stream'
});
const fileName = url.substring(url.lastIndexOf('/') + 1)
const filePath = `${dotminecraft}/mods/${fileName}`;
const writer = fs.createWriteStream(filePath);
response.data.pipe(writer);
await new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
i++;
win.webContents.send('sendProgress', {"type": 'mods', "task": i, "total": Object.keys(missinglinks).length})
console.log(`[DL-INFO] Mod downloaded: ${url.substring(url.lastIndexOf('/') + 1)}`);
} catch (error) {console.log(`[ERROR] Failed to download ${url}: ${error.message}`);}
}
}
}
if(fs.existsSync(datafolder + "/lastindex.json")) {fs.rmSync(datafolder + "/lastindex.json", { recursive: true });}
fs.renameSync(dotminecraft + "/mods/index.json", datafolder + "/lastindex.json");
if(!fs.existsSync(datafolder + `/${JDK_VER}`)) {
await download(JDK_DL, datafolder + `/${JDK_FILE}`, "java");
win.webContents.send('sendProgress', {"type": "java-extract", "task": 1, "total": 1})
decompress(datafolder + `/${JDK_FILE}`, datafolder + "/")
.catch((error) => {
win.webContents.send('sendProgress', {"type": 'error', "task": error, "total": 1})
});
while(!fs.existsSync(datafolder + `/${JDK_VER}/bin/java.exe`)) {
await sleep(100);
// wait for java to be extracted async function shenanigans. cheeky but works ig
}
fs.rmSync(datafolder + `/${JDK_FILE}`);
console.log("[INFO] Extracted and cleaned up JDK Runtime. Launching now!");
}
win.webContents.send('sendProgress', {"type": 'waitforgame', "task": 0, "total": 1})
await sleep(1000);
// possibly refresh token in the future? --> believe this has been done already but i forgor
let opts = {
javaPath: datafolder + `/${JDK_VER}/bin/java.exe`,
overrides: {
detached: false
},
clientPackage: null,
authorization: token,
root: dotminecraft,
version: {
number: "1.20.1",
type: "release",
custom: "fabric"
},
memory: {
max: `${wam}G`,
min: "2G"
}
};
console.log("[INFO] Starting game!");
try {
launcher.launch(opts);
return true;
} catch {
return false;
}
}
const totalmem = Math.ceil(os.totalmem() / (1024 ** 3));
app.whenReady().then(() => {
console.log(" _____ _ __ _ ");
console.log(" /__ \\___ _ __(_) / / __ _ _ _ _ __ ___| |__ ___ _ __ ");
console.log(" / /\\/ _ \\| '__| |/ / / _` | | | | '_ \\ / __| '_ \\ / _ \\ '__|");
console.log(" / / | (_) | | | / /__| (_| | |_| | | | | (__| | | | __/ | ");
console.log(" \\/ \\___/|_| |_\\____/\\__,_|\\__,_|_| |_|\\___|_| |_|\\___|_| ");
console.log("");
console.log(" can i get tori client early?");
console.log(" ToriLauncher by WifiRouter • Designed by Zero • Created by PiyoFeather");
console.log("");
createWindow();
globalShortcut.unregister('ControlOrControl+R');
globalShortcut.unregister('F5');
console.log("[INFO] Window created. Good luck and have fun!");
console.log("[INFO] Computer has " + totalmem + " GB RAM.");
app.on('activate', () => {
console.log("[INFO] Opening new window...")
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
launcher.on('debug', (e) => {
if(!e.includes("[MCLC]: Launching with arguments")) {
console.log(e);
} else {
win.webContents.send('sendProgress', {"type": 'launching', "task": 0, "total": 0})
}
});
launcher.on('data', (e) => {console.log(e); gamerunning = true;});
launcher.on('close', (e) => {win.webContents.send('sendProgress', {"type": 'stopped', "task": 0, "total": e}); gamerunning = false; console.log("[INFO] Game exited with code " + e);
if(e != 0) {
let response = dialog.showMessageBoxSync(win, {
type: 'error',
buttons: ['OK'],
title: 'ToriLauncher - Game error!',
message: 'It looks like your game has crashed or quit unexpectedly.\n\nIf you did not cause this, make sure your mods are compatible with ours\nand you are not missing any dependencies.\n\nIf this issue still occurs, please contact our staff team for more assistance.'
});
}
});
launcher.on('progress', (e) => win.webContents.send('sendProgress', e));
https.get("https://api.toriclient.com/status", (res) => {
if (res.statusCode === 200) {
console.log(`[INFO] Server is reachable!`);
} else {
console.log("[ERROR] Server returned a non-ok response. Exiting now.");
dialog.showMessageBoxSync(win, {
type: 'error',
buttons: ['OK'],
title: 'ToriLauncher - Error!',
message: "Failed to connect to the Tori Client servers. Check your internet connection, and if you believe this is an error, contact the development team for assistance or information on this matter.\n\nPressing OK will close the launcher."
});
app.quit();
}
}).on("error", function(e) {
console.log("[ERROR] Server is not reachable! Exiting now.");
dialog.showMessageBoxSync(win, {
type: 'error',
buttons: ['OK'],
title: 'ToriLauncher - Error!',
message: "Failed to connect to the Tori Client servers. Check your internet connection, and if you believe this is an error, contact the development team for assistance or information on this matter.\n\nPressing OK will close the launcher."
});
app.quit();
});
// setup config file
if(!fs.existsSync(configfile)) {
fs.writeFileSync(configfile, JSON.stringify({"ramAllocatedGB": 6}, null, 4), (err) => {
if (err) {
console.error('[ERROR] Error writing to config file: ', err);
} else {
console.log('[INFO] Config file doesn\'t exist, created successfully.');
}
});
}
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
console.log("[INFO] Shutting down... Goodbye!");
});
const TIMEOUT = 10000
async function download(url, path, title) {
const uri = parse(url)
if (!path) {
path = basename(uri.path)
}
const file = fs.createWriteStream(path)
return new Promise(function(resolve, reject) {
const request = http.get(uri.href).on('response', function(res) {
const len = parseInt(res.headers['content-length'], 10)
let downloaded = 0
res.on('data', function(chunk) {
file.write(chunk)
downloaded += chunk.length
win.webContents.send('sendProgress', {"type": title, "task": downloaded, "total": len})
})
.on('end', function() {
file.end()
console.log(`[INFO] Saved ${JDK_FILE} (${len} bytes).`)
resolve()
})
.on('error', function (err) {
reject(err)
win.webContents.send('sendProgress', {"type": 'error', "task": err, "total": 1})
})
})
request.setTimeout(TIMEOUT, function() {
request.abort()
reject(new Error(`[ERROR] Request timeout after ${TIMEOUT / 1000.0}s`));
win.webContents.send('sendProgress', {"type": 'error', "task": `Request timeout after ${TIMEOUT / 1000.0}s`, "total": 1})
})
})
}

42
Launcher/package.json Normal file
View file

@ -0,0 +1,42 @@
{
"name": "torilauncher",
"version": "0.1.0",
"description": "Official launcher for Tori Client!",
"main": "main.js",
"scripts": {
"start": "electron-forge start",
"test": "echo \"Error: no test specified\" && exit 1",
"package": "electron-forge package",
"make": "electron-forge make"
},
"author": "WifiRouter",
"license": "ISC",
"devDependencies": {
"@electron-forge/cli": "^7.2.0",
"@electron-forge/maker-deb": "^7.2.0",
"@electron-forge/maker-rpm": "^7.2.0",
"@electron-forge/maker-squirrel": "^7.2.0",
"@electron-forge/maker-zip": "^7.2.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.2.0",
"electron": "^28.0.0",
"electron-packager": "^17.1.2",
"electron-vite": "^2.1.0"
},
"dependencies": {
"@herberttn/bytenode-webpack-plugin": "^2.3.1",
"axios": "^1.6.2",
"bytenode": "^1.5.6",
"crypto": "^1.0.1",
"decompress": "^4.2.1",
"electron-squirrel-startup": "^1.0.0",
"explorer-opener": "^1.0.1",
"js-file-download": "^0.4.12",
"minecraft-launcher-core": "^3.17.3",
"msmc": "^4.1.0",
"open-file-explorer": "^1.0.2",
"qs": "^6.11.2",
"simple-discord-webhooks": "^2.1.0",
"unzipper": "^0.10.14",
"winston": "^3.11.0"
}
}

9
Launcher/precheck.txt Normal file
View file

@ -0,0 +1,9 @@
!! MAKE SURE DEV TOOLS IS DISABLED !!
rename build.js to main.js, move main.js to temp.js
start application
empty window will appear
close empty window once main.jsc appears
move build.js outside directory
move temp.js outside directory
rename main-c.js to main.js
npm run start to test, if runs, make build

51
Launcher/preload.js Normal file
View file

@ -0,0 +1,51 @@
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('game', {
launch: (wam) => ipcRenderer.invoke('game:launch', wam),
login: () => ipcRenderer.invoke('game:login'),
getAccount: () => ipcRenderer.invoke('game:getaccount'),
checkLoginStatus: () => ipcRenderer.invoke('game:checkstatus'),
checkWhitelistStatus: () => ipcRenderer.invoke('game:checkwhitelist'),
resolveEssentialsConflict: () => ipcRenderer.invoke('game:resolvessentials')
})
contextBridge.exposeInMainWorld('launcher', {
run: () => ipcRenderer.invoke('launcher:run'),
update: () => ipcRenderer.invoke('launcher:update'),
getChangelog: () => ipcRenderer.invoke('launcher:changelog'),
updateRam: (ram) => ipcRenderer.invoke('launcher:updateram', ram)
})
contextBridge.exposeInMainWorld('socialmedia', {
twitter: () => ipcRenderer.invoke('socialmedia:twitter'),
discord: () => ipcRenderer.invoke('socialmedia:discord'),
store: () => ipcRenderer.invoke('socialmedia:store')
})
contextBridge.exposeInMainWorld('settings', {
openFolder: (mods) => ipcRenderer.invoke('settings:openfolder', mods),
pasteBin: () => ipcRenderer.invoke('settings:pastebin'),
logout: () => ipcRenderer.invoke('settings:logout'),
thirdParty: () => ipcRenderer.invoke('settings:thirdparty')
});
contextBridge.exposeInMainWorld('link', {
open: (supercoollink) => ipcRenderer.invoke('link:open', supercoollink)
});
contextBridge.exposeInMainWorld('modmanager', {
fetchMods: () => ipcRenderer.invoke('modmanager:fetchmods'),
toggleMod: (filename) => ipcRenderer.invoke('modmanager:togglemod', filename),
showInFolder: (filename) => ipcRenderer.invoke('modmanager:showinfolder', filename),
deleteMod: (filename) => ipcRenderer.invoke('modmanager:deletemod', filename),
addMod: () => ipcRenderer.invoke('modmanager:addmod'),
});
contextBridge.exposeInMainWorld('electronAPI', {
sendProgress: (callback) => ipcRenderer.on('sendProgress', (_event, value) => callback(value)),
sendMessage: (callback) => ipcRenderer.on('sendMessage', (_event, value) => callback(value)),
featuredServers: (callback) => ipcRenderer.on('featuredServers', (_event, value) => callback(value)),
news: (callback) => ipcRenderer.on('news', (_event, value) => callback(value)),
partners: (callback) => ipcRenderer.on('partners', (_event, value) => callback(value)),
modslist: (callback) => ipcRenderer.on('modslist', (_event, value) => callback(value))
})

788
Launcher/renderer.js Normal file
View file

@ -0,0 +1,788 @@
// yes its shit
// it works
// good enough
var loginfailure = false;
var gamerunning = false;
var username = 0;
const wallpaperid = Math.floor(Math.random() * 3); console.log(wallpaperid);
const backgroundid = Math.floor(Math.random() * 2);
$(".launch").css("background-image", `linear-gradient(to right, #a5d2ae, rgba(0, 0, 0, 0) 95%), url(resource/wallpaper${wallpaperid + 1}.png)`);
$("#body").css("background-image", `linear-gradient(rgba(43, 48, 64, 0.5),rgba(43, 48, 64, 0.5)) , url(resource/background${backgroundid + 1}.png)`);
$("#whitelist-wallpaper").css("background-image", `linear-gradient(to right, rgba(13, 17, 23,0) 0%,rgba(13, 17, 23,0) 2%,#0d1117 100%), url(resource/background${backgroundid + 1}.png)`);
$("#launching").css("display", "none");
document.getElementById("ram-slider").oninput = function() {$("#mem-size").text(this.value + " GB"); window.launcher.updateRam(this.value);}
function showOverlay() {$(".overlay").css("opacity", 1);}
function hideOverlay() {if(!gamerunning) $(".overlay").css("opacity", 0);}
function addMod() {window.modmanager.addMod();}
async function closeSponsor() {
$(".sponsor-container .sponsor").css("transform", "scale(0.8)");
$(".sponsor-container .sponsor").css("opacity", 0);
$(".darken").css("opacity", 0);
await sleep(500);
$(".sponsor-container").css("display", "none");
$(".darken").css("display", "none");
}
async function openSponsor(id) {
if(id === 1) {
// wepwawet
$(".sponsor-container").css("display", "flex");
$(".wepwawet").css("display", "flex");
$(".ffatl").css("display", "none");
await sleep(1);
$(".sponsor-container .sponsor").css("transform", "scale(1)");
$(".sponsor-container .sponsor").css("opacity", 1);
} else if (id === 2) {
// ffatl
$(".sponsor-container").css("display", "flex");
$(".wepwawet").css("display", "none");
$(".ffatl").css("display", "flex");
await sleep(1);
$(".sponsor-container .sponsor").css("transform", "scale(1)");
$(".sponsor-container .sponsor").css("opacity", 1);
} else {
console.log("[ERROR] Invalid sponsor ID, nothing to open!");
}
$("#sponsor-icon").css("transform", "rotate(0deg)");
$("#sponsor-icon-arrow").css("transform", "rotate(0deg)");
$("#sponsor-icon").css("opacity", 1);
$("#sponsor-icon-arrow").css("opacity", 0);
sponsorsopen = false;
$(".sponsors-icons").css("opacity", 0);
$(".sponsors-icons").css("margin-left", "45px");
$("#button-sponsors").css("z-index", 1);
await sleep(501);
$(".sponsors-icons").css("display", "none");
}
var sponsorsopen = false;
async function openSponsorsPopups() {
$("#button-sponsors").css("z-index", 21);
if(!sponsorsopen) {
$("#sponsor-icon").css("transform", "rotate(180deg)");
$("#sponsor-icon-arrow").css("transform", "rotate(180deg)");
$("#sponsor-icon").css("opacity", 0);
$("#sponsor-icon-arrow").css("opacity", 1);
sponsorsopen = true;
$(".darken").css("display", "block");
$(".sponsors-icons").css("display", "block");
await sleep(5);
$(".darken").css("opacity", 1);
$(".sponsors-icons").css("opacity", 1);
$(".sponsors-icons").css("margin-left", "60px");
} else {
$("#sponsor-icon").css("transform", "rotate(0deg)");
$("#sponsor-icon-arrow").css("transform", "rotate(0deg)");
$("#sponsor-icon").css("opacity", 1);
$("#sponsor-icon-arrow").css("opacity", 0);
sponsorsopen = false;
$(".darken").css("opacity", 0);
$(".sponsors-icons").css("opacity", 0);
$(".sponsors-icons").css("margin-left", "45px");
await sleep(501);
$(".sponsors-icons").css("display", "none");
$(".darken").css("display", "none");
}
}
// openModManager <-- keyword to find this function in this horrific mess => if it works don't touch it :3
document.getElementById("mods-manager").onclick = async function() {
await sleep(5);
if(gamerunning) {
document.getElementById("mod-list").innerHTML = `<div class="loader-container" id="loader-container-gamerunning"><p>Game is running! We can't edit your mods while the game is running :(</p></div>`;
console.log("[INFO] Opening mod manager...");
$("#mod-manager-container").css("display", "flex");
await sleep(10);
$("#mod-manager-container").css("opacity", 1);
$("#mod-manager").css("transform", "scale(1)");
return;
}
$("#loader-container-gamerunning").css('display', 'none');
window.modmanager.fetchMods();
document.getElementById("mod-list").innerHTML = '';
$("#mod-list").append(`<div class="loader-container" id="loader-container"><span class="loader-small"></span></div><div class="loader-container" id="loader-container-nomods" style="display: none;"><p style="color: #8e658d">Nothing here! Perhaps you need to launch the game first?</p></div>`);
//$("#loader-container-nomods").css("display", "none");
console.log("[INFO] Opening mod manager...");
$("#mod-manager-container").css("display", "flex");
await sleep(10);
$("#mod-manager-container").css("opacity", 1);
$("#mod-manager").css("transform", "scale(1)");
}
async function closeModManager() {
$("#mod-manager-container").css("opacity", 0);
$("#mod-manager").css("transform", "scale(0.8)");
await sleep(500);
$("#mod-manager-container").css("display", "none");
}
async function thirdParty() {await window.settings.thirdParty();}
var gotmodlist = false;
var essentialsconflictresolved = true;
var essentialdsmodjar = null;
async function launchGame() {
if($("#username").text() === "Not logged in!") {
$("#popup").css("width", "325px");
$("#popup").removeClass("green").addClass("red");
$("#popup-icon").removeClass("fa-circle-check").addClass("fa-triangle-exclamation");
$(".popup-container").css("display", "flex");
await sleep(5);
$("#popup").css("margin-top", "24px");
$("#popup").css("opacity", 1);
$("#popup-description").text("You're not logged in yet!");
await sleep(3000);
$("#popup").css("margin-top", "-10px");
$("#popup").css("opacity", 0);
await sleep(300);
$(".popup-container").css("display", "none");
$("#popup").removeClass("red").addClass("green");
$("#popup-icon").removeClass("fa-triangle-exclamation").addClass("fa-circle-check");
$("#popup").css("width", "300px");
return;
}
$("#options").css("display", "none");
$("#launching").css("display", "flex");
modlist = {};
await window.modmanager.fetchMods();
while(!gotmodlist) {await sleep(100);}
if(modlist !== "empty") {
if(JSON.stringify(modlist).includes("World Host")) {
if(JSON.stringify(modlist).includes("essential-container") && modlist[essentialdsmodjar]) {
if(modlist[essentialdsmodjar]["enabled"]) {
const essentialmodinfo = modlist[essentialdsmodjar]["enabled"];
const worldhostmodinfo = modlist[getKeysContaining(modlist, "world-host")]["enabled"];
if(essentialmodinfo && worldhostmodinfo) {
essentialsconflictresolved = false;
await window.game.resolveEssentialsConflict();
}
}
} else if (!modlist[getKeysContaining(modlist, "world-host")]["enabled"])
if(!JSON.stringify(modlist).includes("essential-container") || !modlist[essentialdsmodjar]["enabled"])
window.modmanager.toggleMod(getKeysContaining(modlist, "world-host")[0]);
}
}
while(!essentialsconflictresolved) {await sleep(100);}
gamerunning = true;
document.getElementById("mod-list").innerHTML = `<div class="loader-container" id="loader-container-gamerunning"><p>Game is running! We can't edit your mods while the game is running :(</p></div>`;
await window.game.launch(document.getElementById('ram-slider').value);
}
function getKeysContaining(obj, searchString) {
const regex = new RegExp(searchString);
return Object.keys(obj).filter(key => regex.test(key));
}
var logincomplete = false;
async function signIn() {
if(!loginfailure) await window.game.login(); else {window.alert("Something went wrong with the launcher. Please contact the development team."); return;}
$("#loader").css("display", "block");
$("#warning").css("display", "none");
$("#username").css("display", "none");
$("#head").css("display", "none");
while(!await window.game.checkLoginStatus()) {await sleep(100);}
await getAccount();
while(username === 0) {await sleep(100);}
}
async function accountError() {
$("#loader").css("display", "none");
$("#warning").css("display", "block");
$("#username").css("display", "block");
$("#whitelist-wallpaper").css('margin-left', '-2%');
$("#whitelist-wallpaper").css('opacity', 0);
$("#whitelist-container").css('margin-right', '-2%');
$("#whitelist-container").css('opacity', 0);
$("#loader-screen").css('transform', 'scale(1)');
$("#loader-screen").css('opacity', 1);
$("#loader-screen").css('display', 'flex')
$("#head").css("display", "none");
$("#username").text("Internal Error");
$("#soverlay-spinner").hide();
$("#launch-progress").text("Something went wrong while logging in :(");
$("#log-out-button-error").css('display', 'flex');
loginfailure = true;
}
var sleepSetTimeout_ctrl;
async function sleep(ms) {
clearInterval(sleepSetTimeout_ctrl);
return new Promise(resolve => sleepSetTimeout_ctrl = setTimeout(resolve, ms));
}
var accounterror = false;
async function getAccount() {
try {
await window.game.getAccount();
while(username === 0) {await sleep(100);}
if(username === "err") {accountError(); accounterror = true; return;}
if(username === "no_account") {
$("#loader").css("display", "none");
$("#warning").css("display", "block");
$("#username").css("display", "block");
return;
}
$("#loader").css("display", "none");
$("#username").text(username);
$("#head").attr("src", "https://mc-heads.net/head/" + username);
$("#username").css("display", "block");
$("#head").css("display", "block");
} catch {
accountError();
}
}
function guidGenerator() {
var S4 = function() {return (((1+Math.random())*0x10000)|0).toString(16).substring(1);};
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}
async function toggleMod(filename, id, slider_id) {
$(`#${id}`).prop("disabled", true);
$(`#${slider_id}`).addClass("disabled");
window.modmanager.toggleMod(filename);
if(filename.endsWith(".jar")) {
const key = filename + ".disabled";
$(`#${id}`).attr("onchange", `toggleMod('${key}', '${id}', '${slider_id}')`);
} else if (filename.endsWith(".jar.disabled")) {
const key = filename.slice(0, -9);
$(`#${id}`).attr("onchange", `toggleMod('${key}', '${id}', '${slider_id}')`);
} else {
window.alert("Failed to toggle mod. Contact developers.");
}
await new Promise(r => setTimeout(r, 1500));
console.log("[INFO] Toggled mod " + filename);
$(`#${id}`).prop("disabled", false);
$(`#${slider_id}`).removeClass("disabled");
}
async function deleteMod(filename) {window.modmanager.deleteMod(filename);}
async function showInFolder(filename) {window.modmanager.showInFolder(filename);}
var modlist = {};
async function applyModList(list) {
gotmodlist = true;
if(list === "empty") {
console.log(list);
$("#loader-container").hide();
$("#loader-container-nomods").css("display", "flex");
return;
}
modlist = list;
console.log("[INFO] Got mod list!");
$("#loader-container").hide();
console.log(list);
Object.entries(list).forEach((entry) => {
const [key, value] = entry;
const name = value["name"];
const desc = value["desc"];
const enabled = value["enabled"];
const icon = value["icon"];
const id = guidGenerator();
const id2 = guidGenerator();
var essential = null; var essentialclass = null; if(value["essential"] === true) {essential = "disabled"; essentialclass = "disabled";}
if(enabled) {
if(icon) {
$("#mod-list").append(`<div class="mod"><img src="${"data:image/jpg;base64," + icon}" height="48" width="48" class="icon"><div class="text"><p class="name">${name}</p><p class="desc">${desc}</p></div><div class="options"><i class="fa-solid fa-trash-can" onclick="deleteMod('${key}')"></i><i class="fa-solid fa-folder" onclick="showInFolder('${key}')"></i><label class="switch"><input type="checkbox" id="${id}" checked onChange="toggleMod('${key}', '${id}', '${id2}')" ${essential}><span id="${id2}" class="slider2 round ${essentialclass}"></span></label></div></div>`)
} else {
$("#mod-list").append(`<div class="mod"><img src="resource/default-mod-icon.png" height="48" width="48" class="icon"><div class="text"><p class="name">${name}</p><p class="desc">${desc}</p></div><div class="options"><i class="fa-solid fa-trash-can" onclick="deleteMod('${key}')"></i><i class="fa-solid fa-folder" onclick="showInFolder('${key}')"></i><label class="switch"><input type="checkbox" id="${id}" checked onChange="toggleMod('${key}', '${id}', '${id2}')" ${essential}><span id="${id2}" class="slider2 round ${essentialclass}"></span></label></div></div>`)
}
} else {
if(icon) {
$("#mod-list").append(`<div class="mod"><img src="${"data:image/jpg;base64," + icon}" height="48" width="48" class="icon"><div class="text"><p class="name">${name}</p><p class="desc">${desc}</p></div><div class="options"><i class="fa-solid fa-trash-can" onclick="deleteMod('${key}')"></i><i class="fa-solid fa-folder" onclick="showInFolder('${key}')"></i><label class="switch"><input type="checkbox" id="${id}" onChange="toggleMod('${key}', '${id}', '${id2}')" ${essential}><span id="${id2}" class="slider2 round ${essentialclass}"></span></label></div></div>`)
} else {
$("#mod-list").append(`<div class="mod"><img src="resource/default-mod-icon.png" height="48" width="48" class="icon"><div class="text"><p class="name">${name}</p><p class="desc">${desc}</p></div><div class="options"><i class="fa-solid fa-trash-can" onclick="deleteMod('${key}')"></i><i class="fa-solid fa-folder" onclick="showInFolder('${key}')"></i><label class="switch"><input type="checkbox" id="${id}" onChange="toggleMod('${key}', '${id}', '${id2}')" ${essential}><span id="${id2}" class="slider2 round ${essentialclass}"></span></label></div></div>`)
}
}
});
}
var altlogin = false;
async function altLogin() {
altlogin = true;
$("#sign-in-with-microsoft").text('');
$("#sign-in-with-microsoft").append('<span class="alt-login-spinner" id="alt-login-spinner" style="display: inline-block !important;"></span>');
$("#alt-login-image").css('display', 'none');
$("#sign-in-with-microsoft").removeClass('enabled');
await signIn();
while(!await window.game.checkLoginStatus()) {await sleep(100);}
//await window.game.getAccount();
await window.game.checkWhitelistStatus();
}
var logResponse = null;
try {
window.electronAPI.sendMessage(async (json) => {
if(json['message'] === "ram.capacity") {
console.log("[INFO] Setting max RAM to " + json['totalram'] + " GB");
document.getElementById("ram-slider").setAttribute("max", json['totalram']);
$("#page-settings > div:nth-child(3) > div.info > div > p.max").text(json['totalram'] + " GB");
document.getElementById("ram-slider").setAttribute("value", json['allocated']);
$("#mem-size").text(json['allocated'] + " GB")
return;
}
if(json['message'] === "token.refresh") {
logincomplete = true;
return;
}
if(json['message'] === "conflict.essentials.resolved") {
// ** key ** >>> json['mod'] => essential, worldhost, ignore
if(json['mod'] === "essential") {window.modmanager.toggleMod(getKeysContaining(modlist, "Essential")[0]);}
else if (json['mod'] === "worldhost") {window.modmanager.toggleMod(getKeysContaining(modlist, "world-host")[0]);}
essentialsconflictresolved = true;
return;
}
if(json['message'] === "whitelist.success") {
whitelisted = true;
while($("#username").text() === "Not logged in!") {await sleep(100);}
$("#whitelist-wallpaper").css('margin-left', '-2%');
$("#whitelist-wallpaper").css('opacity', 0);
$("#whitelist-container").css('margin-right', '-2%');
$("#whitelist-container").css('opacity', 0);
hideSOverlay(false);
return;
}
if(json['message'] === "whitelist.fail") {
whitelisted = false;
$("#sign-in-with-microsoft").remove();
$("#not-whitelisted").css('display', 'flex');
$("#button-list").css('display', 'flex');
return;
}
if(json['message'] === "essentialsjar") {
essentialdsmodjar = json['response'];
return;
}
if(json['message'] === "modlist") {
applyModList(json['response']);
return;
}
if(json['message'] === "modmanager.addedmod") {
window.modmanager.fetchMods();
document.getElementById("mod-list").innerHTML = '';
$("#mod-list").append(`<div class="loader-container" id="loader-container"><span class="loader-small"></span></div>`);
return;
}
if(json['message'] === "internal.login-fail") {
window.alert("Something went wrong while logging in to your Minecraft account:\n\n" + json['response']);
$("#loader").css("display", "none");
$("#warning").css("display", "block");
$("#username").css("display", "block");
$("#head").css("display", "none");
$("#username").text("Couldn't log in.");
return;
}
if(json['message'] === "internal.login-success") {
username = json['response'];
return;
}
if(json['message'] === "internal.log-response") {
logResponse = json['response'];
return;
}
$("#launch-progress").text(json['message'])
if(json['message'] === "err.update_failed") {
$("#launch-progress").text("Woah... Something didn't quite go to plan.");
setTimeout(function() {hideSOverlay(true);}, 2000);
}
if(json['message'] === "Preparing the launcher...") {
setTimeout(function() {hideSOverlay(false);}, 250);
};
})
} catch {
$("#changelog-box").hide();
//$("#startup-overlay").hide();
}
window.electronAPI.news((json) => {
Object.entries(json).forEach((entry) => {
const [key, value] = entry;
const head = value["title"];
const content = value["content"];
var image = value["icon"];
if(image === "[default]") {image = "resource/news-image-placeholder.png"}
$("#news-div").append(`<div class="article"><span class="heading">${head}</span><p class="text">${content}</p><img src="${image}" class="image"></div>`);
});
});
window.electronAPI.modslist((json) => {
});
window.electronAPI.featuredServers((json) => {
Object.entries(json).forEach((entry) => {
const [key, value] = entry;
const ip = value["ip"];
const icon = value["icon"];
$("#featured-servers-div").append(`<div class='server' onclick="copyIp('${ip}')"><img src="${icon}" class="icon"><span class="ip">${ip}</span></div>`);
});
});
var partners = null;
window.electronAPI.partners((json) => {
partners = json;
Object.entries(json).forEach((entry) => {
const [key, value] = entry;
const type = value["type"];
const icon = value["icon"];
if(type === "Creator") {
$("#partners-div").append(`<div class="partner" onclick="showPartnerOverlay('creator', '${key}');"><img class="head" alt="head" src="${icon}"><p class="username">${key}</p><p class="type">${type}</p></div>`);
} else {
$("#partners-div").append(`<div class="partner" onclick="showPartnerOverlay('server', '${key}');"><img class="head" alt="head" src="${icon}"><p class="username">${key}</p><p class="type">${type}</p></div>`);
}
});
});
async function retryWhitelist() {
$('#not-whitelisted-retry').text('');
$('#not-whitelisted-retry').append(`<span class='alt-login-spinner' id='alt-login-spinner' style='display: block !important;'></button>`);
await sleep(200);
scanned = false;
hideSOverlay(false);
$('#not-whitelisted-retry').text('Retry');
}
var scanned = false;
var whitelisted = null;
async function hideSOverlay(showerror) {
if(accounterror === true) {return;}
while(username === 0) {await sleep(50);}
if(username === "[NOT LOGGED IN]") {
$("#loader-screen").css('transform', 'scale(0.8)');
$("#loader-screen").css('opacity', 0);
$("#whitelist-wallpaper").css('margin-left', 0);
$("#whitelist-wallpaper").css('opacity', 1);
$("#whitelist-container").css('margin-right', 0);
$("#whitelist-container").css('opacity', 1);
await sleep(500);
$("#loader-screen").css('display', 'none');
return;
}
if(scanned === false) {
scanned = true;
await window.game.checkWhitelistStatus();
while(whitelisted === null) {await sleep(100);}
if(whitelisted === false) {
$("#loader-screen").css('transform', 'scale(0.8)');
$("#loader-screen").css('opacity', 0);
$("#whitelist-wallpaper").css('margin-left', 0);
$("#whitelist-wallpaper").css('opacity', 1);
$("#whitelist-container").css('margin-right', 0);
$("#whitelist-container").css('opacity', 1);
$("#button-list").css('display', 'flex');
await sleep(500);
$("#loader-screen").css('display', 'none');
return;
}
}
$("#loader-screen").css('transform', 'scale(0.8)');
$("#loader-screen").css('opacity', 0);
$("#startup-overlay").css('opacity', 0);
await sleep(501);
$("#startup-overlay").css("display", "none");
if(showerror) {updateError();}
}
async function updateError() {
$("#popup").css("width", "325px");
$("#popup").removeClass("green").addClass("red");
$("#popup-icon").removeClass("fa-circle-check").addClass("fa-triangle-exclamation");
$(".popup-container").css("display", "flex");
await sleep(1);
$("#popup").css("margin-top", "24px");
$("#popup").css("opacity", 1);
$("#popup-description").text("Failed to check for updates!");
await sleep(3000);
$("#popup").css("margin-top", "-10px");
$("#popup").css("opacity", 0);
await sleep(300);
$(".popup-container").css("display", "none");
$("#popup").removeClass("red").addClass("green");
$("#popup-icon").removeClass("fa-triangle-exclamation").addClass("fa-circle-check");
$("#popup").css("width", "300px");
}
function bytesToMB(bytes) {
return (bytes / (1024 * 1024)).toFixed(2); // 1 MB = 1024 * 1024 bytes
}
window.electronAPI.sendProgress((json) => {
$("#launching").css("display", "none");
$("#launching-progress").css("display", "block");
if(json['type'] === "error") {
$("#progresstext").text("X_X");
$("#txtprogress").text(`Something didn't go to plan...`);
window.alert("Something went wrong during the launch!\n\n" + json['task']);
}
if(json['type'] === "java") {
$("#progresstext").text("Downloading Java...");
$("#progressbar").attr("value", json['task']);
$("#progressbar").attr("max", json['total']);
$("#txtprogress").text(`${bytesToMB(json['task'])} MB / ${bytesToMB(json['total'])} MB`)
}
if(json['type'] === "java-extract") {
$("#progresstext").text("Downloading Java...");
$("#progressbar").attr("value", json['task']);
$("#progressbar").attr("max", json['total']);
$("#txtprogress").text(`Extracting Java, this may take a moment...`)
}
if(json['type'] === "mods") {
$("#progresstext").text("Downloading mods...");
$("#progressbar").attr("value", json['task']);
$("#progressbar").attr("max", json['total']);
$("#txtprogress").text(`${json['task']}/${json['total']}`)
}
if(json['type'] === "waitforgame") {
$("#progresstext").text("Waiting for game...");
$("#progressbar").attr("value", json['task']);
$("#progressbar").attr("max", json['total']);
$("#txtprogress").text(`This may take a minute...`)
}
if(json['type'] === "assets") {
$("#progresstext").text("Downloading assets...");
$("#progressbar").attr("value", json['task']);
$("#progressbar").attr("max", json['total']);
$("#txtprogress").text(`${json['task']}/${json['total']}`)
}
if(json['type'] === "launching") {
$("#launching").css("display", "flex");
$("#launching-progress").css("display", "none");
}
if(json['type'] === "stopped") {
$("#launching").css("display", "flex");
$("#launching-progress").css("display", "none");
$("#options").css("display", "block");
$("#launching").css("display", "none");
gamerunning = false;
hideOverlay();
}
})
async function popupSuccess(text, width) {
$("#popup").css("width", width);
$("#popup-icon").removeClass("fa-triangle-exclamation").addClass("fa-circle-check");
$("#popup").removeClass("red").addClass("green");
$(".popup-container").css("display", "flex");
await sleep(1);
$("#popup").css("margin-top", "24px");
$("#popup").css("opacity", 1);
$("#popup-description").text(text);
await sleep(3000);
$("#popup").css("margin-top", "-10px");
$("#popup").css("opacity", 0);
await sleep(300);
$(".popup-container").css("display", "none");
}
async function copyIp(ip) {
navigator.clipboard.writeText(ip);
popupSuccess("IP copied to clipboard", "300px");
}
async function copyLink(link) {
window.link.open(link);
}
async function hidePopup() {
$("#popup").css("margin-top", "-10px");
$("#popup").css("opacity", 0);
await sleep(300);
$(".popup-container").css("display", "none");
}
async function run() {
await window.launcher.run();
grabChangelog();
//getAccount();
var response = await window.launcher.update();
if(response) {
$("#pastebinButton").prop("disabled", true);
$("#pastebinButton").addClass("disabled");
$("#pastebinButton").text("ID: " + url);
}
}
async function openStore() {
await window.socialmedia.store();
popupSuccess("Opened in browser!", "250px");
}
async function openTwitter() {
await window.socialmedia.twitter();
popupSuccess("Opened in browser!", "250px");
}
async function openDiscord() {
await window.socialmedia.discord();
popupSuccess("Opened in browser!", "250px");
}
var currentpage = "home"
async function switchPage(page) {
if(currentpage === page) {return;}
if(page === "home") {
$("#button-home").addClass("selected");
$(`#button-${currentpage}`).removeClass("selected");
$("#page-home").css("display", "block");
$("#page-customization").css("display", "none");
$("#page-store").css("display", "none");
$("#page-settings").css("display", "none");
currentpage = "home";
}
if(page === "customization") {
$("#button-customization").addClass("selected");
$(`#button-${currentpage}`).removeClass("selected");
$("#page-store").css("display", "none");
$("#page-home").css("display", "none");
$("#page-customization").css("display", "flex");
$("#page-settings").css("display", "none");
currentpage = "customization";
}
if(page === "settings") {
$("#button-settings").addClass("selected");
$(`#button-${currentpage}`).removeClass("selected");
$("#page-store").css("display", "none");
$("#page-home").css("display", "none");
$("#page-customization").css("display", "none");
$("#page-settings").css("display", "block");
currentpage = "settings";
}
}
async function grabChangelog() {
var changelog = await window.launcher.getChangelog();
if(changelog === "no more :(") {
$('#changelog-box').css('display', 'none');
return;
}
fetch("https://cdn.toriclient.com/launcher/changelog.txt").then(function(response) {
response.text().then(function(text) {
storedText = text;
done();
});
});
function done() {
$("#changelog").text(storedText);
}
//$("#changelog").text(changelog);
}
async function openFolder(mods) {var response = await window.settings.openFolder(mods); if(response) {window.alert(response);} else {popupSuccess("Folder opened!", "200px");}}
async function pastebin() {
$("#pastebinButton").prop("disabled", true);
$("#pastebinButton").addClass("disabled");
await window.settings.pasteBin();
while(logResponse === null) {await sleep(100);}
if(logResponse === "general_fail") {
window.alert("Failed to upload your log. Please try again later.");
return;
} else if (logResponse['status'] != "OK") {
window.alert("Failed to upload the log. Consider relaunching or contacting the development team.\n\nError: " + logResponse['error']);
return;
}
const id = logResponse['id']
$("#pastebinButton").text("ID: " + id);
window.alert("Your log ID is:\n\n" + id + "\n\nPlease open a support ticket in the Discord and provide this ID so we can assist you. Thank you!");
}
async function logOut() {
var response = await window.settings.logout();
accounterror = false;
if(response === "done") {
$("#logoutButton").prop("disabled", true);
$("#logoutButton").addClass("disabled");
$("#logoutButton").text("Logged out.");
await popupSuccess("Launcher restarting in 3 seconds!", "350px");
window.location.reload();
} else {
if(response === "not_logged_in") {
$("#popup").css("width", "325px");
$("#popup").removeClass("green").addClass("red");
$("#popup-icon").removeClass("fa-circle-check").addClass("fa-triangle-exclamation");
$(".popup-container").css("display", "flex");
await sleep(5);
$("#popup").css("margin-top", "24px");
$("#popup").css("opacity", 1);
$("#popup-description").text("Can't log out, not logged in!");
await sleep(3000);
$("#popup").css("margin-top", "-10px");
$("#popup").css("opacity", 0);
await sleep(300);
$(".popup-container").css("display", "none");
$("#popup").removeClass("red").addClass("green");
$("#popup-icon").removeClass("fa-triangle-exclamation").addClass("fa-circle-check");
$("#popup").css("width", "300px");
return
}
window.alert("Something went wrong. Please contact the development team.\nError: " + response);
}
}
async function showPartnerOverlay(type, username) {
if(type === "creator") {
$("#partner-creator-yt").remove();
$("#partner-creator-discord").remove();
$("#partner-creator-x").remove();
$("#partner-overlay-creator").show();
$("#partner-creator-head").attr('src', partners[username]['icon']);
$("#partner-creator-username").text(username);
$("#partner-creator-type").text(partners[username]['type']);
if(partners[username]['socials']['youtube']) {
$("#partner-creator-sm").append(`<i class="fa-brands fa-youtube" style="color: #ff2100;" id="partner-creator-yt" onclick="copyLink('${partners[username]['socials']['youtube']}')"></i>`)
}
if(partners[username]['socials']['discord']) {
$("#partner-creator-sm").append(`<i class="fa-brands fa-discord" style="color: #5662f6;" id="partner-creator-discord" onclick="copyLink('${partners[username]['socials']['discord']}')"></i>`)
}
if(partners[username]['socials']['x']) {
$("#partner-creator-sm").append(`<i class="fa-brands fa-x-twitter" id="partner-creator-x" onclick="copyLink('${partners[username]['socials']['x']}')"></i>`)
}
}
if(type === "server") {
$("#partner-server-yt").remove();
$("#partner-server-discord").remove();
$("#partner-server-x").remove();
$("div[id=server-members]").remove();
$(".people").remove();
$("#partner-overlay-server").show();
$("#partner-server-head").attr('src', partners[username]['icon']);
$("#partner-server-username").text(username);
$("#partner-server-type").text(partners[username]['type']);
if(partners[username]['socials']['youtube']) {
$("#partner-server-sm").append(`<i class="fa-brands fa-youtube" style="color: #ff2100;" id="partner-server-yt" onclick="copyLink('${partners[username]['socials']['youtube']}')"></i>`)
}
if(partners[username]['socials']['discord']) {
$("#partner-server-sm").append(`<i class="fa-brands fa-discord" style="color: #5662f6;" id="partner-server-discord" onclick="copyLink('${partners[username]['socials']['discord']}')"></i>`)
}
if(partners[username]['socials']['x']) {
$("#partner-server-sm").append(`<i class="fa-brands fa-x-twitter" id="partner-server-x" onclick="copyLink('${partners[username]['socials']['x']}')"></i>`)
}
if(partners[username]['people']) {
$("#partner-server-details").append(`<p class="people">People</p>`);
Object.entries(partners[username]['people']).forEach((entry) => {
const [key, value] = entry;
$("#partner-server-details").append(`<div class="member" id="server-members"><img class="head" alt="head" src="${value['icon']}"><p class="username">${key}</p><p class="role">${value['role']}</p></div>`);
});
}
}
}
async function removePartnerOverlay(type) {
if(type === "creator") {
$("#partner-overlay").remove();
$("#partner-overlay-creator").hide();
}
if(type === "server") {
$("#partner-overlay").remove();
$("#partner-overlay-server").hide();
}
}
// these should go last!! (2 func.)
run();
getAccount();

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
Launcher/resource/ffatl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
Launcher/resource/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
Launcher/resource/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 KiB

1599
Launcher/style.css Normal file

File diff suppressed because it is too large Load diff