diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/BotManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/BotManager.java index ff2b1d72..7fd3ee93 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/BotManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/BotManager.java @@ -188,7 +188,11 @@ public class BotManager { if (pickedUpEvent.isCancelled()) return; - if (habbo == null || (bot.getOwnerId() == habbo.getHabboInfo().getId() || habbo.hasPermission(Permission.ACC_ANYROOMOWNER))) { + Room currentRoom = habbo != null ? habbo.getHabboInfo().getCurrentRoom() : null; + if (habbo == null + || bot.getOwnerId() == habbo.getHabboInfo().getId() + || habbo.hasPermission(Permission.ACC_ANYROOMOWNER) + || (currentRoom != null && (currentRoom.getOwnerId() == habbo.getHabboInfo().getId() || habbo.hasPermission(Permission.ACC_PLACEFURNI)))) { if (habbo != null && !habbo.hasPermission(Permission.ACC_UNLIMITED_BOTS) && habbo.getInventory().getBotsComponent().getBots().size() >= BotManager.MAXIMUM_BOT_INVENTORY_SIZE) { habbo.alert(Emulator.getTexts().getValue("error.bots.max.inventory").replace("%amount%", BotManager.MAXIMUM_BOT_INVENTORY_SIZE + "")); return; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetBreedingNest.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetBreedingNest.java index 78880aab..075efac1 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetBreedingNest.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/pets/InteractionPetBreedingNest.java @@ -190,6 +190,14 @@ public class InteractionPetBreedingNest extends HabboItem { } public void breed(Habbo habbo, String name, int petOneId, int petTwoId) { + // Guard before the destructive delete below: a crafted packet can call + // this on a nest that isn't full, which would delete the nest furni and + // then NPE on petOne/petTwo in the async runnable (losing the furni). + if (habbo == null || this.petOne == null || this.petTwo == null + || habbo.getHabboInfo().getCurrentRoom() == null) { + return; + } + Emulator.getThreading().run(new QueryDeleteHabboItem(this.getId())); this.setExtradata("2"); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java index 92c60e46..6a5e4b59 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java @@ -151,7 +151,7 @@ public class WiredEffectTeleport extends InteractionWiredEffect { @Override public boolean execute(InteractionWiredTrigger object) { if (!object.isTriggeredByRoomUnit()) { - invalidTriggers.add(object.getId()); + invalidTriggers.add(object.getBaseItem().getSpriteId()); } return true; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserFurniBase.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserFurniBase.java index 359e463b..7973090d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserFurniBase.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserFurniBase.java @@ -252,7 +252,7 @@ public abstract class WiredEffectUserFurniBase extends InteractionWiredEffect { @Override public boolean execute(InteractionWiredTrigger object) { if (!object.isTriggeredByRoomUnit()) { - invalidTriggers.add(object.getId()); + invalidTriggers.add(object.getBaseItem().getSpriteId()); } return true; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java index ff4f4e25..da3ccc3a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java @@ -300,37 +300,35 @@ public class RoomCycleManager { return; } - // currentBots is a TCollections.synchronizedMap; the iterator is not - // safe against concurrent put/remove from IO threads (bot place/pickup), - // so we must hold the map's monitor for the whole traversal. + // Snapshot under the map monitor (currentBots is a synchronizedMap whose + // iterator isn't concurrency-safe), then cycle OFF-lock. Holding the + // monitor across the whole tick would block bot place/pickup and room + // dispose for the tick duration AND invert the lock order vs + // roomUnitLock -> currentBots taken by RoomUnitManager.addBot/clear. + final ArrayList bots; synchronized (currentBots) { - TIntObjectIterator botIterator = currentBots.iterator(); - for (int i = currentBots.size(); i-- > 0; ) { - try { - final Bot bot; - try { - botIterator.advance(); - bot = botIterator.value(); - } catch (Exception e) { - break; - } + bots = new ArrayList<>(currentBots.valueCollection()); + } - if (!this.room.isAllowBotsWalk() && bot.getRoomUnit().isWalking()) { - bot.getRoomUnit().stopWalking(); - updatedUnit.add(bot.getRoomUnit()); - continue; - } - - bot.cycle(this.room.isAllowBotsWalk()); - - if (this.cycleRoomUnit(bot.getRoomUnit(), RoomUnitType.BOT)) { - updatedUnit.add(bot.getRoomUnit()); - } - - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; + for (Bot bot : bots) { + try { + if (bot == null || bot.getRoomUnit() == null) { + continue; } + + if (!this.room.isAllowBotsWalk() && bot.getRoomUnit().isWalking()) { + bot.getRoomUnit().stopWalking(); + updatedUnit.add(bot.getRoomUnit()); + continue; + } + + bot.cycle(this.room.isAllowBotsWalk()); + + if (this.cycleRoomUnit(bot.getRoomUnit(), RoomUnitType.BOT)) { + updatedUnit.add(bot.getRoomUnit()); + } + } catch (Exception e) { + LOGGER.error("Caught exception", e); } } } @@ -344,19 +342,19 @@ public class RoomCycleManager { return; } - // currentPets is a TCollections.synchronizedMap; hold its monitor for the - // whole traversal to stay safe against concurrent pet place/pickup. + // Snapshot under the monitor, then cycle off-lock (see processBots): avoids + // holding currentPets for the whole tick and the roomUnitLock inversion. + final ArrayList pets; synchronized (currentPets) { - TIntObjectIterator petIterator = currentPets.iterator(); - for (int i = currentPets.size(); i-- > 0; ) { - try { - petIterator.advance(); - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; + pets = new ArrayList<>(currentPets.valueCollection()); + } + + for (Pet pet : pets) { + try { + if (pet == null || pet.getRoomUnit() == null) { + continue; } - Pet pet = petIterator.value(); if (this.cycleRoomUnit(pet.getRoomUnit(), RoomUnitType.PET)) { updatedUnit.add(pet.getRoomUnit()); } @@ -373,6 +371,8 @@ public class RoomCycleManager { pet.getRoomUnit().removeStatus(RoomUnitStatus.GESTURE); updatedUnit.add(pet.getRoomUnit()); } + } catch (Exception e) { + LOGGER.error("Caught exception", e); } } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRightsManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRightsManager.java index c9eb266d..9166305b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRightsManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRightsManager.java @@ -272,10 +272,16 @@ public class RoomRightsManager { } else if (this.isOwner(habbo)) { habbo.getClient().sendResponse(new RoomOwnerComposer()); flatCtrl = RoomRightLevels.MODERATOR; - } else if (this.hasRights(habbo) && !this.room.hasGuild()) { - flatCtrl = RoomRightLevels.RIGHTS; } else if (this.room.hasGuild()) { - flatCtrl = this.getGuildRightLevel(habbo); + // Explicit room rights must still be honoured in guild rooms (the old + // `&& !hasGuild()` guard stripped them for non-guild members) — take + // whichever of the two is stronger. + RoomRightLevels guildLevel = this.getGuildRightLevel(habbo); + flatCtrl = (this.hasRights(habbo) && RoomRightLevels.RIGHTS.isEqualOrGreaterThan(guildLevel)) + ? RoomRightLevels.RIGHTS + : guildLevel; + } else if (this.hasRights(habbo)) { + flatCtrl = RoomRightLevels.RIGHTS; } habbo.getClient().sendResponse(new RoomRightsComposer(flatCtrl)); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java index 544aca51..9e775568 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java @@ -152,7 +152,9 @@ public class RoomSpecialTypes { public InteractionNest getNest(int itemId) { - return this.nests.get(itemId); + synchronized (this.nests) { + return this.nests.get(itemId); + } } public void addNest(InteractionNest item) { @@ -180,7 +182,9 @@ public class RoomSpecialTypes { public InteractionPetDrink getPetDrink(int itemId) { - return this.petDrinks.get(itemId); + synchronized (this.petDrinks) { + return this.petDrinks.get(itemId); + } } public void addPetDrink(InteractionPetDrink item) { @@ -208,7 +212,9 @@ public class RoomSpecialTypes { public InteractionPetFood getPetFood(int itemId) { - return this.petFoods.get(itemId); + synchronized (this.petFoods) { + return this.petFoods.get(itemId); + } } public void addPetFood(InteractionPetFood item) { @@ -236,7 +242,9 @@ public class RoomSpecialTypes { public InteractionPetToy getPetToy(int itemId) { - return this.petToys.get(itemId); + synchronized (this.petToys) { + return this.petToys.get(itemId); + } } public void addPetToy(InteractionPetToy item) { @@ -264,7 +272,9 @@ public class RoomSpecialTypes { public InteractionPetTree getPetTree(int itemId) { - return this.petTrees.get(itemId); + synchronized (this.petTrees) { + return this.petTrees.get(itemId); + } } public void addPetTree(InteractionPetTree item) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboInfo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboInfo.java index aa441a05..06bff0a0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboInfo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboInfo.java @@ -254,7 +254,11 @@ public class HabboInfo implements Runnable { } public TIntIntHashMap getCurrencies() { - return this.currencies; + // Return a snapshot under the lock: callers iterate this map, which would + // otherwise corrupt during a concurrent adjustOrPutValue rehash. + synchronized (this.currencyLock) { + return new TIntIntHashMap(this.currencies); + } } public void addCurrencyAmount(int type, int amount) { @@ -396,7 +400,7 @@ public class HabboInfo implements Runnable { } public boolean canBuy(CatalogItem item) { - return this.credits >= item.getCredits() && this.getCurrencies().get(item.getPointsType()) >= item.getPoints(); + return this.getCredits() >= item.getCredits() && this.getCurrencyAmount(item.getPointsType()) >= item.getPoints(); } public int getCredits() { @@ -622,6 +626,13 @@ public class HabboInfo implements Runnable { public void run() { this.saveCurrencies(); + // Read credits under the lock so the persisted value is consistent with + // concurrent addCredits/setCredits (matches the currencyLock invariant). + final int creditsForSave; + synchronized (this.currencyLock) { + creditsForSave = this.credits; + } + try { SqlQueries.update( "UPDATE users SET motto = ?, online = ?, look = ?, gender = ?, credits = ?, last_login = ?, last_online = ?, home_room = ?, ip_current = ?, `rank` = ?, machine_id = ?, username = ?, background_id = ?, background_stand_id = ?, background_overlay_id = ?, background_card_id = ?, background_border_id = ? WHERE id = ?", @@ -629,7 +640,7 @@ public class HabboInfo implements Runnable { this.online ? "1" : "0", this.look, this.gender.name(), - this.credits, + creditsForSave, Emulator.getIntUnixTimestamp(), this.lastOnline, this.homeRoom, diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomRequestBannedUsersEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomRequestBannedUsersEvent.java index 321bdb42..e103bd0e 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomRequestBannedUsersEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomRequestBannedUsersEvent.java @@ -14,7 +14,7 @@ public class RoomRequestBannedUsersEvent extends MessageHandler { Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId); if (room == null) return; - if (!room.hasRights(this.client.getHabbo()) || !this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER)) return; + if (!room.hasRights(this.client.getHabbo()) && !this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER)) return; this.client.sendResponse(new RoomBannedUsersComposer(room)); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/ConfirmPetBreedingEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/ConfirmPetBreedingEvent.java index 13a1e200..17692282 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/ConfirmPetBreedingEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/ConfirmPetBreedingEvent.java @@ -1,6 +1,7 @@ package com.eu.habbo.messages.incoming.rooms.pets; import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetBreedingNest; +import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.incoming.MessageHandler; @@ -13,7 +14,10 @@ public class ConfirmPetBreedingEvent extends MessageHandler { int petOneId = this.packet.readInt(); int petTwoId = this.packet.readInt(); - HabboItem item = this.client.getHabbo().getHabboInfo().getCurrentRoom().getHabboItem(itemId); + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + if (room == null) return; + + HabboItem item = room.getHabboItem(itemId); if (item instanceof InteractionPetBreedingNest) { ((InteractionPetBreedingNest) item).breed(this.client.getHabbo(), name, petOneId, petTwoId); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java index 3cce0a34..7da0c5f4 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java @@ -23,7 +23,7 @@ public class PetPickupEvent extends MessageHandler { Pet pet = room.getPet(petId); if (pet != null) { - if (this.client.getHabbo().getHabboInfo().getId() == pet.getId() || room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER)) { + if (this.client.getHabbo().getHabboInfo().getId() == pet.getUserId() || room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER)) { if (!this.client.getHabbo().hasPermission(Permission.ACC_UNLIMITED_PETS) && this.client.getHabbo().getInventory().getPetsComponent().getPets().size() >= PetManager.MAXIMUM_PET_INVENTORY_SIZE) { this.client.getHabbo().alert(Emulator.getTexts().getValue("error.pets.max.inventory").replace("%amount%", PetManager.MAXIMUM_PET_INVENTORY_SIZE + "")); return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUsersComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUsersComposer.java index 51e00c4c..64bf2d1f 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUsersComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUsersComposer.java @@ -99,7 +99,7 @@ public class RoomUsersComposer extends MessageComposer { this.response.appendInt(1); this.response.appendString(habbo.getHabboInfo().getGender().name().toUpperCase()); this.response.appendInt(habbo.getHabboStats().guild != 0 ? habbo.getHabboStats().guild : -1); - this.response.appendInt(habbo.getHabboStats().guild != 0 ? habbo.getHabboStats().guild : -1); + this.response.appendInt(habbo.getHabboStats().guild != 0 ? 1 : -1); String name = ""; if (habbo.getHabboStats().guild != 0) { Guild g = Emulator.getGameEnvironment().getGuildManager().getGuild(habbo.getHabboStats().guild); diff --git a/Emulator/src/main/java/com/eu/habbo/networking/gameserver/auth/AuthHttpUtil.java b/Emulator/src/main/java/com/eu/habbo/networking/gameserver/auth/AuthHttpUtil.java index acd03b82..644e9e1b 100644 --- a/Emulator/src/main/java/com/eu/habbo/networking/gameserver/auth/AuthHttpUtil.java +++ b/Emulator/src/main/java/com/eu/habbo/networking/gameserver/auth/AuthHttpUtil.java @@ -169,8 +169,13 @@ public final class AuthHttpUtil { : ""; if (trusted.isEmpty()) return false; for (String entry : trusted.split(",")) { - String prefix = entry.trim(); - if (!prefix.isEmpty() && (peerIp.equals(prefix) || peerIp.startsWith(prefix))) { + String t = entry.trim(); + if (t.isEmpty()) continue; + // Exact IP match, or a dotted/colon prefix range (e.g. "10.0.0." or + // "2001:db8:") — never a bare-IP prefix, so "10.0.0.1" can't also + // trust "10.0.0.12". + boolean isRange = t.endsWith(".") || t.endsWith(":"); + if (peerIp.equals(t) || (isRange && peerIp.startsWith(t))) { return true; } }