From 0a56f46d28dc17616dc2896ad6f13be65e4f4d5c Mon Sep 17 00:00:00 2001 From: Life Date: Tue, 24 Mar 2026 21:47:54 +0100 Subject: [PATCH] Fix CatalogPagesListComposer parsing error in Nitro client The Nitro client's NodeData parser enforces safety limits (max 1000 offers, 500 children, 20 depth) using Math.min() on the count but the server was sending all data beyond those limits. The unread bytes left in the buffer corrupted parsing of subsequent nodes, causing RangeError. Cap server-side output to match client limits and snapshot offerIds array to prevent potential race conditions between size() and iteration. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../catalog/CatalogPagesListComposer.java | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/catalog/CatalogPagesListComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/catalog/CatalogPagesListComposer.java index 018d441a..92c1bc5a 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/catalog/CatalogPagesListComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/catalog/CatalogPagesListComposer.java @@ -15,6 +15,10 @@ import java.util.List; public class CatalogPagesListComposer extends MessageComposer { private static final Logger LOGGER = LoggerFactory.getLogger(CatalogPagesListComposer.class); + private static final int MAX_OFFERS = 1000; + private static final int MAX_CHILDREN = 500; + private static final int MAX_DEPTH = 20; + private final Habbo habbo; private final String mode; private final boolean hasPermission; @@ -38,10 +42,12 @@ public class CatalogPagesListComposer extends MessageComposer { this.response.appendString("root"); this.response.appendString(""); this.response.appendInt(0); - this.response.appendInt(pages.size()); - for (CatalogPage category : pages) { - this.append(category); + int childCount = Math.min(pages.size(), MAX_CHILDREN); + this.response.appendInt(childCount); + + for (int idx = 0; idx < childCount; idx++) { + this.append(pages.get(idx), 1); } this.response.appendBoolean(false); @@ -55,7 +61,7 @@ public class CatalogPagesListComposer extends MessageComposer { return null; } - private void append(CatalogPage category) { + private void append(CatalogPage category, int depth) { List pagesList = Emulator.getGameEnvironment().getCatalogManager().getCatalogPages(category.getId(), this.habbo); this.response.appendBoolean(category.isVisible()); @@ -63,16 +69,25 @@ public class CatalogPagesListComposer extends MessageComposer { this.response.appendInt(category.isEnabled() ? category.getId() : -1); this.response.appendString(category.getPageName()); this.response.appendString(category.getCaption() + (this.hasPermission ? " (" + category.getId() + ")" : "")); - this.response.appendInt(category.getOfferIds().size()); - for (int i : category.getOfferIds().toArray()) { - this.response.appendInt(i); + int[] offers = category.getOfferIds().toArray(); + int offerCount = Math.min(offers.length, MAX_OFFERS); + this.response.appendInt(offerCount); + + for (int idx = 0; idx < offerCount; idx++) { + this.response.appendInt(offers[idx]); } - this.response.appendInt(pagesList.size()); + if (depth >= MAX_DEPTH) { + this.response.appendInt(0); + return; + } - for (CatalogPage page : pagesList) { - this.append(page); + int childCount = Math.min(pagesList.size(), MAX_CHILDREN); + this.response.appendInt(childCount); + + for (int idx = 0; idx < childCount; idx++) { + this.append(pagesList.get(idx), depth + 1); } }