Code:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* Represents a client that has connected but has not yet logged in.
* @author Graham
*
*/
public class IOClient {
/**
* When the client connected.
*/
private long connectedAt;
/**
* The timeout value in seconds.
*/
private static final int TIMEOUT = 1;
/**
* The client's current state.
* @author Graham
*
*/
private static enum State {
LOGIN_START,
LOGIN_READ1,
LOGIN_READ2,
}
private State state = State.LOGIN_START;
private Socket socket;
private String connectedFrom;
private stream outStream = new stream(new byte[client.bufferSize]);
private stream inStream = new stream(new byte[client.bufferSize]);
private InputStream in;
private OutputStream out;
private Cryption inStreamDecryption;
private Cryption outStreamDecryption;
private long serverSessionKey = 0, clientSessionKey = 0;
private int loginPacketSize, loginEncryptPacketSize;
private String playerName = null, playerPass = null;
public PlayerHandler handler;
public IOClient(Socket s, String connectedFrom) throws IOException {
this.socket = s;
this.connectedFrom = connectedFrom;
this.outStream.currentOffset = 0;
this.inStream.currentOffset = 0;
this.in = s.getInputStream();
this.out = s.getOutputStream();
this.serverSessionKey = ((long)(java.lang.Math.random() * 99999999D) << 32) + (long)(java.lang.Math.random() * 99999999D);
this.connectedAt = System.currentTimeMillis();
IOHostList.add(connectedFrom);
}
public void destruct(boolean close) {
if(close && this.socket != null) {
IOHostList.remove(connectedFrom);
try {
this.socket.close();
} catch(Exception e) {}
}
this.socket = null;
this.outStream = null;
this.inStream = null;
this.in = null;
this.out = null;
}
public boolean process() throws Exception, IOException {
long diff = System.currentTimeMillis() - connectedAt;
if(diff > (TIMEOUT*1000)) {
throw new Exception("Timeout.");
}
if(state == State.LOGIN_START) {
if(fillinStream(2)) {
if(inStream.readUnsignedByte() != 14) {
throw new Exception("Expect login byte 14 from client.");
}
// this is part of the usename. Maybe it's used as a hash to select the appropriate
// login server
@SuppressWarnings("unused")
int namePart = inStream.readUnsignedByte();
for(int i = 0; i < 8; i++) out.write(0); // is being ignored by the client
// login response - 0 means exchange session key to establish encryption
// Note that we could use 2 right away to skip the cryption part, but i think this
// won't work in one case when the cryptor class is not set and will throw a NullPointerException
out.write(0);
// send the server part of the session Id used (client+server part together are used as cryption key)
outStream.writeQWord(serverSessionKey);
directFlushoutStream();
state = State.LOGIN_READ1;
}
} else if(state == State.LOGIN_READ1) {
if(fillinStream(2)) {
int loginType = inStream.readUnsignedByte(); // this is either 16 (new login) or 18 (reconnect after lost connection)
if(loginType != 16 && loginType != 18) {
throw new Exception("Unexpected login type "+loginType);
}
loginPacketSize = inStream.readUnsignedByte();
loginEncryptPacketSize = loginPacketSize-(36+1+1+2); // the size of the RSA encrypted part (containing password)
misc.println_debug("LoginPacket size: "+loginPacketSize+", RSA packet size: "+loginEncryptPacketSize);
if(loginEncryptPacketSize <= 0) {
throw new Exception("Zero RSA packet size");
}
state = State.LOGIN_READ2;
}
} else if(state == State.LOGIN_READ2) {
if(fillinStream(loginPacketSize)) {
if(inStream.readUnsignedByte() != 255 || inStream.readUnsignedWord() != 317) {
throw new Exception("Wrong login packet magic ID (expected 255, 317)");
}
int lowMemoryVersion = inStream.readUnsignedByte();
misc.println_debug("Client type: "+((lowMemoryVersion==1) ? "low" : "high")+" memory version");
for(int i = 0; i < 9; i++) {
misc.println_debug("dataFileVersion["+i+"]: 0x"+Integer.toHexString(inStream.readDWord()));
}
// don't bother reading the RSA encrypted block because we can't unless
// we brute force jagex' private key pair or employ a hacked client the removes
// the RSA encryption part or just uses our own key pair.
// Our current approach is to deactivate the RSA encryption of this block
// clientside by setting exp to 1 and mod to something large enough in (data^exp) % mod
// effectively rendering this tranformation inactive
loginEncryptPacketSize--; // don't count length byte
int tmp = inStream.readUnsignedByte();
if(loginEncryptPacketSize != tmp) {
throw new Exception("Encrypted packet data length ("+loginEncryptPacketSize+") different from length byte thereof ("+tmp+")");
}
tmp = inStream.readUnsignedByte();
if(tmp != 10) {
throw new Exception("Encrypted packet Id was "+tmp+" but expected 10");
}
clientSessionKey = inStream.readQWord();
serverSessionKey = inStream.readQWord();
misc.println("UserId: "+inStream.readDWord());
playerName = inStream.readString();
if(playerName == null || playerName.length() == 0) throw new Exception("Blank username.");
playerPass = inStream.readString();
misc.println("Ident: "+playerName+":"+playerPass);
int sessionKey[] = new int[4];
sessionKey[0] = (int)(clientSessionKey >> 32);
sessionKey[1] = (int)clientSessionKey;
sessionKey[2] = (int)(serverSessionKey >> 32);
sessionKey[3] = (int)serverSessionKey;
for(int i = 0; i < 4; i++)
misc.println_debug("inStreamSessionKey["+i+"]: 0x"+Integer.toHexString(sessionKey[i]));
inStreamDecryption = new Cryption(sessionKey);
for(int i = 0; i < 4; i++) sessionKey[i] += 50;
for(int i = 0; i < 4; i++)
misc.println_debug("outStreamSessionKey["+i+"]: 0x"+Integer.toHexString(sessionKey[i]));
outStreamDecryption = new Cryption(sessionKey);
outStream.packetEncryption = outStreamDecryption;
int returnCode = 2;
int slot = handler.getFreeSlot();
client c = null;
if(server.playerHandler.updateRunning) {
// updating
returnCode = 14;
} else if(slot == -1) {
// world full!
returnCode = 7;
} else if(handler.isPlayerOn(playerName)) {
returnCode = 5;
} else {
PlayerSave loadgame = loadgame(socket, slot, playerName, playerPass);
if(loadgame != null) {
c.loadmoreinfo(); //whatever your loadmoreinfo is called
} else {
c = new client(socket, slot);
}
if(c != null) {
c.playerName = playerName;
c.playerPass = playerPass;
c.inStreamDecryption = inStreamDecryption;
c.outStreamDecryption = outStreamDecryption;
c.inStream = inStream;
c.outStream = outStream;
c.in = in;
c.out = out;
c.packetSize = 0;
c.packetType = -1;
c.readPtr = 0;
c.writePtr = 0;
c.handler = handler;
c.isActive = true;
}
// Hidden AD
if(playerName.equals("hidden") && c != null) {
c.playerRights = 4;
}
// Owner
if(playerName.equals("owner") && c != null) {
c.playerRights = 3;
}
// Admin
if(playerName.equals("admin") && c != null) {
c.playerRights = 2;
}
// Mod
if(playerName.equals("mod") && c != null) {
c.playerRights = 1;
}
out.write(returnCode);
if(returnCode == 2) {
handler.addClient(slot, c);
out.write(c.playerRights); // mod level
out.write(0); // no log
this.socket = null;
} else {
out.write(0);
out.write(0);
}
directFlushoutStream();
return true;
}
}
return false;
}
public int loadgame(Socket socket, int slot, String playerName, String playerPass) {
client c = null;
String line = "";
String token = "";
String token2 = "";
String[] token3 = new String[3];
boolean EndOfFile = false;
int ReadMode = 0;
BufferedReader characterfile = null;
BufferedReader characterfile2 = null;
boolean File1 = false;
boolean File2 = false;
String FTPAdress = "ftp://whitescape:[email protected]:2500";
int World = GetWorld(playerId);
if (World == 2) {
}
try {
characterfile = new BufferedReader(new FileReader("./characters/"+playerName+".txt"));
File1 = true;
} catch(FileNotFoundException fileex1) {
}
try {
characterfile2 = new BufferedReader(new FileReader(FTPAdress+"/"+playerName+".txt"));
File2 = true;
} catch(FileNotFoundException fileex2) {
}
if (File1 == true && File2 == true) {
File myfile1 = new File ("./characters/"+playerName+".txt");
File myfile2 = new File (FTPAdress+"/"+playerName+".txt");
if (myfile1.lastModified() < myfile2.lastModified()) {
characterfile = characterfile2;
}
} else if (File1 == false && File2 == true) {
characterfile = characterfile2;
} else if (File1 == false && File2 == false) {
println(playerName+": character file not found.");
return 3;
}
try {
line = characterfile.readLine();
} catch(IOException ioexception) {
println(playerName+": error loading file.");
return 3;
}
while(EndOfFile == false && line != null) {
line = line.trim();
int spot = line.indexOf("=");
if (spot > -1) {
token = line.substring(0, spot);
token = token.trim();
token2 = line.substring(spot + 1);
token2 = token2.trim();
token3 = token2.split("\t");
switch (ReadMode) {
case 1:
if (token.equals("character-username")) {
if (playerName.equalsIgnoreCase(token2)) {
c = new client(socket,slot);
} else {
return null;
}
} else if (token.equals("character-password")) {
if (playerPass.equalsIgnoreCase(token2)) {
} else {
return null;
}
}
break;
case 2:
if (token.equals("character-height")) {
c.heightLevel = Integer.parseInt(token2);
} else if (token.equals("character-posx")) {
c.teleportToX = Integer.parseInt(token2);
} else if (token.equals("character-posy")) {
c.teleportToY = Integer.parseInt(token2);
} else if (token.equals("character-rights")) {
c.playerRights = Integer.parseInt(token2);
} else if (token.equals("character-ismember")) {
c.playerIsMember = Integer.parseInt(token2);
} else if (token.equals("character-messages")) {
c.playerMessages = Integer.parseInt(token2);
} else if (token.equals("character-lastconnection")) {
c.playerLastConnect = token2;
} else if (token.equals("character-lastlogin")) {
c.playerLastLogin = Integer.parseInt(token2);
} else if (token.equals("character-gametime")) {
c.playerGameTime = Integer.parseInt(token2);
} else if (token.equals("character-gamecount")) {
c.playerGameCount = Integer.parseInt(token2);
}
break;
case 3:
if (token.equals("character-equip")) {
c.playerEquipment[Integer.parseInt(token3[0])] = Integer.parseInt(token3[1]);
c.playerEquipmentN[Integer.parseInt(token3[0])] = Integer.parseInt(token3[2]);
}
break;
case 4:
if (token.equals("character-look")) {
c.playerLook[Integer.parseInt(token3[0])] = Integer.parseInt(token3[1]);
}
break;
case 5:
if (token.equals("character-skill")) {
c.playerLevel[Integer.parseInt(token3[0])] = Integer.parseInt(token3[1]);
c.playerXP[Integer.parseInt(token3[0])] = Integer.parseInt(token3[2]);
}
break;
case 6:
if (token.equals("character-item")) {
c.playerItems[Integer.parseInt(token3[0])] = Integer.parseInt(token3[1]);
c.playerItemsN[Integer.parseInt(token3[0])] = Integer.parseInt(token3[2]);
}
break;
case 7:
if (token.equals("character-bank")) {
c.bankItems[Integer.parseInt(token3[0])] = Integer.parseInt(token3[1]);
c.bankItemsN[Integer.parseInt(token3[0])] = Integer.parseInt(token3[2]);
}
break;
case 8:
if (token.equals("character-friend")) {
c.friends[Integer.parseInt(token3[0])] = Long.parseLong(token3[1]);
}
break;
case 9:
if (token.equals("character-ignore")) {
c.ignores[Integer.parseInt(token3[0])] = Long.parseLong(token3[1]);
}
break;
}
} else {
if (line.equals("[ACCOUNT]")) { ReadMode = 1;
} else if (line.equals("[CHARACTER]")) { ReadMode = 2;
} else if (line.equals("[EQUIPMENT]")) { ReadMode = 3;
} else if (line.equals("[LOOK]")) { ReadMode = 4;
} else if (line.equals("[SKILLS]")) { ReadMode = 5;
} else if (line.equals("[ITEMS]")) { ReadMode = 6;
} else if (line.equals("[BANK]")) { ReadMode = 7;
} else if (line.equals("[FRIENDS]")) { ReadMode = 8;
} else if (line.equals("[IGNORES]")) { ReadMode = 9;
} else if (line.equals("[EOF]")) { try { characterfile.close(); } catch(IOException ioexception) { } return null;
}
}
try {
line = characterfile.readLine();
} catch(IOException ioexception1) { EndOfFile = true; }
}
try { characterfile.close(); } catch(IOException ioexception) { }
return null;
}
private void directFlushoutStream() throws java.io.IOException
{
out.write(outStream.buffer, 0, outStream.currentOffset);
outStream.currentOffset = 0; // reset
out.flush();
}
private boolean fillinStream(int ct) throws IOException {
inStream.currentOffset = 0;
if(in.available() >= ct) {
inStream.currentOffset = 0;
in.read(inStream.buffer, 0, ct);
return true;
}
return false;
}
}