added launching, patching, and ui override
This commit is contained in:
		
							
								
								
									
										166
									
								
								main.js
									
									
									
									
									
								
							
							
						
						
									
										166
									
								
								main.js
									
									
									
									
									
								
							@@ -7,16 +7,51 @@ const config = require("./src/config/config");
 | 
			
		||||
const { setupTitlebar, attachTitlebarToWindow } = require(
 | 
			
		||||
  "custom-electron-titlebar/main",
 | 
			
		||||
);
 | 
			
		||||
const { isValidOsuFolder } = require("./src/util/osuUtil");
 | 
			
		||||
const { isValidOsuFolder, getUpdateFiles, getGlobalConfig, getFilesThatNeedUpdate, downloadUpdateFiles, getUserConfig, runOsuWithDevServer, getPatcherUpdates, downloadPatcherUpdates, getUIFiles, downloadUIFiles, replaceUIFile } = require("./src/util/osuUtil");
 | 
			
		||||
const { formatBytes } = require("./src/util/formattingUtil");
 | 
			
		||||
const windowName = require("get-window-by-name");
 | 
			
		||||
const { existsSync } = require("fs");
 | 
			
		||||
const { runFileDetached } = require("./src/util/executeUtil");
 | 
			
		||||
 | 
			
		||||
// Keep a global reference of the window object, if you don't, the window will
 | 
			
		||||
// be closed automatically when the JavaScript object is garbage collected.
 | 
			
		||||
let mainWindow;
 | 
			
		||||
let osuCheckInterval;
 | 
			
		||||
let userOsuPath;
 | 
			
		||||
let osuLoaded = false;
 | 
			
		||||
 | 
			
		||||
let currentUser = undefined;
 | 
			
		||||
 | 
			
		||||
function isDev() {
 | 
			
		||||
  return !app.isPackaged;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function startOsuStatus() {
 | 
			
		||||
  osuCheckInterval = setInterval(async () => {
 | 
			
		||||
    const osuWindowTitle = windowName.getWindowText("osu!.exe");
 | 
			
		||||
    if (osuWindowTitle.length < 0) {
 | 
			
		||||
      console.log("No osu! window found");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const firstInstance = osuWindowTitle[0];
 | 
			
		||||
    if (firstInstance) {
 | 
			
		||||
      if (!osuLoaded) {
 | 
			
		||||
        osuLoaded = true;
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          const patcherExecuteable = path.join(userOsuPath, "EZPPLauncher", "patcher.exe");
 | 
			
		||||
          if (existsSync(patcherExecuteable)) {
 | 
			
		||||
            runFileDetached(userOsuPath, patcherExecuteable);
 | 
			
		||||
          }
 | 
			
		||||
        }, 3000);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, 1000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function stopOsuStatus() {
 | 
			
		||||
  clearInterval(osuCheckInterval);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function registerIPCPipes() {
 | 
			
		||||
  ipcMain.handle("ezpplauncher:login", async (e, args) => {
 | 
			
		||||
    const timeout = new AbortController();
 | 
			
		||||
@@ -43,6 +78,7 @@ function registerIPCPipes() {
 | 
			
		||||
            config.set("username", args.username);
 | 
			
		||||
            config.set("password", args.password);
 | 
			
		||||
          }
 | 
			
		||||
          currentUser = args;
 | 
			
		||||
          config.remove("guest");
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
@@ -86,6 +122,12 @@ function registerIPCPipes() {
 | 
			
		||||
 | 
			
		||||
      if (fetchResult.ok) {
 | 
			
		||||
        const result = await fetchResult.json();
 | 
			
		||||
        if ("user" in result) {
 | 
			
		||||
          currentUser = {
 | 
			
		||||
            username: username,
 | 
			
		||||
            password: password,
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
      }
 | 
			
		||||
      return {
 | 
			
		||||
@@ -104,12 +146,14 @@ function registerIPCPipes() {
 | 
			
		||||
    config.remove("username");
 | 
			
		||||
    config.remove("password");
 | 
			
		||||
    config.set("guest", "1");
 | 
			
		||||
    currentUser = undefined;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  ipcMain.handle("ezpplauncher:logout", (e) => {
 | 
			
		||||
    config.remove("username");
 | 
			
		||||
    config.remove("password");
 | 
			
		||||
    config.remove("guest");
 | 
			
		||||
    currentUser = undefined
 | 
			
		||||
    return true;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@@ -140,12 +184,22 @@ function registerIPCPipes() {
 | 
			
		||||
    return config.all();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  ipcMain.handle("ezpplauncher:launch", async (e) => {
 | 
			
		||||
  ipcMain.handle("ezpplauncher:launch", async (e, args) => {
 | 
			
		||||
    const patch = args.patch;
 | 
			
		||||
    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!",
 | 
			
		||||
      });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (!(await isValidOsuFolder(osuPath))) {
 | 
			
		||||
      mainWindow.webContents.send("ezpplauncher:launchabort");
 | 
			
		||||
      mainWindow.webContents.send("ezpplauncher:alert", {
 | 
			
		||||
@@ -158,6 +212,114 @@ function registerIPCPipes() {
 | 
			
		||||
      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);
 | 
			
		||||
      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 (updateFiles.length > 0) {
 | 
			
		||||
      const updateDownloader = downloadUpdateFiles(osuPath, updateFiles);
 | 
			
		||||
      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,
 | 
			
		||||
      });
 | 
			
		||||
      mainWindow.webContents.send("ezpplauncher:launchstatus", {
 | 
			
		||||
        status: "osu! is now up to date!",
 | 
			
		||||
      });
 | 
			
		||||
      await new Promise((res) => setTimeout(res, 1000));
 | 
			
		||||
    } else {
 | 
			
		||||
      mainWindow.webContents.send("ezpplauncher:launchstatus", {
 | 
			
		||||
        status: "osu! is up to date!",
 | 
			
		||||
      });
 | 
			
		||||
      await new Promise((res) => setTimeout(res, 1000));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (patch) {
 | 
			
		||||
      mainWindow.webContents.send("ezpplauncher:launchstatus", {
 | 
			
		||||
        status: "Looking for patcher updates...",
 | 
			
		||||
      });
 | 
			
		||||
      await new Promise((res) => setTimeout(res, 1000));
 | 
			
		||||
      const patchFiles = await getPatcherUpdates(osuPath);
 | 
			
		||||
      if (patchFiles.length > 0) {
 | 
			
		||||
        const patcherDownloader = downloadPatcherUpdates(osuPath, patchFiles);
 | 
			
		||||
        patcherDownloader.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 patcherDownloader.startDownload();
 | 
			
		||||
        mainWindow.webContents.send("ezpplauncher:launchprogress", {
 | 
			
		||||
          progress: -1,
 | 
			
		||||
        })
 | 
			
		||||
        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));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mainWindow.webContents.send("ezpplauncher:launchstatus", {
 | 
			
		||||
      status: "Preparing launch...",
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    //TODO: save credentials to osu!.%username%.cfg
 | 
			
		||||
    if (currentUser) {
 | 
			
		||||
      const userConfig = getUserConfig(osuPath);
 | 
			
		||||
      await userConfig.set("Username", currentUser.username);
 | 
			
		||||
      await userConfig.set("Password", currentUser.password);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mainWindow.webContents.send("ezpplauncher:launchstatus", {
 | 
			
		||||
      status: "Launching osu!...",
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const onExitHook = () => {
 | 
			
		||||
      mainWindow.show();
 | 
			
		||||
      stopOsuStatus();
 | 
			
		||||
      mainWindow.webContents.send("ezpplauncher:launchstatus", {
 | 
			
		||||
        status: "Waiting for cleanup...",
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      setTimeout(async () => {
 | 
			
		||||
        await replaceUIFile(osuPath, true);
 | 
			
		||||
        mainWindow.webContents.send("ezpplauncher:launchabort");
 | 
			
		||||
      }, 5000);
 | 
			
		||||
    }
 | 
			
		||||
    await replaceUIFile(osuPath, false);
 | 
			
		||||
    runOsuWithDevServer(osuPath, "ez-pp.farm", onExitHook);
 | 
			
		||||
    mainWindow.hide();
 | 
			
		||||
    startOsuStatus();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /* mainWindow.webContents.send("ezpplauncher:launchprogress", {
 | 
			
		||||
      progress: 0,
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										93
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										93
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -12,8 +12,10 @@
 | 
			
		||||
        "@types/better-sqlite3": "^7.6.8",
 | 
			
		||||
        "axios": "^1.6.5",
 | 
			
		||||
        "better-sqlite3": "^9.2.2",
 | 
			
		||||
        "crypto": "^1.0.1",
 | 
			
		||||
        "custom-electron-titlebar": "^4.2.7",
 | 
			
		||||
        "electron-serve": "^1.1.0",
 | 
			
		||||
        "get-window-by-name": "^2.0.0",
 | 
			
		||||
        "svelte-french-toast": "^1.2.0"
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
@@ -3049,6 +3051,12 @@
 | 
			
		||||
        "node": ">= 8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/crypto": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
 | 
			
		||||
      "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in."
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/css-declaration-sorter": {
 | 
			
		||||
      "version": "6.4.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz",
 | 
			
		||||
@@ -4291,6 +4299,78 @@
 | 
			
		||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/get-window-by-name": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/get-window-by-name/-/get-window-by-name-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-VXszlUFwkmWAZzxEERRJisiVvGMeB+Zjl5I9f0mwJjhfLTOkD5n5OR9Z518XBZemKLRzIs91TlfKbPmZywS84Q==",
 | 
			
		||||
      "hasInstallScript": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "bindings": "^1.2.1",
 | 
			
		||||
        "cross-spawn": "^6.0.5",
 | 
			
		||||
        "nan": "^2.0.5"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/get-window-by-name/node_modules/cross-spawn": {
 | 
			
		||||
      "version": "6.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
 | 
			
		||||
      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "nice-try": "^1.0.4",
 | 
			
		||||
        "path-key": "^2.0.1",
 | 
			
		||||
        "semver": "^5.5.0",
 | 
			
		||||
        "shebang-command": "^1.2.0",
 | 
			
		||||
        "which": "^1.2.9"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=4.8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/get-window-by-name/node_modules/path-key": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/get-window-by-name/node_modules/semver": {
 | 
			
		||||
      "version": "5.7.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
 | 
			
		||||
      "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "semver": "bin/semver"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/get-window-by-name/node_modules/shebang-command": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "shebang-regex": "^1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/get-window-by-name/node_modules/shebang-regex": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/get-window-by-name/node_modules/which": {
 | 
			
		||||
      "version": "1.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
 | 
			
		||||
      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "isexe": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "which": "bin/which"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/github-from-package": {
 | 
			
		||||
      "version": "0.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
 | 
			
		||||
@@ -4905,8 +4985,7 @@
 | 
			
		||||
    "node_modules/isexe": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/jackspeak": {
 | 
			
		||||
      "version": "2.3.6",
 | 
			
		||||
@@ -5553,6 +5632,11 @@
 | 
			
		||||
        "thenify-all": "^1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/nan": {
 | 
			
		||||
      "version": "2.18.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz",
 | 
			
		||||
      "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/nanoid": {
 | 
			
		||||
      "version": "3.3.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
 | 
			
		||||
@@ -5585,6 +5669,11 @@
 | 
			
		||||
        "node": ">= 0.6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/nice-try": {
 | 
			
		||||
      "version": "1.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
 | 
			
		||||
      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/node-abi": {
 | 
			
		||||
      "version": "3.54.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -37,8 +37,10 @@
 | 
			
		||||
    "@types/better-sqlite3": "^7.6.8",
 | 
			
		||||
    "axios": "^1.6.5",
 | 
			
		||||
    "better-sqlite3": "^9.2.2",
 | 
			
		||||
    "crypto": "^1.0.1",
 | 
			
		||||
    "custom-electron-titlebar": "^4.2.7",
 | 
			
		||||
    "electron-serve": "^1.1.0",
 | 
			
		||||
    "get-window-by-name": "^2.0.0",
 | 
			
		||||
    "svelte-french-toast": "^1.2.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
 
 | 
			
		||||
@@ -37,8 +37,8 @@ window.addEventListener("guest-login", async () => {
 | 
			
		||||
  await ipcRenderer.invoke("ezpplauncher:guestlogin");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
window.addEventListener("launch", async () => {
 | 
			
		||||
  await ipcRenderer.invoke("ezpplauncher:launch");
 | 
			
		||||
window.addEventListener("launch", async (e) => {
 | 
			
		||||
  await ipcRenderer.invoke("ezpplauncher:launch", e.detail);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
window.addEventListener("settings-get", async () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,7 @@
 | 
			
		||||
    launchPercentage.set(progress);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  window.addEventListener("launchabort", () => {
 | 
			
		||||
  window.addEventListener("launch-abort", () => {
 | 
			
		||||
    launchPercentage.set(-1);
 | 
			
		||||
    launchStatus.set("");
 | 
			
		||||
    launching.set(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
 | 
			
		||||
  const launch = () => {
 | 
			
		||||
    launching.set(true);
 | 
			
		||||
    window.dispatchEvent(new CustomEvent("launch"));
 | 
			
		||||
    window.dispatchEvent(new CustomEvent("launch", { detail: { patch: $patch } }));;
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -42,11 +42,10 @@
 | 
			
		||||
      >
 | 
			
		||||
    </ButtonGroup>
 | 
			
		||||
    <div class="flex flex-row justify-center items-center gap-5">
 | 
			
		||||
      <Button color="light" class="dark:active:!bg-gray-900">Save</Button>
 | 
			
		||||
      <Button
 | 
			
		||||
        color="red"
 | 
			
		||||
        class="dark:active:!bg-red-900 border-red-400"
 | 
			
		||||
        on:click={() => currentPage.set(Page.Launch)}>Cancel</Button
 | 
			
		||||
        color="light"
 | 
			
		||||
        class="dark:active:!bg-gray-900"
 | 
			
		||||
        on:click={() => currentPage.set(Page.Launch)}>Go Back</Button
 | 
			
		||||
      >
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								src/util/executeUtil.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/util/executeUtil.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
const childProcess = require("child_process");
 | 
			
		||||
 | 
			
		||||
const runFile = (folder, file, args, onExit) => {
 | 
			
		||||
    childProcess.execFile(file, args, {
 | 
			
		||||
        cwd: folder
 | 
			
		||||
    }, (_err, _stdout, _stdin) => {
 | 
			
		||||
        if (onExit) onExit();
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const runFileDetached = (folder, file, args) => {
 | 
			
		||||
    const subProcess = childProcess.spawn(file + (args ? " " + args : ''), {
 | 
			
		||||
        cwd: folder,
 | 
			
		||||
        detached: true,
 | 
			
		||||
        stdio: 'ignore'
 | 
			
		||||
    });
 | 
			
		||||
    subProcess.unref();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = { runFile, runFileDetached };
 | 
			
		||||
							
								
								
									
										13
									
								
								src/util/formattingUtil.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/util/formattingUtil.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
function formatBytes(bytes, decimals = 2) {
 | 
			
		||||
    if (!+bytes) return '0 Bytes'
 | 
			
		||||
 | 
			
		||||
    const k = 1024
 | 
			
		||||
    const dm = decimals < 0 ? 0 : decimals
 | 
			
		||||
    const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
 | 
			
		||||
 | 
			
		||||
    const i = Math.floor(Math.log(bytes) / Math.log(k))
 | 
			
		||||
 | 
			
		||||
    return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))}${sizes[i]}`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = { formatBytes };
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
export const clamp = (val: number, min: number, max: number) => {
 | 
			
		||||
  return Math.max(min, Math.min(val, max));
 | 
			
		||||
};
 | 
			
		||||
};
 | 
			
		||||
@@ -1,5 +1,9 @@
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
const path = require("path");
 | 
			
		||||
const crypto = require("crypto");
 | 
			
		||||
const EventEmitter = require("events");
 | 
			
		||||
const { default: axios } = require("axios");
 | 
			
		||||
const { runFile } = require("./executeUtil");
 | 
			
		||||
 | 
			
		||||
const checkUpdateURL =
 | 
			
		||||
  "https://osu.ppy.sh/web/check-updates.php?action=check&stream=";
 | 
			
		||||
@@ -30,6 +34,27 @@ const osuEntities = [
 | 
			
		||||
  "scores.db",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const patcherFiles = [
 | 
			
		||||
  {
 | 
			
		||||
    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) {
 | 
			
		||||
  const allFiles = await fs.promises.readdir(path);
 | 
			
		||||
  let matches = 0;
 | 
			
		||||
@@ -39,10 +64,71 @@ async function isValidOsuFolder(path) {
 | 
			
		||||
  return (Math.round((matches / osuEntities.length) * 100) >= 60);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getUserConfig(osuPath) {
 | 
			
		||||
function getGlobalConfig(osuPath) {
 | 
			
		||||
  const configFileInfo = {
 | 
			
		||||
    name: "",
 | 
			
		||||
    path: "",
 | 
			
		||||
    get: async (key) => {
 | 
			
		||||
      if (!configFileInfo.path) {
 | 
			
		||||
        console.log("config file not loaded");
 | 
			
		||||
        return "";
 | 
			
		||||
      }
 | 
			
		||||
      const fileStream = await fs.promises.readFile(
 | 
			
		||||
        configFileInfo.path,
 | 
			
		||||
        "utf-8",
 | 
			
		||||
      );
 | 
			
		||||
      const lines = fileStream.split(/\r?\n/);
 | 
			
		||||
      for (const line of lines) {
 | 
			
		||||
        if (line.includes(" = ")) {
 | 
			
		||||
          const argsPair = line.split(" = ", 2);
 | 
			
		||||
          const keyname = argsPair[0];
 | 
			
		||||
          const value = argsPair[1];
 | 
			
		||||
          if (keyname == key) {
 | 
			
		||||
            return value;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
  const globalOsuConfig = path.join(osuPath, "osu!.cfg");
 | 
			
		||||
  if (fs.existsSync(globalOsuConfig)) {
 | 
			
		||||
    configFileInfo.name = "osu!.cfg";
 | 
			
		||||
    configFileInfo.path = globalOsuConfig;
 | 
			
		||||
  }
 | 
			
		||||
  return configFileInfo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getUserConfig(osuPath) {
 | 
			
		||||
  const configFileInfo = {
 | 
			
		||||
    name: "",
 | 
			
		||||
    path: "",
 | 
			
		||||
    set: async (key, newValue) => {
 | 
			
		||||
      if (!configFileInfo.path) {
 | 
			
		||||
        return "";
 | 
			
		||||
      }
 | 
			
		||||
      const fileContents = [];
 | 
			
		||||
      const fileStream = await fs.promises.readFile(
 | 
			
		||||
        configFileInfo.path,
 | 
			
		||||
        "utf-8",
 | 
			
		||||
      );
 | 
			
		||||
      const lines = fileStream.split(/\r?\n/);
 | 
			
		||||
      for (const line of lines) {
 | 
			
		||||
        if (line.includes(" = ")) {
 | 
			
		||||
          const argsPair = line.split(" = ", 2);
 | 
			
		||||
          const keyname = argsPair[0];
 | 
			
		||||
          if (keyname == key) {
 | 
			
		||||
            fileContents.push(`${key} = ${newValue}`);
 | 
			
		||||
          } else {
 | 
			
		||||
            fileContents.push(line);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          fileContents.push(line);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      await fs.promises.writeFile(configFileInfo.path, fileContents.join("\n"));
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    },
 | 
			
		||||
    get: async (key) => {
 | 
			
		||||
      if (!configFileInfo.path) {
 | 
			
		||||
        return "";
 | 
			
		||||
@@ -80,4 +166,194 @@ async function getUpdateFiles(releaseStream) {
 | 
			
		||||
  return releaseData.ok ? await releaseData.json() : undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = { isValidOsuFolder, getUserConfig, getUpdateFiles };
 | 
			
		||||
async function getFilesThatNeedUpdate(osuPath, releaseStreamFiles) {
 | 
			
		||||
  const updateFiles = [];
 | 
			
		||||
  for (const updatePatch of releaseStreamFiles) {
 | 
			
		||||
    const fileName = updatePatch.filename;
 | 
			
		||||
    const fileHash = updatePatch.file_hash;
 | 
			
		||||
 | 
			
		||||
    const fileOnDisk = path.join(osuPath, fileName);
 | 
			
		||||
    if (fs.existsSync(fileOnDisk)) {
 | 
			
		||||
      if (ignoredOsuEntities.includes(fileName)) continue;
 | 
			
		||||
      const fileHashOnDisk = crypto.createHash("md5").update(fs.readFileSync(fileOnDisk)).digest("hex");
 | 
			
		||||
      if (fileHashOnDisk.trim().toLowerCase() != fileHash.trim().toLowerCase()) {
 | 
			
		||||
        console.log({
 | 
			
		||||
          fileOnDisk,
 | 
			
		||||
          fileHashOnDisk,
 | 
			
		||||
          fileHash
 | 
			
		||||
        })
 | 
			
		||||
        updateFiles.push(updatePatch);
 | 
			
		||||
      }
 | 
			
		||||
    } else updateFiles.push(updatePatch);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return updateFiles;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function downloadUpdateFiles(osuPath, updateFiles) {
 | 
			
		||||
  const eventEmitter = new EventEmitter();
 | 
			
		||||
 | 
			
		||||
  const startDownload = async () => {
 | 
			
		||||
    for (const updatePatch of updateFiles) {
 | 
			
		||||
      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
 | 
			
		||||
        });
 | 
			
		||||
      })
 | 
			
		||||
      await fs.promises.writeFile(path.join(osuPath, fileName), axiosDownloadWithProgress.data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // wait until all files are downloaded
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    eventEmitter,
 | 
			
		||||
    startDownload,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function runOsuWithDevServer(osuPath, serverDomain, onExit) {
 | 
			
		||||
  const osuExecuteable = path.join(osuPath, "osu!.exe");
 | 
			
		||||
  runFile(osuPath, osuExecuteable, ["-devserver", serverDomain], onExit);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getPatcherUpdates(osuPath) {
 | 
			
		||||
 | 
			
		||||
  const filesToDownload = [];
 | 
			
		||||
 | 
			
		||||
  const patcherDir = path.join(osuPath, "EZPPLauncher");
 | 
			
		||||
  if (!fs.existsSync(patcherDir)) fs.mkdirSync(patcherDir);
 | 
			
		||||
 | 
			
		||||
  for (const patcherFile of patcherFiles) {
 | 
			
		||||
    if (fs.existsSync(path.join(patcherDir, patcherFile.name))) {
 | 
			
		||||
      const latestPatchFileHash = await (await fetch(patcherFile.url_hash)).text();
 | 
			
		||||
      const localPatchFileHash = crypto.createHash("md5").update(fs.readFileSync(path.join(patcherDir, patcherFile.name))).digest("hex");
 | 
			
		||||
      if (latestPatchFileHash.trim().toLowerCase() != localPatchFileHash.trim().toLowerCase()) filesToDownload.push(patcherFile);
 | 
			
		||||
    } else filesToDownload.push(patcherFile);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return filesToDownload;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function downloadPatcherUpdates(osuPath, patcherUpdates) {
 | 
			
		||||
  const eventEmitter = new EventEmitter();
 | 
			
		||||
 | 
			
		||||
  const startDownload = async () => {
 | 
			
		||||
 | 
			
		||||
    const patcherDir = path.join(osuPath, "EZPPLauncher");
 | 
			
		||||
    if (!fs.existsSync(patcherDir)) fs.mkdirSync(patcherDir);
 | 
			
		||||
 | 
			
		||||
    for (const patcherFile of patcherUpdates) {
 | 
			
		||||
      const fileName = patcherFile.name;
 | 
			
		||||
      const fileURL = patcherFile.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)
 | 
			
		||||
          });
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await fs.promises.writeFile(path.join(osuPath, "EZPPLauncher", fileName), axiosDownloadWithProgress.data);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    eventEmitter,
 | 
			
		||||
    startDownload,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getUIFiles(osuPath) {
 | 
			
		||||
 | 
			
		||||
  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)
 | 
			
		||||
          });
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      await fs.promises.writeFile(path.join(osuPath, "EZPPLauncher", fileName), axiosDownloadWithProgress.data);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    eventEmitter,
 | 
			
		||||
    startDownload,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function replaceUIFile(osuPath, revert) {
 | 
			
		||||
  if (!revert) {
 | 
			
		||||
    const ezppUIFile = path.join(osuPath, "EZPPLauncher", "ezpp!ui.dll");
 | 
			
		||||
    const oldOsuUIFile = path.join(osuPath, "osu!ui.dll");
 | 
			
		||||
    await fs.promises.rename(oldOsuUIFile, path.join(osuPath, "osu!ui.dll.bak"));
 | 
			
		||||
    await fs.promises.rename(ezppUIFile, oldOsuUIFile);
 | 
			
		||||
  } else {
 | 
			
		||||
    const oldOsuUIFile = path.join(osuPath, "osu!ui.dll");
 | 
			
		||||
    const ezppUIFile = path.join(osuPath, "EZPPLauncher", "ezpp!ui.dll");
 | 
			
		||||
    await fs.promises.rename(oldOsuUIFile, ezppUIFile);
 | 
			
		||||
    await fs.promises.rename(path.join(osuPath, "osu!ui.dll.bak"), oldOsuUIFile);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = { isValidOsuFolder, getUserConfig, getGlobalConfig, getUpdateFiles, getFilesThatNeedUpdate, downloadUpdateFiles, runOsuWithDevServer, getPatcherUpdates, downloadPatcherUpdates, downloadUIFiles, getUIFiles, replaceUIFile };
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								tests/fileHash.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								tests/fileHash.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
const crypto = require("crypto");
 | 
			
		||||
 | 
			
		||||
(async () => {
 | 
			
		||||
    const correctHash = 'b66478cc0f9ec50810489a039ced642b';
 | 
			
		||||
    const filePath = 'C:\\Users\\horiz\\AppData\\Local\\osu!\\avcodec-51.dll';
 | 
			
		||||
    const fileHash = crypto.createHash('md5').update(await fs.promises.readFile(filePath)).digest('hex');
 | 
			
		||||
 | 
			
		||||
    console.log({
 | 
			
		||||
        correctHash,
 | 
			
		||||
        fileHash,
 | 
			
		||||
        matching: correctHash === fileHash,
 | 
			
		||||
    })
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										9
									
								
								tests/osuConfig.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/osuConfig.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
const { getGlobalConfig } = require("../src/util/osuUtil");
 | 
			
		||||
const config = require("../src/config/config");
 | 
			
		||||
(async () => {
 | 
			
		||||
    const osuPath = config.get("osuPath");
 | 
			
		||||
    const globalConfig = getGlobalConfig(osuPath);
 | 
			
		||||
 | 
			
		||||
    const globalConfigContent = await globalConfig.get("_ReleaseStream");
 | 
			
		||||
    console.log(globalConfigContent);
 | 
			
		||||
})();
 | 
			
		||||
		Reference in New Issue
	
	Block a user