Merge branch 'next' of https://git.ez-pp.farm/EZPPFarm/EZPPLauncher into next
This commit is contained in:
@@ -5,8 +5,10 @@
|
||||
DropdownItem,
|
||||
DropdownHeader,
|
||||
DropdownDivider,
|
||||
Button,
|
||||
} from "flowbite-svelte";
|
||||
import {
|
||||
ArrowLeftSolid,
|
||||
ArrowRightFromBracketSolid,
|
||||
ArrowRightToBracketSolid,
|
||||
UserSettingsSolid,
|
||||
@@ -38,6 +40,12 @@
|
||||
window.dispatchEvent(new CustomEvent("logout"));
|
||||
currentUser.set(undefined);
|
||||
currentPage.set(Page.Login);
|
||||
toast.success("Successfully logged out!", {
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 2000,
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("launchStatusUpdate", (e) => {
|
||||
@@ -67,7 +75,7 @@
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 1500,
|
||||
duration: 2000,
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -76,7 +84,7 @@
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 1500,
|
||||
duration: 4000,
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -96,14 +104,27 @@
|
||||
<Toaster></Toaster>
|
||||
|
||||
<div class="p-2 flex flex-row justify-between items-center">
|
||||
<div class="flex flex-row items-center">
|
||||
<div class="flex flex-row items-center animate-fadeIn opacity-0">
|
||||
{#if $currentPage == Page.Settings}
|
||||
<Button
|
||||
class="dark:!bg-gray-800 dark:active:!bg-gray-950 !ring-0 outline-none !border-none dark:text-white w-10 h-10 mr-1 rounded-lg animate-sideIn opacity-0"
|
||||
color="none"
|
||||
on:click={() => {
|
||||
currentPage.set(Page.Launch);
|
||||
}}
|
||||
>
|
||||
<ArrowLeftSolid class="outline-none border-none" size="sm" />
|
||||
</Button>
|
||||
{/if}
|
||||
<img src={ezppLogo} alt="EZPPFarm Logo" class="w-12 h-12 mr-2" />
|
||||
<span class="text-gray-700 dark:text-gray-100 text-xl font-extralight">
|
||||
EZPPLauncher
|
||||
</span>
|
||||
</div>
|
||||
{#if $currentPage == Page.Launch}
|
||||
<div class="flex flex-row gap-2 w-fill cursor-pointer md:order-2">
|
||||
<div
|
||||
class="flex flex-row gap-2 w-fill cursor-pointer md:order-2 animate-fadeIn opacity-0"
|
||||
>
|
||||
<Avatar
|
||||
class="rounded-lg border dark:border-gray-700 hover:ring-4 hover:ring-gray-200 dark:hover:ring-gray-800"
|
||||
src={loggedIn
|
||||
@@ -167,11 +188,3 @@
|
||||
{:else}
|
||||
<Launch />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
|
@@ -20,6 +20,10 @@ html .cet-titlebar .cet-control-icon svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cet-container {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.indeterminate {
|
||||
background-image: repeating-linear-gradient(
|
||||
90deg,
|
||||
|
@@ -1,53 +0,0 @@
|
||||
const sqlite = require("better-sqlite3");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const configFolder = path.join(
|
||||
process.platform == "win32"
|
||||
? process.env["LOCALAPPDATA"]
|
||||
: process.env["HOME"],
|
||||
"EZPPLauncher",
|
||||
);
|
||||
if (!fs.existsSync(configFolder)) fs.mkdirSync(configFolder);
|
||||
|
||||
const dbFile = path.join(configFolder, "ezpplauncher.db");
|
||||
|
||||
const db = sqlite(dbFile);
|
||||
db.pragma("journal_mode = WAL");
|
||||
|
||||
db.exec(
|
||||
"CREATE TABLE IF NOT EXISTS config (configKey VARCHAR PRIMARY KEY, configValue VARCHAR);",
|
||||
);
|
||||
|
||||
const set = (key, value) => {
|
||||
db.prepare(
|
||||
`INSERT OR REPLACE INTO config (configKey, configValue) VALUES (?, ?)`,
|
||||
).run(key, value);
|
||||
};
|
||||
|
||||
const remove = (key) => {
|
||||
db.prepare(`DELETE FROM config WHERE configKey = ?`).run(key);
|
||||
};
|
||||
|
||||
const get = (
|
||||
key,
|
||||
) => {
|
||||
const result = db.prepare(
|
||||
"SELECT configKey key, configValue val FROM config WHERE key = ?",
|
||||
).get(key);
|
||||
return result ? result.val ?? undefined : undefined;
|
||||
};
|
||||
|
||||
const all = () => {
|
||||
const result = db.prepare(
|
||||
`SELECT configKey key, configValue val FROM config WHERE 1`,
|
||||
).all();
|
||||
return result ?? undefined;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
all,
|
||||
get,
|
||||
set,
|
||||
remove,
|
||||
};
|
@@ -15,81 +15,118 @@
|
||||
|
||||
const processLogin = async () => {
|
||||
loading = true;
|
||||
window.addEventListener(
|
||||
"login-result",
|
||||
(e) => {
|
||||
const customEvent = e as CustomEvent;
|
||||
const resultData = customEvent.detail;
|
||||
const wasSuccessful = "user" in resultData;
|
||||
const loginPromise = new Promise<void>((res, rej) => {
|
||||
window.addEventListener(
|
||||
"login-result",
|
||||
(e) => {
|
||||
const customEvent = e as CustomEvent;
|
||||
const resultData = customEvent.detail;
|
||||
const wasSuccessful = "user" in resultData;
|
||||
|
||||
if (!wasSuccessful) {
|
||||
const errorResult = resultData as Error;
|
||||
toast.error(errorResult.message, {
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 1500,
|
||||
});
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
const userResult = resultData.user as User;
|
||||
currentUser.set(userResult);
|
||||
currentPage.set(Page.Launch);
|
||||
toast.success(`Welcome back, ${userResult.name}!`, {
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 3000,
|
||||
});
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("login-attempt", {
|
||||
detail: { username, password, saveCredentials },
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const tryAutoLogin = async () => {
|
||||
loading = true;
|
||||
await new Promise((res) => setTimeout(res, 1500));
|
||||
window.addEventListener(
|
||||
"login-result",
|
||||
(e) => {
|
||||
const customEvent = e as CustomEvent;
|
||||
const resultData = customEvent.detail;
|
||||
const isGuest = "guest" in resultData;
|
||||
const wasSuccessful = "user" in resultData;
|
||||
if (isGuest) {
|
||||
if (!wasSuccessful) {
|
||||
/* const errorResult = resultData as Error;
|
||||
toast.error(errorResult.message, {
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 1500,
|
||||
}); */
|
||||
rej();
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
const userResult = resultData.user as User;
|
||||
currentUser.set(userResult);
|
||||
currentPage.set(Page.Launch);
|
||||
toast.success(`Logged in as Guest`, {
|
||||
res();
|
||||
toast.success(`Welcome back, ${userResult.name}!`, {
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!wasSuccessful) {
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
const userResult = resultData.user as User;
|
||||
currentUser.set(userResult);
|
||||
currentPage.set(Page.Launch);
|
||||
toast.success(`Welcome back, ${userResult.name}!`, {
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 3000,
|
||||
});
|
||||
loading = false;
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("login-attempt", {
|
||||
detail: { username, password, saveCredentials },
|
||||
})
|
||||
);
|
||||
});
|
||||
toast.promise(
|
||||
loginPromise,
|
||||
{
|
||||
loading: "Logging in...",
|
||||
success: "Successfully logged in!",
|
||||
error: "Failed to login.",
|
||||
},
|
||||
{ once: true }
|
||||
{
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 3000,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const tryAutoLogin = async () => {
|
||||
loading = true;
|
||||
const loginPromise = new Promise<void>((res, rej) => {
|
||||
window.addEventListener(
|
||||
"login-result",
|
||||
(e) => {
|
||||
const customEvent = e as CustomEvent;
|
||||
const resultData = customEvent.detail;
|
||||
const isGuest = "guest" in resultData;
|
||||
const wasSuccessful = "user" in resultData;
|
||||
console.log(resultData);
|
||||
if (isGuest) {
|
||||
currentPage.set(Page.Launch);
|
||||
res();
|
||||
toast.success(`Logged in as Guest`, {
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 3000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!wasSuccessful) {
|
||||
loading = false;
|
||||
rej();
|
||||
return;
|
||||
}
|
||||
const userResult = resultData.user as User;
|
||||
currentUser.set(userResult);
|
||||
currentPage.set(Page.Launch);
|
||||
res();
|
||||
toast.success(`Welcome back, ${userResult.name}!`, {
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 3000,
|
||||
});
|
||||
loading = false;
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
window.dispatchEvent(new CustomEvent("autologin-attempt"));
|
||||
});
|
||||
toast.promise(
|
||||
loginPromise,
|
||||
{
|
||||
loading: "Logging in...",
|
||||
success: "Successfully logged in!",
|
||||
error: "Failed to login.",
|
||||
},
|
||||
{
|
||||
position: "bottom-center",
|
||||
className:
|
||||
"dark:!bg-gray-800 border-1 dark:!border-gray-700 dark:!text-gray-100",
|
||||
duration: 3000,
|
||||
}
|
||||
);
|
||||
window.dispatchEvent(new CustomEvent("autologin-attempt"));
|
||||
};
|
||||
|
||||
const proceedAsGuest = () => {
|
||||
@@ -103,10 +140,24 @@
|
||||
});
|
||||
};
|
||||
|
||||
if (!$startup) {
|
||||
startup.set(true);
|
||||
tryAutoLogin();
|
||||
}
|
||||
const shouldAutologin = async () => {
|
||||
const shouldAutologin = await new Promise<boolean>((res) => {
|
||||
window.addEventListener("autologin-result", (e) => {
|
||||
const customEvent = e as CustomEvent;
|
||||
const resultData = customEvent.detail;
|
||||
res(resultData);
|
||||
});
|
||||
window.dispatchEvent(new CustomEvent("autologin-active"));
|
||||
});
|
||||
return shouldAutologin;
|
||||
};
|
||||
|
||||
(async () => {
|
||||
if (!$startup) {
|
||||
startup.set(true);
|
||||
if (await shouldAutologin()) tryAutoLogin();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<main
|
||||
|
@@ -1,11 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { Button, ButtonGroup, Input } from "flowbite-svelte";
|
||||
import { FolderSolid } from "flowbite-svelte-icons";
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Input,
|
||||
Label,
|
||||
Toggle,
|
||||
Tooltip,
|
||||
} from "flowbite-svelte";
|
||||
import { FileSearchSolid, FolderSolid } from "flowbite-svelte-icons";
|
||||
import { currentPage } from "../storage/localStore";
|
||||
import { Page } from "../consts/pages";
|
||||
|
||||
let folderPath: string = "";
|
||||
|
||||
let patching: boolean = true;
|
||||
let presence: boolean = true;
|
||||
|
||||
window.addEventListener("settings-result", (e) => {
|
||||
const settings: Record<string, string>[] = (e as CustomEvent).detail;
|
||||
const osuPath = settings.find((setting) => setting.key == "osuPath");
|
||||
@@ -16,17 +26,38 @@
|
||||
const setFolderPath = () => {
|
||||
window.dispatchEvent(new CustomEvent("folder-set"));
|
||||
};
|
||||
|
||||
const detectFolderPath = () => {
|
||||
window.dispatchEvent(new CustomEvent("folder-auto"));
|
||||
};
|
||||
|
||||
const togglePatching = () => {
|
||||
patching = !patching;
|
||||
};
|
||||
|
||||
const togglePresence = () => {
|
||||
presence = !presence;
|
||||
};
|
||||
</script>
|
||||
|
||||
<main
|
||||
class="h-[265px] my-auto flex flex-col justify-center items-center p-5 animate-fadeIn opacity-0"
|
||||
class="h-[265px] flex flex-col justify-start p-3 animate-fadeIn opacity-0"
|
||||
>
|
||||
<div class="flex flex-col gap-2 p-3">
|
||||
<Toggle class="w-fit" bind:checked={presence} on:click={togglePresence}
|
||||
>Discord Presence</Toggle
|
||||
>
|
||||
<Toggle class="w-fit" bind:checked={patching} on:click={togglePatching}
|
||||
>Patching</Toggle
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="container flex flex-col items-center justify-center gap-5 rounded-lg p-3"
|
||||
>
|
||||
<ButtonGroup class="w-full">
|
||||
<Input
|
||||
type="text"
|
||||
id="oip"
|
||||
placeholder="Path to your osu! installation"
|
||||
value={folderPath}
|
||||
readonly
|
||||
@@ -34,19 +65,23 @@
|
||||
<Button
|
||||
color="light"
|
||||
class="dark:active:!bg-gray-900"
|
||||
on:click={setFolderPath}
|
||||
><FolderSolid
|
||||
on:click={detectFolderPath}
|
||||
>
|
||||
<FileSearchSolid
|
||||
size="sm"
|
||||
class="dark:text-gray-300 text-gray-500 outline-none border-none select-none pointer-events-none"
|
||||
/></Button
|
||||
>
|
||||
</ButtonGroup>
|
||||
<div class="flex flex-row justify-center items-center gap-5">
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
color="light"
|
||||
class="dark:active:!bg-gray-900"
|
||||
on:click={() => currentPage.set(Page.Launch)}>Go Back</Button
|
||||
class="dark:active:!bg-gray-900 active:!rounded-lg"
|
||||
on:click={setFolderPath}
|
||||
>
|
||||
</div>
|
||||
<FolderSolid
|
||||
size="sm"
|
||||
class="dark:text-gray-300 text-gray-500 outline-none border-none select-none pointer-events-none"
|
||||
/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</main>
|
||||
|
@@ -1,20 +0,0 @@
|
||||
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 };
|
@@ -1,13 +0,0 @@
|
||||
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,358 +0,0 @@
|
||||
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=";
|
||||
const ignoredOsuEntities = [
|
||||
"osu!auth.dll",
|
||||
];
|
||||
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 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;
|
||||
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) {
|
||||
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 "";
|
||||
}
|
||||
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()) {
|
||||
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 };
|
Reference in New Issue
Block a user