add optional logging, cleanup fixes #15

Merged
HorizonCode merged 4 commits from dev into master 2024-04-15 11:12:11 +00:00
12 changed files with 498 additions and 277 deletions

View File

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

View File

@ -1,20 +1,20 @@
const childProcess = require("child_process");
const runFile = (folder, file, args, onExit) => {
childProcess.execFile(file, args, {
cwd: folder
}, (_err, _stdout, _stdin) => {
if (onExit) onExit();
})
}
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();
}
const subProcess = childProcess.spawn(file + (args ? " " + args : ""), {
cwd: folder,
detached: true,
stdio: "ignore",
});
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) {
if (!+bytes) return '0 Bytes'
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 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))
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 };

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;

View File

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

View File

@ -18,8 +18,8 @@ const gamemodes = {
4: "osu!(rx)",
5: "taiko(rx)",
6: "catch(rx)",
8: "osu!(ap)"
}
8: "osu!(ap)",
};
const osuEntities = [
"avcodec-51.dll",
"avformat-52.dll",
@ -44,7 +44,7 @@ const osuEntities = [
"scores.db",
];
const ezppLauncherUpdateList = "https://ez-pp.farm/ezpplauncher"
const ezppLauncherUpdateList = "https://ez-pp.farm/ezpplauncher";
async function isValidOsuFolder(path) {
const allFiles = await fs.promises.readdir(path);
@ -184,31 +184,24 @@ function downloadUpdateFiles(osuPath, updateFiles) {
const startDownload = async () => {
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 {
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,
@ -222,6 +215,7 @@ function downloadUpdateFiles(osuPath, updateFiles) {
console.log(err);
eventEmitter.emit("error", {
fileName,
error: err,
});
}
}
@ -248,10 +242,16 @@ function runOsuWithDevServer(osuPath, serverDomain, onExit) {
async function getEZPPLauncherUpdateFiles(osuPath) {
const filesToDownload = [];
const updateFilesRequest = await fetch(ezppLauncherUpdateList, { method: "PATCH" });
const updateFilesRequest = await fetch(ezppLauncherUpdateList, {
method: "PATCH",
});
const updateFiles = await updateFilesRequest.json();
for (const updateFile of updateFiles) {
const filePath = path.join(osuPath, ...updateFile.folder.split("/"), updateFile.name);
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(
@ -272,22 +272,30 @@ async function downloadEZPPLauncherUpdateFiles(osuPath, updateFiles) {
const startDownload = async () => {
for (const updateFile of updateFiles) {
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 { loaded, total } = progressEvent;
eventEmitter.emit("data", {
fileName: path.basename(filePath),
loaded,
total,
progress: Math.floor((loaded / total) * 100),
});
},
});
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,
@ -301,10 +309,11 @@ async function downloadEZPPLauncherUpdateFiles(osuPath, updateFiles) {
console.log(err);
eventEmitter.emit("error", {
fileName: path.basename(filePath),
error: err,
});
}
}
}
};
return {
eventEmitter,
@ -316,7 +325,11 @@ 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 ezppGameplayFile = path.join(
osuPath,
"EZPPLauncher",
"ezpp!gameplay.dll",
);
const oldOsuGameplayFile = path.join(osuPath, "osu!gameplay.dll");
await fs.promises.rename(
@ -334,7 +347,11 @@ async function replaceUIFiles(osuPath, revert) {
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");
const ezppGameplayFile = path.join(
osuPath,
"EZPPLauncher",
"ezpp!gameplay.dll",
);
await fs.promises.rename(oldOsuUIFile, ezppUIFile);
await fs.promises.rename(
@ -413,5 +430,5 @@ module.exports = {
runOsuUpdater,
getEZPPLauncherUpdateFiles,
downloadEZPPLauncherUpdateFiles,
gamemodes
gamemodes,
};

View File

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

504
main.js
View File

@ -20,7 +20,6 @@ const {
runOsuWithDevServer,
replaceUIFiles,
findOsuInstallation,
updateOsuConfigHashes,
runOsuUpdater,
gamemodes,
getEZPPLauncherUpdateFiles,
@ -38,6 +37,8 @@ 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.
@ -46,6 +47,13 @@ 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;
@ -66,10 +74,18 @@ function startOsuStatus() {
osuLoaded = true;
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`,
);
const currentUserInfoJson = await currentUserInfo.json();
if ("player" in currentUserInfoJson && currentUserInfoJson.player != null) {
if ("info" in currentUserInfoJson.player && currentUserInfoJson.player.info != null) {
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({
@ -80,7 +96,6 @@ function startOsuStatus() {
}
}
} catch {
}
setTimeout(() => {
@ -112,7 +127,8 @@ function startOsuStatus() {
const currentModeString = gamemodes[currentMode];
const currentInfoRequest = await fetch(
"https://api.ez-pp.farm/get_player_info?name=" + currentUser.username + "&scope=all",
"https://api.ez-pp.farm/get_player_info?name=" + currentUser.username +
"&scope=all",
);
const currentInfo = await currentInfoRequest.json();
let currentUsername = currentInfo.player.info.name;
@ -192,7 +208,7 @@ function startOsuStatus() {
richPresence.updateUser({
username: currentUsername,
id: currentId,
})
});
richPresence.updateStatus({
details,
@ -211,9 +227,19 @@ function stopOsuStatus() {
function registerIPCPipes() {
ipcMain.handle("ezpplauncher:login", async (e, args) => {
const hwid = getHwId();
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(), 8000);
logger.log(`Logging in with user ${args.username}...`);
try {
const fetchResult = await fetch("https://ez-pp.farm/login/check", {
signal: timeout.signal,
@ -238,14 +264,20 @@ function registerIPCPipes() {
}
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 ${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.",
@ -275,6 +307,7 @@ function registerIPCPipes() {
}
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,
@ -297,16 +330,23 @@ function registerIPCPipes() {
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.",
@ -319,6 +359,7 @@ function registerIPCPipes() {
config.remove("password");
config.set("guest", "1");
currentUser = undefined;
logger.log("Logged in as guest user.");
});
ipcMain.handle("ezpplauncher:logout", (e) => {
@ -326,6 +367,7 @@ function registerIPCPipes() {
config.remove("password");
config.remove("guest");
currentUser = undefined;
logger.log("Loging out.");
return true;
});
@ -342,6 +384,10 @@ function registerIPCPipes() {
else richPresence.connect();
}
if (key == "logging") {
logger.enabled = logging;
}
if (typeof value == "boolean") {
config.set(key, value ? "true" : "false");
} else {
@ -391,100 +437,78 @@ function registerIPCPipes() {
});
ipcMain.handle("ezpplauncher:launch", async (e) => {
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");
return;
}
if (!(await isValidOsuFolder(osuPath))) {
mainWindow.webContents.send("ezpplauncher:launchabort");
mainWindow.webContents.send("ezpplauncher:alert", {
type: "error",
message: "invalid osu! path!",
});
return;
}
if (patch) {
if (!(await isNet8Installed())) {
mainWindow.webContents.send("ezpplauncher:launchabort");
try {
const osuWindowTitle = windowName.getWindowText("osu!.exe");
if (osuWindowTitle.length > 0) {
mainWindow.webContents.send("ezpplauncher:alert", {
type: "error",
message: ".NET 8 is not installed.",
message: "osu! is running, please exit.",
});
//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');
}
}
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) {
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", {
status: "Looking for patcher updates...",
status: "Checking osu! directory...",
});
await new Promise((res) => setTimeout(res, 1000));
const patchFiles = await getEZPPLauncherUpdateFiles(osuPath);
if (patchFiles.length > 0) {
const patcherDownloader = await downloadEZPPLauncherUpdateFiles(osuPath, patchFiles);
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;
patcherDownloader.eventEmitter.on("error", (data) => {
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",
@ -492,16 +516,20 @@ function registerIPCPipes() {
`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", {
progress: Math.ceil(data.progress),
});
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", {
progress: -1,
});
@ -510,119 +538,206 @@ function registerIPCPipes() {
return;
}
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 {
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) => {
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);
if (patch) {
mainWindow.webContents.send("ezpplauncher:launchstatus", {
status: "Looking for patcher updates...",
});
});
}
await new Promise((res) => setTimeout(res, 1000));
mainWindow.webContents.send("ezpplauncher:launchstatus", {
status: "Preparing launch...",
});
await updateOsuConfigHashes(osuPath);
await replaceUIFiles(osuPath, false);
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,
await new Promise((res) => setTimeout(res, 1000));
const patchFiles = await getEZPPLauncherUpdateFiles(osuPath);
if (patchFiles.length > 0) {
logger.log("EZPPLauncher updates found.");
const patcherDownloader = await downloadEZPPLauncherUpdateFiles(
osuPath,
patchFiles,
);
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)})...`,
});
});
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);
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);
}
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);
});
});
}
mainWindow.webContents.send("ezpplauncher:launchstatus", {
status: "Launching osu!...",
});
await new Promise((res) => setTimeout(res, 1000));
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", {
status: "Waiting for cleanup...",
status: "Preparing launch...",
});
setTimeout(async () => {
await replaceUIFiles(osuPath, true);
/* 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");
osuLoaded = false;
}, 5000);
};
runOsuWithDevServer(osuPath, "ez-pp.farm", onExitHook);
mainWindow.hide();
startOsuStatus();
return;
}
/* mainWindow.webContents.send("ezpplauncher:launchprogress", {
progress: 0,
});
mainWindow.webContents.send("ezpplauncher:launchprogress", {
progress: 100,
}); */
return true;
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");
}
});
}
@ -678,6 +793,13 @@ function createWindow() {
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);

View File

@ -1,6 +1,6 @@
{
"name": "ezpplauncher-next",
"version": "2.1.4",
"version": "2.1.5",
"description": "EZPPLauncher rewritten with Svelte.",
"private": false,
"license": "MIT",

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { Button, ButtonGroup, Input, Toggle } from "flowbite-svelte";
import { FileSearchSolid, FolderSolid } from "flowbite-svelte-icons";
import { patch, presence } from "./../storage/localStore";
import { patch, presence, logging } from "./../storage/localStore";
let folderPath: string = "";
@ -12,8 +12,10 @@
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"));
@ -39,19 +41,18 @@
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="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
class="container flex flex-col items-center justify-center gap-5 rounded-lg p-3"
>
@ -77,4 +78,15 @@
</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>

View File

@ -7,8 +7,11 @@ 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 currentUser: Writable<undefined | User> = writable(undefined);
export const currentPage = writable(Page.Login);
export const logging = writable(false);