From 7f38a25eef0af35fdceaeec75f54053dc26b5b34 Mon Sep 17 00:00:00 2001 From: duckietm Date: Thu, 21 May 2026 15:44:30 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=86=99=20Small=20SQL=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Database Updates/003_live_required_schema.sql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Database Updates/003_live_required_schema.sql b/Database Updates/003_live_required_schema.sql index cd84766c..7a5c5692 100644 --- a/Database Updates/003_live_required_schema.sql +++ b/Database Updates/003_live_required_schema.sql @@ -23,6 +23,10 @@ SET NAMES utf8mb4; ALTER TABLE `emulator_settings` ADD COLUMN IF NOT EXISTS `comment` TEXT NULL DEFAULT '' AFTER `value`; +ALTER TABLE catalog_pages + ADD COLUMN IF NOT EXISTS `catalog_mode1` ENUM('NORMAL', 'BUILDER', 'BOTH') NOT NULL DEFAULT 'NORMAL' AFTER `includes`; + + CREATE TABLE IF NOT EXISTS `wired_emulator_settings` ( `key` VARCHAR(255) NOT NULL, `value` TEXT NOT NULL, From d321ff3b85f0a845adb634ef0636f6fddbc665ba Mon Sep 17 00:00:00 2001 From: duckietm Date: Thu, 21 May 2026 15:54:10 +0200 Subject: [PATCH 2/3] Update 003_live_required_schema.sql --- Database Updates/003_live_required_schema.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Database Updates/003_live_required_schema.sql b/Database Updates/003_live_required_schema.sql index 7a5c5692..f3d73527 100644 --- a/Database Updates/003_live_required_schema.sql +++ b/Database Updates/003_live_required_schema.sql @@ -24,7 +24,7 @@ ALTER TABLE `emulator_settings` ADD COLUMN IF NOT EXISTS `comment` TEXT NULL DEFAULT '' AFTER `value`; ALTER TABLE catalog_pages - ADD COLUMN IF NOT EXISTS `catalog_mode1` ENUM('NORMAL', 'BUILDER', 'BOTH') NOT NULL DEFAULT 'NORMAL' AFTER `includes`; + ADD COLUMN IF NOT EXISTS `catalog_mode` ENUM('NORMAL', 'BUILDER', 'BOTH') NOT NULL DEFAULT 'NORMAL' AFTER `includes`; CREATE TABLE IF NOT EXISTS `wired_emulator_settings` ( From 91263969737cc25e7274a4f9ae181f499dc5501e Mon Sep 17 00:00:00 2001 From: duckietm Date: Thu, 21 May 2026 17:01:56 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=86=99=20Fix=20Catalog=20Edit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CatalogAdminCreatePageEvent.java | 15 ++++- .../CatalogAdminMovePageEvent.java | 50 ++++++++++++--- .../CatalogAdminSavePageEvent.java | 63 ++++++++++++++++++- .../catalog/CatalogPagesListComposer.java | 4 +- 4 files changed, 122 insertions(+), 10 deletions(-) diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminCreatePageEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminCreatePageEvent.java index 598582ad..420e21c0 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminCreatePageEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminCreatePageEvent.java @@ -36,8 +36,21 @@ public class CatalogAdminCreatePageEvent extends MessageHandler { pageLayout = CatalogPageLayouts.default_3x3; } + if (parentId != -1 && Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(parentId) == null) { + this.client.sendResponse(new CatalogAdminResultComposer(false, "Parent page not found: " + parentId)); + return; + } + + if (iconType < 0) iconType = 0; + if (minRank < 1) minRank = 1; + if (orderNum < 0) orderNum = 0; + if (caption == null) caption = ""; + if (caption2 == null) caption2 = ""; + if (caption.length() > 128) caption = caption.substring(0, 128); + if (caption2.length() > 25) caption2 = caption2.substring(0, 25); + CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().createCatalogPage( - caption, caption2, 0, iconType, pageLayout, minRank, parentId, pageType, catalogMode + caption, caption2, 0, iconType, pageLayout, minRank, parentId, pageType, catalogMode ); if (page == null) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminMovePageEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminMovePageEvent.java index 20be1400..c88725c0 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminMovePageEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminMovePageEvent.java @@ -1,6 +1,7 @@ package com.eu.habbo.messages.incoming.catalog.catalogadmin; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.catalog.CatalogPage; import com.eu.habbo.habbohotel.catalog.CatalogPageType; import com.eu.habbo.habbohotel.permissions.Permission; import com.eu.habbo.messages.incoming.MessageHandler; @@ -11,6 +12,9 @@ import java.sql.PreparedStatement; public class CatalogAdminMovePageEvent extends MessageHandler { + private static final int MAX_PARENT_WALK = 64; + private static final int ROOT_PARENT_ID = -1; + @Override public void handle() throws Exception { if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) { @@ -24,12 +28,10 @@ public class CatalogAdminMovePageEvent extends MessageHandler { CatalogPageType pageType = CatalogPageType.fromString(this.packet.readString()); String tableName = (pageType == CatalogPageType.BUILDER) ? "catalog_pages_bc" : "catalog_pages"; - // Special values: -1 = toggle enabled, -2 = toggle visible if (newParentId == -1) { - // Toggle enabled try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement( - "UPDATE " + tableName + " SET enabled = IF(enabled = '1', '0', '1') WHERE id = ?")) { + "UPDATE " + tableName + " SET enabled = IF(enabled = '1', '0', '1') WHERE id = ?")) { statement.setInt(1, pageId); statement.execute(); } @@ -38,21 +40,43 @@ public class CatalogAdminMovePageEvent extends MessageHandler { } if (newParentId == -2) { - // Toggle visible try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement( - "UPDATE " + tableName + " SET visible = IF(visible = '1', '0', '1') WHERE id = ?")) { + "UPDATE " + tableName + " SET visible = IF(visible = '1', '0', '1') WHERE id = ?")) { statement.setInt(1, pageId); statement.execute(); } this.client.sendResponse(new CatalogAdminResultComposer(true, "Visibility toggled")); return; } + + CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(pageId, pageType); + if (page == null) { + this.client.sendResponse(new CatalogAdminResultComposer(false, "Page not found: " + pageId)); + return; + } + + if (newParentId == pageId) { + this.client.sendResponse(new CatalogAdminResultComposer(false, "A page cannot be its own parent")); + return; + } + + CatalogPage parent = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(newParentId); + if (parent == null) { + this.client.sendResponse(new CatalogAdminResultComposer(false, "Parent page not found: " + newParentId)); + return; + } + + if (this.wouldCreateCycle(pageId, newParentId)) { + this.client.sendResponse(new CatalogAdminResultComposer(false, "Refusing to move: that would create a cycle")); + return; + } + + if (newIndex < 0) newIndex = 0; - // Normal move: update parent and order try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement( - "UPDATE " + tableName + " SET parent_id = ?, order_num = ? WHERE id = ?")) { + "UPDATE " + tableName + " SET parent_id = ?, order_num = ? WHERE id = ?")) { statement.setInt(1, newParentId); statement.setInt(2, newIndex); statement.setInt(3, pageId); @@ -61,4 +85,16 @@ public class CatalogAdminMovePageEvent extends MessageHandler { this.client.sendResponse(new CatalogAdminResultComposer(true, "Page moved")); } + + private boolean wouldCreateCycle(int pageId, int parentId) { + int current = parentId; + for (int hops = 0; hops < MAX_PARENT_WALK; hops++) { + if (current == ROOT_PARENT_ID) return false; + if (current == pageId) return true; + CatalogPage parent = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(current); + if (parent == null) return false; + current = parent.getParentId(); + } + return true; + } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminSavePageEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminSavePageEvent.java index 7c8ab7ed..be5e0ec1 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminSavePageEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/catalogadmin/CatalogAdminSavePageEvent.java @@ -2,6 +2,7 @@ package com.eu.habbo.messages.incoming.catalog.catalogadmin; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.catalog.CatalogPage; +import com.eu.habbo.habbohotel.catalog.CatalogPageLayouts; import com.eu.habbo.habbohotel.catalog.CatalogPageType; import com.eu.habbo.habbohotel.permissions.Permission; import com.eu.habbo.messages.incoming.MessageHandler; @@ -12,6 +13,14 @@ import java.sql.PreparedStatement; public class CatalogAdminSavePageEvent extends MessageHandler { + private static final int MAX_CAPTION_LENGTH = 128; + private static final int MAX_CAPTION_SAVE_LENGTH = 25; + private static final int MAX_HEADLINE_LENGTH = 1024; + private static final int MAX_TEASER_LENGTH = 64; + private static final int MAX_TEXT_LENGTH = 8192; + private static final int MAX_PARENT_WALK = 64; + private static final int ROOT_PARENT_ID = -1; + @Override public void handle() throws Exception { if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) { @@ -34,7 +43,6 @@ public class CatalogAdminSavePageEvent extends MessageHandler { String textDetails = this.packet.readString(); CatalogPageType pageType = CatalogPageType.fromString(this.packet.readString()); CatalogPageType catalogMode = CatalogPageType.fromString(this.packet.readString()); - CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(pageId, pageType); if (page == null) { @@ -42,6 +50,41 @@ public class CatalogAdminSavePageEvent extends MessageHandler { return; } + try { + CatalogPageLayouts.valueOf(layout); + } catch (IllegalArgumentException | NullPointerException e) { + this.client.sendResponse(new CatalogAdminResultComposer(false, "Invalid layout: " + layout)); + return; + } + + if (parentId != ROOT_PARENT_ID) { + if (parentId == pageId) { + this.client.sendResponse(new CatalogAdminResultComposer(false, "A page cannot be its own parent")); + return; + } + + CatalogPage parent = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(parentId); + if (parent == null) { + this.client.sendResponse(new CatalogAdminResultComposer(false, "Parent page not found: " + parentId)); + return; + } + + if (this.wouldCreateCycle(pageId, parentId)) { + this.client.sendResponse(new CatalogAdminResultComposer(false, "Refusing to re-parent: that would create a cycle")); + return; + } + } + + if (iconType < 0) iconType = 0; + if (minRank < 1) minRank = 1; + if (orderNum < 0) orderNum = 0; + + caption = this.clampLength(caption, MAX_CAPTION_LENGTH); + caption2 = this.clampLength(caption2, MAX_CAPTION_SAVE_LENGTH); + headline = this.clampLength(headline, MAX_HEADLINE_LENGTH); + teaser = this.clampLength(teaser, MAX_TEASER_LENGTH); + textDetails = this.clampLength(textDetails, MAX_TEXT_LENGTH); + String query = (pageType == CatalogPageType.BUILDER) ? "UPDATE catalog_pages_bc SET caption = ?, page_layout = ?, icon_image = ?, visible = ?, enabled = ?, order_num = ?, parent_id = ?, page_headline = ?, page_teaser = ?, page_text_details = ? WHERE id = ?" : "UPDATE catalog_pages SET caption = ?, caption_save = ?, page_layout = ?, icon_image = ?, min_rank = ?, visible = ?, enabled = ?, order_num = ?, parent_id = ?, page_headline = ?, page_teaser = ?, page_text_details = ?, catalog_mode = ? WHERE id = ?"; @@ -82,4 +125,22 @@ public class CatalogAdminSavePageEvent extends MessageHandler { this.client.sendResponse(new CatalogAdminResultComposer(true, "Page saved")); } + + private boolean wouldCreateCycle(int pageId, int parentId) { + int current = parentId; + for (int hops = 0; hops < MAX_PARENT_WALK; hops++) { + if (current == ROOT_PARENT_ID) return false; + if (current == pageId) return true; + CatalogPage parent = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(current); + if (parent == null) return false; + current = parent.getParentId(); + } + return true; + } + + private String clampLength(String value, int max) { + if (value == null) return ""; + if (value.length() <= max) return value; + return value.substring(0, max); + } } 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 c0acdc53..babc9bfd 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 @@ -41,6 +41,7 @@ public class CatalogPagesListComposer extends MessageComposer { this.response.appendBoolean(true); this.response.appendInt(0); this.response.appendInt(-1); + this.response.appendInt(-1); this.response.appendString("root"); this.response.appendString(""); this.response.appendInt(0); @@ -68,7 +69,8 @@ public class CatalogPagesListComposer extends MessageComposer { this.response.appendBoolean(category.isVisible()); this.response.appendInt(category.getIconImage()); - this.response.appendInt(category.isEnabled() ? category.getId() : -1); + this.response.appendInt(category.isEnabled() || this.hasPermission ? category.getId() : -1); + this.response.appendInt(category.getParentId()); this.response.appendString(category.getPageName()); this.response.appendString(category.getCaption() + (this.hasPermission ? " (" + category.getId() + ")" : ""));