From 3c9a599505b5297ee6f7378aba2d62575ad54105 Mon Sep 17 00:00:00 2001 From: Lorenzune Date: Sat, 25 Apr 2026 13:29:48 +0200 Subject: [PATCH] Add secure configuration bootstrap flow --- .gitignore | 7 + README.md | 11 +- docs/secure-runtime-modes.en.html | 236 ++ docs/secure-runtime-modes.en.md | 44 +- docs/secure-runtime-modes.html | 236 ++ docs/secure-runtime-modes.md | 44 +- localization/badge-texts-en.json | 3 - localization/badge-texts-it.json | 3 - public/UITexts.example | 113 - public/client-mode.json | 8 - public/configuration/UITexts.example | 116 + .../adsense.example} | 0 public/{ => configuration}/asset-loader.js | 4 + public/configuration/bootstrap.js | 133 + .../client-mode.example} | 2 +- .../hotlooks.example} | 0 .../renderer-config.example | 0 public/{ => configuration}/ui-config.example | 0 public/renderer-config.json | 598 ---- public/ui-config.json | 2816 ----------------- scripts/minify-dist.mjs | 8 +- scripts/write-asset-loader.mjs | 158 +- src/bootstrap.ts | 4 +- src/components/ads/GoogleAdsView.tsx | 5 +- src/components/login/LoginView.tsx | 3 +- src/secure-assets.ts | 13 +- vite.config.mjs | 13 +- 27 files changed, 962 insertions(+), 3616 deletions(-) create mode 100644 docs/secure-runtime-modes.en.html create mode 100644 docs/secure-runtime-modes.html delete mode 100644 localization/badge-texts-en.json delete mode 100644 localization/badge-texts-it.json delete mode 100644 public/UITexts.example delete mode 100644 public/client-mode.json create mode 100644 public/configuration/UITexts.example rename public/{adsense.json => configuration/adsense.example} (100%) rename public/{ => configuration}/asset-loader.js (97%) create mode 100644 public/configuration/bootstrap.js rename public/{client-mode.example.json => configuration/client-mode.example} (76%) rename public/{hotlooks.json => configuration/hotlooks.example} (100%) rename public/{ => configuration}/renderer-config.example (100%) rename public/{ => configuration}/ui-config.example (100%) delete mode 100644 public/renderer-config.json delete mode 100644 public/ui-config.json diff --git a/.gitignore b/.gitignore index 249e7db..90a9bfe 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,10 @@ Thumbs.db *.zip .env .claude/ + +# Local runtime config copies +/public/configuration/renderer-config.json +/public/configuration/ui-config.json +/public/configuration/client-mode.json +/public/configuration/adsense.json +/public/configuration/hotlooks.json diff --git a/README.md b/README.md index a733fba..bc91d90 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,13 @@ - `yarn install` - `yarn link "@nitrots/nitro-renderer"` <== This will link the renderer in the project - Rename a few files - - Rename `public/renderer-config.json.example` to `public/renderer-config.json` - - Rename `public/ui-config.json.example` to `public/ui-config.json` -- Set your links - - Open `public/renderer-config.json` + - Copy `public/configuration/renderer-config.example` to `public/configuration/renderer-config.json` + - Copy `public/configuration/ui-config.example` to `public/configuration/ui-config.json` + - Copy `public/configuration/client-mode.example` to `public/configuration/client-mode.json` + - Set your links + - Open `public/configuration/renderer-config.json` - Update `socket.url, asset.url, image.library.url, & hof.furni.url` - - Open `public/ui-config.json` + - Open `public/configuration/ui-config.json` - Update `camera.url, thumbnails.url, url.prefix, habbopages.url` - `yarn build` <== the final step to build the DIST folder this is where your browser needs to point / or upload this to your /client if you do the compile on a other machine (preferd) - You can override any variable by passing it to `NitroConfig` in the index.html diff --git a/docs/secure-runtime-modes.en.html b/docs/secure-runtime-modes.en.html new file mode 100644 index 0000000..491608d --- /dev/null +++ b/docs/secure-runtime-modes.en.html @@ -0,0 +1,236 @@ + + + + + + Nitro Secure Runtime Modes + + + +
+
+
+ Nitro V3 + Secure Runtime +
+

Runtime configuration guide

+

+ This page gives you a cleaner, readable overview of runtime toggles, example files and the values that belong in config files + rather than hardcoded inside src. +

+
+ +
+ + +
+
+

Overview

+
+
+

Dist Obfuscation

+

Chooses whether the client loads app.js/app.css or the obfuscated .dat versions.

+
+
+

Secure Assets

+

Controls whether renderer-config, ui-config and gamedata go through /nitro-sec/file.

+
+
+

Secure API

+

Enables or disables runtime encryption for /api/* requests.

+
+
+
+ +
+

Files to use

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FilePurposeNote
public/configuration/client-mode.exampleTemplate for runtime togglesCopy it into a real configuration/client-mode.json in deployment; that real file stays ignored by Git
public/configuration/renderer-config.exampleClean renderer config templateDoes not touch your local configuration/renderer-config.json
public/configuration/ui-config.exampleUI config reference templateUse it as the source of truth for UI URLs and widgets
Latest_Compiled_Version/config.ini.exampleBackend secure flagsDefines the emulator-side runtime settings
+
+
+ +
+

client-mode.example

+

This is the main runtime switchboard. You can enable or disable behavior without editing client source code.

+
{
+    "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/"
+}
+
+
+

Fields

+
    +
  • distObfuscationEnabled: use .dat or plain assets
  • +
  • secureAssetsEnabled: enables /nitro-sec/file
  • +
  • secureApiEnabled: encrypts /api/* requests
  • +
  • apiBaseUrl: emulator/API base URL
  • +
+
+
+

Recommendation

+

Always set apiBaseUrl explicitly so you do not rely on fallback logic.

+
+
+
+ +
+

renderer-config.example

+

Socket, API, asset and gamedata URLs should live here, not inside React components.

+
+
+

Main keys

+
    +
  • socket.url
  • +
  • api.url
  • +
  • asset.url
  • +
  • image.library.url
  • +
  • images.url
  • +
  • gamedata.url
  • +
+
+
+

Translations

+
    +
  • external.texts.translation.url
  • +
  • furnidata.translation.url
  • +
  • Uses %locale% and %timestamp%
  • +
+
+
+
+ +
+

ui-config.example

+

UI image and login view sources should come from config values here or from renderer config, never from hardcoded URLs in components.

+
+

Login view

+
    +
  • loginview.images.background
  • +
  • loginview.images.drape
  • +
  • loginview.images.left
  • +
  • loginview.images.right
  • +
  • loginview.widgets for promotional blocks
  • +
+
+
+ +
+

Runtime code involved

+
+
+

src/bootstrap.ts

+

Reads client-mode, builds NitroConfig['config.urls'] and prepares client bootstrap.

+
+
+

src/secure-assets.ts

+

Handles ECDH, decrypt/encrypt, plain fallback and secure API runtime behavior.

+
+
+

scripts/write-asset-loader.mjs

+

Generates public/configuration/asset-loader.js and decides between plain assets and .dat.

+
+
+

scripts/minify-dist.mjs

+

Generates .dat files while keeping plain files available for runtime switching.

+
+
+
+ +
+

Emulator

+
nitro.secure.assets.enabled=true
+nitro.secure.api.enabled=true
+nitro.secure.config.root=C:/path/to/Nitro-V3/public
+nitro.secure.gamedata.root=C:/path/to/gamedata
+nitro.secure.master_key=change-me-to-a-long-random-secret
+
    +
  • nitro.secure.assets.enabled: enables /nitro-sec/bootstrap and /nitro-sec/file
  • +
  • nitro.secure.api.enabled: enables secure handling for /api/*
  • +
  • nitro.secure.config.root: path to live config files
  • +
  • nitro.secure.gamedata.root: path to live gamedata
  • +
  • nitro.secure.master_key: persistent server-side secret
  • +
+
+ +
+

Quick scenarios

+
+
+

Everything enabled

+

Secure assets, secure API and dist obfuscation all enabled.

+
+
+

Only .dat

+

Uses obfuscated assets but leaves config/API in plain mode.

+
+
+

Everything plain

+

Complete fallback mode for local testing or debugging.

+
+
+
+ +
+

Final checklist

+
+
You created real files from client-mode.example, renderer-config.example and ui-config.example
+
Public URLs live in config files, not in React components
+
Both plain files and .dat files are deployed
+
Your server exposes a proper MIME type for .dat
+
You set nitro.secure.master_key on the emulator side
+
+
+
+
+
+ + + + diff --git a/docs/secure-runtime-modes.en.md b/docs/secure-runtime-modes.en.md index 5ff441e..3bac47f 100644 --- a/docs/secure-runtime-modes.en.md +++ b/docs/secure-runtime-modes.en.md @@ -3,11 +3,11 @@ This document summarizes all values you may need to configure for: - `dist` bundle obfuscation (`app.js` / `app.css` → `.dat`) -- secure runtime assets (`renderer-config.json`, `ui-config.json`, `gamedata`) +- secure runtime assets (`configuration/renderer-config.json`, `configuration/ui-config.json`, `gamedata`) - secure runtime API (`/api/*`) - plain fallbacks when you want to disable the secure layer without removing the code -## 1. `Nitro-V3/public/client-mode.json` +## 1. `Nitro-V3/public/configuration/client-mode.json` This file controls everything at runtime. @@ -17,7 +17,7 @@ This file controls everything at runtime. "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/" } ``` @@ -30,7 +30,7 @@ This file controls everything at runtime. - `secureAssetsEnabled` - `true`: `bootstrap.ts` and `secure-assets.ts` use `/nitro-sec/file` - - `false`: `renderer-config.json`, `ui-config.json`, and gamedata are loaded in plain mode + - `false`: `configuration/renderer-config.json`, `configuration/ui-config.json`, and gamedata are loaded in plain mode - `secureApiEnabled` - `true`: the `fetch` wrapper encrypts `/api/*` requests @@ -43,7 +43,7 @@ This file controls everything at runtime. - `plainConfigBaseUrl` - base URL for plain config files - - usually: `https://hotel.example.com/` + - usually: `https://hotel.example.com/configuration/` - `plainGamedataBaseUrl` - base URL for plain gamedata files @@ -74,7 +74,7 @@ The current fallback is: (window as any).NitroSecureApiUrl = clientMode.apiBaseUrl || 'https://nitro.example.com:2096/'; ``` -So in production it is better to always set `apiBaseUrl` inside `client-mode.json`. +So in production it is better to always set `apiBaseUrl` inside `configuration/client-mode.json`. ## 3. `Nitro-V3/src/secure-assets.ts` @@ -95,7 +95,7 @@ This file contains the runtime logic for: Normally you should not need to touch it unless you want to change the secure protocol itself. -## 4. `Nitro-V3/public/renderer-config.json` +## 4. `Nitro-V3/public/configuration/renderer-config.json` This file still defines the paths used by the renderer. @@ -129,7 +129,7 @@ You can use plain classic paths, for example: or you can keep the renderer config as-is and let `secure-assets.ts` handle the fallback conversion. -## 5. `Nitro-V3/public/ui-config.json` +## 5. `Nitro-V3/public/configuration/ui-config.json` There is no secure logic here, but it is one of the files loaded through `config.urls`. @@ -140,12 +140,12 @@ So you only need to maintain the content itself correctly. ## 6. `Nitro-V3/scripts/write-asset-loader.mjs` -This script generates `public/asset-loader.js`. +This script generates `public/configuration/asset-loader.js`. ### What it does now - renders the initial shell -- reads `client-mode.json` +- reads `configuration/client-mode.json` - decides whether to load: - `app.css.dat` / `app.js.dat` - or `assets/app.css` / `assets/app.js` @@ -194,7 +194,7 @@ nitro.secure.master_key=change-me-to-a-long-random-secret - enables the secure layer for `/api/*` - `nitro.secure.config.root` - - folder used to read `renderer-config.json` and `ui-config.json` + - folder used to read `configuration/renderer-config.json` and `configuration/ui-config.json` - `nitro.secure.gamedata.root` - folder used to read live gamedata @@ -207,7 +207,7 @@ nitro.secure.master_key=change-me-to-a-long-random-secret ### Everything enabled -`client-mode.json` +`configuration/client-mode.json` ```json { @@ -215,7 +215,7 @@ nitro.secure.master_key=change-me-to-a-long-random-secret "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/" } ``` @@ -232,7 +232,7 @@ nitro.secure.master_key=a-long-random-secret ### `.dat` only, no secure assets/API -`client-mode.json` +`configuration/client-mode.json` ```json { @@ -240,7 +240,7 @@ nitro.secure.master_key=a-long-random-secret "secureAssetsEnabled": false, "secureApiEnabled": false, "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/" } ``` @@ -254,7 +254,7 @@ nitro.secure.api.enabled=false ### Everything plain -`client-mode.json` +`configuration/client-mode.json` ```json { @@ -262,7 +262,7 @@ nitro.secure.api.enabled=false "secureAssetsEnabled": false, "secureApiEnabled": false, "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/" } ``` @@ -273,9 +273,9 @@ nitro.secure.api.enabled=false For changes to: -- `client-mode.json` -- `renderer-config.json` -- `ui-config.json` +- `configuration/client-mode.json` +- `configuration/renderer-config.json` +- `configuration/ui-config.json` - live gamedata - `config.ini` @@ -298,10 +298,12 @@ To make the toggles work properly: ## 12. Quick checklist -- `client-mode.json` configured +- `configuration/client-mode.json` configured - `apiBaseUrl` correct - `nitro.secure.master_key` set - `nitro.secure.config.root` correct - `nitro.secure.gamedata.root` correct - both `.dat` and plain files deployed - `.dat` MIME type configured on the web server + + diff --git a/docs/secure-runtime-modes.html b/docs/secure-runtime-modes.html new file mode 100644 index 0000000..d54c635 --- /dev/null +++ b/docs/secure-runtime-modes.html @@ -0,0 +1,236 @@ + + + + + + Nitro Secure Runtime Modes + + + +
+
+
+ Nitro V3 + Secure Runtime +
+

Documentazione configurazione runtime

+

+ Questa pagina riassume in modo ordinato come configurare i toggle runtime, i file example e i parametri lato client / emulatore + senza sporcare i componenti in src. +

+
+ +
+ + +
+
+

Overview

+
+
+

Dist Obfuscation

+

Sceglie se caricare app.js/app.css oppure .dat.

+
+
+

Secure Assets

+

Controlla se renderer-config, ui-config e gamedata passano da /nitro-sec/file.

+
+
+

Secure API

+

Attiva o disattiva la cifratura runtime automatica su /api/*.

+
+
+
+ +
+

File da usare

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileScopoNota
public/configuration/client-mode.exampleTemplate per i toggle runtimeDa copiare in configuration/client-mode.json nel deploy reale, che resta ignorato da Git
public/configuration/renderer-config.exampleTemplate sicuro del renderer configNon tocca il tuo configuration/renderer-config.json locale
public/configuration/ui-config.exampleTemplate UI configDa mantenere come riferimento pulito
Latest_Compiled_Version/config.ini.exampleFlag backend secureSpecifica la parte lato emulatore
+
+
+ +
+

client-mode.example

+

È il punto centrale per attivare o disattivare il comportamento runtime senza dover modificare il codice.

+
{
+    "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/"
+}
+
+
+

Campi

+
    +
  • distObfuscationEnabled: usa .dat oppure file plain
  • +
  • secureAssetsEnabled: attiva /nitro-sec/file
  • +
  • secureApiEnabled: cifra le richieste /api/*
  • +
  • apiBaseUrl: base URL emulatore/API
  • +
+
+
+

Suggerimento

+

Conviene impostare sempre apiBaseUrl in modo esplicito, così non dipendi da fallback impliciti del runtime.

+
+
+
+ +
+

renderer-config.example

+

Qui definisci URL di socket, API, asset library e gamedata. Tutti i link pubblici dovrebbero vivere qui, non nei componenti React.

+
+
+

Chiavi principali

+
    +
  • socket.url
  • +
  • api.url
  • +
  • asset.url
  • +
  • image.library.url
  • +
  • images.url
  • +
  • gamedata.url
  • +
+
+
+

Traduzioni

+
    +
  • external.texts.translation.url
  • +
  • furnidata.translation.url
  • +
  • Usano %locale% e %timestamp%
  • +
+
+
+
+ +
+

ui-config.example

+

Per la login view e altre immagini UI, la sorgente deve stare qui o in renderer config, non hardcoded nei componenti.

+
+

Login view

+
    +
  • loginview.images.background
  • +
  • loginview.images.drape
  • +
  • loginview.images.left
  • +
  • loginview.images.right
  • +
  • loginview.widgets per i blocchi promozionali
  • +
+
+
+ +
+

Codice runtime coinvolto

+
+
+

src/bootstrap.ts

+

Legge client-mode, costruisce NitroConfig['config.urls'] e prepara il bootstrap del client.

+
+
+

src/secure-assets.ts

+

Gestisce ECDH, decrypt/encrypt, fallback plain e secure API runtime.

+
+
+

scripts/write-asset-loader.mjs

+

Genera public/configuration/asset-loader.js e decide se usare file plain o .dat.

+
+
+

scripts/minify-dist.mjs

+

Genera i .dat ma mantiene anche i file plain per il toggle runtime.

+
+
+
+ +
+

Emulatore

+
nitro.secure.assets.enabled=true
+nitro.secure.api.enabled=true
+nitro.secure.config.root=C:/path/to/Nitro-V3/public
+nitro.secure.gamedata.root=C:/path/to/gamedata
+nitro.secure.master_key=change-me-to-a-long-random-secret
+
    +
  • nitro.secure.assets.enabled: abilita /nitro-sec/bootstrap e /nitro-sec/file
  • +
  • nitro.secure.api.enabled: abilita la cifratura su /api/*
  • +
  • nitro.secure.config.root: cartella dei config live
  • +
  • nitro.secure.gamedata.root: cartella del gamedata live
  • +
  • nitro.secure.master_key: chiave persistente server-side
  • +
+
+ +
+

Scenari rapidi

+
+
+

Tutto attivo

+

Secure assets, secure API e dist obfuscation tutti attivi.

+
+
+

Solo .dat

+

Usi i .dat, ma lasci config/API in plain.

+
+
+

Tutto plain

+

Modalità fallback completa per debug o test locali.

+
+
+
+ +
+

Checklist finale

+
+
Hai creato i file reali partendo da client-mode.example, renderer-config.example e ui-config.example
+
Gli URL pubblici stanno nei file config, non nei componenti React
+
Hai deployato sia i file plain sia i .dat
+
Il server espone correttamente il MIME type per .dat
+
Hai impostato nitro.secure.master_key lato emulatore
+
+
+
+
+
+ + + + diff --git a/docs/secure-runtime-modes.md b/docs/secure-runtime-modes.md index 3b59b39..4a9d311 100644 --- a/docs/secure-runtime-modes.md +++ b/docs/secure-runtime-modes.md @@ -3,11 +3,11 @@ Questa doc riassume tutti i dati da impostare per: - offuscamento bundle `dist` (`app.js` / `app.css` → `.dat`) -- secure assets runtime (`renderer-config.json`, `ui-config.json`, `gamedata`) +- secure assets runtime (`configuration/renderer-config.json`, `configuration/ui-config.json`, `gamedata`) - secure API runtime (`/api/*`) - fallback plain quando vuoi spegnere tutto senza togliere il codice -## 1. `Nitro-V3/public/client-mode.json` +## 1. `Nitro-V3/public/configuration/client-mode.json` Questo file controlla tutto a runtime. @@ -17,7 +17,7 @@ Questo file controlla tutto a runtime. "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/" } ``` @@ -30,7 +30,7 @@ Questo file controlla tutto a runtime. - `secureAssetsEnabled` - `true`: `bootstrap.ts` e `secure-assets.ts` usano `/nitro-sec/file` - - `false`: `renderer-config.json`, `ui-config.json` e gamedata vengono letti in plain + - `false`: `configuration/renderer-config.json`, `configuration/ui-config.json` e gamedata vengono letti in plain - `secureApiEnabled` - `true`: il wrapper `fetch` cifra le chiamate `/api/*` @@ -43,7 +43,7 @@ Questo file controlla tutto a runtime. - `plainConfigBaseUrl` - base URL dei file config plain - - normalmente: `https://hotel.example.com/` + - normalmente: `https://hotel.example.com/configuration/` - `plainGamedataBaseUrl` - base URL del gamedata plain @@ -74,7 +74,7 @@ Il fallback attuale è: (window as any).NitroSecureApiUrl = clientMode.apiBaseUrl || 'https://nitro.example.com:2096/'; ``` -Quindi in produzione conviene sempre valorizzare `apiBaseUrl` dentro `client-mode.json`. +Quindi in produzione conviene sempre valorizzare `apiBaseUrl` dentro `configuration/client-mode.json`. ## 3. `Nitro-V3/src/secure-assets.ts` @@ -95,7 +95,7 @@ Qui vive tutta la logica runtime: Normalmente non serve toccarlo, a meno che tu non voglia cambiare il protocollo secure. -## 4. `Nitro-V3/public/renderer-config.json` +## 4. `Nitro-V3/public/configuration/renderer-config.json` Questo file continua a definire i path usati dal renderer. @@ -129,7 +129,7 @@ Conviene usare i path plain classici, per esempio: oppure lasciare il renderer configurato com’è e demandare il fallback a `secure-assets.ts`. -## 5. `Nitro-V3/public/ui-config.json` +## 5. `Nitro-V3/public/configuration/ui-config.json` Qui non c’è logica secure, ma è uno dei file caricati da `config.urls`. @@ -140,12 +140,12 @@ Quindi basta mantenerlo corretto come contenuto, non serve altro. ## 6. `Nitro-V3/scripts/write-asset-loader.mjs` -Questo script genera `public/asset-loader.js`. +Questo script genera `public/configuration/asset-loader.js`. ### Cosa fa ora - mostra la shell iniziale -- legge `client-mode.json` +- legge `configuration/client-mode.json` - decide se caricare: - `app.css.dat` / `app.js.dat` - oppure `assets/app.css` / `assets/app.js` @@ -194,7 +194,7 @@ nitro.secure.master_key=change-me-to-a-long-random-secret - abilita il layer secure per `/api/*` - `nitro.secure.config.root` - - cartella dove leggere `renderer-config.json` e `ui-config.json` + - cartella dove leggere `configuration/renderer-config.json` e `configuration/ui-config.json` - `nitro.secure.gamedata.root` - cartella dove leggere il gamedata live @@ -207,7 +207,7 @@ nitro.secure.master_key=change-me-to-a-long-random-secret ### Tutto attivo -`client-mode.json` +`configuration/client-mode.json` ```json { @@ -215,7 +215,7 @@ nitro.secure.master_key=change-me-to-a-long-random-secret "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/" } ``` @@ -232,7 +232,7 @@ nitro.secure.master_key=una-chiave-lunga-random ### Solo `.dat`, senza secure assets/api -`client-mode.json` +`configuration/client-mode.json` ```json { @@ -240,7 +240,7 @@ nitro.secure.master_key=una-chiave-lunga-random "secureAssetsEnabled": false, "secureApiEnabled": false, "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/" } ``` @@ -254,7 +254,7 @@ nitro.secure.api.enabled=false ### Tutto plain -`client-mode.json` +`configuration/client-mode.json` ```json { @@ -262,7 +262,7 @@ nitro.secure.api.enabled=false "secureAssetsEnabled": false, "secureApiEnabled": false, "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/" } ``` @@ -273,9 +273,9 @@ nitro.secure.api.enabled=false Per cambiare: -- `client-mode.json` -- `renderer-config.json` -- `ui-config.json` +- `configuration/client-mode.json` +- `configuration/renderer-config.json` +- `configuration/ui-config.json` - gamedata live - `config.ini` @@ -298,10 +298,12 @@ Per usare bene i toggle: ## 12. Checklist veloce -- `client-mode.json` configurato +- `configuration/client-mode.json` configurato - `apiBaseUrl` corretto - `nitro.secure.master_key` valorizzata - `nitro.secure.config.root` corretto - `nitro.secure.gamedata.root` corretto - `.dat` e file plain entrambi deployati - MIME `.dat` presente sul web server + + diff --git a/localization/badge-texts-en.json b/localization/badge-texts-en.json deleted file mode 100644 index a8ab19a..0000000 --- a/localization/badge-texts-en.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "notification.badge.received": "New Badge!" -} diff --git a/localization/badge-texts-it.json b/localization/badge-texts-it.json deleted file mode 100644 index 10c1271..0000000 --- a/localization/badge-texts-it.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "notification.badge.received": "Nuovo Distintivo!" -} diff --git a/public/UITexts.example b/public/UITexts.example deleted file mode 100644 index bb8779b..0000000 --- a/public/UITexts.example +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/public/client-mode.json b/public/client-mode.json deleted file mode 100644 index a738f14..0000000 --- a/public/client-mode.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "distObfuscationEnabled": true, - "secureAssetsEnabled": true, - "secureApiEnabled": true, - "apiBaseUrl": "", - "plainConfigBaseUrl": "", - "plainGamedataBaseUrl": "" -} diff --git a/public/configuration/UITexts.example b/public/configuration/UITexts.example new file mode 100644 index 0000000..acf246e --- /dev/null +++ b/public/configuration/UITexts.example @@ -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" +} diff --git a/public/adsense.json b/public/configuration/adsense.example similarity index 100% rename from public/adsense.json rename to public/configuration/adsense.example diff --git a/public/asset-loader.js b/public/configuration/asset-loader.js similarity index 97% rename from public/asset-loader.js rename to public/configuration/asset-loader.js index f64cbe6..c1cfde3 100644 --- a/public/asset-loader.js +++ b/public/configuration/asset-loader.js @@ -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); diff --git a/public/configuration/bootstrap.js b/public/configuration/bootstrap.js new file mode 100644 index 0000000..f2a9d7e --- /dev/null +++ b/public/configuration/bootstrap.js @@ -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."; + }); +})(); \ No newline at end of file diff --git a/public/client-mode.example.json b/public/configuration/client-mode.example similarity index 76% rename from public/client-mode.example.json rename to public/configuration/client-mode.example index 4582313..a6e11ed 100644 --- a/public/client-mode.example.json +++ b/public/configuration/client-mode.example @@ -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/" } diff --git a/public/hotlooks.json b/public/configuration/hotlooks.example similarity index 100% rename from public/hotlooks.json rename to public/configuration/hotlooks.example diff --git a/public/renderer-config.example b/public/configuration/renderer-config.example similarity index 100% rename from public/renderer-config.example rename to public/configuration/renderer-config.example diff --git a/public/ui-config.example b/public/configuration/ui-config.example similarity index 100% rename from public/ui-config.example rename to public/configuration/ui-config.example diff --git a/public/renderer-config.json b/public/renderer-config.json deleted file mode 100644 index 6b769d7..0000000 --- a/public/renderer-config.json +++ /dev/null @@ -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" - ] -} diff --git a/public/ui-config.json b/public/ui-config.json deleted file mode 100644 index f625755..0000000 --- a/public/ui-config.json +++ /dev/null @@ -1,2816 +0,0 @@ -{ - "image.library.notifications.url": "${image.library.url}notifications/%image%.png", - "achievements.images.url": "${image.library.url}Quests/%image%.png", - "camera.url": "https://hotel.example.com/client/camera/", - "thumbnails.url": "https://hotel.example.com/client/camera/thumbnail/%thumbnail%.png", - "url.prefix": "", - "habbopages.url": "/gamedata/habbopages/", - "group.homepage.url": "${url.prefix}/groups/%groupid%/id", - "guide.help.alpha.groupid": 0, - "chat.viewer.height.percentage": 0.4, - "widget.dimmer.colorwheel": false, - "avatar.wardrobe.max.slots": 10, - "user.badges.max.slots": 5, - "user.tags.enabled": false, - "camera.publish.disabled": false, - "hc.disabled": false, - "badge.descriptions.enabled": true, - "motto.max.length": 38, - "bot.name.max.length": 15, - "pet.package.name.max.length": 15, - "wired.action.bot.talk.to.avatar.max.length": 64, - "wired.action.bot.talk.max.length": 64, - "wired.action.chat.max.length": 100, - "wired.action.kick.from.room.max.length": 100, - "wired.action.mute.user.max.length": 100, - "game.center.enabled": false, - "guides.enabled": true, - "toolbar.hide.quests": true, - "catalog.style.new": true, - "show.google.ads": false, - "loginview": { - "images": { - "background": "https://hotel.example.com/client/nitro/images/reception/background_gradient_apr25.png", "drape": "https://hotel.example.com/client/nitro/images/reception/drape.png", - "left": "https://hotel.example.com/client/nitro/images/reception/mute_reception_backdrop_left.png", - "right": "https://hotel.example.com/client/nitro/images/reception/background_right.png" - } - }, - "navigator.room.models": [ - { - "clubLevel": 0, - "tileSize": 104, - "name": "a" - }, - { - "clubLevel": 0, - "tileSize": 94, - "name": "b" - }, - { - "clubLevel": 0, - "tileSize": 36, - "name": "c" - }, - { - "clubLevel": 0, - "tileSize": 84, - "name": "d" - }, - { - "clubLevel": 0, - "tileSize": 80, - "name": "e" - }, - { - "clubLevel": 0, - "tileSize": 80, - "name": "f" - }, - { - "clubLevel": 0, - "tileSize": 416, - "name": "i" - }, - { - "clubLevel": 0, - "tileSize": 320, - "name": "j" - }, - { - "clubLevel": 0, - "tileSize": 448, - "name": "k" - }, - { - "clubLevel": 0, - "tileSize": 352, - "name": "l" - }, - { - "clubLevel": 0, - "tileSize": 384, - "name": "m" - }, - { - "clubLevel": 0, - "tileSize": 372, - "name": "n" - }, - { - "clubLevel": 1, - "tileSize": 80, - "name": "g" - }, - { - "clubLevel": 1, - "tileSize": 74, - "name": "h" - }, - { - "clubLevel": 1, - "tileSize": 416, - "name": "o" - }, - { - "clubLevel": 1, - "tileSize": 352, - "name": "p" - }, - { - "clubLevel": 1, - "tileSize": 304, - "name": "q" - }, - { - "clubLevel": 1, - "tileSize": 336, - "name": "r" - }, - { - "clubLevel": 1, - "tileSize": 748, - "name": "u" - }, - { - "clubLevel": 1, - "tileSize": 438, - "name": "v" - }, - { - "clubLevel": 2, - "tileSize": 540, - "name": "t" - }, - { - "clubLevel": 2, - "tileSize": 512, - "name": "w" - }, - { - "clubLevel": 2, - "tileSize": 396, - "name": "x" - }, - { - "clubLevel": 2, - "tileSize": 440, - "name": "y" - }, - { - "clubLevel": 2, - "tileSize": 456, - "name": "z" - }, - { - "clubLevel": 2, - "tileSize": 208, - "name": "0" - }, - { - "clubLevel": 2, - "tileSize": 1009, - "name": "1" - }, - { - "clubLevel": 2, - "tileSize": 1044, - "name": "2" - }, - { - "clubLevel": 2, - "tileSize": 183, - "name": "3" - }, - { - "clubLevel": 2, - "tileSize": 254, - "name": "4" - }, - { - "clubLevel": 2, - "tileSize": 1024, - "name": "5" - }, - { - "clubLevel": 2, - "tileSize": 801, - "name": "6" - }, - { - "clubLevel": 2, - "tileSize": 354, - "name": "7" - }, - { - "clubLevel": 2, - "tileSize": 888, - "name": "8" - }, - { - "clubLevel": 2, - "tileSize": 926, - "name": "9" - } - ], - "backgrounds.data": [ - { - "backgroundId": 0, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 1, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 2, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 3, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 4, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 5, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 6, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 7, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 8, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 9, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 10, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 11, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 12, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 13, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 14, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 15, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 16, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 17, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 18, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 19, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 20, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 21, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 22, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 23, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 24, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 25, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 26, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 27, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 28, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 29, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 30, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 31, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 32, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 33, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 34, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 35, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 36, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 37, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 38, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 39, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 40, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 41, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 42, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 43, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 44, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 45, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 46, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 47, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 48, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 49, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 50, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 51, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 52, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 53, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 54, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 55, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 56, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 57, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 58, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 59, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 60, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 61, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 62, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 63, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 64, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 65, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 66, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 67, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 68, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 69, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 70, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 71, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 72, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 73, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 74, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 75, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 76, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 77, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 78, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 79, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 80, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 81, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 82, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 83, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 84, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 85, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 86, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 87, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 88, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 89, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 90, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 91, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 92, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 93, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 94, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 95, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 96, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 97, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 98, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 99, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 100, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 101, - "minRank": 2, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "backgroundId": 102, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 103, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 104, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 105, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 106, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 107, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 108, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 109, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 110, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 111, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 112, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 113, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 114, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 115, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 116, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 117, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 118, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 119, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 120, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 121, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 122, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 123, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 124, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 125, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 126, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 127, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 128, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 129, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 130, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 131, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 132, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 133, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 134, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 135, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 136, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 137, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 138, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 139, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 140, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 141, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 142, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 143, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 144, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 145, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 146, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 147, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 148, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 149, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 150, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 151, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 152, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 153, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 154, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 155, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 156, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 157, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 158, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 159, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 160, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 161, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 162, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 163, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 164, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 165, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 166, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 167, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 168, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 169, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 170, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 171, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 172, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 173, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 174, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 175, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 176, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 177, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 178, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 179, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 180, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 181, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 182, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 183, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 184, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 185, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 186, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "backgroundId": 187, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - } - ], - "stands.data": [ - { - "standId": 0, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 1, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 2, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 3, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 4, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 5, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 6, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 7, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 8, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 9, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 10, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 11, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 12, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 13, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 14, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 15, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "standId": 16, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "standId": 17, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "standId": 18, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "standId": 19, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "standId": 20, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "standId": 21, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - } - ], - "overlays.data": [ - { - "overlayId": 0, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "overlayId": 1, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "overlayId": 2, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "overlayId": 3, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "overlayId": 4, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "overlayId": 5, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "overlayId": 6, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "overlayId": 7, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "overlayId": 8, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - } - ], - "hotelview": { - "room.pool": "791", - "room.picnic": "2193", - "room.rooftop": "", - "room.rooftop.pool": "", - "room.peaceful": "", - "room.infobus": "5956", - "room.lobby": "1450", - "show.avatar": true, - "widgets": { - "slot.1.widget": "promoarticle", - "slot.1.conf": {}, - "slot.2.widget": "widgetcontainer", - "slot.2.conf": { - "image": "${image.library.url}web_promo_small/spromo_Canal_Bundle.png", - "texts": "2021NitroPromo", - "btnLink": "" - }, - "slot.3.widget": "", - "slot.3.conf": {}, - "slot.4.widget": "", - "slot.4.conf": {}, - "slot.5.widget": "", - "slot.5.conf": {}, - "slot.6.widget": "", - "slot.6.conf": { - "campaign": "" - }, - "slot.7.widget": "", - "slot.7.conf": {} - }, - "images": { - "background": "${asset.url}/images/reception/stretch_blue.png", - "background.colour": "#8ee0f0", - "sun": "${asset.url}/images/reception/sun.png", - "drape": "${asset.url}/images/reception/drape.png", - "left": "", - "right": "", - "right.repeat": "" - } - }, - "loginview": { - "images": { - "background": "https://hotel.example.com/client/nitro/images/reception/background_gradient_apr25.png", - "background.colour": "#6eadc8", - "drape": "https://hotel.example.com/client/nitro/images/reception/drape.png", - "left": "https://hotel.example.com/client/nitro/images/reception/mute_reception_backdrop_left.png", - "right": "https://hotel.example.com/client/nitro/images/reception/background_right.png" - }, - "widgets": { - "slot.1.widget": "promoarticle", - "slot.1.conf": {}, - "slot.2.widget": "widgetcontainer", - "slot.2.conf": { - "image": "${image.library.url}web_promo_small/spromo_Canal_Bundle.png", - "texts": "2021NitroPromo", - "btnLink": "" - }, - "slot.3.widget": "", - "slot.3.conf": {}, - "slot.4.widget": "", - "slot.4.conf": {}, - "slot.5.widget": "", - "slot.5.conf": {}, - "slot.6.widget": "", - "slot.6.conf": { - "campaign": "" - }, - "slot.7.widget": "", - "slot.7.conf": {} - } - }, - "achievements.unseen.ignored": [ - "ACH_AllTimeHotelPresence" - ], - "avatareditor.show.clubitems.dimmed": true, - "avatareditor.show.clubitems.first": true, - "chat.history.max.items": 100, - "system.currency.types": [ - -1, - 0, - 5, - 105 - ], - "catalog.links": { - "hc.buy_hc": "habbo_club", - "hc.hc_gifts": "club_gifts", - "pets.buy_food": "pet_food", - "pets.buy_saddle": "saddles" - }, - "hc.center": { - "benefits.info": true, - "payday.info": true, - "gift.info": true, - "benefits.habbopage": "habboclub", - "payday.habbopage": "hcpayday" - }, - "respect.options": { - "enabled": false, - "sound": "sound_respect_received" - }, - "currency.display.number.short": false, - "currency.asset.icon.url": "${images.url}/wallet/%type%.png", - "catalog.asset.url": "${image.library.url}catalogue", - "catalog.asset.image.url": "${catalog.asset.url}/%name%.gif", - "catalog.asset.icon.url": "${catalog.asset.url}/icon_%name%.png", - "catalog.tab.icons": false, - "catalog.headers": false, - "chat.input.maxlength": 100, - "chat.styles.disabled": [], - "chat.styles": [ - { - "styleId": 0, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 1, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 2, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 3, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 4, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 5, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 6, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 7, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 8, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 9, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 10, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 11, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 12, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 13, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 14, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 15, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 16, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 17, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 18, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 19, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 20, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 21, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 22, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 23, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 24, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 25, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 26, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 27, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 28, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 29, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 30, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 31, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 32, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 33, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 34, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, - { - "styleId": 35, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 36, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 37, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 38, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, - { - "styleId": 39, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 40, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 41, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 42, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 43, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 44, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 45, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 46, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 47, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 48, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 49, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 50, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 51, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 52, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, - { - "styleId": 53, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - } - ], - "camera.available.effects": [ - { - "name": "dark_sepia", - "colorMatrix": [ - 0.4, - 0.4, - 0.1, - 0, - 110, - 0.3, - 0.4, - 0.1, - 0, - 30, - 0.3, - 0.2, - 0.1, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 0, - "enabled": true - }, - { - "name": "increase_saturation", - "colorMatrix": [ - 2, - -0.5, - -0.5, - 0, - 0, - -0.5, - 2, - -0.5, - 0, - 0, - -0.5, - -0.5, - 2, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 0, - "enabled": true - }, - { - "name": "increase_contrast", - "colorMatrix": [ - 1.5, - 0, - 0, - 0, - -50, - 0, - 1.5, - 0, - 0, - -50, - 0, - 0, - 1.5, - 0, - -50, - 0, - 0, - 0, - 1.5, - 0 - ], - "minLevel": 0, - "enabled": true - }, - { - "name": "shadow_multiply_02", - "colorMatrix": [], - "minLevel": 0, - "blendMode": 2, - "enabled": true - }, - { - "name": "color_1", - "colorMatrix": [ - 0.393, - 0.769, - 0.189, - 0, - 0, - 0.349, - 0.686, - 0.168, - 0, - 0, - 0.272, - 0.534, - 0.131, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 1, - "enabled": true - }, - { - "name": "hue_bright_sat", - "colorMatrix": [ - 1, - 0.6, - 0.2, - 0, - -50, - 0.2, - 1, - 0.6, - 0, - -50, - 0.6, - 0.2, - 1, - 0, - -50, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 1, - "enabled": true - }, - { - "name": "hearts_hardlight_02", - "colorMatrix": [], - "minLevel": 1, - "blendMode": 9, - "enabled": true - }, - { - "name": "texture_overlay", - "colorMatrix": [], - "minLevel": 1, - "blendMode": 4, - "enabled": true - }, - { - "name": "pinky_nrm", - "colorMatrix": [], - "minLevel": 1, - "blendMode": 0, - "enabled": true - }, - { - "name": "color_2", - "colorMatrix": [ - 0.333, - 0.333, - 0.333, - 0, - 0, - 0.333, - 0.333, - 0.333, - 0, - 0, - 0.333, - 0.333, - 0.333, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 2, - "enabled": true - }, - { - "name": "night_vision", - "colorMatrix": [ - 0, - 0, - 0, - 0, - 0, - 0, - 1.1, - 0, - 0, - -50, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 2, - "enabled": true - }, - { - "name": "stars_hardlight_02", - "colorMatrix": [], - "minLevel": 2, - "blendMode": 9, - "enabled": true - }, - { - "name": "coffee_mpl", - "colorMatrix": [], - "minLevel": 2, - "blendMode": 2, - "enabled": true - }, - { - "name": "security_hardlight", - "colorMatrix": [], - "minLevel": 3, - "blendMode": 9, - "enabled": true - }, - { - "name": "bluemood_mpl", - "colorMatrix": [], - "minLevel": 3, - "blendMode": 2, - "enabled": true - }, - { - "name": "rusty_mpl", - "colorMatrix": [], - "minLevel": 3, - "blendMode": 2, - "enabled": true - }, - { - "name": "decr_conrast", - "colorMatrix": [ - 0.5, - 0, - 0, - 0, - 50, - 0, - 0.5, - 0, - 0, - 50, - 0, - 0, - 0.5, - 0, - 50, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 4, - "enabled": true - }, - { - "name": "green_2", - "colorMatrix": [ - 0.5, - 0.5, - 0.5, - 0, - 0, - 0.5, - 0.5, - 0.5, - 0, - 90, - 0.5, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 4, - "enabled": true - }, - { - "name": "alien_hrd", - "colorMatrix": [], - "minLevel": 4, - "blendMode": 9, - "enabled": true - }, - { - "name": "color_3", - "colorMatrix": [ - 0.609, - 0.609, - 0.082, - 0, - 0, - 0.309, - 0.609, - 0.082, - 0, - 0, - 0.309, - 0.609, - 0.082, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 5, - "enabled": true - }, - { - "name": "color_4", - "colorMatrix": [ - 0.8, - -0.8, - 1, - 0, - 70, - 0.8, - -0.8, - 1, - 0, - 70, - 0.8, - -0.8, - 1, - 0, - 70, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 5, - "enabled": true - }, - { - "name": "toxic_hrd", - "colorMatrix": [], - "minLevel": 5, - "blendMode": 9, - "enabled": true - }, - { - "name": "hypersaturated", - "colorMatrix": [ - 2, - -1, - 0, - 0, - 0, - -1, - 2, - 0, - 0, - 0, - 0, - -1, - 2, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 6, - "enabled": true - }, - { - "name": "Yellow", - "colorMatrix": [ - 1, - 0, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 6, - "enabled": true - }, - { - "name": "misty_hrd", - "colorMatrix": [], - "minLevel": 6, - "blendMode": 9, - "enabled": true - }, - { - "name": "x_ray", - "colorMatrix": [ - 0, - 1.2, - 0, - 0, - -100, - 0, - 2, - 0, - 0, - -120, - 0, - 2, - 0, - 0, - -120, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 7, - "enabled": true - }, - { - "name": "decrease_saturation", - "colorMatrix": [ - 0.7, - 0.2, - 0.2, - 0, - 0, - 0.2, - 0.7, - 0.2, - 0, - 0, - 0.2, - 0.2, - 0.7, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 7, - "enabled": true - }, - { - "name": "drops_mpl", - "colorMatrix": [], - "minLevel": 8, - "blendMode": 2, - "enabled": true - }, - { - "name": "shiny_hrd", - "colorMatrix": [], - "minLevel": 9, - "blendMode": 9, - "enabled": true - }, - { - "name": "glitter_hrd", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 9, - "enabled": true - }, - { - "name": "frame_gold", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 0, - "enabled": true - }, - { - "name": "frame_gray_4", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 0, - "enabled": true - }, - { - "name": "frame_black_2", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 0, - "enabled": true - }, - { - "name": "frame_wood_2", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 0, - "enabled": true - }, - { - "name": "finger_nrm", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 0, - "enabled": true - }, - { - "name": "color_5", - "colorMatrix": [ - 3.309, - 0.609, - 1.082, - 0.2, - 0, - 0.309, - 0.609, - 0.082, - 0, - 0, - 1.309, - 0.609, - 0.082, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 10, - "enabled": true - }, - { - "name": "black_white_negative", - "colorMatrix": [ - -0.5, - -0.5, - -0.5, - 0, - 0, - -0.5, - -0.5, - -0.5, - 0, - 0, - -0.5, - -0.5, - -0.5, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 10, - "enabled": true - }, - { - "name": "blue", - "colorMatrix": [ - 0.5, - 0.5, - 0.5, - 0, - -255, - 0.5, - 0.5, - 0.5, - 0, - -170, - 0.5, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 10, - "enabled": true - }, - { - "name": "red", - "colorMatrix": [ - 0.5, - 0.5, - 0.5, - 0, - 0, - 0.5, - 0.5, - 0.5, - 0, - -170, - 0.5, - 0.5, - 0.5, - 0, - -170, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 10, - "enabled": true - }, - { - "name": "green", - "colorMatrix": [ - 0.5, - 0.5, - 0.5, - 0, - -170, - 0.5, - 0.5, - 0.5, - 0, - 0, - 0.5, - 0.5, - 0.5, - 0, - -170, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 10, - "enabled": true - } - ], - "notification": { - "notification.admin.transient": { - "display": "POP_UP", - "image": "${image.library.url}/album1358/frank_wave_001.gif" - }, - "notification.builders_club.membership_expired": { - "display": "POP_UP" - }, - "notification.builders_club.membership_expires": { - "display": "POP_UP", - "image": "${image.library.url}/notifications/builders_club_room_locked_small.png" - }, - "notification.builders_club.membership_extended": { - "delivery": "PERSISTENT", - "display": "POP_UP" - }, - "notification.builders_club.membership_made": { - "delivery": "PERSISTENT", - "display": "POP_UP", - "image": "${image.library.url}/notifications/builders_club_membership_extended.png" - }, - "notification.builders_club.membership_renewed": { - "delivery": "PERSISTENT", - "display": "POP_UP", - "image": "${image.library.url}/notifications/builders_club_membership_extended.png" - }, - "notification.builders_club.room_locked": { - "display": "BUBBLE", - "image": "${image.library.url}/notifications/builders_club_room_locked_small.png" - }, - "notification.builders_club.room_unlocked": { - "display": "BUBBLE" - }, - "notification.builders_club.visit_denied_for_owner": { - "display": "BUBBLE", - "image": "${image.library.url}/notifications/builders_club_room_locked_small.png" - }, - "notification.builders_club.visit_denied_for_visitor": { - "display": "POP_UP", - "image": "${image.library.url}/notifications/builders_club_room_locked.png" - }, - "notification.campaign.credit.donation": { - "display": "BUBBLE" - }, - "notification.campaign.product.donation": { - "display": "BUBBLE" - }, - "notification.casino.too_many_dice.placement": { - "display": "POP_UP" - }, - "notification.casino.too_many_dice": { - "display": "POP_UP" - }, - "notification.cfh.created": { - "display": "POP_UP", - "title": "" - }, - "notification.feed.enabled": false, - "notification.floorplan_editor.error": { - "display": "POP_UP" - }, - "notification.forums.delivered": { - "delivery": "PERSISTENT", - "display": "POP_UP" - }, - "notification.forums.forum_settings_updated": { - "display": "BUBBLE" - }, - "notification.forums.message.hidden": { - "display": "BUBBLE" - }, - "notification.forums.message.restored": { - "display": "BUBBLE" - }, - "notification.forums.thread.hidden": { - "display": "BUBBLE" - }, - "notification.forums.thread.locked": { - "display": "BUBBLE" - }, - "notification.forums.thread.pinned": { - "display": "BUBBLE" - }, - "notification.forums.thread.restored": { - "display": "BUBBLE" - }, - "notification.forums.thread.unlocked": { - "display": "BUBBLE" - }, - "notification.forums.thread.unpinned": { - "display": "BUBBLE" - }, - "notification.furni_placement_error": { - "display": "BUBBLE" - }, - "notification.gifting.valentine": { - "delivery": "PERSISTENT", - "display": "BUBBLE", - "image": "${image.library.url}/notifications/polaroid_photo.png" - }, - "notification.items.enabled": true, - "notification.mute.forbidden.time": { - "display": "BUBBLE" - }, - "notification.npc.gift.received": { - "display": "BUBBLE", - "image": "${image.library.url}/album1584/X1517.gif" - } - } -} \ No newline at end of file diff --git a/scripts/minify-dist.mjs b/scripts/minify-dist.mjs index a9d3d18..c61ff54 100644 --- a/scripts/minify-dist.mjs +++ b/scripts/minify-dist.mjs @@ -44,12 +44,6 @@ for(const file of walk(dist)) if(file.endsWith('.json')) minifyJson(file); } -for(const file of [ 'renderer-config.json', 'ui-config.json' ]) -{ - const target = join(dist, file); - if(existsSync(target)) rmSync(target); -} - for(const file of walk(dist)) { if(file.endsWith('.js') && !file.endsWith('asset-loader.js')) encryptFile(file); @@ -84,4 +78,4 @@ for(const [ source, file ] of publicLoaderAssets) } } -writeFileSync(join(dist, 'index.html'), `
`); +writeFileSync(join(dist, 'index.html'), `
`); diff --git a/scripts/write-asset-loader.mjs b/scripts/write-asset-loader.mjs index a94d388..59e403c 100644 --- a/scripts/write-asset-loader.mjs +++ b/scripts/write-asset-loader.mjs @@ -1,4 +1,4 @@ -import { mkdirSync, writeFileSync } from 'fs'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import { dirname, resolve } from 'path'; const loader = `(() => { @@ -148,6 +148,10 @@ const loader = `(() => { 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); @@ -185,7 +189,157 @@ const loader = `(() => { }); })();`; -const target = resolve('public', 'asset-loader.js'); +const clientModePath = resolve('public', 'configuration', 'client-mode.json'); +let bootstrapApiBase = ''; + +if(existsSync(clientModePath)) +{ + try + { + const clientMode = JSON.parse(readFileSync(clientModePath, 'utf8')); + + if(typeof clientMode.apiBaseUrl === 'string') bootstrapApiBase = clientMode.apiBaseUrl; + } + catch {} +} + +const bootstrap = `(() => { + const API_BASE = ${ JSON.stringify(bootstrapApiBase) }; + + 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."; + }); +})();`; + +const target = resolve('public', 'configuration', 'asset-loader.js'); +const bootstrapTarget = resolve('public', 'configuration', 'bootstrap.js'); mkdirSync(dirname(target), { recursive: true }); writeFileSync(target, loader); +writeFileSync(bootstrapTarget, bootstrap); diff --git a/src/bootstrap.ts b/src/bootstrap.ts index 2df054b..395f3b2 100644 --- a/src/bootstrap.ts +++ b/src/bootstrap.ts @@ -31,8 +31,8 @@ const cacheBustUrl = (path: string): string => (window as any).NitroClientMode = clientMode; (window as any).NitroConfig = { 'config.urls': [ - clientMode.secureAssetsEnabled ? secureUrl('config', 'renderer-config.json', true) : cacheBustUrl('renderer-config.json'), - clientMode.secureAssetsEnabled ? secureUrl('config', 'ui-config.json', true) : cacheBustUrl('ui-config.json') + clientMode.secureAssetsEnabled ? secureUrl('config', 'renderer-config.json', true) : cacheBustUrl('configuration/renderer-config.json'), + clientMode.secureAssetsEnabled ? secureUrl('config', 'ui-config.json', true) : cacheBustUrl('configuration/ui-config.json') ], 'sso.ticket': search.get('sso') || null, 'forward.type': search.get('room') ? 2 : -1, diff --git a/src/components/ads/GoogleAdsView.tsx b/src/components/ads/GoogleAdsView.tsx index b31574e..4b65295 100644 --- a/src/components/ads/GoogleAdsView.tsx +++ b/src/components/ads/GoogleAdsView.tsx @@ -1,6 +1,7 @@ import { FC, useEffect, useRef, useState } from 'react'; import { GetConfigurationValue } from '../../api'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common'; +import { configFileUrl } from '../../secure-assets'; interface AdsenseConfig { slot: string; @@ -70,7 +71,7 @@ export const GoogleAdsView: FC<{}> = () => { try { const [ adsTxtRes, configRes ] = await Promise.all([ fetch('/ads.txt', { cache: 'no-cache' }), - fetch('/adsense.json', { cache: 'no-cache' }) + fetch(configFileUrl('adsense.json', true), { cache: 'no-cache' }) ]); if (!adsTxtRes.ok) throw new Error(`ads.txt ${ adsTxtRes.status }`); @@ -156,7 +157,7 @@ export const GoogleAdsView: FC<{}> = () => { data-full-width-responsive={ (config.fullWidthResponsive ?? true) ? 'true' : 'false' } /> } { !loadError && publisherId && config && !config.slot && -
Ad slot not configured in adsense.json
} +
Ad slot not configured in configuration/adsense.json
} diff --git a/src/components/login/LoginView.tsx b/src/components/login/LoginView.tsx index ceba536..dd08aed 100644 --- a/src/components/login/LoginView.tsx +++ b/src/components/login/LoginView.tsx @@ -1,6 +1,7 @@ import { GetConfiguration } from '@nitrots/nitro-renderer'; import { FC, FormEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ClearRememberLogin, GetConfigurationValue, GetRememberLogin, StoreRememberLoginFromPayload } from '../../api'; +import { configFileUrl } from '../../secure-assets'; import flagBr from '../../assets/images/flag_icon/flag_icon_br.png'; import flagDe from '../../assets/images/flag_icon/flag_icon_de.png'; import flagEn from '../../assets/images/flag_icon/flag_icon_en.png'; @@ -1054,7 +1055,7 @@ const RegisterDialog: FC = props => { if(step !== 'avatar' || hotLooks.length) return; let cancelled = false; - fetch('hotlooks.json', { credentials: 'omit' }) + fetch(configFileUrl('hotlooks.json', true), { credentials: 'omit' }) .then(r => r.ok ? r.json() : null) .then((json: unknown) => { diff --git a/src/secure-assets.ts b/src/secure-assets.ts index 31018ac..5cdfdc3 100644 --- a/src/secure-assets.ts +++ b/src/secure-assets.ts @@ -204,7 +204,7 @@ const getPlainAssetBase = (kind: 'config' | 'gamedata'): string => if(typeof configured === 'string' && configured.length) return configured.endsWith('/') ? configured : `${ configured }/`; - if(kind === 'config') return `${ window.location.origin }/`; + if(kind === 'config') return `${ window.location.origin }/configuration/`; return `${ window.location.origin }/nitro/gamedata/`; }; @@ -239,6 +239,17 @@ export const secureUrl = (kind: 'config' | 'gamedata', file: string, cacheBust = return `${ base }/nitro-sec/file?kind=${ encodeURIComponent(kind) }&file=${ encodeURIComponent(file) }${ version }`; }; +export const configFileUrl = (file: string, cacheBust = false): string => +{ + if(getClientMode().secureAssetsEnabled) return secureUrl('config', file, cacheBust); + + const plainUrl = new URL(`configuration/${ file.replace(/^\/+/, '') }`, `${ window.location.origin }/`); + + if(cacheBust) plainUrl.searchParams.set('v', Date.now().toString(36)); + + return plainUrl.toString(); +}; + const createSecureSession = async (): Promise => { setDebugState('secure: generating ECDH session'); diff --git a/vite.config.mjs b/vite.config.mjs index 5922a33..7bf9554 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -16,18 +16,7 @@ export default defineConfig({ rendererRoot, ] }, - proxy: { - '/api': { - target: process.env.AUTH_PROXY_TARGET || 'https://nitro.example.com:2096/', - changeOrigin: true, - ws: true, - }, - '/nitro-sec': { - target: process.env.NITRO_PROXY_TARGET || 'https://nitro.example.com:2096/', - changeOrigin: true, - ws: true, - } - } + }, resolve: { tsconfigPaths: true,