You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
fix: address bug-hunt findings across security, concurrency, trade & wired
Security - HousekeepingSetUserRankEvent: add rank-ceiling guard (reject granting a rank above the operator's own and modifying a higher-ranked target), mirroring GiveRankCommand — closes a privilege-escalation path. Trade integrity - RoomTrade.clearAccepted now also resets confirmed; a stale confirmed=true let a user strip their side and still complete once the partner re-confirms. Concurrency - RoomCycleManager: iterate the synchronized bot/pet maps under their own monitor (lock order stays one-directional vs addBot/addPet — no deadlock). - RoomSpecialTypes: synchronize nest/petDrink/petFood/petToy/petTree writers on the same monitor their getters already use. - HabboStats: synchronize achievement-progress map accessors. - RebugKickBallAction: drop redundant direct mutation of the shared tile-cache sets (updateTile invalidates them right after) — removes a data race. Robustness - Wired legacy parsers (HabboCount, NotHabboCount, MatchStatePosition, MoveRotateFurni): guard length/format so one malformed row no longer aborts the whole room's wired load. - RoomLayout: fill malformed/short heightmap rows with INVALID tiles instead of leaving nulls, and bounds-check door coordinates. - FurnidataWatcher: defer (instead of drop) a throttled delta so furni-name changes are never lost between broadcasts. - GuildManager.getGuildMembers: fix LIMIT row-count (page size 14, not offset+14) so member pages no longer overlap from page 1 on.
This commit is contained in:
@@ -423,7 +423,7 @@ public class GuildManager {
|
|||||||
statement.setInt(1, guild.getId());
|
statement.setInt(1, guild.getId());
|
||||||
statement.setString(2, "%" + query + "%");
|
statement.setString(2, "%" + query + "%");
|
||||||
statement.setInt(3, page * 14);
|
statement.setInt(3, page * 14);
|
||||||
statement.setInt(4, (page * 14) + 14);
|
statement.setInt(4, 14);
|
||||||
|
|
||||||
try (ResultSet set = statement.executeQuery()) {
|
try (ResultSet set = statement.executeQuery()) {
|
||||||
while (set.next()) {
|
while (set.next()) {
|
||||||
|
|||||||
@@ -115,21 +115,31 @@ public class FurnidataWatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onChange() {
|
private void onChange() throws InterruptedException {
|
||||||
|
// Re-index under the shared furnidata lock so the watcher and editor
|
||||||
|
// writes never swap the index concurrently. The lock is released before
|
||||||
|
// the throttle/broadcast below so a slow broadcast can't stall editor saves.
|
||||||
|
List<FurnidataEntry> delta;
|
||||||
FurnidataLock.LOCK.lock();
|
FurnidataLock.LOCK.lock();
|
||||||
try {
|
try {
|
||||||
Path source = this.provider.getSource();
|
Path source = this.provider.getSource();
|
||||||
if (source == null) return;
|
if (source == null) return;
|
||||||
|
delta = this.provider.reindex(new FurnidataReader(source, this.maxBytes).read());
|
||||||
List<FurnidataEntry> delta = this.provider.reindex(new FurnidataReader(source, this.maxBytes).read());
|
} finally {
|
||||||
|
FurnidataLock.LOCK.unlock();
|
||||||
|
}
|
||||||
if (delta.isEmpty()) return;
|
if (delta.isEmpty()) return;
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
// Min-interval throttle: the index has already been swapped, so we must
|
||||||
if (now - this.lastBroadcast < this.minIntervalMs) {
|
// not drop this delta (the next reindex would diff against the updated
|
||||||
LOGGER.info("FurnidataWatcher: {} changes indexed but broadcast skipped (min interval) — clients update on next change or reconnect", delta.size());
|
// index and never re-emit it). Instead, defer the broadcast until the
|
||||||
return;
|
// interval elapses. Running on a dedicated daemon thread, sleeping is
|
||||||
|
// safe; file events arriving meanwhile coalesce into the next cycle.
|
||||||
|
long sinceLast = System.currentTimeMillis() - this.lastBroadcast;
|
||||||
|
if (sinceLast < this.minIntervalMs) {
|
||||||
|
Thread.sleep(this.minIntervalMs - sinceLast);
|
||||||
}
|
}
|
||||||
this.lastBroadcast = now;
|
this.lastBroadcast = System.currentTimeMillis();
|
||||||
|
|
||||||
FurnitureDataReloadComposer composer = (delta.size() > this.deltaCap)
|
FurnitureDataReloadComposer composer = (delta.size() > this.deltaCap)
|
||||||
? new FurnitureDataReloadComposer(FurnitureDataReloadComposer.MODE_RELOAD_HINT, List.of())
|
? new FurnitureDataReloadComposer(FurnitureDataReloadComposer.MODE_RELOAD_HINT, List.of())
|
||||||
@@ -138,9 +148,6 @@ public class FurnidataWatcher {
|
|||||||
broadcast(composer);
|
broadcast(composer);
|
||||||
LOGGER.info("FurnidataWatcher: broadcast {} ({} entries)",
|
LOGGER.info("FurnidataWatcher: broadcast {} ({} entries)",
|
||||||
delta.size() > this.deltaCap ? "reload-hint" : "delta", delta.size());
|
delta.size() > this.deltaCap ? "reload-hint" : "delta", delta.size());
|
||||||
} finally {
|
|
||||||
FurnidataLock.LOCK.unlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void broadcast(FurnitureDataReloadComposer composer) {
|
private void broadcast(FurnitureDataReloadComposer composer) {
|
||||||
|
|||||||
+8
-2
@@ -65,8 +65,14 @@ public class WiredConditionHabboCount extends InteractionWiredCondition {
|
|||||||
} else {
|
} else {
|
||||||
String[] data = wiredData.split(":");
|
String[] data = wiredData.split(":");
|
||||||
|
|
||||||
this.lowerLimit = Integer.parseInt(data[0]);
|
if (data.length >= 2) {
|
||||||
this.upperLimit = Integer.parseInt(data[1]);
|
try {
|
||||||
|
this.lowerLimit = Integer.parseInt(data[0].trim());
|
||||||
|
this.upperLimit = Integer.parseInt(data[1].trim());
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
// malformed legacy data — keep the constructed defaults
|
||||||
|
}
|
||||||
|
}
|
||||||
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-1
@@ -263,11 +263,13 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition
|
|||||||
} else {
|
} else {
|
||||||
String[] data = wiredData.split(":");
|
String[] data = wiredData.split(":");
|
||||||
|
|
||||||
|
if (data.length >= 5) {
|
||||||
|
try {
|
||||||
int itemCount = Integer.parseInt(data[0]);
|
int itemCount = Integer.parseInt(data[0]);
|
||||||
|
|
||||||
String[] items = data[1].split(";");
|
String[] items = data[1].split(";");
|
||||||
|
|
||||||
for (int i = 0; i < itemCount; i++) {
|
for (int i = 0; i < itemCount && i < items.length; i++) {
|
||||||
String[] stuff = items[i].split("-");
|
String[] stuff = items[i].split("-");
|
||||||
|
|
||||||
if (stuff.length >= 6)
|
if (stuff.length >= 6)
|
||||||
@@ -279,6 +281,11 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition
|
|||||||
this.state = data[2].equals("1");
|
this.state = data[2].equals("1");
|
||||||
this.direction = data[3].equals("1");
|
this.direction = data[3].equals("1");
|
||||||
this.position = data[4].equals("1");
|
this.position = data[4].equals("1");
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
// malformed legacy data — keep whatever was parsed plus defaults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.altitude = false;
|
this.altitude = false;
|
||||||
this.furniSource = this.settings.isEmpty() ? WiredSourceUtil.SOURCE_TRIGGER : WiredSourceUtil.SOURCE_SELECTED;
|
this.furniSource = this.settings.isEmpty() ? WiredSourceUtil.SOURCE_TRIGGER : WiredSourceUtil.SOURCE_SELECTED;
|
||||||
this.quantifier = QUANTIFIER_ALL;
|
this.quantifier = QUANTIFIER_ALL;
|
||||||
|
|||||||
+8
-2
@@ -64,8 +64,14 @@ public class WiredConditionNotHabboCount extends InteractionWiredCondition {
|
|||||||
this.userSource = data.userSource;
|
this.userSource = data.userSource;
|
||||||
} else {
|
} else {
|
||||||
String[] data = wiredData.split(":");
|
String[] data = wiredData.split(":");
|
||||||
this.lowerLimit = Integer.parseInt(data[0]);
|
if (data.length >= 2) {
|
||||||
this.upperLimit = Integer.parseInt(data[1]);
|
try {
|
||||||
|
this.lowerLimit = Integer.parseInt(data[0].trim());
|
||||||
|
this.upperLimit = Integer.parseInt(data[1].trim());
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
// malformed legacy data — keep the constructed defaults
|
||||||
|
}
|
||||||
|
}
|
||||||
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -190,10 +190,15 @@ public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (String s : data[3].split("\r")) {
|
for (String s : data[3].split("\r")) {
|
||||||
HabboItem item = room.getHabboItem(Integer.parseInt(s));
|
if (s.trim().isEmpty()) continue;
|
||||||
|
try {
|
||||||
|
HabboItem item = room.getHabboItem(Integer.parseInt(s.trim()));
|
||||||
|
|
||||||
if (item != null)
|
if (item != null)
|
||||||
this.items.add(item);
|
this.items.add(item);
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
// skip malformed furni id token
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.furniSource = this.items.isEmpty() ? WiredSourceUtil.SOURCE_TRIGGER : WiredSourceUtil.SOURCE_SELECTED;
|
this.furniSource = this.items.isEmpty() ? WiredSourceUtil.SOURCE_TRIGGER : WiredSourceUtil.SOURCE_SELECTED;
|
||||||
|
|||||||
@@ -300,6 +300,10 @@ public class RoomCycleManager {
|
|||||||
return;
|
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.
|
||||||
|
synchronized (currentBots) {
|
||||||
TIntObjectIterator<Bot> botIterator = currentBots.iterator();
|
TIntObjectIterator<Bot> botIterator = currentBots.iterator();
|
||||||
for (int i = currentBots.size(); i-- > 0; ) {
|
for (int i = currentBots.size(); i-- > 0; ) {
|
||||||
try {
|
try {
|
||||||
@@ -329,6 +333,7 @@ public class RoomCycleManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes all pets in the room.
|
* Processes all pets in the room.
|
||||||
@@ -339,6 +344,9 @@ public class RoomCycleManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// currentPets is a TCollections.synchronizedMap; hold its monitor for the
|
||||||
|
// whole traversal to stay safe against concurrent pet place/pickup.
|
||||||
|
synchronized (currentPets) {
|
||||||
TIntObjectIterator<Pet> petIterator = currentPets.iterator();
|
TIntObjectIterator<Pet> petIterator = currentPets.iterator();
|
||||||
for (int i = currentPets.size(); i-- > 0; ) {
|
for (int i = currentPets.size(); i-- > 0; ) {
|
||||||
try {
|
try {
|
||||||
@@ -367,6 +375,7 @@ public class RoomCycleManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes roller cycle.
|
* Processes roller cycle.
|
||||||
|
|||||||
@@ -130,13 +130,16 @@ public class RoomLayout {
|
|||||||
this.roomTiles = new RoomTile[this.mapSizeX][this.mapSizeY];
|
this.roomTiles = new RoomTile[this.mapSizeX][this.mapSizeY];
|
||||||
|
|
||||||
for (short y = 0; y < this.mapSizeY; y++) {
|
for (short y = 0; y < this.mapSizeY; y++) {
|
||||||
if (modelTemp[y].isEmpty() || modelTemp[y].equalsIgnoreCase("\r")) {
|
// A row shorter/longer than the model width (or empty) cannot be parsed
|
||||||
continue;
|
// per-square. Previously such tiles were left null while tileExists()
|
||||||
}
|
// still reported them present, causing NPEs in the coordinate accessors.
|
||||||
|
// Fill them with INVALID tiles so every in-bounds coordinate is non-null.
|
||||||
|
boolean validRow = !modelTemp[y].isEmpty() && modelTemp[y].length() == this.mapSizeX;
|
||||||
|
|
||||||
for (short x = 0; x < this.mapSizeX; x++) {
|
for (short x = 0; x < this.mapSizeX; x++) {
|
||||||
if (modelTemp[y].length() != this.mapSizeX) {
|
if (!validRow) {
|
||||||
break;
|
this.roomTiles[x][y] = new RoomTile(x, y, (short) 0, RoomTileState.INVALID, true);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String square = modelTemp[y].substring(x, x + 1).trim().toLowerCase();
|
String square = modelTemp[y].substring(x, x + 1).trim().toLowerCase();
|
||||||
@@ -159,7 +162,9 @@ public class RoomLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.doorTile = this.roomTiles[this.doorX][this.doorY];
|
this.doorTile = (this.doorX >= 0 && this.doorX < this.mapSizeX && this.doorY >= 0 && this.doorY < this.mapSizeY)
|
||||||
|
? this.roomTiles[this.doorX][this.doorY]
|
||||||
|
: null;
|
||||||
|
|
||||||
if (this.doorTile != null) {
|
if (this.doorTile != null) {
|
||||||
this.doorTile.setAllowStack(false);
|
this.doorTile.setAllowStack(false);
|
||||||
|
|||||||
@@ -156,11 +156,17 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addNest(InteractionNest item) {
|
public void addNest(InteractionNest item) {
|
||||||
this.nests.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
synchronized (this.nests) {
|
||||||
|
this.nests.put(item.getId(), item);
|
||||||
|
}
|
||||||
|
this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeNest(InteractionNest item) {
|
public void removeNest(InteractionNest item) {
|
||||||
this.nests.remove(item.getId()); this.specialItemsById.remove(item.getId());
|
synchronized (this.nests) {
|
||||||
|
this.nests.remove(item.getId());
|
||||||
|
}
|
||||||
|
this.specialItemsById.remove(item.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<InteractionNest> getNests() {
|
public THashSet<InteractionNest> getNests() {
|
||||||
@@ -178,11 +184,17 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addPetDrink(InteractionPetDrink item) {
|
public void addPetDrink(InteractionPetDrink item) {
|
||||||
this.petDrinks.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
synchronized (this.petDrinks) {
|
||||||
|
this.petDrinks.put(item.getId(), item);
|
||||||
|
}
|
||||||
|
this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePetDrink(InteractionPetDrink item) {
|
public void removePetDrink(InteractionPetDrink item) {
|
||||||
this.petDrinks.remove(item.getId()); this.specialItemsById.remove(item.getId());
|
synchronized (this.petDrinks) {
|
||||||
|
this.petDrinks.remove(item.getId());
|
||||||
|
}
|
||||||
|
this.specialItemsById.remove(item.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<InteractionPetDrink> getPetDrinks() {
|
public THashSet<InteractionPetDrink> getPetDrinks() {
|
||||||
@@ -200,11 +212,17 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addPetFood(InteractionPetFood item) {
|
public void addPetFood(InteractionPetFood item) {
|
||||||
this.petFoods.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
synchronized (this.petFoods) {
|
||||||
|
this.petFoods.put(item.getId(), item);
|
||||||
|
}
|
||||||
|
this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePetFood(InteractionPetFood petFood) {
|
public void removePetFood(InteractionPetFood petFood) {
|
||||||
this.petFoods.remove(petFood.getId()); this.specialItemsById.remove(petFood.getId());
|
synchronized (this.petFoods) {
|
||||||
|
this.petFoods.remove(petFood.getId());
|
||||||
|
}
|
||||||
|
this.specialItemsById.remove(petFood.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<InteractionPetFood> getPetFoods() {
|
public THashSet<InteractionPetFood> getPetFoods() {
|
||||||
@@ -222,11 +240,17 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addPetToy(InteractionPetToy item) {
|
public void addPetToy(InteractionPetToy item) {
|
||||||
this.petToys.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
synchronized (this.petToys) {
|
||||||
|
this.petToys.put(item.getId(), item);
|
||||||
|
}
|
||||||
|
this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePetToy(InteractionPetToy petToy) {
|
public void removePetToy(InteractionPetToy petToy) {
|
||||||
this.petToys.remove(petToy.getId()); this.specialItemsById.remove(petToy.getId());
|
synchronized (this.petToys) {
|
||||||
|
this.petToys.remove(petToy.getId());
|
||||||
|
}
|
||||||
|
this.specialItemsById.remove(petToy.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<InteractionPetToy> getPetToys() {
|
public THashSet<InteractionPetToy> getPetToys() {
|
||||||
@@ -244,11 +268,17 @@ public class RoomSpecialTypes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addPetTree(InteractionPetTree item) {
|
public void addPetTree(InteractionPetTree item) {
|
||||||
this.petTrees.put(item.getId(), item); this.specialItemsById.put(item.getId(), item);
|
synchronized (this.petTrees) {
|
||||||
|
this.petTrees.put(item.getId(), item);
|
||||||
|
}
|
||||||
|
this.specialItemsById.put(item.getId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removePetTree(InteractionPetTree petTree) {
|
public void removePetTree(InteractionPetTree petTree) {
|
||||||
this.petTrees.remove(petTree.getId()); this.specialItemsById.remove(petTree.getId());
|
synchronized (this.petTrees) {
|
||||||
|
this.petTrees.remove(petTree.getId());
|
||||||
|
}
|
||||||
|
this.specialItemsById.remove(petTree.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public THashSet<InteractionPetTree> getPetTrees() {
|
public THashSet<InteractionPetTree> getPetTrees() {
|
||||||
|
|||||||
@@ -264,6 +264,10 @@ public class RoomTrade {
|
|||||||
protected void clearAccepted() {
|
protected void clearAccepted() {
|
||||||
for (RoomTradeUser user : this.users) {
|
for (RoomTradeUser user : this.users) {
|
||||||
user.setAccepted(false);
|
user.setAccepted(false);
|
||||||
|
// Any change to the offered items invalidates a prior confirmation;
|
||||||
|
// without this a stale confirmed=true lets a user strip their side
|
||||||
|
// and still complete the trade once the partner re-confirms.
|
||||||
|
user.setConfirmed(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ public class RoomTradeUser {
|
|||||||
this.confirmed = true;
|
this.confirmed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setConfirmed(boolean value) {
|
||||||
|
this.confirmed = value;
|
||||||
|
}
|
||||||
|
|
||||||
public void addItem(HabboItem item) {
|
public void addItem(HabboItem item) {
|
||||||
this.items.add(item);
|
this.items.add(item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -448,15 +448,17 @@ public class HabboStats implements Runnable {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.achievementProgress.containsKey(achievement))
|
synchronized (this.achievementProgress) {
|
||||||
return this.achievementProgress.get(achievement);
|
Integer progress = this.achievementProgress.get(achievement);
|
||||||
|
return progress != null ? progress : -1;
|
||||||
return -1;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProgress(Achievement achievement, int progress) {
|
public void setProgress(Achievement achievement, int progress) {
|
||||||
|
synchronized (this.achievementProgress) {
|
||||||
this.achievementProgress.put(achievement, progress);
|
this.achievementProgress.put(achievement, progress);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int getRentedTimeEnd() {
|
public int getRentedTimeEnd() {
|
||||||
return this.rentedTimeEnd;
|
return this.rentedTimeEnd;
|
||||||
|
|||||||
+38
-2
@@ -11,6 +11,7 @@ import com.eu.habbo.messages.outgoing.users.UserPermissionsComposer;
|
|||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
public class HousekeepingSetUserRankEvent extends MessageHandler {
|
public class HousekeepingSetUserRankEvent extends MessageHandler {
|
||||||
@@ -44,6 +45,43 @@ public class HousekeepingSetUserRankEvent extends MessageHandler {
|
|||||||
|
|
||||||
Rank rank = permissions.getRank(rankId);
|
Rank rank = permissions.getRank(rankId);
|
||||||
|
|
||||||
|
// Rank-ceiling guard: an operator must never be able to grant a rank
|
||||||
|
// above their own, nor modify a user who already outranks them. This
|
||||||
|
// mirrors GiveRankCommand and prevents privilege escalation through
|
||||||
|
// the housekeeping path (including self-promotion).
|
||||||
|
int operatorRankId = this.client.getHabbo().getHabboInfo().getRank().getId();
|
||||||
|
|
||||||
|
if (rank.getId() > operatorRankId) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
||||||
|
|
||||||
|
int targetRankId;
|
||||||
|
if (online != null) {
|
||||||
|
targetRankId = online.getHabboInfo().getRank().getId();
|
||||||
|
} else {
|
||||||
|
targetRankId = 0;
|
||||||
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("SELECT rank FROM users WHERE id = ? LIMIT 1")) {
|
||||||
|
statement.setInt(1, userId);
|
||||||
|
try (ResultSet set = statement.executeQuery()) {
|
||||||
|
if (set.next()) {
|
||||||
|
targetRankId = set.getInt("rank");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.db_failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetRankId > operatorRankId) {
|
||||||
|
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.rank_too_high"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Persist for the offline path. Online users get their in-memory
|
// Persist for the offline path. Online users get their in-memory
|
||||||
// HabboInfo.rank rebound below so server-side hasPermission()
|
// HabboInfo.rank rebound below so server-side hasPermission()
|
||||||
// checks land on the new permission set without a relogin.
|
// checks land on the new permission set without a relogin.
|
||||||
@@ -57,8 +95,6 @@ public class HousekeepingSetUserRankEvent extends MessageHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Habbo online = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
|
|
||||||
|
|
||||||
if (online != null) {
|
if (online != null) {
|
||||||
online.getHabboInfo().setRank(rank);
|
online.getHabboInfo().setRank(rank);
|
||||||
// Ship the refreshed permissions snapshot — same payload the
|
// Ship the refreshed permissions snapshot — same payload the
|
||||||
|
|||||||
@@ -165,12 +165,10 @@ public class RebugKickBallAction implements Runnable {
|
|||||||
this.dead = true;
|
this.dead = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
THashSet<HabboItem> oldItems = this.room.getItemsAt(oldTile);
|
// updateTile() below removes both tiles from the item cache (rebuilt
|
||||||
if (oldItems != null && !oldItems.isEmpty()) {
|
// lazily from the ball's already-updated position), so mutating the
|
||||||
oldItems.remove(this.ball);
|
// shared cached THashSets here is both redundant and a data race
|
||||||
}
|
// against the room-cycle/IO threads iterating those same sets.
|
||||||
this.room.getItemsAt(nextTile).add(this.ball);
|
|
||||||
|
|
||||||
this.room.updateTile(oldTile);
|
this.room.updateTile(oldTile);
|
||||||
this.room.updateTile(nextTile);
|
this.room.updateTile(nextTile);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user