From 916b9df7a3295ec9c6976b8562b04b787054bed5 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Wed, 17 Jun 2026 18:20:55 +0200 Subject: [PATCH] fix(wired): clamp trigger timers --- .../wired/WiredTimerInputGuard.java | 45 +++++++++++++++++++ .../wired/triggers/WiredTriggerAtSetTime.java | 25 ++++++----- .../triggers/WiredTriggerAtTimeLong.java | 25 ++++++----- .../wired/triggers/WiredTriggerRepeater.java | 31 +++++++------ .../triggers/WiredTriggerRepeaterLong.java | 29 ++++++------ .../triggers/WiredTriggerRepeaterShort.java | 4 +- .../wired/WiredTimerInputGuardTest.java | 35 +++++++++++++++ 7 files changed, 142 insertions(+), 52 deletions(-) create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredTimerInputGuard.java create mode 100644 Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredTimerInputGuardTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredTimerInputGuard.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredTimerInputGuard.java new file mode 100644 index 00000000..e47e7850 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredTimerInputGuard.java @@ -0,0 +1,45 @@ +package com.eu.habbo.habbohotel.items.interactions.wired; + +public final class WiredTimerInputGuard { + public static final int MAX_TIMER_MS = 24 * 60 * 60 * 1000; + + private WiredTimerInputGuard() { + } + + public static int fromClientUnits(int units, int stepMs, int minMs) { + return fromClientUnits(units, stepMs, minMs, MAX_TIMER_MS); + } + + public static int fromClientUnits(int units, int stepMs, int minMs, int maxMs) { + if (units < 1 || stepMs < 1) { + return minMs; + } + + long value = (long) units * stepMs; + return clamp(value, minMs, maxMs); + } + + public static int normalizeStoredMillis(Integer millis, int minMs, int fallbackMs) { + return normalizeStoredMillis(millis, minMs, fallbackMs, MAX_TIMER_MS); + } + + public static int normalizeStoredMillis(Integer millis, int minMs, int fallbackMs, int maxMs) { + if (millis == null || millis < minMs) { + return fallbackMs; + } + + return clamp(millis.longValue(), minMs, maxMs); + } + + private static int clamp(long value, int minMs, int maxMs) { + if (value < minMs) { + return minMs; + } + + if (value > maxMs) { + return maxMs; + } + + return (int) value; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java index 9f460464..c9a02f60 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java @@ -4,6 +4,7 @@ import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; +import com.eu.habbo.habbohotel.items.interactions.wired.WiredTimerInputGuard; import com.eu.habbo.habbohotel.items.interactions.wired.WiredTriggerReset; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; @@ -29,6 +30,9 @@ import java.util.List; */ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { public static final WiredTriggerType type = WiredTriggerType.AT_GIVEN_TIME; + private static final int STEP_MS = 500; + private static final int MIN_DELAY = STEP_MS; + private static final int LEGACY_FALLBACK_DELAY = 20 * STEP_MS; /** The time in milliseconds until the trigger fires */ public int executeTime; @@ -68,18 +72,19 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi public void loadWiredData(ResultSet set, Room room) throws SQLException { String wiredData = set.getString("wired_data"); - if (wiredData.startsWith("{")) { - JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); - this.executeTime = data.executeTime; - } else { - if (wiredData.length() >= 1) { - this.executeTime = (Integer.parseInt(wiredData)); + Integer storedExecuteTime = null; + try { + if (wiredData != null && wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + storedExecuteTime = data != null ? data.executeTime : null; + } else if (wiredData != null && wiredData.length() >= 1) { + storedExecuteTime = Integer.parseInt(wiredData); } + } catch (RuntimeException ignored) { + storedExecuteTime = null; } - if (this.executeTime < 500) { - this.executeTime = 20 * 500; - } + this.executeTime = WiredTimerInputGuard.normalizeStoredMillis(storedExecuteTime, MIN_DELAY, LEGACY_FALLBACK_DELAY); // Initialize for tick system - will be registered by RoomItemManager this.accumulatedTime = 0; @@ -134,7 +139,7 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi @Override public boolean saveData(WiredSettings settings) { if (settings.getIntParams().length < 1) return false; - this.executeTime = settings.getIntParams()[0] * 500; + this.executeTime = WiredTimerInputGuard.fromClientUnits(settings.getIntParams()[0], STEP_MS, MIN_DELAY); this.resetTimer(); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java index 8864f4c3..1ddadc50 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java @@ -4,6 +4,7 @@ import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; +import com.eu.habbo.habbohotel.items.interactions.wired.WiredTimerInputGuard; import com.eu.habbo.habbohotel.items.interactions.wired.WiredTriggerReset; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; @@ -29,6 +30,9 @@ import java.util.List; */ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { private static final WiredTriggerType type = WiredTriggerType.AT_GIVEN_TIME; + private static final int STEP_MS = 500; + private static final int MIN_DELAY = STEP_MS; + private static final int LEGACY_FALLBACK_DELAY = 20 * STEP_MS; /** The time in milliseconds until the trigger fires */ private int executeTime; @@ -68,18 +72,19 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W public void loadWiredData(ResultSet set, Room room) throws SQLException { String wiredData = set.getString("wired_data"); - if (wiredData.startsWith("{")) { - JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); - this.executeTime = data.executeTime; - } else { - if (wiredData.length() >= 1) { - this.executeTime = (Integer.parseInt(wiredData)); + Integer storedExecuteTime = null; + try { + if (wiredData != null && wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + storedExecuteTime = data != null ? data.executeTime : null; + } else if (wiredData != null && wiredData.length() >= 1) { + storedExecuteTime = Integer.parseInt(wiredData); } + } catch (RuntimeException ignored) { + storedExecuteTime = null; } - if (this.executeTime < 500) { - this.executeTime = 20 * 500; - } + this.executeTime = WiredTimerInputGuard.normalizeStoredMillis(storedExecuteTime, MIN_DELAY, LEGACY_FALLBACK_DELAY); // Initialize for tick system this.accumulatedTime = 0; @@ -134,7 +139,7 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W @Override public boolean saveData(WiredSettings settings) { if (settings.getIntParams().length < 1) return false; - this.executeTime = settings.getIntParams()[0] * 500; + this.executeTime = WiredTimerInputGuard.fromClientUnits(settings.getIntParams()[0], STEP_MS, MIN_DELAY); this.resetTimer(); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java index 2ac70e88..2be1b2ff 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java @@ -4,6 +4,7 @@ import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; +import com.eu.habbo.habbohotel.items.interactions.wired.WiredTimerInputGuard; import com.eu.habbo.habbohotel.items.interactions.wired.WiredTriggerReset; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; @@ -30,6 +31,9 @@ import java.util.List; public class WiredTriggerRepeater extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { public static final WiredTriggerType type = WiredTriggerType.PERIODICALLY; public static final int DEFAULT_DELAY = 10 * 500; // 5 seconds default + private static final int STEP_MS = 500; + private static final int MIN_DELAY = STEP_MS; + private static final int LEGACY_FALLBACK_DELAY = 20 * STEP_MS; /** The interval in milliseconds between triggers */ protected int repeatTime = DEFAULT_DELAY; @@ -63,18 +67,19 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements Wir public void loadWiredData(ResultSet set, Room room) throws SQLException { String wiredData = set.getString("wired_data"); - if (wiredData.startsWith("{")) { - JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); - this.repeatTime = data.repeatTime; - } else { - if (wiredData.length() >= 1) { - this.repeatTime = (Integer.parseInt(wiredData)); + Integer storedRepeatTime = null; + try { + if (wiredData != null && wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + storedRepeatTime = data != null ? data.repeatTime : null; + } else if (wiredData != null && wiredData.length() >= 1) { + storedRepeatTime = Integer.parseInt(wiredData); } + } catch (RuntimeException ignored) { + storedRepeatTime = null; } - if (this.repeatTime < 500) { - this.repeatTime = 20 * 500; - } + this.repeatTime = WiredTimerInputGuard.normalizeStoredMillis(storedRepeatTime, MIN_DELAY, LEGACY_FALLBACK_DELAY); } @Override @@ -123,13 +128,7 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements Wir @Override public boolean saveData(WiredSettings settings) { if (settings.getIntParams().length < 1) return false; - int newRepeatTime = settings.getIntParams()[0] * 500; - - if (newRepeatTime < 500) { - newRepeatTime = 500; - } - - this.repeatTime = newRepeatTime; + this.repeatTime = WiredTimerInputGuard.fromClientUnits(settings.getIntParams()[0], STEP_MS, MIN_DELAY); return true; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java index ffc1d92f..5e42f57b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java @@ -4,6 +4,7 @@ import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; +import com.eu.habbo.habbohotel.items.interactions.wired.WiredTimerInputGuard; import com.eu.habbo.habbohotel.items.interactions.wired.WiredTriggerReset; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; @@ -29,6 +30,9 @@ import java.util.List; */ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { public static final int DEFAULT_DELAY = 10 * 5000; // 50 seconds default + private static final int STEP_MS = 5000; + private static final int MIN_DELAY = STEP_MS; + private static final int LEGACY_FALLBACK_DELAY = 20 * STEP_MS; private static final WiredTriggerType type = WiredTriggerType.PERIODICALLY_LONG; /** The interval in milliseconds between triggers */ @@ -63,18 +67,19 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements public void loadWiredData(ResultSet set, Room room) throws SQLException { String wiredData = set.getString("wired_data"); - if (wiredData.startsWith("{")) { - JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); - this.repeatTime = data.repeatTime; - } else { - if (wiredData.length() >= 1) { - this.repeatTime = (Integer.parseInt(wiredData)); + Integer storedRepeatTime = null; + try { + if (wiredData != null && wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + storedRepeatTime = data != null ? data.repeatTime : null; + } else if (wiredData != null && wiredData.length() >= 1) { + storedRepeatTime = Integer.parseInt(wiredData); } + } catch (RuntimeException ignored) { + storedRepeatTime = null; } - if (this.repeatTime < 5000) { - this.repeatTime = 20 * 5000; - } + this.repeatTime = WiredTimerInputGuard.normalizeStoredMillis(storedRepeatTime, MIN_DELAY, LEGACY_FALLBACK_DELAY); } @Override @@ -123,11 +128,7 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements @Override public boolean saveData(WiredSettings settings) { if (settings.getIntParams().length < 1) return false; - int interval = settings.getIntParams()[0]; - if (interval < 1) { - interval = 1; - } - this.repeatTime = interval * 5000; + this.repeatTime = WiredTimerInputGuard.fromClientUnits(settings.getIntParams()[0], STEP_MS, MIN_DELAY); // No accumulated time reset needed - using global tick count return true; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterShort.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterShort.java index e20f5bae..40da9fc7 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterShort.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterShort.java @@ -3,6 +3,7 @@ package com.eu.habbo.habbohotel.items.interactions.wired.triggers; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; +import com.eu.habbo.habbohotel.items.interactions.wired.WiredTimerInputGuard; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.wired.WiredTriggerType; import com.eu.habbo.habbohotel.wired.core.WiredManager; @@ -94,8 +95,7 @@ public class WiredTriggerRepeaterShort extends WiredTriggerRepeater { public boolean saveData(WiredSettings settings) { if (settings.getIntParams().length < 1) return false; - int newRepeatTime = settings.getIntParams()[0] * STEP_MS; - this.repeatTime = clampRepeatTime(newRepeatTime); + this.repeatTime = WiredTimerInputGuard.fromClientUnits(settings.getIntParams()[0], STEP_MS, MIN_DELAY, MAX_DELAY); return true; } diff --git a/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredTimerInputGuardTest.java b/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredTimerInputGuardTest.java new file mode 100644 index 00000000..f739d781 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/WiredTimerInputGuardTest.java @@ -0,0 +1,35 @@ +package com.eu.habbo.habbohotel.items.interactions.wired; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class WiredTimerInputGuardTest { + + @Test + void clientTimerUnitsAreMultipliedWithoutOverflow() { + assertEquals(500, WiredTimerInputGuard.fromClientUnits(1, 500, 500)); + assertEquals(WiredTimerInputGuard.MAX_TIMER_MS, + WiredTimerInputGuard.fromClientUnits(Integer.MAX_VALUE, 5000, 5000)); + } + + @Test + void invalidClientTimerUnitsUseMinimumDelay() { + assertEquals(500, WiredTimerInputGuard.fromClientUnits(0, 500, 500)); + assertEquals(5000, WiredTimerInputGuard.fromClientUnits(-10, 5000, 5000)); + } + + @Test + void storedTimerValuesFallbackOrClamp() { + assertEquals(10000, WiredTimerInputGuard.normalizeStoredMillis(null, 500, 10000)); + assertEquals(10000, WiredTimerInputGuard.normalizeStoredMillis(-1, 500, 10000)); + assertEquals(500, WiredTimerInputGuard.normalizeStoredMillis(500, 500, 10000)); + assertEquals(WiredTimerInputGuard.MAX_TIMER_MS, + WiredTimerInputGuard.normalizeStoredMillis(Integer.MAX_VALUE, 500, 10000)); + } + + @Test + void shortRepeaterKeepsItsLegacyMaximum() { + assertEquals(500, WiredTimerInputGuard.fromClientUnits(Integer.MAX_VALUE, 50, 50, 500)); + } +}