From 76b2d367045e12b5bce42a96b06070c41e005e06 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 17 Sep 2017 13:34:54 -0400 Subject: [PATCH] cache: netty-ize client and server --- .../cache/client/ArchiveResponseHandler.java | 48 +++ .../runelite/cache/client/CacheClient.java | 101 ++++--- .../cache/client/CacheClientHandler.java | 159 ---------- .../client/HandshakeResponseHandler.java | 80 +++++ .../cache/client/PendingFileRequest.java | 23 +- .../cache/client/requests/HelloHandshake.java | 83 ------ .../decoders/ArchiveRequestDecoder.java | 59 ++++ .../decoders/ArchiveResponseDecoder.java | 157 ++++++++++ .../protocol/decoders/EncryptionDecoder.java | 54 ++++ .../protocol/decoders/HandshakeDecoder.java | 56 ++++ .../decoders/HandshakeResponseDecoder.java | 52 ++++ .../encoders/ArchiveRequestEncoder.java | 43 +++ .../encoders/ArchiveResponseEncoder.java | 74 +++++ .../encoders/EncryptionEncoder.java} | 28 +- .../encoders/HandshakeEncoder.java} | 36 +-- .../encoders/HandshakeResponseEncoder.java | 43 +++ .../cache/protocol/encoders/XorEncoder.java | 60 ++++ .../packets/ArchiveRequestPacket.java | 44 +++ .../packets/ArchiveResponsePacket.java | 44 +++ .../protocol/packets/EncryptionPacket.java | 24 ++ .../protocol/packets/HandshakePacket.java | 33 +++ .../packets/HandshakeResponsePacket.java | 22 ++ .../packets/HandshakeResponseType.java | 59 ++++ .../cache/protocol/packets/HandshakeType.java | 30 ++ .../cache/server/ArchiveRequestHandler.java | 150 ++++++++++ .../cache/server/CacheFrameDecoder.java | 93 ++++++ .../runelite/cache/server/CacheServer.java | 14 +- .../cache/server/CacheServerHandler.java | 276 ------------------ .../cache/server/CacheServerInitializer.java | 71 +++++ .../runelite/cache/server/ClientState.java | 1 - .../cache/server/EncryptionHandler.java | 47 +++ .../cache/server/HandshakeHandler.java | 74 +++++ .../cache/client/CacheClientTest.java | 13 +- .../encoders/ArchiveResponseEncoderTest.java | 74 +++++ .../protocol/encoders/XorEncoderTest.java} | 65 ++--- .../cache/server/CacheServerTest.java | 17 +- .../http/service/cache/CacheUpdater.java | 6 +- 37 files changed, 1607 insertions(+), 706 deletions(-) create mode 100644 cache/src/main/java/net/runelite/cache/client/ArchiveResponseHandler.java create mode 100644 cache/src/main/java/net/runelite/cache/client/HandshakeResponseHandler.java delete mode 100644 cache/src/main/java/net/runelite/cache/client/requests/HelloHandshake.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/decoders/ArchiveRequestDecoder.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/decoders/ArchiveResponseDecoder.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/decoders/EncryptionDecoder.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/decoders/HandshakeDecoder.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/decoders/HandshakeResponseDecoder.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/encoders/ArchiveRequestEncoder.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/encoders/ArchiveResponseEncoder.java rename cache/src/main/java/net/runelite/cache/{client/requests/FileRequest.java => protocol/encoders/EncryptionEncoder.java} (71%) rename cache/src/main/java/net/runelite/cache/{client/requests/ConnectionInfo.java => protocol/encoders/HandshakeEncoder.java} (72%) create mode 100644 cache/src/main/java/net/runelite/cache/protocol/encoders/HandshakeResponseEncoder.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/encoders/XorEncoder.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/packets/ArchiveRequestPacket.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/packets/ArchiveResponsePacket.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/packets/EncryptionPacket.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/packets/HandshakePacket.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeResponsePacket.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeResponseType.java create mode 100644 cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeType.java create mode 100644 cache/src/main/java/net/runelite/cache/server/ArchiveRequestHandler.java create mode 100644 cache/src/main/java/net/runelite/cache/server/CacheFrameDecoder.java delete mode 100644 cache/src/main/java/net/runelite/cache/server/CacheServerHandler.java create mode 100644 cache/src/main/java/net/runelite/cache/server/CacheServerInitializer.java create mode 100644 cache/src/main/java/net/runelite/cache/server/EncryptionHandler.java create mode 100644 cache/src/main/java/net/runelite/cache/server/HandshakeHandler.java create mode 100644 cache/src/test/java/net/runelite/cache/protocol/encoders/ArchiveResponseEncoderTest.java rename cache/src/{main/java/net/runelite/cache/server/Chunker.java => test/java/net/runelite/cache/protocol/encoders/XorEncoderTest.java} (58%) diff --git a/cache/src/main/java/net/runelite/cache/client/ArchiveResponseHandler.java b/cache/src/main/java/net/runelite/cache/client/ArchiveResponseHandler.java new file mode 100644 index 0000000000..83b430773e --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/client/ArchiveResponseHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.client; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import net.runelite.cache.protocol.packets.ArchiveResponsePacket; + +public class ArchiveResponseHandler extends SimpleChannelInboundHandler +{ + private final CacheClient client; + + public ArchiveResponseHandler(CacheClient client) + { + this.client = client; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ArchiveResponsePacket archiveResponse) throws Exception + { + client.onFileFinish(archiveResponse.getIndex(), + archiveResponse.getArchive(), + archiveResponse.getData()); + } + +} diff --git a/cache/src/main/java/net/runelite/cache/client/CacheClient.java b/cache/src/main/java/net/runelite/cache/client/CacheClient.java index b57dd86c4f..212bc30f3c 100644 --- a/cache/src/main/java/net/runelite/cache/client/CacheClient.java +++ b/cache/src/main/java/net/runelite/cache/client/CacheClient.java @@ -43,9 +43,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Queue; import java.util.concurrent.CompletableFuture; -import net.runelite.cache.client.requests.ConnectionInfo; -import net.runelite.cache.client.requests.FileRequest; -import net.runelite.cache.client.requests.HelloHandshake; import net.runelite.cache.fs.Archive; import net.runelite.cache.fs.FSFile; import net.runelite.cache.fs.Index; @@ -53,6 +50,14 @@ import net.runelite.cache.fs.Store; import net.runelite.cache.index.ArchiveData; import net.runelite.cache.index.FileData; import net.runelite.cache.index.IndexData; +import net.runelite.cache.protocol.decoders.HandshakeResponseDecoder; +import net.runelite.cache.protocol.encoders.ArchiveRequestEncoder; +import net.runelite.cache.protocol.encoders.EncryptionEncoder; +import net.runelite.cache.protocol.encoders.HandshakeEncoder; +import net.runelite.cache.protocol.packets.ArchiveRequestPacket; +import net.runelite.cache.protocol.packets.HandshakePacket; +import net.runelite.cache.protocol.packets.HandshakeResponseType; +import net.runelite.cache.protocol.packets.HandshakeType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,7 +80,7 @@ public class CacheClient implements AutoCloseable private final EventLoopGroup group = new NioEventLoopGroup(1); private Channel channel; - private CompletableFuture handshakeFuture; + private CompletableFuture handshakeFuture; private final Queue requests = new ArrayDeque<>(); public CacheClient(Store store, int clientRevision) @@ -108,8 +113,21 @@ public class CacheClient implements AutoCloseable public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); + //p.addFirst(new HttpProxyHandler(new InetSocketAddress("runelite.net", 3128))); - p.addLast(new CacheClientHandler(CacheClient.this)); + p.addLast("decoder", new HandshakeResponseDecoder()); + + p.addLast( + new CacheClientHandler(), + new HandshakeResponseHandler(CacheClient.this), + new ArchiveResponseHandler(CacheClient.this) + ); + + p.addLast( + new HandshakeEncoder(), + new EncryptionEncoder(), + new ArchiveRequestEncoder() + ); } }); @@ -118,53 +136,24 @@ public class CacheClient implements AutoCloseable channel = f.channel(); } - public CompletableFuture handshake() + public CompletableFuture handshake() { - HelloHandshake msg = new HelloHandshake(); - msg.setRevision(getClientRevision()); - - ByteBuf message = Unpooled.buffer(5); - message.writeByte(msg.getType()); // handshake type - message.writeInt(msg.getRevision()); // client revision + HandshakePacket handshakePacket = new HandshakePacket(); + handshakePacket.setType(HandshakeType.ON_DEMAND); + handshakePacket.setRevision(getClientRevision()); state = ClientState.HANDSHAKING; assert handshakeFuture == null; handshakeFuture = new CompletableFuture<>(); - channel.writeAndFlush(message); + channel.writeAndFlush(handshakePacket); - logger.info("Sent handshake with revision {}", msg.getRevision()); + logger.info("Sent handshake with revision {}", handshakePacket.getRevision()); return handshakeFuture; } - public void onHandshake(int response) - { - assert handshakeFuture != null; - - if (response != HelloHandshake.RESPONSE_OK) - { - handshakeFuture.complete(response); - close(); - return; - } - - // Send connection info - ConnectionInfo cinfo = new ConnectionInfo(); - ByteBuf outbuf = Unpooled.buffer(4); - outbuf.writeByte(cinfo.getType()); - outbuf.writeMedium(cinfo.getPadding()); - - channel.writeAndFlush(outbuf); - - state = ClientState.CONNECTED; - - logger.info("Client is now connected!"); - - handshakeFuture.complete(response); - } - @Override public void close() { @@ -182,6 +171,16 @@ public class CacheClient implements AutoCloseable return state; } + void setState(ClientState state) + { + this.state = state; + } + + CompletableFuture getHandshakeFuture() + { + return handshakeFuture; + } + public List requestIndexes() throws IOException { logger.info("Requesting indexes"); @@ -263,7 +262,7 @@ public class CacheClient implements AutoCloseable index.setCrc(crc); index.setRevision(revision); - logger.info("Index {} has {} archives", i, index.getArchives().size()); + logger.info("Index {} has {} archives", i, indexData.getArchives().length); for (ArchiveData ad : indexData.getArchives()) { @@ -372,14 +371,14 @@ public class CacheClient implements AutoCloseable } } - ByteBuf buf = Unpooled.buffer(4); - FileRequest request = new FileRequest(index, fileId); - CompletableFuture future = new CompletableFuture<>(); - PendingFileRequest pf = new PendingFileRequest(request, future); + ArchiveRequestPacket archiveRequest = new ArchiveRequestPacket(); + archiveRequest.setPriority(false); + archiveRequest.setIndex(index); + archiveRequest.setArchive(fileId); - buf.writeByte(0); // 0/1 - seems to be a priority? - int hash = pf.computeHash(); - buf.writeMedium(hash); + CompletableFuture future = new CompletableFuture<>(); + PendingFileRequest pf = new PendingFileRequest(index, + fileId, future); logger.trace("Sending request for {}/{}", index, fileId); @@ -387,11 +386,11 @@ public class CacheClient implements AutoCloseable if (!flush) { - channel.write(buf); + channel.write(archiveRequest); } else { - channel.writeAndFlush(buf); + channel.writeAndFlush(archiveRequest); } return future; @@ -401,7 +400,7 @@ public class CacheClient implements AutoCloseable { for (PendingFileRequest pr : requests) { - if (pr.getRequest().getIndex() == index && pr.getRequest().getFile() == file) + if (pr.getIndex() == index && pr.getArchive() == file) { return pr; } diff --git a/cache/src/main/java/net/runelite/cache/client/CacheClientHandler.java b/cache/src/main/java/net/runelite/cache/client/CacheClientHandler.java index 9cd9b0c6b4..9afe8f976b 100644 --- a/cache/src/main/java/net/runelite/cache/client/CacheClientHandler.java +++ b/cache/src/main/java/net/runelite/cache/client/CacheClientHandler.java @@ -24,12 +24,8 @@ */ package net.runelite.cache.client; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.util.ReferenceCountUtil; -import net.runelite.cache.client.requests.HelloHandshake; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,167 +33,12 @@ public class CacheClientHandler extends ChannelInboundHandlerAdapter { private static final Logger logger = LoggerFactory.getLogger(CacheClientHandler.class); - private final CacheClient client; - private final ByteBuf buffer = Unpooled.buffer(512); - - public CacheClientHandler(CacheClient client) - { - this.client = client; - } - @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { logger.warn("Channel has gone inactive"); } - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) - { - ByteBuf inbuf = (ByteBuf) msg; - buffer.writeBytes(inbuf); - - ClientState state = client.getState(); - - if (state == ClientState.HANDSHAKING) - { - int response = buffer.readByte(); - - logger.info("Handshake response {}", response); - - if (response != HelloHandshake.RESPONSE_OK) - { - if (response == HelloHandshake.RESPONSE_OUTDATED) - { - logger.warn("Client version is outdated"); - } - else - { - logger.warn("Handshake response error {}", response); - } - } - - client.onHandshake(response); - } - else if (state == ClientState.CONNECTED) - { - while (readFile()); - } - - buffer.discardReadBytes(); - ReferenceCountUtil.release(msg); - } - - private boolean readFile() - { - if (buffer.readableBytes() < 8) - { - logger.trace("Connected, but not enough data yet to read header"); - return false; - } - - ByteBuf copy = buffer.slice(); - - int index = copy.readUnsignedByte(); - int file = copy.readUnsignedShort(); - // decompress() starts reading here - int compression = copy.readUnsignedByte(); - int compressedFileSize = copy.readInt(); - - int size = compressedFileSize - + 5 // 1 byte compresion type, 4 byte compressed size - + (compression != 0 ? 4 : 0); // compression has leading 4 byte decompressed length - - assert size > 0; - - int breaks = calculateBreaks(size); - - // 3 for index/file - if (size + 3 + breaks > buffer.readableBytes()) - { - logger.trace("Index {} archive {}: Not enough data yet {} > {}", index, file, size + 3 + breaks, buffer.readableBytes()); - return false; - } - - byte[] compressedData = new byte[size]; - int compressedDataOffset = 0; - - int totalRead = 3; - buffer.skipBytes(3); // skip index/file - - for (int i = 0; i < breaks + 1; ++i) - { - int bytesInBlock = 512 - (totalRead % 512); - int bytesToRead = Math.min(bytesInBlock, size - compressedDataOffset); - - logger.trace("{}/{}: reading block {}/{}, read so far this block: {}, file status: {}/{}", - index, file, - (totalRead % 512), 512, - bytesInBlock, - compressedDataOffset, size); - - buffer.getBytes(buffer.readerIndex(), compressedData, compressedDataOffset, bytesToRead); - buffer.skipBytes(bytesToRead); - - compressedDataOffset += bytesToRead; - totalRead += bytesToRead; - - if (i < breaks) - { - assert compressedDataOffset < size; - int b = buffer.readUnsignedByte(); - ++totalRead; - assert b == 0xff; - } - } - - assert compressedDataOffset == size; - - logger.trace("{}/{}: done downloading file, remaining buffer {}", - index, file, - buffer.readableBytes()); - - client.onFileFinish(index, file, compressedData); - return true; - } - - /** - * Calculate how many breaks there are in the file stream. There are - * calculateBreaks()+1 total chunks in the file stream - * - * File contents are sent in 512 byte chunks, with the first byte of - * each chunk except for the first one being 0xff. - * - * The first chunk has an 8 byte header (index, file, compression, - * compressed size). So, the first chunk can contain 512 - 8 bytes of - * the file, and each chunk after 511 bytes. - * - * The 'size' parameter has the compression type and size included in - * it, since they haven't been read yet by the buffer stream, as - * decompress() reads it, so we use 512 - 3 (because 8-5) = 3 - */ - private int calculateBreaks(int size) - { - int initialSize = 512 - 3; - if (size <= initialSize) - { - return 0; // First in the initial chunk, no breaks - } - int left = size - initialSize; - - if (left % 511 == 0) - { - return (left / 511); - } - else - { - // / 511 because 511 bytes of the file per chunk. - // + 1 if there is some left over, it still needs its - // own chunk - return (left / 511) + 1; - } - } - @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { diff --git a/cache/src/main/java/net/runelite/cache/client/HandshakeResponseHandler.java b/cache/src/main/java/net/runelite/cache/client/HandshakeResponseHandler.java new file mode 100644 index 0000000000..80f14c5b9e --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/client/HandshakeResponseHandler.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.client; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.SimpleChannelInboundHandler; +import java.util.concurrent.CompletableFuture; +import net.runelite.cache.protocol.decoders.ArchiveResponseDecoder; +import net.runelite.cache.protocol.packets.EncryptionPacket; +import net.runelite.cache.protocol.packets.HandshakeResponsePacket; +import net.runelite.cache.protocol.packets.HandshakeResponseType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HandshakeResponseHandler extends SimpleChannelInboundHandler +{ + private static final Logger logger = LoggerFactory.getLogger(HandshakeResponseHandler.class); + + private final CacheClient client; + + public HandshakeResponseHandler(CacheClient client) + { + this.client = client; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, HandshakeResponsePacket handshakeResponse) throws Exception + { + Channel channel = ctx.channel(); + ChannelPipeline p = ctx.pipeline(); + CompletableFuture handshakeFuture = client.getHandshakeFuture(); + + assert handshakeFuture != null; + + if (handshakeResponse.getResponse() != HandshakeResponseType.RESPONSE_OK) + { + logger.warn("Non-ok response from server {}", handshakeResponse.getResponse()); + ctx.close(); + return; + } + + // Send encryption packet + EncryptionPacket encryptionPacket = new EncryptionPacket(); + encryptionPacket.setKey((byte) 0); + channel.writeAndFlush(encryptionPacket); + + client.setState(ClientState.CONNECTED); + + logger.info("Client is now connected!"); + + p.replace("decoder", "decoder", new ArchiveResponseDecoder()); + + handshakeFuture.complete(handshakeResponse.getResponse()); + } + +} diff --git a/cache/src/main/java/net/runelite/cache/client/PendingFileRequest.java b/cache/src/main/java/net/runelite/cache/client/PendingFileRequest.java index 6c23b93a9a..d07dc4f606 100644 --- a/cache/src/main/java/net/runelite/cache/client/PendingFileRequest.java +++ b/cache/src/main/java/net/runelite/cache/client/PendingFileRequest.java @@ -25,31 +25,32 @@ package net.runelite.cache.client; import java.util.concurrent.CompletableFuture; -import net.runelite.cache.client.requests.FileRequest; public class PendingFileRequest { - private final FileRequest request; + private final int index; + private final int archive; private final CompletableFuture future; - public PendingFileRequest(FileRequest request, CompletableFuture future) + public PendingFileRequest(int index, int archive, CompletableFuture future) { - this.request = request; + this.index = index; + this.archive = archive; this.future = future; } - public FileRequest getRequest() + public int getIndex() { - return request; + return index; + } + + public int getArchive() + { + return archive; } public CompletableFuture getFuture() { return future; } - - public int computeHash() - { - return (request.getIndex() << 16) | request.getFile(); - } } diff --git a/cache/src/main/java/net/runelite/cache/client/requests/HelloHandshake.java b/cache/src/main/java/net/runelite/cache/client/requests/HelloHandshake.java deleted file mode 100644 index 6cda7577e8..0000000000 --- a/cache/src/main/java/net/runelite/cache/client/requests/HelloHandshake.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2016-2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.cache.client.requests; - -public class HelloHandshake -{ - public static final int RESPONSE_OK = 0; - public static final int ACCOUNT_DISABLED = 4; - public static final int ACCOUNT_ONLINE = 5; - public static final int RESPONSE_OUTDATED = 6; - public static final int WORLD_FULL = 7; - public static final int SERVER_OFFLINE = 8; - public static final int LIMITED_EXCEEDED = 9; - public static final int BAD_SESSION_ID = 10; - public static final int ACCOUNT_HIJACK = 11; - public static final int MEMBERS_WORLD = 12; - public static final int COULD_NOT_COMPLETE_LOGIN = 13; - public static final int SERVER_BEING_UPDATED = 14; - public static final int TOO_MANY_ATTEMPTS = 16; - public static final int MEMBERS_ONLY_AREA = 17; - public static final int ACCOUNT_LOCKED = 18; - public static final int CLOSED_BETA = 19; - public static final int INVALID_LOGINSERVER = 20; - public static final int MALFORMED_PACKET = 22; - public static final int NO_REPLY_FROM_LOGINSERVER = 23; - public static final int ERR_LOADING_PROFILE = 24; - public static final int UNEXPECTED_LOGINSERVER_RESPONSE = 25; - public static final int IP_BANNED = 26; - public static final int SERVICE_UNAVAILABLE = 27; - public static final int NO_DISPLAY_NAME = 31; - public static final int BILLING_ERROR = 32; - public static final int ACCOUNT_INACCESSABLE = 37; - public static final int VOTE_TO_PLAY = 38; - public static final int NOT_ELIGIBLE = 55; - public static final int NEED_AUTHENTICATOR = 56; - public static final int AUTHENTICATOR_CODE_WRONG = 57; - - private byte type = 15; // handshake type - private int revision; - - public int getRevision() - { - return revision; - } - - public void setRevision(int revision) - { - this.revision = revision; - } - - public byte getType() - { - return type; - } - - public void setType(byte type) - { - this.type = type; - } - -} diff --git a/cache/src/main/java/net/runelite/cache/protocol/decoders/ArchiveRequestDecoder.java b/cache/src/main/java/net/runelite/cache/protocol/decoders/ArchiveRequestDecoder.java new file mode 100644 index 0000000000..18feef9076 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/decoders/ArchiveRequestDecoder.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.protocol.decoders; + +import net.runelite.cache.server.CacheFrameDecoder; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import java.util.List; +import net.runelite.cache.protocol.packets.ArchiveRequestPacket; + +public class ArchiveRequestDecoder extends ByteToMessageDecoder +{ + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception + { + byte opcode = in.getByte(in.readerIndex()); + if (opcode != CacheFrameDecoder.ARCHIVE_REQUEST_HIGH + && opcode != CacheFrameDecoder.ARCHIVE_REQUEST_LOW) + { + ctx.fireChannelRead(in.retain()); + return; + } + + byte priority = in.readByte(); + int index = in.readByte() & 0xFF; + int archiveId = in.readShort() & 0xFFFF; + + ArchiveRequestPacket archiveRequest = new ArchiveRequestPacket(); + archiveRequest.setPriority(priority == 1); + archiveRequest.setIndex(index); + archiveRequest.setArchive(archiveId); + out.add(archiveRequest); + } + +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/decoders/ArchiveResponseDecoder.java b/cache/src/main/java/net/runelite/cache/protocol/decoders/ArchiveResponseDecoder.java new file mode 100644 index 0000000000..e439513fc4 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/decoders/ArchiveResponseDecoder.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.protocol.decoders; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import java.util.List; +import net.runelite.cache.fs.jagex.CompressionType; +import net.runelite.cache.protocol.packets.ArchiveResponsePacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ArchiveResponseDecoder extends ByteToMessageDecoder +{ + private static final Logger logger = LoggerFactory.getLogger(ArchiveResponseDecoder.class); + + private static final int CHUNK_SIZE = 512; + + @Override + public void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception + { + if (in.readableBytes() < 8) + { + return; + } + + ByteBuf copy = in.slice(); + + int index = copy.readUnsignedByte(); + int file = copy.readUnsignedShort(); + // decompress() starts reading here + int compression = copy.readUnsignedByte(); + int compressedFileSize = copy.readInt(); + + assert compression == CompressionType.NONE || + compression == CompressionType.BZ2 || + compression == CompressionType.GZ; + + int size = compressedFileSize + + 5 // 1 byte compresion type, 4 byte compressed size + + (compression != CompressionType.NONE ? 4 : 0); // compression has leading 4 byte decompressed length + + assert size > 0; + + int breaks = calculateBreaks(size); + + // 3 for index/file + if (size + 3 + breaks > in.readableBytes()) + { + logger.trace("Index {} archive {}: Not enough data yet {} > {}", index, file, size + 3 + breaks, in.readableBytes()); + return; + } + + ByteBuf compressedData = Unpooled.buffer(size); + + int totalRead = 3; + in.skipBytes(3); // skip index/file + + for (int i = 0; i < breaks + 1; ++i) + { + int bytesInBlock = CHUNK_SIZE - (totalRead % CHUNK_SIZE); + int bytesToRead = Math.min(bytesInBlock, size - compressedData.writerIndex()); + + logger.trace("{}/{}: reading block {}/{}, read so far this block: {}, file status: {}/{}", + index, file, + (totalRead % CHUNK_SIZE), CHUNK_SIZE, + bytesInBlock, + compressedData.writerIndex(), size); + + compressedData.writeBytes(in.readBytes(bytesToRead)); + + totalRead += bytesToRead; + + if (i < breaks) + { + assert compressedData.writerIndex() < size; + int b = in.readUnsignedByte(); + ++totalRead; + assert b == 0xff; + } + } + + assert compressedData.writerIndex() == size; + + logger.trace("{}/{}: done downloading file, remaining buffer {}", + index, file, + in.readableBytes()); + + ArchiveResponsePacket archiveResponse = new ArchiveResponsePacket(); + archiveResponse.setIndex(index); + archiveResponse.setArchive(file); + archiveResponse.setData(compressedData.array()); + out.add(archiveResponse); + } + + /** + * Calculate how many breaks there are in the file stream. There are + * calculateBreaks()+1 total chunks in the file stream + * + * File contents are sent in 512 byte chunks, with the first byte of + * each chunk except for the first one being 0xff. + * + * The first chunk has an 8 byte header (index, file, compression, + * compressed size). So, the first chunk can contain 512 - 8 bytes of + * the file, and each chunk after 511 bytes. + * + * The 'size' parameter has the compression type and size included in + * it, since they haven't been read yet by the buffer stream, as + * decompress() reads it, so we use 512 - 3 (because 8-5) = 3 + */ + private static int calculateBreaks(int size) + { + int initialSize = CHUNK_SIZE - 3; + if (size <= initialSize) + { + return 0; // First in the initial chunk, no breaks + } + int left = size - initialSize; + + if (left % (CHUNK_SIZE - 1) == 0) + { + return (left / (CHUNK_SIZE - 1)); + } + else + { + // / 511 because 511 bytes of the file per chunk. + // + 1 if there is some left over, it still needs its + // own chunk + return (left / (CHUNK_SIZE - 1)) + 1; + } + } + +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/decoders/EncryptionDecoder.java b/cache/src/main/java/net/runelite/cache/protocol/decoders/EncryptionDecoder.java new file mode 100644 index 0000000000..4667822246 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/decoders/EncryptionDecoder.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.protocol.decoders; + +import net.runelite.cache.server.CacheFrameDecoder; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import java.util.List; +import net.runelite.cache.protocol.packets.EncryptionPacket; + +public class EncryptionDecoder extends ByteToMessageDecoder +{ + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception + { + if (in.getByte(in.readerIndex()) != CacheFrameDecoder.ENCRYPTION) + { + ctx.fireChannelRead(in.retain()); + return; + } + + in.readByte(); + byte xorKey = in.readByte(); + in.readShort(); // always 0 + + EncryptionPacket encryptionPacket = new EncryptionPacket(); + encryptionPacket.setKey(xorKey); + out.add(encryptionPacket); + } +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/decoders/HandshakeDecoder.java b/cache/src/main/java/net/runelite/cache/protocol/decoders/HandshakeDecoder.java new file mode 100644 index 0000000000..f484b98001 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/decoders/HandshakeDecoder.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.protocol.decoders; + +import net.runelite.cache.server.CacheFrameDecoder; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import java.util.List; +import net.runelite.cache.protocol.packets.HandshakePacket; +import net.runelite.cache.protocol.packets.HandshakeType; + +public class HandshakeDecoder extends ByteToMessageDecoder +{ + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception + { + if (in.getByte(in.readerIndex()) != CacheFrameDecoder.HANDSHAKE_ON_DEMAND) + { + ctx.fireChannelRead(in.retain()); + return; + } + + byte type = in.readByte(); + int revision = in.readInt(); + + HandshakePacket handshakePacket = new HandshakePacket(); + handshakePacket.setType(HandshakeType.of(type)); + handshakePacket.setRevision(revision); + out.add(handshakePacket); + } + +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/decoders/HandshakeResponseDecoder.java b/cache/src/main/java/net/runelite/cache/protocol/decoders/HandshakeResponseDecoder.java new file mode 100644 index 0000000000..66633b99c9 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/decoders/HandshakeResponseDecoder.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.protocol.decoders; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import java.util.List; +import net.runelite.cache.protocol.packets.HandshakeResponsePacket; +import net.runelite.cache.protocol.packets.HandshakeResponseType; + +public class HandshakeResponseDecoder extends ByteToMessageDecoder +{ + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception + { + if (in.readableBytes() < 1) + { + return; + } + + byte response = in.readByte(); + + HandshakeResponsePacket handshakeResponse = new HandshakeResponsePacket(); + handshakeResponse.setResponse(HandshakeResponseType.of(response)); + out.add(handshakeResponse); + } + +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/encoders/ArchiveRequestEncoder.java b/cache/src/main/java/net/runelite/cache/protocol/encoders/ArchiveRequestEncoder.java new file mode 100644 index 0000000000..57341f4fa4 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/encoders/ArchiveRequestEncoder.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.protocol.encoders; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import net.runelite.cache.protocol.packets.ArchiveRequestPacket; + +public class ArchiveRequestEncoder extends MessageToByteEncoder +{ + + @Override + protected void encode(ChannelHandlerContext ctx, ArchiveRequestPacket archiveRequest, ByteBuf out) throws Exception + { + out.writeByte(archiveRequest.isPriority() ? 1 : 0); + out.writeByte(archiveRequest.getIndex()); + out.writeShort(archiveRequest.getArchive()); + } + +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/encoders/ArchiveResponseEncoder.java b/cache/src/main/java/net/runelite/cache/protocol/encoders/ArchiveResponseEncoder.java new file mode 100644 index 0000000000..64a2f6c529 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/encoders/ArchiveResponseEncoder.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.protocol.encoders; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import net.runelite.cache.protocol.packets.ArchiveResponsePacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ArchiveResponseEncoder extends MessageToByteEncoder +{ + private static final Logger logger = LoggerFactory.getLogger(ArchiveResponseEncoder.class); + + private static final int CHUNK_SIZE = 512; + + @Override + protected void encode(ChannelHandlerContext ctx, ArchiveResponsePacket archiveResponse, ByteBuf out) throws Exception + { + // archive file header + // 1 byte index + // 2 byte archive + out.writeByte(archiveResponse.getIndex()); + out.writeShort(archiveResponse.getArchive()); + + int pos = out.readableBytes(); + + // next is the compressed data which starts with compression + // type and length + ByteBuf file = Unpooled.wrappedBuffer(archiveResponse.getData()); + // - 3 for the header + int chunkSize = Math.min(file.readableBytes(), CHUNK_SIZE - 3); + + out.writeBytes(file.readBytes(chunkSize)); + + while (file.isReadable()) + { + out.writeByte(0xff); + + chunkSize = Math.min(file.readableBytes(), CHUNK_SIZE - 1); + out.writeBytes(file.readBytes(chunkSize)); + } + + int size = out.readableBytes() - pos; + logger.debug("Wrote index {} archive {} (size {}) in {} bytes", + archiveResponse.getIndex(), archiveResponse.getArchive(), + archiveResponse.getData().length, size); + } + +} diff --git a/cache/src/main/java/net/runelite/cache/client/requests/FileRequest.java b/cache/src/main/java/net/runelite/cache/protocol/encoders/EncryptionEncoder.java similarity index 71% rename from cache/src/main/java/net/runelite/cache/client/requests/FileRequest.java rename to cache/src/main/java/net/runelite/cache/protocol/encoders/EncryptionEncoder.java index ff4d010cfd..aba9406c9b 100644 --- a/cache/src/main/java/net/runelite/cache/client/requests/FileRequest.java +++ b/cache/src/main/java/net/runelite/cache/protocol/encoders/EncryptionEncoder.java @@ -22,26 +22,22 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.cache.client.requests; +package net.runelite.cache.protocol.encoders; -public class FileRequest +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import net.runelite.cache.protocol.packets.EncryptionPacket; + +public class EncryptionEncoder extends MessageToByteEncoder { - private final int index; - private final int file; - public FileRequest(int index, int file) + @Override + protected void encode(ChannelHandlerContext ctx, EncryptionPacket encryptionPacket, ByteBuf out) throws Exception { - this.index = index; - this.file = file; + out.writeByte(EncryptionPacket.OPCODE); + out.writeByte(encryptionPacket.getKey()); + out.writeShort(0); } - public int getIndex() - { - return index; - } - - public int getFile() - { - return file; - } } diff --git a/cache/src/main/java/net/runelite/cache/client/requests/ConnectionInfo.java b/cache/src/main/java/net/runelite/cache/protocol/encoders/HandshakeEncoder.java similarity index 72% rename from cache/src/main/java/net/runelite/cache/client/requests/ConnectionInfo.java rename to cache/src/main/java/net/runelite/cache/protocol/encoders/HandshakeEncoder.java index 49b23358c0..316c9d85d1 100644 --- a/cache/src/main/java/net/runelite/cache/client/requests/ConnectionInfo.java +++ b/cache/src/main/java/net/runelite/cache/protocol/encoders/HandshakeEncoder.java @@ -22,35 +22,21 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.cache.client.requests; +package net.runelite.cache.protocol.encoders; -public class ConnectionInfo +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import net.runelite.cache.protocol.packets.HandshakePacket; + +public class HandshakeEncoder extends MessageToByteEncoder { - //login state of client - // 2 - logged in (in-game) - // 3 - logged out (not in-game) - private byte type = 3; - //padding to make packet size == 4 - private int padding; - - public byte getType() + @Override + protected void encode(ChannelHandlerContext ctx, HandshakePacket handshakePacket, ByteBuf out) throws Exception { - return type; + out.writeByte(handshakePacket.getType().getValue()); + out.writeInt(handshakePacket.getRevision()); } - public void setType(byte type) - { - this.type = type; - } - - public int getPadding() - { - return padding; - } - - public void setPadding(int padding) - { - this.padding = padding; - } } diff --git a/cache/src/main/java/net/runelite/cache/protocol/encoders/HandshakeResponseEncoder.java b/cache/src/main/java/net/runelite/cache/protocol/encoders/HandshakeResponseEncoder.java new file mode 100644 index 0000000000..48c41e42af --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/encoders/HandshakeResponseEncoder.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.protocol.encoders; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import net.runelite.cache.protocol.packets.HandshakeResponsePacket; +import net.runelite.cache.protocol.packets.HandshakeResponseType; + +public class HandshakeResponseEncoder extends MessageToByteEncoder +{ + + @Override + protected void encode(ChannelHandlerContext ctx, HandshakeResponsePacket handshakeResponse, ByteBuf out) throws Exception + { + HandshakeResponseType handshakeResponseType = handshakeResponse.getResponse(); + out.writeByte(handshakeResponseType.getValue()); + } + +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/encoders/XorEncoder.java b/cache/src/main/java/net/runelite/cache/protocol/encoders/XorEncoder.java new file mode 100644 index 0000000000..13b9d311d2 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/encoders/XorEncoder.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.protocol.encoders; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +public class XorEncoder extends MessageToByteEncoder +{ + private byte key; + + public byte getKey() + { + return key; + } + + public void setKey(byte key) + { + this.key = key; + } + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception + { + if (key == 0) + { + out.writeBytes(msg); + return; + } + + while (msg.isReadable()) + { + out.writeByte(msg.readByte() ^ key); + } + } + +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/packets/ArchiveRequestPacket.java b/cache/src/main/java/net/runelite/cache/protocol/packets/ArchiveRequestPacket.java new file mode 100644 index 0000000000..847ecb90fd --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/packets/ArchiveRequestPacket.java @@ -0,0 +1,44 @@ +package net.runelite.cache.protocol.packets; + +public class ArchiveRequestPacket +{ + private boolean priority; + private int index; + private int archive; + + @Override + public String toString() + { + return "ArchiveRequestPacket{" + "priority=" + priority + ", index=" + index + ", archive=" + archive + '}'; + } + + public boolean isPriority() + { + return priority; + } + + public void setPriority(boolean priority) + { + this.priority = priority; + } + + public int getIndex() + { + return index; + } + + public void setIndex(int index) + { + this.index = index; + } + + public int getArchive() + { + return archive; + } + + public void setArchive(int archive) + { + this.archive = archive; + } +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/packets/ArchiveResponsePacket.java b/cache/src/main/java/net/runelite/cache/protocol/packets/ArchiveResponsePacket.java new file mode 100644 index 0000000000..edc9fcbb94 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/packets/ArchiveResponsePacket.java @@ -0,0 +1,44 @@ +package net.runelite.cache.protocol.packets; + +public class ArchiveResponsePacket +{ + private int index; + private int archive; + private byte[] data; + + @Override + public String toString() + { + return "ArchiveResponsePacket{" + "index=" + index + ", archive=" + archive + ", data=" + data + '}'; + } + + public int getIndex() + { + return index; + } + + public void setIndex(int index) + { + this.index = index; + } + + public int getArchive() + { + return archive; + } + + public void setArchive(int archive) + { + this.archive = archive; + } + + public byte[] getData() + { + return data; + } + + public void setData(byte[] data) + { + this.data = data; + } +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/packets/EncryptionPacket.java b/cache/src/main/java/net/runelite/cache/protocol/packets/EncryptionPacket.java new file mode 100644 index 0000000000..8818982236 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/packets/EncryptionPacket.java @@ -0,0 +1,24 @@ +package net.runelite.cache.protocol.packets; + +public class EncryptionPacket +{ + public static final int OPCODE = 4; + + private byte key; + + @Override + public String toString() + { + return "EncryptionPacket{" + "key=" + key + '}'; + } + + public byte getKey() + { + return key; + } + + public void setKey(byte key) + { + this.key = key; + } +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakePacket.java b/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakePacket.java new file mode 100644 index 0000000000..e25b12e3e3 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakePacket.java @@ -0,0 +1,33 @@ +package net.runelite.cache.protocol.packets; + +public class HandshakePacket +{ + private HandshakeType type; + private int revision; + + @Override + public String toString() + { + return "HandshakePacket{" + "type=" + type + ", revision=" + revision + '}'; + } + + public HandshakeType getType() + { + return type; + } + + public void setType(HandshakeType type) + { + this.type = type; + } + + public int getRevision() + { + return revision; + } + + public void setRevision(int revision) + { + this.revision = revision; + } +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeResponsePacket.java b/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeResponsePacket.java new file mode 100644 index 0000000000..31e590b7e1 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeResponsePacket.java @@ -0,0 +1,22 @@ +package net.runelite.cache.protocol.packets; + +public class HandshakeResponsePacket +{ + private HandshakeResponseType response; + + @Override + public String toString() + { + return "HandshakeResponsePacket{" + "response=" + response + '}'; + } + + public HandshakeResponseType getResponse() + { + return response; + } + + public void setResponse(HandshakeResponseType response) + { + this.response = response; + } +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeResponseType.java b/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeResponseType.java new file mode 100644 index 0000000000..6dc0a7a806 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeResponseType.java @@ -0,0 +1,59 @@ +package net.runelite.cache.protocol.packets; + +public enum HandshakeResponseType +{ + RESPONSE_OK(0), + ACCOUNT_DISABLED(4), + ACCOUNT_ONLINE(5), + RESPONSE_OUTDATED(6), + WORLD_FULL(7), + SERVER_OFFLINE(8), + LIMITED_EXCEEDED(9), + BAD_SESSION_ID(10), + ACCOUNT_HIJACK(11), + MEMBERS_WORLD(12), + COULD_NOT_COMPLETE_LOGIN(13), + SERVER_BEING_UPDATED(14), + TOO_MANY_ATTEMPTS(16), + MEMBERS_ONLY_AREA(17), + ACCOUNT_LOCKED(18), + CLOSED_BETA(19), + INVALID_LOGINSERVER(20), + MALFORMED_PACKET(22), + NO_REPLY_FROM_LOGINSERVER(23), + ERR_LOADING_PROFILE(24), + UNEXPECTED_LOGINSERVER_RESPONSE(25), + IP_BANNED(26), + SERVICE_UNAVAILABLE(27), + NO_DISPLAY_NAME(31), + BILLING_ERROR(32), + ACCOUNT_INACCESSABLE(37), + VOTE_TO_PLAY(38), + NOT_ELIGIBLE(55), + NEED_AUTHENTICATOR(56), + AUTHENTICATOR_CODE_WRONG(57); + + private final byte value; + + private HandshakeResponseType(int value) + { + this.value = (byte) value; + } + + public byte getValue() + { + return value; + } + + public static HandshakeResponseType of(byte value) + { + for (HandshakeResponseType type : values()) + { + if (type.value == value) + { + return type; + } + } + return null; + } +} diff --git a/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeType.java b/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeType.java new file mode 100644 index 0000000000..1f117d5a6d --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/protocol/packets/HandshakeType.java @@ -0,0 +1,30 @@ +package net.runelite.cache.protocol.packets; + +public enum HandshakeType +{ + ON_DEMAND(15); + + private final byte value; + + private HandshakeType(int value) + { + this.value = (byte) value; + } + + public byte getValue() + { + return value; + } + + public static HandshakeType of(byte value) + { + for (HandshakeType type : values()) + { + if (type.value == value) + { + return type; + } + } + return null; + } +} diff --git a/cache/src/main/java/net/runelite/cache/server/ArchiveRequestHandler.java b/cache/src/main/java/net/runelite/cache/server/ArchiveRequestHandler.java new file mode 100644 index 0000000000..be5c5ee302 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/server/ArchiveRequestHandler.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.server; + +import com.google.common.primitives.Ints; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import java.io.IOException; +import java.util.Arrays; +import net.runelite.cache.fs.Archive; +import net.runelite.cache.fs.Index; +import net.runelite.cache.fs.Store; +import net.runelite.cache.fs.jagex.CompressionType; +import net.runelite.cache.fs.jagex.DataFile; +import net.runelite.cache.protocol.packets.ArchiveRequestPacket; +import net.runelite.cache.protocol.packets.ArchiveResponsePacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ArchiveRequestHandler extends SimpleChannelInboundHandler +{ + private static final Logger logger = LoggerFactory.getLogger(ArchiveRequestHandler.class); + + private final Store store; + + public ArchiveRequestHandler(Store store) + { + this.store = store; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ArchiveRequestPacket archiveRequest) throws Exception + { + if (archiveRequest.getIndex() == 255) + { + handleRequest255(ctx, archiveRequest.getIndex(), + archiveRequest.getArchive()); + } + else + { + handleRequest(ctx, archiveRequest.getIndex(), + archiveRequest.getArchive()); + } + } + + private void handleRequest255(ChannelHandlerContext ctx, int index, int archiveId) throws IOException + { + logger.info("Client {} requests 255: index {}, archive {}", ctx.channel().remoteAddress(), index, archiveId); + + byte[] compressed; + if (archiveId == 255) + { + // index 255 data, for each index: + // 4 byte crc + // 4 byte revision + ByteBuf buffer = Unpooled.buffer(store.getIndexes().size() * 8); + for (Index i : store.getIndexes()) + { + buffer.writeInt(i.getCrc()); + buffer.writeInt(i.getRevision()); + } + + compressed = compress(CompressionType.NONE, Arrays.copyOf(buffer.array(), buffer.readableBytes())); + } + else + { + Index i = store.findIndex(archiveId); + assert i != null; + + byte[] indexData = i.toIndexData().writeIndexData(); + + compressed = compress(CompressionType.NONE, indexData); + } + + ArchiveResponsePacket response = new ArchiveResponsePacket(); + response.setIndex(index); + response.setArchive(archiveId); + response.setData(compressed); + + ctx.writeAndFlush(response); + } + + private void handleRequest(ChannelHandlerContext ctx, int index, int archiveId) throws IOException + { + logger.info("Client {} requests index {} archive {}", ctx.channel().remoteAddress(), index, archiveId); + + Index i = store.findIndex(index); + assert i != null; + + Archive archive = i.getArchive(archiveId); + assert archive != null; + + byte[] packed; + if (archive.getData() != null) + { + packed = archive.getData(); // is compressed, includes length and type + + byte compression = packed[0]; + int compressedSize = Ints.fromBytes(packed[1], packed[2], + packed[3], packed[4]); + + assert packed.length == 1 // compression + + 4 // compressed size + + compressedSize + + (compression != CompressionType.NONE ? 4 : 0) + : "maybe revision is at end of data?"; + } + else + { + byte[] data = archive.saveContents(); + packed = compress(archive.getCompression(), data); + } + + ArchiveResponsePacket response = new ArchiveResponsePacket(); + response.setIndex(index); + response.setArchive(archiveId); + response.setData(packed); + + ctx.writeAndFlush(response); + } + + private byte[] compress(int compression, byte[] data) throws IOException + { + return DataFile.compress(data, compression, -1, null); + } +} diff --git a/cache/src/main/java/net/runelite/cache/server/CacheFrameDecoder.java b/cache/src/main/java/net/runelite/cache/server/CacheFrameDecoder.java new file mode 100644 index 0000000000..e6361de73f --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/server/CacheFrameDecoder.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.server; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import java.util.List; +import net.runelite.cache.protocol.packets.EncryptionPacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CacheFrameDecoder extends ByteToMessageDecoder +{ + private static final Logger logger = LoggerFactory.getLogger(CacheFrameDecoder.class); + + public static final int ARCHIVE_REQUEST_LOW = 0; + public static final int ARCHIVE_REQUEST_HIGH = 1; + public static final int ENCRYPTION = EncryptionPacket.OPCODE; + public static final int HANDSHAKE_ON_DEMAND = 15; + + private ClientState state = ClientState.HANDSHAKING; + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception + { + in.markReaderIndex(); + byte opcode = in.readByte(); + in.resetReaderIndex(); + + int length; + + switch (opcode) + { + case HANDSHAKE_ON_DEMAND: + length = 5; + + if (state != ClientState.HANDSHAKING) + { + ctx.close(); + return; + } + state = ClientState.CONNECTED; + break; + case ARCHIVE_REQUEST_LOW: + case ARCHIVE_REQUEST_HIGH: + case ENCRYPTION: + length = 4; + + if (state != ClientState.CONNECTED) + { + ctx.close(); + return; + } + break; + default: + logger.debug("Unknown packet opcode from {}: {}", + ctx.channel().remoteAddress(), opcode); + ctx.close(); + return; + } + + if (in.readableBytes() < length) + { + return; + } + + ByteBuf packet = in.readRetainedSlice(length); + out.add(packet); + } +} diff --git a/cache/src/main/java/net/runelite/cache/server/CacheServer.java b/cache/src/main/java/net/runelite/cache/server/CacheServer.java index ab0e816cdf..de49c82045 100644 --- a/cache/src/main/java/net/runelite/cache/server/CacheServer.java +++ b/cache/src/main/java/net/runelite/cache/server/CacheServer.java @@ -27,12 +27,9 @@ package net.runelite.cache.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; -import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import net.runelite.cache.fs.Store; import org.slf4j.Logger; @@ -63,16 +60,7 @@ public class CacheServer implements AutoCloseable b.group(group) .channel(NioServerSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) - .childHandler(new ChannelInitializer() - { - @Override - protected void initChannel(SocketChannel ch) throws Exception - { - ChannelPipeline p = ch.pipeline(); - - p.addLast(new CacheServerHandler(CacheServer.this)); - } - }); + .childHandler(new CacheServerInitializer(this)); ChannelFuture f = b.bind(PORT).syncUninterruptibly(); channel = f.channel(); diff --git a/cache/src/main/java/net/runelite/cache/server/CacheServerHandler.java b/cache/src/main/java/net/runelite/cache/server/CacheServerHandler.java deleted file mode 100644 index f8a9d309a3..0000000000 --- a/cache/src/main/java/net/runelite/cache/server/CacheServerHandler.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright (c) 2017, Adam - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package net.runelite.cache.server; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import java.io.IOException; -import java.util.Arrays; -import net.runelite.cache.client.requests.ConnectionInfo; -import net.runelite.cache.client.requests.HelloHandshake; -import net.runelite.cache.fs.Archive; -import net.runelite.cache.fs.jagex.CompressionType; -import net.runelite.cache.fs.jagex.DataFile; -import net.runelite.cache.fs.Index; -import net.runelite.cache.fs.Store; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CacheServerHandler extends SimpleChannelInboundHandler -{ - private static final Logger logger = LoggerFactory.getLogger(CacheServerHandler.class); - - private final CacheServer server; - private final Store store; - - private ClientState state = ClientState.HANDSHAKING; - private final ByteBuf buffer = Unpooled.buffer(); - - public CacheServerHandler(CacheServer server) - { - this.server = server; - this.store = server.getStore(); - } - - @Override - protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception - { - System.out.println(ByteBufUtil.prettyHexDump(buf)); - - buffer.writeBytes(buf); - - int last = -1; - while (buffer.readableBytes() != last) - { - last = buffer.readableBytes(); - - switch (state) - { - case HANDSHAKING: - handshake(ctx, buffer); - break; - case CONNECTING: - connecting(ctx, buffer); - break; - case CONNECTED: - connected(ctx, buffer); - break; - } - } - - buffer.discardReadBytes(); - } - - private void handshake(ChannelHandlerContext ctx, ByteBuf buf) - { - if (buf.readableBytes() < 5) - { - return; - } - - byte type = buf.readByte(); - if (type != 15) - { - logger.warn("Expected handshake type 15, got {}", type); - ctx.close(); - return; - } - - int revision = buf.readInt(); - if (revision != server.getRevision()) - { - logger.warn("Incorrect version for client {}, expected {}", revision, server.getRevision()); - ctx.close(); - return; - } - - logger.info("Handshake complete from client {}, type {}, revision {}", ctx.channel().remoteAddress(), type, revision); - - // Write response - ByteBuf buffer = Unpooled.buffer(1); - buffer.writeByte(HelloHandshake.RESPONSE_OK); - ctx.writeAndFlush(buffer); - - state = ClientState.CONNECTING; - } - - private void connecting(ChannelHandlerContext ctx, ByteBuf buf) - { - if (buf.readableBytes() < 4) - { - return; - } - - ConnectionInfo cinfo = new ConnectionInfo(); - cinfo.setType(buf.readByte()); - cinfo.setPadding(buf.readMedium()); - - logger.info("Connect info from client {} - type {}", ctx.channel().remoteAddress(), cinfo.getType()); - - state = ClientState.CONNECTED; - } - - private void connected(ChannelHandlerContext ctx, ByteBuf buf) - { - // a request for an archive - // byte[0] = 1 if requesting index 255, else 0 - // byte[1] = index - // byte[2-3] = archive id - - if (buf.readableBytes() < 4) - { - return; - } - - byte requesting255 = buf.readByte(); - int index = buf.readByte() & 0xFF; - int archiveId = buf.readShort() & 0xFFFF; - - if (index == 255) - { - handle255(ctx, requesting255, index, archiveId); - } - else - { - handleRequest(ctx, index, archiveId); - } - } - - private void handle255(ChannelHandlerContext ctx, byte requesting255, int index, int archiveId) - { - logger.info("Client {} requests 255 {}, index {}, archive {}", ctx.channel().remoteAddress(), requesting255, index, archiveId); - - if (archiveId == 255) - { - // index 255 data, for each index: - // 4 byte crc - // 4 byte revision - ByteBuf buffer = Unpooled.buffer(); - for (Index i : store.getIndexes()) - { - buffer.writeInt(i.getCrc()); - buffer.writeInt(i.getRevision()); - } - - byte[] compressed = compress(CompressionType.NONE, Arrays.copyOf(buffer.array(), buffer.readableBytes())); - byte[] packed = addHeader(index, archiveId, compressed); - byte[] chunked = chunk(packed); - - ctx.writeAndFlush(Unpooled.wrappedBuffer(chunked)); - } - else - { - Index i = store.findIndex(archiveId); - assert i != null; - - byte[] indexData = i.toIndexData().writeIndexData(); - - byte[] compressed = compress(CompressionType.NONE, indexData); - byte[] packed = addHeader(255, archiveId, compressed); - byte[] chunked = chunk(packed); - - ctx.writeAndFlush(Unpooled.wrappedBuffer(chunked)); - } - } - - private void handleRequest(ChannelHandlerContext ctx, int index, int archiveId) - { - logger.info("Client {} requests index {} archive {}", ctx.channel().remoteAddress(), index, archiveId); - - Index i = store.findIndex(index); - assert i != null; - - Archive archive = i.getArchive(archiveId); - assert archive != null; - - byte[] packed; - if (archive.getData() != null) - { - byte[] data = archive.getData(); // is compressed, includes length and type - packed = addHeader(index, archiveId, Arrays.copyOf(data, data.length - 2)); // compressed size includes revision.. - } - else - { - byte[] data = archive.saveContents(); - data = compress(archive.getCompression(), data); - - packed = addHeader(index, archiveId, data); - } - - byte[] chunked = chunk(packed); - - ctx.writeAndFlush(Unpooled.wrappedBuffer(chunked)); - } - - private byte[] compress(int compression, byte[] data) - { - try - { - return DataFile.compress(data, compression, -1, null); - } - catch (IOException ex) - { - throw new RuntimeException(ex); - } - } - - /** - * add header to archive - * - * @param index - * @param archive - * @param file compressed archive - * @return - */ - private byte[] addHeader(int index, int archive, byte[] file) - { - // archive file header - // 1 byte index - // 2 byte archive - - ByteBuf buf = Unpooled.buffer(); - buf.writeByte((byte) index); - buf.writeShort((short) archive); - buf.writeBytes(file); - - return Arrays.copyOf(buf.array(), buf.readableBytes()); - } - - /** - * Insert in chunk markers to data - * - * @param in - * @return - */ - private byte[] chunk(byte[] in) - { - Chunker chunker = new Chunker(in); - return chunker.chunkData(); - } - -} diff --git a/cache/src/main/java/net/runelite/cache/server/CacheServerInitializer.java b/cache/src/main/java/net/runelite/cache/server/CacheServerInitializer.java new file mode 100644 index 0000000000..d631072f6c --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/server/CacheServerInitializer.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.server; + +import net.runelite.cache.protocol.decoders.HandshakeDecoder; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import net.runelite.cache.protocol.decoders.ArchiveRequestDecoder; +import net.runelite.cache.protocol.decoders.EncryptionDecoder; +import net.runelite.cache.protocol.encoders.ArchiveResponseEncoder; +import net.runelite.cache.protocol.encoders.HandshakeResponseEncoder; +import net.runelite.cache.protocol.encoders.XorEncoder; + +public class CacheServerInitializer extends ChannelInitializer +{ + private final CacheServer server; + + public CacheServerInitializer(CacheServer server) + { + this.server = server; + } + + @Override + protected void initChannel(SocketChannel ch) throws Exception + { + ChannelPipeline p = ch.pipeline(); + + p.addLast( + new CacheFrameDecoder(), + new HandshakeDecoder(), + new EncryptionDecoder(), + new ArchiveRequestDecoder() + ); + + p.addLast( + new HandshakeResponseEncoder(), + new XorEncoder(), + new ArchiveResponseEncoder() + ); + + p.addLast( + new ArchiveRequestHandler(server.getStore()), + new EncryptionHandler(), + new HandshakeHandler(server) + ); + } + +} diff --git a/cache/src/main/java/net/runelite/cache/server/ClientState.java b/cache/src/main/java/net/runelite/cache/server/ClientState.java index fc04db3ea1..088e6087ce 100644 --- a/cache/src/main/java/net/runelite/cache/server/ClientState.java +++ b/cache/src/main/java/net/runelite/cache/server/ClientState.java @@ -27,6 +27,5 @@ package net.runelite.cache.server; public enum ClientState { HANDSHAKING, - CONNECTING, CONNECTED } diff --git a/cache/src/main/java/net/runelite/cache/server/EncryptionHandler.java b/cache/src/main/java/net/runelite/cache/server/EncryptionHandler.java new file mode 100644 index 0000000000..f3309ce826 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/server/EncryptionHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.SimpleChannelInboundHandler; +import net.runelite.cache.protocol.encoders.XorEncoder; +import net.runelite.cache.protocol.packets.EncryptionPacket; + +public class EncryptionHandler extends SimpleChannelInboundHandler +{ + + @Override + protected void channelRead0(ChannelHandlerContext ctx, EncryptionPacket encryptionPacket) throws Exception + { + ChannelPipeline p = ctx.pipeline(); + XorEncoder xorEncoder = p.get(XorEncoder.class); + if (xorEncoder != null) + { + xorEncoder.setKey(encryptionPacket.getKey()); + } + } + +} diff --git a/cache/src/main/java/net/runelite/cache/server/HandshakeHandler.java b/cache/src/main/java/net/runelite/cache/server/HandshakeHandler.java new file mode 100644 index 0000000000..552da91d5e --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/server/HandshakeHandler.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import net.runelite.cache.protocol.packets.HandshakePacket; +import net.runelite.cache.protocol.packets.HandshakeResponsePacket; +import net.runelite.cache.protocol.packets.HandshakeResponseType; +import net.runelite.cache.protocol.packets.HandshakeType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HandshakeHandler extends SimpleChannelInboundHandler +{ + private static final Logger logger = LoggerFactory.getLogger(HandshakeHandler.class); + + private final CacheServer server; + + public HandshakeHandler(CacheServer server) + { + this.server = server; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, HandshakePacket handshakePacket) throws Exception + { + if (handshakePacket.getType() != HandshakeType.ON_DEMAND) + { + logger.warn("Expected handshake type ON_DEMAND"); + ctx.close(); + return; + } + + if (handshakePacket.getRevision() != server.getRevision()) + { + logger.warn("Incorrect version for client {}, expected {}", + handshakePacket.getRevision(), server.getRevision()); + ctx.close(); + return; + } + + logger.info("Handshake complete from client {}, type {}, revision {}", + ctx.channel().remoteAddress(), handshakePacket.getType(), + handshakePacket.getRevision()); + + HandshakeResponsePacket handshakeResponse = new HandshakeResponsePacket(); + handshakeResponse.setResponse(HandshakeResponseType.RESPONSE_OK); + ctx.channel().writeAndFlush(handshakeResponse); + } + +} diff --git a/cache/src/test/java/net/runelite/cache/client/CacheClientTest.java b/cache/src/test/java/net/runelite/cache/client/CacheClientTest.java index fe63edadb0..6a1cadbdb6 100644 --- a/cache/src/test/java/net/runelite/cache/client/CacheClientTest.java +++ b/cache/src/test/java/net/runelite/cache/client/CacheClientTest.java @@ -29,6 +29,7 @@ import java.util.concurrent.CompletableFuture; import net.runelite.cache.CacheProperties; import net.runelite.cache.fs.Store; import net.runelite.cache.fs.tree.TreeStorage; +import net.runelite.cache.protocol.packets.HandshakeResponseType; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; @@ -57,12 +58,12 @@ public class CacheClientTest CacheClient c = new CacheClient(store, CacheProperties.getRsVersion()); c.connect(); - CompletableFuture handshake = c.handshake(); + CompletableFuture handshake = c.handshake(); - Integer result = handshake.get(); + HandshakeResponseType result = handshake.get(); logger.info("Handshake result: {}", result); - Assert.assertEquals(0, (int) result); + Assert.assertEquals(HandshakeResponseType.RESPONSE_OK, result); c.download(); @@ -83,12 +84,12 @@ public class CacheClientTest CacheClient c = new CacheClient(store, CacheProperties.getRsVersion()); c.connect(); - CompletableFuture handshake = c.handshake(); + CompletableFuture handshake = c.handshake(); - Integer result = handshake.get(); + HandshakeResponseType result = handshake.get(); logger.info("Handshake result: {}", result); - Assert.assertEquals(0, (int) result); + Assert.assertEquals(HandshakeResponseType.RESPONSE_OK, result); c.download(); diff --git a/cache/src/test/java/net/runelite/cache/protocol/encoders/ArchiveResponseEncoderTest.java b/cache/src/test/java/net/runelite/cache/protocol/encoders/ArchiveResponseEncoderTest.java new file mode 100644 index 0000000000..3e3f8f80e5 --- /dev/null +++ b/cache/src/test/java/net/runelite/cache/protocol/encoders/ArchiveResponseEncoderTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.cache.protocol.encoders; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import net.runelite.cache.fs.jagex.CompressionType; +import net.runelite.cache.fs.jagex.DataFile; +import net.runelite.cache.protocol.decoders.ArchiveResponseDecoder; +import net.runelite.cache.protocol.packets.ArchiveResponsePacket; +import org.junit.Assert; +import org.junit.Test; + +public class ArchiveResponseEncoderTest +{ + @Test + public void testEncode() throws Exception + { + byte[] data = new byte[1000]; + Random random = new Random(42L); + random.nextBytes(data); + + byte[] compressedData = DataFile.compress(data, CompressionType.NONE, -1, null); + + ArchiveResponsePacket archiveResponse = new ArchiveResponsePacket(); + archiveResponse.setIndex(0); + archiveResponse.setArchive(1); + archiveResponse.setData(compressedData); + + ByteBuf buf = Unpooled.buffer(1024); + ArchiveResponseEncoder encoder = new ArchiveResponseEncoder(); + encoder.encode(null, archiveResponse, buf); + + ArchiveResponseDecoder decoder = new ArchiveResponseDecoder(); + List out = new ArrayList<>(); + decoder.decode(null, buf, out); + + Assert.assertEquals(1, out.size()); + ArchiveResponsePacket response = (ArchiveResponsePacket) out.get(0); + + Assert.assertEquals(archiveResponse.getIndex(), response.getIndex()); + Assert.assertEquals(archiveResponse.getArchive(), response.getArchive()); + Assert.assertArrayEquals(archiveResponse.getData(), response.getData()); + + byte[] decompressedData = DataFile.decompress(response.getData(), null).data; + Assert.assertArrayEquals(data, decompressedData); + } + +} diff --git a/cache/src/main/java/net/runelite/cache/server/Chunker.java b/cache/src/test/java/net/runelite/cache/protocol/encoders/XorEncoderTest.java similarity index 58% rename from cache/src/main/java/net/runelite/cache/server/Chunker.java rename to cache/src/test/java/net/runelite/cache/protocol/encoders/XorEncoderTest.java index fb18f350a8..53612ac78f 100644 --- a/cache/src/main/java/net/runelite/cache/server/Chunker.java +++ b/cache/src/test/java/net/runelite/cache/protocol/encoders/XorEncoderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Adam + * Copyright (c) 2016-2017, Adam * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,57 +22,30 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package net.runelite.cache.server; +package net.runelite.cache.protocol.encoders; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import java.util.Arrays; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.junit.Assert; +import org.junit.Test; -public class Chunker +public class XorEncoderTest { - private static final Logger logger = LoggerFactory.getLogger(Chunker.class); - - private static final int CHUNK_SIZE = 512; - - private final byte[] data; - - public Chunker(byte[] data) + @Test + public void testEncode() throws Exception { - this.data = data; + ByteBuf buf = Unpooled.buffer(1); + buf.markWriterIndex(); + buf.writeByte(0xff); + + XorEncoder encoder = new XorEncoder(); + encoder.setKey((byte) 0x1); + + ByteBuf out = Unpooled.buffer(1); + encoder.encode(null, buf, out); + + byte encoded = out.readByte(); + Assert.assertEquals((Byte) (byte) 0xfe, (Byte) encoded); } - /** - * Split data into 512 byte chunks, with the first byte of each chunk - * being 0xff, except for the first chunk. - * - * @return - */ - public byte[] chunkData() - { - ByteBuf buf = Unpooled.buffer(); - - int pos = 0; - int remaining = data.length; - - int put = Math.min(CHUNK_SIZE, remaining); - buf.writeBytes(data, pos, put); - - pos += put; - remaining -= put; - - while (remaining > 0) - { - buf.writeByte((byte) 0xff); - - put = Math.min(CHUNK_SIZE - 1, remaining); - buf.writeBytes(data, pos, put); - - pos += put; - remaining -= put; - } - - return Arrays.copyOf(buf.array(), buf.readableBytes()); - } } diff --git a/cache/src/test/java/net/runelite/cache/server/CacheServerTest.java b/cache/src/test/java/net/runelite/cache/server/CacheServerTest.java index a003e9b546..dfa0d445c0 100644 --- a/cache/src/test/java/net/runelite/cache/server/CacheServerTest.java +++ b/cache/src/test/java/net/runelite/cache/server/CacheServerTest.java @@ -41,26 +41,11 @@ import org.junit.rules.TemporaryFolder; public class CacheServerTest { private static final String HOST = "localhost"; - private static final int REVISION = 139; + private static final int REVISION = 154; @Rule public TemporaryFolder folder = StoreLocation.getTemporaryFolder(); - @Test - @Ignore - public void run() throws Exception - { - try (Store store = new Store(new File("D:\\rs\\07\\temp\\cache139")); - CacheServer server = new CacheServer(store, REVISION)) - { - store.load(); - store.rebuildCrc(); - - server.start(); - server.waitForClose(); - } - } - @Test @Ignore public void testDownload() throws Exception diff --git a/http-service/src/main/java/net/runelite/http/service/cache/CacheUpdater.java b/http-service/src/main/java/net/runelite/http/service/cache/CacheUpdater.java index 5b2e936fac..90223ad7d3 100644 --- a/http-service/src/main/java/net/runelite/http/service/cache/CacheUpdater.java +++ b/http-service/src/main/java/net/runelite/http/service/cache/CacheUpdater.java @@ -35,9 +35,9 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import net.runelite.cache.client.CacheClient; import net.runelite.cache.client.IndexInfo; -import static net.runelite.cache.client.requests.HelloHandshake.RESPONSE_OK; import net.runelite.cache.fs.Archive; import net.runelite.cache.fs.Store; +import net.runelite.cache.protocol.packets.HandshakeResponseType; import net.runelite.http.api.RuneliteAPI; import net.runelite.http.service.cache.beans.CacheEntry; import net.runelite.http.service.cache.beans.IndexEntry; @@ -99,9 +99,9 @@ public class CacheUpdater (Archive archive) -> executor.submit(new CacheUploader(minioClient, minioBucket, archive))); client.connect(); - int result = client.handshake().join(); + HandshakeResponseType result = client.handshake().join(); - if (result != RESPONSE_OK) + if (result != HandshakeResponseType.RESPONSE_OK) { throw new OutOfDateException(); }