diff --git a/pom.xml b/pom.xml
index dfb330e6d2..da15c52446 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,6 +14,17 @@
guava
18.0
+
+ org.slf4j
+ slf4j-api
+ 1.7.12
+
+
+ org.slf4j
+ slf4j-simple
+ 1.7.12
+ test
+
junit
junit
diff --git a/src/main/java/net/runelite/cache/fs/DataFile.java b/src/main/java/net/runelite/cache/fs/DataFile.java
new file mode 100644
index 0000000000..384b7b2008
--- /dev/null
+++ b/src/main/java/net/runelite/cache/fs/DataFile.java
@@ -0,0 +1,203 @@
+package net.runelite.cache.fs;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DataFile implements Closeable
+{
+ private static final Logger logger = LoggerFactory.getLogger(DataFile.class);
+
+ private static final long SECTOR_SIZE = 520L;
+
+ private final int datafileId;
+ private final RandomAccessFile dat;
+ private final byte[] readCachedBuffer = new byte[520];
+
+ public DataFile(int id, File file) throws FileNotFoundException
+ {
+ this.datafileId = id;
+ dat = new RandomAccessFile(file, "rw");
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ dat.close();
+ }
+
+ public synchronized ByteBuffer read(int archiveId, int sector, int size) throws IOException
+ {
+ if (sector <= 0L || dat.length() / 520L < (long) sector)
+ {
+ logger.warn("bad read, dat length {}", dat.length());
+ return null;
+ }
+
+ ByteBuffer buffer = ByteBuffer.allocate(size);
+
+ dat.seek(SECTOR_SIZE * (long) sector);
+
+ for (int part = 0, readBytesCount = 0, nextSector;
+ size > readBytesCount;
+ sector = nextSector)
+ {
+ if (sector == 0)
+ {
+ return null;
+ }
+
+ dat.seek(SECTOR_SIZE * sector);
+ int dataBlockSize = size - readBytesCount;
+ byte headerSize;
+ int currentIndex;
+ int currentPart;
+ int currentArchive;
+ if (0xFFFF < archiveId)
+ {
+ headerSize = 10;
+ if (dataBlockSize > 510)
+ {
+ dataBlockSize = 510;
+ }
+
+ int i = dat.read(this.readCachedBuffer, 0, headerSize + dataBlockSize);
+ if (i != headerSize + dataBlockSize)
+ {
+ logger.warn("short read");
+ return null;
+ }
+ currentArchive = ((this.readCachedBuffer[1] & 255) << 16) + ((this.readCachedBuffer[0] & 255) << 24) + (('\uff00' & this.readCachedBuffer[2] << 8) - -(this.readCachedBuffer[3] & 255));
+ currentPart = ((this.readCachedBuffer[4] & 255) << 8) + (255 & this.readCachedBuffer[5]);
+ nextSector = (this.readCachedBuffer[8] & 255) + ('\uff00' & this.readCachedBuffer[7] << 8) + ((255 & this.readCachedBuffer[6]) << 16);
+ currentIndex = this.readCachedBuffer[9] & 255;
+ }
+ else
+ {
+ headerSize = 8;
+ if (dataBlockSize > 512)
+ {
+ dataBlockSize = 512;
+ }
+
+ int i = dat.read(this.readCachedBuffer, 0, headerSize + dataBlockSize);
+ if (i != headerSize + dataBlockSize)
+ {
+ logger.warn("short read");
+ return null;
+ }
+ currentArchive = (255 & this.readCachedBuffer[1]) + ('\uff00' & this.readCachedBuffer[0] << 8);
+ currentPart = ((this.readCachedBuffer[2] & 255) << 8) + (255 & this.readCachedBuffer[3]);
+ nextSector = (this.readCachedBuffer[6] & 255) + ('\uff00' & this.readCachedBuffer[5] << 8) + ((255 & this.readCachedBuffer[4]) << 16);
+ currentIndex = this.readCachedBuffer[7] & 255;
+ }
+
+ if (archiveId != currentArchive || currentPart != part || this.datafileId != currentIndex)
+ {
+ return null;
+ }
+
+ if (nextSector < 0 || dat.length() / 520L < (long) nextSector)
+ {
+ return null;
+ }
+
+ buffer.put(readCachedBuffer, headerSize, dataBlockSize);
+ readBytesCount += dataBlockSize;
+
+ ++part;
+ }
+
+ return buffer;
+ }
+
+ public synchronized int write(int archiveId, ByteBuffer data) throws IOException
+ {
+ int e;
+ int startSector;
+
+ e = (int) ((this.dat.length() + 519L) / 520L);
+ if (e == 0)
+ {
+ e = 1;
+ }
+ startSector = e;
+
+ for (int part = 0; data.hasRemaining(); ++part)
+ {
+ int nextSector = 0;
+ int dataToWrite;
+
+ if (nextSector == 0)
+ {
+ nextSector = (int) ((dat.length() + 519L) / 520L);
+ if (nextSector == 0)
+ {
+ ++nextSector;
+ }
+
+ if (nextSector == e)
+ {
+ ++nextSector;
+ }
+ }
+
+ if (data.remaining() <= 512)
+ {
+ nextSector = 0;
+ }
+
+ if (0xFFFF < archiveId)
+ {
+ this.readCachedBuffer[0] = (byte) (archiveId >> 24);
+ this.readCachedBuffer[1] = (byte) (archiveId >> 16);
+ this.readCachedBuffer[2] = (byte) (archiveId >> 8);
+ this.readCachedBuffer[3] = (byte) archiveId;
+ this.readCachedBuffer[4] = (byte) (part >> 8);
+ this.readCachedBuffer[5] = (byte) part;
+ this.readCachedBuffer[6] = (byte) (nextSector >> 16);
+ this.readCachedBuffer[7] = (byte) (nextSector >> 8);
+ this.readCachedBuffer[8] = (byte) nextSector;
+ this.readCachedBuffer[9] = (byte) this.datafileId;
+ dat.seek(SECTOR_SIZE * (long) e);
+ dat.write(this.readCachedBuffer, 0, 10);
+
+ dataToWrite = data.remaining();
+ if (dataToWrite > 510)
+ {
+ dataToWrite = 510;
+ }
+ }
+ else
+ {
+ this.readCachedBuffer[0] = (byte) (archiveId >> 8);
+ this.readCachedBuffer[1] = (byte) archiveId;
+ this.readCachedBuffer[2] = (byte) (part >> 8);
+ this.readCachedBuffer[3] = (byte) part;
+ this.readCachedBuffer[4] = (byte) (nextSector >> 16);
+ this.readCachedBuffer[5] = (byte) (nextSector >> 8);
+ this.readCachedBuffer[6] = (byte) nextSector;
+ this.readCachedBuffer[7] = (byte) this.datafileId;
+ dat.seek(SECTOR_SIZE * (long) e);
+ dat.write(this.readCachedBuffer, 0, 8);
+
+ dataToWrite = data.remaining();
+ if (dataToWrite > 512)
+ {
+ dataToWrite = 512;
+ }
+ }
+
+ data.get(readCachedBuffer, 0, dataToWrite);
+ dat.write(readCachedBuffer, 0, dataToWrite);
+ e = nextSector;
+ }
+
+ return startSector;
+ }
+}
diff --git a/src/test/java/net/runelite/cache/fs/DataFileTest.java b/src/test/java/net/runelite/cache/fs/DataFileTest.java
new file mode 100644
index 0000000000..e8d011e367
--- /dev/null
+++ b/src/test/java/net/runelite/cache/fs/DataFileTest.java
@@ -0,0 +1,36 @@
+package net.runelite.cache.fs;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DataFileTest
+{
+ @Test
+ public void test1() throws IOException
+ {
+ File file = new File("d:/rs/07/test/test.dat");
+ DataFile df = new DataFile(42, file);
+ int sector = df.write(3, ByteBuffer.wrap("test".getBytes()));
+ ByteBuffer buf = df.read(3, sector, 4);
+ String str = new String(buf.array());
+ Assert.assertEquals("test", str);
+ file.delete();
+ }
+
+ @Test
+ public void test2() throws IOException
+ {
+ byte[] b = new byte[1024];
+ for (int i = 0; i < 1024; ++i) b[i] = (byte) i;
+
+ File file = new File("d:/rs/07/test/test.dat");
+ DataFile df = new DataFile(42, file);
+ int sector = df.write(0x1FFFF, ByteBuffer.wrap(b));
+ ByteBuffer buf = df.read(0x1FFFF, sector, b.length);
+ Assert.assertArrayEquals(b, buf.array());
+ file.delete();
+ }
+}