Arcturus' Habbo.connect() runs when the SSOTicket packet is handled and needs the machineId, which is set by the UniqueID packet. Sending the SSO ticket first made such servers reject the login (WS closed with "Bye") because the fingerprint hadn't arrived yet. Send UniqueID before the SSO ticket so the machineId is available when the server processes the login.
Nitro Renderer
nitro-renderer is a Javascript library for rendering Nitro in the browser using PixiJS
Installation
npm
npm install @nitrots/nitro-renderer
yarn
yarn add @nitrots/nitro-renderer
JSON / JSON5 configuration parser
Every configuration file and gamedata file loaded by the renderer (figuredata,
furnidata, productdata, effectmap, avatar actions, etc.) goes through
@nitrots/utils → JsonParser.ts. The parser supports three modes, selected at
the host build time through the compile-time constant __NITRO_JSON_MODE__:
| Mode | Behaviour |
|---|---|
legacy |
Strict JSON.parse only. Comments / trailing commas raise a clear error. |
json5 |
JSON5.parse only. Accepts comments, trailing commas, single quotes. |
auto |
Try strict JSON first, fall back to JSON5. Default when the flag is unset. |
URL hints are still honoured: files ending in .json5 (or served with a
application/json5 content-type) always go through JSON5, regardless of mode.
Wiring the flag into a host
The renderer does not ship its own build for the flag — the host application (typically Nitro V3) defines it via its bundler. Example with Vite:
// vite.config.mjs in the host
export default defineConfig({
define: {
__NITRO_JSON_MODE__: JSON.stringify('json5') // or 'legacy' / 'auto'
}
});
If the constant is not defined the parser falls back to auto, which preserves
the original behaviour of older releases — so existing hosts keep working
without any change.
Using the parser directly
import { parseConfigJson, fetchConfigJson } from '@nitrots/utils';
const data = parseConfigJson<MyConfig>(rawText, '/configuration/ui-config.json');
const data2 = await fetchConfigJson<MyConfig>('/configuration/ui-config.json5');
Errors carry the source URL and, in legacy mode, a hint about switching to
JSON5 — making misconfigurations easy to diagnose in production logs.
Split-aware gamedata loader
@nitrots/utils also exports loadGamedata, the loader that backs every
gamedata consumer in the renderer (FurnitureDataLoader, ProductDataLoader,
EffectAssetDownloadManager, AvatarRenderManager, LocalizationManager). It
accepts either a single-file URL (legacy) or a directory URL (split
mode) — detected automatically by the trailing slash.
Directory layout
<gamedata-dir>/
manifest.json5 # OPTIONAL — { "tiers": ["core", "custom", "seasonal"] }
core/
manifest.json5 # REQUIRED — { "files": ["a.json5", "b.json5", ...] }
a.json5
b.json5
custom/ # OPTIONAL tier
manifest.json5
overrides.json5
seasonal/ # OPTIONAL tier
manifest.json5
xmas.json5
If the directory manifest.json5 is absent, the loader falls back to the
default tier order core → custom → seasonal. Each tier is skipped silently
if its manifest.json5 is missing.
Merge semantics
mergeGamedata(a, b) (also exported) implements the rules below; tiers and
files within a tier are merged in order, with later layers overriding
earlier ones:
| Combination | Result |
|---|---|
| Two plain objects | recursive merge, key by key |
| Two arrays of objects sharing an id key | merged by id (later overrides earlier) |
| Two arrays without an id key | concatenated |
| Anything else | later value wins |
Recognised id keys (in priority order): id, classname, name. Pass
mergeArrayIdKeys in the options object to extend or override them.
Programmatic usage
import { loadGamedata, mergeGamedata } from '@nitrots/utils';
// host code never needs to care whether the URL is split or not
const furnidata = await loadGamedata('https://example.com/gamedata/furnidata/');
// merge ad-hoc if you load tiers manually
const merged = mergeGamedata(coreData, customData);
A CLI splitter for legacy single-file gamedata lives in the Nitro V3 client
repo at scripts/split-gamedata.mjs — see the Nitro V3 README for usage.