Add raw map dumper, and support for xtea encryption. Split datafile reading from deccryption/decompressing, allow cache client to simply save the encrypted+compressed data, too.

This commit is contained in:
Adam
2016-06-18 18:00:41 -04:00
parent 811710450c
commit aeffd8aa67
15 changed files with 443 additions and 148 deletions

View File

@@ -37,7 +37,7 @@ public enum IndexType
CONFIGS(2), CONFIGS(2),
INTERFACES(3), INTERFACES(3),
SOUNDEFFECTS(4), SOUNDEFFECTS(4),
LANDSCAPES(5), MAPS(5),
TRACK1(6), TRACK1(6),
MODELS(7), MODELS(7),
SPRITES(8), SPRITES(8),

View File

@@ -52,7 +52,6 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import net.runelite.cache.downloader.requests.FileRequest; import net.runelite.cache.downloader.requests.FileRequest;
import net.runelite.cache.fs.Archive; import net.runelite.cache.fs.Archive;
import net.runelite.cache.fs.DataFileReadResult;
import net.runelite.cache.fs.Index; import net.runelite.cache.fs.Index;
import net.runelite.cache.fs.Store; import net.runelite.cache.fs.Store;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -132,6 +131,8 @@ public class CacheClient
public void download() throws InterruptedException, ExecutionException, FileNotFoundException public void download() throws InterruptedException, ExecutionException, FileNotFoundException
{ {
FileResult result = requestFile(255, 255).get(); FileResult result = requestFile(255, 255).get();
result.decompress(null);
ByteBuf buffer = Unpooled.wrappedBuffer(result.getContents()); ByteBuf buffer = Unpooled.wrappedBuffer(result.getContents());
int indexCount = result.getContents().length / 8; int indexCount = result.getContents().length / 8;
@@ -141,9 +142,6 @@ public class CacheClient
int crc = buffer.readInt(); int crc = buffer.readInt();
int revision = buffer.readInt(); int revision = buffer.readInt();
if (i == 5)
continue; // XXX maps are xtea encrypted
Index index = store.findIndex(i); Index index = store.findIndex(i);
if (index == null) if (index == null)
@@ -163,6 +161,7 @@ public class CacheClient
logger.info("Downloading index {}", i); logger.info("Downloading index {}", i);
FileResult indexFileResult = requestFile(255, i).get(); FileResult indexFileResult = requestFile(255, i).get();
indexFileResult.decompress(null);
logger.info("Downloaded index {}", i); logger.info("Downloaded index {}", i);
@@ -196,11 +195,9 @@ public class CacheClient
logger.info("Archive {} in index {} is out of date, downloading", archive.getArchiveId(), index.getId()); logger.info("Archive {} in index {} is out of date, downloading", archive.getArchiveId(), index.getId());
FileResult archiveFileResult = requestFile(index.getId(), archive.getArchiveId()).get(); FileResult archiveFileResult = requestFile(index.getId(), archive.getArchiveId()).get();
byte[] compressedContents = archiveFileResult.getCompressedData();
byte[] contents = archiveFileResult.getContents(); archive.setData(compressedContents);
archive.loadContents(contents);
archive.setCompression(archiveFileResult.getCompression());
} }
else else
{ {
@@ -258,7 +255,7 @@ public class CacheClient
return null; return null;
} }
protected synchronized void onFileFinish(int index, int file, DataFileReadResult dresult) protected synchronized void onFileFinish(int index, int file, byte[] compressedData)
{ {
PendingFileRequest pr = findRequest(index, file); PendingFileRequest pr = findRequest(index, file);
@@ -270,9 +267,9 @@ public class CacheClient
requests.remove(pr); requests.remove(pr);
FileResult result = new FileResult(index, file, dresult.data, dresult.revision, dresult.crc, dresult.whirlpool, dresult.compression); FileResult result = new FileResult(index, file, compressedData);
logger.debug("File download finished for index {} file {}, length {}", index, file, result.getContents().length); logger.debug("File download finished for index {} file {}, length {}", index, file, compressedData.length);
pr.getFuture().complete(result); pr.getFuture().complete(result);
} }

View File

@@ -37,8 +37,6 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCountUtil;
import net.runelite.cache.downloader.requests.ConnectionInfo; import net.runelite.cache.downloader.requests.ConnectionInfo;
import net.runelite.cache.downloader.requests.HelloHandshake; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -166,9 +164,7 @@ public class CacheClientHandler extends ChannelInboundHandlerAdapter
buffer.readableBytes()); buffer.readableBytes());
buffer.clear(); buffer.clear();
DataFileReadResult result = DataFile.decompress(compressedData); client.onFileFinish(index, file, compressedData);
client.onFileFinish(index, file, result);
} }
buffer.discardReadBytes(); buffer.discardReadBytes();

View File

@@ -30,25 +30,26 @@
package net.runelite.cache.downloader; package net.runelite.cache.downloader;
import net.runelite.cache.fs.DataFile;
import net.runelite.cache.fs.DataFileReadResult;
public class FileResult public class FileResult
{ {
private final int index; private final int index;
private final int fileId; private final int fileId;
private final byte[] contents; private final byte[] compressedData;
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) private byte[] contents;
private int revision;
private int crc;
private byte[] whirlpool;
private int compression; // compression method used by archive data
public FileResult(int index, int fileId, byte[] compressedData)
{ {
this.index = index; this.index = index;
this.fileId = fileId; this.fileId = fileId;
this.contents = contents; this.compressedData = compressedData;
this.revision = revision;
this.crc = crc;
this.whirlpool = whirlpool;
this.compression = compression;
} }
public int getIndex() public int getIndex()
@@ -61,6 +62,22 @@ public class FileResult
return fileId; return fileId;
} }
public byte[] getCompressedData()
{
return compressedData;
}
public void decompress(int[] keys)
{
DataFileReadResult res = DataFile.decompress(compressedData, keys);
contents = res.data;
revision = res.revision;
crc = res.crc;
whirlpool = res.whirlpool;
compression = res.compression;
}
public byte[] getContents() public byte[] getContents()
{ {
return contents; return contents;

View File

@@ -31,20 +31,29 @@
package net.runelite.cache.fs; package net.runelite.cache.fs;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import net.runelite.cache.io.InputStream; import net.runelite.cache.io.InputStream;
import net.runelite.cache.io.OutputStream; import net.runelite.cache.io.OutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Archive public class Archive
{ {
private static final Logger logger = LoggerFactory.getLogger(Archive.class);
private Index index; // member of this index private Index index; // member of this index
private byte[] data; // raw data from the datafile, compressed/encrypted
private int archiveId; private int archiveId;
private int nameHash; private int nameHash;
private byte[] whirlpool; private byte[] whirlpool;
private int crc; private int crc;
private int revision; private int revision;
private int compression; private int compression;
private List<File> files = new ArrayList<>(); private List<File> files = new ArrayList<>();
public Archive(Index index, int id) public Archive(Index index, int id)
@@ -95,6 +104,16 @@ public class Archive
return true; return true;
} }
public byte[] getData()
{
return data;
}
public void setData(byte[] data)
{
this.data = data;
}
public File addFile(int id) public File addFile(int id)
{ {
File file = new File(this, id); File file = new File(this, id);
@@ -115,6 +134,40 @@ public class Archive
} }
} }
public void decompressAndLoad(int[] keys)
{
byte[] encryptedData = this.getData();
DataFileReadResult res = DataFile.decompress(encryptedData, keys);
if (res == null)
{
logger.warn("Unable to decrypt archive {}", this);
return;
}
byte[] decompressedData = res.data;
if (this.crc != res.crc)
{
logger.warn("crc mismatch for archive {}", this);
}
if (this.getWhirlpool() != null && !Arrays.equals(this.getWhirlpool(), res.whirlpool))
{
logger.warn("whirlpool mismatch for archive {}", this);
}
if (this.getRevision() != res.revision)
{
logger.warn("revision mismatch for archive {}", this);
}
setCompression(res.compression);
loadContents(decompressedData);
this.setData(null); // now that we've loaded it, clean it so it doesn't get written back
}
public void loadContents(byte[] data) public void loadContents(byte[] data)
{ {
if (this.getFiles().size() == 1) if (this.getFiles().size() == 1)
@@ -181,6 +234,11 @@ public class Archive
public byte[] saveContents() public byte[] saveContents()
{ {
if (data != null)
{
return data;
}
OutputStream stream = new OutputStream(); OutputStream stream = new OutputStream();
int filesCount = this.getFiles().size(); int filesCount = this.getFiles().size();

View File

@@ -36,12 +36,18 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import net.runelite.cache.util.BZip2; import net.runelite.cache.util.BZip2;
import net.runelite.cache.io.InputStream; import net.runelite.cache.io.InputStream;
import net.runelite.cache.io.OutputStream; import net.runelite.cache.io.OutputStream;
import net.runelite.cache.util.CRC32HGenerator; import net.runelite.cache.util.Crc32;
import net.runelite.cache.util.GZip; import net.runelite.cache.util.GZip;
import net.runelite.cache.util.Whirlpool; import net.runelite.cache.util.Whirlpool;
import net.runelite.cache.util.Xtea;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -71,14 +77,14 @@ public class DataFile implements Closeable
/** /**
* *
* @param indexId * @param indexId expected index of archive of contents being read
* @param archiveId * @param archiveId expected archive of contents being read
* @param sector sector to start reading at * @param sector sector to start reading at
* @param size expected size of file * @param size size of file
* @return * @return
* @throws IOException * @throws IOException
*/ */
public synchronized DataFileReadResult read(int indexId, int archiveId, int sector, int size) throws IOException public synchronized byte[] read(int indexId, int archiveId, int sector, int size) throws IOException
{ {
if (sector <= 0L || dat.length() / SECTOR_SIZE < (long) sector) if (sector <= 0L || dat.length() / SECTOR_SIZE < (long) sector)
{ {
@@ -94,7 +100,7 @@ public class DataFile implements Closeable
{ {
if (sector == 0) if (sector == 0)
{ {
logger.warn("sector == 0"); logger.warn("Unexpected end of file");
return null; return null;
} }
@@ -105,7 +111,7 @@ public class DataFile implements Closeable
int currentIndex; int currentIndex;
int currentPart; int currentPart;
int currentArchive; int currentArchive;
if (0xFFFF < archiveId) if (archiveId > 0xFFFF)
{ {
headerSize = 10; headerSize = 10;
if (dataBlockSize > SECTOR_SIZE - headerSize) if (dataBlockSize > SECTOR_SIZE - headerSize)
@@ -116,7 +122,7 @@ public class DataFile implements Closeable
int i = dat.read(this.readCachedBuffer, 0, headerSize + dataBlockSize); int i = dat.read(this.readCachedBuffer, 0, headerSize + dataBlockSize);
if (i != headerSize + dataBlockSize) if (i != headerSize + dataBlockSize)
{ {
logger.warn("short read"); logger.warn("Short read when reading file data for {}/{}", indexId, archiveId);
return null; return null;
} }
@@ -168,21 +174,15 @@ public class DataFile implements Closeable
} }
buffer.flip(); buffer.flip();
return buffer.array();
//XTEA decrypt here?
return decompress(buffer.array());
} }
public synchronized DataFileWriteResult write(int indexId, int archiveId, ByteBuffer data, int compression, int revision) throws IOException public synchronized DataFileWriteResult write(int indexId, int archiveId, byte[] compressedData, int revision) throws IOException
{ {
int sector; int sector;
int startSector; int startSector;
byte[] compressedData = this.compress(data.array(), compression, revision); ByteBuffer data = ByteBuffer.wrap(compressedData);
data = ByteBuffer.wrap(compressedData);
//XTEA encrypt here?
sector = (int) ((dat.length() + (long) (SECTOR_SIZE - 1)) / (long) SECTOR_SIZE); sector = (int) ((dat.length() + (long) (SECTOR_SIZE - 1)) / (long) SECTOR_SIZE);
if (sector == 0) if (sector == 0)
@@ -270,13 +270,17 @@ public class DataFile implements Closeable
DataFileWriteResult res = new DataFileWriteResult(); DataFileWriteResult res = new DataFileWriteResult();
res.sector = startSector; res.sector = startSector;
res.compressedLength = compressedData.length; res.compressedLength = compressedData.length;
int length = revision != -1 ? compressedData.length - 2 : compressedData.length; int length = revision != -1 ? compressedData.length - 2 : compressedData.length;
res.crc = CRC32HGenerator.getHash(compressedData, length); Crc32 crc32 = new Crc32();
crc32.update(compressedData, 0, length);
res.crc = crc32.getHash();
res.whirlpool = Whirlpool.getHash(compressedData, length); res.whirlpool = Whirlpool.getHash(compressedData, length);
return res; return res;
} }
public static DataFileReadResult decompress(byte[] b) public static DataFileReadResult decompress(byte[] b, int[] keys)
{ {
InputStream stream = new InputStream(b); InputStream stream = new InputStream(b);
@@ -285,29 +289,85 @@ public class DataFile implements Closeable
if (compressedLength < 0 || compressedLength > 1000000) if (compressedLength < 0 || compressedLength > 1000000)
throw new RuntimeException("Invalid data"); throw new RuntimeException("Invalid data");
Crc32 crc32 = new Crc32();
crc32.update(b, 0, 5); // compression + length
byte[] data; byte[] data;
int revision; int revision = -1;
switch (compression) switch (compression)
{ {
case CompressionType.NONE: case CompressionType.NONE:
data = new byte[compressedLength]; {
revision = checkRevision(stream, compressedLength); byte[] encryptedData = new byte[compressedLength];
stream.readBytes(data, 0, compressedLength); stream.readBytes(encryptedData, 0, compressedLength);
crc32.update(encryptedData, 0, compressedLength);
byte[] decryptedData = decrypt(encryptedData, encryptedData.length, keys);
if (stream.remaining() >= 2)
{
revision = stream.readUnsignedShort();
assert revision != -1;
}
data = decryptedData;
break; break;
}
case CompressionType.BZ2: case CompressionType.BZ2:
{ {
int length = stream.readInt(); byte[] encryptedData = new byte[compressedLength + 4];
revision = checkRevision(stream, compressedLength); stream.readBytes(encryptedData);
crc32.update(encryptedData, 0, encryptedData.length);
byte[] decryptedData = decrypt(encryptedData, encryptedData.length, keys);
if (stream.remaining() >= 2)
{
revision = stream.readUnsignedShort();
assert revision != -1;
}
stream = new InputStream(decryptedData);
int decompressedLength = stream.readInt();
data = BZip2.decompress(stream.getRemaining(), compressedLength); data = BZip2.decompress(stream.getRemaining(), compressedLength);
assert data.length == length;
if (data == null)
{
return null;
}
assert data.length == decompressedLength;
break; break;
} }
case CompressionType.GZ: case CompressionType.GZ:
{ {
int length = stream.readInt(); byte[] encryptedData = new byte[compressedLength + 4];
revision = checkRevision(stream, compressedLength); stream.readBytes(encryptedData);
crc32.update(encryptedData, 0, encryptedData.length);
byte[] decryptedData = decrypt(encryptedData, encryptedData.length, keys);
if (stream.remaining() >= 2)
{
revision = stream.readUnsignedShort();
assert revision != -1;
}
stream = new InputStream(decryptedData);
int decompressedLength = stream.readInt();
data = GZip.decompress(stream.getRemaining(), compressedLength); data = GZip.decompress(stream.getRemaining(), compressedLength);
assert data.length == length;
if (data == null)
{
return null;
}
assert data.length == decompressedLength;
break; break;
} }
default: default:
@@ -317,14 +377,14 @@ public class DataFile implements Closeable
DataFileReadResult res = new DataFileReadResult(); DataFileReadResult res = new DataFileReadResult();
res.data = data; res.data = data;
res.revision = revision; res.revision = revision;
int length = revision != -1 ? b.length - 2 : b.length; int length = revision != -1 ? b.length - 2 : b.length;;
res.crc = CRC32HGenerator.getHash(b, length); res.crc = crc32.getHash();
res.whirlpool = Whirlpool.getHash(b, length); res.whirlpool = Whirlpool.getHash(b, length);
res.compression = compression; res.compression = compression;
return res; return res;
} }
private byte[] compress(byte[] data, int compression, int revision) throws IOException public static byte[] compress(byte[] data, int compression, int revision, int[] keys) throws IOException
{ {
OutputStream stream = new OutputStream(); OutputStream stream = new OutputStream();
stream.writeByte(compression); stream.writeByte(compression);
@@ -333,16 +393,19 @@ public class DataFile implements Closeable
{ {
case CompressionType.NONE: case CompressionType.NONE:
compressedData = data; compressedData = data;
compressedData = encrypt(compressedData, compressedData.length, keys);
stream.writeInt(data.length); stream.writeInt(data.length);
break; break;
case CompressionType.BZ2: case CompressionType.BZ2:
compressedData = BZip2.compress(data); compressedData = BZip2.compress(data);
compressedData = encrypt(compressedData, compressedData.length, keys);
stream.writeInt(compressedData.length); stream.writeInt(compressedData.length);
stream.writeInt(data.length); stream.writeInt(data.length);
break; break;
case CompressionType.GZ: case CompressionType.GZ:
compressedData = GZip.compress(data); compressedData = GZip.compress(data);
compressedData = encrypt(compressedData, compressedData.length, keys);
stream.writeInt(compressedData.length); stream.writeInt(compressedData.length);
stream.writeInt(data.length); stream.writeInt(data.length);
@@ -375,4 +438,38 @@ public class DataFile implements Closeable
} }
return revision; return revision;
} }
private static byte[] decrypt(byte[] data, int length, int[] keys)
{
if (keys == null)
return data;
try
{
Xtea xtea = new Xtea(keys);
return xtea.decrypt(data, length);
}
catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex)
{
logger.warn("unable to xtea decrypt", ex);
return null;
}
}
private static byte[] encrypt(byte[] data, int length, int[] keys)
{
if (keys == null)
return data;
try
{
Xtea xtea = new Xtea(keys);
return xtea.encrypt(data, length);
}
catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex)
{
logger.warn("unable to xtea encrypt", ex);
return null;
}
}
} }

View File

@@ -32,7 +32,6 @@ package net.runelite.cache.fs;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -40,6 +39,7 @@ import java.util.Objects;
import net.runelite.cache.util.Djb2; import net.runelite.cache.util.Djb2;
import net.runelite.cache.io.InputStream; import net.runelite.cache.io.InputStream;
import net.runelite.cache.io.OutputStream; import net.runelite.cache.io.OutputStream;
import net.runelite.cache.util.XteaKeyManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -50,12 +50,16 @@ public class Index implements Closeable
private final Store store; private final Store store;
private final IndexFile index; private final IndexFile index;
private final int id; private final int id;
private XteaKeyManager xteaManager;
private int protocol = 7; private int protocol = 7;
private boolean named = true, usesWhirpool; private boolean named = true, usesWhirpool;
private int revision; private int revision;
private int crc; private int crc;
private byte[] whirlpool; private byte[] whirlpool;
private int compression; // compression method of this index's data in 255 private int compression; // compression method of this index's data in 255
private final List<Archive> archives = new ArrayList<>(); private final List<Archive> archives = new ArrayList<>();
public Index(Store store, IndexFile index, int id) public Index(Store store, IndexFile index, int id)
@@ -108,6 +112,16 @@ public class Index implements Closeable
return true; return true;
} }
public XteaKeyManager getXteaManager()
{
return xteaManager;
}
public void setXteaManager(XteaKeyManager xteaManager)
{
this.xteaManager = xteaManager;
}
public int getId() public int getId()
{ {
return id; return id;
@@ -178,7 +192,8 @@ public class Index implements Closeable
IndexFile index255 = store.getIndex255(); IndexFile index255 = store.getIndex255();
IndexEntry entry = index255.read(id); IndexEntry entry = index255.read(id);
DataFileReadResult res = dataFile.read(index255.getIndexFileId(), entry.getId(), entry.getSector(), entry.getLength()); byte[] indexData = dataFile.read(index255.getIndexFileId(), entry.getId(), entry.getSector(), entry.getLength());
DataFileReadResult res = DataFile.decompress(indexData, null);
byte[] data = res.data; byte[] data = res.data;
archives.clear(); archives.clear();
@@ -190,7 +205,7 @@ public class Index implements Closeable
this.compression = res.compression; this.compression = res.compression;
assert res.revision == -1; assert res.revision == -1;
this.loadFiles(); this.loadArchives();
} }
public void save() throws IOException public void save() throws IOException
@@ -202,7 +217,9 @@ public class Index implements Closeable
DataFile dataFile = store.getData(); DataFile dataFile = store.getData();
IndexFile index255 = store.getIndex255(); IndexFile index255 = store.getIndex255();
DataFileWriteResult res = dataFile.write(index255.getIndexFileId(), this.id, ByteBuffer.wrap(data), this.compression, -1); // index data revision is always -1 byte[] compressedData = DataFile.compress(data, this.compression, -1, null); // index data revision is always -1
DataFileWriteResult res = dataFile.write(index255.getIndexFileId(), this.id, compressedData, revision);
index255.write(new IndexEntry(index255, id, res.sector, res.compressedLength)); index255.write(new IndexEntry(index255, id, res.sector, res.compressedLength));
this.crc = res.crc; this.crc = res.crc;
@@ -300,41 +317,31 @@ public class Index implements Closeable
} }
} }
private void loadFiles() throws IOException private void loadArchives() throws IOException
{ {
// get data from index file // get data from index file
for (Archive a : archives) for (Archive a : new ArrayList<>(archives))
{ {
IndexEntry entry = this.index.read(a.getArchiveId()); IndexEntry entry = this.index.read(a.getArchiveId());
if (entry == null) if (entry == null)
{ {
logger.debug("can't read archive " + a.getArchiveId() + " from index " + this.id); logger.debug("can't read archive " + a.getArchiveId() + " from index " + this.id);
archives.remove(a); // is this the correct behavior?
continue; continue;
} }
assert this.index.getIndexFileId() == this.id; assert this.index.getIndexFileId() == this.id;
assert entry.getId() == a.getArchiveId(); assert entry.getId() == a.getArchiveId();
DataFileReadResult res = store.getData().read(this.id, entry.getId(), entry.getSector(), entry.getLength());
byte[] data = res.data;
if (a.getCrc() != res.crc) byte[] archiveData = store.getData().read(this.id, entry.getId(), entry.getSector(), entry.getLength());
a.setData(archiveData);
if (this.xteaManager != null)
{ {
logger.warn("crc mismatch for archive {}", a); continue; // can't decrypt this yet
} }
if (a.getWhirlpool() != null && !Arrays.equals(a.getWhirlpool(), res.whirlpool)) a.decompressAndLoad(null);
{
logger.warn("whirlpool mismatch for archive {}", a);
}
if (a.getRevision() != res.revision)
{
logger.warn("revision mismatch for archive {}", a);
}
a.setCompression(res.compression);
a.loadContents(data);
} }
} }
@@ -347,7 +354,9 @@ public class Index implements Closeable
assert this.index.getIndexFileId() == this.id; assert this.index.getIndexFileId() == this.id;
DataFile data = store.getData(); DataFile data = store.getData();
DataFileWriteResult res = data.write(this.id, a.getArchiveId(), ByteBuffer.wrap(fileData), a.getCompression(), a.getRevision()); byte[] compressedData = DataFile.compress(fileData, a.getCompression(), a.getRevision(), null);
DataFileWriteResult res = data.write(this.id, a.getArchiveId(), compressedData, a.getRevision());
this.index.write(new IndexEntry(this.index, a.getArchiveId(), res.sector, res.compressedLength)); this.index.write(new IndexEntry(this.index, a.getArchiveId(), res.sector, res.compressedLength));
a.setCrc(res.crc); a.setCrc(res.crc);

View File

@@ -122,7 +122,7 @@ public class IndexFile implements Closeable
int i = idx.read(buffer); int i = idx.read(buffer);
if (i != INDEX_ENTRY_LEN) if (i != INDEX_ENTRY_LEN)
{ {
logger.warn("short read for id {} on index {}: {}", id, indexFileId, i); logger.debug("short read for id {} on index {}: {}", id, indexFileId, i);
return null; return null;
} }

View File

@@ -38,6 +38,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import net.runelite.cache.IndexType; import net.runelite.cache.IndexType;
import net.runelite.cache.util.XteaKeyManager;
public class Store implements Closeable public class Store implements Closeable
{ {
@@ -60,6 +61,14 @@ public class Store implements Closeable
{ {
this.addIndex(i); this.addIndex(i);
} }
Index maps = this.findIndex(IndexType.MAPS.getNumber());
if (maps != null)
{
XteaKeyManager mapKeys = new XteaKeyManager();
mapKeys.loadKeys();
maps.setXteaManager(mapKeys);
}
} }
@Override @Override
@@ -98,7 +107,7 @@ public class Store implements Closeable
return true; return true;
} }
public Index addIndex(int id) throws FileNotFoundException public final Index addIndex(int id) throws FileNotFoundException
{ {
for (Index i : indexes) for (Index i : indexes)
if (i.getIndex().getIndexFileId() == id) if (i.getIndex().getIndexFileId() == id)
@@ -121,14 +130,7 @@ public class Store implements Closeable
public void load() throws IOException public void load() throws IOException
{ {
for (Index i : indexes) for (Index i : indexes)
{
int id = i.getIndex().getIndexFileId();
if (id == 5) // XXX maps, XTEA encrypted, can't decompress
continue;
if (id == 6 || id == 14)
continue; // XXX I get more Indexes than there is length of the index file for these
i.load(); i.load();
}
} }
public void save() throws IOException public void save() throws IOException
@@ -157,7 +159,7 @@ public class Store implements Closeable
return indexes.get(type.getNumber()); return indexes.get(type.getNumber());
} }
public Index findIndex(int id) public final Index findIndex(int id)
{ {
for (Index i : indexes) for (Index i : indexes)
if (i.getId() == id) if (i.getId() == id)

View File

@@ -51,6 +51,12 @@ public class InputStream extends java.io.InputStream
this.buffer = ByteBuffer.wrap(buffer); this.buffer = ByteBuffer.wrap(buffer);
} }
@Override
public String toString()
{
return "InputStream{" + "buffer=" + buffer + '}';
}
public int read24BitInt() public int read24BitInt()
{ {
return (this.readUnsignedByte() << 16) + (this.readUnsignedByte() << 8) + this.readUnsignedByte(); return (this.readUnsignedByte() << 16) + (this.readUnsignedByte() << 8) + this.readUnsignedByte();
@@ -78,6 +84,11 @@ public class InputStream extends java.io.InputStream
return buffer.limit(); return buffer.limit();
} }
public int remaining()
{
return buffer.remaining();
}
public byte readByte() public byte readByte()
{ {
return buffer.get(); return buffer.get();

View File

@@ -32,20 +32,17 @@ package net.runelite.cache.util;
import java.util.zip.CRC32; import java.util.zip.CRC32;
public final class CRC32HGenerator public class Crc32
{ {
public static final CRC32 CRC32Instance = new CRC32(); private final CRC32 crc32 = new CRC32();
public static synchronized int getHash(byte[] data, int len) public void update(byte[] data, int offset, int length)
{ {
CRC32Instance.update(data, 0, len); crc32.update(data, offset, length);
try }
{
return (int) CRC32Instance.getValue(); public int getHash()
} {
finally return (int) crc32.getValue();
{
CRC32Instance.reset();
}
} }
} }

View File

@@ -0,0 +1,79 @@
package net.runelite.cache;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import net.runelite.cache.fs.Archive;
import net.runelite.cache.fs.Index;
import net.runelite.cache.fs.Store;
import net.runelite.cache.util.XteaKeyManager;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MapDumperTest
{
private static final Logger logger = LoggerFactory.getLogger(MapDumperTest.class);
private static final int MAX_REGIONS = 32768;
@Rule
public TemporaryFolder folder = StoreLocation.getTemporaryFolder();
@Test
public void dump() throws IOException
{
File base = StoreLocation.LOCATION,
outDir = new java.io.File("d:/rs/07/cache/maps");//folder.newFolder();
try (Store store = new Store(base))
{
store.load();
Index index = store.getIndex(IndexType.MAPS);
XteaKeyManager keyManager = index.getXteaManager();
for (int i = 0; i < MAX_REGIONS; i++)
{
int[] keys = keyManager.getKeys(i);
int x = i >> 8;
int y = i & 0xFF;
Archive map = index.findArchiveByName("m" + x + "_" + y);
Archive land = index.findArchiveByName("l" + x + "_" + y);
assert (map == null) == (land == null);
if (map == null || land == null)
continue;
assert map.getFiles().size() == 1;
assert land.getFiles().size() == 1;
// maps aren't encrypted, but we don't load archive data of any archive in
// the maps index, so load it
map.decompressAndLoad(null);
byte[] data = map.getFiles().get(0).getContents();
Files.write(data, new File(outDir, "m" + x + "_" + y + ".dat"));
if (keys != null)
{
land.decompressAndLoad(keys);
data = land.getFiles().get(0).getContents();
if (data == null)
continue; // key is probably wrong
Files.write(data, new File(outDir, "l" + x + "_" + y + ".dat"));
}
}
}
}
}

View File

@@ -57,7 +57,7 @@ public class ModelDumperTest
@Test @Test
public void test() throws IOException public void test() throws IOException
{ {
java.io.File modelDir = folder.newFolder("models"); java.io.File modelDir = new java.io.File("d:/rs/07/cache/models");//folder.newFolder("models");
int count = 0; int count = 0;
try (Store store = new Store(StoreLocation.LOCATION)) try (Store store = new Store(StoreLocation.LOCATION))

View File

@@ -42,7 +42,7 @@ public class CacheClientTest
@Before @Before
public void before() public void before()
{ {
System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "TRACE"); System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "TRACE");
} }
@Test @Test

View File

@@ -27,7 +27,6 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package net.runelite.cache.fs; package net.runelite.cache.fs;
import java.io.File; import java.io.File;
@@ -48,14 +47,21 @@ public class DataFileTest
public void test1() throws IOException public void test1() throws IOException
{ {
File file = folder.newFile(); File file = folder.newFile();
Store store = new Store(folder.getRoot());
DataFile df = new DataFile(store, file); try (Store store = new Store(folder.getRoot()))
DataFileWriteResult res = df.write(42, 3, ByteBuffer.wrap("test".getBytes()), CompressionType.NONE, 0); {
DataFileReadResult res2 = df.read(42, 3, res.sector, res.compressedLength); DataFile df = new DataFile(store, file);
byte[] buf = res2.data;
String str = new String(buf); byte[] compressedData = DataFile.compress("test".getBytes(), CompressionType.NONE, 0, null);
Assert.assertEquals("test", str); DataFileWriteResult res = df.write(42, 3, compressedData, 0);
file.delete();
compressedData = df.read(42, 3, res.sector, res.compressedLength);
DataFileReadResult res2 = DataFile.decompress(compressedData, null);
byte[] buf = res2.data;
String str = new String(buf);
Assert.assertEquals("test", str);
}
} }
@Test @Test
@@ -63,16 +69,25 @@ public class DataFileTest
{ {
byte[] b = new byte[1024]; byte[] b = new byte[1024];
for (int i = 0; i < 1024; ++i) for (int i = 0; i < 1024; ++i)
{
b[i] = (byte) i; b[i] = (byte) i;
}
File file = folder.newFile(); File file = folder.newFile();
Store store = new Store(folder.getRoot());
DataFile df = new DataFile(store, file); try (Store store = new Store(folder.getRoot()))
DataFileWriteResult res = df.write(42, 0x1FFFF, ByteBuffer.wrap(b), CompressionType.BZ2, 42); {
DataFileReadResult res2 = df.read(42, 0x1FFFF, res.sector, res.compressedLength); DataFile df = new DataFile(store, file);
byte[] buf = res2.data;
Assert.assertArrayEquals(b, buf); byte[] compressedData = DataFile.compress(b, CompressionType.BZ2, 42, null);
file.delete(); DataFileWriteResult res = df.write(42, 0x1FFFF, compressedData, 42);
compressedData = df.read(42, 0x1FFFF, res.sector, res.compressedLength);
DataFileReadResult res2 = DataFile.decompress(compressedData, null);
byte[] buf = res2.data;
Assert.assertArrayEquals(b, buf);
}
} }
@Test @Test
@@ -81,8 +96,13 @@ public class DataFileTest
try (Store store = new Store(folder.getRoot())) try (Store store = new Store(folder.getRoot()))
{ {
DataFile df = new DataFile(store, folder.newFile()); DataFile df = new DataFile(store, folder.newFile());
DataFileWriteResult res = df.write(41, 4, ByteBuffer.wrap("test".getBytes()), CompressionType.GZ, 0);
DataFileReadResult res2 = df.read(41, 4, res.sector, res.compressedLength); byte[] compressedData = DataFile.compress("test".getBytes(), CompressionType.GZ, 0, null);
DataFileWriteResult res = df.write(41, 4, compressedData, 0);
compressedData = df.read(41, 4, res.sector, res.compressedLength);
DataFileReadResult res2 = DataFile.decompress(compressedData, null);
byte[] buf = res2.data; byte[] buf = res2.data;
String str = new String(buf); String str = new String(buf);
Assert.assertEquals("test", str); Assert.assertEquals("test", str);
@@ -95,8 +115,13 @@ public class DataFileTest
try (Store store = new Store(folder.getRoot())) try (Store store = new Store(folder.getRoot()))
{ {
DataFile df = new DataFile(store, folder.newFile()); DataFile df = new DataFile(store, folder.newFile());
DataFileWriteResult res = df.write(41, 4, ByteBuffer.wrap("test".getBytes()), CompressionType.BZ2, 5);
DataFileReadResult res2 = df.read(41, 4, res.sector, res.compressedLength); byte[] compressedData = DataFile.compress("test".getBytes(), CompressionType.BZ2, 5, null);
DataFileWriteResult res = df.write(41, 4, compressedData, 0);
compressedData = df.read(41, 4, res.sector, res.compressedLength);
DataFileReadResult res2 = DataFile.decompress(compressedData, null);
byte[] buf = res2.data; byte[] buf = res2.data;
String str = new String(buf); String str = new String(buf);
Assert.assertEquals("test", str); Assert.assertEquals("test", str);
@@ -107,15 +132,22 @@ public class DataFileTest
public void testCrc() throws IOException public void testCrc() throws IOException
{ {
File file = folder.newFile(); File file = folder.newFile();
Store store = new Store(folder.getRoot());
DataFile df = new DataFile(store, file); try (Store store = new Store(folder.getRoot()))
DataFileWriteResult res = df.write(42, 3, ByteBuffer.wrap("test".getBytes()), CompressionType.NONE, 42); {
DataFileReadResult res2 = df.read(42, 3, res.sector, res.compressedLength); DataFile df = new DataFile(store, file);
byte[] buf = res2.data;
String str = new String(buf); byte[] compressedData = DataFile.compress("test".getBytes(), CompressionType.NONE, 42, null);
Assert.assertEquals("test", str); DataFileWriteResult res = df.write(42, 3, compressedData, 0);
Assert.assertEquals(res.crc, res2.crc);
Assert.assertEquals(42, res2.revision); compressedData = df.read(42, 3, res.sector, res.compressedLength);
file.delete(); DataFileReadResult res2 = DataFile.decompress(compressedData, null);
byte[] buf = res2.data;
String str = new String(buf);
Assert.assertEquals("test", str);
Assert.assertEquals(res.crc, res2.crc);
Assert.assertEquals(42, res2.revision);
}
} }
} }