Compare commits

..

102 Commits

Author SHA1 Message Date
HorizonCode b70af50634 Merge branch 'master' of https://git.ez-pp.farm/EZPPFarm/EZPPLauncher into master 2023-08-26 22:04:13 +02:00
HorizonCode 6205e2dd17 set button disabled upon start, and wait for scan 2023-08-21 08:17:23 +02:00
HorizonCode 15e1d47ddd add postinstall script 2023-07-29 22:54:00 +02:00
HorizonCode f870b48b25 add package-lock.json 2023-07-29 22:33:35 +02:00
HorizonCode 94db97ef30 add build workflow 2023-07-29 22:28:14 +02:00
HorizonCode 7e2fe2ec96 bump version 2023-07-29 22:21:53 +02:00
HorizonCode 7cba78b270 add support for patch files, bc recently peppy messed something up 2023-07-29 22:21:30 +02:00
HorizonCode f6e5ead7ed prevent tray close when ingame 2023-06-28 11:41:19 +02:00
HorizonCode 7ed7d3045f prevent closing when ingame 2023-06-28 11:38:56 +02:00
HorizonCode 37b7bbd2e3 fix ui patching 2023-06-03 18:07:19 +02:00
HorizonCode 47f86501b8 👀 2023-06-02 14:34:25 +02:00
HorizonCode 42777c9e0c some experimental stuff 👀 2023-06-02 14:13:09 +02:00
HorizonCode 769a88521e fix tray logo 2023-06-02 12:42:12 +02:00
HorizonCode f5287b6378 bump back version, apply fix version to appinfo 2023-06-02 12:30:57 +02:00
HorizonCode dd6866f680 change exe output file 2023-06-02 12:29:21 +02:00
HorizonCode cb1a05d58c titlebar fixes 2023-06-02 12:22:28 +02:00
HorizonCode fdfedab77f add update checking using gitea api 2023-05-31 19:03:11 +02:00
HorizonCode fc284e08f0 remove unused imports 2023-05-31 19:02:06 +02:00
HorizonCode 361db1df17 bump version 2023-05-31 07:05:34 +02:00
HorizonCode 0a664d1f64 add tray icon, fix login if password changed 2023-05-31 07:02:05 +02:00
HorizonCode 232043d686 remove old launcher design 2023-05-31 07:01:33 +02:00
HorizonCode 10a4c540dd remove arm from build script 2023-05-04 22:04:02 +02:00
HorizonCode b111cf4fa4 fix building scripts, update electron 2023-05-04 21:47:21 +02:00
HorizonCode e2713179c8 bump version 2023-05-04 21:29:22 +02:00
HorizonCode 92ee92d636 reformat index 2023-05-04 21:28:04 +02:00
HorizonCode b3463378e9 reformat index 2023-05-04 21:23:22 +02:00
HorizonCode 00e3e80d43 fix ignored list 2023-05-04 21:23:06 +02:00
HorizonCode 2df9f533fe set buttons to constant array 2023-05-04 21:22:51 +02:00
HorizonCode cc93e24ab6 update action 2023-05-04 21:22:32 +02:00
HorizonCode 48cd81f28d update url 2023-05-04 21:22:22 +02:00
HorizonCode 068d02f1f7 remove logging, exclude certain osu files from updating 2023-05-04 21:07:09 +02:00
HorizonCode e725592ef1 remove patch checkbox for now 2023-05-04 21:05:01 +02:00
HorizonCode 6bc0b215d7 change action button links 2023-05-04 21:04:48 +02:00
HorizonCode da1b05c9a1 bg tokyo night style 2023-01-25 15:50:50 +01:00
HorizonCode f1ef41dfea Update 'README.md' 2022-12-31 06:37:01 +00:00
HorizonCode a358ff08fa Merge branch 'master' of https://git.ez-pp.farm/EZPPFarm/EZPPLauncher 2022-12-23 21:43:34 +01:00
HorizonCode 5db981228e distro info for linux 2022-12-23 21:41:47 +01:00
HorizonCode 3247a3fcdf reformat and add distro check for linux 2022-12-23 21:41:39 +01:00
HorizonCode 7b9a6d77ab change colors to tokyo night 2022-12-23 21:41:23 +01:00
HorizonCode 337a8af68f add util method to detect osu folder, still needs to be tested on different systems. 2022-10-27 16:28:22 +02:00
HorizonCode 84c13331d2 add launch button folder choose if not set 2022-10-27 15:42:45 +02:00
HorizonCode e77cb22028 increase height of window 2022-10-27 15:42:29 +02:00
HorizonCode b673b74eee make checkbox disabled for now 2022-10-27 14:41:26 +02:00
HorizonCode 53a2fb55a6 forgot to revert display 2022-10-27 14:35:51 +02:00
HorizonCode 3e4186d3a7 added checkbox for patching 2022-10-27 14:30:46 +02:00
HorizonCode 89cdc8f316 preperations for the patch method 2022-10-27 13:58:15 +02:00
HorizonCode 67a53fdd2d readd rebuild script for get-window-by-name 2022-10-27 11:00:10 +02:00
HorizonCode 78c554be2d add editing status 2022-10-27 06:38:58 +02:00
HorizonCode 586857146c remove unused scripts 2022-10-27 06:38:14 +02:00
HorizonCode 2cea272836 make server logo non-draggable 2022-10-27 06:38:03 +02:00
HorizonCode 02d4fa2199 add package issue notification 2022-10-27 06:37:48 +02:00
HorizonCode 72a0d9b780 Update 'README.md' 2022-10-24 12:38:32 +00:00
HorizonCode 1489d44a31 Upload files to 'preview' 2022-10-24 12:36:16 +00:00
HorizonCode adaaf32acf remove rebuild script usage, add other used libs i forgot to add 2022-10-24 12:29:33 +00:00
HorizonCode 033ce8f3a4 make playing status work under linux 2022-10-24 06:36:03 +02:00
HorizonCode 2857345997 only update currentState on certain types 2022-10-24 05:46:22 +02:00
HorizonCode 4f3eaddfc7 style sweetalert2 2022-10-23 17:31:32 +02:00
HorizonCode 573bcd393c add notice under Linux 2022-10-23 17:07:20 +02:00
HorizonCode 11c3f9f657 forgot to remove image tag in html 2022-10-23 17:01:39 +02:00
HorizonCode c3c5390830 fix user images, many thanks to Wopiie! 2022-10-23 01:31:17 +02:00
HorizonCode 67de9b7c16 remove wincript(PASSWORD IS PLAIN IN CONFIG FOR NOW!) make linux compatible 2022-10-23 01:08:58 +02:00
HorizonCode 37567b5767 bump version 2022-10-21 00:59:19 +02:00
HorizonCode 0a780b84e4 forgor to remove debug log 2022-10-21 00:59:11 +02:00
HorizonCode c8a07fbb0d add basic discord rpc, showing osu status 2022-10-21 00:57:42 +02:00
HorizonCode 1de9fce504 show error modal when update failed 2022-10-20 19:08:48 +02:00
HorizonCode 73fb9464d2 bump version 2022-10-18 01:46:11 +02:00
HorizonCode 5c819938c8 login mechanics and launch is working now 2022-10-18 01:46:04 +02:00
HorizonCode 4f84dd3248 add encrypt test 2022-10-18 01:45:17 +02:00
HorizonCode 2fa6a9b3d5 push electron to latest 2022-10-17 21:31:48 +02:00
HorizonCode 258c01ccc7 add build from source section to readme 2022-10-17 21:30:46 +02:00
HorizonCode d80d6dc889 make logo on lauch page a bit bigger 2022-10-17 20:19:22 +02:00
HorizonCode 7a8495d88a downgrade electron to propper version, add rebuild script 2022-10-17 20:10:04 +02:00
HorizonCode 9ac75a309b update electron, add dpapi function wip 2022-10-17 18:10:17 +02:00
HorizonCode 8ef809be5c add loading page on startup 2022-10-17 16:37:12 +02:00
HorizonCode e6ccec3e93 do previous page fadeout and not immediately hide 2022-10-17 12:58:56 +02:00
HorizonCode 7785ebec92 fixed user image misplacement 2022-10-17 12:58:07 +02:00
HorizonCode 3edf2ce8a3 new design rollout, function still not given 2022-10-17 12:47:47 +02:00
HorizonCode 2b12654825 set author 2022-10-16 23:19:24 +02:00
HorizonCode 293ea1b798 disable set account button 2022-10-16 23:18:21 +02:00
HorizonCode 5f36de6697 add appInfo 2022-10-16 23:16:05 +02:00
HorizonCode a0628cc873 remove/disable console logs 2022-10-16 23:10:55 +02:00
HorizonCode 5c87704b10 launch mechanics working 2022-10-16 23:10:07 +02:00
HorizonCode 72958950b7 adjust window height 2022-10-16 22:51:16 +02:00
HorizonCode 8d29bd6822 whole update logic working 2022-10-16 22:44:56 +02:00
HorizonCode c2a75319dc add alerts dark theme 2022-10-16 22:21:02 +02:00
HorizonCode 2de6cc11cf fix folder detection on launch 2022-10-16 22:20:50 +02:00
HorizonCode 1b8dfbad03 add alerts, darkmode wip 2022-10-16 21:31:05 +02:00
HorizonCode 5565892155 add updating check 2022-10-16 21:13:30 +02:00
HorizonCode 2bf98db729 get updated files from osu 2022-10-16 18:52:34 +02:00
HorizonCode 830b1099ac folder check 2022-10-16 18:42:04 +02:00
HorizonCode 14201fb9cd fix layout 2022-10-16 18:41:53 +02:00
HorizonCode bd7f3ddefd config util 2022-10-16 18:41:24 +02:00
HorizonCode 99d0d927e0 get osu config method 2022-10-16 18:41:15 +02:00
HorizonCode 356ec5d7e0 cancel if result is undefined 2022-10-16 16:47:43 +02:00
HorizonCode 071b13ee6e change pipe name 2022-10-16 16:47:31 +02:00
HorizonCode 283e125e38 detect valid osu folder by matches 2022-10-16 16:47:18 +02:00
HorizonCode 65b607fb31 update readme 2022-10-16 16:15:35 +02:00
HorizonCode 1c6c9a2d6e launch debug window detatched 2022-10-16 16:06:34 +02:00
HorizonCode b84ddaf1b7 set launch button disabled for now 2022-10-16 16:06:20 +02:00
HorizonCode 3baa262ca6 fix window bounds, use modern method to cancel new windows 2022-10-16 16:06:06 +02:00
HorizonCode c648cfd2b3 reinit 2022-10-16 15:55:47 +02:00
HorizonCode d50cdd2a77 initial commit 2022-10-16 15:54:29 +02:00
64 changed files with 5057 additions and 9291 deletions
+50
View File
@@ -0,0 +1,50 @@
name: Compile and Release
on:
release:
types: [released]
jobs:
build-linux:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run rebuild
- run: npm run pack-linux
- name: Add Artifact to Workflow
uses: actions/upload-artifact@v3
with:
name: artifact
path: |
release/*.AppImage
build-win:
runs-on: windows-latest
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run rebuild
- run: npm run pack-win
- name: Add Artifact to Workflow
uses: actions/upload-artifact@v3
with:
name: artifact
path: |
release/*.exe
+3 -23
View File
@@ -1,24 +1,4 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Lock files
yarn.lock
# Dependency directories
node_modules/ node_modules/
release/
# Svelte Distribution yarn.lock
public/build/ pnpm-lock.yaml
# Electron Distribution
dist
# Project Build Automation Directory
private
# Desktop Services Store on macOS
.DS_Store
-3
View File
@@ -1,3 +0,0 @@
{
"recommendations": ["svelte.svelte-vscode"]
}
+17
View File
@@ -0,0 +1,17 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args" : ["."],
"outputCapture": "std"
}
]
}
+2 -2
View File
@@ -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.
EZPPLauncher <one line to give the program's name and a brief idea of what it does.>
Copyright (C) 2024 HorizonCode Copyright (C) <year> <name of author>
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.
+18 -9
View File
@@ -1,27 +1,36 @@
# EZPPLauncher # EZPPLauncher
Welcome to the EZPPLauncher! A new way to connect to the EZPPFarm server. 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 ## Preview Image
![preview](https://git.ez-pp.farm/EZPPFarm/EZPPLauncher/raw/branch/master/preview/EZPPLauncher.png)
## 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
- Custom osu! Logo in MainMenu * Account saving
- Relax misses and much more
- Account saving ## Used Libraries
* [Electron](https://www.npmjs.com/package/electron)
* [custom-electron-titlebar](https://www.npmjs.com/package/custom-electron-titlebar)
* [axios](https://www.npmjs.com/package/axios)
* [jquery](https://www.npmjs.com/package/jquery)
* [discord-auto-rpc](https://www.npmjs.com/package/discord-auto-rpc)
* [get-window-by-name](https://www.npmjs.com/package/get-window-by-name)
* [sweetalert2](https://www.npmjs.com/package/sweetalert2)
* [node-downloader-helper](https://www.npmjs.com/package/node-downloader-helper)
## Build from source ## Build from source
- clone repo - clone repo
- cd into the repo - cd into the repo
- use `npm i` to install all dependencies - use `yarn install` to install all dependencies
- use the buildscript `electron-pack` to build a executeable - use the buildscript `pack-win` 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/)
+412
View File
@@ -0,0 +1,412 @@
const { app, BrowserWindow, ipcMain, dialog, Tray, Menu } = require('electron');
const { setupTitlebar } = require('custom-electron-titlebar/main');
const windowManager = require('./ui/windowManager');
const osuUtil = require('./osuUtil');
const ezppUtil = require('./ezppUtil');
const config = require('./config');
const fs = require('fs');
const path = require('path');
const rpc = require('./discordPresence');
const windowName = require('get-window-by-name');
const terminalUtil = require('./terminalUtil');
const osUtil = require('./osUtil');
const appInfo = require('./appInfo');
const { DownloaderHelper } = require('node-downloader-helper');
let tempOsuPath;
let osuWindowInfo;
let isIngame;
const platform = process.platform;
let linuxWMCtrlFound = false;
let osuLoaded = false;
const run = () => {
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit();
return;
}
setInterval(async () => {
if (platform == "win32") {
osuWindowInfo = windowName.getWindowText("osu!.exe");
const firstInstance = osuWindowInfo[0];
if (firstInstance) {
if (firstInstance.processTitle && firstInstance.processTitle.length > 0) {
if (!osuLoaded) {
osuLoaded = true;
//TODO: do patch
}
const windowTitle = firstInstance.processTitle;
let rpcOsuVersion = "";
let currentMap = undefined;
if (!windowTitle.includes("-")) {
rpcOsuVersion = windowTitle;
rpc.updateState("Idle...");
} else {
var string = windowTitle;
var components = string.split(' - ');
const splitArray = [components.shift(), components.join(' - ')];
rpcOsuVersion = splitArray[0];
currentMap = splitArray[1];
if (currentMap.endsWith(".osu")) {
rpc.updateState("Editing...");
currentMap = currentMap.substring(0, currentMap.length - 4);
} else rpc.updateState("Playing...");
}
rpc.updateStatus(currentMap, rpcOsuVersion);
} else {
if (osuLoaded) osuLoaded = false;
rpc.updateState("Idle in Launcher...");
rpc.updateStatus(undefined, undefined);
}
} else {
if (osuLoaded) osuLoaded = false;
rpc.updateState("Idle in Launcher...");
rpc.updateStatus(undefined, undefined);
}
} else if (platform == "linux") {
if (!linuxWMCtrlFound) {
if (isIngame) {
rpc.updateState("Playing...");
rpc.updateStatus("Clicking circles!", "runningunderwine");
} else {
rpc.updateState("Idle in Launcher...");
rpc.updateStatus(undefined, undefined);
}
return;
}
const processesOutput = await terminalUtil.execCommand(`wmctrl -l|awk '{$3=""; $2=""; $1=""; print $0}'`);
const allLines = processesOutput.split("\n");
const filteredProcesses = allLines.filter((line) => line.trim().startsWith("osu!"));
if (filteredProcesses.length > 0) {
const windowTitle = filteredProcesses[0].trim();
let rpcOsuVersion = "";
let currentMap = undefined;
if (!windowTitle.includes("-")) {
rpcOsuVersion = windowTitle;
rpc.updateState("Idle...");
} else {
var string = windowTitle;
var components = string.split(' - ');
const splitArray = [components.shift(), components.join(' - ')];
rpcOsuVersion = splitArray[0];
currentMap = splitArray[1];
if (currentMap.endsWith(".osu")) {
rpc.updateState("Editing/Modding...");
currentMap = currentMap.substring(0, currentMap.length - 4);
}
else rpc.updateState("Playing...");
}
rpc.updateStatus(currentMap, rpcOsuVersion);
} else {
rpc.updateState("Idle in Launcher...");
rpc.updateStatus(undefined, undefined);
}
} else {
if (isIngame) {
rpc.updateState("Playing...");
rpc.updateStatus("Clicking circles!", "runningunderwine");
} else {
rpc.updateState("Idle in Launcher...");
rpc.updateStatus(undefined, undefined);
}
}
}, 2000);
setupTitlebar();
rpc.connect();
let mainWindow;
let tray = null
app.whenReady().then(async () => {
const logoFile = path.join(config.configFolder, "logo.png");
if (!fs.existsSync(logoFile)) {
const logoDownload = new DownloaderHelper("https://ez-pp.farm/assets/img/icon.png", config.configFolder, {
fileName: "logo.png",
})
await logoDownload.start();
}
tray = new Tray(logoFile);
const trayMenuTemplate = [
{
label: `EZPPLauncher ${appInfo.appVersion}`,
enabled: false
},
{
label: "Show/Hide",
click: function () {
if (mainWindow.isVisible()) mainWindow.hide();
else mainWindow.show();
}
},
{
label: 'Exit',
click: function () {
if (isIngame) return;
app.exit(0);
}
}
]
let trayMenu = Menu.buildFromTemplate(trayMenuTemplate)
tray.setContextMenu(trayMenu)
mainWindow = createWindow();
mainWindow.on('close', (e) => {
if (isIngame) e.preventDefault();
});
mainWindow.once('show', async () => {
await updateConfigVars(mainWindow);
await tryLogin(mainWindow);
await doUpdateCheck(mainWindow);
if (platform === "linux") {
const linuxDistroInfo = await osUtil.getLinuxDistroInfo();
if (linuxDistroInfo?.id != "arch") {
if (linuxDistroInfo?.id_like != "arch") {
mainWindow.webContents.send('status_update', {
type: "info",
message: "We detected that you are running the Launcher under Linux. It's currently just compatible with Arch like distributions!"
});
}
}
try {
await terminalUtil.execCommand(`osu-stable -h`);
} catch (err) {
mainWindow.webContents.send('status_update', {
type: "package-issue",
message: "Seems like you dont have the osu AUR Package installed, please install it."
});
return;
}
/* mainWindow.webContents.send('status_update', {
type: "info",
message: "We detected that you are running the Launcher under Linux. It's currently just compatible with Arch and the osu AUR package!"
}); */
const terminalTest = await terminalUtil.execCommand(`wmctrl -l|awk '{$3=""; $2=""; $1=""; print $0}'`);
const isFailed = terminalTest.trim() == "";
if (isFailed) {
mainWindow.webContents.send('status_update', {
type: "info",
message: "Seems like you are missing the wmctrl package, please install it for the RPC to work!"
});
} else linuxWMCtrlFound = true;
} else {
const osuFolder = await config.get("osuPath");
if (!osuFolder || osuFolder == "") {
const foundFolder = await osuUtil.findOsuInstallation();
if (foundFolder && osuUtil.isValidOsuFolder(foundFolder)) {
mainWindow.webContents.send('alert_message', foundFolder);
}
}
}
});
ipcMain.on("alert_response", async (event, path) => {
await config.set("osuPath", path);
})
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) mainWindow = createWindow();
})
app.on('window-all-closed', () => {
app.quit()
})
ipcMain.handle('launch', async () => {
const osuFolder = await config.get("osuPath");
await osuUtil.updateOsuCfg(path.join(osuFolder, "osu!.cfg"));
const osuConfig = await osuUtil.getLatestConfig(tempOsuPath);
const username = await config.get('username');
const password = await config.get('password');
if (password && username) {
await osuUtil.setConfigValue(osuConfig.path, "SaveUsername", "1");
await osuUtil.setConfigValue(osuConfig.path, "SavePassword", "1");
await osuUtil.setConfigValue(osuConfig.path, "Username", username);
await osuUtil.setConfigValue(osuConfig.path, "Password", password);
await osuUtil.setConfigValue(osuConfig.path, "CredentialEndpoint", "ez-pp.farm");
} else {
await osuUtil.setConfigValue(osuConfig.path, "Username", "");
await osuUtil.setConfigValue(osuConfig.path, "Password", "");
}
await osuUtil.replaceUI(osuFolder, true);
rpc.updateState("Launching osu!...");
isIngame = true;
mainWindow.closable = false;
if (mainWindow.isVisible()) mainWindow.hide();
const result = await osuUtil.startOsuWithDevServer(tempOsuPath, "ez-pp.farm", async () => {
if (!mainWindow.isVisible()) mainWindow.show();
setTimeout(async () => {
isIngame = false;
await osuUtil.replaceUI(osuFolder, false);
await doUpdateCheck(mainWindow);
mainWindow.closable = true;
}, 2000);
});
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"
})
});
downloadTask.on('error', () => {
mainWindow.webContents.send('status_update', {
type: "error",
message: "An error occured while updating."
});
});
} 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, folderPath };
})
ipcMain.handle('perform-login', async (event, data) => {
const { username, password } = data;
const loginData = await ezppUtil.performLogin(username, password);
if (loginData && loginData.code === 200) {
await config.set("username", username);
await config.set("password", password);
}
return loginData;
})
ipcMain.on('perform-logout', async (event) => {
await config.remove("username");
await config.remove("password");
})
})
}
async function updateConfigVars(window) {
const osuPath = await config.get("osuPath", "");
window.webContents.send('config_update', {
osuPath: osuPath
})
}
async function tryLogin(window) {
const username = await config.get("username", "");
const password = await config.get("password", "");
if ((username && username.length > 0) && (password && password.length > 0)) {
const passwordPlain = password;
const loginResponse = await ezppUtil.performLogin(username, passwordPlain);
if (loginResponse && loginResponse.code === 200) {
window.webContents.send('account_update', {
type: "loggedin",
user: loginResponse.user
});
} else {
await config.remove("username");
await config.remove("password");
window.webContents.send('account_update', {
type: "login-failed",
message: loginResponse.message
})
}
} else {
window.webContents.send('account_update', {
type: "not-loggedin"
})
}
const checkUpdate = await appInfo.hasUpdate();
if (checkUpdate) {
window.webContents.send('launcher_update', checkUpdate);
}
}
async function doUpdateCheck(window) {
const osuPath = await config.get("osuPath");
if (!osuPath || osuPath.trim == "") {
window.webContents.send('status_update', {
type: "missing-folder"
})
return;
}
const isValid = await osuUtil.isValidOsuFolder(osuPath);
if (!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(700, 460);
win.loadFile('./html/index.html');
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();
+20
View File
@@ -0,0 +1,20 @@
const { default: axios } = require("axios");
const { compareVersions } = require("compare-versions");
const appName = "EZPPLauncher"
const appVersion = "1.1.5";
const hasUpdate = async () => {
const releaseInfo = await axios.get(`https://git.ez-pp.farm/api/v1/repos/EZPPFarm/${appName}/releases/latest`);
if (releaseInfo.status !== 200) return false;
const latestReleaseVersion = releaseInfo.data.tag_name;
const updateAvailable = compareVersions(latestReleaseVersion, appVersion);
if(updateAvailable > 0)
return {
version: latestReleaseVersion,
url: releaseInfo.data.html_url,
}
return undefined;
}
module.exports = { appName, appVersion, hasUpdate };
+85
View File
@@ -0,0 +1,85 @@
[type="checkbox"]:not(:checked),
[type="checkbox"]:checked {
position: absolute;
left: 0;
opacity: 0.01;
}
[type="checkbox"]:not(:checked)+label,
[type="checkbox"]:checked+label {
position: relative;
padding-left: 2.3em;
font-size: 1.05em;
line-height: 1.7;
cursor: pointer;
}
/* checkbox aspect */
[type="checkbox"]:not(:checked)+label:before,
[type="checkbox"]:checked+label:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 1.4em;
height: 1.4em;
border: 1px solid #565656;
background: #4c4c4c;
border-radius: .2em;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .1), 0 0 0 hsla(var(--main-accent), 93%, 48%, 20%);
-webkit-transition: all .275s;
transition: all .275s;
}
/* checked mark aspect */
[type="checkbox"]:not(:checked)+label:after,
[type="checkbox"]:checked+label:after {
content: '✕';
position: absolute;
top: .525em;
left: .18em;
font-size: 1.375em;
font-weight: bolder;
color: hsl(var(--main-accent), 93%, 48%);
line-height: 0;
-webkit-transition: all .2s;
transition: all .2s;
}
/* checked mark aspect changes */
[type="checkbox"]:not(:checked)+label:after {
opacity: 0;
-webkit-transform: scale(0) rotate(45deg);
transform: scale(0) rotate(45deg);
}
[type="checkbox"]:checked+label:after {
opacity: 1;
-webkit-transform: scale(1) rotate(0);
transform: scale(1) rotate(0);
translate: -1.5px -1px;
}
/* Disabled checkbox */
[type="checkbox"]:disabled:not(:checked)+label:before,
[type="checkbox"]:disabled:checked+label:before {
box-shadow: none;
border-color: #565656;
background-color: #4c4c4c;
cursor: not-allowed;
}
[type="checkbox"]:disabled:checked+label:after {
color: #777;
}
[type="checkbox"]:disabled+label {
color: #aaa;
cursor: not-allowed;
}
/* Accessibility */
[type="checkbox"]:checked:focus+label:before,
[type="checkbox"]:not(:checked):focus+label:before {
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .1), 0 0 0 6px hsla(var(--main-accent), 93%, 48%, 20%);
}
+335
View File
@@ -0,0 +1,335 @@
@import url('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&display=swap');
:root {
--main-accent: 335deg;
--main-bg: 230deg;
}
* {
font-family: 'Exo 2', 'Roboto' !important;
}
body {
background-color: hsl(var(--main-bg), 24%, 19%);
}
p.text-white {
margin: .5rem 0;
color: white;
}
.quotetext {
color: white;
background-color: #727272;
border-radius: .35rem;
font-size: .95rem;
padding: .25rem .5rem;
}
.sections {
display: flex;
flex-flow: column;
}
.app-name {
font-size: 40px;
font-weight: 500;
text-shadow: 0 0 15px rgba(255, 255, 255, 0.37);
}
.launcher-window {
width: 100%;
}
.launch-button-section {
display: flex;
flex-direction: column;
}
.launch-button-section button {
margin-bottom: 10px;
}
.loading-section,
.login-section {
margin-top: 35px;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
}
.loading-section {
margin-top: 0;
}
.loading-section {}
.loading-indicator {}
.loading-indicator-text {
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
}
.folder-section {
margin-top: 50px;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
color: #aaa;
font-weight: 500;
}
.folder-action {
font-style: italic;
cursor: pointer;
width: fit-content;
line-height: .9;
}
.launch-section {
width: 100%;
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
}
.btn.btn-launch {
margin-top: 10px;
width: 300px;
font-size: 23px;
text-transform: none;
}
.account-section {
display: flex;
align-items: center;
gap: 20px;
}
.server-logo img {
user-select: none;
pointer-events: none;
}
.user-info {
text-align: start;
display: flex;
flex-flow: column;
justify-content: center;
align-content: center;
overflow: hidden;
text-overflow: ellipsis;
}
.user-info .welcome-text {
font-size: 31px;
letter-spacing: .02rem;
height: 30px;
line-height: .8;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.349);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-info .account-action {
font-size: 21px;
letter-spacing: .05rem;
font-weight: 500;
font-style: italic;
color: #aaa;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.349);
cursor: pointer;
line-height: 1.2;
width: fit-content;
}
.user-image {
border: 5px solid white;
border-radius: 0.4rem;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.349);
background-image: url(https://a.ez-pp.farm/0);
width: 100px;
height: 100px;
background-size: cover;
background-position: center;
}
.form-outline .form-control:focus~.form-notch .form-notch-leading {
border-color: hsl(var(--main-accent), 93%, 48%);
box-shadow: -1px 0 0 0 hsl(var(--main-accent), 93%, 48%), 0 1px 0 0 hsl(var(--main-accent), 93%, 48%), 0 -1px 0 0 hsl(var(--main-accent), 93%, 48%);
}
.form-outline .form-control:focus~.form-notch .form-notch-middle {
border-color: hsl(var(--main-accent), 93%, 48%);
box-shadow: 0 1px 0 0 hsl(var(--main-accent), 93%, 48%);
border-top: 1px solid transparent;
}
.form-outline .form-control:focus~.form-notch .form-notch-trailing {
border-color: hsl(var(--main-accent), 93%, 48%);
box-shadow: 1px 0 0 0 hsl(var(--main-accent), 93%, 48%), 0 -1px 0 0 hsl(var(--main-accent), 93%, 48%), 0 1px 0 0 hsl(var(--main-accent), 93%, 48%);
}
.form-outline .form-control:focus~.form-label {
color: hsl(var(--main-accent), 93%, 48%);
}
.btn-accent,
.btn-accent:active {
background: hsl(var(--main-accent), 93%, 48%);
color: #fff;
}
.btn-accent:hover {
background: hsl(var(--main-accent), 93%, 42%);
color: #fff;
}
.btn-grey,
.btn-grey:active {
background: #727272;
color: #fff;
}
.btn-grey:hover {
background: #626262;
color: #fff;
}
.btn-grouped {
display: flex;
gap: 50px;
}
.clickable {
cursor: pointer;
}
.loader {
position: relative;
margin: 0px auto;
width: 200px;
height: 200px;
transform: scale(0.5);
}
.loader:before {
content: '';
display: block;
padding-top: 100%;
}
.circular-loader {
-webkit-animation: rotate 2s linear infinite;
animation: rotate 2s linear infinite;
height: 100%;
-webkit-transform-origin: center center;
-ms-transform-origin: center center;
transform-origin: center center;
width: 100%;
position: absolute;
top: 0;
left: 0;
margin: auto;
}
.loader-path {
stroke-dasharray: 150, 200;
stroke-dashoffset: -10;
-webkit-animation: dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite;
animation: dash 1.5s ease-in-out infinite, color 6s ease-in-out infinite;
stroke-linecap: round;
}
@-webkit-keyframes rotate {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes rotate {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-webkit-keyframes dash {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -124;
}
}
@keyframes dash {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -124;
}
}
@-webkit-keyframes color {
0% {
stroke: #fff;
}
40% {
stroke: #fff;
}
66% {
stroke: #fff;
}
80%,
90% {
stroke: #fff;
}
}
@keyframes color {
0% {
stroke: #fff;
}
40% {
stroke: #fff;
}
66% {
stroke: #fff;
}
80%,
90% {
stroke: #fff;
}
}

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

+23
View File
File diff suppressed because one or more lines are too long
+20
View File
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+71
View File
@@ -0,0 +1,71 @@
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 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'));
}
async function remove(key) {
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);
}
}
const arr = [];
for (var [storkey, storvalue] of configValues.entries()) {
if (storkey != key)
arr.push(`${storkey}=${storvalue}`);
}
await fs.promises.writeFile(configLocation, arr.join('\n'));
}
module.exports = { get, set, remove, configFolder }
+59
View File
@@ -0,0 +1,59 @@
const appInfo = require('./appInfo.js');
const DiscordAutoRPC = require("discord-auto-rpc");
const clientId = "1032772293220384808";
let client = undefined;
let lastState = "Idle in Launcher...";
let presenceEnabled = true;
let startDate = new Date();
const actionButtons = [
{
label: "Download the Launcher",
url: "https://git.ez-pp.farm/EZPPFarm/EZPPLauncher/releases/latest"
},
{
label: "Join EZPPFarm",
url: "https://ez-pp.farm/discord"
}
]
let lastActivity = {
details: " ",
state: lastState,
startTimestamp: startDate,
largeImageKey: "ezppfarm",
largeImageText: appInfo.appName + " " + appInfo.appVersion,
buttons: actionButtons,
instance: false,
};
module.exports = {
connect: () => {
if (client === undefined) {
client = new DiscordAutoRPC.AutoClient({ transport: "ipc" });
client.endlessLogin({ clientId: clientId });
client.once("ready", () => {
setInterval(() => {
if (lastActivity !== undefined)
lastActivity.state = lastState;
client.setActivity(presenceEnabled ? lastActivity : undefined);
}, 2500);
});
}
},
enablePresence: () => presenceEnabled = true,
disablePresence: () => presenceEnabled = false,
updateStartDate: () => startDate = new Date(),
updateState: (state) => lastState = state,
updateStatus: (details, osuVersion) => {
lastActivity = {
details: details ? details : " ",
state: lastState,
startTimestamp: startDate,
smallImageKey: osuVersion ? "osu" : " ",
smallImageText: osuVersion ? osuVersion : " ",
largeImageKey: "ezppfarm",
largeImageText: appInfo.appName + " " + appInfo.appVersion,
buttons: actionButtons,
instance: false,
}
}
}
-4
View File
@@ -1,4 +0,0 @@
const appName = "EZPPLauncher";
const appVersion = "2.1.7";
module.exports = { appName, appVersion };
-53
View File
@@ -1,53 +0,0 @@
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,
};
-11
View File
@@ -1,11 +0,0 @@
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 };
-20
View File
@@ -1,20 +0,0 @@
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 };
-15
View File
@@ -1,15 +0,0 @@
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,
};
-13
View File
@@ -1,13 +0,0 @@
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 };
-32
View File
@@ -1,32 +0,0 @@
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;
-21
View File
@@ -1,21 +0,0 @@
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 };
-44
View File
@@ -1,44 +0,0 @@
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;
-26
View File
@@ -1,26 +0,0 @@
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 };
-478
View File
@@ -1,478 +0,0 @@
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,
};
-71
View File
@@ -1,71 +0,0 @@
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,
};
-30
View File
@@ -1,30 +0,0 @@
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,
};
+18
View File
@@ -0,0 +1,18 @@
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()
}
}
+18
View File
@@ -0,0 +1,18 @@
const axios = require('axios').default;
const loginCheckEndpoint = 'https://ez-pp.farm/login/check';
let retries = 0;
const performLogin = async (username, password) => {
const result = await axios.post(loginCheckEndpoint, { username, password });
const code = result.data.code ?? 404;
if (code === 200 || code === 403) {
retries = 0;
return result.data;
} else {
if (retries++ >= 5) return { code: 403, message: "Login failed." }
return await performLogin(username, password);
}
}
module.exports = { performLogin };
+15
View File
@@ -0,0 +1,15 @@
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 };
+121
View File
@@ -0,0 +1,121 @@
<!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" />
<link href="../assets/launcher.css" rel="stylesheet" />
<link href="../assets/checkbox.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-1 w-100 text-center text-lg-end d-flex align-items-center justify-content-center"
style="border-radius: 0.5em;">
<div class="launcher-window position-relative overflow-hidden">
<div class="container px-1 py-2 w-100 mw-100 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div id="loading-page" class="sections col col-xl-10">
<div class="launch-section flex-row">
<div class="server-logo">
<img src="../assets/logo.png" height="120">
</div>
<div class="app-name">EZPPLauncher</div>
</div>
<div class="loading-section">
<div class="loading-indicator">
<div class="loader">
<svg class="circular-loader" viewBox="25 25 50 50">
<circle class="loader-path" cx="50" cy="50" r="20" fill="none"
stroke="#fff" stroke-width="2" />
</svg>
</div>
</div>
<div class="loading-indicator-text">Loading... Please wait</div>
</div>
</div>
<div id="launch-page" class="sections col col-xl-10" style="display: none;">
<div class="account-section">
<div class="user-image">
</div>
<div class="user-info">
<div class="welcome-text" id="welcome-text">
Nice to see you!
</div>
<div class="account-action" id="account-action">
Click to login
</div>
</div>
</div>
<div class="launch-section">
<div class="server-logo">
<img src="../assets/logo.png" height="150">
</div>
<div class="launch-button-section">
<button class="btn btn-lg btn-launch btn-accent" id="launch-btn" disabled>Launch</button>
<div class="patch-checkbox" style="display: none;">
<input type="checkbox" id="enablePatching" disabled />
<label for="enablePatching" style="display: initial;">enable
Patching</label>
</div>
</div>
</div>
<div class="folder-section">
<div class="folder-location">
Current osu! directory: <span id="currentOsuPath"></span>
</div>
<div class="folder-action" id="change-folder-btn">
Not correct?
</div>
</div>
</div>
<div id="login-page" class="sections col col-xl-10" style="display: none;">
<div class="launch-section flex-row">
<div class="server-logo">
<img src="../assets/logo.png" height="120">
</div>
<div class="app-name">EZPPLauncher</div>
</div>
<div class="login-section">
<div class="form-outline mb-3 w-50">
<input type="text" id="login-username" class="form-control form-control-lg" />
<label class="form-label" for="login-username">Username</label>
</div>
<div class="form-outline mb-3 w-50">
<input type="password" id="login-password"
class="form-control form-control-lg" />
<label class="form-label" for="login-password">Password</label>
</div>
<div class="pt-1 mb-4">
<div class="btn-grouped">
<button id="action-cancel" class="btn btn-grey btn-lg"
type="button">Cancel</button>
<button id="action-login" class="btn btn-accent btn-lg"
type="button">Login</button>
</div>
</div>
<p class="text-muted clickable" id="register">Don't have an account?</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</body>
<script type="text/javascript" src="../assets/mdb.min.js"></script>
</html>
-889
View File
@@ -1,889 +0,0 @@
// 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/list@2x.jpg`;
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.
+17
View File
@@ -0,0 +1,17 @@
const fs = require("fs");
const getLinuxDistroInfo = async() => {
let os = await fs.promises.readFile('/etc/os-release', 'utf8')
let opj = {}
os?.split('\n')?.forEach((line, index) => {
let words = line?.split('=')
let key = words[0]?.toLowerCase()
if (key === '') return
let value = words[1]?.replace(/"/g, '')
opj[key] = value
})
return opj;
}
module.exports = { getLinuxDistroInfo };
+286
View File
@@ -0,0 +1,286 @@
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 customUIDLLPath = "https://ez-pp.farm/assets/ezpp!ui.dll";
const customUIDLLHash = "https://ez-pp.farm/assets/ezpp!ui.md5";
const customUIDLLName = "ezpp!ui.dll";
const ignoredOsuEntities = [
'osu!auth.dll',
]
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',
]
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 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 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 axios.get(checkUpdateURL + releaseStream, {});
return releaseData.data;
}
async function getEZPPUIMD5() {
const releaseData = await axios.get(customUIDLLHash, {});
return releaseData.data;
}
async function filesThatNeedUpdate(osuPath, updateFiles) {
const filesToDownload = [];
for (const updatedFile of updateFiles) {
let fileName = updatedFile.filename;
let fileHash = updatedFile.file_hash;
let fileURL = updatedFile.url_full;
if ("url_patch" in updatedFile && updatedFile.url_patch != undefined) {
const stripped = updatedFile.url_patch.split("_");
fileHash = stripped[stripped.length - 1];
const lastIndex = fileURL.lastIndexOf("/");
const baseUrl = fileURL.slice(0, lastIndex);
fileURL = baseUrl + "/f_" + fileHash;
}
if (ignoredOsuEntities.includes(fileName)) continue;
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
});
}
} else {
filesToDownload.push({
fileName,
fileURL
});
}
}
const ezppUI = path.join(osuPath, "EZPPLauncher", customUIDLLName);
if (await fu.existsAsync(ezppUI)) {
const latestMd5Hash = await getEZPPUIMD5();
const binaryUIContents = await fs.promises.readFile(ezppUI);
const existingUIMD5 = crypto.createHash("md5").update(binaryUIContents).digest("hex");
if (existingUIMD5 != latestMd5Hash) {
filesToDownload.push({
folder: "EZPPLauncher",
fileName: "ezpp!ui.dll",
fileURL: customUIDLLPath
})
}
} else {
filesToDownload.push({
folder: "EZPPLauncher",
fileName: "ezpp!ui.dll",
fileURL: customUIDLLPath
})
}
return filesToDownload;
}
async function downloadUpdateFiles(osuPath, filesToUpdate) {
const eventEmitter = new EventEmitter();
let completedIndex = 0;
filesToUpdate.forEach(async (fileToUpdate) => {
if ("folder" in fileToUpdate) {
osuPath = path.join(osuPath, fileToUpdate.folder);
if (!(await fu.existsAsync(osuPath))) await fs.promises.mkdir(osuPath);
}
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.on('error', (err) => {
console.log(err);
eventEmitter.emit('error');
});
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;
switch (process.platform) {
case "linux":
executeUtil.runFile(osuPath, 'osu-stable', ["-devserver", serverDomain], onExit);
return true;
case "win32":
executeUtil.runFile(osuPath, osuExe, ["-devserver", serverDomain], onExit);
return true;
}
return false;
}
async function setConfigValue(configPath, key, value) {
const configLines = new Array();
const fileStream = await fs.promises.readFile(configPath, "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].trim();
if (key == keyname) {
configLines.push(`${keyname} = ${value}`);
} else {
configLines.push(line);
}
} else {
configLines.push(line);
}
}
await fs.promises.writeFile(configPath, configLines.join("\n"), 'utf-8');
}
async function updateOsuCfg(cfgPath) {
const osuFolder = path.dirname(cfgPath);
const fileStream = await fs.promises.readFile(cfgPath, "utf-8");
const lines = fileStream.split(/\r?\n/);
const newLines = [];
for (const line of lines) {
if (line.includes(' = ')) {
const argsPair = line.split(' = ', 2);
const keyname = argsPair[0]
const value = argsPair[1];
if (keyname.startsWith("h_")) {
const filename = keyname.substring(2, keyname.length);
const filepath = path.join(osuFolder, 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(`${keyname} = ${existingFileMD5}`);
} else {
newLines.push(line);
}
}
}
const ezppUI = path.join(osuFolder, customUIDLLName);
if (fs.existsSync(ezppUI)) {
const binaryFileContents = await fs.promises.readFile(ezppUI);
const existingFileMD5 = crypto.createHash("md5").update(binaryFileContents).digest("hex");
newLines.push(`h_${customUIDLLName} = ${existingFileMD5}`);
}
await fs.promises.writeFile(cfgPath, newLines.join("\n"), 'utf-8');
}
async function findOsuInstallation() {
const regedit = require('qiao-regedit');
const osuLocationFromDefaultIcon = "HKLM\\SOFTWARE\\Classes\\osu\\DefaultIcon";
const osuStruct = await regedit.listValuesSync(osuLocationFromDefaultIcon);
for (const line of osuStruct.split("\n")) {
if (line.includes("REG_SZ")) {
let value = (line.trim().split(" ")[2]);
value = value.substring(1, value.length - 3);
return path.dirname(value.trim());
}
}
return undefined;
}
async function replaceUI(folder, isStart) {
const ezppUIFile = path.join(folder, "EZPPLauncher", customUIDLLName);
const osuUIFile = path.join(folder, "osu!ui.dll");
const osuUIFileBackup = path.join(folder, "osu!ui.dll.bak");
if (isStart) {
if (fs.existsSync(osuUIFileBackup)) await fs.promises.unlink(osuUIFileBackup);
await fs.promises.rename(osuUIFile, osuUIFileBackup);
await new Promise((res) => setTimeout(res, 1000));
await fs.promises.copyFile(ezppUIFile, osuUIFile);
} else {
if (!fs.existsSync(osuUIFileBackup)) return;
await fs.promises.unlink(osuUIFile);
await new Promise((res) => setTimeout(res, 1000));
await fs.promises.rename(osuUIFileBackup, osuUIFile);
}
}
module.exports = {
isValidOsuFolder, getLatestConfig, getUpdateFiles, filesThatNeedUpdate,
downloadUpdateFiles, startOsuWithDevServer: startWithDevServer, setConfigValue,
findOsuInstallation, replaceUI, updateOsuCfg, getEZPPUIMD5
}
+1549 -6168
View File
File diff suppressed because it is too large Load Diff
+46 -76
View File
@@ -1,90 +1,60 @@
{ {
"name": "ezpplauncher-next", "name": "ezpplauncher",
"version": "2.1.7", "version": "1.1.5",
"description": "EZPPLauncher rewritten with Svelte.", "main": "app.js",
"private": false,
"license": "MIT", "license": "MIT",
"main": "main.js", "author": "HorizonCode",
"author": "HorizonCode <horizoncode88@gmail.com>",
"build": { "build": {
"icon": "public/favicon.png", "appId": "farm.ezpp.ezppfarm.launcher",
"productName": "EZPPLauncher", "productName": "ezpplauncher",
"files": [ "directories": {
"public/**/*", "output": "release",
"main.js", "buildResources": "dist"
"preload.js", },
"electron/*", "asar": true,
"electron/**/*"
],
"win": { "win": {
"icon": "./assets/logo.png",
"target": [ "target": [
"portable" "portable"
] ]
}, },
"linux": {}, "nsis": {
"mac": {} "runAfterFinish": true
},
"portable": {
"artifactName": "ezpplauncher-${version}.exe"
}
},
"frontend": {
"config": {
"applicationName": "EZPPLauncher"
}
}, },
"scripts": { "scripts": {
"build": "rollup -c --bundleConfigAsCjs", "start": "electron .",
"rebuild": "electron-rebuild", "rebuild": "electron-rebuild -f -w get-window-by-name",
"dev": "rollup -c -w --bundleConfigAsCjs", "pack-win": "electron-builder --win --x64",
"start": "sirv public --no-clear", "pack-linux": "electron-builder --linux --x64",
"electron": "wait-on http://localhost:8080 && electron .", "pack-mac": "electron-builder --mac --x64",
"electron-dev": "concurrently \"yarn run dev\" \"yarn run electron\"", "dist": "electron-builder",
"preelectron-pack": "electron-rebuild && yarn run build", "postinstall": "electron-builder install-app-deps"
"electron-pack": "electron-builder",
"postinstall": "electron-builder install-app-deps",
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"dependencies": {
"@types/better-sqlite3": "^7.6.8",
"axios": "^1.6.5",
"better-sqlite3": "^9.2.2",
"crypto": "^1.0.1",
"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": { "devDependencies": {
"@electron/rebuild": "^3.5.0", "electron": "^24.2.0",
"@rollup/plugin-commonjs": "^25.0.7", "electron-builder": "^23.6.0",
"@rollup/plugin-image": "^3.0.3", "electron-packager": "^17.1.1",
"@rollup/plugin-node-resolve": "^15.2.3", "electron-rebuild": "^3.2.9"
"@rollup/plugin-terser": "^0.4.4", },
"@rollup/plugin-typescript": "^11.1.5", "dependencies": {
"@tsconfig/svelte": "^5.0.2", "axios": "^0.27.2",
"autoprefixer": "^10.4.16", "compare-versions": "^6.0.0-rc.1",
"concurrently": "^8.2.2", "custom-electron-titlebar": "^4.1.1",
"electron": "^28.1.2", "discord-auto-rpc": "^1.0.17",
"electron-builder": "^24.9.1", "discord-rpc": "^4.0.1",
"flowbite": "^2.2.1", "get-window-by-name": "^2.0.0",
"flowbite-svelte": "^0.44.21", "jquery": "^3.6.0",
"flowbite-svelte-icons": "^0.4.5", "node-downloader-helper": "^2.1.4",
"postcss": "^8.4.32", "qiao-regedit": "^0.1.5",
"postcss-load-config": "^5.0.2", "sweetalert2": "^11.5.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"
} }
} }
-13
View File
@@ -1,13 +0,0 @@
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
View File
@@ -1,127 +0,0 @@
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"),
);
});
+278
View File
@@ -0,0 +1,278 @@
const { ipcRenderer, shell } = require('electron');
const { Titlebar, TitlebarColor } = require('custom-electron-titlebar');
const appInfo = require('../appInfo');
let currentPage = "loading";
let loggedIn = false;
window.addEventListener('DOMContentLoaded', () => {
const titlebar = new Titlebar({
backgroundColor: TitlebarColor.fromHex("#24283B"),
itemBackgroundColor: TitlebarColor.fromHex("#121212"),
menu: null,
enableMnemonics: false,
maximizable: false,
});
titlebar.updateTitle(`${appInfo.appName} ${appInfo.appVersion}`);
const $ = require('jquery');
const Swal = require('sweetalert2');
$('#account-action').on('click', () => {
if (!loggedIn) {
changePage('login');
} else {
$('#welcome-text').text(`Nice to see you!`);
$('#account-action').text('Click to login');
$('.user-image').css('background-image', `url(https://a.ez-pp.farm/0)`)
loggedIn = false;
ipcRenderer.send("perform-logout");
Swal.fire({
title: 'See ya soon!',
text: "Successfully logged out!",
icon: 'success',
confirmButtonText: 'Okay'
});
}
});
$('#action-cancel').on('click', () => {
if (!loggedIn) {
changePage('launch');
}
});
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;
case "missing-folder":
const responseData = await ipcRenderer.invoke('set-osu-dir');
if (!responseData)
return;
if (responseData.validOsuDir) {
Swal.fire({
title: 'Success!',
text: 'osu! folder set.',
icon: 'success',
confirmButtonText: 'Cool'
})
$('#currentOsuPath').text(responseData.folderPath);
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!'
})
}
break;
}
});
$("#action-login").on('click', async () => {
const username = $('#login-username').val();
const password = $('#login-password').val();
$("#action-login").attr('disabled', true);
$("#action-cancel").attr('disabled', true);
const responseData = await ipcRenderer.invoke('perform-login', { username, password });
$("#action-login").attr('disabled', false);
$("#action-cancel").attr('disabled', false);
if (!responseData)
return;
if (responseData.code != 200) {
Swal.fire({
title: 'Uh oh!',
text: responseData.message,
icon: 'error',
confirmButtonText: 'Oops.. my bad!'
})
return;
}
$('#login-username').val("");
$('#login-password').val("");
$('#welcome-text').text(`Welcome back, ${responseData.user.name}!`);
$('#account-action').text('Not you?');
$('.user-image').css('background-image', `url(https://a.ez-pp.farm/${responseData.user.id})`);
loggedIn = true;
changePage('launch');
})
$("#change-folder-btn").on('click', async () => {
const responseData = await ipcRenderer.invoke('set-osu-dir');
if (!responseData)
return;
if (responseData.validOsuDir) {
Swal.fire({
title: 'Success!',
text: 'osu! folder set.',
icon: 'success',
confirmButtonText: 'Cool'
})
$('#currentOsuPath').text(responseData.folderPath);
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('config_update', (event, data) => {
if (data.osuPath) {
$('#currentOsuPath').text(data.osuPath);
}
})
ipcRenderer.on('launcher_update', async (event, data) => {
const res = await Swal.fire({
title: 'Update available!',
text: `Version ${data.version} has been released!`,
icon: 'info',
showCancelButton: true,
confirmButtonText: 'Download',
cancelButtonText: 'Remind me later',
});
if (res.isConfirmed) {
shell.openExternal(data.url);
}
})
ipcRenderer.on('account_update', (event, data) => {
switch (data.type) {
case "login-failed":
Swal.fire({
title: 'Uh oh!',
text: data.message,
icon: 'error',
confirmButtonText: 'Okay'
});
changePage("launch");
break;
case "not-loggedin":
changePage("launch");
break;
case "loggedin":
changePage("launch");
$('#welcome-text').text(`Welcome back, ${data.user.name}!`);
$('#account-action').text('Not you?');
$('.user-image').css('background-image', `url(https://a.ez-pp.farm/${data.user.id})`);
loggedIn = true;
break;
}
})
ipcRenderer.on('alert_message', async (event, path) => {
const res = await Swal.fire({
title: 'Hey!',
html: `<p class="text-white">Detected a osu! installation at</p><span class="quotetext">${path}</span><p class="text-white">Is this correct?</p>`,
icon: 'info',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'No',
});
if (res.isConfirmed) {
$('#currentOsuPath').text(path);
ipcRenderer.send('alert_response', path);
Swal.fire({
title: 'Success!',
text: 'osu! folder set.',
icon: 'success',
confirmButtonText: 'Cool'
})
ipcRenderer.send("do-update-check");
}
})
ipcRenderer.on('status_update', (event, status) => {
switch (status.type) {
case "up-to-date":
$("#launch-btn").attr('disabled', false);
$('#launch-btn').html('Launch');
currentState = status.type;
break;
case "update-available":
$("#launch-btn").attr('disabled', false);
$('#launch-btn').html('Update');
currentState = status.type;
break;
case "missing-folder":
$("#launch-btn").attr('disabled', false);
$('#launch-btn').html('set your osu! folder');
currentState = status.type;
break;
case "error":
Swal.fire({
title: 'Uh oh!',
text: status.message,
icon: 'error',
confirmButtonText: 'Okay'
});
ipcRenderer.send("do-update-check");
break;
case "info":
Swal.fire({
title: 'Info!',
text: status.message,
icon: 'info',
confirmButtonText: 'Okay'
});
break;
case "package-issue":
Swal.fire({
title: 'Uh oh!',
text: status.message,
icon: 'error',
confirmButtonText: 'Okay'
});
$("#launch-btn").attr('disabled', true);
$('#launch-btn').html('missing packages');
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;
}
})
function changePage(page) {
$(`#${currentPage}-page`).fadeOut(50, "swing", () => {
$(`#${page}-page`).fadeIn(350);
});
currentPage = page;
}
// workaround for the dark theme
$('head').append($('<link href="../assets/sweetalert2.dark.css" rel="stylesheet" />'));
})
Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

-15
View File
@@ -1,15 +0,0 @@
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;
}
-22
View File
@@ -1,22 +0,0 @@
<!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>
-96
View File
@@ -1,96 +0,0 @@
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
View File
@@ -1,223 +0,0 @@
<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}
-93
View File
@@ -1,93 +0,0 @@
/* 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%;
}
}
-5
View File
@@ -1,5 +0,0 @@
export enum Page {
Login = 0,
Launch = 1,
Settings = 2,
}
-1
View File
@@ -1 +0,0 @@
/// <reference types="svelte" />
-101
View File
@@ -1,101 +0,0 @@
<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>
-8
View File
@@ -1,8 +0,0 @@
import "./app.pcss";
import App from "./App.svelte";
const app = new App({
target: document.body,
});
export default app;
-57
View File
@@ -1,57 +0,0 @@
<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>
-233
View File
@@ -1,233 +0,0 @@
<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>
-96
View File
@@ -1,96 +0,0 @@
<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>
-17
View File
@@ -1,17 +0,0 @@
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);
-4
View File
@@ -1,4 +0,0 @@
export type Error = {
code: number;
message: string;
};
-5
View File
@@ -1,5 +0,0 @@
declare module "*.jpg";
declare module "*.jpeg";
declare module "*.png";
declare module "*.svg";
declare module "*.svelte";
-6
View File
@@ -1,6 +0,0 @@
export type User = {
id: number;
donor: boolean;
name: string;
email: string;
};
-3
View File
@@ -1,3 +0,0 @@
export const clamp = (val: number, min: number, max: number) => {
return Math.max(min, Math.min(val, max));
};
-7
View File
@@ -1,7 +0,0 @@
const preprocess = require("svelte-preprocess");
const config = {
preprocess: [preprocess()],
};
module.exports = config;
-73
View File
@@ -1,73 +0,0 @@
/** @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;
+16
View File
@@ -0,0 +1,16 @@
const exec = require('child_process').exec;
const execPromise = function (cmd) {
return new Promise(function (resolve, reject) {
exec(cmd, function (err, stdout) {
if (err) return reject(err);
resolve(stdout);
});
});
}
const execCommand = async (command) => {
return await execPromise(command);
}
module.exports = { execCommand };
-23
View File
@@ -1,23 +0,0 @@
{
"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"
]
}
}
+45
View File
@@ -0,0 +1,45 @@
const path = require("path");
const appInfo = require('../appInfo');
const { BrowserWindow, Menu } = 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: "#24283B",
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'
})
const menu = Menu.buildFromTemplate([])
Menu.setApplicationMenu(menu);
window.hide();
window.webContents.once("did-finish-load", function (event, input) {
window.show();
});
window.webContents.setUserAgent(`${appInfo.appName} ${appInfo.appVersion}`);
attachTitlebarToWindow(window);
return window;
},
}