diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionGift.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionGift.java index f00252f2..8a7ec640 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionGift.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionGift.java @@ -13,11 +13,13 @@ import org.slf4j.LoggerFactory; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.concurrent.atomic.AtomicBoolean; public class InteractionGift extends HabboItem { private static final Logger LOGGER = LoggerFactory.getLogger(InteractionGift.class); public boolean explode = false; + private final AtomicBoolean opening = new AtomicBoolean(false); private int[] itemId; private int colorId = 0; private int ribbonId = 0; @@ -46,6 +48,15 @@ public class InteractionGift extends HabboItem { } } + /** + * Claims the right to open this gift, returning true exactly once. Guards + * against two near-simultaneous OpenRecycleBox packets both scheduling an + * (async, delayed) OpenGift before the wrapper is removed from the room. + */ + public boolean tryStartOpening() { + return this.opening.compareAndSet(false, true); + } + @Override public void serializeExtradata(ServerMessage serverMessage) { //serverMessage.appendInt(this.colorId * 1000 + this.ribbonId); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/ClientMessage.java b/Emulator/src/main/java/com/eu/habbo/messages/ClientMessage.java index 55fdae44..3cdfb710 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/ClientMessage.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/ClientMessage.java @@ -61,7 +61,14 @@ public class ClientMessage { public String readString() { try { - int length = this.readShort(); + // Length is an unsigned short in the protocol; mask to avoid a + // negative array size, and clamp to what's actually buffered so a + // bogus length can't throw mid-read and desync the remaining fields. + int length = this.readShort() & 0xFFFF; + int available = this.buffer.readableBytes(); + if (length > available) { + length = available; + } byte[] data = new byte[length]; this.buffer.readBytes(data); return new String(data); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/recycler/OpenRecycleBoxEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/recycler/OpenRecycleBoxEvent.java index 523d5d0d..bd8cd616 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/recycler/OpenRecycleBoxEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/recycler/OpenRecycleBoxEvent.java @@ -29,6 +29,11 @@ public class OpenRecycleBoxEvent extends MessageHandler { if (item.getUserId() != this.client.getHabbo().getHabboInfo().getId()) return; if (item instanceof InteractionGift) { + // The actual unwrap (OpenGift) runs async/delayed and only then + // removes the wrapper, so a second packet would otherwise pass + // the room/owner checks and double-process the gift. Claim it once. + if (!((InteractionGift) item).tryStartOpening()) return; + if (item.getBaseItem().getName().contains("present_wrap")) { ((InteractionGift) item).explode = true; room.updateItem(item);