mirror of https://github.com/hykilpikonna/AquaDX
feat: 🎨 finalize server url mode
parent
f6efd392b9
commit
8b9236ae43
|
@ -31,3 +31,5 @@ dist-ssr
|
||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
|
|
||||||
|
public/chu3
|
|
@ -120,15 +120,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
let USERBOX_URL_STATE = useLocalStorage("userboxURL", USERBOX_DEFAULT_URL);
|
let USERBOX_URL_STATE = useLocalStorage("userboxURL", USERBOX_DEFAULT_URL);
|
||||||
function userboxHandleInput(e: KeyboardEvent) {
|
function userboxHandleInput(baseURL: string, isSetByServer: boolean = false) {
|
||||||
if (e.key != "Enter")
|
|
||||||
return;
|
|
||||||
let baseURL = (e.target as HTMLInputElement).value;
|
|
||||||
if (baseURL != "")
|
if (baseURL != "")
|
||||||
try {
|
try {
|
||||||
// validate url
|
// validate url
|
||||||
new URL(baseURL);
|
new URL(baseURL, location.href);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
if (isSetByServer)
|
||||||
|
return;
|
||||||
return error = t("userbox.new.error.invalidUrl")
|
return error = t("userbox.new.error.invalidUrl")
|
||||||
}
|
}
|
||||||
USERBOX_URL_STATE.value = baseURL;
|
USERBOX_URL_STATE.value = baseURL;
|
||||||
|
@ -137,17 +136,17 @@
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (USERBOX_DEFAULT_URL)
|
if (USERBOX_DEFAULT_URL && !USERBOX_URL_STATE.value)
|
||||||
USERBOX_URL_STATE.value = USERBOX_DEFAULT_URL;
|
userboxHandleInput(USERBOX_DEFAULT_URL, true);
|
||||||
|
|
||||||
indexedDB.databases().then(async (dbi) => {
|
indexedDB.databases().then(async (dbi) => {
|
||||||
let databaseExists = dbi.some(db => db.name == "userboxChusanDDS");
|
let databaseExists = dbi.some(db => db.name == "userboxChusanDDS");
|
||||||
if (databaseExists) {
|
if (databaseExists)
|
||||||
await initializeDb();
|
await initializeDb();
|
||||||
|
if (databaseExists || USERBOX_URL_STATE.value) {
|
||||||
DDSreader = new DDS(ddsDB);
|
DDSreader = new DDS(ddsDB);
|
||||||
USERBOX_INSTALLED = databaseExists;
|
USERBOX_INSTALLED = databaseExists || USERBOX_URL_STATE.value != "";
|
||||||
} else if (USERBOX_URL_STATE.value)
|
}
|
||||||
USERBOX_INSTALLED = true;
|
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -251,7 +250,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if USERBOX_SUPPORT}
|
{#if USERBOX_SUPPORT && !USERBOX_DEFAULT_URL}
|
||||||
<p>
|
<p>
|
||||||
<button on:click={() => USERBOX_SETUP_RUN = !USERBOX_SETUP_RUN}>{t(!USERBOX_INSTALLED ? `userbox.new.activate_first` : `userbox.new.activate_update`)}</button>
|
<button on:click={() => USERBOX_SETUP_RUN = !USERBOX_SETUP_RUN}>{t(!USERBOX_INSTALLED ? `userbox.new.activate_first` : `userbox.new.activate_update`)}</button>
|
||||||
</p>
|
</p>
|
||||||
|
@ -282,7 +281,7 @@
|
||||||
<span>{USERBOX_SETUP_MODE ? t('userbox.new.url_warning') : USERBOX_SETUP_TEXT}</span>
|
<span>{USERBOX_SETUP_MODE ? t('userbox.new.url_warning') : USERBOX_SETUP_TEXT}</span>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
{#if USERBOX_SETUP_MODE}
|
{#if USERBOX_SETUP_MODE}
|
||||||
<input type="text" on:keyup={userboxHandleInput} class="add-margin" placeholder="Base URL">
|
<input type="text" on:keyup={e => {if (e.key == "Enter") userboxHandleInput((e.target as HTMLInputElement).value)}} class="add-margin" placeholder="Base URL">
|
||||||
{:else}
|
{:else}
|
||||||
<p class="notice add-margin">
|
<p class="notice add-margin">
|
||||||
{t('userbox.new.setup.notice')}
|
{t('userbox.new.setup.notice')}
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const FADE_OUT = { duration: 200 }
|
||||||
export const FADE_IN = { delay: 400 }
|
export const FADE_IN = { delay: 400 }
|
||||||
export const DEFAULT_PFP = '/assets/imgs/no_profile.png'
|
export const DEFAULT_PFP = '/assets/imgs/no_profile.png'
|
||||||
|
|
||||||
// USERBOX_ASSETS
|
// Documentation for Userbox mode can be found in `docs/aquabox-url-mode.md`
|
||||||
// Please note that if this is set, it must be manually unset by users in Chuni Settings -> Update Userbox -> Switch to URL mode -> (empty value) -> Enter key
|
// Please note that if this is set, it must be manually unset by users in Chuni Settings -> Update Userbox -> Switch to URL mode -> (empty value) -> Enter key
|
||||||
export const USERBOX_DEFAULT_URL = ""
|
export const USERBOX_DEFAULT_URL = ""
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default class DDSCache {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
if (this.userboxURL.value) {
|
if (this.userboxURL.value) {
|
||||||
let targetPath = path.replaceAll(":", "/");
|
let targetPath = path.replaceAll(":", "/");
|
||||||
let response = await fetch(`${this.userboxURL.value}/${targetPath}.dds`).then(b => b.blob()).catch(reject);
|
let response = await fetch(`${this.userboxURL.value}/${targetPath}.chu`).then(b => b.blob()).catch(reject);
|
||||||
if (response)
|
if (response)
|
||||||
return resolve(response);
|
return resolve(response);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# AquaBox URL Mode Setup Guide
|
||||||
|
|
||||||
|
## For users
|
||||||
|
|
||||||
|
1. Go to your Chuni game settings
|
||||||
|
2. Go down to "Enable AquaBox" or "Upgrade AquaBox"
|
||||||
|
3. Click on "Switch to URL mode"
|
||||||
|
4. Enter the base URL for your AquaBox
|
||||||
|
|
||||||
|
## For server owners / asset hosters
|
||||||
|
|
||||||
|
> :warning: Assets are already not hosted on AquaDX for legal reasons.<br>
|
||||||
|
> Hosting SEGA's assets may put you at higher risk of DMCA.
|
||||||
|
|
||||||
|
1. Extract your Chunithm Luminous game files.
|
||||||
|
|
||||||
|
It is recommend you have the latest version of the game and all of the options your users may use.
|
||||||
|
|
||||||
|
The script to generate the proper paths can be found in [/tools/chusan-extractor.js](/tools/chusan-extractor.js). Node.js or Bun is required.<br>
|
||||||
|
Please read the comments at the top of the script for usage instructions.
|
||||||
|
|
||||||
|
2. Copy the new `chu3` folder where you need it to be (read #3 if you're hosting AquaNet and want to host on the same endpoints).
|
||||||
|
3. (Optional) Update `src/lib/config.ts`.
|
||||||
|
```ts
|
||||||
|
// Change this to the base url of where your assets are stored.
|
||||||
|
// If you are hosting on AquaNet, you can put the files @ /public/chu3 & use '/chu3' for your base url.
|
||||||
|
// This will work the same way as setting it on the UI does. TEST IT ON THE UI BEFORE YOU APPLY THIS CONFIG!!!
|
||||||
|
export const USERBOX_DEFAULT_URL = "/chu3";
|
||||||
|
```
|
||||||
|
4. Enjoy!
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Chusan asset extractor for AquaBox URL mode.
|
||||||
|
Place your "option" (or "bin/option") and "data" folders in the same directory as this script as they're named.
|
||||||
|
|
||||||
|
Data will be placed into the "chu3" folder.
|
||||||
|
Place the contents into a public directory that can be accessed by users.
|
||||||
|
|
||||||
|
Know Python or another common scripting language?
|
||||||
|
Feel free to rewrite this tool and submit it to MewoLab/AquaDX!
|
||||||
|
Or rewrite it in JavaScript again! Anything is better than this hot pile of garbage!
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Allows this to be a single-file script
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
const verifyDirectoryExistant = (name) => {
|
||||||
|
return fs.existsSync(name);
|
||||||
|
}
|
||||||
|
const mkdir = (name) => {
|
||||||
|
if (!fs.existsSync(name))
|
||||||
|
fs.mkdirSync(name);
|
||||||
|
};
|
||||||
|
const outputTarget = "chu3";
|
||||||
|
|
||||||
|
const directoryPaths = [
|
||||||
|
{
|
||||||
|
folder: "ddsImage",
|
||||||
|
processName: "Characters",
|
||||||
|
path: "characterThumbnail",
|
||||||
|
filter: (name) => name.substring(name.length - 6, name.length) == "02.dds",
|
||||||
|
id: (name) => `0${name.substring(17, 21)}${name.substring(23, 24)}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
folder: "namePlate",
|
||||||
|
processName: "Nameplates",
|
||||||
|
path: "nameplate",
|
||||||
|
filter: (name) => name.substring(0, 17) == "CHU_UI_NamePlate_",
|
||||||
|
id: (name) => name.substring(17, 25)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
folder: "avatarAccessory",
|
||||||
|
processName: "Avatar Accessory Thumbnails",
|
||||||
|
path: "avatarAccessoryThumbnail",
|
||||||
|
filter: (name) => name.substring(14, 18) == "Icon",
|
||||||
|
id: (name) => name.substring(19, 27)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
folder: "avatarAccessory",
|
||||||
|
processName: "Avatar Accessories",
|
||||||
|
path: "avatarAccessory",
|
||||||
|
filter: (name) => name.substring(14, 17) == "Tex",
|
||||||
|
id: (name) => name.substring(18, 26)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
folder: "texture",
|
||||||
|
processName: "Surfboard Textures",
|
||||||
|
useFileName: true,
|
||||||
|
path: "surfboard",
|
||||||
|
filter: (name) =>
|
||||||
|
([
|
||||||
|
"CHU_UI_Common_Avatar_body_00.dds",
|
||||||
|
"CHU_UI_Common_Avatar_face_00.dds",
|
||||||
|
"CHU_UI_title_rank_00_v10.dds"
|
||||||
|
]).includes(name),
|
||||||
|
id: (name) => name
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const processFile = (fileName, path, subFolder) => {
|
||||||
|
let localReference = directoryPaths.find(p => p.folder == subFolder && p.filter(fileName));
|
||||||
|
if (!localReference) return;
|
||||||
|
files.push({
|
||||||
|
path: `${path}/${fileName}`,
|
||||||
|
target: `${localReference.id(fileName)}.chu`,
|
||||||
|
targetFolder: `${localReference.path}`,
|
||||||
|
name: fileName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let files = [];
|
||||||
|
const processFolder = (path) => {
|
||||||
|
for (const folder of fs.readdirSync(path)) {
|
||||||
|
let folderData = fs.statSync(`${path}/${folder}`);
|
||||||
|
if (!folderData.isDirectory()) continue;
|
||||||
|
for (const subFolder of fs.readdirSync(`${path}/${folder}`)) {
|
||||||
|
let folderData = fs.statSync(`${path}/${folder}/${subFolder}`);
|
||||||
|
let reference = directoryPaths.find(p => p.folder == subFolder);
|
||||||
|
if (!reference || !folderData.isDirectory()) continue;
|
||||||
|
// what a mess
|
||||||
|
for (const subSubFolder of fs.readdirSync(`${path}/${folder}/${subFolder}`))
|
||||||
|
if (fs.statSync(`${path}/${folder}/${subFolder}/${subSubFolder}`).isDirectory()) {
|
||||||
|
for (const subSubSubFile of fs.readdirSync(`${path}/${folder}/${subFolder}/${subSubFolder}`))
|
||||||
|
processFile(subSubSubFile, `${path}/${folder}/${subFolder}/${subSubFolder}`, subFolder)
|
||||||
|
} else
|
||||||
|
processFile(subSubFolder, `${path}/${folder}/${subFolder}`, subFolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!verifyDirectoryExistant("data"))
|
||||||
|
return console.log("Data folder non-existant.")
|
||||||
|
if (!verifyDirectoryExistant("bin"))
|
||||||
|
if (!verifyDirectoryExistant("option"))
|
||||||
|
return console.log("Option folder non-existant.")
|
||||||
|
|
||||||
|
processFolder("data");
|
||||||
|
if (verifyDirectoryExistant("bin")) {
|
||||||
|
processFolder("bin/option");
|
||||||
|
} else
|
||||||
|
processFolder("option");
|
||||||
|
|
||||||
|
|
||||||
|
console.log(`Found ${files.length} files.`);
|
||||||
|
console.log(`Copying now, please wait.`)
|
||||||
|
|
||||||
|
if (verifyDirectoryExistant(outputTarget))
|
||||||
|
return console.log("Output folder exists.");
|
||||||
|
mkdir(outputTarget);
|
||||||
|
|
||||||
|
files.forEach(fileData => {
|
||||||
|
console.log(`Copying ${fileData.name}`)
|
||||||
|
mkdir(`${outputTarget}/${fileData.targetFolder}`)
|
||||||
|
fs.copyFileSync(fileData.path, `${outputTarget}/${fileData.targetFolder}/${fileData.target}`)
|
||||||
|
})
|
Loading…
Reference in New Issue