diff --git a/pom.xml b/pom.xml
index e95d8b9b3c..b41b3494d1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,7 +66,11 @@
netty-buffer
4.1.65.Final
-
+
+ io.netty
+ netty-handler
+ 4.1.65.Final
+
diff --git a/src/main/java/client/MapleClient.java b/src/main/java/client/MapleClient.java
index d0657975dc..fc9ee2f33b 100644
--- a/src/main/java/client/MapleClient.java
+++ b/src/main/java/client/MapleClient.java
@@ -26,6 +26,7 @@ import config.YamlConfig;
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;
@@ -51,6 +52,7 @@ import scripting.npc.NPCScriptManager;
import scripting.quest.QuestActionManager;
import scripting.quest.QuestScriptManager;
import server.ThreadManager;
+import server.TimerManager;
import server.life.MapleMonster;
import server.maps.FieldLimit;
import server.maps.MapleMap;
@@ -70,10 +72,12 @@ import java.sql.*;
import java.util.Date;
import java.util.*;
import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
public class MapleClient extends ChannelInboundHandlerAdapter {
private static final Logger log = LoggerFactory.getLogger(MapleClient.class);
+ private static final Set ignoredDebugRecvPackets = Set.of((short) 167, (short) 197, (short) 89, (short) 91, (short) 41, (short) 188, (short) 107);
public static final int LOGIN_NOTLOGGEDIN = 0;
public static final int LOGIN_SERVER_TRANSITION = 1;
@@ -83,12 +87,14 @@ public class MapleClient extends ChannelInboundHandlerAdapter {
public static final String CLIENT_NIBBLEHWID = "HWID2";
public static final String CLIENT_REMOTE_ADDRESS = "REMOTE_IP";
- private String hostAddress;
+ private String remoteAddress;
private volatile boolean inTransition;
private MapleAESOFB send;
private MapleAESOFB receive;
private final IoSession session;
+
+ private io.netty.channel.Channel ioChannel;
private PacketProcessor packetProcessor;
private MapleCharacter player;
private int channel = 1;
@@ -98,7 +104,7 @@ public class MapleClient extends ChannelInboundHandlerAdapter {
private Calendar birthday = null;
private String accountName = null;
private int world;
- private long lastPong;
+ private volatile long lastPong;
private int gmlevel;
private Set macs = new HashSet<>();
private Map engines = new HashMap<>();
@@ -149,13 +155,19 @@ public class MapleClient extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
+ if (!Server.getInstance().isOnline()) {
+ ctx.channel().close();
+ return;
+ }
+
String hostAddress = "null";
try {
hostAddress = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
} catch (NullPointerException npe) {
log.warn("Unable to get remote address for client", npe);
}
- this.hostAddress = hostAddress;
+ this.remoteAddress = hostAddress;
+ this.ioChannel = ctx.channel();
}
@Override
@@ -168,6 +180,11 @@ public class MapleClient extends ChannelInboundHandlerAdapter {
short opcode = packet.readShort();
final MaplePacketHandler handler = packetProcessor.getHandler(opcode);
+
+ if (YamlConfig.config.server.USE_DEBUG_SHOW_RCVD_PACKET && !ignoredDebugRecvPackets.contains(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
@@ -185,15 +202,50 @@ public class MapleClient extends ChannelInboundHandlerAdapter {
super.channelRead(ctx, msg);
}
+ @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) {
// TODO close session through MapleSessionCoordinator
+ } else if (cause instanceof IOException) {
+ closeSession();
+ } else {
+
}
super.exceptionCaught(ctx, cause);
}
+ @Override
+ public void channelInactive(ChannelHandlerContext ctx) {
+ 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);
+ }
+ }
+
+ public void closeSession() {
+ ioChannel.close();
+ }
+
+ public void disconnectSession() {
+ ioChannel.disconnect();
+ }
+
public MapleAESOFB getReceiveCrypto() {
return receive;
}
@@ -206,8 +258,8 @@ public class MapleClient extends ChannelInboundHandlerAdapter {
return session;
}
- public String getHostAddress() {
- return hostAddress;
+ public String getRemoteAddress() {
+ return remoteAddress;
}
public boolean isInTransition() {
@@ -1101,6 +1153,24 @@ public class MapleClient extends ChannelInboundHandlerAdapter {
}
}
+ public void checkIfIdle(final IdleStateEvent event) {
+ final long pingedAt = System.currentTimeMillis();
+ announce(MaplePacketCreator.getPing());
+ TimerManager.getInstance().schedule(() -> {
+ try {
+ if (lastPong < pingedAt) {
+ if (ioChannel.isActive()) {
+ log.info("Disconnected {} due to being idle. Idle state: {}", remoteAddress, event.state());
+ updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
+ disconnectSession();
+ }
+ }
+ } catch (NullPointerException e) {
+ e.printStackTrace();
+ }
+ }, TimeUnit.SECONDS.toMillis(15));
+ }
+
public String getHWID() {
return hwid;
}
diff --git a/src/main/java/net/netty/ServerChannelInitializer.java b/src/main/java/net/netty/ServerChannelInitializer.java
index 869d47ab2f..927e20cb05 100644
--- a/src/main/java/net/netty/ServerChannelInitializer.java
+++ b/src/main/java/net/netty/ServerChannelInitializer.java
@@ -5,6 +5,7 @@ import constants.net.ServerConstants;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.timeout.IdleStateHandler;
import tools.MaplePacketCreator;
import java.util.concurrent.atomic.AtomicLong;
@@ -16,6 +17,7 @@ public abstract class ServerChannelInitializer extends ChannelInitializer