Add secure configuration bootstrap flow

This commit is contained in:
Lorenzune
2026-04-25 13:29:48 +02:00
parent 6c7d78c156
commit 3c9a599505
27 changed files with 962 additions and 3616 deletions
-113
View File
@@ -1,113 +0,0 @@
{
"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"
}
-8
View File
@@ -1,8 +0,0 @@
{
"distObfuscationEnabled": true,
"secureAssetsEnabled": true,
"secureApiEnabled": true,
"apiBaseUrl": "",
"plainConfigBaseUrl": "",
"plainGamedataBaseUrl": ""
}
+116
View File
@@ -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"
}
@@ -145,6 +145,10 @@
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);
+133
View File
@@ -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.";
});
})();
@@ -3,6 +3,6 @@
"secureAssetsEnabled": true,
"secureApiEnabled": true,
"apiBaseUrl": "https://nitro.example.com:2096",
"plainConfigBaseUrl": "https://hotel.example.com/",
"plainConfigBaseUrl": "https://hotel.example.com/configuration/",
"plainGamedataBaseUrl": "https://hotel.example.com/client/nitro/gamedata/"
}
-598
View File
@@ -1,598 +0,0 @@
{
"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": "",
"avatar.mandatory.libraries": [
"bd:1",
"li:0"
],
"avatar.mandatory.effect.libraries": [
"dance.1",
"dance.2",
"dance.3",
"dance.4"
],
"avatar.default.figuredata": {
"palettes": [
{
"id": 1,
"colors": [
{
"id": 99999,
"index": 1001,
"club": 0,
"selectable": false,
"hexCode": "DDDDDD"
},
{
"id": 99998,
"index": 1001,
"club": 0,
"selectable": false,
"hexCode": "FAFAFA"
}
]
},
{
"id": 3,
"colors": [
{
"id": 10001,
"index": 1001,
"club": 0,
"selectable": false,
"hexCode": "EEEEEE"
},
{
"id": 10002,
"index": 1002,
"club": 0,
"selectable": false,
"hexCode": "FA3831"
},
{
"id": 10003,
"index": 1003,
"club": 0,
"selectable": false,
"hexCode": "FD92A0"
},
{
"id": 10004,
"index": 1004,
"club": 0,
"selectable": false,
"hexCode": "2AC7D2"
},
{
"id": 10005,
"index": 1005,
"club": 0,
"selectable": false,
"hexCode": "35332C"
},
{
"id": 10006,
"index": 1006,
"club": 0,
"selectable": false,
"hexCode": "EFFF92"
},
{
"id": 10007,
"index": 1007,
"club": 0,
"selectable": false,
"hexCode": "C6FF98"
},
{
"id": 10008,
"index": 1008,
"club": 0,
"selectable": false,
"hexCode": "FF925A"
},
{
"id": 10009,
"index": 1009,
"club": 0,
"selectable": false,
"hexCode": "9D597E"
},
{
"id": 10010,
"index": 1010,
"club": 0,
"selectable": false,
"hexCode": "B6F3FF"
},
{
"id": 10011,
"index": 1011,
"club": 0,
"selectable": false,
"hexCode": "6DFF33"
},
{
"id": 10012,
"index": 1012,
"club": 0,
"selectable": false,
"hexCode": "3378C9"
},
{
"id": 10013,
"index": 1013,
"club": 0,
"selectable": false,
"hexCode": "FFB631"
},
{
"id": 10014,
"index": 1014,
"club": 0,
"selectable": false,
"hexCode": "DFA1E9"
},
{
"id": 10015,
"index": 1015,
"club": 0,
"selectable": false,
"hexCode": "F9FB32"
},
{
"id": 10016,
"index": 1016,
"club": 0,
"selectable": false,
"hexCode": "CAAF8F"
},
{
"id": 10017,
"index": 1017,
"club": 0,
"selectable": false,
"hexCode": "C5C6C5"
},
{
"id": 10018,
"index": 1018,
"club": 0,
"selectable": false,
"hexCode": "47623D"
},
{
"id": 10019,
"index": 1019,
"club": 0,
"selectable": false,
"hexCode": "8A8361"
},
{
"id": 10020,
"index": 1020,
"club": 0,
"selectable": false,
"hexCode": "FF8C33"
},
{
"id": 10021,
"index": 1021,
"club": 0,
"selectable": false,
"hexCode": "54C627"
},
{
"id": 10022,
"index": 1022,
"club": 0,
"selectable": false,
"hexCode": "1E6C99"
},
{
"id": 10023,
"index": 1023,
"club": 0,
"selectable": false,
"hexCode": "984F88"
},
{
"id": 10024,
"index": 1024,
"club": 0,
"selectable": false,
"hexCode": "77C8FF"
},
{
"id": 10025,
"index": 1025,
"club": 0,
"selectable": false,
"hexCode": "FFC08E"
},
{
"id": 10026,
"index": 1026,
"club": 0,
"selectable": false,
"hexCode": "3C4B87"
},
{
"id": 10027,
"index": 1027,
"club": 0,
"selectable": false,
"hexCode": "7C2C47"
},
{
"id": 10028,
"index": 1028,
"club": 0,
"selectable": false,
"hexCode": "D7FFE3"
},
{
"id": 10029,
"index": 1029,
"club": 0,
"selectable": false,
"hexCode": "8F3F1C"
},
{
"id": 10030,
"index": 1030,
"club": 0,
"selectable": false,
"hexCode": "FF6393"
},
{
"id": 10031,
"index": 1031,
"club": 0,
"selectable": false,
"hexCode": "1F9B79"
},
{
"id": 10032,
"index": 1032,
"club": 0,
"selectable": false,
"hexCode": "FDFF33"
}
]
}
],
"setTypes": [
{
"type": "hd",
"paletteId": 1,
"mandatory_f_0": true,
"mandatory_f_1": true,
"mandatory_m_0": true,
"mandatory_m_1": true,
"sets": [
{
"id": 99999,
"gender": "U",
"club": 0,
"colorable": true,
"selectable": false,
"preselectable": false,
"sellable": false,
"parts": [
{
"id": 1,
"type": "bd",
"colorable": true,
"index": 0,
"colorindex": 1
},
{
"id": 1,
"type": "hd",
"colorable": true,
"index": 0,
"colorindex": 1
},
{
"id": 1,
"type": "lh",
"colorable": true,
"index": 0,
"colorindex": 1
},
{
"id": 1,
"type": "rh",
"colorable": true,
"index": 0,
"colorindex": 1
}
]
}
]
},
{
"type": "bds",
"paletteId": 1,
"mandatory_f_0": false,
"mandatory_f_1": false,
"mandatory_m_0": false,
"mandatory_m_1": false,
"sets": [
{
"id": 10001,
"gender": "U",
"club": 0,
"colorable": true,
"selectable": false,
"preselectable": false,
"sellable": false,
"parts": [
{
"id": 10001,
"type": "bds",
"colorable": true,
"index": 0,
"colorindex": 1
},
{
"id": 10001,
"type": "lhs",
"colorable": true,
"index": 0,
"colorindex": 1
},
{
"id": 10001,
"type": "rhs",
"colorable": true,
"index": 0,
"colorindex": 1
}
],
"hiddenLayers": [
{
"partType": "bd"
},
{
"partType": "rh"
},
{
"partType": "lh"
}
]
}
]
},
{
"type": "ss",
"paletteId": 3,
"mandatory_f_0": false,
"mandatory_f_1": false,
"mandatory_m_0": false,
"mandatory_m_1": false,
"sets": [
{
"id": 10010,
"gender": "F",
"club": 0,
"colorable": true,
"selectable": false,
"preselectable": false,
"sellable": false,
"parts": [
{
"id": 10001,
"type": "ss",
"colorable": true,
"index": 0,
"colorindex": 1
}
],
"hiddenLayers": [
{
"partType": "ch"
},
{
"partType": "lg"
},
{
"partType": "ca"
},
{
"partType": "wa"
},
{
"partType": "sh"
},
{
"partType": "ls"
},
{
"partType": "rs"
},
{
"partType": "lc"
},
{
"partType": "rc"
},
{
"partType": "cc"
},
{
"partType": "cp"
}
]
},
{
"id": 10011,
"gender": "M",
"club": 0,
"colorable": true,
"selectable": false,
"preselectable": false,
"sellable": false,
"parts": [
{
"id": 10002,
"type": "ss",
"colorable": true,
"index": 0,
"colorindex": 1
}
],
"hiddenLayers": [
{
"partType": "ch"
},
{
"partType": "lg"
},
{
"partType": "ca"
},
{
"partType": "wa"
},
{
"partType": "sh"
},
{
"partType": "ls"
},
{
"partType": "rs"
},
{
"partType": "lc"
},
{
"partType": "rc"
},
{
"partType": "cc"
},
{
"partType": "cp"
}
]
}
]
}
]
},
"avatar.default.actions": {
"actions": [
{
"id": "Default",
"state": "std",
"precedence": 1000,
"main": true,
"isDefault": true,
"geometryType": "vertical",
"activePartSet": "figure",
"assetPartDefinition": "std"
}
]
},
"pet.types": [
"dog",
"cat",
"croco",
"terrier",
"bear",
"pig",
"lion",
"rhino",
"spider",
"turtle",
"chicken",
"frog",
"dragon",
"monster",
"monkey",
"horse",
"monsterplant",
"bunnyeaster",
"bunnyevil",
"bunnydepressed",
"bunnylove",
"pigeongood",
"pigeonevil",
"demonmonkey",
"bearbaby",
"terrierbaby",
"gnome",
"gnome",
"kittenbaby",
"puppybaby",
"pigletbaby",
"haloompa",
"fools",
"pterosaur",
"velociraptor",
"cow",
"LeetPen",
"bbwibb",
"elephants"
],
"preload.assets.urls": [
"${asset.url}/generic/avatar_additions.nitro",
"${asset.url}/generic/group_badge.nitro",
"${asset.url}/generic/floor_editor.nitro",
"${images.url}/loading_icon.png",
"${images.url}/clear_icon.png",
"${images.url}/big_arrow.png"
]
}
File diff suppressed because it is too large Load Diff