Merge pull request #46 from P0nk/netty

Replace Apache Mina with Netty
This commit is contained in:
Ponk
2021-08-18 22:14:46 +02:00
committed by GitHub
76 changed files with 3790 additions and 3568 deletions

52
pom.xml
View File

@@ -19,24 +19,11 @@
<log4j.version>2.14.1</log4j.version>
<graalvm.version>21.1.0</graalvm.version>
<netty.version>4.1.65.Final</netty.version>
<junit.version>5.7.2</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>com.esotericsoftware.yamlbeans</groupId>
<artifactId>yamlbeans</artifactId>
@@ -52,10 +39,39 @@
<artifactId>commons-io</artifactId>
<version>2.10.0</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!-- Networking -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>4.1.65.Final</version>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>${netty.version}</version>
</dependency>
<!-- Logging -->
@@ -96,12 +112,12 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.2</version>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.2</version>
<version>${junit.version}</version>
</dependency>

View File

@@ -52,7 +52,6 @@ import net.server.services.task.world.CharacterSaveService;
import net.server.services.type.ChannelServices;
import net.server.services.type.WorldServices;
import net.server.world.*;
import org.apache.mina.util.ConcurrentHashSet;
import scripting.AbstractPlayerInteraction;
import scripting.event.EventInstanceManager;
import scripting.item.ItemScriptManager;
@@ -85,6 +84,7 @@ import java.time.LocalDateTime;
import java.util.List;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -177,7 +177,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
private final Map<Short, MapleQuestStatus> quests;
private Set<MapleMonster> controlled = new LinkedHashSet<>();
private Map<Integer, String> entered = new LinkedHashMap<>();
private Set<MapleMapObject> visibleMapObjects = new ConcurrentHashSet<>();
private Set<MapleMapObject> visibleMapObjects = Collections.newSetFromMap(new ConcurrentHashMap<>());
private Map<Skill, SkillEntry> skills = new LinkedHashMap<>();
private Map<Integer, Integer> activeCoupons = new LinkedHashMap<>();
private Map<Integer, Integer> activeCouponRates = new LinkedHashMap<>();

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,7 @@ import client.MapleCharacter;
import config.YamlConfig;
import net.server.Server;
import tools.FilePrinter;
import tools.MapleLogger;
import net.packet.logging.MapleLogger;
import tools.MaplePacketCreator;
/**

View File

@@ -36,7 +36,7 @@ public class EnableAuthCommand extends Command {
public void execute(MapleClient c, String[] params) {
if (c.tryacquireClient()) {
try {
MapleLoginBypassCoordinator.getInstance().unregisterLoginBypassEntry(c.getNibbleHWID(), c.getAccID());
MapleLoginBypassCoordinator.getInstance().unregisterLoginBypassEntry(c.getHwid(), c.getAccID());
} finally {
c.releaseClient();
}

View File

@@ -52,7 +52,7 @@ public class BanCommand extends Command {
MapleCharacter target = c.getChannelServer().getPlayerStorage().getCharacterByName(ign);
if (target != null) {
String readableTargetName = MapleCharacter.makeMapleReadable(target.getName());
String ip = target.getClient().getSession().getRemoteAddress().toString().split(":")[0];
String ip = target.getClient().getRemoteAddress();
//Ban ip
try (Connection con = DatabaseConnection.getConnection()) {
if (ip.matches("/[0-9]{1,3}\\..*")) {

View File

@@ -27,7 +27,7 @@ import client.MapleCharacter;
import client.MapleClient;
import client.command.Command;
import net.server.Server;
import tools.MapleLogger;
import net.packet.logging.MapleLogger;
import tools.MaplePacketCreator;
public class IgnoreCommand extends Command {

View File

@@ -26,7 +26,7 @@ package client.command.commands.gm3;
import client.MapleCharacter;
import client.MapleClient;
import client.command.Command;
import tools.MapleLogger;
import net.packet.logging.MapleLogger;
public class IgnoredCommand extends Command {
{

View File

@@ -27,7 +27,7 @@ import client.MapleCharacter;
import client.MapleClient;
import client.command.Command;
import net.server.Server;
import tools.MapleLogger;
import net.packet.logging.MapleLogger;
import tools.MaplePacketCreator;
public class MonitorCommand extends Command {

View File

@@ -26,7 +26,7 @@ package client.command.commands.gm3;
import client.MapleCharacter;
import client.MapleClient;
import client.command.Command;
import tools.MapleLogger;
import net.packet.logging.MapleLogger;
public class MonitorsCommand extends Command {
{

View File

@@ -50,7 +50,7 @@ public class IpListCommand extends Command {
str += "\r\n" + GameConstants.WORLD_NAMES[w.getId()] + "\r\n";
for (MapleCharacter chr : chars) {
str += " " + chr.getName() + " - " + chr.getClient().getSession().getRemoteAddress() + "\r\n";
str += " " + chr.getName() + " - " + chr.getClient().getRemoteAddress() + "\r\n";
}
}
}

View File

@@ -21,7 +21,7 @@ package client.command.commands.gm5;
import client.MapleClient;
import client.command.Command;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.SessionCoordinator;
/**
*
@@ -34,6 +34,6 @@ public class ShowSessionsCommand extends Command {
@Override
public void execute(MapleClient c, String[] params) {
MapleSessionCoordinator.getInstance().printSessionTrace(c);
SessionCoordinator.getInstance().printSessionTrace(c);
}
}

View File

@@ -49,7 +49,7 @@ public class WarpWorldCommand extends Command {
byte worldb = Byte.parseByte(params[0]);
if (worldb <= (server.getWorldsSize() - 1)) {
try {
String[] socket = server.getInetSocket(c.getSession(), worldb, c.getChannel());
String[] socket = server.getInetSocket(c, worldb, c.getChannel());
c.getWorldServer().removePlayer(player);
player.getMap().removePlayer(player);//LOL FORGOT THIS ><
player.setSessionTransitionState();

View File

@@ -3,7 +3,7 @@ package constants.net;
public class ServerConstants {
//Server Version
public static short VERSION = 83;
public static final short VERSION = 83;
//Debug Variables
public static int[] DEBUG_VALUES = new int[10]; // Field designed for packet testing purposes

View File

@@ -1,305 +0,0 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net;
import client.MapleClient;
import config.YamlConfig;
import constants.net.ServerConstants;
import net.server.Server;
import net.server.audit.LockCollector;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReentrantLock;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import net.server.coordinator.session.MapleSessionCoordinator;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import server.TimerManager;
import tools.FilePrinter;
import tools.MapleAESOFB;
import tools.MapleLogger;
import tools.MaplePacketCreator;
import tools.data.input.ByteArrayByteStream;
import tools.data.input.GenericSeekableLittleEndianAccessor;
import tools.data.input.SeekableLittleEndianAccessor;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicLong;
public class MapleServerHandler extends IoHandlerAdapter {
private final static Set<Short> ignoredDebugRecvPackets = new HashSet<>(Arrays.asList((short) 167, (short) 197, (short) 89, (short) 91, (short) 41, (short) 188, (short) 107));
private PacketProcessor processor;
private int world = -1, channel = -1;
private static final SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm");
private static AtomicLong sessionId = new AtomicLong(7777);
private MonitoredReentrantLock idleLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.SRVHANDLER_IDLE, true);
private MonitoredReentrantLock tempLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.SRVHANDLER_TEMP, true);
private Map<MapleClient, Long> idleSessions = new HashMap<>(100);
private Map<MapleClient, Long> tempIdleSessions = new HashMap<>();
private ScheduledFuture<?> idleManager = null;
public MapleServerHandler() {
this.processor = PacketProcessor.getProcessor(-1, -1);
idleManagerTask();
}
public MapleServerHandler(int world, int channel) {
this.processor = PacketProcessor.getProcessor(world, channel);
this.world = world;
this.channel = channel;
idleManagerTask();
}
@Override
public void exceptionCaught(IoSession session, Throwable cause) {
if (cause instanceof IOException) {
closeMapleSession(session);
} else {
MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
if (client != null && client.getPlayer() != null) {
FilePrinter.printError(FilePrinter.EXCEPTION_CAUGHT, cause, "Exception caught by: " + client.getPlayer());
}
}
}
private boolean isLoginServerHandler() {
return channel == -1 && world == -1;
}
@Override
public void sessionOpened(IoSession session) {
String remoteHost;
try {
remoteHost = ((InetSocketAddress) session.getRemoteAddress()).getAddress().getHostAddress();
if (remoteHost == null) {
remoteHost = "null";
} else {
remoteHost = MapleSessionCoordinator.fetchRemoteAddress(remoteHost); // thanks dyz for noticing Local/LAN/WAN connections not interacting properly
}
} catch (NullPointerException npe) { // thanks Agassy, Alchemist for pointing out possibility of remoteHost = null.
remoteHost = "null";
}
session.setAttribute(MapleClient.CLIENT_REMOTE_ADDRESS, remoteHost);
if (!Server.getInstance().isOnline()) {
MapleSessionCoordinator.getInstance().closeSession(session, true);
return;
}
if (!isLoginServerHandler()) {
if (Server.getInstance().getChannel(world, channel) == null) {
MapleSessionCoordinator.getInstance().closeSession(session, true);
return;
}
} else {
if (!MapleSessionCoordinator.getInstance().canStartLoginSession(session)) {
return;
}
FilePrinter.print(FilePrinter.SESSION, "IoSession with " + session.getRemoteAddress() + " opened on " + sdf.format(Calendar.getInstance().getTime()), false);
}
byte[] ivRecv = {70, 114, 122, 82};
byte[] ivSend = {82, 48, 120, 115};
ivRecv[3] = (byte) (Math.random() * 255);
ivSend[3] = (byte) (Math.random() * 255);
MapleAESOFB sendCypher = new MapleAESOFB(ivSend, (short) (0xFFFF - ServerConstants.VERSION));
MapleAESOFB recvCypher = new MapleAESOFB(ivRecv, ServerConstants.VERSION);
MapleClient client = new MapleClient(sendCypher, recvCypher, session);
client.setWorld(world);
client.setChannel(channel);
client.setSessionId(sessionId.getAndIncrement()); // Generates a reasonable session id.
session.write(MaplePacketCreator.getHello(ServerConstants.VERSION, ivSend, ivRecv));
session.setAttribute(MapleClient.CLIENT_KEY, client);
}
private void closeMapleSession(IoSession session) {
if (isLoginServerHandler()) {
MapleSessionCoordinator.getInstance().closeLoginSession(session);
} else {
MapleSessionCoordinator.getInstance().closeSession(session, null);
}
MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
if (client != null) {
try {
// client freeze issues on session transition states found thanks to yolinlin, Omo Oppa, Nozphex
if (!session.containsAttribute(MapleClient.CLIENT_TRANSITION)) {
client.disconnect(false, false);
}
} catch (Throwable t) {
FilePrinter.printError(FilePrinter.ACCOUNT_STUCK, t);
} finally {
session.close();
session.removeAttribute(MapleClient.CLIENT_KEY);
//client.empty();
}
}
}
@Override
public void sessionClosed(IoSession session) throws Exception {
closeMapleSession(session);
super.sessionClosed(session);
}
@Override
public void messageReceived(IoSession session, Object message) {
byte[] content = (byte[]) message;
SeekableLittleEndianAccessor slea = new GenericSeekableLittleEndianAccessor(new ByteArrayByteStream(content));
short packetId = slea.readShort();
MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
if(YamlConfig.config.server.USE_DEBUG_SHOW_RCVD_PACKET && !ignoredDebugRecvPackets.contains(packetId)) System.out.println("Received packet id " + packetId);
final MaplePacketHandler packetHandler = processor.getHandler(packetId);
if (packetHandler != null && packetHandler.validateState(client)) {
try {
MapleLogger.logRecv(client, packetId, message);
packetHandler.handlePacket(slea, client);
} catch (final Throwable t) {
FilePrinter.printError(FilePrinter.PACKET_HANDLER + packetHandler.getClass().getName() + ".txt", t, "Error for " + (client.getPlayer() == null ? "" : "player ; " + client.getPlayer() + " on map ; " + client.getPlayer().getMapId() + " - ") + "account ; " + client.getAccountName() + "\r\n" + slea.toString());
//client.announce(MaplePacketCreator.enableActions());//bugs sometimes
}
client.updateLastPacket();
}
}
@Override
public void messageSent(IoSession session, Object message) {
byte[] content = (byte[]) message;
SeekableLittleEndianAccessor slea = new GenericSeekableLittleEndianAccessor(new ByteArrayByteStream(content));
slea.readShort(); //packetId
}
@Override
public void sessionIdle(final IoSession session, final IdleStatus status) throws Exception {
MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
if (client != null) {
registerIdleSession(client);
}
super.sessionIdle(session, status);
}
private void registerIdleSession(MapleClient c) {
if(idleLock.tryLock()) {
try {
idleSessions.put(c, Server.getInstance().getCurrentTime());
c.announce(MaplePacketCreator.getPing());
} finally {
idleLock.unlock();
}
} else {
tempLock.lock();
try {
tempIdleSessions.put(c, Server.getInstance().getCurrentTime());
c.announce(MaplePacketCreator.getPing());
} finally {
tempLock.unlock();
}
}
}
private void manageIdleSessions() {
long timeNow = Server.getInstance().getCurrentTime();
long timeThen = timeNow - 15000;
Set<MapleClient> pingClients = new HashSet<>();
idleLock.lock();
try {
for(Entry<MapleClient, Long> mc : idleSessions.entrySet()) {
if(timeNow - mc.getValue() >= 15000) {
pingClients.add(mc.getKey());
}
}
idleSessions.clear();
if(!tempIdleSessions.isEmpty()) {
tempLock.lock();
try {
for(Entry<MapleClient, Long> mc : tempIdleSessions.entrySet()) {
idleSessions.put(mc.getKey(), mc.getValue());
}
tempIdleSessions.clear();
} finally {
tempLock.unlock();
}
}
} finally {
idleLock.unlock();
}
for(MapleClient c : pingClients) {
c.testPing(timeThen);
}
}
private void idleManagerTask() {
this.idleManager = TimerManager.getInstance().register(() -> manageIdleSessions(), 10000);
}
private void cancelIdleManagerTask() {
this.idleManager.cancel(false);
this.idleManager = null;
}
private void disposeLocks() {
LockCollector.getInstance().registerDisposeAction(() -> emptyLocks());
}
private void emptyLocks() {
idleLock.dispose();
tempLock.dispose();
}
public void dispose() {
cancelIdleManagerTask();
idleLock.lock();
try {
idleSessions.clear();
} finally {
idleLock.unlock();
}
tempLock.lock();
try {
tempIdleSessions.clear();
} finally {
tempLock.unlock();
}
disposeLocks();
}
}

View File

@@ -21,34 +21,16 @@
*/
package net;
import java.util.LinkedHashMap;
import java.util.Map;
import net.netty.LoginServer;
import net.opcodes.RecvOpcode;
import net.server.channel.handlers.*;
import net.server.handlers.CustomPacketHandler;
import net.server.handlers.KeepAliveHandler;
import net.server.handlers.LoginRequiringNoOpHandler;
import net.server.handlers.login.AcceptToSHandler;
import net.server.handlers.login.AfterLoginHandler;
import net.server.handlers.login.CharSelectedHandler;
import net.server.handlers.login.CharSelectedWithPicHandler;
import net.server.handlers.login.CharlistRequestHandler;
import net.server.handlers.login.CheckCharNameHandler;
import net.server.handlers.login.CreateCharHandler;
import net.server.handlers.login.DeleteCharHandler;
import net.server.handlers.login.GuestLoginHandler;
import net.server.handlers.login.LoginPasswordHandler;
import net.server.handlers.login.RegisterPicHandler;
import net.server.handlers.login.RegisterPinHandler;
import net.server.handlers.login.RelogRequestHandler;
import net.server.handlers.login.ServerStatusRequestHandler;
import net.server.handlers.login.ServerlistRequestHandler;
import net.server.handlers.login.SetGenderHandler;
import net.server.handlers.login.ViewAllCharHandler;
import net.server.handlers.login.ViewAllCharRegisterPicHandler;
import net.server.handlers.login.ViewAllCharSelectedHandler;
import net.server.handlers.login.ViewAllCharSelectedWithPicHandler;
import net.server.handlers.login.*;
import java.util.LinkedHashMap;
import java.util.Map;
public final class PacketProcessor {
@@ -65,6 +47,14 @@ public final class PacketProcessor {
handlers = new MaplePacketHandler[maxRecvOp + 1];
}
public static PacketProcessor getLoginServerProcessor() {
return getProcessor(LoginServer.WORLD_ID, LoginServer.CHANNEL_ID);
}
public static PacketProcessor getChannelServerProcessor(int world, int channel) {
return getProcessor(world, channel);
}
public MaplePacketHandler getHandler(short packetId) {
if (packetId > handlers.length) {
return null;
@@ -86,12 +76,12 @@ public final class PacketProcessor {
}
public synchronized static PacketProcessor getProcessor(int world, int channel) {
final String lolpair = world + " " + channel;
PacketProcessor processor = instances.get(lolpair);
final String processorId = world + " " + channel;
PacketProcessor processor = instances.get(processorId);
if (processor == null) {
processor = new PacketProcessor();
processor.reset(channel);
instances.put(lolpair, processor);
instances.put(processorId, processor);
}
return processor;
}
@@ -99,181 +89,192 @@ public final class PacketProcessor {
public void reset(int channel) {
handlers = new MaplePacketHandler[handlers.length];
registerHandler(RecvOpcode.PONG, new KeepAliveHandler());
registerHandler(RecvOpcode.CUSTOM_PACKET, new CustomPacketHandler());
registerCommonHandlers();
if (channel < 0) {
//LOGIN HANDLERS
registerHandler(RecvOpcode.ACCEPT_TOS, new AcceptToSHandler());
registerHandler(RecvOpcode.AFTER_LOGIN, new AfterLoginHandler());
registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler());
registerHandler(RecvOpcode.CHARLIST_REQUEST, new CharlistRequestHandler());
registerHandler(RecvOpcode.CHAR_SELECT, new CharSelectedHandler());
registerHandler(RecvOpcode.LOGIN_PASSWORD, new LoginPasswordHandler());
registerHandler(RecvOpcode.RELOG, new RelogRequestHandler());
registerHandler(RecvOpcode.SERVERLIST_REQUEST, new ServerlistRequestHandler());
registerHandler(RecvOpcode.SERVERSTATUS_REQUEST, new ServerStatusRequestHandler());
registerHandler(RecvOpcode.CHECK_CHAR_NAME, new CheckCharNameHandler());
registerHandler(RecvOpcode.CREATE_CHAR, new CreateCharHandler());
registerHandler(RecvOpcode.DELETE_CHAR, new DeleteCharHandler());
registerHandler(RecvOpcode.VIEW_ALL_CHAR, new ViewAllCharHandler());
registerHandler(RecvOpcode.PICK_ALL_CHAR, new ViewAllCharSelectedHandler());
registerHandler(RecvOpcode.REGISTER_PIN, new RegisterPinHandler());
registerHandler(RecvOpcode.GUEST_LOGIN, new GuestLoginHandler());
registerHandler(RecvOpcode.REGISTER_PIC, new RegisterPicHandler());
registerHandler(RecvOpcode.CHAR_SELECT_WITH_PIC, new CharSelectedWithPicHandler());
registerHandler(RecvOpcode.SET_GENDER, new SetGenderHandler());
registerHandler(RecvOpcode.VIEW_ALL_WITH_PIC, new ViewAllCharSelectedWithPicHandler());
registerHandler(RecvOpcode.VIEW_ALL_PIC_REGISTER, new ViewAllCharRegisterPicHandler());
registerLoginHandlers();
} else {
//CHANNEL HANDLERS
registerHandler(RecvOpcode.NAME_TRANSFER, new TransferNameHandler());
registerHandler(RecvOpcode.CHECK_CHAR_NAME, new TransferNameResultHandler());
registerHandler(RecvOpcode.WORLD_TRANSFER, new TransferWorldHandler());
registerHandler(RecvOpcode.CHANGE_CHANNEL, new ChangeChannelHandler());
registerHandler(RecvOpcode.STRANGE_DATA, LoginRequiringNoOpHandler.getInstance());
registerHandler(RecvOpcode.GENERAL_CHAT, new GeneralChatHandler());
registerHandler(RecvOpcode.WHISPER, new WhisperHandler());
registerHandler(RecvOpcode.NPC_TALK, new NPCTalkHandler());
registerHandler(RecvOpcode.NPC_TALK_MORE, new NPCMoreTalkHandler());
registerHandler(RecvOpcode.QUEST_ACTION, new QuestActionHandler());
registerHandler(RecvOpcode.GRENADE_EFFECT, new GrenadeEffectHandler());
registerHandler(RecvOpcode.NPC_SHOP, new NPCShopHandler());
registerHandler(RecvOpcode.ITEM_SORT, new InventoryMergeHandler());
registerHandler(RecvOpcode.ITEM_MOVE, new ItemMoveHandler());
registerHandler(RecvOpcode.MESO_DROP, new MesoDropHandler());
registerHandler(RecvOpcode.PLAYER_LOGGEDIN, new PlayerLoggedinHandler());
registerHandler(RecvOpcode.CHANGE_MAP, new ChangeMapHandler());
registerHandler(RecvOpcode.MOVE_LIFE, new MoveLifeHandler());
registerHandler(RecvOpcode.CLOSE_RANGE_ATTACK, new CloseRangeDamageHandler());
registerHandler(RecvOpcode.RANGED_ATTACK, new RangedAttackHandler());
registerHandler(RecvOpcode.MAGIC_ATTACK, new MagicDamageHandler());
registerHandler(RecvOpcode.TAKE_DAMAGE, new TakeDamageHandler());
registerHandler(RecvOpcode.MOVE_PLAYER, new MovePlayerHandler());
registerHandler(RecvOpcode.USE_CASH_ITEM, new UseCashItemHandler());
registerHandler(RecvOpcode.USE_ITEM, new UseItemHandler());
registerHandler(RecvOpcode.USE_RETURN_SCROLL, new UseItemHandler());
registerHandler(RecvOpcode.USE_UPGRADE_SCROLL, new ScrollHandler());
registerHandler(RecvOpcode.USE_SUMMON_BAG, new UseSummonBagHandler());
registerHandler(RecvOpcode.FACE_EXPRESSION, new FaceExpressionHandler());
registerHandler(RecvOpcode.HEAL_OVER_TIME, new HealOvertimeHandler());
registerHandler(RecvOpcode.ITEM_PICKUP, new ItemPickupHandler());
registerHandler(RecvOpcode.CHAR_INFO_REQUEST, new CharInfoRequestHandler());
registerHandler(RecvOpcode.SPECIAL_MOVE, new SpecialMoveHandler());
registerHandler(RecvOpcode.USE_INNER_PORTAL, new InnerPortalHandler());
registerHandler(RecvOpcode.CANCEL_BUFF, new CancelBuffHandler());
registerHandler(RecvOpcode.CANCEL_ITEM_EFFECT, new CancelItemEffectHandler());
registerHandler(RecvOpcode.PLAYER_INTERACTION, new PlayerInteractionHandler());
registerHandler(RecvOpcode.RPS_ACTION, new RPSActionHandler());
registerHandler(RecvOpcode.DISTRIBUTE_AP, new DistributeAPHandler());
registerHandler(RecvOpcode.DISTRIBUTE_SP, new DistributeSPHandler());
registerHandler(RecvOpcode.CHANGE_KEYMAP, new KeymapChangeHandler());
registerHandler(RecvOpcode.CHANGE_MAP_SPECIAL, new ChangeMapSpecialHandler());
registerHandler(RecvOpcode.STORAGE, new StorageHandler());
registerHandler(RecvOpcode.GIVE_FAME, new GiveFameHandler());
registerHandler(RecvOpcode.PARTY_OPERATION, new PartyOperationHandler());
registerHandler(RecvOpcode.DENY_PARTY_REQUEST, new DenyPartyRequestHandler());
registerHandler(RecvOpcode.MULTI_CHAT, new MultiChatHandler());
registerHandler(RecvOpcode.USE_DOOR, new DoorHandler());
registerHandler(RecvOpcode.ENTER_MTS, new EnterMTSHandler());
registerHandler(RecvOpcode.ENTER_CASHSHOP, new EnterCashShopHandler());
registerHandler(RecvOpcode.DAMAGE_SUMMON, new DamageSummonHandler());
registerHandler(RecvOpcode.MOVE_SUMMON, new MoveSummonHandler());
registerHandler(RecvOpcode.SUMMON_ATTACK, new SummonDamageHandler());
registerHandler(RecvOpcode.BUDDYLIST_MODIFY, new BuddylistModifyHandler());
registerHandler(RecvOpcode.USE_ITEMEFFECT, new UseItemEffectHandler());
registerHandler(RecvOpcode.USE_CHAIR, new UseChairHandler());
registerHandler(RecvOpcode.CANCEL_CHAIR, new CancelChairHandler());
registerHandler(RecvOpcode.DAMAGE_REACTOR, new ReactorHitHandler());
registerHandler(RecvOpcode.GUILD_OPERATION, new GuildOperationHandler());
registerHandler(RecvOpcode.DENY_GUILD_REQUEST, new DenyGuildRequestHandler());
registerHandler(RecvOpcode.BBS_OPERATION, new BBSOperationHandler());
registerHandler(RecvOpcode.SKILL_EFFECT, new SkillEffectHandler());
registerHandler(RecvOpcode.MESSENGER, new MessengerHandler());
registerHandler(RecvOpcode.NPC_ACTION, new NPCAnimationHandler());
registerHandler(RecvOpcode.CHECK_CASH, new TouchingCashShopHandler());
registerHandler(RecvOpcode.CASHSHOP_OPERATION, new CashOperationHandler());
registerHandler(RecvOpcode.COUPON_CODE, new CouponCodeHandler());
registerHandler(RecvOpcode.SPAWN_PET, new SpawnPetHandler());
registerHandler(RecvOpcode.MOVE_PET, new MovePetHandler());
registerHandler(RecvOpcode.PET_CHAT, new PetChatHandler());
registerHandler(RecvOpcode.PET_COMMAND, new PetCommandHandler());
registerHandler(RecvOpcode.PET_FOOD, new PetFoodHandler());
registerHandler(RecvOpcode.PET_LOOT, new PetLootHandler());
registerHandler(RecvOpcode.AUTO_AGGRO, new AutoAggroHandler());
registerHandler(RecvOpcode.MONSTER_BOMB, new MonsterBombHandler());
registerHandler(RecvOpcode.CANCEL_DEBUFF, new CancelDebuffHandler());
registerHandler(RecvOpcode.USE_SKILL_BOOK, new SkillBookHandler());
registerHandler(RecvOpcode.SKILL_MACRO, new SkillMacroHandler());
registerHandler(RecvOpcode.NOTE_ACTION, new NoteActionHandler());
registerHandler(RecvOpcode.CLOSE_CHALKBOARD, new CloseChalkboardHandler());
registerHandler(RecvOpcode.USE_MOUNT_FOOD, new UseMountFoodHandler());
registerHandler(RecvOpcode.MTS_OPERATION, new MTSHandler());
registerHandler(RecvOpcode.RING_ACTION, new RingActionHandler());
registerHandler(RecvOpcode.SPOUSE_CHAT, new SpouseChatHandler());
registerHandler(RecvOpcode.PET_AUTO_POT, new PetAutoPotHandler());
registerHandler(RecvOpcode.PET_EXCLUDE_ITEMS, new PetExcludeItemsHandler());
registerHandler(RecvOpcode.OWL_ACTION, new UseOwlOfMinervaHandler());
registerHandler(RecvOpcode.OWL_WARP, new OwlWarpHandler());
registerHandler(RecvOpcode.TOUCH_MONSTER_ATTACK, new TouchMonsterDamageHandler());
registerHandler(RecvOpcode.TROCK_ADD_MAP, new TrockAddMapHandler());
registerHandler(RecvOpcode.HIRED_MERCHANT_REQUEST, new HiredMerchantRequest());
registerHandler(RecvOpcode.MOB_BANISH_PLAYER, new MobBanishPlayerHandler());
registerHandler(RecvOpcode.MOB_DAMAGE_MOB, new MobDamageMobHandler());
registerHandler(RecvOpcode.REPORT, new ReportHandler());
registerHandler(RecvOpcode.MONSTER_BOOK_COVER, new MonsterBookCoverHandler());
registerHandler(RecvOpcode.AUTO_DISTRIBUTE_AP, new AutoAssignHandler());
registerHandler(RecvOpcode.MAKER_SKILL, new MakerSkillHandler());
registerHandler(RecvOpcode.OPEN_FAMILY_PEDIGREE, new OpenFamilyPedigreeHandler());
registerHandler(RecvOpcode.OPEN_FAMILY, new OpenFamilyHandler());
registerHandler(RecvOpcode.ADD_FAMILY, new FamilyAddHandler());
registerHandler(RecvOpcode.SEPARATE_FAMILY_BY_SENIOR, new FamilySeparateHandler());
registerHandler(RecvOpcode.SEPARATE_FAMILY_BY_JUNIOR, new FamilySeparateHandler());
registerHandler(RecvOpcode.USE_FAMILY, new FamilyUseHandler());
registerHandler(RecvOpcode.CHANGE_FAMILY_MESSAGE, new FamilyPreceptsHandler());
registerHandler(RecvOpcode.FAMILY_SUMMON_RESPONSE, new FamilySummonResponseHandler());
registerHandler(RecvOpcode.USE_HAMMER, new UseHammerHandler());
registerHandler(RecvOpcode.SCRIPTED_ITEM, new ScriptedItemHandler());
registerHandler(RecvOpcode.TOUCHING_REACTOR, new TouchReactorHandler());
registerHandler(RecvOpcode.BEHOLDER, new BeholderHandler());
registerHandler(RecvOpcode.ADMIN_COMMAND, new AdminCommandHandler());
registerHandler(RecvOpcode.ADMIN_LOG, new AdminLogHandler());
registerHandler(RecvOpcode.ALLIANCE_OPERATION, new AllianceOperationHandler());
registerHandler(RecvOpcode.DENY_ALLIANCE_REQUEST, new DenyAllianceRequestHandler());
registerHandler(RecvOpcode.USE_SOLOMON_ITEM, new UseSolomonHandler());
registerHandler(RecvOpcode.USE_GACHA_EXP, new UseGachaExpHandler());
registerHandler(RecvOpcode.NEW_YEAR_CARD_REQUEST, new NewYearCardHandler());
registerHandler(RecvOpcode.CASHSHOP_SURPRISE, new CashShopSurpriseHandler());
registerHandler(RecvOpcode.USE_ITEM_REWARD, new ItemRewardHandler());
registerHandler(RecvOpcode.USE_REMOTE, new RemoteGachaponHandler());
registerHandler(RecvOpcode.ACCEPT_FAMILY, new AcceptFamilyHandler());
registerHandler(RecvOpcode.DUEY_ACTION, new DueyHandler());
registerHandler(RecvOpcode.USE_DEATHITEM, new UseDeathItemHandler());
registerHandler(RecvOpcode.PLAYER_MAP_TRANSFER, new PlayerMapTransitionHandler());
registerHandler(RecvOpcode.USE_MAPLELIFE, new UseMapleLifeHandler());
registerHandler(RecvOpcode.USE_CATCH_ITEM, new UseCatchItemHandler());
registerHandler(RecvOpcode.FIELD_DAMAGE_MOB, new FieldDamageMobHandler());
registerHandler(RecvOpcode.MOB_DAMAGE_MOB_FRIENDLY, new MobDamageMobFriendlyHandler());
registerHandler(RecvOpcode.PARTY_SEARCH_REGISTER, new PartySearchRegisterHandler());
registerHandler(RecvOpcode.PARTY_SEARCH_START, new PartySearchStartHandler());
registerHandler(RecvOpcode.PARTY_SEARCH_UPDATE, new PartySearchUpdateHandler());
registerHandler(RecvOpcode.ITEM_SORT2, new InventorySortHandler());
registerHandler(RecvOpcode.LEFT_KNOCKBACK, new LeftKnockbackHandler());
registerHandler(RecvOpcode.SNOWBALL, new SnowballHandler());
registerHandler(RecvOpcode.COCONUT, new CoconutHandler());
registerHandler(RecvOpcode.ARAN_COMBO_COUNTER, new AranComboHandler());
registerHandler(RecvOpcode.CLICK_GUIDE, new ClickGuideHandler());
registerHandler(RecvOpcode.FREDRICK_ACTION, new FredrickHandler());
registerHandler(RecvOpcode.MONSTER_CARNIVAL, new MonsterCarnivalHandler());
registerHandler(RecvOpcode.REMOTE_STORE, new RemoteStoreHandler());
registerHandler(RecvOpcode.WEDDING_ACTION, new WeddingHandler());
registerHandler(RecvOpcode.WEDDING_TALK, new WeddingTalkHandler());
registerHandler(RecvOpcode.WEDDING_TALK_MORE, new WeddingTalkMoreHandler());
registerHandler(RecvOpcode.WATER_OF_LIFE, new UseWaterOfLifeHandler());
registerHandler(RecvOpcode.ADMIN_CHAT, new AdminChatHandler());
registerHandler(RecvOpcode.MOVE_DRAGON, new MoveDragonHandler());
registerHandler(RecvOpcode.OPEN_ITEMUI, new RaiseUIStateHandler());
registerHandler(RecvOpcode.USE_ITEMUI, new RaiseIncExpHandler());
registerHandler(RecvOpcode.CHANGE_QUICKSLOT, new QuickslotKeyMappedModifiedHandler());
registerChannelHandlers();
}
}
private void registerCommonHandlers() {
registerHandler(RecvOpcode.PONG, new KeepAliveHandler());
registerHandler(RecvOpcode.CUSTOM_PACKET, new CustomPacketHandler());
}
private void registerLoginHandlers() {
registerHandler(RecvOpcode.ACCEPT_TOS, new AcceptToSHandler());
registerHandler(RecvOpcode.AFTER_LOGIN, new AfterLoginHandler());
registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler());
registerHandler(RecvOpcode.CHARLIST_REQUEST, new CharlistRequestHandler());
registerHandler(RecvOpcode.CHAR_SELECT, new CharSelectedHandler());
registerHandler(RecvOpcode.LOGIN_PASSWORD, new LoginPasswordHandler());
registerHandler(RecvOpcode.RELOG, new RelogRequestHandler());
registerHandler(RecvOpcode.SERVERLIST_REQUEST, new ServerlistRequestHandler());
registerHandler(RecvOpcode.SERVERSTATUS_REQUEST, new ServerStatusRequestHandler());
registerHandler(RecvOpcode.CHECK_CHAR_NAME, new CheckCharNameHandler());
registerHandler(RecvOpcode.CREATE_CHAR, new CreateCharHandler());
registerHandler(RecvOpcode.DELETE_CHAR, new DeleteCharHandler());
registerHandler(RecvOpcode.VIEW_ALL_CHAR, new ViewAllCharHandler());
registerHandler(RecvOpcode.PICK_ALL_CHAR, new ViewAllCharSelectedHandler());
registerHandler(RecvOpcode.REGISTER_PIN, new RegisterPinHandler());
registerHandler(RecvOpcode.GUEST_LOGIN, new GuestLoginHandler());
registerHandler(RecvOpcode.REGISTER_PIC, new RegisterPicHandler());
registerHandler(RecvOpcode.CHAR_SELECT_WITH_PIC, new CharSelectedWithPicHandler());
registerHandler(RecvOpcode.SET_GENDER, new SetGenderHandler());
registerHandler(RecvOpcode.VIEW_ALL_WITH_PIC, new ViewAllCharSelectedWithPicHandler());
registerHandler(RecvOpcode.VIEW_ALL_PIC_REGISTER, new ViewAllCharRegisterPicHandler());
}
private void registerChannelHandlers() {
registerHandler(RecvOpcode.NAME_TRANSFER, new TransferNameHandler());
registerHandler(RecvOpcode.CHECK_CHAR_NAME, new TransferNameResultHandler());
registerHandler(RecvOpcode.WORLD_TRANSFER, new TransferWorldHandler());
registerHandler(RecvOpcode.CHANGE_CHANNEL, new ChangeChannelHandler());
registerHandler(RecvOpcode.STRANGE_DATA, LoginRequiringNoOpHandler.getInstance());
registerHandler(RecvOpcode.GENERAL_CHAT, new GeneralChatHandler());
registerHandler(RecvOpcode.WHISPER, new WhisperHandler());
registerHandler(RecvOpcode.NPC_TALK, new NPCTalkHandler());
registerHandler(RecvOpcode.NPC_TALK_MORE, new NPCMoreTalkHandler());
registerHandler(RecvOpcode.QUEST_ACTION, new QuestActionHandler());
registerHandler(RecvOpcode.GRENADE_EFFECT, new GrenadeEffectHandler());
registerHandler(RecvOpcode.NPC_SHOP, new NPCShopHandler());
registerHandler(RecvOpcode.ITEM_SORT, new InventoryMergeHandler());
registerHandler(RecvOpcode.ITEM_MOVE, new ItemMoveHandler());
registerHandler(RecvOpcode.MESO_DROP, new MesoDropHandler());
registerHandler(RecvOpcode.PLAYER_LOGGEDIN, new PlayerLoggedinHandler());
registerHandler(RecvOpcode.CHANGE_MAP, new ChangeMapHandler());
registerHandler(RecvOpcode.MOVE_LIFE, new MoveLifeHandler());
registerHandler(RecvOpcode.CLOSE_RANGE_ATTACK, new CloseRangeDamageHandler());
registerHandler(RecvOpcode.RANGED_ATTACK, new RangedAttackHandler());
registerHandler(RecvOpcode.MAGIC_ATTACK, new MagicDamageHandler());
registerHandler(RecvOpcode.TAKE_DAMAGE, new TakeDamageHandler());
registerHandler(RecvOpcode.MOVE_PLAYER, new MovePlayerHandler());
registerHandler(RecvOpcode.USE_CASH_ITEM, new UseCashItemHandler());
registerHandler(RecvOpcode.USE_ITEM, new UseItemHandler());
registerHandler(RecvOpcode.USE_RETURN_SCROLL, new UseItemHandler());
registerHandler(RecvOpcode.USE_UPGRADE_SCROLL, new ScrollHandler());
registerHandler(RecvOpcode.USE_SUMMON_BAG, new UseSummonBagHandler());
registerHandler(RecvOpcode.FACE_EXPRESSION, new FaceExpressionHandler());
registerHandler(RecvOpcode.HEAL_OVER_TIME, new HealOvertimeHandler());
registerHandler(RecvOpcode.ITEM_PICKUP, new ItemPickupHandler());
registerHandler(RecvOpcode.CHAR_INFO_REQUEST, new CharInfoRequestHandler());
registerHandler(RecvOpcode.SPECIAL_MOVE, new SpecialMoveHandler());
registerHandler(RecvOpcode.USE_INNER_PORTAL, new InnerPortalHandler());
registerHandler(RecvOpcode.CANCEL_BUFF, new CancelBuffHandler());
registerHandler(RecvOpcode.CANCEL_ITEM_EFFECT, new CancelItemEffectHandler());
registerHandler(RecvOpcode.PLAYER_INTERACTION, new PlayerInteractionHandler());
registerHandler(RecvOpcode.RPS_ACTION, new RPSActionHandler());
registerHandler(RecvOpcode.DISTRIBUTE_AP, new DistributeAPHandler());
registerHandler(RecvOpcode.DISTRIBUTE_SP, new DistributeSPHandler());
registerHandler(RecvOpcode.CHANGE_KEYMAP, new KeymapChangeHandler());
registerHandler(RecvOpcode.CHANGE_MAP_SPECIAL, new ChangeMapSpecialHandler());
registerHandler(RecvOpcode.STORAGE, new StorageHandler());
registerHandler(RecvOpcode.GIVE_FAME, new GiveFameHandler());
registerHandler(RecvOpcode.PARTY_OPERATION, new PartyOperationHandler());
registerHandler(RecvOpcode.DENY_PARTY_REQUEST, new DenyPartyRequestHandler());
registerHandler(RecvOpcode.MULTI_CHAT, new MultiChatHandler());
registerHandler(RecvOpcode.USE_DOOR, new DoorHandler());
registerHandler(RecvOpcode.ENTER_MTS, new EnterMTSHandler());
registerHandler(RecvOpcode.ENTER_CASHSHOP, new EnterCashShopHandler());
registerHandler(RecvOpcode.DAMAGE_SUMMON, new DamageSummonHandler());
registerHandler(RecvOpcode.MOVE_SUMMON, new MoveSummonHandler());
registerHandler(RecvOpcode.SUMMON_ATTACK, new SummonDamageHandler());
registerHandler(RecvOpcode.BUDDYLIST_MODIFY, new BuddylistModifyHandler());
registerHandler(RecvOpcode.USE_ITEMEFFECT, new UseItemEffectHandler());
registerHandler(RecvOpcode.USE_CHAIR, new UseChairHandler());
registerHandler(RecvOpcode.CANCEL_CHAIR, new CancelChairHandler());
registerHandler(RecvOpcode.DAMAGE_REACTOR, new ReactorHitHandler());
registerHandler(RecvOpcode.GUILD_OPERATION, new GuildOperationHandler());
registerHandler(RecvOpcode.DENY_GUILD_REQUEST, new DenyGuildRequestHandler());
registerHandler(RecvOpcode.BBS_OPERATION, new BBSOperationHandler());
registerHandler(RecvOpcode.SKILL_EFFECT, new SkillEffectHandler());
registerHandler(RecvOpcode.MESSENGER, new MessengerHandler());
registerHandler(RecvOpcode.NPC_ACTION, new NPCAnimationHandler());
registerHandler(RecvOpcode.CHECK_CASH, new TouchingCashShopHandler());
registerHandler(RecvOpcode.CASHSHOP_OPERATION, new CashOperationHandler());
registerHandler(RecvOpcode.COUPON_CODE, new CouponCodeHandler());
registerHandler(RecvOpcode.SPAWN_PET, new SpawnPetHandler());
registerHandler(RecvOpcode.MOVE_PET, new MovePetHandler());
registerHandler(RecvOpcode.PET_CHAT, new PetChatHandler());
registerHandler(RecvOpcode.PET_COMMAND, new PetCommandHandler());
registerHandler(RecvOpcode.PET_FOOD, new PetFoodHandler());
registerHandler(RecvOpcode.PET_LOOT, new PetLootHandler());
registerHandler(RecvOpcode.AUTO_AGGRO, new AutoAggroHandler());
registerHandler(RecvOpcode.MONSTER_BOMB, new MonsterBombHandler());
registerHandler(RecvOpcode.CANCEL_DEBUFF, new CancelDebuffHandler());
registerHandler(RecvOpcode.USE_SKILL_BOOK, new SkillBookHandler());
registerHandler(RecvOpcode.SKILL_MACRO, new SkillMacroHandler());
registerHandler(RecvOpcode.NOTE_ACTION, new NoteActionHandler());
registerHandler(RecvOpcode.CLOSE_CHALKBOARD, new CloseChalkboardHandler());
registerHandler(RecvOpcode.USE_MOUNT_FOOD, new UseMountFoodHandler());
registerHandler(RecvOpcode.MTS_OPERATION, new MTSHandler());
registerHandler(RecvOpcode.RING_ACTION, new RingActionHandler());
registerHandler(RecvOpcode.SPOUSE_CHAT, new SpouseChatHandler());
registerHandler(RecvOpcode.PET_AUTO_POT, new PetAutoPotHandler());
registerHandler(RecvOpcode.PET_EXCLUDE_ITEMS, new PetExcludeItemsHandler());
registerHandler(RecvOpcode.OWL_ACTION, new UseOwlOfMinervaHandler());
registerHandler(RecvOpcode.OWL_WARP, new OwlWarpHandler());
registerHandler(RecvOpcode.TOUCH_MONSTER_ATTACK, new TouchMonsterDamageHandler());
registerHandler(RecvOpcode.TROCK_ADD_MAP, new TrockAddMapHandler());
registerHandler(RecvOpcode.HIRED_MERCHANT_REQUEST, new HiredMerchantRequest());
registerHandler(RecvOpcode.MOB_BANISH_PLAYER, new MobBanishPlayerHandler());
registerHandler(RecvOpcode.MOB_DAMAGE_MOB, new MobDamageMobHandler());
registerHandler(RecvOpcode.REPORT, new ReportHandler());
registerHandler(RecvOpcode.MONSTER_BOOK_COVER, new MonsterBookCoverHandler());
registerHandler(RecvOpcode.AUTO_DISTRIBUTE_AP, new AutoAssignHandler());
registerHandler(RecvOpcode.MAKER_SKILL, new MakerSkillHandler());
registerHandler(RecvOpcode.OPEN_FAMILY_PEDIGREE, new OpenFamilyPedigreeHandler());
registerHandler(RecvOpcode.OPEN_FAMILY, new OpenFamilyHandler());
registerHandler(RecvOpcode.ADD_FAMILY, new FamilyAddHandler());
registerHandler(RecvOpcode.SEPARATE_FAMILY_BY_SENIOR, new FamilySeparateHandler());
registerHandler(RecvOpcode.SEPARATE_FAMILY_BY_JUNIOR, new FamilySeparateHandler());
registerHandler(RecvOpcode.USE_FAMILY, new FamilyUseHandler());
registerHandler(RecvOpcode.CHANGE_FAMILY_MESSAGE, new FamilyPreceptsHandler());
registerHandler(RecvOpcode.FAMILY_SUMMON_RESPONSE, new FamilySummonResponseHandler());
registerHandler(RecvOpcode.USE_HAMMER, new UseHammerHandler());
registerHandler(RecvOpcode.SCRIPTED_ITEM, new ScriptedItemHandler());
registerHandler(RecvOpcode.TOUCHING_REACTOR, new TouchReactorHandler());
registerHandler(RecvOpcode.BEHOLDER, new BeholderHandler());
registerHandler(RecvOpcode.ADMIN_COMMAND, new AdminCommandHandler());
registerHandler(RecvOpcode.ADMIN_LOG, new AdminLogHandler());
registerHandler(RecvOpcode.ALLIANCE_OPERATION, new AllianceOperationHandler());
registerHandler(RecvOpcode.DENY_ALLIANCE_REQUEST, new DenyAllianceRequestHandler());
registerHandler(RecvOpcode.USE_SOLOMON_ITEM, new UseSolomonHandler());
registerHandler(RecvOpcode.USE_GACHA_EXP, new UseGachaExpHandler());
registerHandler(RecvOpcode.NEW_YEAR_CARD_REQUEST, new NewYearCardHandler());
registerHandler(RecvOpcode.CASHSHOP_SURPRISE, new CashShopSurpriseHandler());
registerHandler(RecvOpcode.USE_ITEM_REWARD, new ItemRewardHandler());
registerHandler(RecvOpcode.USE_REMOTE, new RemoteGachaponHandler());
registerHandler(RecvOpcode.ACCEPT_FAMILY, new AcceptFamilyHandler());
registerHandler(RecvOpcode.DUEY_ACTION, new DueyHandler());
registerHandler(RecvOpcode.USE_DEATHITEM, new UseDeathItemHandler());
registerHandler(RecvOpcode.PLAYER_MAP_TRANSFER, new PlayerMapTransitionHandler());
registerHandler(RecvOpcode.USE_MAPLELIFE, new UseMapleLifeHandler());
registerHandler(RecvOpcode.USE_CATCH_ITEM, new UseCatchItemHandler());
registerHandler(RecvOpcode.FIELD_DAMAGE_MOB, new FieldDamageMobHandler());
registerHandler(RecvOpcode.MOB_DAMAGE_MOB_FRIENDLY, new MobDamageMobFriendlyHandler());
registerHandler(RecvOpcode.PARTY_SEARCH_REGISTER, new PartySearchRegisterHandler());
registerHandler(RecvOpcode.PARTY_SEARCH_START, new PartySearchStartHandler());
registerHandler(RecvOpcode.PARTY_SEARCH_UPDATE, new PartySearchUpdateHandler());
registerHandler(RecvOpcode.ITEM_SORT2, new InventorySortHandler());
registerHandler(RecvOpcode.LEFT_KNOCKBACK, new LeftKnockbackHandler());
registerHandler(RecvOpcode.SNOWBALL, new SnowballHandler());
registerHandler(RecvOpcode.COCONUT, new CoconutHandler());
registerHandler(RecvOpcode.ARAN_COMBO_COUNTER, new AranComboHandler());
registerHandler(RecvOpcode.CLICK_GUIDE, new ClickGuideHandler());
registerHandler(RecvOpcode.FREDRICK_ACTION, new FredrickHandler());
registerHandler(RecvOpcode.MONSTER_CARNIVAL, new MonsterCarnivalHandler());
registerHandler(RecvOpcode.REMOTE_STORE, new RemoteStoreHandler());
registerHandler(RecvOpcode.WEDDING_ACTION, new WeddingHandler());
registerHandler(RecvOpcode.WEDDING_TALK, new WeddingTalkHandler());
registerHandler(RecvOpcode.WEDDING_TALK_MORE, new WeddingTalkMoreHandler());
registerHandler(RecvOpcode.WATER_OF_LIFE, new UseWaterOfLifeHandler());
registerHandler(RecvOpcode.ADMIN_CHAT, new AdminChatHandler());
registerHandler(RecvOpcode.MOVE_DRAGON, new MoveDragonHandler());
registerHandler(RecvOpcode.OPEN_ITEMUI, new RaiseUIStateHandler());
registerHandler(RecvOpcode.USE_ITEMUI, new RaiseIncExpHandler());
registerHandler(RecvOpcode.CHANGE_QUICKSLOT, new QuickslotKeyMappedModifiedHandler());
}
}

View File

@@ -0,0 +1,27 @@
package net.encryption;
import constants.net.ServerConstants;
public class ClientCyphers {
private final MapleAESOFB send;
private final MapleAESOFB receive;
private ClientCyphers(MapleAESOFB send, MapleAESOFB receive) {
this.send = send;
this.receive = receive;
}
public static ClientCyphers of(InitializationVector sendIv, InitializationVector receiveIv) {
MapleAESOFB send = new MapleAESOFB(sendIv, (short) (0xFFFF - ServerConstants.VERSION));
MapleAESOFB receive = new MapleAESOFB(receiveIv, ServerConstants.VERSION);
return new ClientCyphers(send, receive);
}
public MapleAESOFB getSendCypher() {
return send;
}
public MapleAESOFB getReceiveCypher() {
return receive;
}
}

View File

@@ -0,0 +1,27 @@
package net.encryption;
public class InitializationVector {
private final byte[] bytes;
private InitializationVector(byte[] bytes) {
this.bytes = bytes;
}
public byte[] getBytes() {
return bytes;
}
public static InitializationVector generateSend() {
byte[] ivSend = {82, 48, 120, getRandomByte()};
return new InitializationVector(ivSend);
}
public static InitializationVector generateReceive() {
byte[] ivRecv = {70, 114, 122, getRandomByte()};
return new InitializationVector(ivRecv);
}
private static byte getRandomByte() {
return (byte) (Math.random() * 255);
}
}

View File

@@ -19,7 +19,11 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package tools;
package net.encryption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.HexTool;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -30,52 +34,73 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class MapleAESOFB {
private byte[] iv;
private Cipher cipher;
private short mapleVersion;
private static final Logger log = LoggerFactory.getLogger(MapleAESOFB.class);
private final static SecretKeySpec skey = new SecretKeySpec(
new byte[]{0x13, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, (byte) 0xB4, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00}, "AES");
new byte[]{
0x13, 0x00, 0x00, 0x00,
0x08, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00,
(byte) 0xB4, 0x00, 0x00, 0x00,
0x1B, 0x00, 0x00, 0x00,
0x0F, 0x00, 0x00, 0x00,
0x33, 0x00, 0x00, 0x00,
0x52, 0x00, 0x00, 0x00}, "AES");
private static final byte[] funnyBytes = new byte[]{(byte) 0xEC, (byte) 0x3F, (byte) 0x77, (byte) 0xA4, (byte) 0x45, (byte) 0xD0, (byte) 0x71, (byte) 0xBF, (byte) 0xB7, (byte) 0x98, (byte) 0x20, (byte) 0xFC,
(byte) 0x4B, (byte) 0xE9, (byte) 0xB3, (byte) 0xE1, (byte) 0x5C, (byte) 0x22, (byte) 0xF7, (byte) 0x0C, (byte) 0x44, (byte) 0x1B, (byte) 0x81, (byte) 0xBD, (byte) 0x63, (byte) 0x8D, (byte) 0xD4, (byte) 0xC3,
(byte) 0xF2, (byte) 0x10, (byte) 0x19, (byte) 0xE0, (byte) 0xFB, (byte) 0xA1, (byte) 0x6E, (byte) 0x66, (byte) 0xEA, (byte) 0xAE, (byte) 0xD6, (byte) 0xCE, (byte) 0x06, (byte) 0x18, (byte) 0x4E, (byte) 0xEB,
(byte) 0x78, (byte) 0x95, (byte) 0xDB, (byte) 0xBA, (byte) 0xB6, (byte) 0x42, (byte) 0x7A, (byte) 0x2A, (byte) 0x83, (byte) 0x0B, (byte) 0x54, (byte) 0x67, (byte) 0x6D, (byte) 0xE8, (byte) 0x65, (byte) 0xE7,
(byte) 0x2F, (byte) 0x07, (byte) 0xF3, (byte) 0xAA, (byte) 0x27, (byte) 0x7B, (byte) 0x85, (byte) 0xB0, (byte) 0x26, (byte) 0xFD, (byte) 0x8B, (byte) 0xA9, (byte) 0xFA, (byte) 0xBE, (byte) 0xA8, (byte) 0xD7,
(byte) 0xCB, (byte) 0xCC, (byte) 0x92, (byte) 0xDA, (byte) 0xF9, (byte) 0x93, (byte) 0x60, (byte) 0x2D, (byte) 0xDD, (byte) 0xD2, (byte) 0xA2, (byte) 0x9B, (byte) 0x39, (byte) 0x5F, (byte) 0x82, (byte) 0x21,
(byte) 0x4C, (byte) 0x69, (byte) 0xF8, (byte) 0x31, (byte) 0x87, (byte) 0xEE, (byte) 0x8E, (byte) 0xAD, (byte) 0x8C, (byte) 0x6A, (byte) 0xBC, (byte) 0xB5, (byte) 0x6B, (byte) 0x59, (byte) 0x13, (byte) 0xF1,
(byte) 0x04, (byte) 0x00, (byte) 0xF6, (byte) 0x5A, (byte) 0x35, (byte) 0x79, (byte) 0x48, (byte) 0x8F, (byte) 0x15, (byte) 0xCD, (byte) 0x97, (byte) 0x57, (byte) 0x12, (byte) 0x3E, (byte) 0x37, (byte) 0xFF,
(byte) 0x9D, (byte) 0x4F, (byte) 0x51, (byte) 0xF5, (byte) 0xA3, (byte) 0x70, (byte) 0xBB, (byte) 0x14, (byte) 0x75, (byte) 0xC2, (byte) 0xB8, (byte) 0x72, (byte) 0xC0, (byte) 0xED, (byte) 0x7D, (byte) 0x68,
(byte) 0xC9, (byte) 0x2E, (byte) 0x0D, (byte) 0x62, (byte) 0x46, (byte) 0x17, (byte) 0x11, (byte) 0x4D, (byte) 0x6C, (byte) 0xC4, (byte) 0x7E, (byte) 0x53, (byte) 0xC1, (byte) 0x25, (byte) 0xC7, (byte) 0x9A,
(byte) 0x1C, (byte) 0x88, (byte) 0x58, (byte) 0x2C, (byte) 0x89, (byte) 0xDC, (byte) 0x02, (byte) 0x64, (byte) 0x40, (byte) 0x01, (byte) 0x5D, (byte) 0x38, (byte) 0xA5, (byte) 0xE2, (byte) 0xAF, (byte) 0x55,
(byte) 0xD5, (byte) 0xEF, (byte) 0x1A, (byte) 0x7C, (byte) 0xA7, (byte) 0x5B, (byte) 0xA6, (byte) 0x6F, (byte) 0x86, (byte) 0x9F, (byte) 0x73, (byte) 0xE6, (byte) 0x0A, (byte) 0xDE, (byte) 0x2B, (byte) 0x99,
(byte) 0x4A, (byte) 0x47, (byte) 0x9C, (byte) 0xDF, (byte) 0x09, (byte) 0x76, (byte) 0x9E, (byte) 0x30, (byte) 0x0E, (byte) 0xE4, (byte) 0xB2, (byte) 0x94, (byte) 0xA0, (byte) 0x3B, (byte) 0x34, (byte) 0x1D,
(byte) 0x28, (byte) 0x0F, (byte) 0x36, (byte) 0xE3, (byte) 0x23, (byte) 0xB4, (byte) 0x03, (byte) 0xD8, (byte) 0x90, (byte) 0xC8, (byte) 0x3C, (byte) 0xFE, (byte) 0x5E, (byte) 0x32, (byte) 0x24, (byte) 0x50,
(byte) 0x1F, (byte) 0x3A, (byte) 0x43, (byte) 0x8A, (byte) 0x96, (byte) 0x41, (byte) 0x74, (byte) 0xAC, (byte) 0x52, (byte) 0x33, (byte) 0xF0, (byte) 0xD9, (byte) 0x29, (byte) 0x80, (byte) 0xB1, (byte) 0x16,
(byte) 0xD3, (byte) 0xAB, (byte) 0x91, (byte) 0xB9, (byte) 0x84, (byte) 0x7F, (byte) 0x61, (byte) 0x1E, (byte) 0xCF, (byte) 0xC5, (byte) 0xD1, (byte) 0x56, (byte) 0x3D, (byte) 0xCA, (byte) 0xF4, (byte) 0x05,
(byte) 0xC6, (byte) 0xE5, (byte) 0x08, (byte) 0x49};
private static final byte[] funnyBytes = new byte[]{
(byte) 0xEC, (byte) 0x3F, (byte) 0x77, (byte) 0xA4, (byte) 0x45, (byte) 0xD0, (byte) 0x71, (byte) 0xBF,
(byte) 0xB7, (byte) 0x98, (byte) 0x20, (byte) 0xFC, (byte) 0x4B, (byte) 0xE9, (byte) 0xB3, (byte) 0xE1,
(byte) 0x5C, (byte) 0x22, (byte) 0xF7, (byte) 0x0C, (byte) 0x44, (byte) 0x1B, (byte) 0x81, (byte) 0xBD,
(byte) 0x63, (byte) 0x8D, (byte) 0xD4, (byte) 0xC3, (byte) 0xF2, (byte) 0x10, (byte) 0x19, (byte) 0xE0,
(byte) 0xFB, (byte) 0xA1, (byte) 0x6E, (byte) 0x66, (byte) 0xEA, (byte) 0xAE, (byte) 0xD6, (byte) 0xCE,
(byte) 0x06, (byte) 0x18, (byte) 0x4E, (byte) 0xEB, (byte) 0x78, (byte) 0x95, (byte) 0xDB, (byte) 0xBA,
(byte) 0xB6, (byte) 0x42, (byte) 0x7A, (byte) 0x2A, (byte) 0x83, (byte) 0x0B, (byte) 0x54, (byte) 0x67,
(byte) 0x6D, (byte) 0xE8, (byte) 0x65, (byte) 0xE7, (byte) 0x2F, (byte) 0x07, (byte) 0xF3, (byte) 0xAA,
(byte) 0x27, (byte) 0x7B, (byte) 0x85, (byte) 0xB0, (byte) 0x26, (byte) 0xFD, (byte) 0x8B, (byte) 0xA9,
(byte) 0xFA, (byte) 0xBE, (byte) 0xA8, (byte) 0xD7, (byte) 0xCB, (byte) 0xCC, (byte) 0x92, (byte) 0xDA,
(byte) 0xF9, (byte) 0x93, (byte) 0x60, (byte) 0x2D, (byte) 0xDD, (byte) 0xD2, (byte) 0xA2, (byte) 0x9B,
(byte) 0x39, (byte) 0x5F, (byte) 0x82, (byte) 0x21, (byte) 0x4C, (byte) 0x69, (byte) 0xF8, (byte) 0x31,
(byte) 0x87, (byte) 0xEE, (byte) 0x8E, (byte) 0xAD, (byte) 0x8C, (byte) 0x6A, (byte) 0xBC, (byte) 0xB5,
(byte) 0x6B, (byte) 0x59, (byte) 0x13, (byte) 0xF1, (byte) 0x04, (byte) 0x00, (byte) 0xF6, (byte) 0x5A,
(byte) 0x35, (byte) 0x79, (byte) 0x48, (byte) 0x8F, (byte) 0x15, (byte) 0xCD, (byte) 0x97, (byte) 0x57,
(byte) 0x12, (byte) 0x3E, (byte) 0x37, (byte) 0xFF, (byte) 0x9D, (byte) 0x4F, (byte) 0x51, (byte) 0xF5,
(byte) 0xA3, (byte) 0x70, (byte) 0xBB, (byte) 0x14, (byte) 0x75, (byte) 0xC2, (byte) 0xB8, (byte) 0x72,
(byte) 0xC0, (byte) 0xED, (byte) 0x7D, (byte) 0x68, (byte) 0xC9, (byte) 0x2E, (byte) 0x0D, (byte) 0x62,
(byte) 0x46, (byte) 0x17, (byte) 0x11, (byte) 0x4D, (byte) 0x6C, (byte) 0xC4, (byte) 0x7E, (byte) 0x53,
(byte) 0xC1, (byte) 0x25, (byte) 0xC7, (byte) 0x9A, (byte) 0x1C, (byte) 0x88, (byte) 0x58, (byte) 0x2C,
(byte) 0x89, (byte) 0xDC, (byte) 0x02, (byte) 0x64, (byte) 0x40, (byte) 0x01, (byte) 0x5D, (byte) 0x38,
(byte) 0xA5, (byte) 0xE2, (byte) 0xAF, (byte) 0x55, (byte) 0xD5, (byte) 0xEF, (byte) 0x1A, (byte) 0x7C,
(byte) 0xA7, (byte) 0x5B, (byte) 0xA6, (byte) 0x6F, (byte) 0x86, (byte) 0x9F, (byte) 0x73, (byte) 0xE6,
(byte) 0x0A, (byte) 0xDE, (byte) 0x2B, (byte) 0x99, (byte) 0x4A, (byte) 0x47, (byte) 0x9C, (byte) 0xDF,
(byte) 0x09, (byte) 0x76, (byte) 0x9E, (byte) 0x30, (byte) 0x0E, (byte) 0xE4, (byte) 0xB2, (byte) 0x94,
(byte) 0xA0, (byte) 0x3B, (byte) 0x34, (byte) 0x1D, (byte) 0x28, (byte) 0x0F, (byte) 0x36, (byte) 0xE3,
(byte) 0x23, (byte) 0xB4, (byte) 0x03, (byte) 0xD8, (byte) 0x90, (byte) 0xC8, (byte) 0x3C, (byte) 0xFE,
(byte) 0x5E, (byte) 0x32, (byte) 0x24, (byte) 0x50, (byte) 0x1F, (byte) 0x3A, (byte) 0x43, (byte) 0x8A,
(byte) 0x96, (byte) 0x41, (byte) 0x74, (byte) 0xAC, (byte) 0x52, (byte) 0x33, (byte) 0xF0, (byte) 0xD9,
(byte) 0x29, (byte) 0x80, (byte) 0xB1, (byte) 0x16, (byte) 0xD3, (byte) 0xAB, (byte) 0x91, (byte) 0xB9,
(byte) 0x84, (byte) 0x7F, (byte) 0x61, (byte) 0x1E, (byte) 0xCF, (byte) 0xC5, (byte) 0xD1, (byte) 0x56,
(byte) 0x3D, (byte) 0xCA, (byte) 0xF4, (byte) 0x05, (byte) 0xC6, (byte) 0xE5, (byte) 0x08, (byte) 0x49};
public MapleAESOFB(byte[] iv, short mapleVersion) {
private final short mapleVersion;
private final Cipher cipher;
private byte[] iv;
public MapleAESOFB(InitializationVector iv, short mapleVersion) {
try {
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
} catch (NoSuchAlgorithmException e) {
System.out.println("ERROR " + e);
} catch (NoSuchPaddingException e) {
System.out.println("ERROR " + e);
} catch (InvalidKeyException e) {
System.out.println("Error initializing the encryption cipher. Make sure you're using the Unlimited Strength cryptography jar files.");
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
log.warn("Cypher initialization error with skey: {}", skey, e);
throw new RuntimeException(e);
}
this.setIv(iv);
this.iv = iv.getBytes();
this.mapleVersion = (short) (((mapleVersion >> 8) & 0xFF) | ((mapleVersion << 8) & 0xFF00));
}
private void setIv(byte[] iv) {
this.iv = iv;
}
private static byte[] multiplyBytes(byte[] in, int count, int mul) {
byte[] ret = new byte[count * mul];
for (int x = 0; x < count * mul; x++) {
final int size = count * mul;
byte[] ret = new byte[size];
for (int x = 0; x < size; x++) {
ret[x] = in[x % count];
}
return ret;
@@ -94,12 +119,8 @@ public class MapleAESOFB {
if ((x - start) % myIv.length == 0) {
try {
byte[] newIv = cipher.doFinal(myIv);
for (int j = 0; j < myIv.length; j++) {
myIv[j] = newIv[j];
}
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
System.arraycopy(newIv, 0, myIv, 0, myIv.length);
} catch (IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
}
@@ -137,11 +158,12 @@ public class MapleAESOFB {
return packetLength;
}
public boolean checkPacket(byte[] packet) {
return ((((packet[0] ^ iv[2]) & 0xFF) == ((mapleVersion >> 8) & 0xFF)) && (((packet[1] ^ iv[3]) & 0xFF) == (mapleVersion & 0xFF)));
private boolean checkPacket(byte[] packet) {
return ((((packet[0] ^ iv[2]) & 0xFF) == ((mapleVersion >> 8) & 0xFF)) &&
(((packet[1] ^ iv[3]) & 0xFF) == (mapleVersion & 0xFF)));
}
public boolean checkPacket(int packetHeader) {
public boolean isValidHeader(int packetHeader) {
byte[] packetHeaderBuf = new byte[2];
packetHeaderBuf[0] = (byte) ((packetHeader >> 24) & 0xFF);
packetHeaderBuf[1] = (byte) ((packetHeader >> 16) & 0xFF);

View File

@@ -19,7 +19,7 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.mina;
package net.encryption;
public class MapleCustomEncryption {
private static byte rollLeft(byte in, int count) {

View File

@@ -0,0 +1,9 @@
package net.encryption;
import io.netty.channel.CombinedChannelDuplexHandler;
public class PacketCodec extends CombinedChannelDuplexHandler<PacketDecoder, PacketEncoder> {
public PacketCodec(ClientCyphers clientCyphers) {
super(new PacketDecoder(clientCyphers.getReceiveCypher()), new PacketEncoder(clientCyphers.getSendCypher()));
}
}

View File

@@ -0,0 +1,48 @@
package net.encryption;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder;
import net.netty.InvalidPacketHeaderException;
import net.packet.ByteBufInPacket;
import java.util.List;
public class PacketDecoder extends ReplayingDecoder<Void> {
private final MapleAESOFB receiveCypher;
public PacketDecoder(MapleAESOFB receiveCypher) {
this.receiveCypher = receiveCypher;
}
@Override
protected void decode(ChannelHandlerContext context, ByteBuf in, List<Object> out) {
final int header = in.readInt();
if (!receiveCypher.isValidHeader(header)) {
throw new InvalidPacketHeaderException("Attempted to decode a packet with an invalid header", header);
}
final int packetLength = decodePacketLength(header);
byte[] packet = new byte[packetLength];
in.readBytes(packet);
receiveCypher.crypt(packet);
MapleCustomEncryption.decryptData(packet);
out.add(new ByteBufInPacket(Unpooled.wrappedBuffer(packet)));
}
/**
* @param header Packet header - the first 4 bytes of the packet
* @return Packet size in bytes
*/
private static int decodePacketLength(byte[] header) {
return (((header[1] ^ header[3]) & 0xFF) << 8) | ((header[0] ^ header[2]) & 0xFF);
}
private static int decodePacketLength(int header) {
int length = ((header >>> 16) ^ (header & 0xFFFF));
length = ((length << 8) & 0xFF00) | ((length >>> 8) & 0xFF);
return length;
}
}

View File

@@ -0,0 +1,28 @@
package net.encryption;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import net.packet.OutPacket;
public class PacketEncoder extends MessageToByteEncoder<OutPacket> {
private final MapleAESOFB sendCypher;
public PacketEncoder(MapleAESOFB sendCypher) {
this.sendCypher = sendCypher;
}
@Override
protected void encode(ChannelHandlerContext ctx, OutPacket in, ByteBuf out) {
byte[] packet = in.getBytes();
out.writeBytes(getEncodedHeader(packet.length));
MapleCustomEncryption.encryptData(packet);
sendCypher.crypt(packet);
out.writeBytes(packet);
}
private byte[] getEncodedHeader(int length) {
return sendCypher.getPacketHeader(length);
}
}

View File

@@ -1,47 +0,0 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.mina;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;
public class MapleCodecFactory implements ProtocolCodecFactory {
private final ProtocolEncoder encoder;
private final ProtocolDecoder decoder;
public MapleCodecFactory() {
encoder = new MaplePacketEncoder();
decoder = new MaplePacketDecoder();
}
@Override
public ProtocolEncoder getEncoder(IoSession session) throws Exception {
return encoder;
}
@Override
public ProtocolDecoder getDecoder(IoSession session) throws Exception {
return decoder;
}
}

View File

@@ -1,105 +0,0 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.mina;
import client.MapleClient;
import config.YamlConfig;
import constants.net.OpcodeConstants;
import net.server.coordinator.session.MapleSessionCoordinator;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import tools.FilePrinter;
import tools.HexTool;
import tools.MapleAESOFB;
import tools.data.input.ByteArrayByteStream;
import tools.data.input.GenericLittleEndianAccessor;
public class MaplePacketDecoder extends CumulativeProtocolDecoder {
private static final String DECODER_STATE_KEY = MaplePacketDecoder.class.getName() + ".STATE";
private static class DecoderState {
public int packetlength = -1;
}
@Override
protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
final MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
if(client == null) {
MapleSessionCoordinator.getInstance().closeSession(session, true);
return false;
}
DecoderState decoderState = (DecoderState) session.getAttribute(DECODER_STATE_KEY);
if (decoderState == null) {
decoderState = new DecoderState();
session.setAttribute(DECODER_STATE_KEY, decoderState);
}
MapleAESOFB rcvdCrypto = client.getReceiveCrypto();
if (in.remaining() >= 4 && decoderState.packetlength == -1) {
int packetHeader = in.getInt();
if (!rcvdCrypto.checkPacket(packetHeader)) {
MapleSessionCoordinator.getInstance().closeSession(session, true);
return false;
}
decoderState.packetlength = MapleAESOFB.getPacketLength(packetHeader);
} else if (in.remaining() < 4 && decoderState.packetlength == -1) {
return false;
}
if (in.remaining() >= decoderState.packetlength) {
byte[] decryptedPacket = new byte[decoderState.packetlength];
in.get(decryptedPacket, 0, decoderState.packetlength);
decoderState.packetlength = -1;
rcvdCrypto.crypt(decryptedPacket);
MapleCustomEncryption.decryptData(decryptedPacket);
out.write(decryptedPacket);
if (YamlConfig.config.server.USE_DEBUG_SHOW_PACKET){ // Atoot's idea: packet traffic log, applied using auto-identation thanks to lrenex
int packetLen = decryptedPacket.length;
int pHeader = readFirstShort(decryptedPacket);
String pHeaderStr = Integer.toHexString(pHeader).toUpperCase();
String op = lookupSend(pHeader);
String Send = "ClientSend:" + op + " [" + pHeaderStr + "] (" + packetLen + ")\r\n";
if (packetLen <= 3000) {
String SendTo = Send + HexTool.toString(decryptedPacket) + "\r\n" + HexTool.toStringFromAscii(decryptedPacket);
System.out.println(SendTo);
if (op == null) {
System.out.println("UnknownPacket:" + SendTo);
}
} else {
FilePrinter.print(FilePrinter.PACKET_STREAM + MapleSessionCoordinator.getSessionRemoteAddress(session) + ".txt", HexTool.toString(new byte[]{decryptedPacket[0], decryptedPacket[1]}) + "...");
}
}
return true;
}
return false;
}
private String lookupSend(int val) {
return OpcodeConstants.recvOpcodeNames.get(val);
}
private int readFirstShort(byte[] arr) {
return new GenericLittleEndianAccessor(new ByteArrayByteStream(arr)).readShort();
}
}

View File

@@ -1,98 +0,0 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.mina;
import config.YamlConfig;
import client.MapleClient;
import constants.net.OpcodeConstants;
import net.server.coordinator.session.MapleSessionCoordinator;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import tools.MapleAESOFB;
import tools.HexTool;
import tools.data.input.ByteArrayByteStream;
import tools.data.input.GenericLittleEndianAccessor;
import tools.FilePrinter;
public class MaplePacketEncoder implements ProtocolEncoder {
@Override
public void encode(final IoSession session, final Object message, final ProtocolEncoderOutput out) throws Exception {
final MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
try {
if (client.tryacquireEncoder()) {
try {
final MapleAESOFB send_crypto = client.getSendCrypto();
final byte[] input = (byte[]) message;
if (YamlConfig.config.server.USE_DEBUG_SHOW_PACKET) {
int packetLen = input.length;
int pHeader = readFirstShort(input);
String pHeaderStr = Integer.toHexString(pHeader).toUpperCase();
String op = lookupRecv(pHeader);
String Recv = "ServerSend:" + op + " [" + pHeaderStr + "] (" + packetLen + ")\r\n";
if (packetLen <= 50000) {
String RecvTo = Recv + HexTool.toString(input) + "\r\n" + HexTool.toStringFromAscii(input);
System.out.println(RecvTo);
if (op == null) {
System.out.println("UnknownPacket:" + RecvTo);
}
} else {
FilePrinter.print(FilePrinter.PACKET_STREAM + MapleSessionCoordinator.getSessionRemoteAddress(session) + ".txt", HexTool.toString(new byte[]{input[0], input[1]}) + " ...");
}
}
final byte[] unencrypted = new byte[input.length];
System.arraycopy(input, 0, unencrypted, 0, input.length);
final byte[] ret = new byte[unencrypted.length + 4];
final byte[] header = send_crypto.getPacketHeader(unencrypted.length);
MapleCustomEncryption.encryptData(unencrypted);
send_crypto.crypt(unencrypted);
System.arraycopy(header, 0, ret, 0, 4);
System.arraycopy(unencrypted, 0, ret, 4, unencrypted.length);
out.write(IoBuffer.wrap(ret));
} finally {
client.unlockEncoder();
}
}
// System.arraycopy(unencrypted, 0, ret, 4, unencrypted.length);
// out.write(ByteBuffer.wrap(ret));
} catch (NullPointerException npe) {
out.write(IoBuffer.wrap(((byte[]) message)));
}
}
private String lookupRecv(int val) {
return OpcodeConstants.sendOpcodeNames.get(val);
}
private int readFirstShort(byte[] arr) {
return new GenericLittleEndianAccessor(new ByteArrayByteStream(arr)).readShort();
}
@Override
public void dispose(IoSession session) throws Exception {}
}

View File

@@ -0,0 +1,12 @@
package net.netty;
public abstract class AbstractServer {
final int port;
AbstractServer(int port) {
this.port = port;
}
public abstract void start();
public abstract void stop();
}

View File

@@ -0,0 +1,40 @@
package net.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class ChannelServer extends AbstractServer {
private final int world;
private final int channel;
private Channel nettyChannel;
public ChannelServer(int port, int world, int channel) {
super(port);
this.world = world;
this.channel = channel;
}
@Override
public void start() {
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelServerInitializer(world, channel));
this.nettyChannel = bootstrap.bind(port).syncUninterruptibly().channel();
}
@Override
public void stop() {
if (nettyChannel == null) {
throw new IllegalStateException("Must start ChannelServer before stopping it");
}
nettyChannel.close().syncUninterruptibly();
}
}

View File

@@ -0,0 +1,40 @@
package net.netty;
import client.MapleClient;
import io.netty.channel.socket.SocketChannel;
import net.PacketProcessor;
import net.server.Server;
import net.server.coordinator.session.SessionCoordinator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ChannelServerInitializer extends ServerChannelInitializer {
private static final Logger log = LoggerFactory.getLogger(ChannelServerInitializer.class);
private final int world;
private final int channel;
public ChannelServerInitializer(int world, int channel) {
this.world = world;
this.channel = channel;
}
@Override
public void initChannel(SocketChannel socketChannel) {
final String clientIp = socketChannel.remoteAddress().getHostString();
log.debug("Client connecting to world {}, channel {} from {}", world, channel, clientIp);
PacketProcessor packetProcessor = PacketProcessor.getChannelServerProcessor(world, channel);
final long clientSessionId = sessionId.getAndIncrement();
final String remoteAddress = getRemoteAddress(socketChannel);
final MapleClient client = MapleClient.createChannelClient(clientSessionId, remoteAddress, packetProcessor, world, channel);
if (Server.getInstance().getChannel(world, channel) == null) {
SessionCoordinator.getInstance().closeSession(client, true);
socketChannel.close();
return;
}
initPipeline(socketChannel, client);
}
}

View File

@@ -0,0 +1,14 @@
package net.netty;
public class InvalidPacketHeaderException extends RuntimeException {
private final int header;
public InvalidPacketHeaderException(String message, int header) {
super(message);
this.header = header;
}
public int getHeader() {
return header;
}
}

View File

@@ -0,0 +1,38 @@
package net.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class LoginServer extends AbstractServer {
public static final int WORLD_ID = -1;
public static final int CHANNEL_ID = -1;
private Channel channel;
public LoginServer(int port) {
super(port);
}
@Override
public void start() {
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new LoginServerInitializer());
this.channel = bootstrap.bind(port).syncUninterruptibly().channel();
}
@Override
public void stop() {
if (channel == null) {
throw new IllegalStateException("Must start LoginServer before stopping it");
}
channel.close().syncUninterruptibly();
}
}

View File

@@ -0,0 +1,30 @@
package net.netty;
import client.MapleClient;
import io.netty.channel.socket.SocketChannel;
import net.PacketProcessor;
import net.server.coordinator.session.SessionCoordinator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoginServerInitializer extends ServerChannelInitializer {
private static final Logger log = LoggerFactory.getLogger(LoginServerInitializer.class);
@Override
public void initChannel(SocketChannel socketChannel) {
final String clientIp = socketChannel.remoteAddress().getHostString();
log.debug("Client connected to login server from {} ", clientIp);
PacketProcessor packetProcessor = PacketProcessor.getLoginServerProcessor();
final long clientSessionId = sessionId.getAndIncrement();
final String remoteAddress = getRemoteAddress(socketChannel);
final MapleClient client = MapleClient.createLoginClient(clientSessionId, remoteAddress, packetProcessor, LoginServer.WORLD_ID, LoginServer.CHANNEL_ID);
if (!SessionCoordinator.getInstance().canStartLoginSession(client)) {
socketChannel.close();
return;
}
initPipeline(socketChannel, client);
}
}

View File

@@ -0,0 +1,71 @@
package net.netty;
import client.MapleClient;
import config.YamlConfig;
import constants.net.ServerConstants;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import net.encryption.ClientCyphers;
import net.encryption.InitializationVector;
import net.encryption.PacketCodec;
import net.packet.logging.InPacketLogger;
import net.packet.logging.OutPacketLogger;
import net.server.coordinator.session.IpAddresses;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.MaplePacketCreator;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicLong;
public abstract class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
private static final Logger log = LoggerFactory.getLogger(ServerChannelInitializer.class);
private static final int IDLE_TIME_SECONDS = 30;
private static final boolean LOG_PACKETS = YamlConfig.config.server.USE_DEBUG_SHOW_PACKET;
private static final ChannelHandler sendPacketLogger = new OutPacketLogger();
private static final ChannelHandler receivePacketLogger = new InPacketLogger();
static final AtomicLong sessionId = new AtomicLong(7777);
String getRemoteAddress(Channel channel) {
String remoteAddress = "null";
try {
String hostAddress = ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress();
if (hostAddress != null) {
remoteAddress = IpAddresses.evaluateRemoteAddress(hostAddress); // thanks dyz for noticing Local/LAN/WAN connections not interacting properly
}
} catch (NullPointerException npe) {
log.warn("Unable to get remote address from netty Channel: {}", channel, npe);
}
return remoteAddress;
}
void initPipeline(SocketChannel socketChannel, MapleClient client) {
final InitializationVector sendIv = InitializationVector.generateSend();
final InitializationVector recvIv = InitializationVector.generateReceive();
writeInitialUnencryptedHelloPacket(socketChannel, sendIv, recvIv);
setUpHandlers(socketChannel.pipeline(), sendIv, recvIv, client);
}
private void writeInitialUnencryptedHelloPacket(SocketChannel socketChannel, InitializationVector sendIv, InitializationVector recvIv) {
socketChannel.writeAndFlush(Unpooled.wrappedBuffer(MaplePacketCreator.getHello(ServerConstants.VERSION, sendIv, recvIv)));
}
private void setUpHandlers(ChannelPipeline pipeline, InitializationVector sendIv, InitializationVector recvIv,
MapleClient client) {
pipeline.addLast("IdleStateHandler", new IdleStateHandler(0, 0, IDLE_TIME_SECONDS));
pipeline.addLast("PacketCodec", new PacketCodec(ClientCyphers.of(sendIv, recvIv)));
pipeline.addLast("MapleClient", client);
if (LOG_PACKETS) {
pipeline.addBefore("MapleClient", "SendPacketLogger", sendPacketLogger);
pipeline.addBefore("MapleClient", "ReceivePacketLogger", receivePacketLogger);
}
}
}

View File

@@ -12,6 +12,11 @@ import java.awt.*;
public class ByteBufOutPacket implements OutPacket {
private final ByteBuf byteBuf;
@Deprecated(forRemoval = true)
public ByteBufOutPacket() {
this.byteBuf = Unpooled.buffer();
}
public ByteBufOutPacket(SendOpcode op) {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeShortLE((short) op.getValue());

View File

@@ -0,0 +1,47 @@
package net.packet.logging;
import constants.net.OpcodeConstants;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import net.packet.InPacket;
import net.packet.Packet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.HexTool;
@Sharable
public class InPacketLogger extends ChannelInboundHandlerAdapter implements PacketLogger {
private static final Logger log = LoggerFactory.getLogger(InPacketLogger.class);
private static final int LOG_CONTENT_THRESHOLD = 3_000;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof InPacket packet) {
log(packet);
}
ctx.fireChannelRead(msg);
}
@Override
public void log(Packet packet) {
final byte[] content = packet.getBytes();
final int packetLength = content.length;
if (packetLength <= LOG_CONTENT_THRESHOLD) {
final short opcode = LoggingUtil.readFirstShort(content);
final String opcodeHex = Integer.toHexString(opcode).toUpperCase();
final String opcodeName = getRecvOpcodeName(opcode);
final String prefix = opcodeName == null ? "<UnknownPacket> " : "";
log.debug("{}ClientSend:{} [{}] ({}) <HEX> {} <TEXT> {}", prefix, opcodeName, opcodeHex, packetLength,
HexTool.toString(content), HexTool.toStringFromAscii(content));
} else {
log.debug(HexTool.toString(new byte[]{content[0], content[1]}) + "...");
}
}
private String getRecvOpcodeName(short opcode) {
return OpcodeConstants.recvOpcodeNames.get((int) opcode);
}
}

View File

@@ -0,0 +1,17 @@
package net.packet.logging;
import io.netty.buffer.Unpooled;
import java.util.Set;
public class LoggingUtil {
private static final Set<Short> ignoredDebugRecvPackets = Set.of((short) 167, (short) 197, (short) 89, (short) 91, (short) 41, (short) 188, (short) 107);
public static short readFirstShort(byte[] bytes) {
return Unpooled.wrappedBuffer(bytes).readShortLE();
}
public static boolean isIgnoredRecvPacket(short opcode) {
return ignoredDebugRecvPackets.contains(opcode);
}
}

View File

@@ -0,0 +1,72 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 ~ 2010 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version 3
as published by the Free Software Foundation. You may not use, modify
or distribute this program under any other version of the
GNU Affero General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.packet.logging;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import net.opcodes.RecvOpcode;
import client.MapleCharacter;
import client.MapleClient;
import tools.FilePrinter;
import tools.HexTool;
/**
* Logs packets to console and file.
*
* @author Alan (SharpAceX)
*/
public class MapleLogger {
public static final Set<Integer> monitored = new HashSet<>();
public static final Set<Integer> ignored = new HashSet<>();
public static void logRecv(MapleClient c, short packetId, byte[] packetContent) {
MapleCharacter chr = c.getPlayer();
if (chr == null) {
return;
}
if (!monitored.contains(chr.getId())) {
return;
}
RecvOpcode op = getOpcodeFromValue(packetId);
if (isRecvBlocked(op)) {
return;
}
String packet = op + "\r\n" + HexTool.toString(packetContent);
FilePrinter.printError(FilePrinter.PACKET_LOGS + c.getAccountName() + "-" + chr.getName() + ".txt", packet);
}
private static boolean isRecvBlocked(RecvOpcode op) {
return switch (op) {
case MOVE_PLAYER, GENERAL_CHAT, TAKE_DAMAGE, MOVE_PET, MOVE_LIFE, NPC_ACTION, FACE_EXPRESSION -> true;
default -> false;
};
}
private static RecvOpcode getOpcodeFromValue(int value) {
return Arrays.stream(RecvOpcode.values())
.filter(opcode -> value == opcode.getValue())
.findAny()
.orElse(null);
}
}

View File

@@ -0,0 +1,48 @@
package net.packet.logging;
import constants.net.OpcodeConstants;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import net.packet.OutPacket;
import net.packet.Packet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.HexTool;
@Sharable
public class OutPacketLogger extends ChannelOutboundHandlerAdapter implements PacketLogger {
private static final Logger log = LoggerFactory.getLogger(OutPacketLogger.class);
private static final int LOG_CONTENT_THRESHOLD = 50_000;
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
if (msg instanceof OutPacket packet) {
log(packet);
}
ctx.write(msg);
}
@Override
public void log(Packet packet) {
final byte[] content = packet.getBytes();
final int packetLength = content.length;
if (packetLength <= LOG_CONTENT_THRESHOLD) {
final short opcode = LoggingUtil.readFirstShort(content);
String opcodeHex = Integer.toHexString(opcode).toUpperCase();
String opcodeName = getSendOpcodeName(opcode);
String prefix = opcodeName == null ? "<UnknownPacket> " : "";
log.debug("{}ServerSend:{} [{}] ({}) <HEX> {} <TEXT> {}", prefix, opcodeName, opcodeHex, packetLength,
HexTool.toString(content), HexTool.toStringFromAscii(content));
} else {
log.debug(HexTool.toString(new byte[]{content[0], content[1]}) + " ...");
}
}
private String getSendOpcodeName(short opcode) {
return OpcodeConstants.sendOpcodeNames.get((int) opcode);
}
}

View File

@@ -0,0 +1,7 @@
package net.packet.logging;
import net.packet.Packet;
public interface PacketLogger {
void log(Packet packet);
}

View File

@@ -35,8 +35,7 @@ import constants.game.GameConstants;
import constants.inventory.ItemConstants;
import constants.net.OpcodeConstants;
import constants.net.ServerConstants;
import net.MapleServerHandler;
import net.mina.MapleCodecFactory;
import net.netty.LoginServer;
import net.server.audit.ThreadTracker;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReadLock;
@@ -46,19 +45,13 @@ import net.server.audit.locks.factory.MonitoredReadLockFactory;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import net.server.audit.locks.factory.MonitoredWriteLockFactory;
import net.server.channel.Channel;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.IpAddresses;
import net.server.coordinator.session.SessionCoordinator;
import net.server.guild.MapleAlliance;
import net.server.guild.MapleGuild;
import net.server.guild.MapleGuildCharacter;
import net.server.task.*;
import net.server.world.World;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.buffer.SimpleBufferAllocator;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.CashShop.CashItemFactory;
@@ -73,8 +66,6 @@ import tools.DatabaseConnection;
import tools.FilePrinter;
import tools.Pair;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.Security;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -105,7 +96,7 @@ public class Server {
private static final Map<Integer, Integer> couponRates = new HashMap<>(30);
private static final List<Integer> activeCoupons = new LinkedList<>();
private IoAcceptor acceptor;
private LoginServer loginServer;
private List<Map<Integer, String>> channels = new LinkedList<>();
private List<World> worlds = new ArrayList<>();
private final Properties subnetInfo = new Properties();
@@ -291,13 +282,13 @@ public class Server {
}
}
public String[] getInetSocket(IoSession session, int world, int channel) {
String remoteIp = MapleSessionCoordinator.getSessionRemoteAddress(session);
public String[] getInetSocket(MapleClient client, int world, int channel) {
String remoteIp = client.getRemoteAddress();
String[] hostAddress = getIP(world, channel).split(":");
if (MapleSessionCoordinator.isLocalAddress(remoteIp)) {
if (IpAddresses.isLocalAddress(remoteIp)) {
hostAddress[0] = YamlConfig.config.server.LOCALHOST;
} else if (MapleSessionCoordinator.isLanAddress(remoteIp)) {
} else if (IpAddresses.isLanAddress(remoteIp)) {
hostAddress[0] = YamlConfig.config.server.LANHOST;
}
@@ -906,17 +897,8 @@ public class Server {
}
}
IoBuffer.setUseDirectBuffer(false); // join IO operations performed by lxconan
IoBuffer.setAllocator(new SimpleBufferAllocator());
acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MapleCodecFactory()));
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30);
acceptor.setHandler(new MapleServerHandler());
try {
acceptor.bind(new InetSocketAddress(8484));
} catch (IOException ex) {
ex.printStackTrace();
}
// acceptor = initAcceptor(8484);
loginServer = initLoginServer(8484);
log.info("Listening on port 8484");
@@ -932,6 +914,12 @@ public class Server {
}
}
private LoginServer initLoginServer(int port) {
LoginServer loginServer = new LoginServer(port);
loginServer.start();
return loginServer;
}
private static void setAllLoggedOut(Connection con) throws SQLException {
try (PreparedStatement ps = con.prepareStatement("UPDATE accounts SET loggedin = 0")) {
ps.executeUpdate();
@@ -1776,7 +1764,7 @@ public class Server {
}
private static String getRemoteHost(MapleClient client) {
return MapleSessionCoordinator.getSessionRemoteHost(client.getSession());
return SessionCoordinator.getSessionRemoteHost(client);
}
public void setCharacteridInTransition(MapleClient client, int charId) {
@@ -1878,7 +1866,7 @@ public class Server {
if (c.isLoggedIn()) {
c.disconnect(false, false);
} else {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
}
}
}
@@ -1943,8 +1931,7 @@ public class Server {
TimerManager.getInstance().stop();
System.out.println("Worlds + Channels are offline.");
acceptor.unbind();
acceptor = null;
loginServer.stop();
if (!restart) { // shutdown hook deadlocks if System.exit() method is used within its body chores, thanks MIKE for pointing that out
new Thread(() -> System.exit(0)).start();
} else {

View File

@@ -24,8 +24,7 @@ package net.server.channel;
import client.MapleCharacter;
import config.YamlConfig;
import constants.game.GameConstants;
import net.MapleServerHandler;
import net.mina.MapleCodecFactory;
import net.netty.ChannelServer;
import net.server.PlayerStorage;
import net.server.Server;
import net.server.audit.LockCollector;
@@ -39,13 +38,6 @@ import net.server.services.type.ChannelServices;
import net.server.world.MapleParty;
import net.server.world.MaplePartyCharacter;
import net.server.world.World;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.buffer.SimpleBufferAllocator;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.SocketSessionConfig;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scripting.event.EventScriptManager;
@@ -58,19 +50,22 @@ import tools.MaplePacketCreator;
import tools.Pair;
import java.io.File;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledFuture;
public final class Channel {
private static final Logger log = LoggerFactory.getLogger(Channel.class);
private static final int BASE_PORT = 7575;
private final int port;
private final String ip;
private final int world;
private final int channel;
private int port = 7575;
private PlayerStorage players = new PlayerStorage();
private int world, channel;
private IoAcceptor acceptor;
private String ip, serverMessage;
private ChannelServer channelServer;
private String serverMessage;
private MapleMapManager mapManager;
private EventScriptManager eventSM;
private ServicesManager services;
@@ -118,18 +113,11 @@ public final class Channel {
this.ongoingStartTime = startTime + 10000; // rude approach to a world's last channel boot time, placeholder for the 1st wedding reservation ever
this.mapManager = new MapleMapManager(null, world, channel);
this.port = BASE_PORT + (this.channel - 1) + (world * 100);
this.ip = YamlConfig.config.server.HOST + ":" + port;
try {
port = 7575 + this.channel - 1;
port += (world * 100);
ip = YamlConfig.config.server.HOST + ":" + port;
IoBuffer.setUseDirectBuffer(false);
IoBuffer.setAllocator(new SimpleBufferAllocator());
acceptor = new NioSocketAcceptor();
acceptor.setHandler(new MapleServerHandler(world, channel));
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30);
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MapleCodecFactory()));
acceptor.bind(new InetSocketAddress(port));
((SocketSessionConfig) acceptor.getSessionConfig()).setTcpNoDelay(true);
this.channelServer = initServer(port, world, channel);
expedType.addAll(Arrays.asList(MapleExpeditionType.values()));
if (Server.getInstance().isOnline()) { // postpone event loading to improve boot time... thanks Riizade, daronhudson for noticing slow startup times
@@ -153,9 +141,15 @@ public final class Channel {
log.info("Channel {}: Listening on port {}", getId(), port);
} catch (Exception e) {
e.printStackTrace();
log.warn("Error during channel initialization", e);
}
}
private ChannelServer initServer(int port, int world, int channel) {
ChannelServer channelServer = new ChannelServer(port, world, channel);
channelServer.start();
return channelServer;
}
public synchronized void reloadEventScriptManager(){
if (finishedShutdown) {
@@ -187,10 +181,8 @@ public final class Channel {
closeChannelSchedules();
players = null;
MapleServerHandler handler = (MapleServerHandler) acceptor.getHandler();
handler.dispose();
acceptor.unbind();
channelServer.stop();
finishedShutdown = true;
System.out.println("Successfully shut down Channel " + channel + " on World " + world + "\r\n");

View File

@@ -86,7 +86,7 @@ public final class AdminCommandHandler extends AbstractMaplePacketHandler {
target = c.getChannelServer().getPlayerStorage().getCharacterByName(victim);
if (target != null) {
String readableTargetName = MapleCharacter.makeMapleReadable(target.getName());
String ip = target.getClient().getSession().getRemoteAddress().toString().split(":")[0];
String ip = target.getClient().getRemoteAddress();
reason += readableTargetName + " (IP: " + ip + ")";
if (duration == -1) {
target.ban(description + " " + reason);

View File

@@ -31,14 +31,14 @@ import net.server.PlayerBuffValueHolder;
import net.server.Server;
import net.server.channel.Channel;
import net.server.channel.CharacterIdChannelPair;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.world.MapleEventRecallCoordinator;
import net.server.guild.MapleAlliance;
import net.server.guild.MapleGuild;
import net.server.world.MaplePartyCharacter;
import net.server.world.PartyOperation;
import net.server.world.World;
import org.apache.mina.core.session.IoSession;
import scripting.event.EventInstanceManager;
import server.life.MobSkill;
import tools.DatabaseConnection;
@@ -58,7 +58,7 @@ import java.util.stream.Collectors;
public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
private static Set<Integer> attemptingLoginAccounts = new HashSet<>();
private static final Set<Integer> attemptingLoginAccounts = new HashSet<>();
private boolean tryAcquireAccount(int accId) {
synchronized (attemptingLoginAccounts) {
@@ -84,70 +84,70 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
final int cid = slea.readInt();
final int cid = slea.readInt(); // TODO: investigate if this is the "client id" supplied in MaplePacketCreator#getServerIP()
final Server server = Server.getInstance();
if (c.tryacquireClient()) { // thanks MedicOP for assisting on concurrency protection here
try {
World wserv = server.getWorld(c.getWorld());
if(wserv == null) {
if (!c.tryacquireClient()) {
// thanks MedicOP for assisting on concurrency protection here
c.announce(MaplePacketCreator.getAfterLoginError(10));
}
try {
World wserv = server.getWorld(c.getWorld());
if (wserv == null) {
c.disconnect(true, false);
return;
}
Channel cserv = wserv.getChannel(c.getChannel());
if (cserv == null) {
c.setChannel(1);
cserv = wserv.getChannel(c.getChannel());
if (cserv == null) {
c.disconnect(true, false);
return;
}
}
Channel cserv = wserv.getChannel(c.getChannel());
if(cserv == null) {
c.setChannel(1);
cserv = wserv.getChannel(c.getChannel());
MapleCharacter player = wserv.getPlayerStorage().getCharacterById(cid);
if(cserv == null) {
c.disconnect(true, false);
return;
}
}
MapleCharacter player = wserv.getPlayerStorage().getCharacterById(cid);
IoSession session = c.getSession();
String remoteHwid;
if (player == null) {
remoteHwid = MapleSessionCoordinator.getInstance().pickLoginSessionHwid(session);
if (remoteHwid == null) {
c.disconnect(true, false);
return;
}
} else {
remoteHwid = player.getClient().getHWID();
}
int hwidLen = remoteHwid.length();
session.setAttribute(MapleClient.CLIENT_HWID, remoteHwid);
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, remoteHwid.substring(hwidLen - 8, hwidLen));
c.setHWID(remoteHwid);
if (!server.validateCharacteridInTransition(c, cid)) {
final Hwid hwid;
if (player == null) {
hwid = SessionCoordinator.getInstance().pickLoginSessionHwid(c);
if (hwid == null) {
c.disconnect(true, false);
return;
}
boolean newcomer = false;
if (player == null) {
try {
player = MapleCharacter.loadCharFromDB(cid, c, true);
newcomer = true;
} catch (SQLException e) {
e.printStackTrace();
}
if (player == null) { //If you are still getting null here then please just uninstall the game >.>, we dont need you fucking with the logs
c.disconnect(true, false);
return;
}
} else {
hwid = player.getClient().getHwid();
}
c.setHwid(hwid);
if (!server.validateCharacteridInTransition(c, cid)) {
c.disconnect(true, false);
return;
}
boolean newcomer = false;
if (player == null) {
try {
player = MapleCharacter.loadCharFromDB(cid, c, true);
newcomer = true;
} catch (SQLException e) {
e.printStackTrace();
}
c.setPlayer(player);
c.setAccID(player.getAccountID());
boolean allowLogin = true;
if (player == null) { //If you are still getting null here then please just uninstall the game >.>, we dont need you fucking with the logs
c.disconnect(true, false);
return;
}
}
c.setPlayer(player);
c.setAccID(player.getAccountID());
boolean allowLogin = true;
/* is this check really necessary?
if (state == MapleClient.LOGIN_SERVER_TRANSITION || state == MapleClient.LOGIN_NOTLOGGEDIN) {
@@ -164,276 +164,277 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
}
}
*/
int accId = c.getAccID();
if (tryAcquireAccount(accId)) { // Sync this to prevent wrong login state for double loggedin handling
try {
int state = c.getLoginState();
if (state != MapleClient.LOGIN_SERVER_TRANSITION || !allowLogin) {
c.setPlayer(null);
c.setAccID(0);
if (state == MapleClient.LOGIN_LOGGEDIN) {
c.disconnect(true, false);
} else {
c.announce(MaplePacketCreator.getAfterLoginError(7));
}
int accId = c.getAccID();
if (tryAcquireAccount(accId)) { // Sync this to prevent wrong login state for double loggedin handling
try {
int state = c.getLoginState();
if (state != MapleClient.LOGIN_SERVER_TRANSITION || !allowLogin) {
c.setPlayer(null);
c.setAccID(0);
return;
}
c.updateLoginState(MapleClient.LOGIN_LOGGEDIN);
} finally {
releaseAccount(accId);
}
} else {
c.setPlayer(null);
c.setAccID(0);
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
}
if (!newcomer) {
c.setLanguage(player.getClient().getLanguage());
c.setCharacterSlots((byte) player.getClient().getCharacterSlots());
player.newClient(c);
}
cserv.addPlayer(player);
wserv.addPlayer(player);
player.setEnteredChannelWorld();
List<PlayerBuffValueHolder> buffs = server.getPlayerBuffStorage().getBuffsFromStorage(cid);
if (buffs != null) {
List<Pair<Long, PlayerBuffValueHolder>> timedBuffs = getLocalStartTimes(buffs);
player.silentGiveBuffs(timedBuffs);
}
Map<MapleDisease, Pair<Long, MobSkill>> diseases = server.getPlayerBuffStorage().getDiseasesFromStorage(cid);
if (diseases != null) {
player.silentApplyDiseases(diseases);
}
c.announce(MaplePacketCreator.getCharInfo(player));
if (!player.isHidden()) {
if(player.isGM() && YamlConfig.config.server.USE_AUTOHIDE_GM) {
player.toggleHide(true);
}
}
player.sendKeymap();
player.sendQuickmap();
player.sendMacros();
// pot bindings being passed through other characters on the account detected thanks to Croosade dev team
MapleKeyBinding autohpPot = player.getKeymap().get(91);
player.announce(MaplePacketCreator.sendAutoHpPot(autohpPot != null ? autohpPot.getAction() : 0));
MapleKeyBinding autompPot = player.getKeymap().get(92);
player.announce(MaplePacketCreator.sendAutoMpPot(autompPot != null ? autompPot.getAction() : 0));
player.getMap().addPlayer(player);
player.visitMap(player.getMap());
BuddyList bl = player.getBuddylist();
int[] buddyIds = bl.getBuddyIds();
wserv.loggedOn(player.getName(), player.getId(), c.getChannel(), buddyIds);
for (CharacterIdChannelPair onlineBuddy : wserv.multiBuddyFind(player.getId(), buddyIds)) {
BuddylistEntry ble = bl.get(onlineBuddy.getCharacterId());
ble.setChannel(onlineBuddy.getChannel());
bl.put(ble);
}
c.announce(MaplePacketCreator.updateBuddylist(bl.getBuddies()));
c.announce(MaplePacketCreator.loadFamily(player));
if (player.getFamilyId() > 0) {
MapleFamily f = wserv.getFamily(player.getFamilyId());
if(f != null) {
MapleFamilyEntry familyEntry = f.getEntryByID(player.getId());
if(familyEntry != null) {
familyEntry.setCharacter(player);
player.setFamilyEntry(familyEntry);
c.announce(MaplePacketCreator.getFamilyInfo(familyEntry));
familyEntry.announceToSenior(MaplePacketCreator.sendFamilyLoginNotice(player.getName(), true), true);
if (state == MapleClient.LOGIN_LOGGEDIN) {
c.disconnect(true, false);
} else {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Player " + player.getName() + "'s family doesn't have an entry for them. (" + f.getID() + ")");
c.announce(MaplePacketCreator.getAfterLoginError(7));
}
return;
}
c.updateLoginState(MapleClient.LOGIN_LOGGEDIN);
} finally {
releaseAccount(accId);
}
} else {
c.setPlayer(null);
c.setAccID(0);
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
}
if (!newcomer) {
c.setLanguage(player.getClient().getLanguage());
c.setCharacterSlots((byte) player.getClient().getCharacterSlots());
player.newClient(c);
}
cserv.addPlayer(player);
wserv.addPlayer(player);
player.setEnteredChannelWorld();
List<PlayerBuffValueHolder> buffs = server.getPlayerBuffStorage().getBuffsFromStorage(cid);
if (buffs != null) {
List<Pair<Long, PlayerBuffValueHolder>> timedBuffs = getLocalStartTimes(buffs);
player.silentGiveBuffs(timedBuffs);
}
Map<MapleDisease, Pair<Long, MobSkill>> diseases = server.getPlayerBuffStorage().getDiseasesFromStorage(cid);
if (diseases != null) {
player.silentApplyDiseases(diseases);
}
c.announce(MaplePacketCreator.getCharInfo(player));
if (!player.isHidden()) {
if (player.isGM() && YamlConfig.config.server.USE_AUTOHIDE_GM) {
player.toggleHide(true);
}
}
player.sendKeymap();
player.sendQuickmap();
player.sendMacros();
// pot bindings being passed through other characters on the account detected thanks to Croosade dev team
MapleKeyBinding autohpPot = player.getKeymap().get(91);
player.announce(MaplePacketCreator.sendAutoHpPot(autohpPot != null ? autohpPot.getAction() : 0));
MapleKeyBinding autompPot = player.getKeymap().get(92);
player.announce(MaplePacketCreator.sendAutoMpPot(autompPot != null ? autompPot.getAction() : 0));
player.getMap().addPlayer(player);
player.visitMap(player.getMap());
BuddyList bl = player.getBuddylist();
int[] buddyIds = bl.getBuddyIds();
wserv.loggedOn(player.getName(), player.getId(), c.getChannel(), buddyIds);
for (CharacterIdChannelPair onlineBuddy : wserv.multiBuddyFind(player.getId(), buddyIds)) {
BuddylistEntry ble = bl.get(onlineBuddy.getCharacterId());
ble.setChannel(onlineBuddy.getChannel());
bl.put(ble);
}
c.announce(MaplePacketCreator.updateBuddylist(bl.getBuddies()));
c.announce(MaplePacketCreator.loadFamily(player));
if (player.getFamilyId() > 0) {
MapleFamily f = wserv.getFamily(player.getFamilyId());
if (f != null) {
MapleFamilyEntry familyEntry = f.getEntryByID(player.getId());
if (familyEntry != null) {
familyEntry.setCharacter(player);
player.setFamilyEntry(familyEntry);
c.announce(MaplePacketCreator.getFamilyInfo(familyEntry));
familyEntry.announceToSenior(MaplePacketCreator.sendFamilyLoginNotice(player.getName(), true), true);
} else {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Player " + player.getName() + " has an invalid family ID. (" + player.getFamilyId() + ")");
c.announce(MaplePacketCreator.getFamilyInfo(null));
FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Player " + player.getName() + "'s family doesn't have an entry for them. (" + f.getID() + ")");
}
} else {
FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Player " + player.getName() + " has an invalid family ID. (" + player.getFamilyId() + ")");
c.announce(MaplePacketCreator.getFamilyInfo(null));
}
if (player.getGuildId() > 0) {
MapleGuild playerGuild = server.getGuild(player.getGuildId(), player.getWorld(), player);
if (playerGuild == null) {
player.deleteGuild(player.getGuildId());
player.getMGC().setGuildId(0);
player.getMGC().setGuildRank(5);
} else {
playerGuild.getMGC(player.getId()).setCharacter(player);
player.setMGC(playerGuild.getMGC(player.getId()));
server.setGuildMemberOnline(player, true, c.getChannel());
c.announce(MaplePacketCreator.showGuildInfo(player));
int allianceId = player.getGuild().getAllianceId();
if (allianceId > 0) {
MapleAlliance newAlliance = server.getAlliance(allianceId);
if (newAlliance == null) {
newAlliance = MapleAlliance.loadAlliance(allianceId);
if (newAlliance != null) {
server.addAlliance(allianceId, newAlliance);
} else {
player.getGuild().setAllianceId(0);
}
}
} else {
c.announce(MaplePacketCreator.getFamilyInfo(null));
}
if (player.getGuildId() > 0) {
MapleGuild playerGuild = server.getGuild(player.getGuildId(), player.getWorld(), player);
if (playerGuild == null) {
player.deleteGuild(player.getGuildId());
player.getMGC().setGuildId(0);
player.getMGC().setGuildRank(5);
} else {
playerGuild.getMGC(player.getId()).setCharacter(player);
player.setMGC(playerGuild.getMGC(player.getId()));
server.setGuildMemberOnline(player, true, c.getChannel());
c.announce(MaplePacketCreator.showGuildInfo(player));
int allianceId = player.getGuild().getAllianceId();
if (allianceId > 0) {
MapleAlliance newAlliance = server.getAlliance(allianceId);
if (newAlliance == null) {
newAlliance = MapleAlliance.loadAlliance(allianceId);
if (newAlliance != null) {
c.announce(MaplePacketCreator.updateAllianceInfo(newAlliance, c.getWorld()));
c.announce(MaplePacketCreator.allianceNotice(newAlliance.getId(), newAlliance.getNotice()));
server.addAlliance(allianceId, newAlliance);
} else {
player.getGuild().setAllianceId(0);
}
}
if (newAlliance != null) {
c.announce(MaplePacketCreator.updateAllianceInfo(newAlliance, c.getWorld()));
c.announce(MaplePacketCreator.allianceNotice(newAlliance.getId(), newAlliance.getNotice()));
if (newcomer) {
server.allianceMessage(allianceId, MaplePacketCreator.allianceMemberOnline(player, true), player.getId(), -1);
}
if (newcomer) {
server.allianceMessage(allianceId, MaplePacketCreator.allianceMemberOnline(player, true), player.getId(), -1);
}
}
}
}
}
player.showNote();
if (player.getParty() != null) {
MaplePartyCharacter pchar = player.getMPC();
player.showNote();
if (player.getParty() != null) {
MaplePartyCharacter pchar = player.getMPC();
//Use this in case of enabling party HPbar HUD when logging in, however "you created a party" will appear on chat.
//c.announce(MaplePacketCreator.partyCreated(pchar));
//Use this in case of enabling party HPbar HUD when logging in, however "you created a party" will appear on chat.
//c.announce(MaplePacketCreator.partyCreated(pchar));
pchar.setChannel(c.getChannel());
pchar.setMapId(player.getMapId());
pchar.setOnline(true);
wserv.updateParty(player.getParty().getId(), PartyOperation.LOG_ONOFF, pchar);
player.updatePartyMemberHP();
pchar.setChannel(c.getChannel());
pchar.setMapId(player.getMapId());
pchar.setOnline(true);
wserv.updateParty(player.getParty().getId(), PartyOperation.LOG_ONOFF, pchar);
player.updatePartyMemberHP();
}
MapleInventory eqpInv = player.getInventory(MapleInventoryType.EQUIPPED);
eqpInv.lockInventory();
try {
for (Item it : eqpInv.list()) {
player.equippedItem((Equip) it);
}
} finally {
eqpInv.unlockInventory();
}
c.announce(MaplePacketCreator.updateBuddylist(player.getBuddylist().getBuddies()));
CharacterNameAndId pendingBuddyRequest = c.getPlayer().getBuddylist().pollPendingRequest();
if (pendingBuddyRequest != null) {
c.announce(MaplePacketCreator.requestBuddylistAdd(pendingBuddyRequest.getId(), c.getPlayer().getId(), pendingBuddyRequest.getName()));
}
c.announce(MaplePacketCreator.updateGender(player));
player.checkMessenger();
c.announce(MaplePacketCreator.enableReport());
player.changeSkillLevel(SkillFactory.getSkill(10000000 * player.getJobType() + 12), (byte) (player.getLinkedLevel() / 10), 20, -1);
player.checkBerserk(player.isHidden());
if (newcomer) {
for (MaplePet pet : player.getPets()) {
if (pet != null) {
wserv.registerPetHunger(player, player.getPetIndex(pet));
}
}
MapleInventory eqpInv = player.getInventory(MapleInventoryType.EQUIPPED);
eqpInv.lockInventory();
try {
for(Item it : eqpInv.list()) {
player.equippedItem((Equip) it);
}
} finally {
eqpInv.unlockInventory();
MapleMount mount = player.getMount(); // thanks Ari for noticing a scenario where Silver Mane quest couldn't be started
if (mount.getItemId() != 0) {
player.announce(MaplePacketCreator.updateMount(player.getId(), mount, false));
}
c.announce(MaplePacketCreator.updateBuddylist(player.getBuddylist().getBuddies()));
CharacterNameAndId pendingBuddyRequest = c.getPlayer().getBuddylist().pollPendingRequest();
if (pendingBuddyRequest != null) {
c.announce(MaplePacketCreator.requestBuddylistAdd(pendingBuddyRequest.getId(), c.getPlayer().getId(), pendingBuddyRequest.getName()));
}
c.announce(MaplePacketCreator.updateGender(player));
player.checkMessenger();
c.announce(MaplePacketCreator.enableReport());
player.changeSkillLevel(SkillFactory.getSkill(10000000 * player.getJobType() + 12), (byte) (player.getLinkedLevel() / 10), 20, -1);
player.checkBerserk(player.isHidden());
if (newcomer) {
for(MaplePet pet : player.getPets()) {
if(pet != null) {
wserv.registerPetHunger(player, player.getPetIndex(pet));
}
}
MapleMount mount = player.getMount(); // thanks Ari for noticing a scenario where Silver Mane quest couldn't be started
if (mount.getItemId() != 0) {
player.announce(MaplePacketCreator.updateMount(player.getId(), mount, false));
}
player.reloadQuestExpirations();
player.reloadQuestExpirations();
/*
if (!c.hasVotedAlready()){
player.announce(MaplePacketCreator.earnTitleMessage("You can vote now! Vote and earn a vote point!"));
}
*/
if (player.isGM()){
Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.earnTitleMessage((player.gmLevel() < 6 ? "GM " : "Admin ") + player.getName() + " has logged in"));
}
if(diseases != null) {
for(Entry<MapleDisease, Pair<Long, MobSkill>> e : diseases.entrySet()) {
final List<Pair<MapleDisease, Integer>> debuff = Collections.singletonList(new Pair<>(e.getKey(), e.getValue().getRight().getX()));
c.announce(MaplePacketCreator.giveDebuff(debuff, e.getValue().getRight()));
}
}
} else {
if(player.isRidingBattleship()) {
player.announceBattleshipHp();
}
}
player.buffExpireTask();
player.diseaseExpireTask();
player.skillCooldownTask();
player.expirationTask();
player.questExpirationTask();
if (GameConstants.hasSPTable(player.getJob()) && player.getJob().getId() != 2001) {
player.createDragon();
if (player.isGM()) {
Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.earnTitleMessage((player.gmLevel() < 6 ? "GM " : "Admin ") + player.getName() + " has logged in"));
}
player.commitExcludedItems();
showDueyNotification(c, player);
if (player.getMap().getHPDec() > 0) player.resetHpDecreaseTask();
player.resetPlayerRates();
if(YamlConfig.config.server.USE_ADD_RATES_BY_LEVEL == true) player.setPlayerRates();
player.setWorldRates();
player.updateCouponRates();
player.receivePartyMemberHP();
if(player.getPartnerId() > 0) {
int partnerId = player.getPartnerId();
final MapleCharacter partner = wserv.getPlayerStorage().getCharacterById(partnerId);
if(partner != null && !partner.isAwayFromWorld()) {
player.announce(Wedding.OnNotifyWeddingPartnerTransfer(partnerId, partner.getMapId()));
partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(player.getId(), player.getMapId()));
if (diseases != null) {
for (Entry<MapleDisease, Pair<Long, MobSkill>> e : diseases.entrySet()) {
final List<Pair<MapleDisease, Integer>> debuff = Collections.singletonList(new Pair<>(e.getKey(), e.getValue().getRight().getX()));
c.announce(MaplePacketCreator.giveDebuff(debuff, e.getValue().getRight()));
}
}
if (newcomer) {
EventInstanceManager eim = MapleEventRecallCoordinator.getInstance().recallEventInstance(cid);
if (eim != null) {
eim.registerPlayer(player);
}
} else {
if (player.isRidingBattleship()) {
player.announceBattleshipHp();
}
// Tell the client to use the custom scripts available for the NPCs provided, instead of the WZ entries.
if (YamlConfig.config.server.USE_NPCS_SCRIPTABLE) {
// Create a copy to prevent always adding entries to the server's list.
Map<Integer, String> npcsIds = YamlConfig.config.server.NPCS_SCRIPTABLE
.entrySet().stream().collect(Collectors.toMap(
entry -> Integer.parseInt(entry.getKey()),
Entry::getValue
));
// Any npc be specified as the rebirth npc. Allow the npc to use custom scripts explicitly.
if (YamlConfig.config.server.USE_REBIRTH_SYSTEM) {
npcsIds.put(YamlConfig.config.server.REBIRTH_NPC_ID, "Rebirth");
}
c.announce(MaplePacketCreator.setNPCScriptable(npcsIds));
}
if(newcomer) player.setLoginTime(System.currentTimeMillis());
} catch(Exception e) {
e.printStackTrace();
} finally {
c.releaseClient();
}
} else {
c.announce(MaplePacketCreator.getAfterLoginError(10));
player.buffExpireTask();
player.diseaseExpireTask();
player.skillCooldownTask();
player.expirationTask();
player.questExpirationTask();
if (GameConstants.hasSPTable(player.getJob()) && player.getJob().getId() != 2001) {
player.createDragon();
}
player.commitExcludedItems();
showDueyNotification(c, player);
if (player.getMap().getHPDec() > 0) player.resetHpDecreaseTask();
player.resetPlayerRates();
if (YamlConfig.config.server.USE_ADD_RATES_BY_LEVEL) {
player.setPlayerRates();
}
player.setWorldRates();
player.updateCouponRates();
player.receivePartyMemberHP();
if (player.getPartnerId() > 0) {
int partnerId = player.getPartnerId();
final MapleCharacter partner = wserv.getPlayerStorage().getCharacterById(partnerId);
if (partner != null && !partner.isAwayFromWorld()) {
player.announce(Wedding.OnNotifyWeddingPartnerTransfer(partnerId, partner.getMapId()));
partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(player.getId(), player.getMapId()));
}
}
if (newcomer) {
EventInstanceManager eim = MapleEventRecallCoordinator.getInstance().recallEventInstance(cid);
if (eim != null) {
eim.registerPlayer(player);
}
}
// Tell the client to use the custom scripts available for the NPCs provided, instead of the WZ entries.
if (YamlConfig.config.server.USE_NPCS_SCRIPTABLE) {
// Create a copy to prevent always adding entries to the server's list.
Map<Integer, String> npcsIds = YamlConfig.config.server.NPCS_SCRIPTABLE
.entrySet().stream().collect(Collectors.toMap(
entry -> Integer.parseInt(entry.getKey()),
Entry::getValue
));
// Any npc be specified as the rebirth npc. Allow the npc to use custom scripts explicitly.
if (YamlConfig.config.server.USE_REBIRTH_SYSTEM) {
npcsIds.put(YamlConfig.config.server.REBIRTH_NPC_ID, "Rebirth");
}
c.announce(MaplePacketCreator.setNPCScriptable(npcsIds));
}
if (newcomer) player.setLoginTime(System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
} finally {
c.releaseClient();
}
}

View File

@@ -22,70 +22,60 @@ package net.server.coordinator.login;
import config.YamlConfig;
import net.server.Server;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
*
* @author Ronan
*/
public class LoginStorage {
private ConcurrentHashMap<Integer, List<Long>> loginHistory = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, List<Instant>> loginHistory = new ConcurrentHashMap<>(); // Key: accountId
public boolean registerLogin(int accountId) {
List<Long> accHist = loginHistory.get(accountId);
if (accHist == null) {
accHist = new LinkedList<>();
loginHistory.put(accountId, accHist);
}
synchronized (accHist) {
if (accHist.size() > YamlConfig.config.server.MAX_ACCOUNT_LOGIN_ATTEMPT) {
long blockExpiration = Server.getInstance().getCurrentTime() + YamlConfig.config.server.LOGIN_ATTEMPT_DURATION;
Collections.fill(accHist, blockExpiration);
List<Instant> attempts = loginHistory.computeIfAbsent(accountId, k -> new ArrayList<>());
synchronized (attempts) {
final Instant attemptExpiry = Instant.ofEpochMilli(Server.getInstance().getCurrentTime() + YamlConfig.config.server.LOGIN_ATTEMPT_DURATION);
if (attempts.size() > YamlConfig.config.server.MAX_ACCOUNT_LOGIN_ATTEMPT) {
Collections.fill(attempts, attemptExpiry);
return false;
}
accHist.add(Server.getInstance().getCurrentTime() + YamlConfig.config.server.LOGIN_ATTEMPT_DURATION);
attempts.add(attemptExpiry);
return true;
}
}
public void updateLoginHistory() {
long timeNow = Server.getInstance().getCurrentTime();
List<Integer> toRemove = new LinkedList<>();
List<Long> toRemoveAttempt = new LinkedList<>();
for (Entry<Integer, List<Long>> loginEntries : loginHistory.entrySet()) {
toRemoveAttempt.clear();
List<Long> accAttempts = loginEntries.getValue();
synchronized (accAttempts) {
for (Long loginAttempt : accAttempts) {
if (loginAttempt < timeNow) {
toRemoveAttempt.add(loginAttempt);
}
public void clearExpiredAttempts() {
final Instant now = Instant.ofEpochMilli(Server.getInstance().getCurrentTime());
List<Integer> accountIdsToClear = new ArrayList<>();
for (Entry<Integer, List<Instant>> loginEntries : loginHistory.entrySet()) {
final List<Instant> attempts = loginEntries.getValue();
synchronized (attempts) {
List<Instant> attemptsToRemove = attempts.stream()
.filter(attempt -> attempt.isBefore(now))
.collect(Collectors.toList());
for (Instant attemptToRemove : attemptsToRemove) {
attempts.remove(attemptToRemove);
}
if (!toRemoveAttempt.isEmpty()) {
for (Long trAttempt : toRemoveAttempt) {
accAttempts.remove(trAttempt);
}
if (accAttempts.isEmpty()) {
toRemove.add(loginEntries.getKey());
}
if (attempts.isEmpty()) {
accountIdsToClear.add(loginEntries.getKey());
}
}
}
for (Integer tr : toRemove) {
loginHistory.remove(tr);
for (Integer accountId : accountIdsToClear) {
loginHistory.remove(accountId);
}
}
}

View File

@@ -19,7 +19,14 @@
*/
package net.server.coordinator.login;
import client.MapleCharacter;
import client.MapleClient;
import config.YamlConfig;
import net.server.Server;
import net.server.coordinator.session.Hwid;
import net.server.world.World;
import tools.Pair;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -27,62 +34,56 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import client.MapleCharacter;
import client.MapleClient;
import net.server.world.World;
import net.server.Server;
import tools.Pair;
/**
*
* @author Ronan
*/
public class MapleLoginBypassCoordinator {
private final static MapleLoginBypassCoordinator instance = new MapleLoginBypassCoordinator();
public static MapleLoginBypassCoordinator getInstance() {
return instance;
}
private final ConcurrentHashMap<Pair<String, Integer>, Pair<Boolean, Long>> loginBypass = new ConcurrentHashMap<>(); // optimized PIN & PIC check
public boolean canLoginBypass(String nibbleHwid, int accId, boolean pic) {
private final ConcurrentHashMap<Pair<Hwid, Integer>, Pair<Boolean, Long>> loginBypass = new ConcurrentHashMap<>(); // optimized PIN & PIC check
public boolean canLoginBypass(Hwid hwid, int accId, boolean pic) {
try {
Pair<String, Integer> entry = new Pair<>(nibbleHwid, accId);
Pair<Hwid, Integer> entry = new Pair<>(hwid, accId);
Boolean p = loginBypass.get(entry).getLeft();
return !pic || p;
} catch (NullPointerException npe) {
return false;
}
}
public void registerLoginBypassEntry(String nibbleHwid, int accId, boolean pic) {
public void registerLoginBypassEntry(Hwid hwid, int accId, boolean pic) {
long expireTime = (pic ? YamlConfig.config.server.BYPASS_PIC_EXPIRATION : YamlConfig.config.server.BYPASS_PIN_EXPIRATION);
if (expireTime > 0) {
Pair<String, Integer> entry = new Pair<>(nibbleHwid, accId);
Pair<Hwid, Integer> entry = new Pair<>(hwid, accId);
expireTime = Server.getInstance().getCurrentTime() + expireTime * 60 * 1000;
try {
pic |= loginBypass.get(entry).getLeft();
expireTime = Math.max(loginBypass.get(entry).getRight(), expireTime);
} catch (NullPointerException npe) {}
} catch (NullPointerException npe) {
}
loginBypass.put(entry, new Pair<>(pic, expireTime));
}
}
public void unregisterLoginBypassEntry(String nibbleHwid, int accId) {
Pair<String, Integer> entry = new Pair<>(nibbleHwid, accId);
public void unregisterLoginBypassEntry(Hwid hwid, int accId) {
String hwidValue = hwid == null ? null : hwid.hwid();
Pair<String, Integer> entry = new Pair<>(hwidValue, accId);
loginBypass.remove(entry);
}
public void runUpdateLoginBypass() {
if (!loginBypass.isEmpty()) {
List<Pair<String, Integer>> toRemove = new LinkedList<>();
List<Pair<Hwid, Integer>> toRemove = new LinkedList<>();
Set<Integer> onlineAccounts = new HashSet<>();
long timeNow = Server.getInstance().getCurrentTime();
for (World w : Server.getInstance().getWorlds()) {
for (MapleCharacter chr : w.getPlayerStorage().getAllCharacters()) {
MapleClient c = chr.getClient();
@@ -91,8 +92,8 @@ public class MapleLoginBypassCoordinator {
}
}
}
for (Entry<Pair<String, Integer>, Pair<Boolean, Long>> e : loginBypass.entrySet()) {
for (Entry<Pair<Hwid, Integer>, Pair<Boolean, Long>> e : loginBypass.entrySet()) {
if (onlineAccounts.contains(e.getKey().getRight())) {
long expireTime = timeNow + 2 * 60 * 1000;
if (expireTime > e.getValue().getRight()) {
@@ -102,13 +103,13 @@ public class MapleLoginBypassCoordinator {
toRemove.add(e.getKey());
}
}
if (!toRemove.isEmpty()) {
for (Pair<String, Integer> p : toRemove) {
for (Pair<Hwid, Integer> p : toRemove) {
loginBypass.remove(p);
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
package net.server.coordinator.session;
import net.server.Server;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
record HostHwid(Hwid hwid, Instant expiry) {
static HostHwid createWithDefaultExpiry(Hwid hwid) {
return new HostHwid(hwid, getDefaultExpiry());
}
private static Instant getDefaultExpiry() {
return Instant.ofEpochMilli(Server.getInstance().getCurrentTime() + TimeUnit.DAYS.toMillis(7));
}
}

View File

@@ -0,0 +1,48 @@
package net.server.coordinator.session;
import net.server.Server;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
class HostHwidCache {
private final ConcurrentHashMap<String, HostHwid> hostHwidCache = new ConcurrentHashMap<>(); // Key: remoteHost
void clearExpired() {
SessionDAO.deleteExpiredHwidAccounts();
Instant now = Instant.ofEpochMilli(Server.getInstance().getCurrentTime());
List<String> remoteHostsToRemove = new ArrayList<>();
for (Map.Entry<String, HostHwid> entry : hostHwidCache.entrySet()) {
if (now.isAfter(entry.getValue().expiry())) {
remoteHostsToRemove.add(entry.getKey());
}
}
for (String remoteHost : remoteHostsToRemove) {
hostHwidCache.remove(remoteHost);
}
}
void addEntry(String remoteHost, Hwid hwid) {
hostHwidCache.put(remoteHost, HostHwid.createWithDefaultExpiry(hwid));
}
HostHwid getEntry(String remoteHost) {
return hostHwidCache.get(remoteHost);
}
Hwid removeEntryAndGetItsHwid(String remoteHost) {
HostHwid hostHwid = hostHwidCache.remove(remoteHost);
return hostHwid == null ? null : hostHwid.hwid();
}
Hwid getEntryHwid(String remoteHost) {
HostHwid hostHwid = hostHwidCache.get(remoteHost);
return hostHwid == null ? null : hostHwid.hwid();
}
}

View File

@@ -0,0 +1,26 @@
package net.server.coordinator.session;
import java.util.regex.Pattern;
public record Hwid (String hwid) {
private static final int HWID_LENGTH = 8;
// First part is a mac address (without dashes), second part is the hwid
private static final Pattern VALID_HOST_STRING_PATTERN = Pattern.compile("[0-9A-F]{12}_[0-9A-F]{8}");
private static boolean isValidHostString(String hostString) {
return VALID_HOST_STRING_PATTERN.matcher(hostString).matches();
}
public static Hwid fromHostString(String hostString) throws IllegalArgumentException {
if (hostString == null || !isValidHostString(hostString)) {
throw new IllegalArgumentException("hostString has invalid format");
}
final String[] split = hostString.split("_");
if (split.length != 2 || split[1].length() != HWID_LENGTH) {
throw new IllegalArgumentException("Hwid validation failed for hwid: " + hostString);
}
return new Hwid(split[1]);
}
}

View File

@@ -0,0 +1,41 @@
package net.server.coordinator.session;
import net.server.Server;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
public class HwidAssociationExpiry {
public static Instant getHwidAccountExpiry(int relevance) {
return Instant.ofEpochMilli(Server.getInstance().getCurrentTime()).plusMillis(hwidExpirationUpdate(relevance));
}
private static long hwidExpirationUpdate(int relevance) {
int degree = getHwidExpirationDegree(relevance);
final long baseHours = switch (degree) {
case 0 -> 2;
case 1 -> TimeUnit.DAYS.toHours(1);
case 2 -> TimeUnit.DAYS.toHours(7);
default -> TimeUnit.DAYS.toHours(70);
};
int subdegreeTime = (degree * 3) + 1;
if (subdegreeTime > 10) {
subdegreeTime = 10;
}
return TimeUnit.HOURS.toMillis(baseHours + subdegreeTime);
}
private static int getHwidExpirationDegree(int relevance) {
int degree = 1;
int subdegree;
while ((subdegree = 5 * degree) <= relevance) {
relevance -= subdegree;
degree++;
}
return --degree;
}
}

View File

@@ -0,0 +1,7 @@
package net.server.coordinator.session;
public record HwidRelevance(String hwid, int relevance) {
public int getIncrementedRelevance() {
return relevance < Byte.MAX_VALUE ? relevance + 1 : relevance;
}
}

View File

@@ -0,0 +1,20 @@
package net.server.coordinator.session;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
enum InitializationResult {
SUCCESS(AntiMulticlientResult.SUCCESS),
ALREADY_INITIALIZED(AntiMulticlientResult.REMOTE_PROCESSING),
TIMED_OUT(AntiMulticlientResult.COORDINATOR_ERROR),
ERROR(AntiMulticlientResult.COORDINATOR_ERROR);
private final AntiMulticlientResult antiMulticlientResult;
InitializationResult(AntiMulticlientResult antiMulticlientResult) {
this.antiMulticlientResult = antiMulticlientResult;
}
public AntiMulticlientResult getAntiMulticlientResult() {
return antiMulticlientResult;
}
}

View File

@@ -0,0 +1,39 @@
package net.server.coordinator.session;
import config.YamlConfig;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class IpAddresses {
private static final List<Pattern> LOCAL_ADDRESS_PATTERNS = loadLocalAddressPatterns();
private static List<Pattern> loadLocalAddressPatterns() {
return Stream.of("10\\.", "192\\.168\\.", "172\\.(1[6-9]|2[0-9]|3[0-1])\\.")
.map(Pattern::compile)
.collect(Collectors.toList());
}
public static String evaluateRemoteAddress(String inetAddress) {
if (isLocalAddress(inetAddress) || isLanAddress(inetAddress)) {
return YamlConfig.config.server.HOST;
} else {
return inetAddress;
}
}
public static boolean isLocalAddress(String inetAddress) {
return inetAddress.startsWith("127.");
}
public static boolean isLanAddress(String inetAddress) {
return LOCAL_ADDRESS_PATTERNS.stream()
.anyMatch(pattern -> matchesPattern(pattern, inetAddress));
}
private static boolean matchesPattern(Pattern pattern, String searchTerm) {
return pattern.matcher(searchTerm).find();
}
}

View File

@@ -1,690 +0,0 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.coordinator.session;
import client.MapleCharacter;
import client.MapleClient;
import config.YamlConfig;
import net.server.Server;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import net.server.coordinator.login.LoginStorage;
import org.apache.mina.core.session.IoSession;
import tools.DatabaseConnection;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* @author Ronan
*/
public class MapleSessionCoordinator {
private final static MapleSessionCoordinator instance = new MapleSessionCoordinator();
public static MapleSessionCoordinator getInstance() {
return instance;
}
public enum AntiMulticlientResult {
SUCCESS,
REMOTE_LOGGEDIN,
REMOTE_REACHED_LIMIT,
REMOTE_PROCESSING,
REMOTE_NO_MATCH,
MANY_ACCOUNT_ATTEMPTS,
COORDINATOR_ERROR
}
private final LoginStorage loginStorage = new LoginStorage();
private final Map<Integer, MapleClient> onlineClients = new HashMap<>();
private final Set<String> onlineRemoteHwids = new HashSet<>();
private final Map<String, Set<IoSession>> loginRemoteHosts = new HashMap<>();
private final Set<String> pooledRemoteHosts = new HashSet<>();
private final ConcurrentHashMap<String, String> cachedHostHwids = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Long> cachedHostTimeout = new ConcurrentHashMap<>();
private final List<ReentrantLock> poolLock = new ArrayList<>(100);
private MapleSessionCoordinator() {
for(int i = 0; i < 100; i++) {
poolLock.add(MonitoredReentrantLockFactory.createLock(MonitoredLockType.SERVER_LOGIN_COORD));
}
}
private static long hwidExpirationUpdate(int relevance) {
int degree = 1, i = relevance, subdegree;
while ((subdegree = 5 * degree) <= i) {
i -= subdegree;
degree++;
}
degree--;
int baseTime, subdegreeTime;
if (degree > 2) {
subdegreeTime = 10;
} else {
subdegreeTime = 1 + (3 * degree);
}
switch(degree) {
case 0:
baseTime = 2; // 2 hours
break;
case 1:
baseTime = 24; // 1 day
break;
case 2:
baseTime = 168; // 7 days
break;
default:
baseTime = 1680; // 70 days
}
return 3600000 * (baseTime + subdegreeTime);
}
private static void updateAccessAccount(Connection con, String remoteHwid, int accountId, int loginRelevance) throws SQLException {
java.sql.Timestamp nextTimestamp = new java.sql.Timestamp(Server.getInstance().getCurrentTime() + hwidExpirationUpdate(loginRelevance));
if(loginRelevance < Byte.MAX_VALUE) {
loginRelevance++;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE hwidaccounts SET relevance = ?, expiresat = ? WHERE accountid = ? AND hwid LIKE ?")) {
ps.setInt(1, loginRelevance);
ps.setTimestamp(2, nextTimestamp);
ps.setInt(3, accountId);
ps.setString(4, remoteHwid);
ps.executeUpdate();
}
}
private static void registerAccessAccount(Connection con, String remoteHwid, int accountId) throws SQLException {
try (PreparedStatement ps = con.prepareStatement("INSERT INTO hwidaccounts (accountid, hwid, expiresat) VALUES (?, ?, ?)")) {
ps.setInt(1, accountId);
ps.setString(2, remoteHwid);
ps.setTimestamp(3, new java.sql.Timestamp(Server.getInstance().getCurrentTime() + hwidExpirationUpdate(0)));
ps.executeUpdate();
}
}
private static boolean associateHwidAccountIfAbsent(String remoteHwid, int accountId) {
try (Connection con = DatabaseConnection.getConnection()) {
int hwidCount = 0;
try (PreparedStatement ps = con.prepareStatement("SELECT SQL_CACHE hwid FROM hwidaccounts WHERE accountid = ?")) {
ps.setInt(1, accountId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String rsHwid = rs.getString("hwid");
if (rsHwid.contentEquals(remoteHwid)) {
return false;
}
hwidCount++;
}
}
if (hwidCount < YamlConfig.config.server.MAX_ALLOWED_ACCOUNT_HWID) {
registerAccessAccount(con, remoteHwid, accountId);
return true;
}
}
} catch (SQLException ex) {
ex.printStackTrace();
}
return false;
}
private static boolean attemptAccessAccount(String nibbleHwid, int accountId, boolean routineCheck) {
try (Connection con = DatabaseConnection.getConnection()) {
int hwidCount = 0;
try (PreparedStatement ps = con.prepareStatement("SELECT SQL_CACHE * FROM hwidaccounts WHERE accountid = ?")) {
ps.setInt(1, accountId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String rsHwid = rs.getString("hwid");
if (rsHwid.endsWith(nibbleHwid)) {
if (!routineCheck) {
// better update HWID relevance as soon as the login is authenticated
int loginRelevance = rs.getInt("relevance");
updateAccessAccount(con, rsHwid, accountId, loginRelevance);
}
return true;
}
hwidCount++;
}
}
if (hwidCount < YamlConfig.config.server.MAX_ALLOWED_ACCOUNT_HWID) {
return true;
}
}
} catch (SQLException ex) {
ex.printStackTrace();
}
return false;
}
private Lock getCoodinatorLock(String remoteHost) {
return poolLock.get(Math.abs(remoteHost.hashCode()) % 100);
}
private static List<Pattern> localComms = loadLocalCommPatterns();
private static List<Pattern> loadLocalCommPatterns() {
String[] localComms = {"10\\.", "192\\.168\\.", "172\\.(1[6-9]|2[0-9]|3[0-1])\\."};
List<Pattern> llc = new ArrayList<>(localComms.length);
for (String lc : localComms) {
llc.add(Pattern.compile(lc));
}
return llc;
}
private static boolean matchesLanAddress(Pattern inetPattern, String inetAddress) {
Matcher searchM = inetPattern.matcher(inetAddress);
return searchM.find();
}
public static boolean isLanAddress(String inetAddress) {
for (Pattern lanPattern : localComms) {
if (matchesLanAddress(lanPattern, inetAddress)) {
return true;
}
}
return false;
}
public static boolean isLocalAddress(String inetAddress) {
return inetAddress.startsWith("127.");
}
public static String fetchRemoteAddress(String inetAddress) {
if (isLocalAddress(inetAddress) || isLanAddress(inetAddress)) {
return YamlConfig.config.server.HOST;
} else {
return inetAddress;
}
}
public static String getSessionRemoteAddress(IoSession session) {
return (String) session.getAttribute(MapleClient.CLIENT_REMOTE_ADDRESS);
}
public static String getSessionRemoteHost(IoSession session) {
String nibbleHwid = (String) session.getAttribute(MapleClient.CLIENT_NIBBLEHWID);
if (nibbleHwid != null) {
return getSessionRemoteAddress(session) + "-" + nibbleHwid;
} else {
return getSessionRemoteAddress(session);
}
}
private static MapleClient getSessionClient(IoSession session) {
return (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
}
public void updateOnlineSession(IoSession session) {
MapleClient client = getSessionClient(session);
if (client != null) {
int accountId = client.getAccID();
MapleClient ingameClient = onlineClients.get(accountId);
if (ingameClient != null) { // thanks MedicOP for finding out a loss of loggedin account uniqueness when using the CMS "Unstuck" feature
ingameClient.forceDisconnect();
}
onlineClients.put(accountId, client);
}
}
public boolean canStartLoginSession(IoSession session) {
if (!YamlConfig.config.server.DETERRED_MULTICLIENT) return true;
String remoteHost = getSessionRemoteHost(session);
Lock lock = getCoodinatorLock(remoteHost);
try {
int tries = 0;
while (true) {
if (lock.tryLock()) {
try {
if (pooledRemoteHosts.contains(remoteHost)) {
return false;
}
pooledRemoteHosts.add(remoteHost);
} finally {
lock.unlock();
}
break;
} else {
if(tries == 2) {
return true;
}
tries++;
Thread.sleep(1777);
}
}
} catch (Exception e) {
e.printStackTrace();
return true;
}
try {
String knownHwid = cachedHostHwids.get(remoteHost);
if (knownHwid != null) {
if (onlineRemoteHwids.contains(knownHwid)) {
return false;
}
}
if (loginRemoteHosts.containsKey(remoteHost)) {
return false;
}
Set<IoSession> lrh = new HashSet<>(2);
lrh.add(session);
loginRemoteHosts.put(remoteHost, lrh);
return true;
} finally {
lock.lock();
try {
pooledRemoteHosts.remove(remoteHost);
} finally {
lock.unlock();
}
}
}
public void closeLoginSession(IoSession session) {
String nibbleHwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID);
String remoteHost = getSessionRemoteHost(session);
Set<IoSession> lrh = loginRemoteHosts.get(remoteHost);
if (lrh != null) {
lrh.remove(session);
if (lrh.isEmpty()) {
loginRemoteHosts.remove(remoteHost);
}
}
if (nibbleHwid != null) {
onlineRemoteHwids.remove(nibbleHwid);
MapleClient client = getSessionClient(session);
if (client != null) {
MapleClient loggedClient = onlineClients.get(client.getAccID());
// do not remove an online game session here, only login session
if (loggedClient != null && loggedClient.getSessionId() == client.getSessionId()) {
onlineClients.remove(client.getAccID());
}
}
}
}
public AntiMulticlientResult attemptLoginSession(IoSession session, String nibbleHwid, int accountId, boolean routineCheck) {
if (!YamlConfig.config.server.DETERRED_MULTICLIENT) {
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid);
return AntiMulticlientResult.SUCCESS;
}
String remoteHost = getSessionRemoteHost(session);
Lock lock = getCoodinatorLock(remoteHost);
try {
int tries = 0;
while (true) {
if (lock.tryLock()) {
try {
if (pooledRemoteHosts.contains(remoteHost)) {
return AntiMulticlientResult.REMOTE_PROCESSING;
}
pooledRemoteHosts.add(remoteHost);
} finally {
lock.unlock();
}
break;
} else {
if(tries == 2) {
return AntiMulticlientResult.COORDINATOR_ERROR;
}
tries++;
Thread.sleep(1777);
}
}
} catch (Exception e) {
e.printStackTrace();
return AntiMulticlientResult.COORDINATOR_ERROR;
}
try {
if (!loginStorage.registerLogin(accountId)) {
return AntiMulticlientResult.MANY_ACCOUNT_ATTEMPTS;
}
if (!routineCheck) {
if (onlineRemoteHwids.contains(nibbleHwid)) {
return AntiMulticlientResult.REMOTE_LOGGEDIN;
}
if (!attemptAccessAccount(nibbleHwid, accountId, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
}
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid);
onlineRemoteHwids.add(nibbleHwid);
} else {
if (!attemptAccessAccount(nibbleHwid, accountId, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
}
}
return AntiMulticlientResult.SUCCESS;
} finally {
lock.lock();
try {
pooledRemoteHosts.remove(remoteHost);
} finally {
lock.unlock();
}
}
}
public AntiMulticlientResult attemptGameSession(IoSession session, int accountId, String remoteHwid) {
String remoteHost = getSessionRemoteHost(session);
if (!YamlConfig.config.server.DETERRED_MULTICLIENT) {
associateRemoteHostHwid(remoteHost, remoteHwid);
associateRemoteHostHwid(getSessionRemoteAddress(session), remoteHwid); // no HWID information on the loggedin newcomer session...
return AntiMulticlientResult.SUCCESS;
}
Lock lock = getCoodinatorLock(remoteHost);
try {
int tries = 0;
while (true) {
if (lock.tryLock()) {
try {
if (pooledRemoteHosts.contains(remoteHost)) {
return AntiMulticlientResult.REMOTE_PROCESSING;
}
pooledRemoteHosts.add(remoteHost);
} finally {
lock.unlock();
}
break;
} else {
if(tries == 2) {
return AntiMulticlientResult.COORDINATOR_ERROR;
}
tries++;
Thread.sleep(1777);
}
}
} catch (Exception e) {
e.printStackTrace();
return AntiMulticlientResult.COORDINATOR_ERROR;
}
try {
String nibbleHwid = (String) session.getAttribute(MapleClient.CLIENT_NIBBLEHWID); // thanks Paxum for noticing account stuck after PIC failure
if (nibbleHwid != null) {
onlineRemoteHwids.remove(nibbleHwid);
if (remoteHwid.endsWith(nibbleHwid)) {
if (!onlineRemoteHwids.contains(remoteHwid)) {
// assumption: after a SUCCESSFUL login attempt, the incoming client WILL receive a new IoSession from the game server
// updated session CLIENT_HWID attribute will be set when the player log in the game
onlineRemoteHwids.add(remoteHwid);
associateRemoteHostHwid(remoteHost, remoteHwid);
associateRemoteHostHwid(getSessionRemoteAddress(session), remoteHwid);
associateHwidAccountIfAbsent(remoteHwid, accountId);
return AntiMulticlientResult.SUCCESS;
} else {
return AntiMulticlientResult.REMOTE_LOGGEDIN;
}
} else {
return AntiMulticlientResult.REMOTE_NO_MATCH;
}
} else {
return AntiMulticlientResult.REMOTE_NO_MATCH;
}
} finally {
lock.lock();
try {
pooledRemoteHosts.remove(remoteHost);
} finally {
lock.unlock();
}
}
}
private static MapleClient fetchInTransitionSessionClient(IoSession session) {
String remoteHwid = MapleSessionCoordinator.getInstance().getGameSessionHwid(session);
if (remoteHwid != null) { // maybe this session was currently in-transition?
int hwidLen = remoteHwid.length();
if (hwidLen <= 8) {
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, remoteHwid);
} else {
session.setAttribute(MapleClient.CLIENT_HWID, remoteHwid);
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, remoteHwid.substring(hwidLen - 8, hwidLen));
}
MapleClient client = new MapleClient(null, null, session);
Integer cid = Server.getInstance().freeCharacteridInTransition(client);
if (cid != null) {
try {
client.setAccID(MapleCharacter.loadCharFromDB(cid, client, false).getAccountID());
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
session.setAttribute(MapleClient.CLIENT_KEY, client);
return client;
}
return null;
}
public void closeSession(IoSession session, Boolean immediately) {
MapleClient client = getSessionClient(session);
if (client == null) {
client = fetchInTransitionSessionClient(session);
}
String hwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID); // making sure to clean up calls to this function on login phase
onlineRemoteHwids.remove(hwid);
hwid = (String) session.removeAttribute(MapleClient.CLIENT_HWID);
onlineRemoteHwids.remove(hwid);
if (client != null) {
if (hwid != null) { // is a game session
onlineClients.remove(client.getAccID());
} else {
MapleClient loggedClient = onlineClients.get(client.getAccID());
// do not remove an online game session here, only login session
if (loggedClient != null && loggedClient.getSessionId() == client.getSessionId()) {
onlineClients.remove(client.getAccID());
}
}
}
if (immediately != null) {
session.close(immediately);
}
// session.removeAttribute(MapleClient.CLIENT_REMOTE_ADDRESS); No real need for removing String property on closed sessions
}
public String pickLoginSessionHwid(IoSession session) {
String remoteHost = getSessionRemoteAddress(session);
return cachedHostHwids.remove(remoteHost); // thanks BHB, resinate for noticing players from same network not being able to login
}
public String getGameSessionHwid(IoSession session) {
String remoteHost = getSessionRemoteHost(session);
return cachedHostHwids.get(remoteHost);
}
private void associateRemoteHostHwid(String remoteHost, String remoteHwid) {
cachedHostHwids.put(remoteHost, remoteHwid);
cachedHostTimeout.put(remoteHost, Server.getInstance().getCurrentTime() + 604800000); // 1 week-time entry
}
public void runUpdateHwidHistory() {
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("DELETE FROM hwidaccounts WHERE expiresat < CURRENT_TIMESTAMP")) {
ps.executeUpdate();
} catch (SQLException ex) {
ex.printStackTrace();
}
long timeNow = Server.getInstance().getCurrentTime();
List<String> toRemove = new LinkedList<>();
for (Entry<String, Long> cht : cachedHostTimeout.entrySet()) {
if (cht.getValue() < timeNow) {
toRemove.add(cht.getKey());
}
}
if (!toRemove.isEmpty()) {
for (String s : toRemove) {
cachedHostHwids.remove(s);
cachedHostTimeout.remove(s);
}
}
}
public void runUpdateLoginHistory() {
loginStorage.updateLoginHistory();
}
public void printSessionTrace() {
if (!onlineClients.isEmpty()) {
List<Entry<Integer, MapleClient>> elist = new ArrayList<>(onlineClients.entrySet());
elist.sort((e1, e2) -> e1.getKey().compareTo(e2.getKey()));
System.out.println("Current online clients: ");
for (Entry<Integer, MapleClient> e : elist) {
System.out.println(" " + e.getKey());
}
}
if (!onlineRemoteHwids.isEmpty()) {
List<String> slist = new ArrayList<>(onlineRemoteHwids);
Collections.sort(slist);
System.out.println("Current online HWIDs: ");
for (String s : slist) {
System.out.println(" " + s);
}
}
if (!loginRemoteHosts.isEmpty()) {
List<Entry<String, Set<IoSession>>> elist = new ArrayList<>(loginRemoteHosts.entrySet());
elist.sort((e1, e2) -> e1.getKey().compareTo(e2.getKey()));
System.out.println("Current login sessions: ");
for (Entry<String, Set<IoSession>> e : elist) {
System.out.println(" " + e.getKey() + ", size: " + e.getValue().size());
}
}
}
public void printSessionTrace(MapleClient c) {
String str = "Opened server sessions:\r\n\r\n";
if (!onlineClients.isEmpty()) {
List<Entry<Integer, MapleClient>> elist = new ArrayList<>(onlineClients.entrySet());
elist.sort((e1, e2) -> e1.getKey().compareTo(e2.getKey()));
str += ("Current online clients:\r\n");
for (Entry<Integer, MapleClient> e : elist) {
str += (" " + e.getKey() + "\r\n");
}
}
if (!onlineRemoteHwids.isEmpty()) {
List<String> slist = new ArrayList<>(onlineRemoteHwids);
Collections.sort(slist);
str += ("Current online HWIDs:\r\n");
for (String s : slist) {
str += (" " + s + "\r\n");
}
}
if (!loginRemoteHosts.isEmpty()) {
List<Entry<String, Set<IoSession>>> elist = new ArrayList<>(loginRemoteHosts.entrySet());
elist.sort((e1, e2) -> e1.getKey().compareTo(e2.getKey()));
str += ("Current login sessions:\r\n");
for (Entry<String, Set<IoSession>> e : elist) {
str += (" " + e.getKey() + ", IP: " + e.getValue() + "\r\n");
}
}
c.getAbstractPlayerInteraction().npcTalk(2140000, str);
}
}

View File

@@ -0,0 +1,407 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2019 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.coordinator.session;
import client.MapleCharacter;
import client.MapleClient;
import config.YamlConfig;
import net.server.Server;
import net.server.coordinator.login.LoginStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.DatabaseConnection;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.Instant;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
*
* @author Ronan
*/
public class SessionCoordinator {
private static final Logger log = LoggerFactory.getLogger(SessionCoordinator.class);
private static final SessionCoordinator instance = new SessionCoordinator();
public static SessionCoordinator getInstance() {
return instance;
}
public enum AntiMulticlientResult {
SUCCESS,
REMOTE_LOGGEDIN,
REMOTE_REACHED_LIMIT,
REMOTE_PROCESSING,
REMOTE_NO_MATCH,
MANY_ACCOUNT_ATTEMPTS,
COORDINATOR_ERROR
}
private final SessionInitialization sessionInit = new SessionInitialization();
private final LoginStorage loginStorage = new LoginStorage();
private final Map<Integer, MapleClient> onlineClients = new HashMap<>(); // Key: account id
private final Set<Hwid> onlineRemoteHwids = new HashSet<>(); // Hwid/nibblehwid
private final Map<String, MapleClient> loginRemoteHosts = new ConcurrentHashMap<>(); // Key: Ip (+ nibblehwid)
private final HostHwidCache hostHwidCache = new HostHwidCache();
private SessionCoordinator() {
}
private static boolean attemptAccountAccess(int accountId, Hwid hwid, boolean routineCheck) {
try (Connection con = DatabaseConnection.getConnection()) {
List<HwidRelevance> hwidRelevances = SessionDAO.getHwidRelevance(con, accountId);
for (HwidRelevance hwidRelevance : hwidRelevances) {
if (hwidRelevance.hwid().endsWith(hwid.hwid())) {
if (!routineCheck) {
// better update HWID relevance as soon as the login is authenticated
Instant expiry = HwidAssociationExpiry.getHwidAccountExpiry(hwidRelevance.relevance());
SessionDAO.updateAccountAccess(con, hwid, accountId, expiry, hwidRelevance.getIncrementedRelevance());
}
return true;
}
}
if (hwidRelevances.size() < YamlConfig.config.server.MAX_ALLOWED_ACCOUNT_HWID) {
return true;
}
} catch (SQLException e) {
log.warn("Failed to update account access. Account id: {}, nibbleHwid: {}", accountId, hwid, e);
}
return false;
}
public static String getSessionRemoteHost(MapleClient client) {
Hwid hwid = client.getHwid();
if (hwid != null) {
return client.getRemoteAddress() + "-" + hwid.hwid();
} else {
return client.getRemoteAddress();
}
}
/**
* Overwrites any existing online client for the account id, making sure to disconnect it as well.
*/
public void updateOnlineClient(MapleClient client) {
if (client != null) {
int accountId = client.getAccID();
disconnectClientIfOnline(accountId);
onlineClients.put(accountId, client);
}
}
private void disconnectClientIfOnline(int accountId) {
MapleClient ingameClient = onlineClients.get(accountId);
if (ingameClient != null) { // thanks MedicOP for finding out a loss of loggedin account uniqueness when using the CMS "Unstuck" feature
ingameClient.forceDisconnect();
}
}
public boolean canStartLoginSession(MapleClient client) {
if (!YamlConfig.config.server.DETERRED_MULTICLIENT) {
return true;
}
String remoteHost = getSessionRemoteHost(client);
final InitializationResult initResult = sessionInit.initialize(remoteHost);
switch (initResult.getAntiMulticlientResult()) {
case REMOTE_PROCESSING -> {
return false;
}
case COORDINATOR_ERROR -> {
return true;
}
}
try {
final HostHwid knownHwid = hostHwidCache.getEntry(remoteHost);
if (knownHwid != null && onlineRemoteHwids.contains(knownHwid.hwid())) {
return false;
} else if (loginRemoteHosts.containsKey(remoteHost)) {
return false;
}
loginRemoteHosts.put(remoteHost, client);
return true;
} finally {
sessionInit.finalize(remoteHost);
}
}
public void closeLoginSession(MapleClient client) {
clearLoginRemoteHost(client);
Hwid nibbleHwid = client.getHwid();
client.setHwid(null);
if (nibbleHwid != null) {
onlineRemoteHwids.remove(nibbleHwid);
if (client != null) {
MapleClient loggedClient = onlineClients.get(client.getAccID());
// do not remove an online game session here, only login session
if (loggedClient != null && loggedClient.getSessionId() == client.getSessionId()) {
onlineClients.remove(client.getAccID());
}
}
}
}
private void clearLoginRemoteHost(MapleClient client) {
String remoteHost = getSessionRemoteHost(client);
loginRemoteHosts.remove(client.getRemoteAddress());
loginRemoteHosts.remove(remoteHost);
}
public AntiMulticlientResult attemptLoginSession(MapleClient client, Hwid hwid, int accountId, boolean routineCheck) {
if (!YamlConfig.config.server.DETERRED_MULTICLIENT) {
client.setHwid(hwid);
return AntiMulticlientResult.SUCCESS;
}
String remoteHost = getSessionRemoteHost(client);
InitializationResult initResult = sessionInit.initialize(remoteHost);
if (initResult != InitializationResult.SUCCESS) {
return initResult.getAntiMulticlientResult();
}
try {
if (!loginStorage.registerLogin(accountId)) {
return AntiMulticlientResult.MANY_ACCOUNT_ATTEMPTS;
} else if (routineCheck && !attemptAccountAccess(accountId, hwid, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
} else if (onlineRemoteHwids.contains(hwid)) {
return AntiMulticlientResult.REMOTE_LOGGEDIN;
} else if (!attemptAccountAccess(accountId, hwid, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
}
client.setHwid(hwid);
onlineRemoteHwids.add(hwid);
return AntiMulticlientResult.SUCCESS;
} finally {
sessionInit.finalize(remoteHost);
}
}
public AntiMulticlientResult attemptGameSession(MapleClient client, int accountId, Hwid hwid) {
final String remoteHost = getSessionRemoteHost(client);
if (!YamlConfig.config.server.DETERRED_MULTICLIENT) {
hostHwidCache.addEntry(remoteHost, hwid);
hostHwidCache.addEntry(client.getRemoteAddress(), hwid); // no HWID information on the loggedin newcomer session...
return AntiMulticlientResult.SUCCESS;
}
final InitializationResult initResult = sessionInit.initialize(remoteHost);
if (initResult != InitializationResult.SUCCESS) {
return initResult.getAntiMulticlientResult();
}
try {
Hwid clientHwid = client.getHwid(); // thanks Paxum for noticing account stuck after PIC failure
if (clientHwid == null) {
return AntiMulticlientResult.REMOTE_NO_MATCH;
}
onlineRemoteHwids.remove(clientHwid);
if (!hwid.equals(clientHwid)) {
return AntiMulticlientResult.REMOTE_NO_MATCH;
} else if (onlineRemoteHwids.contains(hwid)) {
return AntiMulticlientResult.REMOTE_LOGGEDIN;
}
// assumption: after a SUCCESSFUL login attempt, the incoming client WILL receive a new IoSession from the game server
// updated session CLIENT_HWID attribute will be set when the player log in the game
onlineRemoteHwids.add(hwid);
hostHwidCache.addEntry(remoteHost, hwid);
hostHwidCache.addEntry(client.getRemoteAddress(), hwid);
associateHwidAccountIfAbsent(hwid, accountId);
return AntiMulticlientResult.SUCCESS;
} finally {
sessionInit.finalize(remoteHost);
}
}
private static void associateHwidAccountIfAbsent(Hwid hwid, int accountId) {
try (Connection con = DatabaseConnection.getConnection()) {
List<Hwid> hwids = SessionDAO.getHwidsForAccount(con, accountId);
boolean containsRemoteHwid = hwids.stream().anyMatch(accountHwid -> accountHwid.equals(hwid));
if (containsRemoteHwid) {
return;
}
if (hwids.size() < YamlConfig.config.server.MAX_ALLOWED_ACCOUNT_HWID) {
Instant expiry = HwidAssociationExpiry.getHwidAccountExpiry(0);
SessionDAO.registerAccountAccess(con, accountId, hwid, expiry);
}
} catch (SQLException ex) {
log.warn("Failed to associate hwid {} with account id {}", hwid, accountId, ex);
}
}
private static MapleClient fetchInTransitionSessionClient(MapleClient client) {
Hwid hwid = SessionCoordinator.getInstance().getGameSessionHwid(client);
if (hwid == null) { // maybe this session was currently in-transition?
return null;
}
MapleClient fakeClient = MapleClient.createMock();
fakeClient.setHwid(hwid);
Integer chrId = Server.getInstance().freeCharacteridInTransition(client);
if (chrId != null) {
try {
fakeClient.setAccID(MapleCharacter.loadCharFromDB(chrId, client, false).getAccountID());
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
return fakeClient;
}
public void closeSession(MapleClient client, Boolean immediately) {
if (client == null) {
client = fetchInTransitionSessionClient(client);
}
final Hwid hwid = client.getHwid();
client.setHwid(null); // making sure to clean up calls to this function on login phase
if (hwid != null) {
onlineRemoteHwids.remove(hwid);
}
final boolean isGameSession = hwid != null;
if (isGameSession) {
onlineClients.remove(client.getAccID());
} else {
MapleClient loggedClient = onlineClients.get(client.getAccID());
// do not remove an online game session here, only login session
if (loggedClient != null && loggedClient.getSessionId() == client.getSessionId()) {
onlineClients.remove(client.getAccID());
}
}
if (immediately != null && immediately) {
client.closeSession();
}
}
public Hwid pickLoginSessionHwid(MapleClient client) {
String remoteHost = client.getRemoteAddress();
// thanks BHB, resinate for noticing players from same network not being able to login
return hostHwidCache.removeEntryAndGetItsHwid(remoteHost);
}
public Hwid getGameSessionHwid(MapleClient client) {
String remoteHost = getSessionRemoteHost(client);
return hostHwidCache.getEntryHwid(remoteHost);
}
public void clearExpiredHwidHistory() {
hostHwidCache.clearExpired();
}
public void runUpdateLoginHistory() {
loginStorage.clearExpiredAttempts();
}
public void printSessionTrace() {
if (!onlineClients.isEmpty()) {
List<Entry<Integer, MapleClient>> elist = new ArrayList<>(onlineClients.entrySet());
String commaSeparatedClients = elist.stream()
.map(Entry::getKey)
.sorted(Integer::compareTo)
.map(Object::toString)
.collect(Collectors.joining(", "));
System.out.println("Current online clients: " + commaSeparatedClients);
}
if (!onlineRemoteHwids.isEmpty()) {
List<Hwid> hwids = new ArrayList<>(onlineRemoteHwids);
hwids.sort(Comparator.comparing(Hwid::hwid));
System.out.println("Current online HWIDs: ");
for (Hwid s : hwids) {
System.out.println(" " + s);
}
}
if (!loginRemoteHosts.isEmpty()) {
List<Entry<String, MapleClient>> elist = new ArrayList<>(loginRemoteHosts.entrySet());
elist.sort(Entry.comparingByKey());
System.out.println("Current login sessions: ");
for (Entry<String, MapleClient> e : elist) {
System.out.println(" " + e.getKey() + ", client: " + e.getValue());
}
}
}
public void printSessionTrace(MapleClient c) {
String str = "Opened server sessions:\r\n\r\n";
if (!onlineClients.isEmpty()) {
List<Entry<Integer, MapleClient>> elist = new ArrayList<>(onlineClients.entrySet());
elist.sort(Entry.comparingByKey());
str += ("Current online clients:\r\n");
for (Entry<Integer, MapleClient> e : elist) {
str += (" " + e.getKey() + "\r\n");
}
}
if (!onlineRemoteHwids.isEmpty()) {
List<Hwid> hwids = new ArrayList<>(onlineRemoteHwids);
hwids.sort(Comparator.comparing(Hwid::hwid));
str += ("Current online HWIDs:\r\n");
for (Hwid s : hwids) {
str += (" " + s + "\r\n");
}
}
if (!loginRemoteHosts.isEmpty()) {
List<Entry<String, MapleClient>> elist = new ArrayList<>(loginRemoteHosts.entrySet());
elist.sort((e1, e2) -> e1.getKey().compareTo(e2.getKey()));
str += ("Current login sessions:\r\n");
for (Entry<String, MapleClient> e : elist) {
str += (" " + e.getKey() + ", IP: " + e.getValue().getRemoteAddress() + "\r\n");
}
}
c.getAbstractPlayerInteraction().npcTalk(2140000, str);
}
}

View File

@@ -0,0 +1,89 @@
package net.server.coordinator.session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.DatabaseConnection;
import java.sql.*;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
public class SessionDAO {
private static final Logger log = LoggerFactory.getLogger(SessionDAO.class);
public static void deleteExpiredHwidAccounts() {
final String query = "DELETE FROM hwidaccounts WHERE expiresat < CURRENT_TIMESTAMP";
try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement(query)) {
ps.executeUpdate();
} catch (SQLException e) {
log.warn("Failed to delete expired hwidaccounts", e);
}
}
public static List<Hwid> getHwidsForAccount(Connection con, int accountId) throws SQLException {
final List<Hwid> hwids = new ArrayList<>();
final String query = "SELECT hwid FROM hwidaccounts WHERE accountid = ?";
try (PreparedStatement ps = con.prepareStatement(query)) {
ps.setInt(1, accountId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
hwids.add(new Hwid(rs.getString("hwid")));
}
}
}
return hwids;
}
public static void registerAccountAccess(Connection con, int accountId, Hwid hwid, Instant expiry)
throws SQLException {
if (hwid == null) {
throw new IllegalArgumentException("Hwid must not be null");
}
final String query = "INSERT INTO hwidaccounts (accountid, hwid, expiresat) VALUES (?, ?, ?)";
try (PreparedStatement ps = con.prepareStatement(query)) {
ps.setInt(1, accountId);
ps.setString(2, hwid.hwid());
ps.setTimestamp(3, Timestamp.from(expiry));
ps.executeUpdate();
}
}
public static List<HwidRelevance> getHwidRelevance(Connection con, int accountId) throws SQLException {
final List<HwidRelevance> hwidRelevances = new ArrayList<>();
final String query = "SELECT * FROM hwidaccounts WHERE accountid = ?";
try (PreparedStatement ps = con.prepareStatement(query)) {
ps.setInt(1, accountId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String hwid = rs.getString("hwid");
int relevance = rs.getInt("relevance");
hwidRelevances.add(new HwidRelevance(hwid, relevance));
}
}
}
return hwidRelevances;
}
public static void updateAccountAccess(Connection con, Hwid hwid, int accountId, Instant expiry, int loginRelevance)
throws SQLException {
final String query = "UPDATE hwidaccounts SET relevance = ?, expiresat = ? WHERE accountid = ? AND hwid LIKE ?";
try (PreparedStatement ps = con.prepareStatement(query)) {
ps.setInt(1, loginRelevance);
ps.setTimestamp(2, Timestamp.from(expiry));
ps.setInt(3, accountId);
ps.setString(4, hwid.hwid());
ps.executeUpdate();
}
}
}

View File

@@ -0,0 +1,88 @@
package net.server.coordinator.session;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
/**
* Manages session initialization using remote host (ip address).
*/
public class SessionInitialization {
private final static Logger log = LoggerFactory.getLogger(SessionInitialization.class);
private static final int MAX_INIT_TRIES = 2;
private static final long RETRY_DELAY_MILLIS = 1777;
private final Set<String> remoteHostsInInitState = new HashSet<>();
private final List<Lock> locks = new ArrayList<>(100);
SessionInitialization() {
for (int i = 0; i < 100; i++) {
locks.add(MonitoredReentrantLockFactory.createLock(MonitoredLockType.SERVER_LOGIN_COORD));
}
}
private Lock getLock(String remoteHost) {
return locks.get(Math.abs(remoteHost.hashCode()) % 100);
}
/**
* Try to initialize a session. Should be called <em>before</em> any session initialization procedure.
*
* @return InitializationResult.SUCCESS if initialization was successful.
* If it was successful, finalize() needs to be called shortly after,
* or else the initialization will be left hanging in a bad state,
* which means any subsequent initialization from the same remote host will fail.
*/
public InitializationResult initialize(String remoteHost) {
final Lock lock = getLock(remoteHost);
try {
int tries = 0;
while (true) {
if (lock.tryLock()) {
try {
if (remoteHostsInInitState.contains(remoteHost)) {
return InitializationResult.ALREADY_INITIALIZED;
}
remoteHostsInInitState.add(remoteHost);
} finally {
lock.unlock();
}
break;
} else {
if (tries++ == MAX_INIT_TRIES) {
return InitializationResult.TIMED_OUT;
}
Thread.sleep(RETRY_DELAY_MILLIS);
}
}
} catch (Exception e) {
log.error("Failed to initialize session.", e);
return InitializationResult.ERROR;
}
return InitializationResult.SUCCESS;
}
/**
* Finalize an initialization. Should be called <em>after</em> any session initialization procedure.
*/
public void finalize(String remoteHost) {
final Lock lock = getLock(remoteHost);
lock.lock();
try {
remoteHostsInInitState.remove(remoteHost);
} finally {
lock.unlock();
}
}
}

View File

@@ -23,7 +23,7 @@ package net.server.handlers.login;
import client.MapleClient;
import net.AbstractMaplePacketHandler;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.SessionCoordinator;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
@@ -57,7 +57,7 @@ public final class AfterLoginHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.requestPinAfterFailure());
}
} else if (c2 == 0 && c3 == 5) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null);
SessionCoordinator.getInstance().closeSession(c, null);
c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
}
}

View File

@@ -22,36 +22,31 @@
package net.server.handlers.login;
import client.MapleClient;
import java.net.InetAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult;
import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.apache.mina.core.session.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
import java.net.InetAddress;
import java.net.UnknownHostException;
public final class CharSelectedHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(CharSelectedHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) {
case REMOTE_PROCESSING:
return 10;
case REMOTE_LOGGEDIN:
return 7;
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
return switch (res) {
case REMOTE_PROCESSING -> 10;
case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case COORDINATOR_ERROR -> 8;
default -> 9;
};
}
@Override
@@ -59,31 +54,34 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler {
int charId = slea.readInt();
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
String hostString = slea.readMapleAsciiString();
final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17));
return;
}
c.updateMacs(macs);
c.updateHWID(hwid);
IoSession session = c.getSession();
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
c.updateHwid(hwid);
AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return;
}
if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(session, true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(session, true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
@@ -94,7 +92,7 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler {
return;
}
String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel());
String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel());
if(socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;

View File

@@ -1,37 +1,31 @@
package net.server.handlers.login;
import client.MapleClient;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
import java.net.InetAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.apache.mina.core.session.IoSession;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
import client.MapleClient;
public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(CharSelectedWithPicHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) {
case REMOTE_PROCESSING:
return 10;
case REMOTE_LOGGEDIN:
return 7;
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
return switch (res) {
case REMOTE_PROCESSING -> 10;
case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case COORDINATOR_ERROR -> 8;
default -> 9;
};
}
@Override
@@ -40,26 +34,28 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
int charId = slea.readInt();
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
String hostString = slea.readMapleAsciiString();
final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17));
return;
}
c.updateMacs(macs);
c.updateHWID(hwid);
IoSession session = c.getSession();
c.updateHwid(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
@@ -71,13 +67,13 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
return;
}
String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel());
String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel());
if(socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
}
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return;

View File

@@ -26,8 +26,7 @@ import client.MapleClient;
import config.YamlConfig;
import net.MaplePacketHandler;
import net.server.Server;
import net.server.coordinator.session.MapleSessionCoordinator;
import org.apache.mina.core.session.IoSession;
import net.server.coordinator.session.Hwid;
import tools.BCrypt;
import tools.DatabaseConnection;
import tools.HexTool;
@@ -54,13 +53,9 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
return HexTool.toString(digester.digest()).replace(" ", "").toLowerCase();
}
private static String getRemoteIp(IoSession session) {
return MapleSessionCoordinator.getSessionRemoteAddress(session);
}
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
String remoteHost = getRemoteIp(c.getSession());
String remoteHost = c.getRemoteAddress();
if (remoteHost.contentEquals("null")) {
c.announce(MaplePacketCreator.getLoginFailed(14)); // thanks Alchemist for noting remoteHost could be null
return;
@@ -72,8 +67,8 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
slea.skip(6); // localhost masked the initial part with zeroes...
byte[] hwidNibbles = slea.read(4);
String nibbleHwid = HexTool.toCompressedString(hwidNibbles);
int loginok = c.login(login, pwd, nibbleHwid);
Hwid hwid = new Hwid(HexTool.bytesToHex(hwidNibbles));
int loginok = c.login(login, pwd, hwid);
if (YamlConfig.config.server.AUTOMATIC_REGISTER && loginok == 5) {
@@ -93,7 +88,7 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
c.setAccID(-1);
e.printStackTrace();
} finally {
loginok = c.login(login, pwd, nibbleHwid);
loginok = c.login(login, pwd, hwid);
}
}

View File

@@ -1,37 +1,31 @@
package net.server.handlers.login;
import client.MapleClient;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
import java.net.InetAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.world.World;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
import client.MapleClient;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult;
import org.apache.mina.core.session.IoSession;
public final class RegisterPicHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(RegisterPicHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) {
case REMOTE_PROCESSING:
return 10;
case REMOTE_LOGGEDIN:
return 7;
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
return switch (res) {
case REMOTE_PROCESSING -> 10;
case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case COORDINATOR_ERROR -> 8;
default -> 9;
};
}
@Override
@@ -40,31 +34,34 @@ public final class RegisterPicHandler extends AbstractMaplePacketHandler {
int charId = slea.readInt();
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
String hostString = slea.readMapleAsciiString();
final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17));
return;
}
c.updateMacs(macs);
c.updateHWID(hwid);
c.updateHwid(hwid);
IoSession session = c.getSession();
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return;
}
if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
@@ -79,7 +76,7 @@ public final class RegisterPicHandler extends AbstractMaplePacketHandler {
return;
}
String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel());
String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel());
if(socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
@@ -94,7 +91,7 @@ public final class RegisterPicHandler extends AbstractMaplePacketHandler {
e.printStackTrace();
}
} else {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
}
}
}

View File

@@ -23,7 +23,7 @@ package net.server.handlers.login;
import client.MapleClient;
import net.AbstractMaplePacketHandler;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.SessionCoordinator;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
@@ -35,7 +35,7 @@ public final class RegisterPinHandler extends AbstractMaplePacketHandler {
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
byte c2 = slea.readByte();
if (c2 == 0) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null);
SessionCoordinator.getInstance().closeSession(c, null);
c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
} else {
String pin = slea.readMapleAsciiString();
@@ -43,7 +43,7 @@ public final class RegisterPinHandler extends AbstractMaplePacketHandler {
c.setPin(pin);
c.announce(MaplePacketCreator.pinRegistered());
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null);
SessionCoordinator.getInstance().closeSession(c, null);
c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
}
}

View File

@@ -25,7 +25,7 @@ package net.server.handlers.login;
import client.MapleClient;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.SessionCoordinator;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
@@ -44,7 +44,7 @@ public class SetGenderHandler extends AbstractMaplePacketHandler {
Server.getInstance().registerLoginState(c);
} else {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null);
SessionCoordinator.getInstance().closeSession(c, null);
c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
}
}

View File

@@ -1,37 +1,32 @@
package net.server.handlers.login;
import client.MapleClient;
import java.net.InetAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult;
import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.apache.mina.core.session.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.MaplePacketCreator;
import tools.Randomizer;
import tools.data.input.SeekableLittleEndianAccessor;
import java.net.InetAddress;
import java.net.UnknownHostException;
public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(ViewAllCharRegisterPicHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) {
case REMOTE_PROCESSING:
return 10;
case REMOTE_LOGGEDIN:
return 7;
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
return switch (res) {
case REMOTE_PROCESSING -> 10;
case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case COORDINATOR_ERROR -> 8;
default -> 9;
};
}
@Override
@@ -41,23 +36,26 @@ public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHand
slea.readInt(); // please don't let the client choose which world they should login
String mac = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
String hostString = slea.readMapleAsciiString();
final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17));
return;
}
c.updateMacs(mac);
c.updateHWID(hwid);
c.updateHwid(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
IoSession session = c.getSession();
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return;
@@ -65,7 +63,7 @@ public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHand
Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
@@ -82,7 +80,7 @@ public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHand
String pic = slea.readMapleAsciiString();
c.setPic(pic);
String[] socket = server.getInetSocket(session, c.getWorld(), channel);
String[] socket = server.getInetSocket(c, c.getWorld(), channel);
if (socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;

View File

@@ -22,37 +22,32 @@
package net.server.handlers.login;
import client.MapleClient;
import java.net.InetAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult;
import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.apache.mina.core.session.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.MaplePacketCreator;
import tools.Randomizer;
import tools.data.input.SeekableLittleEndianAccessor;
import java.net.InetAddress;
import java.net.UnknownHostException;
public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(ViewAllCharSelectedHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) {
case REMOTE_PROCESSING:
return 10;
case REMOTE_LOGGEDIN:
return 7;
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
return switch (res) {
case REMOTE_PROCESSING -> 10;
case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case COORDINATOR_ERROR -> 8;
default -> 9;
};
}
@Override
@@ -61,23 +56,26 @@ public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler
slea.readInt(); // please don't let the client choose which world they should login
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
String hostString = slea.readMapleAsciiString();
final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17));
return;
}
c.updateMacs(macs);
c.updateHWID(hwid);
c.updateHwid(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
IoSession session = c.getSession();
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return;
@@ -85,7 +83,7 @@ public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler
Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
@@ -105,7 +103,7 @@ public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler
c.setChannel(1);
}
String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel());
String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel());
if(socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;

View File

@@ -1,38 +1,32 @@
package net.server.handlers.login;
import client.MapleClient;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.MaplePacketCreator;
import tools.Randomizer;
import tools.data.input.SeekableLittleEndianAccessor;
import java.net.InetAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.world.World;
import tools.MaplePacketCreator;
import tools.Randomizer;
import tools.data.input.SeekableLittleEndianAccessor;
import client.MapleClient;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult;
import org.apache.mina.core.session.IoSession;
public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(ViewAllCharSelectedWithPicHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) {
case REMOTE_PROCESSING:
return 10;
case REMOTE_LOGGEDIN:
return 7;
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
return switch (res) {
case REMOTE_PROCESSING -> 10;
case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case COORDINATOR_ERROR -> 8;
default -> 9;
};
}
@Override
@@ -43,26 +37,28 @@ public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandle
slea.readInt(); // please don't let the client choose which world they should login
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
String hostString = slea.readMapleAsciiString();
final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17));
return;
}
c.updateMacs(macs);
c.updateHWID(hwid);
c.updateHwid(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
IoSession session = c.getSession();
Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true);
SessionCoordinator.getInstance().closeSession(c, true);
return;
}
@@ -77,13 +73,13 @@ public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandle
c.setChannel(channel);
if (c.checkPic(pic)) {
String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel());
String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel());
if(socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10));
return;
}
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return;

View File

@@ -19,7 +19,7 @@
*/
package net.server.task;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.SessionCoordinator;
/**
*
@@ -29,6 +29,6 @@ public class LoginCoordinatorTask implements Runnable {
@Override
public void run() {
MapleSessionCoordinator.getInstance().runUpdateHwidHistory();
SessionCoordinator.getInstance().clearExpiredHwidHistory();
}
}

View File

@@ -19,7 +19,7 @@
*/
package net.server.task;
import net.server.coordinator.session.MapleSessionCoordinator;
import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.login.MapleLoginBypassCoordinator;
/**
@@ -30,7 +30,7 @@ public class LoginStorageTask implements Runnable {
@Override
public void run() {
MapleSessionCoordinator.getInstance().runUpdateLoginHistory();
SessionCoordinator.getInstance().runUpdateLoginHistory();
MapleLoginBypassCoordinator.getInstance().runUpdateLoginBypass();
}
}

View File

@@ -588,7 +588,7 @@ public class MaplePlayerNPC extends AbstractMapleMapObject {
World wserv = Server.getInstance().getWorld(world);
if (wserv == null) return;
MapleClient c = new MapleClient(null, null, null); // mock client
MapleClient c = MapleClient.createMock();
c.setWorld(world);
c.setChannel(1);

View File

@@ -156,7 +156,7 @@ public class MapleMiniGame extends AbstractMapleMapObject {
public void broadcastToOwner(final byte[] packet) {
MapleClient c = owner.getClient();
if (c != null && c.getSession() != null) {
if (c != null) {
c.announce(packet);
}
}

View File

@@ -386,8 +386,9 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
}
public void broadcast(final byte[] packet) {
if (owner.getClient() != null && owner.getClient().getSession() != null) {
owner.getClient().announce(packet);
MapleClient client = owner.getClient();
if (client != null) {
client.announce(packet);
}
broadcastToVisitors(packet);
}

View File

@@ -22,6 +22,7 @@
package tools;
import constants.string.CharsetConstants;
import io.netty.buffer.ByteBufUtil;
import java.io.ByteArrayOutputStream;
@@ -105,4 +106,14 @@ public class HexTool {
return "";
}
/**
* Get upper case hex dump
*/
public static String bytesToHex(byte[] bytes) {
return ByteBufUtil.hexDump(bytes).toUpperCase();
}
public static byte[] hexToBytes(String hex) {
return ByteBufUtil.decodeHexDump(hex);
}
}

View File

@@ -1,80 +0,0 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 ~ 2010 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version 3
as published by the Free Software Foundation. You may not use, modify
or distribute this program under any other version of the
GNU Affero General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package tools;
import java.util.HashSet;
import java.util.Set;
import net.opcodes.RecvOpcode;
import client.MapleCharacter;
import client.MapleClient;
/**
* Logs packets to console and file.
*
* @author Alan (SharpAceX)
*/
public class MapleLogger {
public static Set<Integer> monitored = new HashSet<>();
public static Set<Integer> ignored = new HashSet<>();
public static void logRecv(MapleClient c, short packetId, Object message) {
MapleCharacter chr = c.getPlayer();
if (chr == null){
return;
}
if (!monitored.contains(chr.getId())){
return;
}
RecvOpcode op = getOpcodeFromValue(packetId);
if (isRecvBlocked(op)){
return;
}
String packet = op.toString() + "\r\n" + HexTool.toString((byte[]) message);
FilePrinter.printError(FilePrinter.PACKET_LOGS + c.getAccountName() + "-" + chr.getName() + ".txt", packet);
}
private static final boolean isRecvBlocked(RecvOpcode op){
switch(op){
case MOVE_PLAYER:
case GENERAL_CHAT:
case TAKE_DAMAGE:
case MOVE_PET:
case MOVE_LIFE:
case NPC_ACTION:
case FACE_EXPRESSION:
return true;
default:
return false;
}
}
private static final RecvOpcode getOpcodeFromValue(int value){
for (RecvOpcode op : RecvOpcode.values()){
if (op.getValue() == value){
return op;
}
}
return null;
}
}

View File

@@ -36,6 +36,7 @@ import constants.inventory.ItemConstants;
import constants.skills.Buccaneer;
import constants.skills.Corsair;
import constants.skills.ThunderBreaker;
import net.encryption.InitializationVector;
import net.opcodes.SendOpcode;
import net.server.PlayerCoolDownValueHolder;
import net.server.Server;
@@ -547,14 +548,14 @@ public class MaplePacketCreator {
* @param recvIv the IV in use by the server for receiving
* @return
*/
public static byte[] getHello(short mapleVersion, byte[] sendIv, byte[] recvIv) {
public static byte[] getHello(short mapleVersion, InitializationVector sendIv, InitializationVector recvIv) {
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(8);
mplew.writeShort(0x0E);
mplew.writeShort(mapleVersion);
mplew.writeShort(1);
mplew.write(49);
mplew.write(recvIv);
mplew.write(sendIv);
mplew.write(recvIv.getBytes());
mplew.write(sendIv.getBytes());
mplew.write(8);
return mplew.getPacket();
}

View File

@@ -2,29 +2,45 @@
<Configuration status="WARN" name="Cosmic">
<Properties>
<Property name="filename">cosmic-log</Property>
<Property name="standard-pattern">%d{HH:mm:ss.SSS} [%t] %-5level %logger{2} - %msg%n</Property>
<Property name="packet-pattern">%d{HH:mm:ss.SSS} %-15logger{1} - %msg%n</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<PatternLayout>
<Pattern>${standard-pattern}</Pattern>
</PatternLayout>
</Console>
<RollingFile name="File"
fileName="logs/${filename}.log"
filePattern="logs/$${date:yyyy-MM}/$${date:yyyy-MM-dd}/${filename}-%d{yyyy-MM-dd_HH-mm-ss}-%i.log">
<PatternLayout>
<Pattern>%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Pattern>
<Pattern>${standard-pattern}</Pattern>
</PatternLayout>
<Policies>
<OnStartupTriggeringPolicy minSize="0"/>
<SizeBasedTriggeringPolicy size="20 MB"/>
</Policies>
</RollingFile>
<Console name="PacketConsole" target="SYSTEM_OUT">
<PatternLayout>
<Pattern>${packet-pattern}</Pattern>
</PatternLayout>
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="File" level="debug"/>
<AppenderRef ref="Console" level="trace"/>
</Root>
<Logger name="org.apache.mina" level="info"/>
<Logger name="net.packet.logging" level="debug" additivity="false">
<AppenderRef ref="PacketConsole"/>
<AppenderRef ref="File"/>
</Logger>
<Logger name="com.zaxxer.hikari" level="info"/>
<Logger name="io.netty" level="info"/>
</Loggers>
</Configuration>

View File

@@ -0,0 +1,22 @@
package tools;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class HexToolTest {
@Test
void upperCaseHexToBytesAndBack() {
String hex = "A1B2C3";
byte[] bytes = HexTool.hexToBytes(hex);
assertEquals(hex, HexTool.bytesToHex(bytes));
}
@Test
void mixedCaseHexToBytesAndBack() {
String hex = "aB5DaA";
byte[] bytes = HexTool.hexToBytes(hex);
assertEquals(hex.toUpperCase(), HexTool.bytesToHex(bytes));
}
}