Commands overhaul + Selective loot + Antimulticlient Coordinator

Completely overhauled commands layout, each command splitted in Java classes.
Optimized "ranks" command, no more calling the DB to get ranking info.
Implemented a mechanic where mobs only spawns loots that are visible/collectable by the player's party.
Implemented a server flag which sets whether explorers, cygnus and legends are allowed to share the cash shop inventory or not.
Implemented support for dynamic server rates at bootup. Rates can now be assigned at the configuration.ini file.
Devised the anti-multiclient login coordinator feature. Besides multiclient attempts by the same machine, it also prevents unauthorized login attempts into an account.
Fixed PQ instances being forcefully closed even when party leader reassignment upon logout is available.
Fixed mob statis not concurrently protected.
This commit is contained in:
ronancpl
2018-08-28 14:12:00 -03:00
parent b5c6831129
commit 132f286391
210 changed files with 10518 additions and 3851 deletions

View File

@@ -59,7 +59,10 @@ import net.server.guild.MapleGuild;
import net.server.guild.MapleGuildCharacter;
import net.server.worker.CharacterDiseaseWorker;
import net.server.worker.CouponWorker;
import net.server.worker.RankingWorker;
import net.server.worker.LoginCoordinatorWorker;
import net.server.worker.LoginStorageWorker;
import net.server.worker.RankingCommandWorker;
import net.server.worker.RankingLoginWorker;
import net.server.worker.ReleaseLockWorker;
import net.server.world.World;
@@ -90,6 +93,16 @@ import tools.FilePrinter;
import tools.Pair;
public class Server {
private static Server instance = null;
public static Server getInstance() {
if (instance == null) {
instance = new Server();
}
return instance;
}
private static final Set<Integer> activeFly = new HashSet<>();
private static final Map<Integer, Integer> couponRates = new HashMap<>(30);
private static final List<Integer> activeCoupons = new LinkedList<>();
@@ -98,7 +111,6 @@ public class Server {
private List<Map<Integer, String>> channels = new LinkedList<>();
private List<World> worlds = new ArrayList<>();
private final Properties subnetInfo = new Properties();
private static Server instance = null;
private final Map<Integer, Set<Integer>> accountChars = new HashMap<>();
private final Map<Integer, Short> accountCharacterCount = new HashMap<>();
private final Map<Integer, Integer> worldChars = new HashMap<>();
@@ -113,6 +125,8 @@ public class Server {
private final List<MapleClient> processDiseaseAnnouncePlayers = new LinkedList<>();
private final List<MapleClient> registeredDiseaseAnnouncePlayers = new LinkedList<>();
private final List<List<Pair<String, Integer>>> playerRanking = new LinkedList<>();
private final Lock srvLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.SERVER);
private final Lock disLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.SERVER_DISEASES);
@@ -131,13 +145,6 @@ public class Server {
private boolean online = false;
public static long uptime = System.currentTimeMillis();
public static Server getInstance() {
if (instance == null) {
instance = new Server();
}
return instance;
}
public long getCurrentTime() { // returns a slightly delayed time value, under frequency of UPDATE_INTERVAL
return serverCurrentTime;
}
@@ -343,8 +350,9 @@ public class Server {
int newWorld = initWorld(p);
if(newWorld > -1) {
Set<Integer> accounts;
installWorldPlayerRanking(newWorld);
Set<Integer> accounts;
lgnRLock.lock();
try {
accounts = new HashSet<>(accountChars.keySet());
@@ -360,6 +368,11 @@ public class Server {
return newWorld;
}
private static int getWorldProperty(Properties p, String property, int wid, int defaultValue) {
String content = p.getProperty(property + wid);
return content != null ? Integer.parseInt(content) : defaultValue;
}
private int initWorld(Properties p) {
wldWLock.lock();
try {
@@ -370,13 +383,16 @@ public class Server {
}
System.out.println("Starting world " + i);
int exprate = getWorldProperty(p, "exprate", i, ServerConstants.EXP_RATE);
int mesorate = getWorldProperty(p, "mesorate", i, ServerConstants.MESO_RATE);
int droprate = getWorldProperty(p, "droprate", i, ServerConstants.DROP_RATE);
int questrate = getWorldProperty(p, "questrate", i, ServerConstants.QUEST_RATE);
int travelrate = getWorldProperty(p, "travelrate", i, ServerConstants.TRAVEL_RATE);
World world = new World(i,
Integer.parseInt(p.getProperty("flag" + i)),
p.getProperty("eventmessage" + i),
ServerConstants.EXP_RATE,
ServerConstants.DROP_RATE,
ServerConstants.MESO_RATE,
ServerConstants.QUEST_RATE);
exprate, droprate, mesorate, questrate, travelrate);
worldRecommendedList.add(new Pair<>(i, p.getProperty("whyamirecommended" + i)));
worlds.add(world);
@@ -446,6 +462,7 @@ public class Server {
wldWLock.lock();
try {
if(worldid == worlds.size() - 1) {
removeWorldPlayerRanking();
w.shutdown();
worlds.remove(worldid);
@@ -627,6 +644,157 @@ public class Server {
}
}
public List<Pair<String, Integer>> getWorldPlayerRanking(int worldid) {
wldRLock.lock();
try {
return new ArrayList<>(playerRanking.get(!ServerConstants.USE_WHOLE_SERVER_RANKING ? worldid : 0));
} finally {
wldRLock.unlock();
}
}
private void installWorldPlayerRanking(int worldid) {
List<Pair<Integer, List<Pair<String, Integer>>>> ranking = updatePlayerRankingFromDB(worldid);
if(!ranking.isEmpty()) {
wldWLock.lock();
try {
if (!ServerConstants.USE_WHOLE_SERVER_RANKING) {
for(int i = playerRanking.size(); i <= worldid; i++) {
playerRanking.add(new ArrayList<Pair<String, Integer>>(0));
}
playerRanking.add(worldid, ranking.get(0).getRight());
} else {
playerRanking.add(0, ranking.get(0).getRight());
}
} finally {
wldWLock.unlock();
}
}
}
private void removeWorldPlayerRanking() {
if (!ServerConstants.USE_WHOLE_SERVER_RANKING) {
wldWLock.lock();
try {
if(playerRanking.size() < this.getWorldsSize()) {
return;
}
playerRanking.remove(playerRanking.size() - 1);
} finally {
wldWLock.unlock();
}
} else {
List<Pair<Integer, List<Pair<String, Integer>>>> ranking = updatePlayerRankingFromDB(-1 * (this.getWorldsSize() - 2)); // update ranking list
wldWLock.lock();
try {
playerRanking.add(0, ranking.get(0).getRight());
} finally {
wldWLock.unlock();
}
}
}
public void updateWorldPlayerRanking() {
List<Pair<Integer, List<Pair<String, Integer>>>> rankUpdates = updatePlayerRankingFromDB(-1 * (this.getWorldsSize() - 1));
if(!rankUpdates.isEmpty()) {
wldWLock.lock();
try {
if (!ServerConstants.USE_WHOLE_SERVER_RANKING) {
for(int i = playerRanking.size(); i <= rankUpdates.get(rankUpdates.size() - 1).getLeft(); i++) {
playerRanking.add(new ArrayList<Pair<String, Integer>>(0));
}
for(Pair<Integer, List<Pair<String, Integer>>> wranks : rankUpdates) {
playerRanking.set(wranks.getLeft(), wranks.getRight());
}
} else {
playerRanking.set(0, rankUpdates.get(0).getRight());
}
} finally {
wldWLock.unlock();
}
}
}
private void initWorldPlayerRanking() {
if (ServerConstants.USE_WHOLE_SERVER_RANKING) {
playerRanking.add(new ArrayList<Pair<String, Integer>>(0));
}
updateWorldPlayerRanking();
}
private static List<Pair<Integer, List<Pair<String, Integer>>>> updatePlayerRankingFromDB(int worldid) {
List<Pair<Integer, List<Pair<String, Integer>>>> rankSystem = new ArrayList<>();
List<Pair<String, Integer>> rankUpdate = new ArrayList<>(0);
PreparedStatement ps = null;
ResultSet rs = null;
Connection con = null;
try {
con = DatabaseConnection.getConnection();
String worldQuery;
if (!ServerConstants.USE_WHOLE_SERVER_RANKING) {
if(worldid >= 0) {
worldQuery = (" AND `characters`.`world` = " + worldid);
} else {
worldQuery = (" AND `characters`.`world` >= 0 AND `characters`.`world` <= " + -worldid);
}
} else {
worldQuery = (" AND `characters`.`world` >= 0 AND `characters`.`world` <= " + Math.abs(worldid));
}
ps = con.prepareStatement("SELECT `characters`.`name`, `characters`.`level`, `characters`.`world` FROM `characters` LEFT JOIN accounts ON accounts.id = characters.accountid WHERE `characters`.`gm` < 2 AND `accounts`.`banned` = '0'" + worldQuery + " ORDER BY " + (!ServerConstants.USE_WHOLE_SERVER_RANKING ? "world, " : "") + "level DESC, exp DESC LIMIT 50");
rs = ps.executeQuery();
if (!ServerConstants.USE_WHOLE_SERVER_RANKING) {
int currentWorld = -1;
while(rs.next()) {
int rsWorld = rs.getInt("world");
if(currentWorld < rsWorld) {
currentWorld = rsWorld;
rankUpdate = new ArrayList<>(50);
rankSystem.add(new Pair<>(rsWorld, rankUpdate));
}
rankUpdate.add(new Pair<>(rs.getString("name"), rs.getInt("level")));
}
} else {
rankUpdate = new ArrayList<>(50);
rankSystem.add(new Pair<>(0, rankUpdate));
while(rs.next()) {
rankUpdate.add(new Pair<>(rs.getString("name"), rs.getInt("level")));
}
}
ps.close();
rs.close();
con.close();
} catch(SQLException ex) {
ex.printStackTrace();
} finally {
try {
if(ps != null && !ps.isClosed()) {
ps.close();
}
if(rs != null && !rs.isClosed()) {
rs.close();
}
if(con != null && !con.isClosed()) {
con.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return rankSystem;
}
public void init() {
Properties p = loadWorldINI();
if(p == null) {
@@ -635,7 +803,7 @@ public class Server {
System.out.println("HeavenMS v" + ServerConstants.VERSION + " starting up.\r\n");
if(ServerConstants.SHUTDOWNHOOK)
Runtime.getRuntime().addShutdownHook(new Thread(shutdown(false)));
@@ -670,7 +838,10 @@ public class Server {
tMan.register(new CharacterDiseaseWorker(), ServerConstants.UPDATE_INTERVAL, ServerConstants.UPDATE_INTERVAL);
tMan.register(new ReleaseLockWorker(), 2 * 60 * 1000, 2 * 60 * 1000);
tMan.register(new CouponWorker(), ServerConstants.COUPON_INTERVAL, timeLeft);
tMan.register(new RankingWorker(), ServerConstants.RANKING_INTERVAL, timeLeft);
tMan.register(new RankingCommandWorker(), 5 * 60 * 1000, 5 * 60 * 1000);
tMan.register(new RankingLoginWorker(), ServerConstants.RANKING_INTERVAL, timeLeft);
tMan.register(new LoginCoordinatorWorker(), 60 * 60 * 1000, timeLeft);
tMan.register(new LoginStorageWorker(), 2 * 60 * 1000, 2 * 60 * 1000);
long timeToTake = System.currentTimeMillis();
SkillFactory.loadAllSkills();
@@ -696,6 +867,7 @@ public class Server {
for (int i = 0; i < worldCount; i++) {
initWorld(p);
}
initWorldPlayerRanking();
MaplePlayerNPCFactory.loadFactoryMetadata();
loadPlayerNpcMapStepFromDb();

View File

@@ -35,13 +35,13 @@ public class LockCollector {
private static final LockCollector instance = new LockCollector();
private Map<Runnable, Integer> disposableLocks = new HashMap<>(200);
private final Lock lock = new ReentrantLock(true);
public static LockCollector getInstance() {
return instance;
}
private Map<Runnable, Integer> disposableLocks = new HashMap<>(200);
private final Lock lock = new ReentrantLock(true);
public void registerDisposeAction(Runnable r) {
lock.lock();
try {

View File

@@ -48,6 +48,14 @@ import tools.FilePrinter;
*/
public class ThreadTracker {
private static ThreadTracker instance = null;
public static ThreadTracker getInstance() {
if (instance == null) {
instance = new ThreadTracker();
}
return instance;
}
private final Lock ttLock = new ReentrantLock(true);
private final Map<Long, List<MonitoredLockType>> threadTracker = new HashMap<>();
@@ -278,11 +286,4 @@ public class ThreadTracker {
public void cancelThreadTrackerTask() {
threadTrackerSchedule.cancel(false);
}
public static ThreadTracker getInstance() {
if (instance == null) {
instance = new ThreadTracker();
}
return instance;
}
}

View File

@@ -45,6 +45,7 @@ public enum MonitoredLockType {
SERVER,
SERVER_DISEASES,
SERVER_LOGIN,
SERVER_LOGIN_COORD,
SERVER_WORLDS,
MERCHANT,
CHANNEL,

View File

@@ -217,7 +217,7 @@ public final class Channel {
acceptor.unbind();
finishedShutdown = true;
System.out.println("Successfully shut down Channel " + channel + " on World " + world + "\r\n");
System.out.println("Successfully shut down Channel " + channel + " on World " + world + "\r\n");
} catch (Exception e) {
e.printStackTrace();
System.err.println("Error while shutting down Channel " + channel + " on World " + world + "\r\n" + e);

View File

@@ -24,23 +24,13 @@ package net.server.channel.handlers;
import client.MapleCharacter;
import client.MapleClient;
import client.autoban.AutobanFactory;
import client.command.Commands;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import client.command.CommandsExecutor;
import net.AbstractMaplePacketHandler;
import tools.FilePrinter;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
public final class GeneralChatHandler extends AbstractMaplePacketHandler {
private static boolean isCommandIssue(char heading, MapleCharacter chr) {
if(chr.gmLevel() > 1 && heading == '!') {
return true;
} else {
return heading == '@';
}
}
public final class GeneralChatHandler extends AbstractMaplePacketHandler {
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
String s = slea.readMapleAsciiString();
@@ -56,19 +46,8 @@ public final class GeneralChatHandler extends AbstractMaplePacketHandler {
return;
}
char heading = s.charAt(0);
if (isCommandIssue(heading, chr)) {
String[] sp = s.split(" ");
sp[0] = sp[0].toLowerCase().substring(1);
if(Commands.executeHeavenMsPlayerCommand(c, sp, heading)) {
String command = "";
for (String used : sp) {
command += used + " ";
}
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm");
FilePrinter.print(FilePrinter.USED_COMMANDS, c.getPlayer().getName() + " used: " + heading + command + "on " + sdf.format(Calendar.getInstance().getTime()) + "\r\n");
}
if (CommandsExecutor.isCommand(c, s)) {
CommandsExecutor.getInstance().handle(c, s);
} else if (heading != '/') {
int show = slea.readByte();
if(chr.getMap().isMuted() && !chr.isGM()) {
@@ -85,4 +64,4 @@ public final class GeneralChatHandler extends AbstractMaplePacketHandler {
chr.getAutobanManager().spam(7);
}
}
}
}

View File

@@ -55,41 +55,40 @@ public final class PetLootHandler extends AbstractMaplePacketHandler {
slea.skip(13);
int oid = slea.readInt();
MapleMapObject ob = chr.getMap().getMapObject(oid);
if(ob == null) {
MapleMapObject ob = chr.getMap().getMapObject(oid);
try {
MapleMapItem mapitem = (MapleMapItem) ob;
if (mapitem.getMeso() > 0) {
if (!chr.isEquippedMesoMagnet()) {
c.announce(MaplePacketCreator.enableActions());
return;
}
if (chr.isEquippedPetItemIgnore()) {
final Set<Integer> petIgnore = chr.getExcludedItems();
if(!petIgnore.isEmpty() && petIgnore.contains(Integer.MAX_VALUE)) {
c.announce(MaplePacketCreator.enableActions());
return;
}
}
} else {
if (!chr.isEquippedItemPouch()) {
c.announce(MaplePacketCreator.enableActions());
return;
}
if (chr.isEquippedPetItemIgnore()) {
final Set<Integer> petIgnore = chr.getExcludedItems();
if(!petIgnore.isEmpty() && petIgnore.contains(mapitem.getItem().getItemId())) {
c.announce(MaplePacketCreator.enableActions());
return;
}
}
}
chr.pickupItem(ob, petIndex);
} catch (NullPointerException | ClassCastException e) {
c.announce(MaplePacketCreator.enableActions());
return;
}
MapleMapItem mapitem = (MapleMapItem) ob;
if (mapitem.getMeso() > 0) {
if (!chr.isEquippedMesoMagnet()) {
c.announce(MaplePacketCreator.enableActions());
return;
}
if (chr.isEquippedPetItemIgnore()) {
final Set<Integer> petIgnore = chr.getExcludedItems();
if(!petIgnore.isEmpty() && petIgnore.contains(Integer.MAX_VALUE)) {
c.announce(MaplePacketCreator.enableActions());
return;
}
}
} else {
if (!chr.isEquippedItemPouch()) {
c.announce(MaplePacketCreator.enableActions());
return;
}
if (chr.isEquippedPetItemIgnore()) {
final Set<Integer> petIgnore = chr.getExcludedItems();
if(!petIgnore.isEmpty() && petIgnore.contains(mapitem.getItem().getItemId())) {
c.announce(MaplePacketCreator.enableActions());
return;
}
}
}
chr.pickupItem(ob, petIndex);
}
}

View File

@@ -0,0 +1,92 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.coordinator;
import constants.ServerConstants;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import net.server.Server;
/**
*
* @author Ronan
*/
public class LoginStorage {
ConcurrentHashMap<Integer, List<Long>> loginHistory = new ConcurrentHashMap<>();
public boolean registerLogin(int accountId) {
List<Long> accHist = loginHistory.putIfAbsent(accountId, new LinkedList<Long>());
if (accHist != null) {
synchronized (accHist) {
if (accHist.size() > ServerConstants.MAX_ACCOUNT_LOGIN_ATTEMPT) {
long blockExpiration = Server.getInstance().getCurrentTime() + ServerConstants.LOGIN_ATTEMPT_DURATION;
Collections.fill(accHist, blockExpiration);
return false;
}
}
} else {
accHist = loginHistory.get(accountId);
}
synchronized (accHist) {
accHist.add(Server.getInstance().getCurrentTime() + ServerConstants.LOGIN_ATTEMPT_DURATION);
return true;
}
}
public void updateLoginHistory() {
long timeNow = Server.getInstance().getCurrentTime();
List<Integer> toRemove = new LinkedList<>();
List<Long> toRemoveAttempt = new LinkedList<>();
for (Entry<Integer, List<Long>> loginEntries : loginHistory.entrySet()) {
toRemoveAttempt.clear();
List<Long> accAttempts = loginEntries.getValue();
synchronized (accAttempts) {
for (Long loginAttempt : accAttempts) {
if (loginAttempt < timeNow) {
toRemoveAttempt.add(loginAttempt);
}
}
if (!toRemoveAttempt.isEmpty()) {
for (Long trAttempt : toRemoveAttempt) {
accAttempts.remove(trAttempt);
}
if (accAttempts.isEmpty()) {
toRemove.add(loginEntries.getKey());
}
}
}
}
for (Integer tr : toRemove) {
loginHistory.remove(tr);
}
}
}

View File

@@ -0,0 +1,339 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.coordinator;
import constants.ServerConstants;
import net.server.Server;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import org.apache.mina.core.session.IoSession;
import tools.DatabaseConnection;
import java.net.InetSocketAddress;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* @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,
MANY_ACCOUNT_ATTEMPTS,
COORDINATOR_ERROR
}
private final LoginStorage loginStorage = new LoginStorage();
private final Set<String> onlineRemoteHosts = new HashSet<>();
private final Map<String, Set<IoSession>> loginRemoteHosts = new HashMap<>();
private final Set<String> pooledRemoteHosts = new HashSet<>();
private final List<ReentrantLock> poolLock = new ArrayList<>(100);
private MapleSessionCoordinator() {
for(int i = 0; i < 100; i++) {
poolLock.add(MonitoredReentrantLockFactory.createLock(MonitoredLockType.SERVER_LOGIN_COORD));
}
}
private static long ipExpirationUpdate(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 remoteHost, int accountId, int loginRelevance) throws SQLException {
java.sql.Timestamp nextTimestamp = new java.sql.Timestamp(Server.getInstance().getCurrentTime() + ipExpirationUpdate(loginRelevance));
if(loginRelevance < Byte.MAX_VALUE) {
loginRelevance++;
}
try (PreparedStatement ps = con.prepareStatement("UPDATE ipaccounts SET relevance = ?, expiresat = ? WHERE accountid = ? AND ip LIKE ?")) {
ps.setInt(1, loginRelevance);
ps.setTimestamp(2, nextTimestamp);
ps.setInt(3, accountId);
ps.setString(4, remoteHost);
ps.executeUpdate();
}
}
private static void registerAccessAccount(Connection con, String remoteHost, int accountId) throws SQLException {
try (PreparedStatement ps = con.prepareStatement("INSERT INTO ipaccounts (accountid, ip, expiresat) VALUES (?, ?, ?)")) {
ps.setInt(1, accountId);
ps.setString(2, remoteHost);
ps.setTimestamp(3, new java.sql.Timestamp(Server.getInstance().getCurrentTime() + ipExpirationUpdate(0)));
ps.executeUpdate();
}
}
private static boolean attemptAccessAccount(String remoteHost, int accountId, boolean routineCheck) {
try {
Connection con = DatabaseConnection.getConnection();
int ipCount = 0;
try (PreparedStatement ps = con.prepareStatement("SELECT SQL_CACHE * FROM ipaccounts WHERE accountid = ?")) {
ps.setInt(1, accountId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
if (remoteHost.contentEquals(rs.getString("ip"))) {
if (!routineCheck) {
int loginRelevance = rs.getInt("relevance");
updateAccessAccount(con, remoteHost, accountId, loginRelevance);
}
return true;
}
ipCount++;
}
}
if (ipCount < ServerConstants.MAX_ALLOWED_ACCOUNT_IP) {
if (!routineCheck) {
registerAccessAccount(con, remoteHost, accountId);
}
return true;
}
} finally {
con.close();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
return false;
}
private Lock getCoodinatorLock(String remoteHost) {
return poolLock.get(remoteHost.hashCode() % 100);
}
private static String getRemoteIp(IoSession session) {
return ((InetSocketAddress) session.getRemoteAddress()).getAddress().getHostAddress();
}
public boolean canStartLoginSession(IoSession session) {
if (!ServerConstants.DETERRED_MULTICLIENT) return true;
String remoteHost = getRemoteIp(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 {
if (onlineRemoteHosts.contains(remoteHost) || loginRemoteHosts.containsKey(remoteHost)) {
return false;
}
Set<IoSession> lrh = new HashSet<>(2);
lrh.add(session);
loginRemoteHosts.put(remoteHost, lrh);
return true;
} finally {
lock.lock();
try {
pooledRemoteHosts.remove(remoteHost);
} finally {
lock.unlock();
}
}
}
public void closeLoginSession(IoSession session) {
String remoteIp = getRemoteIp(session);
Set<IoSession> lrh = loginRemoteHosts.get(remoteIp);
if (lrh != null) {
lrh.remove(session);
if (lrh.isEmpty()) {
loginRemoteHosts.remove(remoteIp);
}
}
}
public AntiMulticlientResult attemptSessionLogin(IoSession session, int accountId, boolean routineCheck) {
if (!ServerConstants.DETERRED_MULTICLIENT) return AntiMulticlientResult.SUCCESS;
String remoteHost = getRemoteIp(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 (onlineRemoteHosts.contains(remoteHost)) {
return AntiMulticlientResult.REMOTE_LOGGEDIN;
}
if (!attemptAccessAccount(remoteHost, accountId, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
}
onlineRemoteHosts.add(remoteHost);
} else {
if (!attemptAccessAccount(remoteHost, accountId, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
}
}
return AntiMulticlientResult.SUCCESS;
} finally {
lock.lock();
try {
pooledRemoteHosts.remove(remoteHost);
} finally {
lock.unlock();
}
}
}
public void closeSession(IoSession session, Boolean immediately) {
onlineRemoteHosts.remove(getRemoteIp(session));
if (immediately != null) session.close(immediately);
}
public void runUpdateIpHistory() {
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("DELETE FROM ipaccounts WHERE expiresat < CURRENT_TIMESTAMP")) {
ps.execute();
} finally {
con.close();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
public void runUpdateLoginHistory() {
loginStorage.updateLoginHistory();
}
}

View File

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

View File

@@ -23,6 +23,7 @@ package net.server.handlers.login;
import client.MapleClient;
import net.AbstractMaplePacketHandler;
import net.server.coordinator.MapleSessionCoordinator;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
@@ -34,12 +35,15 @@ 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);
c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
} else {
String pin = slea.readMapleAsciiString();
if (pin != null) {
c.setPin(pin);
c.announce(MaplePacketCreator.pinRegistered());
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null);
c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
}
}

View File

@@ -25,6 +25,7 @@ package net.server.handlers.login;
import client.MapleClient;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.coordinator.MapleSessionCoordinator;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
@@ -35,13 +36,17 @@ import tools.data.input.SeekableLittleEndianAccessor;
public class SetGenderHandler extends AbstractMaplePacketHandler {
@Override
public void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
byte type = slea.readByte(); //?
if (type == 0x01 && c.getGender() == 10) { //Packet shouldn't come if Gender isn't 10.
c.setGender(slea.readByte());
c.announce(MaplePacketCreator.getAuthSuccess(c));
final MapleClient client = c;
Server.getInstance().registerLoginState(c);
if (c.getGender() == 10) { //Packet shouldn't come if Gender isn't 10.
byte confirmed = slea.readByte();
if (confirmed == 0x01) {
c.setGender(slea.readByte());
c.announce(MaplePacketCreator.getAuthSuccess(c));
Server.getInstance().registerLoginState(c);
} else {
MapleSessionCoordinator.getInstance().closeSession(c.getSession(), null);
c.updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN);
}
}
}

View File

@@ -0,0 +1,34 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.worker;
import net.server.coordinator.MapleSessionCoordinator;
/**
*
* @author Ronan
*/
public class LoginCoordinatorWorker implements Runnable {
@Override
public void run() {
MapleSessionCoordinator.getInstance().runUpdateIpHistory();
}
}

View File

@@ -0,0 +1,34 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.worker;
import net.server.coordinator.MapleSessionCoordinator;
/**
*
* @author Ronan
*/
public class LoginStorageWorker implements Runnable {
@Override
public void run() {
MapleSessionCoordinator.getInstance().runUpdateLoginHistory();
}
}

View File

@@ -0,0 +1,34 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.worker;
import net.server.Server;
/**
*
* @author Ronan
*/
public class RankingCommandWorker implements Runnable {
@Override
public void run() {
Server.getInstance().updateWorldPlayerRanking();
}
}

View File

@@ -35,7 +35,7 @@ import net.server.Server;
* @author Quit
* @author Ronan
*/
public class RankingWorker implements Runnable {
public class RankingLoginWorker implements Runnable {
private Connection con;
private long lastUpdate = System.currentTimeMillis();

View File

@@ -92,7 +92,7 @@ import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
*/
public class World {
private int id, flag, exprate, droprate, mesorate, questrate;
private int id, flag, exprate, droprate, mesorate, questrate, travelrate;
private String eventmsg;
private List<Channel> channels = new ArrayList<>();
private Map<Integer, Byte> pnpcStep = new HashMap<>();
@@ -151,7 +151,7 @@ public class World {
private ScheduledFuture<?> charactersSchedule;
private ScheduledFuture<?> marriagesSchedule;
public World(int world, int flag, String eventmsg, int exprate, int droprate, int mesorate, int questrate) {
public World(int world, int flag, String eventmsg, int exprate, int droprate, int mesorate, int questrate, int travelrate) {
this.id = world;
this.flag = flag;
this.eventmsg = eventmsg;
@@ -159,6 +159,7 @@ public class World {
this.droprate = droprate;
this.mesorate = mesorate;
this.questrate = questrate;
this.travelrate = travelrate;
runningPartyId.set(1);
runningMessengerId.set(1);
@@ -336,7 +337,19 @@ public class World {
public void setQuestRate(int quest) {
this.questrate = quest;
}
public int getTravelRate() {
return travelrate;
}
public void setTravelRate(int quest) {
this.travelrate = quest;
}
public int getTransportationTime(int travelTime) {
return (int) Math.ceil(travelTime / travelrate);
}
public void loadAccountCharactersView(Integer accountId, List<MapleCharacter> chars) {
SortedMap<Integer, MapleCharacter> charsMap = new TreeMap<>();
for(MapleCharacter chr : chars) {
@@ -1825,5 +1838,6 @@ public class World {
players = null;
clearWorldData();
System.out.println("Finished shutting down world " + id + "\r\n");
}
}