From a5dabd924ec28c4975d38de89277ad98592551d6 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Wed, 17 Jun 2026 18:37:25 +0200 Subject: [PATCH] fix(wired): bound match position inputs --- .../WiredConditionMatchStatePosition.java | 46 +++++++--- .../WiredMatchPositionInputGuard.java | 92 +++++++++++++++++++ .../WiredMatchPositionInputGuardTest.java | 33 +++++++ 3 files changed, 158 insertions(+), 13 deletions(-) create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredMatchPositionInputGuard.java create mode 100644 Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredMatchPositionInputGuardTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java index 36e721fc..2a306797 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java @@ -87,7 +87,7 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition this.direction = params[1] == 1; this.position = params[2] == 1; this.altitude = (params.length > 3) && (params[3] == 1); - this.furniSource = (params.length > 4) ? params[4] : ((params.length > 3 && params[3] > 1) ? params[3] : WiredSourceUtil.SOURCE_TRIGGER); + this.furniSource = (params.length > 4) ? WiredMatchPositionInputGuard.normalizeFurniSource(params[4], false) : ((params.length > 3 && params[3] > 1) ? WiredMatchPositionInputGuard.normalizeFurniSource(params[3], false) : WiredSourceUtil.SOURCE_TRIGGER); this.quantifier = (params.length > 5) ? this.normalizeQuantifier(params[5]) : QUANTIFIER_ALL; Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); @@ -108,6 +108,8 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition this.settings.add(new WiredMatchFurniSetting(item.getId(), item.getExtradata(), item.getRotation(), item.getX(), item.getY(), item.getZ())); } + this.furniSource = WiredMatchPositionInputGuard.normalizeFurniSource(this.furniSource, !this.settings.isEmpty()); + return true; } @@ -255,27 +257,23 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition this.position = data.position; this.direction = data.direction; this.altitude = data.altitude; - if (data.settings != null) { - this.settings.addAll(data.settings); - } - this.furniSource = data.furniSource; + this.settings.addAll(WiredMatchPositionInputGuard.sanitizeSettings(data.settings, room)); + this.furniSource = WiredMatchPositionInputGuard.normalizeFurniSource(data.furniSource, !this.settings.isEmpty()); this.quantifier = this.normalizeQuantifier(data.quantifier); } else { String[] data = wiredData.split(":"); if (data.length >= 5) { try { - int itemCount = Integer.parseInt(data[0]); + int itemCount = Math.min(Integer.parseInt(data[0]), WiredManager.MAXIMUM_FURNI_SELECTION); String[] items = data[1].split(";"); for (int i = 0; i < itemCount && i < items.length; i++) { - String[] stuff = items[i].split("-"); - - if (stuff.length >= 6) - this.settings.add(new WiredMatchFurniSetting(Integer.parseInt(stuff[0]), stuff[1], Integer.parseInt(stuff[2]), Integer.parseInt(stuff[3]), Integer.parseInt(stuff[4]), Double.parseDouble(stuff[5]))); - else if (stuff.length >= 5) - this.settings.add(new WiredMatchFurniSetting(Integer.parseInt(stuff[0]), stuff[1], Integer.parseInt(stuff[2]), Integer.parseInt(stuff[3]), Integer.parseInt(stuff[4]))); + WiredMatchFurniSetting setting = this.parseLegacySetting(items[i], room); + if (setting != null) { + this.settings.add(setting); + } } this.state = data[2].equals("1"); @@ -287,11 +285,33 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition } this.altitude = false; - this.furniSource = this.settings.isEmpty() ? WiredSourceUtil.SOURCE_TRIGGER : WiredSourceUtil.SOURCE_SELECTED; + this.furniSource = WiredMatchPositionInputGuard.normalizeFurniSource(WiredSourceUtil.SOURCE_TRIGGER, !this.settings.isEmpty()); this.quantifier = QUANTIFIER_ALL; } } + private WiredMatchFurniSetting parseLegacySetting(String value, Room room) { + String[] parts = value.split("-", 6); + if (parts.length < 5) { + return null; + } + + try { + double z = (parts.length >= 6) ? Double.parseDouble(parts[5]) : 0.0D; + return WiredMatchPositionInputGuard.sanitizeParts( + Integer.parseInt(parts[0]), + parts[1], + Integer.parseInt(parts[2]), + Integer.parseInt(parts[3]), + Integer.parseInt(parts[4]), + z, + room + ); + } catch (NumberFormatException ignored) { + return null; + } + } + @Override public void onPickUp() { this.settings.clear(); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredMatchPositionInputGuard.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredMatchPositionInputGuard.java new file mode 100644 index 00000000..563387d0 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredMatchPositionInputGuard.java @@ -0,0 +1,92 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.conditions; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredMatchFurniSetting; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public final class WiredMatchPositionInputGuard { + public static final int MAX_STATE_LENGTH = 512; + + private WiredMatchPositionInputGuard() { + } + + public static int normalizeFurniSource(int value, boolean hasSelectedSettings) { + int source = switch (value) { + case WiredSourceUtil.SOURCE_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, + WiredSourceUtil.SOURCE_SIGNAL, WiredSourceUtil.SOURCE_TRIGGER -> value; + default -> WiredSourceUtil.SOURCE_TRIGGER; + }; + + return (hasSelectedSettings && source == WiredSourceUtil.SOURCE_TRIGGER) + ? WiredSourceUtil.SOURCE_SELECTED + : source; + } + + public static List sanitizeSettings(Collection settings, Room room) { + List result = new ArrayList<>(); + if (settings == null || room == null) { + return result; + } + + for (WiredMatchFurniSetting setting : settings) { + WiredMatchFurniSetting normalized = sanitizeSetting(setting, room); + if (normalized != null) { + result.add(normalized); + } + + if (result.size() >= WiredManager.MAXIMUM_FURNI_SELECTION) { + break; + } + } + + return result; + } + + public static WiredMatchFurniSetting sanitizeSetting(WiredMatchFurniSetting setting, Room room) { + if (setting == null || room == null) { + return null; + } + + return sanitizeParts(setting.item_id, setting.state, setting.rotation, setting.x, setting.y, setting.z, room); + } + + public static WiredMatchFurniSetting sanitizeParts(int itemId, String state, int rotation, int x, int y, double z, Room room) { + if (itemId < 1 || room == null) { + return null; + } + + HabboItem item = room.getHabboItem(itemId); + if (item == null || rotation < 0 || rotation > 7 || !Double.isFinite(z)) { + return null; + } + + if (x < Short.MIN_VALUE || x > Short.MAX_VALUE || y < Short.MIN_VALUE || y > Short.MAX_VALUE) { + return null; + } + + if (room.getLayout() != null && room.getLayout().getTile((short) x, (short) y) == null) { + return null; + } + + return new WiredMatchFurniSetting(itemId, normalizeState(state), rotation, x, y, z); + } + + public static String normalizeState(String state) { + if (state == null) { + return ""; + } + + String normalized = state.replace('\t', ' ').replace('\r', ' ').replace('\n', ' '); + if (normalized.length() > MAX_STATE_LENGTH) { + return normalized.substring(0, MAX_STATE_LENGTH); + } + + return normalized; + } +} diff --git a/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredMatchPositionInputGuardTest.java b/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredMatchPositionInputGuardTest.java new file mode 100644 index 00000000..3e5878df --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredMatchPositionInputGuardTest.java @@ -0,0 +1,33 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.conditions; + +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class WiredMatchPositionInputGuardTest { + + @Test + void furniSourcesFallBackToTriggerWhenUnknown() { + assertEquals(WiredSourceUtil.SOURCE_TRIGGER, WiredMatchPositionInputGuard.normalizeFurniSource(-1, false)); + assertEquals(WiredSourceUtil.SOURCE_SELECTOR, WiredMatchPositionInputGuard.normalizeFurniSource(WiredSourceUtil.SOURCE_SELECTOR, false)); + assertEquals(WiredSourceUtil.SOURCE_SIGNAL, WiredMatchPositionInputGuard.normalizeFurniSource(WiredSourceUtil.SOURCE_SIGNAL, false)); + } + + @Test + void selectedSettingsPromoteTriggerSourceToSelected() { + assertEquals(WiredSourceUtil.SOURCE_SELECTED, + WiredMatchPositionInputGuard.normalizeFurniSource(WiredSourceUtil.SOURCE_TRIGGER, true)); + } + + @Test + void stateIsNullSafeSingleLineAndBounded() { + assertEquals("", WiredMatchPositionInputGuard.normalizeState(null)); + assertEquals("a b c", WiredMatchPositionInputGuard.normalizeState("a\tb\nc")); + String longState = "x".repeat(WiredMatchPositionInputGuard.MAX_STATE_LENGTH + 10); + String normalized = WiredMatchPositionInputGuard.normalizeState(longState); + assertEquals(WiredMatchPositionInputGuard.MAX_STATE_LENGTH, normalized.length()); + assertFalse(normalized.contains("\n")); + } +}