package client;
import constants.GameConstants;
import constants.ServerConstants;
import database.DatabaseConnection;
import database.DatabaseException;
import handling.cashshop.CashShopServer;
import handling.channel.ChannelServer;
import handling.login.LoginServer;
import handling.world.MapleMessengerCharacter;
import handling.world.MapleParty;
import handling.world.MaplePartyCharacter;
import handling.world.PartyOperation;
import handling.world.World;
import handling.world.family.MapleFamilyCharacter;
import handling.world.guild.MapleGuildCharacter;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.script.ScriptEngine;
import org.apache.mina.common.IoSession;
import server.CharacterCardFactory;
import server.Timer.PingTimer;
import server.farm.MapleFarm;
import server.maps.MapleMap;
import server.quest.MapleQuest;
import server.shark.SharkLogger;
import server.stores.IMaplePlayerShop;
import tools.FileoutputUtil;
import tools.MapleAESOFB;
import tools.Pair;
import tools.packet.CField;
import tools.packet.LoginPacket;
public class MapleClient implements Serializable {
private static final long serialVersionUID = 9179541993413738569L;
public static final byte LOGIN_NOTLOGGEDIN = 0,
LOGIN_SERVER_TRANSITION = 1,
LOGIN_LOGGEDIN = 2,
CHANGE_CHANNEL = 3;
public static final int DEFAULT_CHARSLOT = 6;
public static final String CLIENT_KEY = "CLIENT";
private final transient MapleAESOFB send, receive;
private final transient IoSession session;
private MapleCharacter player;
private int channel = 1, accId = -1, world, birthday;
private int charslots = DEFAULT_CHARSLOT;
private boolean loggedIn = false, serverTransition = false;
private transient Calendar tempban = null;
private String accountName;
private transient long lastPong = 0, lastPing = 0;
private boolean monitored = false, receiving = true;
private boolean gm;
private byte greason = 1, gender = -1;
public transient short loginAttempt = 0;
public transient short couponAttempt = 0;
private final transient List<Integer> allowedChar = new LinkedList<>();
private final transient Set<String> macs = new HashSet<>();
private final transient Map<String, ScriptEngine> engines = new HashMap<>();
private transient ScheduledFuture<?> idleTask = null;
private transient String secondPassword, salt2, tempIP = ""; // To be used only on login
private final transient Lock mutex = new ReentrantLock(true);
private final transient Lock npc_mutex = new ReentrantLock();
private long lastNpcClick = 0;
private final static Lock login_mutex = new ReentrantLock(true);
private final Map<Integer, Pair<Short, Short>> charInfo = new LinkedHashMap<>();
private int client_increnement = 1;
private MapleFarm farm;
private String redirectorUsername;
public SharkLogger sl = new SharkLogger(); // no boilerplate because i'm lazy
public MapleClient(MapleAESOFB send, MapleAESOFB receive, IoSession session) {
this.send = send;
this.receive = receive;
this.session = session;
}
public final MapleAESOFB getReceiveCrypto() {
return receive;
}
public final MapleAESOFB getSendCrypto() {
return send;
}
public final IoSession getSession() {
return session;
}
public final Lock getLock() {
return mutex;
}
public final Lock getNPCLock() {
return npc_mutex;
}
public MapleCharacter getPlayer() {
return player;
}
public void setPlayer(MapleCharacter player) {
this.player = player;
}
public void createdChar(final int id) {
allowedChar.add(id);
}
public final boolean login_Auth(final int id) {
return allowedChar.contains(id);
}
public final List<MapleCharacter> loadCharacters(final int serverId) { // TODO make this less costly zZz
final List<MapleCharacter> chars = new LinkedList<>();
final Map<Integer, CardData> cardss = CharacterCardFactory.getInstance().loadCharacterCards(accId, serverId);
for (final CharNameAndId cni : loadCharactersInternal(serverId)) {
final MapleCharacter chr = MapleCharacter.loadCharFromDB(cni.id, this, false, cardss);
chars.add(chr);
charInfo.put(chr.getId(), new Pair<>(chr.getLevel(), chr.getJob())); // to be used to update charCards
if (!login_Auth(chr.getId())) {
allowedChar.add(chr.getId());
}
}
return chars;
}
public final void updateCharacterCards(final Map<Integer, Integer> cids) {
System.out.println("updateCharacterCards: " + cids.toString());
if (charInfo.isEmpty()) { // no characters
return;
}
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("DELETE FROM `character_cards` WHERE `accid` = ?")) {
ps.setInt(1, accId);
ps.executeUpdate();
}
try (PreparedStatement psu = con.prepareStatement("INSERT INTO `character_cards` (accid, worldid, characterid, position) VALUES (?, ?, ?, ?)")) {
for (final Entry<Integer, Integer> ii : cids.entrySet()) {
final Pair<Short, Short> info = charInfo.get(ii.getValue()); // charinfo we can use here as characters are already loaded
if (info == null || ii.getValue() == 0 || !CharacterCardFactory.getInstance().canHaveCard(info.getLeft(), info.getRight())) {
System.out.println("continue");
continue;
}
psu.setInt(1, accId);
psu.setInt(2, world);
psu.setInt(3, ii.getValue());
psu.setInt(4, ii.getKey()); // position shouldn't matter much, will reset upon login
System.out.println(psu.getResultSet().toString());
psu.executeUpdate();
}
}
} catch (SQLException sqlE) {
System.out.println("Failed to update character cards. Reason: " + sqlE.toString());
}
}
public boolean canMakeCharacter(int serverId) {
return loadCharactersSize(serverId) < getCharacterSlots();
}
public List<String> loadCharacterNames(int serverId) {
List<String> chars = new LinkedList<>();
for (CharNameAndId cni : loadCharactersInternal(serverId)) {
chars.add(cni.name);
}
return chars;
}
private List<CharNameAndId> loadCharactersInternal(int serverId) {
List<CharNameAndId> chars = new LinkedList<>();
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT id, name, gm FROM characters WHERE accountid = ? AND world = ?")) {
ps.setInt(1, accId);
ps.setInt(2, serverId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
chars.add(new CharNameAndId(rs.getString("name"), rs.getInt("id")));
LoginServer.getLoginAuth(rs.getInt("id"));
}
}
}
} catch (SQLException e) {
System.err.println("error loading characters internal");
}
return chars;
}
private int loadCharactersSize(int serverId) {
int chars = 0;
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT count(*) FROM characters WHERE accountid = ? AND world = ?")) {
ps.setInt(1, accId);
ps.setInt(2, serverId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
chars = rs.getInt(1);
}
}
}
} catch (SQLException e) {
System.err.println("error loading characters internal");
}
return chars;
}
public boolean isLoggedIn() {
return loggedIn && accId >= 0;
}
private Calendar getTempBanCalendar(ResultSet rs) throws SQLException {
Calendar lTempban = Calendar.getInstance();
if (rs.getLong("tempban") == 0) { // basically if timestamp in db is 0000-00-00
lTempban.setTimeInMillis(0);
return lTempban;
}
Calendar today = Calendar.getInstance();
lTempban.setTimeInMillis(rs.getTimestamp("tempban").getTime());
if (today.getTimeInMillis() < lTempban.getTimeInMillis()) {
return lTempban;
}
lTempban.setTimeInMillis(0);
return lTempban;
}
public Calendar getTempBanCalendar() {
return tempban;
}
public byte getBanReason() {
return greason;
}
public String showBanReason(String AccountID, boolean permban) {
boolean autoban = getTrueBanReason(AccountID).toLowerCase().equals("autoban") || getTrueBanReason(AccountID) == null;
return showBanReason((byte) 0x7F, AccountID, permban, autoban, false);
}
public String showBanReason(String AccountID, boolean permban, boolean autoban) {
return showBanReason((byte) 0x7F, AccountID, permban, autoban, false);
}
public String showBanReason(byte type, String AccountID, boolean permban, boolean autoban, boolean showId) {
StringBuilder reason = new StringBuilder();
reason.append("Your account ").append(AccountID).append(" has been blocked for ");
switch (type) {
case 1:
reason.append("hacking or illegal use of third-party programs.");
break;
case 2:
reason.append("using macro / auto-keyboard.");
break;
case 3:
reason.append("illicit promotion and advertising.");
break;
case 4:
reason.append("harassment.");
break;
case 5:
reason.append("using profane language.");
break;
case 6:
reason.append("scamming.");
break;
case 7:
reason.append("misconduct.");
break;
case 8:
reason.append("illegal cash transaction.");
break;
case 9:
reason.append("illegal charging/funding. Please contact customer support for further details.");
break;
case 10:
reason.append("temporary request. Please contact customer support for further details.");
break;
case 11:
reason.append("impersonating GM.");
break;
case 12:
reason.append("using illegal programs or violating the game policy.");
break;
case 13:
reason.append("one of cursing, scamming, or illegal trading via Megaphones.");
break;
case 16:
case 17:
case 18:
reason.append("Unknown reason 1.");
break;
case 19:
case 20:
case 21:
reason.append("Unknown reason 2.");
break;
default:
if (autoban) {
reason.append("System has detected hacking or illegal use of third-party programs.");
} else if (showId) {
reason.append("MapleGM has blocked your account ").append(AccountID).append(" for the following reason: ").append(getTrueBanReason(AccountID)); //Default reason
} else {
reason.append("Your account was blocked by the MapleStory GM's for ").append(getTrueBanReason(AccountID));
}
break;
}
reason.append(permban ? "\r\n\r\nThis ban will never be lifted." : "");
return reason.toString();
}
public String getTrueBanReason(String name) {
String ret = null;
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT banreason FROM accounts WHERE name = ?")) {
ps.setString(1, name);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
ret = rs.getString(1);
}
}
}
return ret;
} catch (SQLException ex) {
System.err.println("Error getting ban reason: " + ex);
}
return ret;
}
public boolean hasBannedIP() {
boolean ret = false;
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT COUNT(*) FROM ipbans WHERE ? LIKE CONCAT(ip, '%')")) {
ps.setString(1, getSessionIPAddress());
try (ResultSet rs = ps.executeQuery()) {
rs.next();
if (rs.getInt(1) > 0) {
ret = true;
}
}
}
} catch (SQLException ex) {
System.err.println("Error checking ip bans" + ex);
}
return ret;
}
public boolean hasBannedMac() {
if (macs.isEmpty()) {
return false;
}
boolean ret = false;
int i;
try {
Connection con = DatabaseConnection.getConnection();
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(")");
try (PreparedStatement ps = con.prepareStatement(sql.toString())) {
i = 0;
for (String mac : macs) {
i++;
ps.setString(i, mac);
}
try (ResultSet rs = ps.executeQuery()) {
rs.next();
if (rs.getInt(1) > 0) {
ret = true;
}
}
}
} catch (SQLException ex) {
System.err.println("Error checking mac bans" + ex);
}
return ret;
}
private void loadMacsIfNescessary() throws SQLException {
if (macs.isEmpty()) {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT macs FROM accounts WHERE id = ?")) {
ps.setInt(1, accId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
if (rs.getString("macs") != null) {
String[] macData;
macData = rs.getString("macs").split(", ");
for (String mac : macData) {
if (!mac.equals("")) {
macs.add(mac);
}
}
}
} else {
rs.close();
ps.close();
throw new RuntimeException("No valid account associated with this client.");
}
}
}
}
}
public void banMacs() {
try {
loadMacsIfNescessary();
if (this.macs.size() > 0) {
String[] macBans = new String[this.macs.size()];
int z = 0;
for (String mac : this.macs) {
macBans[z] = mac;
z++;
}
banMacs(macBans);
}
} catch (SQLException e) {
}
}
public static void banMacs(String[] macs) {
Connection con = DatabaseConnection.getConnection();
try {
List<String> filtered = new LinkedList<>();
PreparedStatement ps = con.prepareStatement("SELECT filter FROM macfilters");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
filtered.add(rs.getString("filter"));
}
rs.close();
ps.close();
ps = con.prepareStatement("INSERT INTO macbans (mac) 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);
try {
ps.executeUpdate();
} catch (SQLException e) {
// can fail because of UNIQUE key, we dont care
}
}
}
ps.close();
} catch (SQLException e) {
System.err.println("Error banning MACs" + e);
}
}
/**
* Returns 0 on success, a state to be used for
* {@link CField#getLoginFailed(int)} otherwise.
*
* @return The state of the login.
*/
public int finishLogin() {
login_mutex.lock();
try {
final byte state = getLoginState();
if (state > MapleClient.LOGIN_NOTLOGGEDIN) { // already loggedin
loggedIn = false;
return 7;
}
updateLoginState(MapleClient.LOGIN_LOGGEDIN, getSessionIPAddress());
} finally {
login_mutex.unlock();
}
return 0;
}
public void clearInformation() {
accountName = null;
accId = -1;
secondPassword = null;
salt2 = null;
gm = false;
loggedIn = false;
greason = (byte) 1;
tempban = null;
gender = (byte) -1;
charInfo.clear();
}
public void setRedirectorUsername(String username) {
this.redirectorUsername = username;
}
public String getRedirectorUsername() {
return this.redirectorUsername;
}
public void loginData(String login) {
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM accounts WHERE name = ?")) {
ps.setString(1, login);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
accountName = login;
accId = rs.getInt("id");
secondPassword = rs.getString("2ndpassword");
salt2 = rs.getString("salt2");
gm = rs.getInt("gm") > 0;
greason = rs.getByte("greason");
tempban = getTempBanCalendar(rs);
gender = rs.getByte("gender");
}
if (secondPassword != null && salt2 != null) {
secondPassword = LoginCrypto.rand_r(secondPassword);
}
}
}
loggedIn = true;
} catch (SQLException e) {
System.err.println("ERROR" + e);
}
}
public int login(String login, String pwd, boolean ipMacBanned) {
int loginok = 5;
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT * FROM accounts WHERE name = ?");
ps.setString(1, login);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
final int banned = rs.getInt("banned");
final String passhash = rs.getString("password");
final String salt = rs.getString("salt");
final String oldSession = rs.getString("SessionIP");
accountName = login;
accId = rs.getInt("id");
secondPassword = rs.getString("2ndpassword");
salt2 = rs.getString("salt2");
gm = rs.getInt("gm") > 0;
greason = rs.getByte("greason");
tempban = getTempBanCalendar(rs);
gender = rs.getByte("gender");
final boolean admin = rs.getInt("gm") > 1;
if (secondPassword != null && salt2 != null) {
secondPassword = LoginCrypto.rand_r(secondPassword);
}
ps.close();
if (banned > 0 && gm) {
loginok = 3;
} else {
if (banned == -1) {
unban();
}
byte loginstate = getLoginState();
if (loginstate > MapleClient.LOGIN_NOTLOGGEDIN) { // already loggedin
loggedIn = false;
loginok = 7;
if (pwd.equalsIgnoreCase("fixme")) {
try {
ps = con.prepareStatement("UPDATE accounts SET loggedin = 0 WHERE name = ?");
ps.setString(1, login);
ps.executeUpdate();
ps.close();
} catch (SQLException se) {
}
}
} else {
boolean updatePasswordHash = false;
// Check if the passwords are correct here. :B
if (passhash == null || passhash.isEmpty()) {
//match by sessionIP
if (oldSession != null && !oldSession.isEmpty()) {
loggedIn = getSessionIPAddress().equals(oldSession);
loginok = loggedIn ? 0 : 4;
updatePasswordHash = loggedIn;
} else {
loginok = 4;
loggedIn = false;
}
} else if (LoginCryptoLegacy.isLegacyPassword(passhash) && LoginCryptoLegacy.checkPassword(pwd, passhash)) {
// Check if a password upgrade is needed.
loginok = 0;
updatePasswordHash = true;
} else if (salt == null && LoginCrypto.checkSha1Hash(passhash, pwd)) {
loginok = 0;
updatePasswordHash = true;
} else if (salt != null && LoginCrypto.checkSaltedSha1Hash(passhash, pwd, salt)) {
loginok = 0; //new standard
} else if (salt != null && LoginCrypto.checkSaltedSha512Hash(passhash, pwd, salt)) {
updatePasswordHash = true; //migrates away from Sha512, higher bit count but incompatible
loginok = 0;
/* Take out to reflect salted SHA1 Redirector
Java's SHA512 implementation is incompatible
Enable only if you know what you're doing
*/
} else {
loggedIn = false;
loginok = 4;
}
if (updatePasswordHash) {
try (PreparedStatement pss = con.prepareStatement("UPDATE `accounts` SET `password` = ?, `salt` = ? WHERE id = ?")) {
final String newSalt = LoginCrypto.makeSalt();
pss.setString(1, LoginCrypto.makeSaltedSha1Hash(pwd, newSalt));
pss.setString(2, newSalt);
pss.setInt(3, accId);
pss.executeUpdate();
}
}
}
}
}
rs.close();
ps.close();
} catch (SQLException e) {
System.err.println("ERROR" + e);
}
return loginok;
}
public boolean CheckSecondPassword(String in) {
boolean allow = false;
boolean updatePasswordHash = false;
// Check if the passwords are correct here. :B
if (LoginCryptoLegacy.isLegacyPassword(secondPassword) && LoginCryptoLegacy.checkPassword(in, secondPassword)) {
// Check if a password upgrade is needed.
allow = true;
updatePasswordHash = true;
} else if (salt2 == null && LoginCrypto.checkSha1Hash(secondPassword, in)) {
allow = true;
updatePasswordHash = true;
} else if (LoginCrypto.checkSaltedSha512Hash(secondPassword, in, salt2)) {
allow = true;
}
if (updatePasswordHash) {
Connection con = DatabaseConnection.getConnection();
try {
try (PreparedStatement ps = con.prepareStatement("UPDATE `accounts` SET `2ndpassword` = ?, `salt2` = ? WHERE id = ?")) {
final String newSalt = LoginCrypto.makeSalt();
ps.setString(1, LoginCrypto.rand_s(LoginCrypto.makeSaltedSha512Hash(in, newSalt)));
ps.setString(2, newSalt);
ps.setInt(3, accId);
ps.executeUpdate();
}
} catch (SQLException e) {
return false;
}
}
return allow;
}
private void unban() {
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("UPDATE accounts SET banned = 0, banreason = '' WHERE id = ?")) {
ps.setInt(1, accId);
ps.executeUpdate();
}
} catch (SQLException e) {
System.err.println("Error while unbanning" + e);
}
}
public static byte unban(String charname) {
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT accountid from characters where name = ?");
ps.setString(1, charname);
ResultSet rs = ps.executeQuery();
if (!rs.next()) {
rs.close();
ps.close();
return -1;
}
final int accid = rs.getInt(1);
rs.close();
ps.close();
ps = con.prepareStatement("UPDATE accounts SET banned = 0, banreason = '' WHERE id = ?");
ps.setInt(1, accid);
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
System.err.println("Error while unbanning" + e);
return -2;
}
return 0;
}
public void updateMacs(String macData) {
macs.addAll(Arrays.asList(macData.split(", ")));
StringBuilder newMacData = new StringBuilder();
Iterator<String> iter = macs.iterator();
while (iter.hasNext()) {
newMacData.append(iter.next());
if (iter.hasNext()) {
newMacData.append(", ");
}
}
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("UPDATE accounts SET macs = ? WHERE id = ?")) {
ps.setString(1, newMacData.toString());
ps.setInt(2, accId);
ps.executeUpdate();
}
} catch (SQLException e) {
System.err.println("Error saving MACs" + e);
}
}
public void setAccID(int id) {
this.accId = id;
}
public int getAccID() {
return this.accId;
}
public final void updateLoginState(final int newstate, final String SessionID) { // TODO hide?
try {
final Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("UPDATE accounts SET loggedin = ?, SessionIP = ?, lastlogin = CURRENT_TIMESTAMP() WHERE id = ?")) {
ps.setInt(1, newstate);
ps.setString(2, SessionID);
ps.setInt(3, getAccID());
ps.executeUpdate();
}
} catch (SQLException e) {
System.err.println("error updating login state " + e);
}
if (newstate == MapleClient.LOGIN_NOTLOGGEDIN) {
loggedIn = false;
serverTransition = false;
} else {
serverTransition = (newstate == MapleClient.LOGIN_SERVER_TRANSITION || newstate == MapleClient.CHANGE_CHANNEL);
loggedIn = !serverTransition;
}
}
public final void updateSecondPassword() {
try {
final Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("UPDATE `accounts` SET `2ndpassword` = ?, `salt2` = ? WHERE id = ?")) {
final String newSalt = LoginCrypto.makeSalt();
ps.setString(1, LoginCrypto.rand_s(LoginCrypto.makeSaltedSha512Hash(secondPassword, newSalt)));
ps.setString(2, newSalt);
ps.setInt(3, accId);
ps.executeUpdate();
}
} catch (SQLException e) {
System.err.println("error updating login state " + e);
}
}
public final byte getLoginState() { // TODO hide?
Connection con = DatabaseConnection.getConnection();
try {
PreparedStatement ps;
ps = con.prepareStatement("SELECT loggedin, lastlogin, banned, `birthday` + 0 AS `bday` FROM accounts WHERE id = ?");
ps.setInt(1, getAccID());
byte state;
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next() || rs.getInt("banned") > 0) {
ps.close();
rs.close();
session.close();
throw new DatabaseException("Account doesn't exist or is banned");
}
birthday = rs.getInt("bday");
state = rs.getByte("loggedin");
if (state == MapleClient.LOGIN_SERVER_TRANSITION || state == MapleClient.CHANGE_CHANNEL) {
if (rs.getTimestamp("lastlogin").getTime() + 20000 < System.currentTimeMillis()) { // connecting to chanserver timeout
state = MapleClient.LOGIN_NOTLOGGEDIN;
updateLoginState(state, getSessionIPAddress());
}
}
}
ps.close();
loggedIn = state == MapleClient.LOGIN_LOGGEDIN;
return state;
} catch (SQLException e) {
loggedIn = false;
throw new DatabaseException("error getting login state", e);
}
}
public final boolean checkBirthDate(final int date) {
return birthday == date;
}
public final void removalTask(boolean shutdown) {
try {
player.cancelAllBuffs_();
player.cancelAllDebuffs();
if (player.getMarriageId() > 0) {
final MapleQuestStatus stat1 = player.getQuestNoAdd(MapleQuest.getInstance(160001));
final MapleQuestStatus stat2 = player.getQuestNoAdd(MapleQuest.getInstance(160002));
if (stat1 != null && stat1.getCustomData() != null && (stat1.getCustomData().equals("2_") || stat1.getCustomData().equals("2"))) {
//dc in process of marriage
if (stat2 != null && stat2.getCustomData() != null) {
stat2.setCustomData("0");
}
stat1.setCustomData("3");
}
}
if (player.getMapId() == GameConstants.JAIL) {
final MapleQuestStatus stat1 = player.getQuestNAdd(MapleQuest.getInstance(GameConstants.JAIL_TIME));
final MapleQuestStatus stat2 = player.getQuestNAdd(MapleQuest.getInstance(GameConstants.JAIL_QUEST));
if (stat1.getCustomData() == null) {
stat1.setCustomData(String.valueOf(System.currentTimeMillis()));
} else if (stat2.getCustomData() == null) {
stat2.setCustomData("0"); //seconds of jail
} else { //previous seconds - elapsed seconds
int seconds = Integer.parseInt(stat2.getCustomData()) - (int) ((System.currentTimeMillis() - Long.parseLong(stat1.getCustomData())) / 1000);
if (seconds < 0) {
seconds = 0;
}
stat2.setCustomData(String.valueOf(seconds));
}
}
player.changeRemoval(true);
if (player.getEventInstance() != null) {
player.getEventInstance().playerDisconnected(player, player.getId());
}
final IMaplePlayerShop shop = player.getPlayerShop();
if (shop != null) {
shop.removeVisitor(player);
if (shop.isOwner(player)) {
if (shop.getShopType() == 1 && shop.isAvailable() && !shutdown) {
shop.setOpen(true);
} else {
shop.closeShop(true, !shutdown);
}
}
}
player.setMessenger(null);
if (player.getMap() != null) {
if (shutdown || (getChannelServer() != null && getChannelServer().isShutdown())) {
int questID = -1;
switch (player.getMapId()) {
case 240060200: //HT
questID = 160100;
break;
case 240060201: //ChaosHT
questID = 160103;
break;
case 280030000: //Zakum
questID = 160101;
break;
case 280030001: //ChaosZakum
questID = 160102;
break;
case 270050100: //PB
questID = 160101;
break;
case 105100300: //Balrog
case 105100400: //Balrog
questID = 160106;
break;
case 211070000: //VonLeon
case 211070100: //VonLeon
case 211070101: //VonLeon
case 211070110: //VonLeon
questID = 160107;
break;
case 551030200: //scartar
questID = 160108;
break;
case 271040100: //cygnus
questID = 160109;
break;
case 262030000:
case 262031300: // hilla
questID = 160110;
break;
case 272030400:
questID = 160111;
break;
}
if (questID > 0) {
player.getQuestNAdd(MapleQuest.getInstance(questID)).setCustomData("0"); //reset the time.
}
} else if (player.isAlive()) {
switch (player.getMapId()) {
case 541010100: //latanica
case 541020800: //krexel
case 220080001: //pap
player.getMap().addDisconnected(player.getId());
break;
}
}
player.getMap().removePlayer(player);
}
} catch (final NumberFormatException e) {
FileoutputUtil.outputFileError(FileoutputUtil.Acc_Stuck, e);
}
}
public final void disconnect(final boolean RemoveInChannelServer, final boolean fromCS) {
disconnect(RemoveInChannelServer, fromCS, false);
}
public final void disconnect(final boolean RemoveInChannelServer, final boolean fromCS, final boolean shutdown) {
this.sl.dump();
if (player != null) {
MapleMap map = player.getMap();
final MapleParty party = player.getParty();
final boolean clone = player.isClone();
final String namez = player.getName();
final int idz = player.getId(), messengerid = player.getMessenger() == null ? 0 : player.getMessenger().getId(), gid = player.getGuildId(), fid = player.getFamilyId();
final BuddyList bl = player.getBuddylist();
final MaplePartyCharacter chrp = new MaplePartyCharacter(player);
final MapleMessengerCharacter chrm = new MapleMessengerCharacter(player);
final MapleGuildCharacter chrg = player.getMGC();
final MapleFamilyCharacter chrf = player.getMFC();
removalTask(shutdown);
LoginServer.getLoginAuth(player.getId());
player.saveToDB(true, fromCS);
if (shutdown) {
player = null;
receiving = false;
return;
}
if (!fromCS) {
final ChannelServer ch = ChannelServer.getInstance(map == null ? channel : map.getChannel());
final int chz = World.Find.findChannel(idz);
if (chz < -1) {
disconnect(RemoveInChannelServer, true);//u lie
return;
}
try {
if (chz == -1 || ch == null || clone || ch.isShutdown()) {
player = null;
return;//no idea
}
if (messengerid > 0) {
World.Messenger.leaveMessenger(messengerid, chrm);
}
if (party != null) {
chrp.setOnline(false);
World.Party.updateParty(party.getId(), PartyOperation.LOG_ONOFF, chrp);
if (map != null && party.getLeader().getId() == idz) {
MaplePartyCharacter lchr = null;
for (MaplePartyCharacter pchr : party.getMembers()) {
if (pchr != null && map.getCharacterById(pchr.getId()) != null && (lchr == null || lchr.getLevel() < pchr.getLevel())) {
lchr = pchr;
}
}
if (lchr != null) {
World.Party.updateParty(party.getId(), PartyOperation.CHANGE_LEADER_DC, lchr);
}
}
}
if (bl != null) {
if (!serverTransition) {
World.Buddy.loggedOff(namez, idz, channel, bl.getBuddyIds());
} else { // Change channel
World.Buddy.loggedOn(namez, idz, channel, bl.getBuddyIds());
}
}
if (gid > 0 && chrg != null) {
World.Guild.setGuildMemberOnline(chrg, false, -1);
}
if (fid > 0 && chrf != null) {
World.Family.setFamilyMemberOnline(chrf, false, -1);
}
} catch (final Exception e) {
FileoutputUtil.outputFileError(FileoutputUtil.Acc_Stuck, e);
System.err.println(getLogMessage(this, "ERROR") + e);
} finally {
if (RemoveInChannelServer && ch != null) {
ch.removePlayer(idz, namez);
}
player = null;
}
} else {
final int ch = World.Find.findChannel(idz);
if (ch > 0) {
disconnect(RemoveInChannelServer, false);//u lie
return;
}
try {
if (party != null) {
chrp.setOnline(false);
World.Party.updateParty(party.getId(), PartyOperation.LOG_ONOFF, chrp);
}
if (!serverTransition) {
World.Buddy.loggedOff(namez, idz, channel, bl.getBuddyIds());
} else { // Change channel
World.Buddy.loggedOn(namez, idz, channel, bl.getBuddyIds());
}
if (gid > 0 && chrg != null) {
World.Guild.setGuildMemberOnline(chrg, false, -1);
}
if (fid > 0 && chrf != null) {
World.Family.setFamilyMemberOnline(chrf, false, -1);
}
if (player != null) {
player.setMessenger(null);
}
} catch (final Exception e) {
FileoutputUtil.outputFileError(FileoutputUtil.Acc_Stuck, e);
System.err.println(getLogMessage(this, "ERROR") + e);
} finally {
if (RemoveInChannelServer && ch > 0) {
CashShopServer.getPlayerStorage().deregisterPlayer(idz, namez);
}
player = null;
}
}
}
if (!serverTransition && isLoggedIn()) {
updateLoginState(MapleClient.LOGIN_NOTLOGGEDIN, getSessionIPAddress());
}
engines.clear();
}
public final String getSessionIPAddress() {
return session.getRemoteAddress().toString().split(":")[0];
}
public final boolean CheckIPAddress() {
if (this.accId < 0) {
return false;
}
try {
boolean canlogin = false;
try (PreparedStatement ps = DatabaseConnection.getConnection().prepareStatement("SELECT SessionIP, banned FROM accounts WHERE id = ?");) {
ps.setInt(1, this.accId);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
final String sessionIP = rs.getString("SessionIP");
if (sessionIP != null) { // Probably a login proced skipper?
canlogin = getSessionIPAddress().equals(sessionIP.split(":")[0]);
}
if (rs.getInt("banned") > 0) {
canlogin = false; //canlogin false = close client
}
}
}
}
return canlogin;
} catch (final SQLException e) {
System.out.println("Failed in checking IP address for client.");
}
return true;
}
public final void DebugMessage(final StringBuilder sb) {
sb.append(getSession().getRemoteAddress());
sb.append("Connected: ");
sb.append(getSession().isConnected());
sb.append(" Closing: ");
sb.append(getSession().isClosing());
sb.append(" ClientKeySet: ");
sb.append(getSession().getAttribute(MapleClient.CLIENT_KEY) != null);
sb.append(" loggedin: ");
sb.append(isLoggedIn());
sb.append(" has char: ");
sb.append(getPlayer() != null);
}
public final int getChannel() {
return channel;
}
public final ChannelServer getChannelServer() {
return ChannelServer.getInstance(channel);
}
public final int deleteCharacter(final int cid) {
try {
final Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT guildid, guildrank, familyid, name FROM characters WHERE id = ? AND accountid = ?")) {
ps.setInt(1, cid);
ps.setInt(2, accId);
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) {
rs.close();
ps.close();
return 9;
}
if (rs.getInt("guildid") > 0) { // is in a guild when deleted
if (rs.getInt("guildrank") == 1) { //cant delete when leader
rs.close();
ps.close();
return 22;
}
World.Guild.deleteGuildCharacter(rs.getInt("guildid"), cid);
}
if (rs.getInt("familyid") > 0 && World.Family.getFamily(rs.getInt("familyid")) != null) {
World.Family.getFamily(rs.getInt("familyid")).leaveFamily(cid);
}
}
}
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM characters WHERE id = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM hiredmerch WHERE characterid = ?", cid);
//MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM cheatlog WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM mountdata WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM inventoryitems WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM famelog WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM famelog WHERE characterid_to = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM dueypackages WHERE RecieverId = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM wishlist WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM buddies WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM buddies WHERE buddyid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM keymap WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM trocklocations WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM regrocklocations WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM hyperrocklocations WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM savedlocations WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM skills WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM familiars WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM mountdata WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM skillmacros WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM trocklocations WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM queststatus WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM inventoryslot WHERE characterid = ?", cid);
MapleCharacter.deleteWhereCharacterId(con, "DELETE FROM extendedSlots WHERE characterid = ?", cid);
return 0;
} catch (SQLException e) {
FileoutputUtil.outputFileError(FileoutputUtil.PacketEx_Log, e);
}
return 10;
}
public final byte getGender() {
return gender;
}
public final void setGender(final byte gender) {
this.gender = gender;
}
public final String getSecondPassword() {
return secondPassword;
}
public final void setSecondPassword(final String secondPassword) {
this.secondPassword = secondPassword;
}
public final String getAccountName() {
return accountName;
}
public final void setAccountName(final String accountName) {
this.accountName = accountName;
}
public final void setChannel(final int channel) {
this.channel = channel;
}
public final int getWorld() {
return world;
}
public final void setWorld(final int world) {
this.world = world;
}
public final int getLatency() {
return (int) (lastPong - lastPing);
}
public final long getLastPong() {
return lastPong;
}
public final long getLastPing() {
return lastPing;
}
public final void pongReceived() {
lastPong = System.currentTimeMillis();
}
public final void sendPing() {
lastPing = System.currentTimeMillis();
session.write(LoginPacket.getPing());
PingTimer.getInstance().schedule(new Runnable() {
@Override
public void run() {
try {
if (getLatency() < 0) {
disconnect(true, false);
if (getSession().isConnected()) {
getSession().close();
}
}
} catch (final NullPointerException e) {
// client already gone
}
}
}, 60000); // note: idletime gets added to this too
}
public static String getLogMessage(final MapleClient cfor, final String message) {
return getLogMessage(cfor, message, new Object[0]);
}
public static String getLogMessage(final MapleCharacter cfor, final String message) {
return getLogMessage(cfor == null ? null : cfor.getClient(), message);
}
public static String getLogMessage(final MapleCharacter cfor, final String message, final Object... parms) {
return getLogMessage(cfor == null ? null : cfor.getClient(), message, parms);
}
public static String getLogMessage(final MapleClient cfor, final String message, final Object... parms) {
final StringBuilder builder = new StringBuilder();
if (cfor != null) {
if (cfor.getPlayer() != null) {
builder.append("<");
builder.append(MapleCharacterUtil.makeMapleReadable(cfor.getPlayer().getName()));
builder.append(" (cid: ");
builder.append(cfor.getPlayer().getId());
builder.append(")> ");
}
if (cfor.getAccountName() != null) {
builder.append("(Account: ");
builder.append(cfor.getAccountName());
builder.append(") ");
}
}
builder.append(message);
int start;
for (final Object parm : parms) {
start = builder.indexOf("{}");
builder.replace(start, start + 2, parm.toString());
}
return builder.toString();
}
public static int findAccIdForCharacterName(final String charName) {
try {
Connection con = DatabaseConnection.getConnection();
int ret;
try (PreparedStatement ps = con.prepareStatement("SELECT accountid FROM characters WHERE name = ?")) {
ps.setString(1, charName);
try (ResultSet rs = ps.executeQuery()) {
ret = -1;
if (rs.next()) {
ret = rs.getInt("accountid");
}
}
}
return ret;
} catch (final SQLException e) {
System.err.println("findAccIdForCharacterName SQL error");
}
return -1;
}
public final Set<String> getMacs() {
return Collections.unmodifiableSet(macs);
}
public final boolean isGm() {
return gm;
}
public final void setScriptEngine(final String name, final ScriptEngine e) {
engines.put(name, e);
}
public final ScriptEngine getScriptEngine(final String name) {
return engines.get(name);
}
public final void removeScriptEngine(final String name) {
engines.remove(name);
}
public final ScheduledFuture<?> getIdleTask() {
return idleTask;
}
public final void setIdleTask(final ScheduledFuture<?> idleTask) {
this.idleTask = idleTask;
}
protected static final class CharNameAndId {
public final String name;
public final int id;
public CharNameAndId(final String name, final int id) {
super();
this.name = name;
this.id = id;
}
}
public int getCharacterSlots() {
// if (isGm()) {
// return 24;
// }
if (charslots != DEFAULT_CHARSLOT) {
return charslots; //save a sql
}
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT * FROM character_slots WHERE accid = ? AND worldid = ?")) {
ps.setInt(1, accId);
ps.setInt(2, world);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
charslots = rs.getInt("charslots");
} else {
try (PreparedStatement psu = con.prepareStatement("INSERT INTO character_slots (accid, worldid, charslots) VALUES (?, ?, ?)")) {
psu.setInt(1, accId);
psu.setInt(2, world);
psu.setInt(3, charslots);
psu.executeUpdate();
}
}
}
}
} catch (SQLException sqlE) {
}
return charslots;
}
public boolean gainCharacterSlot() {
if (getCharacterSlots() >= 15) {
return false;
}
charslots++;
try {
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("UPDATE character_slots SET charslots = ? WHERE worldid = ? AND accid = ?")) {
ps.setInt(1, charslots);
ps.setInt(2, world);
ps.setInt(3, accId);
ps.executeUpdate();
ps.close();
}
} catch (SQLException sqlE) {
return false;
}
return true;
}
public static byte unbanIPMacs(String charname) {
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT accountid from characters where name = ?");
ps.setString(1, charname);
ResultSet rs = ps.executeQuery();
if (!rs.next()) {
rs.close();
ps.close();
return -1;
}
final int accid = rs.getInt(1);
rs.close();
ps.close();
ps = con.prepareStatement("SELECT * FROM accounts WHERE id = ?");
ps.setInt(1, accid);
rs = ps.executeQuery();
if (!rs.next()) {
rs.close();
ps.close();
return -1;
}
final String sessionIP = rs.getString("sessionIP");
final String macs = rs.getString("macs");
rs.close();
ps.close();
byte ret = 0;
if (sessionIP != null) {
try (PreparedStatement psa = con.prepareStatement("DELETE FROM ipbans WHERE ip like ?")) {
psa.setString(1, sessionIP);
psa.execute();
}
ret++;
}
if (macs != null) {
String[] macz;
macz = macs.split(", ");
for (String mac : macz) {
if (!mac.equals("")) {
try (PreparedStatement psa = con.prepareStatement("DELETE FROM macbans WHERE mac = ?")) {
psa.setString(1, mac);
psa.execute();
}
}
}
ret++;
}
return ret;
} catch (SQLException e) {
System.err.println("Error while unbanning" + e);
return -2;
}
}
public static byte unHellban(String charname) {
try {
Connection con = DatabaseConnection.getConnection();
PreparedStatement ps = con.prepareStatement("SELECT accountid from characters where name = ?");
ps.setString(1, charname);
ResultSet rs = ps.executeQuery();
if (!rs.next()) {
rs.close();
ps.close();
return -1;
}
final int accid = rs.getInt(1);
rs.close();
ps.close();
ps = con.prepareStatement("SELECT * FROM accounts WHERE id = ?");
ps.setInt(1, accid);
rs = ps.executeQuery();
if (!rs.next()) {
rs.close();
ps.close();
return -1;
}
final String sessionIP = rs.getString("sessionIP");
final String email = rs.getString("email");
rs.close();
ps.close();
ps = con.prepareStatement("UPDATE accounts SET banned = 0, banreason = '' WHERE email = ?" + (sessionIP == null ? "" : " OR sessionIP = ?"));
ps.setString(1, email);
if (sessionIP != null) {
ps.setString(2, sessionIP);
}
ps.execute();
ps.close();
return 0;
} catch (SQLException e) {
System.err.println("Error while unbanning" + e);
return -2;
}
}
public boolean isMonitored() {
return monitored;
}
public void setMonitored(boolean m) {
this.monitored = m;
}
public boolean isReceiving() {
return receiving;
}
public void setReceiving(boolean m) {
this.receiving = m;
}
public boolean canClickNPC() {
return lastNpcClick + 500 < System.currentTimeMillis();
}
public void setClickedNPC() {
lastNpcClick = System.currentTimeMillis();
}
public void removeClickedNPC() {
lastNpcClick = 0;
}
public final Timestamp getCreated() { // TODO hide?
Connection con = DatabaseConnection.getConnection();
try {
PreparedStatement ps;
ps = con.prepareStatement("SELECT createdat FROM accounts WHERE id = ?");
ps.setInt(1, getAccID());
Timestamp ret;
try (ResultSet rs = ps.executeQuery()) {
if (!rs.next()) {
rs.close();
ps.close();
return null;
}
ret = rs.getTimestamp("createdat");
}
ps.close();
return ret;
} catch (SQLException e) {
throw new DatabaseException("error getting create", e);
}
}
public String getTempIP() {
return tempIP;
}
public void setTempIP(String s) {
this.tempIP = s;
}
public boolean isLocalhost() {
return ServerConstants.Use_Localhost;
}
public void setUsername(String what) {
this.accountName = what;
}
public int getNextClientIncrenement() {
int result = client_increnement;
client_increnement++;
return result;
}
public void setFarm(MapleFarm farm) {
this.farm = farm;
}
public MapleFarm getFarm() {
if (farm == null) {
return MapleFarm.getDefault(35549721, this, "Creating...");
//MapleFarm farm2 = MapleFarm.getDefault(35549721, this, "AstralMS");
//farm2.setLevel(1);
//return farm2;
}
return farm;
}
}