simoleo89 05ea0db806 fix(parser): drop unsafe borderId read inside the RoomUnitParser per-user loop
The Infostand Borders merge (origin/Dev 4b7d04d, upstream commit) added

    user.borderId = (wrapper.bytesAvailable ? wrapper.readInt() : 0);

inside the per-user loop in RoomUnitParser (the parser for the
RoomUsersComposer packet — header 3920 — which ships the full roster
on room enter). The guard is unsafe inside a loop: `bytesAvailable`
is a boolean meaning "any bytes left in the WHOLE packet?", not
"any bytes left in THIS user record". For every user except the
last one, `bytesAvailable === true` because the NEXT user's bytes
still follow, so the parser reads an int and steals 4 bytes from
the next user — cascade corruption of the entire roster.

Symptom in production: users don't see each other on first room
sight. The roster arrives, the parser sfasa, RoomEngine drops the
malformed records.

Fix: stop reading borderId inside the loop. The per-user border id
is shipped separately via RoomUnitInfoParser (single-user packet,
no loop), where the bytesAvailable guard is safe. The roster
packet's last-tail extension story stays clean for any future
trailing block the same way other parsers do — but only when the
guard is the LAST read in the packet, not a per-record one.

This also makes the renderer wire-compatible with both old
emulators (no borderId at all) and the new Arcturus version that
ships borderId in RoomUsersComposer — the latter just has 4 extra
trailing bytes per user that the parser ignores. A follow-up change
on Arcturus' RoomUsersComposer can drop the borderId append, or
keep it and the client simply doesn't read it from the roster
(which is fine — the infostand re-fetch via RoomUnitInfoParser
gives the authoritative border).

mvn-equivalent: yarn compile:fast clean, vitest 138/138.
2026-05-19 21:05:36 +02:00
2024-04-03 09:27:56 +02:00
2024-04-03 09:27:56 +02:00
2024-07-04 15:03:26 +02:00
2024-07-04 15:03:26 +02:00
2024-04-03 09:27:56 +02:00
2024-04-03 09:27:56 +02:00
2024-04-03 09:27:56 +02:00
🆙 Updates
2026-01-31 13:21:59 +01:00

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/utilsJsonParser.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.

S
Description
No description provided
Readme GPL-3.0 4.9 MiB
Languages
TypeScript 99.9%
JavaScript 0.1%