From 47f99c82b1a40bde7c3389cce6bb3e152ab5a63b Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 24 Oct 2021 18:53:29 -0400 Subject: [PATCH 01/14] Add start to cache writing and push branch. --- cache/build.gradle.kts | 3 + .../runetopic/cache/extension/ByteBuffer.kt | 18 +++ .../runetopic/cache/hierarchy/index/Index.kt | 3 +- .../store/storage/js5/Js5IndexDecoder.kt | 32 ++-- .../store/storage/js5/Js5IndexEncoder.kt | 147 +++++++++++++++++ .../cache/store/storage/js5/Js5IndexTest.kt | 153 ++++++++++++++++++ 6 files changed, 339 insertions(+), 17 deletions(-) create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexEncoder.kt create mode 100644 cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexTest.kt diff --git a/cache/build.gradle.kts b/cache/build.gradle.kts index 1b5bf1e..823d7c8 100644 --- a/cache/build.gradle.kts +++ b/cache/build.gradle.kts @@ -78,4 +78,7 @@ dependencies { implementation("org.slf4j:slf4j-simple:1.7.32") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.+") implementation("com.runetopic.cryptography:cryptography:1.0.6-SNAPSHOT") + + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("io.mockk:mockk:1.12.0") } diff --git a/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt index bcf0e3c..d52c794 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt @@ -6,9 +6,27 @@ internal fun ByteBuffer.readUnsignedByte(): Int = get().toInt() and 0xFF internal fun ByteBuffer.readUnsignedShort(): Int = short.toInt() and 0xFFFF internal fun ByteBuffer.readUnsignedMedium(): Int = ((readUnsignedByte() shl 16) or (readUnsignedByte() shl 8) or readUnsignedByte()) internal fun ByteBuffer.readUnsignedIntShortSmart(): Int = if (get(position()).toInt() < 0) int and Int.MAX_VALUE else readUnsignedShort() +internal fun ByteBuffer.putIntShortSmart(value: Int) { + val buffer = when { + value >= Short.MAX_VALUE -> ByteBuffer.allocate(Int.SIZE_BYTES).putInt(value - Int.MAX_VALUE - 1) + else -> ByteBuffer.allocate(Short.SIZE_BYTES).putShort(if (value >= 0) value.toShort() else Short.MAX_VALUE) + } + put(buffer) +} internal fun ByteBuffer.remainingBytes(): ByteArray { val bytes = ByteArray(remaining()) get(bytes) return bytes +} + +class Variable { + companion object { + internal fun asSizeBytes( + protocol: Int, + value: Int + ): Int { + return if (protocol >= 7) if (value >= Short.MAX_VALUE) 4 else 2 else 2 + } + } } \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt index b803e0e..9fe452c 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt @@ -17,6 +17,7 @@ data class Index( val protocol: Int, val revision: Int, val isNamed: Boolean, + val isUsingWhirlpool: Boolean, private val groups: Map ): Comparable { @@ -64,6 +65,6 @@ data class Index( } internal companion object { - fun default(indexId: Int): Index = Index(indexId, 0, ByteArray(64), -1, -1, 0, false, hashMapOf()) + fun default(indexId: Int): Index = Index(indexId, 0, ByteArray(64), -1, -1, 0, false, false, hashMapOf()) } } \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexDecoder.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexDecoder.kt index eb288ec..0bb0fde 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexDecoder.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexDecoder.kt @@ -3,10 +3,10 @@ package com.runetopic.cache.store.storage.js5 import com.runetopic.cache.codec.Container import com.runetopic.cache.codec.decompress import com.runetopic.cache.exception.ProtocolException +import com.runetopic.cache.extension.* import com.runetopic.cache.extension.readUnsignedByte import com.runetopic.cache.extension.readUnsignedIntShortSmart import com.runetopic.cache.extension.readUnsignedShort -import com.runetopic.cache.extension.toByteBuffer import com.runetopic.cache.hierarchy.index.Index import com.runetopic.cache.hierarchy.index.group.Group import com.runetopic.cache.hierarchy.index.group.file.File @@ -29,11 +29,11 @@ internal fun decode( protocol >= 6 -> buffer.int else -> 0 } - val hash = buffer.readUnsignedByte() + val mask = buffer.readUnsignedByte() val count = if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort() - val isNamed = (0x1 and hash) != 0 - val isUsingWhirlpool = (0x2 and hash) != 0 + val isNamed = (0x1 and mask) != 0 + val isUsingWhirlpool = (0x2 and mask) != 0 val groupIds = decodeGroupIds(count, buffer, protocol) val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 @@ -66,7 +66,7 @@ internal fun decode( data )) } - return Index(idxFile.id(), decompressed.crc, whirlpool, decompressed.compression, protocol, revision, isNamed, groups) + return Index(idxFile.id(), decompressed.crc, whirlpool, decompressed.compression, protocol, revision, isNamed, isUsingWhirlpool, groups) } private fun decodeGroupIds( @@ -83,7 +83,7 @@ private fun decodeGroupIds( return groupIds } -private fun decodeGroupFileIds( +fun decodeGroupFileIds( maxGroupId: Int, count: Int, groupIds: IntArray, @@ -97,7 +97,7 @@ private fun decodeGroupFileIds( return groupFileIds } -private fun decodeGroupRevisions( +fun decodeGroupRevisions( maxGroupId: Int, count: Int, groupIds: IntArray, @@ -110,7 +110,7 @@ private fun decodeGroupRevisions( return revisions } -private fun decodeGroupWhirlpools( +fun decodeGroupWhirlpools( maxGroupId: Int, usesWhirlpool: Boolean, count: Int, @@ -128,7 +128,7 @@ private fun decodeGroupWhirlpools( return whirlpools } -private fun decodeGroupCrcs( +fun decodeGroupCrcs( maxGroupId: Int, count: Int, groupIds: IntArray, @@ -141,7 +141,7 @@ private fun decodeGroupCrcs( return crcs } -private fun decodeGroupNameHashes( +fun decodeGroupNameHashes( maxGroupId: Int, count: Int, isNamed: Boolean, @@ -187,12 +187,12 @@ private fun decodeFileNameHashes( isNamed: Boolean ): Array { val fileNameHashes = Array(maxGroupId) { IntArray(validFileIds[it]) } - if (isNamed) { - (0 until count).forEach { - val groupId = groupIds[it] - (0 until validFileIds[groupId]).forEach { fileId -> - fileNameHashes[groupId][fileId] = buffer.int - } + if (isNamed.not()) return fileNameHashes + + (0 until count).forEach { + val groupId = groupIds[it] + (0 until validFileIds[groupId]).forEach { fileId -> + fileNameHashes[groupId][fileId] = buffer.int } } return fileNameHashes diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexEncoder.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexEncoder.kt new file mode 100644 index 0000000..47be946 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexEncoder.kt @@ -0,0 +1,147 @@ +package com.runetopic.cache.store.storage.js5 + +import com.runetopic.cache.extension.Variable +import com.runetopic.cache.extension.putIntShortSmart +import com.runetopic.cache.hierarchy.index.Index +import java.nio.ByteBuffer + +/** + * @author Jordan Abraham + */ +internal fun encode( + index: Index +): ByteBuffer { + val header = ByteBuffer.allocate(if (index.protocol >= 7) 6 else 4) + header.put(index.protocol.toByte()) + + val mask = 0x0 + if (index.isNamed) mask or 0x1 + if (index.isUsingWhirlpool) mask or 0x2 + header.put(mask.toByte()) + + val count = index.groups().size + if (index.protocol >= 7) header.putIntShortSmart(count) else header.putShort(count.toShort()) + + val stream = index.groups().stream() + val groupIds = stream.map { it.id }.toList().toIntArray() + val nameHashes = stream.map { it.nameHash }.toList().toIntArray() + val crcs = stream.map { it.crc }.toList().toIntArray() + val whirlpools = stream.map { it.whirlpool }.toList().toTypedArray() + val revisions = stream.map { it.revision }.toList().toIntArray() + + val groupNameHashes = encodeGroupNameHashes(count, index.isNamed, groupIds, nameHashes) + val groupCrcs = encodeGroupCrcs(count, groupIds, crcs) + val groupWhirlpools = encodeGroupWhirlpools(count, groupIds, index.isUsingWhirlpool, whirlpools) + val groupRevisions = encodeGroupRevisions(count, groupIds, revisions) + + val buffer = ByteBuffer.allocate(header.position() + + groupNameHashes.position() + + groupCrcs.position() + + groupWhirlpools.position() + + groupRevisions.position()) + + buffer.put(header) + //TODO Group ids + buffer.put(groupNameHashes) + buffer.put(groupCrcs) + buffer.put(groupWhirlpools) + buffer.put(groupRevisions) + //TODO The rest + return buffer +} + +fun encodeGroupFileIds( + count: Int, + groupIds: IntArray, + protocol: Int, + groupFileIds: IntArray +): ByteBuffer { + var capacity = 0 + (0 until count).forEach { + capacity += Variable.asSizeBytes(protocol, groupFileIds[groupIds[it]]) + } + val buffer = ByteBuffer.allocate(capacity) + (0 until count).forEach { + if (protocol >= 7) buffer.putIntShortSmart(groupFileIds[groupIds[it]]) else buffer.putShort(groupFileIds[groupIds[it]].toShort()) + } + return buffer +} + +fun encodeGroupRevisions( + count: Int, + groupIds: IntArray, + revisions: IntArray +): ByteBuffer { + val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) + (0 until count).forEach { + buffer.putInt(revisions[groupIds[it]]) + } + return buffer +} + +fun encodeGroupWhirlpools( + count: Int, + groupIds: IntArray, + usesWhirlpool: Boolean, + whirlpools: Array +): ByteBuffer { + if ((usesWhirlpool.not())) return ByteBuffer.allocate(0) + + val buffer = ByteBuffer.allocate(count * 64) + (0 until count).forEach { + buffer.put(whirlpools[groupIds[it]]) + } + return buffer +} + +fun encodeGroupCrcs( + count: Int, + groupIds: IntArray, + crcs: IntArray +): ByteBuffer { + val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) + (0 until count).forEach { + buffer.putInt(crcs[groupIds[it]]) + } + return buffer +} + +fun encodeGroupNameHashes( + count: Int, + isNamed: Boolean, + groupIds: IntArray, + nameHashes: IntArray +): ByteBuffer { + if (isNamed.not()) return ByteBuffer.allocate(0) + + val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) + (0 until count).forEach { + buffer.putInt(nameHashes[groupIds[it]]) + } + return buffer +} + +private fun encodeFileNameHashes( + count: Int, + groupIds: IntArray, + validFileIds: IntArray, + isNamed: Boolean, + fileNameHashes: Array +): ByteBuffer { + if (isNamed.not()) return ByteBuffer.allocate(0) + //TODO Figure out a way to calc the number of bytes without doing it like this. + var bytes = 0 + (0 until count).forEach { + (0 until validFileIds[groupIds[it]]).forEach { _ -> + bytes += Int.SIZE_BYTES + } + } + val buffer = ByteBuffer.allocate(bytes) + (0 until count).forEach { + val groupId = groupIds[it] + (0 until validFileIds[groupId]).forEach { fileId -> + buffer.putInt(fileNameHashes[groupId][fileId]) + } + } + return buffer +} \ No newline at end of file diff --git a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexTest.kt b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexTest.kt new file mode 100644 index 0000000..afce789 --- /dev/null +++ b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexTest.kt @@ -0,0 +1,153 @@ +package com.runetopic.cache.store.storage.js5 + +import com.runetopic.cache.extension.Variable +import java.nio.ByteBuffer +import kotlin.random.Random +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * @author Jordan Abraham + */ +class Js5IndexTest { + + @Test + fun `test nameHashes`() { + val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) + val buffer = ByteBuffer.wrap(Random.nextBytes(37 * Int.SIZE_BYTES)) + + val decodedNameHashes = decodeGroupNameHashes( + 55, + 37, + true, + groupIds, + buffer + ) + + val encodedNameHashes = encodeGroupNameHashes( + 37, + true, + groupIds, + decodedNameHashes + ) + + assertTrue(buffer.array().contentEquals(encodedNameHashes.array())) + } + + @Test + fun `test crcs`() { + val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) + val buffer = ByteBuffer.wrap(Random.nextBytes(37 * Int.SIZE_BYTES)) + + val decodedCrcs = decodeGroupCrcs( + 55, + 37, + groupIds, + buffer + ) + + val encodedCrcs = encodeGroupCrcs( + 37, + groupIds, + decodedCrcs + ) + + assertTrue(buffer.array().contentEquals(encodedCrcs.array())) + } + + @Test + fun `test whirlpools`() { + val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) + val buffer = ByteBuffer.wrap(Random.nextBytes(37 * 64)) + + val decodedWhirlpools = decodeGroupWhirlpools( + 55, + true, + 37, + buffer, + groupIds + ) + + val encodedWhirlpools = encodeGroupWhirlpools( + 37, + groupIds, + true, + decodedWhirlpools + ) + + assertTrue(buffer.array().contentEquals(encodedWhirlpools.array())) + } + + @Test + fun `test revisions`() { + val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) + val buffer = ByteBuffer.wrap(Random.nextBytes(37 * Int.SIZE_BYTES)) + + val decodedRevisions = decodeGroupRevisions( + 55, + 37, + groupIds, + buffer + ) + + val encodedRevisions = encodeGroupRevisions( + 37, + groupIds, + decodedRevisions + ) + + assertTrue(buffer.array().contentEquals(encodedRevisions.array())) + } + + @Test + fun `test group file ids`() { + val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) + val buffer = ByteBuffer.wrap(Random.nextBytes(37 * Short.SIZE_BYTES)) + + val decodedFileIds = decodeGroupFileIds( + 55, + 37, + groupIds, + buffer, + 6 + ) + + val encodedFileIds = encodeGroupFileIds( + 37, + groupIds, + 6, + decodedFileIds + ) + + assertTrue(buffer.array().contentEquals(encodedFileIds.array())) + } + + @Test + fun `test group file ids protocol 7`() { + val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) + val groupFileIds = intArrayOf(0, 166, 397, 652, 240, 610, 0, 337, 0, 0, 0, 1405, 0, 0, 0, 331, 2135, 0, 1244, 1413, 103, 9, 1265, 269, 11, 394, 1790, 0, 0, 0, 0, 8, 2024, 178, 100, 191, 1071, 28, 1, 2, 4, 360, 115, 236, 404, 47, 28, 21, 1, 0, 0, 0, 0, 0, 141) + + var capacity = 0 + (0 until 37).forEach { + capacity += Variable.asSizeBytes(7, groupFileIds[groupIds[it]]) + } + val buffer = ByteBuffer.allocate(capacity) + + val decodedFileIds = decodeGroupFileIds( + 55, + 37, + groupIds, + buffer, + 7 + ) + + val encodedFileIds = encodeGroupFileIds( + 37, + groupIds, + 7, + decodedFileIds + ) + + assertTrue(buffer.array().contentEquals(encodedFileIds.array())) + } +} \ No newline at end of file From 5a832f07812b89c01d272c6ca0821d1dece44bda Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 25 Oct 2021 23:59:12 -0400 Subject: [PATCH 02/14] Refactor some of the code for decoding/encoding the data to build an Index. Also jazzed up the testing. --- .../cache/store/storage/js5/Js5DiskStorage.kt | 23 +- .../store/storage/js5/Js5IndexDecoder.kt | 247 ------------ .../store/storage/js5/Js5IndexEncoder.kt | 147 ------- .../storage/js5/{impl => io/dat}/DatFile.kt | 5 +- .../storage/js5/{ => io/dat}/IDatFile.kt | 4 +- .../store/storage/js5/io/dat/IDatSector.kt | 9 + .../js5/io/dat/sector/DatGroupSector.kt | 85 ++++ .../js5/io/dat/sector/DatIndexSector.kt | 367 ++++++++++++++++++ .../storage/js5/{ => io/idx}/IIdxFile.kt | 4 +- .../storage/js5/{impl => io/idx}/IdxFile.kt | 6 +- .../cache/store/storage/js5/Js5IndexTest.kt | 153 -------- .../store/storage/js5/io/dat/DatSectorTest.kt | 44 +++ 12 files changed, 525 insertions(+), 569 deletions(-) delete mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexDecoder.kt delete mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexEncoder.kt rename cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/{impl => io/dat}/DatFile.kt (95%) rename cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/{ => io/dat}/IDatFile.kt (59%) create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt rename cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/{ => io/idx}/IIdxFile.kt (70%) rename cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/{impl => io/idx}/IdxFile.kt (86%) delete mode 100644 cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexTest.kt create mode 100644 cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt index 4bb641f..70f9c97 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt @@ -1,14 +1,15 @@ package com.runetopic.cache.store.storage.js5 import com.github.michaelbull.logging.InlineLogger -import com.runetopic.cache.codec.ContainerCodec import com.runetopic.cache.hierarchy.index.Index import com.runetopic.cache.store.Constants import com.runetopic.cache.store.Js5Store import com.runetopic.cache.store.storage.IStorage -import com.runetopic.cache.store.storage.js5.impl.DatFile -import com.runetopic.cache.store.storage.js5.impl.IdxFile -import com.runetopic.cryptography.toWhirlpool +import com.runetopic.cache.store.storage.js5.io.dat.DatFile +import com.runetopic.cache.store.storage.js5.io.idx.IdxFile +import com.runetopic.cache.store.storage.js5.io.dat.IDatFile +import com.runetopic.cache.store.storage.js5.io.dat.sector.DatIndexSector +import com.runetopic.cache.store.storage.js5.io.idx.IIdxFile import java.io.FileNotFoundException import java.nio.file.Path import java.util.concurrent.CopyOnWriteArrayList @@ -72,29 +73,29 @@ internal class Js5DiskStorage( } override fun open(indexId: Int, store: Js5Store) { - val indexTable = masterIdxFile.loadReferenceTable(indexId) + val indexReferenceTable = masterIdxFile.decode(indexId) idxFiles.add(getIdxFile(indexId)) - if (indexTable.exists().not()) { + if (indexReferenceTable.exists().not()) { store.addIndex(Index.default(indexId)) return } - val indexDatTable = datFile.readReferenceTable(masterIdxFile.id(), indexTable) - store.addIndex(decode(datFile, getIdxFile(indexId), indexDatTable.toWhirlpool(), ContainerCodec.decompress(indexDatTable))) + val data = datFile.decode(masterIdxFile.id(), indexReferenceTable) + store.addIndex(DatIndexSector(datFile, getIdxFile(indexId), data).decode()) } override fun loadMasterReferenceTable(groupId: Int): ByteArray { - return datFile.readReferenceTable(Constants.MASTER_INDEX_ID, masterIdxFile.loadReferenceTable(groupId)) + return datFile.decode(Constants.MASTER_INDEX_ID, masterIdxFile.decode(groupId)) } override fun loadReferenceTable(index: Index, groupId: Int): ByteArray { - return datFile.readReferenceTable(index.id, getIdxFile(index.id).loadReferenceTable(groupId)) + return datFile.decode(index.id, getIdxFile(index.id).decode(groupId)) } override fun loadReferenceTable(index: Index, groupName: String): ByteArray { val group = index.group(groupName) if (group.data.isEmpty()) return group.data - return datFile.readReferenceTable(index.id, getIdxFile(index.id).loadReferenceTable(group.id)) + return datFile.decode(index.id, getIdxFile(index.id).decode(group.id)) } private fun getIdxFile(id: Int): IdxFile { diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexDecoder.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexDecoder.kt deleted file mode 100644 index 8b0f2fe..0000000 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexDecoder.kt +++ /dev/null @@ -1,247 +0,0 @@ -package com.runetopic.cache.store.storage.js5 - -import com.runetopic.cache.codec.Container -import com.runetopic.cache.codec.decompress -import com.runetopic.cache.exception.ProtocolException -import com.runetopic.cache.extension.* -import com.runetopic.cache.extension.readUnsignedByte -import com.runetopic.cache.extension.readUnsignedIntShortSmart -import com.runetopic.cache.extension.readUnsignedShort -import com.runetopic.cache.hierarchy.index.Index -import com.runetopic.cache.hierarchy.index.group.Group -import com.runetopic.cache.hierarchy.index.group.file.File -import java.nio.ByteBuffer -import java.util.zip.ZipException - -/** - * @author Jordan Abraham - */ -internal fun decode( - datFile: IDatFile, - idxFile: IIdxFile, - whirlpool: ByteArray, - decompressed: Container -): Index { - val buffer = decompressed.data.toByteBuffer() - val protocol = buffer.readUnsignedByte() - val revision = when { - protocol < 5 || protocol > 7 -> throw ProtocolException("Unhandled protocol $protocol") - protocol >= 6 -> buffer.int - else -> 0 - } - val mask = buffer.readUnsignedByte() - val count = if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort() - - val isNamed = (0x1 and mask) != 0 - val isUsingWhirlpool = (0x2 and mask) != 0 - - val groupIds = decodeGroupIds(count, buffer, protocol) - val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 - val groupNameHashes = decodeGroupNameHashes(maxGroupId, count, isNamed, groupIds, buffer) - val groupCrcs = decodeGroupCrcs(maxGroupId, count, groupIds, buffer) - val groupWhirlpools = decodeGroupWhirlpools(maxGroupId, isUsingWhirlpool, count, buffer, groupIds) - val groupRevisions = decodeGroupRevisions(maxGroupId, count, groupIds, buffer) - val groupFileIds = decodeGroupFileIds(maxGroupId, count, groupIds, buffer, protocol) - val fileIds = decodeFileIds(maxGroupId, groupFileIds, count, groupIds, buffer, protocol) - val fileNameHashes = decodeFileNameHashes(maxGroupId, groupFileIds, count, groupIds, buffer, isNamed) - - val groups = hashMapOf() - (0 until count).forEach { - val groupId = groupIds[it] - - val groupReferenceTableData = datFile.readReferenceTable(idxFile.id(), idxFile.loadReferenceTable(groupId)) - val data = if (groupReferenceTableData.isEmpty()) byteArrayOf() else try { - groupReferenceTableData.decompress() - } catch (exception: ZipException) { - groupReferenceTableData - } - - groups[groupId] = (Group( - groupId, - groupNameHashes[groupId], - groupCrcs[groupId], - groupWhirlpools[groupId], - groupRevisions[groupId], - intArrayOf(),//TODO - decodeFiles(fileIds, fileNameHashes, data, groupFileIds[groupId], groupId), - data - )) - } - return Index(idxFile.id(), decompressed.crc, whirlpool, decompressed.compression, protocol, revision, isNamed, isUsingWhirlpool, groups) -} - -private fun decodeGroupIds( - count: Int, - buffer: ByteBuffer, - protocol: Int -): IntArray { - val groupIds = IntArray(count) - var offset = 0 - (0 until count).forEach { - groupIds[it] = if (protocol >= 7) { buffer.readUnsignedIntShortSmart() } else { buffer.readUnsignedShort() } - .let { id -> offset += id; offset } - } - return groupIds -} - -fun decodeGroupFileIds( - maxGroupId: Int, - count: Int, - groupIds: IntArray, - buffer: ByteBuffer, - protocol: Int -): IntArray { - val groupFileIds = IntArray(maxGroupId) - (0 until count).forEach { - groupFileIds[groupIds[it]] = if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort() - } - return groupFileIds -} - -fun decodeGroupRevisions( - maxGroupId: Int, - count: Int, - groupIds: IntArray, - buffer: ByteBuffer -): IntArray { - val revisions = IntArray(maxGroupId) - (0 until count).forEach { - revisions[groupIds[it]] = buffer.int - } - return revisions -} - -fun decodeGroupWhirlpools( - maxGroupId: Int, - usesWhirlpool: Boolean, - count: Int, - buffer: ByteBuffer, - groupIds: IntArray -): Array { - val whirlpools = Array(maxGroupId) { ByteArray(64) } - if (usesWhirlpool.not()) return whirlpools - - (0 until count).forEach { - val whirlpool = ByteArray(64) - buffer.get(whirlpool) - whirlpools[groupIds[it]] = whirlpool - } - return whirlpools -} - -fun decodeGroupCrcs( - maxGroupId: Int, - count: Int, - groupIds: IntArray, - buffer: ByteBuffer -): IntArray { - val crcs = IntArray(maxGroupId) - (0 until count).forEach { - crcs[groupIds[it]] = buffer.int - } - return crcs -} - -fun decodeGroupNameHashes( - maxGroupId: Int, - count: Int, - isNamed: Boolean, - groupIds: IntArray, - buffer: ByteBuffer -): IntArray { - val nameHashes = IntArray(maxGroupId) { -1 } - if (isNamed.not()) return nameHashes - - (0 until count).forEach { - nameHashes[groupIds[it]] = buffer.int - } - return nameHashes -} - -private fun decodeFileIds( - maxGroupId: Int, - validFileIds: IntArray, - count: Int, - groupIds: IntArray, - buffer: ByteBuffer, - protocol: Int -): Array { - val fileIds = Array(maxGroupId) { IntArray(validFileIds[it]) } - (0 until count).forEach { - val groupId = groupIds[it] - var offset = 0 - (0 until validFileIds[groupId]).forEach { fileId -> - if (protocol >= 7) { buffer.readUnsignedIntShortSmart() } else { buffer.readUnsignedShort() } - .let { i -> offset += i; offset } - .also { fileIds[groupId][fileId] = offset } - } - } - return fileIds -} - -private fun decodeFileNameHashes( - maxGroupId: Int, - validFileIds: IntArray, - count: Int, - groupIds: IntArray, - buffer: ByteBuffer, - isNamed: Boolean -): Array { - val fileNameHashes = Array(maxGroupId) { IntArray(validFileIds[it]) } - if (isNamed.not()) return fileNameHashes - - (0 until count).forEach { - val groupId = groupIds[it] - (0 until validFileIds[groupId]).forEach { fileId -> - fileNameHashes[groupId][fileId] = buffer.int - } - } - return fileNameHashes -} - -internal fun decodeFiles( - fileIds: Array, - fileNameHashes: Array, - data: ByteArray, - count: Int, - groupId: Int -): Map { - if (data.isEmpty()) return hashMapOf(Pair(0, File.DEFAULT)) - if (count <= 1) return hashMapOf(Pair(0, File(fileIds[groupId][0], fileNameHashes[groupId][0], data))) - - var position = data.size - val chunks = data[--position].toInt() and 0xFF - position -= chunks * (count * 4) - val buffer = data.toByteBuffer() - buffer.position(position) - val filesSizes = IntArray(count) - (0 until chunks).forEach { _ -> - var read = 0 - (0 until count).forEach { - read += buffer.int - filesSizes[it] += read - } - } - val filesDatas = Array(count) { byteArrayOf() } - (0 until count).forEach { - filesDatas[it] = ByteArray(filesSizes[it]) - filesSizes[it] = 0 - } - buffer.position(position) - var offset = 0 - (0 until chunks).forEach { _ -> - var read = 0 - (0 until count).forEach { - read += buffer.int - System.arraycopy(data, offset, filesDatas[it], filesSizes[it], read) - offset += read - filesSizes[it] += read - } - } - - val files = hashMapOf() - (0 until count).forEach { - files[it] = File(fileIds[groupId][it], fileNameHashes[groupId][it], filesDatas[it]) - } - return files -} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexEncoder.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexEncoder.kt deleted file mode 100644 index 47be946..0000000 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexEncoder.kt +++ /dev/null @@ -1,147 +0,0 @@ -package com.runetopic.cache.store.storage.js5 - -import com.runetopic.cache.extension.Variable -import com.runetopic.cache.extension.putIntShortSmart -import com.runetopic.cache.hierarchy.index.Index -import java.nio.ByteBuffer - -/** - * @author Jordan Abraham - */ -internal fun encode( - index: Index -): ByteBuffer { - val header = ByteBuffer.allocate(if (index.protocol >= 7) 6 else 4) - header.put(index.protocol.toByte()) - - val mask = 0x0 - if (index.isNamed) mask or 0x1 - if (index.isUsingWhirlpool) mask or 0x2 - header.put(mask.toByte()) - - val count = index.groups().size - if (index.protocol >= 7) header.putIntShortSmart(count) else header.putShort(count.toShort()) - - val stream = index.groups().stream() - val groupIds = stream.map { it.id }.toList().toIntArray() - val nameHashes = stream.map { it.nameHash }.toList().toIntArray() - val crcs = stream.map { it.crc }.toList().toIntArray() - val whirlpools = stream.map { it.whirlpool }.toList().toTypedArray() - val revisions = stream.map { it.revision }.toList().toIntArray() - - val groupNameHashes = encodeGroupNameHashes(count, index.isNamed, groupIds, nameHashes) - val groupCrcs = encodeGroupCrcs(count, groupIds, crcs) - val groupWhirlpools = encodeGroupWhirlpools(count, groupIds, index.isUsingWhirlpool, whirlpools) - val groupRevisions = encodeGroupRevisions(count, groupIds, revisions) - - val buffer = ByteBuffer.allocate(header.position() - + groupNameHashes.position() - + groupCrcs.position() - + groupWhirlpools.position() - + groupRevisions.position()) - - buffer.put(header) - //TODO Group ids - buffer.put(groupNameHashes) - buffer.put(groupCrcs) - buffer.put(groupWhirlpools) - buffer.put(groupRevisions) - //TODO The rest - return buffer -} - -fun encodeGroupFileIds( - count: Int, - groupIds: IntArray, - protocol: Int, - groupFileIds: IntArray -): ByteBuffer { - var capacity = 0 - (0 until count).forEach { - capacity += Variable.asSizeBytes(protocol, groupFileIds[groupIds[it]]) - } - val buffer = ByteBuffer.allocate(capacity) - (0 until count).forEach { - if (protocol >= 7) buffer.putIntShortSmart(groupFileIds[groupIds[it]]) else buffer.putShort(groupFileIds[groupIds[it]].toShort()) - } - return buffer -} - -fun encodeGroupRevisions( - count: Int, - groupIds: IntArray, - revisions: IntArray -): ByteBuffer { - val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) - (0 until count).forEach { - buffer.putInt(revisions[groupIds[it]]) - } - return buffer -} - -fun encodeGroupWhirlpools( - count: Int, - groupIds: IntArray, - usesWhirlpool: Boolean, - whirlpools: Array -): ByteBuffer { - if ((usesWhirlpool.not())) return ByteBuffer.allocate(0) - - val buffer = ByteBuffer.allocate(count * 64) - (0 until count).forEach { - buffer.put(whirlpools[groupIds[it]]) - } - return buffer -} - -fun encodeGroupCrcs( - count: Int, - groupIds: IntArray, - crcs: IntArray -): ByteBuffer { - val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) - (0 until count).forEach { - buffer.putInt(crcs[groupIds[it]]) - } - return buffer -} - -fun encodeGroupNameHashes( - count: Int, - isNamed: Boolean, - groupIds: IntArray, - nameHashes: IntArray -): ByteBuffer { - if (isNamed.not()) return ByteBuffer.allocate(0) - - val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) - (0 until count).forEach { - buffer.putInt(nameHashes[groupIds[it]]) - } - return buffer -} - -private fun encodeFileNameHashes( - count: Int, - groupIds: IntArray, - validFileIds: IntArray, - isNamed: Boolean, - fileNameHashes: Array -): ByteBuffer { - if (isNamed.not()) return ByteBuffer.allocate(0) - //TODO Figure out a way to calc the number of bytes without doing it like this. - var bytes = 0 - (0 until count).forEach { - (0 until validFileIds[groupIds[it]]).forEach { _ -> - bytes += Int.SIZE_BYTES - } - } - val buffer = ByteBuffer.allocate(bytes) - (0 until count).forEach { - val groupId = groupIds[it] - (0 until validFileIds[groupId]).forEach { fileId -> - buffer.putInt(fileNameHashes[groupId][fileId]) - } - } - return buffer -} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/impl/DatFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt similarity index 95% rename from cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/impl/DatFile.kt rename to cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt index 6f47e38..9f97d01 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/impl/DatFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt @@ -1,4 +1,4 @@ -package com.runetopic.cache.store.storage.js5.impl +package com.runetopic.cache.store.storage.js5.io.dat import com.runetopic.cache.exception.DatFileException import com.runetopic.cache.exception.EndOfDatFileException @@ -8,7 +8,6 @@ import com.runetopic.cache.extension.readUnsignedShort import com.runetopic.cache.extension.toByteBuffer import com.runetopic.cache.hierarchy.ReferenceTable import com.runetopic.cache.store.Constants.DAT_SIZE -import com.runetopic.cache.store.storage.js5.IDatFile import java.io.RandomAccessFile import java.nio.ByteBuffer import java.nio.file.Path @@ -29,7 +28,7 @@ internal class DatFile( datFile.readFully(datBuffer) } - override fun readReferenceTable( + override fun decode( id: Int, referenceTable: ReferenceTable ): ByteArray { diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/IDatFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt similarity index 59% rename from cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/IDatFile.kt rename to cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt index 326094f..8244b11 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/IDatFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt @@ -1,4 +1,4 @@ -package com.runetopic.cache.store.storage.js5 +package com.runetopic.cache.store.storage.js5.io.dat import com.runetopic.cache.hierarchy.ReferenceTable import java.io.Closeable @@ -8,5 +8,5 @@ import java.io.Closeable * @email */ internal interface IDatFile: Closeable { - fun readReferenceTable(id: Int, referenceTable: ReferenceTable): ByteArray + fun decode(id: Int, referenceTable: ReferenceTable): ByteArray } \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt new file mode 100644 index 0000000..a98d9f9 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt @@ -0,0 +1,9 @@ +package com.runetopic.cache.store.storage.js5.io.dat + +/** + * @author Jordan Abraham + */ +internal interface IDatSector { + fun decode(): T + fun encode(override: T): ByteArray +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt new file mode 100644 index 0000000..2b7945e --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt @@ -0,0 +1,85 @@ +package com.runetopic.cache.store.storage.js5.io.dat.sector + +import com.runetopic.cache.extension.toByteBuffer +import com.runetopic.cache.hierarchy.index.group.file.File +import com.runetopic.cache.store.storage.js5.io.dat.IDatSector + +/** + * @author Jordan Abraham + */ +data class DatGroupSector( + val fileIds: Array, + val fileNameHashes: Array, + val data: ByteArray, + val count: Int, + val groupId: Int +): IDatSector> { + override fun decode(): Map { + if (data.isEmpty()) return hashMapOf(Pair(0, File.DEFAULT)) + if (count <= 1) return hashMapOf(Pair(0, File(fileIds[groupId][0], fileNameHashes[groupId][0], data))) + + var position = data.size + val chunks = data[--position].toInt() and 0xFF + position -= chunks * (count * 4) + val buffer = data.toByteBuffer() + buffer.position(position) + val filesSizes = IntArray(count) + (0 until chunks).forEach { _ -> + var read = 0 + (0 until count).forEach { + read += buffer.int + filesSizes[it] += read + } + } + val filesDatas = Array(count) { byteArrayOf() } + (0 until count).forEach { + filesDatas[it] = ByteArray(filesSizes[it]) + filesSizes[it] = 0 + } + buffer.position(position) + var offset = 0 + (0 until chunks).forEach { _ -> + var read = 0 + (0 until count).forEach { + read += buffer.int + System.arraycopy(data, offset, filesDatas[it], filesSizes[it], read) + offset += read + filesSizes[it] += read + } + } + + val files = hashMapOf() + (0 until count).forEach { + files[it] = File(fileIds[groupId][it], fileNameHashes[groupId][it], filesDatas[it]) + } + return files + } + + override fun encode(override: Map): ByteArray { + TODO("Not yet implemented") + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DatGroupSector + + if (!fileIds.contentDeepEquals(other.fileIds)) return false + if (!fileNameHashes.contentDeepEquals(other.fileNameHashes)) return false + if (!data.contentEquals(other.data)) return false + if (count != other.count) return false + if (groupId != other.groupId) return false + + return true + } + + override fun hashCode(): Int { + var result = fileIds.contentDeepHashCode() + result = 31 * result + fileNameHashes.contentDeepHashCode() + result = 31 * result + data.contentHashCode() + result = 31 * result + count + result = 31 * result + groupId + return result + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt new file mode 100644 index 0000000..eda782f --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt @@ -0,0 +1,367 @@ +package com.runetopic.cache.store.storage.js5.io.dat.sector + +import com.runetopic.cache.codec.ContainerCodec +import com.runetopic.cache.codec.decompress +import com.runetopic.cache.exception.ProtocolException +import com.runetopic.cache.extension.* +import com.runetopic.cache.hierarchy.index.Index +import com.runetopic.cache.hierarchy.index.group.Group +import com.runetopic.cache.store.storage.js5.io.dat.IDatFile +import com.runetopic.cache.store.storage.js5.io.dat.IDatSector +import com.runetopic.cache.store.storage.js5.io.idx.IIdxFile +import com.runetopic.cryptography.toWhirlpool +import java.nio.ByteBuffer +import java.util.zip.ZipException + +/** + * @author Jordan Abraham + */ +internal data class DatIndexSector( + val datFile: IDatFile, + val idxFile: IIdxFile, + val data: ByteArray +): IDatSector { + + override fun decode(): Index { + val decompressed = ContainerCodec.decompress(data) + val buffer = decompressed.data.toByteBuffer() + val protocol = buffer.readUnsignedByte() + val revision = when { + protocol < 5 || protocol > 7 -> throw ProtocolException("Unhandled protocol $protocol") + protocol >= 6 -> buffer.int + else -> 0 + } + val mask = buffer.readUnsignedByte() + val count = if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort() + + val isNamed = (0x1 and mask) != 0 + val isUsingWhirlpool = (0x2 and mask) != 0 + + val groupIds = decodeGroupIds(count, buffer, protocol) + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + val groupNameHashes = decodeGroupNameHashes(maxGroupId, count, isNamed, groupIds, buffer) + val groupCrcs = decodeGroupCrcs(maxGroupId, count, groupIds, buffer) + val groupWhirlpools = decodeGroupWhirlpools(maxGroupId, isUsingWhirlpool, count, buffer, groupIds) + val groupRevisions = decodeGroupRevisions(maxGroupId, count, groupIds, buffer) + val fileSizes = decodeGroupFileSizes(maxGroupId, count, groupIds, buffer, protocol) + val fileIds = decodeFileIds(maxGroupId, fileSizes, count, groupIds, buffer, protocol) + val fileNameHashes = decodeFileNameHashes(maxGroupId, fileSizes, count, groupIds, buffer, isNamed) + + val groups = hashMapOf() + (0 until count).forEach { + val groupId = groupIds[it] + + val groupReferenceTableData = datFile.decode(idxFile.id(), idxFile.decode(groupId)) + val data = if (groupReferenceTableData.isEmpty()) byteArrayOf() else try { + groupReferenceTableData.decompress() + } catch (exception: ZipException) { + groupReferenceTableData + } + + val groupSector = DatGroupSector( + fileIds, + fileNameHashes, + data, + fileSizes[groupId], + groupId + ) + + groups[groupId] = (Group( + groupId, + groupNameHashes[groupId], + groupCrcs[groupId], + groupWhirlpools[groupId], + groupRevisions[groupId], + intArrayOf(),//TODO + groupSector.decode(), + data + )) + } + return Index(idxFile.id(), decompressed.crc, data.toWhirlpool(), decompressed.compression, protocol, revision, isNamed, isUsingWhirlpool, groups) + } + + override fun encode(override: Index): ByteArray { + val header = ByteBuffer.allocate(if (override.protocol >= 7) 6 else 4) + header.put(override.protocol.toByte()) + + val mask = 0x0 + if (override.isNamed) mask or 0x1 + if (override.isUsingWhirlpool) mask or 0x2 + header.put(mask.toByte()) + + val count = override.groups().size + if (override.protocol >= 7) header.putIntShortSmart(count) else header.putShort(count.toShort()) + + val stream = override.groups().stream() + val groupIds = stream.map { it.id }.toList().toIntArray() + val nameHashes = stream.map { it.nameHash }.toList().toIntArray() + val crcs = stream.map { it.crc }.toList().toIntArray() + val whirlpools = stream.map { it.whirlpool }.toList().toTypedArray() + val revisions = stream.map { it.revision }.toList().toIntArray() + + val groupNameHashes = encodeGroupNameHashes(count, override.isNamed, groupIds, nameHashes) + val groupCrcs = encodeGroupCrcs(count, groupIds, crcs) + val groupWhirlpools = encodeGroupWhirlpools(count, groupIds, override.isUsingWhirlpool, whirlpools) + val groupRevisions = encodeGroupRevisions(count, groupIds, revisions) + + val buffer = ByteBuffer.allocate(header.position() + + groupNameHashes.position() + + groupCrcs.position() + + groupWhirlpools.position() + + groupRevisions.position()) + + buffer.put(header) + //TODO Group ids + buffer.put(groupNameHashes) + buffer.put(groupCrcs) + buffer.put(groupWhirlpools) + buffer.put(groupRevisions) + //TODO The rest + return buffer.array() + } + + private fun decodeGroupIds( + count: Int, + buffer: ByteBuffer, + protocol: Int + ): IntArray { + val groupIds = IntArray(count) + var offset = 0 + (0 until count).forEach { + groupIds[it] = if (protocol >= 7) { buffer.readUnsignedIntShortSmart() } else { buffer.readUnsignedShort() } + .let { id -> offset += id; offset } + } + return groupIds + } + + private fun decodeGroupFileSizes( + maxGroupId: Int, + count: Int, + groupIds: IntArray, + buffer: ByteBuffer, + protocol: Int + ): IntArray { + val groupFileIds = IntArray(maxGroupId) + (0 until count).forEach { + groupFileIds[groupIds[it]] = if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort() + } + return groupFileIds + } + + private fun encodeGroupFileSizes( + count: Int, + groupIds: IntArray, + protocol: Int, + groupFileIds: IntArray + ): ByteBuffer { + var capacity = 0 + (0 until count).forEach { + capacity += Variable.asSizeBytes(protocol, groupFileIds[groupIds[it]]) + } + val buffer = ByteBuffer.allocate(capacity) + (0 until count).forEach { + if (protocol >= 7) buffer.putIntShortSmart(groupFileIds[groupIds[it]]) else buffer.putShort(groupFileIds[groupIds[it]].toShort()) + } + return buffer + } + + fun decodeGroupRevisions( + maxGroupId: Int, + count: Int, + groupIds: IntArray, + buffer: ByteBuffer + ): IntArray { + val revisions = IntArray(maxGroupId) + (0 until count).forEach { + revisions[groupIds[it]] = buffer.int + } + return revisions + } + + fun encodeGroupRevisions( + count: Int, + groupIds: IntArray, + revisions: IntArray + ): ByteBuffer { + val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) + (0 until count).forEach { + buffer.putInt(revisions[groupIds[it]]) + } + return buffer + } + + private fun decodeGroupWhirlpools( + maxGroupId: Int, + usesWhirlpool: Boolean, + count: Int, + buffer: ByteBuffer, + groupIds: IntArray + ): Array { + val whirlpools = Array(maxGroupId) { ByteArray(64) } + if (usesWhirlpool.not()) return whirlpools + + (0 until count).forEach { + val whirlpool = ByteArray(64) + buffer.get(whirlpool) + whirlpools[groupIds[it]] = whirlpool + } + return whirlpools + } + + private fun encodeGroupWhirlpools( + count: Int, + groupIds: IntArray, + usesWhirlpool: Boolean, + whirlpools: Array + ): ByteBuffer { + if ((usesWhirlpool.not())) return ByteBuffer.allocate(0) + + val buffer = ByteBuffer.allocate(count * 64) + (0 until count).forEach { + buffer.put(whirlpools[groupIds[it]]) + } + return buffer + } + + private fun decodeGroupCrcs( + maxGroupId: Int, + count: Int, + groupIds: IntArray, + buffer: ByteBuffer + ): IntArray { + val crcs = IntArray(maxGroupId) + (0 until count).forEach { + crcs[groupIds[it]] = buffer.int + } + return crcs + } + + private fun encodeGroupCrcs( + count: Int, + groupIds: IntArray, + crcs: IntArray + ): ByteBuffer { + val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) + (0 until count).forEach { + buffer.putInt(crcs[groupIds[it]]) + } + return buffer + } + + private fun decodeGroupNameHashes( + maxGroupId: Int, + count: Int, + isNamed: Boolean, + groupIds: IntArray, + buffer: ByteBuffer + ): IntArray { + val nameHashes = IntArray(maxGroupId) { -1 } + if (isNamed.not()) return nameHashes + + (0 until count).forEach { + nameHashes[groupIds[it]] = buffer.int + } + return nameHashes + } + + private fun encodeGroupNameHashes( + count: Int, + isNamed: Boolean, + groupIds: IntArray, + nameHashes: IntArray + ): ByteBuffer { + if (isNamed.not()) return ByteBuffer.allocate(0) + + val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) + (0 until count).forEach { + buffer.putInt(nameHashes[groupIds[it]]) + } + return buffer + } + + private fun decodeFileIds( + maxGroupId: Int, + validFileIds: IntArray, + count: Int, + groupIds: IntArray, + buffer: ByteBuffer, + protocol: Int + ): Array { + val fileIds = Array(maxGroupId) { IntArray(validFileIds[it]) } + (0 until count).forEach { + val groupId = groupIds[it] + var offset = 0 + (0 until validFileIds[groupId]).forEach { fileId -> + if (protocol >= 7) { buffer.readUnsignedIntShortSmart() } else { buffer.readUnsignedShort() } + .let { i -> offset += i; offset } + .also { fileIds[groupId][fileId] = offset } + } + } + return fileIds + } + + private fun decodeFileNameHashes( + maxGroupId: Int, + validFileIds: IntArray, + count: Int, + groupIds: IntArray, + buffer: ByteBuffer, + isNamed: Boolean + ): Array { + val fileNameHashes = Array(maxGroupId) { IntArray(validFileIds[it]) } + if (isNamed.not()) return fileNameHashes + + (0 until count).forEach { + val groupId = groupIds[it] + (0 until validFileIds[groupId]).forEach { fileId -> + fileNameHashes[groupId][fileId] = buffer.int + } + } + return fileNameHashes + } + + private fun encodeFileNameHashes( + count: Int, + groupIds: IntArray, + validFileIds: IntArray, + isNamed: Boolean, + fileNameHashes: Array + ): ByteBuffer { + if (isNamed.not()) return ByteBuffer.allocate(0) + //TODO Figure out a way to calc the number of bytes without doing it like this. + var bytes = 0 + (0 until count).forEach { + (0 until validFileIds[groupIds[it]]).forEach { _ -> + bytes += Int.SIZE_BYTES + } + } + val buffer = ByteBuffer.allocate(bytes) + (0 until count).forEach { + val groupId = groupIds[it] + (0 until validFileIds[groupId]).forEach { fileId -> + buffer.putInt(fileNameHashes[groupId][fileId]) + } + } + return buffer + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DatIndexSector + + if (datFile != other.datFile) return false + if (idxFile != other.idxFile) return false + if (!data.contentEquals(other.data)) return false + + return true + } + + override fun hashCode(): Int { + var result = datFile.hashCode() + result = 31 * result + idxFile.hashCode() + result = 31 * result + data.contentHashCode() + return result + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/IIdxFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt similarity index 70% rename from cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/IIdxFile.kt rename to cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt index e39adfe..79e9017 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/IIdxFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt @@ -1,4 +1,4 @@ -package com.runetopic.cache.store.storage.js5 +package com.runetopic.cache.store.storage.js5.io.idx import com.runetopic.cache.hierarchy.ReferenceTable import java.io.Closeable @@ -8,7 +8,7 @@ import java.io.Closeable * @email */ internal interface IIdxFile: Closeable { - fun loadReferenceTable(id: Int): ReferenceTable + fun decode(id: Int): ReferenceTable fun validIndexCount(): Int fun id(): Int } \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/impl/IdxFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt similarity index 86% rename from cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/impl/IdxFile.kt rename to cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt index 51a0094..4fedb96 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/impl/IdxFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt @@ -1,12 +1,10 @@ -package com.runetopic.cache.store.storage.js5.impl +package com.runetopic.cache.store.storage.js5.io.idx import com.runetopic.cache.exception.IdxFileException import com.runetopic.cache.extension.readUnsignedMedium import com.runetopic.cache.extension.toByteBuffer import com.runetopic.cache.hierarchy.ReferenceTable -import com.runetopic.cache.store.Constants import com.runetopic.cache.store.Constants.IDX_SIZE -import com.runetopic.cache.store.storage.js5.IIdxFile import java.io.RandomAccessFile import java.nio.file.Path import kotlin.io.path.ExperimentalPathApi @@ -29,7 +27,7 @@ internal class IdxFile( idxFile.readFully(idxBuffer) } - override fun loadReferenceTable(id: Int): ReferenceTable { + override fun decode(id: Int): ReferenceTable { val offset = id * IDX_SIZE val buffer = idxBuffer.copyOfRange(offset, offset + IDX_SIZE).toByteBuffer() val length = buffer.readUnsignedMedium() diff --git a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexTest.kt b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexTest.kt deleted file mode 100644 index afce789..0000000 --- a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/Js5IndexTest.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.runetopic.cache.store.storage.js5 - -import com.runetopic.cache.extension.Variable -import java.nio.ByteBuffer -import kotlin.random.Random -import kotlin.test.Test -import kotlin.test.assertTrue - -/** - * @author Jordan Abraham - */ -class Js5IndexTest { - - @Test - fun `test nameHashes`() { - val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) - val buffer = ByteBuffer.wrap(Random.nextBytes(37 * Int.SIZE_BYTES)) - - val decodedNameHashes = decodeGroupNameHashes( - 55, - 37, - true, - groupIds, - buffer - ) - - val encodedNameHashes = encodeGroupNameHashes( - 37, - true, - groupIds, - decodedNameHashes - ) - - assertTrue(buffer.array().contentEquals(encodedNameHashes.array())) - } - - @Test - fun `test crcs`() { - val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) - val buffer = ByteBuffer.wrap(Random.nextBytes(37 * Int.SIZE_BYTES)) - - val decodedCrcs = decodeGroupCrcs( - 55, - 37, - groupIds, - buffer - ) - - val encodedCrcs = encodeGroupCrcs( - 37, - groupIds, - decodedCrcs - ) - - assertTrue(buffer.array().contentEquals(encodedCrcs.array())) - } - - @Test - fun `test whirlpools`() { - val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) - val buffer = ByteBuffer.wrap(Random.nextBytes(37 * 64)) - - val decodedWhirlpools = decodeGroupWhirlpools( - 55, - true, - 37, - buffer, - groupIds - ) - - val encodedWhirlpools = encodeGroupWhirlpools( - 37, - groupIds, - true, - decodedWhirlpools - ) - - assertTrue(buffer.array().contentEquals(encodedWhirlpools.array())) - } - - @Test - fun `test revisions`() { - val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) - val buffer = ByteBuffer.wrap(Random.nextBytes(37 * Int.SIZE_BYTES)) - - val decodedRevisions = decodeGroupRevisions( - 55, - 37, - groupIds, - buffer - ) - - val encodedRevisions = encodeGroupRevisions( - 37, - groupIds, - decodedRevisions - ) - - assertTrue(buffer.array().contentEquals(encodedRevisions.array())) - } - - @Test - fun `test group file ids`() { - val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) - val buffer = ByteBuffer.wrap(Random.nextBytes(37 * Short.SIZE_BYTES)) - - val decodedFileIds = decodeGroupFileIds( - 55, - 37, - groupIds, - buffer, - 6 - ) - - val encodedFileIds = encodeGroupFileIds( - 37, - groupIds, - 6, - decodedFileIds - ) - - assertTrue(buffer.array().contentEquals(encodedFileIds.array())) - } - - @Test - fun `test group file ids protocol 7`() { - val groupIds = intArrayOf(1, 2, 3, 4, 5, 7, 11, 15, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 54) - val groupFileIds = intArrayOf(0, 166, 397, 652, 240, 610, 0, 337, 0, 0, 0, 1405, 0, 0, 0, 331, 2135, 0, 1244, 1413, 103, 9, 1265, 269, 11, 394, 1790, 0, 0, 0, 0, 8, 2024, 178, 100, 191, 1071, 28, 1, 2, 4, 360, 115, 236, 404, 47, 28, 21, 1, 0, 0, 0, 0, 0, 141) - - var capacity = 0 - (0 until 37).forEach { - capacity += Variable.asSizeBytes(7, groupFileIds[groupIds[it]]) - } - val buffer = ByteBuffer.allocate(capacity) - - val decodedFileIds = decodeGroupFileIds( - 55, - 37, - groupIds, - buffer, - 7 - ) - - val encodedFileIds = encodeGroupFileIds( - 37, - groupIds, - 7, - decodedFileIds - ) - - assertTrue(buffer.array().contentEquals(encodedFileIds.array())) - } -} \ No newline at end of file diff --git a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt new file mode 100644 index 0000000..4d0490f --- /dev/null +++ b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt @@ -0,0 +1,44 @@ +package com.runetopic.cache.store.storage.js5.io.dat + +import com.runetopic.cache.store.storage.js5.io.dat.sector.DatIndexSector +import io.mockk.confirmVerified +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import java.nio.ByteBuffer +import java.util.stream.Stream +import kotlin.random.Random +import kotlin.random.nextInt +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * @author Jordan Abraham + */ +class DatSectorTest { + + @Test + fun `test nameHashes`() { + val count = Random.nextInt(10..50) + val groupIds = Stream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toList().toIntArray() + val maxGroupId = (groupIds.maxOrNull() ?: 0) + 1 + + val buffer = ByteBuffer.wrap(Random.nextBytes(count * Int.SIZE_BYTES)) + + val indexSector = mockk() + every { indexSector.decodeGroupRevisions(any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupRevisions(any(), any(), any()) } answers { callOriginal() } + + val decoded = indexSector.decodeGroupRevisions(maxGroupId, count, groupIds, buffer) + val encoded = indexSector.encodeGroupRevisions(count, groupIds, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupRevisions(maxGroupId, count, groupIds, buffer) } + verify(exactly = 1) { indexSector.encodeGroupRevisions(count, groupIds, decoded) } + confirmVerified(indexSector) + } +} \ No newline at end of file From d8f26e5924088ff970f17b61fa4d360b8c00ce58 Mon Sep 17 00:00:00 2001 From: Jordan Date: Tue, 26 Oct 2021 17:37:40 -0400 Subject: [PATCH 03/14] Add more testing for DatIndexSector. --- .../js5/io/dat/sector/DatIndexSector.kt | 12 +-- .../store/storage/js5/io/dat/DatSectorTest.kt | 80 ++++++++++++++++++- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt index eda782f..2c9c5d2 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt @@ -190,7 +190,7 @@ internal data class DatIndexSector( return buffer } - private fun decodeGroupWhirlpools( + fun decodeGroupWhirlpools( maxGroupId: Int, usesWhirlpool: Boolean, count: Int, @@ -208,7 +208,7 @@ internal data class DatIndexSector( return whirlpools } - private fun encodeGroupWhirlpools( + fun encodeGroupWhirlpools( count: Int, groupIds: IntArray, usesWhirlpool: Boolean, @@ -223,7 +223,7 @@ internal data class DatIndexSector( return buffer } - private fun decodeGroupCrcs( + fun decodeGroupCrcs( maxGroupId: Int, count: Int, groupIds: IntArray, @@ -236,7 +236,7 @@ internal data class DatIndexSector( return crcs } - private fun encodeGroupCrcs( + fun encodeGroupCrcs( count: Int, groupIds: IntArray, crcs: IntArray @@ -248,7 +248,7 @@ internal data class DatIndexSector( return buffer } - private fun decodeGroupNameHashes( + fun decodeGroupNameHashes( maxGroupId: Int, count: Int, isNamed: Boolean, @@ -264,7 +264,7 @@ internal data class DatIndexSector( return nameHashes } - private fun encodeGroupNameHashes( + fun encodeGroupNameHashes( count: Int, isNamed: Boolean, groupIds: IntArray, diff --git a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt index 4d0490f..a5844a4 100644 --- a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt +++ b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt @@ -6,6 +6,9 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import java.nio.ByteBuffer +import java.util.concurrent.ThreadLocalRandom +import java.util.function.IntSupplier +import java.util.stream.IntStream import java.util.stream.Stream import kotlin.random.Random import kotlin.random.nextInt @@ -17,10 +20,85 @@ import kotlin.test.assertEquals */ class DatSectorTest { + @Test + fun `test whirlpools`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + + val buffer = ByteBuffer.wrap(Random.nextBytes(count * 64)) + + val indexSector = mockk() + every { indexSector.decodeGroupWhirlpools(any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupWhirlpools(any(), any(), any(), any()) } answers { callOriginal() } + + val decoded = indexSector.decodeGroupWhirlpools(maxGroupId, true, count, buffer, groupIds) + val encoded = indexSector.encodeGroupWhirlpools(count, groupIds, true, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupWhirlpools(maxGroupId, true, count, buffer, groupIds) } + verify(exactly = 1) { indexSector.encodeGroupWhirlpools(count, groupIds, true, decoded) } + confirmVerified(indexSector) + } + + @Test + fun `test crcs`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + + val buffer = ByteBuffer.wrap(Random.nextBytes(count * Int.SIZE_BYTES)) + + val indexSector = mockk() + every { indexSector.decodeGroupCrcs(any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupCrcs(any(), any(), any()) } answers { callOriginal() } + + val decoded = indexSector.decodeGroupCrcs(maxGroupId, count, groupIds, buffer) + val encoded = indexSector.encodeGroupCrcs(count, groupIds, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupCrcs(maxGroupId, count, groupIds, buffer) } + verify(exactly = 1) { indexSector.encodeGroupCrcs(count, groupIds, decoded) } + confirmVerified(indexSector) + } + @Test fun `test nameHashes`() { val count = Random.nextInt(10..50) - val groupIds = Stream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toList().toIntArray() + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + + val buffer = ByteBuffer.wrap(Random.nextBytes(count * Int.SIZE_BYTES)) + + val indexSector = mockk() + every { indexSector.decodeGroupNameHashes(any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupNameHashes(any(), any(), any(), any()) } answers { callOriginal() } + + val decoded = indexSector.decodeGroupNameHashes(maxGroupId, count, true, groupIds, buffer) + val encoded = indexSector.encodeGroupNameHashes(count, true, groupIds, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupNameHashes(maxGroupId, count, true, groupIds, buffer) } + verify(exactly = 1) { indexSector.encodeGroupNameHashes(count, true, groupIds, decoded) } + confirmVerified(indexSector) + } + + @Test + fun `test revisions`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() val maxGroupId = (groupIds.maxOrNull() ?: 0) + 1 val buffer = ByteBuffer.wrap(Random.nextBytes(count * Int.SIZE_BYTES)) From 1c12077b8ce8ec05e954a91fb7ee6461a03d44bf Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 31 Oct 2021 04:32:38 -0400 Subject: [PATCH 04/14] Add more tests to DatIndexSector and more work to the encoding. --- .../runetopic/cache/extension/ByteBuffer.kt | 13 +- .../cache/store/storage/js5/Js5DiskStorage.kt | 2 +- .../js5/io/dat/sector/DatIndexSector.kt | 115 ++++++++++-------- .../store/storage/js5/io/dat/DatSectorTest.kt | 83 ++++++++++++- 4 files changed, 147 insertions(+), 66 deletions(-) diff --git a/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt index d52c794..0fc8445 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt @@ -11,22 +11,11 @@ internal fun ByteBuffer.putIntShortSmart(value: Int) { value >= Short.MAX_VALUE -> ByteBuffer.allocate(Int.SIZE_BYTES).putInt(value - Int.MAX_VALUE - 1) else -> ByteBuffer.allocate(Short.SIZE_BYTES).putShort(if (value >= 0) value.toShort() else Short.MAX_VALUE) } - put(buffer) + get(buffer.array()) } internal fun ByteBuffer.remainingBytes(): ByteArray { val bytes = ByteArray(remaining()) get(bytes) return bytes -} - -class Variable { - companion object { - internal fun asSizeBytes( - protocol: Int, - value: Int - ): Int { - return if (protocol >= 7) if (value >= Short.MAX_VALUE) 4 else 2 else 2 - } - } } \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt index 70f9c97..f6c1497 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt @@ -6,10 +6,10 @@ import com.runetopic.cache.store.Constants import com.runetopic.cache.store.Js5Store import com.runetopic.cache.store.storage.IStorage import com.runetopic.cache.store.storage.js5.io.dat.DatFile -import com.runetopic.cache.store.storage.js5.io.idx.IdxFile import com.runetopic.cache.store.storage.js5.io.dat.IDatFile import com.runetopic.cache.store.storage.js5.io.dat.sector.DatIndexSector import com.runetopic.cache.store.storage.js5.io.idx.IIdxFile +import com.runetopic.cache.store.storage.js5.io.idx.IdxFile import java.io.FileNotFoundException import java.nio.file.Path import java.util.concurrent.CopyOnWriteArrayList diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt index 2c9c5d2..7477cdb 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt @@ -43,9 +43,9 @@ internal data class DatIndexSector( val groupCrcs = decodeGroupCrcs(maxGroupId, count, groupIds, buffer) val groupWhirlpools = decodeGroupWhirlpools(maxGroupId, isUsingWhirlpool, count, buffer, groupIds) val groupRevisions = decodeGroupRevisions(maxGroupId, count, groupIds, buffer) - val fileSizes = decodeGroupFileSizes(maxGroupId, count, groupIds, buffer, protocol) - val fileIds = decodeFileIds(maxGroupId, fileSizes, count, groupIds, buffer, protocol) - val fileNameHashes = decodeFileNameHashes(maxGroupId, fileSizes, count, groupIds, buffer, isNamed) + val groupFileSizes = decodeGroupFileSizes(maxGroupId, count, groupIds, buffer, protocol) + val groupFileIds = decodeGroupFileIds(maxGroupId, groupFileSizes, count, groupIds, buffer, protocol) + val fileNameHashes = decodeFileNameHashes(maxGroupId, groupFileSizes, count, groupIds, buffer, isNamed) val groups = hashMapOf() (0 until count).forEach { @@ -59,10 +59,10 @@ internal data class DatIndexSector( } val groupSector = DatGroupSector( - fileIds, + groupFileIds, fileNameHashes, data, - fileSizes[groupId], + groupFileSizes[groupId], groupId ) @@ -77,7 +77,17 @@ internal data class DatIndexSector( data )) } - return Index(idxFile.id(), decompressed.crc, data.toWhirlpool(), decompressed.compression, protocol, revision, isNamed, isUsingWhirlpool, groups) + return Index( + idxFile.id(), + decompressed.crc, + data.toWhirlpool(), + decompressed.compression, + protocol, + revision, + isNamed, + isUsingWhirlpool, + groups + ) } override fun encode(override: Index): ByteArray { @@ -93,74 +103,89 @@ internal data class DatIndexSector( if (override.protocol >= 7) header.putIntShortSmart(count) else header.putShort(count.toShort()) val stream = override.groups().stream() - val groupIds = stream.map { it.id }.toList().toIntArray() + val ids = stream.map { it.id }.toList().toIntArray() val nameHashes = stream.map { it.nameHash }.toList().toIntArray() val crcs = stream.map { it.crc }.toList().toIntArray() val whirlpools = stream.map { it.whirlpool }.toList().toTypedArray() val revisions = stream.map { it.revision }.toList().toIntArray() - - val groupNameHashes = encodeGroupNameHashes(count, override.isNamed, groupIds, nameHashes) - val groupCrcs = encodeGroupCrcs(count, groupIds, crcs) - val groupWhirlpools = encodeGroupWhirlpools(count, groupIds, override.isUsingWhirlpool, whirlpools) - val groupRevisions = encodeGroupRevisions(count, groupIds, revisions) - - val buffer = ByteBuffer.allocate(header.position() + val fileSizes = stream.map { it.files().size }.toList().toIntArray() + + val groupIds = encodeGroupIds(count, override.protocol, ids) + val groupNameHashes = encodeGroupNameHashes(count, override.isNamed, ids, nameHashes) + val groupCrcs = encodeGroupCrcs(count, ids, crcs) + val groupWhirlpools = encodeGroupWhirlpools(count, ids, override.isUsingWhirlpool, whirlpools) + val groupRevisions = encodeGroupRevisions(count, ids, revisions) + val groupFileSizes = encodeGroupFileSizes(count, ids, override.protocol, fileSizes) + + val buffer = ByteBuffer.allocate( + /**/header.position() + + groupIds.position() + groupNameHashes.position() + groupCrcs.position() + groupWhirlpools.position() - + groupRevisions.position()) + + groupRevisions.position() + + groupFileSizes.position() + ) buffer.put(header) - //TODO Group ids + buffer.put(groupIds) buffer.put(groupNameHashes) buffer.put(groupCrcs) buffer.put(groupWhirlpools) buffer.put(groupRevisions) + buffer.put(groupFileSizes) //TODO The rest return buffer.array() } - private fun decodeGroupIds( + fun decodeGroupIds( count: Int, buffer: ByteBuffer, protocol: Int ): IntArray { val groupIds = IntArray(count) - var offset = 0 (0 until count).forEach { - groupIds[it] = if (protocol >= 7) { buffer.readUnsignedIntShortSmart() } else { buffer.readUnsignedShort() } - .let { id -> offset += id; offset } + groupIds[it] = (if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort()) + if (it == 0) 0 else groupIds[it - 1] } return groupIds } - private fun decodeGroupFileSizes( + fun encodeGroupIds( + count: Int, + protocol: Int, + groupIds: IntArray + ): ByteBuffer { + val buffer = ByteBuffer.allocate(groupIds.sumOf { (if (protocol >= 7) if (it >= Short.MAX_VALUE) 4 else 2 else 2).toInt() }) + (0 until count).forEach { + val value = groupIds[it] - if (it == 0) 0 else groupIds[it - 1] + if (protocol >= 7) buffer.putIntShortSmart(value) else buffer.putShort(value.toShort()) + } + return buffer + } + + fun decodeGroupFileSizes( maxGroupId: Int, count: Int, groupIds: IntArray, buffer: ByteBuffer, protocol: Int ): IntArray { - val groupFileIds = IntArray(maxGroupId) + val groupFileSizes = IntArray(maxGroupId) (0 until count).forEach { - groupFileIds[groupIds[it]] = if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort() + groupFileSizes[groupIds[it]] = if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort() } - return groupFileIds + return groupFileSizes } - private fun encodeGroupFileSizes( + fun encodeGroupFileSizes( count: Int, groupIds: IntArray, protocol: Int, - groupFileIds: IntArray + groupFileSizes: IntArray ): ByteBuffer { - var capacity = 0 + val buffer = ByteBuffer.allocate(groupIds.sumOf { (if (protocol >= 7) if (it >= Short.MAX_VALUE) 4 else 2 else 2).toInt() }) (0 until count).forEach { - capacity += Variable.asSizeBytes(protocol, groupFileIds[groupIds[it]]) - } - val buffer = ByteBuffer.allocate(capacity) - (0 until count).forEach { - if (protocol >= 7) buffer.putIntShortSmart(groupFileIds[groupIds[it]]) else buffer.putShort(groupFileIds[groupIds[it]].toShort()) + if (protocol >= 7) buffer.putIntShortSmart(groupFileSizes[groupIds[it]]) else buffer.putShort(groupFileSizes[groupIds[it]].toShort()) } return buffer } @@ -279,28 +304,25 @@ internal data class DatIndexSector( return buffer } - private fun decodeFileIds( + private fun decodeGroupFileIds( maxGroupId: Int, - validFileIds: IntArray, + groupFileSizes: IntArray, count: Int, groupIds: IntArray, buffer: ByteBuffer, protocol: Int ): Array { - val fileIds = Array(maxGroupId) { IntArray(validFileIds[it]) } + val fileIds = Array(maxGroupId) { IntArray(groupFileSizes[it]) } (0 until count).forEach { val groupId = groupIds[it] - var offset = 0 - (0 until validFileIds[groupId]).forEach { fileId -> - if (protocol >= 7) { buffer.readUnsignedIntShortSmart() } else { buffer.readUnsignedShort() } - .let { i -> offset += i; offset } - .also { fileIds[groupId][fileId] = offset } + (0 until groupFileSizes[groupId]).forEach { fileId -> + fileIds[groupId][fileId] = (if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort()) + if (fileId == 0) 0 else fileIds[groupId][fileId - 1] } } return fileIds } - private fun decodeFileNameHashes( + fun decodeFileNameHashes( maxGroupId: Int, validFileIds: IntArray, count: Int, @@ -320,7 +342,7 @@ internal data class DatIndexSector( return fileNameHashes } - private fun encodeFileNameHashes( + fun encodeFileNameHashes( count: Int, groupIds: IntArray, validFileIds: IntArray, @@ -328,14 +350,7 @@ internal data class DatIndexSector( fileNameHashes: Array ): ByteBuffer { if (isNamed.not()) return ByteBuffer.allocate(0) - //TODO Figure out a way to calc the number of bytes without doing it like this. - var bytes = 0 - (0 until count).forEach { - (0 until validFileIds[groupIds[it]]).forEach { _ -> - bytes += Int.SIZE_BYTES - } - } - val buffer = ByteBuffer.allocate(bytes) + val buffer = ByteBuffer.allocate(validFileIds.sum() * Int.SIZE_BYTES) (0 until count).forEach { val groupId = groupIds[it] (0 until validFileIds[groupId]).forEach { fileId -> diff --git a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt index a5844a4..1548d5b 100644 --- a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt +++ b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt @@ -6,10 +6,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import java.nio.ByteBuffer -import java.util.concurrent.ThreadLocalRandom -import java.util.function.IntSupplier import java.util.stream.IntStream -import java.util.stream.Stream import kotlin.random.Random import kotlin.random.nextInt import kotlin.test.Test @@ -20,6 +17,86 @@ import kotlin.test.assertEquals */ class DatSectorTest { + @Test + fun `test ids protocol 6`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val protocol = 6 + + val indexSector = mockk() + every { indexSector.decodeGroupIds(any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupIds(any(), any(), any()) } answers { callOriginal() } + + val buffer = ByteBuffer.wrap(Random.nextBytes(groupIds.sumOf { (if (protocol >= 7) if (it >= Short.MAX_VALUE) 4 else 2 else 2).toInt() })) + + val decoded = indexSector.decodeGroupIds(count, buffer, protocol) + val encoded = indexSector.encodeGroupIds(count, protocol, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + println(buffer.array().contentToString()) + println(encoded.array().contentToString()) + + verify(exactly = 1) { indexSector.decodeGroupIds(count, buffer, protocol) } + verify(exactly = 1) { indexSector.encodeGroupIds(count, protocol, decoded) } + confirmVerified(indexSector) + } + + @Test + fun `test fileSizes protocol 6`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + val protocol = 6 + + val indexSector = mockk() + every { indexSector.decodeGroupFileSizes(any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupFileSizes(any(), any(), any(), any()) } answers { callOriginal() } + + val buffer = ByteBuffer.wrap(Random.nextBytes(groupIds.sumOf { (if (protocol >= 7) if (it >= Short.MAX_VALUE) 4 else 2 else 2).toInt() })) + + val decoded = indexSector.decodeGroupFileSizes(maxGroupId, count, groupIds, buffer, protocol) + val encoded = indexSector.encodeGroupFileSizes(count, groupIds, protocol, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupFileSizes(maxGroupId, count, groupIds, buffer, protocol) } + verify(exactly = 1) { indexSector.encodeGroupFileSizes(count, groupIds, protocol, decoded) } + confirmVerified(indexSector) + } + + @Test + fun `test file nameHashes`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + val validFileIds = IntArray(maxGroupId) { if (it in groupIds) Random.nextInt(1..10) else 0 } + + val indexSector = mockk() + every { indexSector.decodeFileNameHashes(any(), any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeFileNameHashes(any(), any(), any(), any(), any()) } answers { callOriginal() } + + val buffer = ByteBuffer.wrap(Random.nextBytes(validFileIds.sum() * Int.SIZE_BYTES)) + + val decoded = indexSector.decodeFileNameHashes(maxGroupId, validFileIds, count, groupIds, buffer, true) + val encoded = indexSector.encodeFileNameHashes(count, groupIds, validFileIds, true, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeFileNameHashes(maxGroupId, validFileIds, count, groupIds, buffer, true) } + verify(exactly = 1) { indexSector.encodeFileNameHashes(count, groupIds, validFileIds, true, decoded) } + confirmVerified(indexSector) + } + @Test fun `test whirlpools`() { val count = Random.nextInt(10..50) From 7eca1200208bb54b8cf8e47e8c54b46bc9c44754 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sun, 31 Oct 2021 04:41:56 -0400 Subject: [PATCH 05/14] Update to Kotlin 1.5.31 and update README.md. --- README.md | 1 + settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 83b5de7..f53daa8 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ A cache library written in Kotlin. # Requirements +- Kotlin Version 1.5 - Java Version 16 # Supported diff --git a/settings.gradle.kts b/settings.gradle.kts index 351b5c6..6b04d8d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ rootProject.name = "cache-lib" pluginManagement { plugins { - kotlin("jvm") version "1.5.21" + kotlin("jvm") version "1.5.31" } } From 9893df4bb93371fa75ef7e33c4fe481b9b9e5939 Mon Sep 17 00:00:00 2001 From: Jordan Date: Tue, 2 Nov 2021 01:14:53 -0400 Subject: [PATCH 06/14] Removing dat file --- .gitignore | 6 + LICENSE | 21 + README.md | 137 ++++++ build.gradle.kts | 33 ++ cache/.gitignore | 3 + cache/build.gradle.kts | 84 ++++ .../com/runetopic/cache/codec/CodecType.kt | 20 + .../com/runetopic/cache/codec/Container.kt | 34 ++ .../runetopic/cache/codec/ContainerCodec.kt | 115 +++++ .../runetopic/cache/codec/Decompression.kt | 8 + .../com/runetopic/cache/codec/IFileCodec.kt | 10 + .../runetopic/cache/codec/impl/BZip2Codec.kt | 43 ++ .../runetopic/cache/codec/impl/GZipCodec.kt | 29 ++ .../runetopic/cache/codec/impl/NoFileCodec.kt | 12 + .../cache/exception/CompressionException.kt | 7 + .../cache/exception/DatFileException.kt | 7 + .../cache/exception/EndOfDatFileException.kt | 7 + .../cache/exception/FileDataException.kt | 7 + .../cache/exception/FileNotFoundException.kt | 7 + .../cache/exception/IdxFileException.kt | 7 + .../cache/exception/ProtocolException.kt | 7 + .../runetopic/cache/extension/ByteArray.kt | 8 + .../runetopic/cache/extension/ByteBuffer.kt | 21 + .../com/runetopic/cache/extension/String.kt | 7 + .../cache/hierarchy/ReferenceTable.kt | 35 ++ .../runetopic/cache/hierarchy/index/Index.kt | 73 +++ .../cache/hierarchy/index/group/Group.kt | 69 +++ .../cache/hierarchy/index/group/file/File.kt | 39 ++ .../com/runetopic/cache/store/Constants.kt | 23 + .../com/runetopic/cache/store/Js5Store.kt | 112 +++++ .../runetopic/cache/store/storage/IStorage.kt | 19 + .../cache/store/storage/js5/Js5DiskStorage.kt | 122 +++++ .../cache/store/storage/js5/io/dat/DatFile.kt | 92 ++++ .../store/storage/js5/io/dat/IDatFile.kt | 13 + .../store/storage/js5/io/dat/IDatSector.kt | 9 + .../js5/io/dat/sector/DatGroupSector.kt | 85 ++++ .../js5/io/dat/sector/DatIndexSector.kt | 435 ++++++++++++++++++ .../store/storage/js5/io/idx/IIdxFile.kt | 15 + .../cache/store/storage/js5/io/idx/IdxFile.kt | 48 ++ .../main/resources/simplelogger.properties | 1 + .../store/storage/js5/io/dat/DatSectorTest.kt | 290 ++++++++++++ gradle.properties | 4 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 ++++++++ gradlew.bat | 89 ++++ loader/build.gradle.kts | 70 +++ .../com/runetopic/loader/IEntryBuilder.kt | 12 + .../com/runetopic/loader/IEntryProvider.kt | 13 + .../kotlin/com/runetopic/loader/IEntryType.kt | 8 + .../kotlin/com/runetopic/loader/Loader.kt | 55 +++ .../runetopic/loader/extension/ByteArray.kt | 8 + .../runetopic/loader/extension/ByteBuffer.kt | 85 ++++ .../com/runetopic/loader/extension/Int.kt | 8 + .../config/idk/IdentityKitEntryBuilder.kt | 71 +++ .../config/idk/IdentityKitEntryProvider.kt | 18 + .../index/config/idk/IdentityKitEntryType.kt | 57 +++ .../index/config/inv/InventoryEntryBuilder.kt | 34 ++ .../config/inv/InventoryEntryProvider.kt | 18 + .../index/config/inv/InventoryEntryType.kt | 11 + .../config/lighting/LightingEntryBuilder.kt | 38 ++ .../config/lighting/LightingEntryProvider.kt | 19 + .../config/lighting/LightingEntryType.kt | 13 + .../config/mouseicon/MouseIconEntryBuilder.kt | 38 ++ .../mouseicon/MouseIconEntryProvider.kt | 18 + .../config/mouseicon/MouseIconEntryType.kt | 13 + .../config/overlay/OverlayEntryBuilder.kt | 51 ++ .../config/overlay/OverlayEntryProvider.kt | 17 + .../index/config/overlay/OverlayEntryType.kt | 23 + .../index/config/param/ParamEntryBuilder.kt | 39 ++ .../index/config/param/ParamEntryProvider.kt | 18 + .../index/config/param/ParamEntryType.kt | 13 + .../index/config/skybox/SkyBoxEntryBuilder.kt | 42 ++ .../config/skybox/SkyBoxEntryProvider.kt | 18 + .../index/config/skybox/SkyBoxEntryType.kt | 36 ++ .../index/config/struct/StructEntryBuilder.kt | 39 ++ .../config/struct/StructEntryProvider.kt | 18 + .../index/config/struct/StructEntryType.kt | 10 + .../config/underlay/UnderlayEntryBuilder.kt | 41 ++ .../config/underlay/UnderlayEntryProvider.kt | 17 + .../config/underlay/UnderlayEntryType.kt | 17 + .../loader/index/loc/LocEntryBuilder.kt | 263 +++++++++++ .../loader/index/loc/LocEntryProvider.kt | 19 + .../loader/index/loc/LocEntryType.kt | 92 ++++ .../loader/index/map/MapEntryBuilder.kt | 162 +++++++ .../loader/index/map/MapEntryProvider.kt | 19 + .../loader/index/map/MapEntryType.kt | 96 ++++ .../index/map/MapLocationEntryBuilder.kt | 67 +++ .../index/map/MapLocationEntryProvider.kt | 19 + .../loader/index/map/MapLocationEntryType.kt | 25 + .../loader/index/npc/NpcEntryBuilder.kt | 213 +++++++++ .../loader/index/npc/NpcEntryProvider.kt | 17 + .../loader/index/npc/NpcEntryType.kt | 75 +++ .../loader/index/obj/ObjEntryBuilder.kt | 150 ++++++ .../loader/index/obj/ObjEntryProvider.kt | 17 + .../loader/index/obj/ObjEntryType.kt | 236 ++++++++++ .../index/particle/ParticleEntryBuilder.kt | 107 +++++ .../index/particle/ParticleEntryProvider.kt | 17 + .../index/particle/ParticleEntryType.kt | 163 +++++++ .../spotanim/SpotAnimationEntryBuilder.kt | 84 ++++ .../spotanim/SpotAnimationEntryProvider.kt | 18 + .../index/spotanim/SpotAnimationEntryType.kt | 83 ++++ .../runetopic/loader/util/vector/Vector3f.kt | 7 + settings.gradle.kts | 21 + 104 files changed, 5379 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.gradle.kts create mode 100644 cache/.gitignore create mode 100644 cache/build.gradle.kts create mode 100644 cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/codec/Container.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/codec/Decompression.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/codec/IFileCodec.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/exception/CompressionException.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/exception/DatFileException.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/exception/EndOfDatFileException.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/exception/FileDataException.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/exception/FileNotFoundException.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/exception/IdxFileException.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/exception/ProtocolException.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/extension/ByteArray.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/extension/String.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/hierarchy/ReferenceTable.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/Group.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/file/File.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/Constants.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/IStorage.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt create mode 100644 cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt create mode 100644 cache/src/main/resources/simplelogger.properties create mode 100644 cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 loader/build.gradle.kts create mode 100644 loader/src/main/kotlin/com/runetopic/loader/IEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/IEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/IEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/Loader.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/extension/ByteArray.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/extension/ByteBuffer.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/extension/Int.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryProvider.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryType.kt create mode 100644 loader/src/main/kotlin/com/runetopic/loader/util/vector/Vector3f.kt create mode 100644 settings.gradle.kts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ad0979 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build/ +.idea/ +.gradle/ +data/cache/ +/data/dumps/ +/app/* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..79a8131 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Xlite + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f53daa8 --- /dev/null +++ b/README.md @@ -0,0 +1,137 @@ +# RuneTopic Cache Library + +[![Discord](https://img.shields.io/discord/212385463418355713?color=%237289DA&logo=Discord&logoColor=%237289DA)](https://discord.gg/3scgBkrfMG) +[![License](https://img.shields.io/github/license/xlite2/xlite)](#) + +A cache library written in Kotlin. + +# Requirements +- Kotlin Version 1.5 +- Java Version 16 + +# Supported +- RS2 (414-772) +- RS3 (773-~788) +- OSRS (1-current) + +# Features +- Cache Reading +- Definitions/Providers Loading +- Very Fast (Limited by I/O) + +# TODO +- Cache Writing +- Flat File System +- Ondemand Data Caching +- 317 and older support +- Tests + +# Implementation +Just use cache if you do not require any of the revision specific loaders. +``` +cache = { module = "com.runetopic.cache:cache", version.ref "1.4.19-SNAPSHOT" } +loader = { module = "com.runetopic.cache:loader", version.ref "647.6.4-SNAPSHOT" } +``` + +``` +//SNAPSHOTS +maven { + url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") +} +``` + +# Usage +Index -> Group -> File + +### Creating a new JS5 store +``` +val store = Js5Store(path = Path.of("/path/"), parallel = true) +``` + +### Getting an index +``` +val index = store.index(indexId = 5) +``` + +### Getting a group by group id +``` +val index = store.index(indexId = 5) +val group = index.group(groupId = 360) +``` + +### Getting a group by group name +``` +val index = store.index(indexId = 5) +val group = index.group(groupName = "m50_50") +``` + +### Getting a file from a group by id +``` +val index = store.index(indexId = 2) +val group = index.group(groupId = 26) +val file = group.file(fileId = 1000) +``` + +### Looping multiple groups from an index + store.index(indexId = 19).use { index -> + (0 until index.expand()).forEach { + val data = index.group(it ushr 8).file(it and 0xFF).data + } + } + +### Looping multiple files from a group + store.index(indexId = 2).group(groupId = 26).files().forEach { + val id = it.id + val data = it.data + } + +### Getting the reference table of an index and group by id. +```store.groupReferenceTable(indexId = 255, groupId = 255)``` + +### Getting an index reference table size by id +```store.indexReferenceTableSize(indexId = 28)``` + +### Getting a group reference table size by name +```store.groupReferenceTableSize(indexId = 30, groupName = "windows/x86/jaclib.dll")``` + +### Getting a group reference table size by id +```store.groupReferenceTableSize(indexId = 30, groupId = 6)``` + +### Getting 255, 255 checksums with RSA/Whirlpool +```val checksums = store.checksumsWithRSA(exponent = BigInteger(""), modulus = BigInteger(""))``` + +### Getting 255, 255 checksums without RSA/Whirlpool +```val checksums = store.checksumsWithoutRSA()``` + +### An example of a single thread loading providers +``` +objs().load(store) +npcs().load(store) +locs().load(store) +particles().load(store) +``` + +### An example of multiple threads parallel loading providers +``` +val pool = Executors.newFixedThreadPool(4) +val providers = listOf( + objs(), + npcs(), + locs(), + particles() +) +val latch = CountDownLatch(providers.size) +providers.forEach { + pool.execute { + it.load(store) + latch.countDown() + } +} +latch.await() +pool.shutdown() +``` + +## Contributing +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +Please make sure to write unit test and or update any test that might have been impacted. diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..3f53df5 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,33 @@ +import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper + +plugins { + kotlin("jvm") +} + +configure(allprojects) { + group = "com.runetopic.cache" + + plugins.withType { + java.sourceCompatibility = JavaVersion.VERSION_16 + java.targetCompatibility = JavaVersion.VERSION_16 + + tasks { + compileKotlin { + kotlinOptions.jvmTarget = "16" + kotlinOptions.freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn") + } + compileTestKotlin { + kotlinOptions.jvmTarget = "16" + kotlinOptions.freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn") + } + } + } +} + +configure(subprojects) { + plugins.withType { + dependencies { + implementation(kotlin("stdlib")) + } + } +} diff --git a/cache/.gitignore b/cache/.gitignore new file mode 100644 index 0000000..ef7cf87 --- /dev/null +++ b/cache/.gitignore @@ -0,0 +1,3 @@ +build/ +src/test/resources/main_file_cache.dat2 +src/test/resources/main_file_cache.idx10 \ No newline at end of file diff --git a/cache/build.gradle.kts b/cache/build.gradle.kts new file mode 100644 index 0000000..73e3217 --- /dev/null +++ b/cache/build.gradle.kts @@ -0,0 +1,84 @@ +plugins { + kotlin("jvm") + `maven-publish` + signing +} + +version = "1.4.20-SNAPSHOT" + +java { + withJavadocJar() + withSourcesJar() +} + +publishing { + publications { + create("mavenJava") { + pom { + packaging = "jar" + name.set("Xlite Cache Library") + description.set("Cache Library for reading and writing to the jagex cache in the 647 protocol.") + url.set("https://github.com/xlite2/cache-lib") + + developers { + developer { + id.set("tylert") + name.set("Tyler Telis") + email.set("xlitersps@gmail.com") + } + + developer { + id.set("ultraviolet-jordan") + name.set("Jordan Abraham") + } + } + + scm { + connection.set("scm:git:git://github.com/runetopic/cache-lib.git") + developerConnection.set("scm:git:ssh://github.com/runetopic/cache-lib.git") + url.set("http://github.com/rune-topic/") + } + } + + artifact(tasks["javadocJar"]) + artifact(tasks["sourcesJar"]) + } + create("maven") { + groupId = project.group.toString() + artifactId = project.name + version = project.version.toString() + + from(components["java"]) + } + } + repositories { + val ossrhUsername: String by project + val ossrhPassword: String by project + + maven { + val releasesRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/releases/") + val snapshotsRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") + url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl + credentials { + username = ossrhUsername + password = ossrhPassword + } + } + } +} + + +signing { + sign(publishing.publications["mavenJava"]) +} + +dependencies { + implementation("org.apache.commons:commons-compress:1.21") + implementation("com.michael-bull.kotlin-inline-logger:kotlin-inline-logger:1.0.3") + implementation("org.slf4j:slf4j-simple:1.7.32") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.+") + implementation("com.runetopic.cryptography:cryptography:1.0.6-SNAPSHOT") + + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("io.mockk:mockk:1.12.0") +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt new file mode 100644 index 0000000..f7642a3 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt @@ -0,0 +1,20 @@ +package com.runetopic.cache.codec + +import com.runetopic.cache.codec.impl.BZip2Codec +import com.runetopic.cache.codec.impl.GZipCodec +import com.runetopic.cache.codec.impl.NoFileCodec + +/** + * @author Tyler Telis + * @email + * + * @author Jordan Abraham + */ +internal sealed class CodecType( + val codec: IFileCodec +) { + object BadCodec: CodecType(NoFileCodec()) + object NoCodec: CodecType(NoFileCodec()) + object BZipCodec: CodecType(BZip2Codec()) + object GZipCodec: CodecType(GZipCodec()) +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/Container.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/Container.kt new file mode 100644 index 0000000..63f8760 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/Container.kt @@ -0,0 +1,34 @@ +package com.runetopic.cache.codec + +/** + * @author Tyler Telis + * @email + */ +internal data class Container( + val data: ByteArray, + val compression: Int, + val revision: Int, + val crc: Int +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Container + + if (!data.contentEquals(other.data)) return false + if (compression != other.compression) return false + if (revision != other.revision) return false + if (crc != other.crc) return false + + return true + } + + override fun hashCode(): Int { + var result = data.contentHashCode() + result = 31 * result + compression + result = 31 * result + revision + result = 31 * result + crc + return result + } +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt new file mode 100644 index 0000000..afd6fb1 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt @@ -0,0 +1,115 @@ +package com.runetopic.cache.codec + +import com.runetopic.cache.codec.CodecType.* +import com.runetopic.cache.exception.CompressionException +import com.runetopic.cache.extension.readUnsignedByte +import com.runetopic.cache.extension.readUnsignedShort +import com.runetopic.cache.extension.remainingBytes +import com.runetopic.cache.extension.toByteBuffer +import com.runetopic.cryptography.fromXTEA +import java.lang.Exception +import java.nio.ByteBuffer +import java.util.zip.CRC32 + +/** + * @author Tyler Telis + * @email + * + * @author Jordan Abraham + */ +internal object ContainerCodec { + + fun compress( + compression: Int, + revision: Int, + data: ByteArray, + keys: IntArray = intArrayOf() + ): ByteBuffer { + val codec = compressionCodec(compression) + val compressed = if (codec != NoCodec) codec.codec.compress(data, keys) else data + val buffer = ByteBuffer.allocate((if (codec != NoCodec) 9 else 5) + compressed.size/* + if (revision == -1) 0 else 2*/) + buffer.put(compressionType(codec).toByte()) + buffer.putInt(compressed.size) + if (codec != NoCodec) { + buffer.putInt(data.size) + } + buffer.put(compressed) + if (keys.isNotEmpty()) { + //TODO xtea + } + if (revision != -1) { + //buffer.putShort(revision.toShort()) + } + return buffer + } + + fun decompress( + data: ByteArray, + keys: IntArray = intArrayOf() + ): Container { + val buffer = data.toByteBuffer() + val compression = buffer.readUnsignedByte() + val length = buffer.int + + if (length < 0 || length > 2000000) { + throw CompressionException("Compression issue. Length=[$length]") + } + + val crc32 = CRC32() + crc32.update(data, 0, 5) + + return when (val type = compressionCodec(compression)) { + BadCodec -> throw CompressionException("Compression type not found with a compression opcode of $compression.") + is NoCodec -> { + val encrypted = ByteArray(length) + buffer.get(encrypted, 0, length) + crc32.update(encrypted, 0, length) + val decrypted = if (keys.isEmpty()) encrypted else encrypted.fromXTEA(32, keys) + + val revision = -1 /*buffer.short.toInt() and 0xFFFF*/ + + Container(decrypted, compression, revision, crc32.value.toInt()) + } + GZipCodec, BZipCodec-> { + val encrypted = ByteArray(length + 4) + buffer.get(encrypted) + crc32.update(encrypted, 0, encrypted.size) + val decrypted = if (keys.isEmpty()) encrypted else encrypted.fromXTEA(32, keys) + + var revision = -1 + + if (buffer.remaining() >= 2) { + revision = buffer.readUnsignedShort() + } + + val byteBuffer = decrypted.toByteBuffer() + val decompressedLength = byteBuffer.int + val decompressedData = type.codec.decompress(byteBuffer.remainingBytes(), length, keys) + + if (decompressedData.size != decompressedLength) { + throw CompressionException("Compression size mismatch.") + } + + Container(decompressedData, compression, revision, crc32.value.toInt()) + } + } + } + + private fun compressionType(codecType: CodecType): Int { + return when (codecType) { + is NoCodec -> 0 + is BZipCodec -> 1 + is GZipCodec -> 2 + else -> throw Exception("Bad compression type.") + } + } + + private fun compressionCodec(compression: Int): CodecType { + return when (compression) { + 0 -> NoCodec + 1 -> BZipCodec + 2 -> GZipCodec + else -> BadCodec + } + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/Decompression.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/Decompression.kt new file mode 100644 index 0000000..670f9bc --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/Decompression.kt @@ -0,0 +1,8 @@ +@file:JvmName("Decompression") +package com.runetopic.cache.codec + +/** + * @author Jordan Abraham + */ +fun ByteArray.decompress(keys: IntArray): ByteArray = ContainerCodec.decompress(this, keys).data +fun ByteArray.decompress(): ByteArray = ContainerCodec.decompress(this, intArrayOf()).data \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/IFileCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/IFileCodec.kt new file mode 100644 index 0000000..39526a8 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/IFileCodec.kt @@ -0,0 +1,10 @@ +package com.runetopic.cache.codec + +/** + * @author Tyler Telis + * @email + */ +internal interface IFileCodec { + fun compress(data: ByteArray, keys: IntArray): ByteArray + fun decompress(data: ByteArray, length: Int, keys: IntArray): ByteArray +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt new file mode 100644 index 0000000..d133976 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt @@ -0,0 +1,43 @@ +package com.runetopic.cache.codec.impl + +import com.runetopic.cache.codec.IFileCodec +import com.runetopic.cache.store.Constants.BZIP_HEADER +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream +import org.apache.commons.compress.utils.IOUtils +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.util.* + +/** + * @author Tyler Telis + * @email + */ +internal class BZip2Codec: IFileCodec { + override fun compress(data: ByteArray, keys: IntArray): ByteArray { + val stream: InputStream = ByteArrayInputStream(data) + val bout = ByteArrayOutputStream() + BZip2CompressorOutputStream(bout, 1).use { os -> IOUtils.copy(stream, os) } + + val buffer = bout.toByteArray() + + assert(BZIP_HEADER[0] == buffer[0]) + assert(BZIP_HEADER[1] == buffer[1]) + assert(BZIP_HEADER[2] == buffer[2]) + assert(BZIP_HEADER[3] == buffer[3]) + + return Arrays.copyOfRange(buffer, BZIP_HEADER.size, buffer.size) + } + + override fun decompress(data: ByteArray, length: Int, keys: IntArray): ByteArray { + val buffer = ByteArray(length + BZIP_HEADER.size) + + System.arraycopy(BZIP_HEADER, 0, buffer,0, BZIP_HEADER.size) + System.arraycopy(data, 0, buffer, BZIP_HEADER.size, length) + + val stream = ByteArrayOutputStream() + BZip2CompressorInputStream(ByteArrayInputStream(buffer)).use { IOUtils.copy(it, stream) } + return stream.toByteArray() + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt new file mode 100644 index 0000000..567e7d7 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt @@ -0,0 +1,29 @@ +package com.runetopic.cache.codec.impl + +import com.runetopic.cache.codec.IFileCodec +import org.apache.commons.compress.utils.IOUtils +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream + +/** + * @author Tyler Telis + * @email + */ +internal class GZipCodec: IFileCodec { + override fun compress(data: ByteArray, keys: IntArray): ByteArray { + val inputStream = ByteArrayInputStream(data) + val outputStream = ByteArrayOutputStream() + GZIPOutputStream(outputStream).use { os -> IOUtils.copy(inputStream, os) } + return outputStream.toByteArray() + } + + override fun decompress(data: ByteArray, length: Int, keys: IntArray): ByteArray { + val outputStream = ByteArrayOutputStream() + GZIPInputStream(ByteArrayInputStream(data, 0, length)).use { + IOUtils.copy(it, outputStream) + } + return outputStream.toByteArray() + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt new file mode 100644 index 0000000..0d29ae1 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt @@ -0,0 +1,12 @@ +package com.runetopic.cache.codec.impl + +import com.runetopic.cache.codec.IFileCodec + +/** + * @author Tyler Telis + * @email + */ +internal class NoFileCodec: IFileCodec { + override fun compress(data: ByteArray, keys: IntArray): ByteArray = throw NotImplementedError("No codec provided.") + override fun decompress(data: ByteArray, length: Int, keys: IntArray): ByteArray = throw NotImplementedError("No codec provided.") +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/CompressionException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/CompressionException.kt new file mode 100644 index 0000000..82cbed5 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/CompressionException.kt @@ -0,0 +1,7 @@ +package com.runetopic.cache.exception + +/** + * @author Tyler Telis + * @email + */ +internal class CompressionException(override val message: String): RuntimeException(message) \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/DatFileException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/DatFileException.kt new file mode 100644 index 0000000..b5e84e5 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/DatFileException.kt @@ -0,0 +1,7 @@ +package com.runetopic.cache.exception + +/** + * @author Tyler Telis + * @email + */ +internal class DatFileException(override val message: String): RuntimeException(message) \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/EndOfDatFileException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/EndOfDatFileException.kt new file mode 100644 index 0000000..709969c --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/EndOfDatFileException.kt @@ -0,0 +1,7 @@ +package com.runetopic.cache.exception + +/** + * @author Tyler Telis + * @email + */ +internal class EndOfDatFileException(override val message: String): RuntimeException(message) \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/FileDataException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/FileDataException.kt new file mode 100644 index 0000000..2d646ce --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/FileDataException.kt @@ -0,0 +1,7 @@ +package com.runetopic.cache.exception + +/** + * @author Tyler Telis + * @email + */ +internal class FileDataException(override val message: String): RuntimeException(message) \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/FileNotFoundException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/FileNotFoundException.kt new file mode 100644 index 0000000..fa7aebd --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/FileNotFoundException.kt @@ -0,0 +1,7 @@ +package com.runetopic.cache.exception + +/** + * @author Tyler Telis + * @email + */ +internal class FileNotFoundException(override val message: String): RuntimeException(message) \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/IdxFileException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/IdxFileException.kt new file mode 100644 index 0000000..70e2bd7 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/IdxFileException.kt @@ -0,0 +1,7 @@ +package com.runetopic.cache.exception + +/** + * @author Tyler Telis + * @email + */ +internal class IdxFileException(override val message: String): RuntimeException(message) \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/ProtocolException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/ProtocolException.kt new file mode 100644 index 0000000..97b9d4e --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/ProtocolException.kt @@ -0,0 +1,7 @@ +package com.runetopic.cache.exception + +/** + * @author Tyler Telis + * @email + */ +internal class ProtocolException(override val message: String): RuntimeException(message) \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/extension/ByteArray.kt b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteArray.kt new file mode 100644 index 0000000..5e1fc85 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteArray.kt @@ -0,0 +1,8 @@ +package com.runetopic.cache.extension + +import java.nio.ByteBuffer + +/** + * @author Jordan Abraham + */ +fun ByteArray.toByteBuffer(): ByteBuffer = ByteBuffer.wrap(this) \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt new file mode 100644 index 0000000..0fc8445 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt @@ -0,0 +1,21 @@ +package com.runetopic.cache.extension + +import java.nio.ByteBuffer + +internal fun ByteBuffer.readUnsignedByte(): Int = get().toInt() and 0xFF +internal fun ByteBuffer.readUnsignedShort(): Int = short.toInt() and 0xFFFF +internal fun ByteBuffer.readUnsignedMedium(): Int = ((readUnsignedByte() shl 16) or (readUnsignedByte() shl 8) or readUnsignedByte()) +internal fun ByteBuffer.readUnsignedIntShortSmart(): Int = if (get(position()).toInt() < 0) int and Int.MAX_VALUE else readUnsignedShort() +internal fun ByteBuffer.putIntShortSmart(value: Int) { + val buffer = when { + value >= Short.MAX_VALUE -> ByteBuffer.allocate(Int.SIZE_BYTES).putInt(value - Int.MAX_VALUE - 1) + else -> ByteBuffer.allocate(Short.SIZE_BYTES).putShort(if (value >= 0) value.toShort() else Short.MAX_VALUE) + } + get(buffer.array()) +} + +internal fun ByteBuffer.remainingBytes(): ByteArray { + val bytes = ByteArray(remaining()) + get(bytes) + return bytes +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/extension/String.kt b/cache/src/main/kotlin/com/runetopic/cache/extension/String.kt new file mode 100644 index 0000000..13f876f --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/extension/String.kt @@ -0,0 +1,7 @@ +package com.runetopic.cache.extension + +internal fun String.nameHash(): Int { + var hash = 0 + this.forEach { element -> hash = element.toInt() + ((hash shl 5) - hash) } + return hash +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/ReferenceTable.kt b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/ReferenceTable.kt new file mode 100644 index 0000000..7f9dd6d --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/ReferenceTable.kt @@ -0,0 +1,35 @@ +package com.runetopic.cache.hierarchy + +/** + * @author Tyler Telis + * @email + * + * @author Jordan Abraham + */ +internal data class ReferenceTable( + val id: Int, + val sector: Int, + val length: Int +) { + fun exists(): Boolean = (length != 0 && sector != 0) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ReferenceTable + + if (id != other.id) return false + if (sector != other.sector) return false + if (length != other.length) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + sector + result = 31 * result + length + return result + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt new file mode 100644 index 0000000..b87f21c --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt @@ -0,0 +1,73 @@ +package com.runetopic.cache.hierarchy.index + +import com.runetopic.cache.extension.nameHash +import com.runetopic.cache.hierarchy.index.group.Group + +/** + * @author Tyler Telis + * @email + * + * @author Jordan Abraham + */ +data class Index( + val id: Int, + val crc: Int, + val whirlpool: ByteArray, + val compression: Int, + val protocol: Int, + val revision: Int, + val isNamed: Boolean, + val isUsingWhirlpool: Boolean, + private val groups: Map +): Comparable { + + @JvmName("getGroups") + fun groups(): Collection = groups.values + + @JvmName("getGroupIds") + fun groupIds(): Collection = groups.keys + + @JvmName("getGroup") + fun group(groupId: Int): Group = groups[groupId] ?: Group.DEFAULT + + @JvmName("getGroup") + fun group(groupName: String): Group = groups.values.find { it.nameHash == groupName.nameHash() } ?: Group.DEFAULT + + fun expand(): Int = groups.values.last().files().size + (groups.values.last().id shl 8) + fun use(block: (Index) -> Unit) = block.invoke(this) + + override fun compareTo(other: Index): Int = this.id.compareTo(other.id) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Index + + if (id != other.id) return false + if (crc != other.crc) return false + if (!whirlpool.contentEquals(other.whirlpool)) return false + if (compression != other.compression) return false + if (protocol != other.protocol) return false + if (revision != other.revision) return false + if (isNamed != other.isNamed) return false + if (groups != other.groups) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + crc + result = 31 * result + whirlpool.contentHashCode() + result = 31 * result + compression + result = 31 * result + protocol + result = 31 * result + revision + result = 31 * result + isNamed.hashCode() + result = 31 * result + groups.hashCode() + return result + } + + internal companion object { + fun default(indexId: Int): Index = Index(indexId, 0, ByteArray(64), -1, -1, 0, false, false, hashMapOf()) + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/Group.kt b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/Group.kt new file mode 100644 index 0000000..6163a5b --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/Group.kt @@ -0,0 +1,69 @@ +package com.runetopic.cache.hierarchy.index.group + +import com.runetopic.cache.hierarchy.index.group.file.File + +/** + * @author Tyler Telis + * @email + * + * @author Jordan Abraham + */ +data class Group( + val id: Int, + val nameHash: Int, + val crc: Int, + val whirlpool: ByteArray, + val revision: Int, + val keys: IntArray, + private val files: MutableMap, + val data: ByteArray +): Comparable { + + @JvmName("getFiles") + fun files(): Collection = files.values + + @JvmName("getFileIds") + fun fileIds(): Collection = files.keys + + @JvmName("getFile") + fun file(fileId: Int): File = files[fileId] ?: File.DEFAULT + + internal fun putFile(id: Int, file: File) { + files[id] = file + } + + override fun compareTo(other: Group): Int = this.id.compareTo(other.id) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Group + + if (id != other.id) return false + if (nameHash != other.nameHash) return false + if (crc != other.crc) return false + if (!whirlpool.contentEquals(other.whirlpool)) return false + if (revision != other.revision) return false + if (!keys.contentEquals(other.keys)) return false + if (files != other.files) return false + if (!data.contentEquals(other.data)) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + nameHash + result = 31 * result + crc + result = 31 * result + whirlpool.contentHashCode() + result = 31 * result + revision + result = 31 * result + keys.contentHashCode() + result = 31 * result + files.hashCode() + result = 31 * result + data.contentHashCode() + return result + } + + internal companion object { + val DEFAULT = Group(-1, -1, 0, byteArrayOf(), 0, intArrayOf(), mutableMapOf(), byteArrayOf()) + } +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/file/File.kt b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/file/File.kt new file mode 100644 index 0000000..373427c --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/file/File.kt @@ -0,0 +1,39 @@ +package com.runetopic.cache.hierarchy.index.group.file + +/** + * @author Tyler Telis + * @email + * + * @author Jordan Abraham + */ +data class File( + val id: Int, + val nameHash: Int, + val data: ByteArray +): Comparable { + + override fun compareTo(other: File): Int = this.id.compareTo(other.id) + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as File + + if (id != other.id) return false + if (nameHash != other.nameHash) return false + if (!data.contentEquals(other.data)) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + nameHash + result = 31 * result + data.contentHashCode() + return result + } + + internal companion object { + val DEFAULT = File(-1, 0, byteArrayOf(0)) + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/Constants.kt b/cache/src/main/kotlin/com/runetopic/cache/store/Constants.kt new file mode 100644 index 0000000..ce769b7 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/Constants.kt @@ -0,0 +1,23 @@ +package com.runetopic.cache.store + +/** + * @author Tyler Telis + * @email + */ +internal object Constants { + private const val PREFIX = "main_file_cache" + const val MAIN_FILE_IDX = "$PREFIX.idx" + const val MAIN_FILE_DAT = "$PREFIX.dat2" + const val MAIN_FILE_255 = "$PREFIX.idx255" + + const val IDX_SIZE = 6 + const val DAT_SIZE = 520 + const val MASTER_INDEX_ID = 255 + + val BZIP_HEADER = byteArrayOf( + 'B'.code.toByte(), + 'Z'.code.toByte(), + 'h'.code.toByte(), + '1'.code.toByte() + ) +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt b/cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt new file mode 100644 index 0000000..619b667 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt @@ -0,0 +1,112 @@ +package com.runetopic.cache.store + +import com.runetopic.cache.hierarchy.index.Index +import com.runetopic.cache.hierarchy.index.group.file.File +import com.runetopic.cache.store.storage.js5.Js5DiskStorage +import com.runetopic.cryptography.toWhirlpool +import java.io.Closeable +import java.math.BigInteger +import java.nio.ByteBuffer +import java.nio.file.Path +import java.util.concurrent.CopyOnWriteArrayList + +/** + * @author Tyler Telis + * @email + * + * @author Jordan Abraham + */ +class Js5Store( + path: Path, + parallel: Boolean = false +) : Closeable { + private var storage = Js5DiskStorage(path, parallel) + private val indexes = CopyOnWriteArrayList() + + init { + storage.open(this) + indexes.sortWith(compareBy { it.id }) + } + + internal fun addIndex(index: Index) { + indexes.forEach { i -> require(index.id != i.id) { "Index with Id={${index.id}} already exists." } } + indexes.add(index) + } + + fun putFile(indexId: Int, groupId: Int, id: Int, data: ByteArray) { + val group = index(indexId).group(groupId) + val exists = group.fileIds().contains(id) + if (exists) { + val file = group.file(id) + group.putFile(id, File(file.id, file.nameHash, data)) + return + } + group.putFile(id, File(id, 0, data)) + } + + fun save(): Boolean { + + return true + } + + fun index(indexId: Int): Index = indexes.find { it.id == indexId }!! + + fun indexReferenceTableSize(indexId: Int): Int { + var size = 0 + index(indexId).use { index -> + index.groups().forEach { size += storage.loadReferenceTable(index, it.id).size } + } + return size + } + + fun groupReferenceTableSize(indexId: Int, groupName: String): Int { + val referenceTable = storage.loadReferenceTable(index(indexId), groupName) + return if (referenceTable.isEmpty()) 0 else referenceTable.size - 2 + } + + fun groupReferenceTableSize(indexId: Int, groupId: Int): Int { + val referenceTable = storage.loadReferenceTable(index(indexId), groupId) + return if (referenceTable.isEmpty()) 0 else referenceTable.size - 2 + } + + fun groupReferenceTable(indexId: Int, groupId: Int): ByteArray { + if (indexId == Constants.MASTER_INDEX_ID) return storage.loadMasterReferenceTable(groupId) + return storage.loadReferenceTable(index(indexId), groupId) + } + + fun checksumsWithoutRSA(): ByteArray { + val header = ByteBuffer.allocate(indexes.size * 8) + indexes.forEach { + header.putInt(it.crc) + header.putInt(it.revision) + } + return header.array() + } + + fun checksumsWithRSA(exponent: BigInteger, modulus: BigInteger): ByteArray { + val header = ByteBuffer.allocate(indexes.size * 72 + 6) + header.position(5) + header.put(indexes.size.toByte()) + indexes.forEach { + header.putInt(it.crc) + header.putInt(it.revision) + header.put(it.whirlpool) + } + val headerPosition = header.position() + val headerArray = header.array() + + val whirlpool = ByteBuffer.allocate(64 + 1) + whirlpool.put(1) + whirlpool.put(headerArray.copyInto(ByteArray(headerPosition - 5), 0, 5, headerPosition).toWhirlpool()) + + val rsa = BigInteger(whirlpool.array()).modPow(exponent, modulus).toByteArray() + val checksums = ByteBuffer.allocate(headerPosition + rsa.size) + checksums.put(0) + checksums.putInt((headerPosition + rsa.size) - 5) + checksums.put(headerArray, 5, headerPosition - 5) + checksums.put(rsa) + return checksums.array() + } + + override fun close() = storage.close() +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/IStorage.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/IStorage.kt new file mode 100644 index 0000000..5f6a45c --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/IStorage.kt @@ -0,0 +1,19 @@ +package com.runetopic.cache.store.storage + +import com.runetopic.cache.hierarchy.index.Index +import com.runetopic.cache.store.Js5Store +import java.io.Closeable +import java.io.Flushable + +/** + * @author Tyler Telis + * @email + */ +internal interface IStorage: Closeable, Flushable { + fun open(store: Js5Store) + fun read(indexId: Int, store: Js5Store) + fun write(indexId: Int, store: Js5Store) + fun loadReferenceTable(index: Index, groupId: Int): ByteArray + fun loadMasterReferenceTable(groupId: Int): ByteArray + fun loadReferenceTable(index: Index, groupName: String): ByteArray +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt new file mode 100644 index 0000000..43051f6 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt @@ -0,0 +1,122 @@ +package com.runetopic.cache.store.storage.js5 + +import com.github.michaelbull.logging.InlineLogger +import com.runetopic.cache.hierarchy.index.Index +import com.runetopic.cache.store.Constants +import com.runetopic.cache.store.Js5Store +import com.runetopic.cache.store.storage.IStorage +import com.runetopic.cache.store.storage.js5.io.dat.DatFile +import com.runetopic.cache.store.storage.js5.io.dat.IDatFile +import com.runetopic.cache.store.storage.js5.io.dat.sector.DatIndexSector +import com.runetopic.cache.store.storage.js5.io.idx.IIdxFile +import com.runetopic.cache.store.storage.js5.io.idx.IdxFile +import java.io.FileNotFoundException +import java.nio.file.Path +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executors +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.exists + +/** + * @author Tyler Telis + * @email + * + * @author Jordan Abraham + */ +@OptIn(ExperimentalPathApi::class) +internal class Js5DiskStorage( + private val path: Path, + private val parallel: Boolean +) : IStorage { + private var masterIdxFile: IIdxFile + private var datFile: IDatFile + private var idxFiles = CopyOnWriteArrayList() + private val logger = InlineLogger() + + init { + val masterIndexFile = Path.of("${path}/${Constants.MAIN_FILE_255}") + + if (masterIndexFile.exists().not()) { + throw FileNotFoundException("Missing ${Constants.MAIN_FILE_255} in directory ${path}/${Constants.MAIN_FILE_255}") + } + + val datFile = Path.of("${path}/${Constants.MAIN_FILE_DAT}") + + if (datFile.exists().not()) { + throw FileNotFoundException("Missing ${Constants.MAIN_FILE_DAT} in directory ${path}/${Constants.MAIN_FILE_DAT}") + } + + this.masterIdxFile = IdxFile(Constants.MASTER_INDEX_ID, masterIndexFile) + this.datFile = DatFile(datFile) + } + + override fun open(store: Js5Store) { + logger.debug { "Opening $path for js5 indexes." } + + if (parallel) { + val latch = CountDownLatch(masterIdxFile.validIndexCount()) + val threads = Runtime.getRuntime().availableProcessors() + val pool = Executors.newFixedThreadPool(if (threads >= 16) 8 else if (threads >= 8) 4 else 2) + (0 until masterIdxFile.validIndexCount()).forEach { + pool.execute { + read(it, store) + latch.countDown() + } + } + latch.await() + pool.shutdown() + } else { + (0 until masterIdxFile.validIndexCount()).forEach { read(it, store) } + } + logger.debug { "Opened ${idxFiles.size} js5 indexes. (Allocated ${((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024)}MB)." } + } + + override fun read(indexId: Int, store: Js5Store) { + val indexReferenceTable = masterIdxFile.decode(indexId) + idxFiles.add(getIdxFile(indexId)) + + if (indexReferenceTable.exists().not()) { + store.addIndex(Index.default(indexId)) + return + } + val data = datFile.decode(masterIdxFile.id(), indexReferenceTable) + store.addIndex(DatIndexSector(datFile, getIdxFile(indexId), data).decode()) + } + + override fun write(indexId: Int, store: Js5Store) { + val idk = masterIdxFile.encode(byteArrayOf()) + val data = datFile.encode(byteArrayOf()) + val index = store.index(indexId) + //val idk2 = DatIndexSector(datFile, getIdxFile(indexId), data).encode(index) + } + + override fun loadMasterReferenceTable(groupId: Int): ByteArray { + return datFile.decode(Constants.MASTER_INDEX_ID, masterIdxFile.decode(groupId)) + } + + override fun loadReferenceTable(index: Index, groupId: Int): ByteArray { + return datFile.decode(index.id, getIdxFile(index.id).decode(groupId)) + } + + override fun loadReferenceTable(index: Index, groupName: String): ByteArray { + val group = index.group(groupName) + if (group.data.isEmpty()) return group.data + return datFile.decode(index.id, getIdxFile(index.id).decode(group.id)) + } + + private fun getIdxFile(id: Int): IdxFile { + idxFiles.find { it.id() == id }?.let { return it } + return IdxFile(id, Path.of("$path/${Constants.MAIN_FILE_IDX}${id}")) + } + + override fun close() { + masterIdxFile.close() + datFile.close() + idxFiles.forEach { it.close() } + } + + override fun flush() { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt new file mode 100644 index 0000000..6214567 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt @@ -0,0 +1,92 @@ +package com.runetopic.cache.store.storage.js5.io.dat + +import com.runetopic.cache.exception.DatFileException +import com.runetopic.cache.exception.EndOfDatFileException +import com.runetopic.cache.extension.readUnsignedByte +import com.runetopic.cache.extension.readUnsignedMedium +import com.runetopic.cache.extension.readUnsignedShort +import com.runetopic.cache.extension.toByteBuffer +import com.runetopic.cache.hierarchy.ReferenceTable +import com.runetopic.cache.store.Constants.DAT_SIZE +import java.io.RandomAccessFile +import java.nio.ByteBuffer +import java.nio.file.Path + +/** + * @author Tyler Telis + * @email + * + * @author Jordan Abraham + */ +internal class DatFile( + path: Path +): IDatFile { + private val datFile: RandomAccessFile = RandomAccessFile(path.toFile(), "rw") + private val datBuffer = ByteArray(datFile.length().toInt()) + + init { + datFile.readFully(datBuffer) + } + + override fun decode( + id: Int, + referenceTable: ReferenceTable + ): ByteArray { + var sector = referenceTable.sector + val length = referenceTable.length + val referenceTableId = referenceTable.id + + val buffer = ByteBuffer.allocate(length) + + var part = 0 + var bytes = 0 + while (length > bytes) { + //validate sector + if (sector <= 0 || datBuffer.size / DAT_SIZE < sector) return byteArrayOf() + + val position = DAT_SIZE * sector + val large = referenceTableId > 0xFFFF + val headerSize = if (large) 10 else 8 + val blockSize = adjustBlockLength(length - bytes, headerSize) + val totalSize = position + headerSize + blockSize + //validate the total size. + if (totalSize > datBuffer.size) return byteArrayOf() + + val header = datBuffer.copyOfRange(position, totalSize).toByteBuffer() + val currentReferenceTableId = if (large) header.int else header.readUnsignedShort() + val currentPart = header.readUnsignedShort() + val nextSector = header.readUnsignedMedium() + val currentIndex = header.readUnsignedByte() + + if (referenceTableId != currentReferenceTableId || currentPart != part || id != currentIndex) { + throw DatFileException("DatFile mismatch Id={${currentIndex}} != {${id}}, ReferenceTableId={${currentReferenceTableId}} != {${referenceTableId}}, CurrentPart={${currentPart}} != {${part}}") + } + if (nextSector < 0 || datBuffer.size / DAT_SIZE < nextSector) { + throw DatFileException("Invalid next sector $nextSector") + } + + buffer.put(header.array(), headerSize, blockSize) + bytes += blockSize + sector = nextSector + ++part + } + + buffer.flip() + return buffer.array() + } + + override fun encode(data: ByteArray) { + + } + + private fun adjustBlockLength( + blockLength: Int, + headerLength: Int + ): Int { + return if (blockLength <= DAT_SIZE - headerLength) blockLength else DAT_SIZE - headerLength + } + + private fun validateSector(sector: Int): Boolean = (sector <= 0 || datBuffer.size / DAT_SIZE < sector) + + override fun close() = datFile.close() +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt new file mode 100644 index 0000000..ce8ba68 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt @@ -0,0 +1,13 @@ +package com.runetopic.cache.store.storage.js5.io.dat + +import com.runetopic.cache.hierarchy.ReferenceTable +import java.io.Closeable + +/** + * @author Tyler Telis + * @email + */ +internal interface IDatFile: Closeable { + fun decode(id: Int, referenceTable: ReferenceTable): ByteArray + fun encode(data: ByteArray) +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt new file mode 100644 index 0000000..a98d9f9 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt @@ -0,0 +1,9 @@ +package com.runetopic.cache.store.storage.js5.io.dat + +/** + * @author Jordan Abraham + */ +internal interface IDatSector { + fun decode(): T + fun encode(override: T): ByteArray +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt new file mode 100644 index 0000000..824bb48 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt @@ -0,0 +1,85 @@ +package com.runetopic.cache.store.storage.js5.io.dat.sector + +import com.runetopic.cache.extension.toByteBuffer +import com.runetopic.cache.hierarchy.index.group.file.File +import com.runetopic.cache.store.storage.js5.io.dat.IDatSector + +/** + * @author Jordan Abraham + */ +data class DatGroupSector( + val fileIds: Array, + val fileNameHashes: Array, + val data: ByteArray, + val count: Int, + val groupId: Int +): IDatSector> { + override fun decode(): MutableMap { + if (data.isEmpty()) return hashMapOf(Pair(0, File.DEFAULT)) + if (count <= 1) return hashMapOf(Pair(0, File(fileIds[groupId][0], fileNameHashes[groupId][0], data))) + + var position = data.size + val chunks = data[--position].toInt() and 0xFF + position -= chunks * (count * 4) + val buffer = data.toByteBuffer() + buffer.position(position) + val filesSizes = IntArray(count) + (0 until chunks).forEach { _ -> + var read = 0 + (0 until count).forEach { + read += buffer.int + filesSizes[it] += read + } + } + val filesDatas = Array(count) { byteArrayOf() } + (0 until count).forEach { + filesDatas[it] = ByteArray(filesSizes[it]) + filesSizes[it] = 0 + } + buffer.position(position) + var offset = 0 + (0 until chunks).forEach { _ -> + var read = 0 + (0 until count).forEach { + read += buffer.int + System.arraycopy(data, offset, filesDatas[it], filesSizes[it], read) + offset += read + filesSizes[it] += read + } + } + + val files = hashMapOf() + (0 until count).forEach { + files[it] = File(fileIds[groupId][it], fileNameHashes[groupId][it], filesDatas[it]) + } + return files + } + + override fun encode(override: Map): ByteArray { + TODO("Not yet implemented") + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DatGroupSector + + if (!fileIds.contentDeepEquals(other.fileIds)) return false + if (!fileNameHashes.contentDeepEquals(other.fileNameHashes)) return false + if (!data.contentEquals(other.data)) return false + if (count != other.count) return false + if (groupId != other.groupId) return false + + return true + } + + override fun hashCode(): Int { + var result = fileIds.contentDeepHashCode() + result = 31 * result + fileNameHashes.contentDeepHashCode() + result = 31 * result + data.contentHashCode() + result = 31 * result + count + result = 31 * result + groupId + return result + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt new file mode 100644 index 0000000..0b0b5c2 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt @@ -0,0 +1,435 @@ +package com.runetopic.cache.store.storage.js5.io.dat.sector + +import com.runetopic.cache.codec.ContainerCodec +import com.runetopic.cache.codec.decompress +import com.runetopic.cache.exception.ProtocolException +import com.runetopic.cache.extension.* +import com.runetopic.cache.hierarchy.index.Index +import com.runetopic.cache.hierarchy.index.group.Group +import com.runetopic.cache.store.storage.js5.io.dat.IDatFile +import com.runetopic.cache.store.storage.js5.io.dat.IDatSector +import com.runetopic.cache.store.storage.js5.io.idx.IIdxFile +import com.runetopic.cryptography.toWhirlpool +import java.nio.ByteBuffer +import java.util.zip.ZipException + +/** + * @author Jordan Abraham + */ +internal data class DatIndexSector( + val datFile: IDatFile, + val idxFile: IIdxFile, + val data: ByteArray +): IDatSector { + + override fun decode(): Index { + val decompressed = ContainerCodec.decompress(data) + val buffer = decompressed.data.toByteBuffer() + val protocol = buffer.readUnsignedByte() + val revision = when { + protocol < 5 || protocol > 7 -> throw ProtocolException("Unhandled protocol $protocol") + protocol >= 6 -> buffer.int + else -> 0 + } + val mask = buffer.readUnsignedByte() + val count = if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort() + + val isNamed = (0x1 and mask) != 0 + val isUsingWhirlpool = (0x2 and mask) != 0 + + val groupIds = decodeGroupIds(count, buffer, protocol) + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + val groupNameHashes = decodeGroupNameHashes(maxGroupId, count, isNamed, groupIds, buffer) + val groupCrcs = decodeGroupCrcs(maxGroupId, count, groupIds, buffer) + val groupWhirlpools = decodeGroupWhirlpools(maxGroupId, isUsingWhirlpool, count, buffer, groupIds) + val groupRevisions = decodeGroupRevisions(maxGroupId, count, groupIds, buffer) + val groupFileSizes = decodeGroupFileSizes(maxGroupId, count, groupIds, buffer, protocol) + val groupFileIds = decodeGroupFileIds(maxGroupId, groupFileSizes, count, groupIds, buffer, protocol) + val groupFileNameHashes = decodeGroupFileNameHashes(maxGroupId, groupFileSizes, count, groupIds, buffer, isNamed) + + val groups = hashMapOf() + (0 until count).forEach { + val groupId = groupIds[it] + + val groupReferenceTableData = datFile.decode(idxFile.id(), idxFile.decode(groupId)) + val data = if (groupReferenceTableData.isEmpty()) byteArrayOf() else try { + groupReferenceTableData.decompress() + } catch (exception: ZipException) { + groupReferenceTableData + } + + val groupSector = DatGroupSector( + groupFileIds, + groupFileNameHashes, + data, + groupFileSizes[groupId], + groupId + ) + + groups[groupId] = (Group( + groupId, + groupNameHashes[groupId], + groupCrcs[groupId], + groupWhirlpools[groupId], + groupRevisions[groupId], + intArrayOf(),//TODO + groupSector.decode(), + data + )) + } + return Index( + idxFile.id(), + decompressed.crc, + data.toWhirlpool(), + decompressed.compression, + protocol, + revision, + isNamed, + isUsingWhirlpool, + groups + ) + } + + override fun encode(override: Index): ByteArray { + val count = override.groups().size + val header = ByteBuffer.allocate(if (override.protocol >= 7) if (count >= Short.MAX_VALUE) 10 else 8 else if (override.protocol >= 6) 8 else 4) + header.put(override.protocol.toByte()) + if (override.protocol >= 6) { + header.putInt(override.revision) + } + + val mask = when { + override.isNamed -> 0x1 + override.isUsingWhirlpool -> 0x2 + else -> 0x0 + } + header.put(mask.toByte()) + + if (override.protocol >= 7) header.putIntShortSmart(count) else header.putShort(count.toShort()) + + val ids = override.groups().stream().map { it.id }.toList().toIntArray() + val max = (ids.maxOrNull() ?: -1) + 1 + val nameHashes = IntArray(max) { override.group(it).nameHash } + val crcs = IntArray(max) { override.group(it).crc } + val whirlpools = Array(max) { override.group(it).whirlpool } + val revisions = IntArray(max) { override.group(it).revision } + val fileSizes = IntArray(max) { override.group(it).fileIds().size } + val fileIds = Array(max) { override.groups().stream().filter { group -> group.id == it }.findFirst().takeIf { it.isPresent }?.get()?.fileIds()?.map { it }?.toList()?.toIntArray() ?: intArrayOf() } + val fileNameHashes = Array(max) { override.groups().stream().filter { group -> group.id == it }.findFirst().takeIf { it.isPresent }?.get()?.files()?.map { it.nameHash }?.toList()?.toIntArray() ?: intArrayOf() } + + val groupIds = encodeGroupIds(count, override.protocol, ids) + val groupNameHashes = encodeGroupNameHashes(count, override.isNamed, ids, nameHashes) + val groupCrcs = encodeGroupCrcs(count, ids, crcs) + val groupWhirlpools = encodeGroupWhirlpools(count, ids, override.isUsingWhirlpool, whirlpools) + val groupRevisions = encodeGroupRevisions(count, ids, revisions) + val groupFileSizes = encodeGroupFileSizes(count, ids, override.protocol, fileSizes) + val groupFileIds = encodeGroupFileIds(count, ids, override.protocol, fileSizes, fileIds) + val groupFileNameHashes = encodeGroupFileNameHashes(count, ids, fileSizes, override.isNamed, fileNameHashes) + + val buffer = ByteBuffer.allocate( + /**/header.position() + + groupIds.position() + + groupNameHashes.position() + + groupCrcs.position() + + groupWhirlpools.position() + + groupRevisions.position() + + groupFileSizes.position() + + groupFileIds.position() + + groupFileNameHashes.position() + ) + + buffer.put(header.array()) + buffer.put(groupIds.array()) + buffer.put(groupNameHashes.array()) + buffer.put(groupCrcs.array()) + buffer.put(groupWhirlpools.array()) + buffer.put(groupRevisions.array()) + buffer.put(groupFileSizes.array()) + buffer.put(groupFileIds.array()) + buffer.put(groupFileNameHashes.array()) + + val compressed = ContainerCodec.compress( + override.compression, + override.revision, + buffer.array(), + intArrayOf()//TODO + ) + return compressed.array() + } + + fun decodeGroupIds( + count: Int, + buffer: ByteBuffer, + protocol: Int + ): IntArray { + val groupIds = IntArray(count) + (0 until count).forEach { + groupIds[it] = (if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort()) + if (it == 0) 0 else groupIds[it - 1] + } + return groupIds + } + + fun encodeGroupIds( + count: Int, + protocol: Int, + groupIds: IntArray + ): ByteBuffer { + //TODO This buffer needs to be allocated properly for protocol >= 7 + val buffer = ByteBuffer.allocate(calc(count, groupIds, protocol)) + (0 until count).forEach { + val value = groupIds[it] - if (it == 0) 0 else groupIds[it - 1] + if (protocol >= 7) buffer.putIntShortSmart(value) else buffer.putShort(value.toShort()) + } + return buffer + } + + fun calc( + count: Int, + groupIds: IntArray, + protocol: Int + ): Int { + var bytes = 0 + (0 until count).forEach { + val value = groupIds[it] - if (it == 0) 0 else groupIds[it - 1] + bytes += if (protocol >= 7) if (value >= Short.MAX_VALUE) 4 else 2 else 2 + } + return bytes + } + + fun decodeGroupFileSizes( + maxGroupId: Int, + count: Int, + groupIds: IntArray, + buffer: ByteBuffer, + protocol: Int + ): IntArray { + val groupFileSizes = IntArray(maxGroupId) + (0 until count).forEach { + groupFileSizes[groupIds[it]] = if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort() + } + return groupFileSizes + } + + fun encodeGroupFileSizes( + count: Int, + groupIds: IntArray, + protocol: Int, + groupFileSizes: IntArray + ): ByteBuffer { + //TODO This buffer needs to be allocated properly for protocol >= 7 + val buffer = ByteBuffer.allocate(groupIds.sumOf { Short.SIZE_BYTES }) + (0 until count).forEach { + if (protocol >= 7) buffer.putIntShortSmart(groupFileSizes[groupIds[it]]) else buffer.putShort(groupFileSizes[groupIds[it]].toShort()) + } + return buffer + } + + fun decodeGroupRevisions( + maxGroupId: Int, + count: Int, + groupIds: IntArray, + buffer: ByteBuffer + ): IntArray { + val revisions = IntArray(maxGroupId) + (0 until count).forEach { + revisions[groupIds[it]] = buffer.int + } + return revisions + } + + fun encodeGroupRevisions( + count: Int, + groupIds: IntArray, + revisions: IntArray + ): ByteBuffer { + val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) + (0 until count).forEach { + buffer.putInt(revisions[groupIds[it]]) + } + return buffer + } + + fun decodeGroupWhirlpools( + maxGroupId: Int, + usesWhirlpool: Boolean, + count: Int, + buffer: ByteBuffer, + groupIds: IntArray + ): Array { + val whirlpools = Array(maxGroupId) { ByteArray(64) } + if (usesWhirlpool.not()) return whirlpools + + (0 until count).forEach { + val whirlpool = ByteArray(64) + buffer.get(whirlpool) + whirlpools[groupIds[it]] = whirlpool + } + return whirlpools + } + + fun encodeGroupWhirlpools( + count: Int, + groupIds: IntArray, + usesWhirlpool: Boolean, + whirlpools: Array + ): ByteBuffer { + if ((usesWhirlpool.not())) return ByteBuffer.allocate(0) + + val buffer = ByteBuffer.allocate(count * 64) + (0 until count).forEach { + buffer.put(whirlpools[groupIds[it]]) + } + return buffer + } + + fun decodeGroupCrcs( + maxGroupId: Int, + count: Int, + groupIds: IntArray, + buffer: ByteBuffer + ): IntArray { + val crcs = IntArray(maxGroupId) + (0 until count).forEach { + crcs[groupIds[it]] = buffer.int + } + return crcs + } + + fun encodeGroupCrcs( + count: Int, + groupIds: IntArray, + crcs: IntArray + ): ByteBuffer { + val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) + (0 until count).forEach { + buffer.putInt(crcs[groupIds[it]]) + } + return buffer + } + + fun decodeGroupNameHashes( + maxGroupId: Int, + count: Int, + isNamed: Boolean, + groupIds: IntArray, + buffer: ByteBuffer + ): IntArray { + val nameHashes = IntArray(maxGroupId) { -1 } + if (isNamed.not()) return nameHashes + + (0 until count).forEach { + nameHashes[groupIds[it]] = buffer.int + } + return nameHashes + } + + fun encodeGroupNameHashes( + count: Int, + isNamed: Boolean, + groupIds: IntArray, + nameHashes: IntArray + ): ByteBuffer { + if (isNamed.not()) return ByteBuffer.allocate(0) + + val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) + (0 until count).forEach { + buffer.putInt(nameHashes[groupIds[it]]) + } + return buffer + } + + fun decodeGroupFileIds( + maxGroupId: Int, + groupFileSizes: IntArray, + count: Int, + groupIds: IntArray, + buffer: ByteBuffer, + protocol: Int + ): Array { + val fileIds = Array(maxGroupId) { IntArray(groupFileSizes[it]) } + (0 until count).forEach { + val groupId = groupIds[it] + (0 until groupFileSizes[groupId]).forEach { fileId -> + fileIds[groupId][fileId] = (if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort()) + if (fileId == 0) 0 else fileIds[groupId][fileId - 1] + } + } + return fileIds + } + + fun encodeGroupFileIds( + count: Int, + groupIds: IntArray, + protocol: Int, + groupFileSizes: IntArray, + fileIds: Array + ): ByteBuffer { + //TODO This buffer needs to be allocated properly for protocol >= 7 + val buffer = ByteBuffer.allocate(groupFileSizes.sumOf { it * Short.SIZE_BYTES }) + (0 until count).forEach { + val groupId = groupIds[it] + (0 until groupFileSizes[groupId]).forEach { fileId -> + val value = fileIds[groupId][fileId] - if (fileId == 0) 0 else fileIds[groupId][fileId - 1] + if (protocol >= 7) buffer.putIntShortSmart(value) else buffer.putShort(value.toShort()) + } + } + return buffer + } + + fun decodeGroupFileNameHashes( + maxGroupId: Int, + groupFileSizes: IntArray, + count: Int, + groupIds: IntArray, + buffer: ByteBuffer, + isNamed: Boolean + ): Array { + val fileNameHashes = Array(maxGroupId) { IntArray(groupFileSizes[it]) { -1 } } + if (isNamed.not()) return fileNameHashes + + (0 until count).forEach { + val groupId = groupIds[it] + (0 until groupFileSizes[groupId]).forEach { fileId -> + fileNameHashes[groupId][fileId] = buffer.int + } + } + return fileNameHashes + } + + fun encodeGroupFileNameHashes( + count: Int, + groupIds: IntArray, + groupFileSizes: IntArray, + isNamed: Boolean, + fileNameHashes: Array + ): ByteBuffer { + if (isNamed.not()) return ByteBuffer.allocate(0) + val buffer = ByteBuffer.allocate(groupFileSizes.sum() * Int.SIZE_BYTES) + (0 until count).forEach { + val groupId = groupIds[it] + (0 until groupFileSizes[groupId]).forEach { fileId -> + buffer.putInt(fileNameHashes[groupId][fileId]) + } + } + return buffer + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DatIndexSector + + if (datFile != other.datFile) return false + if (idxFile != other.idxFile) return false + if (!data.contentEquals(other.data)) return false + + return true + } + + override fun hashCode(): Int { + var result = datFile.hashCode() + result = 31 * result + idxFile.hashCode() + result = 31 * result + data.contentHashCode() + return result + } +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt new file mode 100644 index 0000000..a097db7 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt @@ -0,0 +1,15 @@ +package com.runetopic.cache.store.storage.js5.io.idx + +import com.runetopic.cache.hierarchy.ReferenceTable +import java.io.Closeable + +/** + * @author Tyler Telis + * @email + */ +internal interface IIdxFile: Closeable { + fun decode(id: Int): ReferenceTable + fun encode(data: ByteArray) + fun validIndexCount(): Int + fun id(): Int +} \ No newline at end of file diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt new file mode 100644 index 0000000..5ccbec5 --- /dev/null +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt @@ -0,0 +1,48 @@ +package com.runetopic.cache.store.storage.js5.io.idx + +import com.runetopic.cache.exception.IdxFileException +import com.runetopic.cache.extension.readUnsignedMedium +import com.runetopic.cache.extension.toByteBuffer +import com.runetopic.cache.hierarchy.ReferenceTable +import com.runetopic.cache.store.Constants.IDX_SIZE +import java.io.RandomAccessFile +import java.nio.file.Path +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.fileSize + +/** + * @author Tyler Telis + * @email + * + * @author Jordan Abraham + */ +internal class IdxFile( + private val id: Int, + private val path: Path +) : IIdxFile { + private val idxFile: RandomAccessFile = RandomAccessFile(path.toFile(), "rw") + private val idxBuffer = ByteArray(idxFile.length().toInt()).also { idxFile.readFully(it) } + + override fun decode(id: Int): ReferenceTable { + if (idxBuffer.size < id * IDX_SIZE + 6) { + return ReferenceTable(id, 0, 0) + } + val position = id * IDX_SIZE + val buffer = idxBuffer.copyOfRange(position, position + IDX_SIZE).toByteBuffer() + val length = buffer.readUnsignedMedium() + val sector = buffer.readUnsignedMedium() + if (length < 0) { + throw IdxFileException("Invalid length for sector Length=$length Sector=$sector") + } + return ReferenceTable(id, sector, length) + } + + override fun encode(data: ByteArray) { + TODO("Not yet implemented") + } + + @OptIn(ExperimentalPathApi::class) + override fun validIndexCount(): Int = path.fileSize().toInt() / IDX_SIZE + override fun id(): Int = id + override fun close() = idxFile.close() +} \ No newline at end of file diff --git a/cache/src/main/resources/simplelogger.properties b/cache/src/main/resources/simplelogger.properties new file mode 100644 index 0000000..5c89a5a --- /dev/null +++ b/cache/src/main/resources/simplelogger.properties @@ -0,0 +1 @@ +org.slf4j.simpleLogger.defaultLogLevel=DEBUG \ No newline at end of file diff --git a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt new file mode 100644 index 0000000..d54910f --- /dev/null +++ b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt @@ -0,0 +1,290 @@ +package com.runetopic.cache.store.storage.js5.io.dat + +import com.runetopic.cache.store.storage.js5.io.dat.sector.DatIndexSector +import com.runetopic.cache.store.storage.js5.io.idx.IdxFile +import io.mockk.confirmVerified +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import java.nio.ByteBuffer +import java.nio.file.Path +import java.util.stream.IntStream +import kotlin.random.Random +import kotlin.random.nextInt +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * @author Jordan Abraham + */ +class DatSectorTest { + + @Test + fun `test decode and encode`() { + val indexSector = mockk() + + every { indexSector.datFile } returns DatFile(Path.of("./main_file_cache.dat2")) + every { indexSector.idxFile } returns IdxFile(10, Path.of("./main_file_cache.idx10")) + every { indexSector.data } returns byteArrayOf(0, 0, 0, 0, 30, 6, 0, 0, 0, 5, 1, 0, 1, 0, 1, 74, -4, 115, -83, 120, 17, 126, 78, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0) + + every { indexSector.decode() } answers { callOriginal() } + every { indexSector.decodeGroupIds(any(), any(), any()) } answers { callOriginal() } + every { indexSector.decodeGroupNameHashes(any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.decodeGroupCrcs(any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.decodeGroupWhirlpools(any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.decodeGroupRevisions(any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.decodeGroupFileSizes(any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.decodeGroupFileIds(any(), any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.decodeGroupFileNameHashes(any(), any(), any(), any(), any(), any()) } answers { callOriginal() } + + every { indexSector.encode(any()) } answers { callOriginal() } + every { indexSector.encodeGroupIds(any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupNameHashes(any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupCrcs(any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupWhirlpools(any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupRevisions(any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupFileSizes(any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupFileIds(any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupFileNameHashes(any(), any(), any(), any(), any()) } answers { callOriginal() } + + val decoded = indexSector.decode() + val encoded = indexSector.encode(decoded) + + assertEquals( + indexSector.data.contentToString(), + encoded.contentToString() + ) + + verify(exactly = 1) { indexSector.decode() } + verify(atLeast = 1) { indexSector.decodeGroupIds(any(), any(), any()) } + verify(atLeast = 1) { indexSector.decodeGroupNameHashes(any(), any(), any(), any(), any()) } + verify(atLeast = 1) { indexSector.decodeGroupCrcs(any(), any(), any(), any()) } + verify(atLeast = 1) { indexSector.decodeGroupWhirlpools(any(), any(), any(), any(), any()) } + verify(atLeast = 1) { indexSector.decodeGroupRevisions(any(), any(), any(), any()) } + verify(atLeast = 1) { indexSector.decodeGroupFileSizes(any(), any(), any(), any(), any()) } + verify(atLeast = 1) { indexSector.decodeGroupFileIds(any(), any(), any(), any(), any(), any()) } + verify(atLeast = 1) { indexSector.decodeGroupFileNameHashes(any(), any(), any(), any(), any(), any()) } + + verify(exactly = 1) { indexSector.encode(decoded) } + verify(atLeast = 1) { indexSector.encodeGroupIds(any(), any(), any()) } + verify(atLeast = 1) { indexSector.encodeGroupNameHashes(any(), any(), any(), any()) } + verify(atLeast = 1) { indexSector.encodeGroupCrcs(any(), any(), any()) } + verify(atLeast = 1) { indexSector.encodeGroupWhirlpools(any(), any(), any(), any()) } + verify(atLeast = 1) { indexSector.encodeGroupRevisions(any(), any(), any()) } + verify(atLeast = 1) { indexSector.encodeGroupFileSizes(any(), any(), any(), any()) } + verify(atLeast = 1) { indexSector.encodeGroupFileIds(any(), any(), any(), any(), any()) } + verify(atLeast = 1) { indexSector.encodeGroupFileNameHashes(any(), any(), any(), any(), any()) } + + verify(atLeast = 1) { indexSector.datFile } + verify(atLeast = 1) { indexSector.idxFile } + verify(atLeast = 1) { indexSector.data } + + confirmVerified(indexSector) + } + + @Test + fun `test file ids protocol 6`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + val groupFileSizes = IntArray(maxGroupId) { if (it in groupIds) Random.nextInt(1..10) else 0 } + val protocol = 7 + + val indexSector = mockk() + every { indexSector.decodeGroupFileIds(any(), any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupFileIds(any(), any(), any(), any(), any()) } answers { callOriginal() } + + val buffer = ByteBuffer.wrap(Random.nextBytes(groupFileSizes.sumOf { it * Short.SIZE_BYTES })) + + val decoded = indexSector.decodeGroupFileIds(maxGroupId, groupFileSizes, count, groupIds, buffer, protocol) + val encoded = indexSector.encodeGroupFileIds(count, groupIds, protocol, groupFileSizes, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupFileIds(maxGroupId, groupFileSizes, count, groupIds, buffer, protocol) } + verify(exactly = 1) { indexSector.encodeGroupFileIds(count, groupIds, protocol, groupFileSizes, decoded) } + confirmVerified(indexSector) + } + + @Test + fun `test ids protocol 6`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val protocol = 7 + + val indexSector = mockk() + every { indexSector.decodeGroupIds(any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupIds(any(), any(), any()) } answers { callOriginal() } + every { indexSector.calc(any(), any(), any()) } answers { callOriginal() } + + val buffer = ByteBuffer.wrap(Random.nextBytes(indexSector.calc(count, groupIds, protocol))) + + val decoded = indexSector.decodeGroupIds(count, buffer, protocol) + val encoded = indexSector.encodeGroupIds(count, protocol, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupIds(count, buffer, protocol) } + verify(exactly = 1) { indexSector.encodeGroupIds(count, protocol, decoded) } + verify(atLeast = 1) { indexSector.calc(count, groupIds, protocol) } + //confirmVerified(indexSector) + } + + @Test + fun `test fileSizes protocol 6`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + val protocol = 6 + + val indexSector = mockk() + every { indexSector.decodeGroupFileSizes(any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupFileSizes(any(), any(), any(), any()) } answers { callOriginal() } + + val buffer = ByteBuffer.wrap(Random.nextBytes(groupIds.sumOf { (if (protocol >= 7) if (it >= Short.MAX_VALUE) 4 else 2 else 2).toInt() })) + + val decoded = indexSector.decodeGroupFileSizes(maxGroupId, count, groupIds, buffer, protocol) + val encoded = indexSector.encodeGroupFileSizes(count, groupIds, protocol, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupFileSizes(maxGroupId, count, groupIds, buffer, protocol) } + verify(exactly = 1) { indexSector.encodeGroupFileSizes(count, groupIds, protocol, decoded) } + confirmVerified(indexSector) + } + + @Test + fun `test file nameHashes`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + val validFileIds = IntArray(maxGroupId) { if (it in groupIds) Random.nextInt(1..10) else 0 } + + val indexSector = mockk() + every { indexSector.decodeGroupFileNameHashes(any(), any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupFileNameHashes(any(), any(), any(), any(), any()) } answers { callOriginal() } + + val buffer = ByteBuffer.wrap(Random.nextBytes(validFileIds.sum() * Int.SIZE_BYTES)) + + val decoded = indexSector.decodeGroupFileNameHashes(maxGroupId, validFileIds, count, groupIds, buffer, true) + val encoded = indexSector.encodeGroupFileNameHashes(count, groupIds, validFileIds, true, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupFileNameHashes(maxGroupId, validFileIds, count, groupIds, buffer, true) } + verify(exactly = 1) { indexSector.encodeGroupFileNameHashes(count, groupIds, validFileIds, true, decoded) } + confirmVerified(indexSector) + } + + @Test + fun `test whirlpools`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + + val buffer = ByteBuffer.wrap(Random.nextBytes(count * 64)) + + val indexSector = mockk() + every { indexSector.decodeGroupWhirlpools(any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupWhirlpools(any(), any(), any(), any()) } answers { callOriginal() } + + val decoded = indexSector.decodeGroupWhirlpools(maxGroupId, true, count, buffer, groupIds) + val encoded = indexSector.encodeGroupWhirlpools(count, groupIds, true, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupWhirlpools(maxGroupId, true, count, buffer, groupIds) } + verify(exactly = 1) { indexSector.encodeGroupWhirlpools(count, groupIds, true, decoded) } + confirmVerified(indexSector) + } + + @Test + fun `test crcs`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + + val buffer = ByteBuffer.wrap(Random.nextBytes(count * Int.SIZE_BYTES)) + + val indexSector = mockk() + every { indexSector.decodeGroupCrcs(any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupCrcs(any(), any(), any()) } answers { callOriginal() } + + val decoded = indexSector.decodeGroupCrcs(maxGroupId, count, groupIds, buffer) + val encoded = indexSector.encodeGroupCrcs(count, groupIds, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupCrcs(maxGroupId, count, groupIds, buffer) } + verify(exactly = 1) { indexSector.encodeGroupCrcs(count, groupIds, decoded) } + confirmVerified(indexSector) + } + + @Test + fun `test nameHashes`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: -1) + 1 + + val buffer = ByteBuffer.wrap(Random.nextBytes(count * Int.SIZE_BYTES)) + + val indexSector = mockk() + every { indexSector.decodeGroupNameHashes(any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupNameHashes(any(), any(), any(), any()) } answers { callOriginal() } + + val decoded = indexSector.decodeGroupNameHashes(maxGroupId, count, true, groupIds, buffer) + val encoded = indexSector.encodeGroupNameHashes(count, true, groupIds, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupNameHashes(maxGroupId, count, true, groupIds, buffer) } + verify(exactly = 1) { indexSector.encodeGroupNameHashes(count, true, groupIds, decoded) } + confirmVerified(indexSector) + } + + @Test + fun `test revisions`() { + val count = Random.nextInt(10..50) + val groupIds = IntStream.generate { Random.nextInt(count..count * 2) }.distinct().limit(count.toLong()).sorted().toArray() + val maxGroupId = (groupIds.maxOrNull() ?: 0) + 1 + + val buffer = ByteBuffer.wrap(Random.nextBytes(count * Int.SIZE_BYTES)) + + val indexSector = mockk() + every { indexSector.decodeGroupRevisions(any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.encodeGroupRevisions(any(), any(), any()) } answers { callOriginal() } + + val decoded = indexSector.decodeGroupRevisions(maxGroupId, count, groupIds, buffer) + val encoded = indexSector.encodeGroupRevisions(count, groupIds, decoded) + + assertEquals( + buffer.array().contentToString(), + encoded.array().contentToString() + ) + + verify(exactly = 1) { indexSector.decodeGroupRevisions(maxGroupId, count, groupIds, buffer) } + verify(exactly = 1) { indexSector.encodeGroupRevisions(count, groupIds, decoded) } + confirmVerified(indexSector) + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..c46acb0 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,4 @@ +kotlin.code.style=official +org.gradle.jvmargs=-Xmx2048m +org.gradle.daemon=true +org.gradle.parallel=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 zcma&O1CT9Y(k9%tZQHhO+qUh#ZQHhO+qmuS+qP|E@9xZO?0h@l{(r>DQ>P;GjjD{w zH}lENr;dU&FbEU?00aa80D$0M0RRB{U*7-#kbjS|qAG&4l5%47zyJ#WrfA#1$1Ctx zf&Z_d{GW=lf^w2#qRJ|CvSJUi(^E3iv~=^Z(zH}F)3Z%V3`@+rNB7gTVU{Bb~90p|f+0(v;nz01EG7yDMX9@S~__vVgv%rS$+?IH+oZ03D5zYrv|^ zC1J)SruYHmCki$jLBlTaE5&dFG9-kq3!^i>^UQL`%gn6)jz54$WDmeYdsBE9;PqZ_ zoGd=P4+|(-u4U1dbAVQrFWoNgNd;0nrghPFbQrJctO>nwDdI`Q^i0XJDUYm|T|RWc zZ3^Qgo_Qk$%Fvjj-G}1NB#ZJqIkh;kX%V{THPqOyiq)d)0+(r9o(qKlSp*hmK#iIY zA^)Vr$-Hz<#SF=0@tL@;dCQsm`V9s1vYNq}K1B)!XSK?=I1)tX+bUV52$YQu*0%fnWEukW>mxkz+%3-S!oguE8u#MGzST8_Dy^#U?fA@S#K$S@9msUiX!gd_ow>08w5)nX{-KxqMOo7d?k2&?Vf z&diGDtZr(0cwPe9z9FAUSD9KC)7(n^lMWuayCfxzy8EZsns%OEblHFSzP=cL6}?J| z0U$H!4S_TVjj<`6dy^2j`V`)mC;cB%* z8{>_%E1^FH!*{>4a7*C1v>~1*@TMcLK{7nEQ!_igZC}ikJ$*<$yHy>7)oy79A~#xE zWavoJOIOC$5b6*q*F_qN1>2#MY)AXVyr$6x4b=$x^*aqF*L?vmj>Mgv+|ITnw_BoW zO?jwHvNy^prH{9$rrik1#fhyU^MpFqF2fYEt(;4`Q&XWOGDH8k6M=%@fics4ajI;st# zCU^r1CK&|jzUhRMv;+W~6N;u<;#DI6cCw-otsc@IsN3MoSD^O`eNflIoR~l4*&-%RBYk@gb^|-JXs&~KuSEmMxB}xSb z@K76cXD=Y|=I&SNC2E+>Zg?R6E%DGCH5J1nU!A|@eX9oS(WPaMm==k2s_ueCqdZw| z&hqHp)47`c{BgwgvY2{xz%OIkY1xDwkw!<0veB#yF4ZKJyabhyyVS`gZepcFIk%e2 zTcrmt2@-8`7i-@5Nz>oQWFuMC_KlroCl(PLSodswHqJ3fn<;gxg9=}~3x_L3P`9Sn zChIf}8vCHvTriz~T2~FamRi?rh?>3bX1j}%bLH+uFX+p&+^aXbOK7clZxdU~6Uxgy z8R=obwO4dL%pmVo*Ktf=lH6hnlz_5k3cG;m8lgaPp~?eD!Yn2kf)tU6PF{kLyn|oI@eQ`F z3IF7~Blqg8-uwUuWZScRKn%c2_}dXB6Dx_&xR*n9M9LXasJhtZdr$vBY!rP{c@=)& z#!?L$2UrkvClwQO>U*fSMs67oSj2mxiJ$t;E|>q%Kh_GzzWWO&3;ufU%2z%ucBU8H z3WIwr$n)cfCXR&>tyB7BcSInK>=ByZA%;cVEJhcg<#6N{aZC4>K41XF>ZgjG`z_u& zGY?;Ad?-sgiOnI`oppF1o1Gurqbi*;#x2>+SSV6|1^G@ooVy@fg?wyf@0Y!UZ4!}nGuLeC^l)6pwkh|oRY`s1Pm$>zZ3u-83T|9 zGaKJIV3_x+u1>cRibsaJpJqhcm%?0-L;2 zitBrdRxNmb0OO2J%Y&Ym(6*`_P3&&5Bw157{o7LFguvxC$4&zTy#U=W*l&(Q2MNO} zfaUwYm{XtILD$3864IA_nn34oVa_g^FRuHL5wdUd)+W-p-iWCKe8m_cMHk+=? zeKX)M?Dt(|{r5t7IenkAXo%&EXIb-i^w+0CX0D=xApC=|Xy(`xy+QG^UyFe z+#J6h_&T5i#sV)hj3D4WN%z;2+jJcZxcI3*CHXGmOF3^)JD5j&wfX)e?-|V0GPuA+ zQFot%aEqGNJJHn$!_}#PaAvQ^{3-Ye7b}rWwrUmX53(|~i0v{}G_sI9uDch_brX&6 zWl5Ndj-AYg(W9CGfQf<6!YmY>Ey)+uYd_JNXH=>|`OH-CDCmcH(0%iD_aLlNHKH z7bcW-^5+QV$jK?R*)wZ>r9t}loM@XN&M-Pw=F#xn(;u3!(3SXXY^@=aoj70;_=QE9 zGghsG3ekq#N||u{4We_25U=y#T*S{4I{++Ku)> zQ!DZW;pVcn>b;&g2;YE#+V`v*Bl&Y-i@X6D*OpNA{G@JAXho&aOk(_j^weW{#3X5Y z%$q_wpb07EYPdmyH(1^09i$ca{O<}7) zRWncXdSPgBE%BM#by!E>tdnc$8RwUJg1*x($6$}ae$e9Knj8gvVZe#bLi!<+&BkFj zg@nOpDneyc+hU9P-;jmOSMN|*H#>^Ez#?;%C3hg_65leSUm;iz)UkW)jX#p)e&S&M z1|a?wDzV5NVnlhRBCd_;F87wp>6c<&nkgvC+!@KGiIqWY4l}=&1w7|r6{oBN8xyzh zG$b#2=RJp_iq6)#t5%yLkKx(0@D=C3w+oiXtSuaQ%I1WIb-eiE$d~!)b@|4XLy!CZ z9p=t=%3ad@Ep+<9003D2KZ5VyP~_n$=;~r&YUg5UZ0KVD&tR1DHy9x)qWtKJp#Kq# zP*8p#W(8JJ_*h_3W}FlvRam?<4Z+-H77^$Lvi+#vmhL9J zJ<1SV45xi;SrO2f=-OB(7#iNA5)x1uNC-yNxUw|!00vcW2PufRm>e~toH;M0Q85MQLWd?3O{i8H+5VkR@l9Dg-ma ze2fZ%>G(u5(k9EHj2L6!;(KZ8%8|*-1V|B#EagbF(rc+5iL_5;Eu)L4Z-V;0HfK4d z*{utLse_rvHZeQ>V5H=f78M3Ntg1BPxFCVD{HbNA6?9*^YIq;B-DJd{Ca2L#)qWP? zvX^NhFmX?CTWw&Ns}lgs;r3i+Bq@y}Ul+U%pzOS0Fcv9~aB(0!>GT0)NO?p=25LjN z2bh>6RhgqD7bQj#k-KOm@JLgMa6>%-ok1WpOe)FS^XOU{c?d5shG(lIn3GiVBxmg`u%-j=)^v&pX1JecJics3&jvPI)mDut52? z3jEA)DM%}BYbxxKrizVYwq?(P&19EXlwD9^-6J+4!}9{ywR9Gk42jjAURAF&EO|~N z)?s>$Da@ikI4|^z0e{r`J8zIs>SpM~Vn^{3fArRu;?+43>lD+^XtUcY1HidJwnR6+ z!;oG2=B6Z_=M%*{z-RaHc(n|1RTKQdNjjV!Pn9lFt^4w|AeN06*j}ZyhqZ^!-=cyGP_ShV1rGxkx8t zB;8`h!S{LD%ot``700d0@Grql(DTt4Awgmi+Yr0@#jbe=2#UkK%rv=OLqF)9D7D1j z!~McAwMYkeaL$~kI~90)5vBhBzWYc3Cj1WI0RS`z000R8-@ET0dA~*r(gSiCJmQMN&4%1D zyVNf0?}sBH8zNbBLn>~(W{d3%@kL_eQ6jEcR{l>C|JK z(R-fA!z|TTRG40|zv}7E@PqCAXP3n`;%|SCQ|ZS%ym$I{`}t3KPL&^l5`3>yah4*6 zifO#{VNz3)?ZL$be;NEaAk9b#{tV?V7 zP|wf5YA*1;s<)9A4~l3BHzG&HH`1xNr#%){4xZ!jq%o=7nN*wMuXlFV{HaiQLJ`5G zBhDi#D(m`Q1pLh@Tq+L;OwuC52RdW7b8}~60WCOK5iYMUad9}7aWBuILb({5=z~YF zt?*Jr5NG+WadM{mDL>GyiByCuR)hd zA=HM?J6l1Xv0Dl+LW@w$OTcEoOda^nFCw*Sy^I@$sSuneMl{4ys)|RY#9&NxW4S)9 zq|%83IpslTLoz~&vTo!Ga@?rj_kw{|k{nv+w&Ku?fyk4Ki4I?);M|5Axm)t+BaE)D zm(`AQ#k^DWrjbuXoJf2{Aj^KT zFb1zMSqxq|vceV+Mf-)$oPflsO$@*A0n0Z!R{&(xh8s}=;t(lIy zv$S8x>m;vQNHuRzoaOo?eiWFe{0;$s`Bc+Osz~}Van${u;g(su`3lJ^TEfo~nERfP z)?aFzpDgnLYiERsKPu|0tq4l2wT)Atr6Qb%m-AUn6HnCue*yWICp7TjW$@sO zm5rm4aTcPQ(rfi7a`xP7cKCFrJD}*&_~xgLyr^-bmsL}y;A5P|al8J3WUoBSjqu%v zxC;mK!g(7r6RRJ852Z~feoC&sD3(6}^5-uLK8o)9{8L_%%rItZK9C){UxB|;G>JbP zsRRtS4-3B*5c+K2kvmgZK8472%l>3cntWUOVHxB|{Ay~aOg5RN;{PJgeVD*H%ac+y!h#wi%o2bF2Ca8IyMyH{>4#{E_8u^@+l-+n=V}Sq?$O z{091@v%Bd*3pk0^2UtiF9Z+(a@wy6 zUdw8J*ze$K#=$48IBi1U%;hmhO>lu!uU;+RS}p&6@rQila7WftH->*A4=5W|Fmtze z)7E}jh@cbmr9iup^i%*(uF%LG&!+Fyl@LFA-}Ca#bxRfDJAiR2dt6644TaYw1Ma79 zt8&DYj31j^5WPNf5P&{)J?WlCe@<3u^78wnd(Ja4^a>{^Tw}W>|Cjt^If|7l^l)^Q zbz|7~CF(k_9~n|h;ysZ+jHzkXf(*O*@5m zLzUmbHp=x!Q|!9NVXyipZ3)^GuIG$k;D)EK!a5=8MFLI_lpf`HPKl=-Ww%z8H_0$j ztJ||IfFG1lE9nmQ0+jPQy zCBdKkjArH@K7jVcMNz);Q(Q^R{d5G?-kk;Uu_IXSyWB)~KGIizZL(^&qF;|1PI7!E zTP`%l)gpX|OFn&)M%txpQ2F!hdA~hX1Cm5)IrdljqzRg!f{mN%G~H1&oqe`5eJCIF zHdD7O;AX-{XEV(a`gBFJ9ews#CVS2y!&>Cm_dm3C8*n3MA*e67(WC?uP@8TXuMroq z{#w$%z@CBIkRM7?}Xib+>hRjy?%G!fiw8! z8(gB+8J~KOU}yO7UGm&1g_MDJ$IXS!`+*b*QW2x)9>K~Y*E&bYMnjl6h!{17_8d!%&9D`a7r&LKZjC<&XOvTRaKJ1 zUY@hl5^R&kZl3lU3njk`3dPzxj$2foOL26r(9zsVF3n_F#v)s5vv3@dgs|lP#eylq62{<-vczqP!RpVBTgI>@O6&sU>W|do17+#OzQ7o5A$ICH z?GqwqnK^n2%LR;$^oZM;)+>$X3s2n}2jZ7CdWIW0lnGK-b#EG01)P@aU`pg}th&J-TrU`tIpb5t((0eu|!u zQz+3ZiOQ^?RxxK4;zs=l8q!-n7X{@jSwK(iqNFiRColuEOg}!7cyZi`iBX4g1pNBj zAPzL?P^Ljhn;1$r8?bc=#n|Ed7wB&oHcw()&*k#SS#h}jO?ZB246EGItsz*;^&tzp zu^YJ0=lwsi`eP_pU8}6JA7MS;9pfD;DsSsLo~ogzMNP70@@;Fm8f0^;>$Z>~}GWRw!W5J3tNX*^2+1f3hz{~rIzJo z6W%J(H!g-eI_J1>0juX$X4Cl6i+3wbc~k146UIX&G22}WE>0ga#WLsn9tY(&29zBvH1$`iWtTe zG2jYl@P!P)eb<5DsR72BdI7-zP&cZNI{7q3e@?N8IKc4DE#UVr->|-ryuJXk^u^>4 z$3wE~=q390;XuOQP~TNoDR?#|NSPJ%sTMInA6*rJ%go|=YjGe!B>z6u$IhgQSwoV* zjy3F2#I>uK{42{&IqP59)Y(1*Z>>#W8rCf4_eVsH)`v!P#^;BgzKDR`ARGEZzkNX+ zJUQu=*-ol=Xqqt5=`=pA@BIn@6a9G8C{c&`i^(i+BxQO9?YZ3iu%$$da&Kb?2kCCo zo7t$UpSFWqmydXf@l3bVJ=%K?SSw)|?srhJ-1ZdFu*5QhL$~-IQS!K1s@XzAtv6*Y zl8@(5BlWYLt1yAWy?rMD&bwze8bC3-GfNH=p zynNFCdxyX?K&G(ZZ)afguQ2|r;XoV^=^(;Cku#qYn4Lus`UeKt6rAlFo_rU`|Rq z&G?~iWMBio<78of-2X(ZYHx~=U0Vz4btyXkctMKdc9UM!vYr~B-(>)(Hc|D zMzkN4!PBg%tZoh+=Gba!0++d193gbMk2&krfDgcbx0jI92cq?FFESVg0D$>F+bil} zY~$)|>1HZsX=5sAZ2WgPB5P=8X#TI+NQ(M~GqyVB53c6IdX=k>Wu@A0Svf5#?uHaF zsYn|koIi3$(%GZ2+G+7Fv^lHTb#5b8sAHSTnL^qWZLM<(1|9|QFw9pnRU{svj}_Al zL)b9>fN{QiA($8peNEJyy`(a{&uh-T4_kdZFIVsKKVM(?05}76EEz?#W za^fiZOAd14IJ4zLX-n7Lq0qlQ^lW8Cvz4UKkV9~P}>sq0?xD3vg+$4vLm~C(+ zM{-3Z#qnZ09bJ>}j?6ry^h+@PfaD7*jZxBEY4)UG&daWb??6)TP+|3#Z&?GL?1i+280CFsE|vIXQbm| zM}Pk!U`U5NsNbyKzkrul-DzwB{X?n3E6?TUHr{M&+R*2%yOiXdW-_2Yd6?38M9Vy^ z*lE%gA{wwoSR~vN0=no}tP2Ul5Gk5M(Xq`$nw#ndFk`tcpd5A=Idue`XZ!FS>Q zG^0w#>P4pPG+*NC9gLP4x2m=cKP}YuS!l^?sHSFftZy{4CoQrb_ z^20(NnG`wAhMI=eq)SsIE~&Gp9Ne0nD4%Xiu|0Fj1UFk?6avDqjdXz{O1nKao*46y zT8~iA%Exu=G#{x=KD;_C&M+Zx4+n`sHT>^>=-1YM;H<72k>$py1?F3#T1*ef9mLZw z5naLQr?n7K;2l+{_uIw*_1nsTn~I|kkCgrn;|G~##hM;9l7Jy$yJfmk+&}W@JeKcF zx@@Woiz8qdi|D%aH3XTx5*wDlbs?dC1_nrFpm^QbG@wM=i2?Zg;$VK!c^Dp8<}BTI zyRhAq@#%2pGV49*Y5_mV4+OICP|%I(dQ7x=6Ob}>EjnB_-_18*xrY?b%-yEDT(wrO z9RY2QT0`_OpGfMObKHV;QLVnrK%mc?$WAdIT`kJQT^n%GuzE7|9@k3ci5fYOh(287 zuIbg!GB3xLg$YN=n)^pHGB0jH+_iIiC=nUcD;G6LuJsjn2VI1cyZx=a?ShCsF==QK z;q~*m&}L<-cb+mDDXzvvrRsybcgQ;Vg21P(uLv5I+eGc7o7tc6`;OA9{soHFOz zT~2?>Ts}gprIX$wRBb4yE>ot<8+*Bv`qbSDv*VtRi|cyWS>)Fjs>fkNOH-+PX&4(~ z&)T8Zam2L6puQl?;5zg9h<}k4#|yH9czHw;1jw-pwBM*O2hUR6yvHATrI%^mvs9q_ z&ccT0>f#eDG<^WG^q@oVqlJrhxH)dcq2cty@l3~|5#UDdExyXUmLQ}f4#;6fI{f^t zDCsgIJ~0`af%YR%Ma5VQq-p21k`vaBu6WE?66+5=XUd%Ay%D$irN>5LhluRWt7 zov-=f>QbMk*G##&DTQyou$s7UqjjW@k6=!I@!k+S{pP8R(2=e@io;N8E`EOB;OGoI zw6Q+{X1_I{OO0HPpBz!X!@`5YQ2)t{+!?M_iH25X(d~-Zx~cXnS9z>u?+If|iNJbx zyFU2d1!ITX64D|lE0Z{dLRqL1Ajj=CCMfC4lD3&mYR_R_VZ>_7_~|<^o*%_&jevU+ zQ4|qzci=0}Jydw|LXLCrOl1_P6Xf@c0$ieK2^7@A9UbF{@V_0p%lqW|L?5k>bVM8|p5v&2g;~r>B8uo<4N+`B zH{J)h;SYiIVx@#jI&p-v3dwL5QNV1oxPr8J%ooezTnLW>i*3Isb49%5i!&ac_dEXv zvXmVUck^QHmyrF8>CGXijC_R-y(Qr{3Zt~EmW)-nC!tiH`wlw5D*W7Pip;T?&j%kX z6DkZX4&}iw>hE(boLyjOoupf6JpvBG8}jIh!!VhnD0>}KSMMo{1#uU6kiFcA04~|7 zVO8eI&x1`g4CZ<2cYUI(n#wz2MtVFHx47yE5eL~8bot~>EHbevSt}LLMQX?odD{Ux zJMnam{d)W4da{l7&y-JrgiU~qY3$~}_F#G7|MxT)e;G{U`In&?`j<5D->}cb{}{T(4DF0BOk-=1195KB-E*o@c?`>y#4=dMtYtSY=&L{!TAjFVcq0y@AH`vH! z$41+u!Ld&}F^COPgL(EE{0X7LY&%D7-(?!kjFF7=qw<;`V{nwWBq<)1QiGJgUc^Vz ztMUlq1bZqKn17|6x6iAHbWc~l1HcmAxr%$Puv!znW)!JiukwIrqQ00|H$Z)OmGG@= zv%A8*4cq}(?qn4rN6o`$Y))(MyXr8R<2S^J+v(wmFmtac!%VOfN?&(8Nr!T@kV`N; z*Q33V3t`^rN&aBiHet)18wy{*wi1=W!B%B-Q6}SCrUl$~Hl{@!95ydml@FK8P=u4s z4e*7gV2s=YxEvskw2Ju!2%{8h01rx-3`NCPc(O zH&J0VH5etNB2KY6k4R@2Wvl^Ck$MoR3=)|SEclT2ccJ!RI9Nuter7u9@;sWf-%um;GfI!=eEIQ2l2p_YWUd{|6EG ze{yO6;lMc>;2tPrsNdi@&1K6(1;|$xe8vLgiouj%QD%gYk`4p{Ktv9|j+!OF-P?@p z;}SV|oIK)iwlBs+`ROXkhd&NK zzo__r!B>tOXpBJMDcv!Mq54P+n4(@dijL^EpO1wdg~q+!DT3lB<>9AANSe!T1XgC=J^)IP0XEZ()_vpu!!3HQyJhwh?r`Ae%Yr~b% zO*NY9t9#qWa@GCPYOF9aron7thfWT`eujS4`t2uG6)~JRTI;f(ZuoRQwjZjp5Pg34 z)rp$)Kr?R+KdJ;IO;pM{$6|2y=k_siqvp%)2||cHTe|b5Ht8&A{wazGNca zX$Ol?H)E_R@SDi~4{d-|8nGFhZPW;Cts1;08TwUvLLv&_2$O6Vt=M)X;g%HUr$&06 zISZb(6)Q3%?;3r~*3~USIg=HcJhFtHhIV(siOwV&QkQe#J%H9&E21!C*d@ln3E@J* zVqRO^<)V^ky-R|%{(9`l-(JXq9J)1r$`uQ8a}$vr9E^nNiI*thK8=&UZ0dsFN_eSl z(q~lnD?EymWLsNa3|1{CRPW60>DSkY9YQ;$4o3W7Ms&@&lv9eH!tk~N&dhqX&>K@} zi1g~GqglxkZ5pEFkllJ)Ta1I^c&Bt6#r(QLQ02yHTaJB~- zCcE=5tmi`UA>@P=1LBfBiqk)HB4t8D?02;9eXj~kVPwv?m{5&!&TFYhu>3=_ zsGmYZ^mo*-j69-42y&Jj0cBLLEulNRZ9vXE)8~mt9C#;tZs;=#M=1*hebkS;7(aGf zcs7zH(I8Eui9UU4L--))yy`&d&$In&VA2?DAEss4LAPCLd>-$i?lpXvn!gu^JJ$(DoUlc6wE98VLZ*z`QGQov5l4Fm_h?V-;mHLYDVOwKz7>e4+%AzeO>P6v}ndPW| zM>m#6Tnp7K?0mbK=>gV}=@k*0Mr_PVAgGMu$j+pWxzq4MAa&jpCDU&-5eH27Iz>m^ zax1?*HhG%pJ((tkR(V(O(L%7v7L%!_X->IjS3H5kuXQT2!ow(;%FDE>16&3r){!ex zhf==oJ!}YU89C9@mfDq!P3S4yx$aGB?rbtVH?sHpg?J5C->!_FHM%Hl3#D4eplxzQ zRA+<@LD%LKSkTk2NyWCg7u=$%F#;SIL44~S_OGR}JqX}X+=bc@swpiClB`Zbz|f!4 z7Ysah7OkR8liXfI`}IIwtEoL}(URrGe;IM8%{>b1SsqXh)~w}P>yiFRaE>}rEnNkT z!HXZUtxUp1NmFm)Dm@-{FI^aRQqpSkz}ZSyKR%Y}YHNzBk)ZIp} zMtS=aMvkgWKm9&oTcU0?S|L~CDqA+sHpOxwnswF-fEG)cXCzUR?ps@tZa$=O)=L+5 zf%m58cq8g_o}3?Bhh+c!w4(7AjxwQ3>WnVi<{{38g7yFboo>q|+7qs<$8CPXUFAN< zG&}BHbbyQ5n|qqSr?U~GY{@GJ{(Jny{bMaOG{|IkUj7tj^9pa9|FB_<+KHLxSxR;@ zHpS$4V)PP+tx}22fWx(Ku9y+}Ap;VZqD0AZW4gCDTPCG=zgJmF{|x;(rvdM|2|9a}cex6xrMkERnkE;}jvU-kmzd%_J50$M`lIPCKf+^*zL=@LW`1SaEc%=m zQ+lT06Gw+wVwvQ9fZ~#qd430v2HndFsBa9WjD0P}K(rZYdAt^5WQIvb%D^Q|pkVE^ zte$&#~zmULFACGfS#g=2OLOnIf2Of-k!(BIHjs77nr!5Q1*I9 z1%?=~#Oss!rV~?-6Gm~BWJiA4mJ5TY&iPm_$)H1_rTltuU1F3I(qTQ^U$S>%$l z)Wx1}R?ij0idp@8w-p!Oz{&*W;v*IA;JFHA9%nUvVDy7Q8woheC#|8QuDZb-L_5@R zOqHwrh|mVL9b=+$nJxM`3eE{O$sCt$UK^2@L$R(r^-_+z?lOo+me-VW=Zw z-Bn>$4ovfWd%SPY`ab-u9{INc*k2h+yH%toDHIyqQ zO68=u`N}RIIs7lsn1D){)~%>ByF<>i@qFb<-axvu(Z+6t7v<^z&gm9McRB~BIaDn$ z#xSGT!rzgad8o>~kyj#h1?7g96tOcCJniQ+*#=b7wPio>|6a1Z?_(TS{)KrPe}(8j z!#&A=k(&Pj^F;r)CI=Z{LVu>uj!_W1q4b`N1}E(i%;BWjbEcnD=mv$FL$l?zS6bW!{$7j1GR5ocn94P2u{ z70tAAcpqtQo<@cXw~@i-@6B23;317|l~S>CB?hR5qJ%J3EFgyBdJd^fHZu7AzHF(BQ!tyAz^L0`X z23S4Fe{2X$W0$zu9gm%rg~A>ijaE#GlYlrF9$ds^QtaszE#4M(OLVP2O-;XdT(XIC zatwzF*)1c+t~c{L=fMG8Z=k5lv>U0;C{caN1NItnuSMp)6G3mbahu>E#sj&oy94KC zpH}8oEw{G@N3pvHhp{^-YaZeH;K+T_1AUv;IKD<=mv^&Ueegrb!yf`4VlRl$M?wsl zZyFol(2|_QM`e_2lYSABpKR{{NlxlDSYQNkS;J66aT#MSiTx~;tUmvs-b*CrR4w=f z8+0;*th6kfZ3|5!Icx3RV11sp=?`0Jy3Fs0N4GZQMN=8HmT6%x9@{Dza)k}UwL6JT zHRDh;%!XwXr6yuuy`4;Xsn0zlR$k%r%9abS1;_v?`HX_hI|+EibVnlyE@3aL5vhQq zlIG?tN^w@0(v9M*&L+{_+RQZw=o|&BRPGB>e5=ys7H`nc8nx)|-g;s7mRc7hg{GJC zAe^vCIJhajmm7C6g! zL&!WAQ~5d_5)00?w_*|*H>3$loHrvFbitw#WvLB!JASO?#5Ig5$Ys10n>e4|3d;tS zELJ0|R4n3Az(Fl3-r^QiV_C;)lQ1_CW{5bKS15U|E9?ZgLec@%kXr84>5jV2a5v=w z?pB1GPdxD$IQL4)G||B_lI+A=08MUFFR4MxfGOu07vfIm+j=z9tp~5i_6jb`tR>qV z$#`=BQ*jpCjm$F0+F)L%xRlnS%#&gro6PiRfu^l!EVan|r3y}AHJQOORGx4~ z&<)3=K-tx518DZyp%|!EqpU!+X3Et7n2AaC5(AtrkW>_57i}$eqs$rupubg0a1+WO zGHZKLN2L0D;ab%{_S1Plm|hx8R?O14*w*f&2&bB050n!R2by zw!@XOQx$SqZ5I<(Qu$V6g>o#A!JVwErWv#(Pjx=KeS0@hxr4?13zj#oWwPS(7Ro|v z>Mp@Kmxo79q|}!5qtX2-O@U&&@6s~!I&)1WQIl?lTnh6UdKT_1R640S4~f=_xoN3- zI+O)$R@RjV$F=>Ti7BlnG1-cFKCC(t|Qjm{SalS~V-tX#+2ekRhwmN zZr`8{QF6y~Z!D|{=1*2D-JUa<(1Z=;!Ei!KiRNH?o{p5o3crFF=_pX9O-YyJchr$~ zRC`+G+8kx~fD2k*ZIiiIGR<8r&M@3H?%JVOfE>)})7ScOd&?OjgAGT@WVNSCZ8N(p zuQG~76GE3%(%h1*vUXg$vH{ua0b`sQ4f0*y=u~lgyb^!#CcPJa2mkSEHGLsnO^kb$ zru5_l#nu=Y{rSMWiYx?nO{8I!gH+?wEj~UM?IrG}E|bRIBUM>UlY<`T1EHpRr36vv zBi&dG8oxS|J$!zoaq{+JpJy+O^W(nt*|#g32bd&K^w-t>!Vu9N!k9eA8r!Xc{utY> zg9aZ(D2E0gL#W0MdjwES-7~Wa8iubPrd?8-$C4BP?*wok&O8+ykOx{P=Izx+G~hM8 z*9?BYz!T8~dzcZr#ux8kS7u7r@A#DogBH8km8Ry4slyie^n|GrTbO|cLhpqgMdsjX zJ_LdmM#I&4LqqsOUIXK8gW;V0B(7^$y#h3h>J0k^WJfAMeYek%Y-Dcb_+0zPJez!GM zAmJ1u;*rK=FNM0Nf}Y!!P9c4)HIkMnq^b;JFd!S3?_Qi2G#LIQ)TF|iHl~WKK6JmK zbv7rPE6VkYr_%_BT}CK8h=?%pk@3cz(UrZ{@h40%XgThP*-Oeo`T0eq9 zA8BnWZKzCy5e&&_GEsU4*;_k}(8l_&al5K-V*BFM=O~;MgRkYsOs%9eOY6s6AtE*<7GQAR2ulC3RAJrG_P1iQK5Z~&B z&f8X<>yJV6)oDGIlS$Y*D^Rj(cszTy5c81a5IwBr`BtnC6_e`ArI8CaTX_%rx7;cn zR-0?J_LFg*?(#n~G8cXut(1nVF0Oka$A$1FGcERU<^ggx;p@CZc?3UB41RY+wLS`LWFNSs~YP zuw1@DNN3lTd|jDL7gjBsd9}wIw}4xT2+8dBQzI00m<@?c2L%>}QLfK5%r!a-iII`p zX@`VEUH)uj^$;7jVUYdADQ2k*!1O3WdfgF?OMtUXNpQ1}QINamBTKDuv19^{$`8A1 zeq%q*O0mi@(%sZU>Xdb0Ru96CFqk9-L3pzLVsMQ`Xpa~N6CR{9Rm2)A|CI21L(%GW zh&)Y$BNHa=FD+=mBw3{qTgw)j0b!Eahs!rZnpu)z!!E$*eXE~##yaXz`KE5(nQM`s zD!$vW9XH)iMxu9R>r$VlLk9oIR%HxpUiW=BK@4U)|1WNQ=mz9a z^!KkO=>GaJ!GBXm{KJj^;kh-MkUlEQ%lza`-G&}C5y1>La1sR6hT=d*NeCnuK%_LV zOXt$}iP6(YJKc9j-Fxq~*ItVUqljQ8?oaysB-EYtFQp9oxZ|5m0^Hq(qV!S+hq#g( z?|i*H2MIr^Kxgz+3vIljQ*Feejy6S4v~jKEPTF~Qhq!(ms5>NGtRgO5vfPPc4Z^AM zTj!`5xEreIN)vaNxa|q6qWdg>+T`Ol0Uz)ckXBXEGvPNEL3R8hB3=C5`@=SYgAju1 z!)UBr{2~=~xa{b8>x2@C7weRAEuatC)3pkRhT#pMPTpSbA|tan%U7NGMvzmF?c!V8 z=pEWxbdXbTAGtWTyI?Fml%lEr-^AE}w#l(<7OIw;ctw}imYax&vR4UYNJZK6P7ZOd zP87XfhnUHxCUHhM@b*NbTi#(-8|wcv%3BGNs#zRCVV(W?1Qj6^PPQa<{yaBwZ`+<`w|;rqUY_C z&AeyKwwf*q#OW-F()lir=T^<^wjK65Lif$puuU5+tk$;e_EJ;Lu+pH>=-8=PDhkBg z8cWt%@$Sc#C6F$Vd+0507;{OOyT7Hs%nKS88q-W!$f~9*WGBpHGgNp}=C*7!RiZ5s zn1L_DbKF@B8kwhDiLKRB@lsXVVLK|ph=w%_`#owlf@s@V(pa`GY$8h%;-#h@TsO|Y8V=n@*!Rog7<7Cid%apR|x zOjhHCyfbIt%+*PCveTEcuiDi%Wx;O;+K=W?OFUV%)%~6;gl?<0%)?snDDqIvkHF{ zyI02)+lI9ov42^hL>ZRrh*HhjF9B$A@=H94iaBESBF=eC_KT$8A@uB^6$~o?3Wm5t1OIaqF^~><2?4e3c&)@wKn9bD? zoeCs;H>b8DL^F&>Xw-xjZEUFFTv>JD^O#1E#)CMBaG4DX9bD(Wtc8Rzq}9soQ8`jf zeSnHOL}<+WVSKp4kkq&?SbETjq6yr@4%SAqOG=9E(3YeLG9dtV+8vmzq+6PFPk{L; z(&d++iu=^F%b+ea$i2UeTC{R*0Isk;vFK!no<;L+(`y`3&H-~VTdKROkdyowo1iqR zbVW(3`+(PQ2>TKY>N!jGmGo7oeoB8O|P_!Ic@ zZ^;3dnuXo;WJ?S+)%P>{Hcg!Jz#2SI(s&dY4QAy_vRlmOh)QHvs_7c&zkJCmJGVvV zX;Mtb>QE+xp`KyciG$Cn*0?AK%-a|=o!+7x&&yzHQOS>8=B*R=niSnta^Pxp1`=md z#;$pS$4WCT?mbiCYU?FcHGZ#)kHVJTTBt^%XE(Q};aaO=Zik0UgLcc0I(tUpt(>|& zcxB_|fxCF7>&~5eJ=Dpn&5Aj{A^cV^^}(7w#p;HG&Q)EaN~~EqrE1qKrMAc&WXIE;>@<&)5;gD2?={Xf@Mvn@OJKw=8Mgn z!JUFMwD+s==JpjhroT&d{$kQAy%+d`a*XxDEVxy3`NHzmITrE`o!;5ClXNPb4t*8P zzAivdr{j_v!=9!^?T3y?gzmqDWX6mkzhIzJ-3S{T5bcCFMr&RPDryMcdwbBuZbsgN zGrp@^i?rcfN7v0NKGzDPGE#4yszxu=I_`MI%Z|10nFjU-UjQXXA?k8Pk|OE<(?ae) zE%vG#eZAlj*E7_3dx#Zz4kMLj>H^;}33UAankJiDy5ZvEhrjr`!9eMD8COp}U*hP+ zF}KIYx@pkccIgyxFm#LNw~G&`;o&5)2`5aogs`1~7cMZQ7zj!%L4E`2yzlQN6REX20&O<9 zKV6fyr)TScJPPzNTC2gL+0x#=u>(({{D7j)c-%tvqls3#Y?Z1m zV5WUE)zdJ{$p>yX;^P!UcXP?UD~YM;IRa#Rs5~l+*$&nO(;Ers`G=0D!twR(0GF@c zHl9E5DQI}Oz74n zfKP>&$q0($T4y$6w(p=ERAFh+>n%iaeRA%!T%<^+pg?M)@ucY<&59$x9M#n+V&>}=nO9wCV{O~lg&v#+jcUj(tQ z`0u1YH)-`U$15a{pBkGyPL0THv1P|4e@pf@3IBZS4dVJPo#H>pWq%Lr0YS-SeWash z8R7=jb28KPMI|_lo#GEO|5B?N_e``H*23{~a!AmUJ+fb4HX-%QI@lSEUxKlGV7z7Q zSKw@-TR>@1RL%w{x}dW#k1NgW+q4yt2Xf1J62Bx*O^WG8OJ|FqI4&@d3_o8Id@*)4 zYrk=>@!wv~mh7YWv*bZhxqSmFh2Xq)o=m;%n$I?GSz49l1$xRpPu_^N(vZ>*>Z<04 z2+rP70oM=NDysd!@fQdM2OcyT?3T^Eb@lIC-UG=Bw{BjQ&P`KCv$AcJ;?`vdZ4){d z&gkoUK{$!$$K`3*O-jyM1~p-7T*qb)Ys>Myt^;#1&a%O@x8A+E>! zY8=eD`ZG)LVagDLBeHg>=atOG?Kr%h4B%E6m@J^C+U|y)XX@f z8oyJDW|9g=<#f<{JRr{y#~euMnv)`7j=%cHWLc}ngjq~7k**6%4u>Px&W%4D94(r* z+akunK}O0DC2A%Xo9jyF;DobX?!1I(7%}@7F>i%&nk*LMO)bMGg2N+1iqtg+r(70q zF5{Msgsm5GS7DT`kBsjMvOrkx&|EU!{{~gL4d2MWrAT=KBQ-^zQCUq{5PD1orxlIL zq;CvlWx#f1NWvh`hg011I%?T_s!e38l*lWVt|~z-PO4~~1g)SrJ|>*tXh=QfXT)%( z+ex+inPvD&O4Ur;JGz>$sUOnWdpSLcm1X%aQDw4{dB!cnj`^muI$CJ2%p&-kULVCE z>$eMR36kN$wCPR+OFDM3-U(VOrp9k3)lI&YVFqd;Kpz~K)@Fa&FRw}L(SoD z9B4a+hQzZT-BnVltst&=kq6Y(f^S4hIGNKYBgMxGJ^;2yrO}P3;r)(-I-CZ)26Y6? z&rzHI_1GCvGkgy-t1E;r^3Le30|%$ebDRu2+gdLG)r=A~Qz`}~&L@aGJ{}vVs_GE* zVUjFnzHiXfKQbpv&bR&}l2bzIjAooB)=-XNcYmrGmBh(&iu@o!^hn0^#}m2yZZUK8 zufVm7Gq0y`Mj;9b>`c?&PZkU0j4>IL=UL&-Lp3j&47B5pAW4JceG{!XCA)kT<%2nqCxj<)uy6XR_uws~>_MEKPOpAQ!H zkn>FKh)<9DwwS*|Y(q?$^N!6(51O0 z^JM~Ax{AI1Oj$fs-S5d4T7Z_i1?{%0SsIuQ&r8#(JA=2iLcTN+?>wOL532%&dMYkT z*T5xepC+V6zxhS@vNbMoi|i)=rpli@R9~P!39tWbSSb904ekv7D#quKbgFEMTb48P zuq(VJ+&L8aWU(_FCD$3^uD!YM%O^K(dvy~Wm2hUuh6bD|#(I39Xt>N1Y{ZqXL`Fg6 zKQ?T2htHN!(Bx;tV2bfTtIj7e)liN-29s1kew>v(D^@)#v;}C4-G=7x#;-dM4yRWm zyY`cS21ulzMK{PoaQ6xChEZ}o_#}X-o}<&0)$1#3we?+QeLt;aVCjeA)hn!}UaKt< zat1fHEx13y-rXNMvpUUmCVzocPmN~-Y4(YJvQ#db)4|%B!rBsgAe+*yor~}FrNH08 z3V!97S}D7d$zbSD{$z;@IYMxM6aHdypIuS*pr_U6;#Y!_?0i|&yU*@16l z*dcMqDQgfNBf}?quiu4e>H)yTVfsp#f+Du0@=Kc41QockXkCkvu>FBd6Q+@FL!(Yx z2`YuX#eMEiLEDhp+9uFqME_E^faV&~9qjBHJkIp~%$x^bN=N)K@kvSVEMdDuzA0sn z88CBG?`RX1@#hQNd`o^V{37)!w|nA)QfiYBE^m=yQKv-fQF+UCMcuEe1d4BH7$?>b zJl-r9@0^Ie=)guO1vOd=i$_4sz>y3x^R7n4ED!5oXL3@5**h(xr%Hv)_gILarO46q+MaDOF%ChaymKoI6JU5Pg;7#2n9-18|S1;AK+ zgsn6;k6-%!QD>D?cFy}8F;r@z8H9xN1jsOBw2vQONVqBVEbkiNUqgw~*!^##ht>w0 zUOykwH=$LwX2j&nLy=@{hr)2O&-wm-NyjW7n~Zs9UlH;P7iP3 zI}S(r0YFVYacnKH(+{*)Tbw)@;6>%=&Th=+Z6NHo_tR|JCI8TJiXv2N7ei7M^Q+RM z?9o`meH$5Yi;@9XaNR#jIK^&{N|DYNNbtdb)XW1Lv2k{E>;?F`#Pq|&_;gm~&~Zc9 zf+6ZE%{x4|{YdtE?a^gKyzr}dA>OxQv+pq|@IXL%WS0CiX!V zm$fCePA%lU{%pTKD7|5NJHeXg=I0jL@$tOF@K*MI$)f?om)D63K*M|r`gb9edD1~Y zc|w7N)Y%do7=0{RC|AziW7#am$)9jciRJ?IWl9PE{G3U+$%FcyKs_0Cgq`=K3@ttV z9g;M!3z~f_?P%y3-ph%vBMeS@p7P&Ea8M@97+%XEj*(1E6vHj==d zjsoviB>j^$_^OI_DEPvFkVo(BGRo%cJeD){6Uckei=~1}>sp299|IRjhXe)%?uP0I zF5+>?0#Ye}T^Y$u_rc4=lPcq4K^D(TZG-w30-YiEM=dcK+4#o*>lJ8&JLi+3UcpZk z!^?95S^C0ja^jwP`|{<+3cBVog$(mRdQmadS+Vh~z zS@|P}=|z3P6uS+&@QsMp0no9Od&27O&14zHXGAOEy zh~OKpymK5C%;LLb467@KgIiVwYbYd6wFxI{0-~MOGfTq$nBTB!{SrWmL9Hs}C&l&l#m?s*{tA?BHS4mVKHAVMqm63H<|c5n0~k)-kbg zXidai&9ZUy0~WFYYKT;oe~rytRk?)r8bptITsWj(@HLI;@=v5|XUnSls7$uaxFRL+ zRVMGuL3w}NbV1`^=Pw*0?>bm8+xfeY(1PikW*PB>>Tq(FR`91N0c2&>lL2sZo5=VD zQY{>7dh_TX98L2)n{2OV=T10~*YzX27i2Q7W86M4$?gZIXZaBq#sA*{PH8){|GUi;oM>e?ua7eF4WFuFYZSG| zze?srg|5Ti8Og{O zeFxuw9!U+zhyk?@w zjsA6(oKD=Ka;A>Ca)oPORxK+kxH#O@zhC!!XS4@=swnuMk>t+JmLmFiE^1aX3f<)D@`%K0FGK^gg1a1j>zi z2KhV>sjU7AX3F$SEqrXSC}fRx64GDoc%!u2Yag68Lw@w9v;xOONf@o)Lc|Uh3<21ctTYu-mFZuHk*+R{GjXHIGq3p)tFtQp%TYqD=j1&y)>@zxoxUJ!G@ zgI0XKmP6MNzw>nRxK$-Gbzs}dyfFzt>#5;f6oR27ql!%+{tr+(`(>%51|k`ML} zY4eE)Lxq|JMas(;JibNQds1bUB&r}ydMQXBY4x(^&fY_&LlQC)3hylc$~8&~|06-D z#T+%66rYbHX%^KuqJED_wuGB+=h`nWA!>1n0)3wZrBG3%`b^Ozv6__dNa@%V14|!D zQ?o$z5u0^8`giv%qE!BzZ!3j;BlDlJDk)h@9{nSQeEk!z9RGW) z${RSF3phEM*ce*>Xdp}585vj$|40=&S{S-GTiE?Op*vY&Lvr9}BO$XWy80IF+6@%n z5*2ueT_g@ofP#u5pxb7n*fv^Xtt7&?SRc{*2Ka-*!BuOpf}neHGCiHy$@Ka1^Dint z;DkmIL$-e)rj4o2WQV%Gy;Xg(_Bh#qeOsTM2f@KEe~4kJ8kNLQ+;(!j^bgJMcNhvklP5Z6I+9Fq@c&D~8Fb-4rmDT!MB5QC{Dsb;BharP*O;SF4& zc$wj-7Oep7#$WZN!1nznc@Vb<_Dn%ga-O#J(l=OGB`dy=Sy&$(5-n3zzu%d7E#^8`T@}V+5B;PP8J14#4cCPw-SQTdGa2gWL0*zKM z#DfSXs_iWOMt)0*+Y>Lkd=LlyoHjublNLefhKBv@JoC>P7N1_#> zv=mLWe96%EY;!ZGSQDbZWb#;tzqAGgx~uk+-$+2_8U`!ypbwXl z^2E-FkM1?lY@yt8=J3%QK+xaZ6ok=-y%=KXCD^0r!5vUneW>95PzCkOPO*t}p$;-> ze5j-BLT_;)cZQzR2CEsm@rU7GZfFtdp*a|g4wDr%8?2QkIGasRfDWT-Dvy*U{?IHT z*}wGnzdlSptl#ZF^sf)KT|BJs&kLG91^A6ls{CzFprZ6-Y!V0Xysh%9p%iMd7HLsS zN+^Un$tDV)T@i!v?3o0Fsx2qI(AX_$dDkBzQ@fRM%n zRXk6hb9Py#JXUs+7)w@eo;g%QQ95Yq!K_d=z{0dGS+pToEI6=Bo8+{k$7&Z zo4>PH(`ce8E-Ps&uv`NQ;U$%t;w~|@E3WVOCi~R4oj5wP?%<*1C%}Jq%a^q~T7u>K zML5AKfQDv6>PuT`{SrKHRAF+^&edg6+5R_#H?Lz3iGoWo#PCEd0DS;)2U({{X#zU^ zw_xv{4x7|t!S)>44J;KfA|DC?;uQ($l+5Vp7oeqf7{GBF9356nx|&B~gs+@N^gSdd zvb*>&W)|u#F{Z_b`f#GVtQ`pYv3#||N{xj1NgB<#=Odt6{eB%#9RLt5v zIi|0u70`#ai}9fJjKv7dE!9ZrOIX!3{$z_K5FBd-Kp-&e4(J$LD-)NMTp^_pB`RT; zftVVlK2g@+1Ahv2$D){@Y#cL#dUj9*&%#6 zd2m9{1NYp>)6=oAvqdCn5#cx{AJ%S8skUgMglu2*IAtd+z1>B&`MuEAS(D(<6X#Lj z?f4CFx$)M&$=7*>9v1ER4b6!SIz-m0e{o0BfkySREchp?WdVPpQCh!q$t>?rL!&Jg zd#heM;&~A}VEm8Dvy&P|J*eAV&w!&Nx6HFV&B8jJFVTmgLaswn!cx$&%JbTsloz!3 zMEz1d`k==`Ueub_JAy_&`!ogbwx27^ZXgFNAbx=g_I~5nO^r)}&myw~+yY*cJl4$I znNJ32M&K=0(2Dj_>@39`3=FX!v3nZHno_@q^!y}%(yw0PqOo=);6Y@&ylVe>nMOZ~ zd>j#QQSBn3oaWd;qy$&5(5H$Ayi)0haAYO6TH>FR?rhqHmNOO+(})NB zLI@B@v0)eq!ug`>G<@htRlp3n!EpU|n+G+AvXFrWSUsLMBfL*ZB`CRsIVHNTR&b?K zxBgsN0BjfB>UVcJ|x%=-zb%OV7lmZc& zxiupadZVF7)6QuhoY;;FK2b*qL0J-Rn-8!X4ZY$-ZSUXV5DFd7`T41c(#lAeLMoeT z4%g655v@7AqT!i@)Edt5JMbN(=Q-6{=L4iG8RA%}w;&pKmtWvI4?G9pVRp|RTw`g0 zD5c12B&A2&P6Ng~8WM2eIW=wxd?r7A*N+&!Be7PX3s|7~z=APxm=A?5 zt>xB4WG|*Td@VX{Rs)PV0|yK`oI3^xn(4c_j&vgxk_Y3o(-`_5o`V zRTghg6%l@(qodXN;dB#+OKJEEvhfcnc#BeO2|E(5df-!fKDZ!%9!^BJ_4)9P+9Dq5 zK1=(v?KmIp34r?z{NEWnLB3Px{XYwy-akun4F7xTRr2^zeYW{gcK9)>aJDdU5;w5@ zak=<+-PLH-|04pelTb%ULpuuuJC7DgyT@D|p{!V!0v3KpDnRjANN12q6SUR3mb9<- z>2r~IApQGhstZ!3*?5V z8#)hJ0TdZg0M-BK#nGFP>$i=qk82DO z7h;Ft!D5E15OgW)&%lej*?^1~2=*Z5$2VX>V{x8SC+{i10BbtUk9@I#Vi&hX)q
Q!LwySI{Bnv%Sm)yh{^sSVJ8&h_D-BJ_YZe5eCaAWU9b$O2c z$T|{vWVRtOL!xC0DTc(Qbe`ItNtt5hr<)VijD0{U;T#bUEp381_y`%ZIav?kuYG{iyYdEBPW=*xNSc;Rlt6~F4M`5G+VtOjc z*0qGzCb@gME5udTjJA-9O<&TWd~}ysBd(eVT1-H82-doyH9RST)|+Pb{o*;$j9Tjs zhU!IlsPsj8=(x3bAKJTopW3^6AKROHR^7wZ185wJGVhA~hEc|LP;k7NEz-@4p5o}F z`AD6naG3(n=NF9HTH81=F+Q|JOz$7wm9I<+#BSmB@o_cLt2GkW9|?7mM;r!JZp89l zbo!Hp8=n!XH1{GwaDU+k)pGp`C|cXkCU5%vcH)+v@0eK>%7gWxmuMu9YLlChA|_D@ zi#5zovN_!a-0?~pUV-Rj*1P)KwdU-LguR>YM&*Nen+ln8Q$?WFCJg%DY%K}2!!1FE zDv-A%Cbwo^p(lzac&_TZ-l#9kq`mhLcY3h9ZTUVCM(Ad&=EriQY5{jJv<5K&g|*Lk zgV%ILnf1%8V2B0E&;Sp4sYbYOvvMebLwYwzkRQ#F8GpTQq#uv=J`uaSJ34OWITeSGo6+-8Xw znCk*n{kdDEi)Hi&u^)~cs@iyCkFWB2SWZU|Uc%^43ZIZQ-vWNExCCtDWjqHs;;tWf$v{}0{p0Rvxkq``)*>+Akq%|Na zA`@~-Vfe|+(AIlqru+7Ceh4nsVmO9p9jc8}HX^W&ViBDXT+uXbT#R#idPn&L>+#b6 zflC-4C5-X;kUnR~L>PSLh*gvL68}RBsu#2l`s_9KjUWRhiqF`j)`y`2`YU(>3bdBj z?>iyjEhe-~$^I5!nn%B6Wh+I`FvLNvauve~eX<+Ipl&04 zT}};W&1a3%W?dJ2=N#0t?e+aK+%t}5q%jSLvp3jZ%?&F}nOOWr>+{GFIa%wO_2`et z=JzoRR~}iKuuR+azPI8;Gf9)z3kyA4EIOSl!sRR$DlW}0>&?GbgPojmjmnln;cTqCt=ADbE zZ8GAnoM+S1(5$i8^O4t`ue;vO4i}z0wz-QEIVe5_u03;}-!G1NyY8;h^}y;tzY}i5 zqQr#Ur3Fy8sSa$Q0ys+f`!`+>9WbvU_I`Sj;$4{S>O3?#inLHCrtLy~!s#WXV=oVP zeE93*Nc`PBi4q@%Ao$x4lw9vLHM!6mn3-b_cebF|n-2vt-zYVF_&sDE--J-P;2WHo z+@n2areE0o$LjvjlV2X7ZU@j+`{*8zq`JR3gKF#EW|#+{nMyo-a>nFFTg&vhyT=b} zDa8+v0(Dgx0yRL@ZXOYIlVSZ0|MFizy0VPW8;AfA5|pe!#j zX}Py^8fl5SyS4g1WSKKtnyP+_PoOwMMwu`(i@Z)diJp~U54*-miOchy7Z35eL>^M z4p<-aIxH4VUZgS783@H%M7P9hX>t{|RU7$n4T(brCG#h9e9p! z+o`i;EGGq3&pF;~5V~eBD}lC)>if$w%Vf}AFxGqO88|ApfHf&Bvu+xdG)@vuF}Yvk z)o;~k-%+0K0g+L`Wala!$=ZV|z$e%>f0%XoLib%)!R^RoS+{!#X?h-6uu zF&&KxORdZU&EwQFITIRLo(7TA3W}y6X{?Y%y2j0It!ekU#<)$qghZtpcS>L3uh`Uj z7GY;6f$9qKynP#oS3$$a{p^{D+0oJQ71`1?OAn_m8)UGZmj3l*ZI)`V-a>MKGGFG< z&^jg#Ok%(hhm>hSrZ5;Qga4u(?^i>GiW_j9%_7M>j(^|Om$#{k+^*ULnEgzW_1gCICtAD^WpC`A z{9&DXkG#01Xo)U$OC(L5Y$DQ|Q4C6CjUKk1UkPj$nXH##J{c8e#K|&{mA*;b$r0E4 zUNo0jthwA(c&N1l=PEe8Rw_8cEl|-eya9z&H3#n`B$t#+aJ03RFMzrV@gowbe8v(c zIFM60^0&lCFO10NU4w@|61xiZ4CVXeaKjd;d?sv52XM*lS8XiVjgWpRB;&U_C0g+`6B5V&w|O6B*_q zsATxL!M}+$He)1eOWECce#eS@2n^xhlB4<_Nn?yCVEQWDs(r`|@2GqLe<#(|&P0U? z$7V5IgpWf09uIf_RazRwC?qEqRaHyL?iiS05UiGesJy%^>-C{{ypTBI&B0-iUYhk> zIk<5xpsuV@g|z(AZD+C-;A!fTG=df1=<%nxy(a(IS+U{ME4ZbDEBtcD_3V=icT6*_ z)>|J?>&6%nvHhZERBtjK+s4xnut*@>GAmA5m*OTp$!^CHTr}vM4n(X1Q*;{e-Rd2BCF-u@1ZGm z!S8hJ6L=Gl4T_SDa7Xx|-{4mxveJg=ctf`BJ*fy!yF6Dz&?w(Q_6B}WQVtNI!BVBC zKfX<>7vd6C96}XAQmF-Jd?1Q4eTfRB3q7hCh0f!(JkdWT5<{iAE#dKy*Jxq&3a1@~ z8C||Dn2mFNyrUV|<-)C^_y7@8c2Fz+2jrae9deBDu;U}tJ{^xAdxCD248(k;dCJ%o z`y3sADe>U%suxwwv~8A1+R$VB=Q?%U?4joI$um;aH+eCrBqpn- z%79D_7rb;R-;-9RTrwi9dPlg8&@tfWhhZ(Vx&1PQ+6(huX`;M9x~LrW~~#3{j0Bh2kDU$}@!fFQej4VGkJv?M4rU^x!RU zEwhu$!CA_iDjFjrJa`aocySDX16?~;+wgav;}Zut6Mg%C4>}8FL?8)Kgwc(Qlj{@#2Pt0?G`$h7P#M+qoXtlV@d}%c&OzO+QYKK`kyXaK{U(O^2DyIXCZlNQjt0^8~8JzNGrIxhj}}M z&~QZlbx%t;MJ(Vux;2tgNKGlAqphLq%pd}JG9uoVHUo?|hN{pLQ6Em%r*+7t^<);X zm~6=qChlNAVXNN*Sow->*4;}T;l;D1I-5T{Bif@4_}=>l`tK;qqDdt5zvisCKhMAH z#r}`)7VW?LZqfdmXQ%zo5bJ00{Xb9^YKrk0Nf|oIW*K@(=`o2Vndz}ZDyk{!u}PVx zzd--+_WC*U{~DH3{?GI64IB+@On&@9X>EUAo&L+G{L^dozaI4C3G#2wr~hseW@K&g zKWs{uHu-9Je!3;4pE>eBltKUXb^*hG8I&413)$J&{D4N%7PcloU6bn%jPxJyQL?g* z9g+YFFEDiE`8rW^laCNzQmi7CTnPfwyg3VDHRAl>h=In6jeaVOP@!-CP60j3+#vpL zEYmh_oP0{-gTe7Or`L6x)6w?77QVi~jD8lWN@3RHcm80iV%M1A!+Y6iHM)05iC64tb$X2lV_%Txk@0l^hZqi^%Z?#- zE;LE0uFx)R08_S-#(wC=dS&}vj6P4>5ZWjhthP=*Hht&TdLtKDR;rXEX4*z0h74FA zMCINqrh3Vq;s%3MC1YL`{WjIAPkVL#3rj^9Pj9Ss7>7duy!9H0vYF%>1jh)EPqvlr6h%R%CxDsk| z!BACz7E%j?bm=pH6Eaw{+suniuY7C9Ut~1cWfOX9KW9=H><&kQlinPV3h9R>3nJvK z4L9(DRM=x;R&d#a@oFY7mB|m8h4692U5eYfcw|QKwqRsshN(q^v$4$)HgPpAJDJ`I zkqjq(8Cd!K!+wCd=d@w%~e$=gdUgD&wj$LQ1r>-E=O@c ze+Z$x{>6(JA-fNVr)X;*)40Eym1TtUZI1Pwwx1hUi+G1Jlk~vCYeXMNYtr)1?qwyg zsX_e*$h?380O00ou?0R@7-Fc59o$UvyVs4cUbujHUA>sH!}L54>`e` zHUx#Q+Hn&Og#YVOuo*niy*GU3rH;%f``nk#NN5-xrZ34NeH$l`4@t);4(+0|Z#I>Y z)~Kzs#exIAaf--65L0UHT_SvV8O2WYeD>Mq^Y6L!Xu8%vnpofG@w!}R7M28?i1*T&zp3X4^OMCY6(Dg<-! zXmcGQrRgHXGYre7GfTJ)rhl|rs%abKT_Nt24_Q``XH{88NVPW+`x4ZdrMuO0iZ0g` z%p}y};~T5gbb9SeL8BSc`SO#ixC$@QhXxZ=B}L`tP}&k?1oSPS=4%{UOHe0<_XWln zwbl5cn(j-qK`)vGHY5B5C|QZd5)W7c@{bNVXqJ!!n$^ufc?N9C-BF2QK1(kv++h!>$QbAjq)_b$$PcJdV+F7hz0Hu@ zqj+}m0qn{t^tD3DfBb~0B36|Q`bs*xs|$i^G4uNUEBl4g;op-;Wl~iThgga?+dL7s zUP(8lMO?g{GcYpDS{NM!UA8Hco?#}eNEioRBHy4`mq!Pd-9@-97|k$hpEX>xoX+dY zDr$wfm^P&}Wu{!%?)U_(%Mn79$(ywvu*kJ9r4u|MyYLI_67U7%6Gd_vb##Nerf@>& z8W11z$$~xEZt$dPG}+*IZky+os5Ju2eRi;1=rUEeIn>t-AzC_IGM-IXWK3^6QNU+2pe=MBn4I*R@A%-iLDCOHTE-O^wo$sL_h{dcPl=^muAQb`_BRm};=cy{qSkui;`WSsj9%c^+bIDQ z0`_?KX0<-=o!t{u(Ln)v>%VGL z0pC=GB7*AQ?N7N{ut*a%MH-tdtNmNC+Yf$|KS)BW(gQJ*z$d{+{j?(e&hgTy^2|AR9vx1Xre2fagGv0YXWqtNkg*v%40v?BJBt|f9wX5 z{QTlCM}b-0{mV?IG>TW_BdviUKhtosrBqdfq&Frdz>cF~yK{P@(w{Vr7z2qKFwLhc zQuogKO@~YwyS9%+d-zD7mJG~@?EFJLSn!a&mhE5$_4xBl&6QHMzL?CdzEnC~C3$X@ zvY!{_GR06ep5;<#cKCSJ%srxX=+pn?ywDwtJ2{TV;0DKBO2t++B(tIO4)Wh`rD13P z4fE$#%zkd=UzOB74gi=-*CuID&Z3zI^-`4U^S?dHxK8fP*;fE|a(KYMgMUo`THIS1f!*6dOI2 zFjC3O=-AL`6=9pp;`CYPTdVX z8(*?V&%QoipuH0>WKlL8A*zTKckD!paN@~hh zmXzm~qZhMGVdQGd=AG8&20HW0RGV8X{$9LldFZYm zE?}`Q3i?xJRz43S?VFMmqRyvWaS#(~Lempg9nTM$EFDP(Gzx#$r)W&lpFKqcAoJh-AxEw$-bjW>`_+gEi z2w`99#UbFZGiQjS8kj~@PGqpsPX`T{YOj`CaEqTFag;$jY z8_{Wzz>HXx&G*Dx<5skhpETxIdhKH?DtY@b9l8$l?UkM#J-Snmts7bd7xayKTFJ(u zyAT&@6cAYcs{PBfpqZa%sxhJ5nSZBPji?Zlf&}#L?t)vC4X5VLp%~fz2Sx<*oN<7` z?ge=k<=X7r<~F7Tvp9#HB{!mA!QWBOf%EiSJ6KIF8QZNjg&x~-%e*tflL(ji_S^sO ztmib1rp09uon}RcsFi#k)oLs@$?vs(i>5k3YN%$T(5Or(TZ5JW9mA6mIMD08=749$ z!d+l*iu{Il7^Yu}H;lgw=En1sJpCKPSqTCHy4(f&NPelr31^*l%KHq^QE>z>Ks_bH zjbD?({~8Din7IvZeJ>8Ey=e;I?thpzD=zE5UHeO|neioJwG;IyLk?xOz(yO&0DTU~ z^#)xcs|s>Flgmp;SmYJ4g(|HMu3v7#;c*Aa8iF#UZo7CvDq4>8#qLJ|YdZ!AsH%^_7N1IQjCro

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..f371643 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts new file mode 100644 index 0000000..d6d9d86 --- /dev/null +++ b/loader/build.gradle.kts @@ -0,0 +1,70 @@ +plugins { + kotlin("jvm") + `maven-publish` + signing +} + +version = "647.6.4-SNAPSHOT" + +java { + withJavadocJar() + withSourcesJar() +} + +publishing { + publications { + create("mavenJava") { + pom { + name.set("Xlite Cache Library Type Loaders") + description.set("Definition types of runescape files.") + url.set("https://github.com/xlite2/cache-lib") + + developers { + developer { + id.set("tylert") + name.set("Tyler Telis") + email.set("xlitersps@gmail.com") + } + + developer { + id.set("ultraviolet-jordan") + name.set("Jordan Abraham") + } + } + + + artifact(tasks["javadocJar"]) + artifact(tasks["sourcesJar"]) + } + } + create("maven") { + groupId = project.group.toString() + artifactId = project.name + version = project.version.toString() + + from(components["java"]) + } + repositories { + val ossrhUsername: String by project + val ossrhPassword: String by project + + maven { + val releasesRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/releases/") + val snapshotsRepoUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") + url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl + credentials { + username = ossrhUsername + password = ossrhPassword + } + } + } + } +} + +signing { + sign(publishing.publications["mavenJava"]) +} + +dependencies { + implementation(project(":cache")) +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/IEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/IEntryBuilder.kt new file mode 100644 index 0000000..0504f5d --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/IEntryBuilder.kt @@ -0,0 +1,12 @@ +package com.runetopic.loader + +import com.runetopic.cache.store.Js5Store +import java.nio.ByteBuffer + +/** + * @author Jordan Abraham + */ +internal interface IEntryBuilder { + fun build(store: Js5Store) + fun read(buffer: ByteBuffer, type: T): T +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/IEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/IEntryProvider.kt new file mode 100644 index 0000000..7347f55 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/IEntryProvider.kt @@ -0,0 +1,13 @@ +package com.runetopic.loader + +import com.runetopic.cache.store.Js5Store + +/** + * @author Jordan Abraham + */ +interface IEntryProvider { + fun load(store: Js5Store) + fun lookup(id: Int) : T + fun size(): Int + fun collect(): Set +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/IEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/IEntryType.kt new file mode 100644 index 0000000..f15e787 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/IEntryType.kt @@ -0,0 +1,8 @@ +package com.runetopic.loader + +/** + * @author Jordan Abraham + */ +interface IEntryType { + fun getId(): Int +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/Loader.kt b/loader/src/main/kotlin/com/runetopic/loader/Loader.kt new file mode 100644 index 0000000..f5ec512 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/Loader.kt @@ -0,0 +1,55 @@ +package com.runetopic.loader + +import com.runetopic.loader.index.config.idk.IdentityKitEntryProvider +import com.runetopic.loader.index.config.inv.InventoryEntryProvider +import com.runetopic.loader.index.config.lighting.LightingEntryProvider +import com.runetopic.loader.index.config.mouseicon.MouseIconEntryProvider +import com.runetopic.loader.index.config.overlay.OverlayEntryProvider +import com.runetopic.loader.index.config.param.ParamEntryProvider +import com.runetopic.loader.index.config.skybox.SkyBoxEntryProvider +import com.runetopic.loader.index.config.struct.StructEntryProvider +import com.runetopic.loader.index.config.underlay.UnderlayEntryProvider +import com.runetopic.loader.index.loc.LocEntryProvider +import com.runetopic.loader.index.map.MapEntryProvider +import com.runetopic.loader.index.map.MapLocationEntryProvider +import com.runetopic.loader.index.npc.NpcEntryProvider +import com.runetopic.loader.index.obj.ObjEntryProvider +import com.runetopic.loader.index.particle.ParticleEntryProvider +import com.runetopic.loader.index.spotanim.SpotAnimationEntryProvider + +/** + * @author Jordan Abraham + */ +private val spotAnimationEntryProvider = SpotAnimationEntryProvider() +private val structEntryProvider = StructEntryProvider() +private val mapEntryProvider = MapEntryProvider() +private val objEntryProvider = ObjEntryProvider() +private val mapLocationEntryProvider = MapLocationEntryProvider() +private val particlesEntryProvider = ParticleEntryProvider() +private val underlaysEntryProvider = UnderlayEntryProvider() +private val locEntryProvider = LocEntryProvider() +private val paramEntryProvider = ParamEntryProvider() +private val overlayEntryProvider = OverlayEntryProvider() +private val skyBoxEntryProvider = SkyBoxEntryProvider() +private val mouseIconEntryProvider = MouseIconEntryProvider() +private val inventoryEntryProvider = InventoryEntryProvider() +private val lightingEntryProvider = LightingEntryProvider() +private val identityKitProvider = IdentityKitEntryProvider() +private val npcEntryProvider = NpcEntryProvider() + +fun spotAnimations(): SpotAnimationEntryProvider = spotAnimationEntryProvider +fun structs(): StructEntryProvider = structEntryProvider +fun maps(): MapEntryProvider = mapEntryProvider +fun objs(): ObjEntryProvider = objEntryProvider +fun mapLocs(): MapLocationEntryProvider = mapLocationEntryProvider +fun particles(): ParticleEntryProvider = particlesEntryProvider +fun underlays(): UnderlayEntryProvider = underlaysEntryProvider +fun locs(): LocEntryProvider = locEntryProvider +fun params(): ParamEntryProvider = paramEntryProvider +fun overlays(): OverlayEntryProvider = overlayEntryProvider +fun kits(): IdentityKitEntryProvider = identityKitProvider +fun skyboxes(): SkyBoxEntryProvider = skyBoxEntryProvider +fun mouseIcons(): MouseIconEntryProvider = mouseIconEntryProvider +fun invs(): InventoryEntryProvider = inventoryEntryProvider +fun lightings(): LightingEntryProvider = lightingEntryProvider +fun npcs(): NpcEntryProvider = npcEntryProvider diff --git a/loader/src/main/kotlin/com/runetopic/loader/extension/ByteArray.kt b/loader/src/main/kotlin/com/runetopic/loader/extension/ByteArray.kt new file mode 100644 index 0000000..d1f668b --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/extension/ByteArray.kt @@ -0,0 +1,8 @@ +package com.runetopic.loader.extension + +import java.nio.ByteBuffer + +/** + * @author Jordan Abraham + */ +fun ByteArray.toByteBuffer(): ByteBuffer = ByteBuffer.wrap(this) \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/extension/ByteBuffer.kt b/loader/src/main/kotlin/com/runetopic/loader/extension/ByteBuffer.kt new file mode 100644 index 0000000..8095593 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/extension/ByteBuffer.kt @@ -0,0 +1,85 @@ +package com.runetopic.loader.extension + +import java.nio.ByteBuffer +import java.nio.charset.Charset + +internal fun ByteBuffer.readCp1252Char(): Char { + var unsigned = get().toInt() and 0xff + require(unsigned != 0) { "Non cp1252 character 0x" + unsigned.toString(16) + " provided" } + if (unsigned in 128..159) { + var value: Int = cp1252Identifiers[unsigned - 128].code + if (value == 0) value = 63 + unsigned = value + } + return unsigned.toChar() +} + +internal fun ByteBuffer.readUnsignedSmart(): Int { + val peek = get(position()).toInt() and 0xFF + return if (peek < 128) readUnsignedByte() else (readUnsignedShort()) - 0x8000 +} + +internal fun ByteBuffer.readUnsignedByte(): Int = get().toInt() and 0xFF +internal fun ByteBuffer.readUnsignedShort(): Int = short.toInt() and 0xFFFF +internal fun ByteBuffer.readUnsignedMedium(): Int = ((readUnsignedByte() shl 16) or (readUnsignedByte() shl 8) or readUnsignedByte()) +internal fun ByteBuffer.skip(amount: Int): ByteBuffer = position(position() + amount) + +internal fun ByteBuffer.readUnsignedIntSmartShortCompat(): Int { + var value = 0 + var i = readUnsignedSmart() + while (i == 32767) { + i = readUnsignedSmart() + value += 32767 + } + value += i + return value +} + +internal fun ByteBuffer.readString(): String { + val mark = position() + var length = 0 + while (get().toInt() != 0) { + length++ + } + if (length == 0) return "" + val byteArray = ByteArray(length) + position(mark) + get(byteArray) + position(position() + 1) + return String(byteArray, Charset.defaultCharset()) +} + +val cp1252Identifiers = charArrayOf( + '\u20ac', + '\u0000', + '\u201a', + '\u0192', + '\u201e', + '\u2026', + '\u2020', + '\u2021', + '\u02c6', + '\u2030', + '\u0160', + '\u2039', + '\u0152', + '\u0000', + '\u017d', + '\u0000', + '\u0000', + '\u2018', + '\u2019', + '\u201c', + '\u201d', + '\u2022', + '\u2013', + '\u2014', + '\u02dc', + '\u2122', + '\u0161', + '\u203a', + '\u0153', + '\u0000', + '\u017e', + '\u0178' +) \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/extension/Int.kt b/loader/src/main/kotlin/com/runetopic/loader/extension/Int.kt new file mode 100644 index 0000000..3a025f0 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/extension/Int.kt @@ -0,0 +1,8 @@ +package com.runetopic.loader.extension + +/** + * @author Jordan Abraham + */ +internal fun Int.toBoolean(): Boolean { + return this == 1 +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt new file mode 100644 index 0000000..53a1905 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt @@ -0,0 +1,71 @@ +package com.runetopic.loader.index.config.idk + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.readUnsignedByte +import com.runetopic.loader.extension.readUnsignedShort +import com.runetopic.loader.extension.skip +import com.runetopic.loader.extension.toByteBuffer +import java.nio.ByteBuffer + +/** + * @author Tyler Telis + * @email + */ +internal class IdentityKitEntryBuilder: IEntryBuilder { + + lateinit var identityKitTypes: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + identityKitTypes = buildSet { + store.index(2).group(3).files().forEach { + add(read(it.data.toByteBuffer(), IdentityKitEntryType(it.id))) + } + } + } + + override fun read(buffer: ByteBuffer, type: IdentityKitEntryType): IdentityKitEntryType { + do when (val opcode = buffer.readUnsignedByte()) { + 0 -> break + 1 -> buffer.skip(1) + 2 -> { + val size = buffer.readUnsignedByte() + val models = IntArray(size) + + (0 until size).forEach { + models[it] = buffer.readUnsignedShort() + } + type.models = models + } + 3 -> { + // This is no longer used in higher revisions. OSRS uses this + } + 40 -> { + val size = buffer.readUnsignedByte() + val colorsToFind = ShortArray(size) + val colorsToReplace = ShortArray(size) + (0 until size).forEach { + colorsToFind[it] = buffer.readUnsignedShort().toShort() + colorsToReplace[it] = buffer.readUnsignedShort().toShort() + } + type.colorsToFind = colorsToFind + type.colorsToReplace = colorsToReplace + } + 41 -> { + val size = buffer.readUnsignedByte() + val texturesToFind = ShortArray(size) + val texturesToReplace = ShortArray(size) + (0 until size).forEach { + texturesToFind[it] = buffer.readUnsignedShort().toShort() + texturesToReplace[it] = buffer.readUnsignedShort().toShort() + } + type.texturesToFind = texturesToFind + type.texturesToReplace = texturesToReplace + } + in 60..69 -> buffer.readUnsignedShort().let { type.chatHeadModels[opcode - 60] = it } + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryProvider.kt new file mode 100644 index 0000000..9c3106f --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryProvider.kt @@ -0,0 +1,18 @@ +package com.runetopic.loader.index.config.idk + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + + +/** + * @author Jordan Abraham + */ +class IdentityKitEntryProvider : IEntryProvider { + + private val builder = IdentityKitEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): IdentityKitEntryType = builder.identityKitTypes.elementAt(id) + override fun size(): Int = builder.identityKitTypes.size + override fun collect(): Set = builder.identityKitTypes +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryType.kt new file mode 100644 index 0000000..428b0d0 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryType.kt @@ -0,0 +1,57 @@ +package com.runetopic.loader.index.config.idk + +import com.runetopic.loader.IEntryType + +data class IdentityKitEntryType( + private val id: Int = 0, + var models: IntArray? = null, + var colorsToFind: ShortArray? = null, + var colorsToReplace: ShortArray? = null, + var texturesToFind: ShortArray? = null, + var texturesToReplace: ShortArray? = null, + var chatHeadModels: IntArray = intArrayOf(-1, -1, -1, -1, -1) +): IEntryType { + override fun getId(): Int = id + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as IdentityKitEntryType + + if (id != other.id) return false + if (models != null) { + if (other.models == null) return false + if (!models.contentEquals(other.models)) return false + } else if (other.models != null) return false + if (colorsToFind != null) { + if (other.colorsToFind == null) return false + if (!colorsToFind.contentEquals(other.colorsToFind)) return false + } else if (other.colorsToFind != null) return false + if (colorsToReplace != null) { + if (other.colorsToReplace == null) return false + if (!colorsToReplace.contentEquals(other.colorsToReplace)) return false + } else if (other.colorsToReplace != null) return false + if (texturesToFind != null) { + if (other.texturesToFind == null) return false + if (!texturesToFind.contentEquals(other.texturesToFind)) return false + } else if (other.texturesToFind != null) return false + if (texturesToReplace != null) { + if (other.texturesToReplace == null) return false + if (!texturesToReplace.contentEquals(other.texturesToReplace)) return false + } else if (other.texturesToReplace != null) return false + if (!chatHeadModels.contentEquals(other.chatHeadModels)) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + (models?.contentHashCode() ?: 0) + result = 31 * result + (colorsToFind?.contentHashCode() ?: 0) + result = 31 * result + (colorsToReplace?.contentHashCode() ?: 0) + result = 31 * result + (texturesToFind?.contentHashCode() ?: 0) + result = 31 * result + (texturesToReplace?.contentHashCode() ?: 0) + result = 31 * result + chatHeadModels.contentHashCode() + return result + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt new file mode 100644 index 0000000..fb4a1da --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt @@ -0,0 +1,34 @@ +package com.runetopic.loader.index.config.inv + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.readUnsignedByte +import com.runetopic.loader.extension.readUnsignedShort +import com.runetopic.loader.extension.toByteBuffer +import java.nio.ByteBuffer + +/** + * @author Tyler Telis + * @email + */ +internal class InventoryEntryBuilder: IEntryBuilder { + lateinit var inventoryTypes: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + inventoryTypes = buildSet { + store.index(2).group(5).files().forEach { + add(read(it.data.toByteBuffer(), InventoryEntryType(it.id))) + } + } + } + + override fun read(buffer: ByteBuffer, type: InventoryEntryType): InventoryEntryType { + do when (val opcode = buffer.readUnsignedByte()) { + 0 -> break + 2 -> type.size = buffer.readUnsignedShort() + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryProvider.kt new file mode 100644 index 0000000..3c149a7 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryProvider.kt @@ -0,0 +1,18 @@ +package com.runetopic.loader.index.config.inv + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + + +/** + * @author Jordan Abraham + */ +class InventoryEntryProvider : IEntryProvider { + + private val builder = InventoryEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): InventoryEntryType = builder.inventoryTypes.elementAt(id) + override fun size(): Int = builder.inventoryTypes.size + override fun collect(): Set = builder.inventoryTypes +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryType.kt new file mode 100644 index 0000000..730dcef --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryType.kt @@ -0,0 +1,11 @@ +package com.runetopic.loader.index.config.inv + +import com.runetopic.loader.IEntryType + +data class InventoryEntryType( + private val id: Int = 0, + var size: Int = 0 +): IEntryType { + override fun getId(): Int = id + +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt new file mode 100644 index 0000000..b66e032 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt @@ -0,0 +1,38 @@ +package com.runetopic.loader.index.config.lighting + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.readUnsignedByte +import com.runetopic.loader.extension.readUnsignedShort +import com.runetopic.loader.extension.toByteBuffer +import java.nio.ByteBuffer + +/** + * @author Tyler Telis + * @email + */ +internal class LightingEntryBuilder : IEntryBuilder { + + lateinit var lightings: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + lightings = buildSet { + store.index(2).group(31).files().forEach { + add(read(it.data.toByteBuffer(), LightingEntryType(it.id))) + } + } + } + + override fun read(buffer: ByteBuffer, type: LightingEntryType): LightingEntryType { + do when (val opcode = buffer.readUnsignedByte()) { + 0 -> break + 1 -> type.anInt961 = buffer.readUnsignedByte() + 2-> type.anInt957 = buffer.readUnsignedShort() + 3 -> type.anInt956 = buffer.readUnsignedShort() + 4 -> type.anInt962 = buffer.short.toInt() + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryProvider.kt new file mode 100644 index 0000000..d16efdd --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryProvider.kt @@ -0,0 +1,19 @@ +package com.runetopic.loader.index.config.lighting + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + + +/** + * @author Tyler Telis + * @email + */ +class LightingEntryProvider : IEntryProvider { + + private val builder = LightingEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): LightingEntryType = builder.lightings.elementAt(id) + override fun size(): Int = builder.lightings.size + override fun collect(): Set = builder.lightings +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryType.kt new file mode 100644 index 0000000..fd047ed --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryType.kt @@ -0,0 +1,13 @@ +package com.runetopic.loader.index.config.lighting + +import com.runetopic.loader.IEntryType + +data class LightingEntryType( + private val id: Int = 0, + var anInt961: Int = 0, + var anInt962: Int = 0, + var anInt956: Int = 2048, + var anInt957: Int = 2048, +): IEntryType { + override fun getId(): Int = id +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt new file mode 100644 index 0000000..2d90b44 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt @@ -0,0 +1,38 @@ +package com.runetopic.loader.index.config.mouseicon + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.readUnsignedByte +import com.runetopic.loader.extension.readUnsignedShort +import com.runetopic.loader.extension.toByteBuffer +import java.nio.ByteBuffer + +/** + * @author Tyler Telis + * @email + */ +internal class MouseIconEntryBuilder: IEntryBuilder { + lateinit var mouseIconTypes: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + mouseIconTypes = buildSet { + store.index(2).group(33).files().forEach { + add(read(it.data.toByteBuffer(), MouseIconEntryType(it.id))) + } + } + } + + override fun read(buffer: ByteBuffer, type: MouseIconEntryType): MouseIconEntryType { + do when (val opcode = buffer.readUnsignedByte()) { + 0 -> break + 1 -> type.spriteId = buffer.readUnsignedShort() + 2 -> { + type.xCoord = buffer.readUnsignedByte() + type.yCoord = buffer.readUnsignedByte() + } + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryProvider.kt new file mode 100644 index 0000000..fdf23e5 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryProvider.kt @@ -0,0 +1,18 @@ +package com.runetopic.loader.index.config.mouseicon + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + + +/** + * @author Jordan Abraham + */ +class MouseIconEntryProvider : IEntryProvider { + + private val builder = MouseIconEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): MouseIconEntryType = builder.mouseIconTypes.elementAt(id) + override fun size(): Int = builder.mouseIconTypes.size + override fun collect(): Set = builder.mouseIconTypes +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryType.kt new file mode 100644 index 0000000..3805377 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryType.kt @@ -0,0 +1,13 @@ +package com.runetopic.loader.index.config.mouseicon + +import com.runetopic.loader.IEntryType + +data class MouseIconEntryType( + private val id: Int = 0, + var xCoord: Int = 0, + var yCoord: Int = 0, + var spriteId: Int = 0 +): IEntryType { + override fun getId(): Int = id + +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt new file mode 100644 index 0000000..781ac3c --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt @@ -0,0 +1,51 @@ +package com.runetopic.loader.index.config.overlay + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.readUnsignedByte +import com.runetopic.loader.extension.readUnsignedMedium +import com.runetopic.loader.extension.readUnsignedShort +import com.runetopic.loader.extension.toByteBuffer +import java.nio.ByteBuffer + +/** + * @author Jordan Abraham + */ +class OverlayEntryBuilder: IEntryBuilder { + + lateinit var overlays: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + overlays = buildSet { + store.index(2).group(4).files().forEach { + add(read(it.data.toByteBuffer(), OverlayEntryType(it.id))) + } + } + } + + override fun read(buffer: ByteBuffer, type: OverlayEntryType): OverlayEntryType { + do when (val opcode = buffer.readUnsignedByte()) { + 0 -> break + 1 -> type.color = buffer.readUnsignedMedium() + 2 -> type.textureId = buffer.readUnsignedByte() + 3 -> buffer.readUnsignedShort().let { + type.textureId = if (it == 65535) -1 else it + } + 5 -> type.occlude = false + 7 -> type.secondaryColor = buffer.readUnsignedMedium() + 8 -> { + //Some sort of client usage happens here. + } + 9 -> type.textureResolution = buffer.readUnsignedShort() shl 2 + 10 -> type.aBoolean397 = false + 11 -> type.anInt398 = buffer.readUnsignedByte() + 12 -> type.aBoolean391 = true + 13 -> type.anInt392 = buffer.readUnsignedMedium() + 14 -> type.anInt395 = buffer.readUnsignedByte() shl 2 + 16 -> type.anInt388 = buffer.readUnsignedByte() + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryProvider.kt new file mode 100644 index 0000000..5bdaf94 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryProvider.kt @@ -0,0 +1,17 @@ +package com.runetopic.loader.index.config.overlay + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + +/** + * @author Jordan Abraham + */ +class OverlayEntryProvider: IEntryProvider { + + private val builder = OverlayEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): OverlayEntryType = builder.overlays.elementAt(id) + override fun size(): Int = builder.overlays.size + override fun collect(): Set = builder.overlays +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryType.kt new file mode 100644 index 0000000..e1ee46d --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryType.kt @@ -0,0 +1,23 @@ +package com.runetopic.loader.index.config.overlay + +import com.runetopic.loader.IEntryType + +/** + * @author Jordan Abraham + */ +data class OverlayEntryType( + private val id: Int = 0, + var color: Int = 0, + var textureId: Int = -1, + var occlude: Boolean = true, + var secondaryColor: Int = -1, + var textureResolution: Int = 512, + var aBoolean397: Boolean = true, + var anInt398: Int = 8, + var aBoolean391: Boolean = false, + var anInt392: Int = 1190717, + var anInt395: Int = 64, + var anInt388: Int = 127 +): IEntryType { + override fun getId(): Int = id +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt new file mode 100644 index 0000000..d7a05aa --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt @@ -0,0 +1,39 @@ +package com.runetopic.loader.index.config.param + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.readCp1252Char +import com.runetopic.loader.extension.readString +import com.runetopic.loader.extension.readUnsignedByte +import com.runetopic.loader.extension.toByteBuffer +import java.nio.ByteBuffer + +/** + * @author Tyler Telis + * @email + */ +internal class ParamEntryBuilder: IEntryBuilder { + + lateinit var paramTypes: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + paramTypes = buildSet { + store.index(2).group(11).files().forEach { + add(read(it.data.toByteBuffer(), ParamEntryType(it.id))) + } + } + } + + override fun read(buffer: ByteBuffer, type: ParamEntryType): ParamEntryType { + do when (val opcode = buffer.readUnsignedByte()) { + 0 -> break + 1 -> type.identifier = buffer.readCp1252Char() + 2 -> type.defaultInt = buffer.int + 4 -> type.aBoolean1822 = false + 5 -> type.defaultString = buffer.readString() + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryProvider.kt new file mode 100644 index 0000000..d28b196 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryProvider.kt @@ -0,0 +1,18 @@ +package com.runetopic.loader.index.config.param + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + + +/** + * @author Jordan Abraham + */ +class ParamEntryProvider : IEntryProvider { + + private val builder = ParamEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): ParamEntryType = builder.paramTypes.elementAt(id) + override fun size(): Int = builder.paramTypes.size + override fun collect(): Set = builder.paramTypes +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryType.kt new file mode 100644 index 0000000..d4ab77d --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryType.kt @@ -0,0 +1,13 @@ +package com.runetopic.loader.index.config.param + +import com.runetopic.loader.IEntryType + +data class ParamEntryType( + private val id: Int = 0, + var identifier: Char? = null, + var aBoolean1822: Boolean = true, + var defaultString: String = "", + var defaultInt: Int = 0 +): IEntryType { + override fun getId(): Int = id +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt new file mode 100644 index 0000000..10e7d51 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt @@ -0,0 +1,42 @@ +package com.runetopic.loader.index.config.skybox + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.readUnsignedByte +import com.runetopic.loader.extension.readUnsignedShort +import com.runetopic.loader.extension.toByteBuffer +import java.nio.ByteBuffer + +/** + * @author Tyler Telis + * @email + */ +internal class SkyBoxEntryBuilder: IEntryBuilder { + lateinit var skyBoxTypes: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + skyBoxTypes = buildSet { + store.index(2).group(29).files().forEach { + add(read(it.data.toByteBuffer(), SkyBoxEntryType(it.id))) + } + } + } + + override fun read(buffer: ByteBuffer, type: SkyBoxEntryType): SkyBoxEntryType { + do when (val opcode = buffer.readUnsignedByte()) { + 0 -> break + 1 -> type.textureId = buffer.readUnsignedShort() + 2 -> { + val size = buffer.readUnsignedByte() + val sphereIds = IntArray(size) + (0 until size).forEach { + sphereIds[it] = buffer.readUnsignedShort() + } + } + 3 -> type.anInt2392 = buffer.readUnsignedByte() + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryProvider.kt new file mode 100644 index 0000000..d93f916 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryProvider.kt @@ -0,0 +1,18 @@ +package com.runetopic.loader.index.config.skybox + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + + +/** + * @author Jordan Abraham + */ +class SkyBoxEntryProvider : IEntryProvider { + + private val builder = SkyBoxEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): SkyBoxEntryType = builder.skyBoxTypes.elementAt(id) + override fun size(): Int = builder.skyBoxTypes.size + override fun collect(): Set = builder.skyBoxTypes +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryType.kt new file mode 100644 index 0000000..d2aaea1 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryType.kt @@ -0,0 +1,36 @@ +package com.runetopic.loader.index.config.skybox + +import com.runetopic.loader.IEntryType + +data class SkyBoxEntryType( + private val id: Int = 0, + var anInt2392: Int = -1, + var sphereIds: IntArray? = null, + var textureId: Int = -1 +): IEntryType { + override fun getId(): Int = id + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SkyBoxEntryType + + if (id != other.id) return false + if (anInt2392 != other.anInt2392) return false + if (sphereIds != null) { + if (other.sphereIds == null) return false + if (!sphereIds.contentEquals(other.sphereIds)) return false + } else if (other.sphereIds != null) return false + if (textureId != other.textureId) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + anInt2392 + result = 31 * result + (sphereIds?.contentHashCode() ?: 0) + result = 31 * result + textureId + return result + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt new file mode 100644 index 0000000..22ef172 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt @@ -0,0 +1,39 @@ +package com.runetopic.loader.index.config.struct + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.* +import java.nio.ByteBuffer + +/** + * @author Tyler Telis + * @email + */ +internal class StructEntryBuilder: IEntryBuilder { + + lateinit var structTypes: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + structTypes = buildSet { + store.index(2).group(26).files().forEach { + add(read(it.data.toByteBuffer(), StructEntryType(it.id))) + } + } + } + + override fun read(buffer: ByteBuffer, type: StructEntryType): StructEntryType { + do when (val opcode = buffer.readUnsignedByte()) { + 0 -> break + 249 -> { + val size = buffer.readUnsignedByte() + (0 until size).forEach { _ -> + val string = buffer.readUnsignedByte().toBoolean() + type.params[buffer.readUnsignedMedium()] = if (string) buffer.readString() else buffer.int + } + } + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryProvider.kt new file mode 100644 index 0000000..e1c413a --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryProvider.kt @@ -0,0 +1,18 @@ +package com.runetopic.loader.index.config.struct + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + + +/** + * @author Jordan Abraham + */ +class StructEntryProvider : IEntryProvider { + + private val builder = StructEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): StructEntryType = builder.structTypes.elementAt(id) + override fun size(): Int = builder.structTypes.size + override fun collect(): Set = builder.structTypes +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryType.kt new file mode 100644 index 0000000..0c8992c --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryType.kt @@ -0,0 +1,10 @@ +package com.runetopic.loader.index.config.struct + +import com.runetopic.loader.IEntryType + +data class StructEntryType( + private val id: Int = 0, + var params: MutableMap = mutableMapOf() +): IEntryType { + override fun getId(): Int = id +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt new file mode 100644 index 0000000..53450e4 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt @@ -0,0 +1,41 @@ +package com.runetopic.loader.index.config.underlay + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.readUnsignedByte +import com.runetopic.loader.extension.readUnsignedMedium +import com.runetopic.loader.extension.readUnsignedShort +import com.runetopic.loader.extension.toByteBuffer +import java.nio.ByteBuffer + +/** + * @author Jordan Abraham + */ +class UnderlayEntryBuilder: IEntryBuilder { + + lateinit var underlays: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + underlays = buildSet { + store.index(2).group(1).files().forEach { + add(read(it.data.toByteBuffer(), UnderlayEntryType(it.id))) + } + } + } + + override fun read(buffer: ByteBuffer, type: UnderlayEntryType): UnderlayEntryType { + do when (val opcode = buffer.readUnsignedByte()) { + 0 -> break + 1 -> type.color = buffer.readUnsignedMedium() + 2 -> buffer.readUnsignedShort().let { + type.textureId = if (it == 65535) -1 else it + } + 3 -> type.textureResolution = buffer.readUnsignedShort() shl 2 + 4 -> type.aBoolean2647 = false + 5 -> type.aBoolean2648 = false + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while(true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryProvider.kt new file mode 100644 index 0000000..dff9043 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryProvider.kt @@ -0,0 +1,17 @@ +package com.runetopic.loader.index.config.underlay + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + +/** + * @author Jordan Abraham + */ +class UnderlayEntryProvider : IEntryProvider { + + private val builder = UnderlayEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): UnderlayEntryType = builder.underlays.elementAt(id) + override fun size(): Int = builder.underlays.size + override fun collect(): Set = builder.underlays +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryType.kt new file mode 100644 index 0000000..d61d1bb --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryType.kt @@ -0,0 +1,17 @@ +package com.runetopic.loader.index.config.underlay + +import com.runetopic.loader.IEntryType + +/** + * @author Jordan Abraham + */ +data class UnderlayEntryType( + private val id: Int = 0, + var color: Int = 0, + var textureId: Int = -1, + var textureResolution: Int = 512, + var aBoolean2647: Boolean = true, + var aBoolean2648: Boolean = true +): IEntryType { + override fun getId(): Int = id +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt new file mode 100644 index 0000000..b01fb7b --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt @@ -0,0 +1,263 @@ +package com.runetopic.loader.index.loc + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.* +import java.nio.ByteBuffer + +/** + * @author Tyler Telis + * @email + */ +internal class LocEntryBuilder : IEntryBuilder { + + lateinit var mapTypes: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + mapTypes = buildSet { + store.index(16).use { index -> + (0 until index.expand()).forEach { + add(read(index.group(it ushr 8).file(it and 0xFF).data.toByteBuffer(), LocEntryType(it))) + } + } + } + } + + override fun read(buffer: ByteBuffer, type: LocEntryType): LocEntryType { + do when (val opcode: Int = buffer.readUnsignedByte()) { + 0 -> break + 1, 5 -> { + val size = buffer.readUnsignedByte() + val types = ByteArray(size) + val models = Array(size) { intArrayOf() } + + (0 until size).forEach { + types[it] = buffer.get() + val count = buffer.readUnsignedByte() + models[it] = IntArray(count) + (0 until count).forEach { it2 -> + models[it][it2] = buffer.readUnsignedShort() + } + } + if (opcode == 5) { + skipReadModelIds(buffer) + } + + type.models = models + type.types = types + } + 2 -> type.name = buffer.readString() + 14 -> type.sizeX = buffer.readUnsignedByte() + 15 -> type.sizeZ = buffer.readUnsignedByte() + 17 -> { + type.interactionType = 0 + type.shouldBlockProjectiles = false + } + 18 -> type.shouldBlockProjectiles = false + 19 -> type.wallOrDoor = buffer.readUnsignedByte() + 21 -> type.contouredGround = 1 + 22 -> type.shouldMergeNormals = true + 23 -> type.anInt1146 = 1 + 24 -> buffer.readUnsignedShort().let { + type.animationId = if (it == 65535) -1 else it + } + 27 -> type.interactionType = 1 + 28 -> type.decorDisplacement = (buffer.readUnsignedByte() shl 2) + 29 -> type.ambient = buffer.get().toInt() + 39 -> type.contrast = buffer.get() * 5 + in 30..34 -> buffer.readString().let { type.actions[opcode - 30] = it } + 40 -> { + val size = buffer.readUnsignedByte() + val colorsToFind = ShortArray(size) + val colorsToReplace = ShortArray(size) + (0 until size).forEach { + colorsToFind[it] = buffer.readUnsignedShort().toShort() + colorsToReplace[it] = buffer.readUnsignedShort().toShort() + } + type.colorsToFind = colorsToFind + type.colorsToReplace = colorsToReplace + } + 41 -> { + val size = buffer.readUnsignedByte() + val texturesToFind = ShortArray(size) + val texturesToReplace = ShortArray(size) + (0 until size).forEach { + texturesToFind[it] = buffer.readUnsignedShort().toShort() + texturesToReplace[it] = buffer.readUnsignedShort().toShort() + } + + type.texturesToFind = texturesToFind + type.texturesToReplace = texturesToReplace + } + 42 -> { + val length = buffer.readUnsignedByte() + val aByteArray1118 = ByteArray(length) + + (0 until length).forEach { + aByteArray1118[it] = buffer.get() + } + type.aByteArray1118 = aByteArray1118 + } + 62 -> type.isRotated = true + 64 -> type.shadowed = false + 65 -> type.modelSizeX = buffer.readUnsignedShort() + 66 -> type.modelSizeHeight = buffer.readUnsignedShort() + 67 -> type.modelSizeY = buffer.readUnsignedShort() + 69 -> type.clippingFlag = buffer.readUnsignedByte() + 70 -> type.anInt1150 = (buffer.readUnsignedShort() shl 2) + 71 -> type.anInt1149 = (buffer.readUnsignedShort() shl 2) + 72 -> type.anInt1124 = (buffer.readUnsignedShort() shl 2) + 73 -> type.aBoolean1131 = true + 74 -> type.aBoolean1157 = true + 75 -> type.anInt1166 = buffer.readUnsignedByte() + 77, 92 -> { + buffer.readUnsignedShort().let { + type.varbitId = if (it == 65535) -1 else it + } + + buffer.readUnsignedShort().let { + type.varpId = if (it == 65535) -1 else it + } + + var value = -1 + + if (opcode == 92) { + buffer.readUnsignedShort().let { + value = if (it == 65535) -1 else it + } + } + + val size = buffer.readUnsignedByte() + val configChangeDest = IntArray(size + 2) + + (0..size).forEach { index -> + buffer.readUnsignedShort().let { + if (it == 65535) configChangeDest[index] = -1 + else configChangeDest[index] = it + } + } + + configChangeDest[size + 1] = value + type.configChangeDest = configChangeDest + } + 78 -> { + type.anInt1132 = buffer.readUnsignedShort() + type.anInt1144 = buffer.readUnsignedByte() + } + 79 -> { + type.anInt1145 = buffer.readUnsignedShort() + type.anInt1139 = buffer.readUnsignedShort() + type.anInt1144 = buffer.readUnsignedByte() + val length = buffer.readUnsignedByte() + val anIntArray1127 = IntArray(length) + + (0 until length).forEach { index -> + anIntArray1127[index] = buffer.readUnsignedShort() + } + + type.anIntArray1127 = anIntArray1127 + } + 81 -> { + type.contouredGround = 2.toByte() + type.anInt1142 = buffer.readUnsignedByte() * 256 + } + 82 -> type.aBoolean1108 = true + 88 -> type.aBoolean1151 = true + 89 -> type.aBoolean1103 = true + 91 -> type.aBoolean1171 = true + 93 -> { + type.contouredGround = 5.toByte() + type.anInt1142 = buffer.readUnsignedShort() + } + 94 -> { + type.contouredGround = 4.toByte() + } + 95 -> { + type.contouredGround = 5.toByte() + type.anInt1142 = buffer.short.toInt() + } + 97 -> type.aBoolean1104 = true + 98 -> type.aBoolean1106 = true + 99 -> { + type.anInt1184 = buffer.readUnsignedByte() + type.anInt1173 = buffer.readUnsignedShort() + } + 100 -> { + type.anInt1183 = buffer.readUnsignedByte() + type.anInt1121 = buffer.readUnsignedShort() + } + 101 -> type.anInt1181 = buffer.readUnsignedByte() + 102 -> type.anInt1160 = buffer.readUnsignedShort() + 103 -> type.anInt1146 = 0 + 104 -> type.anInt1136 = buffer.readUnsignedByte() + 105 -> type.aBoolean1148 = true + 106 -> { + val length = buffer.readUnsignedByte() + val anIntArray1170 = IntArray(length) + val anIntArray1154 = IntArray(length) + (0 until length).forEach { index -> + anIntArray1170[index] = buffer.readUnsignedShort() + val size = buffer.readUnsignedByte() + anIntArray1154[index] = size + type.anInt1116 += size + } + + type.anIntArray1170 = anIntArray1170 + type.anIntArray1154 = anIntArray1154 + } + 107 -> type.anInt1101 = buffer.readUnsignedShort() + in 150..154 -> buffer.readString().let { type.actions[opcode -150] = it } + 160 -> { + val length = buffer.readUnsignedByte() + val anIntArray1153 = IntArray(length) + (0 until length).forEach { index -> + anIntArray1153[index] = buffer.readUnsignedShort() + } + type.anIntArray1153 = anIntArray1153 + } + 162 -> { + type.contouredGround = 3.toByte() + type.anInt1142 = buffer.int + } + 163 -> { + type.aByte1123 = buffer.get() + type.aByte1110 = buffer.get() + type.aByte1169 = buffer.get() + type.aByte1109 = buffer.get() + } + 164 -> type.anInt1112 = buffer.short.toInt() + 165 -> type.anInt1115 = buffer.short.toInt() + 166 -> type.anInt1125 = buffer.short.toInt() + 167 -> type.anInt1107 = buffer.readUnsignedShort() + 168 -> type.aBoolean1163 = true + 169 -> type.aBoolean1175 = true + 170 -> type.anInt1156 = buffer.readUnsignedSmart() + 171 -> type.anInt1111 = buffer.readUnsignedSmart() + 173 -> { + type.anInt1128 = buffer.readUnsignedShort() + type.anInt1159 = buffer.readUnsignedShort() + } + 177 -> type.aBoolean1167 = true + 178 -> type.anInt1113 = buffer.readUnsignedByte() + 249 -> { + val length = buffer.readUnsignedByte() + (0 until length).forEach { _ -> + val string = buffer.readUnsignedByte().toBoolean() + type.params[buffer.readUnsignedMedium()] = if (string) buffer.readString() else buffer.int + } + } + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } + + private fun skipReadModelIds(buffer: ByteBuffer) { + val size: Int = buffer.readUnsignedByte() + for (index in 0 until size) { + buffer.position(buffer.position() + 1) + val count: Int = buffer.readUnsignedByte() + buffer.position(buffer.position() + (count * 2)) + } + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryProvider.kt new file mode 100644 index 0000000..9bab71f --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryProvider.kt @@ -0,0 +1,19 @@ +package com.runetopic.loader.index.loc + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + + +/** + * @author Tyler Telis + * @email + */ +class LocEntryProvider : IEntryProvider { + + private val builder = LocEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): LocEntryType = builder.mapTypes.elementAt(id) + override fun size(): Int = builder.mapTypes.size + override fun collect(): Set = builder.mapTypes +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryType.kt new file mode 100644 index 0000000..521e44f --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryType.kt @@ -0,0 +1,92 @@ +package com.runetopic.loader.index.loc + +import com.runetopic.loader.IEntryType + +/** + * @author Tyler Telis + * @email + */ +data class LocEntryType( + private val id: Int= 0, + var types: ByteArray? = null, + var models: Array? = null, + var name: String = "null", + var sizeX: Int = -1, + var sizeZ: Int = -1, + var interactionType: Int = 2, + var shouldBlockProjectiles: Boolean = true, + var wallOrDoor: Int = -1, + var contouredGround: Byte = 0, + var shouldMergeNormals: Boolean = false, + var anInt1146: Int = -1, + var animationId: Int = -1, + var decorDisplacement: Int = 64, + var ambient: Int = 0, + var contrast: Int = 0, + var actions: Array = Array(5) { "" }, + var colorsToFind: ShortArray? = null, + var colorsToReplace: ShortArray? = null, + var texturesToFind: ShortArray? = null, + var texturesToReplace: ShortArray? = null, + var aByteArray1118: ByteArray? = null, + var isRotated: Boolean = false, + var shadowed: Boolean = false, + var modelSizeX: Int = 128, + var modelSizeHeight: Int = 128, + var modelSizeY: Int = 128, + var clippingFlag: Int = 0, + var anInt1150: Int = 0, + var anInt1149: Int = 0, + var anInt1124: Int = 0, + var aBoolean1131: Boolean = false, + var aBoolean1157: Boolean = false, + var anInt1166: Int = -1, + var varbitId: Int = -1, + var varpId: Int = -1, + var anInt1132: Int = -1, + var anInt1144: Int = 0, + var anInt1145: Int = 0, + var anInt1139: Int = 0, + var aBoolean1108: Boolean = false, + var aBoolean1151: Boolean = false, + var aBoolean1103: Boolean = false, + var aBoolean1171: Boolean = false, + var aBoolean1104: Boolean = false, + var aBoolean1106: Boolean = false, + var configChangeDest: IntArray? = null, + var anIntArray1127: IntArray? = null, + var anInt1142: Int = -1, + var anInt1184: Int = -1, + var anInt1173: Int = -1, + var anInt1183: Int = -1, + var anInt1121: Int = -1, + var anInt1181: Int = 0, + var anInt1160: Int = 0, + var anInt1136: Int = 255, + var anInt1116: Int = 0, + var anInt1101: Int = -1, + var aBoolean1148: Boolean = false, + var anIntArray1170: IntArray? = null, + var anIntArray1154: IntArray? = null, + var anIntArray1153: IntArray? = null, + var aByte1123: Byte = 0, + var aByte1110: Byte = 0, + var aByte1169: Byte = 0, + var aByte1109: Byte = 0, + var anInt1112: Int = 0, + var anInt2650: Int = 0, + var anInt1115: Int = 0, + var anInt1125: Int = 0, + var anInt1107: Int = 0, + var aBoolean1163: Boolean = false, + var aBoolean1175: Boolean = false, + var anInt1156: Int = 960, + var anInt1111: Int = 0, + var anInt1128: Int = 256, + var anInt1159: Int = 256, + var aBoolean1167: Boolean = false, + var anInt1113: Int = 0, + var params: MutableMap = mutableMapOf(), + ): IEntryType { + override fun getId(): Int = id +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt new file mode 100644 index 0000000..16d8365 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt @@ -0,0 +1,162 @@ +package com.runetopic.loader.index.map + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.readUnsignedByte +import com.runetopic.loader.extension.readUnsignedShort +import com.runetopic.loader.extension.skip +import com.runetopic.loader.extension.toByteBuffer +import com.runetopic.loader.util.vector.Vector3f +import java.nio.ByteBuffer + +/** + * @author Tyler Telis + * @email + */ +internal class MapEntryBuilder : IEntryBuilder { + + lateinit var mapTypes: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + mapTypes = buildSet { + store.index(5).use { + (0..Short.MAX_VALUE).forEach { regionId -> + val regionX: Int = regionId shr 8 + val regionY: Int = regionId and 0xFF + it.group("m${regionX}_${regionY}").data.let { data -> + if (data.isEmpty()) return@forEach + add(read(data.toByteBuffer(), MapEntryType(regionId, regionX, regionY))) + } + } + } + } + } + + override fun read(buffer: ByteBuffer, type: MapEntryType): MapEntryType { + (0 until MapEntryType.PLANES).forEach { plane -> + (0 until MapEntryType.MAP_SIZE).forEach { x -> + (0 until MapEntryType.MAP_SIZE).forEach { z -> + val tile = MapEntryType.Tile() + + while (true) { + when (val opcode = buffer.readUnsignedByte()) { + 0 -> break + 1 -> { + tile.height = buffer.readUnsignedByte() + break + } + in 0..49 -> { + tile.attrOpcode = opcode + tile.overlayId = buffer.get() + tile.overlayPath = ((opcode - 2) / 4).toByte() + tile.overlayRotation = (opcode - 2 and 3).toByte() + } + in 0..81 -> { + tile.settings = (opcode - 49).toByte() + } + else -> tile.underlayId = (opcode - 81).toByte() + } + } + + type.tiles[plane][x][z] = tile + } + } + } + + if (buffer.remaining() > 0) { + readAtmosphere(buffer, type) + } + return type + } + + private fun readAtmosphere(buffer: ByteBuffer, type: MapEntryType) { + while (buffer.array().size > buffer.position()) { + when (buffer.readUnsignedByte()) { + 0 -> { + val flag = buffer.readUnsignedByte() + if ((flag and 0x1) != 0) type.atmosphere.sunColor = buffer.int + if ((flag and 0x2) != 0) type.atmosphere.sunBrightness = buffer.readUnsignedShort() / 256.0f + if ((flag and 0x4) != 0) type.atmosphere.sunCoordinateX = buffer.readUnsignedShort() / 256.0f + if ((flag and 0x8) != 0) type.atmosphere.sunCoordinateY = buffer.readUnsignedShort() / 256.0f + if ((flag and 0x10) != 0) type.atmosphere.sunAngle = Vector3f(buffer.short.toFloat(), buffer.short.toFloat(), buffer.short.toFloat()) + if ((flag and 0x20) != 0) type.atmosphere.skyColor = buffer.int + if ((flag and 0x40) != 0) type.atmosphere.fogDensity = buffer.readUnsignedShort() + if ((flag and 0x80) != 0) { + val faceTop = buffer.readUnsignedShort() + val faceBottom = buffer.readUnsignedShort() + val faceFront = buffer.readUnsignedShort() + val faceBack = buffer.readUnsignedShort() + val faceLeft = buffer.readUnsignedShort() + val faceRight = buffer.readUnsignedShort() + type.atmosphere.environmentMap = MapEntryType.EnvironmentMap(faceTop, faceBottom, faceFront, faceBack, faceLeft, faceRight) + } + } + 1 -> { + val lightCount = buffer.readUnsignedByte() + + if (lightCount > 0) { + (0 until lightCount).forEach { _ -> + val flag = buffer.readUnsignedByte() + val fromFirstLevel = flag and 0x8 != 0 + val toLastLevel = flag and 0x10 != 0 + val level = flag and 0x7 + + val x = buffer.readUnsignedShort() shl 2 + val z = buffer.readUnsignedShort() shl 2 + val y = buffer.readUnsignedShort() shl 2 + val size = buffer.readUnsignedByte() * 2 + 1 + val ranges = ShortArray(size) + + (ranges.indices).forEach { + val unsigned = buffer.readUnsignedShort() + var start = unsigned ushr 8 + if (size <= start) start = size - 1 + var end = unsigned and 0xFF + if (end > -start + size) end = -start + size + ranges[it] = (end or end shl 8).toShort() + } + val color = buffer.readUnsignedShort() + val mask = buffer.readUnsignedByte() + val fileId = mask and 0x1f + val strength = mask shl 3 and 0x700 + + if (fileId == 31) { + val entryId = buffer.readUnsignedShort() + type.atmosphere.lightingEntryId = entryId + } + type.atmosphere.lightEffectPoint = MapEntryType.LightEffectPoint(fromFirstLevel, toLastLevel, level, x, y, z, color, strength) + } + } + } + 2 -> { + val bloom = (buffer.readUnsignedByte() * 8) / 255.0f + val brightness = (buffer.readUnsignedByte() * 8) / 255.0f + val whitePoint = (buffer.readUnsignedByte() * 8) / 255.0f + type.atmosphere.highDynamicRange = MapEntryType.HighDynamicRange(bloom, brightness, whitePoint) + } + 128 -> buffer.skip(10) + 129 -> { + val cameraAngles = arrayOfNulls>(4) + (0 until 4).forEach { index -> + val i = buffer.get().toInt() + //0 checks if the array exists. + if (i == 1) { + val regionParamX = 104 + val regionParamY = 104 + cameraAngles[index] = Array(regionParamX + 1) { ByteArray(regionParamY + 1) } + (0 until 64 step 4).forEach { x -> + (0 until 64 step 4).forEach { z -> + val angle = buffer.get() + // TODO still some bytes in the buffer to be looked at with camera angles. Will come back when we implement a map tool + cameraAngles[index]?.get(x)?.set(z, angle) + } + } + } + } + type.cameraAngles = cameraAngles + } + } + } + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryProvider.kt new file mode 100644 index 0000000..e6fd1b1 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryProvider.kt @@ -0,0 +1,19 @@ +package com.runetopic.loader.index.map + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + + +/** + * @author Tyler Telis + * @email + */ +class MapEntryProvider : IEntryProvider { + + private val builder = MapEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): MapEntryType = builder.mapTypes.elementAt(id) + override fun size(): Int = builder.mapTypes.size + override fun collect(): Set = builder.mapTypes +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryType.kt new file mode 100644 index 0000000..046fdd8 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryType.kt @@ -0,0 +1,96 @@ +package com.runetopic.loader.index.map + +import com.runetopic.loader.IEntryType +import com.runetopic.loader.util.vector.Vector3f + +/** + * @author Tyler Telis + * @email + */ +data class MapEntryType( + private val id: Int = 0, + val regionX: Int, + val regionY: Int, + val tiles: Array>> = Array(PLANES) { Array(MAP_SIZE) { arrayOfNulls(MAP_SIZE) } }, + var atmosphere: AtmosphereEntryType = AtmosphereEntryType(), + var cameraAngles: Array?> = arrayOfNulls(4) +) : IEntryType { + override fun getId(): Int = id + + data class Tile( + var height: Int? = null, + var attrOpcode: Int = 0, + var settings: Byte = 0, + var overlayId: Byte = 0, + var overlayPath: Byte = 0, + var overlayRotation: Byte = 0, + var underlayId: Byte = 0 + ) + + data class AtmosphereEntryType( + var sunColor: Int = 16777215, + var sunBrightness: Float = 1.1523438f, + var sunCoordinateX: Float = 0.69921875f, + var sunCoordinateY: Float = 1.2f, + var sunAngle: Vector3f = Vector3f(-50f, -60f, -50f), + var skyColor: Int = 13156520, + var fogDensity: Int = 0, + var highDynamicRange: HighDynamicRange = HighDynamicRange(), + var lightingEntryId: Int = -1, + var environmentMap: EnvironmentMap = EnvironmentMap(682, 683, 684, 685, 686, 687), + var lightEffectPoint: LightEffectPoint = LightEffectPoint() + ) + + data class HighDynamicRange( + var bloom: Float = 1.0f, + var brightness: Float = 1.0f, + var whitePoint: Float = 0.25f + ) + + data class LightEffectPoint( + var fromFirstLevel: Boolean = false, + var toLastLevel: Boolean = false, + var level: Int = 0, + var x: Int = 0, + var y: Int = 0, + var z: Int = 0, + var color: Int = 0, + var strength: Int = 0, + ) + + data class EnvironmentMap( + var faceTop: Int, + var faceBottom: Int, + var faceFront: Int, + var faceBack: Int, + var faceLeft: Int, + var faceRight: Int + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MapEntryType + + if (id != other.id) return false + if (regionX != other.regionX) return false + if (regionY != other.regionY) return false + if (!tiles.contentDeepEquals(other.tiles)) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + regionX + result = 31 * result + regionY + result = 31 * result + tiles.contentDeepHashCode() + return result + } + + companion object { + const val MAP_SIZE = 64 + const val PLANES = 4 + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt new file mode 100644 index 0000000..24e0a3b --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt @@ -0,0 +1,67 @@ +package com.runetopic.loader.index.map + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.readUnsignedByte +import com.runetopic.loader.extension.readUnsignedIntSmartShortCompat +import com.runetopic.loader.extension.readUnsignedSmart +import com.runetopic.loader.extension.toByteBuffer +import java.nio.ByteBuffer + +/** + * @author Tyler Telis + * @email + */ +internal class MapLocationEntryBuilder : IEntryBuilder { + + lateinit var mapTypes: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + mapTypes = buildSet { + store.index(5).use { + (0..Short.MAX_VALUE).forEach { regionId -> + val regionX: Int = regionId shr 8 + val regionY: Int = regionId and 0xFF + it.group("l${regionX}_${regionY}").data.let { data -> + if (data.isEmpty()) return@forEach + add(read(data.toByteBuffer(), MapLocationEntryType(regionId, regionX, regionY))) + } + } + } + } + } + + override fun read(buffer: ByteBuffer, type: MapLocationEntryType): MapLocationEntryType { + var id = -1 + var idOffset: Int + + while (buffer.readUnsignedIntSmartShortCompat().also { idOffset = it } != 0) { + id += idOffset + + var position = 0 + var positionOffset: Int + + while (buffer.readUnsignedSmart().also { positionOffset = it } != 0) { + position += positionOffset - 1 + val localY = position and 0x3F + val localX = position shr 6 and 0x3F + val height = position shr 12 and 0x3 + val attributes: Int = buffer.readUnsignedByte() + val locType = attributes shr 2 + val orientation = attributes and 0x3 + type.locations.add( + MapLocationEntryType.MapLocation( + id, + locType, + orientation, + localX, localY, + height + ) + ) + } + } + + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryProvider.kt new file mode 100644 index 0000000..62157d9 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryProvider.kt @@ -0,0 +1,19 @@ +package com.runetopic.loader.index.map + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + + +/** + * @author Tyler Telis + * @email + */ +class MapLocationEntryProvider : IEntryProvider { + + private val builder = MapLocationEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): MapLocationEntryType = builder.mapTypes.elementAt(id) + override fun size(): Int = builder.mapTypes.size + override fun collect(): Set = builder.mapTypes +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryType.kt new file mode 100644 index 0000000..a7524cf --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryType.kt @@ -0,0 +1,25 @@ +package com.runetopic.loader.index.map + +import com.runetopic.loader.IEntryType + +/** + * @author Tyler Telis + * @email + */ +data class MapLocationEntryType( + private val regionId: Int, + private val regionX: Int, + private val regionY: Int, + val locations: ArrayList = arrayListOf() +): IEntryType { + override fun getId(): Int = regionId + + class MapLocation( + private val id: Int, + private val type: Int, + private val orientation: Int, + private val localX: Int, + private val localY: Int, + private val height: Int + ) +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt new file mode 100644 index 0000000..87c1d01 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt @@ -0,0 +1,213 @@ +package com.runetopic.loader.index.npc + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.* +import java.nio.ByteBuffer + +/** + * @author Jordan Abraham + */ +class NpcEntryBuilder: IEntryBuilder { + + lateinit var npcs: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + npcs = buildSet { + store.index(18).use { index -> + (0 until index.expand()).forEach { + add(read(index.group(it ushr 8).file(it and 0xFF).data.toByteBuffer(), NpcEntryType(it))) + } + } + } + } + + override fun read(buffer: ByteBuffer, type: NpcEntryType): NpcEntryType { + do when (val opcode: Int = buffer.readUnsignedByte()) { + 0 -> break + 1 -> { + val size = buffer.readUnsignedByte() + val models = IntArray(size) + (0 until size).forEach { + models[it] = buffer.readUnsignedShort() + if (models[it] == 65535) { + models[it] = -1 + } + } + type.models = models + } + 2 -> type.name = buffer.readString() + 12 -> type.size = buffer.readUnsignedByte() + in 30..34 -> type.options[opcode - 30] = buffer.readString() + 40 -> { + val size = buffer.readUnsignedByte() + val colorToFind = ShortArray(size) + val colorToReplace = ShortArray(size) + (0 until size).forEach { + colorToFind[it] = buffer.readUnsignedShort().toShort() + colorToReplace[it] = buffer.readUnsignedShort().toShort() + } + type.colorToFind = colorToFind + type.colorToReplace = colorToReplace + } + 41 -> { + val size = buffer.readUnsignedByte() + val textureToFind = ShortArray(size) + val textureToReplace = ShortArray(size) + (0 until size).forEach { + textureToFind[it] = buffer.readUnsignedShort().toShort() + textureToReplace[it] = buffer.readUnsignedShort().toShort() + } + type.textureToFind = textureToFind + type.textureToReplace = textureToReplace + } + 42 -> { + val size = buffer.readUnsignedByte() + val aByteArray866 = ByteArray(size) + (0 until size).forEach { + aByteArray866[it] = buffer.get() + } + type.aByteArray866 = aByteArray866 + } + 60 -> { + val size = buffer.readUnsignedByte() + val chatheadModels = IntArray(size) + (0 until size).forEach { + chatheadModels[it] = buffer.readUnsignedShort() + } + type.chatheadModels = chatheadModels + } + 93 -> type.isMinimapVisible = false + 95 -> type.combatLevel = buffer.readUnsignedShort() + 97 -> type.widthScale = buffer.readUnsignedShort() + 98 -> type.heightScale = buffer.readUnsignedShort() + 99 -> type.hasRenderPriority = true + 100 -> type.ambient = buffer.get().toInt() + 101 -> type.contrast = buffer.get() * 5 + 102 -> type.headIcon = buffer.readUnsignedShort() + 103 -> type.rotationSpeed = buffer.readUnsignedShort() + 106, 118 -> { + type.varbitId = buffer.readUnsignedShort() + if (type.varbitId == 65535) { + type.varbitId = -1 + } + type.varpId = buffer.readUnsignedShort() + if (type.varpId == 65535) { + type.varpId = -1 + } + var value = -1 + if (opcode == 118) { + value = buffer.readUnsignedShort() + if (value == 65535) { + value = -1 + } + } + val size = buffer.readUnsignedByte() + val configChangeDest = IntArray(size + 2) + (0..size).forEach { + configChangeDest[it] = buffer.readUnsignedShort() + if (configChangeDest[it] == 65535) { + configChangeDest[it] = -1 + } + } + configChangeDest[size + 1] = value + type.configChangeDest = configChangeDest + } + 107 -> type.isInteractable = false + 109 -> type.rotationFlag = false + 111 -> type.aBoolean812 = false + 113 -> { + type.aShort865 = buffer.readUnsignedShort().toShort() + type.aShort834 = buffer.readUnsignedShort().toShort() + } + 114 -> { + type.aByte806 = buffer.get() + type.aByte804 = buffer.get() + } + 119 -> type.aByte858 = buffer.get() + 121 -> { + val anIntArrayArray845 = arrayOfNulls(type.models!!.size) + val size = buffer.readUnsignedByte() + (0 until size).forEach { + val i_98_ = buffer.readUnsignedByte() + val data = (IntArray(3).also { anIntArrayArray845[i_98_] = it }) + data[0] = buffer.get().toInt() + data[1] = buffer.get().toInt() + data[2] = buffer.get().toInt() + } + } + 122 -> type.anInt854 = buffer.readUnsignedShort() + 123 -> type.anInt852 = buffer.readUnsignedShort() + 125 -> type.aByte819 = buffer.get() + 127 -> type.anInt835 = buffer.readUnsignedShort() + 128 -> buffer.skip(1) + 134 -> { + type.anInt826 = buffer.readUnsignedShort() + if (type.anInt826 == 65535) { + type.anInt826 = -1 + } + type.anInt850 = buffer.readUnsignedShort() + if (type.anInt850 == 65535) { + type.anInt850 = -1 + } + type.anInt811 = buffer.readUnsignedShort() + if (type.anInt811 == 65535) { + type.anInt811 = -1 + } + type.anInt856 = buffer.readUnsignedShort() + if (type.anInt856 == 65535) { + type.anInt856 = -1 + } + type.anInt805 = buffer.readUnsignedByte() + } + 135 -> { + type.anInt803 = buffer.readUnsignedByte() + type.anInt861 = buffer.readUnsignedShort() + } + 136 -> { + type.anInt823 = buffer.readUnsignedByte() + type.anInt857 = buffer.readUnsignedShort() + } + 137 -> type.anInt831 = buffer.readUnsignedShort() + 138 -> type.anInt817 = buffer.readUnsignedShort() + 139 -> type.anInt830 = buffer.readUnsignedShort() + 140 -> type.anInt808 = buffer.readUnsignedByte() + 141 -> type.aBoolean875 = true + 142 -> type.anInt846 = buffer.readUnsignedShort() + 143 -> type.aBoolean869 = true + in 150..154 -> { buffer.readString().let { type.options[opcode -150] = it } } + 155 -> { + type.aByte821 = buffer.get() + type.aByte824 = buffer.get() + type.aByte843 = buffer.get() + type.aByte855 = buffer.get() + } + 158 -> type.aByte833 = 1 + 159 -> type.aByte833 = 0 + 160 -> { + val size = buffer.readUnsignedByte() + val anIntArray867 = IntArray(size) + (0 until size).forEach { + anIntArray867[it] = buffer.readUnsignedShort() + } + } + 162 -> type.aBoolean809 = true + 163 -> type.anInt864 = buffer.readUnsignedByte() + 164 -> { + type.anInt848 = buffer.readUnsignedShort() + type.anInt837 = buffer.readUnsignedShort() + } + 165 -> type.anInt847 = buffer.readUnsignedByte() + 168 -> type.anInt828 = buffer.readUnsignedByte() + 249 -> { + val length = buffer.readUnsignedByte() + (0 until length).forEach { _ -> + val string = buffer.readUnsignedByte().toBoolean() + type.params[buffer.readUnsignedMedium()] = if (string) buffer.readString() else buffer.int + } + } + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryProvider.kt new file mode 100644 index 0000000..4ccf7ed --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryProvider.kt @@ -0,0 +1,17 @@ +package com.runetopic.loader.index.npc + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + +/** + * @author Jordan Abraham + */ +class NpcEntryProvider: IEntryProvider { + + private val builder = NpcEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): NpcEntryType = builder.npcs.elementAt(id) + override fun size(): Int = builder.npcs.size + override fun collect(): Set = builder.npcs +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryType.kt new file mode 100644 index 0000000..35a21be --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryType.kt @@ -0,0 +1,75 @@ +package com.runetopic.loader.index.npc + +import com.runetopic.loader.IEntryType + +/** + * @author Jordan Abraham + */ +data class NpcEntryType( + private val id: Int = 0, + var aBoolean812: Boolean = true, + var combatLevel: Int = -1, + var hasRenderPriority: Boolean = false, + var anInt805: Int = 0, + var anInt803: Int = -1, + var aByte806: Byte = -96, + var anInt835: Int = -1, + var aShort834: Short = 0, + var anInt847: Int = 0, + var anInt811: Int = -1, + var anInt846: Int = -1, + var isMinimapVisible: Boolean = true, + var anInt830: Int = -1, + var anInt831: Int = -1, + var name: String = "null", + var isInteractable: Boolean = true, + var heightScale: Int = 128, + var varbitId: Int = -1, + var aByte833: Byte = -1, + var aByte855: Byte = 0, + var anInt823: Int = -1, + var widthScale: Int = 128, + var anInt826: Int = -1, + var anInt850: Int = -1, + var aByte858: Byte = 0, + var anInt856: Int = -1, + var anInt857: Int = -1, + var anInt861: Int = -1, + var aByte819: Byte = 4, + var size: Int = 1, + var ambient: Int = 0, + var varpId: Int = -1, + var anInt837: Int = 256, + var anInt864: Int = -1, + var aBoolean869: Boolean = false, + var anInt828: Int = 0, + var anInt848: Int = 256, + var anInt852: Int = -1, + var aShort865: Short = 0, + var options: Array = arrayOfNulls(5), + var headIcon: Int = -1, + var rotationFlag: Boolean = true, + var anInt854: Int = -1, + var aBoolean875: Boolean = false, + var rotationSpeed: Int = 32, + var anInt808: Int = 255, + var contrast: Int = 0, + var models: IntArray? = null, + var colorToFind: ShortArray? = null, + var colorToReplace: ShortArray? = null, + var textureToFind: ShortArray? = null, + var textureToReplace: ShortArray? = null, + var aByteArray866: ByteArray? = null, + var chatheadModels: IntArray? = null, + var configChangeDest: IntArray? = null, + var aByte804: Byte = -16, + var anIntArrayArray845: Array? = null, + var anInt817: Int = -1, + var aByte821: Byte = 0, + var aByte824: Byte = 0, + var aByte843: Byte = 0, + var aBoolean809: Boolean = false, + var params: MutableMap = mutableMapOf(), +): IEntryType { + override fun getId(): Int = id +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt new file mode 100644 index 0000000..137f3f0 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt @@ -0,0 +1,150 @@ +package com.runetopic.loader.index.obj + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.* +import java.nio.ByteBuffer + +/** + * @author Jordan Abraham + */ +internal class ObjEntryBuilder : IEntryBuilder { + + lateinit var objs: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + objs = buildSet { + store.index(19).use { index -> + (0 until index.expand()).forEach { + add(read(index.group(it ushr 8).file(it and 0xFF).data.toByteBuffer(), ObjEntryType(it))) + } + } + } + } + + override fun read(buffer: ByteBuffer, type: ObjEntryType): ObjEntryType { + do when (val opcode: Int = buffer.readUnsignedByte()) { + 0 -> break + 1 -> type.inventoryModel = buffer.readUnsignedShort() + 2 -> type.name = buffer.readString() + 4 -> type.zoom2d = buffer.readUnsignedShort() + 5 -> type.xan2d = buffer.readUnsignedShort() + 6 -> type.yan2d = buffer.readUnsignedShort() + 7 -> buffer.readUnsignedShort().let { + type.xOffset2d = if (it > Short.MAX_VALUE) it - 65536 else it + } + 8 -> buffer.readUnsignedShort().let { + type.yOffset2d = if (it > Short.MAX_VALUE) it - 65536 else it + } + 11 -> type.stackable = 1 + 12 -> type.cost = buffer.int + 16 -> type.members = true + 23 -> type.maleModel0 = buffer.readUnsignedShort() + 24 -> type.maleModel1 = buffer.readUnsignedShort() + 25 -> type.femaleModel0 = buffer.readUnsignedShort() + 26 -> type.femaleModel1 = buffer.readUnsignedShort() + in 30..34 -> buffer.readString().let { type.options[opcode - 30] = it } + in 35..39 -> buffer.readString().let { type.interfaceOptions[opcode - 35] = it } + 40 -> { + val size = buffer.readUnsignedByte() + val colorToFind = ShortArray(size) + val colorToReplace = ShortArray(size) + (0 until size).forEach { + colorToFind[it] = buffer.readUnsignedShort().toShort() + colorToReplace[it] = buffer.readUnsignedShort().toShort() + } + type.colorToFind = colorToFind + type.colorToReplace = colorToReplace + } + 41 -> { + val size = buffer.readUnsignedByte() + val textureToFind = ShortArray(size) + val textureToReplace = ShortArray(size) + (0 until size).forEach { + textureToFind[it] = buffer.readUnsignedShort().toShort() + textureToReplace[it] = buffer.readUnsignedShort().toShort() + } + type.textureToFind = textureToFind + type.textureToReplace = textureToReplace + } + 42 -> { + val size = buffer.readUnsignedByte() + val aByteArray1858 = ByteArray(size) + (0 until size).forEach { aByteArray1858[it] = buffer.readUnsignedByte().toByte() } + type.aByteArray1858 = aByteArray1858 + } + 65 -> type.tradeable = true + 78 -> type.maleModel2 = buffer.readUnsignedShort() + 79 -> type.femaleModel2 = buffer.readUnsignedShort() + 90 -> type.maleHeadModel = buffer.readUnsignedShort() + 91 -> type.femaleHeadModel = buffer.readUnsignedShort() + 92 -> type.maleHeadModel2 = buffer.readUnsignedShort() + 93 -> type.femaleHeadModel2 = buffer.readUnsignedShort() + 95 -> type.zan2d = buffer.readUnsignedShort() + 96 -> type.anInt1865 = buffer.readUnsignedByte() + 97 -> type.notedId = buffer.readUnsignedShort() + 98 -> type.notedTemplate = buffer.readUnsignedShort() + in 100..109 -> { + val countObj = IntArray(10) + val countCo = IntArray(10) + countObj[opcode - 100] = buffer.readUnsignedShort() + countCo[opcode - 100] = buffer.readUnsignedShort() + type.countObj = countObj + type.countCo = countCo + } + 110 -> type.resizeX = buffer.readUnsignedShort() + 111 -> type.resizeY = buffer.readUnsignedShort() + 112 -> type.resizeZ = buffer.readUnsignedShort() + 113 -> type.ambient = buffer.readUnsignedByte() + 114 -> type.contrast = buffer.get().toInt() * 5 + 115 -> type.team = buffer.readUnsignedByte() + 121 -> type.lendId = buffer.readUnsignedShort() + 122 -> type.lendTemplateId = buffer.readUnsignedShort() + 125 -> { + type.anInt1895 = buffer.get().toInt() shl 2 + type.anInt1862 = buffer.get().toInt() shl 2 + type.anInt1873 = buffer.get().toInt() shl 2 + } + 126 -> { + type.anInt1866 = buffer.get().toInt() shl 2 + type.anInt1852 = buffer.get().toInt() shl 2 + type.anInt1867 = buffer.get().toInt() shl 2 + } + 127 -> { + type.anInt1899 = buffer.readUnsignedByte() + type.anInt1897 = buffer.readUnsignedShort() + } + 128 -> { + type.anInt1850 = buffer.readUnsignedByte() + type.anInt1863 = buffer.readUnsignedShort() + } + 129 -> { + type.anInt1896 = buffer.readUnsignedByte() + type.anInt1889 = buffer.readUnsignedShort() + } + 130 -> { + type.anInt1842 = buffer.readUnsignedByte() + type.anInt1907 = buffer.readUnsignedShort() + } + 132 -> { + val size = buffer.readUnsignedByte() + val anIntArray1893 = IntArray(size) + (0 until size).forEach { anIntArray1893[it] = buffer.readUnsignedShort() } + type.anIntArray1893 = anIntArray1893 + } + 134 -> type.anInt1902 = buffer.readUnsignedByte() + 139 -> type.anInt1875 = buffer.readUnsignedShort() + 140 -> type.anInt1885 = buffer.readUnsignedShort() + 249 -> { + val size = buffer.readUnsignedByte() + (0 until size).forEach { _ -> + val string = buffer.readUnsignedByte().toBoolean() + type.params[buffer.readUnsignedMedium()] = if (string) buffer.readString() else buffer.int + } + } + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryProvider.kt new file mode 100644 index 0000000..921eaad --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryProvider.kt @@ -0,0 +1,17 @@ +package com.runetopic.loader.index.obj + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + +/** + * @author Jordan Abraham + */ +class ObjEntryProvider : IEntryProvider { + + private val builder = ObjEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): ObjEntryType = builder.objs.elementAt(id) + override fun size(): Int = builder.objs.size + override fun collect(): Set = builder.objs +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryType.kt new file mode 100644 index 0000000..cf5d137 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryType.kt @@ -0,0 +1,236 @@ +package com.runetopic.loader.index.obj + +import com.runetopic.loader.IEntryType + +/** + * @author Jordan Abraham + */ +data class ObjEntryType( + private val id: Int = 0, + var name: String = "null", + var resizeX: Int = 128, + var resizeY: Int = 128, + var resizeZ: Int = 128, + var xan2d: Int = 0, + var yan2d: Int = 0, + var zan2d: Int = 0, + var anInt1865: Int = 0, + var cost: Int = 1, + var tradeable: Boolean = false, + var stackable: Int = 0, + var inventoryModel: Int = 0, + var members: Boolean = false, + var colorToFind: ShortArray? = null, + var colorToReplace: ShortArray? = null, + var textureToFind: ShortArray? = null, + var textureToReplace: ShortArray? = null, + var aByteArray1858: ByteArray? = null, + var zoom2d: Int = 2000, + var xOffset2d: Int = 0, + var yOffset2d: Int = 0, + var ambient: Int = 0, + var contrast: Int = 0, + var countCo: IntArray? = null, + var countObj: IntArray? = null, + var options: Array = arrayOf(null, null, "Take", null, null), + var interfaceOptions: Array = arrayOf(null, null, null, null, "Drop"), + var maleModel0: Int = -1, + var maleModel1: Int = -1, + var maleModel2: Int = -1, + var maleHeadModel: Int = -1, + var maleHeadModel2: Int = -1, + var femaleModel0: Int = -1, + var femaleModel1: Int = -1, + var femaleModel2: Int = -1, + var femaleHeadModel: Int = -1, + var femaleHeadModel2: Int = -1, + var notedId: Int = -1, + var notedTemplate: Int = -1, + var team: Int = 0, + var lendId: Int = -1, + var lendTemplateId: Int = -1, + var anInt1895: Int = 0, + var anInt1862: Int = 0, + var anInt1873: Int = 0, + var anInt1866: Int = 0, + var anInt1852: Int = 0, + var anInt1867: Int = 0, + var anInt1899: Int = -1, + var anInt1897: Int = -1, + var anInt1850: Int = -1, + var anInt1863: Int = -1, + var anInt1896: Int = -1, + var anInt1889: Int = -1, + var anInt1842: Int = -1, + var anInt1907: Int = -1, + var anIntArray1893: IntArray? = null, + var anInt1902: Int = 0, + var anInt1875: Int = -1, + var anInt1885: Int = -1, + var params: MutableMap = mutableMapOf(), +) : IEntryType { + + override fun getId(): Int = id + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ObjEntryType + + if (id != other.id) return false + if (name != other.name) return false + if (resizeX != other.resizeX) return false + if (resizeY != other.resizeY) return false + if (resizeZ != other.resizeZ) return false + if (xan2d != other.xan2d) return false + if (yan2d != other.yan2d) return false + if (zan2d != other.zan2d) return false + if (anInt1865 != other.anInt1865) return false + if (cost != other.cost) return false + if (tradeable != other.tradeable) return false + if (stackable != other.stackable) return false + if (inventoryModel != other.inventoryModel) return false + if (members != other.members) return false + if (colorToFind != null) { + if (other.colorToFind == null) return false + if (!colorToFind.contentEquals(other.colorToFind)) return false + } else if (other.colorToFind != null) return false + if (colorToReplace != null) { + if (other.colorToReplace == null) return false + if (!colorToReplace.contentEquals(other.colorToReplace)) return false + } else if (other.colorToReplace != null) return false + if (textureToFind != null) { + if (other.textureToFind == null) return false + if (!textureToFind.contentEquals(other.textureToFind)) return false + } else if (other.textureToFind != null) return false + if (textureToReplace != null) { + if (other.textureToReplace == null) return false + if (!textureToReplace.contentEquals(other.textureToReplace)) return false + } else if (other.textureToReplace != null) return false + if (aByteArray1858 != null) { + if (other.aByteArray1858 == null) return false + if (!aByteArray1858.contentEquals(other.aByteArray1858)) return false + } else if (other.aByteArray1858 != null) return false + if (zoom2d != other.zoom2d) return false + if (xOffset2d != other.xOffset2d) return false + if (yOffset2d != other.yOffset2d) return false + if (ambient != other.ambient) return false + if (contrast != other.contrast) return false + if (countCo != null) { + if (other.countCo == null) return false + if (!countCo.contentEquals(other.countCo)) return false + } else if (other.countCo != null) return false + if (countObj != null) { + if (other.countObj == null) return false + if (!countObj.contentEquals(other.countObj)) return false + } else if (other.countObj != null) return false + if (!options.contentEquals(other.options)) return false + if (!interfaceOptions.contentEquals(other.interfaceOptions)) return false + if (maleModel0 != other.maleModel0) return false + if (maleModel1 != other.maleModel1) return false + if (maleModel2 != other.maleModel2) return false + if (maleHeadModel != other.maleHeadModel) return false + if (maleHeadModel2 != other.maleHeadModel2) return false + if (femaleModel0 != other.femaleModel0) return false + if (femaleModel1 != other.femaleModel1) return false + if (femaleModel2 != other.femaleModel2) return false + if (femaleHeadModel != other.femaleHeadModel) return false + if (femaleHeadModel2 != other.femaleHeadModel2) return false + if (notedId != other.notedId) return false + if (notedTemplate != other.notedTemplate) return false + if (team != other.team) return false + if (lendId != other.lendId) return false + if (lendTemplateId != other.lendTemplateId) return false + if (anInt1895 != other.anInt1895) return false + if (anInt1862 != other.anInt1862) return false + if (anInt1873 != other.anInt1873) return false + if (anInt1866 != other.anInt1866) return false + if (anInt1852 != other.anInt1852) return false + if (anInt1867 != other.anInt1867) return false + if (anInt1899 != other.anInt1899) return false + if (anInt1897 != other.anInt1897) return false + if (anInt1850 != other.anInt1850) return false + if (anInt1863 != other.anInt1863) return false + if (anInt1896 != other.anInt1896) return false + if (anInt1889 != other.anInt1889) return false + if (anInt1842 != other.anInt1842) return false + if (anInt1907 != other.anInt1907) return false + if (anIntArray1893 != null) { + if (other.anIntArray1893 == null) return false + if (!anIntArray1893.contentEquals(other.anIntArray1893)) return false + } else if (other.anIntArray1893 != null) return false + if (anInt1902 != other.anInt1902) return false + if (anInt1875 != other.anInt1875) return false + if (anInt1885 != other.anInt1885) return false + if (params != other.params) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + name.hashCode() + result = 31 * result + resizeX + result = 31 * result + resizeY + result = 31 * result + resizeZ + result = 31 * result + xan2d + result = 31 * result + yan2d + result = 31 * result + zan2d + result = 31 * result + anInt1865 + result = 31 * result + cost + result = 31 * result + tradeable.hashCode() + result = 31 * result + stackable + result = 31 * result + inventoryModel + result = 31 * result + members.hashCode() + result = 31 * result + (colorToFind?.contentHashCode() ?: 0) + result = 31 * result + (colorToReplace?.contentHashCode() ?: 0) + result = 31 * result + (textureToFind?.contentHashCode() ?: 0) + result = 31 * result + (textureToReplace?.contentHashCode() ?: 0) + result = 31 * result + (aByteArray1858?.contentHashCode() ?: 0) + result = 31 * result + zoom2d + result = 31 * result + xOffset2d + result = 31 * result + yOffset2d + result = 31 * result + ambient + result = 31 * result + contrast + result = 31 * result + (countCo?.contentHashCode() ?: 0) + result = 31 * result + (countObj?.contentHashCode() ?: 0) + result = 31 * result + options.contentHashCode() + result = 31 * result + interfaceOptions.contentHashCode() + result = 31 * result + maleModel0 + result = 31 * result + maleModel1 + result = 31 * result + maleModel2 + result = 31 * result + maleHeadModel + result = 31 * result + maleHeadModel2 + result = 31 * result + femaleModel0 + result = 31 * result + femaleModel1 + result = 31 * result + femaleModel2 + result = 31 * result + femaleHeadModel + result = 31 * result + femaleHeadModel2 + result = 31 * result + notedId + result = 31 * result + notedTemplate + result = 31 * result + team + result = 31 * result + lendId + result = 31 * result + lendTemplateId + result = 31 * result + anInt1895 + result = 31 * result + anInt1862 + result = 31 * result + anInt1873 + result = 31 * result + anInt1866 + result = 31 * result + anInt1852 + result = 31 * result + anInt1867 + result = 31 * result + anInt1899 + result = 31 * result + anInt1897 + result = 31 * result + anInt1850 + result = 31 * result + anInt1863 + result = 31 * result + anInt1896 + result = 31 * result + anInt1889 + result = 31 * result + anInt1842 + result = 31 * result + anInt1907 + result = 31 * result + (anIntArray1893?.contentHashCode() ?: 0) + result = 31 * result + anInt1902 + result = 31 * result + anInt1875 + result = 31 * result + anInt1885 + result = 31 * result + params.hashCode() + return result + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt new file mode 100644 index 0000000..f849a2d --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt @@ -0,0 +1,107 @@ +package com.runetopic.loader.index.particle + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.* +import java.nio.ByteBuffer + +/** + * @author Jordan Abraham + */ +internal class ParticleEntryBuilder : IEntryBuilder { + + lateinit var particles: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + particles = buildSet { + store.index(27).group(0).files().forEach { + add(read(it.data.toByteBuffer(), ParticleEntryType(it.id))) + } + } + } + + override fun read(buffer: ByteBuffer, type: ParticleEntryType): ParticleEntryType { + do when (val opcode: Int = buffer.readUnsignedByte()) { + 0 -> break + 1 -> { + type.minAngleH = (buffer.readUnsignedShort() shl 3).toShort() + type.maxAngleH = (buffer.readUnsignedShort() shl 3).toShort() + type.minAngleV = (buffer.readUnsignedShort() shl 3).toShort() + type.maxAngleV = (buffer.readUnsignedShort() shl 3).toShort() + } + 2 -> buffer.skip(1) + 3 -> { + type.minSpeed = buffer.int + type.maxSpeed = buffer.int + } + 4 -> { + type.decelerationType = buffer.readUnsignedByte() + type.decelerationRate = buffer.get().toInt() + } + 5 -> type.size1 = buffer.readUnsignedShort() shl 12 shl 2.also { type.size2 = it } + 6 -> { + type.minStartColor = buffer.int + type.maxStartColor = buffer.int + } + 7 -> { + type.minLifetime = buffer.readUnsignedShort() + type.maxLifetime = buffer.readUnsignedShort() + } + 8 -> { + type.minParticleRate = buffer.readUnsignedShort() + type.maxParticleRate = buffer.readUnsignedShort() + } + 9 -> { + val size = buffer.readUnsignedByte() + val localMagnets = IntArray(size) + (0 until size).forEach { localMagnets[it] = buffer.readUnsignedShort() } + type.localMagnets = localMagnets + } + 10 -> { + val size = buffer.readUnsignedByte() + val globalMagnets = IntArray(size) + (0 until size).forEach { globalMagnets[it] = buffer.readUnsignedShort() } + type.globalMagnets = globalMagnets + } + 12 -> type.minLevel = buffer.get().toInt() + 13 -> type.maxLevel = buffer.get().toInt() + 14 -> type.startupUpdates = buffer.readUnsignedShort() + 15 -> type.texture = buffer.readUnsignedShort() + 16 -> { + type.activeFirst = buffer.readUnsignedByte().toBoolean() + type.ageMark = buffer.readUnsignedShort() + type.lifetime = buffer.readUnsignedShort() + type.periodic = buffer.readUnsignedByte().toBoolean() + } + 17 -> type.untextured = buffer.readUnsignedShort() + 18 -> type.fadeColor = buffer.int + 19 -> type.minSetting = buffer.readUnsignedByte() + 20 -> type.colorFadePct = buffer.readUnsignedByte() + 21 -> type.alphaFacePct = buffer.readUnsignedByte() + 22 -> type.endSpeed = buffer.int + 23 -> type.speedChangePct = buffer.readUnsignedByte() + 24 -> type.uniformColorVariance = false + 25 -> { + val size = buffer.readUnsignedByte() + val generalMagnets = IntArray(size) + (0 until size).forEach { generalMagnets[it] = buffer.readUnsignedShort() } + type.generalMagnets = generalMagnets + } + 26 -> type.aBoolean3070 = false + 27 -> type.anInt3065 = buffer.readUnsignedShort() shl 12 shl 2 + 28 -> type.anInt3061 = buffer.readUnsignedByte() + 29 -> buffer.skip(2) + 30 -> type.aBoolean3079 = true + 31 -> { + type.size1 = buffer.readUnsignedShort() shl 12 shl 2 + type.size2 = buffer.readUnsignedShort() shl 12 shl 2 + } + 32 -> type.aBoolean3048 = false + 33 -> type.aBoolean3023 = true + 34 -> type.aBoolean3069 = false + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryProvider.kt new file mode 100644 index 0000000..5d74c6a --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryProvider.kt @@ -0,0 +1,17 @@ +package com.runetopic.loader.index.particle + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + +/** + * @author Jordan Abraham + */ +class ParticleEntryProvider : IEntryProvider { + + private val builder = ParticleEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): ParticleEntryType = builder.particles.elementAt(id) + override fun size(): Int = builder.particles.size + override fun collect(): Set = builder.particles +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryType.kt new file mode 100644 index 0000000..6493f65 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryType.kt @@ -0,0 +1,163 @@ +package com.runetopic.loader.index.particle + +import com.runetopic.loader.IEntryType + +/** + * @author Jordan Abraham + */ +data class ParticleEntryType( + private val id: Int = 0, + var texture: Int = -1, + var periodic: Boolean = true, + var aBoolean3023: Boolean = false, + var fadeColor: Int = 0, + var localMagnets: IntArray? = null, + var activeFirst: Boolean = true, + var maxAngleH: Short = 0, + var minLevel: Int = -2, + var size1: Int = 0, + var untextured: Int = -1, + var maxSpeed: Int = 0, + var maxLevel: Int = -2, + var minSetting: Int = 0, + var ageMark: Int = -1, + var minLifetime: Int = 0, + var maxLifetime: Int = 0, + var globalMagnets: IntArray? = null, + var minSpeed: Int = 0, + var decelerationRate: Int = 0, + var aBoolean3048: Boolean = true, + var startupUpdates: Int = 0, + var uniformColorVariance: Boolean = true, + var size2: Int = 0, + var decelerationType: Int = 0, + var lifetime: Int = -1, + var maxAngleV: Short = 0, + var anInt3065: Int = -1, + var maxParticleRate: Int = 0, + var aBoolean3069: Boolean = true, + var aBoolean3070: Boolean = true, + var minAngleV: Short = 0, + var minAngleH: Short = 0, + var endSpeed: Int = -1, + var generalMagnets: IntArray? = null, + var aBoolean3079: Boolean = false, + var minParticleRate: Int = 0, + var minStartColor: Int = 0, + var speedChangePct: Int = 100, + var alphaFacePct: Int = 100, + var anInt3061: Int = 100, + var maxStartColor: Int = 0, + var colorFadePct: Int = 100 +) : IEntryType { + override fun getId(): Int = id + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ParticleEntryType + + if (id != other.id) return false + if (texture != other.texture) return false + if (periodic != other.periodic) return false + if (aBoolean3023 != other.aBoolean3023) return false + if (fadeColor != other.fadeColor) return false + if (localMagnets != null) { + if (other.localMagnets == null) return false + if (!localMagnets.contentEquals(other.localMagnets)) return false + } else if (other.localMagnets != null) return false + if (activeFirst != other.activeFirst) return false + if (maxAngleH != other.maxAngleH) return false + if (minLevel != other.minLevel) return false + if (size1 != other.size1) return false + if (untextured != other.untextured) return false + if (maxSpeed != other.maxSpeed) return false + if (maxLevel != other.maxLevel) return false + if (minSetting != other.minSetting) return false + if (ageMark != other.ageMark) return false + if (minLifetime != other.minLifetime) return false + if (maxLifetime != other.maxLifetime) return false + if (globalMagnets != null) { + if (other.globalMagnets == null) return false + if (!globalMagnets.contentEquals(other.globalMagnets)) return false + } else if (other.globalMagnets != null) return false + if (minSpeed != other.minSpeed) return false + if (decelerationRate != other.decelerationRate) return false + if (aBoolean3048 != other.aBoolean3048) return false + if (startupUpdates != other.startupUpdates) return false + if (uniformColorVariance != other.uniformColorVariance) return false + if (size2 != other.size2) return false + if (decelerationType != other.decelerationType) return false + if (lifetime != other.lifetime) return false + if (maxAngleV != other.maxAngleV) return false + if (anInt3065 != other.anInt3065) return false + if (maxParticleRate != other.maxParticleRate) return false + if (aBoolean3069 != other.aBoolean3069) return false + if (aBoolean3070 != other.aBoolean3070) return false + if (minAngleV != other.minAngleV) return false + if (minAngleH != other.minAngleH) return false + if (endSpeed != other.endSpeed) return false + if (generalMagnets != null) { + if (other.generalMagnets == null) return false + if (!generalMagnets.contentEquals(other.generalMagnets)) return false + } else if (other.generalMagnets != null) return false + if (aBoolean3079 != other.aBoolean3079) return false + if (minParticleRate != other.minParticleRate) return false + if (minStartColor != other.minStartColor) return false + if (speedChangePct != other.speedChangePct) return false + if (alphaFacePct != other.alphaFacePct) return false + if (anInt3061 != other.anInt3061) return false + if (maxStartColor != other.maxStartColor) return false + if (colorFadePct != other.colorFadePct) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + texture + result = 31 * result + periodic.hashCode() + result = 31 * result + aBoolean3023.hashCode() + result = 31 * result + fadeColor + result = 31 * result + (localMagnets?.contentHashCode() ?: 0) + result = 31 * result + activeFirst.hashCode() + result = 31 * result + maxAngleH + result = 31 * result + minLevel + result = 31 * result + size1 + result = 31 * result + untextured + result = 31 * result + maxSpeed + result = 31 * result + maxLevel + result = 31 * result + minSetting + result = 31 * result + ageMark + result = 31 * result + minLifetime + result = 31 * result + maxLifetime + result = 31 * result + (globalMagnets?.contentHashCode() ?: 0) + result = 31 * result + minSpeed + result = 31 * result + decelerationRate + result = 31 * result + aBoolean3048.hashCode() + result = 31 * result + startupUpdates + result = 31 * result + uniformColorVariance.hashCode() + result = 31 * result + size2 + result = 31 * result + decelerationType + result = 31 * result + lifetime + result = 31 * result + maxAngleV + result = 31 * result + anInt3065 + result = 31 * result + maxParticleRate + result = 31 * result + aBoolean3069.hashCode() + result = 31 * result + aBoolean3070.hashCode() + result = 31 * result + minAngleV + result = 31 * result + minAngleH + result = 31 * result + endSpeed + result = 31 * result + (generalMagnets?.contentHashCode() ?: 0) + result = 31 * result + aBoolean3079.hashCode() + result = 31 * result + minParticleRate + result = 31 * result + minStartColor + result = 31 * result + speedChangePct + result = 31 * result + alphaFacePct + result = 31 * result + anInt3061 + result = 31 * result + maxStartColor + result = 31 * result + colorFadePct + return result + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt new file mode 100644 index 0000000..241d175 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt @@ -0,0 +1,84 @@ +package com.runetopic.loader.index.spotanim + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryBuilder +import com.runetopic.loader.extension.readUnsignedByte +import com.runetopic.loader.extension.readUnsignedShort +import com.runetopic.loader.extension.toByteBuffer +import java.nio.ByteBuffer + +/** + * @author Jordan Abraham + */ +internal class SpotAnimationEntryBuilder: IEntryBuilder { + + lateinit var spotAnimations: Set + + @OptIn(ExperimentalStdlibApi::class) + override fun build(store: Js5Store) { + spotAnimations = buildSet { + store.index(21).use { index -> + (0 until index.expand()).forEach { + add(read(index.group(it ushr 8).file(it and 0xFF).data.toByteBuffer(), SpotAnimationEntryType(it))) + } + } + } + } + + override fun read(buffer: ByteBuffer, type: SpotAnimationEntryType): SpotAnimationEntryType { + do when (val opcode = buffer.readUnsignedByte()) { + 0 -> break + 1 -> type.modelId = buffer.readUnsignedShort() + 2 -> type.animationId = buffer.readUnsignedShort() + 4 -> type.resizeX = buffer.readUnsignedShort() + 5 -> type.resizeY = buffer.readUnsignedShort() + 6 -> type.rotation = buffer.readUnsignedShort() + 7 -> type.ambient = buffer.readUnsignedByte() + 8 -> type.contrast = buffer.readUnsignedByte() + 9 -> { + type.anInt2667 = 8224 + type.aByte2664 = 3 + } + 10 -> type.aBoolean2678 = true + 11 -> type.aByte2664 = 1 + 12 -> type.aByte2664 = 4 + 13 -> type.aByte2664 = 5 + 14 -> { + type.aByte2664 = 2 + type.anInt2667 = buffer.readUnsignedByte() * 256 + } + 15 -> { + type.aByte2664 = 3 + type.anInt2667 = buffer.readUnsignedShort() + } + 16 -> { + type.aByte2664 = 3 + type.anInt2667 = buffer.int + } + 40 -> { + val size = buffer.readUnsignedByte() + val colorToFind = ShortArray(size) + val colorToReplace = ShortArray(size) + (0 until size).forEach { + colorToFind[it] = (buffer.readUnsignedShort()).toShort() + colorToReplace[it] = (buffer.readUnsignedShort()).toShort() + } + type.colorToFind = colorToFind + type.colorToReplace = colorToReplace + } + 41 -> { + val size = buffer.readUnsignedByte() + val textureToFind = ShortArray(size) + val textureToReplace = ShortArray(size) + (0 until size).forEach { + textureToFind[it] = (buffer.readUnsignedShort()).toShort() + textureToReplace[it] = (buffer.readUnsignedShort()).toShort() + } + type.textureToFind = textureToFind + type.textureToReplace = textureToReplace + } + else -> throw Exception("Read unused opcode with id: ${opcode}.") + } while (true) + return type + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryProvider.kt new file mode 100644 index 0000000..1036e18 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryProvider.kt @@ -0,0 +1,18 @@ +package com.runetopic.loader.index.spotanim + +import com.runetopic.cache.store.Js5Store +import com.runetopic.loader.IEntryProvider + + +/** + * @author Jordan Abraham + */ +class SpotAnimationEntryProvider : IEntryProvider { + + private val builder = SpotAnimationEntryBuilder() + + override fun load(store: Js5Store) = builder.build(store) + override fun lookup(id: Int): SpotAnimationEntryType = builder.spotAnimations.elementAt(id) + override fun size(): Int = builder.spotAnimations.size + override fun collect(): Set = builder.spotAnimations +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryType.kt new file mode 100644 index 0000000..c7dcf95 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryType.kt @@ -0,0 +1,83 @@ +package com.runetopic.loader.index.spotanim + +import com.runetopic.loader.IEntryType + +/** + * @author Jordan Abraham + */ +data class SpotAnimationEntryType( + private val id: Int = 0, + var rotation: Int = 0, + var textureToReplace: ShortArray? = null, + var textureToFind: ShortArray? = null, + var resizeY: Int = 128, + var animationId: Int = -1, + var colorToFind: ShortArray? = null, + var colorToReplace: ShortArray? = null, + var resizeX: Int = 128, + var modelId: Int = 0, + var ambient: Int = 0, + var contrast: Int = 0, + var anInt2667: Int = -1, + var aByte2664: Int = 0, + var aBoolean2678: Boolean = false +) : IEntryType { + + override fun getId(): Int = id + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as SpotAnimationEntryType + + if (id != other.id) return false + if (rotation != other.rotation) return false + if (textureToReplace != null) { + if (other.textureToReplace == null) return false + if (!textureToReplace.contentEquals(other.textureToReplace)) return false + } else if (other.textureToReplace != null) return false + if (textureToFind != null) { + if (other.textureToFind == null) return false + if (!textureToFind.contentEquals(other.textureToFind)) return false + } else if (other.textureToFind != null) return false + if (resizeY != other.resizeY) return false + if (animationId != other.animationId) return false + if (colorToFind != null) { + if (other.colorToFind == null) return false + if (!colorToFind.contentEquals(other.colorToFind)) return false + } else if (other.colorToFind != null) return false + if (colorToReplace != null) { + if (other.colorToReplace == null) return false + if (!colorToReplace.contentEquals(other.colorToReplace)) return false + } else if (other.colorToReplace != null) return false + if (resizeX != other.resizeX) return false + if (modelId != other.modelId) return false + if (ambient != other.ambient) return false + if (contrast != other.contrast) return false + if (anInt2667 != other.anInt2667) return false + if (aByte2664 != other.aByte2664) return false + if (aBoolean2678 != other.aBoolean2678) return false + + return true + } + + override fun hashCode(): Int { + var result = id + result = 31 * result + rotation + result = 31 * result + (textureToReplace?.contentHashCode() ?: 0) + result = 31 * result + (textureToFind?.contentHashCode() ?: 0) + result = 31 * result + resizeY + result = 31 * result + animationId + result = 31 * result + (colorToFind?.contentHashCode() ?: 0) + result = 31 * result + (colorToReplace?.contentHashCode() ?: 0) + result = 31 * result + resizeX + result = 31 * result + modelId + result = 31 * result + ambient + result = 31 * result + contrast + result = 31 * result + anInt2667 + result = 31 * result + aByte2664 + result = 31 * result + aBoolean2678.hashCode() + return result + } +} \ No newline at end of file diff --git a/loader/src/main/kotlin/com/runetopic/loader/util/vector/Vector3f.kt b/loader/src/main/kotlin/com/runetopic/loader/util/vector/Vector3f.kt new file mode 100644 index 0000000..b52d5c1 --- /dev/null +++ b/loader/src/main/kotlin/com/runetopic/loader/util/vector/Vector3f.kt @@ -0,0 +1,7 @@ +package com.runetopic.loader.util.vector + +data class Vector3f( + val x: Float, + val y: Float, + val z: Float +) \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..6b04d8d --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,21 @@ +rootProject.name = "cache-lib" + +pluginManagement { + plugins { + kotlin("jvm") version "1.5.31" + } +} + +dependencyResolutionManagement { + repositories { + mavenCentral() + maven { + url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") + } + } +} + + +include("app") +include("cache") +include("loader") From 6623f6e549ab2227fa488129d089444aa138e2d1 Mon Sep 17 00:00:00 2001 From: Jordan Date: Sat, 1 Jan 2022 01:00:56 -0500 Subject: [PATCH 07/14] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 83b5de7..346088b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # RuneTopic Cache Library [![Discord](https://img.shields.io/discord/212385463418355713?color=%237289DA&logo=Discord&logoColor=%237289DA)](https://discord.gg/3scgBkrfMG) -[![License](https://img.shields.io/github/license/xlite2/xlite)](#) A cache library written in Kotlin. From ae5ae4c8e491e3d3329af477c1f71940a1fbd590 Mon Sep 17 00:00:00 2001 From: Tyler Telis Date: Sun, 2 Jan 2022 14:48:46 -0500 Subject: [PATCH 08/14] Fixing java version --- build.gradle.kts | 8 ++++---- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3f53df5..0474a54 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,16 +8,16 @@ configure(allprojects) { group = "com.runetopic.cache" plugins.withType { - java.sourceCompatibility = JavaVersion.VERSION_16 - java.targetCompatibility = JavaVersion.VERSION_16 + java.sourceCompatibility = JavaVersion.VERSION_17 + java.targetCompatibility = JavaVersion.VERSION_17 tasks { compileKotlin { - kotlinOptions.jvmTarget = "16" + kotlinOptions.jvmTarget = "1.8" kotlinOptions.freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn") } compileTestKotlin { - kotlinOptions.jvmTarget = "16" + kotlinOptions.jvmTarget = "1.8" kotlinOptions.freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn") } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f371643..e750102 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From ccfd73a2d3765bf077463d2d0a7f9bdf3a6fe5d3 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 3 Jan 2022 20:57:30 -0500 Subject: [PATCH 09/14] Format. --- cache/build.gradle.kts | 1 - .../com/runetopic/cache/codec/CodecType.kt | 10 ++-- .../runetopic/cache/codec/ContainerCodec.kt | 9 ++-- .../runetopic/cache/codec/Decompression.kt | 2 +- .../com/runetopic/cache/codec/IFileCodec.kt | 2 +- .../runetopic/cache/codec/impl/BZip2Codec.kt | 6 +-- .../runetopic/cache/codec/impl/GZipCodec.kt | 4 +- .../runetopic/cache/codec/impl/NoFileCodec.kt | 4 +- .../cache/exception/CompressionException.kt | 2 +- .../cache/exception/DatFileException.kt | 2 +- .../cache/exception/EndOfDatFileException.kt | 2 +- .../cache/exception/FileDataException.kt | 2 +- .../cache/exception/FileNotFoundException.kt | 2 +- .../cache/exception/IdxFileException.kt | 2 +- .../cache/exception/ProtocolException.kt | 2 +- .../runetopic/cache/extension/ByteArray.kt | 2 +- .../runetopic/cache/extension/ByteBuffer.kt | 2 +- .../com/runetopic/cache/extension/String.kt | 2 +- .../cache/hierarchy/ReferenceTable.kt | 2 +- .../runetopic/cache/hierarchy/index/Index.kt | 4 +- .../cache/hierarchy/index/group/Group.kt | 2 +- .../cache/hierarchy/index/group/file/File.kt | 4 +- .../com/runetopic/cache/store/Constants.kt | 2 +- .../com/runetopic/cache/store/Js5Store.kt | 2 +- .../runetopic/cache/store/storage/IStorage.kt | 4 +- .../cache/store/storage/js5/Js5DiskStorage.kt | 14 ++--- .../cache/store/storage/js5/io/dat/DatFile.kt | 12 ++--- .../store/storage/js5/io/dat/IDatFile.kt | 4 +- .../store/storage/js5/io/dat/IDatSector.kt | 2 +- .../js5/io/dat/sector/DatGroupSector.kt | 4 +- .../js5/io/dat/sector/DatIndexSector.kt | 52 ++++++++++--------- .../store/storage/js5/io/idx/IIdxFile.kt | 4 +- .../cache/store/storage/js5/io/idx/IdxFile.kt | 2 +- .../store/storage/js5/io/dat/DatSectorTest.kt | 4 +- loader/build.gradle.kts | 1 - .../com/runetopic/loader/IEntryBuilder.kt | 2 +- .../com/runetopic/loader/IEntryProvider.kt | 4 +- .../kotlin/com/runetopic/loader/IEntryType.kt | 2 +- .../runetopic/loader/extension/ByteArray.kt | 2 +- .../runetopic/loader/extension/ByteBuffer.kt | 2 +- .../com/runetopic/loader/extension/Int.kt | 2 +- .../config/idk/IdentityKitEntryBuilder.kt | 8 +-- .../config/idk/IdentityKitEntryProvider.kt | 3 +- .../index/config/idk/IdentityKitEntryType.kt | 4 +- .../index/config/inv/InventoryEntryBuilder.kt | 6 +-- .../config/inv/InventoryEntryProvider.kt | 3 +- .../index/config/inv/InventoryEntryType.kt | 5 +- .../config/lighting/LightingEntryBuilder.kt | 6 +-- .../config/lighting/LightingEntryProvider.kt | 3 +- .../config/lighting/LightingEntryType.kt | 6 +-- .../config/mouseicon/MouseIconEntryBuilder.kt | 6 +-- .../mouseicon/MouseIconEntryProvider.kt | 3 +- .../config/mouseicon/MouseIconEntryType.kt | 5 +- .../config/overlay/OverlayEntryBuilder.kt | 8 +-- .../config/overlay/OverlayEntryProvider.kt | 4 +- .../index/config/overlay/OverlayEntryType.kt | 4 +- .../index/config/param/ParamEntryBuilder.kt | 6 +-- .../index/config/param/ParamEntryProvider.kt | 3 +- .../index/config/param/ParamEntryType.kt | 4 +- .../index/config/skybox/SkyBoxEntryBuilder.kt | 6 +-- .../config/skybox/SkyBoxEntryProvider.kt | 3 +- .../index/config/skybox/SkyBoxEntryType.kt | 4 +- .../index/config/struct/StructEntryBuilder.kt | 6 +-- .../config/struct/StructEntryProvider.kt | 3 +- .../index/config/struct/StructEntryType.kt | 4 +- .../config/underlay/UnderlayEntryBuilder.kt | 8 +-- .../config/underlay/UnderlayEntryProvider.kt | 2 +- .../config/underlay/UnderlayEntryType.kt | 4 +- .../loader/index/loc/LocEntryBuilder.kt | 6 +-- .../loader/index/loc/LocEntryProvider.kt | 3 +- .../loader/index/loc/LocEntryType.kt | 6 +-- .../loader/index/map/MapEntryBuilder.kt | 6 +-- .../loader/index/map/MapEntryProvider.kt | 3 +- .../loader/index/map/MapEntryType.kt | 2 +- .../index/map/MapLocationEntryBuilder.kt | 4 +- .../index/map/MapLocationEntryProvider.kt | 3 +- .../loader/index/map/MapLocationEntryType.kt | 4 +- .../loader/index/npc/NpcEntryBuilder.kt | 6 +-- .../loader/index/npc/NpcEntryProvider.kt | 4 +- .../loader/index/npc/NpcEntryType.kt | 4 +- .../loader/index/obj/ObjEntryBuilder.kt | 4 +- .../loader/index/obj/ObjEntryProvider.kt | 2 +- .../loader/index/obj/ObjEntryType.kt | 2 +- .../index/particle/ParticleEntryBuilder.kt | 4 +- .../index/particle/ParticleEntryProvider.kt | 2 +- .../index/particle/ParticleEntryType.kt | 2 +- .../spotanim/SpotAnimationEntryBuilder.kt | 6 +-- .../spotanim/SpotAnimationEntryProvider.kt | 3 +- .../index/spotanim/SpotAnimationEntryType.kt | 2 +- .../runetopic/loader/util/vector/Vector3f.kt | 2 +- settings.gradle.kts | 1 - 91 files changed, 191 insertions(+), 208 deletions(-) diff --git a/cache/build.gradle.kts b/cache/build.gradle.kts index 73e3217..0d07adc 100644 --- a/cache/build.gradle.kts +++ b/cache/build.gradle.kts @@ -67,7 +67,6 @@ publishing { } } - signing { sign(publishing.publications["mavenJava"]) } diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt index f7642a3..c59bec7 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt @@ -13,8 +13,8 @@ import com.runetopic.cache.codec.impl.NoFileCodec internal sealed class CodecType( val codec: IFileCodec ) { - object BadCodec: CodecType(NoFileCodec()) - object NoCodec: CodecType(NoFileCodec()) - object BZipCodec: CodecType(BZip2Codec()) - object GZipCodec: CodecType(GZipCodec()) -} \ No newline at end of file + object BadCodec : CodecType(NoFileCodec()) + object NoCodec : CodecType(NoFileCodec()) + object BZipCodec : CodecType(BZip2Codec()) + object GZipCodec : CodecType(GZipCodec()) +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt index afd6fb1..32727cb 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt @@ -1,6 +1,5 @@ package com.runetopic.cache.codec -import com.runetopic.cache.codec.CodecType.* import com.runetopic.cache.exception.CompressionException import com.runetopic.cache.extension.readUnsignedByte import com.runetopic.cache.extension.readUnsignedShort @@ -35,10 +34,10 @@ internal object ContainerCodec { } buffer.put(compressed) if (keys.isNotEmpty()) { - //TODO xtea + // TODO xtea } if (revision != -1) { - //buffer.putShort(revision.toShort()) + // buffer.putShort(revision.toShort()) } return buffer } @@ -70,7 +69,7 @@ internal object ContainerCodec { Container(decrypted, compression, revision, crc32.value.toInt()) } - GZipCodec, BZipCodec-> { + GZipCodec, BZipCodec -> { val encrypted = ByteArray(length + 4) buffer.get(encrypted) crc32.update(encrypted, 0, encrypted.size) @@ -112,4 +111,4 @@ internal object ContainerCodec { else -> BadCodec } } -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/Decompression.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/Decompression.kt index 670f9bc..11930af 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/Decompression.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/Decompression.kt @@ -5,4 +5,4 @@ package com.runetopic.cache.codec * @author Jordan Abraham */ fun ByteArray.decompress(keys: IntArray): ByteArray = ContainerCodec.decompress(this, keys).data -fun ByteArray.decompress(): ByteArray = ContainerCodec.decompress(this, intArrayOf()).data \ No newline at end of file +fun ByteArray.decompress(): ByteArray = ContainerCodec.decompress(this, intArrayOf()).data diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/IFileCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/IFileCodec.kt index 39526a8..75f1820 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/IFileCodec.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/IFileCodec.kt @@ -7,4 +7,4 @@ package com.runetopic.cache.codec internal interface IFileCodec { fun compress(data: ByteArray, keys: IntArray): ByteArray fun decompress(data: ByteArray, length: Int, keys: IntArray): ByteArray -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt index d133976..6bbae97 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt @@ -14,7 +14,7 @@ import java.util.* * @author Tyler Telis * @email */ -internal class BZip2Codec: IFileCodec { +internal class BZip2Codec : IFileCodec { override fun compress(data: ByteArray, keys: IntArray): ByteArray { val stream: InputStream = ByteArrayInputStream(data) val bout = ByteArrayOutputStream() @@ -33,11 +33,11 @@ internal class BZip2Codec: IFileCodec { override fun decompress(data: ByteArray, length: Int, keys: IntArray): ByteArray { val buffer = ByteArray(length + BZIP_HEADER.size) - System.arraycopy(BZIP_HEADER, 0, buffer,0, BZIP_HEADER.size) + System.arraycopy(BZIP_HEADER, 0, buffer, 0, BZIP_HEADER.size) System.arraycopy(data, 0, buffer, BZIP_HEADER.size, length) val stream = ByteArrayOutputStream() BZip2CompressorInputStream(ByteArrayInputStream(buffer)).use { IOUtils.copy(it, stream) } return stream.toByteArray() } -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt index 567e7d7..8e7d9af 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt @@ -11,7 +11,7 @@ import java.util.zip.GZIPOutputStream * @author Tyler Telis * @email */ -internal class GZipCodec: IFileCodec { +internal class GZipCodec : IFileCodec { override fun compress(data: ByteArray, keys: IntArray): ByteArray { val inputStream = ByteArrayInputStream(data) val outputStream = ByteArrayOutputStream() @@ -26,4 +26,4 @@ internal class GZipCodec: IFileCodec { } return outputStream.toByteArray() } -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt index 0d29ae1..a9017ca 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt @@ -6,7 +6,7 @@ import com.runetopic.cache.codec.IFileCodec * @author Tyler Telis * @email */ -internal class NoFileCodec: IFileCodec { +internal class NoFileCodec : IFileCodec { override fun compress(data: ByteArray, keys: IntArray): ByteArray = throw NotImplementedError("No codec provided.") override fun decompress(data: ByteArray, length: Int, keys: IntArray): ByteArray = throw NotImplementedError("No codec provided.") -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/CompressionException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/CompressionException.kt index 82cbed5..039220a 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/exception/CompressionException.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/CompressionException.kt @@ -4,4 +4,4 @@ package com.runetopic.cache.exception * @author Tyler Telis * @email */ -internal class CompressionException(override val message: String): RuntimeException(message) \ No newline at end of file +internal class CompressionException(override val message: String) : RuntimeException(message) diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/DatFileException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/DatFileException.kt index b5e84e5..42b5153 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/exception/DatFileException.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/DatFileException.kt @@ -4,4 +4,4 @@ package com.runetopic.cache.exception * @author Tyler Telis * @email */ -internal class DatFileException(override val message: String): RuntimeException(message) \ No newline at end of file +internal class DatFileException(override val message: String) : RuntimeException(message) diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/EndOfDatFileException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/EndOfDatFileException.kt index 709969c..4a77602 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/exception/EndOfDatFileException.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/EndOfDatFileException.kt @@ -4,4 +4,4 @@ package com.runetopic.cache.exception * @author Tyler Telis * @email */ -internal class EndOfDatFileException(override val message: String): RuntimeException(message) \ No newline at end of file +internal class EndOfDatFileException(override val message: String) : RuntimeException(message) diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/FileDataException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/FileDataException.kt index 2d646ce..62b487f 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/exception/FileDataException.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/FileDataException.kt @@ -4,4 +4,4 @@ package com.runetopic.cache.exception * @author Tyler Telis * @email */ -internal class FileDataException(override val message: String): RuntimeException(message) \ No newline at end of file +internal class FileDataException(override val message: String) : RuntimeException(message) diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/FileNotFoundException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/FileNotFoundException.kt index fa7aebd..d4786f6 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/exception/FileNotFoundException.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/FileNotFoundException.kt @@ -4,4 +4,4 @@ package com.runetopic.cache.exception * @author Tyler Telis * @email */ -internal class FileNotFoundException(override val message: String): RuntimeException(message) \ No newline at end of file +internal class FileNotFoundException(override val message: String) : RuntimeException(message) diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/IdxFileException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/IdxFileException.kt index 70e2bd7..2faba92 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/exception/IdxFileException.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/IdxFileException.kt @@ -4,4 +4,4 @@ package com.runetopic.cache.exception * @author Tyler Telis * @email */ -internal class IdxFileException(override val message: String): RuntimeException(message) \ No newline at end of file +internal class IdxFileException(override val message: String) : RuntimeException(message) diff --git a/cache/src/main/kotlin/com/runetopic/cache/exception/ProtocolException.kt b/cache/src/main/kotlin/com/runetopic/cache/exception/ProtocolException.kt index 97b9d4e..cfdcb3d 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/exception/ProtocolException.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/exception/ProtocolException.kt @@ -4,4 +4,4 @@ package com.runetopic.cache.exception * @author Tyler Telis * @email */ -internal class ProtocolException(override val message: String): RuntimeException(message) \ No newline at end of file +internal class ProtocolException(override val message: String) : RuntimeException(message) diff --git a/cache/src/main/kotlin/com/runetopic/cache/extension/ByteArray.kt b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteArray.kt index 5e1fc85..ee2b93b 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/extension/ByteArray.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteArray.kt @@ -5,4 +5,4 @@ import java.nio.ByteBuffer /** * @author Jordan Abraham */ -fun ByteArray.toByteBuffer(): ByteBuffer = ByteBuffer.wrap(this) \ No newline at end of file +fun ByteArray.toByteBuffer(): ByteBuffer = ByteBuffer.wrap(this) diff --git a/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt index 0fc8445..76e94cb 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/extension/ByteBuffer.kt @@ -18,4 +18,4 @@ internal fun ByteBuffer.remainingBytes(): ByteArray { val bytes = ByteArray(remaining()) get(bytes) return bytes -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/extension/String.kt b/cache/src/main/kotlin/com/runetopic/cache/extension/String.kt index 13f876f..98c196d 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/extension/String.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/extension/String.kt @@ -4,4 +4,4 @@ internal fun String.nameHash(): Int { var hash = 0 this.forEach { element -> hash = element.toInt() + ((hash shl 5) - hash) } return hash -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/ReferenceTable.kt b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/ReferenceTable.kt index 7f9dd6d..4142b42 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/ReferenceTable.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/ReferenceTable.kt @@ -32,4 +32,4 @@ internal data class ReferenceTable( result = 31 * result + length return result } -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt index b87f21c..0589059 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt @@ -19,7 +19,7 @@ data class Index( val isNamed: Boolean, val isUsingWhirlpool: Boolean, private val groups: Map -): Comparable { +) : Comparable { @JvmName("getGroups") fun groups(): Collection = groups.values @@ -70,4 +70,4 @@ data class Index( internal companion object { fun default(indexId: Int): Index = Index(indexId, 0, ByteArray(64), -1, -1, 0, false, false, hashMapOf()) } -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/Group.kt b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/Group.kt index 6163a5b..bd7c77a 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/Group.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/Group.kt @@ -17,7 +17,7 @@ data class Group( val keys: IntArray, private val files: MutableMap, val data: ByteArray -): Comparable { +) : Comparable { @JvmName("getFiles") fun files(): Collection = files.values diff --git a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/file/File.kt b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/file/File.kt index 373427c..75cf3df 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/file/File.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/group/file/File.kt @@ -10,7 +10,7 @@ data class File( val id: Int, val nameHash: Int, val data: ByteArray -): Comparable { +) : Comparable { override fun compareTo(other: File): Int = this.id.compareTo(other.id) override fun equals(other: Any?): Boolean { @@ -36,4 +36,4 @@ data class File( internal companion object { val DEFAULT = File(-1, 0, byteArrayOf(0)) } -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/Constants.kt b/cache/src/main/kotlin/com/runetopic/cache/store/Constants.kt index ce769b7..43b275b 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/Constants.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/Constants.kt @@ -20,4 +20,4 @@ internal object Constants { 'h'.code.toByte(), '1'.code.toByte() ) -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt b/cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt index 619b667..66b4fe8 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt @@ -109,4 +109,4 @@ class Js5Store( } override fun close() = storage.close() -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/IStorage.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/IStorage.kt index 5f6a45c..0f5d7de 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/IStorage.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/IStorage.kt @@ -9,11 +9,11 @@ import java.io.Flushable * @author Tyler Telis * @email */ -internal interface IStorage: Closeable, Flushable { +internal interface IStorage : Closeable, Flushable { fun open(store: Js5Store) fun read(indexId: Int, store: Js5Store) fun write(indexId: Int, store: Js5Store) fun loadReferenceTable(index: Index, groupId: Int): ByteArray fun loadMasterReferenceTable(groupId: Int): ByteArray fun loadReferenceTable(index: Index, groupName: String): ByteArray -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt index 43051f6..48078b0 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt @@ -35,16 +35,16 @@ internal class Js5DiskStorage( private val logger = InlineLogger() init { - val masterIndexFile = Path.of("${path}/${Constants.MAIN_FILE_255}") + val masterIndexFile = Path.of("$path/${Constants.MAIN_FILE_255}") if (masterIndexFile.exists().not()) { - throw FileNotFoundException("Missing ${Constants.MAIN_FILE_255} in directory ${path}/${Constants.MAIN_FILE_255}") + throw FileNotFoundException("Missing ${Constants.MAIN_FILE_255} in directory $path/${Constants.MAIN_FILE_255}") } - val datFile = Path.of("${path}/${Constants.MAIN_FILE_DAT}") + val datFile = Path.of("$path/${Constants.MAIN_FILE_DAT}") if (datFile.exists().not()) { - throw FileNotFoundException("Missing ${Constants.MAIN_FILE_DAT} in directory ${path}/${Constants.MAIN_FILE_DAT}") + throw FileNotFoundException("Missing ${Constants.MAIN_FILE_DAT} in directory $path/${Constants.MAIN_FILE_DAT}") } this.masterIdxFile = IdxFile(Constants.MASTER_INDEX_ID, masterIndexFile) @@ -88,7 +88,7 @@ internal class Js5DiskStorage( val idk = masterIdxFile.encode(byteArrayOf()) val data = datFile.encode(byteArrayOf()) val index = store.index(indexId) - //val idk2 = DatIndexSector(datFile, getIdxFile(indexId), data).encode(index) + // val idk2 = DatIndexSector(datFile, getIdxFile(indexId), data).encode(index) } override fun loadMasterReferenceTable(groupId: Int): ByteArray { @@ -107,7 +107,7 @@ internal class Js5DiskStorage( private fun getIdxFile(id: Int): IdxFile { idxFiles.find { it.id() == id }?.let { return it } - return IdxFile(id, Path.of("$path/${Constants.MAIN_FILE_IDX}${id}")) + return IdxFile(id, Path.of("$path/${Constants.MAIN_FILE_IDX}$id")) } override fun close() { @@ -119,4 +119,4 @@ internal class Js5DiskStorage( override fun flush() { TODO("Not yet implemented") } -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt index 6214567..040161a 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt @@ -1,7 +1,6 @@ package com.runetopic.cache.store.storage.js5.io.dat import com.runetopic.cache.exception.DatFileException -import com.runetopic.cache.exception.EndOfDatFileException import com.runetopic.cache.extension.readUnsignedByte import com.runetopic.cache.extension.readUnsignedMedium import com.runetopic.cache.extension.readUnsignedShort @@ -20,7 +19,7 @@ import java.nio.file.Path */ internal class DatFile( path: Path -): IDatFile { +) : IDatFile { private val datFile: RandomAccessFile = RandomAccessFile(path.toFile(), "rw") private val datBuffer = ByteArray(datFile.length().toInt()) @@ -41,7 +40,7 @@ internal class DatFile( var part = 0 var bytes = 0 while (length > bytes) { - //validate sector + // validate sector if (sector <= 0 || datBuffer.size / DAT_SIZE < sector) return byteArrayOf() val position = DAT_SIZE * sector @@ -49,7 +48,7 @@ internal class DatFile( val headerSize = if (large) 10 else 8 val blockSize = adjustBlockLength(length - bytes, headerSize) val totalSize = position + headerSize + blockSize - //validate the total size. + // validate the total size. if (totalSize > datBuffer.size) return byteArrayOf() val header = datBuffer.copyOfRange(position, totalSize).toByteBuffer() @@ -59,7 +58,7 @@ internal class DatFile( val currentIndex = header.readUnsignedByte() if (referenceTableId != currentReferenceTableId || currentPart != part || id != currentIndex) { - throw DatFileException("DatFile mismatch Id={${currentIndex}} != {${id}}, ReferenceTableId={${currentReferenceTableId}} != {${referenceTableId}}, CurrentPart={${currentPart}} != {${part}}") + throw DatFileException("DatFile mismatch Id={$currentIndex} != {$id}, ReferenceTableId={$currentReferenceTableId} != {$referenceTableId}, CurrentPart={$currentPart} != {$part}") } if (nextSector < 0 || datBuffer.size / DAT_SIZE < nextSector) { throw DatFileException("Invalid next sector $nextSector") @@ -76,7 +75,6 @@ internal class DatFile( } override fun encode(data: ByteArray) { - } private fun adjustBlockLength( @@ -89,4 +87,4 @@ internal class DatFile( private fun validateSector(sector: Int): Boolean = (sector <= 0 || datBuffer.size / DAT_SIZE < sector) override fun close() = datFile.close() -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt index ce8ba68..02669a0 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt @@ -7,7 +7,7 @@ import java.io.Closeable * @author Tyler Telis * @email */ -internal interface IDatFile: Closeable { +internal interface IDatFile : Closeable { fun decode(id: Int, referenceTable: ReferenceTable): ByteArray fun encode(data: ByteArray) -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt index a98d9f9..e753dbc 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt @@ -6,4 +6,4 @@ package com.runetopic.cache.store.storage.js5.io.dat internal interface IDatSector { fun decode(): T fun encode(override: T): ByteArray -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt index 824bb48..db75d8c 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt @@ -13,7 +13,7 @@ data class DatGroupSector( val data: ByteArray, val count: Int, val groupId: Int -): IDatSector> { +) : IDatSector> { override fun decode(): MutableMap { if (data.isEmpty()) return hashMapOf(Pair(0, File.DEFAULT)) if (count <= 1) return hashMapOf(Pair(0, File(fileIds[groupId][0], fileNameHashes[groupId][0], data))) @@ -82,4 +82,4 @@ data class DatGroupSector( result = 31 * result + groupId return result } -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt index 0b0b5c2..99fea6f 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt @@ -20,7 +20,7 @@ internal data class DatIndexSector( val datFile: IDatFile, val idxFile: IIdxFile, val data: ByteArray -): IDatSector { +) : IDatSector { override fun decode(): Index { val decompressed = ContainerCodec.decompress(data) @@ -66,16 +66,18 @@ internal data class DatIndexSector( groupId ) - groups[groupId] = (Group( - groupId, - groupNameHashes[groupId], - groupCrcs[groupId], - groupWhirlpools[groupId], - groupRevisions[groupId], - intArrayOf(),//TODO - groupSector.decode(), - data - )) + groups[groupId] = ( + Group( + groupId, + groupNameHashes[groupId], + groupCrcs[groupId], + groupWhirlpools[groupId], + groupRevisions[groupId], + intArrayOf(), // TODO + groupSector.decode(), + data + ) + ) } return Index( idxFile.id(), @@ -127,15 +129,15 @@ internal data class DatIndexSector( val groupFileNameHashes = encodeGroupFileNameHashes(count, ids, fileSizes, override.isNamed, fileNameHashes) val buffer = ByteBuffer.allocate( - /**/header.position() - + groupIds.position() - + groupNameHashes.position() - + groupCrcs.position() - + groupWhirlpools.position() - + groupRevisions.position() - + groupFileSizes.position() - + groupFileIds.position() - + groupFileNameHashes.position() + /**/header.position() + + groupIds.position() + + groupNameHashes.position() + + groupCrcs.position() + + groupWhirlpools.position() + + groupRevisions.position() + + groupFileSizes.position() + + groupFileIds.position() + + groupFileNameHashes.position() ) buffer.put(header.array()) @@ -152,7 +154,7 @@ internal data class DatIndexSector( override.compression, override.revision, buffer.array(), - intArrayOf()//TODO + intArrayOf() // TODO ) return compressed.array() } @@ -174,7 +176,7 @@ internal data class DatIndexSector( protocol: Int, groupIds: IntArray ): ByteBuffer { - //TODO This buffer needs to be allocated properly for protocol >= 7 + // TODO This buffer needs to be allocated properly for protocol >= 7 val buffer = ByteBuffer.allocate(calc(count, groupIds, protocol)) (0 until count).forEach { val value = groupIds[it] - if (it == 0) 0 else groupIds[it - 1] @@ -216,7 +218,7 @@ internal data class DatIndexSector( protocol: Int, groupFileSizes: IntArray ): ByteBuffer { - //TODO This buffer needs to be allocated properly for protocol >= 7 + // TODO This buffer needs to be allocated properly for protocol >= 7 val buffer = ByteBuffer.allocate(groupIds.sumOf { Short.SIZE_BYTES }) (0 until count).forEach { if (protocol >= 7) buffer.putIntShortSmart(groupFileSizes[groupIds[it]]) else buffer.putShort(groupFileSizes[groupIds[it]].toShort()) @@ -363,7 +365,7 @@ internal data class DatIndexSector( groupFileSizes: IntArray, fileIds: Array ): ByteBuffer { - //TODO This buffer needs to be allocated properly for protocol >= 7 + // TODO This buffer needs to be allocated properly for protocol >= 7 val buffer = ByteBuffer.allocate(groupFileSizes.sumOf { it * Short.SIZE_BYTES }) (0 until count).forEach { val groupId = groupIds[it] @@ -432,4 +434,4 @@ internal data class DatIndexSector( result = 31 * result + data.contentHashCode() return result } -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt index a097db7..d5daae4 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt @@ -7,9 +7,9 @@ import java.io.Closeable * @author Tyler Telis * @email */ -internal interface IIdxFile: Closeable { +internal interface IIdxFile : Closeable { fun decode(id: Int): ReferenceTable fun encode(data: ByteArray) fun validIndexCount(): Int fun id(): Int -} \ No newline at end of file +} diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt index 5ccbec5..3b447d8 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt @@ -45,4 +45,4 @@ internal class IdxFile( override fun validIndexCount(): Int = path.fileSize().toInt() / IDX_SIZE override fun id(): Int = id override fun close() = idxFile.close() -} \ No newline at end of file +} diff --git a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt index d54910f..44bc180 100644 --- a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt +++ b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt @@ -133,7 +133,7 @@ class DatSectorTest { verify(exactly = 1) { indexSector.decodeGroupIds(count, buffer, protocol) } verify(exactly = 1) { indexSector.encodeGroupIds(count, protocol, decoded) } verify(atLeast = 1) { indexSector.calc(count, groupIds, protocol) } - //confirmVerified(indexSector) + // confirmVerified(indexSector) } @Test @@ -287,4 +287,4 @@ class DatSectorTest { verify(exactly = 1) { indexSector.encodeGroupRevisions(count, groupIds, decoded) } confirmVerified(indexSector) } -} \ No newline at end of file +} diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts index d6d9d86..9496935 100644 --- a/loader/build.gradle.kts +++ b/loader/build.gradle.kts @@ -32,7 +32,6 @@ publishing { } } - artifact(tasks["javadocJar"]) artifact(tasks["sourcesJar"]) } diff --git a/loader/src/main/kotlin/com/runetopic/loader/IEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/IEntryBuilder.kt index 0504f5d..c5dfd30 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/IEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/IEntryBuilder.kt @@ -9,4 +9,4 @@ import java.nio.ByteBuffer internal interface IEntryBuilder { fun build(store: Js5Store) fun read(buffer: ByteBuffer, type: T): T -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/IEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/IEntryProvider.kt index 7347f55..c74a38e 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/IEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/IEntryProvider.kt @@ -7,7 +7,7 @@ import com.runetopic.cache.store.Js5Store */ interface IEntryProvider { fun load(store: Js5Store) - fun lookup(id: Int) : T + fun lookup(id: Int): T fun size(): Int fun collect(): Set -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/IEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/IEntryType.kt index f15e787..334f913 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/IEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/IEntryType.kt @@ -5,4 +5,4 @@ package com.runetopic.loader */ interface IEntryType { fun getId(): Int -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/extension/ByteArray.kt b/loader/src/main/kotlin/com/runetopic/loader/extension/ByteArray.kt index d1f668b..174cdd2 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/extension/ByteArray.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/extension/ByteArray.kt @@ -5,4 +5,4 @@ import java.nio.ByteBuffer /** * @author Jordan Abraham */ -fun ByteArray.toByteBuffer(): ByteBuffer = ByteBuffer.wrap(this) \ No newline at end of file +fun ByteArray.toByteBuffer(): ByteBuffer = ByteBuffer.wrap(this) diff --git a/loader/src/main/kotlin/com/runetopic/loader/extension/ByteBuffer.kt b/loader/src/main/kotlin/com/runetopic/loader/extension/ByteBuffer.kt index 8095593..f964712 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/extension/ByteBuffer.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/extension/ByteBuffer.kt @@ -82,4 +82,4 @@ val cp1252Identifiers = charArrayOf( '\u0000', '\u017e', '\u0178' -) \ No newline at end of file +) diff --git a/loader/src/main/kotlin/com/runetopic/loader/extension/Int.kt b/loader/src/main/kotlin/com/runetopic/loader/extension/Int.kt index 3a025f0..a9b220d 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/extension/Int.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/extension/Int.kt @@ -5,4 +5,4 @@ package com.runetopic.loader.extension */ internal fun Int.toBoolean(): Boolean { return this == 1 -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt index 53a1905..8b90a52 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt @@ -12,7 +12,7 @@ import java.nio.ByteBuffer * @author Tyler Telis * @email */ -internal class IdentityKitEntryBuilder: IEntryBuilder { +internal class IdentityKitEntryBuilder : IEntryBuilder { lateinit var identityKitTypes: Set @@ -39,7 +39,7 @@ internal class IdentityKitEntryBuilder: IEntryBuilder { type.models = models } 3 -> { - // This is no longer used in higher revisions. OSRS uses this + // This is no longer used in higher revisions. OSRS uses this } 40 -> { val size = buffer.readUnsignedByte() @@ -64,8 +64,8 @@ internal class IdentityKitEntryBuilder: IEntryBuilder { type.texturesToReplace = texturesToReplace } in 60..69 -> buffer.readUnsignedShort().let { type.chatHeadModels[opcode - 60] = it } - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryProvider.kt index 9c3106f..76d8a9e 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryProvider.kt @@ -3,7 +3,6 @@ package com.runetopic.loader.index.config.idk import com.runetopic.cache.store.Js5Store import com.runetopic.loader.IEntryProvider - /** * @author Jordan Abraham */ @@ -15,4 +14,4 @@ class IdentityKitEntryProvider : IEntryProvider { override fun lookup(id: Int): IdentityKitEntryType = builder.identityKitTypes.elementAt(id) override fun size(): Int = builder.identityKitTypes.size override fun collect(): Set = builder.identityKitTypes -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryType.kt index 428b0d0..8561131 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryType.kt @@ -10,7 +10,7 @@ data class IdentityKitEntryType( var texturesToFind: ShortArray? = null, var texturesToReplace: ShortArray? = null, var chatHeadModels: IntArray = intArrayOf(-1, -1, -1, -1, -1) -): IEntryType { +) : IEntryType { override fun getId(): Int = id override fun equals(other: Any?): Boolean { if (this === other) return true @@ -54,4 +54,4 @@ data class IdentityKitEntryType( result = 31 * result + chatHeadModels.contentHashCode() return result } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt index fb4a1da..a37a0fd 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt @@ -11,7 +11,7 @@ import java.nio.ByteBuffer * @author Tyler Telis * @email */ -internal class InventoryEntryBuilder: IEntryBuilder { +internal class InventoryEntryBuilder : IEntryBuilder { lateinit var inventoryTypes: Set @OptIn(ExperimentalStdlibApi::class) @@ -27,8 +27,8 @@ internal class InventoryEntryBuilder: IEntryBuilder { do when (val opcode = buffer.readUnsignedByte()) { 0 -> break 2 -> type.size = buffer.readUnsignedShort() - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryProvider.kt index 3c149a7..0a53fb6 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryProvider.kt @@ -3,7 +3,6 @@ package com.runetopic.loader.index.config.inv import com.runetopic.cache.store.Js5Store import com.runetopic.loader.IEntryProvider - /** * @author Jordan Abraham */ @@ -15,4 +14,4 @@ class InventoryEntryProvider : IEntryProvider { override fun lookup(id: Int): InventoryEntryType = builder.inventoryTypes.elementAt(id) override fun size(): Int = builder.inventoryTypes.size override fun collect(): Set = builder.inventoryTypes -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryType.kt index 730dcef..4c9764e 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryType.kt @@ -5,7 +5,6 @@ import com.runetopic.loader.IEntryType data class InventoryEntryType( private val id: Int = 0, var size: Int = 0 -): IEntryType { +) : IEntryType { override fun getId(): Int = id - -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt index b66e032..b01da41 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt @@ -28,11 +28,11 @@ internal class LightingEntryBuilder : IEntryBuilder { do when (val opcode = buffer.readUnsignedByte()) { 0 -> break 1 -> type.anInt961 = buffer.readUnsignedByte() - 2-> type.anInt957 = buffer.readUnsignedShort() + 2 -> type.anInt957 = buffer.readUnsignedShort() 3 -> type.anInt956 = buffer.readUnsignedShort() 4 -> type.anInt962 = buffer.short.toInt() - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryProvider.kt index d16efdd..53363d7 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryProvider.kt @@ -3,7 +3,6 @@ package com.runetopic.loader.index.config.lighting import com.runetopic.cache.store.Js5Store import com.runetopic.loader.IEntryProvider - /** * @author Tyler Telis * @email @@ -16,4 +15,4 @@ class LightingEntryProvider : IEntryProvider { override fun lookup(id: Int): LightingEntryType = builder.lightings.elementAt(id) override fun size(): Int = builder.lightings.size override fun collect(): Set = builder.lightings -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryType.kt index fd047ed..4eb2a21 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryType.kt @@ -8,6 +8,6 @@ data class LightingEntryType( var anInt962: Int = 0, var anInt956: Int = 2048, var anInt957: Int = 2048, -): IEntryType { - override fun getId(): Int = id -} \ No newline at end of file +) : IEntryType { + override fun getId(): Int = id +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt index 2d90b44..383a7f0 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt @@ -11,7 +11,7 @@ import java.nio.ByteBuffer * @author Tyler Telis * @email */ -internal class MouseIconEntryBuilder: IEntryBuilder { +internal class MouseIconEntryBuilder : IEntryBuilder { lateinit var mouseIconTypes: Set @OptIn(ExperimentalStdlibApi::class) @@ -31,8 +31,8 @@ internal class MouseIconEntryBuilder: IEntryBuilder { type.xCoord = buffer.readUnsignedByte() type.yCoord = buffer.readUnsignedByte() } - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryProvider.kt index fdf23e5..ded6f66 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryProvider.kt @@ -3,7 +3,6 @@ package com.runetopic.loader.index.config.mouseicon import com.runetopic.cache.store.Js5Store import com.runetopic.loader.IEntryProvider - /** * @author Jordan Abraham */ @@ -15,4 +14,4 @@ class MouseIconEntryProvider : IEntryProvider { override fun lookup(id: Int): MouseIconEntryType = builder.mouseIconTypes.elementAt(id) override fun size(): Int = builder.mouseIconTypes.size override fun collect(): Set = builder.mouseIconTypes -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryType.kt index 3805377..92d502e 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryType.kt @@ -7,7 +7,6 @@ data class MouseIconEntryType( var xCoord: Int = 0, var yCoord: Int = 0, var spriteId: Int = 0 -): IEntryType { +) : IEntryType { override fun getId(): Int = id - -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt index 781ac3c..b02897f 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt @@ -11,7 +11,7 @@ import java.nio.ByteBuffer /** * @author Jordan Abraham */ -class OverlayEntryBuilder: IEntryBuilder { +class OverlayEntryBuilder : IEntryBuilder { lateinit var overlays: Set @@ -35,7 +35,7 @@ class OverlayEntryBuilder: IEntryBuilder { 5 -> type.occlude = false 7 -> type.secondaryColor = buffer.readUnsignedMedium() 8 -> { - //Some sort of client usage happens here. + // Some sort of client usage happens here. } 9 -> type.textureResolution = buffer.readUnsignedShort() shl 2 10 -> type.aBoolean397 = false @@ -44,8 +44,8 @@ class OverlayEntryBuilder: IEntryBuilder { 13 -> type.anInt392 = buffer.readUnsignedMedium() 14 -> type.anInt395 = buffer.readUnsignedByte() shl 2 16 -> type.anInt388 = buffer.readUnsignedByte() - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryProvider.kt index 5bdaf94..982614d 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryProvider.kt @@ -6,7 +6,7 @@ import com.runetopic.loader.IEntryProvider /** * @author Jordan Abraham */ -class OverlayEntryProvider: IEntryProvider { +class OverlayEntryProvider : IEntryProvider { private val builder = OverlayEntryBuilder() @@ -14,4 +14,4 @@ class OverlayEntryProvider: IEntryProvider { override fun lookup(id: Int): OverlayEntryType = builder.overlays.elementAt(id) override fun size(): Int = builder.overlays.size override fun collect(): Set = builder.overlays -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryType.kt index e1ee46d..e6d4660 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryType.kt @@ -18,6 +18,6 @@ data class OverlayEntryType( var anInt392: Int = 1190717, var anInt395: Int = 64, var anInt388: Int = 127 -): IEntryType { +) : IEntryType { override fun getId(): Int = id -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt index d7a05aa..7fbca32 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt @@ -12,7 +12,7 @@ import java.nio.ByteBuffer * @author Tyler Telis * @email */ -internal class ParamEntryBuilder: IEntryBuilder { +internal class ParamEntryBuilder : IEntryBuilder { lateinit var paramTypes: Set @@ -32,8 +32,8 @@ internal class ParamEntryBuilder: IEntryBuilder { 2 -> type.defaultInt = buffer.int 4 -> type.aBoolean1822 = false 5 -> type.defaultString = buffer.readString() - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryProvider.kt index d28b196..933a44e 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryProvider.kt @@ -3,7 +3,6 @@ package com.runetopic.loader.index.config.param import com.runetopic.cache.store.Js5Store import com.runetopic.loader.IEntryProvider - /** * @author Jordan Abraham */ @@ -15,4 +14,4 @@ class ParamEntryProvider : IEntryProvider { override fun lookup(id: Int): ParamEntryType = builder.paramTypes.elementAt(id) override fun size(): Int = builder.paramTypes.size override fun collect(): Set = builder.paramTypes -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryType.kt index d4ab77d..2de786f 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryType.kt @@ -8,6 +8,6 @@ data class ParamEntryType( var aBoolean1822: Boolean = true, var defaultString: String = "", var defaultInt: Int = 0 -): IEntryType { +) : IEntryType { override fun getId(): Int = id -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt index 10e7d51..89dad86 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt @@ -11,7 +11,7 @@ import java.nio.ByteBuffer * @author Tyler Telis * @email */ -internal class SkyBoxEntryBuilder: IEntryBuilder { +internal class SkyBoxEntryBuilder : IEntryBuilder { lateinit var skyBoxTypes: Set @OptIn(ExperimentalStdlibApi::class) @@ -35,8 +35,8 @@ internal class SkyBoxEntryBuilder: IEntryBuilder { } } 3 -> type.anInt2392 = buffer.readUnsignedByte() - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryProvider.kt index d93f916..1681ba8 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryProvider.kt @@ -3,7 +3,6 @@ package com.runetopic.loader.index.config.skybox import com.runetopic.cache.store.Js5Store import com.runetopic.loader.IEntryProvider - /** * @author Jordan Abraham */ @@ -15,4 +14,4 @@ class SkyBoxEntryProvider : IEntryProvider { override fun lookup(id: Int): SkyBoxEntryType = builder.skyBoxTypes.elementAt(id) override fun size(): Int = builder.skyBoxTypes.size override fun collect(): Set = builder.skyBoxTypes -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryType.kt index d2aaea1..fa5d5e4 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryType.kt @@ -7,7 +7,7 @@ data class SkyBoxEntryType( var anInt2392: Int = -1, var sphereIds: IntArray? = null, var textureId: Int = -1 -): IEntryType { +) : IEntryType { override fun getId(): Int = id override fun equals(other: Any?): Boolean { if (this === other) return true @@ -33,4 +33,4 @@ data class SkyBoxEntryType( result = 31 * result + textureId return result } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt index 22ef172..30e25db 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt @@ -9,7 +9,7 @@ import java.nio.ByteBuffer * @author Tyler Telis * @email */ -internal class StructEntryBuilder: IEntryBuilder { +internal class StructEntryBuilder : IEntryBuilder { lateinit var structTypes: Set @@ -32,8 +32,8 @@ internal class StructEntryBuilder: IEntryBuilder { type.params[buffer.readUnsignedMedium()] = if (string) buffer.readString() else buffer.int } } - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryProvider.kt index e1c413a..f4957ec 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryProvider.kt @@ -3,7 +3,6 @@ package com.runetopic.loader.index.config.struct import com.runetopic.cache.store.Js5Store import com.runetopic.loader.IEntryProvider - /** * @author Jordan Abraham */ @@ -15,4 +14,4 @@ class StructEntryProvider : IEntryProvider { override fun lookup(id: Int): StructEntryType = builder.structTypes.elementAt(id) override fun size(): Int = builder.structTypes.size override fun collect(): Set = builder.structTypes -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryType.kt index 0c8992c..03b7ecf 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryType.kt @@ -5,6 +5,6 @@ import com.runetopic.loader.IEntryType data class StructEntryType( private val id: Int = 0, var params: MutableMap = mutableMapOf() -): IEntryType { +) : IEntryType { override fun getId(): Int = id -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt index 53450e4..90d587b 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt @@ -11,7 +11,7 @@ import java.nio.ByteBuffer /** * @author Jordan Abraham */ -class UnderlayEntryBuilder: IEntryBuilder { +class UnderlayEntryBuilder : IEntryBuilder { lateinit var underlays: Set @@ -34,8 +34,8 @@ class UnderlayEntryBuilder: IEntryBuilder { 3 -> type.textureResolution = buffer.readUnsignedShort() shl 2 4 -> type.aBoolean2647 = false 5 -> type.aBoolean2648 = false - else -> throw Exception("Read unused opcode with id: ${opcode}.") - } while(true) + else -> throw Exception("Read unused opcode with id: $opcode.") + } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryProvider.kt index dff9043..6aa1052 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryProvider.kt @@ -14,4 +14,4 @@ class UnderlayEntryProvider : IEntryProvider { override fun lookup(id: Int): UnderlayEntryType = builder.underlays.elementAt(id) override fun size(): Int = builder.underlays.size override fun collect(): Set = builder.underlays -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryType.kt index d61d1bb..51498a3 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryType.kt @@ -12,6 +12,6 @@ data class UnderlayEntryType( var textureResolution: Int = 512, var aBoolean2647: Boolean = true, var aBoolean2648: Boolean = true -): IEntryType { +) : IEntryType { override fun getId(): Int = id -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt index b01fb7b..1de346b 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt @@ -207,7 +207,7 @@ internal class LocEntryBuilder : IEntryBuilder { type.anIntArray1154 = anIntArray1154 } 107 -> type.anInt1101 = buffer.readUnsignedShort() - in 150..154 -> buffer.readString().let { type.actions[opcode -150] = it } + in 150..154 -> buffer.readString().let { type.actions[opcode - 150] = it } 160 -> { val length = buffer.readUnsignedByte() val anIntArray1153 = IntArray(length) @@ -247,7 +247,7 @@ internal class LocEntryBuilder : IEntryBuilder { type.params[buffer.readUnsignedMedium()] = if (string) buffer.readString() else buffer.int } } - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } @@ -260,4 +260,4 @@ internal class LocEntryBuilder : IEntryBuilder { buffer.position(buffer.position() + (count * 2)) } } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryProvider.kt index 9bab71f..6b920ee 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryProvider.kt @@ -3,7 +3,6 @@ package com.runetopic.loader.index.loc import com.runetopic.cache.store.Js5Store import com.runetopic.loader.IEntryProvider - /** * @author Tyler Telis * @email @@ -16,4 +15,4 @@ class LocEntryProvider : IEntryProvider { override fun lookup(id: Int): LocEntryType = builder.mapTypes.elementAt(id) override fun size(): Int = builder.mapTypes.size override fun collect(): Set = builder.mapTypes -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryType.kt index 521e44f..f0bf911 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryType.kt @@ -7,7 +7,7 @@ import com.runetopic.loader.IEntryType * @email */ data class LocEntryType( - private val id: Int= 0, + private val id: Int = 0, var types: ByteArray? = null, var models: Array? = null, var name: String = "null", @@ -87,6 +87,6 @@ data class LocEntryType( var aBoolean1167: Boolean = false, var anInt1113: Int = 0, var params: MutableMap = mutableMapOf(), - ): IEntryType { +) : IEntryType { override fun getId(): Int = id -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt index 16d8365..c62f3f9 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt @@ -24,7 +24,7 @@ internal class MapEntryBuilder : IEntryBuilder { (0..Short.MAX_VALUE).forEach { regionId -> val regionX: Int = regionId shr 8 val regionY: Int = regionId and 0xFF - it.group("m${regionX}_${regionY}").data.let { data -> + it.group("m${regionX}_$regionY").data.let { data -> if (data.isEmpty()) return@forEach add(read(data.toByteBuffer(), MapEntryType(regionId, regionX, regionY))) } @@ -140,7 +140,7 @@ internal class MapEntryBuilder : IEntryBuilder { val cameraAngles = arrayOfNulls>(4) (0 until 4).forEach { index -> val i = buffer.get().toInt() - //0 checks if the array exists. + // 0 checks if the array exists. if (i == 1) { val regionParamX = 104 val regionParamY = 104 @@ -159,4 +159,4 @@ internal class MapEntryBuilder : IEntryBuilder { } } } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryProvider.kt index e6fd1b1..f1c691c 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryProvider.kt @@ -3,7 +3,6 @@ package com.runetopic.loader.index.map import com.runetopic.cache.store.Js5Store import com.runetopic.loader.IEntryProvider - /** * @author Tyler Telis * @email @@ -16,4 +15,4 @@ class MapEntryProvider : IEntryProvider { override fun lookup(id: Int): MapEntryType = builder.mapTypes.elementAt(id) override fun size(): Int = builder.mapTypes.size override fun collect(): Set = builder.mapTypes -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryType.kt index 046fdd8..5b54358 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryType.kt @@ -93,4 +93,4 @@ data class MapEntryType( const val MAP_SIZE = 64 const val PLANES = 4 } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt index 24e0a3b..eb75059 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt @@ -23,7 +23,7 @@ internal class MapLocationEntryBuilder : IEntryBuilder { (0..Short.MAX_VALUE).forEach { regionId -> val regionX: Int = regionId shr 8 val regionY: Int = regionId and 0xFF - it.group("l${regionX}_${regionY}").data.let { data -> + it.group("l${regionX}_$regionY").data.let { data -> if (data.isEmpty()) return@forEach add(read(data.toByteBuffer(), MapLocationEntryType(regionId, regionX, regionY))) } @@ -64,4 +64,4 @@ internal class MapLocationEntryBuilder : IEntryBuilder { return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryProvider.kt index 62157d9..696923d 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryProvider.kt @@ -3,7 +3,6 @@ package com.runetopic.loader.index.map import com.runetopic.cache.store.Js5Store import com.runetopic.loader.IEntryProvider - /** * @author Tyler Telis * @email @@ -16,4 +15,4 @@ class MapLocationEntryProvider : IEntryProvider { override fun lookup(id: Int): MapLocationEntryType = builder.mapTypes.elementAt(id) override fun size(): Int = builder.mapTypes.size override fun collect(): Set = builder.mapTypes -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryType.kt index a7524cf..2c2d081 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryType.kt @@ -11,7 +11,7 @@ data class MapLocationEntryType( private val regionX: Int, private val regionY: Int, val locations: ArrayList = arrayListOf() -): IEntryType { +) : IEntryType { override fun getId(): Int = regionId class MapLocation( @@ -22,4 +22,4 @@ data class MapLocationEntryType( private val localY: Int, private val height: Int ) -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt index 87c1d01..6674532 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt @@ -8,7 +8,7 @@ import java.nio.ByteBuffer /** * @author Jordan Abraham */ -class NpcEntryBuilder: IEntryBuilder { +class NpcEntryBuilder : IEntryBuilder { lateinit var npcs: Set @@ -176,7 +176,7 @@ class NpcEntryBuilder: IEntryBuilder { 141 -> type.aBoolean875 = true 142 -> type.anInt846 = buffer.readUnsignedShort() 143 -> type.aBoolean869 = true - in 150..154 -> { buffer.readString().let { type.options[opcode -150] = it } } + in 150..154 -> { buffer.readString().let { type.options[opcode - 150] = it } } 155 -> { type.aByte821 = buffer.get() type.aByte824 = buffer.get() @@ -210,4 +210,4 @@ class NpcEntryBuilder: IEntryBuilder { } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryProvider.kt index 4ccf7ed..40e656a 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryProvider.kt @@ -6,7 +6,7 @@ import com.runetopic.loader.IEntryProvider /** * @author Jordan Abraham */ -class NpcEntryProvider: IEntryProvider { +class NpcEntryProvider : IEntryProvider { private val builder = NpcEntryBuilder() @@ -14,4 +14,4 @@ class NpcEntryProvider: IEntryProvider { override fun lookup(id: Int): NpcEntryType = builder.npcs.elementAt(id) override fun size(): Int = builder.npcs.size override fun collect(): Set = builder.npcs -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryType.kt index 35a21be..f40b3d8 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryType.kt @@ -70,6 +70,6 @@ data class NpcEntryType( var aByte843: Byte = 0, var aBoolean809: Boolean = false, var params: MutableMap = mutableMapOf(), -): IEntryType { +) : IEntryType { override fun getId(): Int = id -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt index 137f3f0..4866cc1 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt @@ -143,8 +143,8 @@ internal class ObjEntryBuilder : IEntryBuilder { type.params[buffer.readUnsignedMedium()] = if (string) buffer.readString() else buffer.int } } - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryProvider.kt index 921eaad..862c2fa 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryProvider.kt @@ -14,4 +14,4 @@ class ObjEntryProvider : IEntryProvider { override fun lookup(id: Int): ObjEntryType = builder.objs.elementAt(id) override fun size(): Int = builder.objs.size override fun collect(): Set = builder.objs -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryType.kt index cf5d137..6e08e7e 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryType.kt @@ -233,4 +233,4 @@ data class ObjEntryType( result = 31 * result + params.hashCode() return result } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt index f849a2d..061cc4f 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt @@ -100,8 +100,8 @@ internal class ParticleEntryBuilder : IEntryBuilder { 32 -> type.aBoolean3048 = false 33 -> type.aBoolean3023 = true 34 -> type.aBoolean3069 = false - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryProvider.kt index 5d74c6a..4f49b68 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryProvider.kt @@ -14,4 +14,4 @@ class ParticleEntryProvider : IEntryProvider { override fun lookup(id: Int): ParticleEntryType = builder.particles.elementAt(id) override fun size(): Int = builder.particles.size override fun collect(): Set = builder.particles -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryType.kt index 6493f65..0555254 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryType.kt @@ -160,4 +160,4 @@ data class ParticleEntryType( result = 31 * result + colorFadePct return result } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt index 241d175..eb208c0 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt @@ -10,7 +10,7 @@ import java.nio.ByteBuffer /** * @author Jordan Abraham */ -internal class SpotAnimationEntryBuilder: IEntryBuilder { +internal class SpotAnimationEntryBuilder : IEntryBuilder { lateinit var spotAnimations: Set @@ -77,8 +77,8 @@ internal class SpotAnimationEntryBuilder: IEntryBuilder type.textureToFind = textureToFind type.textureToReplace = textureToReplace } - else -> throw Exception("Read unused opcode with id: ${opcode}.") + else -> throw Exception("Read unused opcode with id: $opcode.") } while (true) return type } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryProvider.kt b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryProvider.kt index 1036e18..8cf1dfa 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryProvider.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryProvider.kt @@ -3,7 +3,6 @@ package com.runetopic.loader.index.spotanim import com.runetopic.cache.store.Js5Store import com.runetopic.loader.IEntryProvider - /** * @author Jordan Abraham */ @@ -15,4 +14,4 @@ class SpotAnimationEntryProvider : IEntryProvider { override fun lookup(id: Int): SpotAnimationEntryType = builder.spotAnimations.elementAt(id) override fun size(): Int = builder.spotAnimations.size override fun collect(): Set = builder.spotAnimations -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryType.kt b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryType.kt index c7dcf95..64c64a7 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryType.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryType.kt @@ -80,4 +80,4 @@ data class SpotAnimationEntryType( result = 31 * result + aBoolean2678.hashCode() return result } -} \ No newline at end of file +} diff --git a/loader/src/main/kotlin/com/runetopic/loader/util/vector/Vector3f.kt b/loader/src/main/kotlin/com/runetopic/loader/util/vector/Vector3f.kt index b52d5c1..b49017f 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/util/vector/Vector3f.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/util/vector/Vector3f.kt @@ -4,4 +4,4 @@ data class Vector3f( val x: Float, val y: Float, val z: Float -) \ No newline at end of file +) diff --git a/settings.gradle.kts b/settings.gradle.kts index 6b04d8d..ad194df 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,6 @@ dependencyResolutionManagement { } } - include("app") include("cache") include("loader") From e49e5b54c0d2b2bea785c4c09e9739d5532df775 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 3 Jan 2022 22:09:12 -0500 Subject: [PATCH 10/14] Cache and Loader code refactor. --- .../runetopic/cache/codec/ContainerCodec.kt | 4 + .../runetopic/cache/hierarchy/index/Index.kt | 1 - .../com/runetopic/cache/store/Js5Store.kt | 44 ++-- .../cache/store/storage/js5/Js5DiskStorage.kt | 26 +-- .../js5/io/dat/sector/DatGroupSector.kt | 37 ++-- .../js5/io/dat/sector/DatIndexSector.kt | 198 +++++++++--------- .../config/idk/IdentityKitEntryBuilder.kt | 9 +- .../index/config/inv/InventoryEntryBuilder.kt | 1 - .../config/lighting/LightingEntryBuilder.kt | 1 - .../config/mouseicon/MouseIconEntryBuilder.kt | 1 - .../config/overlay/OverlayEntryBuilder.kt | 1 - .../index/config/param/ParamEntryBuilder.kt | 1 - .../index/config/skybox/SkyBoxEntryBuilder.kt | 5 +- .../index/config/struct/StructEntryBuilder.kt | 3 +- .../config/underlay/UnderlayEntryBuilder.kt | 1 - .../loader/index/loc/LocEntryBuilder.kt | 60 +++--- .../loader/index/map/MapEntryBuilder.kt | 9 +- .../index/map/MapLocationEntryBuilder.kt | 2 +- .../loader/index/npc/NpcEntryBuilder.kt | 31 ++- .../loader/index/obj/ObjEntryBuilder.kt | 15 +- .../index/particle/ParticleEntryBuilder.kt | 7 +- .../spotanim/SpotAnimationEntryBuilder.kt | 9 +- settings.gradle.kts | 2 +- 23 files changed, 210 insertions(+), 258 deletions(-) diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt index 32727cb..6c6b8ad 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/ContainerCodec.kt @@ -1,5 +1,9 @@ package com.runetopic.cache.codec +import com.runetopic.cache.codec.CodecType.BZipCodec +import com.runetopic.cache.codec.CodecType.BadCodec +import com.runetopic.cache.codec.CodecType.GZipCodec +import com.runetopic.cache.codec.CodecType.NoCodec import com.runetopic.cache.exception.CompressionException import com.runetopic.cache.extension.readUnsignedByte import com.runetopic.cache.extension.readUnsignedShort diff --git a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt index 0589059..c966886 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/hierarchy/index/Index.kt @@ -34,7 +34,6 @@ data class Index( fun group(groupName: String): Group = groups.values.find { it.nameHash == groupName.nameHash() } ?: Group.DEFAULT fun expand(): Int = groups.values.last().files().size + (groups.values.last().id shl 8) - fun use(block: (Index) -> Unit) = block.invoke(this) override fun compareTo(other: Index): Int = this.id.compareTo(other.id) override fun equals(other: Any?): Boolean { diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt b/cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt index 66b4fe8..fa629d4 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/Js5Store.kt @@ -53,35 +53,33 @@ class Js5Store( fun indexReferenceTableSize(indexId: Int): Int { var size = 0 - index(indexId).use { index -> + index(indexId).let { index -> index.groups().forEach { size += storage.loadReferenceTable(index, it.id).size } } return size } - fun groupReferenceTableSize(indexId: Int, groupName: String): Int { - val referenceTable = storage.loadReferenceTable(index(indexId), groupName) - return if (referenceTable.isEmpty()) 0 else referenceTable.size - 2 - } - - fun groupReferenceTableSize(indexId: Int, groupId: Int): Int { - val referenceTable = storage.loadReferenceTable(index(indexId), groupId) - return if (referenceTable.isEmpty()) 0 else referenceTable.size - 2 - } - - fun groupReferenceTable(indexId: Int, groupId: Int): ByteArray { - if (indexId == Constants.MASTER_INDEX_ID) return storage.loadMasterReferenceTable(groupId) - return storage.loadReferenceTable(index(indexId), groupId) - } - - fun checksumsWithoutRSA(): ByteArray { - val header = ByteBuffer.allocate(indexes.size * 8) - indexes.forEach { - header.putInt(it.crc) - header.putInt(it.revision) + fun groupReferenceTableSize( + indexId: Int, + groupName: String + ): Int = storage.loadReferenceTable(index(indexId), groupName).let { if (it.isEmpty()) 0 else it.size - 2 } + + fun groupReferenceTableSize( + indexId: Int, + groupId: Int + ): Int = storage.loadReferenceTable(index(indexId), groupId).let { if (it.isEmpty()) 0 else it.size - 2 } + + fun groupReferenceTable( + indexId: Int, + groupId: Int + ): ByteArray = if (indexId == Constants.MASTER_INDEX_ID) storage.loadMasterReferenceTable(groupId) else storage.loadReferenceTable(index(indexId), groupId) + + fun checksumsWithoutRSA(): ByteArray = ByteBuffer.allocate(indexes.size * 8).also { + indexes.forEach { index -> + it.putInt(index.crc) + it.putInt(index.revision) } - return header.array() - } + }.array() fun checksumsWithRSA(exponent: BigInteger, modulus: BigInteger): ByteArray { val header = ByteBuffer.allocate(indexes.size * 72 + 6) diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt index 48078b0..57db204 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt @@ -58,7 +58,7 @@ internal class Js5DiskStorage( val latch = CountDownLatch(masterIdxFile.validIndexCount()) val threads = Runtime.getRuntime().availableProcessors() val pool = Executors.newFixedThreadPool(if (threads >= 16) 8 else if (threads >= 8) 4 else 2) - (0 until masterIdxFile.validIndexCount()).forEach { + repeat(masterIdxFile.validIndexCount()) { pool.execute { read(it, store) latch.countDown() @@ -66,9 +66,7 @@ internal class Js5DiskStorage( } latch.await() pool.shutdown() - } else { - (0 until masterIdxFile.validIndexCount()).forEach { read(it, store) } - } + } else { repeat(masterIdxFile.validIndexCount()) { read(it, store) } } logger.debug { "Opened ${idxFiles.size} js5 indexes. (Allocated ${((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024)}MB)." } } @@ -91,24 +89,16 @@ internal class Js5DiskStorage( // val idk2 = DatIndexSector(datFile, getIdxFile(indexId), data).encode(index) } - override fun loadMasterReferenceTable(groupId: Int): ByteArray { - return datFile.decode(Constants.MASTER_INDEX_ID, masterIdxFile.decode(groupId)) - } + override fun loadMasterReferenceTable(groupId: Int): ByteArray = datFile.decode(Constants.MASTER_INDEX_ID, masterIdxFile.decode(groupId)) - override fun loadReferenceTable(index: Index, groupId: Int): ByteArray { - return datFile.decode(index.id, getIdxFile(index.id).decode(groupId)) - } + override fun loadReferenceTable(index: Index, groupId: Int): ByteArray = datFile.decode(index.id, getIdxFile(index.id).decode(groupId)) - override fun loadReferenceTable(index: Index, groupName: String): ByteArray { - val group = index.group(groupName) - if (group.data.isEmpty()) return group.data - return datFile.decode(index.id, getIdxFile(index.id).decode(group.id)) + override fun loadReferenceTable(index: Index, groupName: String): ByteArray = index.group(groupName).let { + if (it.data.isEmpty()) return it.data + else datFile.decode(index.id, getIdxFile(index.id).decode(it.id)) } - private fun getIdxFile(id: Int): IdxFile { - idxFiles.find { it.id() == id }?.let { return it } - return IdxFile(id, Path.of("$path/${Constants.MAIN_FILE_IDX}$id")) - } + private fun getIdxFile(id: Int): IdxFile = idxFiles.find { it.id() == id } ?: IdxFile(id, Path.of("$path/${Constants.MAIN_FILE_IDX}$id")) override fun close() { masterIdxFile.close() diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt index db75d8c..e8259a5 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt @@ -14,7 +14,9 @@ data class DatGroupSector( val count: Int, val groupId: Int ) : IDatSector> { - override fun decode(): MutableMap { + + @OptIn(ExperimentalStdlibApi::class) + override fun decode(): Map { if (data.isEmpty()) return hashMapOf(Pair(0, File.DEFAULT)) if (count <= 1) return hashMapOf(Pair(0, File(fileIds[groupId][0], fileNameHashes[groupId][0], data))) @@ -24,35 +26,30 @@ data class DatGroupSector( val buffer = data.toByteBuffer() buffer.position(position) val filesSizes = IntArray(count) - (0 until chunks).forEach { _ -> - var read = 0 - (0 until count).forEach { - read += buffer.int - filesSizes[it] += read + repeat(chunks) { + var bytesRead = 0 + repeat(count) { + bytesRead += buffer.int + filesSizes[it] += bytesRead } } val filesDatas = Array(count) { byteArrayOf() } - (0 until count).forEach { + repeat(count) { filesDatas[it] = ByteArray(filesSizes[it]) filesSizes[it] = 0 } buffer.position(position) var offset = 0 - (0 until chunks).forEach { _ -> - var read = 0 - (0 until count).forEach { - read += buffer.int - System.arraycopy(data, offset, filesDatas[it], filesSizes[it], read) - offset += read - filesSizes[it] += read + repeat(chunks) { + var bytesRead = 0 + repeat(count) { + bytesRead += buffer.int + System.arraycopy(data, offset, filesDatas[it], filesSizes[it], bytesRead) + offset += bytesRead + filesSizes[it] += bytesRead } } - - val files = hashMapOf() - (0 until count).forEach { - files[it] = File(fileIds[groupId][it], fileNameHashes[groupId][it], filesDatas[it]) - } - return files + return buildMap { repeat(count) { put(it, File(fileIds[groupId][it], fileNameHashes[groupId][it], filesDatas[it])) } } } override fun encode(override: Map): ByteArray { diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt index 99fea6f..41d20f8 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt @@ -48,7 +48,7 @@ internal data class DatIndexSector( val groupFileNameHashes = decodeGroupFileNameHashes(maxGroupId, groupFileSizes, count, groupIds, buffer, isNamed) val groups = hashMapOf() - (0 until count).forEach { + repeat(count) { val groupId = groupIds[it] val groupReferenceTableData = datFile.decode(idxFile.id(), idxFile.decode(groupId)) @@ -66,19 +66,18 @@ internal data class DatIndexSector( groupId ) - groups[groupId] = ( - Group( - groupId, - groupNameHashes[groupId], - groupCrcs[groupId], - groupWhirlpools[groupId], - groupRevisions[groupId], - intArrayOf(), // TODO - groupSector.decode(), - data - ) - ) + groups[groupId] = Group( + groupId, + groupNameHashes[groupId], + groupCrcs[groupId], + groupWhirlpools[groupId], + groupRevisions[groupId], + intArrayOf(), // TODO + groupSector.decode().toMutableMap(), + data + ) } + return Index( idxFile.id(), decompressed.crc, @@ -164,11 +163,11 @@ internal data class DatIndexSector( buffer: ByteBuffer, protocol: Int ): IntArray { - val groupIds = IntArray(count) - (0 until count).forEach { - groupIds[it] = (if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort()) + if (it == 0) 0 else groupIds[it - 1] + return IntArray(count).also { + repeat(count) { index -> + it[index] = (if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort()) + if (index == 0) 0 else it[index - 1] + } } - return groupIds } fun encodeGroupIds( @@ -177,12 +176,12 @@ internal data class DatIndexSector( groupIds: IntArray ): ByteBuffer { // TODO This buffer needs to be allocated properly for protocol >= 7 - val buffer = ByteBuffer.allocate(calc(count, groupIds, protocol)) - (0 until count).forEach { - val value = groupIds[it] - if (it == 0) 0 else groupIds[it - 1] - if (protocol >= 7) buffer.putIntShortSmart(value) else buffer.putShort(value.toShort()) + return ByteBuffer.allocate(calc(count, groupIds, protocol)).also { + repeat(count) { index -> + val value = groupIds[index] - if (index == 0) 0 else groupIds[index - 1] + if (protocol >= 7) it.putIntShortSmart(value) else it.putShort(value.toShort()) + } } - return buffer } fun calc( @@ -191,7 +190,7 @@ internal data class DatIndexSector( protocol: Int ): Int { var bytes = 0 - (0 until count).forEach { + repeat(count) { val value = groupIds[it] - if (it == 0) 0 else groupIds[it - 1] bytes += if (protocol >= 7) if (value >= Short.MAX_VALUE) 4 else 2 else 2 } @@ -205,11 +204,11 @@ internal data class DatIndexSector( buffer: ByteBuffer, protocol: Int ): IntArray { - val groupFileSizes = IntArray(maxGroupId) - (0 until count).forEach { - groupFileSizes[groupIds[it]] = if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort() + return IntArray(maxGroupId).also { + repeat(count) { index -> + it[groupIds[index]] = if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort() + } } - return groupFileSizes } fun encodeGroupFileSizes( @@ -219,11 +218,11 @@ internal data class DatIndexSector( groupFileSizes: IntArray ): ByteBuffer { // TODO This buffer needs to be allocated properly for protocol >= 7 - val buffer = ByteBuffer.allocate(groupIds.sumOf { Short.SIZE_BYTES }) - (0 until count).forEach { - if (protocol >= 7) buffer.putIntShortSmart(groupFileSizes[groupIds[it]]) else buffer.putShort(groupFileSizes[groupIds[it]].toShort()) + return ByteBuffer.allocate(groupIds.sumOf { Short.SIZE_BYTES }).also { + repeat(count) { index -> + if (protocol >= 7) it.putIntShortSmart(groupFileSizes[groupIds[index]]) else it.putShort(groupFileSizes[groupIds[index]].toShort()) + } } - return buffer } fun decodeGroupRevisions( @@ -232,11 +231,11 @@ internal data class DatIndexSector( groupIds: IntArray, buffer: ByteBuffer ): IntArray { - val revisions = IntArray(maxGroupId) - (0 until count).forEach { - revisions[groupIds[it]] = buffer.int + return IntArray(maxGroupId).also { + repeat(count) { index -> + it[groupIds[index]] = buffer.int + } } - return revisions } fun encodeGroupRevisions( @@ -244,11 +243,11 @@ internal data class DatIndexSector( groupIds: IntArray, revisions: IntArray ): ByteBuffer { - val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) - (0 until count).forEach { - buffer.putInt(revisions[groupIds[it]]) + return ByteBuffer.allocate(count * Int.SIZE_BYTES).also { + repeat(count) { index -> + it.putInt(revisions[groupIds[index]]) + } } - return buffer } fun decodeGroupWhirlpools( @@ -258,15 +257,13 @@ internal data class DatIndexSector( buffer: ByteBuffer, groupIds: IntArray ): Array { - val whirlpools = Array(maxGroupId) { ByteArray(64) } - if (usesWhirlpool.not()) return whirlpools - - (0 until count).forEach { - val whirlpool = ByteArray(64) - buffer.get(whirlpool) - whirlpools[groupIds[it]] = whirlpool + return Array(maxGroupId) { ByteArray(64) }.also { + if (usesWhirlpool) { + repeat(count) { index -> + it[groupIds[index]] = ByteArray(64).also { b -> buffer.get(b) } + } + } } - return whirlpools } fun encodeGroupWhirlpools( @@ -275,13 +272,11 @@ internal data class DatIndexSector( usesWhirlpool: Boolean, whirlpools: Array ): ByteBuffer { - if ((usesWhirlpool.not())) return ByteBuffer.allocate(0) - - val buffer = ByteBuffer.allocate(count * 64) - (0 until count).forEach { - buffer.put(whirlpools[groupIds[it]]) + return if (usesWhirlpool.not()) ByteBuffer.allocate(0) else ByteBuffer.allocate(count * 64).also { + repeat(count) { index -> + it.put(whirlpools[groupIds[index]]) + } } - return buffer } fun decodeGroupCrcs( @@ -290,11 +285,11 @@ internal data class DatIndexSector( groupIds: IntArray, buffer: ByteBuffer ): IntArray { - val crcs = IntArray(maxGroupId) - (0 until count).forEach { - crcs[groupIds[it]] = buffer.int + return IntArray(maxGroupId).also { + repeat(count) { index -> + it[groupIds[index]] = buffer.int + } } - return crcs } fun encodeGroupCrcs( @@ -302,11 +297,11 @@ internal data class DatIndexSector( groupIds: IntArray, crcs: IntArray ): ByteBuffer { - val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) - (0 until count).forEach { - buffer.putInt(crcs[groupIds[it]]) + return ByteBuffer.allocate(count * Int.SIZE_BYTES).also { + repeat(count) { index -> + it.putInt(crcs[groupIds[index]]) + } } - return buffer } fun decodeGroupNameHashes( @@ -316,13 +311,13 @@ internal data class DatIndexSector( groupIds: IntArray, buffer: ByteBuffer ): IntArray { - val nameHashes = IntArray(maxGroupId) { -1 } - if (isNamed.not()) return nameHashes - - (0 until count).forEach { - nameHashes[groupIds[it]] = buffer.int + return IntArray(maxGroupId) { -1 }.also { + if (isNamed) { + repeat(count) { index -> + it[groupIds[index]] = buffer.int + } + } } - return nameHashes } fun encodeGroupNameHashes( @@ -331,13 +326,11 @@ internal data class DatIndexSector( groupIds: IntArray, nameHashes: IntArray ): ByteBuffer { - if (isNamed.not()) return ByteBuffer.allocate(0) - - val buffer = ByteBuffer.allocate(count * Int.SIZE_BYTES) - (0 until count).forEach { - buffer.putInt(nameHashes[groupIds[it]]) + return if (isNamed.not()) ByteBuffer.allocate(0) else ByteBuffer.allocate(count * Int.SIZE_BYTES).also { + repeat(count) { index -> + it.putInt(nameHashes[groupIds[index]]) + } } - return buffer } fun decodeGroupFileIds( @@ -348,14 +341,15 @@ internal data class DatIndexSector( buffer: ByteBuffer, protocol: Int ): Array { - val fileIds = Array(maxGroupId) { IntArray(groupFileSizes[it]) } - (0 until count).forEach { - val groupId = groupIds[it] - (0 until groupFileSizes[groupId]).forEach { fileId -> - fileIds[groupId][fileId] = (if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort()) + if (fileId == 0) 0 else fileIds[groupId][fileId - 1] + return Array(maxGroupId) { IntArray(groupFileSizes[it]) }.also { + repeat(count) { index -> + groupIds[index].let { groupId -> + repeat(groupFileSizes[groupId]) { fileId -> + it[groupId][fileId] = (if (protocol >= 7) buffer.readUnsignedIntShortSmart() else buffer.readUnsignedShort()) + if (fileId == 0) 0 else it[groupId][fileId - 1] + } + } } } - return fileIds } fun encodeGroupFileIds( @@ -366,15 +360,16 @@ internal data class DatIndexSector( fileIds: Array ): ByteBuffer { // TODO This buffer needs to be allocated properly for protocol >= 7 - val buffer = ByteBuffer.allocate(groupFileSizes.sumOf { it * Short.SIZE_BYTES }) - (0 until count).forEach { - val groupId = groupIds[it] - (0 until groupFileSizes[groupId]).forEach { fileId -> - val value = fileIds[groupId][fileId] - if (fileId == 0) 0 else fileIds[groupId][fileId - 1] - if (protocol >= 7) buffer.putIntShortSmart(value) else buffer.putShort(value.toShort()) + return ByteBuffer.allocate(groupFileSizes.sumOf { it * Short.SIZE_BYTES }).also { + repeat(count) { index -> + groupIds[index].let { groupId -> + repeat(groupFileSizes[groupId]) { fileId -> + val value = fileIds[groupId][fileId] - if (fileId == 0) 0 else fileIds[groupId][fileId - 1] + if (protocol >= 7) it.putIntShortSmart(value) else it.putShort(value.toShort()) + } + } } } - return buffer } fun decodeGroupFileNameHashes( @@ -385,16 +380,17 @@ internal data class DatIndexSector( buffer: ByteBuffer, isNamed: Boolean ): Array { - val fileNameHashes = Array(maxGroupId) { IntArray(groupFileSizes[it]) { -1 } } - if (isNamed.not()) return fileNameHashes - - (0 until count).forEach { - val groupId = groupIds[it] - (0 until groupFileSizes[groupId]).forEach { fileId -> - fileNameHashes[groupId][fileId] = buffer.int + return Array(maxGroupId) { IntArray(groupFileSizes[it]) { -1 } }.also { + if (isNamed) { + repeat(count) { index -> + groupIds[index].let { groupId -> + repeat(groupFileSizes[groupId]) { fileId -> + it[groupId][fileId] = buffer.int + } + } + } } } - return fileNameHashes } fun encodeGroupFileNameHashes( @@ -404,15 +400,15 @@ internal data class DatIndexSector( isNamed: Boolean, fileNameHashes: Array ): ByteBuffer { - if (isNamed.not()) return ByteBuffer.allocate(0) - val buffer = ByteBuffer.allocate(groupFileSizes.sum() * Int.SIZE_BYTES) - (0 until count).forEach { - val groupId = groupIds[it] - (0 until groupFileSizes[groupId]).forEach { fileId -> - buffer.putInt(fileNameHashes[groupId][fileId]) + return if (isNamed.not()) ByteBuffer.allocate(0) else ByteBuffer.allocate(groupFileSizes.sum() * Int.SIZE_BYTES).also { + repeat(count) { index -> + groupIds[index].let { groupId -> + repeat(groupFileSizes[groupId]) { fileId -> + it.putInt(fileNameHashes[groupId][fileId]) + } + } } } - return buffer } override fun equals(other: Any?): Boolean { diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt index 8b90a52..f2fbe69 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/idk/IdentityKitEntryBuilder.kt @@ -16,7 +16,6 @@ internal class IdentityKitEntryBuilder : IEntryBuilder { lateinit var identityKitTypes: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { identityKitTypes = buildSet { store.index(2).group(3).files().forEach { @@ -33,9 +32,7 @@ internal class IdentityKitEntryBuilder : IEntryBuilder { val size = buffer.readUnsignedByte() val models = IntArray(size) - (0 until size).forEach { - models[it] = buffer.readUnsignedShort() - } + repeat(size) { models[it] = buffer.readUnsignedShort() } type.models = models } 3 -> { @@ -45,7 +42,7 @@ internal class IdentityKitEntryBuilder : IEntryBuilder { val size = buffer.readUnsignedByte() val colorsToFind = ShortArray(size) val colorsToReplace = ShortArray(size) - (0 until size).forEach { + repeat(size) { colorsToFind[it] = buffer.readUnsignedShort().toShort() colorsToReplace[it] = buffer.readUnsignedShort().toShort() } @@ -56,7 +53,7 @@ internal class IdentityKitEntryBuilder : IEntryBuilder { val size = buffer.readUnsignedByte() val texturesToFind = ShortArray(size) val texturesToReplace = ShortArray(size) - (0 until size).forEach { + repeat(size) { texturesToFind[it] = buffer.readUnsignedShort().toShort() texturesToReplace[it] = buffer.readUnsignedShort().toShort() } diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt index a37a0fd..22a2ce0 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/inv/InventoryEntryBuilder.kt @@ -14,7 +14,6 @@ import java.nio.ByteBuffer internal class InventoryEntryBuilder : IEntryBuilder { lateinit var inventoryTypes: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { inventoryTypes = buildSet { store.index(2).group(5).files().forEach { diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt index b01da41..d082847 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/lighting/LightingEntryBuilder.kt @@ -15,7 +15,6 @@ internal class LightingEntryBuilder : IEntryBuilder { lateinit var lightings: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { lightings = buildSet { store.index(2).group(31).files().forEach { diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt index 383a7f0..5743110 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/mouseicon/MouseIconEntryBuilder.kt @@ -14,7 +14,6 @@ import java.nio.ByteBuffer internal class MouseIconEntryBuilder : IEntryBuilder { lateinit var mouseIconTypes: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { mouseIconTypes = buildSet { store.index(2).group(33).files().forEach { diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt index b02897f..0325876 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/overlay/OverlayEntryBuilder.kt @@ -15,7 +15,6 @@ class OverlayEntryBuilder : IEntryBuilder { lateinit var overlays: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { overlays = buildSet { store.index(2).group(4).files().forEach { diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt index 7fbca32..d0eda38 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/param/ParamEntryBuilder.kt @@ -16,7 +16,6 @@ internal class ParamEntryBuilder : IEntryBuilder { lateinit var paramTypes: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { paramTypes = buildSet { store.index(2).group(11).files().forEach { diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt index 89dad86..d378d3d 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/skybox/SkyBoxEntryBuilder.kt @@ -14,7 +14,6 @@ import java.nio.ByteBuffer internal class SkyBoxEntryBuilder : IEntryBuilder { lateinit var skyBoxTypes: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { skyBoxTypes = buildSet { store.index(2).group(29).files().forEach { @@ -30,9 +29,7 @@ internal class SkyBoxEntryBuilder : IEntryBuilder { 2 -> { val size = buffer.readUnsignedByte() val sphereIds = IntArray(size) - (0 until size).forEach { - sphereIds[it] = buffer.readUnsignedShort() - } + repeat(size) { sphereIds[it] = buffer.readUnsignedShort() } } 3 -> type.anInt2392 = buffer.readUnsignedByte() else -> throw Exception("Read unused opcode with id: $opcode.") diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt index 30e25db..1e2cb50 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/struct/StructEntryBuilder.kt @@ -13,7 +13,6 @@ internal class StructEntryBuilder : IEntryBuilder { lateinit var structTypes: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { structTypes = buildSet { store.index(2).group(26).files().forEach { @@ -27,7 +26,7 @@ internal class StructEntryBuilder : IEntryBuilder { 0 -> break 249 -> { val size = buffer.readUnsignedByte() - (0 until size).forEach { _ -> + repeat(size) { val string = buffer.readUnsignedByte().toBoolean() type.params[buffer.readUnsignedMedium()] = if (string) buffer.readString() else buffer.int } diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt index 90d587b..ae9b085 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/config/underlay/UnderlayEntryBuilder.kt @@ -15,7 +15,6 @@ class UnderlayEntryBuilder : IEntryBuilder { lateinit var underlays: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { underlays = buildSet { store.index(2).group(1).files().forEach { diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt index 1de346b..abc79d0 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/loc/LocEntryBuilder.kt @@ -13,10 +13,9 @@ internal class LocEntryBuilder : IEntryBuilder { lateinit var mapTypes: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { mapTypes = buildSet { - store.index(16).use { index -> + store.index(16).let { index -> (0 until index.expand()).forEach { add(read(index.group(it ushr 8).file(it and 0xFF).data.toByteBuffer(), LocEntryType(it))) } @@ -32,14 +31,13 @@ internal class LocEntryBuilder : IEntryBuilder { val types = ByteArray(size) val models = Array(size) { intArrayOf() } - (0 until size).forEach { + repeat(size) { types[it] = buffer.get() val count = buffer.readUnsignedByte() models[it] = IntArray(count) - (0 until count).forEach { it2 -> - models[it][it2] = buffer.readUnsignedShort() - } + repeat(count) { it2 -> models[it][it2] = buffer.readUnsignedShort() } } + if (opcode == 5) { skipReadModelIds(buffer) } @@ -71,7 +69,7 @@ internal class LocEntryBuilder : IEntryBuilder { val size = buffer.readUnsignedByte() val colorsToFind = ShortArray(size) val colorsToReplace = ShortArray(size) - (0 until size).forEach { + repeat(size) { colorsToFind[it] = buffer.readUnsignedShort().toShort() colorsToReplace[it] = buffer.readUnsignedShort().toShort() } @@ -82,7 +80,7 @@ internal class LocEntryBuilder : IEntryBuilder { val size = buffer.readUnsignedByte() val texturesToFind = ShortArray(size) val texturesToReplace = ShortArray(size) - (0 until size).forEach { + repeat(size) { texturesToFind[it] = buffer.readUnsignedShort().toShort() texturesToReplace[it] = buffer.readUnsignedShort().toShort() } @@ -91,12 +89,10 @@ internal class LocEntryBuilder : IEntryBuilder { type.texturesToReplace = texturesToReplace } 42 -> { - val length = buffer.readUnsignedByte() - val aByteArray1118 = ByteArray(length) + val size = buffer.readUnsignedByte() + val aByteArray1118 = ByteArray(size) - (0 until length).forEach { - aByteArray1118[it] = buffer.get() - } + repeat(size) { aByteArray1118[it] = buffer.get() } type.aByteArray1118 = aByteArray1118 } 62 -> type.isRotated = true @@ -131,7 +127,7 @@ internal class LocEntryBuilder : IEntryBuilder { val size = buffer.readUnsignedByte() val configChangeDest = IntArray(size + 2) - (0..size).forEach { index -> + repeat(size + 1) { index -> buffer.readUnsignedShort().let { if (it == 65535) configChangeDest[index] = -1 else configChangeDest[index] = it @@ -149,12 +145,10 @@ internal class LocEntryBuilder : IEntryBuilder { type.anInt1145 = buffer.readUnsignedShort() type.anInt1139 = buffer.readUnsignedShort() type.anInt1144 = buffer.readUnsignedByte() - val length = buffer.readUnsignedByte() - val anIntArray1127 = IntArray(length) + val size = buffer.readUnsignedByte() + val anIntArray1127 = IntArray(size) - (0 until length).forEach { index -> - anIntArray1127[index] = buffer.readUnsignedShort() - } + repeat(size) { anIntArray1127[it] = buffer.readUnsignedShort() } type.anIntArray1127 = anIntArray1127 } @@ -193,14 +187,14 @@ internal class LocEntryBuilder : IEntryBuilder { 104 -> type.anInt1136 = buffer.readUnsignedByte() 105 -> type.aBoolean1148 = true 106 -> { - val length = buffer.readUnsignedByte() - val anIntArray1170 = IntArray(length) - val anIntArray1154 = IntArray(length) - (0 until length).forEach { index -> - anIntArray1170[index] = buffer.readUnsignedShort() - val size = buffer.readUnsignedByte() - anIntArray1154[index] = size - type.anInt1116 += size + val size = buffer.readUnsignedByte() + val anIntArray1170 = IntArray(size) + val anIntArray1154 = IntArray(size) + repeat(size) { + anIntArray1170[it] = buffer.readUnsignedShort() + val i = buffer.readUnsignedByte() + anIntArray1154[it] = i + type.anInt1116 += i } type.anIntArray1170 = anIntArray1170 @@ -209,11 +203,9 @@ internal class LocEntryBuilder : IEntryBuilder { 107 -> type.anInt1101 = buffer.readUnsignedShort() in 150..154 -> buffer.readString().let { type.actions[opcode - 150] = it } 160 -> { - val length = buffer.readUnsignedByte() - val anIntArray1153 = IntArray(length) - (0 until length).forEach { index -> - anIntArray1153[index] = buffer.readUnsignedShort() - } + val size = buffer.readUnsignedByte() + val anIntArray1153 = IntArray(size) + repeat(size) { anIntArray1153[it] = buffer.readUnsignedShort() } type.anIntArray1153 = anIntArray1153 } 162 -> { @@ -241,8 +233,8 @@ internal class LocEntryBuilder : IEntryBuilder { 177 -> type.aBoolean1167 = true 178 -> type.anInt1113 = buffer.readUnsignedByte() 249 -> { - val length = buffer.readUnsignedByte() - (0 until length).forEach { _ -> + val size = buffer.readUnsignedByte() + repeat(size) { val string = buffer.readUnsignedByte().toBoolean() type.params[buffer.readUnsignedMedium()] = if (string) buffer.readString() else buffer.int } diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt index c62f3f9..14354a4 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapEntryBuilder.kt @@ -17,10 +17,9 @@ internal class MapEntryBuilder : IEntryBuilder { lateinit var mapTypes: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { mapTypes = buildSet { - store.index(5).use { + store.index(5).let { (0..Short.MAX_VALUE).forEach { regionId -> val regionX: Int = regionId shr 8 val regionY: Int = regionId and 0xFF @@ -34,9 +33,9 @@ internal class MapEntryBuilder : IEntryBuilder { } override fun read(buffer: ByteBuffer, type: MapEntryType): MapEntryType { - (0 until MapEntryType.PLANES).forEach { plane -> - (0 until MapEntryType.MAP_SIZE).forEach { x -> - (0 until MapEntryType.MAP_SIZE).forEach { z -> + repeat(MapEntryType.PLANES) { plane -> + repeat(MapEntryType.MAP_SIZE) { x -> + repeat(MapEntryType.MAP_SIZE) { z -> val tile = MapEntryType.Tile() while (true) { diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt index eb75059..24a7dca 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/map/MapLocationEntryBuilder.kt @@ -19,7 +19,7 @@ internal class MapLocationEntryBuilder : IEntryBuilder { @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { mapTypes = buildSet { - store.index(5).use { + store.index(5).let { (0..Short.MAX_VALUE).forEach { regionId -> val regionX: Int = regionId shr 8 val regionY: Int = regionId and 0xFF diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt index 6674532..dc854b4 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/npc/NpcEntryBuilder.kt @@ -12,11 +12,10 @@ class NpcEntryBuilder : IEntryBuilder { lateinit var npcs: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { npcs = buildSet { - store.index(18).use { index -> - (0 until index.expand()).forEach { + store.index(18).let { index -> + repeat(index.expand()) { add(read(index.group(it ushr 8).file(it and 0xFF).data.toByteBuffer(), NpcEntryType(it))) } } @@ -29,7 +28,7 @@ class NpcEntryBuilder : IEntryBuilder { 1 -> { val size = buffer.readUnsignedByte() val models = IntArray(size) - (0 until size).forEach { + repeat(size) { models[it] = buffer.readUnsignedShort() if (models[it] == 65535) { models[it] = -1 @@ -44,7 +43,7 @@ class NpcEntryBuilder : IEntryBuilder { val size = buffer.readUnsignedByte() val colorToFind = ShortArray(size) val colorToReplace = ShortArray(size) - (0 until size).forEach { + repeat(size) { colorToFind[it] = buffer.readUnsignedShort().toShort() colorToReplace[it] = buffer.readUnsignedShort().toShort() } @@ -55,7 +54,7 @@ class NpcEntryBuilder : IEntryBuilder { val size = buffer.readUnsignedByte() val textureToFind = ShortArray(size) val textureToReplace = ShortArray(size) - (0 until size).forEach { + repeat(size) { textureToFind[it] = buffer.readUnsignedShort().toShort() textureToReplace[it] = buffer.readUnsignedShort().toShort() } @@ -65,17 +64,13 @@ class NpcEntryBuilder : IEntryBuilder { 42 -> { val size = buffer.readUnsignedByte() val aByteArray866 = ByteArray(size) - (0 until size).forEach { - aByteArray866[it] = buffer.get() - } + repeat(size) { aByteArray866[it] = buffer.get() } type.aByteArray866 = aByteArray866 } 60 -> { val size = buffer.readUnsignedByte() val chatheadModels = IntArray(size) - (0 until size).forEach { - chatheadModels[it] = buffer.readUnsignedShort() - } + repeat(size) { chatheadModels[it] = buffer.readUnsignedShort() } type.chatheadModels = chatheadModels } 93 -> type.isMinimapVisible = false @@ -105,7 +100,7 @@ class NpcEntryBuilder : IEntryBuilder { } val size = buffer.readUnsignedByte() val configChangeDest = IntArray(size + 2) - (0..size).forEach { + repeat(size + 1) { configChangeDest[it] = buffer.readUnsignedShort() if (configChangeDest[it] == 65535) { configChangeDest[it] = -1 @@ -129,7 +124,7 @@ class NpcEntryBuilder : IEntryBuilder { 121 -> { val anIntArrayArray845 = arrayOfNulls(type.models!!.size) val size = buffer.readUnsignedByte() - (0 until size).forEach { + repeat(size) { val i_98_ = buffer.readUnsignedByte() val data = (IntArray(3).also { anIntArrayArray845[i_98_] = it }) data[0] = buffer.get().toInt() @@ -188,9 +183,7 @@ class NpcEntryBuilder : IEntryBuilder { 160 -> { val size = buffer.readUnsignedByte() val anIntArray867 = IntArray(size) - (0 until size).forEach { - anIntArray867[it] = buffer.readUnsignedShort() - } + repeat(size) { anIntArray867[it] = buffer.readUnsignedShort() } } 162 -> type.aBoolean809 = true 163 -> type.anInt864 = buffer.readUnsignedByte() @@ -201,8 +194,8 @@ class NpcEntryBuilder : IEntryBuilder { 165 -> type.anInt847 = buffer.readUnsignedByte() 168 -> type.anInt828 = buffer.readUnsignedByte() 249 -> { - val length = buffer.readUnsignedByte() - (0 until length).forEach { _ -> + val size = buffer.readUnsignedByte() + repeat(size) { val string = buffer.readUnsignedByte().toBoolean() type.params[buffer.readUnsignedMedium()] = if (string) buffer.readString() else buffer.int } diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt index 4866cc1..a35a65d 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/obj/ObjEntryBuilder.kt @@ -12,11 +12,10 @@ internal class ObjEntryBuilder : IEntryBuilder { lateinit var objs: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { objs = buildSet { - store.index(19).use { index -> - (0 until index.expand()).forEach { + store.index(19).let { index -> + repeat(index.expand()) { add(read(index.group(it ushr 8).file(it and 0xFF).data.toByteBuffer(), ObjEntryType(it))) } } @@ -50,7 +49,7 @@ internal class ObjEntryBuilder : IEntryBuilder { val size = buffer.readUnsignedByte() val colorToFind = ShortArray(size) val colorToReplace = ShortArray(size) - (0 until size).forEach { + repeat(size) { colorToFind[it] = buffer.readUnsignedShort().toShort() colorToReplace[it] = buffer.readUnsignedShort().toShort() } @@ -61,7 +60,7 @@ internal class ObjEntryBuilder : IEntryBuilder { val size = buffer.readUnsignedByte() val textureToFind = ShortArray(size) val textureToReplace = ShortArray(size) - (0 until size).forEach { + repeat(size) { textureToFind[it] = buffer.readUnsignedShort().toShort() textureToReplace[it] = buffer.readUnsignedShort().toShort() } @@ -71,7 +70,7 @@ internal class ObjEntryBuilder : IEntryBuilder { 42 -> { val size = buffer.readUnsignedByte() val aByteArray1858 = ByteArray(size) - (0 until size).forEach { aByteArray1858[it] = buffer.readUnsignedByte().toByte() } + repeat(size) { aByteArray1858[it] = buffer.readUnsignedByte().toByte() } type.aByteArray1858 = aByteArray1858 } 65 -> type.tradeable = true @@ -130,7 +129,7 @@ internal class ObjEntryBuilder : IEntryBuilder { 132 -> { val size = buffer.readUnsignedByte() val anIntArray1893 = IntArray(size) - (0 until size).forEach { anIntArray1893[it] = buffer.readUnsignedShort() } + repeat(size) { anIntArray1893[it] = buffer.readUnsignedShort() } type.anIntArray1893 = anIntArray1893 } 134 -> type.anInt1902 = buffer.readUnsignedByte() @@ -138,7 +137,7 @@ internal class ObjEntryBuilder : IEntryBuilder { 140 -> type.anInt1885 = buffer.readUnsignedShort() 249 -> { val size = buffer.readUnsignedByte() - (0 until size).forEach { _ -> + repeat(size) { val string = buffer.readUnsignedByte().toBoolean() type.params[buffer.readUnsignedMedium()] = if (string) buffer.readString() else buffer.int } diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt index 061cc4f..fcc7678 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/particle/ParticleEntryBuilder.kt @@ -12,7 +12,6 @@ internal class ParticleEntryBuilder : IEntryBuilder { lateinit var particles: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { particles = buildSet { store.index(27).group(0).files().forEach { @@ -55,13 +54,13 @@ internal class ParticleEntryBuilder : IEntryBuilder { 9 -> { val size = buffer.readUnsignedByte() val localMagnets = IntArray(size) - (0 until size).forEach { localMagnets[it] = buffer.readUnsignedShort() } + repeat(size) { localMagnets[it] = buffer.readUnsignedShort() } type.localMagnets = localMagnets } 10 -> { val size = buffer.readUnsignedByte() val globalMagnets = IntArray(size) - (0 until size).forEach { globalMagnets[it] = buffer.readUnsignedShort() } + repeat(size) { globalMagnets[it] = buffer.readUnsignedShort() } type.globalMagnets = globalMagnets } 12 -> type.minLevel = buffer.get().toInt() @@ -85,7 +84,7 @@ internal class ParticleEntryBuilder : IEntryBuilder { 25 -> { val size = buffer.readUnsignedByte() val generalMagnets = IntArray(size) - (0 until size).forEach { generalMagnets[it] = buffer.readUnsignedShort() } + repeat(size) { generalMagnets[it] = buffer.readUnsignedShort() } type.generalMagnets = generalMagnets } 26 -> type.aBoolean3070 = false diff --git a/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt index eb208c0..4348d82 100644 --- a/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt +++ b/loader/src/main/kotlin/com/runetopic/loader/index/spotanim/SpotAnimationEntryBuilder.kt @@ -14,11 +14,10 @@ internal class SpotAnimationEntryBuilder : IEntryBuilder lateinit var spotAnimations: Set - @OptIn(ExperimentalStdlibApi::class) override fun build(store: Js5Store) { spotAnimations = buildSet { - store.index(21).use { index -> - (0 until index.expand()).forEach { + store.index(21).let { index -> + repeat(index.expand()) { add(read(index.group(it ushr 8).file(it and 0xFF).data.toByteBuffer(), SpotAnimationEntryType(it))) } } @@ -59,7 +58,7 @@ internal class SpotAnimationEntryBuilder : IEntryBuilder val size = buffer.readUnsignedByte() val colorToFind = ShortArray(size) val colorToReplace = ShortArray(size) - (0 until size).forEach { + repeat(size) { colorToFind[it] = (buffer.readUnsignedShort()).toShort() colorToReplace[it] = (buffer.readUnsignedShort()).toShort() } @@ -70,7 +69,7 @@ internal class SpotAnimationEntryBuilder : IEntryBuilder val size = buffer.readUnsignedByte() val textureToFind = ShortArray(size) val textureToReplace = ShortArray(size) - (0 until size).forEach { + repeat(size) { textureToFind[it] = (buffer.readUnsignedShort()).toShort() textureToReplace[it] = (buffer.readUnsignedShort()).toShort() } diff --git a/settings.gradle.kts b/settings.gradle.kts index ad194df..0484c32 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ rootProject.name = "cache-lib" pluginManagement { plugins { - kotlin("jvm") version "1.5.31" + kotlin("jvm") version "1.6.10" } } From d87da6ff3510b90d926522a154597f11f62c7502 Mon Sep 17 00:00:00 2001 From: Tyler Telis Date: Tue, 4 Jan 2022 00:55:43 -0500 Subject: [PATCH 11/14] Update README.md --- README.md | 74 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 346088b..48c960e 100644 --- a/README.md +++ b/README.md @@ -26,15 +26,15 @@ A cache library written in Kotlin. # Implementation Just use cache if you do not require any of the revision specific loaders. -``` +```groovy cache = { module = "com.runetopic.cache:cache", version.ref "1.4.19-SNAPSHOT" } loader = { module = "com.runetopic.cache:loader", version.ref "647.6.4-SNAPSHOT" } ``` -``` +```groovy //SNAPSHOTS maven { - url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") + url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") } ``` @@ -42,75 +42,91 @@ maven { Index -> Group -> File ### Creating a new JS5 store -``` +```kotlin val store = Js5Store(path = Path.of("/path/"), parallel = true) ``` ### Getting an index -``` +```kotlin val index = store.index(indexId = 5) ``` ### Getting a group by group id -``` +```kotlin val index = store.index(indexId = 5) val group = index.group(groupId = 360) ``` ### Getting a group by group name -``` +```kotlin val index = store.index(indexId = 5) val group = index.group(groupName = "m50_50") ``` ### Getting a file from a group by id -``` +```kotlin val index = store.index(indexId = 2) val group = index.group(groupId = 26) val file = group.file(fileId = 1000) ``` ### Looping multiple groups from an index - store.index(indexId = 19).use { index -> - (0 until index.expand()).forEach { - val data = index.group(it ushr 8).file(it and 0xFF).data - } +```kotlin +store.index(indexId = 19).use { index -> + (0 until index.expand()).forEach { + val data = index.group(it ushr 8).file(it and 0xFF).data } +} +``` ### Looping multiple files from a group - store.index(indexId = 2).group(groupId = 26).files().forEach { - val id = it.id - val data = it.data - } - +```kotlin +store.index(indexId = 2).group(groupId = 26).files().forEach { + val id = it.id + val data = it.data +} +``` ### Getting the reference table of an index and group by id. -```store.groupReferenceTable(indexId = 255, groupId = 255)``` +```kotlin +val size = store.groupReferenceTable(indexId = 255, groupId = 255) +``` ### Getting an index reference table size by id -```store.indexReferenceTableSize(indexId = 28)``` +```kotlin +val size = store.indexReferenceTableSize(indexId = 28) +``` ### Getting a group reference table size by name -```store.groupReferenceTableSize(indexId = 30, groupName = "windows/x86/jaclib.dll")``` +```kotlin +val size = store.groupReferenceTableSize(indexId = 30, groupName = "windows/x86/jaclib.dll") +``` ### Getting a group reference table size by id -```store.groupReferenceTableSize(indexId = 30, groupId = 6)``` +```kotlin +val size = store.groupReferenceTableSize(indexId = 30, groupId = 6) +``` ### Getting 255, 255 checksums with RSA/Whirlpool -```val checksums = store.checksumsWithRSA(exponent = BigInteger(""), modulus = BigInteger(""))``` +```kotlin +val checksums = store.checksumsWithRSA(exponent = BigInteger(""), modulus = BigInteger("")) +``` ### Getting 255, 255 checksums without RSA/Whirlpool -```val checksums = store.checksumsWithoutRSA()``` +```kotlin +val checksums = store.checksumsWithoutRSA() +``` ### An example of a single thread loading providers -``` -objs().load(store) -npcs().load(store) -locs().load(store) -particles().load(store) + +```kotlin +val objProvider = objs().load(store) +val npcProvider = npcs().load(store) +val locProvider = locs().load(store) +val particleProvider = particles().load(store) ``` ### An example of multiple threads parallel loading providers -``` +```kotlin val pool = Executors.newFixedThreadPool(4) val providers = listOf( objs(), From ea752f5956585b4c06db7d494c15bd92986b385c Mon Sep 17 00:00:00 2001 From: Tyler Telis Date: Tue, 4 Jan 2022 12:46:05 -0500 Subject: [PATCH 12/14] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 48c960e..cfd0317 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,6 @@ A cache library written in Kotlin. -# Requirements -- Java Version 16 - # Supported - RS2 (414-772) - RS3 (773-~788) From 8731b24b7eacc9c4b4d04c83e81417491f88d4a4 Mon Sep 17 00:00:00 2001 From: Tyler Telis Date: Wed, 5 Jan 2022 01:14:07 -0500 Subject: [PATCH 13/14] Removing IInterface pattern --- .../kotlin/com/runetopic/cache/codec/CodecType.kt | 2 +- .../cache/codec/{IFileCodec.kt => FileCodec.kt} | 2 +- .../com/runetopic/cache/codec/impl/BZip2Codec.kt | 4 ++-- .../com/runetopic/cache/codec/impl/GZipCodec.kt | 4 ++-- .../com/runetopic/cache/codec/impl/NoFileCodec.kt | 4 ++-- .../cache/store/storage/{IStorage.kt => Storage.kt} | 2 +- .../cache/store/storage/js5/Js5DiskStorage.kt | 12 ++++++------ .../cache/store/storage/js5/io/dat/DatFile.kt | 2 +- .../js5/io/dat/{IDatFile.kt => DatFileCodec.kt} | 2 +- .../js5/io/dat/{IDatSector.kt => DatSectorCodec.kt} | 2 +- .../storage/js5/io/dat/sector/DatGroupSector.kt | 4 ++-- .../storage/js5/io/dat/sector/DatIndexSector.kt | 12 ++++++------ .../cache/store/storage/js5/io/idx/IdxFile.kt | 2 +- .../js5/io/idx/{IIdxFile.kt => IdxFileCodec.kt} | 2 +- 14 files changed, 28 insertions(+), 28 deletions(-) rename cache/src/main/kotlin/com/runetopic/cache/codec/{IFileCodec.kt => FileCodec.kt} (88%) rename cache/src/main/kotlin/com/runetopic/cache/store/storage/{IStorage.kt => Storage.kt} (91%) rename cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/{IDatFile.kt => DatFileCodec.kt} (86%) rename cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/{IDatSector.kt => DatSectorCodec.kt} (79%) rename cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/{IIdxFile.kt => IdxFileCodec.kt} (87%) diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt index c59bec7..64a8611 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/CodecType.kt @@ -11,7 +11,7 @@ import com.runetopic.cache.codec.impl.NoFileCodec * @author Jordan Abraham */ internal sealed class CodecType( - val codec: IFileCodec + val codec: FileCodec ) { object BadCodec : CodecType(NoFileCodec()) object NoCodec : CodecType(NoFileCodec()) diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/IFileCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/FileCodec.kt similarity index 88% rename from cache/src/main/kotlin/com/runetopic/cache/codec/IFileCodec.kt rename to cache/src/main/kotlin/com/runetopic/cache/codec/FileCodec.kt index 75f1820..aac1c44 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/IFileCodec.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/FileCodec.kt @@ -4,7 +4,7 @@ package com.runetopic.cache.codec * @author Tyler Telis * @email */ -internal interface IFileCodec { +internal interface FileCodec { fun compress(data: ByteArray, keys: IntArray): ByteArray fun decompress(data: ByteArray, length: Int, keys: IntArray): ByteArray } diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt index 6bbae97..b8f795d 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/BZip2Codec.kt @@ -1,6 +1,6 @@ package com.runetopic.cache.codec.impl -import com.runetopic.cache.codec.IFileCodec +import com.runetopic.cache.codec.FileCodec import com.runetopic.cache.store.Constants.BZIP_HEADER import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream @@ -14,7 +14,7 @@ import java.util.* * @author Tyler Telis * @email */ -internal class BZip2Codec : IFileCodec { +internal class BZip2Codec : FileCodec { override fun compress(data: ByteArray, keys: IntArray): ByteArray { val stream: InputStream = ByteArrayInputStream(data) val bout = ByteArrayOutputStream() diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt index 8e7d9af..2bd4c0d 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/GZipCodec.kt @@ -1,6 +1,6 @@ package com.runetopic.cache.codec.impl -import com.runetopic.cache.codec.IFileCodec +import com.runetopic.cache.codec.FileCodec import org.apache.commons.compress.utils.IOUtils import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -11,7 +11,7 @@ import java.util.zip.GZIPOutputStream * @author Tyler Telis * @email */ -internal class GZipCodec : IFileCodec { +internal class GZipCodec : FileCodec { override fun compress(data: ByteArray, keys: IntArray): ByteArray { val inputStream = ByteArrayInputStream(data) val outputStream = ByteArrayOutputStream() diff --git a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt index a9017ca..586926a 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/codec/impl/NoFileCodec.kt @@ -1,12 +1,12 @@ package com.runetopic.cache.codec.impl -import com.runetopic.cache.codec.IFileCodec +import com.runetopic.cache.codec.FileCodec /** * @author Tyler Telis * @email */ -internal class NoFileCodec : IFileCodec { +internal class NoFileCodec : FileCodec { override fun compress(data: ByteArray, keys: IntArray): ByteArray = throw NotImplementedError("No codec provided.") override fun decompress(data: ByteArray, length: Int, keys: IntArray): ByteArray = throw NotImplementedError("No codec provided.") } diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/IStorage.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/Storage.kt similarity index 91% rename from cache/src/main/kotlin/com/runetopic/cache/store/storage/IStorage.kt rename to cache/src/main/kotlin/com/runetopic/cache/store/storage/Storage.kt index 0f5d7de..e823b2e 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/IStorage.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/Storage.kt @@ -9,7 +9,7 @@ import java.io.Flushable * @author Tyler Telis * @email */ -internal interface IStorage : Closeable, Flushable { +internal interface Storage : Closeable, Flushable { fun open(store: Js5Store) fun read(indexId: Int, store: Js5Store) fun write(indexId: Int, store: Js5Store) diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt index 57db204..014fccd 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/Js5DiskStorage.kt @@ -4,11 +4,11 @@ import com.github.michaelbull.logging.InlineLogger import com.runetopic.cache.hierarchy.index.Index import com.runetopic.cache.store.Constants import com.runetopic.cache.store.Js5Store -import com.runetopic.cache.store.storage.IStorage +import com.runetopic.cache.store.storage.Storage import com.runetopic.cache.store.storage.js5.io.dat.DatFile -import com.runetopic.cache.store.storage.js5.io.dat.IDatFile +import com.runetopic.cache.store.storage.js5.io.dat.DatFileCodec import com.runetopic.cache.store.storage.js5.io.dat.sector.DatIndexSector -import com.runetopic.cache.store.storage.js5.io.idx.IIdxFile +import com.runetopic.cache.store.storage.js5.io.idx.IdxFileCodec import com.runetopic.cache.store.storage.js5.io.idx.IdxFile import java.io.FileNotFoundException import java.nio.file.Path @@ -28,9 +28,9 @@ import kotlin.io.path.exists internal class Js5DiskStorage( private val path: Path, private val parallel: Boolean -) : IStorage { - private var masterIdxFile: IIdxFile - private var datFile: IDatFile +) : Storage { + private var masterIdxFile: IdxFileCodec + private var datFile: DatFileCodec private var idxFiles = CopyOnWriteArrayList() private val logger = InlineLogger() diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt index 040161a..c5e6a6a 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFile.kt @@ -19,7 +19,7 @@ import java.nio.file.Path */ internal class DatFile( path: Path -) : IDatFile { +) : DatFileCodec { private val datFile: RandomAccessFile = RandomAccessFile(path.toFile(), "rw") private val datBuffer = ByteArray(datFile.length().toInt()) diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFileCodec.kt similarity index 86% rename from cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt rename to cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFileCodec.kt index 02669a0..0b9e4d8 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatFileCodec.kt @@ -7,7 +7,7 @@ import java.io.Closeable * @author Tyler Telis * @email */ -internal interface IDatFile : Closeable { +internal interface DatFileCodec : Closeable { fun decode(id: Int, referenceTable: ReferenceTable): ByteArray fun encode(data: ByteArray) } diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorCodec.kt similarity index 79% rename from cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt rename to cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorCodec.kt index e753dbc..5f6ec6e 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/IDatSector.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorCodec.kt @@ -3,7 +3,7 @@ package com.runetopic.cache.store.storage.js5.io.dat /** * @author Jordan Abraham */ -internal interface IDatSector { +internal interface DatSectorCodec { fun decode(): T fun encode(override: T): ByteArray } diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt index e8259a5..f7557bc 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatGroupSector.kt @@ -2,7 +2,7 @@ package com.runetopic.cache.store.storage.js5.io.dat.sector import com.runetopic.cache.extension.toByteBuffer import com.runetopic.cache.hierarchy.index.group.file.File -import com.runetopic.cache.store.storage.js5.io.dat.IDatSector +import com.runetopic.cache.store.storage.js5.io.dat.DatSectorCodec /** * @author Jordan Abraham @@ -13,7 +13,7 @@ data class DatGroupSector( val data: ByteArray, val count: Int, val groupId: Int -) : IDatSector> { +) : DatSectorCodec> { @OptIn(ExperimentalStdlibApi::class) override fun decode(): Map { diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt index 41d20f8..fc15848 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/dat/sector/DatIndexSector.kt @@ -6,9 +6,9 @@ import com.runetopic.cache.exception.ProtocolException import com.runetopic.cache.extension.* import com.runetopic.cache.hierarchy.index.Index import com.runetopic.cache.hierarchy.index.group.Group -import com.runetopic.cache.store.storage.js5.io.dat.IDatFile -import com.runetopic.cache.store.storage.js5.io.dat.IDatSector -import com.runetopic.cache.store.storage.js5.io.idx.IIdxFile +import com.runetopic.cache.store.storage.js5.io.dat.DatFileCodec +import com.runetopic.cache.store.storage.js5.io.dat.DatSectorCodec +import com.runetopic.cache.store.storage.js5.io.idx.IdxFileCodec import com.runetopic.cryptography.toWhirlpool import java.nio.ByteBuffer import java.util.zip.ZipException @@ -17,10 +17,10 @@ import java.util.zip.ZipException * @author Jordan Abraham */ internal data class DatIndexSector( - val datFile: IDatFile, - val idxFile: IIdxFile, + val datFile: DatFileCodec, + val idxFile: IdxFileCodec, val data: ByteArray -) : IDatSector { +) : DatSectorCodec { override fun decode(): Index { val decompressed = ContainerCodec.decompress(data) diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt index 3b447d8..5e507ce 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFile.kt @@ -19,7 +19,7 @@ import kotlin.io.path.fileSize internal class IdxFile( private val id: Int, private val path: Path -) : IIdxFile { +) : IdxFileCodec { private val idxFile: RandomAccessFile = RandomAccessFile(path.toFile(), "rw") private val idxBuffer = ByteArray(idxFile.length().toInt()).also { idxFile.readFully(it) } diff --git a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFileCodec.kt similarity index 87% rename from cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt rename to cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFileCodec.kt index d5daae4..771819e 100644 --- a/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IIdxFile.kt +++ b/cache/src/main/kotlin/com/runetopic/cache/store/storage/js5/io/idx/IdxFileCodec.kt @@ -7,7 +7,7 @@ import java.io.Closeable * @author Tyler Telis * @email */ -internal interface IIdxFile : Closeable { +internal interface IdxFileCodec : Closeable { fun decode(id: Int): ReferenceTable fun encode(data: ByteArray) fun validIndexCount(): Int From 9cd8c562fa745a540baa21ba5fec7cf90541a2be Mon Sep 17 00:00:00 2001 From: Tyler Telis Date: Wed, 5 Jan 2022 01:22:11 -0500 Subject: [PATCH 14/14] Fixing failing test --- .../test/kotlin/com/runetopic/cache/store/Js5StoreTest.kt | 4 ++++ .../cache/store/storage/js5/io/dat/DatSectorTest.kt | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 cache/src/test/kotlin/com/runetopic/cache/store/Js5StoreTest.kt diff --git a/cache/src/test/kotlin/com/runetopic/cache/store/Js5StoreTest.kt b/cache/src/test/kotlin/com/runetopic/cache/store/Js5StoreTest.kt new file mode 100644 index 0000000..11d0217 --- /dev/null +++ b/cache/src/test/kotlin/com/runetopic/cache/store/Js5StoreTest.kt @@ -0,0 +1,4 @@ +package com.runetopic.cache.store + +class Js5StoreTest { +} diff --git a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt index 44bc180..f0a1a6d 100644 --- a/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt +++ b/cache/src/test/kotlin/com/runetopic/cache/store/storage/js5/io/dat/DatSectorTest.kt @@ -23,8 +23,8 @@ class DatSectorTest { fun `test decode and encode`() { val indexSector = mockk() - every { indexSector.datFile } returns DatFile(Path.of("./main_file_cache.dat2")) - every { indexSector.idxFile } returns IdxFile(10, Path.of("./main_file_cache.idx10")) + every { indexSector.datFile } returns DatFile(Path.of("src", "test", "resources", "./main_file_cache.dat2")) + every { indexSector.idxFile } returns IdxFile(10, Path.of("src", "test", "resources", "./main_file_cache.idx10")) every { indexSector.data } returns byteArrayOf(0, 0, 0, 0, 30, 6, 0, 0, 0, 5, 1, 0, 1, 0, 1, 74, -4, 115, -83, 120, 17, 126, 78, 0, 0, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0) every { indexSector.decode() } answers { callOriginal() } @@ -37,6 +37,7 @@ class DatSectorTest { every { indexSector.decodeGroupFileIds(any(), any(), any(), any(), any(), any()) } answers { callOriginal() } every { indexSector.decodeGroupFileNameHashes(any(), any(), any(), any(), any(), any()) } answers { callOriginal() } + every { indexSector.calc(any(), any(), any()) } answers { callOriginal() } every { indexSector.encode(any()) } answers { callOriginal() } every { indexSector.encodeGroupIds(any(), any(), any()) } answers { callOriginal() } every { indexSector.encodeGroupNameHashes(any(), any(), any(), any()) } answers { callOriginal() } @@ -66,6 +67,7 @@ class DatSectorTest { verify(atLeast = 1) { indexSector.decodeGroupFileNameHashes(any(), any(), any(), any(), any(), any()) } verify(exactly = 1) { indexSector.encode(decoded) } + verify(atLeast = 1) { indexSector.calc(any(), any(), any()) } verify(atLeast = 1) { indexSector.encodeGroupIds(any(), any(), any()) } verify(atLeast = 1) { indexSector.encodeGroupNameHashes(any(), any(), any(), any()) } verify(atLeast = 1) { indexSector.encodeGroupCrcs(any(), any(), any()) }