52
pom.xml
52
pom.xml
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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}\\..*")) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
{
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
27
src/main/java/net/encryption/ClientCyphers.java
Normal file
27
src/main/java/net/encryption/ClientCyphers.java
Normal 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;
|
||||
}
|
||||
}
|
||||
27
src/main/java/net/encryption/InitializationVector.java
Normal file
27
src/main/java/net/encryption/InitializationVector.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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) {
|
||||
9
src/main/java/net/encryption/PacketCodec.java
Normal file
9
src/main/java/net/encryption/PacketCodec.java
Normal 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()));
|
||||
}
|
||||
}
|
||||
48
src/main/java/net/encryption/PacketDecoder.java
Normal file
48
src/main/java/net/encryption/PacketDecoder.java
Normal 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;
|
||||
}
|
||||
}
|
||||
28
src/main/java/net/encryption/PacketEncoder.java
Normal file
28
src/main/java/net/encryption/PacketEncoder.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
12
src/main/java/net/netty/AbstractServer.java
Normal file
12
src/main/java/net/netty/AbstractServer.java
Normal 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();
|
||||
}
|
||||
40
src/main/java/net/netty/ChannelServer.java
Normal file
40
src/main/java/net/netty/ChannelServer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
40
src/main/java/net/netty/ChannelServerInitializer.java
Normal file
40
src/main/java/net/netty/ChannelServerInitializer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
14
src/main/java/net/netty/InvalidPacketHeaderException.java
Normal file
14
src/main/java/net/netty/InvalidPacketHeaderException.java
Normal 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;
|
||||
}
|
||||
}
|
||||
38
src/main/java/net/netty/LoginServer.java
Normal file
38
src/main/java/net/netty/LoginServer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
30
src/main/java/net/netty/LoginServerInitializer.java
Normal file
30
src/main/java/net/netty/LoginServerInitializer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
71
src/main/java/net/netty/ServerChannelInitializer.java
Normal file
71
src/main/java/net/netty/ServerChannelInitializer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
47
src/main/java/net/packet/logging/InPacketLogger.java
Normal file
47
src/main/java/net/packet/logging/InPacketLogger.java
Normal 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);
|
||||
}
|
||||
}
|
||||
17
src/main/java/net/packet/logging/LoggingUtil.java
Normal file
17
src/main/java/net/packet/logging/LoggingUtil.java
Normal 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);
|
||||
}
|
||||
}
|
||||
72
src/main/java/net/packet/logging/MapleLogger.java
Normal file
72
src/main/java/net/packet/logging/MapleLogger.java
Normal 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);
|
||||
}
|
||||
}
|
||||
48
src/main/java/net/packet/logging/OutPacketLogger.java
Normal file
48
src/main/java/net/packet/logging/OutPacketLogger.java
Normal 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);
|
||||
}
|
||||
}
|
||||
7
src/main/java/net/packet/logging/PacketLogger.java
Normal file
7
src/main/java/net/packet/logging/PacketLogger.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package net.packet.logging;
|
||||
|
||||
import net.packet.Packet;
|
||||
|
||||
public interface PacketLogger {
|
||||
void log(Packet packet);
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
16
src/main/java/net/server/coordinator/session/HostHwid.java
Normal file
16
src/main/java/net/server/coordinator/session/HostHwid.java
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
26
src/main/java/net/server/coordinator/session/Hwid.java
Normal file
26
src/main/java/net/server/coordinator/session/Hwid.java
Normal 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]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
89
src/main/java/net/server/coordinator/session/SessionDAO.java
Normal file
89
src/main/java/net/server/coordinator/session/SessionDAO.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
22
src/test/java/tools/HexToolTest.java
Normal file
22
src/test/java/tools/HexToolTest.java
Normal 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user