diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboStats.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboStats.java index b760e4de..dc29be02 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboStats.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboStats.java @@ -89,6 +89,7 @@ public class HabboStats implements Runnable { public long lastTradeTimestamp = Emulator.getIntUnixTimestamp(); public long lastGiftTimestamp = Emulator.getIntUnixTimestamp(); public long lastPurchaseTimestamp = Emulator.getIntUnixTimestamp(); + public long lastFloorplanSaveTimestamp = 0; public int uiFlags; public boolean hasGottenDefaultSavedSearches; private HabboInfo habboInfo; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/floorplaneditor/FloorPlanEditorSaveEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/floorplaneditor/FloorPlanEditorSaveEvent.java index 48ec258a..b40f9300 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/floorplaneditor/FloorPlanEditorSaveEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/floorplaneditor/FloorPlanEditorSaveEvent.java @@ -11,16 +11,24 @@ import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys; import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer; import com.eu.habbo.messages.outgoing.rooms.ForwardToRoomComposer; import gnu.trove.set.hash.THashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.StringJoiner; +import java.util.regex.Pattern; public class FloorPlanEditorSaveEvent extends MessageHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(FloorPlanEditorSaveEvent.class); + public static int MAXIMUM_FLOORPLAN_WIDTH_LENGTH = 64; public static int MAXIMUM_FLOORPLAN_SIZE = 64 * 64; + private static final int SAVE_COOLDOWN_SECONDS = 3; + private static final Pattern ALLOWED_MAP_CHARS = Pattern.compile("[a-qA-Q0-9xX\r]+"); + @Override public int getRatelimit() { return 500; @@ -38,154 +46,184 @@ public class FloorPlanEditorSaveEvent extends MessageHandler { if (room == null) return; - if (room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER)) { - StringJoiner errors = new StringJoiner("
"); - String map = this.packet.readString(); - map = map.replace("X", "x"); + if (!(room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER))) { + return; + } - String[] mapRows = map.split("\r"); + long now = Emulator.getIntUnixTimestamp(); + if (now - this.client.getHabbo().getHabboStats().lastFloorplanSaveTimestamp < SAVE_COOLDOWN_SECONDS) { + this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key, "Please wait a few seconds before saving again.")); + return; + } - int firstRowSize = mapRows[0].length(); + StringJoiner errors = new StringJoiner("
"); + String map = this.packet.readString(); - if (Emulator.getConfig().getBoolean("hotel.room.floorplan.check.enabled")) { - if (!map.matches("[a-zA-Z0-9\r]+")) errors.add("${notification.floorplan_editor.error.title}"); + if (map == null || map.length() > MAXIMUM_FLOORPLAN_SIZE) { + LOGGER.warn("Floorplan save rejected (oversize): user={} room={} mapLen={}", + this.client.getHabbo().getHabboInfo().getId(), room.getId(), map == null ? 0 : map.length()); + return; + } - Arrays.stream(mapRows) - .filter(line -> line.length() != firstRowSize) - .findAny() - .ifPresent(s -> errors.add("(General): Line " + (Arrays.asList(mapRows).indexOf(s) + 1) + " is of different length than line 1")); + if (!ALLOWED_MAP_CHARS.matcher(map).matches()) { + LOGGER.warn("Floorplan save rejected (illegal chars): user={} room={}", + this.client.getHabbo().getHabboInfo().getId(), room.getId()); + return; + } - if (map.isEmpty() || map.replace("x", "").replace("\r", "").isEmpty()) { - errors.add("${notification.floorplan_editor.error.message.effective_height_is_0}"); - } + map = map.replace("X", "x"); - if (map.length() > MAXIMUM_FLOORPLAN_SIZE) { - errors.add("${notification.floorplan_editor.error.message.too_large_area}"); - } + String[] mapRows = map.split("\r"); - if (mapRows.length > MAXIMUM_FLOORPLAN_WIDTH_LENGTH) errors.add("${notification.floorplan_editor.error.message.too_large_height}"); - else if (Arrays.stream(mapRows).anyMatch(l -> l.length() > MAXIMUM_FLOORPLAN_WIDTH_LENGTH || l.isEmpty())) errors.add("${notification.floorplan_editor.error.message.too_large_width}"); + if (mapRows.length == 0 || mapRows.length > MAXIMUM_FLOORPLAN_WIDTH_LENGTH) { + return; + } - if (errors.length() > 0) { - this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key, errors.toString())); - return; - } - } + int firstRowSize = mapRows[0].length(); - int doorX = this.packet.readInt(); - int doorY = this.packet.readInt(); + if (firstRowSize == 0 || firstRowSize > MAXIMUM_FLOORPLAN_WIDTH_LENGTH) { + return; + } - if (doorX < 0 || doorX > firstRowSize || doorY < 0 || doorY >= mapRows.length) { - errors.add("${notification.floorplan_editor.error.message.entry_tile_outside_map}"); - } - - if (doorY < mapRows.length && doorX < mapRows[doorY].length() && mapRows[doorY].charAt(doorX) == 'x') { - errors.add("${notification.floorplan_editor.error.message.entry_not_on_tile}"); - } - - int doorRotation = this.packet.readInt(); - if (doorRotation < 0 || doorRotation > 7) { - errors.add("${notification.floorplan_editor.error.message.invalid_entry_tile_direction}"); - } - - int wallSize = this.packet.readInt(); - if (wallSize < -2 || wallSize > 1) { - errors.add("${notification.floorplan_editor.error.message.invalid_wall_thickness}"); - } - int floorSize = this.packet.readInt(); - if (floorSize < -2 || floorSize > 1) { - errors.add("${notification.floorplan_editor.error.message.invalid_floor_thickness}"); - } - - int wallHeight = -1; - if (this.packet.bytesAvailable() >= 4) - wallHeight = this.packet.readInt(); - - if (wallHeight < -1 || wallHeight > 15) { - errors.add("${notification.floorplan_editor.error.message.invalid_walls_fixed_height}"); - } - - THashSet locked_tileList = room.getLockedTiles(); - THashSet new_tileList = new THashSet<>(); - blockingRoomItemScan: - for (int y = 0; y < mapRows.length; y++) { - for (int x = 0; x < firstRowSize; x++) { - - RoomTile tile = room.getLayout().getTile((short) x, (short) y); - new_tileList.add(tile); - String square = String.valueOf(mapRows[y].charAt(x)); - short height; - - if (square.equalsIgnoreCase("x") && room.getTopItemAt(x, y) != null) { - errors.add("${notification.floorplan_editor.error.message.change_blocked_by_room_item}"); - break blockingRoomItemScan; - } else { - if (square.isEmpty()) { - height = 0; - } else if (Emulator.isNumeric(square)) { - height = Short.parseShort(square); - } else { - height = (short) (10 + "ABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf(square.toUpperCase())); - } - } - - if (tile != null && tile.state != RoomTileState.INVALID && height != tile.z && room.getTopItemAt(x, y) != null) { - errors.add("${notification.floorplan_editor.error.message.change_blocked_by_room_item}"); - break blockingRoomItemScan; - } - } - } - - locked_tileList.removeAll(new_tileList); - if (!locked_tileList.isEmpty()) { - errors.add("${notification.floorplan_editor.error.message.change_blocked_by_room_item}"); - } - - - - if (errors.length() > 0) { - this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key, errors.toString())); + for (String row : mapRows) { + if (row.length() != firstRowSize) { return; } + } - RoomLayout layout = room.getLayout(); + if (Emulator.getConfig().getBoolean("hotel.room.floorplan.check.enabled")) { + if (map.replace("x", "").replace("\r", "").isEmpty()) { + errors.add("${notification.floorplan_editor.error.message.effective_height_is_0}"); + } + } - if (layout instanceof CustomRoomLayout) { - layout.setDoorX((short) doorX); - layout.setDoorY((short) doorY); - layout.setDoorDirection(doorRotation); - layout.setHeightmap(map); - layout.parse(); + int doorX = this.packet.readInt(); + int doorY = this.packet.readInt(); - if (layout.getDoorTile() == null) { - this.client.getHabbo().alert("Error"); - ((CustomRoomLayout) layout).needsUpdate(false); - Emulator.getGameEnvironment().getRoomManager().unloadRoom(room); + if (doorX < 0 || doorX >= firstRowSize || doorY < 0 || doorY >= mapRows.length) { + errors.add("${notification.floorplan_editor.error.message.entry_tile_outside_map}"); + } else if (mapRows[doorY].charAt(doorX) == 'x') { + errors.add("${notification.floorplan_editor.error.message.entry_not_on_tile}"); + } + + int doorRotation = this.packet.readInt(); + if (doorRotation < 0 || doorRotation > 7) { + errors.add("${notification.floorplan_editor.error.message.invalid_entry_tile_direction}"); + } + + int wallSize = this.packet.readInt(); + if (wallSize < -2 || wallSize > 1) { + errors.add("${notification.floorplan_editor.error.message.invalid_wall_thickness}"); + } + int floorSize = this.packet.readInt(); + if (floorSize < -2 || floorSize > 1) { + errors.add("${notification.floorplan_editor.error.message.invalid_floor_thickness}"); + } + + int wallHeight = -1; + if (this.packet.bytesAvailable() >= 4) + wallHeight = this.packet.readInt(); + + if (wallHeight < -1 || wallHeight > 15) { + errors.add("${notification.floorplan_editor.error.message.invalid_walls_fixed_height}"); + } + + if (errors.length() > 0) { + this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key, errors.toString())); + return; + } + + THashSet locked_tileList = room.getLockedTiles(); + THashSet new_tileList = new THashSet<>(); + blockingRoomItemScan: + for (int y = 0; y < mapRows.length; y++) { + for (int x = 0; x < firstRowSize; x++) { + + RoomTile tile = room.getLayout().getTile((short) x, (short) y); + new_tileList.add(tile); + String square = String.valueOf(mapRows[y].charAt(x)); + short height; + + if (square.equalsIgnoreCase("x") && room.getTopItemAt(x, y) != null) { + errors.add("${notification.floorplan_editor.error.message.change_blocked_by_room_item}"); + break blockingRoomItemScan; + } + + try { + if (square.isEmpty()) { + height = 0; + } else if (Emulator.isNumeric(square)) { + height = Short.parseShort(square); + } else { + int idx = "abcdefghijklmnopq".indexOf(square.toLowerCase()); + if (idx < 0) { + return; + } + height = (short) (10 + idx); + } + } catch (NumberFormatException e) { return; } - ((CustomRoomLayout) layout).needsUpdate(true); - Emulator.getThreading().run((CustomRoomLayout) layout); - } else { - layout = Emulator.getGameEnvironment().getRoomManager().insertCustomLayout(room, map, doorX, doorY, doorRotation); - } - if (layout != null) { - room.setHasCustomLayout(true); - room.setNeedsUpdate(true); - room.setLayout(layout); - room.setWallSize(wallSize); - room.setFloorSize(floorSize); - room.setWallHeight(wallHeight); - room.save(); - Collection habbos = new ArrayList<>(room.getUserCount()); - habbos.addAll(room.getHabbos()); - Emulator.getGameEnvironment().getRoomManager().unloadRoom(room); - room = Emulator.getGameEnvironment().getRoomManager().loadRoom(room.getId()); - ServerMessage message = new ForwardToRoomComposer(room.getId()).compose(); - for (Habbo habbo : habbos) { - habbo.getClient().sendResponse(message); + if (tile != null && tile.state != RoomTileState.INVALID && height != tile.z && room.getTopItemAt(x, y) != null) { + errors.add("${notification.floorplan_editor.error.message.change_blocked_by_room_item}"); + break blockingRoomItemScan; } } } + + locked_tileList.removeAll(new_tileList); + if (!locked_tileList.isEmpty()) { + errors.add("${notification.floorplan_editor.error.message.change_blocked_by_room_item}"); + } + + if (errors.length() > 0) { + this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FLOORPLAN_EDITOR_ERROR.key, errors.toString())); + return; + } + + RoomLayout layout = room.getLayout(); + + if (layout instanceof CustomRoomLayout) { + layout.setDoorX((short) doorX); + layout.setDoorY((short) doorY); + layout.setDoorDirection(doorRotation); + layout.setHeightmap(map); + layout.parse(); + + if (layout.getDoorTile() == null) { + this.client.getHabbo().alert("Error"); + ((CustomRoomLayout) layout).needsUpdate(false); + Emulator.getGameEnvironment().getRoomManager().unloadRoom(room); + return; + } + ((CustomRoomLayout) layout).needsUpdate(true); + Emulator.getThreading().run((CustomRoomLayout) layout); + } else { + layout = Emulator.getGameEnvironment().getRoomManager().insertCustomLayout(room, map, doorX, doorY, doorRotation); + } + + if (layout != null) { + room.setHasCustomLayout(true); + room.setNeedsUpdate(true); + room.setLayout(layout); + room.setWallSize(wallSize); + room.setFloorSize(floorSize); + room.setWallHeight(wallHeight); + room.save(); + + this.client.getHabbo().getHabboStats().lastFloorplanSaveTimestamp = now; + LOGGER.info("Floorplan saved: user={} room={} mapLen={} rows={} cols={}", + this.client.getHabbo().getHabboInfo().getId(), room.getId(), map.length(), mapRows.length, firstRowSize); + + Collection habbos = new ArrayList<>(room.getUserCount()); + habbos.addAll(room.getHabbos()); + Emulator.getGameEnvironment().getRoomManager().unloadRoom(room); + room = Emulator.getGameEnvironment().getRoomManager().loadRoom(room.getId()); + ServerMessage message = new ForwardToRoomComposer(room.getId()).compose(); + for (Habbo habbo : habbos) { + habbo.getClient().sendResponse(message); + } + } } }