diff --git a/src/main/java/com/dori/SpringStory/connection/crypto/ShandaCipher.java b/src/main/java/com/dori/SpringStory/connection/crypto/ShandaCipher.java index 4905a34..d58373b 100644 --- a/src/main/java/com/dori/SpringStory/connection/crypto/ShandaCipher.java +++ b/src/main/java/com/dori/SpringStory/connection/crypto/ShandaCipher.java @@ -6,8 +6,8 @@ public interface ShandaCipher { static void encryptData(ByteBuf buf, + int offset, int length) { - int offset = 0; for (int j = 0; j < 6; j++) { byte remember = 0; byte dataLength = (byte) (length & 0xFF); @@ -43,8 +43,8 @@ static void encryptData(ByteBuf buf, } static void decryptData(ByteBuf buf, + int offset, int length) { - int offset = 0; for (int j = 1; j <= 6; j++) { byte remember = 0; byte dataLength = (byte) (length & 0xFF); diff --git a/src/main/java/com/dori/SpringStory/connection/crypto/ShroomAESCipher.java b/src/main/java/com/dori/SpringStory/connection/crypto/ShroomAESCipher.java index 4410091..c0b8bb4 100644 --- a/src/main/java/com/dori/SpringStory/connection/crypto/ShroomAESCipher.java +++ b/src/main/java/com/dori/SpringStory/connection/crypto/ShroomAESCipher.java @@ -57,32 +57,29 @@ public class ShroomAESCipher { (byte) 0x84, (byte) 0x7F, (byte) 0x61, (byte) 0x1E, (byte) 0xCF, (byte) 0xC5, (byte) 0xD1, (byte) 0x56, (byte) 0x3D, (byte) 0xCA, (byte) 0xF4, (byte) 0x05, (byte) 0xC6, (byte) 0xE5, (byte) 0x08, (byte) 0x49}; - public static final byte[] IG_SEED = {(byte) 0xf2, 0x53, (byte) 0x50, (byte) 0xc6}; + public static final byte[] IG_SEED = { (byte) 0xf2, 0x53, (byte) 0x50, (byte) 0xc6 }; + + private static Cipher cipher = initCipher(); - private final short mapleVersion; - private final Cipher cipher; - private byte[] iv; - - public ShroomAESCipher(InitializationVector iv, short mapleVersion) { + private static Cipher initCipher() { try { - cipher = Cipher.getInstance("AES"); + Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, AES_KEY); + return cipher; } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) { throw new RuntimeException(e); } + } + + private final short mapleVersion; + private byte[] iv; + public ShroomAESCipher(InitializationVector iv, short mapleVersion) { this.iv = iv.getBytes(); this.mapleVersion = mapleVersion; } public ShroomAESCipher(byte[] iv, short mapleVersion) { - try { - cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.ENCRYPT_MODE, AES_KEY); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) { - throw new RuntimeException(e); - } - this.iv = iv; this.mapleVersion = mapleVersion; } diff --git a/src/main/java/com/dori/SpringStory/connection/netty/BaseAcceptor.java b/src/main/java/com/dori/SpringStory/connection/netty/BaseAcceptor.java index 952a0c5..47b3798 100644 --- a/src/main/java/com/dori/SpringStory/connection/netty/BaseAcceptor.java +++ b/src/main/java/com/dori/SpringStory/connection/netty/BaseAcceptor.java @@ -1,6 +1,7 @@ package com.dori.SpringStory.connection.netty; import com.dori.SpringStory.client.MapleClient; +import com.dori.SpringStory.connection.crypto.InitializationVector; import com.dori.SpringStory.connection.crypto.ShroomAESCipher; import com.dori.SpringStory.connection.packet.packets.CLogin; import com.dori.SpringStory.constants.ServerConstants; @@ -19,20 +20,17 @@ import static com.dori.SpringStory.connection.netty.NettyClient.CLIENT_KEY; public interface BaseAcceptor { - - private static byte getFinalRandomByteForIV(){ - return (byte) Math.abs(Math.random() * 255); - } - /** * Creating an acceptor based on ServerBootstrap. - * @param port - server port. - * @param channelPool - The pool of the server (if needed - login / chat acceptor). - * @param channel - The current channel if it's a channel acceptor. - * @param logger - The acceptor logger. + * + * @param port - server port. + * @param channelPool - The pool of the server (if needed - login / chat + * acceptor). + * @param channel - The current channel if it's a channel acceptor. + * @param logger - The acceptor logger. */ static void createAcceptor(int port, @Nullable Map channelPool, - @Nullable MapleChannel channel, @NonNull Logger logger){ + @Nullable MapleChannel channel, @NonNull Logger logger) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { @@ -44,26 +42,30 @@ static void createAcceptor(int port, @Nullable Map channelPool, @Override protected void initChannel(SocketChannel ch) { // Updated to v95 - - byte[] siv = new byte[]{82, 48, 120, getFinalRandomByteForIV()}; - byte[] riv = new byte[]{70, 114, 122, getFinalRandomByteForIV()}; + InitializationVector siv = InitializationVector.generateSend(); + InitializationVector riv = InitializationVector.generateReceive(); + short rver = ServerConstants.VERSION; + short sver = (short) ~ServerConstants.VERSION; // Set Decoder/Handler/Encoder - - ch.pipeline().addLast(new PacketDecoder(new ShroomAESCipher(riv, ServerConstants.VERSION)), new ChannelHandler(), new PacketEncoder(new ShroomAESCipher(siv, (short) ~ServerConstants.VERSION))); + ch.pipeline().addLast(new PacketDecoder(riv, rver), + new ChannelHandler(), + new PacketEncoder(siv, sver)); // Init Encoder outPacketMapping - PacketEncoder.initOutPacketOpcodesHandling(); // Create new client for the connection - MapleClient c = new MapleClient(ch); // Init connection for the client - logger.serverNotice(String.format("[CHAT] Opened session with %s in Acceptor", c.getIP())); - c.write(CLogin.sendConnect(siv,riv)); + c.write(CLogin.sendConnect(siv.getBytes(), riv.getBytes())); // If we get a ChannelPool, then it will add the IP to the pool - - if(channelPool != null){ + if (channelPool != null) { // Add to the channel pool - channelPool.put(c.getIP(), ch); } // ChannelInitializer attributes - ch.attr(CLIENT_KEY).set(c); - //EventManager.addFixedRateEvent(c::sendPing, 0, 10000); + // EventManager.addFixedRateEvent(c::sendPing, 0, 10000); } }); // Adding channel options - @@ -72,8 +74,9 @@ protected void initChannel(SocketChannel ch) { // Bind and start to accept incoming connections. ChannelFuture f = b.bind(port).sync(); // If we get a MapleChannel then print the listening to the channel and port - - if(channel != null){ - logger.notice(String.format("Channel %d-%d listening on port %d", channel.getWorldId(), channel.getChannelId(), channel.getPort())); + if (channel != null) { + logger.notice(String.format("Channel %d-%d listening on port %d", channel.getWorldId(), + channel.getChannelId(), channel.getPort())); } // Wait until the server socket is closed. // In this example, this does not happen, but you can do that to gracefully diff --git a/src/main/java/com/dori/SpringStory/connection/netty/BroadcastSet.java b/src/main/java/com/dori/SpringStory/connection/netty/BroadcastSet.java new file mode 100644 index 0000000..e9f40ea --- /dev/null +++ b/src/main/java/com/dori/SpringStory/connection/netty/BroadcastSet.java @@ -0,0 +1,83 @@ +package com.dori.SpringStory.connection.netty; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; + +import com.dori.SpringStory.connection.packet.OutPacket; + +public class BroadcastSet { + private final Map clients; + private final ReentrantReadWriteLock lock; + + public BroadcastSet() { + this.clients = new HashMap<>(); + // A fair lock is not required as locks will be short-lived + this.lock = new ReentrantReadWriteLock(false); + } + + /** + * Broadcasts the given packet to all clients in the set + * + * @param packet + */ + public void broadcast(OutPacket packet) { + ReadLock lock = this.lock.readLock(); + try { + for (T client : clients.values()) { + client.write((OutPacket) packet.retainedDuplicate()); + } + } finally { + packet.release(); + lock.unlock(); + } + } + + /** + * Broadcasts the given packet to all clients in the set, excluding the client with the given id + * + * @param packet + * @param excludeId + */ + public void broadcastFilter(OutPacket packet, int excludeId) { + ReadLock lock = this.lock.readLock(); + // Atmost one client which would be filtered in theory + // TODO: check if that is a good idea, because that's not always + // if the exclude id is another char + if (clients.size() <= 1) { + packet.release(); + return; + } + try { + for (Entry e : clients.entrySet()) { + if (e.getKey() != excludeId) { + e.getValue().write((OutPacket) packet.retainedDuplicate()); + } + } + } finally { + packet.release(); + lock.unlock(); + } + } + + public void addClient(int id, T client) { + lock.writeLock().lock(); + try { + clients.put(id, client); + } finally { + lock.writeLock().unlock(); + } + } + + public void removeClient(int id) { + lock.writeLock().lock(); + try { + clients.remove(id); + } finally { + lock.writeLock().unlock(); + } + } + +} diff --git a/src/main/java/com/dori/SpringStory/connection/netty/LoginAcceptor.java b/src/main/java/com/dori/SpringStory/connection/netty/LoginAcceptor.java index eaeea3b..999ab3c 100644 --- a/src/main/java/com/dori/SpringStory/connection/netty/LoginAcceptor.java +++ b/src/main/java/com/dori/SpringStory/connection/netty/LoginAcceptor.java @@ -8,7 +8,7 @@ import static com.dori.SpringStory.constants.ServerConstants.LOGIN_PORT; // Taken from http://netty.io/wiki/user-guide-for-4.x.html -public class LoginAcceptor implements Runnable{ +public class LoginAcceptor implements Runnable { // Channel pool - public static Map channelPool = new HashMap<>(); // Logger - diff --git a/src/main/java/com/dori/SpringStory/connection/netty/NettyClient.java b/src/main/java/com/dori/SpringStory/connection/netty/NettyClient.java index 4fe53f8..d6fc529 100644 --- a/src/main/java/com/dori/SpringStory/connection/netty/NettyClient.java +++ b/src/main/java/com/dori/SpringStory/connection/netty/NettyClient.java @@ -23,7 +23,6 @@ import io.netty.util.AttributeKey; import org.jetbrains.annotations.NotNull; -import java.util.concurrent.locks.ReentrantLock; /** * Abstraction for Netty channels that contains some attribute keys @@ -53,12 +52,6 @@ public class NettyClient { */ protected final Channel ch; - /** - * Lock regarding the encoding of packets to be sent to remote - * sessions. - */ - private final ReentrantLock lock; - /** * InPacket object for this specific session since this can help * scaling compared to keeping OutPacket for each session. @@ -68,7 +61,6 @@ public class NettyClient { public NettyClient() { ch = null; r = new InPacket(); - lock = new ReentrantLock(true); // note: lock is fair to ensure logical sequence is maintained server-side } /** @@ -79,7 +71,6 @@ public NettyClient() { public NettyClient(Channel c) { ch = c; r = new InPacket(); - lock = new ReentrantLock(true); // note: lock is fair to ensure logical sequence is maintained server-side } /** @@ -131,21 +122,4 @@ public void close() { public String getIP() { return ch.remoteAddress().toString().split(":")[0].substring(1); } - - /** - * Acquires the encoding state for this specific send IV. This is to - * prevent multiple encoding states to be possible at the same time. If - * allowed, the send IV would mutate to an unusable IV and the session would - * be dropped as a result. - */ - public final void acquireEncoderState() { - lock.lock(); - } - - /** - * Releases the encoding state for this specific send IV. - */ - public final void releaseEncodeState() { - lock.unlock(); - } } diff --git a/src/main/java/com/dori/SpringStory/connection/netty/PacketDecoder.java b/src/main/java/com/dori/SpringStory/connection/netty/PacketDecoder.java index 3ce0a32..3f93775 100644 --- a/src/main/java/com/dori/SpringStory/connection/netty/PacketDecoder.java +++ b/src/main/java/com/dori/SpringStory/connection/netty/PacketDecoder.java @@ -1,8 +1,10 @@ package com.dori.SpringStory.connection.netty; +import com.dori.SpringStory.connection.crypto.InitializationVector; import com.dori.SpringStory.connection.crypto.ShandaCipher; import com.dori.SpringStory.connection.crypto.ShroomAESCipher; import com.dori.SpringStory.connection.packet.InPacket; +import com.dori.SpringStory.connection.packet.Packet; import com.dori.SpringStory.logger.Logger; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; @@ -15,14 +17,15 @@ public class PacketDecoder extends ReplayingDecoder { private static final Logger log = new Logger(PacketDecoder.class); - public static final int MAX_PACKET_LEN = 2 * 4096; + private final ShroomAESCipher cipher; - private final ShroomAESCipher receiveCypher; + public PacketDecoder(InitializationVector iv, short version) { + this(new ShroomAESCipher(iv, version)); + } - public PacketDecoder(ShroomAESCipher receiveCypher) { + public PacketDecoder(ShroomAESCipher cipher) { super(-1); - - this.receiveCypher = receiveCypher; + this.cipher = cipher; } @Override @@ -31,17 +34,17 @@ protected void decode(ChannelHandlerContext chc, List out) { int packetLength = state(); if(packetLength == -1) { - final int packetLen = receiveCypher.decodeHeader(inPacketData.readIntLE()); + final int packetLen = cipher.decodeHeader(inPacketData.readIntLE()); checkpoint(packetLen); - if(packetLen < 0 || packetLen > MAX_PACKET_LEN) + if(packetLen < 0 || packetLen > Packet.MAX_PKT_LEN) throw new EncoderException("Packet length out of limits"); packetLength = packetLen; } ByteBuf pktBuf = inPacketData.readRetainedSlice(packetLength); this.checkpoint(-1); - receiveCypher.crypt(pktBuf, 0, packetLength); + cipher.crypt(pktBuf, 0, packetLength); if (ENABLE_ENCRYPTION) { - ShandaCipher.decryptData(pktBuf, packetLength); + ShandaCipher.decryptData(pktBuf, 0, packetLength); } out.add(new InPacket(pktBuf)); } diff --git a/src/main/java/com/dori/SpringStory/connection/netty/PacketEncoder.java b/src/main/java/com/dori/SpringStory/connection/netty/PacketEncoder.java index 64c4570..c08d782 100644 --- a/src/main/java/com/dori/SpringStory/connection/netty/PacketEncoder.java +++ b/src/main/java/com/dori/SpringStory/connection/netty/PacketEncoder.java @@ -1,5 +1,6 @@ package com.dori.SpringStory.connection.netty; +import com.dori.SpringStory.connection.crypto.InitializationVector; import com.dori.SpringStory.connection.crypto.ShandaCipher; import com.dori.SpringStory.connection.crypto.ShroomAESCipher; import com.dori.SpringStory.connection.packet.OutPacket; @@ -26,46 +27,58 @@ public final class PacketEncoder extends MessageToByteEncoder { private static final Logger logger = new Logger(PacketEncoder.class); private static final Map outPacketHeaders = new HashMap<>(); - private final ShroomAESCipher sendCypher; + private final ShroomAESCipher cipher; + private boolean first; + + public PacketEncoder(InitializationVector iv, short version) { + this(new ShroomAESCipher(iv, version)); + } public PacketEncoder(ShroomAESCipher sendCypher) { - this.sendCypher = sendCypher; + this.cipher = sendCypher; + this.first = true; } public static void initOutPacketOpcodesHandling() { Arrays.stream(OutHeader.values()).forEach(opcode -> outPacketHeaders.put(opcode.getValue(), opcode)); } + void encodeToBuffer(OutPacket pkt, ByteBuf out) { + int len = pkt.getLength(); + out.writeIntLE(cipher.encodeHeader(len)); + int ix = out.writerIndex(); + out.writeBytes(pkt.content()); + + // Encrypt shanda + if (ENABLE_ENCRYPTION) { + ShandaCipher.encryptData(out, ix, len); + } + + // Encrypt aes + cipher.crypt(out, ix, len); + } + @Override - protected void encode(ChannelHandlerContext chc, OutPacket outPacket, ByteBuf bb) { + protected void encode(ChannelHandlerContext chc, OutPacket pkt, ByteBuf out) { try { - ByteBuf bufferData = outPacket.getBufferData(); - int len = bufferData.readableBytes(); - NettyClient c = chc.channel().attr(NettyClient.CLIENT_KEY).get(); - if (c != null) { - OutHeader outHeader = outPacketHeaders.get(outPacket.getHeader()); + // Skip encryption for the first packet(Handshake) + if (!first) { + OutHeader outHeader = outPacketHeaders.get(pkt.getHeader()); if (!OutHeader.isSpamHeader(outHeader)) { - logger.sent(String.valueOf(outPacket.getHeader()), "0x" + Integer.toHexString(outPacket.getHeader()).toUpperCase(), outHeader.name(), outPacket.toString()); - } - bb.writeIntLE(sendCypher.encodeHeader(len)); - if (ENABLE_ENCRYPTION) { - ShandaCipher.encryptData(bufferData, len); - } - sendCypher.crypt(bufferData, 0, len); - c.acquireEncoderState(); - try { - bb.writeBytes(bufferData); - } finally { - c.releaseEncodeState(); + logger.sent(String.valueOf(pkt.getHeader()), + "0x" + Integer.toHexString(pkt.getHeader()).toUpperCase(), outHeader.name(), + pkt.toString()); } + this.encodeToBuffer(pkt, out); } else { - logger.debug("Plain sending packet: " + outPacket); - bb.writeBytes(bufferData); + logger.debug("Plain sending packet: " + pkt); + out.writeBytes(pkt.content()); + this.first = false; } } catch (Exception e) { - logger.error("Error occurred while parsing OutPacket!! ", e); + logger.error("Error occurred while enncoding OutPacket!", e); } finally { - outPacket.release(); + pkt.release(); } } } diff --git a/src/main/java/com/dori/SpringStory/connection/packet/InPacket.java b/src/main/java/com/dori/SpringStory/connection/packet/InPacket.java index 8349110..c558f8e 100644 --- a/src/main/java/com/dori/SpringStory/connection/packet/InPacket.java +++ b/src/main/java/com/dori/SpringStory/connection/packet/InPacket.java @@ -1,12 +1,9 @@ package com.dori.SpringStory.connection.packet; -import com.dori.SpringStory.utils.MapleUtils; import com.dori.SpringStory.utils.utilEntities.Position; import com.dori.SpringStory.utils.utilEntities.Rect; import io.netty.buffer.*; -import java.util.Arrays; - public class InPacket extends Packet { private final ByteBuf byteBuf; @@ -36,23 +33,6 @@ public InPacket(byte[] data) { this(Unpooled.copiedBuffer(data)); } - @Override - public int getLength() { - return byteBuf.readableBytes(); - } - - @Override - public byte[] getData() { - byte[] bytes = new byte[byteBuf.readableBytes()]; - byteBuf.duplicate().readBytes(bytes); - return bytes; - } - - @Override - public InPacket clone() { - return new InPacket(byteBuf); - } - /** * Reads a single byte of the ByteBuf. * @return The byte that has been read. @@ -119,7 +99,8 @@ public String decodeString() { @Override public String toString() { - return MapleUtils.readableByteArray(Arrays.copyOfRange(getData(), getData().length - getUnreadAmount(), getData().length)); // Substring after copy of range xd + return super.toString(); + //return MapleUtils.readableByteArray(Arrays.copyOfRange(getData(), getData().length - getUnreadAmount(), getData().length)); // Substring after copy of range xd } @@ -163,10 +144,6 @@ public int getUnreadAmount() { return byteBuf.readableBytes(); } - public void release() { - byteBuf.release(); - } - /** * Reads a rectangle (int l, int t, int r, int b) and returns this. * @return The rectangle that has been read. @@ -176,6 +153,6 @@ public Rect decodeIntRect() { } public boolean decodeBool(){ - return decodeByte() > 0; + return decodeByte() != 0; } } diff --git a/src/main/java/com/dori/SpringStory/connection/packet/OutPacket.java b/src/main/java/com/dori/SpringStory/connection/packet/OutPacket.java index dd90e3e..4ca671a 100644 --- a/src/main/java/com/dori/SpringStory/connection/packet/OutPacket.java +++ b/src/main/java/com/dori/SpringStory/connection/packet/OutPacket.java @@ -9,25 +9,17 @@ import io.netty.buffer.*; import java.time.LocalDateTime; -import java.util.Arrays; public class OutPacket extends Packet { - private ByteBuf baos; - private boolean loopback = false; - private boolean encryptedByShanda = false; - private short op; private static final Logger log = new Logger(OutPacket.class); - /** * Creates a new OutPacket with a given op. Immediately encodes the op. * * @param op The opcode of this OutPacket. */ public OutPacket(short op) { - super(new byte[]{}); - baos = Unpooled.buffer(); + super(Unpooled.buffer()); encodeShort(op); - this.op = op; } /** @@ -39,26 +31,6 @@ public OutPacket(int op) { this((short) op); } - /** - * Creates a new OutPacket, and initializes the data as empty. - */ - public OutPacket() { - super(new byte[]{}); - baos = Unpooled.buffer(); - } - - /** - * Creates a new OutPacket with given data. - * - * @param data The data this net.swordie.ms.connection.packet has to be initialized with. - */ - public OutPacket(byte[] data, short opcode) { - super(data); - op = opcode; - baos = Unpooled.buffer(); - encodeArr(data); - } - /** * Creates a new OutPacket with a given header. Immediately encodes the header's short value. * @@ -68,14 +40,8 @@ public OutPacket(OutHeader header) { this(header.getValue()); } - /** - * Returns the header of this OutPacket. - * - * @return the header of this OutPacket. - */ - @Override - public int getHeader() { - return op; + public InPacket toInPacket() { + return new InPacket(buf); } /** @@ -93,7 +59,7 @@ public void encodeByte(int b) { * @param b The byte to encode. */ public void encodeByte(byte b) { - baos.writeByte(b); + buf.writeByte(b); } public void encodeBool(boolean bNext) { @@ -107,7 +73,7 @@ public void encodeBool(boolean bNext) { * @param bArr The byte array to encode. */ public void encodeArr(byte[] bArr) { - baos.writeBytes(bArr); + this.buf.writeBytes(bArr); } /** @@ -125,7 +91,7 @@ public void encodeArr(String arr) { * @param c The character to encode */ public void encodeChar(char c) { - baos.writeByte(c); + buf.writeByte(c); } /** @@ -134,7 +100,7 @@ public void encodeChar(char c) { * @param b The boolean to encode (0/1) */ public void encodeByte(boolean b) { - baos.writeBoolean(b); + buf.writeBoolean(b); } /** @@ -143,15 +109,7 @@ public void encodeByte(boolean b) { * @param s The short to encode. */ public void encodeShort(short s) { - baos.writeShortLE(s); - } - - public void encodeShortBE(short s) { - baos.writeShort(s); - } - - public void encodeIntBE(int i) { - baos.writeInt(i); + buf.writeShortLE(s); } /** @@ -160,7 +118,7 @@ public void encodeIntBE(int i) { * @param i The integer to encode. */ public void encodeInt(int i) { - baos.writeIntLE(i); + buf.writeIntLE(i); } /** @@ -169,7 +127,7 @@ public void encodeInt(int i) { * @param l The long to encode. */ public void encodeLong(long l) { - baos.writeLongLE(l); + buf.writeLongLE(l); } /** @@ -187,7 +145,7 @@ public void encodeString(String s) { return; } encodeShort((short) s.length()); - baos.writeCharSequence(s, CHARSET); + buf.writeCharSequence(s, CHARSET); } /** @@ -211,52 +169,9 @@ public void encodeString(String s, short length) { } } - @Override - public void setData(byte[] nD) { - super.setData(nD); - baos.clear(); - encodeArr(nD); - } - - @Override - public byte[] getData() { - if (super.getLength() == 0) { - super.setData(ByteBufUtil.getBytes(baos)); -// baos.release(); - } - return super.getData(); - } - - public ByteBuf getBufferData() { - return this.baos; - } - - @Override - public Packet clone() { - return new OutPacket(getData(), op); - } - - /** - * Returns the length of the ByteArrayOutputStream. - * - * @return The length of baos. - */ - @Override - public int getLength() { - return getData().length; - } - - public boolean isLoopback() { - return loopback; - } - - public boolean isEncryptedByShanda() { - return encryptedByShanda; - } - @Override public String toString() { - return MapleUtils.readableByteArray(Arrays.copyOfRange(getData(), 2, getData().length)); + return super.toString(); } public void encodeShort(int value) { @@ -318,11 +233,4 @@ public void encodeFT(LocalDateTime localDateTime) { public void encode(Encodable encodable) { encodable.encode(this); } - - @Override - public void release() { - super.release(); - this.baos.release(); - this.baos = null; - } } diff --git a/src/main/java/com/dori/SpringStory/connection/packet/Packet.java b/src/main/java/com/dori/SpringStory/connection/packet/Packet.java index 33c5049..8f3552e 100644 --- a/src/main/java/com/dori/SpringStory/connection/packet/Packet.java +++ b/src/main/java/com/dori/SpringStory/connection/packet/Packet.java @@ -17,8 +17,8 @@ */ package com.dori.SpringStory.connection.packet; -import com.dori.SpringStory.utils.MapleUtils; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufHolder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -29,54 +29,90 @@ * functionality because it is a MapleStory packet. * */ -public class Packet implements Cloneable { - - private byte[] data; +public class Packet implements ByteBufHolder { + public static final int MAX_PKT_LEN = 4096 * 2; + protected static final int MAX_BUF_LEN = 2048; protected static final Charset CHARSET = StandardCharsets.ISO_8859_1; + protected ByteBuf buf; - public Packet(byte[] data) { - this.data = new byte[data.length]; - System.arraycopy(data, 0, this.data, 0, data.length); - } - - public Packet(ByteBuf data) { - //this.data = new byte[data.readableBytes()]; - //System.arraycopy(data, 0, this.data, 0, data.length); + public Packet(ByteBuf buf) { + this.buf = buf; } public int getLength() { - if (data != null) { - return data.length; - } - return 0; + return this.buf.readableBytes(); } public int getHeader() { - if (data.length < 2) { + if (this.buf.readableBytes() < 2) { return 0xFFFF; } - return (data[0] + (data[1] << 8)); + return this.buf.getShortLE(0); + } + + @Override + public String toString() { + return "[Pck] | " + this.buf.toString(); } - public void setData(byte[] nD) { - data = nD; + @Override + public int refCnt() { + return this.buf.refCnt(); } - public byte[] getData() { - return data; + @Override + public boolean release(int arg0) { + return this.buf.release(arg0); } - + @Override - public String toString() { - if (data == null) return ""; - return "[Pck] | " + MapleUtils.readableByteArray(data); + public ByteBuf content() { + return this.buf; } - + + @Override + public Packet copy() { + return new Packet(this.buf.copy()); + } + + @Override + public Packet duplicate() { + return new Packet(this.buf.duplicate()); + } + + @Override + public ByteBufHolder retainedDuplicate() { + return new Packet(this.buf.retainedDuplicate()); + } + @Override - public Packet clone() { - return new Packet(data); + public ByteBufHolder replace(ByteBuf content) { + return new Packet(content); } - public void release(){} + @Override + public ByteBufHolder retain() { + return new Packet(this.buf.retain()); + } + + @Override + public ByteBufHolder retain(int increment) { + return new Packet(this.buf.retain(increment)); + } + + @Override + public ByteBufHolder touch() { + return new Packet(this.buf.touch()); + } + + @Override + public ByteBufHolder touch(Object hint) { + return new Packet(this.buf.touch(hint)); + } + + @Override + public boolean release() { + return this.buf.release(); + } } diff --git a/src/main/java/com/dori/SpringStory/connection/packet/handlers/LoginHandler.java b/src/main/java/com/dori/SpringStory/connection/packet/handlers/LoginHandler.java index 4980fce..aff53ab 100644 --- a/src/main/java/com/dori/SpringStory/connection/packet/handlers/LoginHandler.java +++ b/src/main/java/com/dori/SpringStory/connection/packet/handlers/LoginHandler.java @@ -182,12 +182,15 @@ public static void handleCharSelect(MapleClient c, InPacket inPacket) { public static void handleCreateSecurityHandle(MapleClient c, InPacket inPacket) { // If it's true will auto login as admin - if (AUTO_LOGIN) { - OutPacket outPacket = new OutPacket(); + OutPacket outPacket = new OutPacket(0); outPacket.encodeString(AUTO_LOGIN_USERNAME); outPacket.encodeString(AUTO_LOGIN_PASSWORD); outPacket.encodeArr(new byte[27]); - handleLoginPassword(c, new InPacket(outPacket.getData())); + + InPacket pkt = outPacket.toInPacket(); + pkt.decodeShort(); // Skip the header + handleLoginPassword(c, pkt); } } } diff --git a/src/main/java/com/dori/SpringStory/connection/packet/packets/CLogin.java b/src/main/java/com/dori/SpringStory/connection/packet/packets/CLogin.java index ae45367..2d04aa6 100644 --- a/src/main/java/com/dori/SpringStory/connection/packet/packets/CLogin.java +++ b/src/main/java/com/dori/SpringStory/connection/packet/packets/CLogin.java @@ -22,8 +22,7 @@ public interface CLogin { static OutPacket sendConnect(byte[] siv, byte[] riv) { - OutPacket outPacket = new OutPacket(); - outPacket.encodeShort((short) 14); // hand-shake packet size + OutPacket outPacket = new OutPacket(14);// hand-shake packet size outPacket.encodeShort(ServerConstants.VERSION); outPacket.encodeString(ServerConstants.MINOR_VERSION); outPacket.encodeArr(riv); // IV is an int diff --git a/src/main/java/com/dori/SpringStory/world/fieldEntities/Field.java b/src/main/java/com/dori/SpringStory/world/fieldEntities/Field.java index 61e5c76..80edb88 100644 --- a/src/main/java/com/dori/SpringStory/world/fieldEntities/Field.java +++ b/src/main/java/com/dori/SpringStory/world/fieldEntities/Field.java @@ -2,6 +2,7 @@ import com.dori.SpringStory.client.MapleClient; import com.dori.SpringStory.client.character.MapleChar; +import com.dori.SpringStory.connection.netty.BroadcastSet; import com.dori.SpringStory.connection.packet.OutPacket; import com.dori.SpringStory.connection.packet.packets.*; import com.dori.SpringStory.constants.GameConstants; @@ -44,6 +45,7 @@ public class Field extends MapData { private long creationTime; private long deprecationStartTime; private List mobsSpawnPoints = new ArrayList<>(); + private BroadcastSet tx = new BroadcastSet<>(); public Field(int id) { super(id); @@ -67,7 +69,8 @@ public Field(MapData mapData) { this.partyOnly = mapData.isPartyOnly(); this.expeditionOnly = mapData.isExpeditionOnly(); this.needSkillForFly = mapData.isNeedSkillForFly(); - this.fixedMobCapacity = mapData.getFixedMobCapacity() != 0 ? mapData.getFixedMobCapacity() : DEFAULT_FIELD_MOB_CAPACITY; + this.fixedMobCapacity = mapData.getFixedMobCapacity() != 0 ? mapData.getFixedMobCapacity() + : DEFAULT_FIELD_MOB_CAPACITY; this.createMobInterval = mapData.getCreateMobInterval(); this.timeOut = mapData.getTimeOut(); this.timeLimit = mapData.getTimeLimit(); @@ -104,8 +107,8 @@ public Field(MapData mapData) { mobsSpawnPoints.add(new PositionData(mob.getPosition(), mob.getFh())); } } else { - //TODO: see what lifes i've missed - //this.addLife(life.deepCopy()); + // TODO: see what lifes i've missed + // this.addLife(life.deepCopy()); } } this.dropsDisabled = mapData.isDropsDisabled(); @@ -147,12 +150,13 @@ public void spawnPlayer(MapleChar chr, boolean characterData) { MapleClient c = chr.getMapleClient(); // Add player to the field - players.putIfAbsent(chr.getId(), chr); + tx.addClient(chr.getId(), c); // Update for the char instance the field data - chr.setField(this); chr.setMapId(getId()); c.write(CStage.onSetField(c.getChr(), c.getChr().getField(), (short) 0, c.getChannel(), 0, characterData, (byte) 1, (short) 0, - "", new String[]{""})); + "", new String[] { "" })); // Spawn lifes for the client - this.spawnLifesForCharacter(chr); if (firstPlayerInField) { @@ -170,6 +174,7 @@ public void spawnPlayer(MapleChar chr, boolean characterData) { public void removePlayer(MapleChar chr) { players.remove(chr.getId()); + tx.removeClient(chr.getId()); } private void addNPC(Npc npc) { @@ -205,11 +210,13 @@ public void spawnLifesForCharacter(MapleChar chr) { // Spawn Mobs for the client - mobs.values().forEach(mob -> chr.write(CMobPool.mobEnterField(mob))); // Spawn Drops for the client - - drops.values().forEach(drop -> chr.write(CDropPool.dropEnterField(drop, DropEnterType.INSTANT, DropOwnType.USER_OWN, drop.getOwnerID(), drop.getPosition(), (short) 0, true))); + drops.values().forEach(drop -> chr.write(CDropPool.dropEnterField(drop, DropEnterType.INSTANT, + DropOwnType.USER_OWN, drop.getOwnerID(), drop.getPosition(), (short) 0, true))); } public void assignControllerToMobs(MapleChar chr) { - //TODO: assigning a char suppose to be random and not the new char that enter the map each time! + // TODO: assigning a char suppose to be random and not the new char that enter + // the map each time! // Assign Controller to Mobs for the client - mobs.values().forEach(mob -> { @@ -219,7 +226,8 @@ public void assignControllerToMobs(MapleChar chr) { } public void assignControllerToNpcs(MapleChar chr) { - //TODO: assigning a char suppose to be random and not the new char that enter the map each time! + // TODO: assigning a char suppose to be random and not the new char that enter + // the map each time! // Assign Controller to Mobs for the client - npcs.values().forEach(npc -> { @@ -231,7 +239,8 @@ public void assignControllerToNpcs(MapleChar chr) { public void spawnMobById(int mobId, MapleChar controller) { Mob mob = MobDataHandler.getMobByID(mobId); if (mob != null) { - //TODO: i want to redo the position concept - randomize it on the initial spawn points a map have + // TODO: i want to redo the position concept - randomize it on the initial spawn + // points a map have Position pos = controller.getPosition(); mob.setPosition(pos.deepCopy()); mob.setVPosition(pos.deepCopy()); @@ -264,21 +273,12 @@ public void removeMob(int objId) { } } - public void broadcastPacket(OutPacket outPacket) { - getPlayers().values().forEach(chr -> chr.write((OutPacket) outPacket.clone())); + public void broadcastPacket(OutPacket packet) { + tx.broadcast(packet); } public void broadcastPacket(OutPacket outPacket, MapleChar exceptChr) { - // No point broadcast a packet when you're alone in the map - - if (getPlayers().size() > 1) { - getPlayers().values().forEach( - chr -> { - if (chr.getId() != exceptChr.getId()) { - chr.write((OutPacket) outPacket.clone()); - } - } - ); - } + tx.broadcastFilter(outPacket, id); } private Drop generateDropByMobDropData(MobDropData dropData, int ownerID, float mesoRate) { @@ -302,7 +302,8 @@ private Drop generateDropByMobDropData(MobDropData dropData, int ownerID, float return drop; } - public void drop(List dropsData, int srcID, int ownerID, Foothold fh, Position position, float mesoRate, float dropRate) { + public void drop(List dropsData, int srcID, int ownerID, Foothold fh, Position position, + float mesoRate, float dropRate) { int x = position.getX(); int diff = 0; int minX = position.getX(); @@ -353,8 +354,10 @@ public void spawnDrop(Drop drop, int srcID, Position srcPos, boolean isTradeable Position fromPos = new Position(drop.getPosition()); fromPos.setY(fromPos.getY() - 20); - broadcastPacket(CDropPool.dropEnterField(drop, dropEnterType, DropOwnType.USER_OWN, srcID, fromPos, replay, true)); - EventManager.addEvent(getRandomUuidInLong(), EventType.REMOVE_DROP_FROM_FIELD, new RemoveDropFromField(drop, this), DROP_REMAIN_ON_GROUND_TIME); + broadcastPacket( + CDropPool.dropEnterField(drop, dropEnterType, DropOwnType.USER_OWN, srcID, fromPos, replay, true)); + EventManager.addEvent(getRandomUuidInLong(), EventType.REMOVE_DROP_FROM_FIELD, + new RemoveDropFromField(drop, this), DROP_REMAIN_ON_GROUND_TIME); } public void spawnDrop(Drop drop, Position fromPos) { @@ -386,7 +389,8 @@ public void shutdownField() { getNpcs().values().forEach(npc -> npc.setField(null)); getNpcs().clear(); // Clear Drops - - getDrops().keySet().forEach(dropObjID -> EventManager.cancelEvent(MapleUtils.concat((long) getId(), dropObjID), EventType.REMOVE_DROP_FROM_FIELD)); + getDrops().keySet().forEach(dropObjID -> EventManager.cancelEvent(MapleUtils.concat((long) getId(), dropObjID), + EventType.REMOVE_DROP_FROM_FIELD)); getDrops().clear(); } }