Compare commits
121
Commits
1.0.0
..
f046746037
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f046746037 | ||
|
|
74e233ecc3 | ||
|
|
a848f078be | ||
|
|
9f71ad0f8e | ||
|
|
45c5b329b5 | ||
|
|
b0f180f1fb | ||
|
|
6d42b4fe89 | ||
|
|
b8f45ad0b8 | ||
|
|
cd8f42327c | ||
|
|
6881a0d1d5 | ||
|
|
9f9804f161 | ||
|
|
86c9bc4a60 | ||
|
|
3bd1fb9edb | ||
|
|
8a8856772e | ||
|
|
80343bd929 | ||
|
|
9da481b991 | ||
|
|
70643c4287 | ||
|
|
db03ed552f | ||
|
|
6ccc285c61 | ||
|
|
a72ae1df5f | ||
|
|
9fbab69206 | ||
|
|
ecf329dd69 | ||
|
|
4e79809c19 | ||
|
|
f06e63f7f8 | ||
|
|
638f1e852e | ||
|
|
8b30b7c1fa | ||
|
|
2bb4a86df3 | ||
|
|
f41ca92711 | ||
|
|
65a3e86261 | ||
|
|
c74bc57453 | ||
|
|
528af70446 | ||
|
|
6f2764a047 | ||
|
|
1c4a40c495 | ||
|
|
144d1bb86a | ||
|
|
a9de377456 | ||
|
|
c3f0882951 | ||
|
|
c17cbc48d8 | ||
|
|
513692c2d5 | ||
|
|
90717ed960 | ||
|
|
6bcce04b72 | ||
|
|
8d2024aa0a | ||
|
|
22815e74b6 | ||
|
|
c4d9862860 | ||
|
|
d56d4875e0 | ||
|
|
4c33323e9e | ||
|
|
da8e237679 | ||
|
|
eb166c0165 | ||
|
|
c4cd8fed12 | ||
|
|
6bca0b32a9 | ||
|
|
72d466b1ec | ||
|
|
401be3c977 | ||
|
|
0b679d320f | ||
|
|
5b1407638a | ||
|
|
d2d2a618c5 | ||
|
|
b85a3a8795 | ||
|
|
fc6ec3bd69 | ||
|
|
4c26689f4e | ||
|
|
3d2ebdb640 | ||
|
|
9f3c4d4fa4 | ||
|
|
0567d57cb5 | ||
|
|
d907553f3e | ||
|
|
0e0676917a | ||
|
|
51c4be0873 | ||
|
|
8b4fb07f21 | ||
|
|
3b658fd8de | ||
|
|
2329122ef0 | ||
|
|
4348c36fa0 | ||
|
|
8c09719b43 | ||
|
|
03b352bc15 | ||
|
|
0f5eab873f | ||
|
|
75d444b1d5 | ||
|
|
779ff0c009 | ||
|
|
dc88e9f2cc | ||
|
|
bf8a458b5f | ||
|
|
3570582c6b | ||
|
|
b9bd87c099 | ||
|
|
705e47a1b8 | ||
|
|
397c2255c1 | ||
|
|
00d71704ab | ||
|
|
f631c78805 | ||
|
|
8d61a8b2b7 | ||
|
|
61d5182854 | ||
|
|
cef085c13d | ||
|
|
a02737ef1b | ||
|
|
ae566f09a0 | ||
|
|
01905d695a | ||
|
|
94fa7e7bd8 | ||
|
|
d5b2b8012c | ||
|
|
05866946a3 | ||
|
|
0fc9cb828b | ||
|
|
e119166c65 | ||
|
|
6aa0a6c969 | ||
|
|
6369c0e8af | ||
|
|
f11e84efd7 | ||
|
|
d9fec1193e | ||
|
|
2c6b51cbb2 | ||
|
|
756ae1be58 | ||
|
|
4569d35a65 | ||
|
|
1adb1b7dec | ||
|
|
ad2c91db4d | ||
|
|
3588203527 | ||
|
|
dfa933fb87 | ||
|
|
2da2c9fa7e | ||
|
|
145f36f39b | ||
|
|
67cf564b01 | ||
|
|
4bd130aad9 | ||
|
|
d0937f626d | ||
|
|
05b9ddd5a1 | ||
|
|
6790fe5ef6 | ||
|
|
1f1dc65a97 | ||
|
|
bbc3d7ed67 | ||
|
|
917edf0eb3 | ||
|
|
cfeda18b83 | ||
|
|
0eb42c138b | ||
|
|
a4faa9dd1a | ||
|
|
800a424b67 | ||
|
|
873aeaf921 | ||
|
|
310c206367 | ||
|
|
895b71ef16 | ||
|
|
e8f0027424 | ||
|
|
c6fa540575 |
No files matched your search
+23
-2
@@ -1,3 +1,24 @@
|
|||||||
node_modules/
|
# Logs
|
||||||
release/
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Lock files
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Svelte Distribution
|
||||||
|
public/build/
|
||||||
|
|
||||||
|
# Electron Distribution
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Project Build Automation Directory
|
||||||
|
private
|
||||||
|
|
||||||
|
# Desktop Services Store on macOS
|
||||||
|
.DS_Store
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["svelte.svelte-vscode"]
|
||||||
|
}
|
||||||
@@ -219,8 +219,8 @@ If you develop a new program, and you want it to be of the greatest possible use
|
|||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
|
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
<one line to give the program's name and a brief idea of what it does.>
|
EZPPLauncher
|
||||||
Copyright (C) <year> <name of author>
|
Copyright (C) 2024 HorizonCode
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
|||||||
@@ -5,19 +5,23 @@ Welcome to the EZPPLauncher! A new way to connect to the EZPPFarm server.
|
|||||||
Just one click and you are ready to go!
|
Just one click and you are ready to go!
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
The Launcher is a "plug and play thing", download it, place it on the desktop and execute!
|
The Launcher is a "plug and play thing", download it, place it on the desktop and execute!
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Automatic osu! client updating before Launch
|
- Automatic osu! client updating before Launch
|
||||||
* Account saving (soon)
|
- Custom osu! Logo in MainMenu
|
||||||
|
- Relax misses and much more
|
||||||
|
- Account saving
|
||||||
|
|
||||||
## Used Libraries
|
## Build from source
|
||||||
|
|
||||||
* [Electron](https://www.npmjs.com/package/electron)
|
- clone repo
|
||||||
* [custom-electron-titlebar](https://www.npmjs.com/package/custom-electron-titlebar)
|
- cd into the repo
|
||||||
* [axios](https://www.npmjs.com/package/axios)
|
- use `npm i` to install all dependencies
|
||||||
* [jquery](https://www.npmjs.com/package/jquery)
|
- use the buildscript `electron-pack` to build a executeable
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[AGPL-3.0](https://choosealicense.com/licenses/agpl-3.0/)
|
[AGPL-3.0](https://choosealicense.com/licenses/agpl-3.0/)
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
|
||||||
const { setupTitlebar, attachTitlebarToWindow } = require('custom-electron-titlebar/main');
|
|
||||||
const windowManager = require('./ui/windowManager');
|
|
||||||
const osuUtil = require('./osuUtil');
|
|
||||||
const config = require('./config');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
let tempOsuPath;
|
|
||||||
|
|
||||||
const run = () => {
|
|
||||||
const gotTheLock = app.requestSingleInstanceLock()
|
|
||||||
if (!gotTheLock) {
|
|
||||||
app.quit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setupTitlebar();
|
|
||||||
|
|
||||||
let mainWindow;
|
|
||||||
app.whenReady().then(() => {
|
|
||||||
|
|
||||||
mainWindow = createWindow();
|
|
||||||
mainWindow.on('show', async () => {
|
|
||||||
await doUpdateCheck(mainWindow);
|
|
||||||
})
|
|
||||||
app.on('activate', function () {
|
|
||||||
if (BrowserWindow.getAllWindows().length === 0) mainWindow = createWindow();
|
|
||||||
})
|
|
||||||
app.on('window-all-closed', () => {
|
|
||||||
app.quit()
|
|
||||||
})
|
|
||||||
ipcMain.handle('launch', async () => {
|
|
||||||
const result = await osuUtil.startOsuWithDevServer(tempOsuPath, "ez-pp.farm", async () => {
|
|
||||||
await doUpdateCheck(mainWindow);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
})
|
|
||||||
ipcMain.on('do-update-check', async () => {
|
|
||||||
await doUpdateCheck(mainWindow);
|
|
||||||
})
|
|
||||||
ipcMain.on('do-update', async () => {
|
|
||||||
const osuPath = await config.get("osuPath", "");
|
|
||||||
const isValid = await osuUtil.isValidOsuFolder(osuPath);
|
|
||||||
if (osuPath.trim == "" || !isValid) {
|
|
||||||
mainWindow.webContents.send('status_update', {
|
|
||||||
type: "error",
|
|
||||||
message: "Invalid osu! folder"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fs.existsSync(osuPath)) {
|
|
||||||
tempOsuPath = osuPath;
|
|
||||||
const osuConfig = await osuUtil.getLatestConfig(tempOsuPath);
|
|
||||||
const lastVersion = await osuConfig.get("LastVersion");
|
|
||||||
let releaseStream = "stable40";
|
|
||||||
if (lastVersion.endsWith("cuttingedge"))
|
|
||||||
releaseStream = "cuttingedge"
|
|
||||||
else if (lastVersion.endsWith("beta"))
|
|
||||||
releaseStream = "beta";
|
|
||||||
|
|
||||||
const releaseFiles = await osuUtil.getUpdateFiles(releaseStream);
|
|
||||||
const filesToDownload = await osuUtil.filesThatNeedUpdate(tempOsuPath, releaseFiles);
|
|
||||||
const downloadTask = await osuUtil.downloadUpdateFiles(osuPath, filesToDownload);
|
|
||||||
downloadTask.on('completed', () => {
|
|
||||||
mainWindow.webContents.send('status_update', {
|
|
||||||
type: "update-complete"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
} else
|
|
||||||
mainWindow.webContents.send('status_update', {
|
|
||||||
type: "error",
|
|
||||||
message: "Invalid osu! folder"
|
|
||||||
});
|
|
||||||
})
|
|
||||||
ipcMain.handle('set-osu-dir', async (event) => {
|
|
||||||
const yes = await dialog.showOpenDialog({
|
|
||||||
properties: ['openDirectory']
|
|
||||||
})
|
|
||||||
if (yes.filePaths.length <= 0)
|
|
||||||
return undefined;
|
|
||||||
const folderPath = yes.filePaths[0];
|
|
||||||
const validOsuDir = await osuUtil.isValidOsuFolder(folderPath);
|
|
||||||
|
|
||||||
if (validOsuDir) await config.set("osuPath", folderPath);
|
|
||||||
|
|
||||||
return validOsuDir;
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doUpdateCheck(window) {
|
|
||||||
const osuPath = await config.get("osuPath", "");
|
|
||||||
const isValid = await osuUtil.isValidOsuFolder(osuPath);
|
|
||||||
if (osuPath.trim == "" || !isValid) {
|
|
||||||
window.webContents.send('status_update', {
|
|
||||||
type: "missing-folder"
|
|
||||||
})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (fs.existsSync(osuPath)) {
|
|
||||||
tempOsuPath = osuPath;
|
|
||||||
const osuConfig = await osuUtil.getLatestConfig(tempOsuPath);
|
|
||||||
const lastVersion = await osuConfig.get("LastVersion");
|
|
||||||
let releaseStream = "stable40";
|
|
||||||
if (lastVersion.endsWith("cuttingedge"))
|
|
||||||
releaseStream = "cuttingedge"
|
|
||||||
else if (lastVersion.endsWith("beta"))
|
|
||||||
releaseStream = "beta";
|
|
||||||
|
|
||||||
const releaseFiles = await osuUtil.getUpdateFiles(releaseStream);
|
|
||||||
const filesToDownload = await osuUtil.filesThatNeedUpdate(tempOsuPath, releaseFiles);
|
|
||||||
window.webContents.send('status_update', {
|
|
||||||
type: filesToDownload.length > 0 ? "update-available" : "up-to-date"
|
|
||||||
})
|
|
||||||
} else
|
|
||||||
window.webContents.send('status_update', {
|
|
||||||
type: "missing-folder"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function createWindow() {
|
|
||||||
// Create the browser window.
|
|
||||||
const win = windowManager.createWindow(520, 350);
|
|
||||||
|
|
||||||
win.loadFile('./html/index.html');
|
|
||||||
|
|
||||||
attachTitlebarToWindow(win);
|
|
||||||
win.webContents.setWindowOpenHandler(() => "deny");
|
|
||||||
win.webContents.on('did-finish-load', function () {
|
|
||||||
if (win.webContents.getZoomFactor() != 0.9)
|
|
||||||
win.webContents.setZoomFactor(0.9)
|
|
||||||
});
|
|
||||||
|
|
||||||
return win;
|
|
||||||
}
|
|
||||||
|
|
||||||
run();
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
const appName = "EZPPLauncher"
|
|
||||||
const appVersion = "1.0.0";
|
|
||||||
|
|
||||||
module.exports = { appName, appVersion };
|
|
||||||
Vendored
-23
@@ -1,23 +0,0 @@
|
|||||||
:root{--mdb-blue:#0d6efd;--mdb-indigo:#6610f2;--mdb-purple:#6f42c1;--mdb-pink:#d63384;--mdb-red:#dc3545;--mdb-orange:#fd7e14;--mdb-yellow:#ffc107;--mdb-green:#198754;--mdb-teal:#20c997;--mdb-cyan:#0dcaf0;--mdb-gray:#757575;--mdb-gray-dark:#4f4f4f;--mdb-gray-100:#f5f5f5;--mdb-gray-200:#eee;--mdb-gray-300:#e0e0e0;--mdb-gray-400:#bdbdbd;--mdb-gray-500:#9e9e9e;--mdb-gray-600:#757575;--mdb-gray-700:#616161;--mdb-gray-800:#4f4f4f;--mdb-gray-900:#262626;--mdb-primary:#1266f1;--mdb-secondary:#b23cfd;--mdb-success:#00b74a;--mdb-info:#39c0ed;--mdb-warning:#ffa900;--mdb-danger:#f93154;--mdb-light:#f9f9f9;--mdb-dark:#262626;--mdb-white:#fff;--mdb-black:#000;--mdb-primary-rgb:18,102,241;--mdb-secondary-rgb:178,60,253;--mdb-success-rgb:0,183,74;--mdb-info-rgb:57,192,237;--mdb-warning-rgb:255,169,0;--mdb-danger-rgb:249,49,84;--mdb-light-rgb:249,249,249;--mdb-dark-rgb:38,38,38;--mdb-white-rgb:255,255,255;--mdb-black-rgb:0,0,0;--mdb-body-color-rgb:79,79,79;--mdb-body-bg-rgb:255,255,255;--mdb-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--mdb-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--mdb-gradient:linear-gradient(180deg,hsla(0,0%,100%,0.15),hsla(0,0%,100%,0));--mdb-body-font-family:var(--mdb-font-roboto);--mdb-body-font-size:1rem;--mdb-body-font-weight:400;--mdb-body-line-height:1.6;--mdb-body-color:#4f4f4f;--mdb-body-bg:#fff}*,:after,:before{box-sizing:border-box}@media(prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--mdb-body-font-family);font-size:var(--mdb-body-font-size);font-weight:var(--mdb-body-font-weight);line-height:var(--mdb-body-line-height);color:var(--mdb-body-color);text-align:var(--mdb-body-text-align);background-color:var(--mdb-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media(min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media(min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media(min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media(min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-mdb-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#1266f1;text-decoration:underline}a:hover{color:#0e52c1}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--mdb-font-monospace);font-size:1em;/*!rtl:ignore*/direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#262626;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:1rem;padding-bottom:1rem;color:#757575;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border:0 solid;border-color:inherit}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;foLine truncated
|
|
||||||
[type="tel"],
|
|
||||||
[type="url"],
|
|
||||||
[type="email"],
|
|
||||||
[type="number"] {
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
*/::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width:1200px){.display-6{font-size:2.5rem}}.list-inline,.list-unstyled{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#757575}.blockquote-footer:before{content:"— "}.img-fluid,.img-thumbnail{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #e0e0e0;border-radius:.25rem}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#757575}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--mdb-gutter-x,.75rem);padding-left:var(--mdb-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media(min-width:576px){.container,.container-sm{max-width:540px}}@media(min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media(min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media(min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media(min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--mdb-gutter-x:1.5rem;--mdb-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--mdb-gutter-y)*-1);margin-right:calc(var(--mdb-gutter-x)*-0.5);margin-left:calc(var(--mdb-gutter-x)*-0.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--mdb-gutter-x)*0.5);padding-left:calc(var(--mdb-gutter-x)*0.5);margin-top:var(--mdb-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--mdb-gutter-x:0}.g-0,.gy-0{--mdb-gutter-y:0}.g-1,.gx-1{--mdb-gutter-x:0.25rem}.g-1,.gy-1{--mdb-gutter-y:0.25rem}.g-2,.gx-2{--mdb-gutter-x:0.5rem}.g-2,.gy-2{--mdb-gutter-y:0.5rem}.g-3,.gx-3{--mdb-gutter-x:1rem}.g-3,.gy-3{--mdb-gutter-y:1rem}.g-4,.gx-4{--mdb-gutter-x:1.5rem}.g-4,.gy-4{--mdb-gutter-y:1.5rem}.g-5,.gx-5{--mdb-gutter-x:3rem}.g-5,.gy-5{--mdb-gutter-y:3rem}@media(min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;widthLine truncated
|
|
||||||
"autoRename": true,
|
|
||||||
"stringMap":[ {
|
|
||||||
"name" : "prev-next",
|
|
||||||
"search" : "prev",
|
|
||||||
"replace" : "next"
|
|
||||||
} ]
|
|
||||||
}*/.carousel-control-next-icon,.carousel-control-prev-icon{background-image:none}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-mdb-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion:reduce){.carousel-indicators [data-mdb-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-mdb-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{/*!rtl:ignore*/to{transform:rotate(1turn)}}@keyframes spinner-border{/*!rtl:ignore*/to{transform:rotate(1turn)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid;border-right:.25em solid transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}@media(prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media(prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem}.offcanvas-header .btn-close{padding:.5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.6}.offcanvas-body{flex-grow:1;padding:1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom,.offcanvas-top{right:0;left:0;height:30vh;max-height:100%}.offcanvas-bottom{border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.tooltip{position:absolute;z-index:1090;display:block;margin:0;font-family:var(--mdb-font-roboto);font-style:normal;font-weight:400;line-height:1.6;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow:before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow:before,.bs-tooltip-top .tooltip-arrow:before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow:before,.bs-tooltip-end .tooltip-arrow:before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow:before,.bs-tooltip-bottom .tooltip-arrow:bLine truncated
|
|
||||||
* # Semantic UI 2.4.2 - Flag
|
|
||||||
* http://github.com/semantic-org/semantic-ui/
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Released under the MIT license
|
|
||||||
* http://opensource.org/licenses/MIT
|
|
||||||
*
|
|
||||||
*/#mdb-table-flag tr{cursor:pointer}.mdb-flag-selected{border-top-left-radius:5px;border-top-right-radius:5px;text-align:center;max-width:150px;margin:10px auto 0}.mdb-selected-flag-text{margin:0 auto;max-width:150px}i.flag:not(.icon){margin:0 .5em 0 0;line-height:11px;text-decoration:inherit;vertical-align:baseline;-webkit-backface-visibility:hidden;backface-visibility:hidden}i.flag:before,i.flag:not(.icon){display:inline-block;width:16px;height:11px}i.flag:before{content:"";background:url(https://mdbootstrap.com/img/svg/flags.png) no-repeat -108px -1976px}i.flag-ad:before,i.flag-andorra:before{background-position:0 0!important}i.flag-ae:before,i.flag-uae:before,i.flag-united-arab-emirates:before{background-position:0 -26px!important}i.flag-af:before,i.flag-afghanistan:before{background-position:0 -52px!important}i.flag-ag:before,i.flag-antigua:before{background-position:0 -78px!important}i.flag-ai:before,i.flag-anguilla:before{background-position:0 -104px!important}i.flag-al:before,i.flag-albania:before{background-position:0 -130px!important}i.flag-am:before,i.flag-armenia:before{background-position:0 -156px!important}i.flag-an:before,i.flag-netherlands-antilles:before{background-position:0 -182px!important}i.flag-angola:before,i.flag-ao:before{background-position:0 -208px!important}i.flag-ar:before,i.flag-argentina:before{background-position:0 -234px!important}i.flag-american-samoa:before,i.flag-as:before{background-position:0 -260px!important}i.flag-at:before,i.flag-austria:before{background-position:0 -286px!important}i.flag-au:before,i.flag-australia:before{background-position:0 -312px!important}i.flag-aruba:before,i.flag-aw:before{background-position:0 -338px!important}i.flag-aland-islands:before,i.flag-ax:before{background-position:0 -364px!important}i.flag-az:before,i.flag-azerbaijan:before{background-position:0 -390px!important}i.flag-ba:before,i.flag-bosnia:before{background-position:0 -416px!important}i.flag-barbados:before,i.flag-bb:before{background-position:0 -442px!important}i.flag-bangladesh:before,i.flag-bd:before{background-position:0 -468px!important}i.flag-be:before,i.flag-belgium:before{background-position:0 -494px!important}i.flag-bf:before,i.flag-burkina-faso:before{background-position:0 -520px!important}i.flag-bg:before,i.flag-bulgaria:before{background-position:0 -546px!important}i.flag-bahrain:before,i.flag-bh:before{background-position:0 -572px!important}i.flag-bi:before,i.flag-burundi:before{background-position:0 -598px!important}i.flag-benin:before,i.flag-bj:before{background-position:0 -624px!important}i.flag-bermuda:before,i.flag-bm:before{background-position:0 -650px!important}i.flag-bn:before,i.flag-brunei:before{background-position:0 -676px!important}i.flag-bo:before,i.flag-bolivia:before{background-position:0 -702px!important}i.flag-br:before,i.flag-brazil:before{background-position:0 -728px!important}i.flag-bahamas:before,i.flag-bs:before{background-position:0 -754px!important}i.flag-bhutan:before,i.flag-bt:before{background-position:0 -780px!important}i.flag-bouvet-island:before,i.flag-bv:before{background-position:0 -806px!important}i.flag-botswana:before,i.flag-bw:before{background-position:0 -832px!important}i.flag-belarus:before,i.flag-by:before{background-position:0 -858px!important}i.flag-belize:before,i.flag-bz:before{background-position:0 -884px!important}i.flag-ca:before,i.flag-canada:before{background-position:0 -910px!important}i.flag-cc:before,i.flag-cocos-islands:before{background-position:0 -962px!important}i.flag-cd:before,i.flag-congo:before{background-position:0 -988px!important}i.flag-central-african-republic:before,i.flag-cf:before{background-position:0 -1014px!important}i.flag-cg:before,i.flag-congo-brazzaville:before{background-position:0 -1040px!important}i.flag-ch:before,i.flag-switzerland:before{background-position:0 -1066px!important}i.flag-ci:before,i.flag-cote-divoire:before{background-position:0 -1092px!important}i.flag-ck:before,i.flag-cook-islands:before{background-position:0 -1118px!important}i.flag-chile:before,i.flag-cl:before{background-position:0 -1144px!important}i.flag-cameroon:before,i.flag-cm:before{background-position:0 -1170px!important}i.flag-china:before,i.flag-cn:before{background-position:0 -1196px!important}i.flag-co:before,i.flag-colombia:before{background-position:0 -1222px!important}i.flag-costa-rica:before,i.flag-cr:before{background-position:0 -1248px!important}i.flag-cs:before,i.flag-serbia:before{background-position:0 -1274px!important}i.flag-cu:before,i.flag-cuba:before{background-position:0 -1300px!important}i.flag-cape-verde:before,i.flag-cv:before{background-position:0 -1326px!important}i.flag-christmas-island:before,i.flag-cx:before{background-position:0 -1352px!important}i.flag-cy:before,i.flag-cyprus:before{background-position:0 -1378px!important}i.flag-cz:before,i.flag-czech-republic:before{background-position:0 -1404px!important}i.flag-de:before,i.flag-germany:before{background-position:0 -1430px!important}Line truncated
|
|
||||||
Vendored
-20
@@ -1,20 +0,0 @@
|
|||||||
/*!
|
|
||||||
* MDB5
|
|
||||||
* Version: FREE 4.2.0
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Copyright: Material Design for Bootstrap
|
|
||||||
* https://mdbootstrap.com/
|
|
||||||
*
|
|
||||||
* Read the license: https://mdbootstrap.com/general/license/
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Documentation: https://mdbootstrap.com/docs/standard/
|
|
||||||
*
|
|
||||||
* Support: https://mdbootstrap.com/support/
|
|
||||||
*
|
|
||||||
* Contact: [email protected]
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("mdb",[],e):"object"==typeof exports?exports.mdb=e():t.mdb=e()}(this,function(){return n=[function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e,n){"use strict";var i=n(18),n=n(45);i({target:"RegExp",proto:!0,forced:/./.exec!==n},{exec:n})},function(t,e,n){var n=n(33),i=Function.prototype,o=i.bind,r=i.call,s=n&&o.bind(r,r);t.exports=n?function(t){return t&&s(t)}:function(t){return t&&function(){return r.apply(t,arguments)}}},function(t,e){t.exports=function(t){return"function"==typeof t}},function(n,t,e){!function(t){function e(t){return t&&t.Math==Math&&t}n.exports=e("object"==typeof globalThis&&globalThis)||e("object"==typeof window&&window)||e("object"==typeof self&&self)||e("object"==typeof t&&t)||function(){return this}()||Function("return this")()}.call(this,e(75))},function(t,e,n){var i=n(4),o=n(36),r=n(7),s=n(55),a=n(50),c=n(49),l=o("wks"),u=i.Symbol,h=u&&u.for,d=c?u:u&&u.withoutSetter||s;t.exports=function(t){var e;return r(l,t)&&(a||"string"==typeof l[t])||(e="Symbol."+t,a&&r(u,t)?l[t]=u[t]:l[t]=(c&&h?h:d)(e)),l[t]}},function(t,e,n){n=n(0);t.exports=!n(function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})},function(t,e,n){var i=n(2),o=n(28),r=i({}.hasOwnProperty);t.exports=Object.hasOwn||function(t,e){return r(o(t),e)}},function(t,e,n){"use strict";var i=n(18),o=n(61).includes,r=n(0),n=n(71);i({target:"Array",proto:!0,forced:r(function(){return!Array(1).includes()})},{includes:function(t){return o(this,t,1<arguments.length?arguments[1]:void 0)}}),n("includes")},function(t,e,n){var i=n(6),o=n(56),r=n(57),s=n(12),a=n(34),c=TypeError,l=Object.defineProperty,u=Object.getOwnPropertyDescriptor,h="enumerable",d="configurable",f="writable";e.f=i?r?function(t,e,n){var i;return s(t),e=a(e),s(n),"function"==typeof t&&"prototype"===e&&"value"in n&&f in n&&!n[f]&&((i=u(t,e))&&i[f]&&(t[e]=n.value,n={configurable:(d in n?n:i)[d],enumerable:(h in n?n:i)[h],writable:!1})),l(t,e,n)}:l:function(t,e,n){if(s(t),e=a(e),s(n),o)try{return l(t,e,n)}catch(t){}if("get"in n||"set"in n)throw c("Accessors not supported");return"value"in n&&(t[e]=n.value),t}},function(t,e,n){"use strict";var i=n(19),o=n(71),r=n(46),s=n(29),a=n(9).f,c=n(105),l=n(27),n=n(6),u="Array Iterator",h=s.set,d=s.getterFor(u),s=(t.exports=c(Array,"Array",function(t,e){h(this,{type:u,target:i(t),index:0,kind:e})},function(){var t=d(this),e=t.target,n=t.kind,i=t.index++;return!e||i>=e.length?{value:t.target=void 0,done:!0}:"keys"==n?{value:i,done:!1}:"values"==n?{value:e[i],done:!1}:{value:[i,e[i]],done:!1}},"values"),r.Arguments=r.Array);if(o("keys"),o("values"),o("entries"),!l&&n&&"values"!==s.name)try{a(s,"name",{value:"values"})}catch(t){}},function(t,e,n){var n=n(33),i=Function.prototype.call;t.exports=n?i.bind(i):function(){return i.apply(i,arguments)}},function(t,e,n){var i=n(14),o=String,r=TypeError;t.exports=function(t){if(i(t))return t;throw r(o(t)+" is not an object")}},function(t,e,n){function i(e,t){if(e){if(e[u]!==d)try{l(e,u,d)}catch(t){e[u]=d}if(e[h]||l(e,h,t),s[t])for(var n in c)if(e[n]!==c[n])try{l(e,n,c[n])}catch(t){e[n]=c[n]}}}var o,r=n(4),s=n(108),a=n(109),c=n(10),l=n(15),n=n(5),u=n("iterator"),h=n("toStringTag"),d=c.values;for(o in s)i(r[o]&&r[o].prototype,o);i(a,"DOMTokenList")},function(t,e,n){var i=n(3);t.exports=function(t){return"object"==typeof t?null!==t:i(t)}},function(t,e,n){var i=n(6),o=n(9),r=n(24);t.exports=i?function(t,e,n){return o.f(t,e,r(1,n))}:function(t,e,n){return t[e]=n,t}},function(t,e,n){var i=n(86),o=String;t.exports=function(t){if("Symbol"===i(t))throw TypeError("Cannot convert a Symbol value to a string");return o(t)}},function(M,H,t){var e=t(6),n=t(4),i=t(2),o=t(64),l=t(93),u=t(15),r=t(59).f,h=t(35),d=t(95),f=t(16),p=t(96),s=t(66),a=t(97),c=t(22),g=t(0),m=t(7),_=t(29).enforce,v=t(98),b=t(5),y=t(67),w=t(68),E=b("match"),x=n.RegExp,C=x.prototype,T=n.SyntaxError,O=i(C.exec),A=i("".charAt),S=i("".replace),L=i("".indexOf),R=i("".slice),B=/^\?<[^\s\d!#%&*+<=>@^][^\s!#%&*+<=>@^]*>/,I=/a/g,k=/a/g,t=new x(I)!==I,D=s.MISSED_STICKY,W=s.UNSUPPORTED_Y,b=e&&(!t||D||y||w||g(function(){return k[E]=!1,x(I)!=I||x(k)==k||"/a/i"!=x(I,"i")}));if(o("RegExp",b)){function N(t,e){var n,i,o=h(C,this),r=d(t),s=void 0===e,a=[],c=t;if(!o&&r&&s&&t.constructor===N)return t;if((r||h(C,t))&&(t=t.source,s&&(e=p(c))),t=void 0===t?"":f(t),e=void 0===e?"":f(e),c=t,r=e=y&&"dotAll"in I&&(n=!!e&&-1<L(e,"s"))?S(e,/s/g,""):e,D&&"sticky"in I&&(i=!!e&&-1<L(e,"y"))&&W&&(e=S(e,/y/g,"")),w&&(t=(s=function(t){for(var e,n=t.length,i=0,o="",r=[],s={},a=!1,c=!1,l=0,u="";i<=n;i++){if("\\"===(e=A(t,i)))e+=A(t,++i);else if("]"===e)a=!1;else if(!a)switch(!0){case"["===e:a=!0;break;case"("===e:O(B,R(t,i+1))&&(i+=2,c=!0),o+=e,l++;continue;case">"===e&&c:if(""===u||m(s,u))throw new T("Invalid capture group name");s[u]=!0,c=!(r[r.length]=[u,l]),u="";continue}c?u+=e:o+=e}return[o,r]}(Line truncated
|
|
||||||
//# sourceMappingURL=mdb.min.js.map
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,50 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const configFolder = path.join(process.env['LOCALAPPDATA'], 'EZPPLauncher');
|
|
||||||
if (!fs.existsSync(configFolder)) fs.mkdirSync(configFolder);
|
|
||||||
|
|
||||||
const configLocation = path.join(configFolder, `ezpplauncher.${path.basename(process.env['USERNAME'])}.cfg`);
|
|
||||||
if (!fs.existsSync(configLocation)) fs.writeFileSync(configLocation, "");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function get(key, defaultValue) {
|
|
||||||
const fileStream = await fs.promises.readFile(configLocation, "utf-8");
|
|
||||||
const lines = fileStream.split(/\r?\n/)
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.includes('=')) {
|
|
||||||
const argsPair = line.split('=', 2);
|
|
||||||
const keyname = argsPair[0]
|
|
||||||
const value = argsPair[1];
|
|
||||||
if (keyname == key) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function set(key, value) {
|
|
||||||
const configValues = new Map();
|
|
||||||
const fileStream = await fs.promises.readFile(configLocation, "utf-8");
|
|
||||||
const lines = fileStream.split(/\r?\n/)
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.includes('=')) {
|
|
||||||
const argsPair = line.split('=', 2);
|
|
||||||
const keyname = argsPair[0]
|
|
||||||
const value = argsPair[1];
|
|
||||||
configValues.set(keyname, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configValues.set(key, value);
|
|
||||||
|
|
||||||
const arr = [];
|
|
||||||
for (var [storkey, storvalue] of configValues.entries())
|
|
||||||
arr.push(`${storkey}=${storvalue}`);
|
|
||||||
|
|
||||||
await fs.promises.writeFile(configLocation, arr.join('\n'));
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { get, set }
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
const appName = "EZPPLauncher";
|
||||||
|
const appVersion = "2.1.7";
|
||||||
|
|
||||||
|
module.exports = { appName, appVersion };
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
const sqlite = require("better-sqlite3");
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
const configFolder = path.join(
|
||||||
|
process.platform == "win32"
|
||||||
|
? process.env["LOCALAPPDATA"]
|
||||||
|
: process.env["HOME"],
|
||||||
|
"EZPPLauncher",
|
||||||
|
);
|
||||||
|
if (!fs.existsSync(configFolder)) fs.mkdirSync(configFolder);
|
||||||
|
|
||||||
|
const dbFile = path.join(configFolder, "ezpplauncher.db");
|
||||||
|
|
||||||
|
const db = sqlite(dbFile);
|
||||||
|
db.pragma("journal_mode = WAL");
|
||||||
|
|
||||||
|
db.exec(
|
||||||
|
"CREATE TABLE IF NOT EXISTS config (configKey VARCHAR PRIMARY KEY, configValue VARCHAR);",
|
||||||
|
);
|
||||||
|
|
||||||
|
const set = (key, value) => {
|
||||||
|
db.prepare(
|
||||||
|
`INSERT OR REPLACE INTO config (configKey, configValue) VALUES (?, ?)`,
|
||||||
|
).run(key, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = (key) => {
|
||||||
|
db.prepare(`DELETE FROM config WHERE configKey = ?`).run(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const get = (
|
||||||
|
key,
|
||||||
|
) => {
|
||||||
|
const result = db.prepare(
|
||||||
|
"SELECT configKey key, configValue val FROM config WHERE key = ?",
|
||||||
|
).get(key);
|
||||||
|
return result ? result.val ?? undefined : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const all = () => {
|
||||||
|
const result = db.prepare(
|
||||||
|
`SELECT configKey key, configValue val FROM config WHERE 1`,
|
||||||
|
).all();
|
||||||
|
return result ?? undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
all,
|
||||||
|
get,
|
||||||
|
set,
|
||||||
|
remove,
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
const cryptojs = require("crypto-js");
|
||||||
|
|
||||||
|
const encrypt = (string, salt) => {
|
||||||
|
return cryptojs.AES.encrypt(string, salt).toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const decrypt = (string, salt) => {
|
||||||
|
return cryptojs.AES.decrypt(string, salt).toString(cryptojs.enc.Utf8);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { encrypt, decrypt };
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
const childProcess = require("child_process");
|
||||||
|
|
||||||
|
const runFile = (folder, file, args, onExit) => {
|
||||||
|
childProcess.execFile(file, args, {
|
||||||
|
cwd: folder,
|
||||||
|
}, (_err, _stdout, _stdin) => {
|
||||||
|
if (onExit) onExit();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const runFileDetached = (folder, file, args) => {
|
||||||
|
const subProcess = childProcess.spawn(file + (args ? " " + args : ""), {
|
||||||
|
cwd: folder,
|
||||||
|
detached: true,
|
||||||
|
stdio: "ignore",
|
||||||
|
});
|
||||||
|
subProcess.unref();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { runFile, runFileDetached };
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
function isWritable(filePath) {
|
||||||
|
let fileAccess = false;
|
||||||
|
try {
|
||||||
|
fs.closeSync(fs.openSync(filePath, "r+"));
|
||||||
|
fileAccess = true;
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
return fileAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isWritable,
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
function formatBytes(bytes, decimals = 2) {
|
||||||
|
if (!+bytes) return "0 B";
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const dm = decimals < 0 ? 0 : decimals;
|
||||||
|
const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||||
|
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))}${sizes[i]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { formatBytes };
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
const child_process = require("child_process");
|
||||||
|
const options = { encoding: "ascii", windowsHide: true, timeout: 200 };
|
||||||
|
const platforms = {
|
||||||
|
win32: [
|
||||||
|
"REG QUERY HKLM\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid",
|
||||||
|
/MachineGuid\s+REG_SZ\s+(.*?)\s/,
|
||||||
|
],
|
||||||
|
darwin: [
|
||||||
|
"ioreg -rd1 -c IOPlatformExpertDevice",
|
||||||
|
/"IOPlatformUUID" = "(.*?)"/,
|
||||||
|
],
|
||||||
|
linux: [
|
||||||
|
"cat /var/lib/dbus/machine-id /etc/machine-id 2> /dev/null || true",
|
||||||
|
/^([\da-f]+)/,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns machine hardware id.
|
||||||
|
* Returns `undefined` if cannot determine.
|
||||||
|
* @return {string?}
|
||||||
|
*/
|
||||||
|
function getHwId() {
|
||||||
|
const getter = platforms[process.platform];
|
||||||
|
if (!getter) return;
|
||||||
|
const result = getter[1].exec(child_process.execSync(getter[0], options));
|
||||||
|
if (!result) return;
|
||||||
|
return crypto.createHash("md5").update(result[1]).digest("hex") ||
|
||||||
|
undefined;
|
||||||
|
}
|
||||||
|
exports.getHwId = getHwId;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
async function checkImageExists(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "HEAD",
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const contentType = response.headers.get("content-type");
|
||||||
|
if (!contentType) return false;
|
||||||
|
return contentType.startsWith("image/");
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { checkImageExists };
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
constructor(directory) {
|
||||||
|
this.directory = directory;
|
||||||
|
this.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
const filename = `${new Date().toISOString().replace(/:/g, "-")}.log`;
|
||||||
|
this.logPath = path.join(this.directory, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
async log(message) {
|
||||||
|
if (this.logPath === undefined || this.enabled == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(this.logPath)) {
|
||||||
|
await fs.promises.mkdir(this.directory, { recursive: true });
|
||||||
|
await fs.promises.writeFile(this.logPath, "");
|
||||||
|
}
|
||||||
|
const logMessage = `[${new Date().toISOString()}] LOG: ${message}`;
|
||||||
|
await fs.promises.appendFile(this.logPath, `${logMessage}\n`);
|
||||||
|
console.log(logMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
async error(message, error) {
|
||||||
|
if (this.logPath === undefined || this.enabled == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(this.logPath)) {
|
||||||
|
await fs.promises.mkdir(this.directory, { recursive: true });
|
||||||
|
await fs.promises.writeFile(this.logPath, "");
|
||||||
|
}
|
||||||
|
const errorMessage = `[${
|
||||||
|
new Date().toISOString()
|
||||||
|
}] ERROR: ${message}\n${error.stack}`;
|
||||||
|
await fs.promises.appendFile(this.logPath, `${errorMessage}\n`);
|
||||||
|
console.error(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Logger;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
const { exec } = require("child_process");
|
||||||
|
|
||||||
|
async function isNet8Installed() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
exec("dotnet --list-runtimes", (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const version = stdout.trim();
|
||||||
|
for (const line of version.split('\n')) {
|
||||||
|
if (line.startsWith("Microsoft.WindowsDesktop.App 8.")) {
|
||||||
|
resolve(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(false);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { isNet8Installed };
|
||||||
@@ -0,0 +1,478 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
const EventEmitter = require("events");
|
||||||
|
const { default: axios } = require("axios");
|
||||||
|
const { runFile } = require("./executeUtil.js");
|
||||||
|
|
||||||
|
const checkUpdateURL =
|
||||||
|
"https://osu.ppy.sh/web/check-updates.php?action=check&stream=";
|
||||||
|
const ignoredOsuEntities = [
|
||||||
|
"osu!auth.dll",
|
||||||
|
];
|
||||||
|
const gamemodes = {
|
||||||
|
0: "osu!",
|
||||||
|
1: "taiko",
|
||||||
|
2: "catch",
|
||||||
|
3: "mania",
|
||||||
|
4: "osu!(rx)",
|
||||||
|
5: "taiko(rx)",
|
||||||
|
6: "catch(rx)",
|
||||||
|
8: "osu!(ap)",
|
||||||
|
};
|
||||||
|
const osuEntities = [
|
||||||
|
"avcodec-51.dll",
|
||||||
|
"avformat-52.dll",
|
||||||
|
"avutil-49.dll",
|
||||||
|
"bass.dll",
|
||||||
|
"bass_fx.dll",
|
||||||
|
"collection.db",
|
||||||
|
"d3dcompiler_47.dll",
|
||||||
|
"libEGL.dll",
|
||||||
|
"libGLESv2.dll",
|
||||||
|
"Microsoft.Ink.dll",
|
||||||
|
"OpenTK.dll",
|
||||||
|
"osu!.cfg",
|
||||||
|
"osu!.db",
|
||||||
|
"osu!.exe",
|
||||||
|
"osu!auth.dll",
|
||||||
|
"osu!gameplay.dll",
|
||||||
|
"osu!seasonal.dll",
|
||||||
|
"osu!ui.dll",
|
||||||
|
"presence.db",
|
||||||
|
"pthreadGC2.dll",
|
||||||
|
"scores.db",
|
||||||
|
];
|
||||||
|
|
||||||
|
const ezppLauncherUpdateList = "https://ez-pp.farm/ezpplauncher";
|
||||||
|
|
||||||
|
async function isValidOsuFolder(path) {
|
||||||
|
const allFiles = await fs.promises.readdir(path);
|
||||||
|
let matches = 0;
|
||||||
|
for (const file of allFiles) {
|
||||||
|
if (osuEntities.includes(file)) matches = matches + 1;
|
||||||
|
}
|
||||||
|
return (Math.round((matches / osuEntities.length) * 100) >= 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGlobalConfig(osuPath) {
|
||||||
|
const configFileInfo = {
|
||||||
|
name: "",
|
||||||
|
path: "",
|
||||||
|
get: async (key) => {
|
||||||
|
if (!configFileInfo.path) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const fileStream = await fs.promises.readFile(
|
||||||
|
configFileInfo.path,
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
const lines = fileStream.split(/\r?\n/);
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.includes(" = ")) {
|
||||||
|
const argsPair = line.split(" = ", 2);
|
||||||
|
const keyname = argsPair[0];
|
||||||
|
const value = argsPair[1];
|
||||||
|
if (keyname == key) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const globalOsuConfig = path.join(osuPath, "osu!.cfg");
|
||||||
|
if (fs.existsSync(globalOsuConfig)) {
|
||||||
|
configFileInfo.name = "osu!.cfg";
|
||||||
|
configFileInfo.path = globalOsuConfig;
|
||||||
|
}
|
||||||
|
return configFileInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUserConfig(osuPath) {
|
||||||
|
const configFileInfo = {
|
||||||
|
name: "",
|
||||||
|
path: "",
|
||||||
|
set: async (key, newValue) => {
|
||||||
|
if (!configFileInfo.path) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const fileContents = [];
|
||||||
|
const fileStream = await fs.promises.readFile(
|
||||||
|
configFileInfo.path,
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
const lines = fileStream.split(/\r?\n/);
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.includes(" = ")) {
|
||||||
|
const argsPair = line.split(" = ", 2);
|
||||||
|
const keyname = argsPair[0];
|
||||||
|
if (keyname == key) {
|
||||||
|
fileContents.push(`${key} = ${newValue}`);
|
||||||
|
} else {
|
||||||
|
fileContents.push(line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fileContents.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await fs.promises.writeFile(configFileInfo.path, fileContents.join("\n"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
get: async (key) => {
|
||||||
|
if (!configFileInfo.path) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const fileStream = await fs.promises.readFile(
|
||||||
|
configFileInfo.path,
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
const lines = fileStream.split(/\r?\n/);
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.includes(" = ")) {
|
||||||
|
const argsPair = line.split(" = ", 2);
|
||||||
|
const keyname = argsPair[0];
|
||||||
|
const value = argsPair[1];
|
||||||
|
if (keyname == key) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const userOsuConfig = path.join(
|
||||||
|
osuPath,
|
||||||
|
`osu!.${process.env["USERNAME"]}.cfg`,
|
||||||
|
);
|
||||||
|
if (fs.existsSync(userOsuConfig)) {
|
||||||
|
configFileInfo.name = `osu!.${process.env["USERNAME"]}.cfg`;
|
||||||
|
configFileInfo.path = userOsuConfig;
|
||||||
|
}
|
||||||
|
return configFileInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getUpdateFiles(releaseStream) {
|
||||||
|
const releaseData = await fetch(checkUpdateURL + releaseStream, {
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return releaseData.ok ? await releaseData.json() : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFilesThatNeedUpdate(osuPath, releaseStreamFiles) {
|
||||||
|
const updateFiles = [];
|
||||||
|
for (const updatePatch of releaseStreamFiles) {
|
||||||
|
const fileName = updatePatch.filename;
|
||||||
|
const fileHash = updatePatch.file_hash;
|
||||||
|
|
||||||
|
const fileOnDisk = path.join(osuPath, fileName);
|
||||||
|
if (fs.existsSync(fileOnDisk)) {
|
||||||
|
if (ignoredOsuEntities.includes(fileName)) continue;
|
||||||
|
const fileHashOnDisk = crypto.createHash("md5").update(
|
||||||
|
fs.readFileSync(fileOnDisk),
|
||||||
|
).digest("hex");
|
||||||
|
if (
|
||||||
|
fileHashOnDisk.trim().toLowerCase() != fileHash.trim().toLowerCase()
|
||||||
|
) {
|
||||||
|
updateFiles.push(updatePatch);
|
||||||
|
}
|
||||||
|
} else updateFiles.push(updatePatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadUpdateFiles(osuPath, updateFiles) {
|
||||||
|
const eventEmitter = new EventEmitter();
|
||||||
|
|
||||||
|
const startDownload = async () => {
|
||||||
|
for (const updatePatch of updateFiles) {
|
||||||
|
try {
|
||||||
|
const fileName = updatePatch.filename;
|
||||||
|
const fileSize = updatePatch.filesize;
|
||||||
|
const fileURL = updatePatch.url_full;
|
||||||
|
|
||||||
|
const axiosDownloadWithProgress = await axios.get(fileURL, {
|
||||||
|
responseType: "stream",
|
||||||
|
onDownloadProgress: (progressEvent) => {
|
||||||
|
const { loaded, total } = progressEvent;
|
||||||
|
eventEmitter.emit("data", {
|
||||||
|
fileName,
|
||||||
|
loaded,
|
||||||
|
total,
|
||||||
|
progress: Math.floor((loaded / total) * 100),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fs.existsSync(path.join(osuPath, fileName))) {
|
||||||
|
await fs.promises.rm(path.join(osuPath, fileName), {
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await fs.promises.writeFile(
|
||||||
|
path.join(osuPath, fileName),
|
||||||
|
axiosDownloadWithProgress.data,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
eventEmitter.emit("error", {
|
||||||
|
fileName,
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait until all files are downloaded
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
eventEmitter,
|
||||||
|
startDownload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function runOsuUpdater(osuPath, onExit) {
|
||||||
|
const osuExecuteable = path.join(osuPath, "osu!.exe");
|
||||||
|
runFile(osuPath, osuExecuteable, ["-repair"], onExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runOsuWithDevServer(osuPath, serverDomain, onExit) {
|
||||||
|
const osuExecuteable = path.join(osuPath, "osu!.exe");
|
||||||
|
runFile(osuPath, osuExecuteable, ["-devserver", serverDomain], onExit);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEZPPLauncherUpdateFiles(osuPath) {
|
||||||
|
const filesToDownload = [];
|
||||||
|
const updateFilesRequest = await fetch(ezppLauncherUpdateList, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const updateFiles = await updateFilesRequest.json();
|
||||||
|
for (const updateFile of updateFiles) {
|
||||||
|
const filePath = path.join(
|
||||||
|
osuPath,
|
||||||
|
...updateFile.folder.split("/"),
|
||||||
|
updateFile.name,
|
||||||
|
);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const fileHash = updateFile.md5.toLowerCase();
|
||||||
|
const localFileHash = crypto.createHash("md5").update(
|
||||||
|
fs.readFileSync(filePath),
|
||||||
|
).digest("hex").toLowerCase();
|
||||||
|
if (fileHash !== localFileHash) {
|
||||||
|
filesToDownload.push(updateFile);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filesToDownload.push(updateFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [filesToDownload, updateFiles];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadEZPPLauncherUpdateFiles(osuPath, updateFiles, allFiles) {
|
||||||
|
const eventEmitter = new EventEmitter();
|
||||||
|
|
||||||
|
const startDownload = async () => {
|
||||||
|
//NOTE: delete files that are not in the updateFiles array
|
||||||
|
const foldersToPrune = allFiles.map((file) =>
|
||||||
|
path.dirname(path.join(osuPath, ...file.folder.split("/"), file.name))
|
||||||
|
).filter((folder, index, self) => self.indexOf(folder) === index);
|
||||||
|
for (const pruneFolder of foldersToPrune) {
|
||||||
|
//NOTE: check if the folder is not the osu root folder.
|
||||||
|
if (path.basename(pruneFolder) == "osu!") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (fs.existsSync(pruneFolder)) {
|
||||||
|
for (const files of await fs.promises.readdir(pruneFolder)) {
|
||||||
|
const filePath = path.join(pruneFolder, files);
|
||||||
|
const validFolder = allFiles.find((file) =>
|
||||||
|
path.dirname(filePath).endsWith(file.folder)
|
||||||
|
);
|
||||||
|
if (!validFolder) {
|
||||||
|
if (
|
||||||
|
allFiles.find((file) => file.name == path.basename(filePath)) ===
|
||||||
|
undefined
|
||||||
|
) {
|
||||||
|
eventEmitter.emit("data", {
|
||||||
|
fileName: path.basename(filePath),
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await fs.promises.rm(filePath, {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const updateFile of updateFiles) {
|
||||||
|
try {
|
||||||
|
const filePath = path.join(
|
||||||
|
osuPath,
|
||||||
|
...updateFile.folder.split("/"),
|
||||||
|
updateFile.name,
|
||||||
|
);
|
||||||
|
const folder = path.dirname(filePath);
|
||||||
|
if (!fs.existsSync(folder)) {
|
||||||
|
await fs.promises.mkdir(folder, { recursive: true });
|
||||||
|
}
|
||||||
|
const axiosDownloadWithProgress = await axios.get(updateFile.url, {
|
||||||
|
responseType: "stream",
|
||||||
|
onDownloadProgress: (progressEvent) => {
|
||||||
|
const fileSize = updateFile.size;
|
||||||
|
const { loaded } = progressEvent;
|
||||||
|
eventEmitter.emit("data", {
|
||||||
|
fileName: path.basename(filePath),
|
||||||
|
loaded,
|
||||||
|
total: fileSize,
|
||||||
|
progress: Math.floor((loaded / fileSize) * 100),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
await fs.promises.rm(filePath, {
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await fs.promises.writeFile(
|
||||||
|
filePath,
|
||||||
|
axiosDownloadWithProgress.data,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
eventEmitter.emit("error", {
|
||||||
|
fileName: path.basename(filePath),
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
eventEmitter,
|
||||||
|
startDownload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function replaceUIFiles(osuPath, revert) {
|
||||||
|
if (!revert) {
|
||||||
|
const ezppUIFile = path.join(osuPath, "EZPPLauncher", "ezpp!ui.dll");
|
||||||
|
const oldOsuUIFile = path.join(osuPath, "osu!ui.dll");
|
||||||
|
const ezppGameplayFile = path.join(
|
||||||
|
osuPath,
|
||||||
|
"EZPPLauncher",
|
||||||
|
"ezpp!gameplay.dll",
|
||||||
|
);
|
||||||
|
const oldOsuGameplayFile = path.join(osuPath, "osu!gameplay.dll");
|
||||||
|
|
||||||
|
await fs.promises.rename(
|
||||||
|
oldOsuUIFile,
|
||||||
|
path.join(osuPath, "osu!ui.dll.bak"),
|
||||||
|
);
|
||||||
|
await fs.promises.rename(ezppUIFile, oldOsuUIFile);
|
||||||
|
|
||||||
|
await fs.promises.rename(
|
||||||
|
oldOsuGameplayFile,
|
||||||
|
path.join(osuPath, "osu!gameplay.dll.bak"),
|
||||||
|
);
|
||||||
|
await fs.promises.rename(ezppGameplayFile, oldOsuGameplayFile);
|
||||||
|
} else {
|
||||||
|
const oldOsuUIFile = path.join(osuPath, "osu!ui.dll");
|
||||||
|
const ezppUIFile = path.join(osuPath, "EZPPLauncher", "ezpp!ui.dll");
|
||||||
|
const oldOsuGameplayFile = path.join(osuPath, "osu!gameplay.dll");
|
||||||
|
const ezppGameplayFile = path.join(
|
||||||
|
osuPath,
|
||||||
|
"EZPPLauncher",
|
||||||
|
"ezpp!gameplay.dll",
|
||||||
|
);
|
||||||
|
|
||||||
|
await fs.promises.rename(oldOsuUIFile, ezppUIFile);
|
||||||
|
await fs.promises.rename(
|
||||||
|
path.join(osuPath, "osu!ui.dll.bak"),
|
||||||
|
oldOsuUIFile,
|
||||||
|
);
|
||||||
|
|
||||||
|
await fs.promises.rename(oldOsuGameplayFile, ezppGameplayFile);
|
||||||
|
await fs.promises.rename(
|
||||||
|
path.join(osuPath, "osu!gameplay.dll.bak"),
|
||||||
|
oldOsuGameplayFile,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findOsuInstallation() {
|
||||||
|
const regedit = require("regedit-rs");
|
||||||
|
|
||||||
|
const osuLocationFromDefaultIcon =
|
||||||
|
"HKLM\\SOFTWARE\\Classes\\osu\\DefaultIcon";
|
||||||
|
const osuKey = regedit.listSync(osuLocationFromDefaultIcon);
|
||||||
|
if (osuKey[osuLocationFromDefaultIcon].exists) {
|
||||||
|
const key = osuKey[osuLocationFromDefaultIcon].values[""];
|
||||||
|
let value = key.value;
|
||||||
|
value = value.substring(1, value.length - 3);
|
||||||
|
return path.dirname(value.trim());
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateOsuConfigHashes(osuPath) {
|
||||||
|
const osuCfg = path.join(osuPath, "osu!.cfg");
|
||||||
|
const fileStream = await fs.promises.readFile(osuCfg, "utf-8");
|
||||||
|
const lines = fileStream.split(/\r?\n/);
|
||||||
|
const newLines = [];
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.includes(" = ")) {
|
||||||
|
const argsPair = line.split(" = ", 2);
|
||||||
|
const key = argsPair[0];
|
||||||
|
const value = argsPair[1];
|
||||||
|
|
||||||
|
if (key.startsWith("h_")) {
|
||||||
|
const fileName = key.substring(2, key.length);
|
||||||
|
const filePath = path.join(osuPath, fileName);
|
||||||
|
if (!fs.existsSync(filePath)) continue;
|
||||||
|
const binaryFileContents = await fs.promises.readFile(filePath);
|
||||||
|
const existingFileMD5 = crypto.createHash("md5").update(
|
||||||
|
binaryFileContents,
|
||||||
|
).digest("hex");
|
||||||
|
if (value == existingFileMD5) newLines.push(line);
|
||||||
|
else newLines.push(`${key} = ${existingFileMD5}`);
|
||||||
|
} else if (line.startsWith("u_UpdaterAutoStart")) {
|
||||||
|
newLines.push(`${key} = 0`);
|
||||||
|
} else {
|
||||||
|
newLines.push(line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newLines.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.promises.writeFile(osuCfg, newLines.join("\n"), "utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isValidOsuFolder,
|
||||||
|
getUserConfig,
|
||||||
|
getGlobalConfig,
|
||||||
|
getUpdateFiles,
|
||||||
|
getFilesThatNeedUpdate,
|
||||||
|
downloadUpdateFiles,
|
||||||
|
runOsuWithDevServer,
|
||||||
|
replaceUIFiles,
|
||||||
|
findOsuInstallation,
|
||||||
|
updateOsuConfigHashes,
|
||||||
|
runOsuUpdater,
|
||||||
|
getEZPPLauncherUpdateFiles,
|
||||||
|
downloadEZPPLauncherUpdateFiles,
|
||||||
|
gamemodes,
|
||||||
|
};
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
const DiscordRPC = require("discord-auto-rpc");
|
||||||
|
const { appName, appVersion } = require("./appInfo.js");
|
||||||
|
|
||||||
|
const clientId = "1032772293220384808";
|
||||||
|
|
||||||
|
/** @type {DiscordRPC.AutoClient} */
|
||||||
|
let richPresence;
|
||||||
|
|
||||||
|
let intervalId;
|
||||||
|
|
||||||
|
let currentStatus = {
|
||||||
|
details: " ",
|
||||||
|
state: "Idle in Launcher...",
|
||||||
|
startTimestamp: new Date(),
|
||||||
|
largeImageKey: "ezppfarm",
|
||||||
|
largeImageText: `${appName} ${appVersion}`,
|
||||||
|
smallImageKey: " ",
|
||||||
|
smallImageText: " ",
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
label: "Download the Launcher",
|
||||||
|
url: "https://git.ez-pp.farm/EZPPFarm/EZPPLauncher/releases/latest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Join EZPPFarm",
|
||||||
|
url: "https://ez-pp.farm/discord",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
instance: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
connect: () => {
|
||||||
|
if (!richPresence) {
|
||||||
|
richPresence = new DiscordRPC.AutoClient({ transport: "ipc" });
|
||||||
|
richPresence.endlessLogin({ clientId });
|
||||||
|
richPresence.once("ready", () => {
|
||||||
|
console.log(
|
||||||
|
"connected presence with user " + richPresence.user.username,
|
||||||
|
);
|
||||||
|
richPresence.setActivity(currentStatus);
|
||||||
|
intervalId = setInterval(() => {
|
||||||
|
richPresence.setActivity(currentStatus);
|
||||||
|
}, 2500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disconnect: async () => {
|
||||||
|
if (richPresence) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
await richPresence.clearActivity();
|
||||||
|
await richPresence.destroy();
|
||||||
|
richPresence = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateStatus: ({ state, details, largeImageKey }) => {
|
||||||
|
currentStatus.state = state ?? " ";
|
||||||
|
currentStatus.details = details ?? " ";
|
||||||
|
currentStatus.largeImageKey = largeImageKey ?? "ezppfarm";
|
||||||
|
},
|
||||||
|
updateUser: ({ username, id }) => {
|
||||||
|
currentStatus.smallImageKey = id ? `https://a.ez-pp.farm/${id}` : " ";
|
||||||
|
currentStatus.smallImageText = username ?? " ";
|
||||||
|
},
|
||||||
|
update: () => {
|
||||||
|
if (richPresence && richPresence.user) {
|
||||||
|
richPresence.setActivity(currentStatus);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hasPresence: () => richPresence != undefined,
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const semver = require("semver");
|
||||||
|
const { appVersion } = require("./appInfo");
|
||||||
|
|
||||||
|
const repoApiUrl =
|
||||||
|
"https://git.ez-pp.farm/api/v1/repos/EZPPFarm/EZPPLauncher/releases?limit=1";
|
||||||
|
|
||||||
|
const releasesUrl =
|
||||||
|
"https://git.ez-pp.farm/EZPPFarm/EZPPLauncher/releases/latest";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
updateAvailable: async () => {
|
||||||
|
try {
|
||||||
|
const latestRelease = await fetch(repoApiUrl, {
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const json = await latestRelease.json();
|
||||||
|
if (json.length <= 0) return false;
|
||||||
|
return {
|
||||||
|
update: semver.lt(appVersion, json[0].tag_name),
|
||||||
|
release: json[0],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return { update: false };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
releasesUrl,
|
||||||
|
};
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
const childProcess = require('child_process');
|
|
||||||
module.exports = {
|
|
||||||
runFile: (folder, file, args, onExit) => {
|
|
||||||
childProcess.execFile(file, args, {
|
|
||||||
cwd: folder
|
|
||||||
}, (_err, _stdout, _stderr) => {
|
|
||||||
onExit();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
runFileDetached: (folder, file, args) => {
|
|
||||||
const subprocess = childProcess.spawn(file + " " + args, {
|
|
||||||
cwd: folder,
|
|
||||||
detached: true,
|
|
||||||
stdio: 'ignore'
|
|
||||||
})
|
|
||||||
subprocess.unref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
async function existsAsync(filePath) {
|
|
||||||
return new Promise(function (resolve, _reject) {
|
|
||||||
fs.stat(filePath, function (err, _stat) {
|
|
||||||
if (err == null) {
|
|
||||||
resolve(true)
|
|
||||||
} else {
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { existsAsync };
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>EZPPLauncher</title>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<link rel="icon" type="image/png" href="../assets/logo.png" />
|
|
||||||
<link href="../assets/mdb.min.css" rel="stylesheet" />
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="fixed-sn mdb-skin-custom" data-spy="scroll" data-target="#scrollspy" data-offset="15"
|
|
||||||
oncontextmenu="return false;">
|
|
||||||
<main>
|
|
||||||
<div class="noselect">
|
|
||||||
<div class="position-relative overflow-hidden p-3 p-md-5 m-md-3 text-center text-lg-end d-flex align-items-center justify-content-center"
|
|
||||||
style="border-radius: 0.5em;">
|
|
||||||
<div class="position-relative overflow-hidden p-3 p-md-5 m-md-3">
|
|
||||||
<div class="container py-2 h-100">
|
|
||||||
<div class="row d-flex justify-content-center align-items-center h-100">
|
|
||||||
<div class="col col-xl-10">
|
|
||||||
<div class="card" style="border-radius: 1rem;">
|
|
||||||
<div class="row g-0">
|
|
||||||
<div class="card-body p-4 p-lg-5 text-black">
|
|
||||||
<div class="d-flex align-items-center mb-2 pb-1 text-white">
|
|
||||||
<span class="h1 fw-bold mb-0">EZPPLauncher</span>
|
|
||||||
</div>
|
|
||||||
<h5 class="fw-lighter fs-5 mb-3 pb-3 text-white text-start"
|
|
||||||
style="letter-spacing: 1px;">
|
|
||||||
Launch osu! with connection to the EZPPFarm server
|
|
||||||
</h5>
|
|
||||||
<div class="pt-1 mb-4">
|
|
||||||
<button id="launch-btn" class="btn btn-primary btn-lg btn-block"
|
|
||||||
type="button" style="background-color:#d6016f" disabled>Looking for
|
|
||||||
updates...</button>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-dark btn-sm float-start" id="account-btn" disabled>
|
|
||||||
set account
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-dark btn-sm float-end" id="folder-btn">
|
|
||||||
set osu! directory
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
<script type="text/javascript" src="../assets/mdb.min.js"></script>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,889 @@
|
|||||||
|
// Modules to control application life and create native browser window
|
||||||
|
const { app, BrowserWindow, Menu, ipcMain, dialog, shell } = require(
|
||||||
|
"electron",
|
||||||
|
);
|
||||||
|
|
||||||
|
const path = require("path");
|
||||||
|
const serve = require("electron-serve");
|
||||||
|
const loadURL = serve({ directory: "public" });
|
||||||
|
const config = require("./electron/config");
|
||||||
|
const { setupTitlebar, attachTitlebarToWindow } = require(
|
||||||
|
"custom-electron-titlebar/main",
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
isValidOsuFolder,
|
||||||
|
getUpdateFiles,
|
||||||
|
getGlobalConfig,
|
||||||
|
getFilesThatNeedUpdate,
|
||||||
|
downloadUpdateFiles,
|
||||||
|
getUserConfig,
|
||||||
|
runOsuWithDevServer,
|
||||||
|
replaceUIFiles,
|
||||||
|
findOsuInstallation,
|
||||||
|
runOsuUpdater,
|
||||||
|
gamemodes,
|
||||||
|
getEZPPLauncherUpdateFiles,
|
||||||
|
downloadEZPPLauncherUpdateFiles,
|
||||||
|
} = require("./electron/osuUtil");
|
||||||
|
const { formatBytes } = require("./electron/formattingUtil");
|
||||||
|
const windowName = require("get-window-by-name");
|
||||||
|
const fs = require("fs");
|
||||||
|
const { runFileDetached } = require("./electron/executeUtil");
|
||||||
|
const richPresence = require("./electron/richPresence");
|
||||||
|
const cryptUtil = require("./electron/cryptoUtil");
|
||||||
|
const { getHwId } = require("./electron/hwidUtil");
|
||||||
|
const { appName, appVersion } = require("./electron/appInfo");
|
||||||
|
const { updateAvailable, releasesUrl } = require("./electron/updateCheck");
|
||||||
|
const fkill = require("fkill");
|
||||||
|
const { checkImageExists } = require("./electron/imageUtil");
|
||||||
|
const { isNet8Installed } = require("./electron/netUtils");
|
||||||
|
const Logger = require("./electron/logging");
|
||||||
|
const { isWritable } = require("./electron/fileUtil");
|
||||||
|
|
||||||
|
// Keep a global reference of the window object, if you don't, the window will
|
||||||
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
|
let mainWindow;
|
||||||
|
let osuCheckInterval;
|
||||||
|
let userOsuPath;
|
||||||
|
let osuLoaded = false;
|
||||||
|
let patch = false;
|
||||||
|
let logger = new Logger(path.join(
|
||||||
|
process.platform == "win32"
|
||||||
|
? process.env["LOCALAPPDATA"]
|
||||||
|
: process.env["HOME"],
|
||||||
|
"EZPPLauncher",
|
||||||
|
"logs",
|
||||||
|
));
|
||||||
|
|
||||||
|
let currentUser = undefined;
|
||||||
|
|
||||||
|
function isDev() {
|
||||||
|
return !app.isPackaged;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startOsuStatus() {
|
||||||
|
osuCheckInterval = setInterval(async () => {
|
||||||
|
const osuWindowTitle = windowName.getWindowText("osu!.exe");
|
||||||
|
if (osuWindowTitle.length < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const firstInstance = osuWindowTitle[0];
|
||||||
|
|
||||||
|
if (firstInstance) {
|
||||||
|
if (!osuLoaded) {
|
||||||
|
osuLoaded = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const currentUserInfo = await fetch(
|
||||||
|
`https://api.ez-pp.farm/get_player_info?name=${currentUser.username}&scope=info`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const currentUserInfoJson = await currentUserInfo.json();
|
||||||
|
if (
|
||||||
|
"player" in currentUserInfoJson &&
|
||||||
|
currentUserInfoJson.player != null
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
"info" in currentUserInfoJson.player &&
|
||||||
|
currentUserInfoJson.player.info != null
|
||||||
|
) {
|
||||||
|
const id = currentUserInfoJson.player.info.id;
|
||||||
|
const username = currentUserInfoJson.player.info.name;
|
||||||
|
richPresence.updateUser({
|
||||||
|
id,
|
||||||
|
username,
|
||||||
|
});
|
||||||
|
richPresence.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (patch) {
|
||||||
|
const patcherExecuteable = path.join(
|
||||||
|
userOsuPath,
|
||||||
|
"EZPPLauncher",
|
||||||
|
"patcher",
|
||||||
|
"osu!.patcher.exe",
|
||||||
|
);
|
||||||
|
if (fs.existsSync(patcherExecuteable)) {
|
||||||
|
runFileDetached(userOsuPath, patcherExecuteable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const windowTitle = firstInstance.processTitle;
|
||||||
|
lastOsuStatus = windowTitle;
|
||||||
|
const currentStatusRequest = await fetch(
|
||||||
|
"https://api.ez-pp.farm/v1/get_player_status?name=" +
|
||||||
|
currentUser.username,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const currentStatus = await currentStatusRequest.json();
|
||||||
|
|
||||||
|
if (!("player_status" in currentStatus)) return;
|
||||||
|
if (!("status" in currentStatus.player_status)) return;
|
||||||
|
|
||||||
|
const currentMode = currentStatus.player_status.status.mode;
|
||||||
|
const currentModeString = gamemodes[currentMode];
|
||||||
|
|
||||||
|
const currentInfoRequest = await fetch(
|
||||||
|
"https://api.ez-pp.farm/v1/get_player_info?name=" +
|
||||||
|
currentUser.username + "&scope=all",
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const currentInfo = await currentInfoRequest.json();
|
||||||
|
let currentUsername = currentInfo.player.info.name;
|
||||||
|
const currentId = currentInfo.player.info.id;
|
||||||
|
const currentStats = currentInfo.player.stats[currentMode];
|
||||||
|
|
||||||
|
currentUsername += ` (#${currentStats.rank})`;
|
||||||
|
|
||||||
|
let largeImageKey = "ezppfarm";
|
||||||
|
let details = "Idle...";
|
||||||
|
let infoText = currentStatus.player_status.status.info_text.length > 0
|
||||||
|
? currentStatus.player_status.status.info_text
|
||||||
|
: " ";
|
||||||
|
if (
|
||||||
|
"beatmap" in currentStatus.player_status.status &&
|
||||||
|
currentStatus.player_status.status.beatmap !== null
|
||||||
|
) {
|
||||||
|
const setId = currentStatus.player_status.status.beatmap.set_id;
|
||||||
|
if (setId) {
|
||||||
|
const coverImage =
|
||||||
|
`https://assets.ppy.sh/beatmaps/${setId}/covers/[email protected]`;
|
||||||
|
if (
|
||||||
|
checkImageExists(coverImage)
|
||||||
|
) {
|
||||||
|
largeImageKey = coverImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (currentStatus.player_status.status.action) {
|
||||||
|
case 1:
|
||||||
|
details = "AFK...";
|
||||||
|
infoText = " ";
|
||||||
|
largeImageKey = "ezppfarm";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
details = "Playing...";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
details = "Editing...";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
details = "Modding...";
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
details = "Multiplayer: Selecting a Beatmap...";
|
||||||
|
infoText = " ";
|
||||||
|
largeImageKey = "ezppfarm";
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
details = "Watching...";
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
details = "Testing...";
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
details = "Submitting...";
|
||||||
|
largeImageKey = "ezppfarm";
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
details = "Multiplayer: Idle...";
|
||||||
|
infoText = " ";
|
||||||
|
largeImageKey = "ezppfarm";
|
||||||
|
break;
|
||||||
|
case 12:
|
||||||
|
details = "Multiplayer: Playing...";
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
details = "Browsing osu!direct...";
|
||||||
|
infoText = " ";
|
||||||
|
largeImageKey = "ezppfarm";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
details = `[${currentModeString}] ${details}`;
|
||||||
|
|
||||||
|
richPresence.updateUser({
|
||||||
|
username: currentUsername,
|
||||||
|
id: currentId,
|
||||||
|
});
|
||||||
|
|
||||||
|
richPresence.updateStatus({
|
||||||
|
details,
|
||||||
|
state: infoText,
|
||||||
|
largeImageKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
richPresence.update();
|
||||||
|
}
|
||||||
|
}, 2500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopOsuStatus() {
|
||||||
|
clearInterval(osuCheckInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerIPCPipes() {
|
||||||
|
ipcMain.handle("ezpplauncher:login", async (e, args) => {
|
||||||
|
let hwid = "";
|
||||||
|
try {
|
||||||
|
hwid = getHwId();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`Failed to get HWID.`, err);
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
message: "Failed to get HWID.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const timeout = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => timeout.abort(), 1000 * 10);
|
||||||
|
logger.log(`Logging in with user ${args.username}...`);
|
||||||
|
try {
|
||||||
|
const fetchResult = await fetch("https://ez-pp.farm/login/check", {
|
||||||
|
signal: timeout.signal,
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: args.username,
|
||||||
|
password: args.password,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
if (fetchResult.ok) {
|
||||||
|
const result = await fetchResult.json();
|
||||||
|
if ("user" in result) {
|
||||||
|
if (args.saveCredentials) {
|
||||||
|
config.set("username", args.username);
|
||||||
|
config.set("password", cryptUtil.encrypt(args.password, hwid));
|
||||||
|
}
|
||||||
|
currentUser = args;
|
||||||
|
config.remove("guest");
|
||||||
|
logger.log(`Logged in as user ${args.username}!`);
|
||||||
|
} else logger.log(`Login failed for user ${args.username}.`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
logger.log(
|
||||||
|
`Login failed for user ${args.username}.\nResponse:\n${await fetchResult
|
||||||
|
.text()}`,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
message: "Something went wrong while logging you in.",
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Error while logging in:", err);
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
message: "Something went wrong while logging you in.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("ezpplauncher:autologin-active", async (e) => {
|
||||||
|
const username = config.get("username");
|
||||||
|
const password = config.get("password");
|
||||||
|
const guest = config.get("guest");
|
||||||
|
if (guest != undefined) return true;
|
||||||
|
return username != undefined && password != undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("ezpplauncher:autologin", async (e) => {
|
||||||
|
const hwid = getHwId();
|
||||||
|
const username = config.get("username");
|
||||||
|
const guest = config.get("guest");
|
||||||
|
if (guest) return { code: 200, message: "Login as guest", guest: true };
|
||||||
|
if (username == undefined) {
|
||||||
|
return { code: 200, message: "No autologin" };
|
||||||
|
}
|
||||||
|
const password = cryptUtil.decrypt(config.get("password"), hwid);
|
||||||
|
if (username == undefined || password == undefined) {
|
||||||
|
return { code: 200, message: "No autologin" };
|
||||||
|
}
|
||||||
|
const timeout = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => timeout.abort(), 8000);
|
||||||
|
logger.log(`Logging in with user ${username}...`);
|
||||||
|
try {
|
||||||
|
const fetchResult = await fetch("https://ez-pp.farm/login/check", {
|
||||||
|
signal: timeout.signal,
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
if (fetchResult.ok) {
|
||||||
|
const result = await fetchResult.json();
|
||||||
|
if ("user" in result) {
|
||||||
|
currentUser = {
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
};
|
||||||
|
logger.log(`Logged in as user ${username}!`);
|
||||||
|
} else logger.log(`Login failed for user ${username}.`);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
config.remove("password");
|
||||||
|
}
|
||||||
|
logger.log(
|
||||||
|
`Login failed for user ${username}.\nResponse:\n${await fetchResult
|
||||||
|
.text()}`,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
message: "Something went wrong while logging you in.",
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Error while logging in:", err);
|
||||||
|
return {
|
||||||
|
code: 500,
|
||||||
|
message: "Something went wrong while logging you in.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("ezpplauncher:guestlogin", (e) => {
|
||||||
|
config.remove("username");
|
||||||
|
config.remove("password");
|
||||||
|
config.set("guest", "1");
|
||||||
|
currentUser = undefined;
|
||||||
|
logger.log("Logged in as guest user.");
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("ezpplauncher:logout", (e) => {
|
||||||
|
config.remove("username");
|
||||||
|
config.remove("password");
|
||||||
|
config.remove("guest");
|
||||||
|
currentUser = undefined;
|
||||||
|
logger.log("Loging out.");
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("ezpplauncher:settings", async (e) => {
|
||||||
|
return config.all();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("ezpplauncher:setting-update", async (e, args) => {
|
||||||
|
for (const key of Object.keys(args)) {
|
||||||
|
const value = args[key];
|
||||||
|
|
||||||
|
if (key == "presence") {
|
||||||
|
if (!value) richPresence.disconnect();
|
||||||
|
else richPresence.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == "logging") {
|
||||||
|
logger.enabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value == "boolean") {
|
||||||
|
config.set(key, value ? "true" : "false");
|
||||||
|
} else {
|
||||||
|
config.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("ezpplauncher:detect-folder", async (e) => {
|
||||||
|
const detected = await findOsuInstallation();
|
||||||
|
if (detected && await isValidOsuFolder(detected)) {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:alert", {
|
||||||
|
type: "success",
|
||||||
|
message: "osu! path successfully saved!",
|
||||||
|
});
|
||||||
|
config.set("osuPath", detected);
|
||||||
|
}
|
||||||
|
return config.all();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("ezpplauncher:set-folder", async (e) => {
|
||||||
|
const folderResult = await dialog.showOpenDialog({
|
||||||
|
title: "Select osu! installation directory",
|
||||||
|
properties: ["openDirectory"],
|
||||||
|
});
|
||||||
|
if (!folderResult.canceled) {
|
||||||
|
const folder = folderResult.filePaths[0];
|
||||||
|
if (await isValidOsuFolder(folder)) {
|
||||||
|
config.set("osuPath", folder);
|
||||||
|
mainWindow.webContents.send("ezpplauncher:alert", {
|
||||||
|
type: "success",
|
||||||
|
message: "osu! path successfully saved!",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:alert", {
|
||||||
|
type: "error",
|
||||||
|
message: "invalid osu! path!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config.all();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("ezpplauncher:checkUpdate", async (e) => {
|
||||||
|
const updateInfo = await updateAvailable();
|
||||||
|
if (updateInfo.update) {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:update", updateInfo.release);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("ezpplauncher:exitAndUpdate", async (e) => {
|
||||||
|
await shell.openExternal(releasesUrl);
|
||||||
|
app.exit();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("ezpplauncher:launch", async (e) => {
|
||||||
|
try {
|
||||||
|
const osuWindowTitle = windowName.getWindowText("osu!.exe");
|
||||||
|
if (osuWindowTitle.length > 0) {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:alert", {
|
||||||
|
type: "error",
|
||||||
|
message: "osu! is running, please exit.",
|
||||||
|
});
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchabort");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log("Preparing launch...");
|
||||||
|
const configPatch = config.get("patch");
|
||||||
|
patch = configPatch != undefined ? configPatch == "true" : true;
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: "Checking osu! directory...",
|
||||||
|
});
|
||||||
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
|
const osuPath = config.get("osuPath");
|
||||||
|
userOsuPath = osuPath;
|
||||||
|
if (osuPath == undefined) {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchabort");
|
||||||
|
mainWindow.webContents.send("ezpplauncher:alert", {
|
||||||
|
type: "error",
|
||||||
|
message: "osu! path not set!",
|
||||||
|
});
|
||||||
|
mainWindow.webContents.send("ezpplauncher:open-settings");
|
||||||
|
logger.log("osu! path is not set.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(await isValidOsuFolder(osuPath))) {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchabort");
|
||||||
|
mainWindow.webContents.send("ezpplauncher:alert", {
|
||||||
|
type: "error",
|
||||||
|
message: "invalid osu! path!",
|
||||||
|
});
|
||||||
|
logger.log("osu! path is invalid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (patch) {
|
||||||
|
if (!(await isNet8Installed())) {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchabort");
|
||||||
|
mainWindow.webContents.send("ezpplauncher:alert", {
|
||||||
|
type: "error",
|
||||||
|
message: ".NET 8 is not installed.",
|
||||||
|
});
|
||||||
|
//open .net 8 download in browser
|
||||||
|
shell.openExternal(
|
||||||
|
"https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.4-windows-x64-installer",
|
||||||
|
);
|
||||||
|
logger.log(".NET 8 is not installed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: "Checking for osu! updates...",
|
||||||
|
});
|
||||||
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
|
const releaseStream = await getGlobalConfig(osuPath).get(
|
||||||
|
"_ReleaseStream",
|
||||||
|
);
|
||||||
|
const latestFiles = await getUpdateFiles(releaseStream);
|
||||||
|
const updateFiles = await getFilesThatNeedUpdate(osuPath, latestFiles);
|
||||||
|
if (updateFiles.length > 0) {
|
||||||
|
logger.log("osu! updates found.");
|
||||||
|
const updateDownloader = downloadUpdateFiles(osuPath, updateFiles);
|
||||||
|
let errored = false;
|
||||||
|
updateDownloader.eventEmitter.on("error", (data) => {
|
||||||
|
const filename = data.fileName;
|
||||||
|
logger.error(
|
||||||
|
`Failed to download/replace ${filename}!`,
|
||||||
|
data.error,
|
||||||
|
);
|
||||||
|
errored = true;
|
||||||
|
mainWindow.webContents.send("ezpplauncher:alert", {
|
||||||
|
type: "error",
|
||||||
|
message:
|
||||||
|
`Failed to download/replace ${filename}!\nMaybe try to restart EZPPLauncher.`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
updateDownloader.eventEmitter.on("data", (data) => {
|
||||||
|
if (data.progress >= 100) {
|
||||||
|
logger.log(`Downloaded ${data.fileName} successfully.`);
|
||||||
|
}
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchprogress", {
|
||||||
|
progress: Math.ceil(data.progress),
|
||||||
|
});
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: `Downloading ${data.fileName}(${formatBytes(data.loaded)}/${
|
||||||
|
formatBytes(data.total)
|
||||||
|
})...`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await updateDownloader.startDownload();
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchprogress", {
|
||||||
|
progress: -1,
|
||||||
|
});
|
||||||
|
if (errored) {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchabort");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: "osu! is now up to date!",
|
||||||
|
});
|
||||||
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
|
} else {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: "osu! is up to date!",
|
||||||
|
});
|
||||||
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patch) {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: "Looking for patcher updates...",
|
||||||
|
});
|
||||||
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
|
const [patchFiles, allUpdateFiles] = await getEZPPLauncherUpdateFiles(
|
||||||
|
osuPath,
|
||||||
|
);
|
||||||
|
if (patchFiles.length > 0) {
|
||||||
|
logger.log("EZPPLauncher updates found.");
|
||||||
|
const patcherDownloader = await downloadEZPPLauncherUpdateFiles(
|
||||||
|
osuPath,
|
||||||
|
patchFiles,
|
||||||
|
allUpdateFiles,
|
||||||
|
);
|
||||||
|
let errored = false;
|
||||||
|
patcherDownloader.eventEmitter.on("error", (data) => {
|
||||||
|
const filename = data.fileName;
|
||||||
|
logger.error(`Failed to download/replace ${filename}!`, data.error);
|
||||||
|
errored = true;
|
||||||
|
mainWindow.webContents.send("ezpplauncher:alert", {
|
||||||
|
type: "error",
|
||||||
|
message:
|
||||||
|
`Failed to download/replace ${filename}!\nMaybe try to restart EZPPLauncher.`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
patcherDownloader.eventEmitter.on("data", (data) => {
|
||||||
|
if (data.progress >= 100) {
|
||||||
|
logger.log(`Downloaded ${data.fileName} successfully.`);
|
||||||
|
}
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchprogress", {
|
||||||
|
progress: Math.ceil(data.progress),
|
||||||
|
});
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: `Downloading ${data.fileName}(${
|
||||||
|
formatBytes(data.loaded)
|
||||||
|
}/${formatBytes(data.total)})...`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
patcherDownloader.eventEmitter.on("delete", (data) => {
|
||||||
|
logger.log(`Deleting ${data.fileName}!`);
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchprogress", {
|
||||||
|
progress: -1,
|
||||||
|
});
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: `Deleting ${data.fileName}...`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await patcherDownloader.startDownload();
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchprogress", {
|
||||||
|
progress: -1,
|
||||||
|
});
|
||||||
|
if (errored) {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchabort");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: "Patcher is now up to date!",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: "Patcher is up to date!",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
|
}
|
||||||
|
if (updateFiles.length > 0) {
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: "Launching osu! updater to verify updates...",
|
||||||
|
});
|
||||||
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
|
|
||||||
|
await new Promise((res) => {
|
||||||
|
runOsuUpdater(osuPath, async () => {
|
||||||
|
await new Promise((res) => setTimeout(res, 500));
|
||||||
|
const terminationThread = setInterval(async () => {
|
||||||
|
const osuWindowTitle = windowName.getWindowText("osu!.exe");
|
||||||
|
if (osuWindowTitle.length < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const firstInstance = osuWindowTitle[0];
|
||||||
|
if (firstInstance) {
|
||||||
|
const processId = firstInstance.processId;
|
||||||
|
await fkill(processId, { force: true, silent: true });
|
||||||
|
clearInterval(terminationThread);
|
||||||
|
res();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
|
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: "Preparing launch...",
|
||||||
|
});
|
||||||
|
|
||||||
|
/* await updateOsuConfigHashes(osuPath); */
|
||||||
|
logger.log("Replacing UI files...");
|
||||||
|
try {
|
||||||
|
await replaceUIFiles(osuPath, false);
|
||||||
|
logger.log("UI files replaced successfully.");
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Failed to replace UI files:", err);
|
||||||
|
mainWindow.webContents.send("ezpplauncher:alert", {
|
||||||
|
type: "error",
|
||||||
|
message: "Failed to replace UI files. try restarting EZPPLauncher.",
|
||||||
|
});
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchabort");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const forceUpdateFiles = [
|
||||||
|
".require_update",
|
||||||
|
"help.txt",
|
||||||
|
"_pending",
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const updateFileName of forceUpdateFiles) {
|
||||||
|
const updateFile = path.join(osuPath, updateFileName);
|
||||||
|
if (fs.existsSync(updateFile)) {
|
||||||
|
await fs.promises.rm(updateFile, {
|
||||||
|
force: true,
|
||||||
|
recursive: (await fs.promises.lstat(updateFile)).isDirectory(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Failed to remove force update files:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userConfig = getUserConfig(osuPath);
|
||||||
|
if (richPresence.hasPresence) {
|
||||||
|
await userConfig.set("DiscordRichPresence", "0");
|
||||||
|
}
|
||||||
|
await userConfig.set("ShowInterfaceDuringRelax", "1");
|
||||||
|
if (currentUser) {
|
||||||
|
await userConfig.set("CredentialEndpoint", "ez-pp.farm");
|
||||||
|
await userConfig.set("SavePassword", "1");
|
||||||
|
await userConfig.set("SaveUsername", "1");
|
||||||
|
await userConfig.set("Username", currentUser.username);
|
||||||
|
await userConfig.set("Password", currentUser.password);
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: "Launching osu!...",
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((res) => setTimeout(res, 1000));
|
||||||
|
|
||||||
|
logger.log("Launching osu!...");
|
||||||
|
|
||||||
|
const onExitHook = () => {
|
||||||
|
logger.log("osu! has exited.");
|
||||||
|
mainWindow.show();
|
||||||
|
mainWindow.focus();
|
||||||
|
stopOsuStatus();
|
||||||
|
richPresence.updateUser({
|
||||||
|
username: " ",
|
||||||
|
id: undefined,
|
||||||
|
});
|
||||||
|
richPresence.updateStatus({
|
||||||
|
state: "Idle in Launcher...",
|
||||||
|
details: undefined,
|
||||||
|
});
|
||||||
|
richPresence.update();
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchstatus", {
|
||||||
|
status: "Waiting for cleanup...",
|
||||||
|
});
|
||||||
|
const timeStart = performance.now();
|
||||||
|
logger.log("Waiting for cleanup...");
|
||||||
|
|
||||||
|
const cleanup = setInterval(async () => {
|
||||||
|
const osuUIFile = path.join(osuPath, "osu!ui.dll");
|
||||||
|
const osuGameplayFile = path.join(osuPath, "osu!gameplay.dll");
|
||||||
|
if (isWritable(osuUIFile) && isWritable(osuGameplayFile)) {
|
||||||
|
logger.log(
|
||||||
|
`Cleanup complete, took ${
|
||||||
|
((performance.now() - timeStart) / 1000).toFixed(3)
|
||||||
|
} seconds.`,
|
||||||
|
);
|
||||||
|
clearInterval(cleanup);
|
||||||
|
await replaceUIFiles(osuPath, true);
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchabort");
|
||||||
|
osuLoaded = false;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
runOsuWithDevServer(osuPath, "ez-pp.farm", onExitHook);
|
||||||
|
mainWindow.hide();
|
||||||
|
startOsuStatus();
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Failed to launch", err);
|
||||||
|
mainWindow.webContents.send("ezpplauncher:alert", {
|
||||||
|
type: "error",
|
||||||
|
message: "Failed to launch osu!. Please try again.",
|
||||||
|
});
|
||||||
|
mainWindow.webContents.send("ezpplauncher:launchabort");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
|
if (!gotTheLock) {
|
||||||
|
app.quit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupTitlebar();
|
||||||
|
|
||||||
|
// Create the browser window.
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width: 550,
|
||||||
|
height: 350,
|
||||||
|
resizable: false,
|
||||||
|
frame: false,
|
||||||
|
titleBarStyle: "hidden",
|
||||||
|
title: `${appName} ${appVersion}`,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
preload: path.join(__dirname, "preload.js"),
|
||||||
|
},
|
||||||
|
icon: path.join(__dirname, "public/favicon.png"),
|
||||||
|
show: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const menu = Menu.buildFromTemplate([]);
|
||||||
|
Menu.setApplicationMenu(menu);
|
||||||
|
|
||||||
|
// disable electron toolbar
|
||||||
|
/* if (!isDev()) */
|
||||||
|
mainWindow.setMenu(null);
|
||||||
|
|
||||||
|
attachTitlebarToWindow(mainWindow);
|
||||||
|
|
||||||
|
// This block of code is intended for development purpose only.
|
||||||
|
// Delete this entire block of code when you are ready to package the application.
|
||||||
|
if (isDev()) {
|
||||||
|
mainWindow.loadURL("http://localhost:8080/");
|
||||||
|
} else {
|
||||||
|
loadURL(mainWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerIPCPipes();
|
||||||
|
|
||||||
|
const presenceEnabled = config.get("presence");
|
||||||
|
if (presenceEnabled == undefined) {
|
||||||
|
richPresence.connect();
|
||||||
|
} else {
|
||||||
|
if (presenceEnabled == "true") {
|
||||||
|
richPresence.connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.init();
|
||||||
|
|
||||||
|
const loggingEnabled = config.get("logging");
|
||||||
|
if (loggingEnabled && loggingEnabled == "true") {
|
||||||
|
logger.enabled = true;
|
||||||
|
}
|
||||||
|
// Uncomment the following line of code when app is ready to be packaged.
|
||||||
|
// loadURL(mainWindow);
|
||||||
|
|
||||||
|
// Open the DevTools and also disable Electron Security Warning.
|
||||||
|
if (isDev()) {
|
||||||
|
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = true;
|
||||||
|
mainWindow.webContents.openDevTools({ mode: "detach" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emitted when the window is closed.
|
||||||
|
mainWindow.on("closed", function () {
|
||||||
|
// Dereference the window object, usually you would store windows
|
||||||
|
// in an array if your app supports multi windows, this is the time
|
||||||
|
// when you should delete the corresponding element.
|
||||||
|
mainWindow = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emitted when the window is ready to be shown
|
||||||
|
// This helps in showing the window gracefully.
|
||||||
|
mainWindow.once("ready-to-show", async () => {
|
||||||
|
mainWindow.show();
|
||||||
|
mainWindow.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method will be called when Electron has finished
|
||||||
|
// initialization and is ready to create browser windows.
|
||||||
|
// Some APIs can only be used after this event occurs.
|
||||||
|
app.on("ready", createWindow);
|
||||||
|
|
||||||
|
// Quit when all windows are closed.
|
||||||
|
app.on("window-all-closed", async function () {
|
||||||
|
// On macOS it is common for applications and their menu bar
|
||||||
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
|
await richPresence.disconnect();
|
||||||
|
if (process.platform !== "darwin") app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on("activate", function () {
|
||||||
|
// On macOS it's common to re-create a window in the app when the
|
||||||
|
// dock icon is clicked and there are no other windows open.
|
||||||
|
if (mainWindow === null) createWindow();
|
||||||
|
});
|
||||||
|
// In this file you can include the rest of your app's specific main process
|
||||||
|
// code. You can also put them in separate files and require them here.
|
||||||
-150
@@ -1,150 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
const fu = require('./fileUtil');
|
|
||||||
const path = require('path');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
const axios = require('axios').default;
|
|
||||||
const executeUtil = require('./executeUtil');
|
|
||||||
const { EventEmitter } = require('events');
|
|
||||||
const { DownloaderHelper } = require('node-downloader-helper');
|
|
||||||
|
|
||||||
const checkUpdateURL = "https://osu.ppy.sh/web/check-updates.php?action=check&stream=";
|
|
||||||
const osuEntities = [
|
|
||||||
'avcodec-51.dll',
|
|
||||||
'avformat-52.dll',
|
|
||||||
'avutil-49.dll',
|
|
||||||
'bass.dll',
|
|
||||||
'bass_fx.dll',
|
|
||||||
'collection.db',
|
|
||||||
'd3dcompiler_47.dll',
|
|
||||||
'Data',
|
|
||||||
'Downloads',
|
|
||||||
'libEGL.dll',
|
|
||||||
'libGLESv2.dll',
|
|
||||||
'Logs',
|
|
||||||
'Microsoft.Ink.dll',
|
|
||||||
'OpenTK.dll',
|
|
||||||
'osu!.cfg',
|
|
||||||
'osu!.db',
|
|
||||||
'osu!.exe',
|
|
||||||
'osu!auth.dll',
|
|
||||||
'osu!gameplay.dll',
|
|
||||||
'osu!seasonal.dll',
|
|
||||||
'osu!ui.dll',
|
|
||||||
'presence.db',
|
|
||||||
'pthreadGC2.dll',
|
|
||||||
'scores.db',
|
|
||||||
'Skins',
|
|
||||||
'Songs'
|
|
||||||
]
|
|
||||||
|
|
||||||
async function isValidOsuFolder(path) {
|
|
||||||
const allFiles = await fs.promises.readdir(path);
|
|
||||||
let matches = 0;
|
|
||||||
for (const file of allFiles) {
|
|
||||||
if (osuEntities.includes(file)) matches = matches + 1;
|
|
||||||
}
|
|
||||||
return Math.round((matches / osuEntities.length) * 100) >= 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getLatestConfig(osuPath) {
|
|
||||||
const allFiles = await fs.promises.readdir(osuPath);
|
|
||||||
const configFileInfo = {
|
|
||||||
name: "",
|
|
||||||
path: "",
|
|
||||||
lastModified: 0,
|
|
||||||
get: async (key) => {
|
|
||||||
const fileStream = await fs.promises.readFile(configFileInfo.path, "utf-8");
|
|
||||||
const lines = fileStream.split(/\r?\n/)
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.includes(' = ')) {
|
|
||||||
const argsPair = line.split(' = ', 2);
|
|
||||||
const keyname = argsPair[0]
|
|
||||||
const value = argsPair[1];
|
|
||||||
if (keyname == key) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const file of allFiles) {
|
|
||||||
if (file.startsWith('osu!.') && file.endsWith('.cfg') && file !== "osu!.cfg") {
|
|
||||||
const fullFilePath = path.join(osuPath, file);
|
|
||||||
const fileStats = await fs.promises.stat(fullFilePath);
|
|
||||||
const lastModified = fileStats.mtimeMs;
|
|
||||||
if (lastModified > configFileInfo.lastModified) {
|
|
||||||
configFileInfo.name = file;
|
|
||||||
configFileInfo.path = fullFilePath;
|
|
||||||
configFileInfo.lastModified = lastModified;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return configFileInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getUpdateFiles(releaseStream) {
|
|
||||||
const releaseData = await axios.get(checkUpdateURL + releaseStream, {});
|
|
||||||
return releaseData.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function filesThatNeedUpdate(osuPath, updateFiles) {
|
|
||||||
const filesToDownload = [];
|
|
||||||
for (const updatedFile of updateFiles) {
|
|
||||||
const fileName = updatedFile.filename;
|
|
||||||
const fileHash = updatedFile.file_hash;
|
|
||||||
const fileURL = updatedFile.url_full;
|
|
||||||
|
|
||||||
const fileOnDisk = path.join(osuPath, fileName);
|
|
||||||
if (await fu.existsAsync(fileOnDisk)) {
|
|
||||||
const binaryFileContents = await fs.promises.readFile(fileOnDisk);
|
|
||||||
const existingFileMD5 = crypto.createHash("md5").update(binaryFileContents).digest("hex");
|
|
||||||
if (existingFileMD5.toLowerCase() != fileHash.toLowerCase()) {
|
|
||||||
filesToDownload.push({
|
|
||||||
fileName,
|
|
||||||
fileURL
|
|
||||||
})
|
|
||||||
// console.log("hashes are not matching", `(${existingFileMD5} - ${fileHash})`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
filesToDownload.push({
|
|
||||||
fileName,
|
|
||||||
fileURL
|
|
||||||
});
|
|
||||||
// console.log("new file " + fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filesToDownload;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadUpdateFiles(osuPath, filesToUpdate) {
|
|
||||||
const eventEmitter = new EventEmitter();
|
|
||||||
let completedIndex = 0;
|
|
||||||
filesToUpdate.forEach(async (fileToUpdate) => {
|
|
||||||
const filePath = path.join(osuPath, fileToUpdate.fileName);
|
|
||||||
if (await fu.existsAsync(filePath))
|
|
||||||
await fs.promises.rm(filePath);
|
|
||||||
|
|
||||||
const fileDownload = new DownloaderHelper(fileToUpdate.fileURL, osuPath, {
|
|
||||||
fileName: fileToUpdate.fileName,
|
|
||||||
override: true,
|
|
||||||
});
|
|
||||||
fileDownload.on('end', () => {
|
|
||||||
completedIndex = completedIndex + 1;
|
|
||||||
if (completedIndex >= filesToUpdate.length)
|
|
||||||
eventEmitter.emit('completed');
|
|
||||||
});
|
|
||||||
|
|
||||||
fileDownload.start().catch(err => console.error(err));
|
|
||||||
});
|
|
||||||
|
|
||||||
return eventEmitter;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function startWithDevServer(osuPath, serverDomain, onExit) {
|
|
||||||
const osuExe = path.join(osuPath, "osu!.exe");
|
|
||||||
if (!await fu.existsAsync(osuExe)) return false;
|
|
||||||
executeUtil.runFile(osuPath, osuExe, ["-devserver", serverDomain], onExit);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { isValidOsuFolder, getLatestConfig, getUpdateFiles, filesThatNeedUpdate, downloadUpdateFiles, startOsuWithDevServer: startWithDevServer }
|
|
||||||
Generated
+9360
File diff suppressed because it is too large.
Load diff
+76
-40
@@ -1,54 +1,90 @@
|
|||||||
{
|
{
|
||||||
"name": "ezpplauncher",
|
"name": "ezpplauncher-next",
|
||||||
"version": "1.0.0",
|
"version": "2.1.7",
|
||||||
"main": "app.js",
|
"description": "EZPPLauncher rewritten with Svelte.",
|
||||||
|
"private": false,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "HorizonCode",
|
"main": "main.js",
|
||||||
|
"author": "HorizonCode <[email protected]>",
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "farm.ezpp.ezppfarm.launcher",
|
"icon": "public/favicon.png",
|
||||||
"productName": "ezpplauncher",
|
"productName": "EZPPLauncher",
|
||||||
"directories": {
|
"files": [
|
||||||
"output": "release",
|
"public/**/*",
|
||||||
"buildResources": "dist"
|
"main.js",
|
||||||
},
|
"preload.js",
|
||||||
"asar": true,
|
"electron/*",
|
||||||
|
"electron/**/*"
|
||||||
|
],
|
||||||
"win": {
|
"win": {
|
||||||
"icon": "./assets/logo.png",
|
|
||||||
"target": [
|
"target": [
|
||||||
"portable"
|
"portable"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nsis": {
|
"linux": {},
|
||||||
"runAfterFinish": true
|
"mac": {}
|
||||||
},
|
|
||||||
"portable": {
|
|
||||||
"artifactName": "EZPPLauncher.exe"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"frontend": {
|
|
||||||
"config": {
|
|
||||||
"applicationName": "EZPPLauncher"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"build": "rollup -c --bundleConfigAsCjs",
|
||||||
"pack-win": "electron-builder --x64",
|
"rebuild": "electron-rebuild",
|
||||||
"pack-win32": "electron-builder --ia32",
|
"dev": "rollup -c -w --bundleConfigAsCjs",
|
||||||
"pack-winarm": "electron-builder --arm64",
|
"start": "sirv public --no-clear",
|
||||||
"pack-linux": "electron-builder --dir --linux --ia32 --arm64 --x64",
|
"electron": "wait-on http://localhost:8080 && electron .",
|
||||||
"pack-mac": "electron-builder --dir --mac --ia32 --arm64 --x64",
|
"electron-dev": "concurrently \"yarn run dev\" \"yarn run electron\"",
|
||||||
"dist": "electron-builder"
|
"preelectron-pack": "electron-rebuild && yarn run build",
|
||||||
},
|
"electron-pack": "electron-builder",
|
||||||
"devDependencies": {
|
"postinstall": "electron-builder install-app-deps",
|
||||||
"electron": "^17.4.3",
|
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||||
"electron-builder": "^23.0.3",
|
|
||||||
"electron-packager": "^15.5.1"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.27.2",
|
"@types/better-sqlite3": "^7.6.8",
|
||||||
"custom-electron-titlebar": "^4.1.1",
|
"axios": "^1.6.5",
|
||||||
"jquery": "^3.6.0",
|
"better-sqlite3": "^9.2.2",
|
||||||
"node-downloader-helper": "^2.1.4",
|
"crypto": "^1.0.1",
|
||||||
"sweetalert2": "^11.5.2"
|
"crypto-js": "^4.2.0",
|
||||||
|
"custom-electron-titlebar": "^4.2.7",
|
||||||
|
"discord-auto-rpc": "^1.0.17",
|
||||||
|
"electron-serve": "^1.1.0",
|
||||||
|
"electron-unhandled": "^4.0.1",
|
||||||
|
"fkill": "^7.2.1",
|
||||||
|
"get-window-by-name": "^2.0.0",
|
||||||
|
"regedit-rs": "^1.0.2",
|
||||||
|
"semver": "^7.5.4",
|
||||||
|
"svelte-french-toast": "^1.2.0",
|
||||||
|
"sweetalert2": "^11.10.8",
|
||||||
|
"systeminformation": "^5.21.22"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@electron/rebuild": "^3.5.0",
|
||||||
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
|
"@rollup/plugin-image": "^3.0.3",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
|
"@rollup/plugin-typescript": "^11.1.5",
|
||||||
|
"@tsconfig/svelte": "^5.0.2",
|
||||||
|
"autoprefixer": "^10.4.16",
|
||||||
|
"concurrently": "^8.2.2",
|
||||||
|
"electron": "^28.1.2",
|
||||||
|
"electron-builder": "^24.9.1",
|
||||||
|
"flowbite": "^2.2.1",
|
||||||
|
"flowbite-svelte": "^0.44.21",
|
||||||
|
"flowbite-svelte-icons": "^0.4.5",
|
||||||
|
"postcss": "^8.4.32",
|
||||||
|
"postcss-load-config": "^5.0.2",
|
||||||
|
"rollup": "^4.9.2",
|
||||||
|
"rollup-plugin-css-only": "^4.5.2",
|
||||||
|
"rollup-plugin-livereload": "^2.0.5",
|
||||||
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
|
"rollup-plugin-progress": "^1.1.2",
|
||||||
|
"rollup-plugin-svelte": "^7.1.6",
|
||||||
|
"rollup-plugin-unused": "^0.1.1",
|
||||||
|
"sirv-cli": "^2.0.2",
|
||||||
|
"svelte": "^4.2.8",
|
||||||
|
"svelte-check": "^3.6.2",
|
||||||
|
"svelte-preprocess": "^5.1.3",
|
||||||
|
"tailwindcss": "^3.3.6",
|
||||||
|
"tslib": "^2.6.2",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"wait-on": "^7.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
const tailwindcss = require("tailwindcss");
|
||||||
|
const autoprefixer = require("autoprefixer");
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
plugins: [
|
||||||
|
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
||||||
|
tailwindcss(),
|
||||||
|
//But others, like autoprefixer, need to run after,
|
||||||
|
autoprefixer,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
const { Titlebar, TitlebarColor } = require("custom-electron-titlebar");
|
||||||
|
const { ipcRenderer } = require("electron");
|
||||||
|
const { appName, appVersion } = require("./electron/appInfo");
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const titlebar = new Titlebar({
|
||||||
|
backgroundColor: TitlebarColor.fromHex("#202020"),
|
||||||
|
itemBackgroundColor: TitlebarColor.fromHex("#202020"),
|
||||||
|
menu: null,
|
||||||
|
enableMnemonics: false,
|
||||||
|
maximizable: false,
|
||||||
|
});
|
||||||
|
titlebar.updateTitle(`${appName} ${appVersion}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("login-attempt", async (e) => {
|
||||||
|
const loginResult = await ipcRenderer.invoke("ezpplauncher:login", {
|
||||||
|
username: e.detail.username,
|
||||||
|
password: e.detail.password,
|
||||||
|
saveCredentials: e.detail.saveCredentials,
|
||||||
|
});
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("login-result", { detail: loginResult }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("autologin-active", async (e) => {
|
||||||
|
const autologin = await ipcRenderer.invoke(
|
||||||
|
"ezpplauncher:autologin-active",
|
||||||
|
);
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("autologin-result", { detail: autologin }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("autologin-attempt", async () => {
|
||||||
|
const loginResult = await ipcRenderer.invoke("ezpplauncher:autologin");
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("login-result", { detail: loginResult }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("logout", async () => {
|
||||||
|
await ipcRenderer.invoke("ezpplauncher:logout");
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("guest-login", async () => {
|
||||||
|
await ipcRenderer.invoke("ezpplauncher:guestlogin");
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("launch", async (e) => {
|
||||||
|
await ipcRenderer.invoke("ezpplauncher:launch", e.detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("settings-get", async () => {
|
||||||
|
const settings = await ipcRenderer.invoke("ezpplauncher:settings");
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("settings-result", { detail: settings }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("setting-update", async (e) => {
|
||||||
|
const detail = e.detail;
|
||||||
|
await ipcRenderer.invoke("ezpplauncher:setting-update", detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("folder-auto", async (e) => {
|
||||||
|
const result = await ipcRenderer.invoke("ezpplauncher:detect-folder");
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("settings-result", { detail: result }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("folder-set", async (e) => {
|
||||||
|
const result = await ipcRenderer.invoke("ezpplauncher:set-folder");
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("settings-result", { detail: result }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("settings-set", async (e) => {
|
||||||
|
await ipcRenderer.invoke("ezpplauncher:settings-set", e.detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("updateCheck", async () => {
|
||||||
|
await ipcRenderer.invoke("ezpplauncher:checkUpdate");
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener("updateExit", async () => {
|
||||||
|
await ipcRenderer.invoke("ezpplauncher:exitAndUpdate");
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.addListener("ezpplauncher:launchabort", (e, args) => {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("launch-abort"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.addListener("ezpplauncher:alert", (e, args) => {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("alert", { detail: args }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.addListener("ezpplauncher:launchstatus", (e, args) => {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("launchStatusUpdate", { detail: args }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.addListener("ezpplauncher:launchprogress", (e, args) => {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("launchProgressUpdate", { detail: args }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.addListener("ezpplauncher:update", (e, args) => {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("update", { detail: args }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.addListener("ezpplauncher:open-settings", (e, args) => {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("open-settings"),
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
const { ipcRenderer } = require('electron');
|
|
||||||
const { Titlebar, Color } = require('custom-electron-titlebar');
|
|
||||||
const appInfo = require('../appInfo');
|
|
||||||
let titlebar;
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
|
||||||
titlebar = new Titlebar({
|
|
||||||
backgroundColor: Color.fromHex("#303030"),
|
|
||||||
itemBackgroundColor: Color.fromHex("#121212"),
|
|
||||||
menu: null,
|
|
||||||
maximizable: false
|
|
||||||
});
|
|
||||||
|
|
||||||
titlebar.updateTitle(`${appInfo.appName} ${appInfo.appVersion}`);
|
|
||||||
|
|
||||||
const $ = require('jquery');
|
|
||||||
const Swal = require('sweetalert2');
|
|
||||||
|
|
||||||
let currentState;
|
|
||||||
|
|
||||||
$("#launch-btn").on('click', async () => {
|
|
||||||
switch (currentState) {
|
|
||||||
case "up-to-date":
|
|
||||||
$("#launch-btn").attr('disabled', true);
|
|
||||||
$('#launch-btn').html('Launching...');
|
|
||||||
const result = await ipcRenderer.invoke("launch");
|
|
||||||
if (!result) {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Uh oh!',
|
|
||||||
text: "Something went wrong while launching!",
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'Okay'
|
|
||||||
});
|
|
||||||
$("#launch-btn").attr('disabled', false);
|
|
||||||
$('#launch-btn').html('Launch');
|
|
||||||
} else {
|
|
||||||
$("#launch-btn").attr('disabled', true);
|
|
||||||
$('#launch-btn').html('Running...');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "update-available":
|
|
||||||
$("#launch-btn").attr('disabled', true);
|
|
||||||
$('#launch-btn').html('Updating...');
|
|
||||||
ipcRenderer.send("do-update");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#folder-btn").on('click', async () => {
|
|
||||||
const success = await ipcRenderer.invoke('set-osu-dir');
|
|
||||||
if (success == undefined)
|
|
||||||
return;
|
|
||||||
if (success) {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Success!',
|
|
||||||
text: 'osu! folder set.',
|
|
||||||
icon: 'success',
|
|
||||||
confirmButtonText: 'Cool'
|
|
||||||
})
|
|
||||||
ipcRenderer.send("do-update-check");
|
|
||||||
} else {
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Uh oh!',
|
|
||||||
text: 'The selected folder is not a osu! directory.',
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'Oops.. my bad!'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcRenderer.on('status_update', (event, status) => {
|
|
||||||
currentState = status.type;
|
|
||||||
switch (status.type) {
|
|
||||||
case "up-to-date":
|
|
||||||
$("#launch-btn").attr('disabled', false);
|
|
||||||
$('#launch-btn').html('Launch');
|
|
||||||
break;
|
|
||||||
case "update-available":
|
|
||||||
$("#launch-btn").attr('disabled', false);
|
|
||||||
$('#launch-btn').html('Update');
|
|
||||||
break;
|
|
||||||
case "missing-folder":
|
|
||||||
$('#launch-btn').html('Please set your osu! folder');
|
|
||||||
break;
|
|
||||||
case "error":
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Uh oh!',
|
|
||||||
text: status.message,
|
|
||||||
icon: 'error',
|
|
||||||
confirmButtonText: 'Okay'
|
|
||||||
});
|
|
||||||
ipcRenderer.send("do-update-check");
|
|
||||||
break;
|
|
||||||
case "update-complete":
|
|
||||||
Swal.fire({
|
|
||||||
title: 'Yaaay!',
|
|
||||||
text: "Your osu! client has been successfully updated!",
|
|
||||||
icon: 'success',
|
|
||||||
confirmButtonText: 'Thanks :3'
|
|
||||||
});
|
|
||||||
ipcRenderer.send("do-update-check");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// workaround for the dark theme
|
|
||||||
$('head').append($('<link href="../assets/sweetalert2.dark.css" rel="stylesheet" />'));
|
|
||||||
})
|
|
||||||
File renamed without changes.
@@ -0,0 +1,15 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
|
Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
|
||||||
|
<title>EZPPLauncher</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Exo+2:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Prompt:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
|
<link rel="stylesheet" href="/global.css" />
|
||||||
|
<link rel="stylesheet" href="/build/bundle.css" />
|
||||||
|
|
||||||
|
<script defer src="/build/bundle.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="select-none bg-gray-100 dark:bg-gray-900 overflow-hidden"></body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import svelte from "rollup-plugin-svelte";
|
||||||
|
import commonjs from "@rollup/plugin-commonjs";
|
||||||
|
import resolve from "@rollup/plugin-node-resolve";
|
||||||
|
import livereload from "rollup-plugin-livereload";
|
||||||
|
import terser from "@rollup/plugin-terser";
|
||||||
|
import css from "rollup-plugin-css-only";
|
||||||
|
import postcss from "rollup-plugin-postcss";
|
||||||
|
import image from "@rollup/plugin-image";
|
||||||
|
import sveltePreprocess from "svelte-preprocess";
|
||||||
|
import typescript from "@rollup/plugin-typescript";
|
||||||
|
import progress from "rollup-plugin-progress";
|
||||||
|
|
||||||
|
const production = !process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
|
function serve() {
|
||||||
|
let server;
|
||||||
|
|
||||||
|
function toExit() {
|
||||||
|
if (server) server.kill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
writeBundle() {
|
||||||
|
if (server) return;
|
||||||
|
server = require("child_process").spawn("npm", [
|
||||||
|
"run",
|
||||||
|
"start",
|
||||||
|
"--",
|
||||||
|
"--dev",
|
||||||
|
], {
|
||||||
|
stdio: ["ignore", "inherit", "inherit"],
|
||||||
|
shell: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("SIGTERM", toExit);
|
||||||
|
process.on("exit", toExit);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: "src/main.ts",
|
||||||
|
output: {
|
||||||
|
sourcemap: true,
|
||||||
|
format: "iife",
|
||||||
|
name: "app",
|
||||||
|
file: "public/build/bundle.js",
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
!production && progress({ clearLine: true }),
|
||||||
|
svelte({
|
||||||
|
preprocess: sveltePreprocess({ sourceMap: !production }),
|
||||||
|
compilerOptions: {
|
||||||
|
// enable run-time checks when not in production
|
||||||
|
dev: !production,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
// we'll extract any component CSS out into
|
||||||
|
// a separate file - better for performance
|
||||||
|
css({ output: "bundle.css" }),
|
||||||
|
postcss({ sourceMap: "inline" }),
|
||||||
|
|
||||||
|
// If you have external dependencies installed from
|
||||||
|
// npm, you'll most likely need these plugins. In
|
||||||
|
// some cases you'll need additional configuration -
|
||||||
|
// consult the documentation for details:
|
||||||
|
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
||||||
|
resolve({
|
||||||
|
browser: true,
|
||||||
|
dedupe: ["svelte"],
|
||||||
|
exportConditions: ["svelte"],
|
||||||
|
}),
|
||||||
|
typescript({
|
||||||
|
sourceMap: !production,
|
||||||
|
inlineSources: !production,
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
image(),
|
||||||
|
|
||||||
|
// In dev mode, call `npm run start` once
|
||||||
|
// the bundle has been generated
|
||||||
|
!production && serve(),
|
||||||
|
|
||||||
|
// Watch the `public` directory and refresh the
|
||||||
|
// browser on changes when not in production
|
||||||
|
!production && livereload("public"),
|
||||||
|
|
||||||
|
// If we're building for production (npm run build
|
||||||
|
// instead of npm run dev), minify
|
||||||
|
production && terser(),
|
||||||
|
],
|
||||||
|
watch: {
|
||||||
|
clearScreen: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
+223
@@ -0,0 +1,223 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Avatar from "flowbite-svelte/Avatar.svelte";
|
||||||
|
import Dropdown from "flowbite-svelte/Dropdown.svelte";
|
||||||
|
import DropdownItem from "flowbite-svelte/DropdownItem.svelte";
|
||||||
|
import DropdownHeader from "flowbite-svelte/DropdownHeader.svelte";
|
||||||
|
import DropdownDivider from "flowbite-svelte/DropdownDivider.svelte";
|
||||||
|
import Button from "flowbite-svelte/Button.svelte";
|
||||||
|
import Indicator from "flowbite-svelte/Indicator.svelte";
|
||||||
|
import ArrowLeftSolid from "flowbite-svelte-icons/ArrowLeftSolid.svelte";
|
||||||
|
import ArrowRightFromBracketSolid from "flowbite-svelte-icons/ArrowRightFromBracketSolid.svelte";
|
||||||
|
import ArrowRightToBracketSolid from "flowbite-svelte-icons/ArrowRightToBracketSolid.svelte";
|
||||||
|
import HeartSolid from "flowbite-svelte-icons/HeartSolid.svelte";
|
||||||
|
import UserSettingsSolid from "flowbite-svelte-icons/UserSettingsSolid.svelte";
|
||||||
|
import ezppLogo from "../public/favicon.png";
|
||||||
|
import {
|
||||||
|
currentPage,
|
||||||
|
currentUser,
|
||||||
|
launching,
|
||||||
|
launchPercentage,
|
||||||
|
launchStatus,
|
||||||
|
} from "./storage/localStore";
|
||||||
|
import { Page } from "./consts/pages";
|
||||||
|
import Login from "./pages/Login.svelte";
|
||||||
|
import Launch from "./pages/Launch.svelte";
|
||||||
|
import toast, { Toaster } from "svelte-french-toast";
|
||||||
|
import type { User } from "./types/user";
|
||||||
|
import Settings from "./pages/Settings.svelte";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
|
let user: User | undefined = undefined;
|
||||||
|
let loggedIn = false;
|
||||||
|
|
||||||
|
let updateInfo: Record<string, unknown>;
|
||||||
|
|
||||||
|
currentUser.subscribe((newUser) => {
|
||||||
|
loggedIn = newUser != undefined;
|
||||||
|
user = newUser;
|
||||||
|
});
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
window.dispatchEvent(new CustomEvent("logout"));
|
||||||
|
currentUser.set(undefined);
|
||||||
|
currentPage.set(Page.Login);
|
||||||
|
toast.success("Successfully logged out!", {
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("update", async (e) => {
|
||||||
|
const update = (e as CustomEvent).detail;
|
||||||
|
await Swal.fire({
|
||||||
|
html: `EZPPLauncher ${update.tag_name} is now available!<br>Click the Button bellow to download the latest release!`,
|
||||||
|
title: "It's your lucky day!",
|
||||||
|
allowOutsideClick: false,
|
||||||
|
allowEscapeKey: false,
|
||||||
|
allowEnterKey: false,
|
||||||
|
confirmButtonText: "Thanks!",
|
||||||
|
});
|
||||||
|
window.dispatchEvent(new CustomEvent("updateExit"));
|
||||||
|
});
|
||||||
|
|
||||||
|
window.dispatchEvent(new CustomEvent("updateCheck"));
|
||||||
|
|
||||||
|
window.addEventListener("open-settings", (e) => {
|
||||||
|
currentPage.set(Page.Settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("launchStatusUpdate", (e) => {
|
||||||
|
const status = (e as CustomEvent).detail.status;
|
||||||
|
launchStatus.set(status);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("launchProgressUpdate", (e) => {
|
||||||
|
const progress = (e as CustomEvent).detail.progress;
|
||||||
|
launchPercentage.set(progress);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("launch-abort", () => {
|
||||||
|
launchPercentage.set(-1);
|
||||||
|
launchStatus.set("");
|
||||||
|
launching.set(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("alert", (e) => {
|
||||||
|
const toastMessage = (e as CustomEvent).detail;
|
||||||
|
switch (toastMessage.type) {
|
||||||
|
case "success": {
|
||||||
|
toast.success(toastMessage.message, {
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "error": {
|
||||||
|
toast.error(toastMessage.message, {
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 4000,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
toast(toastMessage.message, {
|
||||||
|
icon: "ℹ",
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 1500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Toaster></Toaster>
|
||||||
|
|
||||||
|
{#if !updateInfo}
|
||||||
|
<div class="p-2 flex flex-row justify-between items-center">
|
||||||
|
<div class="flex flex-row items-center animate-fadeIn opacity-0">
|
||||||
|
{#if $currentPage == Page.Settings}
|
||||||
|
<Button
|
||||||
|
class="!ring-0 w-10 h-10 mr-1 rounded-lg animate-sideIn opacity-0 active:scale-95 transition-transform duration-75"
|
||||||
|
color="light"
|
||||||
|
on:click={() => {
|
||||||
|
currentPage.set(Page.Launch);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowLeftSolid class="outline-none border-none" size="sm" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
<img src={ezppLogo} alt="EZPPFarm Logo" class="w-12 h-12 mr-2" />
|
||||||
|
<span class="text-gray-700 dark:text-gray-100 text-xl font-extralight">
|
||||||
|
EZPPLauncher
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{#if $currentPage == Page.Launch}
|
||||||
|
<div
|
||||||
|
class="flex flex-row gap-2 w-fill cursor-pointer md:order-2 animate-lsideIn opacity-0"
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
class="rounded-lg border dark:border-gray-700 hover:ring-4 hover:ring-gray-200 dark:hover:ring-gray-800"
|
||||||
|
src={loggedIn
|
||||||
|
? "https://a.ez-pp.farm/" + user?.id
|
||||||
|
: "https://a.ez-pp.farm/0"}
|
||||||
|
id="avatar-menu"
|
||||||
|
/>
|
||||||
|
<!-- TODO: if user has donator, display heart indicator-->
|
||||||
|
{#if $currentUser && $currentUser.donor}
|
||||||
|
<Indicator
|
||||||
|
class="pointer-events-none"
|
||||||
|
color="red"
|
||||||
|
border
|
||||||
|
size="xl"
|
||||||
|
placement="top-right"
|
||||||
|
>
|
||||||
|
<span class="text-red-300 text-xs font-bold">
|
||||||
|
<HeartSolid class="select-none pointer-events-none" size="xs" />
|
||||||
|
</span>
|
||||||
|
</Indicator>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<Dropdown placement="bottom-start" triggeredBy="#avatar-menu">
|
||||||
|
<DropdownHeader>
|
||||||
|
<span class="block text-sm">{loggedIn ? user?.name : "Guest"}</span>
|
||||||
|
<span
|
||||||
|
class="block truncate text-sm font-medium text-gray-500 dark:text-gray-200"
|
||||||
|
>
|
||||||
|
{loggedIn ? user?.email : "Please log in!"}
|
||||||
|
</span>
|
||||||
|
</DropdownHeader>
|
||||||
|
<DropdownItem
|
||||||
|
class="flex flex-row gap-2 border-0 transition-colors"
|
||||||
|
on:click={() => {
|
||||||
|
if (!$launching) currentPage.set(Page.Settings);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UserSettingsSolid class="select-none outline-none border-none" />
|
||||||
|
Settings
|
||||||
|
</DropdownItem>
|
||||||
|
<DropdownDivider />
|
||||||
|
{#if loggedIn}
|
||||||
|
<DropdownItem
|
||||||
|
class="flex flex-row gap-2 border-0 transition-colors"
|
||||||
|
on:click={() => {
|
||||||
|
if (!$launching) logout();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowRightFromBracketSolid
|
||||||
|
class="select-none outline-none border-none"
|
||||||
|
/>
|
||||||
|
Sign out
|
||||||
|
</DropdownItem>
|
||||||
|
{:else}
|
||||||
|
<DropdownItem
|
||||||
|
class="flex flex-row gap-2 border-0 transition-colors"
|
||||||
|
on:click={() => {
|
||||||
|
if (!$launching) currentPage.set(Page.Login);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowRightToBracketSolid
|
||||||
|
class="select-none outline-none border-none"
|
||||||
|
/>
|
||||||
|
Login
|
||||||
|
</DropdownItem>
|
||||||
|
{/if}
|
||||||
|
</Dropdown>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if $currentPage == Page.Login}
|
||||||
|
<Login />
|
||||||
|
{:else if $currentPage == Page.Settings}
|
||||||
|
<Settings />
|
||||||
|
{:else}
|
||||||
|
<Launch />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/* Write your global styles here, in PostCSS syntax */
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-family: "Prompt";
|
||||||
|
-webkit-user-select: none !important;
|
||||||
|
-khtml-user-select: none !important;
|
||||||
|
-moz-user-select: none !important;
|
||||||
|
-o-user-select: none !important;
|
||||||
|
user-select: none !important;
|
||||||
|
-webkit-user-drag: none !important;
|
||||||
|
-khtml-user-drag: none !important;
|
||||||
|
-moz-user-drag: none !important;
|
||||||
|
-o-user-drag: none !important;
|
||||||
|
user-drag: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html .cet-titlebar {
|
||||||
|
background-color: #ececec !important;
|
||||||
|
color: #202020 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html .cet-titlebar .cet-control-icon svg {
|
||||||
|
fill: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cet-titlebar .cet-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cet-container {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indeterminate {
|
||||||
|
background-image: repeating-linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgb(217 5 89) -1%,
|
||||||
|
rgb(217 5 89) 10%,
|
||||||
|
#d3d3d3 10%,
|
||||||
|
#d3d3d3 90%
|
||||||
|
);
|
||||||
|
background-size: 200%;
|
||||||
|
background-position-x: 15%;
|
||||||
|
animation: progress-loading 5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.indeterminate {
|
||||||
|
background-image: repeating-linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgb(217 5 89) -1%,
|
||||||
|
rgb(217 5 89) 10%,
|
||||||
|
#535353 10%,
|
||||||
|
#535353 90%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
html .cet-titlebar .cet-control-icon svg {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
html .cet-titlebar {
|
||||||
|
background-color: #202020 !important;
|
||||||
|
color: #ececec !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swal2-container {
|
||||||
|
background: #202020 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swal2-container .swal2-popup {
|
||||||
|
background: #323232 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animatedProgress div {
|
||||||
|
transition: width 0.35s cubic-bezier(0.65, -0.02, 0.31, 1.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
.noselect {
|
||||||
|
-webkit-user-select: none !important;
|
||||||
|
user-select: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes progress-loading {
|
||||||
|
50% {
|
||||||
|
background-position-x: -115%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export enum Page {
|
||||||
|
Login = 0,
|
||||||
|
Launch = 1,
|
||||||
|
Settings = 2,
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="svelte" />
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cubicOut } from "svelte/easing";
|
||||||
|
import { tweened } from "svelte/motion";
|
||||||
|
import { twMerge, twJoin } from "tailwind-merge";
|
||||||
|
import { clamp } from "../util/mathUtil";
|
||||||
|
|
||||||
|
export let progress: number = 45;
|
||||||
|
export let precision = 2;
|
||||||
|
export let tweenDuration = 400;
|
||||||
|
export let animate = false;
|
||||||
|
export let size = "h-2.5";
|
||||||
|
export let labelInside = false;
|
||||||
|
export let labelOutside = "";
|
||||||
|
export let easing = cubicOut;
|
||||||
|
export let color = "primary";
|
||||||
|
export let indeterminate = false;
|
||||||
|
export let labelInsideClass =
|
||||||
|
"text-primary-100 text-xs font-medium text-center p-0.5 leading-none rounded-full";
|
||||||
|
export let divClass = "w-full bg-gray-200 rounded-full dark:bg-gray-700";
|
||||||
|
|
||||||
|
const barColors: Record<string, string> = {
|
||||||
|
primary: "bg-primary-600",
|
||||||
|
blue: "bg-blue-600",
|
||||||
|
gray: "bg-gray-600 dark:bg-gray-300",
|
||||||
|
red: "bg-red-600 dark:bg-red-500",
|
||||||
|
green: "bg-green-600 dark:bg-green-500",
|
||||||
|
yellow: "bg-yellow-400",
|
||||||
|
purple: "bg-purple-600 dark:bg-purple-500",
|
||||||
|
indigo: "bg-indigo-600 dark:bg-indigo-500"
|
||||||
|
};
|
||||||
|
|
||||||
|
let _progress = tweened(0, {
|
||||||
|
duration: tweenDuration,
|
||||||
|
easing
|
||||||
|
});
|
||||||
|
|
||||||
|
$: {
|
||||||
|
progress = clamp(Number(progress), 0, 100);
|
||||||
|
_progress.set(progress);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if labelOutside}
|
||||||
|
<div
|
||||||
|
{...$$restProps}
|
||||||
|
class={twMerge("flex justify-between mb-1", $$props.classLabelOutside)}
|
||||||
|
>
|
||||||
|
<span class="text-base font-medium text-blue-700 dark:text-white"
|
||||||
|
>{labelOutside}</span
|
||||||
|
>
|
||||||
|
<span class="text-sm font-medium text-blue-700 dark:text-white"
|
||||||
|
>{animate
|
||||||
|
? isNaN($_progress)
|
||||||
|
? parseInt("100").toFixed(precision)
|
||||||
|
: $_progress.toFixed(precision)
|
||||||
|
: isNaN(progress)
|
||||||
|
? parseInt("100").toFixed(precision)
|
||||||
|
: progress.toFixed(precision)}%</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class={twMerge(divClass, size, $$props.class)}>
|
||||||
|
{#if labelInside}
|
||||||
|
{#if !indeterminate}
|
||||||
|
<div
|
||||||
|
class={twJoin(labelInsideClass, barColors[color])}
|
||||||
|
style="width: {animate ? $_progress : progress}%"
|
||||||
|
>
|
||||||
|
{animate
|
||||||
|
? isNaN($_progress)
|
||||||
|
? parseInt("100").toFixed(precision)
|
||||||
|
: $_progress.toFixed(precision)
|
||||||
|
: isNaN(progress)
|
||||||
|
? parseInt("100").toFixed(precision)
|
||||||
|
: progress.toFixed(precision)}%
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class={twJoin(
|
||||||
|
barColors[color],
|
||||||
|
size,
|
||||||
|
"indeterminate rounded-full animate-pulse"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{:else if !indeterminate}
|
||||||
|
<div
|
||||||
|
class={twJoin(barColors[color], size, "rounded-full")}
|
||||||
|
style="width: {animate ? $_progress : progress}%"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class={twJoin(
|
||||||
|
barColors[color],
|
||||||
|
size,
|
||||||
|
"indeterminate rounded-full animate-pulse"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import "./app.pcss";
|
||||||
|
import App from "./App.svelte";
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Button from "flowbite-svelte/Button.svelte";
|
||||||
|
import Progressbar from "../lib/Progressbar.svelte";
|
||||||
|
import {
|
||||||
|
launching,
|
||||||
|
patch,
|
||||||
|
launchStatus,
|
||||||
|
launchPercentage,
|
||||||
|
} from "./../storage/localStore";
|
||||||
|
let progressbarFix = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
progressbarFix = false;
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
const launch = () => {
|
||||||
|
launching.set(true);
|
||||||
|
const patching = $patch;
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("launch", { detail: { patch: patching } })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main
|
||||||
|
class="h-[265px] my-auto flex flex-col justify-center items-center p-5 animate-fadeIn"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="container flex flex-col items-center justify-center gap-3 rounded-lg p-3"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
color="light"
|
||||||
|
size="xl"
|
||||||
|
class="{$launching
|
||||||
|
? ''
|
||||||
|
: 'active:scale-95 '}transition-transform duration-75"
|
||||||
|
disabled={$launching}
|
||||||
|
on:click={launch}>Launch</Button
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-full flex flex-col justify-center items-center gap-2 mt-2 {$launching
|
||||||
|
? 'animate-fadeIn '
|
||||||
|
: 'animate-fadeOut '}{progressbarFix ? '!opacity-0' : 'opacity-0'}"
|
||||||
|
>
|
||||||
|
<Progressbar
|
||||||
|
animate={true}
|
||||||
|
progress={$launchPercentage}
|
||||||
|
indeterminate={$launchPercentage == -1}
|
||||||
|
labelInside={true}
|
||||||
|
size="h-3"
|
||||||
|
class=""
|
||||||
|
labelInsideClass="bg-primary-600 drop-shadow-xl text-gray-100 text-base font-medium text-center p-1 leading-none rounded-full !text-[0.7rem] !leading-[0.45]"
|
||||||
|
/>
|
||||||
|
<p class="m-0 p-0 dark:text-gray-400 font-light">{$launchStatus}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Input from "flowbite-svelte/Input.svelte";
|
||||||
|
import Button from "flowbite-svelte/Button.svelte";
|
||||||
|
import Spinner from "flowbite-svelte/Spinner.svelte";
|
||||||
|
import Checkbox from "flowbite-svelte/Checkbox.svelte";
|
||||||
|
import { type User } from "../types/user";
|
||||||
|
import { currentPage, currentUser, startup } from "../storage/localStore";
|
||||||
|
import toast from "svelte-french-toast";
|
||||||
|
import { Page } from "../consts/pages";
|
||||||
|
import EyeSolid from "flowbite-svelte-icons/EyeSolid.svelte";
|
||||||
|
import EyeSlashSolid from "flowbite-svelte-icons/EyeSlashSolid.svelte";
|
||||||
|
|
||||||
|
let loading = false;
|
||||||
|
let username = "";
|
||||||
|
let password = "";
|
||||||
|
let saveCredentials = false;
|
||||||
|
let showPassword = false;
|
||||||
|
|
||||||
|
const processLogin = async () => {
|
||||||
|
if (username.length <= 0 || password.length <= 0) {
|
||||||
|
toast.error(`Please provice a valid Username and Password!`, {
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading = true;
|
||||||
|
const loginPromise = new Promise<string>((res, rej) => {
|
||||||
|
window.addEventListener(
|
||||||
|
"login-result",
|
||||||
|
async (e) => {
|
||||||
|
const customEvent = e as CustomEvent;
|
||||||
|
const resultData = customEvent.detail;
|
||||||
|
const wasSuccessful = "user" in resultData;
|
||||||
|
|
||||||
|
if (!wasSuccessful) {
|
||||||
|
/* const errorResult = resultData as Error;
|
||||||
|
toast.error(errorResult.message, {
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 1500,
|
||||||
|
}); */
|
||||||
|
rej(resultData.message);
|
||||||
|
loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const userResult = resultData.user as User;
|
||||||
|
currentUser.set(userResult);
|
||||||
|
currentPage.set(Page.Launch);
|
||||||
|
res("");
|
||||||
|
toast.success(`Welcome back, ${userResult.name}!`, {
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("login-attempt", {
|
||||||
|
detail: { username, password, saveCredentials },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
toast.promise(
|
||||||
|
loginPromise,
|
||||||
|
{
|
||||||
|
loading: "Logging in...",
|
||||||
|
success: "Successfully logged in!",
|
||||||
|
error: (e) => e,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tryAutoLogin = async () => {
|
||||||
|
loading = true;
|
||||||
|
const loginPromise = new Promise<string>((res, rej) => {
|
||||||
|
window.addEventListener(
|
||||||
|
"login-result",
|
||||||
|
(e) => {
|
||||||
|
const customEvent = e as CustomEvent;
|
||||||
|
const resultData = customEvent.detail;
|
||||||
|
const isGuest = "guest" in resultData;
|
||||||
|
const wasSuccessful = "user" in resultData;
|
||||||
|
if (isGuest) {
|
||||||
|
currentPage.set(Page.Launch);
|
||||||
|
res("");
|
||||||
|
toast.success(`Logged in as Guest`, {
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!wasSuccessful) {
|
||||||
|
loading = false;
|
||||||
|
rej(resultData.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const userResult = resultData.user as User;
|
||||||
|
currentUser.set(userResult);
|
||||||
|
currentPage.set(Page.Launch);
|
||||||
|
res("");
|
||||||
|
toast.success(`Welcome back, ${userResult.name}!`, {
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
loading = false;
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
window.dispatchEvent(new CustomEvent("autologin-attempt"));
|
||||||
|
});
|
||||||
|
toast.promise(
|
||||||
|
loginPromise,
|
||||||
|
{
|
||||||
|
loading: "Logging in...",
|
||||||
|
success: "Successfully logged in!",
|
||||||
|
error: (e) => e,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const proceedAsGuest = () => {
|
||||||
|
window.dispatchEvent(new CustomEvent("guest-login"));
|
||||||
|
currentPage.set(Page.Launch);
|
||||||
|
toast.success(`Logged in as Guest`, {
|
||||||
|
position: "bottom-center",
|
||||||
|
className:
|
||||||
|
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldAutologin = async () => {
|
||||||
|
const shouldAutologin = await new Promise<boolean>((res) => {
|
||||||
|
window.addEventListener("autologin-result", (e) => {
|
||||||
|
const customEvent = e as CustomEvent;
|
||||||
|
const resultData = customEvent.detail;
|
||||||
|
res(resultData);
|
||||||
|
});
|
||||||
|
window.dispatchEvent(new CustomEvent("autologin-active"));
|
||||||
|
});
|
||||||
|
return shouldAutologin;
|
||||||
|
};
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
if (!$startup) {
|
||||||
|
startup.set(true);
|
||||||
|
if (await shouldAutologin()) tryAutoLogin();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main
|
||||||
|
class="h-[265px] my-auto flex flex-col justify-center items-center p-5 animate-fadeIn opacity-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="container flex flex-col items-center justify-center gap-3 rounded-lg p-3"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Username"
|
||||||
|
size="md"
|
||||||
|
class="animate-sideIn"
|
||||||
|
disabled={loading}
|
||||||
|
bind:value={username}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
placeholder="Password"
|
||||||
|
size="md"
|
||||||
|
class="animate-lsideIn"
|
||||||
|
disabled={loading}
|
||||||
|
bind:value={password}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
slot="right"
|
||||||
|
color="none"
|
||||||
|
class="!outline-none !ring-0 !p-0 !m-0 !bg-transparent !border-none"
|
||||||
|
on:click={() => (showPassword = !showPassword)}
|
||||||
|
>
|
||||||
|
{#if showPassword}
|
||||||
|
<EyeSolid class="outline-none border-none" />
|
||||||
|
{:else}
|
||||||
|
<EyeSlashSolid class="outline-none border-none" />
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</Input>
|
||||||
|
<Checkbox bind:checked={saveCredentials} disabled={loading}
|
||||||
|
>Save credentials</Checkbox
|
||||||
|
>
|
||||||
|
<div class="flex flex-col justify-center items-center gap-2 mt-1">
|
||||||
|
<Button
|
||||||
|
class="active:scale-95 transition-transform duration-75"
|
||||||
|
color="light"
|
||||||
|
disabled={loading}
|
||||||
|
on:click={processLogin}
|
||||||
|
>
|
||||||
|
{#if loading}
|
||||||
|
<Spinner size={"5"} color="white"></Spinner>
|
||||||
|
{:else}
|
||||||
|
Login
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="!bg-transparent font-light border-none dark:text-gray-700 hover:!bg-gray-700/15 ring-primary active:ring-2 focus:ring-2 active:scale-95 transition-transform duration-75"
|
||||||
|
color="none"
|
||||||
|
disabled={loading}
|
||||||
|
on:click={proceedAsGuest}>Continue without login</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Button from "flowbite-svelte/Button.svelte";
|
||||||
|
import ButtonGroup from "flowbite-svelte/ButtonGroup.svelte";
|
||||||
|
import Input from "flowbite-svelte/Input.svelte";
|
||||||
|
import Toggle from "flowbite-svelte/Toggle.svelte";
|
||||||
|
import FileSearchSolid from "flowbite-svelte-icons/FileSearchSolid.svelte";
|
||||||
|
import FolderSolid from "flowbite-svelte-icons/FolderSolid.svelte";
|
||||||
|
import { patch, presence, logging } from "./../storage/localStore";
|
||||||
|
|
||||||
|
let folderPath: string = "";
|
||||||
|
|
||||||
|
window.addEventListener("settings-result", (e) => {
|
||||||
|
const settings: Record<string, string>[] = (e as CustomEvent).detail;
|
||||||
|
const osuPath = settings.find((setting) => setting.key == "osuPath");
|
||||||
|
const settingPatch = settings.find((setting) => setting.key == "patch");
|
||||||
|
const settingPresence = settings.find(
|
||||||
|
(setting) => setting.key == "presence"
|
||||||
|
);
|
||||||
|
const settingLogging = settings.find((setting) => setting.key == "logging");
|
||||||
|
patch.set(settingPatch ? settingPatch.val == "true" : true);
|
||||||
|
presence.set(settingPresence ? settingPresence.val == "true" : true);
|
||||||
|
logging.set(settingLogging ? settingLogging.val == "true" : false);
|
||||||
|
folderPath = osuPath ? osuPath.val : "";
|
||||||
|
});
|
||||||
|
window.dispatchEvent(new CustomEvent("settings-get"));
|
||||||
|
|
||||||
|
const setFolderPath = () => {
|
||||||
|
window.dispatchEvent(new CustomEvent("folder-set"));
|
||||||
|
};
|
||||||
|
|
||||||
|
const detectFolderPath = () => {
|
||||||
|
window.dispatchEvent(new CustomEvent("folder-auto"));
|
||||||
|
};
|
||||||
|
|
||||||
|
const togglePatching = () => {
|
||||||
|
patch.set(!$patch);
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("setting-update", { detail: { patch: $patch } })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const togglePresence = () => {
|
||||||
|
presence.set(!$presence);
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("setting-update", { detail: { presence: $presence } })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleLogging = () => {
|
||||||
|
logging.set(!$logging);
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("setting-update", { detail: { logging: $logging } })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main
|
||||||
|
class="h-[265px] flex flex-col justify-start p-3 animate-fadeIn opacity-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="container flex flex-col items-center justify-center gap-5 rounded-lg p-3"
|
||||||
|
>
|
||||||
|
<ButtonGroup class="w-full">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
id="oip"
|
||||||
|
placeholder="Path to your osu! installation"
|
||||||
|
value={folderPath}
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
<Button color="light" on:click={detectFolderPath}>
|
||||||
|
<FileSearchSolid
|
||||||
|
size="sm"
|
||||||
|
class="dark:text-gray-300 text-gray-500 outline-none border-none select-none pointer-events-none"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<Button color="light" class="active:!rounded-lg" on:click={setFolderPath}>
|
||||||
|
<FolderSolid
|
||||||
|
size="sm"
|
||||||
|
class="dark:text-gray-300 text-gray-500 outline-none border-none select-none pointer-events-none"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2 p-3">
|
||||||
|
<Toggle class="w-fit" bind:checked={$presence} on:click={togglePresence}
|
||||||
|
>Discord Presence</Toggle
|
||||||
|
>
|
||||||
|
<Toggle class="w-fit" bind:checked={$patch} on:click={togglePatching}
|
||||||
|
>Patching</Toggle
|
||||||
|
>
|
||||||
|
<Toggle class="w-fit" bind:checked={$logging} on:click={toggleLogging}
|
||||||
|
>Debug Logging</Toggle
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { type Writable, writable } from "svelte/store";
|
||||||
|
import { Page } from "../consts/pages";
|
||||||
|
import type { User } from "../types/user";
|
||||||
|
|
||||||
|
export const startup = writable(false);
|
||||||
|
export const updateAvailable = writable(false);
|
||||||
|
export const launching = writable(false);
|
||||||
|
export const launchStatus = writable("Waiting...");
|
||||||
|
export const launchPercentage = writable(-1);
|
||||||
|
|
||||||
|
export const currentUser: Writable<undefined | User> = writable(undefined);
|
||||||
|
export const currentPage = writable(Page.Login);
|
||||||
|
|
||||||
|
export const osuPath: Writable<undefined | string> = writable(undefined);
|
||||||
|
export const patch = writable(true);
|
||||||
|
export const presence = writable(true);
|
||||||
|
export const logging = writable(false);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type Error = {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
declare module "*.jpg";
|
||||||
|
declare module "*.jpeg";
|
||||||
|
declare module "*.png";
|
||||||
|
declare module "*.svg";
|
||||||
|
declare module "*.svelte";
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export type User = {
|
||||||
|
id: number;
|
||||||
|
donor: boolean;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const clamp = (val: number, min: number, max: number) => {
|
||||||
|
return Math.max(min, Math.min(val, max));
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const preprocess = require("svelte-preprocess");
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
preprocess: [preprocess()],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/** @type {import('tailwindcss').Config}*/
|
||||||
|
const config = {
|
||||||
|
content: [
|
||||||
|
"./src/**/*.{html,js,svelte,ts}",
|
||||||
|
"./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}",
|
||||||
|
],
|
||||||
|
darkMode: "media",
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
keyframes: {
|
||||||
|
slideIn: {
|
||||||
|
"0%": { opacity: "0", transform: "translateX(-5px)" },
|
||||||
|
"100%": { opacity: "1" },
|
||||||
|
},
|
||||||
|
lslideIn: {
|
||||||
|
"0%": { opacity: "0", transform: "translateX(5px)" },
|
||||||
|
"100%": { opacity: "1" },
|
||||||
|
},
|
||||||
|
fadeIn: {
|
||||||
|
"0%": { opacity: "0", transform: "translateY(5px)" },
|
||||||
|
"100%": { opacity: "1" },
|
||||||
|
},
|
||||||
|
fadeOut: {
|
||||||
|
"100%": { opacity: "0", transform: "translateY(5px)" },
|
||||||
|
"0%": { opacity: "1" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
sideIn: "slideIn 1s ease forwards",
|
||||||
|
lsideIn: "lslideIn 1s ease forwards",
|
||||||
|
fadeIn: "fadeIn 1s ease forwards",
|
||||||
|
fadeOut: "fadeOut 1s ease forwards",
|
||||||
|
},
|
||||||
|
transitionProperty: {
|
||||||
|
"width": "width",
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
// flowbite-svelte
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "#FA1C74",
|
||||||
|
50: "#FED0E2",
|
||||||
|
100: "#FEBCD6",
|
||||||
|
200: "#FD94BD",
|
||||||
|
300: "#FC6CA5",
|
||||||
|
400: "#FB448C",
|
||||||
|
500: "#FA1C74",
|
||||||
|
600: "#D90559",
|
||||||
|
700: "#A20442",
|
||||||
|
800: "#6B022C",
|
||||||
|
900: "#340115",
|
||||||
|
950: "#19010A",
|
||||||
|
},
|
||||||
|
gray: {
|
||||||
|
50: "#F9F9F9",
|
||||||
|
100: "#ECECEC",
|
||||||
|
200: "#D3D3D3",
|
||||||
|
300: "#B9B9B9",
|
||||||
|
400: "#A0A0A0",
|
||||||
|
500: "#868686",
|
||||||
|
600: "#6D6D6D",
|
||||||
|
700: "#535353",
|
||||||
|
800: "#393939",
|
||||||
|
900: "#202020",
|
||||||
|
950: "#1A1A1A",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [require("flowbite/plugin")],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"extends": "./node_modules/@tsconfig/svelte/tsconfig.json",
|
||||||
|
|
||||||
|
"include": [
|
||||||
|
"src/**/*",
|
||||||
|
"electron/richPresence.js",
|
||||||
|
"electron/config.js",
|
||||||
|
"electron/cryptoUtil.js",
|
||||||
|
"electron/executeUtil.js",
|
||||||
|
"electron/formattingUtil.js",
|
||||||
|
"electron/hwidUtil.js",
|
||||||
|
"electron/osuUtil.js"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules/*", "__sapper__/*", "public/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types",
|
||||||
|
"node_modules/@sveltejs",
|
||||||
|
"node_modules/@sveltejs/types",
|
||||||
|
"src/types"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
const appInfo = require('../appInfo');
|
|
||||||
const { BrowserWindow } = require('electron');
|
|
||||||
const { attachTitlebarToWindow } = require('custom-electron-titlebar/main');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
createWindow: function (windowWidth, windowHeight) {
|
|
||||||
const window = new BrowserWindow({
|
|
||||||
width: windowWidth,
|
|
||||||
height: windowHeight,
|
|
||||||
minHeight: windowHeight / 1.25,
|
|
||||||
minWidth: windowWidth / 1.25,
|
|
||||||
frame: false,
|
|
||||||
titleBarStyle: 'hidden',
|
|
||||||
backgroundColor: "#121212",
|
|
||||||
resizable: false,
|
|
||||||
maximizable: false,
|
|
||||||
minimizable: true,
|
|
||||||
alwaysOnTop: false,
|
|
||||||
webPreferences: {
|
|
||||||
nodeIntegration: true,
|
|
||||||
enableRemoteModule: true,
|
|
||||||
show: false,
|
|
||||||
zoomFactor: 0.9,
|
|
||||||
preload: path.join(__dirname, '../preload/preload.js')
|
|
||||||
},
|
|
||||||
icon: './assets/logo.png'
|
|
||||||
})
|
|
||||||
window.hide();
|
|
||||||
|
|
||||||
window.webContents.once("did-finish-load", function (event, input) {
|
|
||||||
window.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.webContents.setUserAgent(`${appInfo.appName} ${appInfo.appVersion}`);
|
|
||||||
attachTitlebarToWindow(window);
|
|
||||||
// window.webContents.openDevTools({
|
|
||||||
// mode: "detach"
|
|
||||||
// });
|
|
||||||
|
|
||||||
return window;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user