Compare commits

...

39 Commits

Author SHA1 Message Date
f31126e2b1 Merge pull request 'fix hwid error handling' (#21) from dev into master
Reviewed-on: #21
2024-07-21 19:48:52 +00:00
d171545f18 fix hwid error 2024-07-21 21:48:44 +02:00
b6b48da5fa Merge pull request 'fix hwid generation' (#20) from dev into master
Reviewed-on: #20
2024-07-21 12:59:44 +00:00
6551f0b369 fix hwid generation 2024-07-21 14:58:59 +02:00
f046746037 Merge pull request 'Add Useragent Headers, better error message handling on login, fix logger toggle' (#19) from dev into master
Reviewed-on: #19
2024-07-17 08:37:41 +00:00
74e233ecc3 add useragent headers 2024-07-17 10:24:13 +02:00
a848f078be add useragent headers 2024-07-17 10:24:07 +02:00
9f71ad0f8e bump version 2024-07-17 10:20:16 +02:00
45c5b329b5 only import needed elements 2024-07-17 10:20:10 +02:00
b0f180f1fb fix message log on error 2024-07-17 10:19:51 +02:00
6d42b4fe89 extend timeout to 10 seconds, fix undefined values 2024-07-17 10:19:28 +02:00
b8f45ad0b8 Merge pull request 'small bugfixes' (#18) from dev into master
Reviewed-on: #18
2024-04-23 07:57:29 +00:00
cd8f42327c remove logging 2024-04-23 09:56:45 +02:00
6881a0d1d5 pruning fixes 2024-04-23 09:56:22 +02:00
9f9804f161 small bugfixes 2024-04-23 09:38:11 +02:00
86c9bc4a60 Merge pull request 'chore: prune old update files, fix update dialog' (#17) from dev into master
Reviewed-on: #17
2024-04-23 07:08:24 +00:00
3bd1fb9edb fix assurance check 2024-04-23 08:57:21 +02:00
8a8856772e assurance check, that the launcher does not delete stuff directly in the osu folder 2024-04-23 08:55:00 +02:00
80343bd929 clean up unneeded files 2024-04-23 08:52:23 +02:00
9da481b991 bump version 2024-04-23 08:51:54 +02:00
70643c4287 update sweetalert 2, bump version 2024-04-23 08:51:47 +02:00
db03ed552f fix update dialog 2024-04-23 08:51:16 +02:00
6ccc285c61 Merge pull request 'fix api route' (#16) from dev into master
Reviewed-on: #16
2024-04-17 20:15:32 +00:00
a72ae1df5f Merge branch 'dev' of https://git.ez-pp.farm/EZPPFarm/EZPPLauncher into dev 2024-04-17 22:12:19 +02:00
9fbab69206 fix api routes 2024-04-17 22:11:25 +02:00
ecf329dd69 Merge pull request 'add optional logging, cleanup fixes' (#15) from dev into master
Reviewed-on: #15
2024-04-15 11:12:10 +00:00
4e79809c19 bump version 2024-04-15 13:11:52 +02:00
f06e63f7f8 remove unused import 2024-04-15 13:07:51 +02:00
638f1e852e add optional logging, cleanup fixes 2024-04-15 13:07:40 +02:00
8b30b7c1fa fix .net8 detection 2024-04-14 17:55:09 +02:00
2bb4a86df3 Merge pull request 'dev' (#14) from dev into master
Reviewed-on: #14
2024-04-14 15:32:58 +00:00
f41ca92711 update readme 2024-04-14 17:32:11 +02:00
65a3e86261 bump version 2024-04-14 17:31:37 +02:00
c74bc57453 remove test 2024-04-14 17:17:07 +02:00
528af70446 add .net8 check when patch is enabled 2024-04-14 17:16:46 +02:00
6f2764a047 patcher update, no more hardcoded urls, launch fixes 2024-04-14 16:36:32 +02:00
1c4a40c495 add mode and global rank to rpc 2024-03-16 18:48:44 +01:00
144d1bb86a Merge pull request 'bump version' (#13) from dev into master
Reviewed-on: #13
2024-03-11 18:36:17 +00:00
a9de377456 bump version 2024-03-11 19:35:54 +01:00
24 changed files with 1239 additions and 1145 deletions

View File

@ -12,6 +12,7 @@ The Launcher is a "plug and play thing", download it, place it on the desktop an
- Automatic osu! client updating before Launch - Automatic osu! client updating before Launch
- Custom osu! Logo in MainMenu - Custom osu! Logo in MainMenu
- Relax misses and much more
- Account saving - Account saving
## Build from source ## Build from source

View File

@ -1,4 +1,4 @@
const appName = "EZPPLauncher"; const appName = "EZPPLauncher";
const appVersion = "2.1.1"; const appVersion = "2.1.7";
module.exports = { appName, appVersion }; module.exports = { appName, appVersion };

View File

@ -1,20 +1,20 @@
const childProcess = require("child_process"); const childProcess = require("child_process");
const runFile = (folder, file, args, onExit) => { const runFile = (folder, file, args, onExit) => {
childProcess.execFile(file, args, { childProcess.execFile(file, args, {
cwd: folder cwd: folder,
}, (_err, _stdout, _stdin) => { }, (_err, _stdout, _stdin) => {
if (onExit) onExit(); if (onExit) onExit();
}) });
} };
const runFileDetached = (folder, file, args) => { const runFileDetached = (folder, file, args) => {
const subProcess = childProcess.spawn(file + (args ? " " + args : ''), { const subProcess = childProcess.spawn(file + (args ? " " + args : ""), {
cwd: folder, cwd: folder,
detached: true, detached: true,
stdio: 'ignore' stdio: "ignore",
}); });
subProcess.unref(); subProcess.unref();
} };
module.exports = { runFile, runFileDetached }; module.exports = { runFile, runFileDetached };

15
electron/fileUtil.js Normal file
View File

@ -0,0 +1,15 @@
const fs = require("fs");
function isWritable(filePath) {
let fileAccess = false;
try {
fs.closeSync(fs.openSync(filePath, "r+"));
fileAccess = true;
} catch {
}
return fileAccess;
}
module.exports = {
isWritable,
};

View File

@ -1,13 +1,13 @@
function formatBytes(bytes, decimals = 2) { function formatBytes(bytes, decimals = 2) {
if (!+bytes) return '0 Bytes' if (!+bytes) return "0 B";
const k = 1024 const k = 1024;
const dm = decimals < 0 ? 0 : decimals const dm = decimals < 0 ? 0 : decimals;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const i = Math.floor(Math.log(bytes) / Math.log(k)) const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))}${sizes[i]}` return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))}${sizes[i]}`;
} }
module.exports = { formatBytes }; module.exports = { formatBytes };

View File

@ -16,17 +16,25 @@ const platforms = {
}; };
const crypto = require("crypto"); const crypto = require("crypto");
const defaultHWID = "recorderinthesandybridge";
/** /**
* Returns machine hardware id. * Returns machine hardware id.
* Returns `undefined` if cannot determine. * Returns `undefined` if cannot determine.
* @return {string?} * @return {Promise<string>}
*/ */
function getHwId() { function getHwId() {
const getter = platforms[process.platform]; return new Promise((resolve) => {
if (!getter) return; try {
const result = getter[1].exec(child_process.execSync(getter[0], options)); const getter = platforms[process.platform];
if (!result) return; if (getter) {
return crypto.createHash("md5").update(result[1]).digest("hex") || const result = getter[1].exec(child_process.execSync(getter[0], options));
undefined; if (result) resolve(crypto.createHash("md5").update(result[1]).digest("hex"));
}
resolve(crypto.createHash("md5").update(defaultHWID).digest("hex"));
} catch {
resolve(crypto.createHash("md5").update(defaultHWID).digest("hex"));
}
})
} }
exports.getHwId = getHwId; exports.getHwId = getHwId;

View File

@ -1,6 +1,12 @@
async function checkImageExists(url) { async function checkImageExists(url) {
try { try {
const response = await fetch(url, { method: "HEAD" }); 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) { if (!response.ok) {
return false; return false;
} }

44
electron/logging.js Normal file
View File

@ -0,0 +1,44 @@
const fs = require("fs");
const path = require("path");
class Logger {
constructor(directory) {
this.directory = directory;
this.enabled = false;
}
async init() {
const filename = `${new Date().toISOString().replace(/:/g, "-")}.log`;
this.logPath = path.join(this.directory, filename);
}
async log(message) {
if (this.logPath === undefined || this.enabled == false) {
return;
}
if (!fs.existsSync(this.logPath)) {
await fs.promises.mkdir(this.directory, { recursive: true });
await fs.promises.writeFile(this.logPath, "");
}
const logMessage = `[${new Date().toISOString()}] LOG: ${message}`;
await fs.promises.appendFile(this.logPath, `${logMessage}\n`);
console.log(logMessage);
}
async error(message, error) {
if (this.logPath === undefined || this.enabled == false) {
return;
}
if (!fs.existsSync(this.logPath)) {
await fs.promises.mkdir(this.directory, { recursive: true });
await fs.promises.writeFile(this.logPath, "");
}
const errorMessage = `[${
new Date().toISOString()
}] ERROR: ${message}\n${error.stack}`;
await fs.promises.appendFile(this.logPath, `${errorMessage}\n`);
console.error(errorMessage);
}
}
module.exports = Logger;

26
electron/netUtils.js Normal file
View File

@ -0,0 +1,26 @@
const { exec } = require("child_process");
async function isNet8Installed() {
return new Promise((resolve) => {
exec("dotnet --list-runtimes", (error, stdout, stderr) => {
if (error) {
resolve(false);
return;
}
if (stderr) {
resolve(false);
return;
}
const version = stdout.trim();
for (const line of version.split('\n')) {
if (line.startsWith("Microsoft.WindowsDesktop.App 8.")) {
resolve(true);
break;
}
}
resolve(false);
})
});
}
module.exports = { isNet8Installed };

View File

@ -10,6 +10,16 @@ const checkUpdateURL =
const ignoredOsuEntities = [ const ignoredOsuEntities = [
"osu!auth.dll", "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 = [ const osuEntities = [
"avcodec-51.dll", "avcodec-51.dll",
"avformat-52.dll", "avformat-52.dll",
@ -34,26 +44,7 @@ const osuEntities = [
"scores.db", "scores.db",
]; ];
const patcherFiles = [ const ezppLauncherUpdateList = "https://ez-pp.farm/ezpplauncher";
{
name: "patcher.exe",
url_download: "https://ez-pp.farm/assets/patcher.exe",
url_hash: "https://ez-pp.farm/assets/patcher.md5",
},
{
name: "patch.hook.dll",
url_download: "https://ez-pp.farm/assets/patch.hook.dll",
url_hash: "https://ez-pp.farm/assets/patch.hook.md5",
},
];
const uiFiles = [
{
name: "ezpp!ui.dll",
url_download: "https://ez-pp.farm/assets/ezpp!ui.dll",
url_hash: "https://ez-pp.farm/assets/ezpp!ui.md5",
},
];
async function isValidOsuFolder(path) { async function isValidOsuFolder(path) {
const allFiles = await fs.promises.readdir(path); const allFiles = await fs.promises.readdir(path);
@ -161,7 +152,12 @@ function getUserConfig(osuPath) {
} }
async function getUpdateFiles(releaseStream) { async function getUpdateFiles(releaseStream) {
const releaseData = await fetch(checkUpdateURL + 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; return releaseData.ok ? await releaseData.json() : undefined;
} }
@ -193,31 +189,24 @@ function downloadUpdateFiles(osuPath, updateFiles) {
const startDownload = async () => { const startDownload = async () => {
for (const updatePatch of updateFiles) { for (const updatePatch of updateFiles) {
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),
});
},
});
axiosDownloadWithProgress.data.on("end", () => {
eventEmitter.emit("data", {
fileName,
loaded: fileSize,
total: fileSize,
progress: 100,
});
});
try { 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))) { if (fs.existsSync(path.join(osuPath, fileName))) {
await fs.promises.rm(path.join(osuPath, fileName), { await fs.promises.rm(path.join(osuPath, fileName), {
force: true, force: true,
@ -231,6 +220,7 @@ function downloadUpdateFiles(osuPath, updateFiles) {
console.log(err); console.log(err);
eventEmitter.emit("error", { eventEmitter.emit("error", {
fileName, fileName,
error: err,
}); });
} }
} }
@ -255,66 +245,115 @@ function runOsuWithDevServer(osuPath, serverDomain, onExit) {
runFile(osuPath, osuExecuteable, ["-devserver", serverDomain], onExit); runFile(osuPath, osuExecuteable, ["-devserver", serverDomain], onExit);
} }
async function getPatcherUpdates(osuPath) { async function getEZPPLauncherUpdateFiles(osuPath) {
const filesToDownload = []; const filesToDownload = [];
const updateFilesRequest = await fetch(ezppLauncherUpdateList, {
const patcherDir = path.join(osuPath, "EZPPLauncher"); method: "PATCH",
if (!fs.existsSync(patcherDir)) fs.mkdirSync(patcherDir); headers: {
"User-Agent":
for (const patcherFile of patcherFiles) { "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 (fs.existsSync(path.join(patcherDir, patcherFile.name))) { },
const latestPatchFileHash = await (await fetch(patcherFile.url_hash)) });
.text(); const updateFiles = await updateFilesRequest.json();
const localPatchFileHash = crypto.createHash("md5").update( for (const updateFile of updateFiles) {
fs.readFileSync(path.join(patcherDir, patcherFile.name)), const filePath = path.join(
).digest("hex"); osuPath,
if ( ...updateFile.folder.split("/"),
latestPatchFileHash.trim().toLowerCase() != updateFile.name,
localPatchFileHash.trim().toLowerCase() );
) filesToDownload.push(patcherFile); if (fs.existsSync(filePath)) {
} else filesToDownload.push(patcherFile); 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];
return filesToDownload;
} }
function downloadPatcherUpdates(osuPath, patcherUpdates) { async function downloadEZPPLauncherUpdateFiles(osuPath, updateFiles, allFiles) {
const eventEmitter = new EventEmitter(); const eventEmitter = new EventEmitter();
const startDownload = async () => { const startDownload = async () => {
const patcherDir = path.join(osuPath, "EZPPLauncher"); //NOTE: delete files that are not in the updateFiles array
if (!fs.existsSync(patcherDir)) fs.mkdirSync(patcherDir); const foldersToPrune = allFiles.map((file) =>
path.dirname(path.join(osuPath, ...file.folder.split("/"), file.name))
for (const patcherFile of patcherUpdates) { ).filter((folder, index, self) => self.indexOf(folder) === index);
const fileName = patcherFile.name; for (const pruneFolder of foldersToPrune) {
const fileURL = patcherFile.url_download; //NOTE: check if the folder is not the osu root folder.
const axiosDownloadWithProgress = await axios.get(fileURL, { if (path.basename(pruneFolder) == "osu!") {
responseType: "stream", continue;
onDownloadProgress: (progressEvent) => { }
const { loaded, total } = progressEvent; if (fs.existsSync(pruneFolder)) {
eventEmitter.emit("data", { for (const files of await fs.promises.readdir(pruneFolder)) {
fileName, const filePath = path.join(pruneFolder, files);
loaded, const validFolder = allFiles.find((file) =>
total, path.dirname(filePath).endsWith(file.folder)
progress: Math.floor((loaded / total) * 100), );
}); 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 { try {
if (fs.existsSync(path.join(osuPath, "EZPPLauncher", fileName))) { const filePath = path.join(
await fs.promises.rm(path.join(osuPath, "EZPPLauncher", fileName), { 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, force: true,
}); });
} }
await fs.promises.writeFile( await fs.promises.writeFile(
path.join(osuPath, "EZPPLauncher", fileName), filePath,
axiosDownloadWithProgress.data, axiosDownloadWithProgress.data,
); );
} catch (err) { } catch (err) {
console.log(err); console.log(err);
eventEmitter.emit("error", { eventEmitter.emit("error", {
fileName, fileName: path.basename(filePath),
error: err,
}); });
} }
} }
@ -326,93 +365,49 @@ function downloadPatcherUpdates(osuPath, patcherUpdates) {
}; };
} }
async function getUIFiles(osuPath) { async function replaceUIFiles(osuPath, revert) {
const filesToDownload = [];
const ezppLauncherDir = path.join(osuPath, "EZPPLauncher");
if (!fs.existsSync(ezppLauncherDir)) fs.mkdirSync(ezppLauncherDir);
for (const uiFile of uiFiles) {
if (fs.existsSync(path.join(ezppLauncherDir, uiFile.name))) {
const latestPatchFileHash = await (await fetch(uiFile.url_hash)).text();
const localPatchFileHash = crypto.createHash("md5").update(
fs.readFileSync(path.join(ezppLauncherDir, uiFile.name)),
).digest("hex");
if (
latestPatchFileHash.trim().toLowerCase() !=
localPatchFileHash.trim().toLowerCase()
) filesToDownload.push(uiFile);
} else filesToDownload.push(uiFile);
}
return filesToDownload;
}
function downloadUIFiles(osuPath, uiFiles) {
const eventEmitter = new EventEmitter();
const startDownload = async () => {
const ezpplauncherDir = path.join(osuPath, "EZPPLauncher");
if (!fs.existsSync(ezpplauncherDir)) fs.mkdirSync(ezpplauncherDir);
for (const uiFile of uiFiles) {
const fileName = uiFile.name;
const fileURL = uiFile.url_download;
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),
});
},
});
try {
if (fs.existsSync(path.join(osuPath, "EZPPLauncher", fileName))) {
await fs.promises.rm(path.join(osuPath, "EZPPLauncher", fileName), {
force: true,
});
}
await fs.promises.writeFile(
path.join(osuPath, "EZPPLauncher", fileName),
axiosDownloadWithProgress.data,
);
} catch (err) {
console.log(err);
eventEmitter.emit("error", {
fileName,
});
}
}
};
return {
eventEmitter,
startDownload,
};
}
async function replaceUIFile(osuPath, revert) {
if (!revert) { if (!revert) {
const ezppUIFile = path.join(osuPath, "EZPPLauncher", "ezpp!ui.dll"); const ezppUIFile = path.join(osuPath, "EZPPLauncher", "ezpp!ui.dll");
const oldOsuUIFile = path.join(osuPath, "osu!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( await fs.promises.rename(
oldOsuUIFile, oldOsuUIFile,
path.join(osuPath, "osu!ui.dll.bak"), path.join(osuPath, "osu!ui.dll.bak"),
); );
await fs.promises.rename(ezppUIFile, oldOsuUIFile); 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 { } else {
const oldOsuUIFile = path.join(osuPath, "osu!ui.dll"); const oldOsuUIFile = path.join(osuPath, "osu!ui.dll");
const ezppUIFile = path.join(osuPath, "EZPPLauncher", "ezpp!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(oldOsuUIFile, ezppUIFile);
await fs.promises.rename( await fs.promises.rename(
path.join(osuPath, "osu!ui.dll.bak"), path.join(osuPath, "osu!ui.dll.bak"),
oldOsuUIFile, oldOsuUIFile,
); );
await fs.promises.rename(oldOsuGameplayFile, ezppGameplayFile);
await fs.promises.rename(
path.join(osuPath, "osu!gameplay.dll.bak"),
oldOsuGameplayFile,
);
} }
} }
@ -473,12 +468,11 @@ module.exports = {
getFilesThatNeedUpdate, getFilesThatNeedUpdate,
downloadUpdateFiles, downloadUpdateFiles,
runOsuWithDevServer, runOsuWithDevServer,
getPatcherUpdates, replaceUIFiles,
downloadPatcherUpdates,
downloadUIFiles,
getUIFiles,
replaceUIFile,
findOsuInstallation, findOsuInstallation,
updateOsuConfigHashes, updateOsuConfigHashes,
runOsuUpdater, runOsuUpdater,
getEZPPLauncherUpdateFiles,
downloadEZPPLauncherUpdateFiles,
gamemodes,
}; };

View File

@ -35,7 +35,9 @@ module.exports = {
richPresence = new DiscordRPC.AutoClient({ transport: "ipc" }); richPresence = new DiscordRPC.AutoClient({ transport: "ipc" });
richPresence.endlessLogin({ clientId }); richPresence.endlessLogin({ clientId });
richPresence.once("ready", () => { richPresence.once("ready", () => {
console.log("connected presence with user " + richPresence.user.username); console.log(
"connected presence with user " + richPresence.user.username,
);
richPresence.setActivity(currentStatus); richPresence.setActivity(currentStatus);
intervalId = setInterval(() => { intervalId = setInterval(() => {
richPresence.setActivity(currentStatus); richPresence.setActivity(currentStatus);

View File

@ -10,7 +10,12 @@ const releasesUrl =
module.exports = { module.exports = {
updateAvailable: async () => { updateAvailable: async () => {
try { try {
const latestRelease = await fetch(repoApiUrl); 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(); const json = await latestRelease.json();
if (json.length <= 0) return false; if (json.length <= 0) return false;
return { return {

613
main.js
View File

@ -3,15 +3,6 @@ const { app, BrowserWindow, Menu, ipcMain, dialog, shell } = require(
"electron", "electron",
); );
/* const unhandled = require("electron-unhandled");
unhandled({
logger: console.error,
showDialog: true,
reportButton: () => {
shell.openExternal("https://ez-pp.farm/discord");
},
}); */
const path = require("path"); const path = require("path");
const serve = require("electron-serve"); const serve = require("electron-serve");
const loadURL = serve({ directory: "public" }); const loadURL = serve({ directory: "public" });
@ -27,14 +18,12 @@ const {
downloadUpdateFiles, downloadUpdateFiles,
getUserConfig, getUserConfig,
runOsuWithDevServer, runOsuWithDevServer,
getPatcherUpdates, replaceUIFiles,
downloadPatcherUpdates,
getUIFiles,
downloadUIFiles,
replaceUIFile,
findOsuInstallation, findOsuInstallation,
updateOsuConfigHashes,
runOsuUpdater, runOsuUpdater,
gamemodes,
getEZPPLauncherUpdateFiles,
downloadEZPPLauncherUpdateFiles,
} = require("./electron/osuUtil"); } = require("./electron/osuUtil");
const { formatBytes } = require("./electron/formattingUtil"); const { formatBytes } = require("./electron/formattingUtil");
const windowName = require("get-window-by-name"); const windowName = require("get-window-by-name");
@ -47,6 +36,9 @@ const { appName, appVersion } = require("./electron/appInfo");
const { updateAvailable, releasesUrl } = require("./electron/updateCheck"); const { updateAvailable, releasesUrl } = require("./electron/updateCheck");
const fkill = require("fkill"); const fkill = require("fkill");
const { checkImageExists } = require("./electron/imageUtil"); 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 // 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. // be closed automatically when the JavaScript object is garbage collected.
@ -55,6 +47,13 @@ let osuCheckInterval;
let userOsuPath; let userOsuPath;
let osuLoaded = false; let osuLoaded = false;
let patch = false; let patch = false;
let logger = new Logger(path.join(
process.platform == "win32"
? process.env["LOCALAPPDATA"]
: process.env["HOME"],
"EZPPLauncher",
"logs",
));
let currentUser = undefined; let currentUser = undefined;
@ -75,10 +74,24 @@ function startOsuStatus() {
osuLoaded = true; osuLoaded = true;
try { try {
const currentUserInfo = await fetch(`https://api.ez-pp.farm/get_player_info?name=${currentUser.username}&scope=info`); 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(); const currentUserInfoJson = await currentUserInfo.json();
if ("player" in currentUserInfoJson && currentUserInfoJson.player != null) { if (
if ("info" in currentUserInfoJson.player && currentUserInfoJson.player.info != null) { "player" in currentUserInfoJson &&
currentUserInfoJson.player != null
) {
if (
"info" in currentUserInfoJson.player &&
currentUserInfoJson.player.info != null
) {
const id = currentUserInfoJson.player.info.id; const id = currentUserInfoJson.player.info.id;
const username = currentUserInfoJson.player.info.name; const username = currentUserInfoJson.player.info.name;
richPresence.updateUser({ richPresence.updateUser({
@ -89,7 +102,6 @@ function startOsuStatus() {
} }
} }
} catch { } catch {
} }
setTimeout(() => { setTimeout(() => {
@ -97,7 +109,8 @@ function startOsuStatus() {
const patcherExecuteable = path.join( const patcherExecuteable = path.join(
userOsuPath, userOsuPath,
"EZPPLauncher", "EZPPLauncher",
"patcher.exe", "patcher",
"osu!.patcher.exe",
); );
if (fs.existsSync(patcherExecuteable)) { if (fs.existsSync(patcherExecuteable)) {
runFileDetached(userOsuPath, patcherExecuteable); runFileDetached(userOsuPath, patcherExecuteable);
@ -109,13 +122,40 @@ function startOsuStatus() {
const windowTitle = firstInstance.processTitle; const windowTitle = firstInstance.processTitle;
lastOsuStatus = windowTitle; lastOsuStatus = windowTitle;
const currentStatusRequest = await fetch( const currentStatusRequest = await fetch(
"https://api.ez-pp.farm/get_player_status?name=" + currentUser.username, "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(); const currentStatus = await currentStatusRequest.json();
if (!("player_status" in currentStatus)) return; if (!("player_status" in currentStatus)) return;
if (!("status" in currentStatus.player_status)) 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 largeImageKey = "ezppfarm";
let details = "Idle..."; let details = "Idle...";
let infoText = currentStatus.player_status.status.info_text.length > 0 let infoText = currentStatus.player_status.status.info_text.length > 0
@ -182,6 +222,13 @@ function startOsuStatus() {
break; break;
} }
details = `[${currentModeString}] ${details}`;
richPresence.updateUser({
username: currentUsername,
id: currentId,
});
richPresence.updateStatus({ richPresence.updateStatus({
details, details,
state: infoText, state: infoText,
@ -199,9 +246,19 @@ function stopOsuStatus() {
function registerIPCPipes() { function registerIPCPipes() {
ipcMain.handle("ezpplauncher:login", async (e, args) => { ipcMain.handle("ezpplauncher:login", async (e, args) => {
const hwid = getHwId(); let hwid = "";
try {
hwid = await getHwId();
} catch (err) {
logger.error(`Failed to get HWID.`, err);
return {
code: 500,
message: "Failed to get HWID.",
};
}
const timeout = new AbortController(); const timeout = new AbortController();
const timeoutId = setTimeout(() => timeout.abort(), 8000); const timeoutId = setTimeout(() => timeout.abort(), 1000 * 10);
logger.log(`Logging in with user ${args.username}...`);
try { try {
const fetchResult = await fetch("https://ez-pp.farm/login/check", { const fetchResult = await fetch("https://ez-pp.farm/login/check", {
signal: timeout.signal, signal: timeout.signal,
@ -212,6 +269,8 @@ function registerIPCPipes() {
}), }),
headers: { headers: {
"Content-Type": "application/json", "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",
}, },
}); });
@ -226,14 +285,20 @@ function registerIPCPipes() {
} }
currentUser = args; currentUser = args;
config.remove("guest"); config.remove("guest");
} logger.log(`Logged in as user ${args.username}!`);
} else logger.log(`Login failed for user ${args.username}.`);
return result; return result;
} }
logger.log(
`Login failed for user ${args.username}.\nResponse:\n${await fetchResult
.text()}`,
);
return { return {
code: 500, code: 500,
message: "Something went wrong while logging you in.", message: "Something went wrong while logging you in.",
}; };
} catch (err) { } catch (err) {
logger.error("Error while logging in:", err);
return { return {
code: 500, code: 500,
message: "Something went wrong while logging you in.", message: "Something went wrong while logging you in.",
@ -250,7 +315,7 @@ function registerIPCPipes() {
}); });
ipcMain.handle("ezpplauncher:autologin", async (e) => { ipcMain.handle("ezpplauncher:autologin", async (e) => {
const hwid = getHwId(); const hwid = await getHwId();
const username = config.get("username"); const username = config.get("username");
const guest = config.get("guest"); const guest = config.get("guest");
if (guest) return { code: 200, message: "Login as guest", guest: true }; if (guest) return { code: 200, message: "Login as guest", guest: true };
@ -263,6 +328,7 @@ function registerIPCPipes() {
} }
const timeout = new AbortController(); const timeout = new AbortController();
const timeoutId = setTimeout(() => timeout.abort(), 8000); const timeoutId = setTimeout(() => timeout.abort(), 8000);
logger.log(`Logging in with user ${username}...`);
try { try {
const fetchResult = await fetch("https://ez-pp.farm/login/check", { const fetchResult = await fetch("https://ez-pp.farm/login/check", {
signal: timeout.signal, signal: timeout.signal,
@ -273,6 +339,8 @@ function registerIPCPipes() {
}), }),
headers: { headers: {
"Content-Type": "application/json", "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",
}, },
}); });
@ -285,16 +353,23 @@ function registerIPCPipes() {
username: username, username: username,
password: password, password: password,
}; };
} logger.log(`Logged in as user ${username}!`);
} else logger.log(`Login failed for user ${username}.`);
return result; return result;
} else { } else {
config.remove("password"); config.remove("password");
} }
logger.log(
`Login failed for user ${username}.\nResponse:\n${await fetchResult
.text()}`,
);
return { return {
code: 500, code: 500,
message: "Something went wrong while logging you in.", message: "Something went wrong while logging you in.",
}; };
} catch (err) { } catch (err) {
logger.error("Error while logging in:", err);
return { return {
code: 500, code: 500,
message: "Something went wrong while logging you in.", message: "Something went wrong while logging you in.",
@ -307,6 +382,7 @@ function registerIPCPipes() {
config.remove("password"); config.remove("password");
config.set("guest", "1"); config.set("guest", "1");
currentUser = undefined; currentUser = undefined;
logger.log("Logged in as guest user.");
}); });
ipcMain.handle("ezpplauncher:logout", (e) => { ipcMain.handle("ezpplauncher:logout", (e) => {
@ -314,6 +390,7 @@ function registerIPCPipes() {
config.remove("password"); config.remove("password");
config.remove("guest"); config.remove("guest");
currentUser = undefined; currentUser = undefined;
logger.log("Loging out.");
return true; return true;
}); });
@ -330,6 +407,10 @@ function registerIPCPipes() {
else richPresence.connect(); else richPresence.connect();
} }
if (key == "logging") {
logger.enabled = value;
}
if (typeof value == "boolean") { if (typeof value == "boolean") {
config.set(key, value ? "true" : "false"); config.set(key, value ? "true" : "false");
} else { } else {
@ -373,125 +454,91 @@ function registerIPCPipes() {
return config.all(); 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) => { ipcMain.handle("ezpplauncher:exitAndUpdate", async (e) => {
await shell.openExternal(releasesUrl); await shell.openExternal(releasesUrl);
app.exit(); app.exit();
}); });
ipcMain.handle("ezpplauncher:launch", async (e) => { ipcMain.handle("ezpplauncher:launch", async (e) => {
const configPatch = config.get("patch"); try {
patch = configPatch != undefined ? configPatch == "true" : true; const osuWindowTitle = windowName.getWindowText("osu!.exe");
mainWindow.webContents.send("ezpplauncher:launchstatus", { if (osuWindowTitle.length > 0) {
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!",
});
return;
}
if (!(await isValidOsuFolder(osuPath))) {
mainWindow.webContents.send("ezpplauncher:launchabort");
mainWindow.webContents.send("ezpplauncher:alert", {
type: "error",
message: "invalid osu! path!",
});
return;
}
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 uiFiles = await getUIFiles(osuPath);
const updateFiles = await getFilesThatNeedUpdate(osuPath, latestFiles);
if (uiFiles.length > 0) {
const uiDownloader = downloadUIFiles(osuPath, uiFiles);
let errored = false;
uiDownloader.eventEmitter.on("error", (data) => {
const filename = data.fileName;
errored = true;
mainWindow.webContents.send("ezpplauncher:alert", { mainWindow.webContents.send("ezpplauncher:alert", {
type: "error", type: "error",
message: message: "osu! is running, please exit.",
`Failed to download/replace ${filename}!\nMaybe try to restart EZPPLauncher.`,
}); });
});
uiDownloader.eventEmitter.on("data", (data) => {
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 uiDownloader.startDownload();
mainWindow.webContents.send("ezpplauncher:launchprogress", {
progress: -1,
});
if (errored) {
mainWindow.webContents.send("ezpplauncher:launchabort"); mainWindow.webContents.send("ezpplauncher:launchabort");
return; return;
} }
}
if (updateFiles.length > 0) {
const updateDownloader = downloadUpdateFiles(osuPath, updateFiles);
let errored = false;
updateDownloader.eventEmitter.on("error", (data) => {
const filename = data.fileName;
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) => {
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) { logger.log("Preparing launch...");
const configPatch = config.get("patch");
patch = configPatch != undefined ? configPatch == "true" : true;
mainWindow.webContents.send("ezpplauncher:launchstatus", { mainWindow.webContents.send("ezpplauncher:launchstatus", {
status: "Looking for patcher updates...", status: "Checking osu! directory...",
}); });
await new Promise((res) => setTimeout(res, 1000)); await new Promise((res) => setTimeout(res, 1000));
const patchFiles = await getPatcherUpdates(osuPath); const osuPath = config.get("osuPath");
if (patchFiles.length > 0) { userOsuPath = osuPath;
const patcherDownloader = downloadPatcherUpdates(osuPath, patchFiles); 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; let errored = false;
patcherDownloader.eventEmitter.on("error", (data) => { updateDownloader.eventEmitter.on("error", (data) => {
const filename = data.fileName; const filename = data.fileName;
logger.error(
`Failed to download/replace ${filename}!`,
data.error,
);
errored = true; errored = true;
mainWindow.webContents.send("ezpplauncher:alert", { mainWindow.webContents.send("ezpplauncher:alert", {
type: "error", type: "error",
@ -499,16 +546,20 @@ function registerIPCPipes() {
`Failed to download/replace ${filename}!\nMaybe try to restart EZPPLauncher.`, `Failed to download/replace ${filename}!\nMaybe try to restart EZPPLauncher.`,
}); });
}); });
patcherDownloader.eventEmitter.on("data", (data) => { updateDownloader.eventEmitter.on("data", (data) => {
if (data.progress >= 100) {
logger.log(`Downloaded ${data.fileName} successfully.`);
}
mainWindow.webContents.send("ezpplauncher:launchprogress", { mainWindow.webContents.send("ezpplauncher:launchprogress", {
progress: Math.ceil(data.progress), progress: Math.ceil(data.progress),
}); });
mainWindow.webContents.send("ezpplauncher:launchstatus", { mainWindow.webContents.send("ezpplauncher:launchstatus", {
status: `Downloading ${data.fileName}(${formatBytes(data.loaded)}/${formatBytes(data.total) status: `Downloading ${data.fileName}(${formatBytes(data.loaded)}/${
})...`, formatBytes(data.total)
})...`,
}); });
}); });
await patcherDownloader.startDownload(); await updateDownloader.startDownload();
mainWindow.webContents.send("ezpplauncher:launchprogress", { mainWindow.webContents.send("ezpplauncher:launchprogress", {
progress: -1, progress: -1,
}); });
@ -517,119 +568,218 @@ function registerIPCPipes() {
return; return;
} }
mainWindow.webContents.send("ezpplauncher:launchstatus", { mainWindow.webContents.send("ezpplauncher:launchstatus", {
status: "Patcher is now up to date!", status: "osu! is now up to date!",
}); });
await new Promise((res) => setTimeout(res, 1000));
} else { } else {
mainWindow.webContents.send("ezpplauncher:launchstatus", { mainWindow.webContents.send("ezpplauncher:launchstatus", {
status: "Patcher is up to date!", status: "osu! is up to date!",
}); });
await new Promise((res) => setTimeout(res, 1000));
} }
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) => { if (patch) {
runOsuUpdater(osuPath, async () => { mainWindow.webContents.send("ezpplauncher:launchstatus", {
await new Promise((res) => setTimeout(res, 500)); status: "Looking for patcher updates...",
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));
} const [patchFiles, allUpdateFiles] = await getEZPPLauncherUpdateFiles(
osuPath,
await new Promise((res) => setTimeout(res, 1000)); );
if (patchFiles.length > 0) {
mainWindow.webContents.send("ezpplauncher:launchstatus", { logger.log("EZPPLauncher updates found.");
status: "Preparing launch...", const patcherDownloader = await downloadEZPPLauncherUpdateFiles(
}); osuPath,
patchFiles,
await updateOsuConfigHashes(osuPath); allUpdateFiles,
await replaceUIFile(osuPath, false); );
let errored = false;
const forceUpdateFiles = [ patcherDownloader.eventEmitter.on("error", (data) => {
".require_update", const filename = data.fileName;
"help.txt", logger.error(`Failed to download/replace ${filename}!`, data.error);
"_pending", errored = true;
]; mainWindow.webContents.send("ezpplauncher:alert", {
//TODO: needs testing type: "error",
try { message:
for (const updateFileName of forceUpdateFiles) { `Failed to download/replace ${filename}!\nMaybe try to restart EZPPLauncher.`,
const updateFile = path.join(osuPath, updateFileName); });
if (fs.existsSync(updateFile)) { });
await fs.promises.rm(updateFile, { patcherDownloader.eventEmitter.on("data", (data) => {
force: true, if (data.progress >= 100) {
recursive: (await fs.promises.lstat(updateFile)).isDirectory, 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));
} }
} catch { } if (updateFiles.length > 0) {
mainWindow.webContents.send("ezpplauncher:launchstatus", {
status: "Launching osu! updater to verify updates...",
});
await new Promise((res) => setTimeout(res, 1000));
const userConfig = getUserConfig(osuPath); await new Promise((res) => {
if (richPresence.hasPresence) { runOsuUpdater(osuPath, async () => {
await userConfig.set("DiscordRichPresence", "0"); await new Promise((res) => setTimeout(res, 500));
} const terminationThread = setInterval(async () => {
await userConfig.set("ShowInterfaceDuringRelax", "1"); const osuWindowTitle = windowName.getWindowText("osu!.exe");
if (currentUser) { if (osuWindowTitle.length < 0) {
await userConfig.set("CredentialEndpoint", "ez-pp.farm"); return;
await userConfig.set("SavePassword", "1"); }
await userConfig.set("SaveUsername", "1"); const firstInstance = osuWindowTitle[0];
await userConfig.set("Username", currentUser.username); if (firstInstance) {
await userConfig.set("Password", currentUser.password); const processId = firstInstance.processId;
} await fkill(processId, { force: true, silent: true });
clearInterval(terminationThread);
res();
}
}, 500);
});
});
}
mainWindow.webContents.send("ezpplauncher:launchstatus", { await new Promise((res) => setTimeout(res, 1000));
status: "Launching osu!...",
});
const onExitHook = () => {
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", { mainWindow.webContents.send("ezpplauncher:launchstatus", {
status: "Waiting for cleanup...", status: "Preparing launch...",
}); });
setTimeout(async () => { /* await updateOsuConfigHashes(osuPath); */
await replaceUIFile(osuPath, true); 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"); mainWindow.webContents.send("ezpplauncher:launchabort");
osuLoaded = false; return;
}, 5000); }
};
runOsuWithDevServer(osuPath, "ez-pp.farm", onExitHook);
mainWindow.hide();
startOsuStatus();
/* mainWindow.webContents.send("ezpplauncher:launchprogress", { const forceUpdateFiles = [
progress: 0, ".require_update",
}); "help.txt",
mainWindow.webContents.send("ezpplauncher:launchprogress", { "_pending",
progress: 100, ];
}); */
return true; 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");
}
}); });
} }
@ -685,6 +835,13 @@ function createWindow() {
richPresence.connect(); 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. // Uncomment the following line of code when app is ready to be packaged.
// loadURL(mainWindow); // loadURL(mainWindow);
@ -705,10 +862,6 @@ function createWindow() {
// Emitted when the window is ready to be shown // Emitted when the window is ready to be shown
// This helps in showing the window gracefully. // This helps in showing the window gracefully.
mainWindow.once("ready-to-show", async () => { mainWindow.once("ready-to-show", async () => {
const updateInfo = await updateAvailable();
if (updateInfo.update) {
mainWindow.webContents.send("ezpplauncher:update", updateInfo.release);
}
mainWindow.show(); mainWindow.show();
mainWindow.focus(); mainWindow.focus();
}); });

1115
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "ezpplauncher-next", "name": "ezpplauncher-next",
"version": "2.1.1", "version": "2.1.7",
"description": "EZPPLauncher rewritten with Svelte.", "description": "EZPPLauncher rewritten with Svelte.",
"private": false, "private": false,
"license": "MIT", "license": "MIT",
@ -51,7 +51,7 @@
"regedit-rs": "^1.0.2", "regedit-rs": "^1.0.2",
"semver": "^7.5.4", "semver": "^7.5.4",
"svelte-french-toast": "^1.2.0", "svelte-french-toast": "^1.2.0",
"sweetalert2": "^11.10.3", "sweetalert2": "^11.10.8",
"systeminformation": "^5.21.22" "systeminformation": "^5.21.22"
}, },
"devDependencies": { "devDependencies": {
@ -61,7 +61,6 @@
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5", "@rollup/plugin-typescript": "^11.1.5",
"@sveltejs/vite-plugin-svelte": "^3.0.1",
"@tsconfig/svelte": "^5.0.2", "@tsconfig/svelte": "^5.0.2",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",

View File

@ -82,6 +82,10 @@ window.addEventListener("settings-set", async (e) => {
await ipcRenderer.invoke("ezpplauncher:settings-set", e.detail); await ipcRenderer.invoke("ezpplauncher:settings-set", e.detail);
}); });
window.addEventListener("updateCheck", async () => {
await ipcRenderer.invoke("ezpplauncher:checkUpdate");
})
window.addEventListener("updateExit", async () => { window.addEventListener("updateExit", async () => {
await ipcRenderer.invoke("ezpplauncher:exitAndUpdate"); await ipcRenderer.invoke("ezpplauncher:exitAndUpdate");
}); });
@ -115,3 +119,9 @@ ipcRenderer.addListener("ezpplauncher:update", (e, args) => {
new CustomEvent("update", { detail: args }), new CustomEvent("update", { detail: args }),
); );
}); });
ipcRenderer.addListener("ezpplauncher:open-settings", (e, args) => {
window.dispatchEvent(
new CustomEvent("open-settings"),
);
});

View File

@ -69,6 +69,7 @@ export default {
resolve({ resolve({
browser: true, browser: true,
dedupe: ["svelte"], dedupe: ["svelte"],
exportConditions: ["svelte"],
}), }),
typescript({ typescript({
sourceMap: !production, sourceMap: !production,

View File

@ -1,20 +1,16 @@
<script lang="ts"> <script lang="ts">
import { import Avatar from "flowbite-svelte/Avatar.svelte";
Avatar, import Dropdown from "flowbite-svelte/Dropdown.svelte";
Dropdown, import DropdownItem from "flowbite-svelte/DropdownItem.svelte";
DropdownItem, import DropdownHeader from "flowbite-svelte/DropdownHeader.svelte";
DropdownHeader, import DropdownDivider from "flowbite-svelte/DropdownDivider.svelte";
DropdownDivider, import Button from "flowbite-svelte/Button.svelte";
Button, import Indicator from "flowbite-svelte/Indicator.svelte";
Indicator, import ArrowLeftSolid from "flowbite-svelte-icons/ArrowLeftSolid.svelte";
} from "flowbite-svelte"; import ArrowRightFromBracketSolid from "flowbite-svelte-icons/ArrowRightFromBracketSolid.svelte";
import { import ArrowRightToBracketSolid from "flowbite-svelte-icons/ArrowRightToBracketSolid.svelte";
ArrowLeftSolid, import HeartSolid from "flowbite-svelte-icons/HeartSolid.svelte";
ArrowRightFromBracketSolid, import UserSettingsSolid from "flowbite-svelte-icons/UserSettingsSolid.svelte";
ArrowRightToBracketSolid,
HeartSolid,
UserSettingsSolid,
} from "flowbite-svelte-icons";
import ezppLogo from "../public/favicon.png"; import ezppLogo from "../public/favicon.png";
import { import {
currentPage, currentPage,
@ -66,6 +62,12 @@
window.dispatchEvent(new CustomEvent("updateExit")); window.dispatchEvent(new CustomEvent("updateExit"));
}); });
window.dispatchEvent(new CustomEvent("updateCheck"));
window.addEventListener("open-settings", (e) => {
currentPage.set(Page.Settings);
});
window.addEventListener("launchStatusUpdate", (e) => { window.addEventListener("launchStatusUpdate", (e) => {
const status = (e as CustomEvent).detail.status; const status = (e as CustomEvent).detail.status;
launchStatus.set(status); launchStatus.set(status);

View File

@ -26,12 +26,12 @@
green: "bg-green-600 dark:bg-green-500", green: "bg-green-600 dark:bg-green-500",
yellow: "bg-yellow-400", yellow: "bg-yellow-400",
purple: "bg-purple-600 dark:bg-purple-500", purple: "bg-purple-600 dark:bg-purple-500",
indigo: "bg-indigo-600 dark:bg-indigo-500", indigo: "bg-indigo-600 dark:bg-indigo-500"
}; };
let _progress = tweened(0, { let _progress = tweened(0, {
duration: tweenDuration, duration: tweenDuration,
easing, easing
}); });
$: { $: {
@ -50,8 +50,12 @@
> >
<span class="text-sm font-medium text-blue-700 dark:text-white" <span class="text-sm font-medium text-blue-700 dark:text-white"
>{animate >{animate
? $_progress.toFixed(precision) ? isNaN($_progress)
: progress.toFixed(precision)}%</span ? parseInt("100").toFixed(precision)
: $_progress.toFixed(precision)
: isNaN(progress)
? parseInt("100").toFixed(precision)
: progress.toFixed(precision)}%</span
> >
</div> </div>
{/if} {/if}
@ -63,7 +67,13 @@
class={twJoin(labelInsideClass, barColors[color])} class={twJoin(labelInsideClass, barColors[color])}
style="width: {animate ? $_progress : progress}%" style="width: {animate ? $_progress : progress}%"
> >
{animate ? $_progress.toFixed(precision) : progress.toFixed(precision)}% {animate
? isNaN($_progress)
? parseInt("100").toFixed(precision)
: $_progress.toFixed(precision)
: isNaN(progress)
? parseInt("100").toFixed(precision)
: progress.toFixed(precision)}%
</div> </div>
{:else} {:else}
<div <div

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Button, Checkbox } from "flowbite-svelte"; import Button from "flowbite-svelte/Button.svelte";
import Progressbar from "../lib/Progressbar.svelte"; import Progressbar from "../lib/Progressbar.svelte";
import { import {
launching, launching,

View File

@ -1,11 +1,14 @@
<script lang="ts"> <script lang="ts">
import { Input, Button, Spinner, Checkbox } from "flowbite-svelte"; import Input from "flowbite-svelte/Input.svelte";
import type { User } from "../types/user"; import Button from "flowbite-svelte/Button.svelte";
import type { Error } from "../types/error"; 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 { currentPage, currentUser, startup } from "../storage/localStore";
import toast from "svelte-french-toast"; import toast from "svelte-french-toast";
import { Page } from "../consts/pages"; import { Page } from "../consts/pages";
import { EyeSlashSolid, EyeSolid } from "flowbite-svelte-icons"; import EyeSolid from "flowbite-svelte-icons/EyeSolid.svelte";
import EyeSlashSolid from "flowbite-svelte-icons/EyeSlashSolid.svelte";
let loading = false; let loading = false;
let username = ""; let username = "";
@ -14,11 +17,20 @@
let showPassword = false; let showPassword = false;
const processLogin = async () => { 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; loading = true;
const loginPromise = new Promise<void>((res, rej) => { const loginPromise = new Promise<string>((res, rej) => {
window.addEventListener( window.addEventListener(
"login-result", "login-result",
(e) => { async (e) => {
const customEvent = e as CustomEvent; const customEvent = e as CustomEvent;
const resultData = customEvent.detail; const resultData = customEvent.detail;
const wasSuccessful = "user" in resultData; const wasSuccessful = "user" in resultData;
@ -31,14 +43,14 @@
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100", "dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
duration: 1500, duration: 1500,
}); */ }); */
rej(); rej(resultData.message);
loading = false; loading = false;
return; return;
} }
const userResult = resultData.user as User; const userResult = resultData.user as User;
currentUser.set(userResult); currentUser.set(userResult);
currentPage.set(Page.Launch); currentPage.set(Page.Launch);
res(); res("");
toast.success(`Welcome back, ${userResult.name}!`, { toast.success(`Welcome back, ${userResult.name}!`, {
position: "bottom-center", position: "bottom-center",
className: className:
@ -59,20 +71,20 @@
{ {
loading: "Logging in...", loading: "Logging in...",
success: "Successfully logged in!", success: "Successfully logged in!",
error: "Invalid Username or Password!", error: (e) => e,
}, },
{ {
position: "bottom-center", position: "bottom-center",
className: className:
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100", "dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
duration: 3000, duration: 0,
} }
); );
}; };
const tryAutoLogin = async () => { const tryAutoLogin = async () => {
loading = true; loading = true;
const loginPromise = new Promise<void>((res, rej) => { const loginPromise = new Promise<string>((res, rej) => {
window.addEventListener( window.addEventListener(
"login-result", "login-result",
(e) => { (e) => {
@ -82,7 +94,7 @@
const wasSuccessful = "user" in resultData; const wasSuccessful = "user" in resultData;
if (isGuest) { if (isGuest) {
currentPage.set(Page.Launch); currentPage.set(Page.Launch);
res(); res("");
toast.success(`Logged in as Guest`, { toast.success(`Logged in as Guest`, {
position: "bottom-center", position: "bottom-center",
className: className:
@ -93,13 +105,13 @@
} }
if (!wasSuccessful) { if (!wasSuccessful) {
loading = false; loading = false;
rej(); rej(resultData.message);
return; return;
} }
const userResult = resultData.user as User; const userResult = resultData.user as User;
currentUser.set(userResult); currentUser.set(userResult);
currentPage.set(Page.Launch); currentPage.set(Page.Launch);
res(); res("");
toast.success(`Welcome back, ${userResult.name}!`, { toast.success(`Welcome back, ${userResult.name}!`, {
position: "bottom-center", position: "bottom-center",
className: className:
@ -117,13 +129,13 @@
{ {
loading: "Logging in...", loading: "Logging in...",
success: "Successfully logged in!", success: "Successfully logged in!",
error: "Failed to login.", error: (e) => e,
}, },
{ {
position: "bottom-center", position: "bottom-center",
className: className:
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100", "dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
duration: 3000, duration: 0,
} }
); );
}; };

View File

@ -1,7 +1,11 @@
<script lang="ts"> <script lang="ts">
import { Button, ButtonGroup, Input, Toggle } from "flowbite-svelte"; import Button from "flowbite-svelte/Button.svelte";
import { FileSearchSolid, FolderSolid } from "flowbite-svelte-icons"; import ButtonGroup from "flowbite-svelte/ButtonGroup.svelte";
import { patch, presence } from "./../storage/localStore"; 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 = ""; let folderPath: string = "";
@ -12,8 +16,10 @@
const settingPresence = settings.find( const settingPresence = settings.find(
(setting) => setting.key == "presence" (setting) => setting.key == "presence"
); );
const settingLogging = settings.find((setting) => setting.key == "logging");
patch.set(settingPatch ? settingPatch.val == "true" : true); patch.set(settingPatch ? settingPatch.val == "true" : true);
presence.set(settingPresence ? settingPresence.val == "true" : true); presence.set(settingPresence ? settingPresence.val == "true" : true);
logging.set(settingLogging ? settingLogging.val == "true" : false);
folderPath = osuPath ? osuPath.val : ""; folderPath = osuPath ? osuPath.val : "";
}); });
window.dispatchEvent(new CustomEvent("settings-get")); window.dispatchEvent(new CustomEvent("settings-get"));
@ -39,19 +45,18 @@
new CustomEvent("setting-update", { detail: { presence: $presence } }) new CustomEvent("setting-update", { detail: { presence: $presence } })
); );
}; };
const toggleLogging = () => {
logging.set(!$logging);
window.dispatchEvent(
new CustomEvent("setting-update", { detail: { logging: $logging } })
);
};
</script> </script>
<main <main
class="h-[265px] flex flex-col justify-start p-3 animate-fadeIn opacity-0" class="h-[265px] flex flex-col justify-start p-3 animate-fadeIn opacity-0"
> >
<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
>
</div>
<div <div
class="container flex flex-col items-center justify-center gap-5 rounded-lg p-3" class="container flex flex-col items-center justify-center gap-5 rounded-lg p-3"
> >
@ -77,4 +82,15 @@
</Button> </Button>
</ButtonGroup> </ButtonGroup>
</div> </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> </main>

View File

@ -7,8 +7,11 @@ export const updateAvailable = writable(false);
export const launching = writable(false); export const launching = writable(false);
export const launchStatus = writable("Waiting..."); export const launchStatus = writable("Waiting...");
export const launchPercentage = writable(-1); 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 osuPath: Writable<undefined | string> = writable(undefined);
export const patch = writable(true); export const patch = writable(true);
export const presence = writable(true); export const presence = writable(true);
export const currentUser: Writable<undefined | User> = writable(undefined); export const logging = writable(false);
export const currentPage = writable(Page.Login);

View File

@ -1,7 +1,7 @@
const { vitePreprocess } = require("@sveltejs/vite-plugin-svelte"); const preprocess = require("svelte-preprocess");
const config = { const config = {
preprocess: [vitePreprocess({})], preprocess: [preprocess()],
}; };
module.exports = config; module.exports = config;