cache: split cache client and server into their own projects

Split handshake and update protocol also into their own projects
This commit is contained in:
Adam
2017-12-28 21:13:18 -05:00
parent c4bee1127c
commit 82d277a8a5
59 changed files with 1114 additions and 391 deletions

71
cache-client/pom.xml Normal file
View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.runelite</groupId>
<artifactId>runelite-parent</artifactId>
<version>1.2.11-SNAPSHOT</version>
</parent>
<groupId>net.runelite</groupId>
<artifactId>cache-client</artifactId>
<name>Cache Client</name>
<dependencies>
<dependency>
<groupId>net.runelite</groupId>
<artifactId>cache</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>net.runelite</groupId>
<artifactId>protocol</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.runelite</groupId>
<artifactId>cache</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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.protocol.api.update.ArchiveResponsePacket;
public class ArchiveResponseHandler extends SimpleChannelInboundHandler<ArchiveResponsePacket>
{
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());
}
}

View File

@@ -0,0 +1,454 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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 com.google.common.base.Stopwatch;
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 java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import net.runelite.cache.fs.Archive;
import net.runelite.cache.fs.Index;
import net.runelite.cache.fs.Storage;
import net.runelite.cache.fs.Store;
import net.runelite.cache.index.ArchiveData;
import net.runelite.cache.index.IndexData;
import net.runelite.protocol.update.decoders.HandshakeResponseDecoder;
import net.runelite.protocol.update.encoders.ArchiveRequestEncoder;
import net.runelite.protocol.update.encoders.EncryptionEncoder;
import net.runelite.protocol.api.update.ArchiveRequestPacket;
import net.runelite.protocol.api.login.HandshakeResponseType;
import net.runelite.cache.util.Crc32;
import net.runelite.protocol.api.handshake.UpdateHandshakePacket;
import net.runelite.protocol.handshake.UpdateHandshakeEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CacheClient implements AutoCloseable
{
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 MAX_REQUESTS = 19; // too many and the server closes the conncetion
private final Store store; // store cache will be written to
private final String host;
private final int clientRevision;
private DownloadWatcher watcher;
private ClientState state;
private final EventLoopGroup group = new NioEventLoopGroup(1);
private Channel channel;
private CompletableFuture<HandshakeResponseType> handshakeFuture;
private final Queue<PendingFileRequest> requests = new ArrayDeque<>();
public CacheClient(Store store, int clientRevision)
{
this(store, HOST, clientRevision);
}
public CacheClient(Store store, String host, int clientRevision)
{
this.store = store;
this.host = host;
this.clientRevision = clientRevision;
}
public CacheClient(Store store, int clientRevision, DownloadWatcher watcher)
{
this(store, clientRevision);
this.watcher = watcher;
}
public void connect()
{
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>()
{
@Override
public void initChannel(SocketChannel ch) throws Exception
{
ChannelPipeline p = ch.pipeline();
//p.addFirst(new HttpProxyHandler(new InetSocketAddress("runelite.net", 3128)));
p.addLast("decoder", new HandshakeResponseDecoder());
p.addLast(
new CacheClientHandler(),
new HandshakeResponseHandler(CacheClient.this),
new ArchiveResponseHandler(CacheClient.this)
);
p.addLast(
new UpdateHandshakeEncoder(),
new EncryptionEncoder(),
new ArchiveRequestEncoder()
);
}
});
// Start the client.
ChannelFuture f = b.connect(host, PORT).syncUninterruptibly();
channel = f.channel();
}
public CompletableFuture<HandshakeResponseType> handshake()
{
UpdateHandshakePacket handshakePacket = new UpdateHandshakePacket();
handshakePacket.setRevision(getClientRevision());
state = ClientState.HANDSHAKING;
assert handshakeFuture == null;
handshakeFuture = new CompletableFuture<>();
channel.writeAndFlush(handshakePacket);
logger.info("Sent handshake with revision {}", handshakePacket.getRevision());
return handshakeFuture;
}
@Override
public void close()
{
channel.close().syncUninterruptibly();
group.shutdownGracefully();
}
public int getClientRevision()
{
return clientRevision;
}
public ClientState getState()
{
return state;
}
void setState(ClientState state)
{
this.state = state;
}
CompletableFuture<HandshakeResponseType> getHandshakeFuture()
{
return handshakeFuture;
}
public List<IndexInfo> requestIndexes() throws IOException
{
logger.info("Requesting indexes");
FileResult result = requestFile(255, 255, true).join();
result.decompress(null);
ByteBuf buffer = Unpooled.wrappedBuffer(result.getContents());
int indexCount = result.getContents().length / 8;
List<IndexInfo> indexInfo = new ArrayList<>();
for (int i = 0; i < indexCount; ++i)
{
int crc = buffer.readInt();
int revision = buffer.readInt();
indexInfo.add(new IndexInfo(i, crc, revision));
}
return indexInfo;
}
public void download() throws IOException
{
Stopwatch stopwatch = Stopwatch.createStarted();
List<IndexInfo> indexes = requestIndexes();
for (IndexInfo indexInfo : indexes)
{
int i = indexInfo.getId();
int crc = indexInfo.getCrc();
int revision = indexInfo.getRevision();
Index index = store.findIndex(i);
if (index == null)
{
logger.info("Index {} does not exist, creating", i);
}
else if (index.getRevision() != revision)
{
if (revision < index.getRevision())
{
logger.warn("Index {} revision is going BACKWARDS! (our revision {}, their revision {})", index.getId(), index.getRevision(), revision);
}
else
{
logger.info("Index {} has the wrong revision (our revision {}, their revision {})", index.getId(), index.getRevision(), revision);
}
}
else
{
// despite the index being up to date, not everything
// can be downloaded, eg. for tracks.
logger.info("Index {} is up to date", index.getId());
}
logger.info("Downloading index {}", i);
FileResult indexFileResult = requestFile(255, i, true).join();
indexFileResult.decompress(null);
logger.info("Downloaded index {}", i);
if (indexFileResult.getCrc() != crc)
{
logger.warn("Corrupted download for index {}", i);
continue;
}
IndexData indexData = new IndexData();
indexData.load(indexFileResult.getContents());
if (index == null)
{
index = store.addIndex(i);
}
// update index settings
index.setProtocol(indexData.getProtocol());
index.setNamed(indexData.isNamed());
index.setCrc(crc);
index.setRevision(revision);
logger.info("Index {} has {} archives", i, indexData.getArchives().length);
for (ArchiveData ad : indexData.getArchives())
{
Archive existing = index.getArchive(ad.getId());
if (existing != null && existing.getRevision() == ad.getRevision()
&& existing.getCrc() == ad.getCrc()
&& existing.getNameHash() == ad.getNameHash())
{
logger.debug("Archive {}/{} in index {} is up to date",
ad.getId(), indexData.getArchives().length, index.getId());
continue;
}
if (existing == null)
{
logger.info("Archive {}/{} in index {} is out of date, downloading",
ad.getId(), indexData.getArchives().length, index.getId());
}
else if (ad.getRevision() < existing.getRevision())
{
logger.warn("Archive {}/{} in index {} revision is going BACKWARDS! (our revision {}, their revision {})",
ad.getId(), indexData.getArchives().length, index.getId(),
existing.getRevision(), ad.getRevision());
}
else
{
logger.info("Archive {}/{} in index {} is out of date, downloading. " +
"revision: ours: {} theirs: {}, crc: ours: {} theirs {}, name: ours {} theirs {}",
ad.getId(), indexData.getArchives().length, index.getId(),
existing.getRevision(), ad.getRevision(),
existing.getCrc(), ad.getCrc(),
existing.getNameHash(), ad.getNameHash());
}
final Archive archive = existing == null
? index.addArchive(ad.getId())
: existing;
archive.setRevision(ad.getRevision());
archive.setCrc(ad.getCrc());
archive.setNameHash(ad.getNameHash());
// Add files
archive.setFileData(ad.getFiles());
CompletableFuture<FileResult> future = requestFile(index.getId(), ad.getId(), false);
future.handle((fr, ex) ->
{
byte[] data = fr.getCompressedData();
Crc32 crc32 = new Crc32();
crc32.update(data, 0, data.length);
int hash = crc32.getHash();
if (hash != archive.getCrc())
{
logger.warn("crc mismatch on downloaded archive {}/{}: {} != {}",
archive.getIndex().getId(), archive.getArchiveId(),
hash, archive.getCrc());
throw new RuntimeException("crc mismatch");
}
if (watcher != null)
{
watcher.downloadComplete(archive, data);
}
else
{
try
{
Storage storage = store.getStorage();
storage.saveArchive(archive, data);
}
catch (IOException ex1)
{
logger.warn("unable to save archive data", ex1);
}
}
return null;
});
}
}
// flush any pending requests
channel.flush();
while (!requests.isEmpty())
{
// wait for pending requests
synchronized (this)
{
try
{
wait();
}
catch (InterruptedException ex)
{
logger.warn(null, ex);
}
}
}
stopwatch.stop();
logger.info("Download completed in {}", stopwatch);
}
private synchronized CompletableFuture<FileResult> requestFile(int index, int fileId, boolean flush)
{
if (state != ClientState.CONNECTED)
{
throw new IllegalStateException("Can't request files until connected!");
}
if (!flush)
{
while (requests.size() >= MAX_REQUESTS)
{
channel.flush();
try
{
wait();
}
catch (InterruptedException ex)
{
logger.warn("interrupted while waiting for requests", ex);
}
}
}
ArchiveRequestPacket archiveRequest = new ArchiveRequestPacket();
archiveRequest.setPriority(false);
archiveRequest.setIndex(index);
archiveRequest.setArchive(fileId);
CompletableFuture<FileResult> future = new CompletableFuture<>();
PendingFileRequest pf = new PendingFileRequest(index,
fileId, future);
logger.trace("Sending request for {}/{}", index, fileId);
requests.add(pf);
if (!flush)
{
channel.write(archiveRequest);
}
else
{
channel.writeAndFlush(archiveRequest);
}
return future;
}
private PendingFileRequest findRequest(int index, int file)
{
for (PendingFileRequest pr : requests)
{
if (pr.getIndex() == index && pr.getArchive() == file)
{
return pr;
}
}
return null;
}
protected synchronized void onFileFinish(int index, int file, byte[] compressedData)
{
PendingFileRequest pr = findRequest(index, file);
if (pr == null)
{
logger.warn("File download {}/{} with no pending request", index, file);
return;
}
requests.remove(pr);
notify();
FileResult result = new FileResult(index, file, compressedData);
logger.debug("File download finished for index {} file {}, length {}", index, file, compressedData.length);
pr.getFuture().complete(result);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CacheClientHandler extends ChannelInboundHandlerAdapter
{
private static final Logger logger = LoggerFactory.getLogger(CacheClientHandler.class);
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception
{
logger.warn("Channel has gone inactive");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
{
// Close the connection when an exception is raised.
logger.warn(null, cause);
ctx.close();
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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;
public enum ClientState
{
HANDSHAKING,
CONNECTED
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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 net.runelite.cache.fs.Archive;
@FunctionalInterface
public interface DownloadWatcher
{
void downloadComplete(Archive archive, byte[] data);
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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 java.io.IOException;
import net.runelite.cache.fs.Container;
public class FileResult
{
private final int index;
private final int fileId;
private final byte[] compressedData;
private byte[] contents;
private int revision;
private int crc;
private int compression; // compression method used by archive data
public FileResult(int index, int fileId, byte[] compressedData)
{
this.index = index;
this.fileId = fileId;
this.compressedData = compressedData;
}
public int getIndex()
{
return index;
}
public int getFileId()
{
return fileId;
}
public byte[] getCompressedData()
{
return compressedData;
}
public void decompress(int[] keys) throws IOException
{
Container res = Container.decompress(compressedData, keys);
contents = res.data;
revision = res.revision;
crc = res.crc;
compression = res.compression;
}
public byte[] getContents()
{
return contents;
}
public int getRevision()
{
return revision;
}
public int getCrc()
{
return crc;
}
public int getCompression()
{
return compression;
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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.protocol.update.decoders.ArchiveResponseDecoder;
import net.runelite.protocol.api.update.EncryptionPacket;
import net.runelite.protocol.api.handshake.HandshakeResponsePacket;
import net.runelite.protocol.api.login.HandshakeResponseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HandshakeResponseHandler extends SimpleChannelInboundHandler<HandshakeResponsePacket>
{
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<HandshakeResponseType> 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());
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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;
public class IndexInfo
{
private final int id;
private final int crc;
private final int revision;
public IndexInfo(int id, int crc, int revision)
{
this.id = id;
this.crc = crc;
this.revision = revision;
}
@Override
public String toString()
{
return "IndexInfo{" + "id=" + id + ", crc=" + crc + ", revision=" + revision + '}';
}
@Override
public int hashCode()
{
int hash = 5;
hash = 71 * hash + this.id;
hash = 71 * hash + this.crc;
hash = 71 * hash + this.revision;
return hash;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final IndexInfo other = (IndexInfo) obj;
if (this.id != other.id)
{
return false;
}
if (this.crc != other.crc)
{
return false;
}
if (this.revision != other.revision)
{
return false;
}
return true;
}
public int getId()
{
return id;
}
public int getCrc()
{
return crc;
}
public int getRevision()
{
return revision;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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 java.util.concurrent.CompletableFuture;
public class PendingFileRequest
{
private final int index;
private final int archive;
private final CompletableFuture<FileResult> future;
public PendingFileRequest(int index, int archive, CompletableFuture<FileResult> future)
{
this.index = index;
this.archive = archive;
this.future = future;
}
public int getIndex()
{
return index;
}
public int getArchive()
{
return archive;
}
public CompletableFuture<FileResult> getFuture()
{
return future;
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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 java.io.File;
import java.util.concurrent.CompletableFuture;
import net.runelite.cache.CacheProperties;
import net.runelite.cache.fs.Store;
import net.runelite.protocol.api.login.HandshakeResponseType;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.impl.SimpleLogger;
public class CacheClientTest
{
private static final Logger logger = LoggerFactory.getLogger(CacheClientTest.class);
@Before
public void before()
{
System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "TRACE");
}
@Test
@Ignore
public void test() throws Exception
{
try (Store store = new Store(new File("D:\\rs\\07\\temp\\cache")))
{
store.load();
CacheClient c = new CacheClient(store, CacheProperties.getRsVersion());
c.connect();
CompletableFuture<HandshakeResponseType> handshake = c.handshake();
HandshakeResponseType result = handshake.get();
logger.info("Handshake result: {}", result);
Assert.assertEquals(HandshakeResponseType.RESPONSE_OK, result);
c.download();
c.close();
store.save();
}
}
}