diff --git a/File-Server/build.gradle b/File-Server/build.gradle deleted file mode 100644 index 0a41fa85e..000000000 --- a/File-Server/build.gradle +++ /dev/null @@ -1,37 +0,0 @@ -apply plugin: 'application' - -archivesBaseName = 'fileserver' - -mainClassName = 'js5server.JS5Server' - -group 'org.rs09' -version '1.0.0' - -repositories { - mavenCentral() -} - -dependencies { - - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2' - /* Networking */ - implementation "io.ktor:ktor-server-core:1.5.0" - implementation "io.ktor:ktor-network:1.5.0" - - // Cache ops - implementation 'com.displee:rs-cache-library:6.8' - - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' -} - -test { - useJUnitPlatform() -} - -jar { - manifest { - attributes 'Main-Class': 'js5server.JS5Server' - } - from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } } -} \ No newline at end of file diff --git a/File-Server/file-server.properties b/File-Server/file-server.properties deleted file mode 100644 index 50855dcac..000000000 --- a/File-Server/file-server.properties +++ /dev/null @@ -1,4 +0,0 @@ -revision=530 -subrevision=1 -port=43593 -cachePath=/home/ceik/IdeaProjects/rs09-remake/Server/data/cache \ No newline at end of file diff --git a/File-Server/src/main/kotlin/js5server/DataProvider.kt b/File-Server/src/main/kotlin/js5server/DataProvider.kt deleted file mode 100644 index 4ea50802f..000000000 --- a/File-Server/src/main/kotlin/js5server/DataProvider.kt +++ /dev/null @@ -1,18 +0,0 @@ -package js5server - -import com.displee.cache.CacheLibrary - -interface DataProvider { - fun data(index: Int, archive: Int): ByteArray? - - companion object { - operator fun invoke(cache: CacheLibrary) = object : DataProvider { - override fun data(index: Int, archive: Int) = - if (index == 255) { - cache.index255?.readArchiveSector(archive)?.data - } else { - cache.index(index).readArchiveSector(archive)?.data - } - } - } -} \ No newline at end of file diff --git a/File-Server/src/main/kotlin/js5server/FileServer.kt b/File-Server/src/main/kotlin/js5server/FileServer.kt deleted file mode 100644 index 8ea4ef5c1..000000000 --- a/File-Server/src/main/kotlin/js5server/FileServer.kt +++ /dev/null @@ -1,111 +0,0 @@ -package js5server - -import com.displee.cache.CacheLibrary -import io.ktor.utils.io.* -import js5server.ext.readUMedium -import kotlin.math.min - -class FileServer( - private val provider: DataProvider, - private val versionTable: ByteArray -) { - - /** - * Fulfills a request by sending the requested files data to the requester - */ - suspend fun fulfill(read: ByteReadChannel, write: ByteWriteChannel, prefetch: Boolean) { - val value = read.readUMedium() - val index = value shr 16 - val archive = value and 0xffff - val data = data(index, archive) ?: return println("Unable to fulfill request $index $archive $prefetch.") - if (index == 255 && archive == 255) { - serve255(write, data) - } else { - serve(write, index, archive, data, prefetch) - } - } - - /** - * @return data for an [index]'s [archive] file or [versionTable] when index and archive are both 255 - */ - fun data(index: Int, archive: Int): ByteArray? { - if (index == 255 && archive == 255) { - return versionTable - } - return provider.data(index, archive) - } - - /** - * Writes [source] [offset] [size] to [write] and starting at [headerSize] inserting a [SEPARATOR] every [split] bytes - */ - private suspend fun serve255(write: ByteWriteChannel, source: ByteArray) { - write.writeByte(255) - write.writeShort(255) - write.writeByte(0) - write.writeInt(source.size) - write.writeFully(source) - } - - /** - * Writes response header followed by the contents of [data] to [write] - */ - suspend fun serve(write: ByteWriteChannel, index: Int, archive: Int, data: ByteArray, prefetch: Boolean) { - val compression = data[0].toInt() - val size = getInt(data[1], data[2], data[3], data[4]) + if (compression != 0) 8 else 4 - write.writeByte(index) - write.writeShort(archive) - write.writeByte(if (prefetch) compression or 0x80 else compression) - serve(write, HEADER, data, OFFSET, size, SPLIT) - } - - /** - * Writes [source] [offset] [size] to [write] and starting at [headerSize] inserting a [SEPARATOR] every [split] bytes - */ - suspend fun serve(write: ByteWriteChannel, headerSize: Int, source: ByteArray, offset: Int, size: Int, split: Int) { - var length = min(size, split - headerSize) - write.writeFully(source, offset, length) - var written = length - while (written < size) { - write.writeByte(SEPARATOR) - - length = if (size - written < split) size - written else split - 1 - write.writeFully(source, written + offset, length) - written += length - } - } - - companion object { - - private fun getInt(b1: Byte, b2: Byte, b3: Byte, b4: Byte) = b1.toInt() shl 24 or (b2.toInt() and 0xff shl 16) or (b3.toInt() and 0xff shl 8) or (b4.toInt() and 0xff) - - private const val SEPARATOR = 255 - private const val HEADER = 4 - private const val SPLIT = 512 - private const val OFFSET = 1 - } - - /** - * Length of archive with [name] in [index] - */ - fun file(cacheLibrary: CacheLibrary, index: Int, name: String): Int { - val idx = cacheLibrary.index(index) - val archive = idx.archiveId(name) - if (archive == -1) { - return 0 - } - return (idx.readArchiveSector(archive)?.size ?: 2) - 2 - } - - /** - * Length of all archives in [index] - */ - fun archive(cache: CacheLibrary, index: Int): Int { - var total = 0 - val idx = cache.index(index) - idx.archiveIds().forEach { archive -> - total += idx.readArchiveSector(archive)?.size ?: 0 - } - total += cache.index255?.readArchiveSector(index)?.size ?: 0 - return total - } -} \ No newline at end of file diff --git a/File-Server/src/main/kotlin/js5server/JS5Net.kt b/File-Server/src/main/kotlin/js5server/JS5Net.kt deleted file mode 100644 index 16c6f3587..000000000 --- a/File-Server/src/main/kotlin/js5server/JS5Net.kt +++ /dev/null @@ -1,152 +0,0 @@ -package js5server - -import io.ktor.network.selector.* -import io.ktor.network.sockets.* -import io.ktor.utils.io.* -import js5server.ext.readMedium -import kotlinx.coroutines.* -import java.util.concurrent.Executors - -class JS5Net( - private val server: FileServer, - private val revision: Int, - private val subRevision: Int -) { - - private val exceptionHandler = CoroutineExceptionHandler { context, throwable -> - System.err.println("$context | $throwable") - } - - private lateinit var dispatcher: ExecutorCoroutineDispatcher - private var running = false - - /** - * Start the server and begin creating a new coroutine for every new connection accepted - * @param threads a fixed number or 0 to dynamically allocate based on need - */ - fun start(port: Int, threads: Int) = runBlocking { - val executor = if (threads == 0) Executors.newCachedThreadPool() else Executors.newFixedThreadPool(threads) - dispatcher = executor.asCoroutineDispatcher() - val selector = ActorSelectorManager(dispatcher) - val supervisor = SupervisorJob() - val scope = CoroutineScope(coroutineContext + supervisor + exceptionHandler) - with(scope) { - val server = aSocket(selector).tcp().bind(port = port) - running = true - while (running) { - val socket = server.accept() - launch(Dispatchers.IO) { - connect(socket) - } - } - } - } - - suspend fun connect(socket: Socket) { - val read = socket.openReadChannel() - val write = socket.openWriteChannel(autoFlush = true) - synchronise(read, write) - if (acknowledge(read, write)) { - readRequests(read, write) - } - } - - /** - * If the client is up-to-date and in the correct state send it the [prefetchKeys] list so it knows what indices are available to request - */ - private suspend fun synchronise(read: ByteReadChannel, write: ByteWriteChannel) { - val opcode = read.readByte().toInt() - println("Received $opcode") - if (opcode != HANDSHAKE_REQUEST) { - write.writeByte(REJECT_SESSION) - write.close() - return - } - - val revision = read.readInt() - val version = read.readInt() - println("Received ${this.revision}.${this.subRevision} | ${revision}.$version") - if (revision != this.revision || version != this.subRevision) { - write.writeByte(GAME_UPDATED) - write.close() - return - } - write.writeByte(0) - } - - /** - * Confirm the client got our message and is ready to start sending file requests - */ - private suspend fun acknowledge(read: ByteReadChannel, write: ByteWriteChannel): Boolean { - val opcode = read.readByte().toInt() - println("Received $opcode") - if (opcode != ACKNOWLEDGE) { - write.writeByte(REJECT_SESSION) - write.close() - return false - } - - return verify(read, write, ACKNOWLEDGE_ID) - } - - /** - * Confirm a session value send by the client is as the server [expected] - */ - private suspend fun verify(read: ByteReadChannel, write: ByteWriteChannel, expected: Int): Boolean { - val id = read.readMedium() - if (id != expected) { - write.writeByte(BAD_SESSION_ID) - write.close() - return false - } - return true - } - - private suspend fun readRequests(read: ByteReadChannel, write: ByteWriteChannel) = coroutineScope { - try { - while (isActive) { - readRequest(read, write) - } - } finally { - println("Client disconnected js5") - } - } - - /** - * Verify status updates and pass requests onto the [server] to fulfill - */ - private suspend fun readRequest(read: ByteReadChannel, write: ByteWriteChannel) { - when (val opcode = read.readByte().toInt()) { - STATUS_LOGGED_OUT, STATUS_LOGGED_IN -> verify(read, write, STATUS_ID) - PRIORITY_REQUEST, PREFETCH_REQUEST -> server.fulfill(read, write, opcode == PREFETCH_REQUEST) - else -> { - println("Closing write") - write.close() - } - } - } - - fun stop() { - running = false - dispatcher.close() - } - - companion object { - // Session ids - const val ACKNOWLEDGE_ID = 3 - const val STATUS_ID = 0 - - // Opcodes - const val PREFETCH_REQUEST = 0 - const val PRIORITY_REQUEST = 1 - const val HANDSHAKE_REQUEST = 15 - const val STATUS_LOGGED_IN = 2 - const val STATUS_LOGGED_OUT = 3 - const val ACKNOWLEDGE = 6 - - // Response codes - private const val GAME_UPDATED = 6 - private const val BAD_SESSION_ID = 10 - private const val REJECT_SESSION = 11 - } -} \ No newline at end of file diff --git a/File-Server/src/main/kotlin/js5server/JS5Server.kt b/File-Server/src/main/kotlin/js5server/JS5Server.kt deleted file mode 100644 index f9a88cd32..000000000 --- a/File-Server/src/main/kotlin/js5server/JS5Server.kt +++ /dev/null @@ -1,52 +0,0 @@ -package js5server - -import com.displee.cache.CacheLibrary -import java.io.File -import kotlin.concurrent.thread - -object JS5Server { - - @JvmStatic - fun main(args: Array) { - val start = System.currentTimeMillis() - val file = File("./file-server.properties") - if (!file.exists()) { - System.err.println("Unable to find server properties file.") - return - } - - println("Start up...") - - var revision = 530 - var subRevision = 1 - var port = 43593 - var threads = 0 - lateinit var cachePath: String - file.forEachLine { line -> - val (key, value) = line.split("=") - when (key) { - "revision" -> revision = value.toInt() - "subrevision" -> subRevision = value.toInt() - "port" -> port = value.toInt() - "threads" -> threads = value.toInt() - "cachePath" -> cachePath = value - } - } - - println("Loaded configuration.") - - val cache = CacheLibrary(cachePath) - val versionTable = cache.generateOldUkeys() - val fileServer = FileServer(DataProvider(cache), versionTable) - - println("Loaded cache revision $revision from $cachePath") - - val network = JS5Net(fileServer, revision, subRevision) - - println("Loading complete [${System.currentTimeMillis() - start}ms] / bound to 127.0.0.1:$port") - - val runtime = Runtime.getRuntime() - runtime.addShutdownHook(thread(start = false) { network.stop() }) - network.start(port, threads) - } -} \ No newline at end of file diff --git a/File-Server/src/main/kotlin/js5server/ext/JagexTypes.kt b/File-Server/src/main/kotlin/js5server/ext/JagexTypes.kt deleted file mode 100644 index ee356903e..000000000 --- a/File-Server/src/main/kotlin/js5server/ext/JagexTypes.kt +++ /dev/null @@ -1,88 +0,0 @@ -package js5server.ext - -import io.ktor.utils.io.* -import io.ktor.utils.io.core.* -import java.nio.ByteBuffer -import kotlin.text.toByteArray - -private val CHARSET = charset("UTF-8") - -suspend fun ByteReadChannel.readMedium(): Int { - return (readByte().toInt() shl 16) + (readByte().toInt() shl 8) + readByte().toInt() -} - -suspend fun ByteReadChannel.readUByte(): Int { - return readByte().toInt() and 0xff -} - -suspend fun ByteReadChannel.readUMedium(): Int { - return (readUByte() shl 16) + (readUByte() shl 8) + readUByte() -} - -fun ByteBuffer.putSmart(value: Int) { - if (value >= 128) { - putShort((value + 32768).toShort()) - } else { - put(value.toByte()) - } -} - -fun ByteBuffer.putJagexString(string: String) { - put(0) - put(string.toByteArray(CHARSET)) - put(0) -} - -fun ByteBuffer.putShortA(value: Int) { - put(((value shr 8) and 0xFFFF).toByte()) - put(((value + 128) and 0xFF).toByte()) -} - -fun ByteBuffer.putIntB(value: Int) { - put((value shr 16).toByte()) - put((value shr 24).toByte()) - put((value).toByte()) - put((value shr 8).toByte()) -} - -fun ByteBuffer.putUnsignedByteS(value: Byte) { - put(((value + 128) and 0xFF).toByte()) -} - -fun ByteBuffer.putUnsignedShort(value: Int) { - putShort(((value) and 0xFFFF).toShort()) -} - -fun BytePacketBuilder.writeUnsignedByteSubtract(value: Int) { - writeByte(((value + 128) and 0xFF).toByte()) -} - -fun BytePacketBuilder.writeUnsignedShort(value: Int) { - writeShort(((value) and 0xFFFF).toShort()) -} - -fun BytePacketBuilder.writeShortAdd(value: Int) { - writeByte((value shr 8).toByte()) - writeByte((value + 128).toByte()) -} - -fun BytePacketBuilder.writeSmart(value: Int) { - if (value >= 128) { - writeShort((value + 32768).toShort()) - } else { - writeByte(value.toByte()) - } -} - -fun BytePacketBuilder.writeIntME(value: Int) { - writeByte((value shr 16).toByte()) - writeByte((value shr 24).toByte()) - writeByte(value.toByte()) - writeByte((value shr 8).toByte()) -} - -fun BytePacketBuilder.writeVersionedString(value: String) { - writeByte(0) - writeText(value) - writeByte(0) -} \ No newline at end of file