Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package org.darkmeyer.net.protocol;
- import java.math.BigInteger;
- import java.security.SecureRandom;
- import org.apache.mina.core.buffer.IoBuffer;
- import org.apache.mina.core.session.IoSession;
- import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
- import org.apache.mina.filter.codec.ProtocolCodecFilter;
- import org.apache.mina.filter.codec.ProtocolDecoderOutput;
- import org.darkmeyer.Server;
- import org.darkmeyer.cache.Cache;
- import org.darkmeyer.cache.UpdateServer;
- import org.darkmeyer.game.Constants;
- import org.darkmeyer.game.RS2Server;
- import org.darkmeyer.game.world.entity.player.PlayerDetails;
- import org.darkmeyer.net.PacketBuilder;
- import org.darkmeyer.utility.IoBufferUtils;
- import org.darkmeyer.utility.TextUtils;
- import org.darkmeyer.website.impl.PlayerManager;
- /**
- * Login protocol decoding class.
- *
- * @author Graham Edgecombe
- *
- */
- public class RS2LoginDecoder extends CumulativeProtocolDecoder {
- /**
- * Opcode stage.
- */
- private static final int STATE_OPCODE = 0;
- /**
- * Login stage.
- */
- private static final int STATE_LOGIN = 1;
- /**
- * Precrypted stage.
- */
- private static final int STATE_PRECRYPTED = 2;
- /**
- * Crypted stage.
- */
- private static final int STATE_CRYPTED = 3;
- /**
- * Update stage.
- */
- private static final int STATE_UPDATE = -1;
- /**
- * World stage.
- */
- public static final int STATE_WORLD = -2;
- /**
- * Game opcode.
- */
- private static final int OPCODE_GAME = 14;
- /**
- * Update opcode.
- */
- private static final int OPCODE_UPDATE = 15;
- /**
- * Creation opcode.
- */
- public static final int OPCODE_BIRTH = 20;
- /**
- * Creation opcode.
- */
- public static final int OPCODE_USERNAME = 21;
- /**
- * Creation opcode.
- */
- public static final int OPCODE_CONFIRM = 22;
- /**
- * World opcode.
- */
- public static final int OPCODE_WORLD = 255;
- /**
- * Secure random number generator.
- */
- private static final SecureRandom RANDOM = new SecureRandom();
- /**
- * The name hash is a simple hash of the name which is suspected to be used
- * to select the appropriate login server.
- */
- private int nameHashCode;
- private static final BigInteger RSA_MODULUS = new BigInteger("125498824615949350413874884977165023857837620717675913416022592156144512829309361522499485234195039331502997944944451037585403884172514528660074650075696016312742090328026374541501140572023823295937082249637679411659266953715582336176458793896511013722964252960671897268005422957383371176404977134935689179731");
- private static final BigInteger RSA_EXPONENT = new BigInteger("58801782313532152877898837191110462602844497297369032352195030857969231967432176698222251447066955654858515920707527930957092895178073510102154695513593800142844432039991902404675980615338532723208252107984745718385941397714011167941039598108432442828522345374662573784309558472354555033932567667781343503425");
- @SuppressWarnings("unused")
- @Override
- protected boolean doDecode(final IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
- int state = (Integer) session.getAttribute("state", STATE_OPCODE);
- if (!RS2Server.getEngine().isRunning() && state != STATE_LOGIN) {
- //System.out.println("Cannot serve cache when it has yet to be loaded.");
- session.close(false);
- return false;
- }
- switch (state) {
- case STATE_UPDATE:
- if(Constants.USE_SEPARATE_FILE_SERVER) {
- in.rewind();
- return false;
- }
- while (in.remaining() >= 4) {
- int type = in.get() & 0xff;
- final int cache = in.get() & 0xff;
- final int id = in.getShort() & 0xffff;
- // if(cache == 6)
- // return true;
- switch (type) {
- case 0: // non-urgent
- // requests.add(new Request(cache, id));
- session.write(UpdateServer.getRequest(cache, id));
- break;
- case 1: // urgent
- session.write(UpdateServer.getRequest(cache, id));
- break;
- }
- // case 2: // clear requests
- // case 3:
- // requests.clear();
- // break;
- // case 4: // client error
- // break;
- // }
- // while (requests.size() > 0) {
- // Request request = (Request) requests.removeFirst();
- // session.write(UpdateServer.getRequest(request.getCache(),
- // request
- // .getId()));
- // Js5FileRequest request = (Js5FileRequest)
- // requests.removeFirst();
- // Js5Engine.getJs5Engine().addRequest(request);
- //}
- return true;
- }
- in.rewind();
- return false;
- case STATE_OPCODE:
- if (in.remaining() >= 1) {
- /*
- * Here we read the first opcode which indicates the type of
- * connection.
- *
- * 14 = game 15 = update
- *
- * Updating is disabled in the vast majority of 317 clients.
- */
- int opcode = in.get() & 0xFF;
- switch (opcode) {
- case OPCODE_GAME:
- session.setAttribute("state", STATE_LOGIN);
- return true;
- case OPCODE_UPDATE:
- if (in.remaining() >= 4) {
- if (in.getInt() != Server.VERSION) {
- System.out.println("Wrong update version");
- session.write(new PacketBuilder().put((byte) 6).toPacket());
- session.close(false);
- break;
- }
- session.setAttribute("state", STATE_UPDATE);
- session.write(new PacketBuilder().put((byte) 0).toPacket());
- return true;
- }
- in.rewind();
- return false;
- /*
- * if (in.remaining() >= 4) { if(in.getInt() !=
- * Server.VERSION) {
- * System.out.println("server version not matched");
- * session.write(new PacketBuilder().put((byte)
- * 6).toPacket()); session.close(false); break; }
- * //session.setAttribute("state", STATE_UPDATE);
- * session.write(new PacketBuilder().put((byte)
- * 0).toPacket());
- *
- * session.getFilterChain().remove("protocol");
- * session.getFilterChain().addFirst("protocol", new
- * ProtocolCodecFilter(RS2CodecFactory.JS5));
- *
- * return true; } in.rewind(); session.setAttribute("state",
- * STATE_LOGIN); return false;
- */
- default:
- System.out.println("Invalid opcode : " + opcode);
- session.close(false);
- break;
- }
- } else {
- in.rewind();
- return false;
- }
- break;
- case STATE_LOGIN:
- if (in.remaining() >= 1) {
- /*
- * The name hash is a simple hash of the name which is suspected
- * to be used to select the appropriate login server.
- */
- nameHashCode = in.get() & 0xFF;
- /*
- * We generated the server session key using a SecureRandom
- * class for security.
- */
- long serverKey = RANDOM.nextLong();
- /*
- * The initial response is just 0s which the client is set to
- * ignore (probably some sort of modification).
- */
- session.write(new PacketBuilder().put((byte) 0).putLong(serverKey).toPacket());
- session.setAttribute("state", STATE_PRECRYPTED);
- session.setAttribute("serverKey", serverKey);
- return true;
- }
- break;
- case STATE_PRECRYPTED:
- if (in.remaining() >= 2) {
- /*
- * We read the type of login.
- *
- * 16 = normal 18 = reconnection
- */
- int loginOpcode = in.get() & 0xFF;
- if (loginOpcode != 16 && loginOpcode != 18) {
- System.out.println("Invalid login opcode : " + loginOpcode);
- session.close(false);
- in.rewind();
- return false;
- }
- /*
- * We read the size of the login packet.
- */
- int loginSize = in.get() & 0xFF;
- /*
- * And calculated how long the encrypted block will be.
- */
- int loginEncryptSize = loginSize - (69);
- /*
- * This could be invalid so if it is we ignore it.
- */
- if (loginEncryptSize <= 0) {
- System.out.println("Encrypted packet size zero or negative : " + loginEncryptSize);
- session.close(false);
- in.rewind();
- return false;
- }
- session.setAttribute("state", STATE_CRYPTED);
- session.setAttribute("size", loginSize);
- session.setAttribute("encryptSize", loginEncryptSize);
- return true;
- }
- break;
- case STATE_CRYPTED:
- int size = (Integer) session.getAttribute("size");
- int encryptSize = (Integer) session.getAttribute("encryptSize");
- if (in.remaining() >= size) {
- /*
- * We now read a short which is the client version and check if
- * it equals 464.
- */
- final int version = in.getInt();
- final int gameframe = in.getInt();
- final int hitmarks = in.getInt();
- final boolean orbsOn = in.getInt() == 1;
- final boolean hpbars = in.getInt() == 1;
- final boolean newCursors = in.getInt() == 1;
- final boolean newMenus = in.getInt() == 1;
- final boolean newHits = in.getInt() == 1;
- final boolean tweening = in.getInt() == 1;
- final boolean hd = in.getInt() == 1;
- final boolean hdOnLogin = in.getInt() == 1;
- final int clientSize = in.getInt();
- final boolean isCensorOn = in.getInt() == 1;
- final boolean wrong = !(version == 666 || version == 602 || version == 464 || version == 562 || version == 530 || version == 474);
- if (wrong) {
- System.out.println("wrong version: " + version);
- session.write(new PacketBuilder().put((byte)3).toPacket());
- session.close(false);
- in.rewind();
- return false;
- }
- final boolean wrong2 = !(gameframe == 562 || gameframe == 525 || gameframe == 464 || gameframe == 474 || gameframe == 530);
- if (wrong2) {
- System.out.println("wrong gameframe: " + gameframe);
- session.write(new PacketBuilder().put((byte)3).toPacket());
- session.close(false);
- in.rewind();
- return false;
- }
- final boolean lowMemoryVersion = (in.get() & 0xFF) == 1;
- boolean outdated = false;
- for (int i = 0; i < 16; i++) {
- int check = i;
- if (version == 666) {
- if (check == 0) {
- check = 26;
- } else if (check == 1) {
- check = 27;
- } else if (check == 7) {
- check = 28;
- }
- } else if (version == 602) {
- if (check == 0) {
- check = 22;
- } else if (check == 1) {
- check = 23;
- } else if (check == 7) {
- check = 24;
- }
- } else if (version == 562) {
- if (check == 0) {
- check = 16;
- } else if (check == 1) {
- check = 17;
- } else if (check == 7) {
- check = 18;
- }
- } else if (version == 530) {
- if (check == 0) {
- check = 19;
- } else if (check == 1) {
- check = 20;
- } else if (check == 7) {
- check = 21;
- }
- }
- int crc = in.getInt();
- int cachedCrc = Cache.getCacheFileManagers()[check].getInformation().getInformationContainer().getCrc();
- if(cachedCrc != crc) {
- outdated = true;
- }
- }
- /*
- * The encrypted size includes the size byte which we don't
- * need.
- */
- encryptSize--;
- /*
- * We check if there is a mismatch in the sizing.
- */
- int reportedSize = in.get() & 0xFF;
- if (reportedSize != encryptSize) {
- System.out.println("Reported size not equal to encrypted size");
- session.write(new PacketBuilder().put((byte)3).toPacket());
- session.close(false);
- in.rewind();
- return false;
- }
- byte[] rsaPayload = new byte[encryptSize];
- in.get(rsaPayload);
- IoBuffer rsaBuffer = IoBuffer.wrap(new BigInteger(rsaPayload).modPow(RSA_EXPONENT, RSA_MODULUS).toByteArray());
- /*
- * We now read the encrypted block opcode (although in most 317
- * clients and this server the RSA is disabled) and check it is
- * equal to 10.
- */
- int rsaOpcode = rsaBuffer.get() & 0xff;
- if (rsaOpcode != 10) {
- System.out.println("Invalid login block(RSA) opcode : " + rsaOpcode);
- session.write(new PacketBuilder().put((byte)3).toPacket());
- session.close(false);
- in.rewind();
- return false;
- }
- /*
- * We read the client's session key.
- */
- long clientKey = rsaBuffer.getLong();
- /*
- * And verify it has the correct server session key.
- */
- long serverKey = (Long) session.getAttribute("serverKey");
- long reportedServerKey = rsaBuffer.getLong();
- if (reportedServerKey != serverKey) {
- System.out.println("Server key mismatch (expected : " + serverKey + ", reported : " + reportedServerKey + ")");
- session.write(new PacketBuilder().put((byte)3).toPacket());
- session.close(false);
- in.rewind();
- return false;
- }
- /*
- * The UID, found in random.dat in newer clients and uid.dat in
- * older clients is a way of identifying a computer.
- *
- * However, some clients send a hardcoded or random UID, making
- * it useless in the private server scene.
- */
- int uid = rsaBuffer.getInt();
- String name = TextUtils.formatName(IoBufferUtils.getRS2String(rsaBuffer));
- name = name.replace("_", " ");
- name = name.trim().replaceAll("\\s+", " ");
- if (name.length() > Constants.USERNAME_LENGTH_LIMIT) {
- PacketBuilder bldr = new PacketBuilder();
- bldr.put((byte) 3);
- session.write(bldr.toPacket());
- return false;
- }
- String pass = IoBufferUtils.getRS2String(rsaBuffer);
- if (pass.length() > Constants.PASSWORD_LENGTH_LIMIT) {
- session.write(new PacketBuilder().put((byte)3).toPacket());
- session.close(false);
- in.rewind();
- return false;
- }
- if (!RS2Server.getEngine().isRunning()) {
- session.write(new PacketBuilder().put((byte)3).toPacket());
- session.close(false);
- in.rewind();
- return false;
- }
- /*
- * We check if hash matches
- */
- long user_hash = TextUtils.stringToLong(name);
- int expectedHashCode = (int) (user_hash >> 16 & 31L);
- if (expectedHashCode != nameHashCode) {
- session.write(new PacketBuilder().put((byte)3).toPacket());
- session.close(false);
- in.rewind();
- return false;
- }
- /*
- * And setup the ISAAC cipher which is used to encrypt and
- * decrypt opcodes.
- *
- * However, without RSA, this is rendered useless anyway.
- */
- int[] sessionKey = new int[4];
- sessionKey[0] = (int) (clientKey >> 32);
- sessionKey[1] = (int) clientKey;
- sessionKey[2] = (int) (reportedServerKey >> 32);
- sessionKey[3] = (int) reportedServerKey;
- session.removeAttribute("state");
- session.removeAttribute("serverKey");
- session.removeAttribute("size");
- session.removeAttribute("encryptSize");
- ISAACCipher inCipher = new ISAACCipher(sessionKey);
- for (int i = 0; i < 4; i++) {
- sessionKey[i] += 50;
- }
- ISAACCipher outCipher = new ISAACCipher(sessionKey);
- /*
- * Now, the login has completed, and we do the appropriate
- * things to fire off the chain of events which will load and
- * check the saved games etc.
- */
- session.getFilterChain().remove("protocol");
- session.getFilterChain().addFirst("protocol", new ProtocolCodecFilter(RS2CodecFactory.GAME));
- PlayerDetails pd = new PlayerDetails(session, name, pass, inCipher, outCipher, outdated, version, gameframe, hitmarks, orbsOn, hpbars, newCursors, newMenus, newHits, tweening, hd, hdOnLogin, clientSize, isCensorOn);
- PlayerManager.load_game(pd);
- }
- break;
- }
- in.rewind();
- return false;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement