Merge pull request #46 from P0nk/netty

Replace Apache Mina with Netty
This commit is contained in:
Ponk authored and GitHub committed 2021-08-18 22:14:46 +02:00
commit ce726efc4f
76 files changed
+2147 -1925

No files matched your search

+34 -18
View File
@@ -19,24 +19,11 @@
<log4j.version>2.14.1</log4j.version> <log4j.version>2.14.1</log4j.version>
<graalvm.version>21.1.0</graalvm.version> <graalvm.version>21.1.0</graalvm.version>
<netty.version>4.1.65.Final</netty.version>
<junit.version>5.7.2</junit.version>
</properties> </properties>
<dependencies> <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> <dependency>
<groupId>com.esotericsoftware.yamlbeans</groupId> <groupId>com.esotericsoftware.yamlbeans</groupId>
<artifactId>yamlbeans</artifactId> <artifactId>yamlbeans</artifactId>
@@ -52,10 +39,39 @@
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
<version>2.10.0</version> <version>2.10.0</version>
</dependency> </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> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId> <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> </dependency>
<!-- Logging --> <!-- Logging -->
@@ -96,12 +112,12 @@
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId> <artifactId>junit-jupiter-api</artifactId>
<version>5.7.2</version> <version>${junit.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
<version>5.7.2</version> <version>${junit.version}</version>
</dependency> </dependency>
+2 -2
View File
@@ -52,7 +52,6 @@ import net.server.services.task.world.CharacterSaveService;
import net.server.services.type.ChannelServices; import net.server.services.type.ChannelServices;
import net.server.services.type.WorldServices; import net.server.services.type.WorldServices;
import net.server.world.*; import net.server.world.*;
import org.apache.mina.util.ConcurrentHashSet;
import scripting.AbstractPlayerInteraction; import scripting.AbstractPlayerInteraction;
import scripting.event.EventInstanceManager; import scripting.event.EventInstanceManager;
import scripting.item.ItemScriptManager; import scripting.item.ItemScriptManager;
@@ -85,6 +84,7 @@ import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@@ -177,7 +177,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
private final Map<Short, MapleQuestStatus> quests; private final Map<Short, MapleQuestStatus> quests;
private Set<MapleMonster> controlled = new LinkedHashSet<>(); private Set<MapleMonster> controlled = new LinkedHashSet<>();
private Map<Integer, String> entered = new LinkedHashMap<>(); 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<Skill, SkillEntry> skills = new LinkedHashMap<>();
private Map<Integer, Integer> activeCoupons = new LinkedHashMap<>(); private Map<Integer, Integer> activeCoupons = new LinkedHashMap<>();
private Map<Integer, Integer> activeCouponRates = new LinkedHashMap<>(); private Map<Integer, Integer> activeCouponRates = new LinkedHashMap<>();
+263 -116
View File
@@ -24,17 +24,31 @@ package client;
import client.inventory.MapleInventoryType; import client.inventory.MapleInventoryType;
import config.YamlConfig; import config.YamlConfig;
import constants.game.GameConstants; import constants.game.GameConstants;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
import net.MaplePacketHandler;
import net.PacketProcessor;
import net.netty.InvalidPacketHeaderException;
import net.packet.logging.LoggingUtil;
import net.packet.logging.MapleLogger;
import net.packet.ByteBufOutPacket;
import net.packet.InPacket;
import net.packet.OutPacket;
import net.server.Server; import net.server.Server;
import net.server.audit.locks.MonitoredLockType; import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory; import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import net.server.channel.Channel; import net.server.channel.Channel;
import net.server.coordinator.login.MapleLoginBypassCoordinator; import net.server.coordinator.login.MapleLoginBypassCoordinator;
import net.server.coordinator.session.MapleSessionCoordinator; import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; import net.server.coordinator.session.IpAddresses;
import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.guild.MapleGuild; import net.server.guild.MapleGuild;
import net.server.guild.MapleGuildCharacter; import net.server.guild.MapleGuildCharacter;
import net.server.world.*; import net.server.world.*;
import org.apache.mina.core.session.IoSession; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scripting.AbstractPlayerInteraction; import scripting.AbstractPlayerInteraction;
import scripting.event.EventInstanceManager; import scripting.event.EventInstanceManager;
import scripting.event.EventManager; import scripting.event.EventManager;
@@ -43,37 +57,45 @@ import scripting.npc.NPCScriptManager;
import scripting.quest.QuestActionManager; import scripting.quest.QuestActionManager;
import scripting.quest.QuestScriptManager; import scripting.quest.QuestScriptManager;
import server.ThreadManager; import server.ThreadManager;
import server.TimerManager;
import server.life.MapleMonster; import server.life.MapleMonster;
import server.maps.FieldLimit; import server.maps.FieldLimit;
import server.maps.MapleMap; import server.maps.MapleMap;
import server.maps.MapleMiniDungeonInfo; import server.maps.MapleMiniDungeonInfo;
import tools.*; import tools.*;
import tools.data.input.ByteArrayByteStream;
import tools.data.input.GenericSeekableLittleEndianAccessor;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.sql.*; import java.sql.*;
import java.util.Date; import java.util.Date;
import java.util.*; import java.util.*;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
public class MapleClient { public class MapleClient extends ChannelInboundHandlerAdapter {
private static final Logger log = LoggerFactory.getLogger(MapleClient.class);
public static final int LOGIN_NOTLOGGEDIN = 0; public static final int LOGIN_NOTLOGGEDIN = 0;
public static final int LOGIN_SERVER_TRANSITION = 1; public static final int LOGIN_SERVER_TRANSITION = 1;
public static final int LOGIN_LOGGEDIN = 2; public static final int LOGIN_LOGGEDIN = 2;
public static final String CLIENT_KEY = "CLIENT";
public static final String CLIENT_HWID = "HWID"; private final Type type;
public static final String CLIENT_NIBBLEHWID = "HWID2"; private final long sessionId;
public static final String CLIENT_REMOTE_ADDRESS = "REMOTE_IP"; private final PacketProcessor packetProcessor;
public static final String CLIENT_TRANSITION = "TRANSITION";
private MapleAESOFB send; private Hwid hwid;
private MapleAESOFB receive; private String remoteAddress;
private final IoSession session; private volatile boolean inTransition;
private io.netty.channel.Channel ioChannel;
private MapleCharacter player; private MapleCharacter player;
private int channel = 1; private int channel = 1;
private int accId = -4; private int accId = -4;
@@ -82,7 +104,7 @@ public class MapleClient {
private Calendar birthday = null; private Calendar birthday = null;
private String accountName = null; private String accountName = null;
private int world; private int world;
private long lastPong; private volatile long lastPong;
private int gmlevel; private int gmlevel;
private Set<String> macs = new HashSet<>(); private Set<String> macs = new HashSet<>();
private Map<String, ScriptEngine> engines = new HashMap<>(); private Map<String, ScriptEngine> engines = new HashMap<>();
@@ -92,7 +114,6 @@ public class MapleClient {
private int pinattempt = 0; private int pinattempt = 0;
private String pic = ""; private String pic = "";
private int picattempt = 0; private int picattempt = 0;
private String hwid = null;
private byte csattempt = 0; private byte csattempt = 0;
private byte gender = -1; private byte gender = -1;
private boolean disconnecting = false; private boolean disconnecting = false;
@@ -106,10 +127,137 @@ public class MapleClient {
private int voteTime = -1; private int voteTime = -1;
private int visibleWorlds; private int visibleWorlds;
private long lastNpcClick; private long lastNpcClick;
private long sessionId;
private long lastPacket = System.currentTimeMillis(); private long lastPacket = System.currentTimeMillis();
private int lang = 0; private int lang = 0;
public enum Type {
LOGIN,
CHANNEL
}
public MapleClient(Type type, long sessionId, String remoteAddress, PacketProcessor packetProcessor, int world, int channel) {
this.type = type;
this.sessionId = sessionId;
this.remoteAddress = remoteAddress;
this.packetProcessor = packetProcessor;
this.world = world;
this.channel = channel;
}
public static MapleClient createLoginClient(long sessionId, String remoteAddress, PacketProcessor packetProcessor,
int world, int channel) {
return new MapleClient(Type.LOGIN, sessionId, remoteAddress, packetProcessor, world, channel);
}
public static MapleClient createChannelClient(long sessionId, String remoteAddress, PacketProcessor packetProcessor,
int world, int channel) {
return new MapleClient(Type.CHANNEL, sessionId, remoteAddress, packetProcessor, world, channel);
}
public static MapleClient createMock() {
return new MapleClient(null, -1,null, null, -123, -123);
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
final io.netty.channel.Channel channel = ctx.channel();
if (!Server.getInstance().isOnline()) {
channel.close();
return;
}
this.remoteAddress = getRemoteAddress(channel);
this.ioChannel = channel;
}
private static String getRemoteAddress(io.netty.channel.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 for client", npe);
}
return remoteAddress;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!(msg instanceof InPacket packet)) {
log.warn("Received invalid message: {}", msg);
return;
}
short opcode = packet.readShort();
final MaplePacketHandler handler = packetProcessor.getHandler(opcode);
if (YamlConfig.config.server.USE_DEBUG_SHOW_RCVD_PACKET && !LoggingUtil.isIgnoredRecvPacket(opcode)) {
log.debug("Received packet id {}", opcode);
}
if (handler != null && handler.validateState(this)) {
// TODO: pass InPacket directly to handler once all handlers have been ported,
// this is just a temporary workaround
final byte[] content = packet.getBytes();
GenericSeekableLittleEndianAccessor accessor = new GenericSeekableLittleEndianAccessor(new ByteArrayByteStream(content));
try {
MapleLogger.logRecv(this, opcode, content);
handler.handlePacket(accessor, this);
} catch (final Throwable t) {
FilePrinter.printError(FilePrinter.PACKET_HANDLER + handler.getClass().getName() + ".txt", t, "Error for " + (getPlayer() == null ? "" : "player ; " + getPlayer() + " on map ; " + getPlayer().getMapId() + " - ") + "account ; " + getAccountName() + "\r\n" + accessor);
//client.announce(MaplePacketCreator.enableActions());//bugs sometimes
}
}
updateLastPacket();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object event) {
if (event instanceof IdleStateEvent idleEvent) {
checkIfIdle(idleEvent);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (player != null) {
log.warn("Exception caught by {}", player, cause);
}
if (cause instanceof InvalidPacketHeaderException) {
SessionCoordinator.getInstance().closeSession(this, true);
} else if (cause instanceof IOException) {
closeMapleSession();
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
closeMapleSession();
}
private void closeMapleSession() {
switch (type) {
case LOGIN -> SessionCoordinator.getInstance().closeLoginSession(this);
case CHANNEL -> SessionCoordinator.getInstance().closeSession(this, null);
}
try {
// client freeze issues on session transition states found thanks to yolinlin, Omo Oppa, Nozphex
if (!inTransition) {
disconnect(false, false);
}
} catch (Throwable t) {
log.warn("Account stuck", t);
} finally {
closeSession();
}
}
public void updateLastPacket() { public void updateLastPacket() {
lastPacket = System.currentTimeMillis(); lastPacket = System.currentTimeMillis();
} }
@@ -118,22 +266,28 @@ public class MapleClient {
return lastPacket; return lastPacket;
} }
public MapleClient(MapleAESOFB send, MapleAESOFB receive, IoSession session) { public void closeSession() {
this.send = send; ioChannel.close();
this.receive = receive;
this.session = session;
} }
public MapleAESOFB getReceiveCrypto() { public void disconnectSession() {
return receive; ioChannel.disconnect();
} }
public MapleAESOFB getSendCrypto() { public Hwid getHwid() {
return send; return hwid;
} }
public IoSession getSession() { public void setHwid(Hwid hwid) {
return session; this.hwid = hwid;
}
public String getRemoteAddress() {
return remoteAddress;
}
public boolean isInTransition() {
return inTransition;
} }
public EventManager getEventManager(String event) { public EventManager getEventManager(String event) {
@@ -202,7 +356,7 @@ public class MapleClient {
boolean ret = false; boolean ret = false;
try (Connection con = DatabaseConnection.getConnection(); try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM ipbans WHERE ? LIKE CONCAT(ip, '%')")) { PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM ipbans WHERE ? LIKE CONCAT(ip, '%')")) {
ps.setString(1, session.getRemoteAddress().toString()); ps.setString(1, remoteAddress);
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
rs.next(); rs.next();
if (rs.getInt(1) > 0) { if (rs.getInt(1) > 0) {
@@ -240,7 +394,7 @@ public class MapleClient {
voteTime = -1; voteTime = -1;
} }
public boolean hasVotedAlready(){ public boolean hasVotedAlready() {
Date currentDate = new Date(); Date currentDate = new Date();
int timeNow = (int) (currentDate.getTime() / 1000); int timeNow = (int) (currentDate.getTime() / 1000);
int difference = (timeNow - getVoteTime()); int difference = (timeNow - getVoteTime());
@@ -255,7 +409,7 @@ public class MapleClient {
boolean ret = false; boolean ret = false;
try (Connection con = DatabaseConnection.getConnection(); try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM hwidbans WHERE hwid LIKE ?")) { PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM hwidbans WHERE hwid LIKE ?")) {
ps.setString(1, hwid); ps.setString(1, hwid.hwid());
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
if (rs != null && rs.next()) { if (rs != null && rs.next()) {
@@ -313,7 +467,7 @@ public class MapleClient {
try (ResultSet rs = ps.executeQuery()) { try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) { if (rs.next()) {
hwid = rs.getString("hwid"); hwid = new Hwid(rs.getString("hwid"));
} }
} }
} }
@@ -345,7 +499,7 @@ public class MapleClient {
try (Connection con = DatabaseConnection.getConnection(); try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("INSERT INTO hwidbans (hwid) VALUES (?)")) { PreparedStatement ps = con.prepareStatement("INSERT INTO hwidbans (hwid) VALUES (?)")) {
ps.setString(1, hwid); ps.setString(1, hwid.hwid());
ps.executeUpdate(); ps.executeUpdate();
} }
} catch (SQLException e) { } catch (SQLException e) {
@@ -426,11 +580,11 @@ public class MapleClient {
pinattempt++; pinattempt++;
if (pinattempt > 5) { if (pinattempt > 5) {
MapleSessionCoordinator.getInstance().closeSession(session, false); SessionCoordinator.getInstance().closeSession(this, false);
} }
if (pin.equals(other)) { if (pin.equals(other)) {
pinattempt = 0; pinattempt = 0;
MapleLoginBypassCoordinator.getInstance().registerLoginBypassEntry(getNibbleHWID(), accId, false); MapleLoginBypassCoordinator.getInstance().registerLoginBypassEntry(hwid, accId, false);
return true; return true;
} }
return false; return false;
@@ -459,23 +613,23 @@ public class MapleClient {
picattempt++; picattempt++;
if (picattempt > 5) { if (picattempt > 5) {
MapleSessionCoordinator.getInstance().closeSession(session, false); SessionCoordinator.getInstance().closeSession(this, false);
} }
if (pic.equals(other)) { // thanks ryantpayton (HeavenClient) for noticing null pics being checked here if (pic.equals(other)) { // thanks ryantpayton (HeavenClient) for noticing null pics being checked here
picattempt = 0; picattempt = 0;
MapleLoginBypassCoordinator.getInstance().registerLoginBypassEntry(getNibbleHWID(), accId, true); MapleLoginBypassCoordinator.getInstance().registerLoginBypassEntry(hwid, accId, true);
return true; return true;
} }
return false; return false;
} }
public int login(String login, String pwd, String nibbleHwid) { public int login(String login, String pwd, Hwid hwid) {
int loginok = 5; int loginok = 5;
loginattempt++; loginattempt++;
if (loginattempt > 4) { if (loginattempt > 4) {
loggedIn = false; loggedIn = false;
MapleSessionCoordinator.getInstance().closeSession(session, false); SessionCoordinator.getInstance().closeSession(this, false);
return 6; // thanks Survival_Project for finding out an issue with AUTOMATIC_REGISTER here return 6; // thanks Survival_Project for finding out an issue with AUTOMATIC_REGISTER here
} }
@@ -527,7 +681,7 @@ public class MapleClient {
} }
if (loginok == 0 || loginok == 4) { if (loginok == 0 || loginok == 4) {
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptLoginSession(session, nibbleHwid, accId, loginok == 4); AntiMulticlientResult res = SessionCoordinator.getInstance().attemptLoginSession(this, hwid, accId, loginok == 4);
switch (res) { switch (res) {
case SUCCESS: case SUCCESS:
@@ -607,31 +761,17 @@ public class MapleClient {
return ipAddress; return ipAddress;
} }
public void updateHWID(String newHwid) { public void updateHwid(Hwid hwid) {
String[] split = newHwid.split("_"); this.hwid = hwid;
if (split.length > 1 && split[1].length() == 8) {
StringBuilder hwid = new StringBuilder();
String convert = split[1];
int len = convert.length();
for (int i = len - 2; i >= 0; i -= 2) {
hwid.append(convert.substring(i, i + 2));
}
hwid.insert(4, "-");
this.hwid = hwid.toString();
try (Connection con = DatabaseConnection.getConnection(); try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("UPDATE accounts SET hwid = ? WHERE id = ?")) { PreparedStatement ps = con.prepareStatement("UPDATE accounts SET hwid = ? WHERE id = ?")) {
ps.setString(1, this.hwid); ps.setString(1, hwid.hwid());
ps.setInt(2, accId); ps.setInt(2, accId);
ps.executeUpdate(); ps.executeUpdate();
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
} }
} else {
this.disconnect(false, false); // Invalid HWID...
}
} }
public void updateMacs(String macData) { public void updateMacs(String macData) {
@@ -664,17 +804,17 @@ public class MapleClient {
return accId; return accId;
} }
public void updateLoginState(int newstate) { public void updateLoginState(int newState) {
// rules out possibility of multiple account entries // rules out possibility of multiple account entries
if (newstate == LOGIN_LOGGEDIN) { if (newState == LOGIN_LOGGEDIN) {
MapleSessionCoordinator.getInstance().updateOnlineSession(this.getSession()); SessionCoordinator.getInstance().updateOnlineClient(this);
} }
try (Connection con = DatabaseConnection.getConnection(); try (Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("UPDATE accounts SET loggedin = ?, lastlogin = ? WHERE id = ?")) { PreparedStatement ps = con.prepareStatement("UPDATE accounts SET loggedin = ?, lastlogin = ? WHERE id = ?")) {
// using sql currenttime here could potentially break the login, thanks Arnah for pointing this out // using sql currenttime here could potentially break the login, thanks Arnah for pointing this out
ps.setInt(1, newstate); ps.setInt(1, newState);
ps.setTimestamp(2, new java.sql.Timestamp(Server.getInstance().getCurrentTime())); ps.setTimestamp(2, new java.sql.Timestamp(Server.getInstance().getCurrentTime()));
ps.setInt(3, getAccID()); ps.setInt(3, getAccID());
ps.executeUpdate(); ps.executeUpdate();
@@ -682,12 +822,12 @@ public class MapleClient {
e.printStackTrace(); e.printStackTrace();
} }
if (newstate == LOGIN_NOTLOGGEDIN) { if (newState == LOGIN_NOTLOGGEDIN) {
loggedIn = false; loggedIn = false;
serverTransition = false; serverTransition = false;
setAccID(0); setAccID(0);
} else { } else {
serverTransition = (newstate == LOGIN_SERVER_TRANSITION); serverTransition = (newState == LOGIN_SERVER_TRANSITION);
loggedIn = !serverTransition; loggedIn = !serverTransition;
} }
} }
@@ -795,7 +935,7 @@ public class MapleClient {
if (player.getMap() != null) { if (player.getMap() != null) {
int mapId = player.getMapId(); int mapId = player.getMapId();
player.getMap().removePlayer(player); player.getMap().removePlayer(player);
if(GameConstants.isDojo(mapId)) { if (GameConstants.isDojo(mapId)) {
this.getChannelServer().freeDojoSectionIfEmpty(mapId); this.getChannelServer().freeDojoSectionIfEmpty(mapId);
} }
} }
@@ -877,7 +1017,7 @@ public class MapleClient {
FilePrinter.printError(FilePrinter.ACCOUNT_STUCK, e); FilePrinter.printError(FilePrinter.ACCOUNT_STUCK, e);
} finally { } finally {
if (!this.serverTransition) { if (!this.serverTransition) {
if(chrg != null) { if (chrg != null) {
chrg.setCharacter(null); chrg.setCharacter(null);
} }
wserv.removePlayer(player); wserv.removePlayer(player);
@@ -888,7 +1028,9 @@ public class MapleClient {
player.saveCharToDB(true); player.saveCharToDB(true);
player.logOff(); player.logOff();
if(YamlConfig.config.server.INSTANT_NAME_CHANGE) player.doPendingNameChange(); if (YamlConfig.config.server.INSTANT_NAME_CHANGE) {
player.doPendingNameChange();
}
clear(); clear();
} else { } else {
getChannelServer().removePlayer(player); getChannelServer().removePlayer(player);
@@ -899,18 +1041,14 @@ public class MapleClient {
} }
} }
} }
SessionCoordinator.getInstance().closeSession(this, false);
if (!serverTransition && isLoggedIn()) { if (!serverTransition && isLoggedIn()) {
MapleSessionCoordinator.getInstance().closeSession(session, false);
updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
session.removeAttribute(MapleClient.CLIENT_KEY); // prevents double dcing during login
clear(); clear();
} else { } else {
if (session.containsAttribute(MapleClient.CLIENT_KEY)) {
MapleSessionCoordinator.getInstance().closeSession(session, false);
session.removeAttribute(MapleClient.CLIENT_KEY);
}
if (!Server.getInstance().hasCharacteridInTransition(this)) { if (!Server.getInstance().hasCharacteridInTransition(this)) {
updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
} }
@@ -933,14 +1071,11 @@ public class MapleClient {
this.birthday = null; this.birthday = null;
this.engines = null; this.engines = null;
this.player = null; this.player = null;
this.receive = null;
this.send = null;
//this.session = null;
} }
public void setCharacterOnSessionTransitionState(int cid) { public void setCharacterOnSessionTransitionState(int cid) {
this.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION); this.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION);
session.setAttribute(MapleClient.CLIENT_TRANSITION); this.inTransition = true;
Server.getInstance().setCharacteridInTransition(this, cid); Server.getInstance().setCharacteridInTransition(this, cid);
} }
@@ -977,7 +1112,7 @@ public class MapleClient {
} }
return MapleCharacter.deleteCharFromDB(chr, senderAccId); return MapleCharacter.deleteCharFromDB(chr, senderAccId);
} catch(SQLException ex) { } catch (SQLException ex) {
ex.printStackTrace(); ex.printStackTrace();
return false; return false;
} }
@@ -1004,29 +1139,25 @@ public class MapleClient {
} }
public void pongReceived() { public void pongReceived() {
lastPong = Server.getInstance().getCurrentTime(); lastPong = System.currentTimeMillis();
} }
public void testPing(long timeThen) { public void checkIfIdle(final IdleStateEvent event) {
final long pingedAt = System.currentTimeMillis();
announce(MaplePacketCreator.getPing());
TimerManager.getInstance().schedule(() -> {
try { try {
if (lastPong < timeThen) { if (lastPong < pingedAt) {
if (session != null && session.isConnected()) { if (ioChannel.isActive()) {
MapleSessionCoordinator.getInstance().closeSession(session, false); log.info("Disconnected {} due to idling. Reason: {}", remoteAddress, event.state());
updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
session.removeAttribute(MapleClient.CLIENT_KEY); disconnectSession();
} }
} }
} catch (NullPointerException e) { } catch (NullPointerException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }, TimeUnit.SECONDS.toMillis(15));
public String getHWID() {
return hwid;
}
public void setHWID(String hwid) {
this.hwid = hwid;
} }
public Set<String> getMacs() { public Set<String> getMacs() {
@@ -1129,8 +1260,8 @@ public class MapleClient {
saveVotePoints(); saveVotePoints();
} }
public void useVotePoints(int points){ public void useVotePoints(int points) {
if (points > votePoints){ if (points > votePoints) {
//Should not happen, should probably log this //Should not happen, should probably log this
return; return;
} }
@@ -1201,9 +1332,9 @@ public class MapleClient {
private static boolean checkHash(String hash, String type, String password) { private static boolean checkHash(String hash, String type, String password) {
try { try {
MessageDigest digester = MessageDigest.getInstance(type); MessageDigest digester = MessageDigest.getInstance(type);
digester.update(password.getBytes("UTF-8"), 0, password.length()); digester.update(password.getBytes(StandardCharsets.UTF_8), 0, password.length());
return HexTool.toString(digester.digest()).replace(" ", "").toLowerCase().equals(hash); return HexTool.toString(digester.digest()).replace(" ", "").toLowerCase().equals(hash);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { } catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Encoding the string failed", e); throw new RuntimeException("Encoding the string failed", e);
} }
} }
@@ -1282,7 +1413,7 @@ public class MapleClient {
} }
private void announceDisableServerMessage() { private void announceDisableServerMessage() {
if(!this.getWorldServer().registerDisabledServerMessage(player.getId())) { if (!this.getWorldServer().registerDisabledServerMessage(player.getId())) {
announce(MaplePacketCreator.serverMessage("")); announce(MaplePacketCreator.serverMessage(""));
} }
} }
@@ -1295,8 +1426,8 @@ public class MapleClient {
long timeNow = System.currentTimeMillis(); long timeNow = System.currentTimeMillis();
int targetHash = player.getTargetHpBarHash(); int targetHash = player.getTargetHpBarHash();
if(mobHash != targetHash) { if (mobHash != targetHash) {
if(timeNow - player.getTargetHpBarTime() >= 5 * 1000) { if (timeNow - player.getTargetHpBarTime() >= 5 * 1000) {
// is there a way to INTERRUPT this annoying thread running on the client that drops the boss bar after some time at every attack? // is there a way to INTERRUPT this annoying thread running on the client that drops the boss bar after some time at every attack?
announceDisableServerMessage(); announceDisableServerMessage();
announce(packet); announce(packet);
@@ -1312,10 +1443,34 @@ public class MapleClient {
} }
} }
@Deprecated(forRemoval = true, since = "Netty migration")
public void announce(final byte[] packet) { // thanks GitGud for noticing an opportunity for improvement by overcoming "synchronized announce" public void announce(final byte[] packet) { // thanks GitGud for noticing an opportunity for improvement by overcoming "synchronized announce"
announcerLock.lock(); announcerLock.lock();
try { try {
session.write(packet); // session.write(packet);
sendPacket(packet);
} finally {
announcerLock.unlock();
}
}
// Workaround for old packets. All uses of Client#announce(byte[]) should be migrated to Client#sendPacket(OutPacket)
private void sendPacket(final byte[] packet) {
announcerLock.lock();
try {
OutPacket outPacket = new ByteBufOutPacket();
outPacket.writeBytes(packet);
ioChannel.writeAndFlush(outPacket);
} finally {
announcerLock.unlock();
}
}
public void sendPacket(OutPacket outPacket) {
announcerLock.lock();
try {
ioChannel.writeAndFlush(outPacket.getBytes());
} finally { } finally {
announcerLock.unlock(); announcerLock.unlock();
} }
@@ -1335,14 +1490,14 @@ public class MapleClient {
if (!player.isAlive() || FieldLimit.CANNOTMIGRATE.check(player.getMap().getFieldLimit())) { if (!player.isAlive() || FieldLimit.CANNOTMIGRATE.check(player.getMap().getFieldLimit())) {
announce(MaplePacketCreator.enableActions()); announce(MaplePacketCreator.enableActions());
return; return;
} else if(MapleMiniDungeonInfo.isDungeonMap(player.getMapId())) { } else if (MapleMiniDungeonInfo.isDungeonMap(player.getMapId())) {
announce(MaplePacketCreator.serverNotice(5, "Changing channels or entering Cash Shop or MTS are disabled when inside a Mini-Dungeon.")); announce(MaplePacketCreator.serverNotice(5, "Changing channels or entering Cash Shop or MTS are disabled when inside a Mini-Dungeon."));
announce(MaplePacketCreator.enableActions()); announce(MaplePacketCreator.enableActions());
return; return;
} }
String[] socket = Server.getInstance().getInetSocket(this.getSession(), getWorld(), channel); String[] socket = Server.getInstance().getInetSocket(this, getWorld(), channel);
if(socket == null) { if (socket == null) {
announce(MaplePacketCreator.serverNotice(1, "Channel " + channel + " is currently disabled. Try another channel.")); announce(MaplePacketCreator.serverNotice(1, "Channel " + channel + " is currently disabled. Try another channel."));
announce(MaplePacketCreator.enableActions()); announce(MaplePacketCreator.enableActions());
return; return;
@@ -1385,27 +1540,23 @@ public class MapleClient {
return this.sessionId; return this.sessionId;
} }
public void setSessionId(long sessionId) { public boolean canRequestCharlist() {
this.sessionId = sessionId;
}
public boolean canRequestCharlist(){
return lastNpcClick + 877 < Server.getInstance().getCurrentTime(); return lastNpcClick + 877 < Server.getInstance().getCurrentTime();
} }
public boolean canClickNPC(){ public boolean canClickNPC() {
return lastNpcClick + 500 < Server.getInstance().getCurrentTime(); return lastNpcClick + 500 < Server.getInstance().getCurrentTime();
} }
public void setClickedNPC(){ public void setClickedNPC() {
lastNpcClick = Server.getInstance().getCurrentTime(); lastNpcClick = Server.getInstance().getCurrentTime();
} }
public void removeClickedNPC(){ public void removeClickedNPC() {
lastNpcClick = 0; lastNpcClick = 0;
} }
public int getVisibleWorlds(){ public int getVisibleWorlds() {
return visibleWorlds; return visibleWorlds;
} }
@@ -1438,16 +1589,12 @@ public class MapleClient {
announce(MaplePacketCreator.enableCSUse(player)); announce(MaplePacketCreator.enableCSUse(player));
} }
public String getNibbleHWID() {
return (String) session.getAttribute(MapleClient.CLIENT_NIBBLEHWID);
}
public boolean canBypassPin() { public boolean canBypassPin() {
return MapleLoginBypassCoordinator.getInstance().canLoginBypass(getNibbleHWID(), accId, false); return MapleLoginBypassCoordinator.getInstance().canLoginBypass(hwid, accId, false);
} }
public boolean canBypassPic() { public boolean canBypassPic() {
return MapleLoginBypassCoordinator.getInstance().canLoginBypass(getNibbleHWID(), accId, true); return MapleLoginBypassCoordinator.getInstance().canLoginBypass(hwid, accId, true);
} }
public int getLanguage() { public int getLanguage() {
@@ -26,7 +26,7 @@ import client.MapleCharacter;
import config.YamlConfig; import config.YamlConfig;
import net.server.Server; import net.server.Server;
import tools.FilePrinter; import tools.FilePrinter;
import tools.MapleLogger; import net.packet.logging.MapleLogger;
import tools.MaplePacketCreator; import tools.MaplePacketCreator;
/** /**
@@ -36,7 +36,7 @@ public class EnableAuthCommand extends Command {
public void execute(MapleClient c, String[] params) { public void execute(MapleClient c, String[] params) {
if (c.tryacquireClient()) { if (c.tryacquireClient()) {
try { try {
MapleLoginBypassCoordinator.getInstance().unregisterLoginBypassEntry(c.getNibbleHWID(), c.getAccID()); MapleLoginBypassCoordinator.getInstance().unregisterLoginBypassEntry(c.getHwid(), c.getAccID());
} finally { } finally {
c.releaseClient(); c.releaseClient();
} }
@@ -52,7 +52,7 @@ public class BanCommand extends Command {
MapleCharacter target = c.getChannelServer().getPlayerStorage().getCharacterByName(ign); MapleCharacter target = c.getChannelServer().getPlayerStorage().getCharacterByName(ign);
if (target != null) { if (target != null) {
String readableTargetName = MapleCharacter.makeMapleReadable(target.getName()); String readableTargetName = MapleCharacter.makeMapleReadable(target.getName());
String ip = target.getClient().getSession().getRemoteAddress().toString().split(":")[0]; String ip = target.getClient().getRemoteAddress();
//Ban ip //Ban ip
try (Connection con = DatabaseConnection.getConnection()) { try (Connection con = DatabaseConnection.getConnection()) {
if (ip.matches("/[0-9]{1,3}\\..*")) { if (ip.matches("/[0-9]{1,3}\\..*")) {
@@ -27,7 +27,7 @@ import client.MapleCharacter;
import client.MapleClient; import client.MapleClient;
import client.command.Command; import client.command.Command;
import net.server.Server; import net.server.Server;
import tools.MapleLogger; import net.packet.logging.MapleLogger;
import tools.MaplePacketCreator; import tools.MaplePacketCreator;
public class IgnoreCommand extends Command { public class IgnoreCommand extends Command {
@@ -26,7 +26,7 @@ package client.command.commands.gm3;
import client.MapleCharacter; import client.MapleCharacter;
import client.MapleClient; import client.MapleClient;
import client.command.Command; import client.command.Command;
import tools.MapleLogger; import net.packet.logging.MapleLogger;
public class IgnoredCommand extends Command { public class IgnoredCommand extends Command {
{ {
@@ -27,7 +27,7 @@ import client.MapleCharacter;
import client.MapleClient; import client.MapleClient;
import client.command.Command; import client.command.Command;
import net.server.Server; import net.server.Server;
import tools.MapleLogger; import net.packet.logging.MapleLogger;
import tools.MaplePacketCreator; import tools.MaplePacketCreator;
public class MonitorCommand extends Command { public class MonitorCommand extends Command {
@@ -26,7 +26,7 @@ package client.command.commands.gm3;
import client.MapleCharacter; import client.MapleCharacter;
import client.MapleClient; import client.MapleClient;
import client.command.Command; import client.command.Command;
import tools.MapleLogger; import net.packet.logging.MapleLogger;
public class MonitorsCommand extends Command { public class MonitorsCommand extends Command {
{ {
@@ -50,7 +50,7 @@ public class IpListCommand extends Command {
str += "\r\n" + GameConstants.WORLD_NAMES[w.getId()] + "\r\n"; str += "\r\n" + GameConstants.WORLD_NAMES[w.getId()] + "\r\n";
for (MapleCharacter chr : chars) { 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.MapleClient;
import client.command.Command; 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 @Override
public void execute(MapleClient c, String[] params) { 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]); byte worldb = Byte.parseByte(params[0]);
if (worldb <= (server.getWorldsSize() - 1)) { if (worldb <= (server.getWorldsSize() - 1)) {
try { try {
String[] socket = server.getInetSocket(c.getSession(), worldb, c.getChannel()); String[] socket = server.getInetSocket(c, worldb, c.getChannel());
c.getWorldServer().removePlayer(player); c.getWorldServer().removePlayer(player);
player.getMap().removePlayer(player);//LOL FORGOT THIS >< player.getMap().removePlayer(player);//LOL FORGOT THIS ><
player.setSessionTransitionState(); player.setSessionTransitionState();
@@ -3,7 +3,7 @@ package constants.net;
public class ServerConstants { public class ServerConstants {
//Server Version //Server Version
public static short VERSION = 83; public static final short VERSION = 83;
//Debug Variables //Debug Variables
public static int[] DEBUG_VALUES = new int[10]; // Field designed for packet testing purposes public static int[] DEBUG_VALUES = new int[10]; // Field designed for packet testing purposes
-305
View File
@@ -1,305 +0,0 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net;
import client.MapleClient;
import config.YamlConfig;
import constants.net.ServerConstants;
import net.server.Server;
import net.server.audit.LockCollector;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReentrantLock;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import net.server.coordinator.session.MapleSessionCoordinator;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import server.TimerManager;
import tools.FilePrinter;
import tools.MapleAESOFB;
import tools.MapleLogger;
import tools.MaplePacketCreator;
import tools.data.input.ByteArrayByteStream;
import tools.data.input.GenericSeekableLittleEndianAccessor;
import tools.data.input.SeekableLittleEndianAccessor;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicLong;
public class MapleServerHandler extends IoHandlerAdapter {
private final static Set<Short> ignoredDebugRecvPackets = new HashSet<>(Arrays.asList((short) 167, (short) 197, (short) 89, (short) 91, (short) 41, (short) 188, (short) 107));
private PacketProcessor processor;
private int world = -1, channel = -1;
private static final SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm");
private static AtomicLong sessionId = new AtomicLong(7777);
private MonitoredReentrantLock idleLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.SRVHANDLER_IDLE, true);
private MonitoredReentrantLock tempLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.SRVHANDLER_TEMP, true);
private Map<MapleClient, Long> idleSessions = new HashMap<>(100);
private Map<MapleClient, Long> tempIdleSessions = new HashMap<>();
private ScheduledFuture<?> idleManager = null;
public MapleServerHandler() {
this.processor = PacketProcessor.getProcessor(-1, -1);
idleManagerTask();
}
public MapleServerHandler(int world, int channel) {
this.processor = PacketProcessor.getProcessor(world, channel);
this.world = world;
this.channel = channel;
idleManagerTask();
}
@Override
public void exceptionCaught(IoSession session, Throwable cause) {
if (cause instanceof IOException) {
closeMapleSession(session);
} else {
MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
if (client != null && client.getPlayer() != null) {
FilePrinter.printError(FilePrinter.EXCEPTION_CAUGHT, cause, "Exception caught by: " + client.getPlayer());
}
}
}
private boolean isLoginServerHandler() {
return channel == -1 && world == -1;
}
@Override
public void sessionOpened(IoSession session) {
String remoteHost;
try {
remoteHost = ((InetSocketAddress) session.getRemoteAddress()).getAddress().getHostAddress();
if (remoteHost == null) {
remoteHost = "null";
} else {
remoteHost = MapleSessionCoordinator.fetchRemoteAddress(remoteHost); // thanks dyz for noticing Local/LAN/WAN connections not interacting properly
}
} catch (NullPointerException npe) { // thanks Agassy, Alchemist for pointing out possibility of remoteHost = null.
remoteHost = "null";
}
session.setAttribute(MapleClient.CLIENT_REMOTE_ADDRESS, remoteHost);
if (!Server.getInstance().isOnline()) {
MapleSessionCoordinator.getInstance().closeSession(session, true);
return;
}
if (!isLoginServerHandler()) {
if (Server.getInstance().getChannel(world, channel) == null) {
MapleSessionCoordinator.getInstance().closeSession(session, true);
return;
}
} else {
if (!MapleSessionCoordinator.getInstance().canStartLoginSession(session)) {
return;
}
FilePrinter.print(FilePrinter.SESSION, "IoSession with " + session.getRemoteAddress() + " opened on " + sdf.format(Calendar.getInstance().getTime()), false);
}
byte[] ivRecv = {70, 114, 122, 82};
byte[] ivSend = {82, 48, 120, 115};
ivRecv[3] = (byte) (Math.random() * 255);
ivSend[3] = (byte) (Math.random() * 255);
MapleAESOFB sendCypher = new MapleAESOFB(ivSend, (short) (0xFFFF - ServerConstants.VERSION));
MapleAESOFB recvCypher = new MapleAESOFB(ivRecv, ServerConstants.VERSION);
MapleClient client = new MapleClient(sendCypher, recvCypher, session);
client.setWorld(world);
client.setChannel(channel);
client.setSessionId(sessionId.getAndIncrement()); // Generates a reasonable session id.
session.write(MaplePacketCreator.getHello(ServerConstants.VERSION, ivSend, ivRecv));
session.setAttribute(MapleClient.CLIENT_KEY, client);
}
private void closeMapleSession(IoSession session) {
if (isLoginServerHandler()) {
MapleSessionCoordinator.getInstance().closeLoginSession(session);
} else {
MapleSessionCoordinator.getInstance().closeSession(session, null);
}
MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
if (client != null) {
try {
// client freeze issues on session transition states found thanks to yolinlin, Omo Oppa, Nozphex
if (!session.containsAttribute(MapleClient.CLIENT_TRANSITION)) {
client.disconnect(false, false);
}
} catch (Throwable t) {
FilePrinter.printError(FilePrinter.ACCOUNT_STUCK, t);
} finally {
session.close();
session.removeAttribute(MapleClient.CLIENT_KEY);
//client.empty();
}
}
}
@Override
public void sessionClosed(IoSession session) throws Exception {
closeMapleSession(session);
super.sessionClosed(session);
}
@Override
public void messageReceived(IoSession session, Object message) {
byte[] content = (byte[]) message;
SeekableLittleEndianAccessor slea = new GenericSeekableLittleEndianAccessor(new ByteArrayByteStream(content));
short packetId = slea.readShort();
MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
if(YamlConfig.config.server.USE_DEBUG_SHOW_RCVD_PACKET && !ignoredDebugRecvPackets.contains(packetId)) System.out.println("Received packet id " + packetId);
final MaplePacketHandler packetHandler = processor.getHandler(packetId);
if (packetHandler != null && packetHandler.validateState(client)) {
try {
MapleLogger.logRecv(client, packetId, message);
packetHandler.handlePacket(slea, client);
} catch (final Throwable t) {
FilePrinter.printError(FilePrinter.PACKET_HANDLER + packetHandler.getClass().getName() + ".txt", t, "Error for " + (client.getPlayer() == null ? "" : "player ; " + client.getPlayer() + " on map ; " + client.getPlayer().getMapId() + " - ") + "account ; " + client.getAccountName() + "\r\n" + slea.toString());
//client.announce(MaplePacketCreator.enableActions());//bugs sometimes
}
client.updateLastPacket();
}
}
@Override
public void messageSent(IoSession session, Object message) {
byte[] content = (byte[]) message;
SeekableLittleEndianAccessor slea = new GenericSeekableLittleEndianAccessor(new ByteArrayByteStream(content));
slea.readShort(); //packetId
}
@Override
public void sessionIdle(final IoSession session, final IdleStatus status) throws Exception {
MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
if (client != null) {
registerIdleSession(client);
}
super.sessionIdle(session, status);
}
private void registerIdleSession(MapleClient c) {
if(idleLock.tryLock()) {
try {
idleSessions.put(c, Server.getInstance().getCurrentTime());
c.announce(MaplePacketCreator.getPing());
} finally {
idleLock.unlock();
}
} else {
tempLock.lock();
try {
tempIdleSessions.put(c, Server.getInstance().getCurrentTime());
c.announce(MaplePacketCreator.getPing());
} finally {
tempLock.unlock();
}
}
}
private void manageIdleSessions() {
long timeNow = Server.getInstance().getCurrentTime();
long timeThen = timeNow - 15000;
Set<MapleClient> pingClients = new HashSet<>();
idleLock.lock();
try {
for(Entry<MapleClient, Long> mc : idleSessions.entrySet()) {
if(timeNow - mc.getValue() >= 15000) {
pingClients.add(mc.getKey());
}
}
idleSessions.clear();
if(!tempIdleSessions.isEmpty()) {
tempLock.lock();
try {
for(Entry<MapleClient, Long> mc : tempIdleSessions.entrySet()) {
idleSessions.put(mc.getKey(), mc.getValue());
}
tempIdleSessions.clear();
} finally {
tempLock.unlock();
}
}
} finally {
idleLock.unlock();
}
for(MapleClient c : pingClients) {
c.testPing(timeThen);
}
}
private void idleManagerTask() {
this.idleManager = TimerManager.getInstance().register(() -> manageIdleSessions(), 10000);
}
private void cancelIdleManagerTask() {
this.idleManager.cancel(false);
this.idleManager = null;
}
private void disposeLocks() {
LockCollector.getInstance().registerDisposeAction(() -> emptyLocks());
}
private void emptyLocks() {
idleLock.dispose();
tempLock.dispose();
}
public void dispose() {
cancelIdleManagerTask();
idleLock.lock();
try {
idleSessions.clear();
} finally {
idleLock.unlock();
}
tempLock.lock();
try {
tempIdleSessions.clear();
} finally {
tempLock.unlock();
}
disposeLocks();
}
}
+32 -31
View File
@@ -21,34 +21,16 @@
*/ */
package net; package net;
import java.util.LinkedHashMap; import net.netty.LoginServer;
import java.util.Map;
import net.opcodes.RecvOpcode; import net.opcodes.RecvOpcode;
import net.server.channel.handlers.*; import net.server.channel.handlers.*;
import net.server.handlers.CustomPacketHandler; import net.server.handlers.CustomPacketHandler;
import net.server.handlers.KeepAliveHandler; import net.server.handlers.KeepAliveHandler;
import net.server.handlers.LoginRequiringNoOpHandler; import net.server.handlers.LoginRequiringNoOpHandler;
import net.server.handlers.login.AcceptToSHandler; import net.server.handlers.login.*;
import net.server.handlers.login.AfterLoginHandler;
import net.server.handlers.login.CharSelectedHandler; import java.util.LinkedHashMap;
import net.server.handlers.login.CharSelectedWithPicHandler; import java.util.Map;
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;
public final class PacketProcessor { public final class PacketProcessor {
@@ -65,6 +47,14 @@ public final class PacketProcessor {
handlers = new MaplePacketHandler[maxRecvOp + 1]; 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) { public MaplePacketHandler getHandler(short packetId) {
if (packetId > handlers.length) { if (packetId > handlers.length) {
return null; return null;
@@ -86,12 +76,12 @@ public final class PacketProcessor {
} }
public synchronized static PacketProcessor getProcessor(int world, int channel) { public synchronized static PacketProcessor getProcessor(int world, int channel) {
final String lolpair = world + " " + channel; final String processorId = world + " " + channel;
PacketProcessor processor = instances.get(lolpair); PacketProcessor processor = instances.get(processorId);
if (processor == null) { if (processor == null) {
processor = new PacketProcessor(); processor = new PacketProcessor();
processor.reset(channel); processor.reset(channel);
instances.put(lolpair, processor); instances.put(processorId, processor);
} }
return processor; return processor;
} }
@@ -99,10 +89,21 @@ public final class PacketProcessor {
public void reset(int channel) { public void reset(int channel) {
handlers = new MaplePacketHandler[handlers.length]; handlers = new MaplePacketHandler[handlers.length];
registerCommonHandlers();
if (channel < 0) {
registerLoginHandlers();
} else {
registerChannelHandlers();
}
}
private void registerCommonHandlers() {
registerHandler(RecvOpcode.PONG, new KeepAliveHandler()); registerHandler(RecvOpcode.PONG, new KeepAliveHandler());
registerHandler(RecvOpcode.CUSTOM_PACKET, new CustomPacketHandler()); registerHandler(RecvOpcode.CUSTOM_PACKET, new CustomPacketHandler());
if (channel < 0) { }
//LOGIN HANDLERS
private void registerLoginHandlers() {
registerHandler(RecvOpcode.ACCEPT_TOS, new AcceptToSHandler()); registerHandler(RecvOpcode.ACCEPT_TOS, new AcceptToSHandler());
registerHandler(RecvOpcode.AFTER_LOGIN, new AfterLoginHandler()); registerHandler(RecvOpcode.AFTER_LOGIN, new AfterLoginHandler());
registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler()); registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler());
@@ -124,8 +125,9 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.SET_GENDER, new SetGenderHandler()); registerHandler(RecvOpcode.SET_GENDER, new SetGenderHandler());
registerHandler(RecvOpcode.VIEW_ALL_WITH_PIC, new ViewAllCharSelectedWithPicHandler()); registerHandler(RecvOpcode.VIEW_ALL_WITH_PIC, new ViewAllCharSelectedWithPicHandler());
registerHandler(RecvOpcode.VIEW_ALL_PIC_REGISTER, new ViewAllCharRegisterPicHandler()); registerHandler(RecvOpcode.VIEW_ALL_PIC_REGISTER, new ViewAllCharRegisterPicHandler());
} else { }
//CHANNEL HANDLERS
private void registerChannelHandlers() {
registerHandler(RecvOpcode.NAME_TRANSFER, new TransferNameHandler()); registerHandler(RecvOpcode.NAME_TRANSFER, new TransferNameHandler());
registerHandler(RecvOpcode.CHECK_CHAR_NAME, new TransferNameResultHandler()); registerHandler(RecvOpcode.CHECK_CHAR_NAME, new TransferNameResultHandler());
registerHandler(RecvOpcode.WORLD_TRANSFER, new TransferWorldHandler()); registerHandler(RecvOpcode.WORLD_TRANSFER, new TransferWorldHandler());
@@ -275,5 +277,4 @@ public final class PacketProcessor {
registerHandler(RecvOpcode.USE_ITEMUI, new RaiseIncExpHandler()); registerHandler(RecvOpcode.USE_ITEMUI, new RaiseIncExpHandler());
registerHandler(RecvOpcode.CHANGE_QUICKSLOT, new QuickslotKeyMappedModifiedHandler()); registerHandler(RecvOpcode.CHANGE_QUICKSLOT, new QuickslotKeyMappedModifiedHandler());
} }
}
} }
@@ -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;
}
}
@@ -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 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/>. 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.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
@@ -30,52 +34,73 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
public class MapleAESOFB { public class MapleAESOFB {
private byte[] iv; private static final Logger log = LoggerFactory.getLogger(MapleAESOFB.class);
private Cipher cipher;
private short mapleVersion;
private final static SecretKeySpec skey = new SecretKeySpec( 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, private static final byte[] funnyBytes = new byte[]{
(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) 0xEC, (byte) 0x3F, (byte) 0x77, (byte) 0xA4, (byte) 0x45, (byte) 0xD0, (byte) 0x71, (byte) 0xBF,
(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) 0xB7, (byte) 0x98, (byte) 0x20, (byte) 0xFC, (byte) 0x4B, (byte) 0xE9, (byte) 0xB3, (byte) 0xE1,
(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) 0x5C, (byte) 0x22, (byte) 0xF7, (byte) 0x0C, (byte) 0x44, (byte) 0x1B, (byte) 0x81, (byte) 0xBD,
(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) 0x63, (byte) 0x8D, (byte) 0xD4, (byte) 0xC3, (byte) 0xF2, (byte) 0x10, (byte) 0x19, (byte) 0xE0,
(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) 0xFB, (byte) 0xA1, (byte) 0x6E, (byte) 0x66, (byte) 0xEA, (byte) 0xAE, (byte) 0xD6, (byte) 0xCE,
(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) 0x06, (byte) 0x18, (byte) 0x4E, (byte) 0xEB, (byte) 0x78, (byte) 0x95, (byte) 0xDB, (byte) 0xBA,
(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) 0xB6, (byte) 0x42, (byte) 0x7A, (byte) 0x2A, (byte) 0x83, (byte) 0x0B, (byte) 0x54, (byte) 0x67,
(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) 0x6D, (byte) 0xE8, (byte) 0x65, (byte) 0xE7, (byte) 0x2F, (byte) 0x07, (byte) 0xF3, (byte) 0xAA,
(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) 0x27, (byte) 0x7B, (byte) 0x85, (byte) 0xB0, (byte) 0x26, (byte) 0xFD, (byte) 0x8B, (byte) 0xA9,
(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) 0xFA, (byte) 0xBE, (byte) 0xA8, (byte) 0xD7, (byte) 0xCB, (byte) 0xCC, (byte) 0x92, (byte) 0xDA,
(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) 0xF9, (byte) 0x93, (byte) 0x60, (byte) 0x2D, (byte) 0xDD, (byte) 0xD2, (byte) 0xA2, (byte) 0x9B,
(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) 0x39, (byte) 0x5F, (byte) 0x82, (byte) 0x21, (byte) 0x4C, (byte) 0x69, (byte) 0xF8, (byte) 0x31,
(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) 0x87, (byte) 0xEE, (byte) 0x8E, (byte) 0xAD, (byte) 0x8C, (byte) 0x6A, (byte) 0xBC, (byte) 0xB5,
(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) 0x6B, (byte) 0x59, (byte) 0x13, (byte) 0xF1, (byte) 0x04, (byte) 0x00, (byte) 0xF6, (byte) 0x5A,
(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) 0x35, (byte) 0x79, (byte) 0x48, (byte) 0x8F, (byte) 0x15, (byte) 0xCD, (byte) 0x97, (byte) 0x57,
(byte) 0xC6, (byte) 0xE5, (byte) 0x08, (byte) 0x49}; (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 { try {
cipher = Cipher.getInstance("AES"); cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skey); cipher.init(Cipher.ENCRYPT_MODE, skey);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
System.out.println("ERROR " + e); log.warn("Cypher initialization error with skey: {}", skey, e);
} catch (NoSuchPaddingException e) { throw new RuntimeException(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.");
} }
this.setIv(iv);
this.iv = iv.getBytes();
this.mapleVersion = (short) (((mapleVersion >> 8) & 0xFF) | ((mapleVersion << 8) & 0xFF00)); 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) { private static byte[] multiplyBytes(byte[] in, int count, int mul) {
byte[] ret = new byte[count * mul]; final int size = count * mul;
for (int x = 0; x < count * mul; x++) { byte[] ret = new byte[size];
for (int x = 0; x < size; x++) {
ret[x] = in[x % count]; ret[x] = in[x % count];
} }
return ret; return ret;
@@ -94,12 +119,8 @@ public class MapleAESOFB {
if ((x - start) % myIv.length == 0) { if ((x - start) % myIv.length == 0) {
try { try {
byte[] newIv = cipher.doFinal(myIv); byte[] newIv = cipher.doFinal(myIv);
for (int j = 0; j < myIv.length; j++) { System.arraycopy(newIv, 0, myIv, 0, myIv.length);
myIv[j] = newIv[j]; } catch (IllegalBlockSizeException | BadPaddingException e) {
}
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@@ -137,11 +158,12 @@ public class MapleAESOFB {
return packetLength; return packetLength;
} }
public boolean checkPacket(byte[] packet) { private boolean checkPacket(byte[] packet) {
return ((((packet[0] ^ iv[2]) & 0xFF) == ((mapleVersion >> 8) & 0xFF)) && (((packet[1] ^ iv[3]) & 0xFF) == (mapleVersion & 0xFF))); 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]; byte[] packetHeaderBuf = new byte[2];
packetHeaderBuf[0] = (byte) ((packetHeader >> 24) & 0xFF); packetHeaderBuf[0] = (byte) ((packetHeader >> 24) & 0xFF);
packetHeaderBuf[1] = (byte) ((packetHeader >> 16) & 0xFF); packetHeaderBuf[1] = (byte) ((packetHeader >> 16) & 0xFF);
@@ -19,7 +19,7 @@
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package net.mina; package net.encryption;
public class MapleCustomEncryption { public class MapleCustomEncryption {
private static byte rollLeft(byte in, int count) { private static byte rollLeft(byte in, int count) {
@@ -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()));
}
}
@@ -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;
}
}
@@ -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 {}
}
@@ -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();
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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
View File
@@ -0,0 +1,38 @@
package net.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class LoginServer extends AbstractServer {
public static final int WORLD_ID = -1;
public static final int CHANNEL_ID = -1;
private Channel channel;
public LoginServer(int port) {
super(port);
}
@Override
public void start() {
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap()
.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new LoginServerInitializer());
this.channel = bootstrap.bind(port).syncUninterruptibly().channel();
}
@Override
public void stop() {
if (channel == null) {
throw new IllegalStateException("Must start LoginServer before stopping it");
}
channel.close().syncUninterruptibly();
}
}
@@ -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);
}
}
@@ -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 { public class ByteBufOutPacket implements OutPacket {
private final ByteBuf byteBuf; private final ByteBuf byteBuf;
@Deprecated(forRemoval = true)
public ByteBufOutPacket() {
this.byteBuf = Unpooled.buffer();
}
public ByteBufOutPacket(SendOpcode op) { public ByteBufOutPacket(SendOpcode op) {
ByteBuf byteBuf = Unpooled.buffer(); ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeShortLE((short) op.getValue()); byteBuf.writeShortLE((short) op.getValue());
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -0,0 +1,7 @@
package net.packet.logging;
import net.packet.Packet;
public interface PacketLogger {
void log(Packet packet);
}
+19 -32
View File
@@ -35,8 +35,7 @@ import constants.game.GameConstants;
import constants.inventory.ItemConstants; import constants.inventory.ItemConstants;
import constants.net.OpcodeConstants; import constants.net.OpcodeConstants;
import constants.net.ServerConstants; import constants.net.ServerConstants;
import net.MapleServerHandler; import net.netty.LoginServer;
import net.mina.MapleCodecFactory;
import net.server.audit.ThreadTracker; import net.server.audit.ThreadTracker;
import net.server.audit.locks.MonitoredLockType; import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReadLock; 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.MonitoredReentrantLockFactory;
import net.server.audit.locks.factory.MonitoredWriteLockFactory; import net.server.audit.locks.factory.MonitoredWriteLockFactory;
import net.server.channel.Channel; 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.MapleAlliance;
import net.server.guild.MapleGuild; import net.server.guild.MapleGuild;
import net.server.guild.MapleGuildCharacter; import net.server.guild.MapleGuildCharacter;
import net.server.task.*; import net.server.task.*;
import net.server.world.World; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import server.CashShop.CashItemFactory; import server.CashShop.CashItemFactory;
@@ -73,8 +66,6 @@ import tools.DatabaseConnection;
import tools.FilePrinter; import tools.FilePrinter;
import tools.Pair; import tools.Pair;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.Security; import java.security.Security;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
@@ -105,7 +96,7 @@ public class Server {
private static final Map<Integer, Integer> couponRates = new HashMap<>(30); private static final Map<Integer, Integer> couponRates = new HashMap<>(30);
private static final List<Integer> activeCoupons = new LinkedList<>(); private static final List<Integer> activeCoupons = new LinkedList<>();
private IoAcceptor acceptor; private LoginServer loginServer;
private List<Map<Integer, String>> channels = new LinkedList<>(); private List<Map<Integer, String>> channels = new LinkedList<>();
private List<World> worlds = new ArrayList<>(); private List<World> worlds = new ArrayList<>();
private final Properties subnetInfo = new Properties(); private final Properties subnetInfo = new Properties();
@@ -291,13 +282,13 @@ public class Server {
} }
} }
public String[] getInetSocket(IoSession session, int world, int channel) { public String[] getInetSocket(MapleClient client, int world, int channel) {
String remoteIp = MapleSessionCoordinator.getSessionRemoteAddress(session); String remoteIp = client.getRemoteAddress();
String[] hostAddress = getIP(world, channel).split(":"); String[] hostAddress = getIP(world, channel).split(":");
if (MapleSessionCoordinator.isLocalAddress(remoteIp)) { if (IpAddresses.isLocalAddress(remoteIp)) {
hostAddress[0] = YamlConfig.config.server.LOCALHOST; hostAddress[0] = YamlConfig.config.server.LOCALHOST;
} else if (MapleSessionCoordinator.isLanAddress(remoteIp)) { } else if (IpAddresses.isLanAddress(remoteIp)) {
hostAddress[0] = YamlConfig.config.server.LANHOST; hostAddress[0] = YamlConfig.config.server.LANHOST;
} }
@@ -906,17 +897,8 @@ public class Server {
} }
} }
IoBuffer.setUseDirectBuffer(false); // join IO operations performed by lxconan // acceptor = initAcceptor(8484);
IoBuffer.setAllocator(new SimpleBufferAllocator()); loginServer = initLoginServer(8484);
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();
}
log.info("Listening on port 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 { private static void setAllLoggedOut(Connection con) throws SQLException {
try (PreparedStatement ps = con.prepareStatement("UPDATE accounts SET loggedin = 0")) { try (PreparedStatement ps = con.prepareStatement("UPDATE accounts SET loggedin = 0")) {
ps.executeUpdate(); ps.executeUpdate();
@@ -1776,7 +1764,7 @@ public class Server {
} }
private static String getRemoteHost(MapleClient client) { private static String getRemoteHost(MapleClient client) {
return MapleSessionCoordinator.getSessionRemoteHost(client.getSession()); return SessionCoordinator.getSessionRemoteHost(client);
} }
public void setCharacteridInTransition(MapleClient client, int charId) { public void setCharacteridInTransition(MapleClient client, int charId) {
@@ -1878,7 +1866,7 @@ public class Server {
if (c.isLoggedIn()) { if (c.isLoggedIn()) {
c.disconnect(false, false); c.disconnect(false, false);
} else { } else {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); SessionCoordinator.getInstance().closeSession(c, true);
} }
} }
} }
@@ -1943,8 +1931,7 @@ public class Server {
TimerManager.getInstance().stop(); TimerManager.getInstance().stop();
System.out.println("Worlds + Channels are offline."); System.out.println("Worlds + Channels are offline.");
acceptor.unbind(); loginServer.stop();
acceptor = null;
if (!restart) { // shutdown hook deadlocks if System.exit() method is used within its body chores, thanks MIKE for pointing that out 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(); new Thread(() -> System.exit(0)).start();
} else { } else {
+21 -29
View File
@@ -24,8 +24,7 @@ package net.server.channel;
import client.MapleCharacter; import client.MapleCharacter;
import config.YamlConfig; import config.YamlConfig;
import constants.game.GameConstants; import constants.game.GameConstants;
import net.MapleServerHandler; import net.netty.ChannelServer;
import net.mina.MapleCodecFactory;
import net.server.PlayerStorage; import net.server.PlayerStorage;
import net.server.Server; import net.server.Server;
import net.server.audit.LockCollector; import net.server.audit.LockCollector;
@@ -39,13 +38,6 @@ import net.server.services.type.ChannelServices;
import net.server.world.MapleParty; import net.server.world.MapleParty;
import net.server.world.MaplePartyCharacter; import net.server.world.MaplePartyCharacter;
import net.server.world.World; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import scripting.event.EventScriptManager; import scripting.event.EventScriptManager;
@@ -58,19 +50,22 @@ import tools.MaplePacketCreator;
import tools.Pair; import tools.Pair;
import java.io.File; import java.io.File;
import java.net.InetSocketAddress;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
public final class Channel { public final class Channel {
private static final Logger log = LoggerFactory.getLogger(Channel.class); 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 PlayerStorage players = new PlayerStorage();
private int world, channel; private ChannelServer channelServer;
private IoAcceptor acceptor; private String serverMessage;
private String ip, serverMessage;
private MapleMapManager mapManager; private MapleMapManager mapManager;
private EventScriptManager eventSM; private EventScriptManager eventSM;
private ServicesManager services; 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.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.mapManager = new MapleMapManager(null, world, channel);
this.port = BASE_PORT + (this.channel - 1) + (world * 100);
this.ip = YamlConfig.config.server.HOST + ":" + port;
try { try {
port = 7575 + this.channel - 1; this.channelServer = initServer(port, world, channel);
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);
expedType.addAll(Arrays.asList(MapleExpeditionType.values())); 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 if (Server.getInstance().isOnline()) { // postpone event loading to improve boot time... thanks Riizade, daronhudson for noticing slow startup times
@@ -153,10 +141,16 @@ public final class Channel {
log.info("Channel {}: Listening on port {}", getId(), port); log.info("Channel {}: Listening on port {}", getId(), port);
} catch (Exception e) { } 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(){ public synchronized void reloadEventScriptManager(){
if (finishedShutdown) { if (finishedShutdown) {
return; return;
@@ -188,9 +182,7 @@ public final class Channel {
closeChannelSchedules(); closeChannelSchedules();
players = null; players = null;
MapleServerHandler handler = (MapleServerHandler) acceptor.getHandler(); channelServer.stop();
handler.dispose();
acceptor.unbind();
finishedShutdown = true; finishedShutdown = true;
System.out.println("Successfully shut down Channel " + channel + " on World " + world + "\r\n"); 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); target = c.getChannelServer().getPlayerStorage().getCharacterByName(victim);
if (target != null) { if (target != null) {
String readableTargetName = MapleCharacter.makeMapleReadable(target.getName()); String readableTargetName = MapleCharacter.makeMapleReadable(target.getName());
String ip = target.getClient().getSession().getRemoteAddress().toString().split(":")[0]; String ip = target.getClient().getRemoteAddress();
reason += readableTargetName + " (IP: " + ip + ")"; reason += readableTargetName + " (IP: " + ip + ")";
if (duration == -1) { if (duration == -1) {
target.ban(description + " " + reason); target.ban(description + " " + reason);
@@ -31,14 +31,14 @@ import net.server.PlayerBuffValueHolder;
import net.server.Server; import net.server.Server;
import net.server.channel.Channel; import net.server.channel.Channel;
import net.server.channel.CharacterIdChannelPair; 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.coordinator.world.MapleEventRecallCoordinator;
import net.server.guild.MapleAlliance; import net.server.guild.MapleAlliance;
import net.server.guild.MapleGuild; import net.server.guild.MapleGuild;
import net.server.world.MaplePartyCharacter; import net.server.world.MaplePartyCharacter;
import net.server.world.PartyOperation; import net.server.world.PartyOperation;
import net.server.world.World; import net.server.world.World;
import org.apache.mina.core.session.IoSession;
import scripting.event.EventInstanceManager; import scripting.event.EventInstanceManager;
import server.life.MobSkill; import server.life.MobSkill;
import tools.DatabaseConnection; import tools.DatabaseConnection;
@@ -58,7 +58,7 @@ import java.util.stream.Collectors;
public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { 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) { private boolean tryAcquireAccount(int accId) {
synchronized (attemptingLoginAccounts) { synchronized (attemptingLoginAccounts) {
@@ -84,46 +84,46 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
@Override @Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { 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(); final Server server = Server.getInstance();
if (c.tryacquireClient()) { // thanks MedicOP for assisting on concurrency protection here if (!c.tryacquireClient()) {
// thanks MedicOP for assisting on concurrency protection here
c.announce(MaplePacketCreator.getAfterLoginError(10));
}
try { try {
World wserv = server.getWorld(c.getWorld()); World wserv = server.getWorld(c.getWorld());
if(wserv == null) { if (wserv == null) {
c.disconnect(true, false); c.disconnect(true, false);
return; return;
} }
Channel cserv = wserv.getChannel(c.getChannel()); Channel cserv = wserv.getChannel(c.getChannel());
if(cserv == null) { if (cserv == null) {
c.setChannel(1); c.setChannel(1);
cserv = wserv.getChannel(c.getChannel()); cserv = wserv.getChannel(c.getChannel());
if(cserv == null) { if (cserv == null) {
c.disconnect(true, false); c.disconnect(true, false);
return; return;
} }
} }
MapleCharacter player = wserv.getPlayerStorage().getCharacterById(cid); MapleCharacter player = wserv.getPlayerStorage().getCharacterById(cid);
IoSession session = c.getSession();
String remoteHwid; final Hwid hwid;
if (player == null) { if (player == null) {
remoteHwid = MapleSessionCoordinator.getInstance().pickLoginSessionHwid(session); hwid = SessionCoordinator.getInstance().pickLoginSessionHwid(c);
if (remoteHwid == null) { if (hwid == null) {
c.disconnect(true, false); c.disconnect(true, false);
return; return;
} }
} else { } else {
remoteHwid = player.getClient().getHWID(); hwid = player.getClient().getHwid();
} }
int hwidLen = remoteHwid.length(); c.setHwid(hwid);
session.setAttribute(MapleClient.CLIENT_HWID, remoteHwid);
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, remoteHwid.substring(hwidLen - 8, hwidLen));
c.setHWID(remoteHwid);
if (!server.validateCharacteridInTransition(c, cid)) { if (!server.validateCharacteridInTransition(c, cid)) {
c.disconnect(true, false); c.disconnect(true, false);
@@ -215,7 +215,7 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.getCharInfo(player)); c.announce(MaplePacketCreator.getCharInfo(player));
if (!player.isHidden()) { if (!player.isHidden()) {
if(player.isGM() && YamlConfig.config.server.USE_AUTOHIDE_GM) { if (player.isGM() && YamlConfig.config.server.USE_AUTOHIDE_GM) {
player.toggleHide(true); player.toggleHide(true);
} }
} }
@@ -246,9 +246,9 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.loadFamily(player)); c.announce(MaplePacketCreator.loadFamily(player));
if (player.getFamilyId() > 0) { if (player.getFamilyId() > 0) {
MapleFamily f = wserv.getFamily(player.getFamilyId()); MapleFamily f = wserv.getFamily(player.getFamilyId());
if(f != null) { if (f != null) {
MapleFamilyEntry familyEntry = f.getEntryByID(player.getId()); MapleFamilyEntry familyEntry = f.getEntryByID(player.getId());
if(familyEntry != null) { if (familyEntry != null) {
familyEntry.setCharacter(player); familyEntry.setCharacter(player);
player.setFamilyEntry(familyEntry); player.setFamilyEntry(familyEntry);
@@ -264,6 +264,7 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
} else { } else {
c.announce(MaplePacketCreator.getFamilyInfo(null)); c.announce(MaplePacketCreator.getFamilyInfo(null));
} }
if (player.getGuildId() > 0) { if (player.getGuildId() > 0) {
MapleGuild playerGuild = server.getGuild(player.getGuildId(), player.getWorld(), player); MapleGuild playerGuild = server.getGuild(player.getGuildId(), player.getWorld(), player);
if (playerGuild == null) { if (playerGuild == null) {
@@ -315,7 +316,7 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
MapleInventory eqpInv = player.getInventory(MapleInventoryType.EQUIPPED); MapleInventory eqpInv = player.getInventory(MapleInventoryType.EQUIPPED);
eqpInv.lockInventory(); eqpInv.lockInventory();
try { try {
for(Item it : eqpInv.list()) { for (Item it : eqpInv.list()) {
player.equippedItem((Equip) it); player.equippedItem((Equip) it);
} }
} finally { } finally {
@@ -336,8 +337,8 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
player.checkBerserk(player.isHidden()); player.checkBerserk(player.isHidden());
if (newcomer) { if (newcomer) {
for(MaplePet pet : player.getPets()) { for (MaplePet pet : player.getPets()) {
if(pet != null) { if (pet != null) {
wserv.registerPetHunger(player, player.getPetIndex(pet)); wserv.registerPetHunger(player, player.getPetIndex(pet));
} }
} }
@@ -354,18 +355,18 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
player.announce(MaplePacketCreator.earnTitleMessage("You can vote now! Vote and earn a vote point!")); player.announce(MaplePacketCreator.earnTitleMessage("You can vote now! Vote and earn a vote point!"));
} }
*/ */
if (player.isGM()){ if (player.isGM()) {
Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.earnTitleMessage((player.gmLevel() < 6 ? "GM " : "Admin ") + player.getName() + " has logged in")); Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.earnTitleMessage((player.gmLevel() < 6 ? "GM " : "Admin ") + player.getName() + " has logged in"));
} }
if(diseases != null) { if (diseases != null) {
for(Entry<MapleDisease, Pair<Long, MobSkill>> e : diseases.entrySet()) { 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())); final List<Pair<MapleDisease, Integer>> debuff = Collections.singletonList(new Pair<>(e.getKey(), e.getValue().getRight().getX()));
c.announce(MaplePacketCreator.giveDebuff(debuff, e.getValue().getRight())); c.announce(MaplePacketCreator.giveDebuff(debuff, e.getValue().getRight()));
} }
} }
} else { } else {
if(player.isRidingBattleship()) { if (player.isRidingBattleship()) {
player.announceBattleshipHp(); player.announceBattleshipHp();
} }
} }
@@ -385,17 +386,20 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
if (player.getMap().getHPDec() > 0) player.resetHpDecreaseTask(); if (player.getMap().getHPDec() > 0) player.resetHpDecreaseTask();
player.resetPlayerRates(); player.resetPlayerRates();
if(YamlConfig.config.server.USE_ADD_RATES_BY_LEVEL == true) player.setPlayerRates(); if (YamlConfig.config.server.USE_ADD_RATES_BY_LEVEL) {
player.setPlayerRates();
}
player.setWorldRates(); player.setWorldRates();
player.updateCouponRates(); player.updateCouponRates();
player.receivePartyMemberHP(); player.receivePartyMemberHP();
if(player.getPartnerId() > 0) { if (player.getPartnerId() > 0) {
int partnerId = player.getPartnerId(); int partnerId = player.getPartnerId();
final MapleCharacter partner = wserv.getPlayerStorage().getCharacterById(partnerId); final MapleCharacter partner = wserv.getPlayerStorage().getCharacterById(partnerId);
if(partner != null && !partner.isAwayFromWorld()) { if (partner != null && !partner.isAwayFromWorld()) {
player.announce(Wedding.OnNotifyWeddingPartnerTransfer(partnerId, partner.getMapId())); player.announce(Wedding.OnNotifyWeddingPartnerTransfer(partnerId, partner.getMapId()));
partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(player.getId(), player.getMapId())); partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(player.getId(), player.getMapId()));
} }
@@ -426,15 +430,12 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.setNPCScriptable(npcsIds)); c.announce(MaplePacketCreator.setNPCScriptable(npcsIds));
} }
if(newcomer) player.setLoginTime(System.currentTimeMillis()); if (newcomer) player.setLoginTime(System.currentTimeMillis());
} catch(Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
c.releaseClient(); c.releaseClient();
} }
} else {
c.announce(MaplePacketCreator.getAfterLoginError(10));
}
} }
private static void showDueyNotification(MapleClient c, MapleCharacter player) { private static void showDueyNotification(MapleClient c, MapleCharacter player) {
@@ -22,70 +22,60 @@ package net.server.coordinator.login;
import config.YamlConfig; import config.YamlConfig;
import net.server.Server; import net.server.Server;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/** /**
* *
* @author Ronan * @author Ronan
*/ */
public class LoginStorage { public class LoginStorage {
private final ConcurrentHashMap<Integer, List<Instant>> loginHistory = new ConcurrentHashMap<>(); // Key: accountId
private ConcurrentHashMap<Integer, List<Long>> loginHistory = new ConcurrentHashMap<>();
public boolean registerLogin(int accountId) { public boolean registerLogin(int accountId) {
List<Long> accHist = loginHistory.get(accountId); List<Instant> attempts = loginHistory.computeIfAbsent(accountId, k -> new ArrayList<>());
if (accHist == null) {
accHist = new LinkedList<>();
loginHistory.put(accountId, accHist);
}
synchronized (accHist) { synchronized (attempts) {
if (accHist.size() > YamlConfig.config.server.MAX_ACCOUNT_LOGIN_ATTEMPT) { final Instant attemptExpiry = Instant.ofEpochMilli(Server.getInstance().getCurrentTime() + YamlConfig.config.server.LOGIN_ATTEMPT_DURATION);
long blockExpiration = Server.getInstance().getCurrentTime() + YamlConfig.config.server.LOGIN_ATTEMPT_DURATION;
Collections.fill(accHist, blockExpiration);
if (attempts.size() > YamlConfig.config.server.MAX_ACCOUNT_LOGIN_ATTEMPT) {
Collections.fill(attempts, attemptExpiry);
return false; return false;
} }
accHist.add(Server.getInstance().getCurrentTime() + YamlConfig.config.server.LOGIN_ATTEMPT_DURATION); attempts.add(attemptExpiry);
return true; return true;
} }
} }
public void updateLoginHistory() { public void clearExpiredAttempts() {
long timeNow = Server.getInstance().getCurrentTime(); final Instant now = Instant.ofEpochMilli(Server.getInstance().getCurrentTime());
List<Integer> toRemove = new LinkedList<>(); List<Integer> accountIdsToClear = new ArrayList<>();
List<Long> toRemoveAttempt = new LinkedList<>();
for (Entry<Integer, List<Long>> loginEntries : loginHistory.entrySet()) { for (Entry<Integer, List<Instant>> loginEntries : loginHistory.entrySet()) {
toRemoveAttempt.clear(); final List<Instant> attempts = loginEntries.getValue();
synchronized (attempts) {
List<Instant> attemptsToRemove = attempts.stream()
.filter(attempt -> attempt.isBefore(now))
.collect(Collectors.toList());
List<Long> accAttempts = loginEntries.getValue(); for (Instant attemptToRemove : attemptsToRemove) {
synchronized (accAttempts) { attempts.remove(attemptToRemove);
for (Long loginAttempt : accAttempts) {
if (loginAttempt < timeNow) {
toRemoveAttempt.add(loginAttempt);
}
} }
if (!toRemoveAttempt.isEmpty()) { if (attempts.isEmpty()) {
for (Long trAttempt : toRemoveAttempt) { accountIdsToClear.add(loginEntries.getKey());
accAttempts.remove(trAttempt);
}
if (accAttempts.isEmpty()) {
toRemove.add(loginEntries.getKey());
}
} }
} }
} }
for (Integer tr : toRemove) { for (Integer accountId : accountIdsToClear) {
loginHistory.remove(tr); loginHistory.remove(accountId);
} }
} }
} }
@@ -19,7 +19,14 @@
*/ */
package net.server.coordinator.login; package net.server.coordinator.login;
import client.MapleCharacter;
import client.MapleClient;
import config.YamlConfig; 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.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@@ -27,29 +34,21 @@ import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; 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 * @author Ronan
*/ */
public class MapleLoginBypassCoordinator { public class MapleLoginBypassCoordinator {
private final static MapleLoginBypassCoordinator instance = new MapleLoginBypassCoordinator(); private final static MapleLoginBypassCoordinator instance = new MapleLoginBypassCoordinator();
public static MapleLoginBypassCoordinator getInstance() { public static MapleLoginBypassCoordinator getInstance() {
return instance; return instance;
} }
private final ConcurrentHashMap<Pair<String, Integer>, Pair<Boolean, Long>> loginBypass = new ConcurrentHashMap<>(); // optimized PIN & PIC check private final ConcurrentHashMap<Pair<Hwid, Integer>, Pair<Boolean, Long>> loginBypass = new ConcurrentHashMap<>(); // optimized PIN & PIC check
public boolean canLoginBypass(String nibbleHwid, int accId, boolean pic) { public boolean canLoginBypass(Hwid hwid, int accId, boolean pic) {
try { try {
Pair<String, Integer> entry = new Pair<>(nibbleHwid, accId); Pair<Hwid, Integer> entry = new Pair<>(hwid, accId);
Boolean p = loginBypass.get(entry).getLeft(); Boolean p = loginBypass.get(entry).getLeft();
return !pic || p; return !pic || p;
@@ -58,28 +57,30 @@ public class MapleLoginBypassCoordinator {
} }
} }
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); long expireTime = (pic ? YamlConfig.config.server.BYPASS_PIC_EXPIRATION : YamlConfig.config.server.BYPASS_PIN_EXPIRATION);
if (expireTime > 0) { 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; expireTime = Server.getInstance().getCurrentTime() + expireTime * 60 * 1000;
try { try {
pic |= loginBypass.get(entry).getLeft(); pic |= loginBypass.get(entry).getLeft();
expireTime = Math.max(loginBypass.get(entry).getRight(), expireTime); expireTime = Math.max(loginBypass.get(entry).getRight(), expireTime);
} catch (NullPointerException npe) {} } catch (NullPointerException npe) {
}
loginBypass.put(entry, new Pair<>(pic, expireTime)); loginBypass.put(entry, new Pair<>(pic, expireTime));
} }
} }
public void unregisterLoginBypassEntry(String nibbleHwid, int accId) { public void unregisterLoginBypassEntry(Hwid hwid, int accId) {
Pair<String, Integer> entry = new Pair<>(nibbleHwid, accId); String hwidValue = hwid == null ? null : hwid.hwid();
Pair<String, Integer> entry = new Pair<>(hwidValue, accId);
loginBypass.remove(entry); loginBypass.remove(entry);
} }
public void runUpdateLoginBypass() { public void runUpdateLoginBypass() {
if (!loginBypass.isEmpty()) { if (!loginBypass.isEmpty()) {
List<Pair<String, Integer>> toRemove = new LinkedList<>(); List<Pair<Hwid, Integer>> toRemove = new LinkedList<>();
Set<Integer> onlineAccounts = new HashSet<>(); Set<Integer> onlineAccounts = new HashSet<>();
long timeNow = Server.getInstance().getCurrentTime(); long timeNow = Server.getInstance().getCurrentTime();
@@ -92,7 +93,7 @@ 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())) { if (onlineAccounts.contains(e.getKey().getRight())) {
long expireTime = timeNow + 2 * 60 * 1000; long expireTime = timeNow + 2 * 60 * 1000;
if (expireTime > e.getValue().getRight()) { if (expireTime > e.getValue().getRight()) {
@@ -104,7 +105,7 @@ public class MapleLoginBypassCoordinator {
} }
if (!toRemove.isEmpty()) { if (!toRemove.isEmpty()) {
for (Pair<String, Integer> p : toRemove) { for (Pair<Hwid, Integer> p : toRemove) {
loginBypass.remove(p); loginBypass.remove(p);
} }
} }
@@ -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();
}
}
@@ -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);
}
}
@@ -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 client.MapleClient;
import net.AbstractMaplePacketHandler; import net.AbstractMaplePacketHandler;
import net.server.coordinator.session.MapleSessionCoordinator; import net.server.coordinator.session.SessionCoordinator;
import tools.MaplePacketCreator; import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor; import tools.data.input.SeekableLittleEndianAccessor;
@@ -57,7 +57,7 @@ public final class AfterLoginHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.requestPinAfterFailure()); c.announce(MaplePacketCreator.requestPinAfterFailure());
} }
} else if (c2 == 0 && c3 == 5) { } else if (c2 == 0 && c3 == 5) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null); SessionCoordinator.getInstance().closeSession(c, null);
c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
} }
} }
@@ -22,36 +22,31 @@
package net.server.handlers.login; package net.server.handlers.login;
import client.MapleClient; import client.MapleClient;
import java.net.InetAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler; import net.AbstractMaplePacketHandler;
import net.server.Server; import net.server.Server;
import net.server.coordinator.session.MapleSessionCoordinator; import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World; 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.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor; import tools.data.input.SeekableLittleEndianAccessor;
import java.net.InetAddress;
import java.net.UnknownHostException;
public final class CharSelectedHandler extends AbstractMaplePacketHandler { public final class CharSelectedHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(CharSelectedHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) { private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) { return switch (res) {
case REMOTE_PROCESSING: case REMOTE_PROCESSING -> 10;
return 10; case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case REMOTE_LOGGEDIN: case COORDINATOR_ERROR -> 8;
return 7; default -> 9;
};
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
} }
@Override @Override
@@ -59,31 +54,34 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler {
int charId = slea.readInt(); int charId = slea.readInt();
String macs = slea.readMapleAsciiString(); String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString(); String hostString = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17)); c.announce(MaplePacketCreator.getAfterLoginError(17));
return; return;
} }
c.updateMacs(macs); c.updateMacs(macs);
c.updateHWID(hwid); c.updateHwid(hwid);
IoSession session = c.getSession(); AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) { if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return; return;
} }
if (c.hasBannedMac() || c.hasBannedHWID()) { if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(session, true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
Server server = Server.getInstance(); Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) { if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(session, true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
@@ -94,7 +92,7 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler {
return; return;
} }
String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel()); String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel());
if(socket == null) { if(socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10)); c.announce(MaplePacketCreator.getAfterLoginError(10));
return; return;
@@ -1,37 +1,31 @@
package net.server.handlers.login; 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.InetAddress;
import java.net.UnknownHostException; 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 { public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(CharSelectedWithPicHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) { private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) { return switch (res) {
case REMOTE_PROCESSING: case REMOTE_PROCESSING -> 10;
return 10; case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case REMOTE_LOGGEDIN: case COORDINATOR_ERROR -> 8;
return 7; default -> 9;
};
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
} }
@Override @Override
@@ -40,26 +34,28 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
int charId = slea.readInt(); int charId = slea.readInt();
String macs = slea.readMapleAsciiString(); String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString(); String hostString = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17)); c.announce(MaplePacketCreator.getAfterLoginError(17));
return; return;
} }
c.updateMacs(macs); c.updateMacs(macs);
c.updateHWID(hwid); c.updateHwid(hwid);
IoSession session = c.getSession();
if (c.hasBannedMac() || c.hasBannedHWID()) { if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
Server server = Server.getInstance(); Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) { if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
@@ -71,13 +67,13 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
return; return;
} }
String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel()); String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel());
if(socket == null) { if(socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10)); c.announce(MaplePacketCreator.getAfterLoginError(10));
return; return;
} }
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid); AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) { if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return; return;
@@ -26,8 +26,7 @@ import client.MapleClient;
import config.YamlConfig; import config.YamlConfig;
import net.MaplePacketHandler; import net.MaplePacketHandler;
import net.server.Server; import net.server.Server;
import net.server.coordinator.session.MapleSessionCoordinator; import net.server.coordinator.session.Hwid;
import org.apache.mina.core.session.IoSession;
import tools.BCrypt; import tools.BCrypt;
import tools.DatabaseConnection; import tools.DatabaseConnection;
import tools.HexTool; import tools.HexTool;
@@ -54,13 +53,9 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
return HexTool.toString(digester.digest()).replace(" ", "").toLowerCase(); return HexTool.toString(digester.digest()).replace(" ", "").toLowerCase();
} }
private static String getRemoteIp(IoSession session) {
return MapleSessionCoordinator.getSessionRemoteAddress(session);
}
@Override @Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
String remoteHost = getRemoteIp(c.getSession()); String remoteHost = c.getRemoteAddress();
if (remoteHost.contentEquals("null")) { if (remoteHost.contentEquals("null")) {
c.announce(MaplePacketCreator.getLoginFailed(14)); // thanks Alchemist for noting remoteHost could be null c.announce(MaplePacketCreator.getLoginFailed(14)); // thanks Alchemist for noting remoteHost could be null
return; return;
@@ -72,8 +67,8 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
slea.skip(6); // localhost masked the initial part with zeroes... slea.skip(6); // localhost masked the initial part with zeroes...
byte[] hwidNibbles = slea.read(4); byte[] hwidNibbles = slea.read(4);
String nibbleHwid = HexTool.toCompressedString(hwidNibbles); Hwid hwid = new Hwid(HexTool.bytesToHex(hwidNibbles));
int loginok = c.login(login, pwd, nibbleHwid); int loginok = c.login(login, pwd, hwid);
if (YamlConfig.config.server.AUTOMATIC_REGISTER && loginok == 5) { if (YamlConfig.config.server.AUTOMATIC_REGISTER && loginok == 5) {
@@ -93,7 +88,7 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
c.setAccID(-1); c.setAccID(-1);
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
loginok = c.login(login, pwd, nibbleHwid); loginok = c.login(login, pwd, hwid);
} }
} }
@@ -1,37 +1,31 @@
package net.server.handlers.login; 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.InetAddress;
import java.net.UnknownHostException; 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 { public final class RegisterPicHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(RegisterPicHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) { private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) { return switch (res) {
case REMOTE_PROCESSING: case REMOTE_PROCESSING -> 10;
return 10; case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case REMOTE_LOGGEDIN: case COORDINATOR_ERROR -> 8;
return 7; default -> 9;
};
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
} }
@Override @Override
@@ -40,31 +34,34 @@ public final class RegisterPicHandler extends AbstractMaplePacketHandler {
int charId = slea.readInt(); int charId = slea.readInt();
String macs = slea.readMapleAsciiString(); String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString(); String hostString = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17)); c.announce(MaplePacketCreator.getAfterLoginError(17));
return; return;
} }
c.updateMacs(macs); c.updateMacs(macs);
c.updateHWID(hwid); c.updateHwid(hwid);
IoSession session = c.getSession(); AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) { if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return; return;
} }
if (c.hasBannedMac() || c.hasBannedHWID()) { if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
Server server = Server.getInstance(); Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) { if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
@@ -79,7 +76,7 @@ public final class RegisterPicHandler extends AbstractMaplePacketHandler {
return; return;
} }
String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel()); String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel());
if(socket == null) { if(socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10)); c.announce(MaplePacketCreator.getAfterLoginError(10));
return; return;
@@ -94,7 +91,7 @@ public final class RegisterPicHandler extends AbstractMaplePacketHandler {
e.printStackTrace(); e.printStackTrace();
} }
} else { } 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 client.MapleClient;
import net.AbstractMaplePacketHandler; import net.AbstractMaplePacketHandler;
import net.server.coordinator.session.MapleSessionCoordinator; import net.server.coordinator.session.SessionCoordinator;
import tools.MaplePacketCreator; import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor; import tools.data.input.SeekableLittleEndianAccessor;
@@ -35,7 +35,7 @@ public final class RegisterPinHandler extends AbstractMaplePacketHandler {
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
byte c2 = slea.readByte(); byte c2 = slea.readByte();
if (c2 == 0) { if (c2 == 0) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null); SessionCoordinator.getInstance().closeSession(c, null);
c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
} else { } else {
String pin = slea.readMapleAsciiString(); String pin = slea.readMapleAsciiString();
@@ -43,7 +43,7 @@ public final class RegisterPinHandler extends AbstractMaplePacketHandler {
c.setPin(pin); c.setPin(pin);
c.announce(MaplePacketCreator.pinRegistered()); c.announce(MaplePacketCreator.pinRegistered());
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null); SessionCoordinator.getInstance().closeSession(c, null);
c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
} }
} }
@@ -25,7 +25,7 @@ package net.server.handlers.login;
import client.MapleClient; import client.MapleClient;
import net.AbstractMaplePacketHandler; import net.AbstractMaplePacketHandler;
import net.server.Server; import net.server.Server;
import net.server.coordinator.session.MapleSessionCoordinator; import net.server.coordinator.session.SessionCoordinator;
import tools.MaplePacketCreator; import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor; import tools.data.input.SeekableLittleEndianAccessor;
@@ -44,7 +44,7 @@ public class SetGenderHandler extends AbstractMaplePacketHandler {
Server.getInstance().registerLoginState(c); Server.getInstance().registerLoginState(c);
} else { } else {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null); SessionCoordinator.getInstance().closeSession(c, null);
c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
} }
} }
@@ -1,37 +1,32 @@
package net.server.handlers.login; package net.server.handlers.login;
import client.MapleClient; import client.MapleClient;
import java.net.InetAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler; import net.AbstractMaplePacketHandler;
import net.server.Server; import net.server.Server;
import net.server.coordinator.session.MapleSessionCoordinator; import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World; 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.MaplePacketCreator;
import tools.Randomizer; import tools.Randomizer;
import tools.data.input.SeekableLittleEndianAccessor; import tools.data.input.SeekableLittleEndianAccessor;
import java.net.InetAddress;
import java.net.UnknownHostException;
public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHandler { public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(ViewAllCharRegisterPicHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) { private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) { return switch (res) {
case REMOTE_PROCESSING: case REMOTE_PROCESSING -> 10;
return 10; case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case REMOTE_LOGGEDIN: case COORDINATOR_ERROR -> 8;
return 7; default -> 9;
};
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
} }
@Override @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 slea.readInt(); // please don't let the client choose which world they should login
String mac = slea.readMapleAsciiString(); String mac = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString(); String hostString = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17)); c.announce(MaplePacketCreator.getAfterLoginError(17));
return; return;
} }
c.updateMacs(mac); c.updateMacs(mac);
c.updateHWID(hwid); c.updateHwid(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) { if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
IoSession session = c.getSession(); AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) { if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return; return;
@@ -65,7 +63,7 @@ public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHand
Server server = Server.getInstance(); Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) { if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
@@ -82,7 +80,7 @@ public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHand
String pic = slea.readMapleAsciiString(); String pic = slea.readMapleAsciiString();
c.setPic(pic); c.setPic(pic);
String[] socket = server.getInetSocket(session, c.getWorld(), channel); String[] socket = server.getInetSocket(c, c.getWorld(), channel);
if (socket == null) { if (socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10)); c.announce(MaplePacketCreator.getAfterLoginError(10));
return; return;
@@ -22,37 +22,32 @@
package net.server.handlers.login; package net.server.handlers.login;
import client.MapleClient; import client.MapleClient;
import java.net.InetAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler; import net.AbstractMaplePacketHandler;
import net.server.Server; import net.server.Server;
import net.server.coordinator.session.MapleSessionCoordinator; import net.server.coordinator.session.Hwid;
import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult;
import net.server.world.World; 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.MaplePacketCreator;
import tools.Randomizer; import tools.Randomizer;
import tools.data.input.SeekableLittleEndianAccessor; import tools.data.input.SeekableLittleEndianAccessor;
import java.net.InetAddress;
import java.net.UnknownHostException;
public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler { public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(ViewAllCharSelectedHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) { private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) { return switch (res) {
case REMOTE_PROCESSING: case REMOTE_PROCESSING -> 10;
return 10; case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case REMOTE_LOGGEDIN: case COORDINATOR_ERROR -> 8;
return 7; default -> 9;
};
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
} }
@Override @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 slea.readInt(); // please don't let the client choose which world they should login
String macs = slea.readMapleAsciiString(); String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString(); String hostString = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17)); c.announce(MaplePacketCreator.getAfterLoginError(17));
return; return;
} }
c.updateMacs(macs); c.updateMacs(macs);
c.updateHWID(hwid); c.updateHwid(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) { if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
IoSession session = c.getSession(); AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) { if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return; return;
@@ -85,7 +83,7 @@ public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler
Server server = Server.getInstance(); Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) { if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
@@ -105,7 +103,7 @@ public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler
c.setChannel(1); c.setChannel(1);
} }
String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel()); String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel());
if(socket == null) { if(socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10)); c.announce(MaplePacketCreator.getAfterLoginError(10));
return; return;
@@ -1,38 +1,32 @@
package net.server.handlers.login; 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.InetAddress;
import java.net.UnknownHostException; 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 { public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandler {
private static final Logger log = LoggerFactory.getLogger(ViewAllCharSelectedWithPicHandler.class);
private static int parseAntiMulticlientError(AntiMulticlientResult res) { private static int parseAntiMulticlientError(AntiMulticlientResult res) {
switch (res) { return switch (res) {
case REMOTE_PROCESSING: case REMOTE_PROCESSING -> 10;
return 10; case REMOTE_LOGGEDIN -> 7;
case REMOTE_NO_MATCH -> 17;
case REMOTE_LOGGEDIN: case COORDINATOR_ERROR -> 8;
return 7; default -> 9;
};
case REMOTE_NO_MATCH:
return 17;
case COORDINATOR_ERROR:
return 8;
default:
return 9;
}
} }
@Override @Override
@@ -43,26 +37,28 @@ public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandle
slea.readInt(); // please don't let the client choose which world they should login slea.readInt(); // please don't let the client choose which world they should login
String macs = slea.readMapleAsciiString(); String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString(); String hostString = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { final Hwid hwid;
try {
hwid = Hwid.fromHostString(hostString);
} catch (IllegalArgumentException e) {
log.warn("Invalid host string: {}", hostString, e);
c.announce(MaplePacketCreator.getAfterLoginError(17)); c.announce(MaplePacketCreator.getAfterLoginError(17));
return; return;
} }
c.updateMacs(macs); c.updateMacs(macs);
c.updateHWID(hwid); c.updateHwid(hwid);
if (c.hasBannedMac() || c.hasBannedHWID()) { if (c.hasBannedMac() || c.hasBannedHWID()) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
IoSession session = c.getSession();
Server server = Server.getInstance(); Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) { if(!server.haveCharacterEntry(c.getAccID(), charId)) {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); SessionCoordinator.getInstance().closeSession(c, true);
return; return;
} }
@@ -77,13 +73,13 @@ public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandle
c.setChannel(channel); c.setChannel(channel);
if (c.checkPic(pic)) { 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) { if(socket == null) {
c.announce(MaplePacketCreator.getAfterLoginError(10)); c.announce(MaplePacketCreator.getAfterLoginError(10));
return; return;
} }
AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid); AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid);
if (res != AntiMulticlientResult.SUCCESS) { if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return; return;
@@ -19,7 +19,7 @@
*/ */
package net.server.task; 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 @Override
public void run() { public void run() {
MapleSessionCoordinator.getInstance().runUpdateHwidHistory(); SessionCoordinator.getInstance().clearExpiredHwidHistory();
} }
} }
@@ -19,7 +19,7 @@
*/ */
package net.server.task; package net.server.task;
import net.server.coordinator.session.MapleSessionCoordinator; import net.server.coordinator.session.SessionCoordinator;
import net.server.coordinator.login.MapleLoginBypassCoordinator; import net.server.coordinator.login.MapleLoginBypassCoordinator;
/** /**
@@ -30,7 +30,7 @@ public class LoginStorageTask implements Runnable {
@Override @Override
public void run() { public void run() {
MapleSessionCoordinator.getInstance().runUpdateLoginHistory(); SessionCoordinator.getInstance().runUpdateLoginHistory();
MapleLoginBypassCoordinator.getInstance().runUpdateLoginBypass(); MapleLoginBypassCoordinator.getInstance().runUpdateLoginBypass();
} }
} }
@@ -588,7 +588,7 @@ public class MaplePlayerNPC extends AbstractMapleMapObject {
World wserv = Server.getInstance().getWorld(world); World wserv = Server.getInstance().getWorld(world);
if (wserv == null) return; if (wserv == null) return;
MapleClient c = new MapleClient(null, null, null); // mock client MapleClient c = MapleClient.createMock();
c.setWorld(world); c.setWorld(world);
c.setChannel(1); c.setChannel(1);
+1 -1
View File
@@ -156,7 +156,7 @@ public class MapleMiniGame extends AbstractMapleMapObject {
public void broadcastToOwner(final byte[] packet) { public void broadcastToOwner(final byte[] packet) {
MapleClient c = owner.getClient(); MapleClient c = owner.getClient();
if (c != null && c.getSession() != null) { if (c != null) {
c.announce(packet); c.announce(packet);
} }
} }
@@ -386,8 +386,9 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
} }
public void broadcast(final byte[] packet) { public void broadcast(final byte[] packet) {
if (owner.getClient() != null && owner.getClient().getSession() != null) { MapleClient client = owner.getClient();
owner.getClient().announce(packet); if (client != null) {
client.announce(packet);
} }
broadcastToVisitors(packet); broadcastToVisitors(packet);
} }
+11
View File
@@ -22,6 +22,7 @@
package tools; package tools;
import constants.string.CharsetConstants; import constants.string.CharsetConstants;
import io.netty.buffer.ByteBufUtil;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -105,4 +106,14 @@ public class HexTool {
return ""; 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);
}
} }
-80
View File
@@ -1,80 +0,0 @@
/*
This file is part of the OdinMS Maple Story Server
Copyright (C) 2008 ~ 2010 Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version 3
as published by the Free Software Foundation. You may not use, modify
or distribute this program under any other version of the
GNU Affero General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package tools;
import java.util.HashSet;
import java.util.Set;
import net.opcodes.RecvOpcode;
import client.MapleCharacter;
import client.MapleClient;
/**
* Logs packets to console and file.
*
* @author Alan (SharpAceX)
*/
public class MapleLogger {
public static Set<Integer> monitored = new HashSet<>();
public static Set<Integer> ignored = new HashSet<>();
public static void logRecv(MapleClient c, short packetId, Object message) {
MapleCharacter chr = c.getPlayer();
if (chr == null){
return;
}
if (!monitored.contains(chr.getId())){
return;
}
RecvOpcode op = getOpcodeFromValue(packetId);
if (isRecvBlocked(op)){
return;
}
String packet = op.toString() + "\r\n" + HexTool.toString((byte[]) message);
FilePrinter.printError(FilePrinter.PACKET_LOGS + c.getAccountName() + "-" + chr.getName() + ".txt", packet);
}
private static final boolean isRecvBlocked(RecvOpcode op){
switch(op){
case MOVE_PLAYER:
case GENERAL_CHAT:
case TAKE_DAMAGE:
case MOVE_PET:
case MOVE_LIFE:
case NPC_ACTION:
case FACE_EXPRESSION:
return true;
default:
return false;
}
}
private static final RecvOpcode getOpcodeFromValue(int value){
for (RecvOpcode op : RecvOpcode.values()){
if (op.getValue() == value){
return op;
}
}
return null;
}
}
+4 -3
View File
@@ -36,6 +36,7 @@ import constants.inventory.ItemConstants;
import constants.skills.Buccaneer; import constants.skills.Buccaneer;
import constants.skills.Corsair; import constants.skills.Corsair;
import constants.skills.ThunderBreaker; import constants.skills.ThunderBreaker;
import net.encryption.InitializationVector;
import net.opcodes.SendOpcode; import net.opcodes.SendOpcode;
import net.server.PlayerCoolDownValueHolder; import net.server.PlayerCoolDownValueHolder;
import net.server.Server; import net.server.Server;
@@ -547,14 +548,14 @@ public class MaplePacketCreator {
* @param recvIv the IV in use by the server for receiving * @param recvIv the IV in use by the server for receiving
* @return * @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); final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(8);
mplew.writeShort(0x0E); mplew.writeShort(0x0E);
mplew.writeShort(mapleVersion); mplew.writeShort(mapleVersion);
mplew.writeShort(1); mplew.writeShort(1);
mplew.write(49); mplew.write(49);
mplew.write(recvIv); mplew.write(recvIv.getBytes());
mplew.write(sendIv); mplew.write(sendIv.getBytes());
mplew.write(8); mplew.write(8);
return mplew.getPacket(); return mplew.getPacket();
} }
+19 -3
View File
@@ -2,29 +2,45 @@
<Configuration status="WARN" name="Cosmic"> <Configuration status="WARN" name="Cosmic">
<Properties> <Properties>
<Property name="filename">cosmic-log</Property> <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> </Properties>
<Appenders> <Appenders>
<Console name="Console" target="SYSTEM_OUT"> <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> </Console>
<RollingFile name="File" <RollingFile name="File"
fileName="logs/${filename}.log" fileName="logs/${filename}.log"
filePattern="logs/$${date:yyyy-MM}/$${date:yyyy-MM-dd}/${filename}-%d{yyyy-MM-dd_HH-mm-ss}-%i.log"> filePattern="logs/$${date:yyyy-MM}/$${date:yyyy-MM-dd}/${filename}-%d{yyyy-MM-dd_HH-mm-ss}-%i.log">
<PatternLayout> <PatternLayout>
<Pattern>%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Pattern> <Pattern>${standard-pattern}</Pattern>
</PatternLayout> </PatternLayout>
<Policies> <Policies>
<OnStartupTriggeringPolicy minSize="0"/> <OnStartupTriggeringPolicy minSize="0"/>
<SizeBasedTriggeringPolicy size="20 MB"/> <SizeBasedTriggeringPolicy size="20 MB"/>
</Policies> </Policies>
</RollingFile> </RollingFile>
<Console name="PacketConsole" target="SYSTEM_OUT">
<PatternLayout>
<Pattern>${packet-pattern}</Pattern>
</PatternLayout>
</Console>
</Appenders> </Appenders>
<Loggers> <Loggers>
<Root level="trace"> <Root level="trace">
<AppenderRef ref="File" level="debug"/> <AppenderRef ref="File" level="debug"/>
<AppenderRef ref="Console" level="trace"/> <AppenderRef ref="Console" level="trace"/>
</Root> </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="com.zaxxer.hikari" level="info"/>
<Logger name="io.netty" level="info"/>
</Loggers> </Loggers>
</Configuration> </Configuration>
+22
View File
@@ -0,0 +1,22 @@
package tools;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class HexToolTest {
@Test
void upperCaseHexToBytesAndBack() {
String hex = "A1B2C3";
byte[] bytes = HexTool.hexToBytes(hex);
assertEquals(hex, HexTool.bytesToHex(bytes));
}
@Test
void mixedCaseHexToBytesAndBack() {
String hex = "aB5DaA";
byte[] bytes = HexTool.hexToBytes(hex);
assertEquals(hex.toUpperCase(), HexTool.bytesToHex(bytes));
}
}