Add support for reading existing .dat2m caches

Signed-off-by: Graham <gpe@openrs2.org>
Graham 4 years ago
parent 6be16f1748
commit bc907d5309
  1. 48
      cache/src/main/kotlin/org/openrs2/cache/DiskStore.kt
  2. 30
      cache/src/test/kotlin/org/openrs2/cache/DiskStoreTest.kt
  3. 0
      cache/src/test/resources/org/openrs2/cache/disk-store/dat2m-empty/main_file_cache.dat2
  4. 0
      cache/src/test/resources/org/openrs2/cache/disk-store/dat2m-empty/main_file_cache.dat2m
  5. BIN
      cache/src/test/resources/org/openrs2/cache/disk-store/dat2m/main_file_cache.dat2
  6. BIN
      cache/src/test/resources/org/openrs2/cache/disk-store/dat2m/main_file_cache.dat2m
  7. BIN
      cache/src/test/resources/org/openrs2/cache/disk-store/dat2m/main_file_cache.idx0
  8. BIN
      cache/src/test/resources/org/openrs2/cache/disk-store/dat2m/main_file_cache.idx40

@ -17,11 +17,17 @@ import kotlin.math.min
* A [Store] implementation compatible with the native `main_file_cache.dat2` * A [Store] implementation compatible with the native `main_file_cache.dat2`
* and `main_file_cache.idx*` format used by the client. * and `main_file_cache.idx*` format used by the client.
* *
* It supports opening existing caches with a `main_file_cache.dat2m` file for
* compatibility purposes. It does not support creating new caches with a
* `.dat2m` file, as [FlatFileStore] is a much better choice for storing large
* caches.
*
* This class is not thread safe. * This class is not thread safe.
*/ */
public class DiskStore private constructor( public class DiskStore private constructor(
private val root: Path, private val root: Path,
private val data: BufferedFileChannel, private val data: BufferedFileChannel,
private val musicData: BufferedFileChannel?,
private val indexes: Array<BufferedFileChannel?>, private val indexes: Array<BufferedFileChannel?>,
private val alloc: ByteBufAllocator private val alloc: ByteBufAllocator
) : Store { ) : Store {
@ -134,6 +140,14 @@ public class DiskStore private constructor(
createOrGetIndex(archive) createOrGetIndex(archive)
} }
private fun getData(archive: Int): BufferedFileChannel {
return if (musicData != null && archive == MUSIC_ARCHIVE) {
musicData
} else {
data
}
}
override fun read(archive: Int, group: Int): ByteBuf { override fun read(archive: Int, group: Int): ByteBuf {
alloc.buffer(TEMP_BUFFER_SIZE, TEMP_BUFFER_SIZE).use { tempBuf -> alloc.buffer(TEMP_BUFFER_SIZE, TEMP_BUFFER_SIZE).use { tempBuf ->
val entry = readIndexEntry(archive, group, tempBuf) ?: throw FileNotFoundException() val entry = readIndexEntry(archive, group, tempBuf) ?: throw FileNotFoundException()
@ -142,6 +156,8 @@ public class DiskStore private constructor(
} }
alloc.buffer(entry.size, entry.size).use { buf -> alloc.buffer(entry.size, entry.size).use { buf ->
val data = getData(archive)
val extended = group >= 65536 val extended = group >= 65536
val headerSize = if (extended) { val headerSize = if (extended) {
EXTENDED_BLOCK_HEADER_SIZE EXTENDED_BLOCK_HEADER_SIZE
@ -201,7 +217,7 @@ public class DiskStore private constructor(
} }
} }
private fun allocateBlock(): Int { private fun allocateBlock(data: BufferedFileChannel): Int {
var block = (data.size() + BLOCK_SIZE - 1) / BLOCK_SIZE var block = (data.size() + BLOCK_SIZE - 1) / BLOCK_SIZE
if (block == 0L) { if (block == 0L) {
@ -254,6 +270,8 @@ public class DiskStore private constructor(
val index = createOrGetIndex(archive) val index = createOrGetIndex(archive)
alloc.buffer(TEMP_BUFFER_SIZE, TEMP_BUFFER_SIZE).use { tempBuf -> alloc.buffer(TEMP_BUFFER_SIZE, TEMP_BUFFER_SIZE).use { tempBuf ->
val data = getData(archive)
// read existing index entry, if it exists // read existing index entry, if it exists
val indexPos = group.toLong() * INDEX_ENTRY_SIZE val indexPos = group.toLong() * INDEX_ENTRY_SIZE
@ -309,7 +327,7 @@ public class DiskStore private constructor(
// allocate a new block if necessary // allocate a new block if necessary
var overwrite: Boolean var overwrite: Boolean
if (block == 0) { if (block == 0) {
block = allocateBlock() block = allocateBlock(data)
overwrite = false overwrite = false
} else { } else {
overwrite = true overwrite = true
@ -362,7 +380,7 @@ public class DiskStore private constructor(
// allocate a new block if necessary // allocate a new block if necessary
if (nextBlock == 0) { if (nextBlock == 0) {
nextBlock = allocateBlock() nextBlock = allocateBlock(data)
overwrite = false overwrite = false
} }
} else { } else {
@ -428,6 +446,7 @@ public class DiskStore private constructor(
override fun flush() { override fun flush() {
data.flush() data.flush()
musicData?.close()
for (index in indexes) { for (index in indexes) {
index?.flush() index?.flush()
@ -436,6 +455,7 @@ public class DiskStore private constructor(
override fun close() { override fun close() {
data.close() data.close()
musicData?.close()
for (index in indexes) { for (index in indexes) {
index?.close() index?.close()
@ -458,10 +478,16 @@ public class DiskStore private constructor(
private const val INDEX_BUFFER_SIZE = INDEX_ENTRY_SIZE * 1000 private const val INDEX_BUFFER_SIZE = INDEX_ENTRY_SIZE * 1000
private const val DATA_BUFFER_SIZE = BLOCK_SIZE * 10 private const val DATA_BUFFER_SIZE = BLOCK_SIZE * 10
private const val MUSIC_ARCHIVE = 40
internal fun dataPath(root: Path): Path { internal fun dataPath(root: Path): Path {
return root.resolve("main_file_cache.dat2") return root.resolve("main_file_cache.dat2")
} }
private fun musicDataPath(root: Path): Path {
return root.resolve("main_file_cache.dat2m")
}
private fun indexPath(root: Path, archive: Int): Path { private fun indexPath(root: Path, archive: Int): Path {
return root.resolve("main_file_cache.idx$archive") return root.resolve("main_file_cache.idx$archive")
} }
@ -474,6 +500,18 @@ public class DiskStore private constructor(
alloc alloc
) )
val path = musicDataPath(root)
val musicData = if (Files.exists(path)) {
BufferedFileChannel(
FileChannel.open(musicDataPath(root), READ, WRITE),
DATA_BUFFER_SIZE,
DATA_BUFFER_SIZE,
alloc
)
} else {
null
}
val archives = Array(Store.MAX_ARCHIVE + 1) { archive -> val archives = Array(Store.MAX_ARCHIVE + 1) { archive ->
val path = indexPath(root, archive) val path = indexPath(root, archive)
if (Files.exists(path)) { if (Files.exists(path)) {
@ -488,7 +526,7 @@ public class DiskStore private constructor(
} }
} }
return DiskStore(root, data, archives, alloc) return DiskStore(root, data, musicData, archives, alloc)
} }
public fun create(root: Path, alloc: ByteBufAllocator = ByteBufAllocator.DEFAULT): Store { public fun create(root: Path, alloc: ByteBufAllocator = ByteBufAllocator.DEFAULT): Store {
@ -503,7 +541,7 @@ public class DiskStore private constructor(
val archives = Array<BufferedFileChannel?>(Store.MAX_ARCHIVE + 1) { null } val archives = Array<BufferedFileChannel?>(Store.MAX_ARCHIVE + 1) { null }
return DiskStore(root, data, archives, alloc) return DiskStore(root, data, null, archives, alloc)
} }
} }
} }

@ -730,6 +730,36 @@ class DiskStoreTest {
} }
} }
@Test
fun testDat2mRead() {
readTest("dat2m") { store ->
store.read(0, 0).use { actual ->
copiedBuffer("Open").use { expected ->
assertEquals(expected, actual)
}
}
store.read(40, 0).use { actual ->
copiedBuffer("RS2").use { expected ->
assertEquals(expected, actual)
}
}
}
}
@Test
fun testDat2mWrite() {
overwriteTest("dat2m-empty", "dat2m") { store ->
copiedBuffer("Open").use { buf ->
store.write(0, 0, buf)
}
copiedBuffer("RS2").use { buf ->
store.write(40, 0, buf)
}
}
}
private fun readTest(name: String, f: (Store) -> Unit) { private fun readTest(name: String, f: (Store) -> Unit) {
DiskStore.open(ROOT.resolve(name)).use { store -> DiskStore.open(ROOT.resolve(name)).use { store ->
f(store) f(store)

Loading…
Cancel
Save