Event Recall + Cash Shop bestsellers + MapleSessionCoordinator rework

Implemented an event recall system. Players that went disconnected during an event instance are able to rejoin the ongoing event upon relogin.
Implemented a player-activity backed best-sellers system for the Cash Shop.
Patched the recently added selective loot system interfering with quest items, ever disabling drops after the player picked up one item.
Implemented a server flag for everlasting buffs.
Fixed some inconsistencies with Priest Dispel skill, sometimes crashing party players.
Fixed change job not properly showing effects for other players.
Fixed wrong fee value being taken from players that expands their guild size. Also, implemented GMS-like fee for this action.
Reworked the MapleSessionCoordinator, now evaluating client's HWID as well as remote IP. This's expected to lessen account drought time for players that are constantly changing their IP.

Last but not least, added world maps for Mushroom Castle, Zipangu, CBD/Malaysia and Ellin Forest regions. Original artwork content used on files depicted in this topic are rightful property of Nexon Corps., these files thoroughly trying to adhere the "Fair Use" disclaimer policy, their purpose being solely to fulfill gaming experience for the areas that were already present on v83 GMS but still lacked worldmaps. For more info regarding Fair Use, please refer to "http://www.dmlp.org/legal-guide/fair-use".
This commit is contained in:
ronancpl
2018-09-07 14:55:29 -03:00
parent 132f286391
commit c3e3c6dfbb
83 changed files with 2718 additions and 626 deletions

View File

@@ -59,6 +59,7 @@ import net.server.guild.MapleGuild;
import net.server.guild.MapleGuildCharacter;
import net.server.worker.CharacterDiseaseWorker;
import net.server.worker.CouponWorker;
import net.server.worker.EventRecallCoordinatorWorker;
import net.server.worker.LoginCoordinatorWorker;
import net.server.worker.LoginStorageWorker;
import net.server.worker.RankingCommandWorker;
@@ -841,6 +842,7 @@ public class Server {
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 EventRecallCoordinatorWorker(), 60 * 60 * 1000, timeLeft);
tMan.register(new LoginStorageWorker(), 2 * 60 * 1000, 2 * 60 * 1000);
long timeToTake = System.currentTimeMillis();

View File

@@ -70,6 +70,7 @@ public enum MonitoredLockType {
WORLD_PSHOPS,
WORLD_MERCHS,
WORLD_MAPOBJS,
WORLD_SUGGEST,
EIM,
EIM_PARTY,
EIM_SCRIPT,

View File

@@ -53,14 +53,14 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.enableActions());
return;
}
final int action = slea.readByte();
final int action = slea.readByte();
if (action == 0x03 || action == 0x1E) {
slea.readByte();
final int useNX = slea.readInt();
final int snCS = slea.readInt();
CashItem cItem = CashItemFactory.getItem(snCS);
if (cItem == null || !cItem.isOnSale() || cs.getCash(useNX) < cItem.getPrice()) {
if (!canBuy(cItem, cs.getCash(useNX))) {
FilePrinter.printError(FilePrinter.ITEM, "Denied to sell cash item with SN " + cItem.getSN());
c.announce(MaplePacketCreator.enableActions());
return;
@@ -87,7 +87,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
}
c.announce(MaplePacketCreator.showBoughtCashPackage(cashPackage, c.getAccID()));
}
cs.gainCash(useNX, -cItem.getPrice());
cs.gainCash(useNX, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
} else if (action == 0x04) {//TODO check for gender
int birthday = slea.readInt();
@@ -113,7 +113,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
}
cs.gift(Integer.parseInt(recipient.get("id")), chr.getName(), message, cItem.getSN());
c.announce(MaplePacketCreator.showGiftSucceed(recipient.get("name"), cItem));
cs.gainCash(4, -cItem.getPrice());
cs.gainCash(4, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
try {
chr.sendNote(recipient.get("name"), chr.getName() + " has sent you a gift! Go check out the Cash Shop.", (byte) 0); //fame or not
@@ -156,7 +156,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
}
if (chr.gainSlots(type, 8, false)) {
c.announce(MaplePacketCreator.showBoughtInventorySlots(type, chr.getSlots(type)));
cs.gainCash(cash, -cItem.getPrice());
cs.gainCash(cash, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
}
}
@@ -186,7 +186,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
}
if (chr.getStorage().gainSlots(8)) {
c.announce(MaplePacketCreator.showBoughtStorageSlots(chr.getStorage().getSlots()));
cs.gainCash(cash, -cItem.getPrice());
cs.gainCash(cash, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
}
}
@@ -202,7 +202,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
if (c.gainCharacterSlot()) {
c.announce(MaplePacketCreator.showBoughtCharacterSlot(c.getCharacterSlots()));
cs.gainCash(cash, -cItem.getPrice());
cs.gainCash(cash, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
} else {
chr.dropMessage(1, "You have already used up all 12 extra character slots.");
@@ -276,7 +276,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
cs.addToInventory(eqp);
c.announce(MaplePacketCreator.showBoughtCashItem(eqp, c.getAccID()));
cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), (ringid + 1));
cs.gainCash(toCharge, -itemRing.getPrice());
cs.gainCash(toCharge, itemRing, chr.getWorld());
chr.addCrushRing(MapleRing.loadFromDb(ringid));
try {
chr.sendNote(partner.getName(), text, (byte) 1);
@@ -357,7 +357,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
return c.checkBirthDate(cal);
}
public static boolean canBuy(CashItem item, int cash) {
private static boolean canBuy(CashItem item, int cash) {
return item != null && item.isOnSale() && item.getPrice() <= cash;
}
}

View File

@@ -42,24 +42,23 @@ public final class GiveFameHandler extends AbstractMaplePacketHandler {
if (target == null || target.getId() == player.getId() || player.getLevel() < 15) {
return;
} else if (famechange != 1 && famechange != -1) {
AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit fame.");
FilePrinter.printError(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to fame hack with famechange " + famechange + "\r\n");
c.disconnect(true, false);
return;
AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit fame.");
FilePrinter.printError(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to fame hack with famechange " + famechange + "\r\n");
c.disconnect(true, false);
return;
}
FameStatus status = player.canGiveFame(target);
if (status == FameStatus.OK || player.isGM()){
if (Math.abs(target.getFame() + famechange) < 30001) {
target.addFame(famechange);
target.updateSingleStat(MapleStat.FAME, target.getFame());
}
if (!player.isGM()) {
player.hasGivenFame(target);
}
c.announce(MaplePacketCreator.giveFameResponse(mode, target.getName(), target.getFame()));
target.getClient().announce(MaplePacketCreator.receiveFame(mode, player.getName()));
if (status == FameStatus.OK) {
if (target.gainFame(famechange, player, mode)) {
if (!player.isGM()) {
player.hasGivenFame(target);
}
} else {
player.message("Could not process the request, since this character currently has the minimum/maximum level of fame.");
}
} else {
c.announce(MaplePacketCreator.giveFameErrorResponse(status == FameStatus.NOT_TODAY ? 3 : 4));
c.announce(MaplePacketCreator.giveFameErrorResponse(status == FameStatus.NOT_TODAY ? 3 : 4));
}
}
}

View File

@@ -77,7 +77,6 @@ public final class NoteActionHandler extends AbstractMaplePacketHandler {
}
if (fame > 0) {
c.getPlayer().gainFame(fame);
c.announce(MaplePacketCreator.getShowFameGain(fame));
}
}
}

View File

@@ -62,7 +62,11 @@ import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import net.server.coordinator.MapleEventRecallCoordinator;
import net.server.coordinator.MapleSessionCoordinator;
import org.apache.mina.core.session.IoSession;
import server.life.MobSkill;
import scripting.event.EventInstanceManager;
import tools.packets.Wedding;
public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
@@ -79,11 +83,23 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
MapleCharacter player = c.getWorldServer().getPlayerStorage().getCharacterById(cid);
boolean newcomer = false;
if (player == null) {
if(!server.validateCharacteridInTransition((InetSocketAddress) c.getSession().getRemoteAddress(), cid)) {
IoSession session = c.getSession();
if (!server.validateCharacteridInTransition((InetSocketAddress) session.getRemoteAddress(), cid)) {
c.disconnect(true, false);
return;
}
if (ServerConstants.DETERRED_MULTICLIENT) {
String remoteHwid = MapleSessionCoordinator.getInstance().getGameSessionHwid(session);
if (remoteHwid == null) {
c.disconnect(true, false);
return;
}
session.setAttribute(MapleClient.CLIENT_HWID, remoteHwid);
}
try {
player = MapleCharacter.loadCharFromDB(cid, c, true);
newcomer = true;
@@ -263,7 +279,7 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.requestBuddylistAdd(pendingBuddyRequest.getId(), c.getPlayer().getId(), pendingBuddyRequest.getName()));
}
if(newcomer) {
if (newcomer) {
for(MaplePet pet : player.getPets()) {
if(pet != null)
world.registerPetHunger(player, player.getPetIndex(pet));
@@ -332,6 +348,13 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(player.getId(), player.getMapId()));
}
}
if (newcomer) {
EventInstanceManager eim = MapleEventRecallCoordinator.getInstance().recallEventInstance(cid);
if (eim != null) {
eim.registerPlayer(player);
}
}
}
private static void showDueyNotification(MapleClient c, MapleCharacter player) {

View File

@@ -117,8 +117,8 @@ public final class SpecialMoveHandler extends AbstractMaplePacketHandler {
int gain = lose * (ef.getY() / 100);
chr.setMp(chr.getMp() + gain);
chr.updateSingleStat(MapleStat.MP, chr.getMp());
} else if (skillid == Priest.DISPEL || skillid == SuperGM.HEAL_PLUS_DISPEL) {
slea.skip((skillid == Priest.DISPEL) ? 10 : 11);
} else if (skillid == SuperGM.HEAL_PLUS_DISPEL) {
slea.skip(11);
chr.getMap().broadcastMessage(chr, MaplePacketCreator.showBuffeffect(chr.getId(), skillid, chr.getSkillLevel(skillid)), false);
} else if (skillid % 10000000 == 1004) {
slea.readShort();

View File

@@ -352,7 +352,7 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler {
} else if (itemType == 523) {
int itemid = slea.readInt();
if(!ServerConstants.USE_ENFORCE_OWL_SUGGESTIONS) c.getWorldServer().addOwlItemSearch(itemid);
if(!ServerConstants.USE_ENFORCE_ITEM_SUGGESTION) c.getWorldServer().addOwlItemSearch(itemid);
player.setOwlSearch(itemid);
List<Pair<MaplePlayerShopItem, AbstractMapleMapObject>> hmsAvailable = c.getWorldServer().getAvailableItemBundles(itemid);
if(!hmsAvailable.isEmpty()) remove(c, itemId);

View File

@@ -56,7 +56,7 @@ public final class UseOwlOfMinervaHandler extends AbstractMaplePacketHandler {
}
};
PriorityQueue<Pair<Integer, Integer>> queue = new PriorityQueue<>(10, comparator);
PriorityQueue<Pair<Integer, Integer>> queue = new PriorityQueue<>(Math.max(1, owlSearched.size()), comparator);
for(Pair<Integer, Integer> p : owlSearched) {
queue.add(p);
}

View File

@@ -34,7 +34,7 @@ import net.server.Server;
*/
public class LoginStorage {
ConcurrentHashMap<Integer, List<Long>> loginHistory = new ConcurrentHashMap<>();
private ConcurrentHashMap<Integer, List<Long>> loginHistory = new ConcurrentHashMap<>();
public boolean registerLogin(int accountId) {
List<Long> accHist = loginHistory.putIfAbsent(accountId, new LinkedList<Long>());

View File

@@ -0,0 +1,73 @@
/*
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 scripting.event.EventInstanceManager;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* @author Ronan
*/
public class MapleEventRecallCoordinator {
private final static MapleEventRecallCoordinator instance = new MapleEventRecallCoordinator();
public static MapleEventRecallCoordinator getInstance() {
return instance;
}
private ConcurrentHashMap<Integer, EventInstanceManager> eventHistory = new ConcurrentHashMap<>();
private static boolean isRecallableEvent(EventInstanceManager eim) {
return eim != null && !eim.isEventDisposed() && !eim.isEventCleared();
}
public EventInstanceManager recallEventInstance(int characterId) {
EventInstanceManager eim = eventHistory.remove(characterId);
return isRecallableEvent(eim) ? eim : null;
}
public void storeEventInstance(int characterId, EventInstanceManager eim) {
if (ServerConstants.USE_ENABLE_RECALL_EVENT && isRecallableEvent(eim)) {
eventHistory.put(characterId, eim);
}
}
public void manageEventInstances() {
if (!eventHistory.isEmpty()) {
List<Integer> toRemove = new LinkedList<>();
for (Entry<Integer, EventInstanceManager> eh : eventHistory.entrySet()) {
if (!isRecallableEvent(eh.getValue())) {
toRemove.add(eh.getKey());
}
}
for (Integer r : toRemove) {
eventHistory.remove(r);
}
}
}
}

View File

@@ -19,6 +19,7 @@
*/
package net.server.coordinator;
import client.MapleClient;
import constants.ServerConstants;
import net.server.Server;
@@ -33,11 +34,14 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -58,14 +62,18 @@ public class MapleSessionCoordinator {
REMOTE_LOGGEDIN,
REMOTE_REACHED_LIMIT,
REMOTE_PROCESSING,
REMOTE_NO_MATCH,
MANY_ACCOUNT_ATTEMPTS,
COORDINATOR_ERROR
}
private final LoginStorage loginStorage = new LoginStorage();
private final Set<String> onlineRemoteHosts = new HashSet<>();
private final Set<String> onlineRemoteHwids = new HashSet<>();
private final Map<String, Set<IoSession>> loginRemoteHosts = new HashMap<>();
private final Set<String> pooledRemoteHosts = new HashSet<>();
private final ConcurrentHashMap<String, String> cachedHostHwids = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Long> cachedHostTimeout = new ConcurrentHashMap<>();
private final List<ReentrantLock> poolLock = new ArrayList<>(100);
private MapleSessionCoordinator() {
@@ -74,7 +82,7 @@ public class MapleSessionCoordinator {
}
}
private static long ipExpirationUpdate(int relevance) {
private static long hwidExpirationUpdate(int relevance) {
int degree = 1, i = relevance, subdegree;
while ((subdegree = 5 * degree) <= i) {
i -= subdegree;
@@ -109,59 +117,90 @@ public class MapleSessionCoordinator {
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));
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 ipaccounts SET relevance = ?, expiresat = ? WHERE accountid = ? AND ip LIKE ?")) {
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, remoteHost);
ps.setString(4, remoteHwid);
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 (?, ?, ?)")) {
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, remoteHost);
ps.setTimestamp(3, new java.sql.Timestamp(Server.getInstance().getCurrentTime() + ipExpirationUpdate(0)));
ps.setString(2, remoteHwid);
ps.setTimestamp(3, new java.sql.Timestamp(Server.getInstance().getCurrentTime() + hwidExpirationUpdate(0)));
ps.executeUpdate();
}
}
private static boolean attemptAccessAccount(String remoteHost, int accountId, boolean routineCheck) {
private static boolean associateHwidAccountIfAbsent(String remoteHwid, int accountId) {
try {
Connection con = DatabaseConnection.getConnection();
int ipCount = 0;
int hwidCount = 0;
try (PreparedStatement ps = con.prepareStatement("SELECT SQL_CACHE * FROM ipaccounts WHERE accountid = ?")) {
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()) {
if (remoteHost.contentEquals(rs.getString("ip"))) {
String rsHwid = rs.getString("hwid");
if (rsHwid.contentEquals(remoteHwid)) {
return false;
}
hwidCount++;
}
}
if (hwidCount < ServerConstants.MAX_ALLOWED_ACCOUNT_HWID) {
registerAccessAccount(con, remoteHwid, accountId);
return true;
}
} finally {
con.close();
}
} 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, remoteHost, accountId, loginRelevance);
updateAccessAccount(con, rsHwid, accountId, loginRelevance);
}
return true;
}
ipCount++;
hwidCount++;
}
}
if (ipCount < ServerConstants.MAX_ALLOWED_ACCOUNT_IP) {
if (!routineCheck) {
registerAccessAccount(con, remoteHost, accountId);
}
if (hwidCount < ServerConstants.MAX_ALLOWED_ACCOUNT_HWID) {
return true;
}
} finally {
@@ -218,7 +257,14 @@ public class MapleSessionCoordinator {
}
try {
if (onlineRemoteHosts.contains(remoteHost) || loginRemoteHosts.containsKey(remoteHost)) {
String knownHwid = cachedHostHwids.get(remoteHost);
if (knownHwid != null) {
if (onlineRemoteHwids.contains(knownHwid)) {
return false;
}
}
if (loginRemoteHosts.containsKey(remoteHost)) {
return false;
}
@@ -244,11 +290,16 @@ public class MapleSessionCoordinator {
lrh.remove(session);
if (lrh.isEmpty()) {
loginRemoteHosts.remove(remoteIp);
String nibbleHwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID);
if (nibbleHwid != null) {
onlineRemoteHwids.remove(nibbleHwid);
}
}
}
}
public AntiMulticlientResult attemptSessionLogin(IoSession session, int accountId, boolean routineCheck) {
public AntiMulticlientResult attemptLoginSession(IoSession session, String nibbleHwid, int accountId, boolean routineCheck) {
if (!ServerConstants.DETERRED_MULTICLIENT) return AntiMulticlientResult.SUCCESS;
String remoteHost = getRemoteIp(session);
@@ -289,17 +340,18 @@ public class MapleSessionCoordinator {
}
if (!routineCheck) {
if (onlineRemoteHosts.contains(remoteHost)) {
if (onlineRemoteHwids.contains(nibbleHwid)) {
return AntiMulticlientResult.REMOTE_LOGGEDIN;
}
if (!attemptAccessAccount(remoteHost, accountId, routineCheck)) {
if (!attemptAccessAccount(nibbleHwid, accountId, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
}
onlineRemoteHosts.add(remoteHost);
session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid);
onlineRemoteHwids.add(nibbleHwid);
} else {
if (!attemptAccessAccount(remoteHost, accountId, routineCheck)) {
if (!attemptAccessAccount(nibbleHwid, accountId, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
}
}
@@ -315,15 +367,99 @@ public class MapleSessionCoordinator {
}
}
public void closeSession(IoSession session, Boolean immediately) {
onlineRemoteHosts.remove(getRemoteIp(session));
if (immediately != null) session.close(immediately);
public AntiMulticlientResult attemptGameSession(IoSession session, int accountId, String remoteHwid) {
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 {
String nibbleHwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID);
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);
cachedHostHwids.put(remoteHost, remoteHwid);
cachedHostTimeout.put(remoteHost, Server.getInstance().getCurrentTime() + 604800000); // 1 week-time entry
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();
}
}
}
public void runUpdateIpHistory() {
public void closeSession(IoSession session, Boolean immediately) {
String hwid = (String) session.removeAttribute(MapleClient.CLIENT_HWID);
onlineRemoteHwids.remove(hwid);
hwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID); // making sure to clean up calls to this function on login phase
onlineRemoteHwids.remove(hwid);
if (immediately != null) {
session.close(immediately);
}
}
public String getGameSessionHwid(IoSession session) {
String remoteHost = getRemoteIp(session);
return cachedHostHwids.get(remoteHost);
}
public void runUpdateHwidHistory() {
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("DELETE FROM ipaccounts WHERE expiresat < CURRENT_TIMESTAMP")) {
try (PreparedStatement ps = con.prepareStatement("DELETE FROM hwidaccounts WHERE expiresat < CURRENT_TIMESTAMP")) {
ps.execute();
} finally {
con.close();
@@ -331,6 +467,21 @@ public class MapleSessionCoordinator {
} catch (SQLException ex) {
ex.printStackTrace();
}
long timeNow = Server.getInstance().getCurrentTime();
List<String> toRemove = new LinkedList<>();
for (Entry<String, Long> cht : cachedHostTimeout.entrySet()) {
if (cht.getValue() < timeNow) {
toRemove.add(cht.getKey());
}
}
if (!toRemove.isEmpty()) {
for (String s : toRemove) {
cachedHostHwids.remove(s);
cachedHostTimeout.remove(s);
}
}
}
public void runUpdateLoginHistory() {

View File

@@ -23,6 +23,7 @@ package net.server.guild;
import client.MapleCharacter;
import client.MapleClient;
import constants.ServerConstants;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -752,6 +753,12 @@ public class MapleGuild {
}
public static int getIncreaseGuildCost(int size) {
return 500000 * (size - 6) / 6;
int cost = ServerConstants.EXPAND_GUILD_BASE_COST + Math.max(0, (size - 15) / 5) * ServerConstants.EXPAND_GUILD_TIER_COST;
if (size > 30) {
return Math.min(ServerConstants.EXPAND_GUILD_MAX_COST, Math.max(cost, 5000000));
} else {
return cost;
}
}
}

View File

@@ -27,28 +27,64 @@ import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.coordinator.MapleSessionCoordinator;
import net.server.coordinator.MapleSessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
import org.apache.mina.core.session.IoSession;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
public final class CharSelectedHandler extends AbstractMaplePacketHandler {
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;
}
}
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
int charId = slea.readInt();
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
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);
if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return;
}
if (c.hasBannedMac() || c.hasBannedHWID()) {
c.getSession().close(true);
session.close(true);
return;
}
Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) {
c.getSession().close(true);
session.close(true);
return;
}
@@ -67,7 +103,7 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler {
server.unregisterLoginState(c);
c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION);
server.setCharacteridInTransition((InetSocketAddress) c.getSession().getRemoteAddress(), charId);
server.setCharacteridInTransition((InetSocketAddress) session.getRemoteAddress(), charId);
try {
c.announce(MaplePacketCreator.getServerIP(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]), charId));

View File

@@ -6,13 +6,35 @@ import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
import net.server.coordinator.MapleSessionCoordinator;
import net.server.coordinator.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 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;
}
}
@Override
public void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
String pic = slea.readMapleAsciiString();
@@ -20,8 +42,22 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
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);
if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return;
}
if (c.hasBannedMac() || c.hasBannedHWID()) {
c.getSession().close(true);
return;

View File

@@ -31,6 +31,7 @@ import net.MaplePacketHandler;
import net.server.Server;
import tools.BCrypt;
import tools.DatabaseConnection;
import tools.HexTool;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
import client.MapleClient;
@@ -51,8 +52,10 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
String login = slea.readMapleAsciiString();
String pwd = slea.readMapleAsciiString();
c.setAccountName(login);
int loginok = c.login(login, pwd);
slea.skip(6); // localhost masked the initial part with zeroes...
byte[] hwidNibbles = slea.read(4);
int loginok = c.login(login, pwd, HexTool.toCompressedString(hwidNibbles));
Connection con = null;
PreparedStatement ps = null;
@@ -63,8 +66,8 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
ps = con.prepareStatement("INSERT INTO accounts (name, password, birthday, tempban) VALUES (?, ?, ?, ?);", Statement.RETURN_GENERATED_KEYS); //Jayd: Added birthday, tempban
ps.setString(1, login);
ps.setString(2, BCrypt.hashpw(pwd, BCrypt.gensalt(12)));
ps.setString(3, "2018-06-20"); //Jayd: was added to solve the MySQL 5.7 strict checking (birthday)
ps.setString(4, "2018-06-20"); //Jayd: was added to solve the MySQL 5.7 strict checking (tempban)
ps.setString(3, "2018-06-20"); //Jayd's idea: was added to solve the MySQL 5.7 strict checking (birthday)
ps.setString(4, "2018-06-20"); //Jayd's idea: was added to solve the MySQL 5.7 strict checking (tempban)
ps.executeUpdate();
ResultSet rs = ps.getGeneratedKeys();
@@ -75,7 +78,7 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
e.printStackTrace();
} finally {
disposeSql(con, ps);
loginok = c.login(login, pwd);
loginok = c.login(login, pwd, HexTool.toCompressedString(hwidNibbles));
}
}
@@ -100,7 +103,7 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
}
Calendar tempban = c.getTempBanCalendar();
if (tempban != null) {
if (tempban.getTimeInMillis() > System.currentTimeMillis()) {
if (tempban.getTimeInMillis() > Calendar.getInstance().getTimeInMillis()) {
c.announce(MaplePacketCreator.getTempBan(tempban.getTimeInMillis(), c.getGReason()));
return;
}

View File

@@ -10,18 +10,54 @@ import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
import client.MapleClient;
import java.net.InetSocketAddress;
import net.server.coordinator.MapleSessionCoordinator;
import net.server.coordinator.MapleSessionCoordinator.AntiMulticlientResult;
import org.apache.mina.core.session.IoSession;
public final class RegisterPicHandler extends AbstractMaplePacketHandler {
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;
}
}
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
slea.readByte();
int charId = slea.readInt();
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
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);
if (res != AntiMulticlientResult.SUCCESS) {
c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
return;
}
if (c.hasBannedMac() || c.hasBannedHWID()) {
c.getSession().close(true);
return;

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.MapleEventRecallCoordinator;
/**
*
* @author Ronan
*/
public class EventRecallCoordinatorWorker implements Runnable {
@Override
public void run() {
MapleEventRecallCoordinator.getInstance().manageEventInstances();
}
}

View File

@@ -29,6 +29,6 @@ public class LoginCoordinatorWorker implements Runnable {
@Override
public void run() {
MapleSessionCoordinator.getInstance().runUpdateIpHistory();
MapleSessionCoordinator.getInstance().runUpdateHwidHistory();
}
}

View File

@@ -27,6 +27,7 @@ import client.BuddyList.BuddyOperation;
import client.BuddylistEntry;
import client.MapleCharacter;
import client.MapleFamily;
import constants.GameConstants;
import constants.ServerConstants;
import java.sql.Connection;
@@ -53,6 +54,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.Set;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.concurrent.ScheduledFuture;
import scripting.event.EventInstanceManager;
@@ -122,6 +124,11 @@ public class World {
private MonitoredReentrantLock partyLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.WORLD_PARTY, true);
private Map<Integer, Integer> owlSearched = new LinkedHashMap<>();
private List<Map<Integer, Integer>> cashItemBought = new ArrayList<>(9);
private final ReentrantReadWriteLock suggestLock = new MonitoredReentrantReadWriteLock(MonitoredLockType.WORLD_SUGGEST, true);
private ReadLock suggestRLock = suggestLock.readLock();
private WriteLock suggestWLock = suggestLock.writeLock();
private Map<Integer, Integer> disabledServerMessages = new HashMap<>(); // reuse owl lock
private MonitoredReentrantLock srvMessagesLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.WORLD_SRVMESSAGES);
private ScheduledFuture<?> srvMessagesSchedule;
@@ -166,6 +173,10 @@ public class World {
petUpdate = System.currentTimeMillis();
mountUpdate = petUpdate;
for (int i = 0; i < 9; i++) {
cashItemBought.add(new LinkedHashMap<Integer, Integer>());
}
TimerManager tman = TimerManager.getInstance();
petsSchedule = tman.register(new PetFullnessWorker(this), 60 * 1000, 60 * 1000);
srvMessagesSchedule = tman.register(new ServerMessageWorker(this), 10 * 1000, 10 * 1000);
@@ -1152,7 +1163,7 @@ public class World {
}
public void addOwlItemSearch(Integer itemid) {
srvMessagesLock.lock();
suggestWLock.lock();
try {
Integer cur = owlSearched.get(itemid);
if(cur != null) {
@@ -1161,16 +1172,16 @@ public class World {
owlSearched.put(itemid, 1);
}
} finally {
srvMessagesLock.unlock();
suggestWLock.unlock();
}
}
public List<Pair<Integer, Integer>> getOwlSearchedItems() {
if(ServerConstants.USE_ENFORCE_OWL_SUGGESTIONS) {
if(ServerConstants.USE_ENFORCE_ITEM_SUGGESTION) {
return new ArrayList<>(0);
}
srvMessagesLock.lock();
suggestRLock.lock();
try {
List<Pair<Integer, Integer>> searchCounts = new ArrayList<>(owlSearched.size());
@@ -1180,10 +1191,108 @@ public class World {
return searchCounts;
} finally {
srvMessagesLock.unlock();
suggestRLock.unlock();
}
}
public void addCashItemBought(Integer snid) {
suggestWLock.lock();
try {
Map<Integer, Integer> tabItemBought = cashItemBought.get(snid / 10000000);
Integer cur = tabItemBought.get(snid);
if (cur != null) {
tabItemBought.put(snid, cur + 1);
} else {
tabItemBought.put(snid, 1);
}
} finally {
suggestWLock.unlock();
}
}
private List<List<Pair<Integer, Integer>>> getBoughtCashItems() {
if (ServerConstants.USE_ENFORCE_ITEM_SUGGESTION) {
return new ArrayList<>(0);
}
suggestRLock.lock();
try {
List<List<Pair<Integer, Integer>>> boughtCounts = new ArrayList<>(cashItemBought.size());
for (Map<Integer, Integer> tab : cashItemBought) {
List<Pair<Integer, Integer>> tabItems = new LinkedList<>();
boughtCounts.add(tabItems);
for (Entry<Integer, Integer> e : tab.entrySet()) {
tabItems.add(new Pair<>(e.getKey(), e.getValue()));
}
}
return boughtCounts;
} finally {
suggestRLock.unlock();
}
}
private List<Integer> getMostSellerOnTab(List<Pair<Integer, Integer>> tabSellers) {
List<Integer> tabLeaderboards;
Comparator<Pair<Integer, Integer>> comparator = new Comparator<Pair<Integer, Integer>>() { // descending order
@Override
public int compare(Pair<Integer, Integer> p1, Pair<Integer, Integer> p2) {
return p2.getRight().compareTo(p1.getRight());
}
};
PriorityQueue<Pair<Integer, Integer>> queue = new PriorityQueue<>(Math.max(1, tabSellers.size()), comparator);
for(Pair<Integer, Integer> p : tabSellers) {
queue.add(p);
}
tabLeaderboards = new LinkedList<>();
for(int i = 0; i < Math.min(tabSellers.size(), 5); i++) {
tabLeaderboards.add(queue.remove().getLeft());
}
return tabLeaderboards;
}
public List<List<Integer>> getMostSellerCashItems() {
List<List<Pair<Integer, Integer>>> mostSellers = this.getBoughtCashItems();
List<List<Integer>> cashLeaderboards = new ArrayList<>(9);
List<Integer> tabLeaderboards;
List<Integer> allLeaderboards = null;
for(List<Pair<Integer, Integer>> tabSellers : mostSellers) {
if (tabSellers.size() < 5) {
if (allLeaderboards == null) {
List<Pair<Integer, Integer>> allSellers = new LinkedList<>();
for (List<Pair<Integer, Integer>> tabItems : mostSellers) {
allSellers.addAll(tabItems);
}
allLeaderboards = getMostSellerOnTab(allSellers);
}
tabLeaderboards = new LinkedList<>();
if (allLeaderboards.size() < 5) {
for(int i : GameConstants.CASH_DATA) {
tabLeaderboards.add(i);
}
} else {
tabLeaderboards.addAll(allLeaderboards);
}
} else {
tabLeaderboards = getMostSellerOnTab(tabSellers);
}
cashLeaderboards.add(tabLeaderboards);
}
return cashLeaderboards;
}
public void registerPetHunger(MapleCharacter chr, byte petSlot) {
if(chr.isGM() && ServerConstants.GM_PETS_NEVER_HUNGRY || ServerConstants.PETS_NEVER_HUNGRY) {
return;