418 lines
12 KiB
JavaScript
418 lines
12 KiB
JavaScript
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.js");
|
|
|
|
const checkUpdateURL =
|
|
"https://osu.ppy.sh/web/check-updates.php?action=check&stream=";
|
|
const ignoredOsuEntities = [
|
|
"osu!auth.dll",
|
|
];
|
|
const gamemodes = {
|
|
0: "osu!",
|
|
1: "taiko",
|
|
2: "catch",
|
|
3: "mania",
|
|
4: "osu!(rx)",
|
|
5: "taiko(rx)",
|
|
6: "catch(rx)",
|
|
8: "osu!(ap)"
|
|
}
|
|
const osuEntities = [
|
|
"avcodec-51.dll",
|
|
"avformat-52.dll",
|
|
"avutil-49.dll",
|
|
"bass.dll",
|
|
"bass_fx.dll",
|
|
"collection.db",
|
|
"d3dcompiler_47.dll",
|
|
"libEGL.dll",
|
|
"libGLESv2.dll",
|
|
"Microsoft.Ink.dll",
|
|
"OpenTK.dll",
|
|
"osu!.cfg",
|
|
"osu!.db",
|
|
"osu!.exe",
|
|
"osu!auth.dll",
|
|
"osu!gameplay.dll",
|
|
"osu!seasonal.dll",
|
|
"osu!ui.dll",
|
|
"presence.db",
|
|
"pthreadGC2.dll",
|
|
"scores.db",
|
|
];
|
|
|
|
const ezppLauncherUpdateList = "https://ez-pp.farm/ezpplauncher"
|
|
|
|
async function isValidOsuFolder(path) {
|
|
const allFiles = await fs.promises.readdir(path);
|
|
let matches = 0;
|
|
for (const file of allFiles) {
|
|
if (osuEntities.includes(file)) matches = matches + 1;
|
|
}
|
|
return (Math.round((matches / osuEntities.length) * 100) >= 60);
|
|
}
|
|
|
|
function getGlobalConfig(osuPath) {
|
|
const configFileInfo = {
|
|
name: "",
|
|
path: "",
|
|
get: async (key) => {
|
|
if (!configFileInfo.path) {
|
|
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 "";
|
|
}
|
|
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 userOsuConfig = path.join(
|
|
osuPath,
|
|
`osu!.${process.env["USERNAME"]}.cfg`,
|
|
);
|
|
if (fs.existsSync(userOsuConfig)) {
|
|
configFileInfo.name = `osu!.${process.env["USERNAME"]}.cfg`;
|
|
configFileInfo.path = userOsuConfig;
|
|
}
|
|
return configFileInfo;
|
|
}
|
|
|
|
async function getUpdateFiles(releaseStream) {
|
|
const releaseData = await fetch(checkUpdateURL + releaseStream);
|
|
return releaseData.ok ? await releaseData.json() : undefined;
|
|
}
|
|
|
|
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()
|
|
) {
|
|
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,
|
|
});
|
|
});
|
|
try {
|
|
if (fs.existsSync(path.join(osuPath, fileName))) {
|
|
await fs.promises.rm(path.join(osuPath, fileName), {
|
|
force: true,
|
|
});
|
|
}
|
|
await fs.promises.writeFile(
|
|
path.join(osuPath, fileName),
|
|
axiosDownloadWithProgress.data,
|
|
);
|
|
} catch (err) {
|
|
console.log(err);
|
|
eventEmitter.emit("error", {
|
|
fileName,
|
|
});
|
|
}
|
|
}
|
|
|
|
// wait until all files are downloaded
|
|
return true;
|
|
};
|
|
|
|
return {
|
|
eventEmitter,
|
|
startDownload,
|
|
};
|
|
}
|
|
|
|
function runOsuUpdater(osuPath, onExit) {
|
|
const osuExecuteable = path.join(osuPath, "osu!.exe");
|
|
runFile(osuPath, osuExecuteable, ["-repair"], onExit);
|
|
}
|
|
|
|
function runOsuWithDevServer(osuPath, serverDomain, onExit) {
|
|
const osuExecuteable = path.join(osuPath, "osu!.exe");
|
|
runFile(osuPath, osuExecuteable, ["-devserver", serverDomain], onExit);
|
|
}
|
|
|
|
async function getEZPPLauncherUpdateFiles(osuPath) {
|
|
const filesToDownload = [];
|
|
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);
|
|
if (fs.existsSync(filePath)) {
|
|
const fileHash = updateFile.md5.toLowerCase();
|
|
const localFileHash = crypto.createHash("md5").update(
|
|
fs.readFileSync(filePath),
|
|
).digest("hex").toLowerCase();
|
|
if (fileHash !== localFileHash) {
|
|
filesToDownload.push(updateFile);
|
|
}
|
|
} else {
|
|
filesToDownload.push(updateFile);
|
|
}
|
|
}
|
|
return filesToDownload;
|
|
}
|
|
|
|
async function downloadEZPPLauncherUpdateFiles(osuPath, updateFiles) {
|
|
const eventEmitter = new EventEmitter();
|
|
|
|
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 {
|
|
if (fs.existsSync(filePath)) {
|
|
await fs.promises.rm(filePath, {
|
|
force: true,
|
|
});
|
|
}
|
|
await fs.promises.writeFile(
|
|
filePath,
|
|
axiosDownloadWithProgress.data,
|
|
);
|
|
} catch (err) {
|
|
console.log(err);
|
|
eventEmitter.emit("error", {
|
|
fileName: path.basename(filePath),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
eventEmitter,
|
|
startDownload,
|
|
};
|
|
}
|
|
|
|
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 oldOsuGameplayFile = path.join(osuPath, "osu!gameplay.dll");
|
|
|
|
await fs.promises.rename(
|
|
oldOsuUIFile,
|
|
path.join(osuPath, "osu!ui.dll.bak"),
|
|
);
|
|
await fs.promises.rename(ezppUIFile, oldOsuUIFile);
|
|
|
|
await fs.promises.rename(
|
|
oldOsuGameplayFile,
|
|
path.join(osuPath, "osu!gameplay.dll.bak"),
|
|
);
|
|
await fs.promises.rename(ezppGameplayFile, oldOsuGameplayFile);
|
|
} else {
|
|
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");
|
|
|
|
await fs.promises.rename(oldOsuUIFile, ezppUIFile);
|
|
await fs.promises.rename(
|
|
path.join(osuPath, "osu!ui.dll.bak"),
|
|
oldOsuUIFile,
|
|
);
|
|
|
|
await fs.promises.rename(oldOsuGameplayFile, ezppGameplayFile);
|
|
await fs.promises.rename(
|
|
path.join(osuPath, "osu!gameplay.dll.bak"),
|
|
oldOsuGameplayFile,
|
|
);
|
|
}
|
|
}
|
|
|
|
async function findOsuInstallation() {
|
|
const regedit = require("regedit-rs");
|
|
|
|
const osuLocationFromDefaultIcon =
|
|
"HKLM\\SOFTWARE\\Classes\\osu\\DefaultIcon";
|
|
const osuKey = regedit.listSync(osuLocationFromDefaultIcon);
|
|
if (osuKey[osuLocationFromDefaultIcon].exists) {
|
|
const key = osuKey[osuLocationFromDefaultIcon].values[""];
|
|
let value = key.value;
|
|
value = value.substring(1, value.length - 3);
|
|
return path.dirname(value.trim());
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
async function updateOsuConfigHashes(osuPath) {
|
|
const osuCfg = path.join(osuPath, "osu!.cfg");
|
|
const fileStream = await fs.promises.readFile(osuCfg, "utf-8");
|
|
const lines = fileStream.split(/\r?\n/);
|
|
const newLines = [];
|
|
for (const line of lines) {
|
|
if (line.includes(" = ")) {
|
|
const argsPair = line.split(" = ", 2);
|
|
const key = argsPair[0];
|
|
const value = argsPair[1];
|
|
|
|
if (key.startsWith("h_")) {
|
|
const fileName = key.substring(2, key.length);
|
|
const filePath = path.join(osuPath, fileName);
|
|
if (!fs.existsSync(filePath)) continue;
|
|
const binaryFileContents = await fs.promises.readFile(filePath);
|
|
const existingFileMD5 = crypto.createHash("md5").update(
|
|
binaryFileContents,
|
|
).digest("hex");
|
|
if (value == existingFileMD5) newLines.push(line);
|
|
else newLines.push(`${key} = ${existingFileMD5}`);
|
|
} else if (line.startsWith("u_UpdaterAutoStart")) {
|
|
newLines.push(`${key} = 0`);
|
|
} else {
|
|
newLines.push(line);
|
|
}
|
|
} else {
|
|
newLines.push(line);
|
|
}
|
|
}
|
|
|
|
await fs.promises.writeFile(osuCfg, newLines.join("\n"), "utf-8");
|
|
}
|
|
|
|
module.exports = {
|
|
isValidOsuFolder,
|
|
getUserConfig,
|
|
getGlobalConfig,
|
|
getUpdateFiles,
|
|
getFilesThatNeedUpdate,
|
|
downloadUpdateFiles,
|
|
runOsuWithDevServer,
|
|
replaceUIFiles,
|
|
findOsuInstallation,
|
|
updateOsuConfigHashes,
|
|
runOsuUpdater,
|
|
getEZPPLauncherUpdateFiles,
|
|
downloadEZPPLauncherUpdateFiles,
|
|
gamemodes
|
|
};
|