From 535cbaa6a329faf83c1f190f0cd0e8b9a57d5d09 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 3 Jun 2016 14:13:28 -0400 Subject: [PATCH] Add cache downloader client --- cache/pom.xml | 5 + .../cache/downloader/CacheClient.java | 270 ++++++++++++++++++ .../cache/downloader/CacheClientHandler.java | 228 +++++++++++++++ .../cache/downloader/ClientState.java | 37 +++ .../runelite/cache/downloader/FileResult.java | 88 ++++++ .../cache/downloader/PendingFileRequest.java | 61 ++++ .../downloader/requests/ConnectionInfo.java | 57 ++++ .../downloader/requests/FileRequest.java | 53 ++++ .../downloader/requests/HelloHandshake.java | 60 ++++ .../java/net/runelite/cache/fs/Index.java | 10 + .../cache/downloader/CacheClientTest.java | 78 +++++ 11 files changed, 947 insertions(+) create mode 100644 cache/src/main/java/net/runelite/cache/downloader/CacheClient.java create mode 100644 cache/src/main/java/net/runelite/cache/downloader/CacheClientHandler.java create mode 100644 cache/src/main/java/net/runelite/cache/downloader/ClientState.java create mode 100644 cache/src/main/java/net/runelite/cache/downloader/FileResult.java create mode 100644 cache/src/main/java/net/runelite/cache/downloader/PendingFileRequest.java create mode 100644 cache/src/main/java/net/runelite/cache/downloader/requests/ConnectionInfo.java create mode 100644 cache/src/main/java/net/runelite/cache/downloader/requests/FileRequest.java create mode 100644 cache/src/main/java/net/runelite/cache/downloader/requests/HelloHandshake.java create mode 100644 cache/src/test/java/net/runelite/cache/downloader/CacheClientTest.java diff --git a/cache/pom.xml b/cache/pom.xml index c81033c4b3..b9a34155b0 100644 --- a/cache/pom.xml +++ b/cache/pom.xml @@ -79,6 +79,11 @@ bcprov-ext-jdk14 1.54 + + io.netty + netty-all + 4.1.0.Final + junit diff --git a/cache/src/main/java/net/runelite/cache/downloader/CacheClient.java b/cache/src/main/java/net/runelite/cache/downloader/CacheClient.java new file mode 100644 index 0000000000..ebedb8937a --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/downloader/CacheClient.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2016, 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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.downloader; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +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.NioSocketChannel; +import io.netty.handler.proxy.HttpProxyHandler; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import net.runelite.cache.downloader.requests.FileRequest; +import net.runelite.cache.fs.Archive; +import net.runelite.cache.fs.DataFileReadResult; +import net.runelite.cache.fs.Index; +import net.runelite.cache.fs.Store; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CacheClient +{ + private static final Logger logger = LoggerFactory.getLogger(CacheClient.class); + + private static final String HOST = "oldschool1.runescape.com"; + private static final int PORT = 43594; + + private static final int CLIENT_REVISION = 115; + + private Store store; // store cache will be written to + private int clientRevision; + + private EventLoopGroup group = new NioEventLoopGroup(1); + private Channel channel; + + private Queue requests = new ArrayDeque<>(); + + public CacheClient(Store store) + { + this(store, CLIENT_REVISION); + } + + public CacheClient(Store store, int clientRevision) + { + this.store = store; + this.clientRevision = clientRevision; + } + + public void connect() throws InterruptedException + { + Bootstrap b = new Bootstrap(); + b.group(group) + .channel(NioSocketChannel.class) + .option(ChannelOption.TCP_NODELAY, true) + .handler(new ChannelInitializer() + { + @Override + 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)); + } + }); + + // Start the client. + ChannelFuture f = b.connect(HOST, PORT).sync(); + channel = f.channel(); + } + + public void stop() + { + group.shutdownGracefully(); + } + + public int getClientRevision() + { + return clientRevision; + } + + public void download() throws InterruptedException, ExecutionException, FileNotFoundException + { + FileResult result = requestFile(255, 255).get(); + ByteBuf buffer = Unpooled.wrappedBuffer(result.getContents()); + + int indexCount = result.getContents().length / 8; + + for (int i = 0; i < indexCount; ++i) + { + int crc = buffer.readInt(); + int revision = buffer.readInt(); + + if (i == 5) + continue; // XXX maps are xtea encrypted + + Index index = store.findIndex(i); + + if (index == null) + { + logger.info("Index {} does not exist, creating", i); + } + else if (index.getRevision() != revision) + { + logger.info("Index {} has the wrong revision (our revision {}, their revision {}", index.getId(), index.getRevision(), revision); + } + else + { + logger.info("Index {} is up to date", index.getId()); + continue; + } + + logger.info("Downloading index {}", i); + + FileResult indexFileResult = requestFile(255, i).get(); + + logger.info("Downloaded index {}", i); + + if (indexFileResult.getCrc() != crc) + { + logger.info("Corrupted download for index {}", i); + continue; + } + + Index oldIndex = null; + if (index != null) + { + store.removeIndex(index); + + oldIndex = index; + } + + index = store.addIndex(i); + + index.readIndexData(indexFileResult.getContents()); + index.setCrc(crc); + + logger.info("Index {} has {} archives", i, index.getArchives().size()); + + for (Archive archive : index.getArchives()) + { + Archive oldArchive = oldIndex != null ? oldIndex.getArchive(archive.getArchiveId()) : null; + + if (oldArchive == null || oldArchive.getRevision() != archive.getRevision()) + { + logger.info("Archive {} in index {} is out of date, downloading", archive.getArchiveId(), index.getId()); + + FileResult archiveFileResult = requestFile(index.getId(), archive.getArchiveId()).get(); + + byte[] contents = archiveFileResult.getContents(); + + archive.loadContents(contents); + archive.setCompression(archiveFileResult.getCompression()); + } + else + { + logger.info("Active {} in index {} is up to date", archive.getArchiveId(), index.getId()); + + // copy existing contents, this is sort of hackish. + byte[] contents = oldArchive.saveContents(); + archive.loadContents(contents); + archive.setCompression(oldArchive.getCompression()); + } + } + + try + { + store.save(); // save up to this point to disk + // XXX if this save takes too long, server closes the connection + } + catch (IOException ex) + { + logger.warn("unable to save cache", ex); + } + + } + } + + public synchronized CompletableFuture requestFile(int index, int fileId) + { + ByteBuf buf = Unpooled.buffer(4); + FileRequest request = new FileRequest(index, fileId); + CompletableFuture future = new CompletableFuture<>(); + PendingFileRequest pf = new PendingFileRequest(request, future); + + buf.writeByte(request.getIndex() == 255 ? 1 : 0); + int hash = pf.computeHash(); + buf.writeByte(hash >> 16); + buf.writeByte(hash >> 8); + buf.writeByte(hash); + + logger.trace("Sending request for {}/{}", index, fileId); + + channel.writeAndFlush(buf); + + requests.add(pf); + + return future; + } + + private PendingFileRequest findRequest(int index, int file) + { + for (PendingFileRequest pr : requests) + { + if (pr.getRequest().getIndex() == index && pr.getRequest().getFile() == file) + { + return pr; + } + } + return null; + } + + protected synchronized void onFileFinish(int index, int file, DataFileReadResult dresult) + { + PendingFileRequest pr = findRequest(index, file); + + if (pr == null) + { + logger.warn("File download {}/{} with no pending request", index, file); + return; + } + + requests.remove(pr); + + FileResult result = new FileResult(index, file, dresult.data, dresult.revision, dresult.crc, dresult.whirlpool, dresult.compression); + + logger.debug("File download finished for index {} file {}, length {}", index, file, result.getContents().length); + + pr.getFuture().complete(result); + } +} diff --git a/cache/src/main/java/net/runelite/cache/downloader/CacheClientHandler.java b/cache/src/main/java/net/runelite/cache/downloader/CacheClientHandler.java new file mode 100644 index 0000000000..4a7750863e --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/downloader/CacheClientHandler.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2016, 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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.downloader; + +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.downloader.requests.ConnectionInfo; +import net.runelite.cache.downloader.requests.HelloHandshake; +import net.runelite.cache.fs.DataFile; +import net.runelite.cache.fs.DataFileReadResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CacheClientHandler extends ChannelInboundHandlerAdapter +{ + private static final Logger logger = LoggerFactory.getLogger(CacheClientHandler.class); + + private CacheClient client; + private ClientState state; + private ByteBuf buffer = Unpooled.buffer(512); + + public CacheClientHandler(CacheClient client) + { + this.client = client; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) + { + HelloHandshake msg = new HelloHandshake(); + msg.setRevision(client.getClientRevision()); + + ByteBuf message = Unpooled.buffer(5); + message.writeByte(msg.getVersion()); // handshake type + message.writeInt(msg.getRevision()); // client revision + + ctx.writeAndFlush(message); + + logger.info("Sent handshake with revision {}", msg.getRevision()); + + state = ClientState.HANDSHAKING; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) + { + ByteBuf inbuf = (ByteBuf) msg; + buffer.writeBytes(inbuf); + + 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); + + ctx.close(); + return; + } + + ConnectionInfo cinfo = new ConnectionInfo(); + ByteBuf outbuf = Unpooled.buffer(4); + outbuf.writeByte(cinfo.getVar1()); + outbuf.writeByte(cinfo.getVar2() >> 16); + outbuf.writeByte(cinfo.getVar2() >> 8); + outbuf.writeByte(cinfo.getVar2()); + + ctx.writeAndFlush(outbuf); + state = ClientState.CONNECTED; + } + else if (state == ClientState.CONNECTED) + { + ByteBuf copy = buffer.slice(); + + int index = copy.readUnsignedByte(); + int file = copy.readShort(); + // 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 + ;//+ (index != 255 ? 2 : 0); // for the revision + + 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; + } + + 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()); + buffer.clear(); + + DataFileReadResult result = DataFile.decompress(compressedData); + + client.onFileFinish(index, file, result); + } + + buffer.discardReadBytes(); + ReferenceCountUtil.release(msg); + } + + /** 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 channelReadComplete(ChannelHandlerContext ctx) + { + ctx.flush(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) + { + // Close the connection when an exception is raised. + cause.printStackTrace(); + ctx.close(); + } +} diff --git a/cache/src/main/java/net/runelite/cache/downloader/ClientState.java b/cache/src/main/java/net/runelite/cache/downloader/ClientState.java new file mode 100644 index 0000000000..39328b0f30 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/downloader/ClientState.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016, 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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.downloader; + +public enum ClientState +{ + HANDSHAKING, + CONNECTED +} diff --git a/cache/src/main/java/net/runelite/cache/downloader/FileResult.java b/cache/src/main/java/net/runelite/cache/downloader/FileResult.java new file mode 100644 index 0000000000..b1d16e9c86 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/downloader/FileResult.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016, 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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.downloader; + +public class FileResult +{ + private final int index; + private final int fileId; + private final byte[] contents; + private final int revision; + private final int crc; + private final byte[] whirlpool; + private final int compression; // compression method used by archive data + + public FileResult(int index, int fileId, byte[] contents, int revision, int crc, byte[] whirlpool, int compression) + { + this.index = index; + this.fileId = fileId; + this.contents = contents; + this.revision = revision; + this.crc = crc; + this.whirlpool = whirlpool; + this.compression = compression; + } + + public int getIndex() + { + return index; + } + + public int getFileId() + { + return fileId; + } + + public byte[] getContents() + { + return contents; + } + + public int getRevision() + { + return revision; + } + + public int getCrc() + { + return crc; + } + + public byte[] getWhirlpool() + { + return whirlpool; + } + + public int getCompression() + { + return compression; + } +} diff --git a/cache/src/main/java/net/runelite/cache/downloader/PendingFileRequest.java b/cache/src/main/java/net/runelite/cache/downloader/PendingFileRequest.java new file mode 100644 index 0000000000..04cbc16c05 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/downloader/PendingFileRequest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016, 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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.downloader; + +import java.util.concurrent.CompletableFuture; +import net.runelite.cache.downloader.requests.FileRequest; + +public class PendingFileRequest +{ + private final FileRequest request; + private final CompletableFuture future; + + public PendingFileRequest(FileRequest request, CompletableFuture future) + { + this.request = request; + this.future = future; + } + + public FileRequest getRequest() + { + return request; + } + + public CompletableFuture getFuture() + { + return future; + } + + public int computeHash() + { + return (request.getIndex() << 16) | request.getFile(); + } +} diff --git a/cache/src/main/java/net/runelite/cache/downloader/requests/ConnectionInfo.java b/cache/src/main/java/net/runelite/cache/downloader/requests/ConnectionInfo.java new file mode 100644 index 0000000000..8ec5bf7bee --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/downloader/requests/ConnectionInfo.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016, 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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.downloader.requests; + +public class ConnectionInfo +{ + private byte var1 = 3; // I don't know what this is + private int var2; + + public byte getVar1() + { + return var1; + } + + public void setVar1(byte var1) + { + this.var1 = var1; + } + + public int getVar2() + { + return var2; + } + + public void setVar2(int var2) + { + this.var2 = var2; + } +} diff --git a/cache/src/main/java/net/runelite/cache/downloader/requests/FileRequest.java b/cache/src/main/java/net/runelite/cache/downloader/requests/FileRequest.java new file mode 100644 index 0000000000..ded1e140fc --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/downloader/requests/FileRequest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016, 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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.downloader.requests; + +public class FileRequest +{ + private final int index; + private final int file; + + public FileRequest(int index, int file) + { + this.index = index; + this.file = file; + } + + public int getIndex() + { + return index; + } + + public int getFile() + { + return file; + } +} diff --git a/cache/src/main/java/net/runelite/cache/downloader/requests/HelloHandshake.java b/cache/src/main/java/net/runelite/cache/downloader/requests/HelloHandshake.java new file mode 100644 index 0000000000..bab5327cd1 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/downloader/requests/HelloHandshake.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016, 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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.downloader.requests; + +public class HelloHandshake +{ + public static final int RESPONSE_OK = 0; + public static final int RESPONSE_OUTDATED = 6; + + private byte version = 15; // handshake type + private int revision; + + public int getRevision() + { + return revision; + } + + public void setRevision(int revision) + { + this.revision = revision; + } + public byte getVersion() + { + return version; + } + + public void setVersion(byte version) + { + this.version = version; + } + +} diff --git a/cache/src/main/java/net/runelite/cache/fs/Index.java b/cache/src/main/java/net/runelite/cache/fs/Index.java index 55063bf821..cf6fa0f431 100644 --- a/cache/src/main/java/net/runelite/cache/fs/Index.java +++ b/cache/src/main/java/net/runelite/cache/fs/Index.java @@ -123,11 +123,21 @@ public class Index implements Closeable return crc; } + public void setCrc(int crc) + { + this.crc = crc; + } + public byte[] getWhirlpool() { return whirlpool; } + public void setWhirlpool(byte[] whirlpool) + { + this.whirlpool = whirlpool; + } + public IndexFile getIndex() { return index; diff --git a/cache/src/test/java/net/runelite/cache/downloader/CacheClientTest.java b/cache/src/test/java/net/runelite/cache/downloader/CacheClientTest.java new file mode 100644 index 0000000000..60e6f9c0b5 --- /dev/null +++ b/cache/src/test/java/net/runelite/cache/downloader/CacheClientTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2016, 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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.downloader; + +import java.io.File; +import net.runelite.cache.fs.Store; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.impl.SimpleLogger; + +public class CacheClientTest +{ + @Before + public void before() + { + System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "TRACE"); + } + + @Test + @Ignore + public void test() throws Exception + { + Store store = new Store(new File("d:/temp")); + store.load(); + + CacheClient c = new CacheClient(store); + c.connect(); + +// c.requestFile(0, 205).get(); +// c.requestFile(3, 278).get(); +// c.requestFile(3, 127).get(); +// c.requestFile(0, 1210).get(); + +// c.requestFile(255, 255).get(); +// c.requestFile(255, 2).get(); + +// c.requestFile(4, 2047); + +// c.requestFile(6, 546); + +// c.requestFile(255, 6).get(); + + c.download(); + + c.stop(); + + store.save(); + } +}