diff --git a/Server/src/main/java/core/game/content/global/report/AbuseReport.java b/Server/src/main/java/core/game/content/global/report/AbuseReport.java
index 2a6c2f50b..ab7594032 100644
--- a/Server/src/main/java/core/game/content/global/report/AbuseReport.java
+++ b/Server/src/main/java/core/game/content/global/report/AbuseReport.java
@@ -1,6 +1,7 @@
package core.game.content.global.report;
import core.game.node.entity.player.Player;
+import rs09.game.system.command.CommandMapping;
/**
* Represents an abuse report to file.
@@ -45,6 +46,9 @@ public final class AbuseReport {
* @param player the player.
*/
public void construct(Player player, boolean mute) {
+ if (mute) {
+ CommandMapping.INSTANCE.get("mute").attemptHandling(player, new String[] {"mute", victim, "48h"});
+ }
player.getPacketDispatch().sendMessage("Thank-you, your abuse report has been received.");
}
diff --git a/Server/src/main/java/core/game/node/entity/player/Player.java b/Server/src/main/java/core/game/node/entity/player/Player.java
index 60f7aefe5..2c8add884 100644
--- a/Server/src/main/java/core/game/node/entity/player/Player.java
+++ b/Server/src/main/java/core/game/node/entity/player/Player.java
@@ -385,6 +385,7 @@ public class Player extends Entity {
CommunicationInfo.notifyPlayers(this, false, false);
HouseManager.leave(this);
UpdateSequence.getRenderablePlayers().remove(this);
+ details.save();
Repository.getDisconnectionQueue().add(this);
}
@@ -867,7 +868,6 @@ public class Player extends Entity {
if (this.details != null) {
details.setBanTime(this.details.getBanTime());
details.setMuteTime(this.details.getMuteTime());
- details.setIcon(this.details.getIcon());
}
details.getSession().setObject(this);
this.details = details;
diff --git a/Server/src/main/java/core/game/node/entity/player/info/PlayerDetails.java b/Server/src/main/java/core/game/node/entity/player/info/PlayerDetails.java
index 2ec604452..c7f763a82 100644
--- a/Server/src/main/java/core/game/node/entity/player/info/PlayerDetails.java
+++ b/Server/src/main/java/core/game/node/entity/player/info/PlayerDetails.java
@@ -1,9 +1,11 @@
package core.game.node.entity.player.info;
import core.game.node.entity.player.info.portal.Icon;
-import core.game.node.entity.player.info.portal.PlayerSQLManager;
import core.game.system.communication.CommunicationInfo;
import core.net.IoSession;
+import org.jetbrains.annotations.NotNull;
+import rs09.auth.UserAccountInfo;
+import rs09.game.world.GameWorld;
import java.util.concurrent.TimeUnit;
@@ -13,75 +15,27 @@ import java.util.concurrent.TimeUnit;
*
*/
public class PlayerDetails {
-
- /**
- * The sql manager for this account.
- */
- private final PlayerSQLManager sqlManager = new PlayerSQLManager(this);
+ public UserAccountInfo accountInfo = UserAccountInfo.createDefault();
/**
* The communication info.
*/
private final CommunicationInfo communicationInfo = new CommunicationInfo();
+ public int getCredits() {
+ return accountInfo.getCredits();
+ }
+
+ public void setCredits(int amount) {
+ accountInfo.setCredits(amount);
+ }
+
/**
* The unique id info.
*/
private final UIDInfo info = new UIDInfo();
- /**
- * The username of the account.
- */
- private final String username;
-
- /**
- * The password of the account.
- */
- private String password;
-
- /**
- * The unique id of the account.
- */
- private int uid;
-
- /**
- * The account's last game login.
- */
- private long lastLogin = -1;
-
- /**
- * The account's last game login.
- */
- private long timePlayed = 0;
-
- /**
- * The time the player is muted for.
- */
- private long muteTime;
-
- /**
- * The time the player is banned for.
- */
- private long banTime;
-
- /**
- * If the sql data has been parsed.
- */
- private boolean parsed;
-
- /**
- * The rights of the player.
- */
- private Rights rights = Rights.REGULAR_PLAYER;
-
- /**
- * The chat icon value.
- */
- private Icon icon;
-
- public int credits;
-
/**
* Represents the session.
*/
@@ -89,62 +43,10 @@ public class PlayerDetails {
/**
* Constructs a new {@code PlayerDetails}.
- * @param username the username to set.
- * @param password the password to set.
- */
- public PlayerDetails(String username, String password) {
- this.username = username;
- this.password = password;
- this.uid = username.hashCode();
- }
-
- /**
- * Constructs a new {@Code PlayerDetails} {@Code Object}
* @param username the username to set.
*/
public PlayerDetails(String username) {
- this(username, null);
- }
-
- /**
- * Parses the details from the database for this object.
- * @return {@code True} if parsed.
- */
- public boolean parse() {
- if (sqlManager.parse()) {
- return parsed = true;
- }
- return parsed = false;
- }
-
- /**
- * Saves the player's details.
- * @return {@code True} if saved.
- */
- public boolean save() {
- sqlManager.save();
- return true;
- }
-
- /**
- * Gets the player details for an account name.
- * @param username the username.
- * @return the player details.
- */
- public static PlayerDetails getDetails(String username) {
- PlayerDetails details = new PlayerDetails(username);
- if (!details.parse()) {
- return details = null;
- }
- return details;
- }
-
- /**
- * Checks if the ban is permanent.
- * @return {@code True} if so.
- */
- public boolean isPermBan() {
- return TimeUnit.MILLISECONDS.toDays(banTime - System.currentTimeMillis()) > 1000;
+ accountInfo.setUsername(username);
}
/**
@@ -152,7 +54,7 @@ public class PlayerDetails {
* @return {@code True} if so.
*/
public boolean isBanned() {
- return banTime > System.currentTimeMillis();
+ return accountInfo.getBanEndTime() > System.currentTimeMillis();
}
/**
@@ -160,7 +62,7 @@ public class PlayerDetails {
* @return {@code True} if so.
*/
public boolean isPermMute() {
- return TimeUnit.MILLISECONDS.toDays(muteTime - System.currentTimeMillis()) > 1000;
+ return TimeUnit.MILLISECONDS.toDays(accountInfo.getMuteEndTime() - System.currentTimeMillis()) > 1000;
}
/**
@@ -168,15 +70,7 @@ public class PlayerDetails {
* @return {@code True} if so.
*/
public boolean isMuted() {
- return muteTime > System.currentTimeMillis();
- }
-
- /**
- * Gets the sql manager.
- * @return the sql manager.
- */
- public PlayerSQLManager getSqlManager() {
- return sqlManager;
+ return accountInfo.getMuteEndTime() > System.currentTimeMillis();
}
/**
@@ -184,7 +78,7 @@ public class PlayerDetails {
* @return The rights.
*/
public Rights getRights() {
- return rights;
+ return Rights.values()[accountInfo.getRights()];
}
/**
@@ -192,7 +86,7 @@ public class PlayerDetails {
* @param rights The credentials to set.
*/
public void setRights(Rights rights) {
- this.rights = rights;
+ this.accountInfo.setRights(rights.ordinal());
}
/**
@@ -216,7 +110,7 @@ public class PlayerDetails {
* @param password the password.
*/
public void setPassword(final String password) {
- this.password = password;
+ this.accountInfo.setPassword(password);
}
/**
@@ -225,7 +119,7 @@ public class PlayerDetails {
*/
public String getUsername() {
- return username;
+ return this.accountInfo.getUsername();
}
/**
@@ -233,7 +127,7 @@ public class PlayerDetails {
* @return the uid.
*/
public int getUid() {
- return uid;
+ return this.accountInfo.getUid();
}
/**
@@ -241,7 +135,7 @@ public class PlayerDetails {
* @param uid the uid.
*/
public void setUid(int uid) {
- this.uid = uid;
+
}
/**
@@ -249,15 +143,7 @@ public class PlayerDetails {
* @return The password.
*/
public String getPassword() {
- return password;
- }
-
- /**
- * Checks if the details have been parsed.
- * @return {@code True} if parsed.
- */
- public boolean isParsed() {
- return parsed;
+ return this.accountInfo.getPassword();
}
/**
@@ -311,28 +197,12 @@ public class PlayerDetails {
return communicationInfo;
}
- /**
- * Gets the icon.
- * @return the icon.
- */
- public Icon getIcon() {
- return icon;
- }
-
- /**
- * Sets the icon.
- * @param icon the icon to set
- */
- public void setIcon(Icon icon) {
- this.icon = icon;
- }
-
/**
* Gets the lastLogin.
* @return the lastLogin.
*/
public long getLastLogin() {
- return lastLogin;
+ return this.accountInfo.getLastLogin();
}
/**
@@ -340,7 +210,7 @@ public class PlayerDetails {
* @param lastLogin the lastLogin to set
*/
public void setLastLogin(long lastLogin) {
- this.lastLogin = lastLogin;
+ this.accountInfo.setLastLogin(lastLogin);
}
/**
@@ -348,7 +218,7 @@ public class PlayerDetails {
* @return the timePlayed.
*/
public long getTimePlayed() {
- return timePlayed;
+ return this.accountInfo.getTimePlayed();
}
/**
@@ -356,7 +226,7 @@ public class PlayerDetails {
* @param timePlayed the timePlayed to set
*/
public void setTimePlayed(long timePlayed) {
- this.timePlayed = timePlayed;
+ this.accountInfo.setTimePlayed(timePlayed);
}
/**
@@ -364,7 +234,7 @@ public class PlayerDetails {
* @param muteTime the mute time.
*/
public void setMuteTime(long muteTime) {
- this.muteTime = muteTime;
+ this.accountInfo.setMuteEndTime(muteTime);
}
/**
@@ -372,7 +242,7 @@ public class PlayerDetails {
* @return The mute time.
*/
public long getMuteTime() {
- return muteTime;
+ return this.accountInfo.getMuteEndTime();
}
/**
@@ -380,7 +250,7 @@ public class PlayerDetails {
* @return the banTime.
*/
public long getBanTime() {
- return banTime;
+ return this.accountInfo.getBanEndTime();
}
/**
@@ -388,12 +258,17 @@ public class PlayerDetails {
* @param banTime the banTime to set
*/
public void setBanTime(long banTime) {
- this.banTime = banTime;
+ this.accountInfo.setBanEndTime(banTime);
}
- @Override
- public String toString() {
- return "PlayerDetails [sqlManager=" + sqlManager + ", communicationInfo=" + communicationInfo + ", info=" + info + ", username=" + username + ", password=" + password + ", uid=" + uid + ", lastLogin=" + lastLogin + ", muteTime=" + muteTime + ", parsed=" + parsed + ", rights=" + rights + ", icon=" + icon + ", session=" + session + "]";
+ public void save() {
+ if(isBanned()) return;
+ try {
+ GameWorld.getAccountStorage().update(accountInfo);
+ } catch (IllegalStateException ignored) {}
}
+ public static PlayerDetails getDetails(@NotNull String username) {
+ return new PlayerDetails(username);
+ }
}
\ No newline at end of file
diff --git a/Server/src/main/java/core/game/node/entity/player/info/login/LoginConfiguration.java b/Server/src/main/java/core/game/node/entity/player/info/login/LoginConfiguration.java
index 33eb9b3d3..1ed452d73 100644
--- a/Server/src/main/java/core/game/node/entity/player/info/login/LoginConfiguration.java
+++ b/Server/src/main/java/core/game/node/entity/player/info/login/LoginConfiguration.java
@@ -106,7 +106,7 @@ public final class LoginConfiguration {
player.getPacketDispatch().sendString("Discord Invite", 378, 14);
player.getPacketDispatch().sendString("Discord Invite", 378, 129);
player.getPacketDispatch().sendString("Credits", 378, 94);
- player.getPacketDispatch().sendString(player.getDetails().credits + "", 378, 96);
+ player.getPacketDispatch().sendString(player.getDetails().getCredits() + "", 378, 96);
player.getPacketDispatch().sendString(" ", 378, 229);
player.getPacketDispatch().sendString("Want to contribute to 2009scape?
Visit the github using the link below!", 378, 230);
player.getPacketDispatch().sendString(" ", 378, 231);
@@ -266,10 +266,11 @@ public final class LoginConfiguration {
* @return the last login.
*/
public static String getLastLogin(Player player) {
- String lastIp = (String) player.getDetails().getSqlManager().getTable().getColumn("lastGameIp").getValue();
- if (lastIp == null || lastIp == "") {
+ String lastIp = player.getDetails().accountInfo.getLastUsedIp();
+ if (lastIp.equals("")) {
lastIp = player.getDetails().getIpAddress();
}
+ player.getDetails().accountInfo.setLastUsedIp(player.getDetails().getIpAddress());
String string = "You last logged in @timeAgo from: " + lastIp;
long time = player.getDetails().getLastLogin();
Date lastLogin = new Date(time);
diff --git a/Server/src/main/java/core/game/node/entity/player/info/portal/PlayerSQLManager.java b/Server/src/main/java/core/game/node/entity/player/info/portal/PlayerSQLManager.java
deleted file mode 100644
index 41de6532b..000000000
--- a/Server/src/main/java/core/game/node/entity/player/info/portal/PlayerSQLManager.java
+++ /dev/null
@@ -1,236 +0,0 @@
-package core.game.node.entity.player.info.portal;
-
-import core.game.node.entity.player.Player;
-import core.game.node.entity.player.info.PlayerDetails;
-import core.game.node.entity.player.info.Rights;
-import core.game.node.entity.player.info.login.Response;
-import core.game.system.SystemManager;
-import core.game.system.mysql.SQLColumn;
-import core.game.system.mysql.SQLEntryHandler;
-import core.game.system.mysql.SQLManager;
-import core.game.system.mysql.SQLTable;
-import core.game.system.mysql.impl.PlayerSQLHandler;
-import core.net.amsc.WorldCommunicator;
-import rs09.game.ge.GrandExchangeRecords;
-
-import java.math.BigInteger;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-
-/**
- * Manages the SQL aspect for an account.
- * @author Vexia
- *
- */
-public final class PlayerSQLManager {
-
- /**
- * The representation of an account sql table.
- */
- private final SQLTable table = new SQLTable(
- new SQLColumn("UID", Integer.class),
- new SQLColumn("rights", Integer.class),
- new SQLColumn("donatorType", Integer.class),
- new SQLColumn("credits", Integer.class),
- new SQLColumn("icon", Integer.class),
- new SQLColumn("perks", String.class),
- new SQLColumn("ip", String.class, false),
- new SQLColumn("mac", String.class, false),
- new SQLColumn("serial", String.class, false),
- new SQLColumn("computerName", String.class, false),
- new SQLColumn("netWorth", BigInteger.class, false),
- new SQLColumn("ironManMode", String.class, false),
- new SQLColumn("bank", String.class, false),
- new SQLColumn("inventory", String.class, false),
- new SQLColumn("equipment", String.class, false),
- new SQLColumn("ge", String.class, false),
- new SQLColumn("muteTime", Long.class),
- new SQLColumn("banTime", Long.class),
- new SQLColumn("contacts", String.class),
- new SQLColumn("blocked", String.class),
- new SQLColumn("clanName", String.class),
- new SQLColumn("currentClan", String.class),
- new SQLColumn("clanReqs", String.class),
- new SQLColumn("timePlayed", Long.class),
- new SQLColumn("lastLogin", Long.class),
- new SQLColumn("online", Integer.class),
- new SQLColumn("lastGameIp", String.class));
-
- /**
- * The details the SQL manager is managing.
- */
- private final PlayerDetails details;
-
- /**
- * Constructs a new {@Code PlayerSQLManager} {@Code Object}
- * @param details the details.
- */
- public PlayerSQLManager(PlayerDetails details) {
- this.details = details;
- }
-
- /**
- * Parses the SQL table.
- * @return {@code True} if parsed.
- */
- @SuppressWarnings("deprecation")
- public boolean parse() {
- if(!SQLEntryHandler.read(new PlayerSQLHandler(details))) {
- return false;
- }
- details.getCommunication().parse(table);
- details.credits = (int) table.getColumn("credits").getValue();
- details.setBanTime((long) table.getColumn("banTime").getValue());
- details.setMuteTime((long) table.getColumn("muteTime").getValue());
- details.setIcon(Icon.forId((int) table.getColumn("icon").getValue()));
- details.setRights(Rights.forId((int) table.getColumn("rights").getValue()));
- details.setLastLogin(System.currentTimeMillis());
- details.setTimePlayed((long) table.getColumn("timePlayed").getValue());
- return true;
- }
-
- /**
- * Saves the changed SQL columns to the database.
- */
- public void save() {
- SQLEntryHandler.write(new PlayerSQLHandler(details));
- }
-
- /**
- * Updates column values with the player instance.
- * @param player The player instance.
- */
- public void update(Player player) {
- if (!WorldCommunicator.isEnabled()) {
- details.getCommunication().save(table);
- }
- table.getColumn("credits").updateValue(player.getDetails().credits);
- table.getColumn("bank").updateValue(player.getBank().format());
- table.getColumn("lastLogin").updateValue(player.getDetails().getLastLogin());
- table.getColumn("ge").updateValue(GrandExchangeRecords.getInstance(player).format());
- table.getColumn("inventory").updateValue(player.getInventory().format());
- table.getColumn("equipment").updateValue(player.getEquipment().format());
- table.getColumn("netWorth").updateValue(player.getMonitor().getNetworth());
- table.getColumn("lastGameIp").updateValue(player.getDetails().getIpAddress());
- table.getColumn("ironManMode").updateValue(player.getIronmanManager().getMode().name());
- table.getColumn("timePlayed").updateValue(player.getDetails().getTimePlayed() + (System.currentTimeMillis() - player.getDetails().getLastLogin()));
- table.getColumn("ip").updateValue(getAddressLog((String) table.getColumn("ip").getValue(), details.getInfo().getIp()));
- table.getColumn("mac").updateValue(getAddressLog((String) table.getColumn("mac").getValue(), details.getInfo().getMac()));
- table.getColumn("serial").updateValue(getAddressLog((String) table.getColumn("serial").getValue(), details.getInfo().getSerial()));
- table.getColumn("computerName").updateValue(getAddressLog((String) table.getColumn("computerName").getValue(), details.getInfo().getCompName()));
- }
-
- /**
- * Checks if an account was created under a name.
- * @param name the name.
- * @param field the field
- * @return {@code True} if so.
- */
- public static boolean hasSqlAccount(String name, String field) throws SQLException {
- Connection connection = SQLManager.getConnection();
- if (connection == null) {
- return true;
- }
- ResultSet result = null;
- PreparedStatement statement;
- statement = connection.prepareStatement("SELECT * FROM " + "members" + " WHERE " + "" + field + "='" + name.toLowerCase() + "' LIMIT 1");
- result = statement.executeQuery();
- if (result == null || !result.next()) {
- SQLManager.close(connection);
- return false;
- }
- SQLManager.close(connection);
- return true;
- }
-
- public static Response updatePassword(String name, String pass) throws SQLException{
- if (!SQLManager.isInitialized()) {
- return Response.INVALID_CREDENTIALS;
- }
- Connection connection = SQLManager.getConnection();
- if (connection == null) {
- return Response.INVALID_LOGIN_SERVER;
- }
- PreparedStatement statement;
- statement = connection.prepareStatement("UPDATE " + "members" + " SET password='" + pass + "' WHERE " + "username" + "='" + name.toLowerCase() + "' LIMIT 1");
- boolean Success = statement.execute();
- if(Success) return Response.SUCCESSFUL;
- return null;
- }
-
- /**
- * Checks if a username & password are correct.
- * @param name the name.
- * @param pass the pass.
- * @return the response.
- * @throws SQLException the exception if thrown.
- */
- public static Response getCredentialResponse(String name, String pass) throws SQLException {
- if (!SQLManager.isInitialized()) {
- return Response.INVALID_CREDENTIALS;
- }
- Connection connection = SQLManager.getConnection();
- if (connection == null) {
- return Response.INVALID_LOGIN_SERVER;
- }
- ResultSet result = null;
- PreparedStatement statement;
- statement = connection.prepareStatement("SELECT * FROM " + "members" + " WHERE " + "" + "username" + "='" + name.toLowerCase() + "' LIMIT 1");
- result = statement.executeQuery();
- if (result == null || !result.next()) {
- SQLManager.close(connection);
- return Response.INVALID_CREDENTIALS;
- }
- String realPass = result.getString("password");
- if (SystemManager.getEncryption().checkPassword(pass, realPass)) {
- SQLManager.close(connection);
- return Response.SUCCESSFUL;
- }
- SQLManager.close(connection);
- return Response.INVALID_CREDENTIALS;
- }
-
- /**
- * Gets the address log.
- * @param original the original log.
- * @param address the address.
- * @return the address.
- */
- private String getAddressLog(String original, String address) {
- String log = "";
- if (original != null && original.length() > 0) {
- log += original;
- if (log.charAt(log.length() - 1) != '|') {
- log += "|";
- }
- }
- if (address != null && address.length() > 0 && (original == null || !original.contains(address))) {
- log += address + "|";
- }
- if (log.length() > 0 && log.charAt(log.length() - 1) == '|') {
- log = log.substring(0, log.length() - 1);
- }
- if (log == null) {
- log = "";
- }
- return log;
- }
-
- /**
- * Gets the details.
- * @return the details.
- */
- public PlayerDetails getDetails() {
- return details;
- }
-
- /**
- * Gets the table.
- * @return the table.
- */
- public SQLTable getTable() {
- return table;
- }
-}
diff --git a/Server/src/main/java/core/game/system/mysql/SQLEntryHandler.java b/Server/src/main/java/core/game/system/mysql/SQLEntryHandler.java
deleted file mode 100644
index 3da9131de..000000000
--- a/Server/src/main/java/core/game/system/mysql/SQLEntryHandler.java
+++ /dev/null
@@ -1,210 +0,0 @@
-package core.game.system.mysql;
-
-import rs09.game.system.SystemLogger;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-
-/**
- * Handles an SQL entry.
- * @author Emperor
- * @param The entry type.
- */
-public abstract class SQLEntryHandler {
-
- /**
- * The connection.
- */
- protected Connection connection;
-
- /**
- * The entry.
- */
- protected T entry;
-
- /**
- * The result set.
- */
- protected ResultSet result;
-
- /**
- * The table name.
- */
- protected String table;
-
- /**
- * The column name.
- */
- private String column;
-
- /**
- * The value to use.
- */
- protected String value;
-
- /**
- * Constructs a new {@code SQLEntryHandler} {@code Object}.
- * @param entry The entry.
- * @param table The table name.
- * @param column column name.
- * @param value The column value.
- */
- public SQLEntryHandler(T entry, String table, String column, String value) {
- this.entry = entry;
- this.table = table;
- this.column = column;
- this.value = value;
- }
-
- /**
- * Reads the SQL entry.
- * @param entry The entry.
- */
- public static boolean read(SQLEntryHandler> entry) {
- if (!SQLManager.isInitialized()) {
- return false;
- }
- entry.connection = entry.getConnection();
- if (entry.connection == null) {
- SystemLogger.logErr("Could not read SQL data: connection is null!");
- return false;
- }
- boolean success = false;
- try {
- entry.read();
- if (entry.result == null || !entry.result.next()) {
- entry.create();
- } else {
- entry.parse();
- }
- success = true;
- } catch (SQLException e) {
- e.printStackTrace();
- }
- SQLManager.close(entry.connection);
- entry.connection = null;
- return success;
- }
-
- /**
- * Writes the entry data on the SQL database.
- * @param entry The entry.
- */
- public static void write(SQLEntryHandler> entry) {
- write(entry, entry.getConnection(), true);
- }
-
- /**
- * Reads the SQL entry.
- * @param entry The entry.
- */
- public static void write(SQLEntryHandler> entry, Connection connection, boolean commit) {
- if (connection == null) {
- SystemLogger.logErr("Could not write SQL data: connection is null!");
- return;
- }
- entry.connection = connection;
- try {
- if (!commit) {
- entry.connection.setAutoCommit(false);
- }
- entry.save();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- if (commit) {
- SQLManager.close(entry.connection);
- }
- entry.connection = null;
- }
-
- /**
- * Reads the table from the SQL database.
- * @throws SQLException When an SQL exception occurs.
- */
- public void read() throws SQLException {
- PreparedStatement statement = connection.prepareStatement("SELECT " + getReadSelection() + " FROM " + table + " WHERE " + column + " = ?");
- statement.setString(1, value);
- result = statement.executeQuery();
- }
-
- /**
- * Gets the result.
- * @return the result.
- */
- public ResultSet getResult() {
- return result;
- }
-
- /**
- * Gets the selection.
- * @return The selection.
- */
- public String getReadSelection() {
- return "*";
- }
-
- /**
- * Gets the writing statement.
- * @param create If we are creating a new row.
- * @param columns The columns to update.
- */
- public PreparedStatement getWritingStatement(boolean create, String... columns) {
- PreparedStatement statement = null;
- try {
- StringBuilder sb = new StringBuilder();
- if (create) {
- sb.append("INSERT INTO ").append(table).append(" (").append(column);
- for (String name : columns) {
- sb.append(",").append(name);
- }
- sb.append(") VALUES (?");
- for (int i = 0; i < columns.length; i++) {
- sb.append(",?");
- }
- sb.append(")");
- } else {
- sb.append("UPDATE ").append(table).append(" SET ");
- for (int i = 0; i < columns.length; i++) {
- sb.append(columns[i]).append("=?");
- if (i < columns.length - 1) {
- sb.append(",");
- }
- }
- sb.append(" WHERE ").append(column).append("=?");
- }
- statement = connection.prepareStatement(sb.toString());
- statement.setString(create ? 1 : columns.length + 1, value);
- } catch (SQLException e) {
- e.printStackTrace();
- }
- return statement;
- }
-
- /**
- * Parses the entry.
- * @throws SQLException When an SQL exception occurs.
- */
- public abstract void parse() throws SQLException;
-
- /**
- * Creates a new row in the database.
- * @throws SQLException When an SQL exception occurs.
- */
- public abstract void create() throws SQLException;
-
- /**
- * Saves the entry.
- * @throws SQLException When an SQL exception occurs.
- */
- public abstract void save() throws SQLException;
-
- /**
- * Gets the connection.
- * @return The connection.
- */
- public abstract Connection getConnection();
-
-}
\ No newline at end of file
diff --git a/Server/src/main/java/core/game/system/mysql/impl/HighscoreSQLHandler.java b/Server/src/main/java/core/game/system/mysql/impl/HighscoreSQLHandler.java
deleted file mode 100644
index e2d48807a..000000000
--- a/Server/src/main/java/core/game/system/mysql/impl/HighscoreSQLHandler.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package core.game.system.mysql.impl;
-
-import rs09.ServerConstants;
-import core.game.node.entity.skill.Skills;
-import core.game.node.entity.player.Player;
-import core.game.node.entity.player.info.Rights;
-import core.game.system.mysql.SQLEntryHandler;
-import core.game.system.mysql.SQLManager;
-import rs09.game.world.GameWorld;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-
-/**
- * Handles the sql handler.
- * @author Vexia
- */
-public final class HighscoreSQLHandler extends SQLEntryHandler {
-
- /**
- * Constructs a new {@code HighscoreSQLHandler} {@code Object}.
- */
- public HighscoreSQLHandler(Player entry) {
- super(entry, (SQLManager.LOCAL ? "global" : ServerConstants.DATABASE_NAME) + ".highscores", "username", entry.getName());
- }
-
- @Override
- public void parse() throws SQLException {
-
- }
-
- @Override
- public void create() throws SQLException {
- if (entry.getDetails().getRights() == Rights.ADMINISTRATOR || GameWorld.getSettings().isDevMode()) {
- return;
- }
- StringBuilder b = new StringBuilder("INSERT highscores(username,overall_xp,total_level,ironManMode,xp_0,xp_1,xp_2,xp_3,xp_4,xp_5,xp_6,xp_7,xp_8,xp_9,xp_10,xp_11,xp_12,xp_13,xp_14,xp_15,xp_16,xp_17,xp_18,xp_19,xp_20,xp_21,xp_22,xp_23) ");
- b.append("VALUES('" + value + "', '" + entry.getSkills().getTotalXp() + "', '" + entry.getSkills().getTotalLevel() + "', '" + entry.getIronmanManager().getMode().name() + "', ");
- int xp;
- for (int i = 0; i < Skills.SKILL_NAME.length; i++) {
- xp = (int) entry.getSkills().getExperience(i);
- b.append("'" + xp + "'" + (i == Skills.SKILL_NAME.length - 1 ? "" : ","));
- }
- b.append(")");
- PreparedStatement statement = connection.prepareStatement(b.toString());
- statement.executeUpdate();
- SQLManager.close(statement.getConnection());
- }
-
- @Override
- public void save() throws SQLException {
- if (entry.getDetails().getRights() == Rights.ADMINISTRATOR || GameWorld.getSettings().isDevMode()) {
- return;
- }
- super.read();
- if (result == null || !result.next()) {
- create();
- return;
- }
- if (entry.getSavedData().getActivityData().getHardcoreDeath() == true){
- //Update the SQL table to indicate the player was a Hardcore ironman that died, do not update hiscores
- StringBuilder b = new StringBuilder("UPDATE highscores SET ironManMode='HARDCORE_DEAD' WHERE username ='" + value + "'");
- PreparedStatement statement = connection.prepareStatement(b.toString());
- statement.executeUpdate();
- SQLManager.close(statement.getConnection());
- return;
- }
- StringBuilder b = new StringBuilder("UPDATE highscores SET overall_xp='" + entry.getSkills().getTotalXp() + "', total_level='" + entry.getSkills().getTotalLevel() + "', ironManMode='" + entry.getIronmanManager().getMode().name() + "', ");
- int xp;
- for (int i = 0; i < Skills.SKILL_NAME.length; i++) {
- xp = (int) entry.getSkills().getExperience(i);
- b.append("xp_" + i + "='" + xp + "'" + (i == Skills.SKILL_NAME.length - 1 ? "" : ","));
- }
- b.append("WHERE username='" + value + "'");
- PreparedStatement statement = connection.prepareStatement(b.toString());
- statement.executeUpdate();
- SQLManager.close(statement.getConnection());
- }
-
- /**
- * Sets the data.
- * @param statement the statement.
- * @param startIndex the start index.
- * @throws SQLException the exception.
- */
- public void setData(PreparedStatement statement, int startIndex) throws SQLException {
- for (int i = 0; i < Skills.SKILL_NAME.length; i++) {
- statement.setInt(startIndex + i, entry.getSkills().getStaticLevel(i));
- }
- }
-
- @Override
- public Connection getConnection() {
- return SQLManager.getConnection();
- }
-
-}
\ No newline at end of file
diff --git a/Server/src/main/java/core/game/system/mysql/impl/PlayerLogSQLHandler.java b/Server/src/main/java/core/game/system/mysql/impl/PlayerLogSQLHandler.java
deleted file mode 100644
index d0f2a53c9..000000000
--- a/Server/src/main/java/core/game/system/mysql/impl/PlayerLogSQLHandler.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package core.game.system.mysql.impl;
-
-import rs09.ServerConstants;
-import core.game.system.monitor.MessageLog;
-import core.game.system.monitor.PlayerMonitor;
-import core.game.system.mysql.SQLEntryHandler;
-import core.game.system.mysql.SQLManager;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * SQL Entry handler to log player's messages, duplications, & addresses.
- * @author Vexia
- *
- */
-public class PlayerLogSQLHandler extends SQLEntryHandler {
-
- /**
- * The column names.
- */
- private static final String[] MESSAGE_COLUMNS = new String[] { "public_chat", "private_chat", "clan_chat", "address_log", "command_log", "trade_log", "ge_log", "duel_log" };
-
- /**
- * Constructs a new {@Code PlayerLogSQLHandler} {@Code Object}
- * @param entry the player monitor entry.
- */
- public PlayerLogSQLHandler(PlayerMonitor entry, String playerName) {
- super(entry, (SQLManager.LOCAL ? "global" : ServerConstants.DATABASE_NAME) + ".player_logs", "username", playerName);
- }
-
- @Override
- public void parse() throws SQLException {
- }
-
- @Override
- public void create() throws SQLException {
- }
-
- @Override
- public void save() throws SQLException {
- Connection connection = getConnection();
- if (connection.prepareStatement("SELECT * FROM " + table + " WHERE username='" + value + "' LIMIT 1").executeQuery().next()) {
- String b = "SET ";
- int size = 0;
- List columns = new ArrayList<>(20);
- for (int i = 0; i < MESSAGE_COLUMNS.length; i++) {
- if (!entry.getLogs()[i].getMessages().isEmpty()) {
- b += MESSAGE_COLUMNS[i] + " = CONCAT(" + MESSAGE_COLUMNS[i] + ", ?)" + (i == MESSAGE_COLUMNS.length - 1 ? "" : ",");
- size++;
- columns.add(i);
- }
- }
- if (!entry.getDuplicationLog().getMessages().isEmpty()) {
- b += (b.charAt(b.length() - 1) != ',' ? "," : "") + "duplication_log = CONCAT(duplication_log, ?)";
- }
- if (b.charAt(b.length() - 1) == ',') {
- b = b.substring(0, b.length() - 1);
- }
- PreparedStatement statement = connection.prepareStatement("UPDATE " + table + " " + b + " WHERE username='" + value + "'");
- for (int i = 0; i < size; i++) {
- writeLog(statement, 1 + i, columns.get(i));
- }
- if (!entry.getDuplicationLog().getMessages().isEmpty()) {
- String log = "";
- MessageLog messageLog = entry.getDuplicationLog();
- for (int i = 0; i < messageLog.getMessages().size(); i++) {
- if (log.contains(messageLog.getMessages().get(i))) {
- continue;
- }
- log += messageLog.getMessages().get(i) + "\n";
- }
- statement.setString(size + 1, log);
- }
- //statement.executeUpdate();
- } else {
- PreparedStatement statement = connection.prepareStatement("INSERT INTO " + table + " (username,public_chat,private_chat,clan_chat,address_log,command_log,trade_log,ge_log,duel_log,duplication_log) VALUES(?,?,?,?,?,?,?,?,?,?)");
- statement.setString(1, value);
- for (int i = 0; i < MESSAGE_COLUMNS.length; i++) {
- //writeLog(statement, 2 + i, i);
- }
- String log = "";
- MessageLog messageLog = entry.getDuplicationLog();
- for (int i = 0; i < messageLog.getMessages().size(); i++) {
- if (log.contains(messageLog.getMessages().get(i))) {
- continue;
- }
- log += messageLog.getMessages().get(i) + "\n";
- }
- statement.setString(10, log);
- //statement.executeUpdate();
- }
- entry.clear();
- SQLManager.close(connection);
- }
-
- /**
- * Writes a message log to the prepared statement.
- * @param statement the statement.
- * @param columnIndex the column index.
- * @param logIndex the log index.
- * @throws SQLException the exception if thrown.
- */
- private void writeLog(PreparedStatement statement, int columnIndex, int logIndex) throws SQLException {
- String log = "";
- MessageLog messageLog = entry.getLogs()[logIndex];
- for (int i = 0; i < messageLog.getMessages().size(); i++) {
- log += messageLog.getMessages().get(i) + "\n";
- }
- statement.setString(columnIndex, log);
- }
-
- @Override
- public Connection getConnection() {
- return SQLManager.getConnection();
- }
-
-}
diff --git a/Server/src/main/java/core/game/system/mysql/impl/PlayerSQLHandler.java b/Server/src/main/java/core/game/system/mysql/impl/PlayerSQLHandler.java
deleted file mode 100644
index d6acf22cd..000000000
--- a/Server/src/main/java/core/game/system/mysql/impl/PlayerSQLHandler.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package core.game.system.mysql.impl;
-
-import rs09.ServerConstants;
-import core.game.node.entity.player.info.PlayerDetails;
-import core.game.system.mysql.SQLColumn;
-import core.game.system.mysql.SQLEntryHandler;
-import core.game.system.mysql.SQLManager;
-
-import java.math.BigInteger;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.sql.Timestamp;
-import java.util.List;
-
-/**
- * Handles player details parsing/saving using SQL.
- * @author Emperor
- */
-public final class PlayerSQLHandler extends SQLEntryHandler {
-
- /**
- * Constructs a new {@code PlayerSQLHandler} {@code Object}.
- * @param entry The player details.
- */
- public PlayerSQLHandler(PlayerDetails entry) {
- super(entry, (SQLManager.LOCAL ? "global" : ServerConstants.DATABASE_NAME) + ".members", "username", entry.getUsername());
- }
-
- @Override
- public void parse() throws SQLException {
- for (SQLColumn column : entry.getSqlManager().getTable().getColumns()) {
- if (!column.isParse()) {
- continue;
- }
- if (column.getType() == Integer.class) {
- column.setValue(result.getInt(column.getName()));
- } else if (column.getType() == String.class) {
- column.setValue(result.getString(column.getName()));
- } else if (column.getType() == Boolean.class) {
- column.setValue(result.getBoolean(column.getName()));
- } else if (column.getType() == Long.class) {
- column.setValue(result.getLong(column.getName()));
- } else if (column.getType() == Timestamp.class) {
- column.setValue(result.getTimestamp(column.getName()));
- } else if (column.getType() == BigInteger.class) {
- column.setValue(result.getLong(column.getName()));
- }
- }
- }
-
- @Override
- public String getReadSelection() {
- StringBuilder selection = new StringBuilder();
- boolean first = true;
- for (SQLColumn column : entry.getSqlManager().getTable().getColumns()) {
- if (!first) {
- selection.append(",");
- } else {
- first = false;
- }
- selection.append(column.getName());
- }
- return selection.toString();
- }
-
- @Override
- public void create() throws SQLException {
- if (entry == null) {
- return;
- }
- String[] names = new String[entry.getSqlManager().getTable().getColumns().length];
- for (int i = 0; i < entry.getSqlManager().getTable().getColumns().length; i++) {
- names[i] = entry.getSqlManager().getTable().getColumns()[i].getName();
- }
- PreparedStatement statement = getWritingStatement(true, names);
- for (int i = 0; i < entry.getSqlManager().getTable().getColumns().length; i++) {
- SQLColumn column = entry.getSqlManager().getTable().getColumns()[i];
- if (column.getType() == Integer.class) {
- statement.setInt(i + 2, (int) column.getValue());
- } else if (column.getType() == String.class) {
- statement.setString(i + 2, (String) column.getValue());
- } else if (column.getType() == Boolean.class) {
- statement.setBoolean(i + 2, (boolean) column.getValue());
- } else if (column.getType() == Long.class) {
- statement.setLong(i + 2, (Long) column.getValue());
- } else if (column.getType() == Timestamp.class) {
- statement.setTimestamp(i + 2, (Timestamp) column.getValue());
- } else if (column.getType() == java.math.BigInteger.class) {
- statement.setLong(i + 2, (Long) column.getValue());
- }
- }
- statement.executeUpdate();
- SQLManager.close(statement.getConnection());
- }
-
- @Override
- public void save() throws SQLException {
- List updated = entry.getSqlManager().getTable().getChanged();
- if (updated.isEmpty()) {
- return;
- }
- String[] names = new String[updated.size()];
- for (int i = 0; i < names.length; i++) {
- names[i] = updated.get(i).getName();
- }
- PreparedStatement statement = getWritingStatement(false, names);
- for (int i = 0; i < updated.size(); i++) {
- SQLColumn column = updated.get(i);
- if (column.getType() == Integer.class) {
- statement.setInt(i + 1, (int) column.getValue());
- } else if (column.getType() == String.class) {
- statement.setString(i + 1, (String) column.getValue());
- } else if (column.getType() == Boolean.class) {
- statement.setBoolean(i + 1, (boolean) column.getValue());
- } else if (column.getType() == Long.class) {
- statement.setLong(i + 1, (Long) column.getValue());
- } else if (column.getType() == Timestamp.class) {
- statement.setTimestamp(i + 1, (Timestamp) column.getValue());
- } else if (column.getType() == BigInteger.class) {
- statement.setLong(i + 1, (Long) column.getValue());
- }
- }
- statement.executeUpdate();
- SQLManager.close(statement.getConnection());
- }
-
- @Override
- public Connection getConnection() {
- return SQLManager.getConnection();
- }
-
-}
\ No newline at end of file
diff --git a/Server/src/main/java/core/net/IoSession.java b/Server/src/main/java/core/net/IoSession.java
index f3b8d5f1f..1f2e74078 100644
--- a/Server/src/main/java/core/net/IoSession.java
+++ b/Server/src/main/java/core/net/IoSession.java
@@ -5,6 +5,7 @@ import core.game.node.entity.player.Player;
import core.game.node.entity.player.info.ClientInfo;
import core.game.node.entity.player.info.login.Response;
import core.game.system.task.Pulse;
+import rs09.auth.AuthResponse;
import rs09.game.world.GameWorld;
import core.net.producer.HSEventProducer;
import core.net.producer.LoginEventProducer;
@@ -142,7 +143,7 @@ public class IoSession {
if (context == null) {
throw new IllegalStateException("Invalid writing context!");
}
- if (!(context instanceof Response) && producer instanceof LoginEventProducer) {
+ if (!(context instanceof AuthResponse) && producer instanceof LoginEventProducer) {
// new Throwable().printStackTrace();
return;
}
diff --git a/Server/src/main/java/core/net/amsc/MSPacketRepository.java b/Server/src/main/java/core/net/amsc/MSPacketRepository.java
index 0917a4281..8e7ca3a4f 100644
--- a/Server/src/main/java/core/net/amsc/MSPacketRepository.java
+++ b/Server/src/main/java/core/net/amsc/MSPacketRepository.java
@@ -16,6 +16,7 @@ import core.net.packet.context.MessageContext;
import core.net.packet.out.CommunicationMessage;
import core.net.packet.out.ContactPackets;
import core.net.packet.out.UpdateClanChat;
+import rs09.auth.AuthResponse;
import rs09.game.node.entity.player.info.login.LoginParser;
import rs09.game.system.SystemLogger;
import rs09.game.world.GameWorld;
@@ -306,17 +307,17 @@ public final class MSPacketRepository {
LoginParser parser = WorldCommunicator.finishLoginAttempt(username);
if (parser != null) {
PlayerDetails details = parser.getDetails();
- Response response = Response.get(opcode);
+ AuthResponse response = AuthResponse.values()[opcode];
Player player = null;
switch (response) {
- case ALREADY_ONLINE:
+ case AlreadyOnline:
player = Repository.getPlayerByName(username);
if (player == null || player.getSession().isActive() || !player.getSession().getAddress().equals(details.getSession().getAddress())) {
details.getSession().write(response, true);
break;
}
player.getPacketDispatch().sendLogout();
- case SUCCESSFUL:
+ case Success:
if (!details.getSession().isActive()) {
sendPlayerRemoval(username);
break;
@@ -326,10 +327,10 @@ public final class MSPacketRepository {
} else {
player.updateDetails(details);
}
- parser.initialize(player, response == Response.ALREADY_ONLINE);
+ parser.initialize(player, response == AuthResponse.AlreadyOnline);
break;
- case MOVING_WORLD:
+ case MovingWorld:
details.getSession().setServerKey(buffer.get());
default:
details.getSession().write(response, true);
diff --git a/Server/src/main/java/core/net/amsc/WorldCommunicator.java b/Server/src/main/java/core/net/amsc/WorldCommunicator.java
index cce4f911e..56a5f4fdb 100644
--- a/Server/src/main/java/core/net/amsc/WorldCommunicator.java
+++ b/Server/src/main/java/core/net/amsc/WorldCommunicator.java
@@ -8,6 +8,7 @@ import core.net.IoSession;
import core.net.NioReactor;
import core.net.producer.MSHSEventProducer;
import kotlin.Unit;
+import rs09.auth.AuthResponse;
import rs09.game.node.entity.player.info.login.LoginParser;
import rs09.game.system.SystemLogger;
import rs09.game.world.GameWorld;
@@ -73,15 +74,11 @@ public final class WorldCommunicator {
public static void register(final LoginParser parser) {
LoginParser p = loginAttempts.get(parser.getDetails().getUsername());
if (p != null && GameWorld.getTicks() - p.getTimeStamp() < 50 && p.getDetails().getRights() == Rights.REGULAR_PLAYER) {
- parser.getDetails().getSession().write(Response.ALREADY_ONLINE, true);
+ parser.getDetails().getSession().write(AuthResponse.AlreadyOnline, true);
return;
}
loginAttempts.put(parser.getDetails().getUsername(), parser);
TaskExecutor.executeSQL(() -> {
- if (!parser.getDetails().parse()) {
- parser.getDetails().getSession().write(Response.INVALID_LOGIN_SERVER, true);
- return Unit.INSTANCE;
- }
MSPacketRepository.sendPlayerRegistry(parser);
return Unit.INSTANCE;
});
diff --git a/Server/src/main/java/core/net/event/LoginWriteEvent.java b/Server/src/main/java/core/net/event/LoginWriteEvent.java
index 95b4e61c8..793477da9 100644
--- a/Server/src/main/java/core/net/event/LoginWriteEvent.java
+++ b/Server/src/main/java/core/net/event/LoginWriteEvent.java
@@ -6,6 +6,7 @@ import core.net.EventProducer;
import core.net.IoSession;
import core.net.IoWriteEvent;
import core.net.producer.GameEventProducer;
+import rs09.auth.AuthResponse;
import java.nio.ByteBuffer;
@@ -31,10 +32,10 @@ public final class LoginWriteEvent extends IoWriteEvent {
@Override
public void write(IoSession session, Object context) {
- Response response = (Response) context;
+ AuthResponse response = (AuthResponse) context;
ByteBuffer buffer = ByteBuffer.allocate(500);
- buffer.put((byte) response.opcode());
- switch (response.opcode()) {
+ buffer.put((byte) response.ordinal());
+ switch (response.ordinal()) {
case 2: //successful login
buffer.put(getWorldResponse(session));
session.setProducer(GAME_PRODUCER);
diff --git a/Server/src/main/java/core/net/packet/in/ReportAbusePacket.java b/Server/src/main/java/core/net/packet/in/ReportAbusePacket.java
index 466f65e15..b13f9544b 100644
--- a/Server/src/main/java/core/net/packet/in/ReportAbusePacket.java
+++ b/Server/src/main/java/core/net/packet/in/ReportAbusePacket.java
@@ -7,6 +7,7 @@ import core.game.node.entity.player.Player;
import core.net.packet.IncomingPacket;
import core.net.packet.IoBuffer;
import core.tools.StringUtils;
+import rs09.game.world.GameWorld;
import java.io.File;
@@ -21,8 +22,7 @@ public class ReportAbusePacket implements IncomingPacket {
String target = StringUtils.longToString(buffer.getLong());
Rule rule = Rule.forId(buffer.get());
boolean mute = buffer.get() == 1;
- File file = new File(ServerConstants.PLAYER_SAVE_PATH + target + ".save");
- if (!file.exists()) {
+ if (!GameWorld.getAccountStorage().checkUsernameTaken(target.toLowerCase())) {
player.getPacketDispatch().sendMessage("Invalid player name.");
return;
}
diff --git a/Server/src/main/java/core/net/registry/AccountRegister.java b/Server/src/main/java/core/net/registry/AccountRegister.java
index 7fc98a9a5..4497da838 100644
--- a/Server/src/main/java/core/net/registry/AccountRegister.java
+++ b/Server/src/main/java/core/net/registry/AccountRegister.java
@@ -1,21 +1,17 @@
package core.net.registry;
import core.cache.misc.buffer.ByteBufferUtils;
-import core.game.node.entity.player.info.portal.PlayerSQLManager;
-import core.game.system.SystemManager;
-import core.game.system.mysql.SQLEntryHandler;
-import core.game.system.mysql.SQLManager;
-import core.game.system.task.TaskExecutor;
+import core.game.system.task.Pulse;
import core.net.Constants;
import core.net.IoSession;
-import kotlin.Unit;
import rs09.ServerConstants;
+import rs09.auth.UserAccountInfo;
import rs09.game.system.SystemLogger;
import rs09.game.world.GameWorld;
import rs09.net.event.LoginReadEvent;
+import rs09.net.packet.in.Login;
import java.nio.ByteBuffer;
-import java.sql.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -24,31 +20,12 @@ import java.util.regex.Pattern;
* @author Vexia
*
*/
-public class AccountRegister extends SQLEntryHandler {
-
- /**
- * The table name.
- */
- private static final String TABLE = "members";
-
- /**
- * The column name.
- */
- private static final String COLUMN = "username";
-
+public class AccountRegister {
/**
* The pattern compiler.
*/
private static final Pattern PATTERN = Pattern.compile("[a-z0-9_]{1,12}");
- /**
- * Constructs a new {@Code AccountRegister} {@Code Object}
- * @param entry The registry entry.
- */
- public AccountRegister(RegistryDetails entry) {
- super(entry, TABLE, COLUMN, entry.getUsername());
- }
-
/**
* Reads the incoming opcode of an account register.
* @param session the session.
@@ -57,6 +34,7 @@ public class AccountRegister extends SQLEntryHandler {
*/
public static void read(final IoSession session, int opcode, ByteBuffer buffer) {
int day,month,year,country;
+ UserAccountInfo info = UserAccountInfo.createDefault();
switch (opcode) {
case 147://details
day = buffer.get();
@@ -67,33 +45,29 @@ public class AccountRegister extends SQLEntryHandler {
break;
case 186://username
final String username = ByteBufferUtils.getString(buffer).replace(" ", "_").toLowerCase().replace("|", "");
+ info.setUsername(username);
if (username.length() <= 0 || username.length() > 12) {
response(session, RegistryResponse.INVALID_USERNAME);
break;
}
- if (!validUsername(username)) {
+ if (invalidUsername(username)) {
System.out.println("AHAHHA " + username);
response(session,RegistryResponse.INVALID_USERNAME);
break;
}
System.out.println(username);
- TaskExecutor.executeSQL(() -> {
- try {
- if (PlayerSQLManager.hasSqlAccount(username, "username")) {
- response(session, RegistryResponse.NOT_AVAILBLE_USER);
- return Unit.INSTANCE;
- }
- response(session, RegistryResponse.SUCCESS);
- } catch (SQLException e) {
- e.printStackTrace();
- }
- return Unit.INSTANCE;
- });
+ if (!GameWorld.getAuthenticator().canCreateAccountWith(info)) {
+ response(session, RegistryResponse.NOT_AVAILBLE_USER);
+ return;
+ }
+ response(session, RegistryResponse.SUCCESS);
break;
case 36://Register details
+ SystemLogger.logInfo("Made it to final stage");
buffer.get(); //Useless size being written that is already written in the RSA block
- buffer = LoginReadEvent.getRSABlock(buffer);
+ buffer = Login.decryptRSABuffer(buffer, ServerConstants.EXPONENT, ServerConstants.MODULUS);
if(buffer.get() != 10){ //RSA header (aka did this decrypt properly)
+ SystemLogger.logInfo("Decryption failed during registration :(");
response(session, RegistryResponse.CANNOT_CREATE);
break;
}
@@ -106,6 +80,8 @@ public class AccountRegister extends SQLEntryHandler {
final String name = ByteBufferUtils.getString(buffer).replace(" ", "_").toLowerCase().replace("|", "");
buffer.getInt();
String password = ByteBufferUtils.getString(buffer);
+ info.setUsername(name);
+ info.setPassword(password);
if (password.length() < 5 || password.length() > 20) {
response(session, RegistryResponse.INVALID_PASS_LENGTH);
break;
@@ -114,7 +90,7 @@ public class AccountRegister extends SQLEntryHandler {
response(session, RegistryResponse.PASS_SIMILAR_TO_USER);
break;
}
- if (!validUsername(name)) {
+ if (invalidUsername(name)) {
response(session, RegistryResponse.INVALID_USERNAME);
break;
}
@@ -126,21 +102,17 @@ public class AccountRegister extends SQLEntryHandler {
year = buffer.getShort();
country = buffer.getShort();
buffer.getInt();
- @SuppressWarnings("deprecation")
- final RegistryDetails details = new RegistryDetails(name, SystemManager.getEncryption().hashPassword(password), new Date(year, month, day), country);
- TaskExecutor.execute(() -> {
- try {
- if (PlayerSQLManager.hasSqlAccount(name, "username")) {
- response(session, RegistryResponse.CANNOT_CREATE);
- return Unit.INSTANCE;
- }
- SQLEntryHandler.write(new AccountRegister(details));
+ if (!GameWorld.getAuthenticator().canCreateAccountWith(info)) {
+ response(session, RegistryResponse.CANNOT_CREATE);
+ return;
+ }
+ GameWorld.getAuthenticator().createAccountWith(info);
+ GameWorld.getPulser().submit(new Pulse() {
+ @Override
+ public boolean pulse() {
response(session, RegistryResponse.SUCCESS);
- } catch (SQLException e) {
- e.printStackTrace();
- response(session, RegistryResponse.CANNOT_CREATE);
+ return true;
}
- return Unit.INSTANCE;
});
break;
default:
@@ -149,28 +121,6 @@ public class AccountRegister extends SQLEntryHandler {
}
}
- @Override
- public void create() throws SQLException {}
-
- @Override
- public void parse() throws SQLException {}
-
- @Override
- public void save() throws SQLException {
- PreparedStatement statement = getWritingStatement(true, "password", "salt", "birthday", "countryCode", "joined_date","currentClan");
- statement.setString(1, entry.getUsername());
- statement.setString(2, entry.getPassword());
- statement.setString(3, entry.getPassword().substring(0, 29));
- statement.setDate(4, entry.getBirth());
- statement.setInt(5, entry.getCountry());
- statement.setTimestamp(6, new Timestamp(System.currentTimeMillis()));
-
- statement.setString(7, GameWorld.getSettings().getEnable_default_clan() ? ServerConstants.SERVER_NAME.toLowerCase(): null);
-
- statement.executeUpdate();
- SQLManager.close(statement.getConnection());
- }
-
/**
* Sends a registry response code.
* @param response the response.
@@ -178,22 +128,15 @@ public class AccountRegister extends SQLEntryHandler {
private static void response(IoSession session, RegistryResponse response) {
ByteBuffer buf = ByteBuffer.allocate(100);
buf.put((byte) response.getId());
- session.queue((ByteBuffer) buf.flip());
+ session.queue(buf.flip());
}
/**
* Checks if a username is valid.
* @return {@code True} if so.
*/
- public static boolean validUsername(final String username) {
+ public static boolean invalidUsername(final String username) {
Matcher matcher = PATTERN.matcher(username);
- return matcher.matches();
-
+ return !matcher.matches();
}
-
- @Override
- public Connection getConnection() {
- return SQLManager.getConnection();
- }
-
}
diff --git a/Server/src/main/java/core/tools/PlayerLoader.java b/Server/src/main/java/core/tools/PlayerLoader.java
index baebf05a9..ee37c855a 100644
--- a/Server/src/main/java/core/tools/PlayerLoader.java
+++ b/Server/src/main/java/core/tools/PlayerLoader.java
@@ -19,8 +19,8 @@ public final class PlayerLoader {
* @return the player.
*/
public static Player getPlayerFile(String name) {
- final PlayerDetails playerDetails = new PlayerDetails(name, "");
- playerDetails.parse();
+ final PlayerDetails playerDetails = new PlayerDetails(name);
+ //playerDetails.parse();
final Player player = new Player(playerDetails);
PlayerParser.parse(player);
// GameWorld.getWorld().getAccountService().loadPlayer(player);
@@ -34,8 +34,8 @@ public final class PlayerLoader {
* @return the details
*/
public static PlayerDetails getPlayerDetailFile(String name) {
- final PlayerDetails playerDetails = new PlayerDetails(name, "");
- playerDetails.parse();
+ final PlayerDetails playerDetails = new PlayerDetails(name);
+ // playerDetails.parse();
return playerDetails;
}
}
diff --git a/Server/src/main/java/core/tools/mysql/DatabaseManager.java b/Server/src/main/java/core/tools/mysql/DatabaseManager.java
deleted file mode 100644
index a3210ccaa..000000000
--- a/Server/src/main/java/core/tools/mysql/DatabaseManager.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package core.tools.mysql;
-
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.HashMap;
-import java.util.Map;
-
-import rs09.ServerConstants;
-import rs09.game.system.SystemLogger;
-
-public class DatabaseManager {
-
- private Map connections = new HashMap<>();
- private Map databases;
-
- private Database db;
- private boolean connected;
-
- public DatabaseManager(Database db) {
- this.db = db;
- }
-
- public DatabaseManager connect() {
- try {
- Class.forName("com.mysql.cj.jdbc.Driver");
- Connection connection = DriverManager.getConnection("jdbc:mysql://" + db.host() + "/" + db.name() + "?useTimezone=true&serverTimezone=UTC", db.username(), db.password());
- connections.put(db.name(), connection);
-
- SystemLogger.logInfo("Successfully connected with '" + db.name() + "'.");
-
- this.connected = true;
-
-
- } catch (SQLException e) {
- SystemLogger.logErr("Couldn't connect to the database.");
- e.printStackTrace();
- ServerConstants.MYSQL = false;
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- return this;
- }
-
- public ResultSet query(String name, String query) {
-
- try {
-
- Connection connection = connections().get(name);
-
- if (connection == null)
- return null;
-
- Statement statement = connection.createStatement();
-
- return statement.executeQuery(query);
-
- } catch (SQLException e) {
- e.printStackTrace();
- }
-
- return null;
- }
-
- public int update(String name, String query) {
-
- try {
-
- Connection connection = connections().get(name);
-
- if (connection == null)
- return -1;
-
- Statement statement = connection.createStatement();
-
- return statement.executeUpdate(query);
-
- } catch (SQLException e) {
- e.printStackTrace();
- }
-
- return -1;
- }
-
- public Map connections() {
- return connections;
- }
-
- public Map databases() {
- return databases;
- }
-
- public boolean isConnected() {
- return connected;
- }
-
-}
diff --git a/Server/src/main/java/rs09/game/ai/resource/ResourceAIPManager.java b/Server/src/main/java/rs09/game/ai/resource/ResourceAIPManager.java
index 3bc015317..0b62143ad 100644
--- a/Server/src/main/java/rs09/game/ai/resource/ResourceAIPManager.java
+++ b/Server/src/main/java/rs09/game/ai/resource/ResourceAIPManager.java
@@ -57,21 +57,6 @@ public class ResourceAIPManager {
}
public ResourceAIPManager load(Player player) {
- try {
- Statement statement = GameWorld.getDatabaseManager().connections().get("global").createStatement();
-
- ResultSet result = statement.executeQuery("SELECT * FROM `members` WHERE username='" + player.getUsername() + "'");
- // Results result = new Results(GameWorld.getDatabaseManager().query("global", "SELECT * FROM `members` WHERE username='" + player.getUsername() + "'"));
-
- while (result.next()) {
- String eventName = result.getString("taskName");
- String eventTime = result.getString("taskTime");
- reActivate(eventName, Long.valueOf(eventTime));
- }
-
- } catch (Exception e) {
- e.printStackTrace();
- }
return this;
}
@@ -87,7 +72,6 @@ public class ResourceAIPManager {
StringBuilder query = new StringBuilder();
query.append("UPDATE `members` SET `taskName`='" + entry.getKey().getTaskName() + "',`taskTime`='" + entry.getValue() + "' WHERE `username`='" + player.getUsername() + "'");
System.out.println("ResourceAIPManager: " + query.toString());
- GameWorld.getDatabaseManager().update("global", query.toString());
}
return this;
diff --git a/Server/src/main/kotlin/rs09/auth/AuthProvider.kt b/Server/src/main/kotlin/rs09/auth/AuthProvider.kt
new file mode 100644
index 000000000..5d6203d63
--- /dev/null
+++ b/Server/src/main/kotlin/rs09/auth/AuthProvider.kt
@@ -0,0 +1,22 @@
+package rs09.auth
+
+import core.game.node.entity.player.Player
+import rs09.storage.AccountStorageProvider
+
+abstract class AuthProvider {
+ lateinit var storageProvider: T
+
+ abstract fun configureFor(provider: T)
+
+ fun canCreateAccountWith(info: UserAccountInfo) : Boolean {
+ return !storageProvider.checkUsernameTaken(info.username)
+ }
+
+ abstract fun createAccountWith(info: UserAccountInfo) : Boolean
+
+ abstract fun checkLogin(username: String, password: String) : Pair
+
+ abstract fun checkPassword(player: Player, password: String) : Boolean
+
+ abstract fun updatePassword(username: String, newPassword: String)
+}
diff --git a/Server/src/main/kotlin/rs09/auth/AuthResponse.kt b/Server/src/main/kotlin/rs09/auth/AuthResponse.kt
new file mode 100644
index 000000000..ef1c7393c
--- /dev/null
+++ b/Server/src/main/kotlin/rs09/auth/AuthResponse.kt
@@ -0,0 +1,27 @@
+package rs09.auth
+
+enum class AuthResponse {
+ UnexpectedError,
+ CouldNotAd,
+ Success,
+ InvalidCredentials,
+ AccountDisabled,
+ AlreadyOnline,
+ Updated,
+ FullWorld,
+ LoginServerOffline,
+ LoginLimitExceeded,
+ BadSessionID,
+ WeakPassword,
+ MembersWorld,
+ CouldNotLogin,
+ Updating,
+ TooManyIncorrectLogins,
+ StandingInMembersArea,
+ AccountLocked,
+ ClosedBeta,
+ InvalidLoginServer,
+ MovingWorld,
+ ErrorLoadingProfile,
+ BannedUser
+}
diff --git a/Server/src/main/kotlin/rs09/auth/DevelopmentAuthenticator.kt b/Server/src/main/kotlin/rs09/auth/DevelopmentAuthenticator.kt
new file mode 100644
index 000000000..7b1e283e8
--- /dev/null
+++ b/Server/src/main/kotlin/rs09/auth/DevelopmentAuthenticator.kt
@@ -0,0 +1,39 @@
+package rs09.auth
+
+import core.game.node.entity.player.Player
+import rs09.storage.InMemoryStorageProvider
+
+class DevelopmentAuthenticator : AuthProvider() {
+ override fun configureFor(provider: InMemoryStorageProvider) {
+ storageProvider = provider
+ }
+
+ override fun checkLogin(username: String, password: String): Pair {
+ val info: UserAccountInfo
+ if(!storageProvider.checkUsernameTaken(username.toLowerCase())) {
+ info = UserAccountInfo.createDefault()
+ info.username = username
+ createAccountWith(info)
+ } else {
+ info = storageProvider.getAccountInfo(username.toLowerCase())
+ }
+ return Pair(AuthResponse.Success, storageProvider.getAccountInfo(username))
+ }
+
+ override fun createAccountWith(info: UserAccountInfo): Boolean {
+ info.username = info.username.toLowerCase()
+ info.rights = 2
+ storageProvider.store(info)
+ return true
+ }
+
+ override fun checkPassword(player: Player, password: String): Boolean {
+ return password == player.details.password
+ }
+
+ override fun updatePassword(username: String, newPassword: String) {
+ val info = storageProvider.getAccountInfo(username)
+ info.password = newPassword
+ storageProvider.update(info)
+ }
+}
diff --git a/Server/src/main/kotlin/rs09/auth/ProductionAuthenticator.kt b/Server/src/main/kotlin/rs09/auth/ProductionAuthenticator.kt
new file mode 100644
index 000000000..424fe5357
--- /dev/null
+++ b/Server/src/main/kotlin/rs09/auth/ProductionAuthenticator.kt
@@ -0,0 +1,61 @@
+package rs09.auth
+
+import core.game.node.entity.player.Player
+import core.game.system.SystemManager
+import rs09.ServerConstants
+import rs09.storage.AccountStorageProvider
+import rs09.storage.SQLStorageProvider
+import java.sql.SQLDataException
+
+class ProductionAuthenticator : AuthProvider() {
+ override fun configureFor(provider: AccountStorageProvider) {
+ storageProvider = provider
+ if (provider is SQLStorageProvider) {
+ provider.configure(ServerConstants.DATABASE_ADDRESS!!, ServerConstants.DATABASE_NAME!!, ServerConstants.DATABASE_USER!!, ServerConstants.DATABASE_PASS!!)
+ }
+ }
+
+ override fun createAccountWith(info: UserAccountInfo): Boolean {
+ try {
+ info.password = SystemManager.getEncryption().hashPassword(info.password)
+ storageProvider.store(info)
+ } catch (e: SQLDataException) {
+ return false
+ } catch (e: Exception) {
+ e.printStackTrace()
+ return false
+ }
+ return true
+ }
+
+ override fun checkLogin(username: String, password: String): Pair {
+ val info: UserAccountInfo
+ try {
+ if (!storageProvider.checkUsernameTaken(username.toLowerCase())) {
+ return Pair(AuthResponse.InvalidCredentials, null)
+ }
+ info = storageProvider.getAccountInfo(username.toLowerCase())
+ val passCorrect = SystemManager.getEncryption().checkPassword(password, info.password)
+ if(!passCorrect || info.password.isEmpty())
+ return Pair(AuthResponse.InvalidCredentials, null)
+ if(info.banEndTime > System.currentTimeMillis())
+ return Pair(AuthResponse.AccountDisabled, null)
+ if(info.online)
+ return Pair(AuthResponse.AlreadyOnline, null)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ return Pair(AuthResponse.CouldNotLogin, null)
+ }
+ return Pair(AuthResponse.Success, info)
+ }
+
+ override fun checkPassword(player: Player, password: String): Boolean {
+ return SystemManager.getEncryption().checkPassword(password, player.details.password)
+ }
+
+ override fun updatePassword(username: String, newPassword: String) {
+ val info = storageProvider.getAccountInfo(username)
+ info.password = SystemManager.getEncryption().hashPassword(newPassword)
+ storageProvider.update(info)
+ }
+}
\ No newline at end of file
diff --git a/Server/src/main/kotlin/rs09/auth/UserAccountInfo.kt b/Server/src/main/kotlin/rs09/auth/UserAccountInfo.kt
new file mode 100644
index 000000000..f16637b79
--- /dev/null
+++ b/Server/src/main/kotlin/rs09/auth/UserAccountInfo.kt
@@ -0,0 +1,81 @@
+package rs09.auth
+
+class UserAccountInfo(
+ var username: String,
+ var password: String,
+ var uid: Int,
+ var rights: Int,
+ var credits: Int,
+ var ip: String,
+ var lastUsedIp: String,
+ var muteEndTime: Long,
+ var banEndTime: Long,
+ var contacts: String,
+ var blocked: String,
+ var clanName: String,
+ var currentClan: String,
+ var clanReqs: String,
+ var timePlayed: Long,
+ var lastLogin: Long,
+ var online: Boolean
+) {
+ companion object {
+ @JvmStatic fun createDefault() : UserAccountInfo {
+ return UserAccountInfo("", "", 0, 0, 0, "", "", 0L, 0L, "", "", "", "", "1,0,8,9", 0L, 0L, false)
+ }
+ }
+
+ override fun toString(): String {
+ return "USER:$username,PASS:$password,UID:$uid,RIGHTS:$rights,CREDITS:$credits,IP:$ip,LASTIP:$lastUsedIp"
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as UserAccountInfo
+
+ if (username != other.username) return false
+ if (password != other.password) return false
+ if (uid != other.uid) return false
+ if (rights != other.rights) return false
+ if (credits != other.credits) return false
+ if (ip != other.ip) return false
+ if (lastUsedIp != other.lastUsedIp) return false
+ if (muteEndTime != other.muteEndTime) return false
+ if (banEndTime != other.banEndTime) return false
+ if (contacts != other.contacts) return false
+ if (blocked != other.blocked) return false
+ if (clanName != other.clanName) return false
+ if (currentClan != other.currentClan) return false
+ if (clanReqs != other.clanReqs) return false
+ if (timePlayed != other.timePlayed) return false
+ if (lastLogin != other.lastLogin) return false
+ if (online != other.online) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = username.hashCode()
+ result = 31 * result + password.hashCode()
+ result = 31 * result + uid
+ result = 31 * result + rights
+ result = 31 * result + credits
+ result = 31 * result + ip.hashCode()
+ result = 31 * result + lastUsedIp.hashCode()
+ result = 31 * result + muteEndTime.hashCode()
+ result = 31 * result + banEndTime.hashCode()
+ result = 31 * result + contacts.hashCode()
+ result = 31 * result + blocked.hashCode()
+ result = 31 * result + clanName.hashCode()
+ result = 31 * result + currentClan.hashCode()
+ result = 31 * result + clanReqs.hashCode()
+ result = 31 * result + timePlayed.hashCode()
+ result = 31 * result + lastLogin.hashCode()
+ result = 31 * result + online.hashCode()
+ return result
+ }
+
+
+}
\ No newline at end of file
diff --git a/Server/src/main/kotlin/rs09/game/content/global/worldevents/holiday/easter/EasterBunnyDialogueFile.kt b/Server/src/main/kotlin/rs09/game/content/global/worldevents/holiday/easter/EasterBunnyDialogueFile.kt
index 199048611..b8a43cead 100644
--- a/Server/src/main/kotlin/rs09/game/content/global/worldevents/holiday/easter/EasterBunnyDialogueFile.kt
+++ b/Server/src/main/kotlin/rs09/game/content/global/worldevents/holiday/easter/EasterBunnyDialogueFile.kt
@@ -60,7 +60,7 @@ class EasterBunnyDialogueFile(val NEED_BASKET : Boolean) : DialogueFile() {
player!!.dialogueInterpreter.sendDialogue("You need 5 eggs to afford that.")
} else {
player!!.incrementAttribute(EGG_ATTRIBUTE, -5)
- player!!.details.credits += 1
+ player!!.details.accountInfo.credits += 1
player!!.dialogueInterpreter.sendDialogue(
"You turn in 5 eggs in exchange for a credit.",
"You now have ${player!!.getAttribute(EGG_ATTRIBUTE, 0)} eggs."
diff --git a/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/LoginParser.kt b/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/LoginParser.kt
index c048c16ce..f0df87236 100644
--- a/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/LoginParser.kt
+++ b/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/LoginParser.kt
@@ -13,6 +13,7 @@ import core.game.system.task.Pulse
import core.net.amsc.MSPacketRepository
import core.net.amsc.ManagementServerState
import core.net.amsc.WorldCommunicator
+import rs09.auth.AuthResponse
import rs09.game.system.SystemLogger
import rs09.game.world.GameWorld
import rs09.game.world.GameWorld.loginListeners
@@ -70,7 +71,7 @@ class LoginParser(
} catch (t: Throwable) {
t.printStackTrace()
try {
- flag(Response.ERROR_LOADING_PROFILE)
+ flag(AuthResponse.ErrorLoadingProfile)
Repository.LOGGED_IN_PLAYERS.remove(details.username)
} catch (e: Throwable) {
e.printStackTrace()
@@ -84,10 +85,6 @@ class LoginParser(
*/
private fun handleLogin() {
val p = worldInstance
- if (!details.parse()) {
- flag(Response.INVALID_LOGIN_SERVER)
- return
- }
val player = p ?: Player(details)
player.setAttribute("login_type", type)
if (p != null) { // Reconnecting
@@ -122,7 +119,7 @@ class LoginParser(
Repository.lobbyPlayers.remove(player)
Repository.playerNames.remove(player.name)
MSPacketRepository.sendPlayerRemoval(player.name)
- flag(Response.ERROR_LOADING_PROFILE)
+ flag(AuthResponse.ErrorLoadingProfile)
}
//Repository.getPlayerNames().put(player.getName(), player);
GameWorld.Pulser.submit(object : Pulse(1) {
@@ -140,7 +137,7 @@ class LoginParser(
Repository.addPlayer(player)
}
player.details.session.setObject(player)
- flag(Response.SUCCESSFUL)
+ flag(AuthResponse.Success)
player.init()
player.monitor.log(player.details.ipAddress, PlayerMonitor.ADDRESS_LOG)
player.monitor.log(player.details.serial, PlayerMonitor.ADDRESS_LOG)
@@ -156,7 +153,7 @@ class LoginParser(
Repository.lobbyPlayers.remove(player)
Repository.playerNames.remove(player.name)
MSPacketRepository.sendPlayerRemoval(player.name)
- flag(Response.ERROR_LOADING_PROFILE)
+ flag(AuthResponse.ErrorLoadingProfile)
}
return true
}
@@ -185,7 +182,7 @@ class LoginParser(
Repository.disconnectionQueue.remove(details.username)
player.initReconnect()
player.isActive = true
- flag(Response.SUCCESSFUL)
+ flag(AuthResponse.Success)
player.updateSceneGraph(true)
player.configManager.init()
LoginConfiguration.configureGameWorld(player)
@@ -208,22 +205,22 @@ class LoginParser(
//This is supposed to prevent the double-logging issue. Will it work? Who knows.
if (Repository.LOGGED_IN_PLAYERS.contains(details.username)) {
SystemLogger.logWarn("LOGGED_IN_PLAYERS contains ${details.username}")
- return flag(Response.ALREADY_ONLINE)
+ return flag(AuthResponse.AlreadyOnline)
}
if (WorldCommunicator.getState() == ManagementServerState.CONNECTING) {
- return flag(Response.LOGIN_SERVER_OFFLINE)
+ return flag(AuthResponse.LoginServerOffline)
}
if (!details.session.isActive) {
return false
}
if (SystemManager.isUpdating()) {
- return flag(Response.UPDATING)
+ return flag(AuthResponse.Updating)
}
if (Repository.getPlayerByName(details.username).also { gamePlayer = it } != null && gamePlayer!!.session.isActive) {
- return flag(Response.ALREADY_ONLINE)
+ return flag(AuthResponse.AlreadyOnline)
}
return if (details.isBanned) {
- flag(Response.ACCOUNT_DISABLED)
+ flag(AuthResponse.AccountDisabled)
} else true
}
@@ -232,9 +229,9 @@ class LoginParser(
* @param response the [Response].
* @return `True` if successfully logged in.
*/
- fun flag(response: Response): Boolean {
+ fun flag(response: AuthResponse): Boolean {
details.session.write(response, true)
- return response == Response.SUCCESSFUL
+ return response == AuthResponse.Success
}
companion object {
diff --git a/Server/src/main/kotlin/rs09/game/system/Auth.kt b/Server/src/main/kotlin/rs09/game/system/Auth.kt
new file mode 100644
index 000000000..7bf5e6998
--- /dev/null
+++ b/Server/src/main/kotlin/rs09/game/system/Auth.kt
@@ -0,0 +1,27 @@
+package rs09.game.system
+
+import rs09.auth.AuthProvider
+import rs09.auth.DevelopmentAuthenticator
+import rs09.auth.ProductionAuthenticator
+import rs09.storage.AccountStorageProvider
+import rs09.storage.InMemoryStorageProvider
+import rs09.storage.SQLStorageProvider
+
+object Auth {
+ lateinit var authenticator: AuthProvider<*>
+ lateinit var storageProvider: AccountStorageProvider
+
+ fun configureFor(devMode: Boolean) {
+ if (devMode) {
+ authenticator = DevelopmentAuthenticator()
+ storageProvider = InMemoryStorageProvider()
+ (authenticator as DevelopmentAuthenticator).configureFor(storageProvider as InMemoryStorageProvider)
+ SystemLogger.logInfo("[AUTH] Using Development Authenticator with In-Memory Storage")
+ } else {
+ authenticator = ProductionAuthenticator()
+ storageProvider = SQLStorageProvider()
+ (authenticator as ProductionAuthenticator).configureFor(storageProvider as SQLStorageProvider)
+ SystemLogger.logInfo("[AUTH] Using Production Authenticator with SQL Storage")
+ }
+ }
+}
\ No newline at end of file
diff --git a/Server/src/main/kotlin/rs09/game/system/command/sets/ModerationCommandSet.kt b/Server/src/main/kotlin/rs09/game/system/command/sets/ModerationCommandSet.kt
index 56111ef6d..fbe793351 100644
--- a/Server/src/main/kotlin/rs09/game/system/command/sets/ModerationCommandSet.kt
+++ b/Server/src/main/kotlin/rs09/game/system/command/sets/ModerationCommandSet.kt
@@ -2,7 +2,6 @@ package rs09.game.system.command.sets
import core.game.node.entity.player.Player
import core.game.node.entity.player.info.Rights
-import rs09.game.system.command.Command
import core.game.system.task.Pulse
import rs09.game.world.GameWorld
import core.game.world.map.Location
@@ -25,7 +24,7 @@ class ModerationCommandSet : CommandSet(Privilege.MODERATOR){
* Kick a player
* =============================================================================================================
*/
- define("kick", Privilege.ADMIN){ player, args ->
+ define("kick", Privilege.MODERATOR){ player, args ->
val playerToKick: Player? = Repository.getPlayerByName(args[1])
if (playerToKick != null) {
playerToKick.clear(true)
@@ -38,6 +37,100 @@ class ModerationCommandSet : CommandSet(Privilege.MODERATOR){
* =============================================================================================================
*/
+ /**
+ * Ban a player
+ * =============================================================================================================
+ */
+ define("ban", Privilege.ADMIN){ player, args ->
+ val name = args[1]
+ if(!GameWorld.accountStorage.checkUsernameTaken(name)) {
+ reject(player, "Invalid username: $name")
+ }
+ val playerToKick: Player? = Repository.getPlayerByName(name)
+ val durationString = args[2]
+ val durationTokens = durationString.toCharArray()
+ var intToken = ""
+ var durationMillis = 0L
+ var durationUnit: TimeUnit = TimeUnit.NANOSECONDS
+ for(token in durationTokens){
+ if(token.toString().toIntOrNull() != null) intToken += token
+ else {
+ val durationInt: Int = (intToken.toIntOrNull() ?: -1).also { if(it == -1) reject(player, "Invalid duration: $intToken") }
+ durationUnit = when(token) {
+ 'd' -> TimeUnit.DAYS
+ 's' -> TimeUnit.SECONDS
+ 'm' -> TimeUnit.MINUTES
+ 'h' -> TimeUnit.HOURS
+ else -> TimeUnit.SECONDS
+ }
+ durationMillis = durationUnit.toMillis(durationInt.toLong())
+ }
+ }
+
+ playerToKick?.details?.accountInfo?.banEndTime = System.currentTimeMillis() + durationMillis
+ playerToKick?.clear(true)
+ GameWorld.Pulser.submit(object : Pulse(2) {
+ override fun pulse(): Boolean {
+ val info = GameWorld.accountStorage.getAccountInfo(name)
+ info.banEndTime = System.currentTimeMillis() + durationMillis
+ GameWorld.accountStorage.update(info)
+ return true
+ }
+ })
+
+ notify(player, "Banned user $name for $intToken ${durationUnit.name.toLowerCase()}.")
+ }
+ /**
+ * =============================================================================================================
+ */
+
+ /**
+ * Mute a player
+ * =============================================================================================================
+ */
+ define("mute", Privilege.MODERATOR){ player, args ->
+ val name = args[1]
+ if(!GameWorld.accountStorage.checkUsernameTaken(name)) {
+ reject(player, "Invalid username: $name")
+ }
+ val playerToMute: Player? = Repository.getPlayerByName(name)
+ val durationString = args[2]
+ val durationTokens = durationString.toCharArray()
+ var intToken = ""
+ var durationMillis = 0L
+ var durationUnit: TimeUnit = TimeUnit.NANOSECONDS
+ for(token in durationTokens){
+ if(token.toString().toIntOrNull() != null) intToken += token
+ else {
+ val durationInt: Int = (intToken.toIntOrNull() ?: -1).also { if(it == -1) reject(player, "Invalid duration: $intToken") }
+ durationUnit = when(token) {
+ 'd' -> TimeUnit.DAYS
+ 's' -> TimeUnit.SECONDS
+ 'm' -> TimeUnit.MINUTES
+ 'h' -> TimeUnit.HOURS
+ else -> TimeUnit.SECONDS
+ }
+ durationMillis = durationUnit.toMillis(durationInt.toLong())
+ }
+ }
+
+ playerToMute?.details?.accountInfo?.muteEndTime = System.currentTimeMillis() + durationMillis
+ if(playerToMute == null) { //Player was offline at the time
+ GameWorld.Pulser.submit(object : Pulse(2) {
+ override fun pulse(): Boolean {
+ val info = GameWorld.accountStorage.getAccountInfo(name)
+ info.muteEndTime = System.currentTimeMillis() + durationMillis
+ GameWorld.accountStorage.update(info)
+ return true
+ }
+ })
+ }
+
+ notify(player, "Muted user $name for $intToken ${durationUnit.name.toLowerCase()}.")
+ }
+ /**
+ * =============================================================================================================
+ */
/**
* Jail a player
diff --git a/Server/src/main/kotlin/rs09/game/system/command/sets/SystemCommandSet.kt b/Server/src/main/kotlin/rs09/game/system/command/sets/SystemCommandSet.kt
index 9051ea006..b062537aa 100644
--- a/Server/src/main/kotlin/rs09/game/system/command/sets/SystemCommandSet.kt
+++ b/Server/src/main/kotlin/rs09/game/system/command/sets/SystemCommandSet.kt
@@ -1,17 +1,18 @@
package rs09.game.system.command.sets
-import core.cache.Cache
-import core.cache.def.Definition
+import api.InputType
+import api.runTask
+import api.sendDialogue
+import api.sendInputDialogue
import core.cache.def.impl.ItemDefinition
-import core.game.node.entity.player.info.login.Response
-import core.game.node.entity.player.info.portal.PlayerSQLManager
import core.game.node.item.Item
import core.game.system.SystemManager
import core.game.system.SystemState
import core.plugin.Initializable
import org.rs09.consts.Items
-import rs09.game.system.command.Command
+import rs09.game.system.SystemLogger
import rs09.game.system.command.Privilege
+import rs09.game.world.GameWorld
import rs09.game.world.repository.Repository
import kotlin.system.exitProcess
@@ -40,31 +41,29 @@ class SystemCommandSet : CommandSet(Privilege.ADMIN) {
* Allows a player to reset their password
*/
define("resetpassword", Privilege.STANDARD) { player, args ->
- if (args.size != 3) {
- reject(player, "Usage: ::resetpassword current new", "WARNING: THIS IS PERMANENT.", "WARNING: PASSWORD CAN NOT CONTAIN SPACES.")
+ sendInputDialogue(player, InputType.STRING_SHORT, "Enter Current Password:"){value ->
+ val pass = value.toString()
+ SystemLogger.logInfo(pass)
+ runTask(player) {
+ if (GameWorld.authenticator.checkPassword(player, pass)) {
+ sendInputDialogue(player, InputType.STRING_SHORT, "Enter New Password:") { value2 ->
+ val newPass = value2.toString()
+ if (pass == newPass) {
+ sendDialogue(player, "Failed: Passwords Match")
+ } else if (newPass.length !in 5..20) {
+ sendDialogue(player, "Failed: Password Too Long Or Too Short")
+ } else if (newPass == player.details.accountInfo.username) {
+ sendDialogue(player, "Failed: Password Is Username")
+ } else {
+ GameWorld.authenticator.updatePassword(player.details.accountInfo.username, newPass)
+ sendDialogue(player, "Success: Password Updated!")
+ }
+ }
+ } else {
+ sendDialogue(player, "Fail: Wrong Password.")
+ }
+ }
}
- val oldPass = args[1]
- var newPass = args[2]
-
- if (PlayerSQLManager.getCredentialResponse(player.details.username, oldPass) != Response.SUCCESSFUL) {
- reject(player, "INVALID PASSWORD!")
- }
-
- if (newPass.length < 5 || newPass.length > 20) {
- reject(player, "NEW PASSWORD MUST BE BETWEEN 5 AND 20 CHARACTERS")
- }
-
- if (newPass == player.username) {
- reject(player, "PASSWORD CAN NOT BE SAME AS USERNAME.")
- }
-
- if (newPass == oldPass) {
- reject(player, "PASSWORDS CAN NOT BE THE SAME")
- }
-
- newPass = SystemManager.getEncryption().hashPassword(newPass)
- PlayerSQLManager.updatePassword(player.username.toLowerCase().replace(" ", "_"), newPass)
- notify(player, "Password updated successfully.")
}
/**
@@ -75,9 +74,9 @@ class SystemCommandSet : CommandSet(Privilege.ADMIN) {
reject(player, "Usage: ::resetpasswordother user new", "WARNING: THIS IS PERMANENT.", "WARNING: PASSWORD CAN NOT CONTAIN SPACES.")
}
val otherUser = args[1]
- var newPass = args[2]
+ val newPass = args[2]
- if (PlayerSQLManager.hasSqlAccount(otherUser, "username")) {
+ if (GameWorld.accountStorage.checkUsernameTaken(otherUser)) {
if (newPass.length < 5 || newPass.length > 20) {
reject(player, "NEW PASSWORD MUST BE BETWEEN 5 AND 20 CHARACTERS")
@@ -87,8 +86,7 @@ class SystemCommandSet : CommandSet(Privilege.ADMIN) {
reject(player, "PASSWORD CAN NOT BE SAME AS USERNAME.")
}
- newPass = SystemManager.getEncryption().hashPassword(newPass)
- PlayerSQLManager.updatePassword(otherUser.toLowerCase().replace(" ","_"),newPass)
+ GameWorld.authenticator.updatePassword(otherUser, newPass)
notify(player, "Password updated successfully.")
} else {
diff --git a/Server/src/main/kotlin/rs09/game/world/GameWorld.kt b/Server/src/main/kotlin/rs09/game/world/GameWorld.kt
index aeefd8d5c..81a5bb729 100644
--- a/Server/src/main/kotlin/rs09/game/world/GameWorld.kt
+++ b/Server/src/main/kotlin/rs09/game/world/GameWorld.kt
@@ -12,13 +12,15 @@ import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.plugin.CorePluginTypes.StartupPlugin
import core.tools.RandomFunction
-import core.tools.mysql.DatabaseManager
import rs09.ServerConstants
+import rs09.auth.AuthProvider
+import rs09.game.system.Auth
import rs09.game.system.SystemLogger
import rs09.game.system.SystemLogger.logInfo
import rs09.game.system.config.ConfigParser
import rs09.game.world.repository.Repository
import rs09.plugin.ClassScanner
+import rs09.storage.AccountStorageProvider
import rs09.worker.MajorUpdateWorker
import java.text.SimpleDateFormat
import java.util.*
@@ -83,14 +85,17 @@ object GameWorld {
*/
@JvmStatic
var settings: GameSettings? = null
+ @JvmStatic
+ val authenticator: AuthProvider<*>
+ get() = Auth.authenticator
+ @JvmStatic
+ val accountStorage: AccountStorageProvider
+ get() = Auth.storageProvider
/**
* The current amount of (600ms) cycles elapsed.
*/
@JvmStatic
var ticks = 0
- @JvmStatic
- var databaseManager: DatabaseManager? = null
- private set
@JvmStatic
var Pulser = PulseRunner()
@@ -158,9 +163,12 @@ object GameWorld {
fun prompt(run: Boolean, directory: String?){
logInfo("Prompting ${settings?.name} Game World...")
Cache.init(ServerConstants.CACHE_PATH)
- databaseManager = DatabaseManager(ServerConstants.DATABASE)
- databaseManager!!.connect()
- configParser.prePlugin()
+ //go overboard with checks to make sure dev mode authenticator never triggers on live
+ Auth.configureFor(
+ settings!!.isDevMode
+ && ServerConstants.MS_SECRET_KEY == "2009scape_development"
+ )
+ ConfigParser().prePlugin()
ClassScanner.scanClasspath()
ClassScanner.loadPureInterfaces()
worldPersists.forEach { it.parse() }
diff --git a/Server/src/main/kotlin/rs09/game/world/repository/DisconnectionQueue.kt b/Server/src/main/kotlin/rs09/game/world/repository/DisconnectionQueue.kt
index 5ba79b7b2..1081bd886 100644
--- a/Server/src/main/kotlin/rs09/game/world/repository/DisconnectionQueue.kt
+++ b/Server/src/main/kotlin/rs09/game/world/repository/DisconnectionQueue.kt
@@ -195,12 +195,6 @@ class DisconnectionQueue {
fun save(player: Player, sql: Boolean): Boolean {
try {
PlayerParser.save(player)
- if (sql) {
- player.details.sqlManager.update(player)
- player.details.save()
- /*SQLEntryHandler.write(HighscoreSQLHandler(player))
- SQLEntryHandler.write(PlayerLogSQLHandler(player.monitor, player.name))*/
- }
return true
} catch (t: Throwable) {
t.printStackTrace()
diff --git a/Server/src/main/kotlin/rs09/net/event/LoginReadEvent.kt b/Server/src/main/kotlin/rs09/net/event/LoginReadEvent.kt
index c4addd162..fca310685 100644
--- a/Server/src/main/kotlin/rs09/net/event/LoginReadEvent.kt
+++ b/Server/src/main/kotlin/rs09/net/event/LoginReadEvent.kt
@@ -1,190 +1,39 @@
package rs09.net.event
-import core.cache.Cache
-import core.cache.crypto.ISAACCipher
-import core.cache.crypto.ISAACPair
-import core.cache.misc.buffer.ByteBufferUtils
import core.game.node.entity.player.info.ClientInfo
import core.game.node.entity.player.info.PlayerDetails
-import core.game.node.entity.player.info.UIDInfo
-import core.game.node.entity.player.info.login.LoginType
-import core.game.node.entity.player.info.login.Response
-import core.game.node.entity.player.info.portal.PlayerSQLManager
-import core.game.system.task.TaskExecutor
-import core.net.Constants
import core.net.IoReadEvent
import core.net.IoSession
-import core.net.amsc.WorldCommunicator
-import core.tools.StringUtils
-import rs09.ServerConstants
-import rs09.game.node.entity.player.info.login.LoginParser
-import rs09.game.system.SystemLogger
-import rs09.game.world.repository.Repository
-import java.lang.Runnable
-import java.math.BigInteger
+import rs09.auth.AuthResponse
+import rs09.game.world.GameWorld
+import rs09.net.packet.`in`.Login
import java.nio.ByteBuffer
-import java.util.*
/**
* Handles login reading events.
- * @author Emperor
+ * @author Ceikry
*/
-class LoginReadEvent
-/**
- * Constructs a new `LoginReadEvent`.
- * @param session The session.
- * @param buffer The buffer with data to read from.
- */
-(session: IoSession?, buffer: ByteBuffer?) : IoReadEvent(session, buffer) {
+class LoginReadEvent(session: IoSession?, buffer: ByteBuffer?) : IoReadEvent(session, buffer) {
override fun read(session: IoSession, buffer: ByteBuffer) {
- SystemLogger.logInfo("login read")
- val opcode: Int = buffer.get().toInt()
- if (buffer.short.toInt() != buffer.remaining()) {
- session.write(Response.BAD_SESSION_ID)
+ val (response, info) = Login.decodeFromBuffer(buffer)
+
+ if(response != AuthResponse.Success || info == null) {
+ session.write(response)
return
}
- val build = buffer.int
- if (build != Constants.REVISION) { // || buffer.getInt() != Constants.CLIENT_BUILD) {
- session.write(Response.UPDATED)
+
+ val (authResponse, accountInfo) = GameWorld.authenticator.checkLogin(info.username, info.password)
+
+ if(authResponse != AuthResponse.Success || accountInfo == null) {
+ session.write(authResponse)
return
}
- when (opcode) {
- 12 -> println("User details event detected")
- 16, 18 -> decodeWorld(opcode, session, buffer)
- else -> {
- SystemLogger.logErr("[Login] Unhandled login type [opcode=$opcode]!")
- session.disconnect()
- }
- }
- }
- companion object {
+ val details = PlayerDetails(info.username)
+ details.accountInfo = accountInfo
+ session.clientInfo = ClientInfo(info.displayMode, info.windowMode, info.screenWidth, info.screenHeight)
+ session.isaacPair = info.isaacPair
- /**
- * Decodes a world login request.
- * @param session The session.
- * @param buffer The buffer to read from.
- */
- private fun decodeWorld(opcode: Int, session: IoSession, buffer: ByteBuffer) {
- var buffer = buffer
- val d = buffer.get() // Memory?
- val e = buffer.get() // no advertisement = 1
- val f = buffer.get() // 1
- val windowMode = buffer.get().toInt() // Screen size mode
- val screenWidth = buffer.short.toInt() // Screen size Width
- val screenHeight = buffer.short.toInt() // Screen size Height
- val displayMode = buffer.get().toInt() // Display mode
- val data = ByteArray(24) // random.dat data.
- buffer[data]
- ByteBufferUtils.getString(buffer)
- buffer.int // Affiliate id
- buffer.int // Hash containing a bunch of settings
- val curpackets = buffer.short //Current interface packet counter.
- for (i in Cache.getIndexes().indices) {
- val crc = if (Cache.getIndexes()[i] == null) 0 else Cache.getIndexes()[i].information.informationContainer.crc
- if (crc != buffer.int && crc != 0) {
- /*session.write(Response.UPDATED);
- return;*/
- }
- }
- buffer = getRSABlock(buffer)
- buffer.rewind()
- if(buffer.get().toInt() != 10){
- session.write(Response.COULD_NOT_LOGIN)
- return
- }
- val isaacSeed = getISAACSeed(buffer)
- val inCipher = ISAACCipher(isaacSeed)
- for(i in 0..curpackets){
- inCipher.nextValue
- }
- for (i in 0..3) {
- isaacSeed[i] += 50
- }
- val outCipher = ISAACCipher(isaacSeed)
- session.isaacPair = ISAACPair(inCipher, outCipher)
- session.clientInfo = ClientInfo(displayMode, windowMode, screenWidth, screenHeight)
- val b = buffer
- SystemLogger.logInfo("spawning thread to handle login")
- TaskExecutor.executeSQL {
- SystemLogger.logInfo("login thread start")
- Thread.currentThread().name = "Login Password Response"
- SystemLogger.logInfo("login thread named")
- try {
- val username = StringUtils.longToString(b.long)
- SystemLogger.logInfo("got username")
- val password = ByteBufferUtils.getString(b)
- SystemLogger.logInfo("got password")
- val response = PlayerSQLManager.getCredentialResponse(username, password)
- SystemLogger.logInfo("got sql response")
- if (response != Response.SUCCESSFUL) {
- SystemLogger.logInfo("not success :(")
- session.write(response, true)
- return@executeSQL
- }
- SystemLogger.logInfo("great success, attempting login")
- login(PlayerDetails(username, password), session, b, opcode)
- SystemLogger.logInfo("done")
- } catch (e: Exception) {
- SystemLogger.logInfo("big whoops")
- e.printStackTrace()
- session.write(Response.COULD_NOT_LOGIN)
- }
- SystemLogger.logInfo("end login thread")
- }
- }
-
- /**
- * Handles the login procedure after we check an acc is registered & certified.
- * @param details the player's details.
- * @param session the session.
- * @param buffer the byte buffer.
- * @param opcode the opcode.
- */
- @JvmStatic
- private fun login(details: PlayerDetails, session: IoSession, buffer: ByteBuffer, opcode: Int) {
- SystemLogger.logInfo("login")
- if(!Repository.LOGGED_IN_PLAYERS.contains(details.username))
- Repository.LOGGED_IN_PLAYERS.add(details.username)
- val parser = LoginParser(details, LoginType.fromType(opcode))
- details.session = session
- details.info.translate(UIDInfo(details.ipAddress, ByteBufferUtils.getString(buffer), ByteBufferUtils.getString(buffer), ByteBufferUtils.getString(buffer)))
- if (WorldCommunicator.isEnabled()) {
- WorldCommunicator.register(parser)
- } else {
- TaskExecutor.executeSQL {parser.run()}
- }
- }
-
- /**
- * Gets the ISAAC seed from the buffer.
- * @param buffer The buffer to read from.
- * @return The ISAAC seed.
- */
- @JvmStatic
- fun getISAACSeed(buffer: ByteBuffer): IntArray {
- val seed = IntArray(4)
- for (i in 0..3) {
- seed[i] = buffer.int
- }
- return seed
- }
-
- /**
- * Gets the RSA block buffer.
- * @param buffer The buffer to get the RSA block from.
- * @return The RSA block buffer.
- */
- @JvmStatic
- fun getRSABlock(buffer: ByteBuffer): ByteBuffer {
- SystemLogger.logInfo("getRSABlock")
- fun ByteArray.toHex(): String = joinToString(separator = "") { eachByte -> "%02x".format(eachByte) }
- SystemLogger.logInfo(buffer.array().sliceArray(0..1500).toHex())
- val numBytes = 256 + buffer.get()
- val encryptedByteArray = ByteArray(numBytes)
- buffer.get(encryptedByteArray)
- val encryptedBytes = BigInteger(encryptedByteArray)
- return ByteBuffer.wrap(encryptedBytes.modPow(ServerConstants.EXPONENT, ServerConstants.MODULUS).toByteArray())
- }
+ Login.proceedWith(session, details, info.opcode)
}
}
\ No newline at end of file
diff --git a/Server/src/main/kotlin/rs09/net/packet/in/Login.kt b/Server/src/main/kotlin/rs09/net/packet/in/Login.kt
new file mode 100644
index 000000000..8e7cb9a81
--- /dev/null
+++ b/Server/src/main/kotlin/rs09/net/packet/in/Login.kt
@@ -0,0 +1,114 @@
+package rs09.net.packet.`in`
+
+import core.cache.crypto.ISAACCipher
+import core.cache.crypto.ISAACPair
+import core.cache.misc.buffer.ByteBufferUtils
+import core.game.node.entity.player.info.PlayerDetails
+import core.game.node.entity.player.info.UIDInfo
+import core.game.node.entity.player.info.login.LoginType
+import core.net.Constants
+import core.net.IoSession
+import core.net.amsc.WorldCommunicator
+import core.tools.StringUtils
+import rs09.ServerConstants
+import rs09.auth.AuthResponse
+import rs09.game.node.entity.player.info.login.LoginParser
+import rs09.game.system.SystemLogger
+import rs09.game.world.repository.Repository
+import java.math.BigInteger
+import java.nio.BufferUnderflowException
+import java.nio.ByteBuffer
+
+object Login {
+ private const val ENCRYPTION_VERIFICATION_BYTE: Int = 10
+ private const val NORMAL_LOGIN_OP = 16
+ private const val RECONNECT_LOGIN_OP = 18
+ const val CACHE_INDEX_COUNT = 29
+
+ fun decodeFromBuffer(buffer: ByteBuffer) : Pair {
+ try {
+ val info = LoginInfo.createDefault()
+
+ info.opcode = buffer.get().toInt()
+ if (buffer.short.toInt() != buffer.remaining()) {
+ return Pair(AuthResponse.BadSessionID, null)
+ }
+ val revision = buffer.int
+ if (revision != Constants.REVISION) {
+ return Pair(AuthResponse.Updated, null)
+ }
+ if (info.opcode != NORMAL_LOGIN_OP && info.opcode != RECONNECT_LOGIN_OP) {
+ SystemLogger.logInfo("Invalid Login Opcode: ${info.opcode}")
+ return Pair(AuthResponse.InvalidLoginServer, null)
+ }
+
+ noop(buffer)
+ info.showAds = buffer.get().toInt() == 1
+ noop(buffer)
+ info.windowMode = buffer.get().toInt()
+ info.screenWidth = buffer.short.toInt()
+ info.screenHeight = buffer.short.toInt()
+ info.displayMode = buffer.get().toInt()
+ noop(buffer, 24) //Skip past a bunch of random (actually random) data the client sends
+ ByteBufferUtils.getString(buffer) //same as above
+ info.adAffiliateId = buffer.int
+ info.settingsHash = buffer.int
+ info.currentPacketCount = buffer.short.toInt()
+
+ //Read client-reported CRC sums
+ for (i in 0 until CACHE_INDEX_COUNT) info.crcSums[i] = buffer.int
+
+ val decryptedBuffer = decryptRSABuffer(buffer, ServerConstants.EXPONENT, ServerConstants.MODULUS)
+ decryptedBuffer.rewind()
+
+ if (decryptedBuffer.get().toInt() != ENCRYPTION_VERIFICATION_BYTE) {
+ return Pair(AuthResponse.UnexpectedError, info)
+ }
+
+ info.isaacPair = produceISAACPairFrom(decryptedBuffer)
+ info.username = StringUtils.longToString(decryptedBuffer.long)
+ info.password = ByteBufferUtils.getString(decryptedBuffer)
+ return Pair(AuthResponse.Success, info)
+ } catch (e: Exception) {
+ SystemLogger.logErr("Exception encountered during login packet parsing! See stack trace below.")
+ e.printStackTrace()
+ return Pair(AuthResponse.UnexpectedError, null)
+ }
+ }
+
+ private fun produceISAACPairFrom(buffer: ByteBuffer): ISAACPair {
+ val incomingSeed = IntArray(4)
+ for(i in incomingSeed.indices) {
+ incomingSeed[i] = buffer.int
+ }
+ val inCipher = ISAACCipher(incomingSeed)
+ for(i in incomingSeed.indices) {
+ incomingSeed[i] += 50
+ }
+ val outCipher = ISAACCipher(incomingSeed)
+ return ISAACPair(inCipher, outCipher)
+ }
+
+ @JvmStatic fun decryptRSABuffer(buffer: ByteBuffer, exponent: BigInteger, modulus: BigInteger): ByteBuffer {
+ return try {
+ val numBytes = buffer.get().toInt() and 0xFF
+ val encryptedBytes = ByteArray(numBytes)
+ buffer[encryptedBytes]
+
+ val encryptedBigInt = BigInteger(encryptedBytes)
+ ByteBuffer.wrap(encryptedBigInt.modPow(exponent, modulus).toByteArray())
+ } catch (e: BufferUnderflowException) {
+ ByteBuffer.wrap(byteArrayOf(-1))
+ }
+ }
+
+ private fun noop(buffer: ByteBuffer, amount: Int = 1) {buffer[ByteArray(amount)]}
+
+ fun proceedWith(session: IoSession, details: PlayerDetails, opcode: Int) {
+ if (!Repository.LOGGED_IN_PLAYERS.contains(details.username))
+ Repository.LOGGED_IN_PLAYERS.add(details.username)
+ details.session = session
+ details.info.translate(UIDInfo(details.ipAddress, "DEPRECATED", "DEPRECATED", "DEPRECATED"))
+ WorldCommunicator.register(LoginParser(details, LoginType.fromType(opcode)))
+ }
+}
diff --git a/Server/src/main/kotlin/rs09/net/packet/in/LoginInfo.kt b/Server/src/main/kotlin/rs09/net/packet/in/LoginInfo.kt
new file mode 100644
index 000000000..f0da764c5
--- /dev/null
+++ b/Server/src/main/kotlin/rs09/net/packet/in/LoginInfo.kt
@@ -0,0 +1,30 @@
+package rs09.net.packet.`in`
+
+import core.cache.crypto.ISAACCipher
+import core.cache.crypto.ISAACPair
+
+class LoginInfo(
+ var showAds: Boolean, //Unused
+ var windowMode : Int,
+ var screenWidth: Int,
+ var screenHeight: Int,
+ var displayMode: Int,
+ var adAffiliateId: Int, //Unused
+ var settingsHash: Int,
+ var currentPacketCount: Int,
+ var username: String,
+ var password: String,
+ var isaacPair: ISAACPair,
+ var opcode: Int,
+ var crcSums: IntArray
+) {
+ companion object {
+ fun createDefault(): LoginInfo {
+ return LoginInfo(false, 0, 0, 0, 0, 0, 0, 0, "", "", ISAACPair(ISAACCipher(intArrayOf()), ISAACCipher(intArrayOf())), 0, IntArray(Login.CACHE_INDEX_COUNT))
+ }
+ }
+
+ override fun toString(): String {
+ return "ads:$showAds,wm:$windowMode,sw:$screenWidth,sh:$screenHeight,dm:$displayMode,adid:$adAffiliateId,settings:$settingsHash,pkt:$currentPacketCount,un:$username,pw:$password"
+ }
+}
diff --git a/Server/src/main/kotlin/rs09/storage/AccountStorageProvider.kt b/Server/src/main/kotlin/rs09/storage/AccountStorageProvider.kt
new file mode 100644
index 000000000..6151ba72b
--- /dev/null
+++ b/Server/src/main/kotlin/rs09/storage/AccountStorageProvider.kt
@@ -0,0 +1,11 @@
+package rs09.storage
+
+import rs09.auth.UserAccountInfo
+
+interface AccountStorageProvider {
+ fun checkUsernameTaken(username: String): Boolean
+ fun getAccountInfo(username: String): UserAccountInfo
+ fun store(info: UserAccountInfo)
+ fun update(info: UserAccountInfo)
+ fun remove(info: UserAccountInfo)
+}
diff --git a/Server/src/main/kotlin/rs09/storage/InMemoryStorageProvider.kt b/Server/src/main/kotlin/rs09/storage/InMemoryStorageProvider.kt
new file mode 100644
index 000000000..54a09fff9
--- /dev/null
+++ b/Server/src/main/kotlin/rs09/storage/InMemoryStorageProvider.kt
@@ -0,0 +1,27 @@
+package rs09.storage
+
+import rs09.auth.UserAccountInfo
+
+class InMemoryStorageProvider : AccountStorageProvider {
+ private val storage = HashMap()
+
+ override fun checkUsernameTaken(username: String): Boolean {
+ return storage[username] != null
+ }
+
+ override fun getAccountInfo(username: String): UserAccountInfo {
+ return storage[username] ?: UserAccountInfo.createDefault()
+ }
+
+ override fun store(info: UserAccountInfo) {
+ storage[info.username] = info
+ }
+
+ override fun update(info: UserAccountInfo) {
+ storage[info.username] = info
+ }
+
+ override fun remove(info: UserAccountInfo) {
+ storage.remove(info.username)
+ }
+}
diff --git a/Server/src/main/kotlin/rs09/storage/SQLStorageProvider.kt b/Server/src/main/kotlin/rs09/storage/SQLStorageProvider.kt
new file mode 100644
index 000000000..d27a96b6c
--- /dev/null
+++ b/Server/src/main/kotlin/rs09/storage/SQLStorageProvider.kt
@@ -0,0 +1,214 @@
+package rs09.storage
+
+import rs09.auth.UserAccountInfo
+import java.lang.Long.max
+import java.sql.*
+
+class SQLStorageProvider : AccountStorageProvider {
+ var connectionString = ""
+ var connectionUsername = ""
+ var connectionPassword = ""
+
+ fun getConnection(): Connection {
+ Class.forName("com.mysql.cj.jdbc.Driver")
+ return DriverManager.getConnection(connectionString, connectionUsername, connectionPassword)
+ }
+
+ fun configure(host: String, databaseName: String, username: String, password: String) {
+ connectionString = "jdbc:mysql://$host/$databaseName?useTimezone=true&serverTimezone=UTC"
+ connectionUsername = username
+ connectionPassword = password
+ }
+
+ override fun checkUsernameTaken(username: String): Boolean {
+ val conn = getConnection()
+ conn.use {
+ val compiledUsernameQuery = it.prepareStatement(usernameQuery)
+ compiledUsernameQuery.setString(1, username.toLowerCase())
+ val result = compiledUsernameQuery.executeQuery()
+ val exists = result.next()
+ result.close()
+ return exists
+ }
+ }
+
+ override fun getAccountInfo(username: String): UserAccountInfo {
+ val conn = getConnection()
+ conn.use { con ->
+ val compiledAccountInfoQuery = con.prepareStatement(accountInfoQuery)
+ compiledAccountInfoQuery.setString(1, username.toLowerCase())
+ val result = compiledAccountInfoQuery.executeQuery()
+ if (result.next()) {
+ val userData = UserAccountInfo.createDefault()
+ userData.username = username
+
+ result.getString(2) ?.let { userData.password = it }
+ result.getInt(3) .let { userData.uid = it }
+ result.getInt(4) .let { userData.rights = it }
+ result.getInt(5) .let { userData.credits = it }
+ result.getString(6) ?.let { userData.ip = it }
+ result.getString(7) ?.let { userData.lastUsedIp = it }
+ result.getLong(8) .let { userData.muteEndTime = max(0L, it) }
+ result.getLong(9) .let { userData.banEndTime = max(0L, it) }
+ result.getString(10)?.let { userData.contacts = it }
+ result.getString(11)?.let { userData.blocked = it }
+ result.getString(12)?.let { userData.clanName = it }
+ result.getString(13)?.let { userData.currentClan = it }
+ result.getString(14)?.let { userData.clanReqs = it }
+ result.getLong(15) .let { userData.timePlayed = max(0L, it) }
+ result.getLong(16) .let { userData.lastLogin = max(0L, it) }
+ result.getBoolean(17).let { userData.online = it }
+
+ return userData
+ } else {
+ return UserAccountInfo.createDefault()
+ }
+ }
+ }
+
+ override fun store(info: UserAccountInfo) {
+ val conn = getConnection()
+ conn.use {
+ val compiledInsertInfoQuery = it.prepareStatement(insertInfoQuery, Statement.RETURN_GENERATED_KEYS)
+ val emptyInfo = UserAccountInfo.createDefault()
+ if (info == emptyInfo) {
+ throw IllegalStateException("Tried to store empty data!")
+ }
+ emptyInfo.username = info.username
+ if (info == emptyInfo) {
+ throw IllegalStateException("Tried to store empty data!")
+ }
+
+ if (checkUsernameTaken(info.username)) {
+ throw SQLDataException("Account already exists!")
+ }
+ compiledInsertInfoQuery.setString(1, info.username)
+ compiledInsertInfoQuery.setString(2, info.password)
+ compiledInsertInfoQuery.setInt(3, info.rights)
+ compiledInsertInfoQuery.setInt(4, info.credits)
+ compiledInsertInfoQuery.setString(5, info.ip)
+ compiledInsertInfoQuery.setString(6, info.ip)
+ compiledInsertInfoQuery.setLong(7, info.muteEndTime)
+ compiledInsertInfoQuery.setLong(8, info.banEndTime)
+ compiledInsertInfoQuery.setString(9, info.contacts)
+ compiledInsertInfoQuery.setString(10, info.blocked)
+ compiledInsertInfoQuery.setString(11, info.clanName)
+ compiledInsertInfoQuery.setString(12, info.currentClan)
+ compiledInsertInfoQuery.setString(13, info.clanReqs)
+ compiledInsertInfoQuery.setLong(14, info.timePlayed)
+ compiledInsertInfoQuery.setLong(15, info.lastLogin)
+ compiledInsertInfoQuery.setBoolean(16, info.online)
+ compiledInsertInfoQuery.execute()
+ val result = compiledInsertInfoQuery.generatedKeys
+ if (result.next()) {
+ info.uid = result.getInt(1)
+ }
+ }
+ }
+
+ override fun update(info: UserAccountInfo) {
+ val conn = getConnection()
+ conn.use {
+ val compiledUpdateInfoQuery = it.prepareStatement(updateInfoQuery)
+ val emptyInfo = UserAccountInfo.createDefault()
+ if (info == emptyInfo) {
+ throw IllegalStateException("Tried to store empty data!")
+ }
+ emptyInfo.username = info.username
+ if (info == emptyInfo) {
+ throw IllegalStateException("Tried to store empty data!")
+ }
+ emptyInfo.password = info.password
+ if (info == emptyInfo) {
+ throw IllegalStateException("Tried to store empty data!")
+ }
+
+ compiledUpdateInfoQuery.setString(1, info.username)
+ compiledUpdateInfoQuery.setString(2, info.password)
+ compiledUpdateInfoQuery.setInt(3, info.rights)
+ compiledUpdateInfoQuery.setInt(4, info.credits)
+ compiledUpdateInfoQuery.setString(5, info.ip)
+ compiledUpdateInfoQuery.setLong(6, info.muteEndTime)
+ compiledUpdateInfoQuery.setLong(7, info.banEndTime)
+ compiledUpdateInfoQuery.setString(8, info.contacts)
+ compiledUpdateInfoQuery.setString(9, info.blocked)
+ compiledUpdateInfoQuery.setString(10, info.clanName)
+ compiledUpdateInfoQuery.setString(11, info.currentClan)
+ compiledUpdateInfoQuery.setString(12, info.clanReqs)
+ compiledUpdateInfoQuery.setLong(13, info.timePlayed)
+ compiledUpdateInfoQuery.setLong(14, info.lastLogin)
+ compiledUpdateInfoQuery.setBoolean(15, info.online)
+ compiledUpdateInfoQuery.setInt(16, info.uid)
+ compiledUpdateInfoQuery.execute()
+ }
+ }
+
+ override fun remove(info: UserAccountInfo) {
+ val conn = getConnection()
+ conn.use {
+ val compiledRemoveInfoQuery = it.prepareStatement(removeInfoQuery)
+ compiledRemoveInfoQuery.setString(1, info.username)
+ compiledRemoveInfoQuery.execute()
+ }
+ }
+
+ companion object {
+ private const val usernameQuery = "SELECT username FROM members WHERE username = ?;"
+ private const val removeInfoQuery = "DELETE FROM members WHERE username = ?;"
+ private const val accountInfoQuery = "SELECT " +
+ "username," +
+ "password," +
+ "UID," +
+ "rights," +
+ "credits," +
+ "ip," +
+ "lastGameIp," +
+ "muteTime," +
+ "banTime," +
+ "contacts," +
+ "blocked," +
+ "clanName," +
+ "currentClan," +
+ "clanReqs," +
+ "timePlayed," +
+ "lastLogin," +
+ "online" +
+ " FROM members WHERE username = ?;"
+ private const val insertInfoQuery = "INSERT INTO members (" +
+ "username," +
+ "password," +
+ "rights," +
+ "credits," +
+ "ip," +
+ "lastGameIp," +
+ "muteTime," +
+ "banTime," +
+ "contacts," +
+ "blocked," +
+ "clanName," +
+ "currentClan," +
+ "clanReqs," +
+ "timePlayed," +
+ "lastLogin," +
+ "online" +
+ ") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);"
+
+ private const val updateInfoQuery = "UPDATE members SET " +
+ "username = ?," +
+ "password = ?," +
+ "rights = ?," +
+ "credits = ?," +
+ "lastGameIp = ?," +
+ "muteTime = ?," +
+ "banTime = ?," +
+ "contacts = ?," +
+ "blocked = ?," +
+ "clanName = ?," +
+ "currentClan = ?," +
+ "clanReqs = ?," +
+ "timePlayed = ?," +
+ "lastLogin = ?," +
+ "online = ?" +
+ " WHERE uid = ?;"
+ }
+}
diff --git a/Server/src/test/kotlin/TestUtils.kt b/Server/src/test/kotlin/TestUtils.kt
index 002d115c0..a949f9364 100644
--- a/Server/src/test/kotlin/TestUtils.kt
+++ b/Server/src/test/kotlin/TestUtils.kt
@@ -19,7 +19,7 @@ import java.nio.ByteBuffer
object TestUtils {
fun getMockPlayer(name: String, ironman: IronmanMode = IronmanMode.NONE): Player {
- val p = Player(PlayerDetails(name, name))
+ val p = Player(PlayerDetails(name))
p.details.session = MockSession()
p.ironmanManager.mode = ironman
Repository.addPlayer(p)
diff --git a/Server/src/test/kotlin/core/auth/DevelopmentAuthenticatorTests.kt b/Server/src/test/kotlin/core/auth/DevelopmentAuthenticatorTests.kt
new file mode 100644
index 000000000..f3b9ba5ba
--- /dev/null
+++ b/Server/src/test/kotlin/core/auth/DevelopmentAuthenticatorTests.kt
@@ -0,0 +1,55 @@
+package core.auth
+
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import rs09.auth.DevelopmentAuthenticator
+import rs09.auth.AuthResponse
+import rs09.auth.UserAccountInfo
+import rs09.storage.InMemoryStorageProvider
+
+class DevelopmentAuthenticatorTests {
+ private val authProvider = DevelopmentAuthenticator()
+ private val storageProvider = InMemoryStorageProvider()
+
+ init {
+ authProvider.configureFor(storageProvider)
+ }
+
+ @Test fun shouldAllowCheckingIfAccountExists() {
+ val info = UserAccountInfo.createDefault()
+ info.username = "Billy"
+ Assertions.assertEquals(true, authProvider.canCreateAccountWith(info))
+ authProvider.createAccountWith(info)
+ Assertions.assertEquals(false, authProvider.canCreateAccountWith(info))
+ }
+
+ @Test fun loginWithValidAccountInfoReturnsSuccess() {
+ val info = UserAccountInfo.createDefault()
+ info.username = "Billy"
+ authProvider.createAccountWith(info)
+ Assertions.assertEquals(AuthResponse.Success, authProvider.checkLogin("Billy", "").first)
+ }
+
+ //Development authenticator should work regardless if account exists or not.
+ @Test fun loginWithInvalidAccountInfoReturnsSuccess() {
+ val info = UserAccountInfo.createDefault()
+ info.username = "Billy"
+ authProvider.createAccountWith(info)
+ Assertions.assertEquals(AuthResponse.Success, authProvider.checkLogin("Bilbo", "ebbeb").first)
+ }
+
+ @Test fun loginUsernameIsNotCaseSensitive() {
+ val info = UserAccountInfo.createDefault()
+ info.username = "Billy"
+ authProvider.createAccountWith(info)
+ Assertions.assertEquals(AuthResponse.Success, authProvider.checkLogin("Billy", "").first)
+ Assertions.assertEquals(AuthResponse.Success, authProvider.checkLogin("billy", "").first)
+ }
+
+ //Development authenticator should basically bypass needing/creating an account entirely. useful for SP too.
+ @Test fun loginToUnregisteredAccountCreatesIt() {
+ authProvider.checkLogin("masterbaggins", "whatever")
+ val info = storageProvider.getAccountInfo("masterbaggins")
+ Assertions.assertEquals(2, info.rights)
+ }
+}
\ No newline at end of file
diff --git a/Server/src/test/kotlin/core/auth/ProductionAuthenticatorTests.kt b/Server/src/test/kotlin/core/auth/ProductionAuthenticatorTests.kt
new file mode 100644
index 000000000..c06ef15a7
--- /dev/null
+++ b/Server/src/test/kotlin/core/auth/ProductionAuthenticatorTests.kt
@@ -0,0 +1,62 @@
+package core.auth
+
+import org.junit.Assert
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.Test
+import rs09.auth.AuthResponse
+import rs09.auth.ProductionAuthenticator
+import rs09.auth.UserAccountInfo
+import rs09.storage.InMemoryStorageProvider
+
+class ProductionAuthenticatorTests {
+ companion object {
+ private val authProvider = ProductionAuthenticator()
+ private val storageProvider = InMemoryStorageProvider()
+
+ init {
+ authProvider.configureFor(storageProvider)
+ }
+
+ @BeforeAll @JvmStatic fun createTestAccount() {
+ val details = UserAccountInfo.createDefault()
+ details.username = "test"
+ details.password = "testing"
+ if(!storageProvider.checkUsernameTaken("test")) {
+ authProvider.createAccountWith(details)
+ }
+ }
+ }
+
+ @Test fun shouldRejectLoginWithInvalidDetails() {
+ Assertions.assertEquals(AuthResponse.InvalidCredentials, authProvider.checkLogin("test", "test2").first)
+ }
+
+ @Test fun loginUsernameIsNotCaseSensitive() {
+ Assertions.assertEquals(AuthResponse.Success, authProvider.checkLogin("Test", "testing").first)
+ Assertions.assertEquals(AuthResponse.Success, authProvider.checkLogin("test", "testing").first)
+ }
+
+ @Test fun shouldHashPasswords() {
+ Assertions.assertNotEquals("testing", storageProvider.getAccountInfo("test").password)
+ }
+
+ @Test fun shouldNotAllowBannedLogin() {
+ val info = storageProvider.getAccountInfo("test")
+ info.banEndTime = System.currentTimeMillis() + 1000L
+ storageProvider.update(info)
+ Assertions.assertEquals(AuthResponse.AccountDisabled, authProvider.checkLogin("test", "testing").first)
+ info.banEndTime = 0L
+ storageProvider.update(info)
+ Assertions.assertEquals(AuthResponse.Success, authProvider.checkLogin("test", "testing").first)
+ }
+
+ @Test fun shouldNotAllowAlreadyOnlineLogin() {
+ val info = storageProvider.getAccountInfo("test")
+ info.online = true
+ storageProvider.update(info)
+ Assertions.assertEquals(AuthResponse.AlreadyOnline, authProvider.checkLogin("test", "testing").first)
+ info.online = false
+ storageProvider.update(info)
+ }
+}
\ No newline at end of file
diff --git a/Server/src/test/kotlin/core/net/LoginTests.kt b/Server/src/test/kotlin/core/net/LoginTests.kt
new file mode 100644
index 000000000..a6ff5be50
--- /dev/null
+++ b/Server/src/test/kotlin/core/net/LoginTests.kt
@@ -0,0 +1,40 @@
+package core.net
+
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import rs09.auth.AuthResponse
+import rs09.net.packet.`in`.Login
+import java.nio.ByteBuffer
+
+class LoginTests {
+ @Test fun shouldDecodeCorrectlyEncodedPacket() {
+ val localCopy = validLoginPacket.copyOf(validLoginPacket.size)
+ val loginInfo = Login.decodeFromBuffer(ByteBuffer.wrap(localCopy))
+ Assertions.assertEquals(AuthResponse.Success, loginInfo.first, "Info: ${loginInfo.second}")
+ }
+
+ @Test fun shouldNeverThrowExceptionButReturnUnexpectedErrorResponseInstead() {
+ val localCopy = validLoginPacket.copyOf(validLoginPacket.size)
+ for(i in 15 until validLoginPacket.size) {
+ localCopy[i] = 0 //corrupt some data on purpose
+ }
+ var response: AuthResponse = AuthResponse.Updating
+ Assertions.assertDoesNotThrow {
+ val (res, _) = Login.decodeFromBuffer(ByteBuffer.wrap(localCopy))
+ response = res
+ }
+
+ Assertions.assertEquals(AuthResponse.UnexpectedError, response)
+ }
+
+ @Test fun loginPacketWithInvalidOpcodeShouldReturnAHelpfulResponse() {
+ val localCopy = validLoginPacket.copyOf(validLoginPacket.size)
+ localCopy[0] = 2 //set to invalid login opcode
+ val (response, _) = Login.decodeFromBuffer(ByteBuffer.wrap(localCopy))
+ Assertions.assertEquals(AuthResponse.InvalidLoginServer, response)
+ }
+
+ companion object {
+ val validLoginPacket = byteArrayOf(16, 1, 80, 0, 0, 2, 18, 0, 1, 1, 2, 2, -3, 1, -9, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 107, 75, 109, 111, 107, 51, 107, 74, 113, 79, 101, 78, 54, 68, 51, 109, 68, 100, 105, 104, 99, 111, 51, 111, 80, 101, 89, 78, 50, 75, 70, 121, 54, 87, 53, 45, 45, 118, 90, 85, 98, 78, 65, 0, 0, 0, 0, 0, 1, 89, -73, -29, 0, 0, -47, -49, 34, 92, -124, 110, -97, 46, -33, -18, 122, 8, 76, 93, -82, 16, 53, 23, -113, -73, 101, 126, -93, 125, -65, 115, -59, 19, 90, -54, -102, -76, -99, -5, -68, -51, 95, -20, -27, 10, 60, 60, 108, 5, 76, 9, -63, 97, 106, 74, 116, 0, 58, 0, 75, -111, -128, -34, 12, 64, 47, -92, -33, -120, -109, -7, 23, 124, 122, 40, -107, 56, -34, -93, 64, -82, 58, -90, 7, 127, -15, -85, 125, 43, 15, 0, 112, 60, 4, 75, 72, 55, 18, 83, -119, -39, 32, -113, 21, 104, -49, 66, -102, 104, -13, 32, 117, -106, 94, -30, 37, -56, -67, 21, 77, 70, -128, 113, 86, 84, 83, 115, 3, 55, -106, 127, -14, -15, 6, 42, -92, -56, 114, 24, 83, 32, -127, 78, 14, -98, -30, -38, -115, 9, -106, 120, 101, -117, -50, -40, -64, -50, -106, -98, 5, -86, 28, -127, -5, 109, -107, 49, -107, 63, 81, -81, -109, -21, 25, -68, 63, 102, -5, 8, -96, 126, -128, -116, -32, 26, 76, 54, -63, -37, 41, 57, 65, -53, -22, 83, 61, -128, -17, -21, 87, 76, -8, -95, -45, -80, -60, -62, 96, 49, 26, 49, 94, 80, 11, -12, -95, -58, -116, -54, -107, 91, 104, 58, 33, 20, -93, -68, -83, -116, 63, -18, 36, 30, -98, 77, -107, 122, 79, -27, 67, -94, 125, -81, -21, -75, -71, -45, -39, 112, 40)
+ }
+}
\ No newline at end of file
diff --git a/Server/src/test/kotlin/core/storage/SQLStorageProviderTests.kt b/Server/src/test/kotlin/core/storage/SQLStorageProviderTests.kt
new file mode 100644
index 000000000..ab3895a29
--- /dev/null
+++ b/Server/src/test/kotlin/core/storage/SQLStorageProviderTests.kt
@@ -0,0 +1,181 @@
+package core.storage
+
+import core.auth.ProductionAuthenticatorTests
+import org.junit.After
+import org.junit.jupiter.api.AfterAll
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import rs09.auth.UserAccountInfo
+import rs09.game.system.SystemLogger
+import rs09.storage.SQLStorageProvider
+import java.sql.SQLDataException
+
+class SQLStorageProviderTests {
+ companion object {
+ val storage = SQLStorageProvider()
+ val testAccountNames = ArrayList()
+ init {
+ storage.configure("localhost", "global", "root", "")
+ val details = UserAccountInfo.createDefault()
+ details.rights = 2
+ details.username = "test"
+ }
+
+ @AfterAll @JvmStatic fun cleanup() {
+ SystemLogger.logInfo("Cleaning up unit test accounts")
+ testAccountNames.forEach {name ->
+ SystemLogger.logInfo("Removing test account $name")
+ val info = UserAccountInfo.createDefault()
+ info.username = name
+ storage.remove(info)
+ }
+ }
+ }
+
+ @Test
+ fun shouldReturnTrueIfUsernameExists() {
+ val data = UserAccountInfo.createDefault()
+ data.username = "test123123"
+ data.password = "test"
+ testAccountNames.add("test123123")
+ storage.store(data)
+ val exists = storage.checkUsernameTaken("test")
+ Assertions.assertEquals(true, exists)
+ }
+
+ @Test fun shouldReturnCorrectUserData() {
+ val data = UserAccountInfo.createDefault()
+ data.username = "test111"
+ data.password = "test"
+ data.rights = 2
+ testAccountNames.add("test111")
+ storage.store(data)
+ val accountInfo = storage.getAccountInfo("test111")
+ Assertions.assertEquals(2, accountInfo.rights)
+ }
+
+ @Test fun shouldAllowStoreUserData() {
+ val userData = UserAccountInfo.createDefault()
+ userData.username = "storageTest"
+ userData.password = "test"
+ testAccountNames.add("storageTest")
+ storage.store(userData)
+ val exists = storage.checkUsernameTaken("storageTest")
+ Assertions.assertEquals(true, exists)
+ }
+
+ @Test fun shouldNotAllowDuplicateAccountStorage() {
+ val userData = UserAccountInfo.createDefault()
+ userData.username = "bilbo111"
+ userData.password = "test"
+ testAccountNames.add("bilbo111")
+ storage.store(userData)
+ Assertions.assertThrows(SQLDataException::class.java) {
+ storage.store(userData)
+ }
+ }
+
+ @Test fun shouldAllowRemoveUserInfo() {
+ val userData = UserAccountInfo.createDefault()
+ userData.username = "bepis"
+ userData.password = "test"
+ testAccountNames.add("bepis")
+ storage.store(userData)
+ storage.remove(userData)
+ Assertions.assertEquals(false, storage.checkUsernameTaken("bepis"))
+ }
+
+ @Test fun shouldUpdateUserData() {
+ val userData = UserAccountInfo.createDefault()
+ userData.username = "borpis"
+ userData.password = "test"
+ testAccountNames.add("borpis")
+ storage.store(userData)
+ userData.credits = 2
+ storage.update(userData)
+ val data = storage.getAccountInfo(userData.username)
+ Assertions.assertEquals(2, data.credits, "Wrong data: $data")
+ }
+
+ @Test fun shouldNotAllowStoreOrUpdateEmptyData() {
+ val info = UserAccountInfo.createDefault()
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ storage.store(info)
+ }
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ storage.update(info)
+ }
+ info.username = "test"
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ storage.store(info)
+ }
+ Assertions.assertThrows(IllegalStateException::class.java) {
+ storage.update(info)
+ }
+ }
+
+ @Test fun shouldSetDefaultValuesWhenDBFieldIsNull() {
+ val defaultData = UserAccountInfo.createDefault()
+
+ //manually insert a definitely-mostly-null entry into the DB
+ val conn = storage.getConnection()
+ conn.use {
+ val stmt = conn.prepareStatement("INSERT INTO members (username) VALUES (?);")
+ stmt.setString(1, "nulltestacc")
+ testAccountNames.add("nulltestacc")
+ stmt.execute()
+ }
+
+ var data: UserAccountInfo = UserAccountInfo.createDefault()
+ Assertions.assertDoesNotThrow {
+ data = storage.getAccountInfo("nulltestacc")
+ }
+
+ Assertions.assertEquals(defaultData.password, data.password)
+ Assertions.assertEquals(defaultData.rights, data.rights)
+ Assertions.assertEquals(defaultData.credits, data.credits)
+ Assertions.assertEquals(defaultData.ip, data.ip)
+ Assertions.assertEquals(defaultData.lastUsedIp, data.lastUsedIp)
+ Assertions.assertEquals(defaultData.muteEndTime, data.muteEndTime)
+ Assertions.assertEquals(defaultData.banEndTime, data.banEndTime)
+ Assertions.assertEquals(defaultData.contacts, data.contacts)
+ Assertions.assertEquals(defaultData.blocked, data.blocked)
+ Assertions.assertEquals(defaultData.clanName, data.clanName)
+ Assertions.assertEquals(defaultData.currentClan, data.currentClan)
+ Assertions.assertEquals(defaultData.clanReqs, data.clanReqs)
+ Assertions.assertEquals(defaultData.timePlayed, data.timePlayed)
+ Assertions.assertEquals(defaultData.lastLogin, data.lastLogin)
+ Assertions.assertEquals(defaultData.online, data.online)
+ }
+
+ @Test fun updatingPropertiesOnTheDatabaseEndShouldBePreservedWhenFetchingAccountInfo() {
+ val conn = storage.getConnection()
+ conn.use {
+ val stmt = conn.prepareStatement("INSERT INTO members (username) VALUES (?);")
+ stmt.setString(1, "dbupdateacc")
+ testAccountNames.add("dbupdateacc")
+ stmt.execute()
+ stmt.close()
+
+ val stmt2 = conn.prepareStatement("UPDATE members SET rights = 2 WHERE username = \"dbupdateacc\";")
+ stmt2.execute()
+ }
+
+ val info = storage.getAccountInfo("dbupdateacc")
+ Assertions.assertEquals(2, info.rights)
+ info.rights = 1
+ storage.update(info)
+
+ val info2 = storage.getAccountInfo("dbupdateacc")
+ Assertions.assertEquals(1, info2.rights)
+
+ val conn2 = storage.getConnection()
+ conn2.use {
+ val stmt = conn2.prepareStatement("UPDATE members SET rights = 2 WHERE username = \"dbupdateacc\";")
+ stmt.execute()
+ }
+
+ val info3 = storage.getAccountInfo("dbupdateacc")
+ Assertions.assertEquals(2, info3.rights)
+ }
+}
\ No newline at end of file