Disconnect client by throwing exception in handler

This makes it easier to add checks in handlers, which should improve security over time.
I think this approach is more readable and testable than calling Client#disconnect straight up,
while it also decentralizes the handling.
This commit is contained in:
P0nk
2023-08-06 15:48:49 +02:00
parent e9819fac87
commit 2686b2b02d
36 changed files with 180 additions and 106 deletions

View File

@@ -367,7 +367,7 @@ public class Server {
wldRLock.unlock();
}
Channel channel = new Channel(worldid, channelid, getCurrentTime(), channelDependencies.dropProvider());
Channel channel = new Channel(worldid, channelid, getCurrentTime(), channelDependencies);
channel.setServerMessage(YamlConfig.config.worlds.get(worldid).why_am_i_recommended);
if (world.addChannel(channel)) {
@@ -440,7 +440,7 @@ public class Server {
long bootTime = getCurrentTime();
for (int j = 1; j <= YamlConfig.config.worlds.get(i).channels; j++) {
int channelid = j;
Channel channel = new Channel(i, channelid, bootTime, channelDependencies.dropProvider());
Channel channel = new Channel(i, channelid, bootTime, channelDependencies);
world.addChannel(channel);
channelInfo.put(channelid, channel.getIP());
@@ -928,7 +928,7 @@ public class Server {
}
}
loginServer = initLoginServer(8484);
loginServer = initLoginServer(8484, channelDependencies.characterSaver());
log.info("Listening on port 8484");
@@ -999,8 +999,8 @@ public class Server {
return channelDependencies;
}
private LoginServer initLoginServer(int port) {
LoginServer loginServer = new LoginServer(port);
private LoginServer initLoginServer(int port, CharacterSaver characterSaver) {
LoginServer loginServer = new LoginServer(port, characterSaver);
loginServer.start();
return loginServer;
}

View File

@@ -24,7 +24,9 @@ package net.server.channel;
import client.Character;
import config.YamlConfig;
import constants.id.MapId;
import database.character.CharacterSaver;
import database.drop.DropProvider;
import net.ChannelDependencies;
import net.netty.ChannelServer;
import net.packet.Packet;
import net.server.PlayerStorage;
@@ -109,10 +111,10 @@ public final class Channel {
private final Lock merchRlock;
private final Lock merchWlock;
public Channel(final int world, final int channel, long startTime, DropProvider dropProvider) {
public Channel(final int world, final int channel, long startTime, ChannelDependencies channelDependencies) {
this.world = world;
this.channel = channel;
this.dropProvider = dropProvider;
this.dropProvider = channelDependencies.dropProvider();
this.ongoingStartTime = startTime + 10000; // rude approach to a world's last channel boot time, placeholder for the 1st wedding reservation ever
this.mapManager = new MapManager(null, world, channel, dropProvider);
@@ -124,7 +126,7 @@ public final class Channel {
this.merchWlock = rwLock.writeLock();
try {
this.channelServer = initServer(port, world, channel);
this.channelServer = initServer(port, world, channel, channelDependencies.characterSaver());
expedType.addAll(Arrays.asList(ExpeditionType.values()));
if (Server.getInstance().isOnline()) { // postpone event loading to improve boot time... thanks Riizade, daronhudson for noticing slow startup times
@@ -152,8 +154,8 @@ public final class Channel {
}
}
private ChannelServer initServer(int port, int world, int channel) {
ChannelServer channelServer = new ChannelServer(port, world, channel);
private ChannelServer initServer(int port, int world, int channel, CharacterSaver characterSaver) {
ChannelServer channelServer = new ChannelServer(port, world, channel, characterSaver);
channelServer.start();
return channelServer;
}

View File

@@ -33,6 +33,7 @@ import config.YamlConfig;
import constants.id.ItemId;
import constants.inventory.ItemConstants;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import net.server.Server;
import org.slf4j.Logger;
@@ -287,8 +288,7 @@ public final class CashOperationHandler extends AbstractPacketHandler {
byte invType = p.readByte();
if (invType < 1 || invType > 5) {
c.disconnect(false, false);
return;
throw GameViolationException.inventoryType(invType);
}
Inventory mi = chr.getInventory(InventoryType.getByType(invType));

View File

@@ -24,6 +24,7 @@ package net.server.channel.handlers;
import client.Client;
import client.autoban.AutobanFactory;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import net.server.Server;
import service.ChannelService;
@@ -45,8 +46,7 @@ public final class ChangeChannelHandler extends AbstractPacketHandler {
c.getPlayer().getAutobanManager().setTimestamp(6, Server.getInstance().getCurrentTimestamp(), 3);
if (c.getChannel() == channel) {
AutobanFactory.GENERAL.alert(c.getPlayer(), "CCing to same channel.");
c.disconnect(false, false);
return;
throw new GameViolationException("Change to same channel");
} else if (c.getPlayer().getCashShop().isOpened() || c.getPlayer().getMiniGame() != null || c.getPlayer().getPlayerShop() != null) {
return;
}

View File

@@ -28,6 +28,7 @@ import client.inventory.manipulator.InventoryManipulator;
import constants.id.ItemId;
import constants.id.MapId;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -66,8 +67,7 @@ public final class ChangeMapHandler extends AbstractPacketHandler {
}
if (chr.getCashShop().isOpened()) {
c.disconnect(false, false);
return;
throw new GameViolationException("Changing channel inside cash shop");
}
try {
@@ -178,8 +178,7 @@ public final class ChangeMapHandler extends AbstractPacketHandler {
final Character chr = c.getPlayer();
if (!chr.getCashShop().isOpened()) {
c.disconnect(false, false);
return;
throw new GameViolationException("Enter map from cash shop, but is not in cash shop");
}
String[] socket = c.getChannelServer().getIP().split(":");
chr.getCashShop().open(false);
@@ -191,4 +190,4 @@ public final class ChangeMapHandler extends AbstractPacketHandler {
ex.printStackTrace();
}
}
}
}

View File

@@ -28,6 +28,7 @@ import constants.game.GameConstants;
import constants.id.MapId;
import constants.skills.*;
import database.drop.DropProvider;
import net.netty.GameViolationException;
import net.packet.InPacket;
import server.StatEffect;
import tools.PacketCreator;
@@ -56,12 +57,8 @@ public final class CloseRangeDamageHandler extends AbstractDealDamageHandler {
chr.getAutobanManager().spam(8);*/
AttackInfo attack = parseDamage(p, chr, false, false);
if (chr.getBuffEffect(BuffStat.MORPH) != null) {
if (chr.getBuffEffect(BuffStat.MORPH).isMorphWithoutAttack()) {
// How are they attacking when the client won't let them?
chr.getClient().disconnect(false, false);
return;
}
if (chr.getBuffEffect(BuffStat.MORPH) != null && chr.getBuffEffect(BuffStat.MORPH).isMorphWithoutAttack()) {
throw new GameViolationException("Attempt to attack with morph skill that disallows attacking");
}
if (chr.getDojoEnergy() < 10000 && (attack.skill == 1009 || attack.skill == 10001009 || attack.skill == 20001009)) // PE hacking or maybe just lagging

View File

@@ -26,6 +26,7 @@ import client.Client;
import client.autoban.AutobanFactory;
import client.command.CommandsExecutor;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -51,8 +52,7 @@ public final class GeneralChatHandler extends AbstractPacketHandler {
if (s.length() > Byte.MAX_VALUE && !chr.isGM()) {
AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit in General Chat.");
log.warn("Chr {} tried to send text with length of {}", c.getPlayer().getName(), s.length());
c.disconnect(true, false);
return;
throw GameViolationException.textLength(s);
}
char heading = s.charAt(0);
if (CommandsExecutor.isCommand(c, s)) {

View File

@@ -26,6 +26,7 @@ import client.Character.FameStatus;
import client.Client;
import client.autoban.AutobanFactory;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,8 +46,7 @@ public final class GiveFameHandler extends AbstractPacketHandler {
} else if (famechange != 1 && famechange != -1) {
AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit fame.");
log.warn("Chr {} tried to fame hack with famechange {}", c.getPlayer().getName(), famechange);
c.disconnect(true, false);
return;
throw new GameViolationException("Give too much fame");
}
FameStatus status = player.canGiveFame(target);
@@ -62,4 +62,4 @@ public final class GiveFameHandler extends AbstractPacketHandler {
c.sendPacket(PacketCreator.giveFameErrorResponse(status == FameStatus.NOT_TODAY ? 3 : 4));
}
}
}
}

View File

@@ -29,6 +29,7 @@ import client.inventory.Item;
import client.inventory.manipulator.InventoryManipulator;
import config.YamlConfig;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import net.server.Server;
import server.ItemInformationProvider;
@@ -49,8 +50,7 @@ public final class InventoryMergeHandler extends AbstractPacketHandler {
byte invType = p.readByte();
if (invType < 1 || invType > 5) {
c.disconnect(false, false);
return;
throw GameViolationException.inventoryType(invType);
}
InventoryType inventoryType = InventoryType.getByType(invType);
@@ -117,4 +117,4 @@ public final class InventoryMergeHandler extends AbstractPacketHandler {
c.sendPacket(PacketCreator.finishedSort(inventoryType.getType()));
c.sendPacket(PacketCreator.enableActions());
}
}
}

View File

@@ -26,6 +26,7 @@ import client.Client;
import client.inventory.*;
import config.YamlConfig;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import net.server.Server;
import server.ItemInformationProvider;
@@ -298,8 +299,7 @@ public final class InventorySortHandler extends AbstractPacketHandler {
byte invType = p.readByte();
if (invType < 1 || invType > 5) {
c.disconnect(false, false);
return;
throw GameViolationException.inventoryType(invType);
}
ArrayList<Item> itemarray = new ArrayList<>();

View File

@@ -28,6 +28,7 @@ import client.inventory.InventoryType;
import client.keybind.KeyBinding;
import constants.game.GameConstants;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
public final class KeymapChangeHandler extends AbstractPacketHandler {
@@ -66,15 +67,13 @@ public final class KeymapChangeHandler extends AbstractPacketHandler {
} else if (mode == 1) { // Auto HP Potion
int itemID = p.readInt();
if (itemID != 0 && c.getPlayer().getInventory(InventoryType.USE).findById(itemID) == null) {
c.disconnect(false, false); // Don't let them send a packet with a use item they dont have.
return;
throw new GameViolationException("Set auto hp potion without owning the item");
}
c.getPlayer().changeKeybinding(91, new KeyBinding(7, itemID));
} else if (mode == 2) { // Auto MP Potion
int itemID = p.readInt();
if (itemID != 0 && c.getPlayer().getInventory(InventoryType.USE).findById(itemID) == null) {
c.disconnect(false, false); // Don't let them send a packet with a use item they dont have.
return;
throw new GameViolationException("Set auto mp potion without owning the item");
}
c.getPlayer().changeKeybinding(92, new KeyBinding(7, itemID));
}

View File

@@ -30,6 +30,7 @@ import constants.skills.Evan;
import constants.skills.FPArchMage;
import constants.skills.ILArchMage;
import database.drop.DropProvider;
import net.netty.GameViolationException;
import net.packet.InPacket;
import net.packet.Packet;
import server.StatEffect;
@@ -56,9 +57,7 @@ public final class MagicDamageHandler extends AbstractDealDamageHandler {
if (chr.getBuffEffect(BuffStat.MORPH) != null) {
if (chr.getBuffEffect(BuffStat.MORPH).isMorphWithoutAttack()) {
// How are they attacking when the client won't let them?
chr.getClient().disconnect(false, false);
return;
throw new GameViolationException("Attempt to attack with morph skill that disallows attacking");
}
}

View File

@@ -25,6 +25,7 @@ import client.Character;
import client.Client;
import client.autoban.AutobanFactory;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import net.server.Server;
import net.server.world.World;
@@ -53,9 +54,9 @@ public final class MultiChatHandler extends AbstractPacketHandler {
if (chattext.length() > Byte.MAX_VALUE && !player.isGM()) {
AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit chats.");
log.warn("Chr {} tried to send text with length of {}", c.getPlayer().getName(), chattext.length());
c.disconnect(true, false);
return;
throw GameViolationException.textLength(chattext);
}
World world = c.getWorldServer();
if (type == 0) {
world.buddyChat(recipients, player.getId(), player.getName(), chattext);

View File

@@ -25,6 +25,7 @@ import client.Client;
import client.autoban.AutobanFactory;
import constants.inventory.ItemConstants;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,8 +48,7 @@ public final class NPCShopHandler extends AbstractPacketHandler {
AutobanFactory.PACKET_EDIT.alert(c.getPlayer(),
c.getPlayer().getName() + " tried to packet edit a npc shop.");
log.warn("Chr {} tried to buy quantity {} of itemid {}", c.getPlayer().getName(), quantity, itemId);
c.disconnect(true, false);
return;
throw new GameViolationException("Buy invalid amount of items");
}
c.getPlayer().getShop().buy(c, slot, itemId, quantity);
break;

View File

@@ -24,6 +24,7 @@ package net.server.channel.handlers;
import client.Client;
import client.autoban.AutobanFactory;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,9 +48,9 @@ public final class PetChatHandler extends AbstractPacketHandler {
if (text.length() > Byte.MAX_VALUE) {
AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit with pets.");
log.warn("Chr {} tried to send text with length of {}", c.getPlayer().getName(), text.length());
c.disconnect(true, false);
return;
throw GameViolationException.textLength(text);
}
c.getPlayer().getMap().broadcastMessage(c.getPlayer(), PacketCreator.petChat(c.getPlayer().getId(), pet, act, text), true);
ChatLogger.log(c, "Pet", text);
}

View File

@@ -35,6 +35,7 @@ import constants.id.ItemId;
import constants.inventory.ItemConstants;
import database.character.CharacterSaver;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -674,8 +675,7 @@ public final class PlayerInteractionHandler extends AbstractPacketHandler {
if (slot >= shop.getItems().size() || slot < 0) {
AutobanFactory.PACKET_EDIT.alert(chr, chr.getName() + " tried to packet edit with a player shop.");
log.warn("Chr {} tried to remove item at slot {}", chr.getName(), slot);
c.disconnect(true, false);
return;
throw new GameViolationException("Remove item from invalid slot in shop");
}
shop.takeItemBack(slot, chr);
@@ -740,9 +740,9 @@ public final class PlayerInteractionHandler extends AbstractPacketHandler {
if (quantity < 1) {
AutobanFactory.PACKET_EDIT.alert(chr, chr.getName() + " tried to packet edit with a hired merchant and or player shop.");
log.warn("Chr {} tried to buy item {} with quantity {}", chr.getName(), itemid, quantity);
c.disconnect(true, false);
return;
throw new GameViolationException("Buy item with invalid quantity");
}
PlayerShop shop = chr.getPlayerShop();
HiredMerchant merchant = chr.getHiredMerchant();
if (shop != null && shop.isVisitor(chr)) {
@@ -769,8 +769,7 @@ public final class PlayerInteractionHandler extends AbstractPacketHandler {
if (slot >= merchant.getItems().size() || slot < 0) {
AutobanFactory.PACKET_EDIT.alert(chr, chr.getName() + " tried to packet edit with a hired merchant.");
log.warn("Chr {} tried to remove item at slot {}", chr.getName(), slot);
c.disconnect(true, false);
return;
throw new GameViolationException("Withdraw item from merchant with invalid slot");
}
merchant.takeItemBack(slot, chr, chrSaver);

View File

@@ -29,6 +29,7 @@ import config.YamlConfig;
import constants.game.GameConstants;
import database.character.CharacterLoader;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import net.server.PlayerBuffValueHolder;
import net.server.Server;
@@ -108,8 +109,7 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler {
try {
World wserv = server.getWorld(c.getWorld());
if (wserv == null) {
c.disconnect(true, false);
return;
throw new GameViolationException("World not found");
}
Channel cserv = wserv.getChannel(c.getChannel());
@@ -118,8 +118,7 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler {
cserv = wserv.getChannel(c.getChannel());
if (cserv == null) {
c.disconnect(true, false);
return;
throw new GameViolationException("Channel not found");
}
}
@@ -129,8 +128,7 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler {
if (player == null) {
hwid = SessionCoordinator.getInstance().pickLoginSessionHwid(c);
if (hwid == null) {
c.disconnect(true, false);
return;
throw new GameViolationException("Unable to pick hwid");
}
} else {
hwid = player.getClient().getHwid();
@@ -139,8 +137,7 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler {
c.setHwid(hwid);
if (!server.validateCharacteridInTransition(c, chrId)) {
c.disconnect(true, false);
return;
throw new GameViolationException("Attempt to enter game without chr id in transition");
}
boolean newcomer = false;
@@ -150,8 +147,7 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler {
player = loadedChr.get();
newcomer = true;
} else {
c.disconnect(true, false);
return;
throw new GameViolationException("Unable to load chr");
}
}
c.setPlayer(player);
@@ -184,7 +180,7 @@ public final class PlayerLoggedinHandler extends AbstractPacketHandler {
c.setAccID(0);
if (state == Client.LOGIN_LOGGEDIN) {
c.disconnect(true, false);
throw new GameViolationException("Attempt to log in when already logged in");
} else {
c.sendPacket(PacketCreator.getAfterLoginError(7));
}

View File

@@ -34,6 +34,7 @@ import constants.id.MapId;
import constants.inventory.ItemConstants;
import constants.skills.*;
import database.drop.DropProvider;
import net.netty.GameViolationException;
import net.packet.InPacket;
import net.packet.Packet;
import org.slf4j.Logger;
@@ -67,9 +68,7 @@ public final class RangedAttackHandler extends AbstractDealDamageHandler {
if (chr.getBuffEffect(BuffStat.MORPH) != null) {
if (chr.getBuffEffect(BuffStat.MORPH).isMorphWithoutAttack()) {
// How are they attacking when the client won't let them?
chr.getClient().disconnect(false, false);
return;
throw new GameViolationException("Attempt to attack with morph skill that disallows attacking");
}
}

View File

@@ -27,6 +27,7 @@ import constants.id.ItemId;
import constants.id.NpcId;
import constants.inventory.ItemConstants;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import scripting.npc.NPCScriptManager;
@@ -40,16 +41,13 @@ public final class RemoteGachaponHandler extends AbstractPacketHandler {
int gacha = p.readInt();
if (ticket != ItemId.REMOTE_GACHAPON_TICKET) {
AutobanFactory.GENERAL.alert(c.getPlayer(), " Tried to use RemoteGachaponHandler with item id: " + ticket);
c.disconnect(false, false);
return;
throw new GameViolationException("Use Remote gachapon without owning the item");
} else if (gacha < 0 || gacha > 11) {
AutobanFactory.GENERAL.alert(c.getPlayer(), " Tried to use RemoteGachaponHandler with mode: " + gacha);
c.disconnect(false, false);
return;
throw new GameViolationException("Use Remote gachapon with invalid mode");
} else if (c.getPlayer().getInventory(ItemConstants.getInventoryType(ticket)).countById(ticket) < 1) {
AutobanFactory.GENERAL.alert(c.getPlayer(), " Tried to use RemoteGachaponHandler without a ticket.");
c.disconnect(false, false);
return;
throw new GameViolationException("Use Remote gachapon without a ticket");
}
int npcId = NpcId.GACHAPON_HENESYS;
if (gacha != 8 && gacha != 9) {

View File

@@ -26,6 +26,7 @@ import client.Client;
import client.SkillMacro;
import client.autoban.AutobanFactory;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
public final class SkillMacroHandler extends AbstractPacketHandler {
@@ -42,8 +43,7 @@ public final class SkillMacroHandler extends AbstractPacketHandler {
String name = p.readString();
if (name.length() > 12) {
AutobanFactory.PACKET_EDIT.alert(chr, "Invalid name length " + name + " (" + name.length() + ") for skill macro.");
c.disconnect(false, false);
break;
throw GameViolationException.textLength(name);
}
int shout = p.readByte();

View File

@@ -25,6 +25,7 @@ import client.Character;
import client.Client;
import client.autoban.AutobanFactory;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -96,8 +97,7 @@ public final class WhisperHandler extends AbstractPacketHandler {
if (message.length() > Byte.MAX_VALUE) {
AutobanFactory.PACKET_EDIT.alert(user, user.getName() + " tried to packet edit with whispers.");
log.warn("Chr {} tried to send text with length of {}", user.getName(), message.length());
user.getClient().disconnect(true, false);
return;
throw GameViolationException.textLength(message);
}
ChatLogger.log(user.getClient(), "Whisper To " + target.getName(), message);

View File

@@ -2,6 +2,7 @@ package net.server.handlers.login;
import client.Client;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import tools.PacketCreator;
@@ -16,11 +17,11 @@ public final class AcceptToSHandler extends AbstractPacketHandler {
}
@Override
public final void handlePacket(InPacket p, Client c) {
public void handlePacket(InPacket p, Client c) {
if (p.available() == 0 || p.readByte() != 1 || c.acceptToS()) {
c.disconnect(false, false);//Client dc's but just because I am cool I do this (:
return;
throw new GameViolationException("ToS not accepted");
}
if (c.finishLogin() == 0) {
c.sendPacket(PacketCreator.getAuthSuccess(c));
} else {

View File

@@ -27,6 +27,7 @@ import client.creator.novice.LegendCreator;
import client.creator.novice.NoblesseCreator;
import constants.id.ItemId;
import net.AbstractPacketHandler;
import net.netty.GameViolationException;
import net.packet.InPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -80,8 +81,7 @@ public final class CreateCharHandler extends AbstractPacketHandler {
for (int item : items) {
if (!isLegal(item)) {
log.warn("Owner from account {} tried to packet edit in chr creation", c.getAccountName());
c.disconnect(true, false);
return;
throw new GameViolationException("Create character with invalid equip");
}
}
@@ -105,4 +105,4 @@ public final class CreateCharHandler extends AbstractPacketHandler {
c.sendPacket(PacketCreator.deleteCharResponse(0, 9));
}
}
}
}