Commit Graph

690 Commits

Author SHA1 Message Date
simoleo89 9edb984f56 style(startup): improve universal console layout
Keep the Morningstar ASCII logo while using a structured plain-text startup card that works in CMD, Windows Terminal, and other consoles without ANSI support.

Compact the Logback console pattern to use simple class names, clean separators, and a wider message column so startup logs do not wrap as aggressively. Simplify Infostand startup output to a one-line asset count while preserving category breakdown at DEBUG level.

Also normalize generic server start/stop messages so Game Server and RCON Server are labeled correctly instead of being glued to host:port output.
2026-06-13 18:24:02 +02:00
simoleo89 ea55258979 style(startup): use universal console splash
Replace the temporary ASCII-art banner with a structured startup splash that uses plain ASCII, aligned fields, and no ANSI or terminal-specific features. This keeps the emulator startup readable across CMD, PowerShell, Linux terminals, Docker logs, CI output, and copied log files. Add a contract test to keep the splash universal.
2026-06-13 18:24:02 +02:00
simoleo89 16d89cdb31 style(startup): customize emulator console banner
Add a clean ASCII startup banner for the emulator CMD window and use it instead of the legacy wide block logo. The new banner stays ASCII-only for Windows console compatibility and keeps the Morningstar identity visible before the startup logs.
2026-06-13 18:24:01 +02:00
simoleo89 ede7eb8284 style(startup): tidy console banner logs
Shorten the infostand background startup message into a compact asset summary and print the project/version/build details as a single ASCII startup card instead of several timestamped log lines. Add a small contract test for the compact infostand summary format.
2026-06-13 18:24:01 +02:00
DuckieTM 87e1ef94f7 Merge pull request #169 from duckietm/main
Main to Dev
2026-06-12 15:56:21 +02:00
github-actions[bot] 510e0d082e 🆙 Bump version to 4.2.44 [skip ci] v4.2.44 2026-06-12 13:53:22 +00:00
DuckieTM e13c7fdbb6 Merge pull request #168 from hotellidev/multicolorfurnifix
Fix multicolor furni in furni editor
2026-06-12 15:52:23 +02:00
hotellidev 2a28fbd2e5 Fix multicolor furni in furni editor 2026-06-11 04:07:22 +03:00
github-actions[bot] cd60cba355 🆙 Bump version to 4.2.43 [skip ci] v4.2.43 2026-06-10 13:32:38 +00:00
DuckieTM e62f461962 Merge pull request #167 from duckietm/dev
㊙️ Security updates
2026-06-10 15:31:38 +02:00
duckietm 7f8c98e4f3 ㊙️ Security updates 2026-06-10 15:31:18 +02:00
github-actions[bot] d95e09e64f 🆙 Bump version to 4.2.42 [skip ci] v4.2.42 2026-06-10 13:10:40 +00:00
DuckieTM ebe0690e46 Merge pull request #166 from duckietm/dev
🆙 Fix multiheight
2026-06-10 15:09:31 +02:00
duckietm 0dda0ae0f7 🆙 Fix multiheight 2026-06-10 15:09:14 +02:00
github-actions[bot] 54ab6613f1 🆙 Bump version to 4.2.41 [skip ci] v4.2.41 2026-06-10 12:18:32 +00:00
DuckieTM 9fda766ba5 Merge pull request #165 from duckietm/dev
🆙 Fix Group Forum buy
2026-06-10 14:17:32 +02:00
duckietm 3da9325344 🆙 Fix Group Forum buy 2026-06-10 14:17:17 +02:00
github-actions[bot] 770739c256 🆙 Bump version to 4.2.40 [skip ci] v4.2.40 2026-06-10 08:16:33 +00:00
DuckieTM 3ec468993a Merge pull request #164 from duckietm/dev
Dev
2026-06-10 10:15:20 +02:00
duckietm 0e0f1cbb15 🆙 Navigator Group Filter 2026-06-10 10:14:49 +02:00
DuckieTM daeda761cd Merge pull request #163 from simoleo89/feat/security-concurrency-economy-hardening
Security, concurrency & economy hardening + dependency upgrades and modernization
2026-06-10 06:43:45 +02:00
DuckieTM 0906048a3a Merge pull request #162 from RemcoEpicnabbo/main
Handle '.' in vending_ids parsing
2026-06-10 06:43:31 +02:00
simoleo89 19cde45d3e fix(marketplace): avoid inventory desync on failed offer insert
Expose whether a marketplace offer was persisted before mutating inventory state, refuse sells whose database insert failed, and synchronize the sold timestamp into the online seller's in-memory offer when present. This keeps failed or racing marketplace operations from desynchronizing credits/items.
2026-06-09 22:02:53 +02:00
simoleo89 8161e3d7e5 fix(moderation): harden ban and modtool edge cases
Use executeUpdate with generated keys for offline ban inserts, return an empty result when an offline target cannot be loaded, and make ban commands handle empty results instead of indexing blindly. Modtool chatlog requests now guard missing users instead of dereferencing null.
2026-06-09 22:02:33 +02:00
simoleo89 5c0f2d2855 fix(session): separate forced disconnects from resume parking
Add a forced dispose path for bans, RCON disconnects, logout/account endpoints, plugin-cancelled login, duplicate login replacement, and late MAC-ban enforcement. Soft channel closes still park a session for reconnect, while security-driven closes now bypass session resume. Also null-guard client/channel disposal and cover the contract with focused tests.
2026-06-09 22:02:07 +02:00
simoleo89 d984461cc0 fix(login): don't reject login when the machine fingerprint arrives after the SSO ticket
The Nitro renderer sends the UniqueID (machine fingerprint) packet right AFTER the SSOTicket, so Habbo.connect() ran before the machineId was set and returned false on the empty machineId — silently disposing the client (WS closed with Netty's default "Bye"), so login never completed and no SecureLoginOK was sent.

- Habbo.connect(): only set machineID + run the MAC-ban check when the fingerprint is already present; never reject the login solely for a missing machineId (also drop a duplicated MAC/IP-ban block).

- MachineIDEvent: enforce the MAC ban when the fingerprint arrives after login, preserving the ban check that connect() now defers.
2026-06-09 20:50:12 +02:00
simoleo89 61ea33ac28 docs(config): document new networking/threading keys from the hardening batch
Add commented examples for the config keys introduced by this PR so operators
can discover and tune them (defaults apply if unset):
- ws.ip.header.trusted (trusted reverse-proxy gate for the forwarded-IP header)
- io.packet.handler.threads (game packet-handler pool, off the Netty I/O loop)
- auth.http.pool.size (dedicated /api/auth/* worker pool)
- io.netty.allocator.pooled (opt-in pooled ByteBuf allocator)
2026-06-09 20:06:31 +02:00
simoleo89 b6ee400b83 refactor: drop Joda-Time (-> java.time) and make protocol charsets explicit
Modernization following the dependency upgrades:
- Joda-Time was used in exactly one place (ModToolSanctionInfoComposer, to
  subtract probation days from a Date). Migrated to java.time
  (Instant/ZoneId.systemDefault, calendar-accurate like the old Joda call) and
  removed the joda-time dependency entirely — confirmed gone from the shaded jar.
- Make string<->bytes conversions explicitly UTF-8 instead of relying on the
  platform default. Most importantly the wire codec (ClientMessage.readString /
  ServerMessage.appendString) — both sides now pinned to UTF-8 so international
  characters are robust regardless of -Dfile.encoding. Also RCONServerHandler,
  PluginManager and the WS origin-forbidden response.

Verified: clean compile, 15/15 tests, shaded jar.
2026-06-09 20:05:31 +02:00
simoleo89 62104596ac refactor(netty): migrate off the deprecated NioEventLoopGroup (Netty 4.2)
Netty 4.2 deprecates NioEventLoopGroup in favour of the generic
MultiThreadIoEventLoopGroup driven by an IoHandlerFactory. Server.java now
builds its boss/worker groups with MultiThreadIoEventLoopGroup(..,
NioIoHandler.newFactory()) — functionally equivalent, and the codebase now
compiles with zero deprecation warnings. Verified: compile, 15/15 tests, shaded
jar.
2026-06-09 20:05:31 +02:00
simoleo89 fad6be158a chore(deps): upgrade Netty (4.2), HikariCP (7) and JUnit (6) to latest major
Major-version upgrades of the three the previous bump deliberately held back.
Verified: clean compile, all 15 tests run green (surefire 3.5.2 drives the JUnit 6
platform fine — no extra launcher dep needed), and the shaded jar assembles.

- io.netty:netty-all            4.1.135.Final -> 4.2.15.Final
- com.zaxxer:HikariCP           6.3.3         -> 7.0.2
- org.junit.jupiter:junit-jupiter 5.14.4      -> 6.1.0

Notes:
- Stayed on Netty 4.2 (GA), not 5.0 which is still Alpha. No source changes
  needed; the channel ALLOCATOR is set explicitly so 4.2's new default adaptive
  allocator doesn't apply. NioEventLoopGroup is deprecated in 4.2 but still
  functions as before (left as-is to avoid an event-loop behavioural change).
  netty-all 4.2 pulls more transitive modules, so the fat jar grows (~20->32 MB).
- HikariCP 7.x baselines Java 17; our HikariConfig usage is unchanged.
2026-06-09 20:05:31 +02:00
simoleo89 a9f1903465 chore(deps): update dependencies to latest stable
Bumped to the latest stable within each safe major line (no source changes
needed — all APIs compatible; verified with clean compile + test + shaded jar):

- io.netty:netty-all            4.1.118 -> 4.1.135.Final
- com.google.code.gson:gson     2.11.0  -> 2.14.0
- org.mariadb.jdbc:*            3.5.1    -> 3.5.8
- com.zaxxer:HikariCP           6.2.1    -> 6.3.3
- org.apache.commons:commons-lang3 3.17.0 -> 3.20.0
- org.jsoup:jsoup               1.18.3   -> 1.22.2
- org.slf4j:slf4j-api           2.0.16   -> 2.0.18
- ch.qos.logback:logback-classic 1.5.15  -> 1.5.34
- org.fusesource.jansi:jansi    2.4.1    -> 2.4.3
- joda-time:joda-time           2.13.0   -> 2.14.2
- org.eclipse.angus:jakarta.mail 2.0.3   -> 2.0.5
- org.junit.jupiter:junit-jupiter 5.10.2 -> 5.14.4
- maven-surefire-plugin         3.2.5    -> 3.5.2

Deliberately NOT changed:
- Stayed on the netty 4.1.x line (4.2/5.0 are new majors with API changes),
  slf4j 2.0.x (logback 1.5.x requires it), junit 5.x (6.x needs a new baseline),
  and HikariCP 6.x (kept the current major for DB-pool stability; 7.x available).
- trove4j 3.0.3, commons-math3 3.6.1, jbcrypt 0.4 — already at their final
  (unmaintained) releases.
- compiler source/target=19 / release=21 — intentional per project convention.
2026-06-09 20:05:31 +02:00
simoleo89 af82352f24 feat: configurable pool sizes (#2) + pool-safe buffers and opt-in pooled allocator (#5)
#2 — tunable thread pools (sensible defaults kept):
- io.packet.handler.threads overrides the packet-handler EventExecutorGroup size
  (default max(16, 2x cores)).
- auth.http.pool.size overrides the auth HTTP pool max threads (default 16).

#5 — Netty buffer pooling:
- Make the crypto handlers pool-safe: GameByteEncryption/GameByteDecryption no
  longer call ByteBuf.array() on a readBytes-derived buffer (whose arrayOffset is
  non-zero under a pooled allocator, which would have read/encrypted the wrong
  region). They now copy the readable region into a plain byte[] (offset-safe)
  and wrap the result — also drops one intermediate buffer allocation. This is
  correct for the current unpooled allocator too. (ServerMessage uses its own
  Unpooled buffer, and ClientMessage reads via buffer methods, so both are
  already offset-safe.)
- Add a shared channel allocator selected by io.netty.allocator.pooled
  (default false = unpooled-heap, unchanged). Set true for a pooled HEAP
  allocator (preferDirect=false, so array-backed paths keep working) to cut
  per-packet alloc/GC churn. Opt-in until validated under load with the Netty
  leak detector, since unreleased pooled buffers accumulate rather than being
  GC-reclaimed.

New optional config keys (insert into emulator_settings to set/silence the
"key not found" notice): io.packet.handler.threads, auth.http.pool.size,
io.netty.allocator.pooled.
2026-06-09 20:05:30 +02:00
simoleo89 dcc23ba744 feat: housekeeping audit log + shared Gson instances
Security:
- HousekeepingAuditLog: append-only audit trail of privileged actions. There was
  no record of which operator granted ranks/currency to whom. SetUserRank,
  GiveCredits and GiveCurrency now log operator id+name, action, target, detail
  and IP. Writes are async; the housekeeping_log table is created on first use
  (CREATE TABLE IF NOT EXISTS) so no manual migration is needed.

Speed (minor):
- RCONServerHandler / PluginManager: reuse a shared Gson instead of allocating a
  new parser per request/plugin-config load (Gson is thread-safe). The wired
  Gson builders were already cached singletons.
2026-06-09 20:05:30 +02:00
simoleo89 f7556138aa feat: LIKE-wildcard escaping (security) + recycle/craft reward rollback (stability)
Security / speed:
- New util SqlLikeEscaper: escapes %, _ and \ in user search input. Applied to
  the user-facing LIKE searches (messenger user search, marketplace search,
  furni-editor search, housekeeping room search, guild member search) so a query
  like "%" can no longer match everything or trigger a needless full scan, and
  usernames containing "_" are matched literally.

Stability (item-loss fixes):
- RecycleEvent: compute the recycler reward BEFORE consuming the 8 inputs. The
  inputs were deleted from the DB first, so a null reward (misconfig) destroyed
  them permanently with nothing back. Now the inputs are only removed once the
  reward is confirmed.
- CraftingCraftItemEvent: restore the pulled ingredients to the inventory if the
  recipe can't be completed (not enough ingredients mid-pull, or reward creation
  returns null) — previously they silently vanished from the inventory.
2026-06-09 20:05:30 +02:00
simoleo89 a0910d822c fix: deep-analysis pass — self-review regressions + pre-existing logic bugs
Regressions found by an adversarial review of this branch's own diff:
- RoomCycleManager: stop holding the currentBots/currentPets monitor across the
  whole bot/pet tick — snapshot under the lock then cycle off-lock. The previous
  fix blocked place/pickup and room dispose for the full tick and inverted lock
  order vs roomUnitLock->currentBots (latent deadlock for any future cycle code
  touching roomUnitLock).
- HabboInfo: complete the currencyLock invariant — getCurrencies() now returns a
  snapshot under the lock (UserInfoCommand iterated the live Trove map off-lock,
  the exact rehash corruption the lock guards); canBuy() uses the lock-guarded
  getCredits()/getCurrencyAmount(); run() reads credits under the lock for save.
- RoomSpecialTypes: synchronize the by-id pet getters (getNest/getPetDrink/
  getPetFood/getPetToy/getPetTree) to match their now-synchronized mutators.
- AuthHttpUtil.isTrustedProxy: exact-match trusted IPs; only treat an entry as a
  range when it ends with "."/":" so "10.0.0.1" can't also trust "10.0.0.12".

Pre-existing logic bugs found by the deep subsystem analysis:
- RoomUsersComposer: the bulk (room-entry) branch wrote the guild id twice;
  the second field must be the 1/-1 group-membership flag (matches the single
  branch) — every user showed a wrong group indicator on room entry.
- BotManager.pickUpBot: room owners (and ACC_PLACEFURNI) couldn't pick up bots
  placed in their own room — added the room-owner clause that placeBot has.
- PetPickupEvent: compared user id to pet.getId() instead of pet.getUserId(), so
  a pet owner who isn't the room owner couldn't pick up their own pet.
- RoomRightsManager.refreshRightsForHabbo: in guild rooms, explicit room_rights
  were stripped (overwritten by guild level NONE); now takes the stronger of
  explicit rights and guild level.
- RoomRequestBannedUsersEvent: `!hasRights || !ACC_ANYROOMOWNER` required BOTH,
  denying legitimate owners the banned-users list — corrected to `&&`.
- InteractionPetBreedingNest.breed: a crafted packet on a not-full nest deleted
  the nest furni then NPE'd (furni loss); guard petOne/petTwo/room before the
  destructive delete; ConfirmPetBreedingEvent null-checks the room.
- WiredEffectTeleport/UserFurniBase: appended item id instead of sprite id in the
  incompatible-triggers list (cosmetic wired-dialog mismatch) — matched the ~10
  other effects' getBaseItem().getSpriteId() convention.
2026-06-09 20:05:30 +02:00
simoleo89 4eb1484daf perf: run game packet handlers off the Netty I/O loop + bound A* pathfinding (P2)
Root cause from the CPU audit: every incoming packet handler ran on the Netty
I/O event loop (MULTI_THREADED_PACKET_HANDLING is false by default), so any
blocking handler — login DB + loadHabbo, friends/polls/catalog/guild-forum
JDBC (~48 handlers), synchronous A* per walk — stalled socket I/O for every
other client sharing that I/O thread.

- WebSocketChannelInitializer: register GameMessageHandler on a dedicated
  DefaultEventExecutorGroup (max(16, 2x cores), daemon). Netty pins each channel
  to one executor in the group, so a client's packets stay strictly ordered (no
  new intra-client races) while blocking work moves off the I/O loop. The
  cross-client concurrency degree matches the already-multi-threaded I/O group,
  and this is strictly safer than the existing (order-losing) shared-pool
  MULTI_THREADED_PACKET_HANDLING mode the codebase already supported.
- GameMessageHandler: always run the handler inline (now on the group thread);
  drop the shared-pool branch (which would break per-channel ordering and also
  removes the rejectable-pool ByteBuf-drop path).
- PathfinderImpl: default the A* execution-time guard ON (25ms) so a pathological
  search returns an empty path instead of running unbounded on its thread.

Note: this changes the server's packet-threading model — verified to compile,
unit-test, and assemble the shaded jar, but should be load-tested before prod.
Group size is currently derived from CPU count; can be made a config key if
tuning is needed.
2026-06-09 20:05:30 +02:00
simoleo89 45d01876c1 fix: bound the move-blocking Future.get in RoomUserWalkEvent
roomUnit.getMoveBlockingTask().get() blocked the Netty event loop with no
timeout; a stuck/delayed move-blocking task would park the worker thread (and
every client on it) indefinitely. Wait at most 2s, then proceed with the walk.
2026-06-09 20:05:30 +02:00
simoleo89 1c4449fb88 perf: run auth HTTP endpoints off the Netty event loop (P1)
The /api/auth/* handlers ran inline on the Netty worker event loop, so their
blocking work — BCrypt (cost 12 ~tens of ms), JDBC, the Turnstile HTTPS
round-trip and SMTP — stalled every other client multiplexed on that thread; a
burst of logins/registers could freeze game traffic.

Dispatch each auth request to a dedicated bounded pool (4..16 daemon threads,
bounded queue, 503 on saturation) instead. It is deliberately SEPARATE from the
shared game ThreadPooling so auth load can't starve room cycles either. Netty
writes are thread-safe, so the endpoints' sendJson calls work unchanged from the
worker; the FullHttpRequest is released when the task finishes.

Caveat: this allows concurrent handling of pipelined requests on a single
keep-alive connection (out-of-order responses) — not a concern for the Nitro
client which is strictly request/response, but worth load-testing before prod.
2026-06-09 20:05:29 +02:00
simoleo89 373d0399c1 fix: trusted-proxy gate for forwarded IP, wired-var cache + ghost-session cleanup
Security (S3):
- AuthHttpUtil/WebSocketHttpHandler: only honour the configured ws.ip.header
  forwarded-IP header when the DIRECT peer is a trusted reverse proxy, instead
  of trusting it unconditionally. Loopback is always trusted; extra proxies can
  be allow-listed (exact IP or string prefix, comma-separated) via the new
  `ws.ip.header.trusted` config key — default-deny so the header can't be
  spoofed from the open internet to evade per-IP rate limiting and IP bans.
  Also take only the first comma token when setting the game-session WS_IP.

Leak cleanup (C4):
- WiredVariableReferenceSupport.invalidateRoom(): drop a room's shared
  wired-variable assignment caches; called from Room.dispose so the static
  USER/ROOM_ASSIGNMENT_CACHE maps don't retain entries for the JVM lifetime.
- SessionResumeManager.parkHabbo: if the scheduler refuses the grace-expiry
  task (future == null), disconnect immediately instead of parking an
  un-reapable GhostSession that would pin the Habbo + room refs forever.

Note: ws.ip.header.trusted defaults to loopback-only; deployments whose proxy
is on another host must add its IP/prefix to that key or client IPs will
collapse to the proxy address.
2026-06-09 20:05:29 +02:00
simoleo89 01c17c0511 fix: wired double-fire guard, RoomUnit path race, roomItems iteration, Netty CVE
Continuation of the concurrency hardening from the audit:
- InteractionWired/WiredHandler (E4): add an atomic per-box processing guard so
  one trigger box is handled by a single thread at a time, making the cooldown
  check-and-set effectively atomic; mark `cooldown` volatile. Prevents a packet
  thread and the room cycle thread from double-firing the same wired stack
  (double teleport/reward).
- RoomUnit (C1): the walk path is now a volatile ConcurrentLinkedDeque instead of
  a plain LinkedList, so the room cycle popping steps can't corrupt it while a
  walk packet rebuilds it via findPath/setPath.
- RoomItemManager (C2): iterate roomItems under its own monitor in getFloorItems/
  getWallItems/getPostItNotes/getUserUniqueFurniCount/getItemsAt, matching the
  existing put/remove sync sites — stops place/pickup from corrupting the
  traversal into a silently-incomplete item set.
- pom.xml (S4): bump netty-all 4.1.115 -> 4.1.118.Final (CVE-2025-24970 SslHandler
  pre-auth DoS, CVE-2025-25193).
2026-06-09 20:05:29 +02:00
simoleo89 d1570d3574 fix: economy-integrity, currency thread-safety, and resource-leak hardening
From the full-codebase audit. Economy/security (Batch A):
- CatalogBuyItemEvent: clamp client `count` to 1..100 — the club-offer branch
  accumulated cost in plain ints, so a huge count overflowed to a negative
  total, bypassed the affordability checks and CREDITED the buyer (free
  currency/subscription exploit).
- HousekeepingGiveCredits/GiveCurrency: bound `amount` to +/-1e9 to stop
  overflow/negative-balance grants via the privileged path.
- RoomTrade: synchronize accept/confirm/offer/remove and add a `completed`
  re-entry guard so two simultaneous confirms can't run tradeItems() twice
  (item/credit duplication).
- HabboInfo: serialize credits + currencies read-modify-write and the
  saveCurrencies snapshot on a dedicated lock (never held across DB I/O) —
  fixes lost updates and Trove rehash-during-iteration corruption between the
  credit-roller thread and purchase/trade handlers.
- AchievementManager/HabboStats: atomic incrementProgress() so concurrent
  progress sources don't lose updates.

Resource/stability (Batch B):
- GameMessageRateLimit: release the wrapped ByteBuf on every drop path
  (ClientMessage isn't ReferenceCounted, so the decoder's auto-release is a
  no-op) — fixes a refcount leak on pre-auth/rate-limited packets.
- AuthRateLimiter: opportunistically evict window-expired STATE/PROBE_STATE
  entries — previously grew unbounded, one entry per unique client IP.
- ForumThread/ForumThreadComment: close getGeneratedKeys() ResultSets via
  try-with-resources, and create the first comment after the thread's
  connection is released (was holding two pooled connections at once).
- DatabasePool: add socketTimeout/connectTimeout/tcpKeepAlive so a stalled
  MariaDB can't pin a pooled connection (and its thread) indefinitely.

Concurrency visibility (Batch C, partial):
- Room: mark allowBotsWalk/allowPets/allowPetsEat volatile (read every cycle,
  written from settings handlers on another thread).
2026-06-09 20:05:29 +02:00
simoleo89 c98d3a3205 fix: guard double gift-open and harden client string reads
- InteractionGift/OpenRecycleBoxEvent: add an atomic open-once guard so two
  near-simultaneous OpenRecycleBox packets can't both schedule the async,
  delayed OpenGift before the wrapper is removed (redundant double-process).
- ClientMessage.readString: treat the length prefix as unsigned (mask 0xFFFF)
  and clamp to the buffered bytes, so a bogus/oversized length no longer
  throws mid-read and desyncs the remaining fields of the packet.
2026-06-09 20:05:29 +02:00
simoleo89 da1fd01074 fix: address bug-hunt findings across security, concurrency, trade & wired
Security
- HousekeepingSetUserRankEvent: add rank-ceiling guard (reject granting a
  rank above the operator's own and modifying a higher-ranked target),
  mirroring GiveRankCommand — closes a privilege-escalation path.

Trade integrity
- RoomTrade.clearAccepted now also resets confirmed; a stale confirmed=true
  let a user strip their side and still complete once the partner re-confirms.

Concurrency
- RoomCycleManager: iterate the synchronized bot/pet maps under their own
  monitor (lock order stays one-directional vs addBot/addPet — no deadlock).
- RoomSpecialTypes: synchronize nest/petDrink/petFood/petToy/petTree writers
  on the same monitor their getters already use.
- HabboStats: synchronize achievement-progress map accessors.
- RebugKickBallAction: drop redundant direct mutation of the shared tile-cache
  sets (updateTile invalidates them right after) — removes a data race.

Robustness
- Wired legacy parsers (HabboCount, NotHabboCount, MatchStatePosition,
  MoveRotateFurni): guard length/format so one malformed row no longer aborts
  the whole room's wired load.
- RoomLayout: fill malformed/short heightmap rows with INVALID tiles instead
  of leaving nulls, and bounds-check door coordinates.
- FurnidataWatcher: defer (instead of drop) a throttled delta so furni-name
  changes are never lost between broadcasts.
- GuildManager.getGuildMembers: fix LIMIT row-count (page size 14, not
  offset+14) so member pages no longer overlap from page 1 on.
2026-06-09 20:05:29 +02:00
Remco Epicnabbo f7bd452cb0 Handle '.' in vending_ids parsing
Normalize vending_ids by replacing semicolons and dots with commas before splitting. This ensures values separated by '.' are treated like other delimiters and parsed correctly as integers, avoiding parsing errors from unexpected separators.
2026-06-09 17:20:02 +02:00
duckietm 48fcd3f78b 🆙 Update SQL 2026-06-08 11:26:03 +02:00
DuckieTM 1275254fa0 Merge pull request #161 from duckietm/main
Sync Main to DEV
2026-06-08 07:31:07 +02:00
duckietm 7ed7a1ec5a 🆙 update the SQL 2026-06-08 07:18:37 +02:00
github-actions[bot] d383c43bbf 🆙 Bump version to 4.2.39 [skip ci] v4.2.39 2026-06-07 21:19:40 +00:00
DuckieTM 44bfcc49b4 Merge pull request #160 from simoleo89/feat/furnidata-source-diagnostics
Resolve furnidata from configured source
2026-06-07 23:18:42 +02:00
DuckieTM b0ffb64cb2 Merge pull request #159 from hotellidev/nulljsonfix
Safely handle JsonNull types
2026-06-07 23:18:20 +02:00