added launching, patching, and ui override

This commit is contained in:
2024-01-13 23:41:40 +01:00
parent d9fec1193e
commit f11e84efd7
13 changed files with 599 additions and 15 deletions

20
src/util/executeUtil.js Normal file
View 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 };

View 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 };

View File

@@ -1,3 +1,3 @@
export const clamp = (val: number, min: number, max: number) => {
return Math.max(min, Math.min(val, max));
};
};

View File

@@ -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 };