diff --git a/nge.cfg b/nge.cfg index b9d722136..590ea9dc6 100644 --- a/nge.cfg +++ b/nge.cfg @@ -1,5 +1,18 @@ -#Tue Nov 04 11:53:02 CST 2014 +#Mon Nov 24 03:05:06 CST 2014 +PACKET-SENDING-INTERVAL=50 LOGIN_PORT=44453 -PING_PORT=44462 ZONE_PORT=44463 +LOCAL-USER=nge +LOGIN-SERVER-NAME=LoginServer +ZONE-PORT=44463 +SESSION-TOKEN-LENGTH=24 +LOGIN-PORT=44453 MAX_PACKET_SIZE=496 +PING_PORT=44462 +MAX-PACKET-SIZE=496 +LOCAL-DB=nge +LOGIN-SERVER-ID=1 +LOCAL-PASS=nge +GALAXY-MAX-ONLINE=3000 +GALAXY-MAX-CHARACTERS=2 +PING-PORT=44462 diff --git a/src/main/ProjectSWG.java b/src/main/ProjectSWG.java index 51d98e2cd..cd0678ed9 100644 --- a/src/main/ProjectSWG.java +++ b/src/main/ProjectSWG.java @@ -4,12 +4,13 @@ import services.CoreManager; public class ProjectSWG { + private static ProjectSWG server; private final Thread mainThread; private CoreManager manager; private boolean shutdownRequested; - public static void main(String [] args) { - final ProjectSWG server = new ProjectSWG(); + public static final void main(String [] args) { + server = new ProjectSWG(); Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { server.forceShutdown(); @@ -26,7 +27,11 @@ public class ProjectSWG { server.terminate(); } - public ProjectSWG() { + public static final long getCoreTime() { + return (long) server.manager.getCoreTime(); + } + + private ProjectSWG() { mainThread = Thread.currentThread(); shutdownRequested = false; } @@ -69,6 +74,7 @@ public class ProjectSWG { private void loop() { while (manager.isOperational() && !shutdownRequested) { try { + manager.flushPackets(); // Sends any packets that weren't sent Thread.sleep(100); // Checks the state of the server every 100ms } catch (InterruptedException e) { if (!shutdownRequested) diff --git a/src/services/CoreManager.java b/src/services/CoreManager.java index 9b083ba5a..9e4db8a23 100644 --- a/src/services/CoreManager.java +++ b/src/services/CoreManager.java @@ -1,5 +1,10 @@ package services; +import network.packets.soe.DataChannelA; +import network.packets.swg.SWGPacket; +import intents.InboundPacketIntent; +import intents.OutboundPacketIntent; +import resources.control.Intent; import resources.control.Manager; import services.player.PlayerManager; @@ -28,9 +33,32 @@ public class CoreManager extends Manager { @Override public boolean initialize() { startTime = System.nanoTime(); + registerForIntent(InboundPacketIntent.TYPE); + registerForIntent(OutboundPacketIntent.TYPE); return super.initialize(); } + @Override + public void onIntentReceived(Intent i) { + if (i instanceof InboundPacketIntent) { + InboundPacketIntent in = (InboundPacketIntent) i; + System.out.println("IN " + in.getNetworkId() + ":" + in.getServerType() + "\t" + in.getPacket().getClass().getSimpleName()); + if (in.getPacket() instanceof DataChannelA) { + for (SWGPacket p : ((DataChannelA) in.getPacket()).getPackets()) { + System.out.println(" " + p.getClass().getSimpleName()); + } + } + } else if (i instanceof OutboundPacketIntent) { + OutboundPacketIntent out = (OutboundPacketIntent) i; + System.out.println("OUT " + out.getNetworkId() + " \t" + out.getPacket().getClass().getSimpleName()); + if (out.getPacket() instanceof DataChannelA) { + for (SWGPacket p : ((DataChannelA) out.getPacket()).getPackets()) { + System.out.println(" " + p.getClass().getSimpleName()); + } + } + } + } + /** * Returns the time in milliseconds since the server started initialization * @return the core time represented as a double diff --git a/src/services/player/LoginService.java b/src/services/player/LoginService.java index 76699aa7f..8b1ab2443 100644 --- a/src/services/player/LoginService.java +++ b/src/services/player/LoginService.java @@ -1,17 +1,190 @@ package services.player; +import intents.LoginEventIntent; +import intents.LoginEventIntent.LoginEvent; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import main.ProjectSWG; import network.packets.Packet; +import network.packets.soe.SessionRequest; +import network.packets.swg.ErrorMessage; +import network.packets.swg.ServerUnixEpochTime; +import network.packets.swg.login.CharacterCreationDisabled; +import network.packets.swg.login.EnumerateCharacterId; +import network.packets.swg.login.EnumerateCharacterId.SWGCharacter; +import network.packets.swg.login.LoginClientId; +import network.packets.swg.login.LoginClientToken; +import network.packets.swg.login.LoginClusterStatus; +import network.packets.swg.login.LoginEnumCluster; +import network.packets.swg.login.ServerId; +import network.packets.swg.login.ServerString; +import network.packets.swg.login.StationIdHasJediSlot; +import resources.Galaxy; +import resources.Race; +import resources.config.ConfigFile; import resources.control.Service; -import utilities.ByteUtilities; +import resources.player.AccessLevel; +import resources.player.Player; +import resources.player.PlayerState; +import resources.services.Config; public class LoginService extends Service { + private static final String REQUIRED_VERSION = "20111130-15:46"; + + private Random random; + private PreparedStatement getUser; + private PreparedStatement getGalaxies; + private PreparedStatement getCharacters; + public LoginService() { - + random = new Random(); } - public void handlePacket(Packet p) { - System.out.println(p.getClass().getSimpleName() + ": " + ByteUtilities.getHexString(p.getData().array())); + @Override + public boolean initialize() { + getUser = getLocalDatabase().prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?"); + getGalaxies = getLocalDatabase().prepareStatement("SELECT * FROM galaxies"); + getCharacters = getLocalDatabase().prepareStatement("SELECT * FROM characters WHERE userid = ?"); + return super.initialize(); + } + + public void handlePacket(Player player, Packet p) { + if (p instanceof SessionRequest) { + player.setConnectionId(((SessionRequest)p).getConnectionID()); + player.setPlayerState(PlayerState.DISCONNECTED); + sendServerInfo(player); + } + if (p instanceof LoginClientId) + handleLogin(player, (LoginClientId) p); + } + + private void sendServerInfo(Player player) { + Config c = getConfig(ConfigFile.PRIMARY); + String name = c.getString("LOGIN-SERVER-NAME", "LoginServer"); + int id = c.getInt("LOGIN-SERVER-ID", 1); + sendPacket(player.getNetworkId(), new ServerString(name + ":" + id)); + sendPacket(player.getNetworkId(), new ServerId(id)); + } + + private void handleLogin(Player player, LoginClientId id) { + if (player.getPlayerState() != PlayerState.DISCONNECTED) { + System.err.println("Player cannot login when " + player.getPlayerState()); + return; + } + if (!id.getVersion().equals(REQUIRED_VERSION)) { + System.err.println("LoginService: " + id.getUsername() + " cannot login due to invalid version code: " + id.getVersion()); + String type = "Login Failed!"; + String message = "Invalid Client Version Code: " + id.getVersion(); + sendPacket(player.getNetworkId(), new ErrorMessage(type, message, false)); + player.setPlayerState(PlayerState.DISCONNECTED); + new LoginEventIntent(player.getNetworkId(), LoginEvent.LOGIN_FAIL_INVALID_VERSION_CODE).broadcast(); + return; + } + try { + ResultSet user = getUser(id.getUsername(), id.getPassword()); + if (user.next()) { // User exists! Right username/password combo! + player.setUsername(id.getUsername()); + player.setUserId(user.getInt("id")); + int tokenLength = getConfig(ConfigFile.PRIMARY).getInt("SESSION-TOKEN-LENGTH", 24); + byte [] sessionToken = new byte[tokenLength]; + random.nextBytes(sessionToken); + player.setSessionToken(sessionToken); + player.setPlayerState(PlayerState.LOGGING_IN); + sendLoginSuccessPacket(player); + System.out.println("LoginService: " + player.getUsername() + " has logged in."); + new LoginEventIntent(player.getNetworkId(), LoginEvent.LOGIN_SUCCESS).broadcast(); + } else { // User does not exist! + String type = "Login Failed!"; + String message = "Invalid username or password."; + sendPacket(player.getNetworkId(), new ErrorMessage(type, message, false)); + System.err.println("LoginService: " + id.getUsername() + " tried logging in with invalid user/pass combo!"); + player.setPlayerState(PlayerState.DISCONNECTED); + new LoginEventIntent(player.getNetworkId(), LoginEvent.LOGIN_FAIL_INVALID_USER_PASS).broadcast(); + } + } catch (SQLException e) { + e.printStackTrace(); + String type = "Login Failed!"; + String message = "Server Error."; + sendPacket(player.getNetworkId(), new ErrorMessage(type, message, false)); + player.setPlayerState(PlayerState.DISCONNECTED); + new LoginEventIntent(player.getNetworkId(), LoginEvent.LOGIN_FAIL_SERVER_ERROR).broadcast(); + } + } + + private void sendLoginSuccessPacket(Player p) throws SQLException { + LoginClientToken token = new LoginClientToken(p.getSessionToken(), p.getUserId(), p.getUsername()); + LoginEnumCluster cluster = new LoginEnumCluster(); + LoginClusterStatus clusterStatus = new LoginClusterStatus(); + List galaxies = getGalaxies(p); + SWGCharacter [] characters = getCharacters(p.getUserId()); + for (Galaxy g : galaxies) { + cluster.addGalaxy(g); + clusterStatus.addGalaxy(g); + } + cluster.setMaxCharacters(getConfig(ConfigFile.PRIMARY).getInt("GALAXY-MAX-CHARACTERS", 2)); + sendPacket(p.getNetworkId(), new ServerUnixEpochTime((int) (ProjectSWG.getCoreTime() / 1000))); + sendPacket(p.getNetworkId(), token); + sendPacket(p.getNetworkId(), cluster); + sendPacket(p.getNetworkId(), new CharacterCreationDisabled()); + sendPacket(p.getNetworkId(), clusterStatus); + sendPacket(p.getNetworkId(), new StationIdHasJediSlot(0)); + sendPacket(p.getNetworkId(), new EnumerateCharacterId(characters)); + p.setPlayerState(PlayerState.LOGGED_IN); + } + + private ResultSet getUser(String username, String password) throws SQLException { + getUser.setString(1, username); + getUser.setString(2, password); + return getUser.executeQuery(); + } + + private List getGalaxies(Player p) throws SQLException { + Config c = getConfig(ConfigFile.PRIMARY); + ResultSet set = getGalaxies.executeQuery(); + List galaxies = new ArrayList(); + while (set.next()) { + Galaxy g = new Galaxy(); + g.setId(set.getInt("id")); + g.setName(set.getString("name")); + g.setAddress(set.getString("address")); + g.setPopulation(set.getInt("population")); + g.setTimeZone(set.getInt("timezone")); + g.setZonePort(set.getInt("zone_port")); + g.setPingPort(set.getInt("ping_port")); + g.setStatus(set.getInt("status")); + g.setMaxCharacters(c.getInt("GALAXY-MAX-CHARACTERS", 2)); + g.setOnlinePlayerLimit(c.getInt("GALAXY-MAX-ONLINE", 3000)); + g.setOnlineFreeTrialLimit(c.getInt("GALAXY-MAX-ONLINE", 3000)); + g.setRecommended(true); + // If locked, restricted, or full + if (p.getAccessLevel() == AccessLevel.ADMIN && g.getStatus() >= 3) + g.setStatus(2); + galaxies.add(g); + } + return galaxies; + } + + private SWGCharacter [] getCharacters(int userId) throws SQLException { + getCharacters.setInt(1, userId); + ResultSet set = getCharacters.executeQuery(); + List characters = new ArrayList(); + while (set.next()) { + SWGCharacter c = new SWGCharacter(); + c.setId(set.getInt("id")); + c.setName(set.getString("name")); + c.setRaceCrc(Race.getRace(set.getString("race")).getCrc()); + c.setGalaxyId(set.getInt("galaxyid")); + c.setType(0); // 0 = Normal (1 = Jedi, 2 = Spectral) + characters.add(c); + } + return characters.toArray(new SWGCharacter[characters.size()]); } } diff --git a/src/services/player/PlayerManager.java b/src/services/player/PlayerManager.java index c9a0f2305..39006c4a7 100644 --- a/src/services/player/PlayerManager.java +++ b/src/services/player/PlayerManager.java @@ -1,19 +1,31 @@ package services.player; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + import network.packets.Packet; +import network.packets.swg.login.ClientIdMsg; import intents.InboundPacketIntent; import resources.control.Intent; import resources.control.Manager; import resources.network.ServerType; +import resources.player.Player; public class PlayerManager extends Manager { private LoginService loginService; + private ZoneService zoneService; + + private Map players; public PlayerManager() { loginService = new LoginService(); + zoneService = new ZoneService(); + + players = new ConcurrentHashMap(); addChildService(loginService); + addChildService(zoneService); } @Override @@ -25,11 +37,47 @@ public class PlayerManager extends Manager { @Override public void onIntentReceived(Intent i) { if (i instanceof InboundPacketIntent) { - Packet p = ((InboundPacketIntent) i).getPacket(); - if (((InboundPacketIntent) i).getServerType() == ServerType.LOGIN) { - loginService.handlePacket(p); + InboundPacketIntent ipi = (InboundPacketIntent) i; + Packet packet = ipi.getPacket(); + ServerType type = ipi.getServerType(); + long networkId = ipi.getNetworkId(); + Player player = null; + if (type == ServerType.ZONE && packet instanceof ClientIdMsg) + player = transitionLoginToZone(networkId, (ClientIdMsg) packet); + else + player = players.get(networkId); + if (player == null && type == ServerType.LOGIN) { + player = new Player(networkId); + players.put(networkId, player); + } + if (player != null) { + if (type == ServerType.LOGIN) + loginService.handlePacket(player, packet); + else if (type == ServerType.ZONE) + zoneService.handlePacket(player, networkId, packet); } } } + private Player transitionLoginToZone(long networkId, ClientIdMsg clientId) { + byte [] nToken = clientId.getSessionToken(); + for (Player p : players.values()) { + byte [] pToken = p.getSessionToken(); + if (pToken.length != nToken.length) + continue; + boolean match = true; + for (int t = 0; t < pToken.length && match; t++) { + if (pToken[t] != nToken[t]) + match = false; + } + if (match) { + players.remove(p.getNetworkId()); + p.setNetworkId(networkId); + players.put(networkId, p); + return p; + } + } + return null; + } + } diff --git a/src/services/player/ZoneService.java b/src/services/player/ZoneService.java new file mode 100644 index 000000000..426ef7f06 --- /dev/null +++ b/src/services/player/ZoneService.java @@ -0,0 +1,81 @@ +package services.player; + +import network.packets.Packet; +import network.packets.soe.SessionRequest; +import network.packets.swg.login.AccountFeatureBits; +import network.packets.swg.login.ClientIdMsg; +import network.packets.swg.login.ClientPermissionsMessage; +import network.packets.swg.login.ServerId; +import network.packets.swg.login.ServerString; +import network.packets.swg.login.creation.ApproveNameRequest; +import network.packets.swg.login.creation.ApproveNameResponse; +import network.packets.swg.login.creation.ClientCreateCharacter; +import network.packets.swg.login.creation.RandomNameRequest; +import network.packets.swg.login.creation.RandomNameResponse; +import network.packets.swg.zone.HeartBeatMessage; +import resources.Race; +import resources.config.ConfigFile; +import resources.control.Service; +import resources.player.Player; +import resources.services.Config; +import utilities.namegen.SWGNameGenerator; + +public class ZoneService extends Service { + + private SWGNameGenerator nameGenerator; + + public ZoneService() { + nameGenerator = new SWGNameGenerator(); + } + + @Override + public boolean initialize() { + nameGenerator.loadAllRules(); + return super.initialize(); + } + + public void handlePacket(Player player, long networkId, Packet p) { + if (p instanceof SessionRequest) + sendServerInfo(networkId); + if (p instanceof ClientIdMsg) + handleClientIdMsg(player, (ClientIdMsg) p); + if (p instanceof RandomNameRequest) + handleRandomNameRequest(player, (RandomNameRequest) p); + if (p instanceof ApproveNameRequest) + handleApproveNameRequest(player, (ApproveNameRequest) p); + if (p instanceof ClientCreateCharacter) + handleCharCreation(player, (ClientCreateCharacter) p); + } + + private void sendServerInfo(long networkId) { + Config c = getConfig(ConfigFile.PRIMARY); + String name = c.getString("ZONE-SERVER-NAME", "ZoneServer"); + int id = c.getInt("ZONE-SERVER-ID", 1); + sendPacket(networkId, new ServerString(name + ":" + id)); + sendPacket(networkId, new ServerId(id)); + } + + private void handleClientIdMsg(Player player, ClientIdMsg clientId) { + System.out.println(player.getUsername() + " has connected to the zone server."); + sendPacket(player.getNetworkId(), new HeartBeatMessage()); + sendPacket(player.getNetworkId(), new AccountFeatureBits()); + sendPacket(player.getNetworkId(), new ClientPermissionsMessage()); + } + + private void handleRandomNameRequest(Player player, RandomNameRequest request) { + RandomNameResponse response = new RandomNameResponse(request.getRace(), ""); + String race = Race.getRace(request.getRace()).getIFF().split("_")[0]; + response.setRandomName(nameGenerator.generateRandomName(race)); + sendPacket(player.getNetworkId(), response); + } + + private void handleApproveNameRequest(Player player, ApproveNameRequest request) { + sendPacket(player.getNetworkId(), new ApproveNameResponse(request.getName())); + } + + private void handleCharCreation(Player player, ClientCreateCharacter create) { + System.out.println("Create Character: " + create.getName()); + } + + +}