From 6b4da4f916339c82775dc319903f64dd6224411d Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Wed, 17 Jun 2026 21:39:52 +0200 Subject: [PATCH] fix(crafting): bound secret ingredients --- .../crafting/CraftingCraftSecretEvent.java | 14 +++++-- .../CraftingCraftSecretContractTest.java | 40 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 Emulator/src/test/java/com/eu/habbo/messages/incoming/crafting/CraftingCraftSecretContractTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/crafting/CraftingCraftSecretEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/crafting/CraftingCraftSecretEvent.java index f93f2084..7f074a91 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/crafting/CraftingCraftSecretEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/crafting/CraftingCraftSecretEvent.java @@ -20,11 +20,18 @@ import java.util.Map; import java.util.Set; public class CraftingCraftSecretEvent extends MessageHandler { + static final int MAX_SECRET_CRAFT_INGREDIENTS = 50; + @Override public void handle() throws Exception { int altarId = this.packet.readInt(); int count = this.packet.readInt(); + if (count <= 0 || count > MAX_SECRET_CRAFT_INGREDIENTS) { + this.client.sendResponse(new CraftingResultComposer(null)); + return; + } + HabboItem craftingAltar = this.client.getHabbo().getHabboInfo().getCurrentRoom().getHabboItem(altarId); if (craftingAltar != null) { @@ -35,15 +42,14 @@ public class CraftingCraftSecretEvent extends MessageHandler { Map items = new THashMap<>(); for (int i = 0; i < count; i++) { - HabboItem habboItem = this.client.getHabbo().getInventory().getItemsComponent().getHabboItem(this.packet.readInt()); + int itemId = this.packet.readInt(); + HabboItem habboItem = this.client.getHabbo().getInventory().getItemsComponent().getHabboItem(itemId); - if (habboItem == null) { + if (habboItem == null || !habboItems.add(habboItem)) { this.client.sendResponse(new CraftingResultComposer(null)); return; } - habboItems.add(habboItem); - if (!items.containsKey(habboItem.getBaseItem())) { items.put(habboItem.getBaseItem(), 0); } diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/crafting/CraftingCraftSecretContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/crafting/CraftingCraftSecretContractTest.java new file mode 100644 index 00000000..90c9e10b --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/crafting/CraftingCraftSecretContractTest.java @@ -0,0 +1,40 @@ +package com.eu.habbo.messages.incoming.crafting; + +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CraftingCraftSecretContractTest { + private static String source() throws Exception { + return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/crafting/CraftingCraftSecretEvent.java")); + } + + @Test + void rejectsInvalidIngredientCountsBeforeReadingItemIds() throws Exception { + String source = source(); + + int countRead = source.indexOf("int count = this.packet.readInt()"); + int guard = source.indexOf("count <= 0 || count > MAX_SECRET_CRAFT_INGREDIENTS", countRead); + int loop = source.indexOf("for (int i = 0; i < count; i++)", guard); + + assertTrue(countRead > -1, "secret crafting must read the client supplied ingredient count"); + assertTrue(guard > countRead, "secret crafting must validate the ingredient count"); + assertTrue(loop > guard, "ingredient count validation must happen before item id reads"); + } + + @Test + void rejectsDuplicateIngredientItemsBeforeRecipeLookup() throws Exception { + String source = source(); + + int setDeclaration = source.indexOf("Set habboItems = new THashSet<>()"); + int duplicateGuard = source.indexOf("habboItem == null || !habboItems.add(habboItem)", setDeclaration); + int recipeLookup = source.indexOf("CraftingRecipe recipe = altar.getRecipe(items)", duplicateGuard); + + assertTrue(setDeclaration > -1, "secret crafting should track unique inventory items"); + assertTrue(duplicateGuard > setDeclaration, "secret crafting must reject duplicate item ids"); + assertTrue(recipeLookup > duplicateGuard, "duplicate rejection must happen before recipe lookup/reward creation"); + } +}