mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 23:46:19 +00:00
Add secure configuration bootstrap flow
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"notification.badge.received": "Nuovo Distintivo!",
|
||||
"wiredfurni.badgereceived.title": "Distintivo ricevuto!",
|
||||
"wiredfurni.badgereceived.body": "Hai appena ricevuto un nuovo Distintivo! Controlla nel tuo Inventario!",
|
||||
"friendlist.search": "Search friends",
|
||||
"purse.seasonal.currency.101": "cash",
|
||||
"widget.chooser.checkall": "Select furniture",
|
||||
"widget.chooser.btn.pickall": "pick up selected items!",
|
||||
"wiredfurni.params.requireall.2": "If one of the selected furni has an avatar",
|
||||
"wiredfurni.params.requireall.3": "If all selected furni have avatars on them",
|
||||
"widget.settings.general": "General",
|
||||
"widget.settings.general.title": "Adjust the default Nitro settings",
|
||||
"widget.settings.volume": "Volume",
|
||||
"widget.settings.interface": "Interface",
|
||||
"widget.settings.interface.title": "Adjust the interface settings",
|
||||
"widget.settings.interface.fps.automatic": "Set FPS to unlimited",
|
||||
"widget.settings.interface.fps.warning": "Setting FPS to unlimited may cause performance issues!",
|
||||
"widget.settings.interface.secondary": "Change the window header color",
|
||||
"widget.settings.interface.reset": "Reset header color to default",
|
||||
"widget.room.chat.hide_pets": "Hide pets",
|
||||
"widget.room.chat.hide_avatars": "Hide avatars",
|
||||
"widget.room.chat.hide_balloon": "Hide speech bubble",
|
||||
"widget.room.chat.show_balloon": "Speech bubble",
|
||||
"widget.room.chat.clear_history": "clear history",
|
||||
"widget.room.youtube.shared": "YouTube is being shared",
|
||||
"widget.room.youtube.open_video": "Open the video",
|
||||
"wiredfurni.tooltip.select.tile": "Select tile",
|
||||
"wiredfurni.tooltip.remove.tile": "Deselect tile",
|
||||
"wiredfurni.tooltip.remove.5x5_tile": "select 5x5 tiles",
|
||||
"wiredfurni.tooltip.remove.clear_tile": "Clear all selections",
|
||||
"wiredfurni.params.furni_neighborhood.group.user": "Players",
|
||||
"wiredfurni.params.furni_neighborhood.group.furni": "Furniture",
|
||||
"wiredfurni.params.selector_option.bot": "No bots",
|
||||
"wiredfurni.params.selector_option.pet": "No pets",
|
||||
"catalog.title": "Catalog",
|
||||
"catalog.favorites": "Favorites",
|
||||
"catalog.favorites.pages": "Pages",
|
||||
"catalog.favorites.furni": "Furni",
|
||||
"catalog.favorites.empty": "No favorites",
|
||||
"catalog.favorites.empty.hint": "Click the heart on furni or the star on pages to add them.",
|
||||
"catalog.admin": "Admin",
|
||||
"catalog.admin.new": "New",
|
||||
"catalog.admin.root": "Root",
|
||||
"catalog.admin.new.root.category": "New root category",
|
||||
"catalog.admin.edit.root": "Edit Root",
|
||||
"catalog.admin.edit": "Edit:",
|
||||
"catalog.admin.edit.page": "Edit Page",
|
||||
"catalog.admin.hidden": "hidden",
|
||||
"catalog.admin.edit.title": "Edit \"%name%\"",
|
||||
"catalog.admin.show": "Show",
|
||||
"catalog.admin.hide": "Hide",
|
||||
"catalog.admin.delete": "Delete",
|
||||
"catalog.admin.delete.title": "Delete \"%name%\"",
|
||||
"catalog.admin.delete.category.confirm": "Delete category \"%name%\" and all its content?",
|
||||
"catalog.admin.delete.page": "Delete page",
|
||||
"catalog.admin.delete.page.confirm": "Delete page \"%name%\"?",
|
||||
"catalog.admin.delete.offer.confirm": "Are you sure you want to delete this offer?",
|
||||
"catalog.admin.create": "Create",
|
||||
"catalog.admin.save": "Save",
|
||||
"catalog.admin.create.subpage": "Create sub-page",
|
||||
"catalog.admin.order": "Order",
|
||||
"catalog.admin.visible": "Visible",
|
||||
"catalog.admin.enabled": "Enabled",
|
||||
"catalog.admin.offer.new": "New Offer",
|
||||
"catalog.admin.offer.edit": "Edit Offer",
|
||||
"catalog.admin.offer.name": "Catalog Name",
|
||||
"catalog.admin.offer.general": "General",
|
||||
"catalog.admin.offer.quantity": "Quantity",
|
||||
"catalog.admin.offer.prices": "Prices",
|
||||
"catalog.admin.offer.credits": "Credits",
|
||||
"catalog.admin.offer.points": "Points",
|
||||
"catalog.admin.offer.points.type": "Points Type",
|
||||
"catalog.admin.offer.options": "Options",
|
||||
"catalog.admin.offer.club.only": "Club Only",
|
||||
"catalog.admin.offer.extradata": "Extra Data (optional)....",
|
||||
"catalog.admin.offer.have.offer": "Multi-discount (have_offer)",
|
||||
"catalog.trophies.title": "Trophies",
|
||||
"catalog.trophies.write.hint": "Write a text for the trophy before purchasing",
|
||||
"catalog.trophies.inscription": "Trophy Inscription",
|
||||
"catalog.trophies.inscription.placeholder": "Write the text that will appear on the trophy...",
|
||||
"catalog.pets.show.colors": "Show colors",
|
||||
"catalog.pets.choose.color": "Choose color",
|
||||
"catalog.pets.choose.breed": "Choose breed",
|
||||
"catalog.pets.back.breeds": "? Breeds",
|
||||
"catalog.prefix.text": "Text",
|
||||
"catalog.prefix.text.placeholder": "Enter text...",
|
||||
"catalog.prefix.icon": "Icon",
|
||||
"catalog.prefix.icon.remove": "Remove icon",
|
||||
"catalog.prefix.effect": "Effect",
|
||||
"catalog.prefix.color": "Color",
|
||||
"catalog.prefix.color.single": "?? Single",
|
||||
"catalog.prefix.color.per.letter": "?? Per Letter",
|
||||
"catalog.prefix.color.hint": "Select a letter, then choose the color. Auto-advances.",
|
||||
"catalog.prefix.color.apply.all.title": "Apply current color to all letters",
|
||||
"catalog.prefix.color.apply.all": "Apply to all",
|
||||
"catalog.prefix.color.selected": "Selected letter:",
|
||||
"catalog.prefix.price": "Price:",
|
||||
"catalog.prefix.price.amount": "5 Credits",
|
||||
"catalog.prefix.purchased": "? Purchased!",
|
||||
"catalog.prefix.purchase": "Purchase",
|
||||
"groupforum.list.tab.most_active": "Most active threads",
|
||||
"groupforum.list.tab.my_forums": "My group forums",
|
||||
"groupforum.list.no_forums": "There are no forums",
|
||||
"groupforum.view.threads": "Number of threads",
|
||||
"groupforum.thread.pin": "Pin thread",
|
||||
"groupforum.thread.unpin": "Unpin thread",
|
||||
"groupforum.thread.lock": "Lock thread",
|
||||
"groupforum.thread.unlock": "Unlock thread",
|
||||
"groupforum.thread.hide": "Hide thread",
|
||||
"groupforum.thread.restore": "Restore thread",
|
||||
"groupforum.thread.delete": "Delete thread + posts",
|
||||
"groupforum.message.hide": "Hide message",
|
||||
"group.forum.enable.caption": "Enable / Disable group forum",
|
||||
"group.forum.enable.help": "If you disable the group forum, all posts will also be deleted!",
|
||||
"groupforum.view.no_threads": "There are currently no active threads"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"slot": "### SLOT ID FROM GOOGLE - data-ad-slot ###",
|
||||
"format": "auto",
|
||||
"fullWidthResponsive": true
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
(() => {
|
||||
const ASSET_KEY = new TextEncoder().encode("slogga-dist-assets-2026");
|
||||
const MODE_DEFAULTS = {
|
||||
distObfuscationEnabled: true,
|
||||
secureAssetsEnabled: true,
|
||||
secureApiEnabled: true
|
||||
};
|
||||
|
||||
const isDebug = () => {
|
||||
try {
|
||||
const search = new URLSearchParams(location.search);
|
||||
return search.get("loaderDebug") === "1" || localStorage.getItem("nitro.loader.debug") === "1";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const debug = (message) => {
|
||||
try {
|
||||
window.__nitroLoaderDebug = message;
|
||||
const log = Array.isArray(window.__nitroLoaderDebugLog) ? window.__nitroLoaderDebugLog : [];
|
||||
log.push(message);
|
||||
window.__nitroLoaderDebugLog = log.slice(-30);
|
||||
if(!isDebug()) {
|
||||
document.getElementById("nitro-loader-debug")?.remove();
|
||||
return;
|
||||
}
|
||||
let node = document.getElementById("nitro-loader-debug");
|
||||
if(!node) {
|
||||
node = document.createElement("div");
|
||||
node.id = "nitro-loader-debug";
|
||||
node.style.cssText = "position:fixed;left:8px;top:8px;z-index:2147483647;padding:6px 8px;max-width:70vw;background:rgba(0,0,0,.85);color:#fff;font:12px monospace;white-space:pre-wrap";
|
||||
document.body.appendChild(node);
|
||||
}
|
||||
node.textContent = window.__nitroLoaderDebugLog.slice(-10).join("\n");
|
||||
} catch {}
|
||||
};
|
||||
|
||||
const getBase = () => {
|
||||
const source = document.currentScript?.src || location.href;
|
||||
return new URL(".", source);
|
||||
};
|
||||
|
||||
const withCacheBust = (url) => {
|
||||
url.searchParams.set("v", Date.now().toString(36));
|
||||
return url;
|
||||
};
|
||||
|
||||
const renderShell = () => {
|
||||
const root = document.getElementById("root");
|
||||
if(!root || root.firstChild) return;
|
||||
root.innerHTML = '<div style="position:fixed;inset:0;background:linear-gradient(180deg,#6eadc8 0%,#78b7cf 45%,#8ec4d7 100%);overflow:hidden;z-index:1"><div style="position:absolute;left:0;top:0;width:220px;height:220px;background:linear-gradient(135deg,rgba(255,255,255,.18),rgba(255,255,255,0));clip-path:polygon(0 0,100% 0,0 100%)"></div><div style="position:absolute;right:0;bottom:0;width:32vw;max-width:420px;height:100%;background:linear-gradient(270deg,rgba(255,255,255,.16),rgba(255,255,255,0))"></div><div style="position:absolute;top:50%;right:8vw;transform:translateY(-50%);display:flex;flex-direction:column;gap:18px;width:260px"><div style="height:86px;background:#a2bfd1;border:2px solid #3f6a85;border-radius:8px;box-shadow:inset 0 2px rgba(255,255,255,.35),0 4px 6px rgba(0,0,0,.25)"></div><div style="height:190px;background:#a2bfd1;border:2px solid #3f6a85;border-radius:8px;box-shadow:inset 0 2px rgba(255,255,255,.35),0 4px 6px rgba(0,0,0,.25)"></div></div></div>';
|
||||
};
|
||||
|
||||
const decodeAsset = (bytes) => {
|
||||
const output = new Uint8Array(bytes.length);
|
||||
for(let index = 0; index < bytes.length; index++) {
|
||||
output[index] = bytes[index] ^ ASSET_KEY[index % ASSET_KEY.length] ^ ((index * 31) & 255);
|
||||
}
|
||||
return output;
|
||||
};
|
||||
|
||||
const gunzip = async (bytes) => {
|
||||
if(!("DecompressionStream" in self)) throw new Error("gzip decompression unsupported");
|
||||
const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip"));
|
||||
return new Uint8Array(await new Response(stream).arrayBuffer());
|
||||
};
|
||||
|
||||
const resolveAssetCandidates = (path) => {
|
||||
const base = getBase();
|
||||
const normalized = path.replace(/^\.\//, "");
|
||||
const file = normalized.split("/").pop();
|
||||
const urls = [
|
||||
new URL("./src/assets/" + file, base),
|
||||
new URL("./assets/" + file, base),
|
||||
new URL("/src/assets/" + file, base.origin),
|
||||
new URL("/assets/" + file, base.origin),
|
||||
new URL("/client/src/assets/" + file, base.origin),
|
||||
new URL("/client/assets/" + file, base.origin)
|
||||
];
|
||||
return [...new Map(urls.map(url => [url.href, url])).values()];
|
||||
};
|
||||
|
||||
const fetchBytes = async (path) => {
|
||||
let error = null;
|
||||
debug("loader: fetching " + path);
|
||||
for(const candidate of resolveAssetCandidates(path)) {
|
||||
try {
|
||||
debug("loader: try " + candidate.href);
|
||||
const response = await fetch(withCacheBust(candidate), { cache: "no-store" });
|
||||
if(!response.ok) {
|
||||
error = new Error("asset " + candidate.pathname + " " + response.status);
|
||||
continue;
|
||||
}
|
||||
debug("loader: ok " + candidate.href);
|
||||
return new Uint8Array(await response.arrayBuffer());
|
||||
} catch(caught) {
|
||||
error = caught;
|
||||
}
|
||||
}
|
||||
throw error || new Error("asset " + path + " not found");
|
||||
};
|
||||
|
||||
const loadDatAsset = async (path) => gunzip(decodeAsset(await fetchBytes(path)));
|
||||
|
||||
const injectCssText = (bytes) => {
|
||||
const node = document.createElement("style");
|
||||
node.textContent = new TextDecoder().decode(bytes);
|
||||
document.head.appendChild(node);
|
||||
debug("loader: css injected from dat");
|
||||
};
|
||||
|
||||
const loadPlainCss = async (path) => {
|
||||
const href = resolveAssetCandidates(path)[0];
|
||||
href.searchParams.set("v", Date.now().toString(36));
|
||||
await new Promise((resolve, reject) => {
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.href = href.href;
|
||||
link.onload = () => resolve();
|
||||
link.onerror = () => reject(new Error("plain css failed"));
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
debug("loader: css linked");
|
||||
};
|
||||
|
||||
const importBytes = async (bytes) => {
|
||||
const blobUrl = URL.createObjectURL(new Blob([bytes], { type: "text/javascript" }));
|
||||
try {
|
||||
debug("loader: importing app blob");
|
||||
await import(blobUrl);
|
||||
debug("loader: app blob imported");
|
||||
} finally {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const importPlainJs = async (path) => {
|
||||
const href = resolveAssetCandidates(path)[0];
|
||||
href.searchParams.set("v", Date.now().toString(36));
|
||||
debug("loader: importing plain js");
|
||||
await import(href.href);
|
||||
debug("loader: plain js imported");
|
||||
};
|
||||
|
||||
const readClientMode = async () => {
|
||||
try {
|
||||
if(window.__nitroClientMode && typeof window.__nitroClientMode === "object") {
|
||||
debug("loader: client-mode preset");
|
||||
return window.__nitroClientMode;
|
||||
}
|
||||
const url = withCacheBust(new URL("./client-mode.json", getBase()));
|
||||
const response = await fetch(url, { cache: "no-store" });
|
||||
if(!response.ok) throw new Error("client-mode " + response.status);
|
||||
const payload = await response.json();
|
||||
const mode = { ...MODE_DEFAULTS, ...(payload && typeof payload === "object" ? payload : {}) };
|
||||
window.__nitroClientMode = mode;
|
||||
debug("loader: client-mode loaded");
|
||||
return mode;
|
||||
} catch(error) {
|
||||
window.__nitroClientMode = { ...MODE_DEFAULTS };
|
||||
debug("loader: client-mode fallback " + (error?.message || error));
|
||||
return window.__nitroClientMode;
|
||||
}
|
||||
};
|
||||
|
||||
(async () => {
|
||||
debug("loader: start");
|
||||
renderShell();
|
||||
const mode = await readClientMode();
|
||||
if(mode.distObfuscationEnabled) {
|
||||
const [cssBytes, jsBytes] = await Promise.all([
|
||||
loadDatAsset("./assets/app.css.dat"),
|
||||
loadDatAsset("./assets/app.js.dat")
|
||||
]);
|
||||
injectCssText(cssBytes);
|
||||
await importBytes(jsBytes);
|
||||
return;
|
||||
}
|
||||
await loadPlainCss("./assets/app.css");
|
||||
await importPlainJs("./assets/app.js");
|
||||
})().catch(error => {
|
||||
console.error(error);
|
||||
debug("loader: failed " + (error?.message || error));
|
||||
document.body.textContent = "Unable to load client.";
|
||||
});
|
||||
})();
|
||||
Vendored
+133
@@ -0,0 +1,133 @@
|
||||
(() => {
|
||||
const API_BASE = "https://nitro.slogga.it:2096";
|
||||
|
||||
const getBase = () => {
|
||||
const source = document.currentScript?.src || location.href;
|
||||
return new URL(".", source);
|
||||
};
|
||||
|
||||
const withCacheBust = (url) => {
|
||||
url.searchParams.set("v", Date.now().toString(36));
|
||||
return url;
|
||||
};
|
||||
|
||||
const bytesToBase64 = (buffer) => {
|
||||
let binary = "";
|
||||
const bytes = new Uint8Array(buffer);
|
||||
for(let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
||||
return btoa(binary);
|
||||
};
|
||||
|
||||
const hexValue = (code) => {
|
||||
if(code >= 48 && code <= 57) return code - 48;
|
||||
if(code >= 65 && code <= 70) return code - 55;
|
||||
if(code >= 97 && code <= 102) return code - 87;
|
||||
return -1;
|
||||
};
|
||||
|
||||
const hexToBytes = (hex) => {
|
||||
const normalized = hex.trim();
|
||||
if((normalized.length % 2) !== 0) throw new Error("Invalid encrypted hex payload.");
|
||||
const bytes = new Uint8Array(normalized.length / 2);
|
||||
for(let i = 0; i < bytes.length; i++) {
|
||||
const high = hexValue(normalized.charCodeAt(i * 2));
|
||||
const low = hexValue(normalized.charCodeAt((i * 2) + 1));
|
||||
if(high < 0 || low < 0) throw new Error("Invalid encrypted hex payload.");
|
||||
bytes[i] = (high << 4) | low;
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
const deriveAesKey = async (privateKey, serverKeyBase64) => {
|
||||
const serverBytes = Uint8Array.from(atob(serverKeyBase64), char => char.charCodeAt(0));
|
||||
const serverKey = await crypto.subtle.importKey("spki", serverBytes, { name: "ECDH", namedCurve: "P-256" }, false, []);
|
||||
const secret = await crypto.subtle.deriveBits({ name: "ECDH", public: serverKey }, privateKey, 256);
|
||||
const salt = new TextEncoder().encode("nitro-secure-assets-v1");
|
||||
const material = new Uint8Array(secret.byteLength + salt.length);
|
||||
material.set(new Uint8Array(secret), 0);
|
||||
material.set(salt, secret.byteLength);
|
||||
const hash = await crypto.subtle.digest("SHA-256", material);
|
||||
return crypto.subtle.importKey("raw", hash, "AES-GCM", false, ["decrypt"]);
|
||||
};
|
||||
|
||||
const decryptPayload = async (key, response) => {
|
||||
if(response.headers.get("X-Nitro-Sec") !== "1") return response.text();
|
||||
const bytes = hexToBytes(await response.text());
|
||||
if(bytes.length < 13) throw new Error("Encrypted response is too short.");
|
||||
const iv = bytes.slice(0, 12);
|
||||
const payload = bytes.slice(12);
|
||||
const clear = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, payload);
|
||||
return new TextDecoder().decode(clear);
|
||||
};
|
||||
|
||||
const importTextModule = async (sourceText) => {
|
||||
const blobUrl = URL.createObjectURL(new Blob([sourceText], { type: "text/javascript" }));
|
||||
try {
|
||||
await import(blobUrl);
|
||||
} finally {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
};
|
||||
|
||||
const loadPlainBootstrap = async () => {
|
||||
const url = withCacheBust(new URL("./asset-loader.js", getBase()));
|
||||
await import(url.href);
|
||||
};
|
||||
|
||||
const loadSecureBootstrap = async () => {
|
||||
if(!API_BASE) throw new Error("Missing apiBaseUrl for secure bootstrap.");
|
||||
|
||||
const pair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveBits"]);
|
||||
const publicKeyBuffer = await crypto.subtle.exportKey("spki", pair.publicKey);
|
||||
const publicKey = bytesToBase64(publicKeyBuffer);
|
||||
const base = API_BASE.replace(/\/$/, "");
|
||||
const bootstrapResponse = await fetch(base + "/nitro-sec/bootstrap", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ key: publicKey })
|
||||
});
|
||||
|
||||
if(!bootstrapResponse.ok) throw new Error("Secure bootstrap failed: HTTP " + bootstrapResponse.status);
|
||||
|
||||
const bootstrapPayload = await bootstrapResponse.json();
|
||||
if(!bootstrapPayload || typeof bootstrapPayload.key !== "string" || !bootstrapPayload.key.length) {
|
||||
throw new Error("Secure bootstrap returned an invalid server key.");
|
||||
}
|
||||
|
||||
const sessionKey = await deriveAesKey(pair.privateKey, bootstrapPayload.key);
|
||||
|
||||
const fetchSecureConfig = async (file) => {
|
||||
const url = new URL(base + "/nitro-sec/file");
|
||||
url.searchParams.set("kind", "config");
|
||||
url.searchParams.set("file", file);
|
||||
url.searchParams.set("v", Date.now().toString(36));
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: { "X-Nitro-Key": publicKey },
|
||||
cache: "no-store"
|
||||
});
|
||||
|
||||
if(!response.ok) throw new Error("Failed to load secure config " + file + ": HTTP " + response.status);
|
||||
|
||||
return decryptPayload(sessionKey, response);
|
||||
};
|
||||
|
||||
const modeText = await fetchSecureConfig("client-mode.json");
|
||||
window.__nitroClientMode = JSON.parse(modeText);
|
||||
|
||||
const loaderText = await fetchSecureConfig("asset-loader.js");
|
||||
await importTextModule(loaderText);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await loadSecureBootstrap();
|
||||
} catch(error) {
|
||||
console.warn("[Nitro] Secure bootstrap fallback:", error?.message || error);
|
||||
await loadPlainBootstrap();
|
||||
}
|
||||
})().catch(error => {
|
||||
console.error(error);
|
||||
document.body.textContent = "Unable to load client.";
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"distObfuscationEnabled": true,
|
||||
"secureAssetsEnabled": true,
|
||||
"secureApiEnabled": true,
|
||||
"apiBaseUrl": "https://nitro.example.com:2096",
|
||||
"plainConfigBaseUrl": "https://hotel.example.com/configuration/",
|
||||
"plainGamedataBaseUrl": "https://hotel.example.com/client/nitro/gamedata/"
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
[
|
||||
{
|
||||
"_gender": "m",
|
||||
"_figure": "hr-155-40.hd-180-10.ch-255-1408.lg-280-64.sh-290-64.ha-1003-64",
|
||||
"_hash": "b5d1a24d16c9d516b3d793c66d152b77"
|
||||
},
|
||||
{
|
||||
"_gender": "f",
|
||||
"_figure": "hr-515-34.hd-629-8.ch-665-1408.lg-715-1320.sh-740-1408.he-1608",
|
||||
"_hash": "694573ec86cf5346f1c88b1017f069f8"
|
||||
},
|
||||
{
|
||||
"_gender": "f",
|
||||
"_figure": "hr-890-36.hd-629-8.ch-685-71.lg-715-71.sh-3068-71-73.ha-1018.fa-1202-71.ca-1802",
|
||||
"_hash": "10b9a935209e6c213e54108474186dc8"
|
||||
},
|
||||
{
|
||||
"_gender": "m",
|
||||
"_figure": "hr-115-42.hd-209-1.ch-255-73.lg-3078-82.sh-300-64",
|
||||
"_hash": "1457ce2369b982bcce30e8307c005d98"
|
||||
},
|
||||
{
|
||||
"_gender": "m",
|
||||
"_figure": "hr-115-40.hd-190-14.ch-235-1408.lg-280-1408.sh-908-1408.he-1608",
|
||||
"_hash": "d35b7492386963d7612341b222f7f5d9"
|
||||
},
|
||||
{
|
||||
"_gender": "m",
|
||||
"_figure": "hr-115-31.hd-180-14.ch-210-64.lg-3023-91.sh-300-91",
|
||||
"_hash": "b49e529b7604fbd3596951bc69d6551b"
|
||||
},
|
||||
{
|
||||
"_gender": "m",
|
||||
"_figure": "hr-100.hd-180-1.ch-210-1408.lg-270-64.sh-300-64.ha-1002-64.cc-260-64",
|
||||
"_hash": "f052b0ccc54cfa933d473b433b154ef5"
|
||||
},
|
||||
{
|
||||
"_gender": "m",
|
||||
"_figure": "hr-125-34.hd-205-14.ch-235-1408.lg-285-81.sh-300-64.wa-3211-64-64",
|
||||
"_hash": "08c77292a4462c36f0393820d5753de3"
|
||||
},
|
||||
{
|
||||
"_gender": "f",
|
||||
"_figure": "hr-890-31.hd-600-1.ch-822-71.lg-715-74.he-1602-71",
|
||||
"_hash": "4102a76da4bca25d5125b75d9ea1ca14"
|
||||
},
|
||||
{
|
||||
"_gender": "f",
|
||||
"_figure": "hr-515-35.hd-628-14.ch-667.lg-696-73.he-1606-82.ca-1810.cp-3124-81",
|
||||
"_hash": "4987ff565ec8e6ecedb31b08c2b017a6"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"socket.url": "wss://nitro.example.com:2096",
|
||||
"api.url": "https://nitro.example.com:2096",
|
||||
"asset.url": "https://hotel.example.com/client/nitro/bundled",
|
||||
"image.library.url": "https://hotel.example.com/client/c_images/",
|
||||
"hof.furni.url": "https://hotel.example.com/client/c_images/dcr/hof_furni",
|
||||
"images.url": "https://hotel.example.com/client/nitro/images",
|
||||
"gamedata.url": "https://nitro.example.com:2096/nitro-sec/file?kind=gamedata&file=",
|
||||
"sounds.url": "${asset.url}/sounds/%sample%.mp3",
|
||||
"external.texts.url": [
|
||||
"${gamedata.url}/ExternalTexts.json",
|
||||
"${gamedata.url}/UITexts.json"
|
||||
],
|
||||
"external.texts.translation.url": "${gamedata.url}/text_translate/ExternalTexts_%locale%.json?t=%timestamp%",
|
||||
"external.samples.url": "${hof.furni.url}/mp3/sound_machine_sample_%sample%.mp3",
|
||||
"furnidata.url": "${gamedata.url}/FurnitureData.json?t=%timestamp%",
|
||||
"furnidata.translation.url": "${gamedata.url}/furniture_translate/FurnitureData_%locale%.json?t=%timestamp%",
|
||||
"productdata.url": "${gamedata.url}/ProductData.json?t=%timestamp%",
|
||||
"avatar.actions.url": "${gamedata.url}/HabboAvatarActions.json?t=%timestamp%",
|
||||
"avatar.figuredata.url": "${gamedata.url}/FigureData.json?t=%timestamp%",
|
||||
"avatar.figuremap.url": "${gamedata.url}/FigureMap.json?t=%timestamp%",
|
||||
"avatar.effectmap.url": "${gamedata.url}/EffectMap.json?t=%timestamp%",
|
||||
"avatar.asset.url": "${asset.url}/figure/%libname%.nitro",
|
||||
"avatar.asset.effect.url": "${asset.url}/effect/%libname%.nitro",
|
||||
"furni.asset.url": "${asset.url}/furniture/%libname%.nitro",
|
||||
"furni.asset.icon.url": "${hof.furni.url}/icons/%libname%%param%_icon.png",
|
||||
"pet.asset.url": "${asset.url}/pets/%libname%.nitro",
|
||||
"generic.asset.url": "${asset.url}/generic/%libname%.nitro",
|
||||
"badge.asset.url": "${image.library.url}album1584/%badgename%.gif",
|
||||
"furni.rotation.bounce.steps": 20,
|
||||
"furni.rotation.bounce.height": 0.0625,
|
||||
"enable.avatar.arrow": false,
|
||||
"system.log.debug": true,
|
||||
"system.log.warn": true,
|
||||
"system.log.error": true,
|
||||
"system.log.events": false,
|
||||
"system.log.packets": true,
|
||||
"system.fps.animation": 24,
|
||||
"system.fps.max": 60,
|
||||
"system.pong.manually": true,
|
||||
"system.pong.interval.ms": 20000,
|
||||
"room.color.skip.transition": true,
|
||||
"room.landscapes.enabled": true,
|
||||
"room.zoom.enabled": true,
|
||||
"login.screen.enabled": true,
|
||||
"login.endpoint": "${api.url}/api/auth/login",
|
||||
"login.register.endpoint": "${api.url}/api/auth/register",
|
||||
"login.forgot.endpoint": "${api.url}/api/auth/forgot-password",
|
||||
"login.logout.endpoint": "${api.url}/api/auth/logout",
|
||||
"login.remember.endpoint": "${api.url}/api/auth/remember",
|
||||
"login.turnstile.enabled": false,
|
||||
"login.turnstile.sitekey": ""
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user