diff --git a/pom.xml b/pom.xml index 12b00548cf..0334d6b9c4 100644 --- a/pom.xml +++ b/pom.xml @@ -19,24 +19,11 @@ 2.14.1 21.1.0 + 4.1.65.Final + 5.7.2 - - com.zaxxer - HikariCP - 4.0.3 - - - org.apache.mina - mina-core - 2.1.3 - - - mysql - mysql-connector-java - 8.0.23 - com.esotericsoftware.yamlbeans yamlbeans @@ -52,10 +39,39 @@ commons-io 2.10.0 + + + + com.zaxxer + HikariCP + 4.0.3 + + + mysql + mysql-connector-java + 8.0.23 + + + + + io.netty + netty-transport + ${netty.version} + + + io.netty + netty-codec + ${netty.version} + io.netty netty-buffer - 4.1.65.Final + ${netty.version} + + + io.netty + netty-handler + ${netty.version} @@ -96,12 +112,12 @@ org.junit.jupiter junit-jupiter-api - 5.7.2 + ${junit.version} org.junit.jupiter junit-jupiter-engine - 5.7.2 + ${junit.version} diff --git a/src/main/java/client/MapleCharacter.java b/src/main/java/client/MapleCharacter.java index f3fc390ed5..5434e00e2d 100644 --- a/src/main/java/client/MapleCharacter.java +++ b/src/main/java/client/MapleCharacter.java @@ -52,7 +52,6 @@ import net.server.services.task.world.CharacterSaveService; import net.server.services.type.ChannelServices; import net.server.services.type.WorldServices; import net.server.world.*; -import org.apache.mina.util.ConcurrentHashSet; import scripting.AbstractPlayerInteraction; import scripting.event.EventInstanceManager; import scripting.item.ItemScriptManager; @@ -85,6 +84,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.*; import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -177,7 +177,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject { private final Map quests; private Set controlled = new LinkedHashSet<>(); private Map entered = new LinkedHashMap<>(); - private Set visibleMapObjects = new ConcurrentHashSet<>(); + private Set visibleMapObjects = Collections.newSetFromMap(new ConcurrentHashMap<>()); private Map skills = new LinkedHashMap<>(); private Map activeCoupons = new LinkedHashMap<>(); private Map activeCouponRates = new LinkedHashMap<>(); diff --git a/src/main/java/client/MapleClient.java b/src/main/java/client/MapleClient.java index 83f76f6e5b..4d72ec87ba 100644 --- a/src/main/java/client/MapleClient.java +++ b/src/main/java/client/MapleClient.java @@ -24,17 +24,31 @@ package client; import client.inventory.MapleInventoryType; 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; +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.audit.locks.MonitoredLockType; import net.server.audit.locks.factory.MonitoredReentrantLockFactory; import net.server.channel.Channel; import net.server.coordinator.login.MapleLoginBypassCoordinator; -import net.server.coordinator.session.MapleSessionCoordinator; -import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; +import net.server.coordinator.session.Hwid; +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.MapleGuildCharacter; import net.server.world.*; -import org.apache.mina.core.session.IoSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import scripting.AbstractPlayerInteraction; import scripting.event.EventInstanceManager; import scripting.event.EventManager; @@ -43,1418 +57,1551 @@ 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; import server.maps.MapleMiniDungeonInfo; import tools.*; +import tools.data.input.ByteArrayByteStream; +import tools.data.input.GenericSeekableLittleEndianAccessor; import javax.script.ScriptEngine; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; 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 { +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_SERVER_TRANSITION = 1; - public static final int LOGIN_LOGGEDIN = 2; - public static final String CLIENT_KEY = "CLIENT"; - public static final String CLIENT_HWID = "HWID"; - public static final String CLIENT_NIBBLEHWID = "HWID2"; - public static final String CLIENT_REMOTE_ADDRESS = "REMOTE_IP"; - public static final String CLIENT_TRANSITION = "TRANSITION"; - private MapleAESOFB send; - private MapleAESOFB receive; - private final IoSession session; - private MapleCharacter player; - private int channel = 1; - private int accId = -4; - private boolean loggedIn = false; - private boolean serverTransition = false; - private Calendar birthday = null; - private String accountName = null; - private int world; - private long lastPong; - private int gmlevel; - private Set macs = new HashSet<>(); - private Map engines = new HashMap<>(); - private byte characterSlots = 3; - private byte loginattempt = 0; - private String pin = ""; - private int pinattempt = 0; - private String pic = ""; - private int picattempt = 0; - private String hwid = null; - private byte csattempt = 0; - private byte gender = -1; - private boolean disconnecting = false; - private final Semaphore actionsSemaphore = new Semaphore(7); - private final Lock lock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT, true); - private final Lock encoderLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT_ENCODER, true); - private final Lock announcerLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT_ANNOUNCER, true); - // thanks Masterrulax & try2hack for pointing out a bottleneck issue with shared locks, shavit for noticing an opportunity for improvement - private Calendar tempBanCalendar; - private int votePoints; - private int voteTime = -1; - private int visibleWorlds; - private long lastNpcClick; - private long sessionId; - private long lastPacket = System.currentTimeMillis(); - private int lang = 0; - - public void updateLastPacket() { - lastPacket = System.currentTimeMillis(); - } + public static final int LOGIN_NOTLOGGEDIN = 0; + public static final int LOGIN_SERVER_TRANSITION = 1; + public static final int LOGIN_LOGGEDIN = 2; - public long getLastPacket() { - return lastPacket; - } + private final Type type; + private final long sessionId; + private final PacketProcessor packetProcessor; - public MapleClient(MapleAESOFB send, MapleAESOFB receive, IoSession session) { - this.send = send; - this.receive = receive; - this.session = session; - } + private Hwid hwid; + private String remoteAddress; + private volatile boolean inTransition; - public MapleAESOFB getReceiveCrypto() { - return receive; - } + private io.netty.channel.Channel ioChannel; + private MapleCharacter player; + private int channel = 1; + private int accId = -4; + private boolean loggedIn = false; + private boolean serverTransition = false; + private Calendar birthday = null; + private String accountName = null; + private int world; + private volatile long lastPong; + private int gmlevel; + private Set macs = new HashSet<>(); + private Map engines = new HashMap<>(); + private byte characterSlots = 3; + private byte loginattempt = 0; + private String pin = ""; + private int pinattempt = 0; + private String pic = ""; + private int picattempt = 0; + private byte csattempt = 0; + private byte gender = -1; + private boolean disconnecting = false; + private final Semaphore actionsSemaphore = new Semaphore(7); + private final Lock lock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT, true); + private final Lock encoderLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT_ENCODER, true); + private final Lock announcerLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT_ANNOUNCER, true); + // thanks Masterrulax & try2hack for pointing out a bottleneck issue with shared locks, shavit for noticing an opportunity for improvement + private Calendar tempBanCalendar; + private int votePoints; + private int voteTime = -1; + private int visibleWorlds; + private long lastNpcClick; + private long lastPacket = System.currentTimeMillis(); + private int lang = 0; - public MapleAESOFB getSendCrypto() { - return send; - } + public enum Type { + LOGIN, + CHANNEL + } - public IoSession getSession() { - return session; - } - - public EventManager getEventManager(String event) { - return getChannelServer().getEventSM().getEventManager(event); + 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; } - public MapleCharacter getPlayer() { - return player; - } + this.remoteAddress = getRemoteAddress(channel); + this.ioChannel = channel; + } - public void setPlayer(MapleCharacter player) { - this.player = player; - } - - public AbstractPlayerInteraction getAbstractPlayerInteraction() { - return new AbstractPlayerInteraction(this); + 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); } - public void sendCharList(int server) { - this.announce(MaplePacketCreator.getCharList(this, server, 0)); - } + return remoteAddress; + } - public List loadCharacters(int serverId) { - List chars = new ArrayList<>(15); - try { - for (CharNameAndId cni : loadCharactersInternal(serverId)) { - chars.add(MapleCharacter.loadCharFromDB(cni.id, this, false)); - } - } catch (Exception e) { - e.printStackTrace(); - } - return chars; - } - - public List loadCharacterNames(int worldId) { - List chars = new ArrayList<>(15); - for (CharNameAndId cni : loadCharactersInternal(worldId)) { - chars.add(cni.name); - } - return chars; - } - - private List loadCharactersInternal(int worldId) { - List chars = new ArrayList<>(15); - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT id, name FROM characters WHERE accountid = ? AND world = ?")) { - ps.setInt(1, this.getAccID()); - ps.setInt(2, worldId); - - try (ResultSet rs = ps.executeQuery()) { - while (rs.next()) { - chars.add(new CharNameAndId(rs.getString("name"), rs.getInt("id"))); - } - } - } catch (SQLException e) { - e.printStackTrace(); - } - return chars; - } - - public boolean isLoggedIn() { - return loggedIn; - } - - public boolean hasBannedIP() { - boolean ret = false; - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM ipbans WHERE ? LIKE CONCAT(ip, '%')")) { - ps.setString(1, session.getRemoteAddress().toString()); - try (ResultSet rs = ps.executeQuery()) { - rs.next(); - if (rs.getInt(1) > 0) { - ret = true; - } - } - } catch (SQLException e) { - e.printStackTrace(); - } - return ret; - } - - public int getVoteTime() { - if (voteTime != -1) { - return voteTime; - } - - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT date FROM bit_votingrecords WHERE UPPER(account) = UPPER(?)")) { - ps.setString(1, accountName); - try (ResultSet rs = ps.executeQuery()) { - if (!rs.next()) { - return -1; - } - voteTime = rs.getInt("date"); - } - } catch (SQLException e) { - FilePrinter.printError("hasVotedAlready.txt", e); - return -1; - } - return voteTime; - } - - public void resetVoteTime() { - voteTime = -1; + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (!(msg instanceof InPacket packet)) { + log.warn("Received invalid message: {}", msg); + return; } - public boolean hasVotedAlready(){ - Date currentDate = new Date(); - int timeNow = (int) (currentDate.getTime() / 1000); - int difference = (timeNow - getVoteTime()); - return difference < 86400 && difference > 0; - } + short opcode = packet.readShort(); + final MaplePacketHandler handler = packetProcessor.getHandler(opcode); - public boolean hasBannedHWID() { - if (hwid == null) { - return false; - } + if (YamlConfig.config.server.USE_DEBUG_SHOW_RCVD_PACKET && !LoggingUtil.isIgnoredRecvPacket(opcode)) { + log.debug("Received packet id {}", opcode); + } - boolean ret = false; - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM hwidbans WHERE hwid LIKE ?")) { - ps.setString(1, hwid); + 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 + } + } - try (ResultSet rs = ps.executeQuery()) { - if (rs != null && rs.next()) { - if (rs.getInt(1) > 0) { - ret = true; - } - } - } - } catch (SQLException e) { - e.printStackTrace(); - } + updateLastPacket(); + } - return ret; - } + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object event) { + if (event instanceof IdleStateEvent idleEvent) { + checkIfIdle(idleEvent); + } + } - public boolean hasBannedMac() { - if (macs.isEmpty()) { - return false; - } - boolean ret = false; - int i; - StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM macbans WHERE mac IN ("); - for (i = 0; i < macs.size(); i++) { - sql.append("?"); - if (i != macs.size() - 1) { - sql.append(", "); - } - } - sql.append(")"); + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (player != null) { + log.warn("Exception caught by {}", player, cause); + } - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement(sql.toString())) { - i = 0; - for (String mac : macs) { - ps.setString(++i, mac); - } - try (ResultSet rs = ps.executeQuery()) { - rs.next(); - if (rs.getInt(1) > 0) { - ret = true; - } - } - } catch (Exception e) { - e.printStackTrace(); - } + if (cause instanceof InvalidPacketHeaderException) { + SessionCoordinator.getInstance().closeSession(this, true); + } else if (cause instanceof IOException) { + closeMapleSession(); + } + } - return ret; - } + @Override + public void channelInactive(ChannelHandlerContext ctx) { + closeMapleSession(); + } - private void loadHWIDIfNescessary() throws SQLException { - if (hwid == null) { - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT hwid FROM accounts WHERE id = ?")) { - ps.setInt(1, accId); + private void closeMapleSession() { + switch (type) { + case LOGIN -> SessionCoordinator.getInstance().closeLoginSession(this); + case CHANNEL -> SessionCoordinator.getInstance().closeSession(this, null); + } - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - hwid = rs.getString("hwid"); - } - } - } - } - } + 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(); + } + } - // TODO: Recode to close statements... - private void loadMacsIfNescessary() throws SQLException { - if (macs.isEmpty()) { - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT macs FROM accounts WHERE id = ?")) { - ps.setInt(1, accId); - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - for (String mac : rs.getString("macs").split(", ")) { - if (!mac.equals("")) { - macs.add(mac); - } - } - } - } - } - } - } + public void updateLastPacket() { + lastPacket = System.currentTimeMillis(); + } - public void banHWID() { - try { - loadHWIDIfNescessary(); + public long getLastPacket() { + return lastPacket; + } - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("INSERT INTO hwidbans (hwid) VALUES (?)")) { - ps.setString(1, hwid); - ps.executeUpdate(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - } + public void closeSession() { + ioChannel.close(); + } - public void banMacs() { - try { - loadMacsIfNescessary(); + public void disconnectSession() { + ioChannel.disconnect(); + } - List filtered = new LinkedList<>(); - try (Connection con = DatabaseConnection.getConnection()) { - try (PreparedStatement ps = con.prepareStatement("SELECT filter FROM macfilters"); - ResultSet rs = ps.executeQuery()) { - while (rs.next()) { - filtered.add(rs.getString("filter")); - } - } + public Hwid getHwid() { + return hwid; + } - try (PreparedStatement ps = con.prepareStatement("INSERT INTO macbans (mac, aid) VALUES (?, ?)")) { - for (String mac : macs) { - boolean matched = false; - for (String filter : filtered) { - if (mac.matches(filter)) { - matched = true; - break; - } - } - if (!matched) { - ps.setString(1, mac); - ps.setString(2, String.valueOf(getAccID())); - ps.executeUpdate(); - } - } - } - } - } catch (SQLException e) { - e.printStackTrace(); - } - } + public void setHwid(Hwid hwid) { + this.hwid = hwid; + } - public int finishLogin() { - encoderLock.lock(); - try { - if (getLoginState() > LOGIN_NOTLOGGEDIN) { // 0 = LOGIN_NOTLOGGEDIN, 1= LOGIN_SERVER_TRANSITION, 2 = LOGIN_LOGGEDIN - loggedIn = false; - return 7; + public String getRemoteAddress() { + return remoteAddress; + } + + public boolean isInTransition() { + return inTransition; + } + + public EventManager getEventManager(String event) { + return getChannelServer().getEventSM().getEventManager(event); + } + + public MapleCharacter getPlayer() { + return player; + } + + public void setPlayer(MapleCharacter player) { + this.player = player; + } + + public AbstractPlayerInteraction getAbstractPlayerInteraction() { + return new AbstractPlayerInteraction(this); + } + + public void sendCharList(int server) { + this.announce(MaplePacketCreator.getCharList(this, server, 0)); + } + + public List loadCharacters(int serverId) { + List chars = new ArrayList<>(15); + try { + for (CharNameAndId cni : loadCharactersInternal(serverId)) { + chars.add(MapleCharacter.loadCharFromDB(cni.id, this, false)); + } + } catch (Exception e) { + e.printStackTrace(); + } + return chars; + } + + public List loadCharacterNames(int worldId) { + List chars = new ArrayList<>(15); + for (CharNameAndId cni : loadCharactersInternal(worldId)) { + chars.add(cni.name); + } + return chars; + } + + private List loadCharactersInternal(int worldId) { + List chars = new ArrayList<>(15); + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT id, name FROM characters WHERE accountid = ? AND world = ?")) { + ps.setInt(1, this.getAccID()); + ps.setInt(2, worldId); + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + chars.add(new CharNameAndId(rs.getString("name"), rs.getInt("id"))); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + return chars; + } + + public boolean isLoggedIn() { + return loggedIn; + } + + public boolean hasBannedIP() { + boolean ret = false; + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM ipbans WHERE ? LIKE CONCAT(ip, '%')")) { + ps.setString(1, remoteAddress); + try (ResultSet rs = ps.executeQuery()) { + rs.next(); + if (rs.getInt(1) > 0) { + ret = true; + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + return ret; + } + + public int getVoteTime() { + if (voteTime != -1) { + return voteTime; + } + + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT date FROM bit_votingrecords WHERE UPPER(account) = UPPER(?)")) { + ps.setString(1, accountName); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return -1; + } + voteTime = rs.getInt("date"); + } + } catch (SQLException e) { + FilePrinter.printError("hasVotedAlready.txt", e); + return -1; + } + return voteTime; + } + + public void resetVoteTime() { + voteTime = -1; + } + + public boolean hasVotedAlready() { + Date currentDate = new Date(); + int timeNow = (int) (currentDate.getTime() / 1000); + int difference = (timeNow - getVoteTime()); + return difference < 86400 && difference > 0; + } + + public boolean hasBannedHWID() { + if (hwid == null) { + return false; + } + + boolean ret = false; + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM hwidbans WHERE hwid LIKE ?")) { + ps.setString(1, hwid.hwid()); + + try (ResultSet rs = ps.executeQuery()) { + if (rs != null && rs.next()) { + if (rs.getInt(1) > 0) { + ret = true; } - updateLoginState(MapleClient.LOGIN_LOGGEDIN); - } finally { - encoderLock.unlock(); } - - return 0; - } + } + } catch (SQLException e) { + e.printStackTrace(); + } - public void setPin(String pin) { - this.pin = pin; - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("UPDATE accounts SET pin = ? WHERE id = ?")) { - ps.setString(1, pin); - ps.setInt(2, accId); - ps.executeUpdate(); - } catch (SQLException e) { - e.printStackTrace(); - } - } + return ret; + } - public String getPin() { - return pin; - } + public boolean hasBannedMac() { + if (macs.isEmpty()) { + return false; + } + boolean ret = false; + int i; + StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM macbans WHERE mac IN ("); + for (i = 0; i < macs.size(); i++) { + sql.append("?"); + if (i != macs.size() - 1) { + sql.append(", "); + } + } + sql.append(")"); - public boolean checkPin(String other) { - if (!(YamlConfig.config.server.ENABLE_PIN && !canBypassPin())) { - return true; + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement(sql.toString())) { + i = 0; + for (String mac : macs) { + ps.setString(++i, mac); + } + try (ResultSet rs = ps.executeQuery()) { + rs.next(); + if (rs.getInt(1) > 0) { + ret = true; } - - pinattempt++; - if (pinattempt > 5) { - MapleSessionCoordinator.getInstance().closeSession(session, false); - } - if (pin.equals(other)) { - pinattempt = 0; - MapleLoginBypassCoordinator.getInstance().registerLoginBypassEntry(getNibbleHWID(), accId, false); - return true; - } - return false; - } + } + } catch (Exception e) { + e.printStackTrace(); + } - public void setPic(String pic) { - this.pic = pic; - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("UPDATE accounts SET pic = ? WHERE id = ?")) { - ps.setString(1, pic); - ps.setInt(2, accId); - ps.executeUpdate(); - } catch (SQLException e) { - e.printStackTrace(); - } - } + return ret; + } - public String getPic() { - return pic; - } + private void loadHWIDIfNescessary() throws SQLException { + if (hwid == null) { + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT hwid FROM accounts WHERE id = ?")) { + ps.setInt(1, accId); - public boolean checkPic(String other) { - if (!(YamlConfig.config.server.ENABLE_PIC && !canBypassPic())) { - return true; + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + hwid = new Hwid(rs.getString("hwid")); + } } - - picattempt++; - if (picattempt > 5) { - MapleSessionCoordinator.getInstance().closeSession(session, false); - } - if (pic.equals(other)) { // thanks ryantpayton (HeavenClient) for noticing null pics being checked here - picattempt = 0; - MapleLoginBypassCoordinator.getInstance().registerLoginBypassEntry(getNibbleHWID(), accId, true); - return true; - } - return false; - } + } + } + } - public int login(String login, String pwd, String nibbleHwid) { - int loginok = 5; - - loginattempt++; - if (loginattempt > 4) { - loggedIn = false; - MapleSessionCoordinator.getInstance().closeSession(session, false); - return 6; // thanks Survival_Project for finding out an issue with AUTOMATIC_REGISTER here - } - - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT id, password, gender, banned, pin, pic, characterslots, tos, language FROM accounts WHERE name = ?")) { - ps.setString(1, login); - - try (ResultSet rs = ps.executeQuery()) { - accId = -2; - if (rs.next()) { - accId = rs.getInt("id"); - if (accId <= 0) { - FilePrinter.printError(FilePrinter.LOGIN_EXCEPTION, "Tried to login with accid " + accId); - return 15; - } - - boolean banned = (rs.getByte("banned") == 1); - gmlevel = 0; - pin = rs.getString("pin"); - pic = rs.getString("pic"); - gender = rs.getByte("gender"); - characterSlots = rs.getByte("characterslots"); - lang = rs.getInt("language"); - String passhash = rs.getString("password"); - byte tos = rs.getByte("tos"); - - if (banned) { - return 3; - } - - if (getLoginState() > LOGIN_NOTLOGGEDIN) { // already loggedin - loggedIn = false; - loginok = 7; - } else if (passhash.charAt(0) == '$' && passhash.charAt(1) == '2' && BCrypt.checkpw(pwd, passhash)) { - loginok = (tos == 0) ? 23 : 0; - } else if (pwd.equals(passhash) || checkHash(passhash, "SHA-1", pwd) || checkHash(passhash, "SHA-512", pwd)) { - // thanks GabrielSin for detecting some no-bcrypt inconsistencies here - loginok = (tos == 0) ? (!YamlConfig.config.server.BCRYPT_MIGRATION ? 23 : -23) : (!YamlConfig.config.server.BCRYPT_MIGRATION ? 0 : -10); // migrate to bcrypt - } else { - loggedIn = false; - loginok = 4; - } - } else { - accId = -3; - } - } - } catch (SQLException e) { - e.printStackTrace(); - } - - if (loginok == 0 || loginok == 4) { - AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptLoginSession(session, nibbleHwid, accId, loginok == 4); - - switch (res) { - case SUCCESS: - if (loginok == 0) { - loginattempt = 0; - } - - return loginok; - - case REMOTE_LOGGEDIN: - return 17; - - case REMOTE_REACHED_LIMIT: - return 13; - - case REMOTE_PROCESSING: - return 10; - - case MANY_ACCOUNT_ATTEMPTS: - return 16; - - default: - return 8; - } - } else { - return loginok; - } - } - - public Calendar getTempBanCalendarFromDB() { - final Calendar lTempban = Calendar.getInstance(); - - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT `tempban` FROM accounts WHERE id = ?")) { - ps.setInt(1, getAccID()); - - final Timestamp tempban; - try (ResultSet rs = ps.executeQuery()) { - if (!rs.next()) { - return null; - } - - tempban = rs.getTimestamp("tempban"); - if (tempban.toLocalDateTime().equals(DefaultDates.getTempban())) { - return null; - } - } - - lTempban.setTimeInMillis(tempban.getTime()); - tempBanCalendar = lTempban; - return lTempban; - } catch (SQLException e) { - e.printStackTrace(); - } - - return null;//why oh why!?! - } - - public Calendar getTempBanCalendar() { - return tempBanCalendar; - } - - public boolean hasBeenBanned() { - return tempBanCalendar != null; - } - - public static long dottedQuadToLong(String dottedQuad) throws RuntimeException { - String[] quads = dottedQuad.split("\\."); - if (quads.length != 4) { - throw new RuntimeException("Invalid IP Address format."); - } - long ipAddress = 0; - for (int i = 0; i < 4; i++) { - int quad = Integer.parseInt(quads[i]); - ipAddress += (long) (quad % 256) * (long) Math.pow(256, 4 - i); - } - return ipAddress; - } - - public void updateHWID(String newHwid) { - String[] split = newHwid.split("_"); - 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(); - PreparedStatement ps = con.prepareStatement("UPDATE accounts SET hwid = ? WHERE id = ?")) { - ps.setString(1, this.hwid); - ps.setInt(2, accId); - ps.executeUpdate(); - } catch (SQLException e) { - e.printStackTrace(); - } - } else { - this.disconnect(false, false); // Invalid HWID... - } - } - - public void updateMacs(String macData) { - macs.addAll(Arrays.asList(macData.split(", "))); - StringBuilder newMacData = new StringBuilder(); - Iterator iter = macs.iterator(); - while (iter.hasNext()) { - String cur = iter.next(); - newMacData.append(cur); - if (iter.hasNext()) { - newMacData.append(", "); - } - } - - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("UPDATE accounts SET macs = ? WHERE id = ?")) { - ps.setString(1, newMacData.toString()); - ps.setInt(2, accId); - ps.executeUpdate(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void setAccID(int id) { - this.accId = id; - } - - public int getAccID() { - return accId; - } - - public void updateLoginState(int newstate) { - // rules out possibility of multiple account entries - if (newstate == LOGIN_LOGGEDIN) { - MapleSessionCoordinator.getInstance().updateOnlineSession(this.getSession()); - } - - try (Connection con = DatabaseConnection.getConnection(); - 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 - - ps.setInt(1, newstate); - ps.setTimestamp(2, new java.sql.Timestamp(Server.getInstance().getCurrentTime())); - ps.setInt(3, getAccID()); - ps.executeUpdate(); - } catch (SQLException e) { - e.printStackTrace(); - } - - if (newstate == LOGIN_NOTLOGGEDIN) { - loggedIn = false; - serverTransition = false; - setAccID(0); - } else { - serverTransition = (newstate == LOGIN_SERVER_TRANSITION); - loggedIn = !serverTransition; - } - } - - public int getLoginState() { // 0 = LOGIN_NOTLOGGEDIN, 1= LOGIN_SERVER_TRANSITION, 2 = LOGIN_LOGGEDIN - try (Connection con = DatabaseConnection.getConnection()) { - int state; - try (PreparedStatement ps = con.prepareStatement("SELECT loggedin, lastlogin, birthday FROM accounts WHERE id = ?")) { - ps.setInt(1, getAccID()); - - try (ResultSet rs = ps.executeQuery()) { - if (!rs.next()) { - throw new RuntimeException("getLoginState - MapleClient AccID: " + getAccID()); - } - - birthday = Calendar.getInstance(); - try { - birthday.setTime(rs.getDate("birthday")); - } catch (SQLException e) { - } - - state = rs.getInt("loggedin"); - if (state == LOGIN_SERVER_TRANSITION) { - if (rs.getTimestamp("lastlogin").getTime() + 30000 < Server.getInstance().getCurrentTime()) { - int accountId = accId; - state = LOGIN_NOTLOGGEDIN; - updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); // ACCID = 0, issue found thanks to Tochi & K u ssss o & Thora & Omo Oppa - this.setAccID(accountId); - } - } - } - } - if (state == LOGIN_LOGGEDIN) { - loggedIn = true; - } else if (state == LOGIN_SERVER_TRANSITION) { - try (PreparedStatement ps2 = con.prepareStatement("UPDATE accounts SET loggedin = 0 WHERE id = ?")) { - ps2.setInt(1, getAccID()); - ps2.executeUpdate(); - } - } else { - loggedIn = false; - } - return state; - } catch (SQLException e) { - loggedIn = false; - e.printStackTrace(); - throw new RuntimeException("login state"); - } - } - - public boolean checkBirthDate(Calendar date) { - return date.get(Calendar.YEAR) == birthday.get(Calendar.YEAR) && date.get(Calendar.MONTH) == birthday.get(Calendar.MONTH) && date.get(Calendar.DAY_OF_MONTH) == birthday.get(Calendar.DAY_OF_MONTH); - } - - private void removePartyPlayer(World wserv) { - MapleMap map = player.getMap(); - final MapleParty party = player.getParty(); - final int idz = player.getId(); - - if (party != null) { - final MaplePartyCharacter chrp = new MaplePartyCharacter(player); - chrp.setOnline(false); - wserv.updateParty(party.getId(), PartyOperation.LOG_ONOFF, chrp); - if (party.getLeader().getId() == idz && map != null) { - MaplePartyCharacter lchr = null; - for (MaplePartyCharacter pchr : party.getMembers()) { - if (pchr != null && pchr.getId() != idz && (lchr == null || lchr.getLevel() <= pchr.getLevel()) && map.getCharacterById(pchr.getId()) != null) { - lchr = pchr; - } - } - if (lchr != null) { - wserv.updateParty(party.getId(), PartyOperation.CHANGE_LEADER, lchr); - } + // TODO: Recode to close statements... + private void loadMacsIfNescessary() throws SQLException { + if (macs.isEmpty()) { + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT macs FROM accounts WHERE id = ?")) { + ps.setInt(1, accId); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + for (String mac : rs.getString("macs").split(", ")) { + if (!mac.equals("")) { + macs.add(mac); + } } + } } + } } - - private void removePlayer(World wserv, boolean serverTransition) { - try { - player.setDisconnectedFromChannelWorld(); - player.notifyMapTransferToPartner(-1); - player.removeIncomingInvites(); - player.cancelAllBuffs(true); - - player.closePlayerInteractions(); - player.closePartySearchInteractions(); - - if (!serverTransition) { // thanks MedicOP for detecting an issue with party leader change on changing channels - removePartyPlayer(wserv); + } - EventInstanceManager eim = player.getEventInstance(); - if (eim != null) { - eim.playerDisconnected(player); - } - - if (player.getMonsterCarnival() != null) { - player.getMonsterCarnival().playerDisconnected(getPlayer().getId()); - } - - if (player.getAriantColiseum() != null) { - player.getAriantColiseum().playerDisconnected(getPlayer()); - } + public void banHWID() { + try { + loadHWIDIfNescessary(); + + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("INSERT INTO hwidbans (hwid) VALUES (?)")) { + ps.setString(1, hwid.hwid()); + ps.executeUpdate(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void banMacs() { + try { + loadMacsIfNescessary(); + + List filtered = new LinkedList<>(); + try (Connection con = DatabaseConnection.getConnection()) { + try (PreparedStatement ps = con.prepareStatement("SELECT filter FROM macfilters"); + ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + filtered.add(rs.getString("filter")); + } + } + + try (PreparedStatement ps = con.prepareStatement("INSERT INTO macbans (mac, aid) VALUES (?, ?)")) { + for (String mac : macs) { + boolean matched = false; + for (String filter : filtered) { + if (mac.matches(filter)) { + matched = true; + break; + } } - - if (player.getMap() != null) { - int mapId = player.getMapId(); - player.getMap().removePlayer(player); - if(GameConstants.isDojo(mapId)) { - this.getChannelServer().freeDojoSectionIfEmpty(mapId); - } - } - - } catch (final Throwable t) { - FilePrinter.printError(FilePrinter.ACCOUNT_STUCK, t); - } - } - - public final void disconnect(final boolean shutdown, final boolean cashshop) { - if (canDisconnect()) { - ThreadManager.getInstance().newTask(() -> disconnectInternal(shutdown, cashshop)); + if (!matched) { + ps.setString(1, mac); + ps.setString(2, String.valueOf(getAccID())); + ps.executeUpdate(); + } + } } + } + } catch (SQLException e) { + e.printStackTrace(); } - - public final void forceDisconnect() { - if (canDisconnect()) { - disconnectInternal(true, false); + } + + public int finishLogin() { + encoderLock.lock(); + try { + if (getLoginState() > LOGIN_NOTLOGGEDIN) { // 0 = LOGIN_NOTLOGGEDIN, 1= LOGIN_SERVER_TRANSITION, 2 = LOGIN_LOGGEDIN + loggedIn = false; + return 7; + } + updateLoginState(MapleClient.LOGIN_LOGGEDIN); + } finally { + encoderLock.unlock(); + } + + return 0; + } + + public void setPin(String pin) { + this.pin = pin; + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE accounts SET pin = ? WHERE id = ?")) { + ps.setString(1, pin); + ps.setInt(2, accId); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public String getPin() { + return pin; + } + + public boolean checkPin(String other) { + if (!(YamlConfig.config.server.ENABLE_PIN && !canBypassPin())) { + return true; + } + + pinattempt++; + if (pinattempt > 5) { + SessionCoordinator.getInstance().closeSession(this, false); + } + if (pin.equals(other)) { + pinattempt = 0; + MapleLoginBypassCoordinator.getInstance().registerLoginBypassEntry(hwid, accId, false); + return true; + } + return false; + } + + public void setPic(String pic) { + this.pic = pic; + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE accounts SET pic = ? WHERE id = ?")) { + ps.setString(1, pic); + ps.setInt(2, accId); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public String getPic() { + return pic; + } + + public boolean checkPic(String other) { + if (!(YamlConfig.config.server.ENABLE_PIC && !canBypassPic())) { + return true; + } + + picattempt++; + if (picattempt > 5) { + SessionCoordinator.getInstance().closeSession(this, false); + } + if (pic.equals(other)) { // thanks ryantpayton (HeavenClient) for noticing null pics being checked here + picattempt = 0; + MapleLoginBypassCoordinator.getInstance().registerLoginBypassEntry(hwid, accId, true); + return true; + } + return false; + } + + public int login(String login, String pwd, Hwid hwid) { + int loginok = 5; + + loginattempt++; + if (loginattempt > 4) { + loggedIn = false; + SessionCoordinator.getInstance().closeSession(this, false); + return 6; // thanks Survival_Project for finding out an issue with AUTOMATIC_REGISTER here + } + + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT id, password, gender, banned, pin, pic, characterslots, tos, language FROM accounts WHERE name = ?")) { + ps.setString(1, login); + + try (ResultSet rs = ps.executeQuery()) { + accId = -2; + if (rs.next()) { + accId = rs.getInt("id"); + if (accId <= 0) { + FilePrinter.printError(FilePrinter.LOGIN_EXCEPTION, "Tried to login with accid " + accId); + return 15; + } + + boolean banned = (rs.getByte("banned") == 1); + gmlevel = 0; + pin = rs.getString("pin"); + pic = rs.getString("pic"); + gender = rs.getByte("gender"); + characterSlots = rs.getByte("characterslots"); + lang = rs.getInt("language"); + String passhash = rs.getString("password"); + byte tos = rs.getByte("tos"); + + if (banned) { + return 3; + } + + if (getLoginState() > LOGIN_NOTLOGGEDIN) { // already loggedin + loggedIn = false; + loginok = 7; + } else if (passhash.charAt(0) == '$' && passhash.charAt(1) == '2' && BCrypt.checkpw(pwd, passhash)) { + loginok = (tos == 0) ? 23 : 0; + } else if (pwd.equals(passhash) || checkHash(passhash, "SHA-1", pwd) || checkHash(passhash, "SHA-512", pwd)) { + // thanks GabrielSin for detecting some no-bcrypt inconsistencies here + loginok = (tos == 0) ? (!YamlConfig.config.server.BCRYPT_MIGRATION ? 23 : -23) : (!YamlConfig.config.server.BCRYPT_MIGRATION ? 0 : -10); // migrate to bcrypt + } else { + loggedIn = false; + loginok = 4; + } + } else { + accId = -3; } + } + } catch (SQLException e) { + e.printStackTrace(); } - - private synchronized boolean canDisconnect() { - if (disconnecting) { - return false; - } - - disconnecting = true; - return true; + + if (loginok == 0 || loginok == 4) { + AntiMulticlientResult res = SessionCoordinator.getInstance().attemptLoginSession(this, hwid, accId, loginok == 4); + + switch (res) { + case SUCCESS: + if (loginok == 0) { + loginattempt = 0; + } + + return loginok; + + case REMOTE_LOGGEDIN: + return 17; + + case REMOTE_REACHED_LIMIT: + return 13; + + case REMOTE_PROCESSING: + return 10; + + case MANY_ACCOUNT_ATTEMPTS: + return 16; + + default: + return 8; + } + } else { + return loginok; } - - private void disconnectInternal(boolean shutdown, boolean cashshop) {//once per MapleClient instance - if (player != null && player.isLoggedin() && player.getClient() != null) { - final int messengerid = player.getMessenger() == null ? 0 : player.getMessenger().getId(); - //final int fid = player.getFamilyId(); - final BuddyList bl = player.getBuddylist(); - final MapleMessengerCharacter chrm = new MapleMessengerCharacter(player, 0); - final MapleGuildCharacter chrg = player.getMGC(); - final MapleGuild guild = player.getGuild(); - - player.cancelMagicDoor(); - - final World wserv = getWorldServer(); // obviously wserv is NOT null if this player was online on it - try { - removePlayer(wserv, this.serverTransition); - - if (!(channel == -1 || shutdown)) { - if (!cashshop) { - if (!this.serverTransition) { // meaning not changing channels - if (messengerid > 0) { - wserv.leaveMessenger(messengerid, chrm); - } + } + + public Calendar getTempBanCalendarFromDB() { + final Calendar lTempban = Calendar.getInstance(); + + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT `tempban` FROM accounts WHERE id = ?")) { + ps.setInt(1, getAccID()); + + final Timestamp tempban; + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + return null; + } + + tempban = rs.getTimestamp("tempban"); + if (tempban.toLocalDateTime().equals(DefaultDates.getTempban())) { + return null; + } + } + + lTempban.setTimeInMillis(tempban.getTime()); + tempBanCalendar = lTempban; + return lTempban; + } catch (SQLException e) { + e.printStackTrace(); + } + + return null;//why oh why!?! + } + + public Calendar getTempBanCalendar() { + return tempBanCalendar; + } + + public boolean hasBeenBanned() { + return tempBanCalendar != null; + } + + public static long dottedQuadToLong(String dottedQuad) throws RuntimeException { + String[] quads = dottedQuad.split("\\."); + if (quads.length != 4) { + throw new RuntimeException("Invalid IP Address format."); + } + long ipAddress = 0; + for (int i = 0; i < 4; i++) { + int quad = Integer.parseInt(quads[i]); + ipAddress += (long) (quad % 256) * (long) Math.pow(256, 4 - i); + } + return ipAddress; + } + + public void updateHwid(Hwid hwid) { + this.hwid = hwid; + + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE accounts SET hwid = ? WHERE id = ?")) { + ps.setString(1, hwid.hwid()); + ps.setInt(2, accId); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void updateMacs(String macData) { + macs.addAll(Arrays.asList(macData.split(", "))); + StringBuilder newMacData = new StringBuilder(); + Iterator iter = macs.iterator(); + while (iter.hasNext()) { + String cur = iter.next(); + newMacData.append(cur); + if (iter.hasNext()) { + newMacData.append(", "); + } + } + + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE accounts SET macs = ? WHERE id = ?")) { + ps.setString(1, newMacData.toString()); + ps.setInt(2, accId); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void setAccID(int id) { + this.accId = id; + } + + public int getAccID() { + return accId; + } + + public void updateLoginState(int newState) { + // rules out possibility of multiple account entries + if (newState == LOGIN_LOGGEDIN) { + SessionCoordinator.getInstance().updateOnlineClient(this); + } + + try (Connection con = DatabaseConnection.getConnection(); + 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 + + ps.setInt(1, newState); + ps.setTimestamp(2, new java.sql.Timestamp(Server.getInstance().getCurrentTime())); + ps.setInt(3, getAccID()); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + + if (newState == LOGIN_NOTLOGGEDIN) { + loggedIn = false; + serverTransition = false; + setAccID(0); + } else { + serverTransition = (newState == LOGIN_SERVER_TRANSITION); + loggedIn = !serverTransition; + } + } + + public int getLoginState() { // 0 = LOGIN_NOTLOGGEDIN, 1= LOGIN_SERVER_TRANSITION, 2 = LOGIN_LOGGEDIN + try (Connection con = DatabaseConnection.getConnection()) { + int state; + try (PreparedStatement ps = con.prepareStatement("SELECT loggedin, lastlogin, birthday FROM accounts WHERE id = ?")) { + ps.setInt(1, getAccID()); + + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next()) { + throw new RuntimeException("getLoginState - MapleClient AccID: " + getAccID()); + } + + birthday = Calendar.getInstance(); + try { + birthday.setTime(rs.getDate("birthday")); + } catch (SQLException e) { + } + + state = rs.getInt("loggedin"); + if (state == LOGIN_SERVER_TRANSITION) { + if (rs.getTimestamp("lastlogin").getTime() + 30000 < Server.getInstance().getCurrentTime()) { + int accountId = accId; + state = LOGIN_NOTLOGGEDIN; + updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); // ACCID = 0, issue found thanks to Tochi & K u ssss o & Thora & Omo Oppa + this.setAccID(accountId); + } + } + } + } + if (state == LOGIN_LOGGEDIN) { + loggedIn = true; + } else if (state == LOGIN_SERVER_TRANSITION) { + try (PreparedStatement ps2 = con.prepareStatement("UPDATE accounts SET loggedin = 0 WHERE id = ?")) { + ps2.setInt(1, getAccID()); + ps2.executeUpdate(); + } + } else { + loggedIn = false; + } + return state; + } catch (SQLException e) { + loggedIn = false; + e.printStackTrace(); + throw new RuntimeException("login state"); + } + } + + public boolean checkBirthDate(Calendar date) { + return date.get(Calendar.YEAR) == birthday.get(Calendar.YEAR) && date.get(Calendar.MONTH) == birthday.get(Calendar.MONTH) && date.get(Calendar.DAY_OF_MONTH) == birthday.get(Calendar.DAY_OF_MONTH); + } + + private void removePartyPlayer(World wserv) { + MapleMap map = player.getMap(); + final MapleParty party = player.getParty(); + final int idz = player.getId(); + + if (party != null) { + final MaplePartyCharacter chrp = new MaplePartyCharacter(player); + chrp.setOnline(false); + wserv.updateParty(party.getId(), PartyOperation.LOG_ONOFF, chrp); + if (party.getLeader().getId() == idz && map != null) { + MaplePartyCharacter lchr = null; + for (MaplePartyCharacter pchr : party.getMembers()) { + if (pchr != null && pchr.getId() != idz && (lchr == null || lchr.getLevel() <= pchr.getLevel()) && map.getCharacterById(pchr.getId()) != null) { + lchr = pchr; + } + } + if (lchr != null) { + wserv.updateParty(party.getId(), PartyOperation.CHANGE_LEADER, lchr); + } + } + } + } + + private void removePlayer(World wserv, boolean serverTransition) { + try { + player.setDisconnectedFromChannelWorld(); + player.notifyMapTransferToPartner(-1); + player.removeIncomingInvites(); + player.cancelAllBuffs(true); + + player.closePlayerInteractions(); + player.closePartySearchInteractions(); + + if (!serverTransition) { // thanks MedicOP for detecting an issue with party leader change on changing channels + removePartyPlayer(wserv); + + EventInstanceManager eim = player.getEventInstance(); + if (eim != null) { + eim.playerDisconnected(player); + } + + if (player.getMonsterCarnival() != null) { + player.getMonsterCarnival().playerDisconnected(getPlayer().getId()); + } + + if (player.getAriantColiseum() != null) { + player.getAriantColiseum().playerDisconnected(getPlayer()); + } + } + + if (player.getMap() != null) { + int mapId = player.getMapId(); + player.getMap().removePlayer(player); + if (GameConstants.isDojo(mapId)) { + this.getChannelServer().freeDojoSectionIfEmpty(mapId); + } + } + + } catch (final Throwable t) { + FilePrinter.printError(FilePrinter.ACCOUNT_STUCK, t); + } + } + + public final void disconnect(final boolean shutdown, final boolean cashshop) { + if (canDisconnect()) { + ThreadManager.getInstance().newTask(() -> disconnectInternal(shutdown, cashshop)); + } + } + + public final void forceDisconnect() { + if (canDisconnect()) { + disconnectInternal(true, false); + } + } + + private synchronized boolean canDisconnect() { + if (disconnecting) { + return false; + } + + disconnecting = true; + return true; + } + + private void disconnectInternal(boolean shutdown, boolean cashshop) {//once per MapleClient instance + if (player != null && player.isLoggedin() && player.getClient() != null) { + final int messengerid = player.getMessenger() == null ? 0 : player.getMessenger().getId(); + //final int fid = player.getFamilyId(); + final BuddyList bl = player.getBuddylist(); + final MapleMessengerCharacter chrm = new MapleMessengerCharacter(player, 0); + final MapleGuildCharacter chrg = player.getMGC(); + final MapleGuild guild = player.getGuild(); + + player.cancelMagicDoor(); + + final World wserv = getWorldServer(); // obviously wserv is NOT null if this player was online on it + try { + removePlayer(wserv, this.serverTransition); + + if (!(channel == -1 || shutdown)) { + if (!cashshop) { + if (!this.serverTransition) { // meaning not changing channels + if (messengerid > 0) { + wserv.leaveMessenger(messengerid, chrm); + } /* if (fid > 0) { final MapleFamily family = worlda.getFamily(fid); family. } */ - - player.forfeitExpirableQuests(); //This is for those quests that you have to stay logged in for a certain amount of time - - if (guild != null) { - final Server server = Server.getInstance(); - server.setGuildMemberOnline(player, false, player.getClient().getChannel()); - player.getClient().announce(MaplePacketCreator.showGuildInfo(player)); - } - if (bl != null) { - wserv.loggedOff(player.getName(), player.getId(), channel, player.getBuddylist().getBuddyIds()); - } - } - } else { - if (!this.serverTransition) { // if dc inside of cash shop. - if (bl != null) { - wserv.loggedOff(player.getName(), player.getId(), channel, player.getBuddylist().getBuddyIds()); - } - } - } - } - } catch (final Exception e) { - FilePrinter.printError(FilePrinter.ACCOUNT_STUCK, e); - } finally { - if (!this.serverTransition) { - if(chrg != null) { - chrg.setCharacter(null); - } - wserv.removePlayer(player); - //getChannelServer().removePlayer(player); already being done - - player.saveCooldowns(); - player.cancelAllDebuffs(); - player.saveCharToDB(true); - - player.logOff(); - if(YamlConfig.config.server.INSTANT_NAME_CHANGE) player.doPendingNameChange(); - clear(); - } else { - getChannelServer().removePlayer(player); - player.saveCooldowns(); - player.cancelAllDebuffs(); - player.saveCharToDB(); - } - } - } - if (!serverTransition && isLoggedIn()) { - MapleSessionCoordinator.getInstance().closeSession(session, false); - updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); - session.removeAttribute(MapleClient.CLIENT_KEY); // prevents double dcing during login - - clear(); - } else { - if (session.containsAttribute(MapleClient.CLIENT_KEY)) { - MapleSessionCoordinator.getInstance().closeSession(session, false); - session.removeAttribute(MapleClient.CLIENT_KEY); + player.forfeitExpirableQuests(); //This is for those quests that you have to stay logged in for a certain amount of time + + if (guild != null) { + final Server server = Server.getInstance(); + server.setGuildMemberOnline(player, false, player.getClient().getChannel()); + player.getClient().announce(MaplePacketCreator.showGuildInfo(player)); + } + if (bl != null) { + wserv.loggedOff(player.getName(), player.getId(), channel, player.getBuddylist().getBuddyIds()); + } } - - if (!Server.getInstance().hasCharacteridInTransition(this)) { - updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); + } else { + if (!this.serverTransition) { // if dc inside of cash shop. + if (bl != null) { + wserv.loggedOff(player.getName(), player.getId(), channel, player.getBuddylist().getBuddyIds()); + } } - - engines = null; // thanks Tochi for pointing out a NPE here + } } - } + } catch (final Exception e) { + FilePrinter.printError(FilePrinter.ACCOUNT_STUCK, e); + } finally { + if (!this.serverTransition) { + if (chrg != null) { + chrg.setCharacter(null); + } + wserv.removePlayer(player); + //getChannelServer().removePlayer(player); already being done - private void clear() { - // player hard reference removal thanks to Steve (kaito1410) - if (this.player != null) { - this.player.empty(true); // clears schedules and stuff + player.saveCooldowns(); + player.cancelAllDebuffs(); + player.saveCharToDB(true); + + player.logOff(); + if (YamlConfig.config.server.INSTANT_NAME_CHANGE) { + player.doPendingNameChange(); + } + clear(); + } else { + getChannelServer().removePlayer(player); + + player.saveCooldowns(); + player.cancelAllDebuffs(); + player.saveCharToDB(); } - - Server.getInstance().unregisterLoginState(this); - - this.accountName = null; - this.macs = null; - this.hwid = null; - this.birthday = null; - this.engines = null; - this.player = null; - this.receive = null; - this.send = null; - //this.session = null; - } - - public void setCharacterOnSessionTransitionState(int cid) { - this.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION); - session.setAttribute(MapleClient.CLIENT_TRANSITION); - Server.getInstance().setCharacteridInTransition(this, cid); - } - - public int getChannel() { - return channel; - } - - public Channel getChannelServer() { - return Server.getInstance().getChannel(world, channel); - } - - public World getWorldServer() { - return Server.getInstance().getWorld(world); - } - - public Channel getChannelServer(byte channel) { - return Server.getInstance().getChannel(world, channel); - } - - public boolean deleteCharacter(int cid, int senderAccId) { - try { - MapleCharacter chr = MapleCharacter.loadCharFromDB(cid, this, false); - - Integer partyid = chr.getWorldServer().getCharacterPartyid(cid); - if (partyid != null) { - this.setPlayer(chr); - - MapleParty party = chr.getWorldServer().getParty(partyid); - chr.setParty(party); - chr.getMPC(); - chr.leaveParty(); // thanks Vcoc for pointing out deleted characters would still stay in a party - - this.setPlayer(null); - } - - return MapleCharacter.deleteCharFromDB(chr, senderAccId); - } catch(SQLException ex) { - ex.printStackTrace(); - return false; - } - } - - public String getAccountName() { - return accountName; - } - - public void setAccountName(String a) { - this.accountName = a; - } - - public void setChannel(int channel) { - this.channel = channel; - } - - public int getWorld() { - return world; - } - - public void setWorld(int world) { - this.world = world; - } - - public void pongReceived() { - lastPong = Server.getInstance().getCurrentTime(); - } - - public void testPing(long timeThen) { - try { - if (lastPong < timeThen) { - if (session != null && session.isConnected()) { - MapleSessionCoordinator.getInstance().closeSession(session, false); - updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); - session.removeAttribute(MapleClient.CLIENT_KEY); - } - } - } catch (NullPointerException e) { - e.printStackTrace(); - } - } - - public String getHWID() { - return hwid; - } - - public void setHWID(String hwid) { - this.hwid = hwid; - } - - public Set getMacs() { - return Collections.unmodifiableSet(macs); - } - - public int getGMLevel() { - return gmlevel; - } - - public void setGMLevel(int level) { - gmlevel = level; - } - - public void setScriptEngine(String name, ScriptEngine e) { - engines.put(name, e); - } - - public ScriptEngine getScriptEngine(String name) { - return engines.get(name); - } - - public void removeScriptEngine(String name) { - engines.remove(name); - } - - public NPCConversationManager getCM() { - return NPCScriptManager.getInstance().getCM(this); - } - - public QuestActionManager getQM() { - return QuestScriptManager.getInstance().getQM(this); - } - - public boolean acceptToS() { - if (accountName == null) { - return true; - } - - boolean disconnect = false; - try (Connection con = DatabaseConnection.getConnection()) { - try (PreparedStatement ps = con.prepareStatement("SELECT `tos` FROM accounts WHERE id = ?")) { - ps.setInt(1, accId); - - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - if (rs.getByte("tos") == 1) { - disconnect = true; - } - } - } - } - - try (PreparedStatement ps = con.prepareStatement("UPDATE accounts SET tos = 1 WHERE id = ?")) { - ps.setInt(1, accId); - ps.executeUpdate(); - } - } catch (SQLException e) { - e.printStackTrace(); - } - return disconnect; - } - - public void checkChar(int accid) { /// issue with multiple chars from same account login found by shavit, resinate - if (!YamlConfig.config.server.USE_CHARACTER_ACCOUNT_CHECK) { - return; } - - for (World w : Server.getInstance().getWorlds()) { - for (MapleCharacter chr : w.getPlayerStorage().getAllCharacters()) { - if (accid == chr.getAccountID()) { - FilePrinter.print(FilePrinter.EXPLOITS, "Player: " + chr.getName() + " has been removed from " + GameConstants.WORLD_NAMES[w.getId()] + ". Possible Dupe attempt."); - chr.getClient().forceDisconnect(); - w.getPlayerStorage().removePlayer(chr.getId()); + } + + SessionCoordinator.getInstance().closeSession(this, false); + + if (!serverTransition && isLoggedIn()) { + updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); + + clear(); + } else { + if (!Server.getInstance().hasCharacteridInTransition(this)) { + updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); + } + + engines = null; // thanks Tochi for pointing out a NPE here + } + } + + private void clear() { + // player hard reference removal thanks to Steve (kaito1410) + if (this.player != null) { + this.player.empty(true); // clears schedules and stuff + } + + Server.getInstance().unregisterLoginState(this); + + this.accountName = null; + this.macs = null; + this.hwid = null; + this.birthday = null; + this.engines = null; + this.player = null; + } + + public void setCharacterOnSessionTransitionState(int cid) { + this.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION); + this.inTransition = true; + Server.getInstance().setCharacteridInTransition(this, cid); + } + + public int getChannel() { + return channel; + } + + public Channel getChannelServer() { + return Server.getInstance().getChannel(world, channel); + } + + public World getWorldServer() { + return Server.getInstance().getWorld(world); + } + + public Channel getChannelServer(byte channel) { + return Server.getInstance().getChannel(world, channel); + } + + public boolean deleteCharacter(int cid, int senderAccId) { + try { + MapleCharacter chr = MapleCharacter.loadCharFromDB(cid, this, false); + + Integer partyid = chr.getWorldServer().getCharacterPartyid(cid); + if (partyid != null) { + this.setPlayer(chr); + + MapleParty party = chr.getWorldServer().getParty(partyid); + chr.setParty(party); + chr.getMPC(); + chr.leaveParty(); // thanks Vcoc for pointing out deleted characters would still stay in a party + + this.setPlayer(null); + } + + return MapleCharacter.deleteCharFromDB(chr, senderAccId); + } catch (SQLException ex) { + ex.printStackTrace(); + return false; + } + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String a) { + this.accountName = a; + } + + public void setChannel(int channel) { + this.channel = channel; + } + + public int getWorld() { + return world; + } + + public void setWorld(int world) { + this.world = world; + } + + public void pongReceived() { + lastPong = System.currentTimeMillis(); + } + + 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 idling. Reason: {}", remoteAddress, event.state()); + updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); + disconnectSession(); + } + } + } catch (NullPointerException e) { + e.printStackTrace(); + } + }, TimeUnit.SECONDS.toMillis(15)); + } + + public Set getMacs() { + return Collections.unmodifiableSet(macs); + } + + public int getGMLevel() { + return gmlevel; + } + + public void setGMLevel(int level) { + gmlevel = level; + } + + public void setScriptEngine(String name, ScriptEngine e) { + engines.put(name, e); + } + + public ScriptEngine getScriptEngine(String name) { + return engines.get(name); + } + + public void removeScriptEngine(String name) { + engines.remove(name); + } + + public NPCConversationManager getCM() { + return NPCScriptManager.getInstance().getCM(this); + } + + public QuestActionManager getQM() { + return QuestScriptManager.getInstance().getQM(this); + } + + public boolean acceptToS() { + if (accountName == null) { + return true; + } + + boolean disconnect = false; + try (Connection con = DatabaseConnection.getConnection()) { + try (PreparedStatement ps = con.prepareStatement("SELECT `tos` FROM accounts WHERE id = ?")) { + ps.setInt(1, accId); + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + if (rs.getByte("tos") == 1) { + disconnect = true; + } } } } + + try (PreparedStatement ps = con.prepareStatement("UPDATE accounts SET tos = 1 WHERE id = ?")) { + ps.setInt(1, accId); + ps.executeUpdate(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return disconnect; + } + + public void checkChar(int accid) { /// issue with multiple chars from same account login found by shavit, resinate + if (!YamlConfig.config.server.USE_CHARACTER_ACCOUNT_CHECK) { + return; } - public int getVotePoints() { - int points = 0; - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT `votepoints` FROM accounts WHERE id = ?")) { - ps.setInt(1, accId); - - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - points = rs.getInt("votepoints"); - } - } - } catch (SQLException e) { - e.printStackTrace(); - } - votePoints = points; - return votePoints; - } - - public void addVotePoints(int points) { - votePoints += points; - saveVotePoints(); - } - - public void useVotePoints(int points){ - if (points > votePoints){ - //Should not happen, should probably log this - return; - } - votePoints -= points; - saveVotePoints(); - LogHelper.logLeaf(player, false, Integer.toString(points)); - } - - private void saveVotePoints() { - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("UPDATE accounts SET votepoints = ? WHERE id = ?")) { - ps.setInt(1, votePoints); - ps.setInt(2, accId); - ps.executeUpdate(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - public void lockClient() { - lock.lock(); - } - - public void unlockClient() { - lock.unlock(); - } - - public boolean tryacquireClient() { - if (actionsSemaphore.tryAcquire()) { - lockClient(); - return true; - } else { - return false; + for (World w : Server.getInstance().getWorlds()) { + for (MapleCharacter chr : w.getPlayerStorage().getAllCharacters()) { + if (accid == chr.getAccountID()) { + FilePrinter.print(FilePrinter.EXPLOITS, "Player: " + chr.getName() + " has been removed from " + GameConstants.WORLD_NAMES[w.getId()] + ". Possible Dupe attempt."); + chr.getClient().forceDisconnect(); + w.getPlayerStorage().removePlayer(chr.getId()); } - } - - public void releaseClient() { - unlockClient(); - actionsSemaphore.release(); + } } - - public boolean tryacquireEncoder() { - if (actionsSemaphore.tryAcquire()) { - encoderLock.lock(); - return true; - } else { - return false; + } + + public int getVotePoints() { + int points = 0; + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT `votepoints` FROM accounts WHERE id = ?")) { + ps.setInt(1, accId); + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + points = rs.getInt("votepoints"); } - } - - public void unlockEncoder() { - encoderLock.unlock(); - actionsSemaphore.release(); - } + } + } catch (SQLException e) { + e.printStackTrace(); + } + votePoints = points; + return votePoints; + } - private static class CharNameAndId { + public void addVotePoints(int points) { + votePoints += points; + saveVotePoints(); + } - public String name; - public int id; + public void useVotePoints(int points) { + if (points > votePoints) { + //Should not happen, should probably log this + return; + } + votePoints -= points; + saveVotePoints(); + LogHelper.logLeaf(player, false, Integer.toString(points)); + } - public CharNameAndId(String name, int id) { - super(); - this.name = name; - this.id = id; - } - } + private void saveVotePoints() { + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE accounts SET votepoints = ? WHERE id = ?")) { + ps.setInt(1, votePoints); + ps.setInt(2, accId); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } - private static boolean checkHash(String hash, String type, String password) { - try { - MessageDigest digester = MessageDigest.getInstance(type); - digester.update(password.getBytes("UTF-8"), 0, password.length()); - return HexTool.toString(digester.digest()).replace(" ", "").toLowerCase().equals(hash); - } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { - throw new RuntimeException("Encoding the string failed", e); - } - } + public void lockClient() { + lock.lock(); + } + + public void unlockClient() { + lock.unlock(); + } + + public boolean tryacquireClient() { + if (actionsSemaphore.tryAcquire()) { + lockClient(); + return true; + } else { + return false; + } + } + + public void releaseClient() { + unlockClient(); + actionsSemaphore.release(); + } + + public boolean tryacquireEncoder() { + if (actionsSemaphore.tryAcquire()) { + encoderLock.lock(); + return true; + } else { + return false; + } + } + + public void unlockEncoder() { + encoderLock.unlock(); + actionsSemaphore.release(); + } + + private static class CharNameAndId { + + public String name; + public int id; + + public CharNameAndId(String name, int id) { + super(); + this.name = name; + this.id = id; + } + } + + private static boolean checkHash(String hash, String type, String password) { + try { + MessageDigest digester = MessageDigest.getInstance(type); + digester.update(password.getBytes(StandardCharsets.UTF_8), 0, password.length()); + return HexTool.toString(digester.digest()).replace(" ", "").toLowerCase().equals(hash); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Encoding the string failed", e); + } + } + + public short getAvailableCharacterSlots() { + return (short) Math.max(0, characterSlots - Server.getInstance().getAccountCharacterCount(accId)); + } + + public short getAvailableCharacterWorldSlots() { + return (short) Math.max(0, characterSlots - Server.getInstance().getAccountWorldCharacterCount(accId, world)); + } - public short getAvailableCharacterSlots() { - return (short) Math.max(0, characterSlots - Server.getInstance().getAccountCharacterCount(accId)); - } - - public short getAvailableCharacterWorldSlots() { - return (short) Math.max(0, characterSlots - Server.getInstance().getAccountWorldCharacterCount(accId, world)); - } - public short getAvailableCharacterWorldSlots(int world) { return (short) Math.max(0, characterSlots - Server.getInstance().getAccountWorldCharacterCount(accId, world)); } - - public short getCharacterSlots() { - return characterSlots; - } - - public void setCharacterSlots(byte slots) { - characterSlots = slots; - } - - public boolean canGainCharacterSlot() { - return characterSlots < 15; - } - public synchronized boolean gainCharacterSlot() { - if (canGainCharacterSlot()) { - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("UPDATE accounts SET characterslots = ? WHERE id = ?")) { - ps.setInt(1, this.characterSlots += 1); - ps.setInt(2, accId); - ps.executeUpdate(); + public short getCharacterSlots() { + return characterSlots; + } - } catch (SQLException e) { - e.printStackTrace(); - } - return true; - } - return false; - } + public void setCharacterSlots(byte slots) { + characterSlots = slots; + } - public final byte getGReason() { - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT `greason` FROM `accounts` WHERE id = ?")) { - ps.setInt(1, accId); + public boolean canGainCharacterSlot() { + return characterSlots < 15; + } - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - return rs.getByte("greason"); - } - } - } catch (SQLException e) { - e.printStackTrace(); - } - return 0; - } + public synchronized boolean gainCharacterSlot() { + if (canGainCharacterSlot()) { + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE accounts SET characterslots = ? WHERE id = ?")) { + ps.setInt(1, this.characterSlots += 1); + ps.setInt(2, accId); + ps.executeUpdate(); - public byte getGender() { - return gender; - } - - public void setGender(byte m) { - this.gender = m; - - try (Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("UPDATE accounts SET gender = ? WHERE id = ?")) { - ps.setByte(1, gender); - ps.setInt(2, accId); - ps.executeUpdate(); - } catch (SQLException e) { - e.printStackTrace(); - } - } - - private void announceDisableServerMessage() { - if(!this.getWorldServer().registerDisabledServerMessage(player.getId())) { - announce(MaplePacketCreator.serverMessage("")); + } catch (SQLException e) { + e.printStackTrace(); } + return true; } - - public void announceServerMessage() { - announce(MaplePacketCreator.serverMessage(this.getChannelServer().getServerMessage())); - } - - public synchronized void announceBossHpBar(MapleMonster mm, final int mobHash, final byte[] packet) { - long timeNow = System.currentTimeMillis(); - int targetHash = player.getTargetHpBarHash(); - - if(mobHash != targetHash) { - 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? - announceDisableServerMessage(); - announce(packet); - - player.setTargetHpBarHash(mobHash); - player.setTargetHpBarTime(timeNow); - } - } else { - announceDisableServerMessage(); - announce(packet); - - player.setTargetHpBarTime(timeNow); + return false; + } + + public final byte getGReason() { + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT `greason` FROM `accounts` WHERE id = ?")) { + ps.setInt(1, accId); + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return rs.getByte("greason"); } - } - - public void announce(final byte[] packet) { // thanks GitGud for noticing an opportunity for improvement by overcoming "synchronized announce" - announcerLock.lock(); - try { - session.write(packet); - } finally { - announcerLock.unlock(); - } - } + } + } catch (SQLException e) { + e.printStackTrace(); + } + return 0; + } - public void announceHint(String msg, int length) { - announce(MaplePacketCreator.sendHint(msg, length, 10)); - announce(MaplePacketCreator.enableActions()); + public byte getGender() { + return gender; + } + + public void setGender(byte m) { + this.gender = m; + + try (Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("UPDATE accounts SET gender = ? WHERE id = ?")) { + ps.setByte(1, gender); + ps.setInt(2, accId); + ps.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + private void announceDisableServerMessage() { + if (!this.getWorldServer().registerDisabledServerMessage(player.getId())) { + announce(MaplePacketCreator.serverMessage("")); + } + } + + public void announceServerMessage() { + announce(MaplePacketCreator.serverMessage(this.getChannelServer().getServerMessage())); + } + + public synchronized void announceBossHpBar(MapleMonster mm, final int mobHash, final byte[] packet) { + long timeNow = System.currentTimeMillis(); + int targetHash = player.getTargetHpBarHash(); + + if (mobHash != targetHash) { + 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? + announceDisableServerMessage(); + announce(packet); + + player.setTargetHpBarHash(mobHash); + player.setTargetHpBarTime(timeNow); + } + } else { + announceDisableServerMessage(); + announce(packet); + + player.setTargetHpBarTime(timeNow); + } + } + + @Deprecated(forRemoval = true, since = "Netty migration") + public void announce(final byte[] packet) { // thanks GitGud for noticing an opportunity for improvement by overcoming "synchronized announce" + announcerLock.lock(); + try { + // 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 { + announcerLock.unlock(); + } + } + + public void announceHint(String msg, int length) { + announce(MaplePacketCreator.sendHint(msg, length, 10)); + announce(MaplePacketCreator.enableActions()); + } + + public void changeChannel(int channel) { + Server server = Server.getInstance(); + if (player.isBanned()) { + disconnect(false, false); + return; + } + if (!player.isAlive() || FieldLimit.CANNOTMIGRATE.check(player.getMap().getFieldLimit())) { + announce(MaplePacketCreator.enableActions()); + return; + } 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.enableActions()); + return; } - public void changeChannel(int channel) { - Server server = Server.getInstance(); - if (player.isBanned()) { - disconnect(false, false); - return; - } - if (!player.isAlive() || FieldLimit.CANNOTMIGRATE.check(player.getMap().getFieldLimit())) { - announce(MaplePacketCreator.enableActions()); - return; - } 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.enableActions()); - return; - } - - String[] socket = Server.getInstance().getInetSocket(this.getSession(), getWorld(), channel); - if(socket == null) { - announce(MaplePacketCreator.serverNotice(1, "Channel " + channel + " is currently disabled. Try another channel.")); - announce(MaplePacketCreator.enableActions()); - return; - } - - player.closePlayerInteractions(); - player.closePartySearchInteractions(); - - player.unregisterChairBuff(); - server.getPlayerBuffStorage().addBuffsToStorage(player.getId(), player.getAllBuffs()); - server.getPlayerBuffStorage().addDiseasesToStorage(player.getId(), player.getAllDiseases()); - player.setDisconnectedFromChannelWorld(); - player.notifyMapTransferToPartner(-1); - player.removeIncomingInvites(); - player.cancelAllBuffs(true); - player.cancelAllDebuffs(); - player.cancelBuffExpireTask(); - player.cancelDiseaseExpireTask(); - player.cancelSkillCooldownTask(); - player.cancelQuestExpirationTask(); - //Cancelling magicdoor? Nope - //Cancelling mounts? Noty - - player.getInventory(MapleInventoryType.EQUIPPED).checked(false); //test - player.getMap().removePlayer(player); - player.clearBanishPlayerData(); - player.getClient().getChannelServer().removePlayer(player); - - player.saveCharToDB(); - - player.setSessionTransitionState(); - try { - announce(MaplePacketCreator.getChannelChange(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]))); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public long getSessionId() { - return this.sessionId; - } - - public void setSessionId(long sessionId) { - this.sessionId = sessionId; - } - - public boolean canRequestCharlist(){ - return lastNpcClick + 877 < Server.getInstance().getCurrentTime(); - } - - public boolean canClickNPC(){ - return lastNpcClick + 500 < Server.getInstance().getCurrentTime(); - } - - public void setClickedNPC(){ - lastNpcClick = Server.getInstance().getCurrentTime(); - } - - public void removeClickedNPC(){ - lastNpcClick = 0; - } - - public int getVisibleWorlds(){ - return visibleWorlds; - } - - public void requestedServerlist(int worlds) { - visibleWorlds = worlds; - setClickedNPC(); - } - - public void closePlayerScriptInteractions() { - this.removeClickedNPC(); - NPCScriptManager.getInstance().dispose(this); - QuestScriptManager.getInstance().dispose(this); - } - - public boolean attemptCsCoupon() { - if (csattempt > 2) { - resetCsCoupon(); - return false; - } - - csattempt++; - return true; - } - - public void resetCsCoupon() { - csattempt = 0; - } - - public void enableCSActions() { - announce(MaplePacketCreator.enableCSUse(player)); - } - - public String getNibbleHWID() { - return (String) session.getAttribute(MapleClient.CLIENT_NIBBLEHWID); - } - - public boolean canBypassPin() { - return MapleLoginBypassCoordinator.getInstance().canLoginBypass(getNibbleHWID(), accId, false); - } - - public boolean canBypassPic() { - return MapleLoginBypassCoordinator.getInstance().canLoginBypass(getNibbleHWID(), accId, true); - } - - public int getLanguage() { - return lang; + String[] socket = Server.getInstance().getInetSocket(this, getWorld(), channel); + if (socket == null) { + announce(MaplePacketCreator.serverNotice(1, "Channel " + channel + " is currently disabled. Try another channel.")); + announce(MaplePacketCreator.enableActions()); + return; } - public void setLanguage(int lingua) { - this.lang = lingua; + player.closePlayerInteractions(); + player.closePartySearchInteractions(); + + player.unregisterChairBuff(); + server.getPlayerBuffStorage().addBuffsToStorage(player.getId(), player.getAllBuffs()); + server.getPlayerBuffStorage().addDiseasesToStorage(player.getId(), player.getAllDiseases()); + player.setDisconnectedFromChannelWorld(); + player.notifyMapTransferToPartner(-1); + player.removeIncomingInvites(); + player.cancelAllBuffs(true); + player.cancelAllDebuffs(); + player.cancelBuffExpireTask(); + player.cancelDiseaseExpireTask(); + player.cancelSkillCooldownTask(); + player.cancelQuestExpirationTask(); + //Cancelling magicdoor? Nope + //Cancelling mounts? Noty + + player.getInventory(MapleInventoryType.EQUIPPED).checked(false); //test + player.getMap().removePlayer(player); + player.clearBanishPlayerData(); + player.getClient().getChannelServer().removePlayer(player); + + player.saveCharToDB(); + + player.setSessionTransitionState(); + try { + announce(MaplePacketCreator.getChannelChange(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]))); + } catch (IOException e) { + e.printStackTrace(); } + } + + public long getSessionId() { + return this.sessionId; + } + + public boolean canRequestCharlist() { + return lastNpcClick + 877 < Server.getInstance().getCurrentTime(); + } + + public boolean canClickNPC() { + return lastNpcClick + 500 < Server.getInstance().getCurrentTime(); + } + + public void setClickedNPC() { + lastNpcClick = Server.getInstance().getCurrentTime(); + } + + public void removeClickedNPC() { + lastNpcClick = 0; + } + + public int getVisibleWorlds() { + return visibleWorlds; + } + + public void requestedServerlist(int worlds) { + visibleWorlds = worlds; + setClickedNPC(); + } + + public void closePlayerScriptInteractions() { + this.removeClickedNPC(); + NPCScriptManager.getInstance().dispose(this); + QuestScriptManager.getInstance().dispose(this); + } + + public boolean attemptCsCoupon() { + if (csattempt > 2) { + resetCsCoupon(); + return false; + } + + csattempt++; + return true; + } + + public void resetCsCoupon() { + csattempt = 0; + } + + public void enableCSActions() { + announce(MaplePacketCreator.enableCSUse(player)); + } + + public boolean canBypassPin() { + return MapleLoginBypassCoordinator.getInstance().canLoginBypass(hwid, accId, false); + } + + public boolean canBypassPic() { + return MapleLoginBypassCoordinator.getInstance().canLoginBypass(hwid, accId, true); + } + + public int getLanguage() { + return lang; + } + + public void setLanguage(int lingua) { + this.lang = lingua; + } } \ No newline at end of file diff --git a/src/main/java/client/autoban/AutobanFactory.java b/src/main/java/client/autoban/AutobanFactory.java index dbca257ea4..8f65f5463e 100644 --- a/src/main/java/client/autoban/AutobanFactory.java +++ b/src/main/java/client/autoban/AutobanFactory.java @@ -26,7 +26,7 @@ import client.MapleCharacter; import config.YamlConfig; import net.server.Server; import tools.FilePrinter; -import tools.MapleLogger; +import net.packet.logging.MapleLogger; import tools.MaplePacketCreator; /** diff --git a/src/main/java/client/command/commands/gm0/EnableAuthCommand.java b/src/main/java/client/command/commands/gm0/EnableAuthCommand.java index 000c6fe54d..97f6ffd366 100644 --- a/src/main/java/client/command/commands/gm0/EnableAuthCommand.java +++ b/src/main/java/client/command/commands/gm0/EnableAuthCommand.java @@ -36,7 +36,7 @@ public class EnableAuthCommand extends Command { public void execute(MapleClient c, String[] params) { if (c.tryacquireClient()) { try { - MapleLoginBypassCoordinator.getInstance().unregisterLoginBypassEntry(c.getNibbleHWID(), c.getAccID()); + MapleLoginBypassCoordinator.getInstance().unregisterLoginBypassEntry(c.getHwid(), c.getAccID()); } finally { c.releaseClient(); } diff --git a/src/main/java/client/command/commands/gm3/BanCommand.java b/src/main/java/client/command/commands/gm3/BanCommand.java index 21080eb3a8..38a773852b 100644 --- a/src/main/java/client/command/commands/gm3/BanCommand.java +++ b/src/main/java/client/command/commands/gm3/BanCommand.java @@ -52,7 +52,7 @@ public class BanCommand extends Command { MapleCharacter target = c.getChannelServer().getPlayerStorage().getCharacterByName(ign); if (target != null) { String readableTargetName = MapleCharacter.makeMapleReadable(target.getName()); - String ip = target.getClient().getSession().getRemoteAddress().toString().split(":")[0]; + String ip = target.getClient().getRemoteAddress(); //Ban ip try (Connection con = DatabaseConnection.getConnection()) { if (ip.matches("/[0-9]{1,3}\\..*")) { diff --git a/src/main/java/client/command/commands/gm3/IgnoreCommand.java b/src/main/java/client/command/commands/gm3/IgnoreCommand.java index 4fc0ceb168..226e63c323 100644 --- a/src/main/java/client/command/commands/gm3/IgnoreCommand.java +++ b/src/main/java/client/command/commands/gm3/IgnoreCommand.java @@ -27,7 +27,7 @@ import client.MapleCharacter; import client.MapleClient; import client.command.Command; import net.server.Server; -import tools.MapleLogger; +import net.packet.logging.MapleLogger; import tools.MaplePacketCreator; public class IgnoreCommand extends Command { diff --git a/src/main/java/client/command/commands/gm3/IgnoredCommand.java b/src/main/java/client/command/commands/gm3/IgnoredCommand.java index f15617e6dc..daf704699d 100644 --- a/src/main/java/client/command/commands/gm3/IgnoredCommand.java +++ b/src/main/java/client/command/commands/gm3/IgnoredCommand.java @@ -26,7 +26,7 @@ package client.command.commands.gm3; import client.MapleCharacter; import client.MapleClient; import client.command.Command; -import tools.MapleLogger; +import net.packet.logging.MapleLogger; public class IgnoredCommand extends Command { { diff --git a/src/main/java/client/command/commands/gm3/MonitorCommand.java b/src/main/java/client/command/commands/gm3/MonitorCommand.java index 89aa250421..0a82ce454b 100644 --- a/src/main/java/client/command/commands/gm3/MonitorCommand.java +++ b/src/main/java/client/command/commands/gm3/MonitorCommand.java @@ -27,7 +27,7 @@ import client.MapleCharacter; import client.MapleClient; import client.command.Command; import net.server.Server; -import tools.MapleLogger; +import net.packet.logging.MapleLogger; import tools.MaplePacketCreator; public class MonitorCommand extends Command { diff --git a/src/main/java/client/command/commands/gm3/MonitorsCommand.java b/src/main/java/client/command/commands/gm3/MonitorsCommand.java index 2a72da5ed5..97fd37a9d4 100644 --- a/src/main/java/client/command/commands/gm3/MonitorsCommand.java +++ b/src/main/java/client/command/commands/gm3/MonitorsCommand.java @@ -26,7 +26,7 @@ package client.command.commands.gm3; import client.MapleCharacter; import client.MapleClient; import client.command.Command; -import tools.MapleLogger; +import net.packet.logging.MapleLogger; public class MonitorsCommand extends Command { { diff --git a/src/main/java/client/command/commands/gm5/IpListCommand.java b/src/main/java/client/command/commands/gm5/IpListCommand.java index 1a832916b7..c7ae291fbd 100644 --- a/src/main/java/client/command/commands/gm5/IpListCommand.java +++ b/src/main/java/client/command/commands/gm5/IpListCommand.java @@ -50,7 +50,7 @@ public class IpListCommand extends Command { str += "\r\n" + GameConstants.WORLD_NAMES[w.getId()] + "\r\n"; for (MapleCharacter chr : chars) { - str += " " + chr.getName() + " - " + chr.getClient().getSession().getRemoteAddress() + "\r\n"; + str += " " + chr.getName() + " - " + chr.getClient().getRemoteAddress() + "\r\n"; } } } diff --git a/src/main/java/client/command/commands/gm5/ShowSessionsCommand.java b/src/main/java/client/command/commands/gm5/ShowSessionsCommand.java index 40ed9ed114..71d31c9982 100644 --- a/src/main/java/client/command/commands/gm5/ShowSessionsCommand.java +++ b/src/main/java/client/command/commands/gm5/ShowSessionsCommand.java @@ -21,7 +21,7 @@ package client.command.commands.gm5; import client.MapleClient; import client.command.Command; -import net.server.coordinator.session.MapleSessionCoordinator; +import net.server.coordinator.session.SessionCoordinator; /** * @@ -34,6 +34,6 @@ public class ShowSessionsCommand extends Command { @Override public void execute(MapleClient c, String[] params) { - MapleSessionCoordinator.getInstance().printSessionTrace(c); + SessionCoordinator.getInstance().printSessionTrace(c); } } diff --git a/src/main/java/client/command/commands/gm6/WarpWorldCommand.java b/src/main/java/client/command/commands/gm6/WarpWorldCommand.java index 9670f16a87..0512f1f4fc 100644 --- a/src/main/java/client/command/commands/gm6/WarpWorldCommand.java +++ b/src/main/java/client/command/commands/gm6/WarpWorldCommand.java @@ -49,7 +49,7 @@ public class WarpWorldCommand extends Command { byte worldb = Byte.parseByte(params[0]); if (worldb <= (server.getWorldsSize() - 1)) { try { - String[] socket = server.getInetSocket(c.getSession(), worldb, c.getChannel()); + String[] socket = server.getInetSocket(c, worldb, c.getChannel()); c.getWorldServer().removePlayer(player); player.getMap().removePlayer(player);//LOL FORGOT THIS >< player.setSessionTransitionState(); diff --git a/src/main/java/constants/net/ServerConstants.java b/src/main/java/constants/net/ServerConstants.java index 2905adbccd..9b5f6b1d8b 100644 --- a/src/main/java/constants/net/ServerConstants.java +++ b/src/main/java/constants/net/ServerConstants.java @@ -3,7 +3,7 @@ package constants.net; public class ServerConstants { //Server Version - public static short VERSION = 83; + public static final short VERSION = 83; //Debug Variables public static int[] DEBUG_VALUES = new int[10]; // Field designed for packet testing purposes diff --git a/src/main/java/net/MapleServerHandler.java b/src/main/java/net/MapleServerHandler.java deleted file mode 100644 index edca4bfda8..0000000000 --- a/src/main/java/net/MapleServerHandler.java +++ /dev/null @@ -1,305 +0,0 @@ -/* -This file is part of the OdinMS Maple Story Server -Copyright (C) 2008 Patrick Huy -Matthias Butz -Jan Christian Meyer - -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 . - */ -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 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 idleSessions = new HashMap<>(100); - private Map 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 pingClients = new HashSet<>(); - idleLock.lock(); - try { - for(Entry mc : idleSessions.entrySet()) { - if(timeNow - mc.getValue() >= 15000) { - pingClients.add(mc.getKey()); - } - } - idleSessions.clear(); - - if(!tempIdleSessions.isEmpty()) { - tempLock.lock(); - try { - for(Entry 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(); - } -} diff --git a/src/main/java/net/PacketProcessor.java b/src/main/java/net/PacketProcessor.java index 94e07b5441..487a5c2985 100644 --- a/src/main/java/net/PacketProcessor.java +++ b/src/main/java/net/PacketProcessor.java @@ -21,34 +21,16 @@ */ package net; -import java.util.LinkedHashMap; -import java.util.Map; - +import net.netty.LoginServer; import net.opcodes.RecvOpcode; import net.server.channel.handlers.*; import net.server.handlers.CustomPacketHandler; import net.server.handlers.KeepAliveHandler; import net.server.handlers.LoginRequiringNoOpHandler; -import net.server.handlers.login.AcceptToSHandler; -import net.server.handlers.login.AfterLoginHandler; -import net.server.handlers.login.CharSelectedHandler; -import net.server.handlers.login.CharSelectedWithPicHandler; -import net.server.handlers.login.CharlistRequestHandler; -import net.server.handlers.login.CheckCharNameHandler; -import net.server.handlers.login.CreateCharHandler; -import net.server.handlers.login.DeleteCharHandler; -import net.server.handlers.login.GuestLoginHandler; -import net.server.handlers.login.LoginPasswordHandler; -import net.server.handlers.login.RegisterPicHandler; -import net.server.handlers.login.RegisterPinHandler; -import net.server.handlers.login.RelogRequestHandler; -import net.server.handlers.login.ServerStatusRequestHandler; -import net.server.handlers.login.ServerlistRequestHandler; -import net.server.handlers.login.SetGenderHandler; -import net.server.handlers.login.ViewAllCharHandler; -import net.server.handlers.login.ViewAllCharRegisterPicHandler; -import net.server.handlers.login.ViewAllCharSelectedHandler; -import net.server.handlers.login.ViewAllCharSelectedWithPicHandler; +import net.server.handlers.login.*; + +import java.util.LinkedHashMap; +import java.util.Map; public final class PacketProcessor { @@ -65,6 +47,14 @@ public final class PacketProcessor { handlers = new MaplePacketHandler[maxRecvOp + 1]; } + public static PacketProcessor getLoginServerProcessor() { + return getProcessor(LoginServer.WORLD_ID, LoginServer.CHANNEL_ID); + } + + public static PacketProcessor getChannelServerProcessor(int world, int channel) { + return getProcessor(world, channel); + } + public MaplePacketHandler getHandler(short packetId) { if (packetId > handlers.length) { return null; @@ -86,12 +76,12 @@ public final class PacketProcessor { } public synchronized static PacketProcessor getProcessor(int world, int channel) { - final String lolpair = world + " " + channel; - PacketProcessor processor = instances.get(lolpair); + final String processorId = world + " " + channel; + PacketProcessor processor = instances.get(processorId); if (processor == null) { processor = new PacketProcessor(); processor.reset(channel); - instances.put(lolpair, processor); + instances.put(processorId, processor); } return processor; } @@ -99,181 +89,192 @@ public final class PacketProcessor { public void reset(int channel) { handlers = new MaplePacketHandler[handlers.length]; - registerHandler(RecvOpcode.PONG, new KeepAliveHandler()); - registerHandler(RecvOpcode.CUSTOM_PACKET, new CustomPacketHandler()); + registerCommonHandlers(); + if (channel < 0) { - //LOGIN HANDLERS - registerHandler(RecvOpcode.ACCEPT_TOS, new AcceptToSHandler()); - registerHandler(RecvOpcode.AFTER_LOGIN, new AfterLoginHandler()); - registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler()); - registerHandler(RecvOpcode.CHARLIST_REQUEST, new CharlistRequestHandler()); - registerHandler(RecvOpcode.CHAR_SELECT, new CharSelectedHandler()); - registerHandler(RecvOpcode.LOGIN_PASSWORD, new LoginPasswordHandler()); - registerHandler(RecvOpcode.RELOG, new RelogRequestHandler()); - registerHandler(RecvOpcode.SERVERLIST_REQUEST, new ServerlistRequestHandler()); - registerHandler(RecvOpcode.SERVERSTATUS_REQUEST, new ServerStatusRequestHandler()); - registerHandler(RecvOpcode.CHECK_CHAR_NAME, new CheckCharNameHandler()); - registerHandler(RecvOpcode.CREATE_CHAR, new CreateCharHandler()); - registerHandler(RecvOpcode.DELETE_CHAR, new DeleteCharHandler()); - registerHandler(RecvOpcode.VIEW_ALL_CHAR, new ViewAllCharHandler()); - registerHandler(RecvOpcode.PICK_ALL_CHAR, new ViewAllCharSelectedHandler()); - registerHandler(RecvOpcode.REGISTER_PIN, new RegisterPinHandler()); - registerHandler(RecvOpcode.GUEST_LOGIN, new GuestLoginHandler()); - registerHandler(RecvOpcode.REGISTER_PIC, new RegisterPicHandler()); - registerHandler(RecvOpcode.CHAR_SELECT_WITH_PIC, new CharSelectedWithPicHandler()); - registerHandler(RecvOpcode.SET_GENDER, new SetGenderHandler()); - registerHandler(RecvOpcode.VIEW_ALL_WITH_PIC, new ViewAllCharSelectedWithPicHandler()); - registerHandler(RecvOpcode.VIEW_ALL_PIC_REGISTER, new ViewAllCharRegisterPicHandler()); + registerLoginHandlers(); } else { - //CHANNEL HANDLERS - registerHandler(RecvOpcode.NAME_TRANSFER, new TransferNameHandler()); - registerHandler(RecvOpcode.CHECK_CHAR_NAME, new TransferNameResultHandler()); - registerHandler(RecvOpcode.WORLD_TRANSFER, new TransferWorldHandler()); - registerHandler(RecvOpcode.CHANGE_CHANNEL, new ChangeChannelHandler()); - registerHandler(RecvOpcode.STRANGE_DATA, LoginRequiringNoOpHandler.getInstance()); - registerHandler(RecvOpcode.GENERAL_CHAT, new GeneralChatHandler()); - registerHandler(RecvOpcode.WHISPER, new WhisperHandler()); - registerHandler(RecvOpcode.NPC_TALK, new NPCTalkHandler()); - registerHandler(RecvOpcode.NPC_TALK_MORE, new NPCMoreTalkHandler()); - registerHandler(RecvOpcode.QUEST_ACTION, new QuestActionHandler()); - registerHandler(RecvOpcode.GRENADE_EFFECT, new GrenadeEffectHandler()); - registerHandler(RecvOpcode.NPC_SHOP, new NPCShopHandler()); - registerHandler(RecvOpcode.ITEM_SORT, new InventoryMergeHandler()); - registerHandler(RecvOpcode.ITEM_MOVE, new ItemMoveHandler()); - registerHandler(RecvOpcode.MESO_DROP, new MesoDropHandler()); - registerHandler(RecvOpcode.PLAYER_LOGGEDIN, new PlayerLoggedinHandler()); - registerHandler(RecvOpcode.CHANGE_MAP, new ChangeMapHandler()); - registerHandler(RecvOpcode.MOVE_LIFE, new MoveLifeHandler()); - registerHandler(RecvOpcode.CLOSE_RANGE_ATTACK, new CloseRangeDamageHandler()); - registerHandler(RecvOpcode.RANGED_ATTACK, new RangedAttackHandler()); - registerHandler(RecvOpcode.MAGIC_ATTACK, new MagicDamageHandler()); - registerHandler(RecvOpcode.TAKE_DAMAGE, new TakeDamageHandler()); - registerHandler(RecvOpcode.MOVE_PLAYER, new MovePlayerHandler()); - registerHandler(RecvOpcode.USE_CASH_ITEM, new UseCashItemHandler()); - registerHandler(RecvOpcode.USE_ITEM, new UseItemHandler()); - registerHandler(RecvOpcode.USE_RETURN_SCROLL, new UseItemHandler()); - registerHandler(RecvOpcode.USE_UPGRADE_SCROLL, new ScrollHandler()); - registerHandler(RecvOpcode.USE_SUMMON_BAG, new UseSummonBagHandler()); - registerHandler(RecvOpcode.FACE_EXPRESSION, new FaceExpressionHandler()); - registerHandler(RecvOpcode.HEAL_OVER_TIME, new HealOvertimeHandler()); - registerHandler(RecvOpcode.ITEM_PICKUP, new ItemPickupHandler()); - registerHandler(RecvOpcode.CHAR_INFO_REQUEST, new CharInfoRequestHandler()); - registerHandler(RecvOpcode.SPECIAL_MOVE, new SpecialMoveHandler()); - registerHandler(RecvOpcode.USE_INNER_PORTAL, new InnerPortalHandler()); - registerHandler(RecvOpcode.CANCEL_BUFF, new CancelBuffHandler()); - registerHandler(RecvOpcode.CANCEL_ITEM_EFFECT, new CancelItemEffectHandler()); - registerHandler(RecvOpcode.PLAYER_INTERACTION, new PlayerInteractionHandler()); - registerHandler(RecvOpcode.RPS_ACTION, new RPSActionHandler()); - registerHandler(RecvOpcode.DISTRIBUTE_AP, new DistributeAPHandler()); - registerHandler(RecvOpcode.DISTRIBUTE_SP, new DistributeSPHandler()); - registerHandler(RecvOpcode.CHANGE_KEYMAP, new KeymapChangeHandler()); - registerHandler(RecvOpcode.CHANGE_MAP_SPECIAL, new ChangeMapSpecialHandler()); - registerHandler(RecvOpcode.STORAGE, new StorageHandler()); - registerHandler(RecvOpcode.GIVE_FAME, new GiveFameHandler()); - registerHandler(RecvOpcode.PARTY_OPERATION, new PartyOperationHandler()); - registerHandler(RecvOpcode.DENY_PARTY_REQUEST, new DenyPartyRequestHandler()); - registerHandler(RecvOpcode.MULTI_CHAT, new MultiChatHandler()); - registerHandler(RecvOpcode.USE_DOOR, new DoorHandler()); - registerHandler(RecvOpcode.ENTER_MTS, new EnterMTSHandler()); - registerHandler(RecvOpcode.ENTER_CASHSHOP, new EnterCashShopHandler()); - registerHandler(RecvOpcode.DAMAGE_SUMMON, new DamageSummonHandler()); - registerHandler(RecvOpcode.MOVE_SUMMON, new MoveSummonHandler()); - registerHandler(RecvOpcode.SUMMON_ATTACK, new SummonDamageHandler()); - registerHandler(RecvOpcode.BUDDYLIST_MODIFY, new BuddylistModifyHandler()); - registerHandler(RecvOpcode.USE_ITEMEFFECT, new UseItemEffectHandler()); - registerHandler(RecvOpcode.USE_CHAIR, new UseChairHandler()); - registerHandler(RecvOpcode.CANCEL_CHAIR, new CancelChairHandler()); - registerHandler(RecvOpcode.DAMAGE_REACTOR, new ReactorHitHandler()); - registerHandler(RecvOpcode.GUILD_OPERATION, new GuildOperationHandler()); - registerHandler(RecvOpcode.DENY_GUILD_REQUEST, new DenyGuildRequestHandler()); - registerHandler(RecvOpcode.BBS_OPERATION, new BBSOperationHandler()); - registerHandler(RecvOpcode.SKILL_EFFECT, new SkillEffectHandler()); - registerHandler(RecvOpcode.MESSENGER, new MessengerHandler()); - registerHandler(RecvOpcode.NPC_ACTION, new NPCAnimationHandler()); - registerHandler(RecvOpcode.CHECK_CASH, new TouchingCashShopHandler()); - registerHandler(RecvOpcode.CASHSHOP_OPERATION, new CashOperationHandler()); - registerHandler(RecvOpcode.COUPON_CODE, new CouponCodeHandler()); - registerHandler(RecvOpcode.SPAWN_PET, new SpawnPetHandler()); - registerHandler(RecvOpcode.MOVE_PET, new MovePetHandler()); - registerHandler(RecvOpcode.PET_CHAT, new PetChatHandler()); - registerHandler(RecvOpcode.PET_COMMAND, new PetCommandHandler()); - registerHandler(RecvOpcode.PET_FOOD, new PetFoodHandler()); - registerHandler(RecvOpcode.PET_LOOT, new PetLootHandler()); - registerHandler(RecvOpcode.AUTO_AGGRO, new AutoAggroHandler()); - registerHandler(RecvOpcode.MONSTER_BOMB, new MonsterBombHandler()); - registerHandler(RecvOpcode.CANCEL_DEBUFF, new CancelDebuffHandler()); - registerHandler(RecvOpcode.USE_SKILL_BOOK, new SkillBookHandler()); - registerHandler(RecvOpcode.SKILL_MACRO, new SkillMacroHandler()); - registerHandler(RecvOpcode.NOTE_ACTION, new NoteActionHandler()); - registerHandler(RecvOpcode.CLOSE_CHALKBOARD, new CloseChalkboardHandler()); - registerHandler(RecvOpcode.USE_MOUNT_FOOD, new UseMountFoodHandler()); - registerHandler(RecvOpcode.MTS_OPERATION, new MTSHandler()); - registerHandler(RecvOpcode.RING_ACTION, new RingActionHandler()); - registerHandler(RecvOpcode.SPOUSE_CHAT, new SpouseChatHandler()); - registerHandler(RecvOpcode.PET_AUTO_POT, new PetAutoPotHandler()); - registerHandler(RecvOpcode.PET_EXCLUDE_ITEMS, new PetExcludeItemsHandler()); - registerHandler(RecvOpcode.OWL_ACTION, new UseOwlOfMinervaHandler()); - registerHandler(RecvOpcode.OWL_WARP, new OwlWarpHandler()); - registerHandler(RecvOpcode.TOUCH_MONSTER_ATTACK, new TouchMonsterDamageHandler()); - registerHandler(RecvOpcode.TROCK_ADD_MAP, new TrockAddMapHandler()); - registerHandler(RecvOpcode.HIRED_MERCHANT_REQUEST, new HiredMerchantRequest()); - registerHandler(RecvOpcode.MOB_BANISH_PLAYER, new MobBanishPlayerHandler()); - registerHandler(RecvOpcode.MOB_DAMAGE_MOB, new MobDamageMobHandler()); - registerHandler(RecvOpcode.REPORT, new ReportHandler()); - registerHandler(RecvOpcode.MONSTER_BOOK_COVER, new MonsterBookCoverHandler()); - registerHandler(RecvOpcode.AUTO_DISTRIBUTE_AP, new AutoAssignHandler()); - registerHandler(RecvOpcode.MAKER_SKILL, new MakerSkillHandler()); - registerHandler(RecvOpcode.OPEN_FAMILY_PEDIGREE, new OpenFamilyPedigreeHandler()); - registerHandler(RecvOpcode.OPEN_FAMILY, new OpenFamilyHandler()); - registerHandler(RecvOpcode.ADD_FAMILY, new FamilyAddHandler()); - registerHandler(RecvOpcode.SEPARATE_FAMILY_BY_SENIOR, new FamilySeparateHandler()); - registerHandler(RecvOpcode.SEPARATE_FAMILY_BY_JUNIOR, new FamilySeparateHandler()); - registerHandler(RecvOpcode.USE_FAMILY, new FamilyUseHandler()); - registerHandler(RecvOpcode.CHANGE_FAMILY_MESSAGE, new FamilyPreceptsHandler()); - registerHandler(RecvOpcode.FAMILY_SUMMON_RESPONSE, new FamilySummonResponseHandler()); - registerHandler(RecvOpcode.USE_HAMMER, new UseHammerHandler()); - registerHandler(RecvOpcode.SCRIPTED_ITEM, new ScriptedItemHandler()); - registerHandler(RecvOpcode.TOUCHING_REACTOR, new TouchReactorHandler()); - registerHandler(RecvOpcode.BEHOLDER, new BeholderHandler()); - registerHandler(RecvOpcode.ADMIN_COMMAND, new AdminCommandHandler()); - registerHandler(RecvOpcode.ADMIN_LOG, new AdminLogHandler()); - registerHandler(RecvOpcode.ALLIANCE_OPERATION, new AllianceOperationHandler()); - registerHandler(RecvOpcode.DENY_ALLIANCE_REQUEST, new DenyAllianceRequestHandler()); - registerHandler(RecvOpcode.USE_SOLOMON_ITEM, new UseSolomonHandler()); - registerHandler(RecvOpcode.USE_GACHA_EXP, new UseGachaExpHandler()); - registerHandler(RecvOpcode.NEW_YEAR_CARD_REQUEST, new NewYearCardHandler()); - registerHandler(RecvOpcode.CASHSHOP_SURPRISE, new CashShopSurpriseHandler()); - registerHandler(RecvOpcode.USE_ITEM_REWARD, new ItemRewardHandler()); - registerHandler(RecvOpcode.USE_REMOTE, new RemoteGachaponHandler()); - registerHandler(RecvOpcode.ACCEPT_FAMILY, new AcceptFamilyHandler()); - registerHandler(RecvOpcode.DUEY_ACTION, new DueyHandler()); - registerHandler(RecvOpcode.USE_DEATHITEM, new UseDeathItemHandler()); - registerHandler(RecvOpcode.PLAYER_MAP_TRANSFER, new PlayerMapTransitionHandler()); - registerHandler(RecvOpcode.USE_MAPLELIFE, new UseMapleLifeHandler()); - registerHandler(RecvOpcode.USE_CATCH_ITEM, new UseCatchItemHandler()); - registerHandler(RecvOpcode.FIELD_DAMAGE_MOB, new FieldDamageMobHandler()); - registerHandler(RecvOpcode.MOB_DAMAGE_MOB_FRIENDLY, new MobDamageMobFriendlyHandler()); - registerHandler(RecvOpcode.PARTY_SEARCH_REGISTER, new PartySearchRegisterHandler()); - registerHandler(RecvOpcode.PARTY_SEARCH_START, new PartySearchStartHandler()); - registerHandler(RecvOpcode.PARTY_SEARCH_UPDATE, new PartySearchUpdateHandler()); - registerHandler(RecvOpcode.ITEM_SORT2, new InventorySortHandler()); - registerHandler(RecvOpcode.LEFT_KNOCKBACK, new LeftKnockbackHandler()); - registerHandler(RecvOpcode.SNOWBALL, new SnowballHandler()); - registerHandler(RecvOpcode.COCONUT, new CoconutHandler()); - registerHandler(RecvOpcode.ARAN_COMBO_COUNTER, new AranComboHandler()); - registerHandler(RecvOpcode.CLICK_GUIDE, new ClickGuideHandler()); - registerHandler(RecvOpcode.FREDRICK_ACTION, new FredrickHandler()); - registerHandler(RecvOpcode.MONSTER_CARNIVAL, new MonsterCarnivalHandler()); - registerHandler(RecvOpcode.REMOTE_STORE, new RemoteStoreHandler()); - registerHandler(RecvOpcode.WEDDING_ACTION, new WeddingHandler()); - registerHandler(RecvOpcode.WEDDING_TALK, new WeddingTalkHandler()); - registerHandler(RecvOpcode.WEDDING_TALK_MORE, new WeddingTalkMoreHandler()); - registerHandler(RecvOpcode.WATER_OF_LIFE, new UseWaterOfLifeHandler()); - registerHandler(RecvOpcode.ADMIN_CHAT, new AdminChatHandler()); - registerHandler(RecvOpcode.MOVE_DRAGON, new MoveDragonHandler()); - registerHandler(RecvOpcode.OPEN_ITEMUI, new RaiseUIStateHandler()); - registerHandler(RecvOpcode.USE_ITEMUI, new RaiseIncExpHandler()); - registerHandler(RecvOpcode.CHANGE_QUICKSLOT, new QuickslotKeyMappedModifiedHandler()); + registerChannelHandlers(); } } + + private void registerCommonHandlers() { + registerHandler(RecvOpcode.PONG, new KeepAliveHandler()); + registerHandler(RecvOpcode.CUSTOM_PACKET, new CustomPacketHandler()); + } + + private void registerLoginHandlers() { + registerHandler(RecvOpcode.ACCEPT_TOS, new AcceptToSHandler()); + registerHandler(RecvOpcode.AFTER_LOGIN, new AfterLoginHandler()); + registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler()); + registerHandler(RecvOpcode.CHARLIST_REQUEST, new CharlistRequestHandler()); + registerHandler(RecvOpcode.CHAR_SELECT, new CharSelectedHandler()); + registerHandler(RecvOpcode.LOGIN_PASSWORD, new LoginPasswordHandler()); + registerHandler(RecvOpcode.RELOG, new RelogRequestHandler()); + registerHandler(RecvOpcode.SERVERLIST_REQUEST, new ServerlistRequestHandler()); + registerHandler(RecvOpcode.SERVERSTATUS_REQUEST, new ServerStatusRequestHandler()); + registerHandler(RecvOpcode.CHECK_CHAR_NAME, new CheckCharNameHandler()); + registerHandler(RecvOpcode.CREATE_CHAR, new CreateCharHandler()); + registerHandler(RecvOpcode.DELETE_CHAR, new DeleteCharHandler()); + registerHandler(RecvOpcode.VIEW_ALL_CHAR, new ViewAllCharHandler()); + registerHandler(RecvOpcode.PICK_ALL_CHAR, new ViewAllCharSelectedHandler()); + registerHandler(RecvOpcode.REGISTER_PIN, new RegisterPinHandler()); + registerHandler(RecvOpcode.GUEST_LOGIN, new GuestLoginHandler()); + registerHandler(RecvOpcode.REGISTER_PIC, new RegisterPicHandler()); + registerHandler(RecvOpcode.CHAR_SELECT_WITH_PIC, new CharSelectedWithPicHandler()); + registerHandler(RecvOpcode.SET_GENDER, new SetGenderHandler()); + registerHandler(RecvOpcode.VIEW_ALL_WITH_PIC, new ViewAllCharSelectedWithPicHandler()); + registerHandler(RecvOpcode.VIEW_ALL_PIC_REGISTER, new ViewAllCharRegisterPicHandler()); + } + + private void registerChannelHandlers() { + registerHandler(RecvOpcode.NAME_TRANSFER, new TransferNameHandler()); + registerHandler(RecvOpcode.CHECK_CHAR_NAME, new TransferNameResultHandler()); + registerHandler(RecvOpcode.WORLD_TRANSFER, new TransferWorldHandler()); + registerHandler(RecvOpcode.CHANGE_CHANNEL, new ChangeChannelHandler()); + registerHandler(RecvOpcode.STRANGE_DATA, LoginRequiringNoOpHandler.getInstance()); + registerHandler(RecvOpcode.GENERAL_CHAT, new GeneralChatHandler()); + registerHandler(RecvOpcode.WHISPER, new WhisperHandler()); + registerHandler(RecvOpcode.NPC_TALK, new NPCTalkHandler()); + registerHandler(RecvOpcode.NPC_TALK_MORE, new NPCMoreTalkHandler()); + registerHandler(RecvOpcode.QUEST_ACTION, new QuestActionHandler()); + registerHandler(RecvOpcode.GRENADE_EFFECT, new GrenadeEffectHandler()); + registerHandler(RecvOpcode.NPC_SHOP, new NPCShopHandler()); + registerHandler(RecvOpcode.ITEM_SORT, new InventoryMergeHandler()); + registerHandler(RecvOpcode.ITEM_MOVE, new ItemMoveHandler()); + registerHandler(RecvOpcode.MESO_DROP, new MesoDropHandler()); + registerHandler(RecvOpcode.PLAYER_LOGGEDIN, new PlayerLoggedinHandler()); + registerHandler(RecvOpcode.CHANGE_MAP, new ChangeMapHandler()); + registerHandler(RecvOpcode.MOVE_LIFE, new MoveLifeHandler()); + registerHandler(RecvOpcode.CLOSE_RANGE_ATTACK, new CloseRangeDamageHandler()); + registerHandler(RecvOpcode.RANGED_ATTACK, new RangedAttackHandler()); + registerHandler(RecvOpcode.MAGIC_ATTACK, new MagicDamageHandler()); + registerHandler(RecvOpcode.TAKE_DAMAGE, new TakeDamageHandler()); + registerHandler(RecvOpcode.MOVE_PLAYER, new MovePlayerHandler()); + registerHandler(RecvOpcode.USE_CASH_ITEM, new UseCashItemHandler()); + registerHandler(RecvOpcode.USE_ITEM, new UseItemHandler()); + registerHandler(RecvOpcode.USE_RETURN_SCROLL, new UseItemHandler()); + registerHandler(RecvOpcode.USE_UPGRADE_SCROLL, new ScrollHandler()); + registerHandler(RecvOpcode.USE_SUMMON_BAG, new UseSummonBagHandler()); + registerHandler(RecvOpcode.FACE_EXPRESSION, new FaceExpressionHandler()); + registerHandler(RecvOpcode.HEAL_OVER_TIME, new HealOvertimeHandler()); + registerHandler(RecvOpcode.ITEM_PICKUP, new ItemPickupHandler()); + registerHandler(RecvOpcode.CHAR_INFO_REQUEST, new CharInfoRequestHandler()); + registerHandler(RecvOpcode.SPECIAL_MOVE, new SpecialMoveHandler()); + registerHandler(RecvOpcode.USE_INNER_PORTAL, new InnerPortalHandler()); + registerHandler(RecvOpcode.CANCEL_BUFF, new CancelBuffHandler()); + registerHandler(RecvOpcode.CANCEL_ITEM_EFFECT, new CancelItemEffectHandler()); + registerHandler(RecvOpcode.PLAYER_INTERACTION, new PlayerInteractionHandler()); + registerHandler(RecvOpcode.RPS_ACTION, new RPSActionHandler()); + registerHandler(RecvOpcode.DISTRIBUTE_AP, new DistributeAPHandler()); + registerHandler(RecvOpcode.DISTRIBUTE_SP, new DistributeSPHandler()); + registerHandler(RecvOpcode.CHANGE_KEYMAP, new KeymapChangeHandler()); + registerHandler(RecvOpcode.CHANGE_MAP_SPECIAL, new ChangeMapSpecialHandler()); + registerHandler(RecvOpcode.STORAGE, new StorageHandler()); + registerHandler(RecvOpcode.GIVE_FAME, new GiveFameHandler()); + registerHandler(RecvOpcode.PARTY_OPERATION, new PartyOperationHandler()); + registerHandler(RecvOpcode.DENY_PARTY_REQUEST, new DenyPartyRequestHandler()); + registerHandler(RecvOpcode.MULTI_CHAT, new MultiChatHandler()); + registerHandler(RecvOpcode.USE_DOOR, new DoorHandler()); + registerHandler(RecvOpcode.ENTER_MTS, new EnterMTSHandler()); + registerHandler(RecvOpcode.ENTER_CASHSHOP, new EnterCashShopHandler()); + registerHandler(RecvOpcode.DAMAGE_SUMMON, new DamageSummonHandler()); + registerHandler(RecvOpcode.MOVE_SUMMON, new MoveSummonHandler()); + registerHandler(RecvOpcode.SUMMON_ATTACK, new SummonDamageHandler()); + registerHandler(RecvOpcode.BUDDYLIST_MODIFY, new BuddylistModifyHandler()); + registerHandler(RecvOpcode.USE_ITEMEFFECT, new UseItemEffectHandler()); + registerHandler(RecvOpcode.USE_CHAIR, new UseChairHandler()); + registerHandler(RecvOpcode.CANCEL_CHAIR, new CancelChairHandler()); + registerHandler(RecvOpcode.DAMAGE_REACTOR, new ReactorHitHandler()); + registerHandler(RecvOpcode.GUILD_OPERATION, new GuildOperationHandler()); + registerHandler(RecvOpcode.DENY_GUILD_REQUEST, new DenyGuildRequestHandler()); + registerHandler(RecvOpcode.BBS_OPERATION, new BBSOperationHandler()); + registerHandler(RecvOpcode.SKILL_EFFECT, new SkillEffectHandler()); + registerHandler(RecvOpcode.MESSENGER, new MessengerHandler()); + registerHandler(RecvOpcode.NPC_ACTION, new NPCAnimationHandler()); + registerHandler(RecvOpcode.CHECK_CASH, new TouchingCashShopHandler()); + registerHandler(RecvOpcode.CASHSHOP_OPERATION, new CashOperationHandler()); + registerHandler(RecvOpcode.COUPON_CODE, new CouponCodeHandler()); + registerHandler(RecvOpcode.SPAWN_PET, new SpawnPetHandler()); + registerHandler(RecvOpcode.MOVE_PET, new MovePetHandler()); + registerHandler(RecvOpcode.PET_CHAT, new PetChatHandler()); + registerHandler(RecvOpcode.PET_COMMAND, new PetCommandHandler()); + registerHandler(RecvOpcode.PET_FOOD, new PetFoodHandler()); + registerHandler(RecvOpcode.PET_LOOT, new PetLootHandler()); + registerHandler(RecvOpcode.AUTO_AGGRO, new AutoAggroHandler()); + registerHandler(RecvOpcode.MONSTER_BOMB, new MonsterBombHandler()); + registerHandler(RecvOpcode.CANCEL_DEBUFF, new CancelDebuffHandler()); + registerHandler(RecvOpcode.USE_SKILL_BOOK, new SkillBookHandler()); + registerHandler(RecvOpcode.SKILL_MACRO, new SkillMacroHandler()); + registerHandler(RecvOpcode.NOTE_ACTION, new NoteActionHandler()); + registerHandler(RecvOpcode.CLOSE_CHALKBOARD, new CloseChalkboardHandler()); + registerHandler(RecvOpcode.USE_MOUNT_FOOD, new UseMountFoodHandler()); + registerHandler(RecvOpcode.MTS_OPERATION, new MTSHandler()); + registerHandler(RecvOpcode.RING_ACTION, new RingActionHandler()); + registerHandler(RecvOpcode.SPOUSE_CHAT, new SpouseChatHandler()); + registerHandler(RecvOpcode.PET_AUTO_POT, new PetAutoPotHandler()); + registerHandler(RecvOpcode.PET_EXCLUDE_ITEMS, new PetExcludeItemsHandler()); + registerHandler(RecvOpcode.OWL_ACTION, new UseOwlOfMinervaHandler()); + registerHandler(RecvOpcode.OWL_WARP, new OwlWarpHandler()); + registerHandler(RecvOpcode.TOUCH_MONSTER_ATTACK, new TouchMonsterDamageHandler()); + registerHandler(RecvOpcode.TROCK_ADD_MAP, new TrockAddMapHandler()); + registerHandler(RecvOpcode.HIRED_MERCHANT_REQUEST, new HiredMerchantRequest()); + registerHandler(RecvOpcode.MOB_BANISH_PLAYER, new MobBanishPlayerHandler()); + registerHandler(RecvOpcode.MOB_DAMAGE_MOB, new MobDamageMobHandler()); + registerHandler(RecvOpcode.REPORT, new ReportHandler()); + registerHandler(RecvOpcode.MONSTER_BOOK_COVER, new MonsterBookCoverHandler()); + registerHandler(RecvOpcode.AUTO_DISTRIBUTE_AP, new AutoAssignHandler()); + registerHandler(RecvOpcode.MAKER_SKILL, new MakerSkillHandler()); + registerHandler(RecvOpcode.OPEN_FAMILY_PEDIGREE, new OpenFamilyPedigreeHandler()); + registerHandler(RecvOpcode.OPEN_FAMILY, new OpenFamilyHandler()); + registerHandler(RecvOpcode.ADD_FAMILY, new FamilyAddHandler()); + registerHandler(RecvOpcode.SEPARATE_FAMILY_BY_SENIOR, new FamilySeparateHandler()); + registerHandler(RecvOpcode.SEPARATE_FAMILY_BY_JUNIOR, new FamilySeparateHandler()); + registerHandler(RecvOpcode.USE_FAMILY, new FamilyUseHandler()); + registerHandler(RecvOpcode.CHANGE_FAMILY_MESSAGE, new FamilyPreceptsHandler()); + registerHandler(RecvOpcode.FAMILY_SUMMON_RESPONSE, new FamilySummonResponseHandler()); + registerHandler(RecvOpcode.USE_HAMMER, new UseHammerHandler()); + registerHandler(RecvOpcode.SCRIPTED_ITEM, new ScriptedItemHandler()); + registerHandler(RecvOpcode.TOUCHING_REACTOR, new TouchReactorHandler()); + registerHandler(RecvOpcode.BEHOLDER, new BeholderHandler()); + registerHandler(RecvOpcode.ADMIN_COMMAND, new AdminCommandHandler()); + registerHandler(RecvOpcode.ADMIN_LOG, new AdminLogHandler()); + registerHandler(RecvOpcode.ALLIANCE_OPERATION, new AllianceOperationHandler()); + registerHandler(RecvOpcode.DENY_ALLIANCE_REQUEST, new DenyAllianceRequestHandler()); + registerHandler(RecvOpcode.USE_SOLOMON_ITEM, new UseSolomonHandler()); + registerHandler(RecvOpcode.USE_GACHA_EXP, new UseGachaExpHandler()); + registerHandler(RecvOpcode.NEW_YEAR_CARD_REQUEST, new NewYearCardHandler()); + registerHandler(RecvOpcode.CASHSHOP_SURPRISE, new CashShopSurpriseHandler()); + registerHandler(RecvOpcode.USE_ITEM_REWARD, new ItemRewardHandler()); + registerHandler(RecvOpcode.USE_REMOTE, new RemoteGachaponHandler()); + registerHandler(RecvOpcode.ACCEPT_FAMILY, new AcceptFamilyHandler()); + registerHandler(RecvOpcode.DUEY_ACTION, new DueyHandler()); + registerHandler(RecvOpcode.USE_DEATHITEM, new UseDeathItemHandler()); + registerHandler(RecvOpcode.PLAYER_MAP_TRANSFER, new PlayerMapTransitionHandler()); + registerHandler(RecvOpcode.USE_MAPLELIFE, new UseMapleLifeHandler()); + registerHandler(RecvOpcode.USE_CATCH_ITEM, new UseCatchItemHandler()); + registerHandler(RecvOpcode.FIELD_DAMAGE_MOB, new FieldDamageMobHandler()); + registerHandler(RecvOpcode.MOB_DAMAGE_MOB_FRIENDLY, new MobDamageMobFriendlyHandler()); + registerHandler(RecvOpcode.PARTY_SEARCH_REGISTER, new PartySearchRegisterHandler()); + registerHandler(RecvOpcode.PARTY_SEARCH_START, new PartySearchStartHandler()); + registerHandler(RecvOpcode.PARTY_SEARCH_UPDATE, new PartySearchUpdateHandler()); + registerHandler(RecvOpcode.ITEM_SORT2, new InventorySortHandler()); + registerHandler(RecvOpcode.LEFT_KNOCKBACK, new LeftKnockbackHandler()); + registerHandler(RecvOpcode.SNOWBALL, new SnowballHandler()); + registerHandler(RecvOpcode.COCONUT, new CoconutHandler()); + registerHandler(RecvOpcode.ARAN_COMBO_COUNTER, new AranComboHandler()); + registerHandler(RecvOpcode.CLICK_GUIDE, new ClickGuideHandler()); + registerHandler(RecvOpcode.FREDRICK_ACTION, new FredrickHandler()); + registerHandler(RecvOpcode.MONSTER_CARNIVAL, new MonsterCarnivalHandler()); + registerHandler(RecvOpcode.REMOTE_STORE, new RemoteStoreHandler()); + registerHandler(RecvOpcode.WEDDING_ACTION, new WeddingHandler()); + registerHandler(RecvOpcode.WEDDING_TALK, new WeddingTalkHandler()); + registerHandler(RecvOpcode.WEDDING_TALK_MORE, new WeddingTalkMoreHandler()); + registerHandler(RecvOpcode.WATER_OF_LIFE, new UseWaterOfLifeHandler()); + registerHandler(RecvOpcode.ADMIN_CHAT, new AdminChatHandler()); + registerHandler(RecvOpcode.MOVE_DRAGON, new MoveDragonHandler()); + registerHandler(RecvOpcode.OPEN_ITEMUI, new RaiseUIStateHandler()); + registerHandler(RecvOpcode.USE_ITEMUI, new RaiseIncExpHandler()); + registerHandler(RecvOpcode.CHANGE_QUICKSLOT, new QuickslotKeyMappedModifiedHandler()); + } } \ No newline at end of file diff --git a/src/main/java/net/encryption/ClientCyphers.java b/src/main/java/net/encryption/ClientCyphers.java new file mode 100644 index 0000000000..13c09292dd --- /dev/null +++ b/src/main/java/net/encryption/ClientCyphers.java @@ -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; + } +} diff --git a/src/main/java/net/encryption/InitializationVector.java b/src/main/java/net/encryption/InitializationVector.java new file mode 100644 index 0000000000..fb5edaa7b5 --- /dev/null +++ b/src/main/java/net/encryption/InitializationVector.java @@ -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); + } +} diff --git a/src/main/java/tools/MapleAESOFB.java b/src/main/java/net/encryption/MapleAESOFB.java similarity index 50% rename from src/main/java/tools/MapleAESOFB.java rename to src/main/java/net/encryption/MapleAESOFB.java index 7ccfc16ac2..96ab7d52bb 100644 --- a/src/main/java/tools/MapleAESOFB.java +++ b/src/main/java/net/encryption/MapleAESOFB.java @@ -19,7 +19,11 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package tools; +package net.encryption; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tools.HexTool; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -30,52 +34,73 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; public class MapleAESOFB { - private byte[] iv; - private Cipher cipher; - private short mapleVersion; + private static final Logger log = LoggerFactory.getLogger(MapleAESOFB.class); private final static SecretKeySpec skey = new SecretKeySpec( - new byte[]{0x13, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, (byte) 0xB4, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00}, "AES"); + new byte[]{ + 0x13, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + (byte) 0xB4, 0x00, 0x00, 0x00, + 0x1B, 0x00, 0x00, 0x00, + 0x0F, 0x00, 0x00, 0x00, + 0x33, 0x00, 0x00, 0x00, + 0x52, 0x00, 0x00, 0x00}, "AES"); - private static final byte[] funnyBytes = new byte[]{(byte) 0xEC, (byte) 0x3F, (byte) 0x77, (byte) 0xA4, (byte) 0x45, (byte) 0xD0, (byte) 0x71, (byte) 0xBF, (byte) 0xB7, (byte) 0x98, (byte) 0x20, (byte) 0xFC, - (byte) 0x4B, (byte) 0xE9, (byte) 0xB3, (byte) 0xE1, (byte) 0x5C, (byte) 0x22, (byte) 0xF7, (byte) 0x0C, (byte) 0x44, (byte) 0x1B, (byte) 0x81, (byte) 0xBD, (byte) 0x63, (byte) 0x8D, (byte) 0xD4, (byte) 0xC3, - (byte) 0xF2, (byte) 0x10, (byte) 0x19, (byte) 0xE0, (byte) 0xFB, (byte) 0xA1, (byte) 0x6E, (byte) 0x66, (byte) 0xEA, (byte) 0xAE, (byte) 0xD6, (byte) 0xCE, (byte) 0x06, (byte) 0x18, (byte) 0x4E, (byte) 0xEB, - (byte) 0x78, (byte) 0x95, (byte) 0xDB, (byte) 0xBA, (byte) 0xB6, (byte) 0x42, (byte) 0x7A, (byte) 0x2A, (byte) 0x83, (byte) 0x0B, (byte) 0x54, (byte) 0x67, (byte) 0x6D, (byte) 0xE8, (byte) 0x65, (byte) 0xE7, - (byte) 0x2F, (byte) 0x07, (byte) 0xF3, (byte) 0xAA, (byte) 0x27, (byte) 0x7B, (byte) 0x85, (byte) 0xB0, (byte) 0x26, (byte) 0xFD, (byte) 0x8B, (byte) 0xA9, (byte) 0xFA, (byte) 0xBE, (byte) 0xA8, (byte) 0xD7, - (byte) 0xCB, (byte) 0xCC, (byte) 0x92, (byte) 0xDA, (byte) 0xF9, (byte) 0x93, (byte) 0x60, (byte) 0x2D, (byte) 0xDD, (byte) 0xD2, (byte) 0xA2, (byte) 0x9B, (byte) 0x39, (byte) 0x5F, (byte) 0x82, (byte) 0x21, - (byte) 0x4C, (byte) 0x69, (byte) 0xF8, (byte) 0x31, (byte) 0x87, (byte) 0xEE, (byte) 0x8E, (byte) 0xAD, (byte) 0x8C, (byte) 0x6A, (byte) 0xBC, (byte) 0xB5, (byte) 0x6B, (byte) 0x59, (byte) 0x13, (byte) 0xF1, - (byte) 0x04, (byte) 0x00, (byte) 0xF6, (byte) 0x5A, (byte) 0x35, (byte) 0x79, (byte) 0x48, (byte) 0x8F, (byte) 0x15, (byte) 0xCD, (byte) 0x97, (byte) 0x57, (byte) 0x12, (byte) 0x3E, (byte) 0x37, (byte) 0xFF, - (byte) 0x9D, (byte) 0x4F, (byte) 0x51, (byte) 0xF5, (byte) 0xA3, (byte) 0x70, (byte) 0xBB, (byte) 0x14, (byte) 0x75, (byte) 0xC2, (byte) 0xB8, (byte) 0x72, (byte) 0xC0, (byte) 0xED, (byte) 0x7D, (byte) 0x68, - (byte) 0xC9, (byte) 0x2E, (byte) 0x0D, (byte) 0x62, (byte) 0x46, (byte) 0x17, (byte) 0x11, (byte) 0x4D, (byte) 0x6C, (byte) 0xC4, (byte) 0x7E, (byte) 0x53, (byte) 0xC1, (byte) 0x25, (byte) 0xC7, (byte) 0x9A, - (byte) 0x1C, (byte) 0x88, (byte) 0x58, (byte) 0x2C, (byte) 0x89, (byte) 0xDC, (byte) 0x02, (byte) 0x64, (byte) 0x40, (byte) 0x01, (byte) 0x5D, (byte) 0x38, (byte) 0xA5, (byte) 0xE2, (byte) 0xAF, (byte) 0x55, - (byte) 0xD5, (byte) 0xEF, (byte) 0x1A, (byte) 0x7C, (byte) 0xA7, (byte) 0x5B, (byte) 0xA6, (byte) 0x6F, (byte) 0x86, (byte) 0x9F, (byte) 0x73, (byte) 0xE6, (byte) 0x0A, (byte) 0xDE, (byte) 0x2B, (byte) 0x99, - (byte) 0x4A, (byte) 0x47, (byte) 0x9C, (byte) 0xDF, (byte) 0x09, (byte) 0x76, (byte) 0x9E, (byte) 0x30, (byte) 0x0E, (byte) 0xE4, (byte) 0xB2, (byte) 0x94, (byte) 0xA0, (byte) 0x3B, (byte) 0x34, (byte) 0x1D, - (byte) 0x28, (byte) 0x0F, (byte) 0x36, (byte) 0xE3, (byte) 0x23, (byte) 0xB4, (byte) 0x03, (byte) 0xD8, (byte) 0x90, (byte) 0xC8, (byte) 0x3C, (byte) 0xFE, (byte) 0x5E, (byte) 0x32, (byte) 0x24, (byte) 0x50, - (byte) 0x1F, (byte) 0x3A, (byte) 0x43, (byte) 0x8A, (byte) 0x96, (byte) 0x41, (byte) 0x74, (byte) 0xAC, (byte) 0x52, (byte) 0x33, (byte) 0xF0, (byte) 0xD9, (byte) 0x29, (byte) 0x80, (byte) 0xB1, (byte) 0x16, - (byte) 0xD3, (byte) 0xAB, (byte) 0x91, (byte) 0xB9, (byte) 0x84, (byte) 0x7F, (byte) 0x61, (byte) 0x1E, (byte) 0xCF, (byte) 0xC5, (byte) 0xD1, (byte) 0x56, (byte) 0x3D, (byte) 0xCA, (byte) 0xF4, (byte) 0x05, - (byte) 0xC6, (byte) 0xE5, (byte) 0x08, (byte) 0x49}; + private static final byte[] funnyBytes = new byte[]{ + (byte) 0xEC, (byte) 0x3F, (byte) 0x77, (byte) 0xA4, (byte) 0x45, (byte) 0xD0, (byte) 0x71, (byte) 0xBF, + (byte) 0xB7, (byte) 0x98, (byte) 0x20, (byte) 0xFC, (byte) 0x4B, (byte) 0xE9, (byte) 0xB3, (byte) 0xE1, + (byte) 0x5C, (byte) 0x22, (byte) 0xF7, (byte) 0x0C, (byte) 0x44, (byte) 0x1B, (byte) 0x81, (byte) 0xBD, + (byte) 0x63, (byte) 0x8D, (byte) 0xD4, (byte) 0xC3, (byte) 0xF2, (byte) 0x10, (byte) 0x19, (byte) 0xE0, + (byte) 0xFB, (byte) 0xA1, (byte) 0x6E, (byte) 0x66, (byte) 0xEA, (byte) 0xAE, (byte) 0xD6, (byte) 0xCE, + (byte) 0x06, (byte) 0x18, (byte) 0x4E, (byte) 0xEB, (byte) 0x78, (byte) 0x95, (byte) 0xDB, (byte) 0xBA, + (byte) 0xB6, (byte) 0x42, (byte) 0x7A, (byte) 0x2A, (byte) 0x83, (byte) 0x0B, (byte) 0x54, (byte) 0x67, + (byte) 0x6D, (byte) 0xE8, (byte) 0x65, (byte) 0xE7, (byte) 0x2F, (byte) 0x07, (byte) 0xF3, (byte) 0xAA, + (byte) 0x27, (byte) 0x7B, (byte) 0x85, (byte) 0xB0, (byte) 0x26, (byte) 0xFD, (byte) 0x8B, (byte) 0xA9, + (byte) 0xFA, (byte) 0xBE, (byte) 0xA8, (byte) 0xD7, (byte) 0xCB, (byte) 0xCC, (byte) 0x92, (byte) 0xDA, + (byte) 0xF9, (byte) 0x93, (byte) 0x60, (byte) 0x2D, (byte) 0xDD, (byte) 0xD2, (byte) 0xA2, (byte) 0x9B, + (byte) 0x39, (byte) 0x5F, (byte) 0x82, (byte) 0x21, (byte) 0x4C, (byte) 0x69, (byte) 0xF8, (byte) 0x31, + (byte) 0x87, (byte) 0xEE, (byte) 0x8E, (byte) 0xAD, (byte) 0x8C, (byte) 0x6A, (byte) 0xBC, (byte) 0xB5, + (byte) 0x6B, (byte) 0x59, (byte) 0x13, (byte) 0xF1, (byte) 0x04, (byte) 0x00, (byte) 0xF6, (byte) 0x5A, + (byte) 0x35, (byte) 0x79, (byte) 0x48, (byte) 0x8F, (byte) 0x15, (byte) 0xCD, (byte) 0x97, (byte) 0x57, + (byte) 0x12, (byte) 0x3E, (byte) 0x37, (byte) 0xFF, (byte) 0x9D, (byte) 0x4F, (byte) 0x51, (byte) 0xF5, + (byte) 0xA3, (byte) 0x70, (byte) 0xBB, (byte) 0x14, (byte) 0x75, (byte) 0xC2, (byte) 0xB8, (byte) 0x72, + (byte) 0xC0, (byte) 0xED, (byte) 0x7D, (byte) 0x68, (byte) 0xC9, (byte) 0x2E, (byte) 0x0D, (byte) 0x62, + (byte) 0x46, (byte) 0x17, (byte) 0x11, (byte) 0x4D, (byte) 0x6C, (byte) 0xC4, (byte) 0x7E, (byte) 0x53, + (byte) 0xC1, (byte) 0x25, (byte) 0xC7, (byte) 0x9A, (byte) 0x1C, (byte) 0x88, (byte) 0x58, (byte) 0x2C, + (byte) 0x89, (byte) 0xDC, (byte) 0x02, (byte) 0x64, (byte) 0x40, (byte) 0x01, (byte) 0x5D, (byte) 0x38, + (byte) 0xA5, (byte) 0xE2, (byte) 0xAF, (byte) 0x55, (byte) 0xD5, (byte) 0xEF, (byte) 0x1A, (byte) 0x7C, + (byte) 0xA7, (byte) 0x5B, (byte) 0xA6, (byte) 0x6F, (byte) 0x86, (byte) 0x9F, (byte) 0x73, (byte) 0xE6, + (byte) 0x0A, (byte) 0xDE, (byte) 0x2B, (byte) 0x99, (byte) 0x4A, (byte) 0x47, (byte) 0x9C, (byte) 0xDF, + (byte) 0x09, (byte) 0x76, (byte) 0x9E, (byte) 0x30, (byte) 0x0E, (byte) 0xE4, (byte) 0xB2, (byte) 0x94, + (byte) 0xA0, (byte) 0x3B, (byte) 0x34, (byte) 0x1D, (byte) 0x28, (byte) 0x0F, (byte) 0x36, (byte) 0xE3, + (byte) 0x23, (byte) 0xB4, (byte) 0x03, (byte) 0xD8, (byte) 0x90, (byte) 0xC8, (byte) 0x3C, (byte) 0xFE, + (byte) 0x5E, (byte) 0x32, (byte) 0x24, (byte) 0x50, (byte) 0x1F, (byte) 0x3A, (byte) 0x43, (byte) 0x8A, + (byte) 0x96, (byte) 0x41, (byte) 0x74, (byte) 0xAC, (byte) 0x52, (byte) 0x33, (byte) 0xF0, (byte) 0xD9, + (byte) 0x29, (byte) 0x80, (byte) 0xB1, (byte) 0x16, (byte) 0xD3, (byte) 0xAB, (byte) 0x91, (byte) 0xB9, + (byte) 0x84, (byte) 0x7F, (byte) 0x61, (byte) 0x1E, (byte) 0xCF, (byte) 0xC5, (byte) 0xD1, (byte) 0x56, + (byte) 0x3D, (byte) 0xCA, (byte) 0xF4, (byte) 0x05, (byte) 0xC6, (byte) 0xE5, (byte) 0x08, (byte) 0x49}; - public MapleAESOFB(byte[] iv, short mapleVersion) { + private final short mapleVersion; + private final Cipher cipher; + private byte[] iv; + + public MapleAESOFB(InitializationVector iv, short mapleVersion) { try { cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, skey); - } catch (NoSuchAlgorithmException e) { - System.out.println("ERROR " + e); - } catch (NoSuchPaddingException e) { - System.out.println("ERROR " + e); - } catch (InvalidKeyException e) { - System.out.println("Error initializing the encryption cipher. Make sure you're using the Unlimited Strength cryptography jar files."); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) { + log.warn("Cypher initialization error with skey: {}", skey, e); + throw new RuntimeException(e); } - this.setIv(iv); + + this.iv = iv.getBytes(); this.mapleVersion = (short) (((mapleVersion >> 8) & 0xFF) | ((mapleVersion << 8) & 0xFF00)); } - private void setIv(byte[] iv) { - this.iv = iv; - } - private static byte[] multiplyBytes(byte[] in, int count, int mul) { - byte[] ret = new byte[count * mul]; - for (int x = 0; x < count * mul; x++) { + final int size = count * mul; + byte[] ret = new byte[size]; + for (int x = 0; x < size; x++) { ret[x] = in[x % count]; } return ret; @@ -94,12 +119,8 @@ public class MapleAESOFB { if ((x - start) % myIv.length == 0) { try { byte[] newIv = cipher.doFinal(myIv); - for (int j = 0; j < myIv.length; j++) { - myIv[j] = newIv[j]; - } - } catch (IllegalBlockSizeException e) { - e.printStackTrace(); - } catch (BadPaddingException e) { + System.arraycopy(newIv, 0, myIv, 0, myIv.length); + } catch (IllegalBlockSizeException | BadPaddingException e) { e.printStackTrace(); } } @@ -137,11 +158,12 @@ public class MapleAESOFB { return packetLength; } - public boolean checkPacket(byte[] packet) { - return ((((packet[0] ^ iv[2]) & 0xFF) == ((mapleVersion >> 8) & 0xFF)) && (((packet[1] ^ iv[3]) & 0xFF) == (mapleVersion & 0xFF))); + private boolean checkPacket(byte[] packet) { + return ((((packet[0] ^ iv[2]) & 0xFF) == ((mapleVersion >> 8) & 0xFF)) && + (((packet[1] ^ iv[3]) & 0xFF) == (mapleVersion & 0xFF))); } - public boolean checkPacket(int packetHeader) { + public boolean isValidHeader(int packetHeader) { byte[] packetHeaderBuf = new byte[2]; packetHeaderBuf[0] = (byte) ((packetHeader >> 24) & 0xFF); packetHeaderBuf[1] = (byte) ((packetHeader >> 16) & 0xFF); diff --git a/src/main/java/net/mina/MapleCustomEncryption.java b/src/main/java/net/encryption/MapleCustomEncryption.java similarity index 99% rename from src/main/java/net/mina/MapleCustomEncryption.java rename to src/main/java/net/encryption/MapleCustomEncryption.java index 93f0a1b121..67e94844c3 100644 --- a/src/main/java/net/mina/MapleCustomEncryption.java +++ b/src/main/java/net/encryption/MapleCustomEncryption.java @@ -19,7 +19,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package net.mina; +package net.encryption; public class MapleCustomEncryption { private static byte rollLeft(byte in, int count) { diff --git a/src/main/java/net/encryption/PacketCodec.java b/src/main/java/net/encryption/PacketCodec.java new file mode 100644 index 0000000000..e3541cc734 --- /dev/null +++ b/src/main/java/net/encryption/PacketCodec.java @@ -0,0 +1,9 @@ +package net.encryption; + +import io.netty.channel.CombinedChannelDuplexHandler; + +public class PacketCodec extends CombinedChannelDuplexHandler { + public PacketCodec(ClientCyphers clientCyphers) { + super(new PacketDecoder(clientCyphers.getReceiveCypher()), new PacketEncoder(clientCyphers.getSendCypher())); + } +} diff --git a/src/main/java/net/encryption/PacketDecoder.java b/src/main/java/net/encryption/PacketDecoder.java new file mode 100644 index 0000000000..896d565d0f --- /dev/null +++ b/src/main/java/net/encryption/PacketDecoder.java @@ -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 { + private final MapleAESOFB receiveCypher; + + public PacketDecoder(MapleAESOFB receiveCypher) { + this.receiveCypher = receiveCypher; + } + + @Override + protected void decode(ChannelHandlerContext context, ByteBuf in, List 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; + } +} diff --git a/src/main/java/net/encryption/PacketEncoder.java b/src/main/java/net/encryption/PacketEncoder.java new file mode 100644 index 0000000000..d558e7b13b --- /dev/null +++ b/src/main/java/net/encryption/PacketEncoder.java @@ -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 { + 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); + } +} diff --git a/src/main/java/net/mina/MapleCodecFactory.java b/src/main/java/net/mina/MapleCodecFactory.java deleted file mode 100644 index 9ca3b76bd1..0000000000 --- a/src/main/java/net/mina/MapleCodecFactory.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer - - 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 . -*/ -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; - } -} diff --git a/src/main/java/net/mina/MaplePacketDecoder.java b/src/main/java/net/mina/MaplePacketDecoder.java deleted file mode 100644 index aa182f1528..0000000000 --- a/src/main/java/net/mina/MaplePacketDecoder.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer - - 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 . -*/ -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(); - } -} diff --git a/src/main/java/net/mina/MaplePacketEncoder.java b/src/main/java/net/mina/MaplePacketEncoder.java deleted file mode 100644 index 2bd7fda9b4..0000000000 --- a/src/main/java/net/mina/MaplePacketEncoder.java +++ /dev/null @@ -1,98 +0,0 @@ -/* -This file is part of the OdinMS Maple Story Server -Copyright (C) 2008 Patrick Huy -Matthias Butz -Jan Christian Meyer - -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 . - */ -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 {} -} \ No newline at end of file diff --git a/src/main/java/net/netty/AbstractServer.java b/src/main/java/net/netty/AbstractServer.java new file mode 100644 index 0000000000..1fd77f7a9c --- /dev/null +++ b/src/main/java/net/netty/AbstractServer.java @@ -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(); +} diff --git a/src/main/java/net/netty/ChannelServer.java b/src/main/java/net/netty/ChannelServer.java new file mode 100644 index 0000000000..cd620db608 --- /dev/null +++ b/src/main/java/net/netty/ChannelServer.java @@ -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(); + } +} diff --git a/src/main/java/net/netty/ChannelServerInitializer.java b/src/main/java/net/netty/ChannelServerInitializer.java new file mode 100644 index 0000000000..a4a64c15ce --- /dev/null +++ b/src/main/java/net/netty/ChannelServerInitializer.java @@ -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); + } +} diff --git a/src/main/java/net/netty/InvalidPacketHeaderException.java b/src/main/java/net/netty/InvalidPacketHeaderException.java new file mode 100644 index 0000000000..7eed5f3992 --- /dev/null +++ b/src/main/java/net/netty/InvalidPacketHeaderException.java @@ -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; + } +} diff --git a/src/main/java/net/netty/LoginServer.java b/src/main/java/net/netty/LoginServer.java new file mode 100644 index 0000000000..a34145f3f2 --- /dev/null +++ b/src/main/java/net/netty/LoginServer.java @@ -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(); + } +} diff --git a/src/main/java/net/netty/LoginServerInitializer.java b/src/main/java/net/netty/LoginServerInitializer.java new file mode 100644 index 0000000000..f4ada797b1 --- /dev/null +++ b/src/main/java/net/netty/LoginServerInitializer.java @@ -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); + } +} diff --git a/src/main/java/net/netty/ServerChannelInitializer.java b/src/main/java/net/netty/ServerChannelInitializer.java new file mode 100644 index 0000000000..9e520f593d --- /dev/null +++ b/src/main/java/net/netty/ServerChannelInitializer.java @@ -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 { + 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); + } + } +} diff --git a/src/main/java/net/packet/ByteBufOutPacket.java b/src/main/java/net/packet/ByteBufOutPacket.java index 5aa7a20b79..b062afcd3b 100644 --- a/src/main/java/net/packet/ByteBufOutPacket.java +++ b/src/main/java/net/packet/ByteBufOutPacket.java @@ -12,6 +12,11 @@ import java.awt.*; public class ByteBufOutPacket implements OutPacket { private final ByteBuf byteBuf; + @Deprecated(forRemoval = true) + public ByteBufOutPacket() { + this.byteBuf = Unpooled.buffer(); + } + public ByteBufOutPacket(SendOpcode op) { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeShortLE((short) op.getValue()); diff --git a/src/main/java/net/packet/logging/InPacketLogger.java b/src/main/java/net/packet/logging/InPacketLogger.java new file mode 100644 index 0000000000..da9e8aa16d --- /dev/null +++ b/src/main/java/net/packet/logging/InPacketLogger.java @@ -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 ? " " : ""; + log.debug("{}ClientSend:{} [{}] ({}) {} {}", 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); + } +} diff --git a/src/main/java/net/packet/logging/LoggingUtil.java b/src/main/java/net/packet/logging/LoggingUtil.java new file mode 100644 index 0000000000..29af8bfc34 --- /dev/null +++ b/src/main/java/net/packet/logging/LoggingUtil.java @@ -0,0 +1,17 @@ +package net.packet.logging; + +import io.netty.buffer.Unpooled; + +import java.util.Set; + +public class LoggingUtil { + private static final Set 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); + } +} diff --git a/src/main/java/net/packet/logging/MapleLogger.java b/src/main/java/net/packet/logging/MapleLogger.java new file mode 100644 index 0000000000..68ccc002b4 --- /dev/null +++ b/src/main/java/net/packet/logging/MapleLogger.java @@ -0,0 +1,72 @@ +/* + This file is part of the OdinMS Maple Story Server + Copyright (C) 2008 ~ 2010 Patrick Huy + Matthias Butz + Jan Christian Meyer + + 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 . + */ +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 monitored = new HashSet<>(); + public static final Set 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); + } +} diff --git a/src/main/java/net/packet/logging/OutPacketLogger.java b/src/main/java/net/packet/logging/OutPacketLogger.java new file mode 100644 index 0000000000..ea8a96356e --- /dev/null +++ b/src/main/java/net/packet/logging/OutPacketLogger.java @@ -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 ? " " : ""; + log.debug("{}ServerSend:{} [{}] ({}) {} {}", 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); + } +} diff --git a/src/main/java/net/packet/logging/PacketLogger.java b/src/main/java/net/packet/logging/PacketLogger.java new file mode 100644 index 0000000000..db5ed00222 --- /dev/null +++ b/src/main/java/net/packet/logging/PacketLogger.java @@ -0,0 +1,7 @@ +package net.packet.logging; + +import net.packet.Packet; + +public interface PacketLogger { + void log(Packet packet); +} diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index 40cd18c488..b64eaa2daf 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -35,8 +35,7 @@ import constants.game.GameConstants; import constants.inventory.ItemConstants; import constants.net.OpcodeConstants; import constants.net.ServerConstants; -import net.MapleServerHandler; -import net.mina.MapleCodecFactory; +import net.netty.LoginServer; import net.server.audit.ThreadTracker; import net.server.audit.locks.MonitoredLockType; import net.server.audit.locks.MonitoredReadLock; @@ -46,19 +45,13 @@ import net.server.audit.locks.factory.MonitoredReadLockFactory; import net.server.audit.locks.factory.MonitoredReentrantLockFactory; import net.server.audit.locks.factory.MonitoredWriteLockFactory; import net.server.channel.Channel; -import net.server.coordinator.session.MapleSessionCoordinator; +import net.server.coordinator.session.IpAddresses; +import net.server.coordinator.session.SessionCoordinator; import net.server.guild.MapleAlliance; import net.server.guild.MapleGuild; import net.server.guild.MapleGuildCharacter; import net.server.task.*; import net.server.world.World; -import org.apache.mina.core.buffer.IoBuffer; -import org.apache.mina.core.buffer.SimpleBufferAllocator; -import org.apache.mina.core.service.IoAcceptor; -import org.apache.mina.core.session.IdleStatus; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.codec.ProtocolCodecFilter; -import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import server.CashShop.CashItemFactory; @@ -73,8 +66,6 @@ import tools.DatabaseConnection; import tools.FilePrinter; import tools.Pair; -import java.io.IOException; -import java.net.InetSocketAddress; import java.security.Security; import java.sql.Connection; import java.sql.PreparedStatement; @@ -105,7 +96,7 @@ public class Server { private static final Map couponRates = new HashMap<>(30); private static final List activeCoupons = new LinkedList<>(); - private IoAcceptor acceptor; + private LoginServer loginServer; private List> channels = new LinkedList<>(); private List worlds = new ArrayList<>(); private final Properties subnetInfo = new Properties(); @@ -291,13 +282,13 @@ public class Server { } } - public String[] getInetSocket(IoSession session, int world, int channel) { - String remoteIp = MapleSessionCoordinator.getSessionRemoteAddress(session); + public String[] getInetSocket(MapleClient client, int world, int channel) { + String remoteIp = client.getRemoteAddress(); String[] hostAddress = getIP(world, channel).split(":"); - if (MapleSessionCoordinator.isLocalAddress(remoteIp)) { + if (IpAddresses.isLocalAddress(remoteIp)) { hostAddress[0] = YamlConfig.config.server.LOCALHOST; - } else if (MapleSessionCoordinator.isLanAddress(remoteIp)) { + } else if (IpAddresses.isLanAddress(remoteIp)) { hostAddress[0] = YamlConfig.config.server.LANHOST; } @@ -906,17 +897,8 @@ public class Server { } } - IoBuffer.setUseDirectBuffer(false); // join IO operations performed by lxconan - IoBuffer.setAllocator(new SimpleBufferAllocator()); - acceptor = new NioSocketAcceptor(); - acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MapleCodecFactory())); - acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30); - acceptor.setHandler(new MapleServerHandler()); - try { - acceptor.bind(new InetSocketAddress(8484)); - } catch (IOException ex) { - ex.printStackTrace(); - } + // acceptor = initAcceptor(8484); + loginServer = initLoginServer(8484); log.info("Listening on port 8484"); @@ -932,6 +914,12 @@ public class Server { } } + private LoginServer initLoginServer(int port) { + LoginServer loginServer = new LoginServer(port); + loginServer.start(); + return loginServer; + } + private static void setAllLoggedOut(Connection con) throws SQLException { try (PreparedStatement ps = con.prepareStatement("UPDATE accounts SET loggedin = 0")) { ps.executeUpdate(); @@ -1776,7 +1764,7 @@ public class Server { } private static String getRemoteHost(MapleClient client) { - return MapleSessionCoordinator.getSessionRemoteHost(client.getSession()); + return SessionCoordinator.getSessionRemoteHost(client); } public void setCharacteridInTransition(MapleClient client, int charId) { @@ -1878,7 +1866,7 @@ public class Server { if (c.isLoggedIn()) { c.disconnect(false, false); } else { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); } } } @@ -1943,8 +1931,7 @@ public class Server { TimerManager.getInstance().stop(); System.out.println("Worlds + Channels are offline."); - acceptor.unbind(); - acceptor = null; + loginServer.stop(); if (!restart) { // shutdown hook deadlocks if System.exit() method is used within its body chores, thanks MIKE for pointing that out new Thread(() -> System.exit(0)).start(); } else { diff --git a/src/main/java/net/server/channel/Channel.java b/src/main/java/net/server/channel/Channel.java index 1956daf09e..d90e46c64a 100644 --- a/src/main/java/net/server/channel/Channel.java +++ b/src/main/java/net/server/channel/Channel.java @@ -24,8 +24,7 @@ package net.server.channel; import client.MapleCharacter; import config.YamlConfig; import constants.game.GameConstants; -import net.MapleServerHandler; -import net.mina.MapleCodecFactory; +import net.netty.ChannelServer; import net.server.PlayerStorage; import net.server.Server; import net.server.audit.LockCollector; @@ -39,13 +38,6 @@ import net.server.services.type.ChannelServices; import net.server.world.MapleParty; import net.server.world.MaplePartyCharacter; import net.server.world.World; -import org.apache.mina.core.buffer.IoBuffer; -import org.apache.mina.core.buffer.SimpleBufferAllocator; -import org.apache.mina.core.service.IoAcceptor; -import org.apache.mina.core.session.IdleStatus; -import org.apache.mina.filter.codec.ProtocolCodecFilter; -import org.apache.mina.transport.socket.SocketSessionConfig; -import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import scripting.event.EventScriptManager; @@ -58,19 +50,22 @@ import tools.MaplePacketCreator; import tools.Pair; import java.io.File; -import java.net.InetSocketAddress; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.ScheduledFuture; public final class Channel { private static final Logger log = LoggerFactory.getLogger(Channel.class); + private static final int BASE_PORT = 7575; + + private final int port; + private final String ip; + private final int world; + private final int channel; - private int port = 7575; private PlayerStorage players = new PlayerStorage(); - private int world, channel; - private IoAcceptor acceptor; - private String ip, serverMessage; + private ChannelServer channelServer; + private String serverMessage; private MapleMapManager mapManager; private EventScriptManager eventSM; private ServicesManager services; @@ -118,18 +113,11 @@ public final class Channel { this.ongoingStartTime = startTime + 10000; // rude approach to a world's last channel boot time, placeholder for the 1st wedding reservation ever this.mapManager = new MapleMapManager(null, world, channel); + this.port = BASE_PORT + (this.channel - 1) + (world * 100); + this.ip = YamlConfig.config.server.HOST + ":" + port; + try { - port = 7575 + this.channel - 1; - port += (world * 100); - ip = YamlConfig.config.server.HOST + ":" + port; - IoBuffer.setUseDirectBuffer(false); - IoBuffer.setAllocator(new SimpleBufferAllocator()); - acceptor = new NioSocketAcceptor(); - acceptor.setHandler(new MapleServerHandler(world, channel)); - acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30); - acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MapleCodecFactory())); - acceptor.bind(new InetSocketAddress(port)); - ((SocketSessionConfig) acceptor.getSessionConfig()).setTcpNoDelay(true); + this.channelServer = initServer(port, world, channel); expedType.addAll(Arrays.asList(MapleExpeditionType.values())); if (Server.getInstance().isOnline()) { // postpone event loading to improve boot time... thanks Riizade, daronhudson for noticing slow startup times @@ -153,9 +141,15 @@ public final class Channel { log.info("Channel {}: Listening on port {}", getId(), port); } catch (Exception e) { - e.printStackTrace(); + log.warn("Error during channel initialization", e); } } + + private ChannelServer initServer(int port, int world, int channel) { + ChannelServer channelServer = new ChannelServer(port, world, channel); + channelServer.start(); + return channelServer; + } public synchronized void reloadEventScriptManager(){ if (finishedShutdown) { @@ -187,10 +181,8 @@ public final class Channel { closeChannelSchedules(); players = null; - - MapleServerHandler handler = (MapleServerHandler) acceptor.getHandler(); - handler.dispose(); - acceptor.unbind(); + + channelServer.stop(); finishedShutdown = true; System.out.println("Successfully shut down Channel " + channel + " on World " + world + "\r\n"); diff --git a/src/main/java/net/server/channel/handlers/AdminCommandHandler.java b/src/main/java/net/server/channel/handlers/AdminCommandHandler.java index 7cc357ffea..9c8b80a88a 100644 --- a/src/main/java/net/server/channel/handlers/AdminCommandHandler.java +++ b/src/main/java/net/server/channel/handlers/AdminCommandHandler.java @@ -86,7 +86,7 @@ public final class AdminCommandHandler extends AbstractMaplePacketHandler { target = c.getChannelServer().getPlayerStorage().getCharacterByName(victim); if (target != null) { String readableTargetName = MapleCharacter.makeMapleReadable(target.getName()); - String ip = target.getClient().getSession().getRemoteAddress().toString().split(":")[0]; + String ip = target.getClient().getRemoteAddress(); reason += readableTargetName + " (IP: " + ip + ")"; if (duration == -1) { target.ban(description + " " + reason); diff --git a/src/main/java/net/server/channel/handlers/PlayerLoggedinHandler.java b/src/main/java/net/server/channel/handlers/PlayerLoggedinHandler.java index 75c2d12958..dfa22824e4 100644 --- a/src/main/java/net/server/channel/handlers/PlayerLoggedinHandler.java +++ b/src/main/java/net/server/channel/handlers/PlayerLoggedinHandler.java @@ -31,14 +31,14 @@ import net.server.PlayerBuffValueHolder; import net.server.Server; import net.server.channel.Channel; import net.server.channel.CharacterIdChannelPair; -import net.server.coordinator.session.MapleSessionCoordinator; +import net.server.coordinator.session.Hwid; +import net.server.coordinator.session.SessionCoordinator; import net.server.coordinator.world.MapleEventRecallCoordinator; import net.server.guild.MapleAlliance; import net.server.guild.MapleGuild; import net.server.world.MaplePartyCharacter; import net.server.world.PartyOperation; import net.server.world.World; -import org.apache.mina.core.session.IoSession; import scripting.event.EventInstanceManager; import server.life.MobSkill; import tools.DatabaseConnection; @@ -58,7 +58,7 @@ import java.util.stream.Collectors; public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { - private static Set attemptingLoginAccounts = new HashSet<>(); + private static final Set attemptingLoginAccounts = new HashSet<>(); private boolean tryAcquireAccount(int accId) { synchronized (attemptingLoginAccounts) { @@ -84,70 +84,70 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - final int cid = slea.readInt(); + final int cid = slea.readInt(); // TODO: investigate if this is the "client id" supplied in MaplePacketCreator#getServerIP() final Server server = Server.getInstance(); - if (c.tryacquireClient()) { // thanks MedicOP for assisting on concurrency protection here - try { - World wserv = server.getWorld(c.getWorld()); - if(wserv == null) { + if (!c.tryacquireClient()) { + // thanks MedicOP for assisting on concurrency protection here + c.announce(MaplePacketCreator.getAfterLoginError(10)); + } + + try { + World wserv = server.getWorld(c.getWorld()); + if (wserv == null) { + c.disconnect(true, false); + return; + } + + Channel cserv = wserv.getChannel(c.getChannel()); + if (cserv == null) { + c.setChannel(1); + cserv = wserv.getChannel(c.getChannel()); + + if (cserv == null) { c.disconnect(true, false); return; } + } - Channel cserv = wserv.getChannel(c.getChannel()); - if(cserv == null) { - c.setChannel(1); - cserv = wserv.getChannel(c.getChannel()); + MapleCharacter player = wserv.getPlayerStorage().getCharacterById(cid); - if(cserv == null) { - c.disconnect(true, false); - return; - } - } - - MapleCharacter player = wserv.getPlayerStorage().getCharacterById(cid); - IoSession session = c.getSession(); - - String remoteHwid; - if (player == null) { - remoteHwid = MapleSessionCoordinator.getInstance().pickLoginSessionHwid(session); - if (remoteHwid == null) { - c.disconnect(true, false); - return; - } - } else { - remoteHwid = player.getClient().getHWID(); - } - - int hwidLen = remoteHwid.length(); - session.setAttribute(MapleClient.CLIENT_HWID, remoteHwid); - session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, remoteHwid.substring(hwidLen - 8, hwidLen)); - c.setHWID(remoteHwid); - - if (!server.validateCharacteridInTransition(c, cid)) { + final Hwid hwid; + if (player == null) { + hwid = SessionCoordinator.getInstance().pickLoginSessionHwid(c); + if (hwid == null) { c.disconnect(true, false); return; } - - boolean newcomer = false; - if (player == null) { - try { - player = MapleCharacter.loadCharFromDB(cid, c, true); - newcomer = true; - } catch (SQLException e) { - e.printStackTrace(); - } - - if (player == null) { //If you are still getting null here then please just uninstall the game >.>, we dont need you fucking with the logs - c.disconnect(true, false); - return; - } + } else { + hwid = player.getClient().getHwid(); + } + + c.setHwid(hwid); + + if (!server.validateCharacteridInTransition(c, cid)) { + c.disconnect(true, false); + return; + } + + boolean newcomer = false; + if (player == null) { + try { + player = MapleCharacter.loadCharFromDB(cid, c, true); + newcomer = true; + } catch (SQLException e) { + e.printStackTrace(); } - c.setPlayer(player); - c.setAccID(player.getAccountID()); - - boolean allowLogin = true; + + if (player == null) { //If you are still getting null here then please just uninstall the game >.>, we dont need you fucking with the logs + c.disconnect(true, false); + return; + } + } + c.setPlayer(player); + c.setAccID(player.getAccountID()); + + boolean allowLogin = true; /* is this check really necessary? if (state == MapleClient.LOGIN_SERVER_TRANSITION || state == MapleClient.LOGIN_NOTLOGGEDIN) { @@ -164,276 +164,277 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { } } */ - - int accId = c.getAccID(); - if (tryAcquireAccount(accId)) { // Sync this to prevent wrong login state for double loggedin handling - try { - int state = c.getLoginState(); - if (state != MapleClient.LOGIN_SERVER_TRANSITION || !allowLogin) { - c.setPlayer(null); - c.setAccID(0); - if (state == MapleClient.LOGIN_LOGGEDIN) { - c.disconnect(true, false); - } else { - c.announce(MaplePacketCreator.getAfterLoginError(7)); - } + int accId = c.getAccID(); + if (tryAcquireAccount(accId)) { // Sync this to prevent wrong login state for double loggedin handling + try { + int state = c.getLoginState(); + if (state != MapleClient.LOGIN_SERVER_TRANSITION || !allowLogin) { + c.setPlayer(null); + c.setAccID(0); - return; - } - c.updateLoginState(MapleClient.LOGIN_LOGGEDIN); - } finally { - releaseAccount(accId); - } - } else { - c.setPlayer(null); - c.setAccID(0); - c.announce(MaplePacketCreator.getAfterLoginError(10)); - return; - } - - if (!newcomer) { - c.setLanguage(player.getClient().getLanguage()); - c.setCharacterSlots((byte) player.getClient().getCharacterSlots()); - player.newClient(c); - } - - cserv.addPlayer(player); - wserv.addPlayer(player); - player.setEnteredChannelWorld(); - - List buffs = server.getPlayerBuffStorage().getBuffsFromStorage(cid); - if (buffs != null) { - List> timedBuffs = getLocalStartTimes(buffs); - player.silentGiveBuffs(timedBuffs); - } - - Map> diseases = server.getPlayerBuffStorage().getDiseasesFromStorage(cid); - if (diseases != null) { - player.silentApplyDiseases(diseases); - } - - c.announce(MaplePacketCreator.getCharInfo(player)); - if (!player.isHidden()) { - if(player.isGM() && YamlConfig.config.server.USE_AUTOHIDE_GM) { - player.toggleHide(true); - } - } - player.sendKeymap(); - player.sendQuickmap(); - player.sendMacros(); - - // pot bindings being passed through other characters on the account detected thanks to Croosade dev team - MapleKeyBinding autohpPot = player.getKeymap().get(91); - player.announce(MaplePacketCreator.sendAutoHpPot(autohpPot != null ? autohpPot.getAction() : 0)); - - MapleKeyBinding autompPot = player.getKeymap().get(92); - player.announce(MaplePacketCreator.sendAutoMpPot(autompPot != null ? autompPot.getAction() : 0)); - - player.getMap().addPlayer(player); - player.visitMap(player.getMap()); - - BuddyList bl = player.getBuddylist(); - int[] buddyIds = bl.getBuddyIds(); - wserv.loggedOn(player.getName(), player.getId(), c.getChannel(), buddyIds); - for (CharacterIdChannelPair onlineBuddy : wserv.multiBuddyFind(player.getId(), buddyIds)) { - BuddylistEntry ble = bl.get(onlineBuddy.getCharacterId()); - ble.setChannel(onlineBuddy.getChannel()); - bl.put(ble); - } - c.announce(MaplePacketCreator.updateBuddylist(bl.getBuddies())); - - c.announce(MaplePacketCreator.loadFamily(player)); - if (player.getFamilyId() > 0) { - MapleFamily f = wserv.getFamily(player.getFamilyId()); - if(f != null) { - MapleFamilyEntry familyEntry = f.getEntryByID(player.getId()); - if(familyEntry != null) { - familyEntry.setCharacter(player); - player.setFamilyEntry(familyEntry); - - c.announce(MaplePacketCreator.getFamilyInfo(familyEntry)); - familyEntry.announceToSenior(MaplePacketCreator.sendFamilyLoginNotice(player.getName(), true), true); + if (state == MapleClient.LOGIN_LOGGEDIN) { + c.disconnect(true, false); } else { - FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Player " + player.getName() + "'s family doesn't have an entry for them. (" + f.getID() + ")"); + c.announce(MaplePacketCreator.getAfterLoginError(7)); } + + return; + } + c.updateLoginState(MapleClient.LOGIN_LOGGEDIN); + } finally { + releaseAccount(accId); + } + } else { + c.setPlayer(null); + c.setAccID(0); + c.announce(MaplePacketCreator.getAfterLoginError(10)); + return; + } + + if (!newcomer) { + c.setLanguage(player.getClient().getLanguage()); + c.setCharacterSlots((byte) player.getClient().getCharacterSlots()); + player.newClient(c); + } + + cserv.addPlayer(player); + wserv.addPlayer(player); + player.setEnteredChannelWorld(); + + List buffs = server.getPlayerBuffStorage().getBuffsFromStorage(cid); + if (buffs != null) { + List> timedBuffs = getLocalStartTimes(buffs); + player.silentGiveBuffs(timedBuffs); + } + + Map> diseases = server.getPlayerBuffStorage().getDiseasesFromStorage(cid); + if (diseases != null) { + player.silentApplyDiseases(diseases); + } + + c.announce(MaplePacketCreator.getCharInfo(player)); + if (!player.isHidden()) { + if (player.isGM() && YamlConfig.config.server.USE_AUTOHIDE_GM) { + player.toggleHide(true); + } + } + player.sendKeymap(); + player.sendQuickmap(); + player.sendMacros(); + + // pot bindings being passed through other characters on the account detected thanks to Croosade dev team + MapleKeyBinding autohpPot = player.getKeymap().get(91); + player.announce(MaplePacketCreator.sendAutoHpPot(autohpPot != null ? autohpPot.getAction() : 0)); + + MapleKeyBinding autompPot = player.getKeymap().get(92); + player.announce(MaplePacketCreator.sendAutoMpPot(autompPot != null ? autompPot.getAction() : 0)); + + player.getMap().addPlayer(player); + player.visitMap(player.getMap()); + + BuddyList bl = player.getBuddylist(); + int[] buddyIds = bl.getBuddyIds(); + wserv.loggedOn(player.getName(), player.getId(), c.getChannel(), buddyIds); + for (CharacterIdChannelPair onlineBuddy : wserv.multiBuddyFind(player.getId(), buddyIds)) { + BuddylistEntry ble = bl.get(onlineBuddy.getCharacterId()); + ble.setChannel(onlineBuddy.getChannel()); + bl.put(ble); + } + c.announce(MaplePacketCreator.updateBuddylist(bl.getBuddies())); + + c.announce(MaplePacketCreator.loadFamily(player)); + if (player.getFamilyId() > 0) { + MapleFamily f = wserv.getFamily(player.getFamilyId()); + if (f != null) { + MapleFamilyEntry familyEntry = f.getEntryByID(player.getId()); + if (familyEntry != null) { + familyEntry.setCharacter(player); + player.setFamilyEntry(familyEntry); + + c.announce(MaplePacketCreator.getFamilyInfo(familyEntry)); + familyEntry.announceToSenior(MaplePacketCreator.sendFamilyLoginNotice(player.getName(), true), true); } else { - FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Player " + player.getName() + " has an invalid family ID. (" + player.getFamilyId() + ")"); - c.announce(MaplePacketCreator.getFamilyInfo(null)); + FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Player " + player.getName() + "'s family doesn't have an entry for them. (" + f.getID() + ")"); } } else { + FilePrinter.printError(FilePrinter.FAMILY_ERROR, "Player " + player.getName() + " has an invalid family ID. (" + player.getFamilyId() + ")"); c.announce(MaplePacketCreator.getFamilyInfo(null)); } - if (player.getGuildId() > 0) { - MapleGuild playerGuild = server.getGuild(player.getGuildId(), player.getWorld(), player); - if (playerGuild == null) { - player.deleteGuild(player.getGuildId()); - player.getMGC().setGuildId(0); - player.getMGC().setGuildRank(5); - } else { - playerGuild.getMGC(player.getId()).setCharacter(player); - player.setMGC(playerGuild.getMGC(player.getId())); - server.setGuildMemberOnline(player, true, c.getChannel()); - c.announce(MaplePacketCreator.showGuildInfo(player)); - int allianceId = player.getGuild().getAllianceId(); - if (allianceId > 0) { - MapleAlliance newAlliance = server.getAlliance(allianceId); - if (newAlliance == null) { - newAlliance = MapleAlliance.loadAlliance(allianceId); - if (newAlliance != null) { - server.addAlliance(allianceId, newAlliance); - } else { - player.getGuild().setAllianceId(0); - } - } + } else { + c.announce(MaplePacketCreator.getFamilyInfo(null)); + } + + if (player.getGuildId() > 0) { + MapleGuild playerGuild = server.getGuild(player.getGuildId(), player.getWorld(), player); + if (playerGuild == null) { + player.deleteGuild(player.getGuildId()); + player.getMGC().setGuildId(0); + player.getMGC().setGuildRank(5); + } else { + playerGuild.getMGC(player.getId()).setCharacter(player); + player.setMGC(playerGuild.getMGC(player.getId())); + server.setGuildMemberOnline(player, true, c.getChannel()); + c.announce(MaplePacketCreator.showGuildInfo(player)); + int allianceId = player.getGuild().getAllianceId(); + if (allianceId > 0) { + MapleAlliance newAlliance = server.getAlliance(allianceId); + if (newAlliance == null) { + newAlliance = MapleAlliance.loadAlliance(allianceId); if (newAlliance != null) { - c.announce(MaplePacketCreator.updateAllianceInfo(newAlliance, c.getWorld())); - c.announce(MaplePacketCreator.allianceNotice(newAlliance.getId(), newAlliance.getNotice())); + server.addAlliance(allianceId, newAlliance); + } else { + player.getGuild().setAllianceId(0); + } + } + if (newAlliance != null) { + c.announce(MaplePacketCreator.updateAllianceInfo(newAlliance, c.getWorld())); + c.announce(MaplePacketCreator.allianceNotice(newAlliance.getId(), newAlliance.getNotice())); - if (newcomer) { - server.allianceMessage(allianceId, MaplePacketCreator.allianceMemberOnline(player, true), player.getId(), -1); - } + if (newcomer) { + server.allianceMessage(allianceId, MaplePacketCreator.allianceMemberOnline(player, true), player.getId(), -1); } } } } + } - player.showNote(); - if (player.getParty() != null) { - MaplePartyCharacter pchar = player.getMPC(); + player.showNote(); + if (player.getParty() != null) { + MaplePartyCharacter pchar = player.getMPC(); - //Use this in case of enabling party HPbar HUD when logging in, however "you created a party" will appear on chat. - //c.announce(MaplePacketCreator.partyCreated(pchar)); + //Use this in case of enabling party HPbar HUD when logging in, however "you created a party" will appear on chat. + //c.announce(MaplePacketCreator.partyCreated(pchar)); - pchar.setChannel(c.getChannel()); - pchar.setMapId(player.getMapId()); - pchar.setOnline(true); - wserv.updateParty(player.getParty().getId(), PartyOperation.LOG_ONOFF, pchar); - player.updatePartyMemberHP(); + pchar.setChannel(c.getChannel()); + pchar.setMapId(player.getMapId()); + pchar.setOnline(true); + wserv.updateParty(player.getParty().getId(), PartyOperation.LOG_ONOFF, pchar); + player.updatePartyMemberHP(); + } + + MapleInventory eqpInv = player.getInventory(MapleInventoryType.EQUIPPED); + eqpInv.lockInventory(); + try { + for (Item it : eqpInv.list()) { + player.equippedItem((Equip) it); + } + } finally { + eqpInv.unlockInventory(); + } + + c.announce(MaplePacketCreator.updateBuddylist(player.getBuddylist().getBuddies())); + + CharacterNameAndId pendingBuddyRequest = c.getPlayer().getBuddylist().pollPendingRequest(); + if (pendingBuddyRequest != null) { + c.announce(MaplePacketCreator.requestBuddylistAdd(pendingBuddyRequest.getId(), c.getPlayer().getId(), pendingBuddyRequest.getName())); + } + + c.announce(MaplePacketCreator.updateGender(player)); + player.checkMessenger(); + c.announce(MaplePacketCreator.enableReport()); + player.changeSkillLevel(SkillFactory.getSkill(10000000 * player.getJobType() + 12), (byte) (player.getLinkedLevel() / 10), 20, -1); + player.checkBerserk(player.isHidden()); + + if (newcomer) { + for (MaplePet pet : player.getPets()) { + if (pet != null) { + wserv.registerPetHunger(player, player.getPetIndex(pet)); + } } - MapleInventory eqpInv = player.getInventory(MapleInventoryType.EQUIPPED); - eqpInv.lockInventory(); - try { - for(Item it : eqpInv.list()) { - player.equippedItem((Equip) it); - } - } finally { - eqpInv.unlockInventory(); + MapleMount mount = player.getMount(); // thanks Ari for noticing a scenario where Silver Mane quest couldn't be started + if (mount.getItemId() != 0) { + player.announce(MaplePacketCreator.updateMount(player.getId(), mount, false)); } - c.announce(MaplePacketCreator.updateBuddylist(player.getBuddylist().getBuddies())); - - CharacterNameAndId pendingBuddyRequest = c.getPlayer().getBuddylist().pollPendingRequest(); - if (pendingBuddyRequest != null) { - c.announce(MaplePacketCreator.requestBuddylistAdd(pendingBuddyRequest.getId(), c.getPlayer().getId(), pendingBuddyRequest.getName())); - } - - c.announce(MaplePacketCreator.updateGender(player)); - player.checkMessenger(); - c.announce(MaplePacketCreator.enableReport()); - player.changeSkillLevel(SkillFactory.getSkill(10000000 * player.getJobType() + 12), (byte) (player.getLinkedLevel() / 10), 20, -1); - player.checkBerserk(player.isHidden()); - - if (newcomer) { - for(MaplePet pet : player.getPets()) { - if(pet != null) { - wserv.registerPetHunger(player, player.getPetIndex(pet)); - } - } - - MapleMount mount = player.getMount(); // thanks Ari for noticing a scenario where Silver Mane quest couldn't be started - if (mount.getItemId() != 0) { - player.announce(MaplePacketCreator.updateMount(player.getId(), mount, false)); - } - - player.reloadQuestExpirations(); + player.reloadQuestExpirations(); /* if (!c.hasVotedAlready()){ player.announce(MaplePacketCreator.earnTitleMessage("You can vote now! Vote and earn a vote point!")); } */ - if (player.isGM()){ - Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.earnTitleMessage((player.gmLevel() < 6 ? "GM " : "Admin ") + player.getName() + " has logged in")); - } - - if(diseases != null) { - for(Entry> e : diseases.entrySet()) { - final List> debuff = Collections.singletonList(new Pair<>(e.getKey(), e.getValue().getRight().getX())); - c.announce(MaplePacketCreator.giveDebuff(debuff, e.getValue().getRight())); - } - } - } else { - if(player.isRidingBattleship()) { - player.announceBattleshipHp(); - } - } - - player.buffExpireTask(); - player.diseaseExpireTask(); - player.skillCooldownTask(); - player.expirationTask(); - player.questExpirationTask(); - if (GameConstants.hasSPTable(player.getJob()) && player.getJob().getId() != 2001) { - player.createDragon(); + if (player.isGM()) { + Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.earnTitleMessage((player.gmLevel() < 6 ? "GM " : "Admin ") + player.getName() + " has logged in")); } - player.commitExcludedItems(); - showDueyNotification(c, player); - - if (player.getMap().getHPDec() > 0) player.resetHpDecreaseTask(); - - player.resetPlayerRates(); - if(YamlConfig.config.server.USE_ADD_RATES_BY_LEVEL == true) player.setPlayerRates(); - player.setWorldRates(); - player.updateCouponRates(); - - player.receivePartyMemberHP(); - - if(player.getPartnerId() > 0) { - int partnerId = player.getPartnerId(); - final MapleCharacter partner = wserv.getPlayerStorage().getCharacterById(partnerId); - - if(partner != null && !partner.isAwayFromWorld()) { - player.announce(Wedding.OnNotifyWeddingPartnerTransfer(partnerId, partner.getMapId())); - partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(player.getId(), player.getMapId())); + if (diseases != null) { + for (Entry> e : diseases.entrySet()) { + final List> debuff = Collections.singletonList(new Pair<>(e.getKey(), e.getValue().getRight().getX())); + c.announce(MaplePacketCreator.giveDebuff(debuff, e.getValue().getRight())); } } - - if (newcomer) { - EventInstanceManager eim = MapleEventRecallCoordinator.getInstance().recallEventInstance(cid); - if (eim != null) { - eim.registerPlayer(player); - } + } else { + if (player.isRidingBattleship()) { + player.announceBattleshipHp(); } - - // Tell the client to use the custom scripts available for the NPCs provided, instead of the WZ entries. - if (YamlConfig.config.server.USE_NPCS_SCRIPTABLE) { - - // Create a copy to prevent always adding entries to the server's list. - Map npcsIds = YamlConfig.config.server.NPCS_SCRIPTABLE - .entrySet().stream().collect(Collectors.toMap( - entry -> Integer.parseInt(entry.getKey()), - Entry::getValue - )); - - // Any npc be specified as the rebirth npc. Allow the npc to use custom scripts explicitly. - if (YamlConfig.config.server.USE_REBIRTH_SYSTEM) { - npcsIds.put(YamlConfig.config.server.REBIRTH_NPC_ID, "Rebirth"); - } - - c.announce(MaplePacketCreator.setNPCScriptable(npcsIds)); - } - - if(newcomer) player.setLoginTime(System.currentTimeMillis()); - } catch(Exception e) { - e.printStackTrace(); - } finally { - c.releaseClient(); } - } else { - c.announce(MaplePacketCreator.getAfterLoginError(10)); + + player.buffExpireTask(); + player.diseaseExpireTask(); + player.skillCooldownTask(); + player.expirationTask(); + player.questExpirationTask(); + if (GameConstants.hasSPTable(player.getJob()) && player.getJob().getId() != 2001) { + player.createDragon(); + } + + player.commitExcludedItems(); + showDueyNotification(c, player); + + if (player.getMap().getHPDec() > 0) player.resetHpDecreaseTask(); + + player.resetPlayerRates(); + if (YamlConfig.config.server.USE_ADD_RATES_BY_LEVEL) { + player.setPlayerRates(); + } + + player.setWorldRates(); + player.updateCouponRates(); + + player.receivePartyMemberHP(); + + if (player.getPartnerId() > 0) { + int partnerId = player.getPartnerId(); + final MapleCharacter partner = wserv.getPlayerStorage().getCharacterById(partnerId); + + if (partner != null && !partner.isAwayFromWorld()) { + player.announce(Wedding.OnNotifyWeddingPartnerTransfer(partnerId, partner.getMapId())); + partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(player.getId(), player.getMapId())); + } + } + + if (newcomer) { + EventInstanceManager eim = MapleEventRecallCoordinator.getInstance().recallEventInstance(cid); + if (eim != null) { + eim.registerPlayer(player); + } + } + + // Tell the client to use the custom scripts available for the NPCs provided, instead of the WZ entries. + if (YamlConfig.config.server.USE_NPCS_SCRIPTABLE) { + + // Create a copy to prevent always adding entries to the server's list. + Map npcsIds = YamlConfig.config.server.NPCS_SCRIPTABLE + .entrySet().stream().collect(Collectors.toMap( + entry -> Integer.parseInt(entry.getKey()), + Entry::getValue + )); + + // Any npc be specified as the rebirth npc. Allow the npc to use custom scripts explicitly. + if (YamlConfig.config.server.USE_REBIRTH_SYSTEM) { + npcsIds.put(YamlConfig.config.server.REBIRTH_NPC_ID, "Rebirth"); + } + + c.announce(MaplePacketCreator.setNPCScriptable(npcsIds)); + } + + if (newcomer) player.setLoginTime(System.currentTimeMillis()); + } catch (Exception e) { + e.printStackTrace(); + } finally { + c.releaseClient(); } } diff --git a/src/main/java/net/server/coordinator/login/LoginStorage.java b/src/main/java/net/server/coordinator/login/LoginStorage.java index c4957f6899..d8e14e49f4 100644 --- a/src/main/java/net/server/coordinator/login/LoginStorage.java +++ b/src/main/java/net/server/coordinator/login/LoginStorage.java @@ -22,70 +22,60 @@ package net.server.coordinator.login; import config.YamlConfig; import net.server.Server; +import java.time.Instant; +import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; /** * * @author Ronan */ public class LoginStorage { - - private ConcurrentHashMap> loginHistory = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> loginHistory = new ConcurrentHashMap<>(); // Key: accountId public boolean registerLogin(int accountId) { - List accHist = loginHistory.get(accountId); - if (accHist == null) { - accHist = new LinkedList<>(); - loginHistory.put(accountId, accHist); - } - - synchronized (accHist) { - if (accHist.size() > YamlConfig.config.server.MAX_ACCOUNT_LOGIN_ATTEMPT) { - long blockExpiration = Server.getInstance().getCurrentTime() + YamlConfig.config.server.LOGIN_ATTEMPT_DURATION; - Collections.fill(accHist, blockExpiration); + List attempts = loginHistory.computeIfAbsent(accountId, k -> new ArrayList<>()); + synchronized (attempts) { + final Instant attemptExpiry = Instant.ofEpochMilli(Server.getInstance().getCurrentTime() + YamlConfig.config.server.LOGIN_ATTEMPT_DURATION); + + if (attempts.size() > YamlConfig.config.server.MAX_ACCOUNT_LOGIN_ATTEMPT) { + Collections.fill(attempts, attemptExpiry); return false; } - accHist.add(Server.getInstance().getCurrentTime() + YamlConfig.config.server.LOGIN_ATTEMPT_DURATION); + attempts.add(attemptExpiry); return true; } } - public void updateLoginHistory() { - long timeNow = Server.getInstance().getCurrentTime(); - List toRemove = new LinkedList<>(); - List toRemoveAttempt = new LinkedList<>(); - - for (Entry> loginEntries : loginHistory.entrySet()) { - toRemoveAttempt.clear(); - - List accAttempts = loginEntries.getValue(); - synchronized (accAttempts) { - for (Long loginAttempt : accAttempts) { - if (loginAttempt < timeNow) { - toRemoveAttempt.add(loginAttempt); - } + public void clearExpiredAttempts() { + final Instant now = Instant.ofEpochMilli(Server.getInstance().getCurrentTime()); + List accountIdsToClear = new ArrayList<>(); + + for (Entry> loginEntries : loginHistory.entrySet()) { + final List attempts = loginEntries.getValue(); + synchronized (attempts) { + List attemptsToRemove = attempts.stream() + .filter(attempt -> attempt.isBefore(now)) + .collect(Collectors.toList()); + + for (Instant attemptToRemove : attemptsToRemove) { + attempts.remove(attemptToRemove); } - if (!toRemoveAttempt.isEmpty()) { - for (Long trAttempt : toRemoveAttempt) { - accAttempts.remove(trAttempt); - } - - if (accAttempts.isEmpty()) { - toRemove.add(loginEntries.getKey()); - } + if (attempts.isEmpty()) { + accountIdsToClear.add(loginEntries.getKey()); } } } - - for (Integer tr : toRemove) { - loginHistory.remove(tr); + + for (Integer accountId : accountIdsToClear) { + loginHistory.remove(accountId); } } } diff --git a/src/main/java/net/server/coordinator/login/MapleLoginBypassCoordinator.java b/src/main/java/net/server/coordinator/login/MapleLoginBypassCoordinator.java index 30c111ab3d..498a273165 100644 --- a/src/main/java/net/server/coordinator/login/MapleLoginBypassCoordinator.java +++ b/src/main/java/net/server/coordinator/login/MapleLoginBypassCoordinator.java @@ -19,7 +19,14 @@ */ package net.server.coordinator.login; +import client.MapleCharacter; +import client.MapleClient; import config.YamlConfig; +import net.server.Server; +import net.server.coordinator.session.Hwid; +import net.server.world.World; +import tools.Pair; + import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -27,62 +34,56 @@ import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import client.MapleCharacter; -import client.MapleClient; -import net.server.world.World; -import net.server.Server; -import tools.Pair; - /** - * * @author Ronan */ public class MapleLoginBypassCoordinator { - private final static MapleLoginBypassCoordinator instance = new MapleLoginBypassCoordinator(); - + public static MapleLoginBypassCoordinator getInstance() { return instance; } - - private final ConcurrentHashMap, Pair> loginBypass = new ConcurrentHashMap<>(); // optimized PIN & PIC check - - public boolean canLoginBypass(String nibbleHwid, int accId, boolean pic) { + + private final ConcurrentHashMap, Pair> loginBypass = new ConcurrentHashMap<>(); // optimized PIN & PIC check + + public boolean canLoginBypass(Hwid hwid, int accId, boolean pic) { try { - Pair entry = new Pair<>(nibbleHwid, accId); + Pair entry = new Pair<>(hwid, accId); Boolean p = loginBypass.get(entry).getLeft(); - + return !pic || p; } catch (NullPointerException npe) { return false; } } - - public void registerLoginBypassEntry(String nibbleHwid, int accId, boolean pic) { + + public void registerLoginBypassEntry(Hwid hwid, int accId, boolean pic) { long expireTime = (pic ? YamlConfig.config.server.BYPASS_PIC_EXPIRATION : YamlConfig.config.server.BYPASS_PIN_EXPIRATION); if (expireTime > 0) { - Pair entry = new Pair<>(nibbleHwid, accId); + Pair entry = new Pair<>(hwid, accId); expireTime = Server.getInstance().getCurrentTime() + expireTime * 60 * 1000; try { pic |= loginBypass.get(entry).getLeft(); expireTime = Math.max(loginBypass.get(entry).getRight(), expireTime); - } catch (NullPointerException npe) {} - + } catch (NullPointerException npe) { + } + loginBypass.put(entry, new Pair<>(pic, expireTime)); } } - - public void unregisterLoginBypassEntry(String nibbleHwid, int accId) { - Pair entry = new Pair<>(nibbleHwid, accId); + + public void unregisterLoginBypassEntry(Hwid hwid, int accId) { + String hwidValue = hwid == null ? null : hwid.hwid(); + Pair entry = new Pair<>(hwidValue, accId); loginBypass.remove(entry); } - + public void runUpdateLoginBypass() { if (!loginBypass.isEmpty()) { - List> toRemove = new LinkedList<>(); + List> toRemove = new LinkedList<>(); Set onlineAccounts = new HashSet<>(); long timeNow = Server.getInstance().getCurrentTime(); - + for (World w : Server.getInstance().getWorlds()) { for (MapleCharacter chr : w.getPlayerStorage().getAllCharacters()) { MapleClient c = chr.getClient(); @@ -91,8 +92,8 @@ public class MapleLoginBypassCoordinator { } } } - - for (Entry, Pair> e : loginBypass.entrySet()) { + + for (Entry, Pair> e : loginBypass.entrySet()) { if (onlineAccounts.contains(e.getKey().getRight())) { long expireTime = timeNow + 2 * 60 * 1000; if (expireTime > e.getValue().getRight()) { @@ -102,13 +103,13 @@ public class MapleLoginBypassCoordinator { toRemove.add(e.getKey()); } } - + if (!toRemove.isEmpty()) { - for (Pair p : toRemove) { + for (Pair p : toRemove) { loginBypass.remove(p); } } } } - + } diff --git a/src/main/java/net/server/coordinator/session/HostHwid.java b/src/main/java/net/server/coordinator/session/HostHwid.java new file mode 100644 index 0000000000..9516e2f2af --- /dev/null +++ b/src/main/java/net/server/coordinator/session/HostHwid.java @@ -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)); + } +} diff --git a/src/main/java/net/server/coordinator/session/HostHwidCache.java b/src/main/java/net/server/coordinator/session/HostHwidCache.java new file mode 100644 index 0000000000..21a9cafca1 --- /dev/null +++ b/src/main/java/net/server/coordinator/session/HostHwidCache.java @@ -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 hostHwidCache = new ConcurrentHashMap<>(); // Key: remoteHost + + void clearExpired() { + SessionDAO.deleteExpiredHwidAccounts(); + + Instant now = Instant.ofEpochMilli(Server.getInstance().getCurrentTime()); + List remoteHostsToRemove = new ArrayList<>(); + for (Map.Entry 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(); + } + +} diff --git a/src/main/java/net/server/coordinator/session/Hwid.java b/src/main/java/net/server/coordinator/session/Hwid.java new file mode 100644 index 0000000000..bd8ad7ce61 --- /dev/null +++ b/src/main/java/net/server/coordinator/session/Hwid.java @@ -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]); + } +} diff --git a/src/main/java/net/server/coordinator/session/HwidAssociationExpiry.java b/src/main/java/net/server/coordinator/session/HwidAssociationExpiry.java new file mode 100644 index 0000000000..5c93f96802 --- /dev/null +++ b/src/main/java/net/server/coordinator/session/HwidAssociationExpiry.java @@ -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; + } +} diff --git a/src/main/java/net/server/coordinator/session/HwidRelevance.java b/src/main/java/net/server/coordinator/session/HwidRelevance.java new file mode 100644 index 0000000000..6dbbf6f838 --- /dev/null +++ b/src/main/java/net/server/coordinator/session/HwidRelevance.java @@ -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; + } +} diff --git a/src/main/java/net/server/coordinator/session/InitializationResult.java b/src/main/java/net/server/coordinator/session/InitializationResult.java new file mode 100644 index 0000000000..3c6f596dfa --- /dev/null +++ b/src/main/java/net/server/coordinator/session/InitializationResult.java @@ -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; + } +} diff --git a/src/main/java/net/server/coordinator/session/IpAddresses.java b/src/main/java/net/server/coordinator/session/IpAddresses.java new file mode 100644 index 0000000000..16d551f6e5 --- /dev/null +++ b/src/main/java/net/server/coordinator/session/IpAddresses.java @@ -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 LOCAL_ADDRESS_PATTERNS = loadLocalAddressPatterns(); + + private static List 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(); + } +} diff --git a/src/main/java/net/server/coordinator/session/MapleSessionCoordinator.java b/src/main/java/net/server/coordinator/session/MapleSessionCoordinator.java deleted file mode 100644 index 93e4135649..0000000000 --- a/src/main/java/net/server/coordinator/session/MapleSessionCoordinator.java +++ /dev/null @@ -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 . -*/ -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 onlineClients = new HashMap<>(); - private final Set onlineRemoteHwids = new HashSet<>(); - private final Map> loginRemoteHosts = new HashMap<>(); - private final Set pooledRemoteHosts = new HashSet<>(); - - private final ConcurrentHashMap cachedHostHwids = new ConcurrentHashMap<>(); - private final ConcurrentHashMap cachedHostTimeout = new ConcurrentHashMap<>(); - private final List 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 localComms = loadLocalCommPatterns(); - - private static List loadLocalCommPatterns() { - String[] localComms = {"10\\.", "192\\.168\\.", "172\\.(1[6-9]|2[0-9]|3[0-1])\\."}; - List 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 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 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 toRemove = new LinkedList<>(); - for (Entry 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> elist = new ArrayList<>(onlineClients.entrySet()); - elist.sort((e1, e2) -> e1.getKey().compareTo(e2.getKey())); - - System.out.println("Current online clients: "); - for (Entry e : elist) { - System.out.println(" " + e.getKey()); - } - } - - if (!onlineRemoteHwids.isEmpty()) { - List 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>> elist = new ArrayList<>(loginRemoteHosts.entrySet()); - - elist.sort((e1, e2) -> e1.getKey().compareTo(e2.getKey())); - - System.out.println("Current login sessions: "); - for (Entry> 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> elist = new ArrayList<>(onlineClients.entrySet()); - elist.sort((e1, e2) -> e1.getKey().compareTo(e2.getKey())); - - str += ("Current online clients:\r\n"); - for (Entry e : elist) { - str += (" " + e.getKey() + "\r\n"); - } - } - - if (!onlineRemoteHwids.isEmpty()) { - List 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>> elist = new ArrayList<>(loginRemoteHosts.entrySet()); - - elist.sort((e1, e2) -> e1.getKey().compareTo(e2.getKey())); - - str += ("Current login sessions:\r\n"); - for (Entry> e : elist) { - str += (" " + e.getKey() + ", IP: " + e.getValue() + "\r\n"); - } - } - - c.getAbstractPlayerInteraction().npcTalk(2140000, str); - } -} diff --git a/src/main/java/net/server/coordinator/session/SessionCoordinator.java b/src/main/java/net/server/coordinator/session/SessionCoordinator.java new file mode 100644 index 0000000000..5a266383e9 --- /dev/null +++ b/src/main/java/net/server/coordinator/session/SessionCoordinator.java @@ -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 . +*/ +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 onlineClients = new HashMap<>(); // Key: account id + private final Set onlineRemoteHwids = new HashSet<>(); // Hwid/nibblehwid + private final Map 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 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 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> 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 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> elist = new ArrayList<>(loginRemoteHosts.entrySet()); + elist.sort(Entry.comparingByKey()); + + System.out.println("Current login sessions: "); + for (Entry 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> elist = new ArrayList<>(onlineClients.entrySet()); + elist.sort(Entry.comparingByKey()); + + str += ("Current online clients:\r\n"); + for (Entry e : elist) { + str += (" " + e.getKey() + "\r\n"); + } + } + + if (!onlineRemoteHwids.isEmpty()) { + List 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> elist = new ArrayList<>(loginRemoteHosts.entrySet()); + + elist.sort((e1, e2) -> e1.getKey().compareTo(e2.getKey())); + + str += ("Current login sessions:\r\n"); + for (Entry e : elist) { + str += (" " + e.getKey() + ", IP: " + e.getValue().getRemoteAddress() + "\r\n"); + } + } + + c.getAbstractPlayerInteraction().npcTalk(2140000, str); + } +} diff --git a/src/main/java/net/server/coordinator/session/SessionDAO.java b/src/main/java/net/server/coordinator/session/SessionDAO.java new file mode 100644 index 0000000000..ca862632bf --- /dev/null +++ b/src/main/java/net/server/coordinator/session/SessionDAO.java @@ -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 getHwidsForAccount(Connection con, int accountId) throws SQLException { + final List 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 getHwidRelevance(Connection con, int accountId) throws SQLException { + final List 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(); + } + } +} diff --git a/src/main/java/net/server/coordinator/session/SessionInitialization.java b/src/main/java/net/server/coordinator/session/SessionInitialization.java new file mode 100644 index 0000000000..6e160099de --- /dev/null +++ b/src/main/java/net/server/coordinator/session/SessionInitialization.java @@ -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 remoteHostsInInitState = new HashSet<>(); + private final List 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 before 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 after any session initialization procedure. + */ + public void finalize(String remoteHost) { + final Lock lock = getLock(remoteHost); + lock.lock(); + try { + remoteHostsInInitState.remove(remoteHost); + } finally { + lock.unlock(); + } + } +} diff --git a/src/main/java/net/server/handlers/login/AfterLoginHandler.java b/src/main/java/net/server/handlers/login/AfterLoginHandler.java index f3acb93bf0..2a4b306665 100644 --- a/src/main/java/net/server/handlers/login/AfterLoginHandler.java +++ b/src/main/java/net/server/handlers/login/AfterLoginHandler.java @@ -23,7 +23,7 @@ package net.server.handlers.login; import client.MapleClient; import net.AbstractMaplePacketHandler; -import net.server.coordinator.session.MapleSessionCoordinator; +import net.server.coordinator.session.SessionCoordinator; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; @@ -57,7 +57,7 @@ public final class AfterLoginHandler extends AbstractMaplePacketHandler { c.announce(MaplePacketCreator.requestPinAfterFailure()); } } else if (c2 == 0 && c3 == 5) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null); + SessionCoordinator.getInstance().closeSession(c, null); c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); } } diff --git a/src/main/java/net/server/handlers/login/CharSelectedHandler.java b/src/main/java/net/server/handlers/login/CharSelectedHandler.java index 9dfe1f5dad..b879b30c21 100644 --- a/src/main/java/net/server/handlers/login/CharSelectedHandler.java +++ b/src/main/java/net/server/handlers/login/CharSelectedHandler.java @@ -22,36 +22,31 @@ package net.server.handlers.login; import client.MapleClient; -import java.net.InetAddress; -import java.net.UnknownHostException; import net.AbstractMaplePacketHandler; import net.server.Server; -import net.server.coordinator.session.MapleSessionCoordinator; -import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; +import net.server.coordinator.session.Hwid; +import net.server.coordinator.session.SessionCoordinator; +import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult; import net.server.world.World; -import org.apache.mina.core.session.IoSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; +import java.net.InetAddress; +import java.net.UnknownHostException; + public final class CharSelectedHandler extends AbstractMaplePacketHandler { + private static final Logger log = LoggerFactory.getLogger(CharSelectedHandler.class); private static int parseAntiMulticlientError(AntiMulticlientResult res) { - switch (res) { - case REMOTE_PROCESSING: - return 10; - - case REMOTE_LOGGEDIN: - return 7; - - case REMOTE_NO_MATCH: - return 17; - - case COORDINATOR_ERROR: - return 8; - - default: - return 9; - } + return switch (res) { + case REMOTE_PROCESSING -> 10; + case REMOTE_LOGGEDIN -> 7; + case REMOTE_NO_MATCH -> 17; + case COORDINATOR_ERROR -> 8; + default -> 9; + }; } @Override @@ -59,31 +54,34 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler { int charId = slea.readInt(); String macs = slea.readMapleAsciiString(); - String hwid = slea.readMapleAsciiString(); - - if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { + String hostString = slea.readMapleAsciiString(); + + final Hwid hwid; + try { + hwid = Hwid.fromHostString(hostString); + } catch (IllegalArgumentException e) { + log.warn("Invalid host string: {}", hostString, e); c.announce(MaplePacketCreator.getAfterLoginError(17)); return; } c.updateMacs(macs); - c.updateHWID(hwid); - - IoSession session = c.getSession(); - AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid); + c.updateHwid(hwid); + + AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid); if (res != AntiMulticlientResult.SUCCESS) { c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); return; } if (c.hasBannedMac() || c.hasBannedHWID()) { - MapleSessionCoordinator.getInstance().closeSession(session, true); + SessionCoordinator.getInstance().closeSession(c, true); return; } Server server = Server.getInstance(); if(!server.haveCharacterEntry(c.getAccID(), charId)) { - MapleSessionCoordinator.getInstance().closeSession(session, true); + SessionCoordinator.getInstance().closeSession(c, true); return; } @@ -94,7 +92,7 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler { return; } - String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel()); + String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel()); if(socket == null) { c.announce(MaplePacketCreator.getAfterLoginError(10)); return; diff --git a/src/main/java/net/server/handlers/login/CharSelectedWithPicHandler.java b/src/main/java/net/server/handlers/login/CharSelectedWithPicHandler.java index 7b8005e41a..7311b931df 100644 --- a/src/main/java/net/server/handlers/login/CharSelectedWithPicHandler.java +++ b/src/main/java/net/server/handlers/login/CharSelectedWithPicHandler.java @@ -1,37 +1,31 @@ package net.server.handlers.login; +import client.MapleClient; +import net.AbstractMaplePacketHandler; +import net.server.Server; +import net.server.coordinator.session.Hwid; +import net.server.coordinator.session.SessionCoordinator; +import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult; +import net.server.world.World; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tools.MaplePacketCreator; +import tools.data.input.SeekableLittleEndianAccessor; + import java.net.InetAddress; import java.net.UnknownHostException; -import net.AbstractMaplePacketHandler; -import net.server.Server; -import net.server.coordinator.session.MapleSessionCoordinator; -import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; -import net.server.world.World; -import org.apache.mina.core.session.IoSession; -import tools.MaplePacketCreator; -import tools.data.input.SeekableLittleEndianAccessor; -import client.MapleClient; - public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler { + private static final Logger log = LoggerFactory.getLogger(CharSelectedWithPicHandler.class); private static int parseAntiMulticlientError(AntiMulticlientResult res) { - switch (res) { - case REMOTE_PROCESSING: - return 10; - - case REMOTE_LOGGEDIN: - return 7; - - case REMOTE_NO_MATCH: - return 17; - - case COORDINATOR_ERROR: - return 8; - - default: - return 9; - } + return switch (res) { + case REMOTE_PROCESSING -> 10; + case REMOTE_LOGGEDIN -> 7; + case REMOTE_NO_MATCH -> 17; + case COORDINATOR_ERROR -> 8; + default -> 9; + }; } @Override @@ -40,26 +34,28 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler { int charId = slea.readInt(); String macs = slea.readMapleAsciiString(); - String hwid = slea.readMapleAsciiString(); - - if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { + String hostString = slea.readMapleAsciiString(); + + final Hwid hwid; + try { + hwid = Hwid.fromHostString(hostString); + } catch (IllegalArgumentException e) { + log.warn("Invalid host string: {}", hostString, e); c.announce(MaplePacketCreator.getAfterLoginError(17)); return; } c.updateMacs(macs); - c.updateHWID(hwid); - - IoSession session = c.getSession(); - + c.updateHwid(hwid); + if (c.hasBannedMac() || c.hasBannedHWID()) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); return; } Server server = Server.getInstance(); if(!server.haveCharacterEntry(c.getAccID(), charId)) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); return; } @@ -71,13 +67,13 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler { return; } - String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel()); + String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel()); if(socket == null) { c.announce(MaplePacketCreator.getAfterLoginError(10)); return; } - AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid); + AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid); if (res != AntiMulticlientResult.SUCCESS) { c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); return; diff --git a/src/main/java/net/server/handlers/login/LoginPasswordHandler.java b/src/main/java/net/server/handlers/login/LoginPasswordHandler.java index 5a2094bcb7..3928d9506c 100644 --- a/src/main/java/net/server/handlers/login/LoginPasswordHandler.java +++ b/src/main/java/net/server/handlers/login/LoginPasswordHandler.java @@ -26,8 +26,7 @@ import client.MapleClient; import config.YamlConfig; import net.MaplePacketHandler; import net.server.Server; -import net.server.coordinator.session.MapleSessionCoordinator; -import org.apache.mina.core.session.IoSession; +import net.server.coordinator.session.Hwid; import tools.BCrypt; import tools.DatabaseConnection; import tools.HexTool; @@ -54,13 +53,9 @@ public final class LoginPasswordHandler implements MaplePacketHandler { return HexTool.toString(digester.digest()).replace(" ", "").toLowerCase(); } - private static String getRemoteIp(IoSession session) { - return MapleSessionCoordinator.getSessionRemoteAddress(session); - } - @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - String remoteHost = getRemoteIp(c.getSession()); + String remoteHost = c.getRemoteAddress(); if (remoteHost.contentEquals("null")) { c.announce(MaplePacketCreator.getLoginFailed(14)); // thanks Alchemist for noting remoteHost could be null return; @@ -72,8 +67,8 @@ public final class LoginPasswordHandler implements MaplePacketHandler { slea.skip(6); // localhost masked the initial part with zeroes... byte[] hwidNibbles = slea.read(4); - String nibbleHwid = HexTool.toCompressedString(hwidNibbles); - int loginok = c.login(login, pwd, nibbleHwid); + Hwid hwid = new Hwid(HexTool.bytesToHex(hwidNibbles)); + int loginok = c.login(login, pwd, hwid); if (YamlConfig.config.server.AUTOMATIC_REGISTER && loginok == 5) { @@ -93,7 +88,7 @@ public final class LoginPasswordHandler implements MaplePacketHandler { c.setAccID(-1); e.printStackTrace(); } finally { - loginok = c.login(login, pwd, nibbleHwid); + loginok = c.login(login, pwd, hwid); } } diff --git a/src/main/java/net/server/handlers/login/RegisterPicHandler.java b/src/main/java/net/server/handlers/login/RegisterPicHandler.java index f32fac4579..8d3f4a7ad8 100644 --- a/src/main/java/net/server/handlers/login/RegisterPicHandler.java +++ b/src/main/java/net/server/handlers/login/RegisterPicHandler.java @@ -1,37 +1,31 @@ package net.server.handlers.login; +import client.MapleClient; +import net.AbstractMaplePacketHandler; +import net.server.Server; +import net.server.coordinator.session.Hwid; +import net.server.coordinator.session.SessionCoordinator; +import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult; +import net.server.world.World; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tools.MaplePacketCreator; +import tools.data.input.SeekableLittleEndianAccessor; + import java.net.InetAddress; import java.net.UnknownHostException; -import net.AbstractMaplePacketHandler; -import net.server.Server; -import net.server.world.World; -import tools.MaplePacketCreator; -import tools.data.input.SeekableLittleEndianAccessor; -import client.MapleClient; -import net.server.coordinator.session.MapleSessionCoordinator; -import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; -import org.apache.mina.core.session.IoSession; - public final class RegisterPicHandler extends AbstractMaplePacketHandler { + private static final Logger log = LoggerFactory.getLogger(RegisterPicHandler.class); private static int parseAntiMulticlientError(AntiMulticlientResult res) { - switch (res) { - case REMOTE_PROCESSING: - return 10; - - case REMOTE_LOGGEDIN: - return 7; - - case REMOTE_NO_MATCH: - return 17; - - case COORDINATOR_ERROR: - return 8; - - default: - return 9; - } + return switch (res) { + case REMOTE_PROCESSING -> 10; + case REMOTE_LOGGEDIN -> 7; + case REMOTE_NO_MATCH -> 17; + case COORDINATOR_ERROR -> 8; + default -> 9; + }; } @Override @@ -40,31 +34,34 @@ public final class RegisterPicHandler extends AbstractMaplePacketHandler { int charId = slea.readInt(); String macs = slea.readMapleAsciiString(); - String hwid = slea.readMapleAsciiString(); - - if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { + String hostString = slea.readMapleAsciiString(); + + final Hwid hwid; + try { + hwid = Hwid.fromHostString(hostString); + } catch (IllegalArgumentException e) { + log.warn("Invalid host string: {}", hostString, e); c.announce(MaplePacketCreator.getAfterLoginError(17)); return; } c.updateMacs(macs); - c.updateHWID(hwid); + c.updateHwid(hwid); - IoSession session = c.getSession(); - AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid); + AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid); if (res != AntiMulticlientResult.SUCCESS) { c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); return; } if (c.hasBannedMac() || c.hasBannedHWID()) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); return; } Server server = Server.getInstance(); if(!server.haveCharacterEntry(c.getAccID(), charId)) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); return; } @@ -79,7 +76,7 @@ public final class RegisterPicHandler extends AbstractMaplePacketHandler { return; } - String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel()); + String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel()); if(socket == null) { c.announce(MaplePacketCreator.getAfterLoginError(10)); return; @@ -94,7 +91,7 @@ public final class RegisterPicHandler extends AbstractMaplePacketHandler { e.printStackTrace(); } } else { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); } } } \ No newline at end of file diff --git a/src/main/java/net/server/handlers/login/RegisterPinHandler.java b/src/main/java/net/server/handlers/login/RegisterPinHandler.java index 65a3e667a9..d35d6717e7 100644 --- a/src/main/java/net/server/handlers/login/RegisterPinHandler.java +++ b/src/main/java/net/server/handlers/login/RegisterPinHandler.java @@ -23,7 +23,7 @@ package net.server.handlers.login; import client.MapleClient; import net.AbstractMaplePacketHandler; -import net.server.coordinator.session.MapleSessionCoordinator; +import net.server.coordinator.session.SessionCoordinator; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; @@ -35,7 +35,7 @@ public final class RegisterPinHandler extends AbstractMaplePacketHandler { public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { byte c2 = slea.readByte(); if (c2 == 0) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null); + SessionCoordinator.getInstance().closeSession(c, null); c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); } else { String pin = slea.readMapleAsciiString(); @@ -43,7 +43,7 @@ public final class RegisterPinHandler extends AbstractMaplePacketHandler { c.setPin(pin); c.announce(MaplePacketCreator.pinRegistered()); - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null); + SessionCoordinator.getInstance().closeSession(c, null); c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); } } diff --git a/src/main/java/net/server/handlers/login/SetGenderHandler.java b/src/main/java/net/server/handlers/login/SetGenderHandler.java index 3cf57342f8..50c9292643 100644 --- a/src/main/java/net/server/handlers/login/SetGenderHandler.java +++ b/src/main/java/net/server/handlers/login/SetGenderHandler.java @@ -25,7 +25,7 @@ package net.server.handlers.login; import client.MapleClient; import net.AbstractMaplePacketHandler; import net.server.Server; -import net.server.coordinator.session.MapleSessionCoordinator; +import net.server.coordinator.session.SessionCoordinator; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; @@ -44,7 +44,7 @@ public class SetGenderHandler extends AbstractMaplePacketHandler { Server.getInstance().registerLoginState(c); } else { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null); + SessionCoordinator.getInstance().closeSession(c, null); c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN); } } diff --git a/src/main/java/net/server/handlers/login/ViewAllCharRegisterPicHandler.java b/src/main/java/net/server/handlers/login/ViewAllCharRegisterPicHandler.java index f6fc999fd3..a2fc4842b5 100644 --- a/src/main/java/net/server/handlers/login/ViewAllCharRegisterPicHandler.java +++ b/src/main/java/net/server/handlers/login/ViewAllCharRegisterPicHandler.java @@ -1,37 +1,32 @@ package net.server.handlers.login; import client.MapleClient; -import java.net.InetAddress; -import java.net.UnknownHostException; import net.AbstractMaplePacketHandler; import net.server.Server; -import net.server.coordinator.session.MapleSessionCoordinator; -import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; +import net.server.coordinator.session.Hwid; +import net.server.coordinator.session.SessionCoordinator; +import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult; import net.server.world.World; -import org.apache.mina.core.session.IoSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import tools.MaplePacketCreator; import tools.Randomizer; import tools.data.input.SeekableLittleEndianAccessor; +import java.net.InetAddress; +import java.net.UnknownHostException; + public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHandler { + private static final Logger log = LoggerFactory.getLogger(ViewAllCharRegisterPicHandler.class); private static int parseAntiMulticlientError(AntiMulticlientResult res) { - switch (res) { - case REMOTE_PROCESSING: - return 10; - - case REMOTE_LOGGEDIN: - return 7; - - case REMOTE_NO_MATCH: - return 17; - - case COORDINATOR_ERROR: - return 8; - - default: - return 9; - } + return switch (res) { + case REMOTE_PROCESSING -> 10; + case REMOTE_LOGGEDIN -> 7; + case REMOTE_NO_MATCH -> 17; + case COORDINATOR_ERROR -> 8; + default -> 9; + }; } @Override @@ -41,23 +36,26 @@ public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHand slea.readInt(); // please don't let the client choose which world they should login String mac = slea.readMapleAsciiString(); - String hwid = slea.readMapleAsciiString(); - - if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { + String hostString = slea.readMapleAsciiString(); + + final Hwid hwid; + try { + hwid = Hwid.fromHostString(hostString); + } catch (IllegalArgumentException e) { + log.warn("Invalid host string: {}", hostString, e); c.announce(MaplePacketCreator.getAfterLoginError(17)); return; } c.updateMacs(mac); - c.updateHWID(hwid); + c.updateHwid(hwid); if (c.hasBannedMac() || c.hasBannedHWID()) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); return; } - - IoSession session = c.getSession(); - AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid); + + AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid); if (res != AntiMulticlientResult.SUCCESS) { c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); return; @@ -65,7 +63,7 @@ public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHand Server server = Server.getInstance(); if(!server.haveCharacterEntry(c.getAccID(), charId)) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); return; } @@ -82,7 +80,7 @@ public final class ViewAllCharRegisterPicHandler extends AbstractMaplePacketHand String pic = slea.readMapleAsciiString(); c.setPic(pic); - String[] socket = server.getInetSocket(session, c.getWorld(), channel); + String[] socket = server.getInetSocket(c, c.getWorld(), channel); if (socket == null) { c.announce(MaplePacketCreator.getAfterLoginError(10)); return; diff --git a/src/main/java/net/server/handlers/login/ViewAllCharSelectedHandler.java b/src/main/java/net/server/handlers/login/ViewAllCharSelectedHandler.java index 9bda9fc14b..82157f8c89 100644 --- a/src/main/java/net/server/handlers/login/ViewAllCharSelectedHandler.java +++ b/src/main/java/net/server/handlers/login/ViewAllCharSelectedHandler.java @@ -22,37 +22,32 @@ package net.server.handlers.login; import client.MapleClient; -import java.net.InetAddress; -import java.net.UnknownHostException; import net.AbstractMaplePacketHandler; import net.server.Server; -import net.server.coordinator.session.MapleSessionCoordinator; -import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; +import net.server.coordinator.session.Hwid; +import net.server.coordinator.session.SessionCoordinator; +import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult; import net.server.world.World; -import org.apache.mina.core.session.IoSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import tools.MaplePacketCreator; import tools.Randomizer; import tools.data.input.SeekableLittleEndianAccessor; +import java.net.InetAddress; +import java.net.UnknownHostException; + public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler { + private static final Logger log = LoggerFactory.getLogger(ViewAllCharSelectedHandler.class); private static int parseAntiMulticlientError(AntiMulticlientResult res) { - switch (res) { - case REMOTE_PROCESSING: - return 10; - - case REMOTE_LOGGEDIN: - return 7; - - case REMOTE_NO_MATCH: - return 17; - - case COORDINATOR_ERROR: - return 8; - - default: - return 9; - } + return switch (res) { + case REMOTE_PROCESSING -> 10; + case REMOTE_LOGGEDIN -> 7; + case REMOTE_NO_MATCH -> 17; + case COORDINATOR_ERROR -> 8; + default -> 9; + }; } @Override @@ -61,23 +56,26 @@ public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler slea.readInt(); // please don't let the client choose which world they should login String macs = slea.readMapleAsciiString(); - String hwid = slea.readMapleAsciiString(); - - if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { + String hostString = slea.readMapleAsciiString(); + + final Hwid hwid; + try { + hwid = Hwid.fromHostString(hostString); + } catch (IllegalArgumentException e) { + log.warn("Invalid host string: {}", hostString, e); c.announce(MaplePacketCreator.getAfterLoginError(17)); return; } c.updateMacs(macs); - c.updateHWID(hwid); + c.updateHwid(hwid); if (c.hasBannedMac() || c.hasBannedHWID()) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); return; } - - IoSession session = c.getSession(); - AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid); + + AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid); if (res != AntiMulticlientResult.SUCCESS) { c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); return; @@ -85,7 +83,7 @@ public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler Server server = Server.getInstance(); if(!server.haveCharacterEntry(c.getAccID(), charId)) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); return; } @@ -105,7 +103,7 @@ public final class ViewAllCharSelectedHandler extends AbstractMaplePacketHandler c.setChannel(1); } - String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel()); + String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel()); if(socket == null) { c.announce(MaplePacketCreator.getAfterLoginError(10)); return; diff --git a/src/main/java/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java b/src/main/java/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java index 019ff9f8d5..2b3a33e14c 100644 --- a/src/main/java/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java +++ b/src/main/java/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java @@ -1,38 +1,32 @@ package net.server.handlers.login; +import client.MapleClient; +import net.AbstractMaplePacketHandler; +import net.server.Server; +import net.server.coordinator.session.Hwid; +import net.server.coordinator.session.SessionCoordinator; +import net.server.coordinator.session.SessionCoordinator.AntiMulticlientResult; +import net.server.world.World; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tools.MaplePacketCreator; +import tools.Randomizer; +import tools.data.input.SeekableLittleEndianAccessor; + import java.net.InetAddress; import java.net.UnknownHostException; -import net.AbstractMaplePacketHandler; -import net.server.Server; -import net.server.world.World; -import tools.MaplePacketCreator; -import tools.Randomizer; -import tools.data.input.SeekableLittleEndianAccessor; -import client.MapleClient; -import net.server.coordinator.session.MapleSessionCoordinator; -import net.server.coordinator.session.MapleSessionCoordinator.AntiMulticlientResult; -import org.apache.mina.core.session.IoSession; - public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandler { + private static final Logger log = LoggerFactory.getLogger(ViewAllCharSelectedWithPicHandler.class); private static int parseAntiMulticlientError(AntiMulticlientResult res) { - switch (res) { - case REMOTE_PROCESSING: - return 10; - - case REMOTE_LOGGEDIN: - return 7; - - case REMOTE_NO_MATCH: - return 17; - - case COORDINATOR_ERROR: - return 8; - - default: - return 9; - } + return switch (res) { + case REMOTE_PROCESSING -> 10; + case REMOTE_LOGGEDIN -> 7; + case REMOTE_NO_MATCH -> 17; + case COORDINATOR_ERROR -> 8; + default -> 9; + }; } @Override @@ -43,26 +37,28 @@ public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandle slea.readInt(); // please don't let the client choose which world they should login String macs = slea.readMapleAsciiString(); - String hwid = slea.readMapleAsciiString(); - - if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) { + String hostString = slea.readMapleAsciiString(); + + final Hwid hwid; + try { + hwid = Hwid.fromHostString(hostString); + } catch (IllegalArgumentException e) { + log.warn("Invalid host string: {}", hostString, e); c.announce(MaplePacketCreator.getAfterLoginError(17)); return; } c.updateMacs(macs); - c.updateHWID(hwid); + c.updateHwid(hwid); if (c.hasBannedMac() || c.hasBannedHWID()) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); return; } - - IoSession session = c.getSession(); - + Server server = Server.getInstance(); if(!server.haveCharacterEntry(c.getAccID(), charId)) { - MapleSessionCoordinator.getInstance().closeSession(c.getSession(), true); + SessionCoordinator.getInstance().closeSession(c, true); return; } @@ -77,13 +73,13 @@ public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandle c.setChannel(channel); if (c.checkPic(pic)) { - String[] socket = server.getInetSocket(session, c.getWorld(), c.getChannel()); + String[] socket = server.getInetSocket(c, c.getWorld(), c.getChannel()); if(socket == null) { c.announce(MaplePacketCreator.getAfterLoginError(10)); return; } - AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid); + AntiMulticlientResult res = SessionCoordinator.getInstance().attemptGameSession(c, c.getAccID(), hwid); if (res != AntiMulticlientResult.SUCCESS) { c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res))); return; diff --git a/src/main/java/net/server/task/LoginCoordinatorTask.java b/src/main/java/net/server/task/LoginCoordinatorTask.java index bf9109a40b..7f37434d62 100644 --- a/src/main/java/net/server/task/LoginCoordinatorTask.java +++ b/src/main/java/net/server/task/LoginCoordinatorTask.java @@ -19,7 +19,7 @@ */ package net.server.task; -import net.server.coordinator.session.MapleSessionCoordinator; +import net.server.coordinator.session.SessionCoordinator; /** * @@ -29,6 +29,6 @@ public class LoginCoordinatorTask implements Runnable { @Override public void run() { - MapleSessionCoordinator.getInstance().runUpdateHwidHistory(); + SessionCoordinator.getInstance().clearExpiredHwidHistory(); } } diff --git a/src/main/java/net/server/task/LoginStorageTask.java b/src/main/java/net/server/task/LoginStorageTask.java index a7b99eaa84..fdf60eeb82 100644 --- a/src/main/java/net/server/task/LoginStorageTask.java +++ b/src/main/java/net/server/task/LoginStorageTask.java @@ -19,7 +19,7 @@ */ package net.server.task; -import net.server.coordinator.session.MapleSessionCoordinator; +import net.server.coordinator.session.SessionCoordinator; import net.server.coordinator.login.MapleLoginBypassCoordinator; /** @@ -30,7 +30,7 @@ public class LoginStorageTask implements Runnable { @Override public void run() { - MapleSessionCoordinator.getInstance().runUpdateLoginHistory(); + SessionCoordinator.getInstance().runUpdateLoginHistory(); MapleLoginBypassCoordinator.getInstance().runUpdateLoginBypass(); } } diff --git a/src/main/java/server/life/MaplePlayerNPC.java b/src/main/java/server/life/MaplePlayerNPC.java index 5c801fccf7..8c5c8d27b5 100644 --- a/src/main/java/server/life/MaplePlayerNPC.java +++ b/src/main/java/server/life/MaplePlayerNPC.java @@ -588,7 +588,7 @@ public class MaplePlayerNPC extends AbstractMapleMapObject { World wserv = Server.getInstance().getWorld(world); if (wserv == null) return; - MapleClient c = new MapleClient(null, null, null); // mock client + MapleClient c = MapleClient.createMock(); c.setWorld(world); c.setChannel(1); diff --git a/src/main/java/server/maps/MapleMiniGame.java b/src/main/java/server/maps/MapleMiniGame.java index bd4b00a1bb..4f383da923 100644 --- a/src/main/java/server/maps/MapleMiniGame.java +++ b/src/main/java/server/maps/MapleMiniGame.java @@ -156,7 +156,7 @@ public class MapleMiniGame extends AbstractMapleMapObject { public void broadcastToOwner(final byte[] packet) { MapleClient c = owner.getClient(); - if (c != null && c.getSession() != null) { + if (c != null) { c.announce(packet); } } diff --git a/src/main/java/server/maps/MaplePlayerShop.java b/src/main/java/server/maps/MaplePlayerShop.java index 2550bdf114..b2648d9854 100644 --- a/src/main/java/server/maps/MaplePlayerShop.java +++ b/src/main/java/server/maps/MaplePlayerShop.java @@ -386,8 +386,9 @@ public class MaplePlayerShop extends AbstractMapleMapObject { } public void broadcast(final byte[] packet) { - if (owner.getClient() != null && owner.getClient().getSession() != null) { - owner.getClient().announce(packet); + MapleClient client = owner.getClient(); + if (client != null) { + client.announce(packet); } broadcastToVisitors(packet); } diff --git a/src/main/java/tools/HexTool.java b/src/main/java/tools/HexTool.java index e610055e95..f23e2ac5fc 100644 --- a/src/main/java/tools/HexTool.java +++ b/src/main/java/tools/HexTool.java @@ -22,6 +22,7 @@ package tools; import constants.string.CharsetConstants; +import io.netty.buffer.ByteBufUtil; import java.io.ByteArrayOutputStream; @@ -105,4 +106,14 @@ public class HexTool { return ""; } + /** + * Get upper case hex dump + */ + public static String bytesToHex(byte[] bytes) { + return ByteBufUtil.hexDump(bytes).toUpperCase(); + } + + public static byte[] hexToBytes(String hex) { + return ByteBufUtil.decodeHexDump(hex); + } } diff --git a/src/main/java/tools/MapleLogger.java b/src/main/java/tools/MapleLogger.java deleted file mode 100644 index 7f2f0b8a6e..0000000000 --- a/src/main/java/tools/MapleLogger.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 ~ 2010 Patrick Huy - Matthias Butz - Jan Christian Meyer - - 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 . - */ -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 monitored = new HashSet<>(); - public static Set 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; - } -} diff --git a/src/main/java/tools/MaplePacketCreator.java b/src/main/java/tools/MaplePacketCreator.java index e135021a86..c84994030b 100644 --- a/src/main/java/tools/MaplePacketCreator.java +++ b/src/main/java/tools/MaplePacketCreator.java @@ -36,6 +36,7 @@ import constants.inventory.ItemConstants; import constants.skills.Buccaneer; import constants.skills.Corsair; import constants.skills.ThunderBreaker; +import net.encryption.InitializationVector; import net.opcodes.SendOpcode; import net.server.PlayerCoolDownValueHolder; import net.server.Server; @@ -547,14 +548,14 @@ public class MaplePacketCreator { * @param recvIv the IV in use by the server for receiving * @return */ - public static byte[] getHello(short mapleVersion, byte[] sendIv, byte[] recvIv) { + public static byte[] getHello(short mapleVersion, InitializationVector sendIv, InitializationVector recvIv) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(8); mplew.writeShort(0x0E); mplew.writeShort(mapleVersion); mplew.writeShort(1); mplew.write(49); - mplew.write(recvIv); - mplew.write(sendIv); + mplew.write(recvIv.getBytes()); + mplew.write(sendIv.getBytes()); mplew.write(8); return mplew.getPacket(); } diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 286496cbf0..3b0f9d5966 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -2,29 +2,45 @@ cosmic-log + %d{HH:mm:ss.SSS} [%t] %-5level %logger{2} - %msg%n + %d{HH:mm:ss.SSS} %-15logger{1} - %msg%n - + + ${standard-pattern} + + - %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + ${standard-pattern} + + + + ${packet-pattern} + + - + + + + + + \ No newline at end of file diff --git a/src/test/java/tools/HexToolTest.java b/src/test/java/tools/HexToolTest.java new file mode 100644 index 0000000000..8ea59c403a --- /dev/null +++ b/src/test/java/tools/HexToolTest.java @@ -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)); + } +} \ No newline at end of file