Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Copyright (C) 2010 Mike Linden
- // This program is free software; you can redistribute it and/or
- // modify it under the terms of the GNU General Public License
- // as published by the Free Software Foundation; either version 2
- // of the License, or (at your option) any later version.
- //
- // This program 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 General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with this program; if not, write to the Free Software
- // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- package org.ex.ws.net.codec;
- import java.security.SecureRandom;
- import java.util.logging.Logger;
- import org.ex.constants.GlobalConstants;
- import org.ex.core.util.ChannelBufferUtils;
- import org.ex.ws.model.World;
- import org.ex.ws.model.player.PlayerDetails;
- import org.ex.ws.net.ISAACCipher;
- import org.ex.ws.net.codec.game.GamePacketBuilder;
- import org.ex.ws.net.ondemand.OnDemandPool;
- import org.ex.ws.net.ondemand.OnDemandRequest;
- import org.ex.ws.util.NameUtils;
- import org.jboss.netty.buffer.ChannelBuffer;
- import org.jboss.netty.channel.Channel;
- import org.jboss.netty.channel.ChannelHandlerContext;
- import org.jboss.netty.handler.codec.replay.ReplayingDecoder;
- /**
- * Login protocol decoding class.
- *
- * @author Mike
- *
- */
- public final class RS2LoginDecoder extends ReplayingDecoder<RS2LoginStage> {
- /**
- * Logger instance.
- */
- private static final Logger logger = Logger.getLogger(RS2LoginDecoder.class
- .getName());
- /**
- * Packet opcode for game requests.
- */
- public static final int OPCODE_GAME = 14;
- /**
- * Packet opcode for update requests.
- */
- public static final int OPCODE_UPDATE = 15;
- /**
- * The secured random number generator.
- */
- private static final SecureRandom random = new SecureRandom();
- /**
- * The initial login response data.
- */
- private static final byte[] INITIAL_RESPONSE = new byte[] { 0x0, 0x0, 0x0,
- 0x0, 0x0, 0x0, 0x0, 0x0 };
- @Override
- protected Object decode(ChannelHandlerContext ctx, Channel channel,
- ChannelBuffer buffer, RS2LoginStage stage) throws Exception {
- switch (stage) {
- case UPDATE:
- /*
- * Here we read the cache id (idx file), file id and priority.
- */
- int cacheId = buffer.readUnsignedByte();
- int fileId = (buffer.readUnsignedByte() << 8)
- | buffer.readUnsignedByte();
- int priority = buffer.readUnsignedByte();
- /*
- * We push the request into the on-demand pool so it can be served.
- */
- OnDemandPool.getOnDemandPool().pushRequest(
- new OnDemandRequest(channel, cacheId, fileId, priority));
- break;
- case OPCODE:
- /*
- * 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 = buffer.readUnsignedByte();
- switch (opcode) {
- case OPCODE_GAME:
- checkpoint(RS2LoginStage.LOGIN);
- return null;
- case OPCODE_UPDATE:
- checkpoint(RS2LoginStage.UPDATE);
- channel.write(new GamePacketBuilder().put(INITIAL_RESPONSE)
- .toGamePacket());
- return null;
- }
- break;
- case LOGIN:
- /*
- * The name hash is a simple hash of the name which is suspected to
- * be used to select the appropiate login server.
- */
- @SuppressWarnings("unused")
- int nameHash = buffer.readUnsignedByte();
- /*
- * 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).
- */
- channel.write(new GamePacketBuilder().put(INITIAL_RESPONSE)
- .put((byte) 0).putLong(serverKey).toGamePacket());
- checkpoint(RS2LoginStage.PRECRYPTED);
- RS2ChannelAttributes.SERVER_KEY.set(channel, serverKey);
- return null;
- case PRECRYPTED:
- /*
- * Read the type of login.
- *
- * 16 = normal, 18 = reconnection.
- */
- int loginOpcode = buffer.readUnsignedByte();
- if (loginOpcode != 16 && loginOpcode != 18) {
- logger.info("Invalid login opcode : " + loginOpcode);
- channel.close();
- return null;
- }
- /*
- * Read the size of the login packet.
- */
- int loginSize = buffer.readUnsignedByte();
- /*
- * And calculate how long the encrypted block will be.
- */
- int loginEncryptSize = loginSize - (36 + 1 + 1 + 2);
- /*
- * This could be invalid, if so, ignore it.
- */
- if (loginEncryptSize <= 0) {
- logger.info("Encrypted packet size zero or negative : "
- + loginEncryptSize);
- channel.close();
- return null;
- }
- checkpoint(RS2LoginStage.CRYPTED);
- RS2ChannelAttributes.LOGIN_SIZE.set(channel, loginSize);
- RS2ChannelAttributes.LOGIN_SIZE_ENCRYPTED.set(channel,
- loginEncryptSize);
- return null;
- case CRYPTED:
- int size = RS2ChannelAttributes.LOGIN_SIZE.get(channel);
- int encryptSize = RS2ChannelAttributes.LOGIN_SIZE_ENCRYPTED
- .get(channel);
- if (super.actualReadableBytes() >= size) {
- /*
- * Read the magic ID which is 255 (0xFF) which indicates this is
- * the real login packet.
- */
- final int magicId = buffer.readUnsignedByte();
- if (magicId != 255) {
- logger.info("Incorrect magic id : " + magicId);
- channel.close();
- return null;
- }
- /*
- * Now read a short which is the client version and check if it
- * equals the server version.
- */
- final int version = buffer.readUnsignedShort();
- if (version != GlobalConstants.VERSION) {
- logger.info("Incorrect version : " + version);
- channel.close();
- return null;
- }
- /*
- * The following byte indicates if the client is on low or high
- * memory.
- */
- @SuppressWarnings("unused")
- final boolean lowMemoryVersion = buffer.readUnsignedByte() == 1;
- /*
- * Read the cache indices.
- */
- for (int i = 0; i < 9; i++) {
- buffer.readInt();
- }
- /*
- * The encrypted size includes the size byte which is not
- * needed.
- */
- encryptSize--;
- /*
- * Check if there is a mismatch in the sizing.
- */
- final int reportedSize = buffer.readUnsignedByte();
- if (reportedSize != encryptSize) {
- logger.info("Packet size mismatch (expected : "
- + encryptSize + ", reported : " + reportedSize
- + ")");
- channel.close();
- return null;
- }
- /*
- * Now read the encrypted block opcode (although in most 317
- * clients and this server the RSA is disabled) and check if it
- * is equal to 10.
- */
- int blockOpcode = buffer.readUnsignedByte();
- if (blockOpcode != 10) {
- logger.info("Invalid login block opcode : " + blockOpcode);
- channel.close();
- return null;
- }
- /*
- * Read the client's session key.
- */
- long clientKey = buffer.readLong();
- /*
- * And verifiy it has the correct server session key.
- */
- long servKey = RS2ChannelAttributes.SERVER_KEY.get(channel);
- long reportedServerKey = buffer.readLong();
- if (reportedServerKey != servKey) {
- logger.info("Server key mismatch (expected : " + servKey
- + ", reported : " + reportedServerKey + ").");
- channel.close();
- return null;
- }
- /*
- * 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.
- *
- * Excellia no longer uses any form of UID identification.
- * Instead, a specific long is used which must match the server
- * sided copy or the channel will close.
- */
- // int uid = buffer.readInt();
- final long verification = buffer.readLong();
- if (verification != 839527L) {
- logger.info("Invalid security verification (expected : 839527L, reported : "
- + verification + ").");
- channel.close();
- return null;
- }
- /*
- * Read and format the name and password.
- */
- String name = NameUtils.formatName(ChannelBufferUtils
- .getRS2String(buffer));
- String pass = ChannelBufferUtils.getRS2String(buffer);
- logger.info("Login request : username=" + name + " password="
- + pass);
- /*
- * 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) (servKey >> 32);
- sessionKey[3] = (int) servKey;
- this.setState(null);
- RS2ChannelAttributes.SERVER_KEY.remove(channel);
- RS2ChannelAttributes.LOGIN_SIZE.remove(channel);
- RS2ChannelAttributes.LOGIN_SIZE_ENCRYPTED.remove(channel);
- ISAACCipher inCipher = new ISAACCipher(sessionKey);
- for (int i = 0; i < 4; i++) {
- sessionKey[i] += 50;
- }
- ISAACCipher outCipher = new ISAACCipher(sessionKey);
- /*
- * The login has successfully completed, do the appropiate
- * things to fire orr the chain of events which will load and
- * check saved games, etc.
- */
- channel.getPipeline().remove("protocol");
- channel.getPipeline()
- .addFirst("protocol", RS2CodecFactory.GAME);
- PlayerDetails pd = new PlayerDetails(channel, name, pass,
- inCipher, outCipher);
- World.getWorld().load(pd);
- }
- }
- return null;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement