/** * Warlock, the open-source cross-platform game client * * Copyright 2008, Warlock LLC, and individual contributors as indicated * by the @authors tag. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ /* * Created on Sep 20, 2004 */ package cc.warlock.core.stormfront.network; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import cc.warlock.core.configuration.Profile; import cc.warlock.core.network.IConnection; import cc.warlock.core.network.ILineConnectionListener; import cc.warlock.core.network.LineConnection; import cc.warlock.core.stormfront.network.ISGEGame.AccountStatus; import cc.warlock.core.stormfront.network.ISGEGame.GameURL; /** * @author Marshall */ public class SGEConnection extends LineConnection implements ILineConnectionListener { public static final String SGE_SERVER = "eaccess.play.net"; public static final int SGE_PORT = 7900; public static final int INVALID_PASSWORD = 0; public static final int INVALID_ACCOUNT = 1; public static final int ACCOUNT_REJECTED = 2; public static final int LOGIN_SUCCESS = 3; public static final int ACCOUNT_EXPIRED = 4; public static final String PROPERTY_KEY = "KEY"; public static final String PROPERTY_GAMEHOST = "GAMEHOST"; public static final String PROPERTY_GAMEPORT = "GAMEPORT"; public static final String NEW_CHARACTER_CODE = "0"; protected static final int SGE_NONE = 0; protected static final int SGE_INITIAL = 1; protected static final int SGE_ACCOUNT = 2; protected static final int SGE_MENU = 3; protected static final int SGE_GAME = 4; protected static final int SGE_PICK = 5; protected static final int SGE_CHARACTERS = 6; protected static final int SGE_LOAD = 7; protected static final int LOGIN_READY = 0; protected static final int LOGIN_FINISHED = 1; protected static final int GAMES_READY = 2; protected static final int CHARACTERS_READY = 3; protected static final int READY_TO_PLAY = 4; protected static final int SGE_ERROR = 5; protected int state, errorCode; protected String passwordHash; protected ArrayList<ISGEConnectionListener> sgeListeners; protected boolean retrieveGameInfo = false; protected HashMap<String, String> characters, loginProperties; protected ArrayList<SGEGame> games; public SGEConnection () { super(); addConnectionListener(this); state = SGE_NONE; sgeListeners = new ArrayList<ISGEConnectionListener>(); games = new ArrayList<SGEGame>(); characters = new HashMap<String, String>(); loginProperties = new HashMap<String, String>(); } protected void resetState () { state = SGE_NONE; passwordHash = null; games.clear(); characters.clear(); loginProperties.clear(); } public void connect () { try { if (!connected) { resetState(); connect (SGE_SERVER, SGE_PORT); } } catch (IOException e) { e.printStackTrace(); } } public void addSGEConnectionListener (ISGEConnectionListener listener) { sgeListeners.add (listener); } public void login (String username, String password) { state = SGE_ACCOUNT; // This is an ugly hack. If we try to append the encrypted password as a "String", the java language String will // try interpreting the characters in that string as Unicode, and fail miserably, giving us a botched encrypted string. // The only way to make this work the right way is manually cast each and every "char" primitive to "byte" and send it // raw over the socket. try { byte usernameBytes[] = username.getBytes(); byte bytes[] = new byte[2 + usernameBytes.length + 1 + password.length() + 1]; bytes[0] = (byte) 'A'; bytes[1] = (byte) '\t'; System.arraycopy(usernameBytes, 0, bytes, 2, usernameBytes.length); bytes[usernameBytes.length + 2] = (byte)'\t'; char encrypted[] = encryptPassword(password, passwordHash); for (int i = 0; i < password.length(); i++) { int index = usernameBytes.length + 3 + i; bytes[index] = (byte) encrypted[i]; } bytes[bytes.length - 1] = (byte) '\n'; send (bytes); } catch (IOException e) { e.printStackTrace(); } } protected char[] encryptPassword (String password, String hash) { char encrypted[] = new char[33]; for (int i = 0; i < 32 && password.length() > i && hash.length() > i; i++) { encrypted[i] = (char) ((hash.charAt(i) ^ (password.charAt(i) - 32)) + 32); } String encryptedString = ""; for (int i = 0; i < password.length()+1; i++) { encryptedString += encrypted[i]; } return encrypted; } public void selectGame (String gameCode) { state = SGE_GAME; try { sendLine("G\t" +gameCode); } catch (IOException e) { e.printStackTrace(); } } public void selectCharacter (String characterCode) { state = SGE_CHARACTERS; try { sendLine("L\t"+characterCode+"\tSTORM"); } catch (IOException e) { e.printStackTrace(); } } public void connected(IConnection connection) { try { connection.sendLine("K"); state = SGE_INITIAL; } catch (IOException e) { e.printStackTrace(); } } public static class SGEGame implements ISGEGame { public AccountStatus accountStatus; public int interval; public String gameCode, gameName; public HashMap<GameURL, String> gameURLs = new HashMap<GameURL, String>(); public AccountStatus getAccountStatus() { return accountStatus; } public int getAccountStatusInterval() { return interval; } public String getGameCode() { return gameCode; } public String getGameName() { return gameName; } public String getGameURL(GameURL url) { if (!gameURLs.containsKey(url)) { return null; } String gameUrl = gameURLs.get(url); if (gameUrl.indexOf("http://") == -1 && gameUrl.indexOf("https://") == -1) { String root = gameURLs.get(GameURL.Root); return "http://www.play.net/" + root + "/" + gameUrl; } else { return gameUrl; } } } protected ListIterator<SGEGame> gameIterator; protected SGEGame currentGame; protected boolean retrievingGames = false; public void dataReady(IConnection connection, char[] data, int start, int length) { // TODO Auto-generated method stub } public void lineReady(IConnection connection, String line) { try { System.out.println("SGE: " + line); if (state == SGE_INITIAL) { passwordHash = line; fireEvent (LOGIN_READY); return; } switch (line.charAt(0)) { /* response from user/pass sending */ case 'A': { if (line.indexOf("REJECT") != -1) { errorCode = ACCOUNT_REJECTED; } else if (line.indexOf("PASSWORD") != -1) { errorCode = INVALID_PASSWORD; } else if (line.indexOf("NORECORD") != -1) { errorCode = INVALID_ACCOUNT; } else errorCode = LOGIN_SUCCESS; if (errorCode == LOGIN_SUCCESS) { sendLine("M"); state = SGE_GAME; fireEvent (LOGIN_FINISHED); } else { fireEvent (SGE_ERROR); } } break; /* Server is giving us a list of games */ case 'M': { games.clear(); String tokens[] = line.split("\t"); for (int i = 1; i < tokens.length; i+=2) { SGEGame game = new SGEGame(); game.gameCode = tokens[i]; game.gameName = tokens[i+1]; games.add(game); } if (retrieveGameInfo) { retrievingGames = true; gameIterator = games.listIterator(); if (gameIterator.hasNext()) { currentGame = gameIterator.next(); sendLine("G\t" + currentGame.gameCode); } } else { fireEvent(GAMES_READY); } } break; /* Server is responding with Game Details */ case 'G': { if (retrievingGames) { String tokens[] = line.split("\t"); if ("NORMAL".equals(tokens[2])) { currentGame.accountStatus = AccountStatus.Normal; } else if ("TRIAL".equals(tokens[2])) { currentGame.accountStatus = AccountStatus.Trial; } else if ("EXPIRED".equals(tokens[2])) { currentGame.accountStatus = AccountStatus.Expired; } else { currentGame.accountStatus = AccountStatus.Unknown; } int startIndex = 3; if (currentGame.accountStatus != AccountStatus.Unknown) { startIndex = 4; currentGame.interval = Integer.parseInt(tokens[3]); } for (int i = startIndex; i < tokens.length; i++) { if (tokens[i].indexOf("=") != -1) { String keyval[] = tokens[i].split("="); GameURL url = GameURL.getURL(keyval[0]); if (url != null) { currentGame.gameURLs.put(url, keyval[1]); } } } if (gameIterator.hasNext()) { currentGame = (SGEGame) gameIterator.next(); sendLine("G\t" + currentGame.gameCode); } else { retrievingGames = false; fireEvent(GAMES_READY); } } else { sendLine("C"); } } break; /* Server is giving us a list of characters in that game */ case 'C': { characters.clear(); String tokens[] = line.split("\t"); for (int i = 5; i < tokens.length; i+=2) { characters.put(tokens[i], tokens[i+1]); } fireEvent (CHARACTERS_READY); } break; /* Server is responding to a request to play a character */ case 'L': { String tokens[] = line.split("\t"); String loginResponse = tokens[1]; if ("OK".equals(loginResponse)) { for (int i = 2; i < tokens.length; i++) { String property[] = tokens[i].split("="); loginProperties.put(property[0], property[1]); } fireEvent(READY_TO_PLAY); } else if ("PROBLEM".equals(loginResponse)) { errorCode = ACCOUNT_EXPIRED; fireEvent(SGE_ERROR); } disconnect(); } break; } } catch (IOException e) { e.printStackTrace(); } } protected void fireEvent (final int event) { for (ISGEConnectionListener listener : sgeListeners) { switch (event) { case LOGIN_READY: listener.loginReady(SGEConnection.this); break; case LOGIN_FINISHED: listener.loginFinished(SGEConnection.this); break; case GAMES_READY: listener.gamesReady(SGEConnection.this, games); break; case CHARACTERS_READY: listener.charactersReady(SGEConnection.this, characters); break; case READY_TO_PLAY: listener.readyToPlay(SGEConnection.this, loginProperties); break; case SGE_ERROR: listener.sgeError(SGEConnection.this, errorCode); break; default: break; } } } public void disconnected(IConnection connection) { } private static class AutoLoginListener extends SGEConnectionListener { public Profile profile; public boolean loggedIn = false; public Map <String,String> properties = null; public void loginReady(SGEConnection connection) { connection.login(profile.getAccount().getAccountName(), profile.getAccount().getPassword()); } public void loginFinished(SGEConnection connection, int status) {/* noop */} public void gamesReady(SGEConnection connection, List<? extends ISGEGame> games) { connection.selectGame(profile.getGameCode()); } public void charactersReady(SGEConnection connection, Map<String, String> characters) { connection.selectCharacter(profile.getId()); } @Override public void readyToPlay(SGEConnection connection, Map<String, String> loginProperties) { this.properties = loginProperties; this.loggedIn = true; } } public static Map<String, String> autoLogin (Profile profile, ISGEConnectionListener extraListener) { SGEConnection connection = new SGEConnection(); connection.setRetrieveGameInfo(false); AutoLoginListener listener = new AutoLoginListener(); listener.profile = profile; connection.addSGEConnectionListener(listener); if (extraListener != null) { connection.addSGEConnectionListener(extraListener); } connection.connect(); while (!listener.loggedIn) { try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return listener.properties; } public void setRetrieveGameInfo(boolean retrieveGameInfo) { this.retrieveGameInfo = retrieveGameInfo; } public void connectionError(IConnection connection, ErrorType errorType) { } }