diff --git a/electron/executeUtil.js b/electron/executeUtil.js index e563e81..9d25e5e 100644 --- a/electron/executeUtil.js +++ b/electron/executeUtil.js @@ -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 }; \ No newline at end of file +module.exports = { runFile, runFileDetached }; diff --git a/electron/fileUtil.js b/electron/fileUtil.js new file mode 100644 index 0000000..66ff252 --- /dev/null +++ b/electron/fileUtil.js @@ -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, +}; diff --git a/electron/formattingUtil.js b/electron/formattingUtil.js index b60a9d0..78e5b6e 100644 --- a/electron/formattingUtil.js +++ b/electron/formattingUtil.js @@ -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 }; \ No newline at end of file +module.exports = { formatBytes }; diff --git a/electron/logging.js b/electron/logging.js new file mode 100644 index 0000000..f3523c6 --- /dev/null +++ b/electron/logging.js @@ -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; diff --git a/electron/osuUtil.js b/electron/osuUtil.js index 10d946f..fe334b8 100644 --- a/electron/osuUtil.js +++ b/electron/osuUtil.js @@ -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, }; diff --git a/electron/richPresence.js b/electron/richPresence.js index 5f55283..7f26e2a 100644 --- a/electron/richPresence.js +++ b/electron/richPresence.js @@ -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); diff --git a/main.js b/main.js index 1a7493c..3446ec1 100644 --- a/main.js +++ b/main.js @@ -38,6 +38,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 +48,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 +75,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 +97,6 @@ function startOsuStatus() { } } } catch { - } setTimeout(() => { @@ -112,7 +128,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 +209,7 @@ function startOsuStatus() { richPresence.updateUser({ username: currentUsername, id: currentId, - }) + }); richPresence.updateStatus({ details, @@ -211,9 +228,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 +265,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 +308,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 +331,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 +360,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 +368,7 @@ function registerIPCPipes() { config.remove("password"); config.remove("guest"); currentUser = undefined; + logger.log("Loging out."); return true; }); @@ -342,6 +385,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 +438,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 +517,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 +539,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 +794,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); diff --git a/src/pages/Settings.svelte b/src/pages/Settings.svelte index 2acc72a..71e4aff 100644 --- a/src/pages/Settings.svelte +++ b/src/pages/Settings.svelte @@ -1,7 +1,7 @@
-
- Discord Presence - Patching -
@@ -77,4 +78,15 @@
+
+ Discord Presence + Patching + Debug Logging +
diff --git a/src/storage/localStore.ts b/src/storage/localStore.ts index d6f32cf..f115314 100644 --- a/src/storage/localStore.ts +++ b/src/storage/localStore.ts @@ -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 = writable(undefined); +export const currentPage = writable(Page.Login); + export const osuPath: Writable = writable(undefined); export const patch = writable(true); export const presence = writable(true); -export const currentUser: Writable = writable(undefined); -export const currentPage = writable(Page.Login); +export const logging = writable(false);