A RuneTek3 client (377) that is deobfuscated, converted to Kotlin, and includes QoL improvements.
0

Configure Feed

Select the types of activity you want to include in your feed.

Add Kotlin Gradle plugin (migration Phase 1)

+4545 -4964
+10 -3
build.gradle
··· 1 - group 'com.jagex.runescape' 1 + plugins { 2 + id 'java' 3 + id 'idea' 4 + id 'org.jetbrains.kotlin.jvm' version '2.1.20' 5 + } 2 6 3 - apply plugin: 'java' 4 - apply plugin: 'idea' 7 + group = 'com.jagex.runescape' 5 8 6 9 repositories { 7 10 mavenCentral() ··· 11 14 implementation 'org.yaml:snakeyaml:2.0' 12 15 testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' 13 16 testImplementation 'org.testng:testng:7.9.0' 17 + } 18 + 19 + kotlin { 20 + jvmToolchain(21) 14 21 } 15 22 16 23 tasks.withType(Test).configureEach {
gradlew
+1
settings.gradle
··· 1 + rootProject.name = '377'
+6 -6
src/main/java/com/jagex/runescape/Game.java
··· 6338 6338 if (onDemandNode == null) 6339 6339 return; 6340 6340 if (onDemandNode.type == 0) { 6341 - Model.loadModelHeader(onDemandNode.buffer, onDemandNode.id); 6342 - if ((onDemandRequester.modelId(onDemandNode.id) & 0x62) != 0) { 6341 + Model.loadModelHeader(onDemandNode.buffer, onDemandNode.fileId); 6342 + if ((onDemandRequester.modelId(onDemandNode.fileId) & 0x62) != 0) { 6343 6343 redrawTabArea = true; 6344 6344 if (openChatboxWidgetId != -1 || dialogueId != -1) 6345 6345 redrawChatbox = true; ··· 6347 6347 } 6348 6348 if (onDemandNode.type == 1 && onDemandNode.buffer != null) 6349 6349 Animation.method236(onDemandNode.buffer); 6350 - if (onDemandNode.type == 2 && onDemandNode.id == nextSong && onDemandNode.buffer != null) 6350 + if (onDemandNode.type == 2 && onDemandNode.fileId == nextSong && onDemandNode.buffer != null) 6351 6351 saveMidi(songChanging, onDemandNode.buffer); 6352 6352 if (onDemandNode.type == 3 && loadingStage == 1) { 6353 6353 for (int i = 0; i < terrainData.length; i++) { 6354 - if (terrainDataIds[i] == onDemandNode.id) { 6354 + if (terrainDataIds[i] == onDemandNode.fileId) { 6355 6355 terrainData[i] = onDemandNode.buffer; 6356 6356 if (onDemandNode.buffer == null) 6357 6357 terrainDataIds[i] = -1; 6358 6358 break; 6359 6359 } 6360 - if (objectDataIds[i] != onDemandNode.id) 6360 + if (objectDataIds[i] != onDemandNode.fileId) 6361 6361 continue; 6362 6362 objectData[i] = onDemandNode.buffer; 6363 6363 if (onDemandNode.buffer == null) ··· 6366 6366 } 6367 6367 6368 6368 } 6369 - } while (onDemandNode.type != 93 || !onDemandRequester.method334(onDemandNode.id, false)); 6369 + } while (onDemandNode.type != 93 || !onDemandRequester.method334(onDemandNode.fileId, false)); 6370 6370 MapRegion.passiveRequestGameObjectModels(onDemandRequester, new Buffer(onDemandNode.buffer)); 6371 6371 } while (true); 6372 6372 }
-84
src/main/java/com/jagex/runescape/cache/Archive.java
··· 1 - package com.jagex.runescape.cache; 2 - 3 - import com.jagex.runescape.cache.bzip.BZip2Decompressor; 4 - import com.jagex.runescape.net.Buffer; 5 - 6 - public class Archive { 7 - 8 - public byte archiveBuffer[]; 9 - public int dataSize; 10 - public int nameHashes[]; 11 - public int uncompressedSizes[]; 12 - public int compressedSizes[]; 13 - public int startOffsets[]; 14 - public boolean compressed; 15 - 16 - /** 17 - * Creates the archive. 18 - * 19 - * @param dataBuffer 20 - * The buffer of the archive. 21 - */ 22 - public Archive(byte[] dataBuffer) { 23 - Buffer buffer = new Buffer(dataBuffer); 24 - int uncompressed = buffer.getMediumBE(); 25 - int compressed = buffer.getMediumBE(); 26 - if (compressed != uncompressed) { 27 - byte[] data = new byte[uncompressed]; 28 - BZip2Decompressor.decompress(data, uncompressed, dataBuffer, compressed, 6); 29 - archiveBuffer = data; 30 - buffer = new Buffer(archiveBuffer); 31 - this.compressed = true; 32 - } else { 33 - archiveBuffer = dataBuffer; 34 - this.compressed = false; 35 - } 36 - dataSize = buffer.getUnsignedShortBE(); 37 - nameHashes = new int[dataSize]; 38 - uncompressedSizes = new int[dataSize]; 39 - compressedSizes = new int[dataSize]; 40 - startOffsets = new int[dataSize]; 41 - int offset = buffer.currentPosition + dataSize * 10; 42 - for (int index = 0; index < dataSize; index++) { 43 - nameHashes[index] = buffer.getIntBE(); 44 - uncompressedSizes[index] = buffer.getMediumBE(); 45 - compressedSizes[index] = buffer.getMediumBE(); 46 - startOffsets[index] = offset; 47 - offset += compressedSizes[index]; 48 - } 49 - } 50 - 51 - /** 52 - * Gets a file by its name. 53 - * 54 - * @param file 55 - * The file name. 56 - * @return The file contents. 57 - */ 58 - public byte[] getFile(String file) { 59 - byte[] dataBuffer = null; 60 - int hash = 0; 61 - file = file.toUpperCase(); 62 - 63 - for (int pos = 0; pos < file.length(); pos++) 64 - hash = (hash * 61 + file.charAt(pos)) - 32; 65 - 66 - for (int index = 0; index < dataSize; index++) { 67 - if (nameHashes[index] == hash) { 68 - if (dataBuffer == null) 69 - dataBuffer = new byte[uncompressedSizes[index]]; 70 - 71 - if (!compressed) { 72 - BZip2Decompressor.decompress(dataBuffer, uncompressedSizes[index], archiveBuffer, compressedSizes[index], startOffsets[index]); 73 - } else { 74 - for (int pos = 0; pos < uncompressedSizes[index]; pos++) 75 - dataBuffer[pos] = archiveBuffer[startOffsets[index] + pos]; 76 - 77 - } 78 - return dataBuffer; 79 - } 80 - } 81 - return null; 82 - } 83 - 84 - }
+69
src/main/java/com/jagex/runescape/cache/Archive.kt
··· 1 + package com.jagex.runescape.cache 2 + 3 + import com.jagex.runescape.cache.bzip.BZip2Decompressor 4 + import com.jagex.runescape.net.Buffer 5 + 6 + class Archive(dataBuffer: ByteArray) { 7 + 8 + @JvmField var archiveBuffer: ByteArray 9 + @JvmField var dataSize: Int 10 + @JvmField var nameHashes: IntArray 11 + @JvmField var uncompressedSizes: IntArray 12 + @JvmField var compressedSizes: IntArray 13 + @JvmField var startOffsets: IntArray 14 + @JvmField var compressed: Boolean 15 + 16 + init { 17 + var buffer = Buffer(dataBuffer) 18 + val uncompressed = buffer.getMediumBE() 19 + val compressedSize = buffer.getMediumBE() 20 + if (compressedSize != uncompressed) { 21 + val data = ByteArray(uncompressed) 22 + BZip2Decompressor.decompress(data, uncompressed, dataBuffer, compressedSize, 6) 23 + archiveBuffer = data 24 + buffer = Buffer(archiveBuffer) 25 + this.compressed = true 26 + } else { 27 + archiveBuffer = dataBuffer 28 + this.compressed = false 29 + } 30 + dataSize = buffer.getUnsignedShortBE() 31 + nameHashes = IntArray(dataSize) 32 + uncompressedSizes = IntArray(dataSize) 33 + compressedSizes = IntArray(dataSize) 34 + startOffsets = IntArray(dataSize) 35 + var offset = buffer.currentPosition + dataSize * 10 36 + for (index in 0 until dataSize) { 37 + nameHashes[index] = buffer.getIntBE() 38 + uncompressedSizes[index] = buffer.getMediumBE() 39 + compressedSizes[index] = buffer.getMediumBE() 40 + startOffsets[index] = offset 41 + offset += compressedSizes[index] 42 + } 43 + } 44 + 45 + fun getFile(file: String): ByteArray? { 46 + var dataBuffer: ByteArray? = null 47 + var hash = 0 48 + val upper = file.uppercase() 49 + 50 + for (pos in upper.indices) 51 + hash = (hash * 61 + upper[pos].code) - 32 52 + 53 + for (index in 0 until dataSize) { 54 + if (nameHashes[index] == hash) { 55 + if (dataBuffer == null) 56 + dataBuffer = ByteArray(uncompressedSizes[index]) 57 + 58 + if (!compressed) { 59 + BZip2Decompressor.decompress(dataBuffer, uncompressedSizes[index], archiveBuffer, compressedSizes[index], startOffsets[index]) 60 + } else { 61 + for (pos in 0 until uncompressedSizes[index]) 62 + dataBuffer[pos] = archiveBuffer[startOffsets[index] + pos] 63 + } 64 + return dataBuffer 65 + } 66 + } 67 + return null 68 + } 69 + }
-51
src/main/java/com/jagex/runescape/cache/FileOperations.java
··· 1 - package com.jagex.runescape.cache;/* 2 - * Class: FileOperations.java 3 - * Reads a separate file. 4 - * */ 5 - 6 - import java.io.*; 7 - 8 - public class FileOperations { 9 - 10 - public static int TotalRead = 0; 11 - public static int TotalWrite = 0; 12 - public static int CompleteWrite = 0; 13 - 14 - public FileOperations() { 15 - } 16 - 17 - public static final byte[] ReadFile(String s) { 18 - try { 19 - File file = new File(s); 20 - int i = (int) file.length(); 21 - byte[] abyte0 = new byte[i]; 22 - DataInputStream datainputstream = new DataInputStream(new BufferedInputStream(new FileInputStream(s))); 23 - datainputstream.readFully(abyte0, 0, i); 24 - datainputstream.close(); 25 - TotalRead++; 26 - return abyte0; 27 - } catch(Exception exception) { 28 - System.out.println((new StringBuilder()).append("Read Error: ").append(s).toString()); 29 - } 30 - return null; 31 - } 32 - 33 - public static final void WriteFile(String s, byte[] abyte0) { 34 - try { 35 - (new File((new File(s)).getParent())).mkdirs(); 36 - FileOutputStream fileoutputstream = new FileOutputStream(s); 37 - fileoutputstream.write(abyte0, 0, abyte0.length); 38 - fileoutputstream.close(); 39 - TotalWrite++; 40 - CompleteWrite++; 41 - } catch(Throwable throwable) { 42 - System.out.println((new StringBuilder()).append("Write Error: ").append(s).toString()); 43 - } 44 - } 45 - 46 - public static boolean FileExists(String file) { 47 - File f = new File(file); 48 - return f.exists(); 49 - } 50 - 51 - }
+49
src/main/java/com/jagex/runescape/cache/FileOperations.kt
··· 1 + package com.jagex.runescape.cache 2 + 3 + import java.io.* 4 + 5 + class FileOperations { 6 + 7 + companion object { 8 + @JvmField var TotalRead: Int = 0 9 + @JvmField var TotalWrite: Int = 0 10 + @JvmField var CompleteWrite: Int = 0 11 + 12 + @JvmStatic 13 + fun ReadFile(s: String): ByteArray? { 14 + try { 15 + val file = File(s) 16 + val i = file.length().toInt() 17 + val abyte0 = ByteArray(i) 18 + val datainputstream = DataInputStream(BufferedInputStream(FileInputStream(s))) 19 + datainputstream.readFully(abyte0, 0, i) 20 + datainputstream.close() 21 + TotalRead++ 22 + return abyte0 23 + } catch (exception: Exception) { 24 + println("Read Error: $s") 25 + } 26 + return null 27 + } 28 + 29 + @JvmStatic 30 + fun WriteFile(s: String, abyte0: ByteArray) { 31 + try { 32 + File(File(s).parent).mkdirs() 33 + val fileoutputstream = FileOutputStream(s) 34 + fileoutputstream.write(abyte0, 0, abyte0.size) 35 + fileoutputstream.close() 36 + TotalWrite++ 37 + CompleteWrite++ 38 + } catch (throwable: Throwable) { 39 + println("Write Error: $s") 40 + } 41 + } 42 + 43 + @JvmStatic 44 + fun FileExists(file: String): Boolean { 45 + val f = File(file) 46 + return f.exists() 47 + } 48 + } 49 + }
-183
src/main/java/com/jagex/runescape/cache/Index.java
··· 1 - package com.jagex.runescape.cache; 2 - 3 - import java.io.IOException; 4 - import java.io.RandomAccessFile; 5 - 6 - public class Index { 7 - 8 - public static byte buffer[] = new byte[520]; 9 - public RandomAccessFile dataFile; 10 - public RandomAccessFile indexFile; 11 - public int type; 12 - public int maxSize; 13 - 14 - public Index(int _type, int _maxSize, RandomAccessFile dataFile, RandomAccessFile indexFile) { 15 - type = _type; 16 - this.dataFile = dataFile; 17 - this.indexFile = indexFile; 18 - maxSize = _maxSize; 19 - } 20 - 21 - public synchronized byte[] get(int index) { 22 - try { 23 - seek(indexFile, index * 6); 24 - int fileSize; 25 - for (int indexPart = 0; indexPart < 6; indexPart += fileSize) { 26 - fileSize = indexFile.read(buffer, indexPart, 6 - indexPart); 27 - if (fileSize == -1) 28 - return null; 29 - } 30 - 31 - fileSize = ((buffer[0] & 0xff) << 16) + ((buffer[1] & 0xff) << 8) + (buffer[2] & 0xff); 32 - int fileBlock = ((buffer[3] & 0xff) << 16) + ((buffer[4] & 0xff) << 8) + (buffer[5] & 0xff); 33 - if (fileSize < 0 || fileSize > maxSize) 34 - return null; 35 - if (fileBlock <= 0 || fileBlock > dataFile.length() / 520L) 36 - return null; 37 - byte fileBuffer[] = new byte[fileSize]; 38 - int read = 0; 39 - for (int cycle = 0; read < fileSize; cycle++) { 40 - if (fileBlock == 0) 41 - return null; 42 - seek(dataFile, fileBlock * 520); 43 - int size = 0; 44 - int remaining = fileSize - read; 45 - if (remaining > 512) 46 - remaining = 512; 47 - int nextFileId; 48 - for (; size < remaining + 8; size += nextFileId) { 49 - nextFileId = dataFile.read(buffer, size, (remaining + 8) - size); 50 - if (nextFileId == -1) 51 - return null; 52 - } 53 - 54 - nextFileId = ((buffer[0] & 0xff) << 8) + (buffer[1] & 0xff); 55 - int currentPartId = ((buffer[2] & 0xff) << 8) + (buffer[3] & 0xff); 56 - int nextBlockId = ((buffer[4] & 0xff) << 16) + ((buffer[5] & 0xff) << 8) 57 - + (buffer[6] & 0xff); 58 - int nextStoreId = buffer[7] & 0xff; 59 - if (nextFileId != index || currentPartId != cycle || nextStoreId != type) 60 - return null; 61 - if (nextBlockId < 0 || nextBlockId > dataFile.length() / 520L) 62 - return null; 63 - for (int offset = 0; offset < remaining; offset++) 64 - fileBuffer[read++] = buffer[offset + 8]; 65 - 66 - fileBlock = nextBlockId; 67 - } 68 - 69 - return fileBuffer; 70 - } catch (IOException _ex) { 71 - return null; 72 - } 73 - } 74 - 75 - public synchronized boolean put(int len, byte buf[], int id) { 76 - boolean success = put(buf, id, true, len); 77 - if (!success) 78 - success = put(buf, id, false, len); 79 - return success; 80 - } 81 - 82 - public synchronized boolean put(byte buf[], int index, boolean overwrite, int len) { 83 - try { 84 - int sector; 85 - if (overwrite) { 86 - seek(indexFile, index * 6); 87 - int lenght; 88 - for (int offset = 0; offset < 6; offset += lenght) { 89 - lenght = indexFile.read(buffer, offset, 6 - offset); 90 - if (lenght == -1) 91 - return false; 92 - } 93 - 94 - sector = ((buffer[3] & 0xff) << 16) + ((buffer[4] & 0xff) << 8) + (buffer[5] & 0xff); 95 - if (sector <= 0 || sector > dataFile.length() / 520L) 96 - return false; 97 - } else { 98 - sector = (int) ((dataFile.length() + 519L) / 520L); 99 - if (sector == 0) 100 - sector = 1; 101 - } 102 - buffer[0] = (byte) (len >> 16); 103 - buffer[1] = (byte) (len >> 8); 104 - buffer[2] = (byte) len; 105 - buffer[3] = (byte) (sector >> 16); 106 - buffer[4] = (byte) (sector >> 8); 107 - buffer[5] = (byte) sector; 108 - seek(indexFile, index * 6); 109 - indexFile.write(buffer, 0, 6); 110 - int written = 0; 111 - for (int chunk = 0; written < len; chunk++) { 112 - int nextSector = 0; 113 - if (overwrite) { 114 - seek(dataFile, sector * 520); 115 - int pos; 116 - int tmp; 117 - for (pos = 0; pos < 8; pos += tmp) { 118 - tmp = dataFile.read(buffer, pos, 8 - pos); 119 - if (tmp == -1) 120 - break; 121 - } 122 - 123 - if (pos == 8) { 124 - int _id = ((buffer[0] & 0xff) << 8) + (buffer[1] & 0xff); 125 - int _chunk = ((buffer[2] & 0xff) << 8) + (buffer[3] & 0xff); 126 - nextSector = ((buffer[4] & 0xff) << 16) + ((buffer[5] & 0xff) << 8) 127 - + (buffer[6] & 0xff); 128 - int _type = buffer[7] & 0xff; 129 - if (_id != index || _chunk != chunk || _type != type) 130 - return false; 131 - if (nextSector < 0 || nextSector > dataFile.length() / 520L) 132 - return false; 133 - } 134 - } 135 - if (nextSector == 0) { 136 - overwrite = false; 137 - nextSector = (int) ((dataFile.length() + 519L) / 520L); 138 - if (nextSector == 0) 139 - nextSector++; 140 - if (nextSector == sector) 141 - nextSector++; 142 - } 143 - if (len - written <= 512) 144 - nextSector = 0; 145 - buffer[0] = (byte) (index >> 8); 146 - buffer[1] = (byte) index; 147 - buffer[2] = (byte) (chunk >> 8); 148 - buffer[3] = (byte) chunk; 149 - buffer[4] = (byte) (nextSector >> 16); 150 - buffer[5] = (byte) (nextSector >> 8); 151 - buffer[6] = (byte) nextSector; 152 - buffer[7] = (byte) type; 153 - seek(dataFile, sector * 520); 154 - dataFile.write(buffer, 0, 8); 155 - int sectorLen = len - written; 156 - if (sectorLen > 512) 157 - sectorLen = 512; 158 - dataFile.write(buf, written, sectorLen); 159 - written += sectorLen; 160 - sector = nextSector; 161 - } 162 - 163 - return true; 164 - } catch (IOException _ex) { 165 - return false; 166 - } 167 - } 168 - 169 - public synchronized void seek(RandomAccessFile file, int position) throws IOException { 170 - if (position < 0 || position > 0x3c00000) { 171 - System.out.println("Badseek - coord:" + position + " len:" + file.length()); 172 - position = 0x3c00000; 173 - try { 174 - Thread.sleep(1000L); 175 - } catch (Exception _ex) { 176 - } 177 - } 178 - file.seek(position); 179 - } 180 - 181 - 182 - 183 - }
+188
src/main/java/com/jagex/runescape/cache/Index.kt
··· 1 + package com.jagex.runescape.cache 2 + 3 + import java.io.IOException 4 + import java.io.RandomAccessFile 5 + 6 + class Index( 7 + @JvmField var type: Int, 8 + @JvmField var maxSize: Int, 9 + @JvmField var dataFile: RandomAccessFile, 10 + @JvmField var indexFile: RandomAccessFile 11 + ) { 12 + 13 + @Synchronized 14 + fun get(index: Int): ByteArray? { 15 + try { 16 + seek(indexFile, index * 6) 17 + var fileSize: Int 18 + var indexPart = 0 19 + while (indexPart < 6) { 20 + fileSize = indexFile.read(buffer, indexPart, 6 - indexPart) 21 + if (fileSize == -1) 22 + return null 23 + indexPart += fileSize 24 + } 25 + 26 + fileSize = ((buffer[0].toInt() and 0xff) shl 16) + ((buffer[1].toInt() and 0xff) shl 8) + (buffer[2].toInt() and 0xff) 27 + var fileBlock = ((buffer[3].toInt() and 0xff) shl 16) + ((buffer[4].toInt() and 0xff) shl 8) + (buffer[5].toInt() and 0xff) 28 + if (fileSize < 0 || fileSize > maxSize) 29 + return null 30 + if (fileBlock <= 0 || fileBlock > dataFile.length() / 520L) 31 + return null 32 + val fileBuffer = ByteArray(fileSize) 33 + var read = 0 34 + var cycle = 0 35 + while (read < fileSize) { 36 + if (fileBlock == 0) 37 + return null 38 + seek(dataFile, fileBlock * 520) 39 + var size = 0 40 + var remaining = fileSize - read 41 + if (remaining > 512) 42 + remaining = 512 43 + while (size < remaining + 8) { 44 + val nextFileId = dataFile.read(buffer, size, (remaining + 8) - size) 45 + if (nextFileId == -1) 46 + return null 47 + size += nextFileId 48 + } 49 + 50 + val nextFileId = ((buffer[0].toInt() and 0xff) shl 8) + (buffer[1].toInt() and 0xff) 51 + val currentPartId = ((buffer[2].toInt() and 0xff) shl 8) + (buffer[3].toInt() and 0xff) 52 + val nextBlockId = ((buffer[4].toInt() and 0xff) shl 16) + ((buffer[5].toInt() and 0xff) shl 8) + (buffer[6].toInt() and 0xff) 53 + val nextStoreId = buffer[7].toInt() and 0xff 54 + if (nextFileId != index || currentPartId != cycle || nextStoreId != type) 55 + return null 56 + if (nextBlockId < 0 || nextBlockId > dataFile.length() / 520L) 57 + return null 58 + for (offset in 0 until remaining) 59 + fileBuffer[read++] = buffer[offset + 8] 60 + 61 + fileBlock = nextBlockId 62 + cycle++ 63 + } 64 + 65 + return fileBuffer 66 + } catch (_ex: IOException) { 67 + return null 68 + } 69 + } 70 + 71 + @Synchronized 72 + fun put(len: Int, buf: ByteArray, id: Int): Boolean { 73 + var success = put(buf, id, true, len) 74 + if (!success) 75 + success = put(buf, id, false, len) 76 + return success 77 + } 78 + 79 + @Synchronized 80 + fun put(buf: ByteArray, index: Int, overwrite: Boolean, len: Int): Boolean { 81 + var overwrite = overwrite 82 + try { 83 + var sector: Int 84 + if (overwrite) { 85 + seek(indexFile, index * 6) 86 + var offset = 0 87 + while (offset < 6) { 88 + val lenght = indexFile.read(buffer, offset, 6 - offset) 89 + if (lenght == -1) 90 + return false 91 + offset += lenght 92 + } 93 + 94 + sector = ((buffer[3].toInt() and 0xff) shl 16) + ((buffer[4].toInt() and 0xff) shl 8) + (buffer[5].toInt() and 0xff) 95 + if (sector <= 0 || sector > dataFile.length() / 520L) 96 + return false 97 + } else { 98 + sector = ((dataFile.length() + 519L) / 520L).toInt() 99 + if (sector == 0) 100 + sector = 1 101 + } 102 + buffer[0] = (len shr 16).toByte() 103 + buffer[1] = (len shr 8).toByte() 104 + buffer[2] = len.toByte() 105 + buffer[3] = (sector shr 16).toByte() 106 + buffer[4] = (sector shr 8).toByte() 107 + buffer[5] = sector.toByte() 108 + seek(indexFile, index * 6) 109 + indexFile.write(buffer, 0, 6) 110 + var written = 0 111 + var chunk = 0 112 + while (written < len) { 113 + var nextSector = 0 114 + if (overwrite) { 115 + seek(dataFile, sector * 520) 116 + var pos = 0 117 + while (pos < 8) { 118 + val tmp = dataFile.read(buffer, pos, 8 - pos) 119 + if (tmp == -1) 120 + break 121 + pos += tmp 122 + } 123 + 124 + if (pos == 8) { 125 + val _id = ((buffer[0].toInt() and 0xff) shl 8) + (buffer[1].toInt() and 0xff) 126 + val _chunk = ((buffer[2].toInt() and 0xff) shl 8) + (buffer[3].toInt() and 0xff) 127 + nextSector = ((buffer[4].toInt() and 0xff) shl 16) + ((buffer[5].toInt() and 0xff) shl 8) + (buffer[6].toInt() and 0xff) 128 + val _type = buffer[7].toInt() and 0xff 129 + if (_id != index || _chunk != chunk || _type != type) 130 + return false 131 + if (nextSector < 0 || nextSector > dataFile.length() / 520L) 132 + return false 133 + } 134 + } 135 + if (nextSector == 0) { 136 + overwrite = false 137 + nextSector = ((dataFile.length() + 519L) / 520L).toInt() 138 + if (nextSector == 0) 139 + nextSector++ 140 + if (nextSector == sector) 141 + nextSector++ 142 + } 143 + if (len - written <= 512) 144 + nextSector = 0 145 + buffer[0] = (index shr 8).toByte() 146 + buffer[1] = index.toByte() 147 + buffer[2] = (chunk shr 8).toByte() 148 + buffer[3] = chunk.toByte() 149 + buffer[4] = (nextSector shr 16).toByte() 150 + buffer[5] = (nextSector shr 8).toByte() 151 + buffer[6] = nextSector.toByte() 152 + buffer[7] = type.toByte() 153 + seek(dataFile, sector * 520) 154 + dataFile.write(buffer, 0, 8) 155 + var sectorLen = len - written 156 + if (sectorLen > 512) 157 + sectorLen = 512 158 + dataFile.write(buf, written, sectorLen) 159 + written += sectorLen 160 + sector = nextSector 161 + chunk++ 162 + } 163 + 164 + return true 165 + } catch (_ex: IOException) { 166 + return false 167 + } 168 + } 169 + 170 + @Synchronized 171 + @Throws(IOException::class) 172 + fun seek(file: RandomAccessFile, position: Int) { 173 + var position = position 174 + if (position < 0 || position > 0x3c00000) { 175 + println("Badseek - coord:$position len:${file.length()}") 176 + position = 0x3c00000 177 + try { 178 + Thread.sleep(1000L) 179 + } catch (_ex: Exception) { 180 + } 181 + } 182 + file.seek(position.toLong()) 183 + } 184 + 185 + companion object { 186 + @JvmField var buffer: ByteArray = ByteArray(520) 187 + } 188 + }
-829
src/main/java/com/jagex/runescape/cache/cfg/ChatCensor.java
··· 1 - package com.jagex.runescape.cache.cfg; 2 - 3 - import com.jagex.runescape.cache.Archive; 4 - import com.jagex.runescape.net.Buffer; 5 - 6 - public class ChatCensor { 7 - 8 - 9 - private static int[] fragments; 10 - private static char[][] badWords; 11 - private static byte[][][] badBytes; 12 - private static char[][] domains; 13 - private static char[][] topLevelDomains; 14 - private static int[] topLevelDomainsType; 15 - private static final String[] exceptions = {"cook", "cook's", "cooks", "seeks", "sheet", "woop", "woops", 16 - "faq", "noob", "noobs"}; 17 - 18 - public static void load(Archive archive) { 19 - Buffer fragmentsEnc = new Buffer(archive.getFile("fragmentsenc.txt")); 20 - Buffer badEnc = new Buffer(archive.getFile("badenc.txt")); 21 - Buffer domainEnc = new Buffer(archive.getFile("domainenc.txt")); 22 - Buffer topLevelDomainsBuffer = new Buffer(archive.getFile("tldlist.txt")); 23 - loadDictionaries(fragmentsEnc, badEnc, domainEnc, topLevelDomainsBuffer); 24 - } 25 - 26 - private static void loadDictionaries(Buffer fragmentsEnc, Buffer badEnc, 27 - Buffer domainEnc, Buffer topLevelDomainsBuffer) { 28 - loadBadEnc(badEnc); 29 - loadDomainEnc(domainEnc); 30 - loadFragmentsEnc(fragmentsEnc); 31 - loadTopLevelDomains(topLevelDomainsBuffer); 32 - } 33 - 34 - private static void loadTopLevelDomains(Buffer buffer) { 35 - int length = buffer.getIntBE(); 36 - topLevelDomains = new char[length][]; 37 - topLevelDomainsType = new int[length]; 38 - for (int index = 0; index < length; index++) { 39 - topLevelDomainsType[index] = buffer.getUnsignedByte(); 40 - char[] topLevelDomain = new char[buffer.getUnsignedByte()]; 41 - for (int character = 0; character < topLevelDomain.length; character++) 42 - topLevelDomain[character] = (char) buffer.getUnsignedByte(); 43 - 44 - topLevelDomains[index] = topLevelDomain; 45 - } 46 - 47 - } 48 - 49 - private static void loadBadEnc(Buffer buffer) { 50 - int length = buffer.getIntBE(); 51 - badWords = new char[length][]; 52 - badBytes = new byte[length][][]; 53 - loadBadWords(buffer, badWords, badBytes); 54 - 55 - } 56 - 57 - private static void loadDomainEnc(Buffer buffer) { 58 - int length = buffer.getIntBE(); 59 - domains = new char[length][]; 60 - loadDomains(buffer, domains); 61 - } 62 - 63 - private static void loadFragmentsEnc(Buffer buffer) { 64 - fragments = new int[buffer.getIntBE()]; 65 - for (int index = 0; index < fragments.length; index++) 66 - fragments[index] = buffer.getUnsignedShortBE(); 67 - 68 - } 69 - 70 - private static void loadBadWords(Buffer buffer, char[][] badWords, byte[][][] badBytes) { 71 - for (int index = 0; index < badWords.length; index++) { 72 - char[] badWord = new char[buffer.getUnsignedByte()]; 73 - for (int character = 0; character < badWord.length; character++) 74 - badWord[character] = (char) buffer.getUnsignedByte(); 75 - 76 - badWords[index] = badWord; 77 - byte[][] badByte = new byte[buffer.getUnsignedByte()][2]; 78 - for (int l = 0; l < badByte.length; l++) { 79 - badByte[l][0] = (byte) buffer.getUnsignedByte(); 80 - badByte[l][1] = (byte) buffer.getUnsignedByte(); 81 - } 82 - 83 - if (badByte.length > 0) 84 - badBytes[index] = badByte; 85 - } 86 - 87 - } 88 - 89 - private static void loadDomains(Buffer buffer, char[][] cs) { 90 - for (int index = 0; index < cs.length; index++) { 91 - char[] domainEnc = new char[buffer.getUnsignedByte()]; 92 - for (int character = 0; character < domainEnc.length; character++) 93 - domainEnc[character] = (char) buffer.getUnsignedByte(); 94 - 95 - cs[index] = domainEnc; 96 - } 97 - 98 - } 99 - 100 - private static void formatLegalCharacters(char[] characters) { 101 - int character = 0; 102 - for (int index = 0; index < characters.length; index++) { 103 - if (isLegalCharacter(characters[index])) 104 - characters[character] = characters[index]; 105 - else 106 - characters[character] = ' '; 107 - if (character == 0 || characters[character] != ' ' || characters[character - 1] != ' ') 108 - character++; 109 - } 110 - 111 - for (int characterIndex = character; characterIndex < characters.length; characterIndex++) 112 - characters[characterIndex] = ' '; 113 - 114 - } 115 - 116 - private static boolean isLegalCharacter(char character) { 117 - return character >= ' ' && character <= '\177' || character == ' ' || character == '\n' || character == '\t' || character == '\243' || character == '\u20AC'; 118 - } 119 - 120 - public static String censorString(String string) { 121 - char[] censoredString = string.toCharArray(); 122 - formatLegalCharacters(censoredString); 123 - String censoredStringTrimmed = (new String(censoredString)).trim(); 124 - censoredString = censoredStringTrimmed.toLowerCase().toCharArray(); 125 - String censoredStringLowercased = censoredStringTrimmed.toLowerCase(); 126 - method391(censoredString); 127 - method386(censoredString); 128 - method387(censoredString); 129 - method400(censoredString); 130 - for (String exception : exceptions) { 131 - for (int index = -1; (index = censoredStringLowercased.indexOf(exception, index + 1)) != -1; ) { 132 - char[] ac1 = exception.toCharArray(); 133 - System.arraycopy(ac1, 0, censoredString, index, ac1.length); 134 - 135 - } 136 - 137 - } 138 - 139 - method384(censoredString, censoredStringTrimmed.toCharArray()); 140 - method385(censoredString); 141 - return (new String(censoredString)).trim(); 142 - } 143 - 144 - private static void method384(char[] ac, char[] ac1) { 145 - for (int j = 0; j < ac1.length; j++) 146 - if (ac[j] != '*' && method408(ac1[j])) 147 - ac[j] = ac1[j]; 148 - 149 - } 150 - 151 - private static void method385(char[] ac) { 152 - boolean flag = true; 153 - for (int j = 0; j < ac.length; j++) { 154 - char c = ac[j]; 155 - if (method405(c)) { 156 - if (flag) { 157 - if (method407(c)) 158 - flag = false; 159 - } else if (method408(c)) 160 - ac[j] = (char) ((c + 97) - 65); 161 - } else { 162 - flag = true; 163 - } 164 - } 165 - } 166 - 167 - private static void method386(char[] ac) { 168 - for (int j = 0; j < 2; j++) { 169 - for (int k = badWords.length - 1; k >= 0; k--) 170 - method395(badBytes[k], badWords[k], ac); 171 - 172 - } 173 - 174 - } 175 - 176 - private static void method387(char[] ac) { 177 - char[] ac1 = ac.clone(); 178 - char[] ac2 = {'(', 'a', ')'}; 179 - method395(null, ac2, ac1); 180 - char[] ac3 = ac.clone(); 181 - char[] ac4 = {'d', 'o', 't'}; 182 - method395(null, ac4, ac3); 183 - for (int j = domains.length - 1; j >= 0; j--) 184 - method388(ac, ac3, ac1, domains[j]); 185 - 186 - } 187 - 188 - private static void method388(char[] ac, char[] ac1, char[] ac2, char[] ac3) { 189 - if (ac3.length > ac.length) 190 - return; 191 - int j; 192 - for (int k = 0; k <= ac.length - ac3.length; k += j) { 193 - int l = k; 194 - int i1 = 0; 195 - j = 1; 196 - while (l < ac.length) { 197 - int j1; 198 - char c = ac[l]; 199 - char c1 = '\0'; 200 - if (l + 1 < ac.length) 201 - c1 = ac[l + 1]; 202 - if (i1 < ac3.length && (j1 = method397(c, ac3[i1], c1)) > 0) { 203 - l += j1; 204 - i1++; 205 - continue; 206 - } 207 - if (i1 == 0) 208 - break; 209 - if ((j1 = method397(c, ac3[i1 - 1], c1)) > 0) { 210 - l += j1; 211 - if (i1 == 1) 212 - j++; 213 - continue; 214 - } 215 - if (i1 >= ac3.length || !method403(c)) 216 - break; 217 - l++; 218 - } 219 - if (i1 >= ac3.length) { 220 - boolean flag1 = false; 221 - int k1 = method389(ac, ac2, k); 222 - int l1 = method390(ac1, l - 1, ac); 223 - if (k1 > 2 || l1 > 2) 224 - flag1 = true; 225 - if (flag1) { 226 - for (int i2 = k; i2 < l; i2++) 227 - ac[i2] = '*'; 228 - 229 - } 230 - } 231 - } 232 - 233 - } 234 - 235 - private static int method389(char[] ac, char[] ac1, int i) { 236 - if (i == 0) 237 - return 2; 238 - for (int j = i - 1; j >= 0; j--) { 239 - if (!method403(ac[j])) 240 - break; 241 - if (ac[j] == '@') 242 - return 3; 243 - } 244 - 245 - int k = 0; 246 - for (int l = i - 1; l >= 0; l--) { 247 - if (!method403(ac1[l])) 248 - break; 249 - if (ac1[l] == '*') 250 - k++; 251 - } 252 - 253 - if (k >= 3) 254 - return 4; 255 - return !method403(ac[i - 1]) ? 0 : 1; 256 - } 257 - 258 - private static int method390(char[] ac, int j, char[] ac1) { 259 - if (j + 1 == ac1.length) 260 - return 2; 261 - for (int k = j + 1; k < ac1.length; k++) { 262 - if (!method403(ac1[k])) 263 - break; 264 - if (ac1[k] == '.' || ac1[k] == ',') 265 - return 3; 266 - } 267 - 268 - int l = 0; 269 - for (int i1 = j + 1; i1 < ac1.length; i1++) { 270 - if (!method403(ac[i1])) 271 - break; 272 - if (ac[i1] == '*') 273 - l++; 274 - } 275 - 276 - if (l >= 3) 277 - return 4; 278 - return !method403(ac1[j + 1]) ? 0 : 1; 279 - } 280 - 281 - private static void method391(char[] ac) { 282 - char[] ac1 = ac.clone(); 283 - char[] ac2 = {'d', 'o', 't'}; 284 - method395(null, ac2, ac1); 285 - char[] ac3 = ac.clone(); 286 - char[] ac4 = {'s', 'l', 'a', 's', 'h'}; 287 - method395(null, ac4, ac3); 288 - for (int j = 0; j < topLevelDomains.length; j++) 289 - method392(ac, ac1, topLevelDomainsType[j], topLevelDomains[j], ac3); 290 - 291 - } 292 - 293 - private static void method392(char[] ac, char[] ac1, int i, char[] ac2, char[] ac3) { 294 - if (ac2.length > ac.length) 295 - return; 296 - int j; 297 - for (int k = 0; k <= ac.length - ac2.length; k += j) { 298 - int l = k; 299 - int i1 = 0; 300 - j = 1; 301 - while (l < ac.length) { 302 - int j1; 303 - char c = ac[l]; 304 - char c1 = '\0'; 305 - if (l + 1 < ac.length) 306 - c1 = ac[l + 1]; 307 - if (i1 < ac2.length && (j1 = method397(c, ac2[i1], c1)) > 0) { 308 - l += j1; 309 - i1++; 310 - continue; 311 - } 312 - if (i1 == 0) 313 - break; 314 - if ((j1 = method397(c, ac2[i1 - 1], c1)) > 0) { 315 - l += j1; 316 - if (i1 == 1) 317 - j++; 318 - continue; 319 - } 320 - if (i1 >= ac2.length || !method403(c)) 321 - break; 322 - l++; 323 - } 324 - if (i1 >= ac2.length) { 325 - boolean flag1 = false; 326 - int k1 = method393(ac1, k, ac); 327 - int l1 = method394(ac3, l - 1, ac); 328 - if (i == 1 && k1 > 0 && l1 > 0) 329 - flag1 = true; 330 - if (i == 2 && (k1 > 2 && l1 > 0 || k1 > 0 && l1 > 2)) 331 - flag1 = true; 332 - if (i == 3 && k1 > 0 && l1 > 2) 333 - flag1 = true; 334 - //boolean _tmp = i == 3 && k1 > 2 && l1 > 0; 335 - if (flag1) { 336 - int i2 = k; 337 - int j2 = l - 1; 338 - if (k1 > 2) { 339 - if (k1 == 4) { 340 - boolean flag2 = false; 341 - for (int l2 = i2 - 1; l2 >= 0; l2--) 342 - if (flag2) { 343 - if (ac1[l2] != '*') 344 - break; 345 - i2 = l2; 346 - } else if (ac1[l2] == '*') { 347 - i2 = l2; 348 - flag2 = true; 349 - } 350 - 351 - } 352 - boolean flag3 = false; 353 - for (int i3 = i2 - 1; i3 >= 0; i3--) 354 - if (flag3) { 355 - if (method403(ac[i3])) 356 - break; 357 - i2 = i3; 358 - } else if (!method403(ac[i3])) { 359 - flag3 = true; 360 - i2 = i3; 361 - } 362 - 363 - } 364 - if (l1 > 2) { 365 - if (l1 == 4) { 366 - boolean flag4 = false; 367 - for (int j3 = j2 + 1; j3 < ac.length; j3++) 368 - if (flag4) { 369 - if (ac3[j3] != '*') 370 - break; 371 - j2 = j3; 372 - } else if (ac3[j3] == '*') { 373 - j2 = j3; 374 - flag4 = true; 375 - } 376 - 377 - } 378 - boolean flag5 = false; 379 - for (int k3 = j2 + 1; k3 < ac.length; k3++) 380 - if (flag5) { 381 - if (method403(ac[k3])) 382 - break; 383 - j2 = k3; 384 - } else if (!method403(ac[k3])) { 385 - flag5 = true; 386 - j2 = k3; 387 - } 388 - 389 - } 390 - for (int k2 = i2; k2 <= j2; k2++) 391 - ac[k2] = '*'; 392 - 393 - } 394 - } 395 - } 396 - } 397 - 398 - private static int method393(char[] ac, int i, char[] ac1) { 399 - if (i == 0) 400 - return 2; 401 - for (int k = i - 1; k >= 0; k--) { 402 - if (!method403(ac1[k])) 403 - break; 404 - if (ac1[k] == ',' || ac1[k] == '.') 405 - return 3; 406 - } 407 - 408 - int l = 0; 409 - for (int i1 = i - 1; i1 >= 0; i1--) { 410 - if (!method403(ac[i1])) 411 - break; 412 - if (ac[i1] == '*') 413 - l++; 414 - } 415 - 416 - if (l >= 3) 417 - return 4; 418 - return !method403(ac1[i - 1]) ? 0 : 1; 419 - } 420 - 421 - private static int method394(char[] ac, int i, char[] ac1) { 422 - if (i + 1 == ac1.length) 423 - return 2; 424 - for (int l = i + 1; l < ac1.length; l++) { 425 - if (!method403(ac1[l])) 426 - break; 427 - if (ac1[l] == '\\' || ac1[l] == '/') 428 - return 3; 429 - } 430 - 431 - int i1 = 0; 432 - for (int j1 = i + 1; j1 < ac1.length; j1++) { 433 - if (!method403(ac[j1])) 434 - break; 435 - if (ac[j1] == '*') 436 - i1++; 437 - } 438 - 439 - if (i1 >= 5) 440 - return 4; 441 - return !method403(ac1[i + 1]) ? 0 : 1; 442 - } 443 - 444 - private static void method395(byte[][] abyte0, char[] ac, char[] ac1) { 445 - if (ac.length > ac1.length) 446 - return; 447 - int j; 448 - for (int k = 0; k <= ac1.length - ac.length; k += j) { 449 - int l = k; 450 - int i1 = 0; 451 - int j1 = 0; 452 - j = 1; 453 - boolean flag1 = false; 454 - boolean flag2 = false; 455 - boolean flag3 = false; 456 - while (l < ac1.length && (!flag2 || !flag3)) { 457 - int k1; 458 - char c = ac1[l]; 459 - char c2 = '\0'; 460 - if (l + 1 < ac1.length) 461 - c2 = ac1[l + 1]; 462 - if (i1 < ac.length && (k1 = method398(ac[i1], c, c2)) > 0) { 463 - if (k1 == 1 && method406(c)) 464 - flag2 = true; 465 - if (k1 == 2 && (method406(c) || method406(c2))) 466 - flag2 = true; 467 - l += k1; 468 - i1++; 469 - continue; 470 - } 471 - if (i1 == 0) 472 - break; 473 - if ((k1 = method398(ac[i1 - 1], c, c2)) > 0) { 474 - l += k1; 475 - if (i1 == 1) 476 - j++; 477 - continue; 478 - } 479 - if (i1 >= ac.length || !method404(c)) 480 - break; 481 - if (method403(c) && c != '\'') 482 - flag1 = true; 483 - if (method406(c)) 484 - flag3 = true; 485 - l++; 486 - if ((++j1 * 100) / (l - k) > 90) 487 - break; 488 - } 489 - if (i1 >= ac.length && (!flag2 || !flag3)) { 490 - boolean flag4 = true; 491 - if (!flag1) { 492 - char c1 = ' '; 493 - if (k - 1 >= 0) 494 - c1 = ac1[k - 1]; 495 - char c3 = ' '; 496 - if (l < ac1.length) 497 - c3 = ac1[l]; 498 - byte byte0 = method399(c1); 499 - byte byte1 = method399(c3); 500 - if (abyte0 != null && method396(byte1, abyte0, byte0)) 501 - flag4 = false; 502 - } else { 503 - boolean flag5 = false; 504 - boolean flag6 = false; 505 - if (k - 1 < 0 || method403(ac1[k - 1]) && ac1[k - 1] != '\'') 506 - flag5 = true; 507 - if (l >= ac1.length || method403(ac1[l]) && ac1[l] != '\'') 508 - flag6 = true; 509 - if (!flag5 || !flag6) { 510 - boolean flag7 = false; 511 - int k2 = k - 2; 512 - if (flag5) 513 - k2 = k; 514 - for (; !flag7 && k2 < l; k2++) 515 - if (k2 >= 0 && (!method403(ac1[k2]) || ac1[k2] == '\'')) { 516 - char[] ac2 = new char[3]; 517 - int j3; 518 - for (j3 = 0; j3 < 3; j3++) { 519 - if (k2 + j3 >= ac1.length || method403(ac1[k2 + j3]) && ac1[k2 + j3] != '\'') 520 - break; 521 - ac2[j3] = ac1[k2 + j3]; 522 - } 523 - 524 - boolean flag8 = true; 525 - if (j3 == 0) 526 - flag8 = false; 527 - if (j3 < 3 && k2 - 1 >= 0 && (!method403(ac1[k2 - 1]) || ac1[k2 - 1] == '\'')) 528 - flag8 = false; 529 - if (flag8 && !method409(ac2)) 530 - flag7 = true; 531 - } 532 - 533 - if (!flag7) 534 - flag4 = false; 535 - } 536 - } 537 - if (flag4) { 538 - int l1 = 0; 539 - int i2 = 0; 540 - int j2 = -1; 541 - for (int l2 = k; l2 < l; l2++) 542 - if (method406(ac1[l2])) 543 - l1++; 544 - else if (method405(ac1[l2])) { 545 - i2++; 546 - j2 = l2; 547 - } 548 - 549 - if (j2 > -1) 550 - l1 -= l - 1 - j2; 551 - if (l1 <= i2) { 552 - for (int i3 = k; i3 < l; i3++) 553 - ac1[i3] = '*'; 554 - 555 - } else { 556 - j = 1; 557 - } 558 - } 559 - } 560 - } 561 - 562 - } 563 - 564 - private static boolean method396(byte byte0, byte[][] abyte0, byte byte1) { 565 - int j = 0; 566 - if (abyte0[j][0] == byte1 && abyte0[j][1] == byte0) 567 - return true; 568 - int k = abyte0.length - 1; 569 - if (abyte0[k][0] == byte1 && abyte0[k][1] == byte0) 570 - return true; 571 - do { 572 - int l = (j + k) / 2; 573 - if (abyte0[l][0] == byte1 && abyte0[l][1] == byte0) 574 - return true; 575 - if (byte1 < abyte0[l][0] || byte1 == abyte0[l][0] && byte0 < abyte0[l][1]) 576 - k = l; 577 - else 578 - j = l; 579 - } while (j != k && j + 1 != k); 580 - return false; 581 - } 582 - 583 - private static int method397(char c, char c1, char c2) { 584 - if (c1 == c) 585 - return 1; 586 - if (c1 == 'o' && c == '0') 587 - return 1; 588 - if (c1 == 'o' && c == '(' && c2 == ')') 589 - return 2; 590 - if (c1 == 'c' && (c == '(' || c == '<' || c == '[')) 591 - return 1; 592 - if (c1 == 'e' && c == '\u20AC') 593 - return 1; 594 - if (c1 == 's' && c == '$') 595 - return 1; 596 - return c1 != 'l' || c != 'i' ? 0 : 1; 597 - } 598 - 599 - private static int method398(char c, char c1, char c2) { 600 - if (c == c1) 601 - return 1; 602 - if (c >= 'a' && c <= 'm') { 603 - if (c == 'a') { 604 - if (c1 == '4' || c1 == '@' || c1 == '^') 605 - return 1; 606 - return c1 != '/' || c2 != '\\' ? 0 : 2; 607 - } 608 - if (c == 'b') { 609 - if (c1 == '6' || c1 == '8') 610 - return 1; 611 - return (c1 != '1' || c2 != '3') && (c1 != 'i' || c2 != '3') ? 0 : 2; 612 - } 613 - if (c == 'c') 614 - return c1 != '(' && c1 != '<' && c1 != '{' && c1 != '[' ? 0 : 1; 615 - if (c == 'd') 616 - return (c1 != '[' || c2 != ')') && (c1 != 'i' || c2 != ')') ? 0 : 2; 617 - if (c == 'e') 618 - return c1 != '3' && c1 != '\u20AC' ? 0 : 1; 619 - if (c == 'f') { 620 - if (c1 == 'p' && c2 == 'h') 621 - return 2; 622 - return c1 != '\243' ? 0 : 1; 623 - } 624 - if (c == 'g') 625 - return c1 != '9' && c1 != '6' && c1 != 'q' ? 0 : 1; 626 - if (c == 'h') 627 - return c1 != '#' ? 0 : 1; 628 - if (c == 'i') 629 - return c1 != 'y' && c1 != 'l' && c1 != 'j' && c1 != '1' && c1 != '!' && c1 != ':' && c1 != ';' 630 - && c1 != '|' ? 0 : 1; 631 - if (c == 'j') 632 - return 0; 633 - if (c == 'k') 634 - return 0; 635 - if (c == 'l') 636 - return c1 != '1' && c1 != '|' && c1 != 'i' ? 0 : 1; 637 - if (c == 'm') 638 - return 0; 639 - } 640 - if (c >= 'n' && c <= 'z') { 641 - if (c == 'n') 642 - return 0; 643 - if (c == 'o') { 644 - if (c1 == '0' || c1 == '*') 645 - return 1; 646 - return (c1 != '(' || c2 != ')') && (c1 != '[' || c2 != ']') && (c1 != '{' || c2 != '}') 647 - && (c1 != '<' || c2 != '>') ? 0 : 2; 648 - } 649 - if (c == 'p') 650 - return 0; 651 - if (c == 'q') 652 - return 0; 653 - if (c == 'r') 654 - return 0; 655 - if (c == 's') 656 - return c1 != '5' && c1 != 'z' && c1 != '$' && c1 != '2' ? 0 : 1; 657 - if (c == 't') 658 - return c1 != '7' && c1 != '+' ? 0 : 1; 659 - if (c == 'u') { 660 - if (c1 == 'v') 661 - return 1; 662 - return (c1 != '\\' || c2 != '/') && (c1 != '\\' || c2 != '|') && (c1 != '|' || c2 != '/') ? 0 : 2; 663 - } 664 - if (c == 'v') 665 - return (c1 != '\\' || c2 != '/') && (c1 != '\\' || c2 != '|') && (c1 != '|' || c2 != '/') ? 0 : 2; 666 - if (c == 'w') 667 - return c1 != 'v' || c2 != 'v' ? 0 : 2; 668 - if (c == 'x') 669 - return (c1 != ')' || c2 != '(') && (c1 != '}' || c2 != '{') && (c1 != ']' || c2 != '[') 670 - && (c1 != '>' || c2 != '<') ? 0 : 2; 671 - if (c == 'y') 672 - return 0; 673 - if (c == 'z') 674 - return 0; 675 - } 676 - if (c >= '0' && c <= '9') { 677 - if (c == '0') { 678 - if (c1 == 'o' || c1 == 'O') 679 - return 1; 680 - return (c1 != '(' || c2 != ')') && (c1 != '{' || c2 != '}') && (c1 != '[' || c2 != ']') ? 0 : 2; 681 - } 682 - if (c == '1') 683 - return c1 != 'l' ? 0 : 1; 684 - else 685 - return 0; 686 - } 687 - if (c == ',') 688 - return c1 != '.' ? 0 : 1; 689 - if (c == '.') 690 - return c1 != ',' ? 0 : 1; 691 - if (c == '!') 692 - return c1 != 'i' ? 0 : 1; 693 - else 694 - return 0; 695 - } 696 - 697 - private static byte method399(char c) { 698 - if (c >= 'a' && c <= 'z') 699 - return (byte) ((c - 97) + 1); 700 - if (c == '\'') 701 - return 28; 702 - if (c >= '0' && c <= '9') 703 - return (byte) ((c - 48) + 29); 704 - else 705 - return 27; 706 - } 707 - 708 - private static void method400(char[] ac) { 709 - int j; 710 - int k = 0; 711 - int l = 0; 712 - int i1 = 0; 713 - while ((j = method401(k, ac)) != -1) { 714 - boolean flag = false; 715 - for (int j1 = k; j1 >= 0 && j1 < j && !flag; j1++) 716 - if (!method403(ac[j1]) && !method404(ac[j1])) 717 - flag = true; 718 - 719 - if (flag) 720 - l = 0; 721 - if (l == 0) 722 - i1 = j; 723 - k = method402(j, ac); 724 - int k1 = 0; 725 - for (int l1 = j; l1 < k; l1++) 726 - k1 = (k1 * 10 + ac[l1]) - 48; 727 - 728 - if (k1 > 255 || k - j > 8) 729 - l = 0; 730 - else 731 - l++; 732 - if (l == 4) { 733 - for (int i2 = i1; i2 < k; i2++) 734 - ac[i2] = '*'; 735 - 736 - l = 0; 737 - } 738 - } 739 - } 740 - 741 - private static int method401(int j, char[] ac) { 742 - for (int k = j; k < ac.length && k >= 0; k++) 743 - if (ac[k] >= '0' && ac[k] <= '9') 744 - return k; 745 - 746 - return -1; 747 - } 748 - 749 - private static int method402(int i, char[] ac) { 750 - for (int l = i; l < ac.length && l >= 0; l++) 751 - if (ac[l] < '0' || ac[l] > '9') 752 - return l; 753 - 754 - return ac.length; 755 - } 756 - 757 - private static boolean method403(char c) { 758 - return !method405(c) && !method406(c); 759 - } 760 - 761 - private static boolean method404(char c) { 762 - if (c < 'a' || c > 'z') 763 - return true; 764 - return c == 'v' || c == 'x' || c == 'j' || c == 'q' || c == 'z'; 765 - } 766 - 767 - private static boolean method405(char c) { 768 - return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; 769 - } 770 - 771 - private static boolean method406(char c) { 772 - return c >= '0' && c <= '9'; 773 - } 774 - 775 - private static boolean method407(char c) { 776 - return c >= 'a' && c <= 'z'; 777 - } 778 - 779 - private static boolean method408(char c) { 780 - return c >= 'A' && c <= 'Z'; 781 - } 782 - 783 - private static boolean method409(char[] ac) { 784 - boolean flag = true; 785 - for (int j = 0; j < ac.length; j++) 786 - if (!method406(ac[j]) && ac[j] != 0) 787 - flag = false; 788 - 789 - if (flag) 790 - return true; 791 - int k = method410(ac); 792 - int l = 0; 793 - int i1 = fragments.length - 1; 794 - if (k == fragments[l] || k == fragments[i1]) 795 - return true; 796 - do { 797 - int j1 = (l + i1) / 2; 798 - if (k == fragments[j1]) 799 - return true; 800 - if (k < fragments[j1]) 801 - i1 = j1; 802 - else 803 - l = j1; 804 - } while (l != i1 && l + 1 != i1); 805 - return false; 806 - } 807 - 808 - private static int method410(char[] ac) { 809 - if (ac.length > 6) 810 - return 0; 811 - int i = 0; 812 - 813 - for (int j = 0; j < ac.length; j++) { 814 - char c = ac[ac.length - j - 1]; 815 - if (c >= 'a' && c <= 'z') 816 - i = i * 38 + ((c - 97) + 1); 817 - else if (c == '\'') 818 - i = i * 38 + 27; 819 - else if (c >= '0' && c <= '9') 820 - i = i * 38 + ((c - 48) + 28); 821 - else if (c != 0) 822 - return 0; 823 - } 824 - 825 - return i; 826 - } 827 - 828 - 829 - }
+812
src/main/java/com/jagex/runescape/cache/cfg/ChatCensor.kt
··· 1 + package com.jagex.runescape.cache.cfg 2 + 3 + import com.jagex.runescape.cache.Archive 4 + import com.jagex.runescape.net.Buffer 5 + 6 + class ChatCensor { 7 + 8 + companion object { 9 + private var fragments: IntArray? = null 10 + private var badWords: Array<CharArray?>? = null 11 + private var badBytes: Array<Array<ByteArray>?>? = null 12 + private var domains: Array<CharArray?>? = null 13 + private var topLevelDomains: Array<CharArray?>? = null 14 + private var topLevelDomainsType: IntArray? = null 15 + private val exceptions: Array<String> = arrayOf("cook", "cook's", "cooks", "seeks", "sheet", "woop", "woops", 16 + "faq", "noob", "noobs") 17 + 18 + @JvmStatic 19 + fun load(archive: Archive) { 20 + val fragmentsEnc = Buffer(archive.getFile("fragmentsenc.txt")!!) 21 + val badEnc = Buffer(archive.getFile("badenc.txt")!!) 22 + val domainEnc = Buffer(archive.getFile("domainenc.txt")!!) 23 + val topLevelDomainsBuffer = Buffer(archive.getFile("tldlist.txt")!!) 24 + loadDictionaries(fragmentsEnc, badEnc, domainEnc, topLevelDomainsBuffer) 25 + } 26 + 27 + private fun loadDictionaries(fragmentsEnc: Buffer, badEnc: Buffer, 28 + domainEnc: Buffer, topLevelDomainsBuffer: Buffer) { 29 + loadBadEnc(badEnc) 30 + loadDomainEnc(domainEnc) 31 + loadFragmentsEnc(fragmentsEnc) 32 + loadTopLevelDomains(topLevelDomainsBuffer) 33 + } 34 + 35 + private fun loadTopLevelDomains(buffer: Buffer) { 36 + val length = buffer.getIntBE() 37 + topLevelDomains = arrayOfNulls(length) 38 + topLevelDomainsType = IntArray(length) 39 + for (index in 0 until length) { 40 + topLevelDomainsType!![index] = buffer.getUnsignedByte() 41 + val topLevelDomain = CharArray(buffer.getUnsignedByte()) 42 + for (character in topLevelDomain.indices) 43 + topLevelDomain[character] = buffer.getUnsignedByte().toChar() 44 + topLevelDomains!![index] = topLevelDomain 45 + } 46 + } 47 + 48 + private fun loadBadEnc(buffer: Buffer) { 49 + val length = buffer.getIntBE() 50 + badWords = arrayOfNulls(length) 51 + badBytes = arrayOfNulls(length) 52 + loadBadWords(buffer, badWords!!, badBytes!!) 53 + } 54 + 55 + private fun loadDomainEnc(buffer: Buffer) { 56 + val length = buffer.getIntBE() 57 + domains = arrayOfNulls(length) 58 + loadDomains(buffer, domains!!) 59 + } 60 + 61 + private fun loadFragmentsEnc(buffer: Buffer) { 62 + fragments = IntArray(buffer.getIntBE()) 63 + for (index in fragments!!.indices) 64 + fragments!![index] = buffer.getUnsignedShortBE() 65 + } 66 + 67 + private fun loadBadWords(buffer: Buffer, badWords: Array<CharArray?>, badBytes: Array<Array<ByteArray>?>) { 68 + for (index in badWords.indices) { 69 + val badWord = CharArray(buffer.getUnsignedByte()) 70 + for (character in badWord.indices) 71 + badWord[character] = buffer.getUnsignedByte().toChar() 72 + badWords[index] = badWord 73 + val badByte = Array(buffer.getUnsignedByte()) { ByteArray(2) } 74 + for (l in badByte.indices) { 75 + badByte[l][0] = buffer.getUnsignedByte().toByte() 76 + badByte[l][1] = buffer.getUnsignedByte().toByte() 77 + } 78 + if (badByte.isNotEmpty()) 79 + badBytes[index] = badByte 80 + } 81 + } 82 + 83 + private fun loadDomains(buffer: Buffer, cs: Array<CharArray?>) { 84 + for (index in cs.indices) { 85 + val domainEnc = CharArray(buffer.getUnsignedByte()) 86 + for (character in domainEnc.indices) 87 + domainEnc[character] = buffer.getUnsignedByte().toChar() 88 + cs[index] = domainEnc 89 + } 90 + } 91 + 92 + private fun formatLegalCharacters(characters: CharArray) { 93 + var character = 0 94 + for (index in characters.indices) { 95 + if (isLegalCharacter(characters[index])) 96 + characters[character] = characters[index] 97 + else 98 + characters[character] = ' ' 99 + if (character == 0 || characters[character] != ' ' || characters[character - 1] != ' ') 100 + character++ 101 + } 102 + for (characterIndex in character until characters.size) 103 + characters[characterIndex] = ' ' 104 + } 105 + 106 + private fun isLegalCharacter(character: Char): Boolean { 107 + return character in ' '..'\u007F' || character == ' ' || character == '\n' || character == '\t' || character == '\u00A3' || character == '\u20AC' 108 + } 109 + 110 + @JvmStatic 111 + fun censorString(string: String): String { 112 + val censoredString = string.toCharArray() 113 + formatLegalCharacters(censoredString) 114 + val censoredStringTrimmed = String(censoredString).trim() 115 + val censoredChars = censoredStringTrimmed.lowercase().toCharArray() 116 + val censoredStringLowercased = censoredStringTrimmed.lowercase() 117 + method391(censoredChars) 118 + method386(censoredChars) 119 + method387(censoredChars) 120 + method400(censoredChars) 121 + for (exception in exceptions) { 122 + var index = -1 123 + while (true) { 124 + index = censoredStringLowercased.indexOf(exception, index + 1) 125 + if (index == -1) break 126 + val ac1 = exception.toCharArray() 127 + System.arraycopy(ac1, 0, censoredChars, index, ac1.size) 128 + } 129 + } 130 + method384(censoredChars, censoredStringTrimmed.toCharArray()) 131 + method385(censoredChars) 132 + return String(censoredChars).trim() 133 + } 134 + 135 + private fun method384(ac: CharArray, ac1: CharArray) { 136 + for (j in ac1.indices) 137 + if (ac[j] != '*' && method408(ac1[j])) 138 + ac[j] = ac1[j] 139 + } 140 + 141 + private fun method385(ac: CharArray) { 142 + var flag = true 143 + for (j in ac.indices) { 144 + val c = ac[j] 145 + if (method405(c)) { 146 + if (flag) { 147 + if (method407(c)) 148 + flag = false 149 + } else if (method408(c)) 150 + ac[j] = (c.code + 97 - 65).toChar() 151 + } else { 152 + flag = true 153 + } 154 + } 155 + } 156 + 157 + private fun method386(ac: CharArray) { 158 + for (j in 0 until 2) { 159 + for (k in badWords!!.size - 1 downTo 0) 160 + method395(badBytes!![k], badWords!![k]!!, ac) 161 + } 162 + } 163 + 164 + private fun method387(ac: CharArray) { 165 + val ac1 = ac.clone() 166 + val ac2 = charArrayOf('(', 'a', ')') 167 + method395(null, ac2, ac1) 168 + val ac3 = ac.clone() 169 + val ac4 = charArrayOf('d', 'o', 't') 170 + method395(null, ac4, ac3) 171 + for (j in domains!!.size - 1 downTo 0) 172 + method388(ac, ac3, ac1, domains!![j]!!) 173 + } 174 + 175 + private fun method388(ac: CharArray, ac1: CharArray, ac2: CharArray, ac3: CharArray) { 176 + if (ac3.size > ac.size) 177 + return 178 + var j: Int 179 + var k = 0 180 + while (k <= ac.size - ac3.size) { 181 + var l = k 182 + var i1 = 0 183 + j = 1 184 + while (l < ac.size) { 185 + val j1: Int 186 + val c = ac[l] 187 + var c1 = '\u0000' 188 + if (l + 1 < ac.size) 189 + c1 = ac[l + 1] 190 + if (i1 < ac3.size) { 191 + j1 = method397(c, ac3[i1], c1) 192 + if (j1 > 0) { 193 + l += j1 194 + i1++ 195 + continue 196 + } 197 + } 198 + if (i1 == 0) 199 + break 200 + val j1b = method397(c, ac3[i1 - 1], c1) 201 + if (j1b > 0) { 202 + l += j1b 203 + if (i1 == 1) 204 + j++ 205 + continue 206 + } 207 + if (i1 >= ac3.size || !method403(c)) 208 + break 209 + l++ 210 + } 211 + if (i1 >= ac3.size) { 212 + var flag1 = false 213 + val k1 = method389(ac, ac2, k) 214 + val l1 = method390(ac1, l - 1, ac) 215 + if (k1 > 2 || l1 > 2) 216 + flag1 = true 217 + if (flag1) { 218 + for (i2 in k until l) 219 + ac[i2] = '*' 220 + } 221 + } 222 + k += j 223 + } 224 + } 225 + 226 + private fun method389(ac: CharArray, ac1: CharArray, i: Int): Int { 227 + if (i == 0) 228 + return 2 229 + for (j in i - 1 downTo 0) { 230 + if (!method403(ac[j])) 231 + break 232 + if (ac[j] == '@') 233 + return 3 234 + } 235 + var k = 0 236 + for (l in i - 1 downTo 0) { 237 + if (!method403(ac1[l])) 238 + break 239 + if (ac1[l] == '*') 240 + k++ 241 + } 242 + if (k >= 3) 243 + return 4 244 + return if (!method403(ac[i - 1])) 0 else 1 245 + } 246 + 247 + private fun method390(ac: CharArray, j: Int, ac1: CharArray): Int { 248 + if (j + 1 == ac1.size) 249 + return 2 250 + for (k in j + 1 until ac1.size) { 251 + if (!method403(ac1[k])) 252 + break 253 + if (ac1[k] == '.' || ac1[k] == ',') 254 + return 3 255 + } 256 + var l = 0 257 + for (i1 in j + 1 until ac1.size) { 258 + if (!method403(ac[i1])) 259 + break 260 + if (ac[i1] == '*') 261 + l++ 262 + } 263 + if (l >= 3) 264 + return 4 265 + return if (!method403(ac1[j + 1])) 0 else 1 266 + } 267 + 268 + private fun method391(ac: CharArray) { 269 + val ac1 = ac.clone() 270 + val ac2 = charArrayOf('d', 'o', 't') 271 + method395(null, ac2, ac1) 272 + val ac3 = ac.clone() 273 + val ac4 = charArrayOf('s', 'l', 'a', 's', 'h') 274 + method395(null, ac4, ac3) 275 + for (j in 0 until topLevelDomains!!.size) 276 + method392(ac, ac1, topLevelDomainsType!![j], topLevelDomains!![j]!!, ac3) 277 + } 278 + 279 + private fun method392(ac: CharArray, ac1: CharArray, i: Int, ac2: CharArray, ac3: CharArray) { 280 + if (ac2.size > ac.size) 281 + return 282 + var j: Int 283 + var k = 0 284 + while (k <= ac.size - ac2.size) { 285 + var l = k 286 + var i1 = 0 287 + j = 1 288 + while (l < ac.size) { 289 + val j1: Int 290 + val c = ac[l] 291 + var c1 = '\u0000' 292 + if (l + 1 < ac.size) 293 + c1 = ac[l + 1] 294 + if (i1 < ac2.size) { 295 + j1 = method397(c, ac2[i1], c1) 296 + if (j1 > 0) { 297 + l += j1 298 + i1++ 299 + continue 300 + } 301 + } 302 + if (i1 == 0) 303 + break 304 + val j1b = method397(c, ac2[i1 - 1], c1) 305 + if (j1b > 0) { 306 + l += j1b 307 + if (i1 == 1) 308 + j++ 309 + continue 310 + } 311 + if (i1 >= ac2.size || !method403(c)) 312 + break 313 + l++ 314 + } 315 + if (i1 >= ac2.size) { 316 + var flag1 = false 317 + val k1 = method393(ac1, k, ac) 318 + val l1 = method394(ac3, l - 1, ac) 319 + if (i == 1 && k1 > 0 && l1 > 0) 320 + flag1 = true 321 + if (i == 2 && (k1 > 2 && l1 > 0 || k1 > 0 && l1 > 2)) 322 + flag1 = true 323 + if (i == 3 && k1 > 0 && l1 > 2) 324 + flag1 = true 325 + if (flag1) { 326 + var i2 = k 327 + var j2 = l - 1 328 + if (k1 > 2) { 329 + if (k1 == 4) { 330 + var flag2 = false 331 + for (l2 in i2 - 1 downTo 0) 332 + if (flag2) { 333 + if (ac1[l2] != '*') 334 + break 335 + i2 = l2 336 + } else if (ac1[l2] == '*') { 337 + i2 = l2 338 + flag2 = true 339 + } 340 + } 341 + var flag3 = false 342 + for (i3 in i2 - 1 downTo 0) 343 + if (flag3) { 344 + if (method403(ac[i3])) 345 + break 346 + i2 = i3 347 + } else if (!method403(ac[i3])) { 348 + flag3 = true 349 + i2 = i3 350 + } 351 + } 352 + if (l1 > 2) { 353 + if (l1 == 4) { 354 + var flag4 = false 355 + for (j3 in j2 + 1 until ac.size) 356 + if (flag4) { 357 + if (ac3[j3] != '*') 358 + break 359 + j2 = j3 360 + } else if (ac3[j3] == '*') { 361 + j2 = j3 362 + flag4 = true 363 + } 364 + } 365 + var flag5 = false 366 + for (k3 in j2 + 1 until ac.size) 367 + if (flag5) { 368 + if (method403(ac[k3])) 369 + break 370 + j2 = k3 371 + } else if (!method403(ac[k3])) { 372 + flag5 = true 373 + j2 = k3 374 + } 375 + } 376 + for (k2 in i2..j2) 377 + ac[k2] = '*' 378 + } 379 + } 380 + k += j 381 + } 382 + } 383 + 384 + private fun method393(ac: CharArray, i: Int, ac1: CharArray): Int { 385 + if (i == 0) 386 + return 2 387 + for (k in i - 1 downTo 0) { 388 + if (!method403(ac1[k])) 389 + break 390 + if (ac1[k] == ',' || ac1[k] == '.') 391 + return 3 392 + } 393 + var l = 0 394 + for (i1 in i - 1 downTo 0) { 395 + if (!method403(ac[i1])) 396 + break 397 + if (ac[i1] == '*') 398 + l++ 399 + } 400 + if (l >= 3) 401 + return 4 402 + return if (!method403(ac1[i - 1])) 0 else 1 403 + } 404 + 405 + private fun method394(ac: CharArray, i: Int, ac1: CharArray): Int { 406 + if (i + 1 == ac1.size) 407 + return 2 408 + for (l in i + 1 until ac1.size) { 409 + if (!method403(ac1[l])) 410 + break 411 + if (ac1[l] == '\\' || ac1[l] == '/') 412 + return 3 413 + } 414 + var i1 = 0 415 + for (j1 in i + 1 until ac1.size) { 416 + if (!method403(ac[j1])) 417 + break 418 + if (ac[j1] == '*') 419 + i1++ 420 + } 421 + if (i1 >= 5) 422 + return 4 423 + return if (!method403(ac1[i + 1])) 0 else 1 424 + } 425 + 426 + private fun method395(abyte0: Array<ByteArray>?, ac: CharArray, ac1: CharArray) { 427 + if (ac.size > ac1.size) 428 + return 429 + var j: Int 430 + var k = 0 431 + while (k <= ac1.size - ac.size) { 432 + var l = k 433 + var i1 = 0 434 + var j1 = 0 435 + j = 1 436 + var flag1 = false 437 + var flag2 = false 438 + var flag3 = false 439 + while (l < ac1.size && (!flag2 || !flag3)) { 440 + val k1: Int 441 + val c = ac1[l] 442 + var c2 = '\u0000' 443 + if (l + 1 < ac1.size) 444 + c2 = ac1[l + 1] 445 + if (i1 < ac.size) { 446 + k1 = method398(ac[i1], c, c2) 447 + if (k1 > 0) { 448 + if (k1 == 1 && method406(c)) 449 + flag2 = true 450 + if (k1 == 2 && (method406(c) || method406(c2))) 451 + flag2 = true 452 + l += k1 453 + i1++ 454 + continue 455 + } 456 + } 457 + if (i1 == 0) 458 + break 459 + val k1b = method398(ac[i1 - 1], c, c2) 460 + if (k1b > 0) { 461 + l += k1b 462 + if (i1 == 1) 463 + j++ 464 + continue 465 + } 466 + if (i1 >= ac.size || !method404(c)) 467 + break 468 + if (method403(c) && c != '\'') 469 + flag1 = true 470 + if (method406(c)) 471 + flag3 = true 472 + l++ 473 + if ((++j1 * 100) / (l - k) > 90) 474 + break 475 + } 476 + if (i1 >= ac.size && (!flag2 || !flag3)) { 477 + var flag4 = true 478 + if (!flag1) { 479 + var c1 = ' ' 480 + if (k - 1 >= 0) 481 + c1 = ac1[k - 1] 482 + var c3 = ' ' 483 + if (l < ac1.size) 484 + c3 = ac1[l] 485 + val byte0 = method399(c1) 486 + val byte1 = method399(c3) 487 + if (abyte0 != null && method396(byte1, abyte0, byte0)) 488 + flag4 = false 489 + } else { 490 + var flag5 = false 491 + var flag6 = false 492 + if (k - 1 < 0 || method403(ac1[k - 1]) && ac1[k - 1] != '\'') 493 + flag5 = true 494 + if (l >= ac1.size || method403(ac1[l]) && ac1[l] != '\'') 495 + flag6 = true 496 + if (!flag5 || !flag6) { 497 + var flag7 = false 498 + var k2 = k - 2 499 + if (flag5) 500 + k2 = k 501 + while (!flag7 && k2 < l) { 502 + if (k2 >= 0 && (!method403(ac1[k2]) || ac1[k2] == '\'')) { 503 + val ac2 = CharArray(3) 504 + var j3: Int 505 + j3 = 0 506 + while (j3 < 3) { 507 + if (k2 + j3 >= ac1.size || method403(ac1[k2 + j3]) && ac1[k2 + j3] != '\'') 508 + break 509 + ac2[j3] = ac1[k2 + j3] 510 + j3++ 511 + } 512 + var flag8 = true 513 + if (j3 == 0) 514 + flag8 = false 515 + if (j3 < 3 && k2 - 1 >= 0 && (!method403(ac1[k2 - 1]) || ac1[k2 - 1] == '\'')) 516 + flag8 = false 517 + if (flag8 && !method409(ac2)) 518 + flag7 = true 519 + } 520 + k2++ 521 + } 522 + if (!flag7) 523 + flag4 = false 524 + } 525 + } 526 + if (flag4) { 527 + var l1 = 0 528 + var i2 = 0 529 + var j2 = -1 530 + for (l2 in k until l) 531 + if (method406(ac1[l2])) 532 + l1++ 533 + else if (method405(ac1[l2])) { 534 + i2++ 535 + j2 = l2 536 + } 537 + if (j2 > -1) 538 + l1 -= l - 1 - j2 539 + if (l1 <= i2) { 540 + for (i3 in k until l) 541 + ac1[i3] = '*' 542 + } else { 543 + j = 1 544 + } 545 + } 546 + } 547 + k += j 548 + } 549 + } 550 + 551 + private fun method396(byte0: Byte, abyte0: Array<ByteArray>, byte1: Byte): Boolean { 552 + var j = 0 553 + if (abyte0[j][0] == byte1 && abyte0[j][1] == byte0) 554 + return true 555 + var k = abyte0.size - 1 556 + if (abyte0[k][0] == byte1 && abyte0[k][1] == byte0) 557 + return true 558 + do { 559 + val l = (j + k) / 2 560 + if (abyte0[l][0] == byte1 && abyte0[l][1] == byte0) 561 + return true 562 + if (byte1 < abyte0[l][0] || byte1 == abyte0[l][0] && byte0 < abyte0[l][1]) 563 + k = l 564 + else 565 + j = l 566 + } while (j != k && j + 1 != k) 567 + return false 568 + } 569 + 570 + private fun method397(c: Char, c1: Char, c2: Char): Int { 571 + if (c1 == c) 572 + return 1 573 + if (c1 == 'o' && c == '0') 574 + return 1 575 + if (c1 == 'o' && c == '(' && c2 == ')') 576 + return 2 577 + if (c1 == 'c' && (c == '(' || c == '<' || c == '[')) 578 + return 1 579 + if (c1 == 'e' && c == '\u20AC') 580 + return 1 581 + if (c1 == 's' && c == '$') 582 + return 1 583 + return if (c1 != 'l' || c != 'i') 0 else 1 584 + } 585 + 586 + private fun method398(c: Char, c1: Char, c2: Char): Int { 587 + if (c == c1) 588 + return 1 589 + if (c in 'a'..'m') { 590 + if (c == 'a') { 591 + if (c1 == '4' || c1 == '@' || c1 == '^') 592 + return 1 593 + return if (c1 != '/' || c2 != '\\') 0 else 2 594 + } 595 + if (c == 'b') { 596 + if (c1 == '6' || c1 == '8') 597 + return 1 598 + return if ((c1 != '1' || c2 != '3') && (c1 != 'i' || c2 != '3')) 0 else 2 599 + } 600 + if (c == 'c') 601 + return if (c1 != '(' && c1 != '<' && c1 != '{' && c1 != '[') 0 else 1 602 + if (c == 'd') 603 + return if ((c1 != '[' || c2 != ')') && (c1 != 'i' || c2 != ')')) 0 else 2 604 + if (c == 'e') 605 + return if (c1 != '3' && c1 != '\u20AC') 0 else 1 606 + if (c == 'f') { 607 + if (c1 == 'p' && c2 == 'h') 608 + return 2 609 + return if (c1 != '\u00A3') 0 else 1 610 + } 611 + if (c == 'g') 612 + return if (c1 != '9' && c1 != '6' && c1 != 'q') 0 else 1 613 + if (c == 'h') 614 + return if (c1 != '#') 0 else 1 615 + if (c == 'i') 616 + return if (c1 != 'y' && c1 != 'l' && c1 != 'j' && c1 != '1' && c1 != '!' && c1 != ':' && c1 != ';' 617 + && c1 != '|') 0 else 1 618 + if (c == 'j') 619 + return 0 620 + if (c == 'k') 621 + return 0 622 + if (c == 'l') 623 + return if (c1 != '1' && c1 != '|' && c1 != 'i') 0 else 1 624 + if (c == 'm') 625 + return 0 626 + } 627 + if (c in 'n'..'z') { 628 + if (c == 'n') 629 + return 0 630 + if (c == 'o') { 631 + if (c1 == '0' || c1 == '*') 632 + return 1 633 + return if ((c1 != '(' || c2 != ')') && (c1 != '[' || c2 != ']') && (c1 != '{' || c2 != '}') 634 + && (c1 != '<' || c2 != '>')) 0 else 2 635 + } 636 + if (c == 'p') 637 + return 0 638 + if (c == 'q') 639 + return 0 640 + if (c == 'r') 641 + return 0 642 + if (c == 's') 643 + return if (c1 != '5' && c1 != 'z' && c1 != '$' && c1 != '2') 0 else 1 644 + if (c == 't') 645 + return if (c1 != '7' && c1 != '+') 0 else 1 646 + if (c == 'u') { 647 + if (c1 == 'v') 648 + return 1 649 + return if ((c1 != '\\' || c2 != '/') && (c1 != '\\' || c2 != '|') && (c1 != '|' || c2 != '/')) 0 else 2 650 + } 651 + if (c == 'v') 652 + return if ((c1 != '\\' || c2 != '/') && (c1 != '\\' || c2 != '|') && (c1 != '|' || c2 != '/')) 0 else 2 653 + if (c == 'w') 654 + return if (c1 != 'v' || c2 != 'v') 0 else 2 655 + if (c == 'x') 656 + return if ((c1 != ')' || c2 != '(') && (c1 != '}' || c2 != '{') && (c1 != ']' || c2 != '[') 657 + && (c1 != '>' || c2 != '<')) 0 else 2 658 + if (c == 'y') 659 + return 0 660 + if (c == 'z') 661 + return 0 662 + } 663 + if (c in '0'..'9') { 664 + if (c == '0') { 665 + if (c1 == 'o' || c1 == 'O') 666 + return 1 667 + return if ((c1 != '(' || c2 != ')') && (c1 != '{' || c2 != '}') && (c1 != '[' || c2 != ']')) 0 else 2 668 + } 669 + if (c == '1') 670 + return if (c1 != 'l') 0 else 1 671 + else 672 + return 0 673 + } 674 + if (c == ',') 675 + return if (c1 != '.') 0 else 1 676 + if (c == '.') 677 + return if (c1 != ',') 0 else 1 678 + if (c == '!') 679 + return if (c1 != 'i') 0 else 1 680 + else 681 + return 0 682 + } 683 + 684 + private fun method399(c: Char): Byte { 685 + if (c in 'a'..'z') 686 + return ((c.code - 97) + 1).toByte() 687 + if (c == '\'') 688 + return 28 689 + return if (c in '0'..'9') 690 + ((c.code - 48) + 29).toByte() 691 + else 692 + 27 693 + } 694 + 695 + private fun method400(ac: CharArray) { 696 + var j: Int 697 + var k = 0 698 + var l = 0 699 + var i1 = 0 700 + while (true) { 701 + j = method401(k, ac) 702 + if (j == -1) break 703 + var flag = false 704 + var j1 = k 705 + while (j1 >= 0 && j1 < j && !flag) { 706 + if (!method403(ac[j1]) && !method404(ac[j1])) 707 + flag = true 708 + j1++ 709 + } 710 + if (flag) 711 + l = 0 712 + if (l == 0) 713 + i1 = j 714 + k = method402(j, ac) 715 + var k1 = 0 716 + for (l1 in j until k) 717 + k1 = (k1 * 10 + ac[l1].code) - 48 718 + if (k1 > 255 || k - j > 8) 719 + l = 0 720 + else 721 + l++ 722 + if (l == 4) { 723 + for (i2 in i1 until k) 724 + ac[i2] = '*' 725 + l = 0 726 + } 727 + } 728 + } 729 + 730 + private fun method401(j: Int, ac: CharArray): Int { 731 + for (k in j until ac.size) 732 + if (ac[k] in '0'..'9') 733 + return k 734 + return -1 735 + } 736 + 737 + private fun method402(i: Int, ac: CharArray): Int { 738 + for (l in i until ac.size) 739 + if (ac[l] < '0' || ac[l] > '9') 740 + return l 741 + return ac.size 742 + } 743 + 744 + private fun method403(c: Char): Boolean { 745 + return !method405(c) && !method406(c) 746 + } 747 + 748 + private fun method404(c: Char): Boolean { 749 + if (c < 'a' || c > 'z') 750 + return true 751 + return c == 'v' || c == 'x' || c == 'j' || c == 'q' || c == 'z' 752 + } 753 + 754 + private fun method405(c: Char): Boolean { 755 + return c in 'a'..'z' || c in 'A'..'Z' 756 + } 757 + 758 + private fun method406(c: Char): Boolean { 759 + return c in '0'..'9' 760 + } 761 + 762 + private fun method407(c: Char): Boolean { 763 + return c in 'a'..'z' 764 + } 765 + 766 + private fun method408(c: Char): Boolean { 767 + return c in 'A'..'Z' 768 + } 769 + 770 + private fun method409(ac: CharArray): Boolean { 771 + var flag = true 772 + for (j in ac.indices) 773 + if (!method406(ac[j]) && ac[j].code != 0) 774 + flag = false 775 + if (flag) 776 + return true 777 + val k = method410(ac) 778 + var l = 0 779 + var i1 = fragments!!.size - 1 780 + if (k == fragments!![l] || k == fragments!![i1]) 781 + return true 782 + do { 783 + val j1 = (l + i1) / 2 784 + if (k == fragments!![j1]) 785 + return true 786 + if (k < fragments!![j1]) 787 + i1 = j1 788 + else 789 + l = j1 790 + } while (l != i1 && l + 1 != i1) 791 + return false 792 + } 793 + 794 + private fun method410(ac: CharArray): Int { 795 + if (ac.size > 6) 796 + return 0 797 + var i = 0 798 + for (j in ac.indices) { 799 + val c = ac[ac.size - j - 1] 800 + if (c in 'a'..'z') 801 + i = i * 38 + ((c.code - 97) + 1) 802 + else if (c == '\'') 803 + i = i * 38 + 27 804 + else if (c in '0'..'9') 805 + i = i * 38 + ((c.code - 48) + 28) 806 + else if (c.code != 0) 807 + return 0 808 + } 809 + return i 810 + } 811 + } 812 + }
-60
src/main/java/com/jagex/runescape/cache/cfg/Varbit.java
··· 1 - package com.jagex.runescape.cache.cfg; 2 - 3 - import com.jagex.runescape.cache.Archive; 4 - import com.jagex.runescape.net.Buffer; 5 - 6 - public class Varbit { 7 - public static int count; 8 - public static Varbit cache[]; 9 - public int configId; 10 - public int leastSignificantBit; 11 - public int mostSignificantBit; 12 - public boolean aBoolean829 = false; 13 - public boolean aBoolean832 = true; 14 - 15 - public static void load(Archive archive) { 16 - Buffer buffer = new Buffer(archive.getFile("varbit.dat")); 17 - count = buffer.getUnsignedShortBE(); 18 - 19 - if (cache == null) 20 - cache = new Varbit[count]; 21 - 22 - for (int index = 0; index < count; index++) { 23 - if (cache[index] == null) 24 - cache[index] = new Varbit(); 25 - cache[index].init(buffer); 26 - if (cache[index].aBoolean829) 27 - Varp.cache[cache[index].configId].aBoolean716 = true; 28 - } 29 - 30 - if (buffer.currentPosition != buffer.buffer.length) 31 - System.out.println("varbit load mismatch"); 32 - } 33 - 34 - public void init(Buffer buf) { 35 - while (true) { 36 - int attribute = buf.getUnsignedByte(); 37 - if (attribute == 0) 38 - return; 39 - if (attribute == 1) { 40 - configId = buf.getUnsignedShortBE(); 41 - leastSignificantBit = buf.getUnsignedByte(); 42 - mostSignificantBit = buf.getUnsignedByte(); 43 - } else if (attribute == 10) 44 - buf.getString(); // dummy 45 - else if (attribute == 2) 46 - aBoolean829 = true; 47 - else if (attribute == 3) 48 - buf.getIntBE(); // dummy 49 - else if (attribute == 4) 50 - buf.getIntBE(); // dummy 51 - else if (attribute == 5) 52 - aBoolean832 = false; 53 - else 54 - System.out.println("Error unrecognised config code: " + attribute); 55 - } 56 - } 57 - 58 - 59 - 60 - }
+61
src/main/java/com/jagex/runescape/cache/cfg/Varbit.kt
··· 1 + package com.jagex.runescape.cache.cfg 2 + 3 + import com.jagex.runescape.cache.Archive 4 + import com.jagex.runescape.net.Buffer 5 + 6 + class Varbit { 7 + @JvmField var configId: Int = 0 8 + @JvmField var leastSignificantBit: Int = 0 9 + @JvmField var mostSignificantBit: Int = 0 10 + @JvmField var aBoolean829: Boolean = false 11 + @JvmField var aBoolean832: Boolean = true 12 + 13 + fun init(buf: Buffer) { 14 + while (true) { 15 + val attribute = buf.getUnsignedByte() 16 + if (attribute == 0) 17 + return 18 + if (attribute == 1) { 19 + configId = buf.getUnsignedShortBE() 20 + leastSignificantBit = buf.getUnsignedByte() 21 + mostSignificantBit = buf.getUnsignedByte() 22 + } else if (attribute == 10) 23 + buf.getString() // dummy 24 + else if (attribute == 2) 25 + aBoolean829 = true 26 + else if (attribute == 3) 27 + buf.getIntBE() // dummy 28 + else if (attribute == 4) 29 + buf.getIntBE() // dummy 30 + else if (attribute == 5) 31 + aBoolean832 = false 32 + else 33 + println("Error unrecognised config code: $attribute") 34 + } 35 + } 36 + 37 + companion object { 38 + @JvmField var count: Int = 0 39 + @JvmField var cache: Array<Varbit?>? = null 40 + 41 + @JvmStatic 42 + fun load(archive: Archive) { 43 + val buffer = Buffer(archive.getFile("varbit.dat")!!) 44 + count = buffer.getUnsignedShortBE() 45 + 46 + if (cache == null) 47 + cache = arrayOfNulls(count) 48 + 49 + for (index in 0 until count) { 50 + if (cache!![index] == null) 51 + cache!![index] = Varbit() 52 + cache!![index]!!.init(buffer) 53 + if (cache!![index]!!.aBoolean829) 54 + Varp.cache!![cache!![index]!!.configId]!!.aBoolean716 = true 55 + } 56 + 57 + if (buffer.currentPosition != buffer.buffer.size) 58 + println("varbit load mismatch") 59 + } 60 + } 61 + }
-86
src/main/java/com/jagex/runescape/cache/cfg/Varp.java
··· 1 - package com.jagex.runescape.cache.cfg; 2 - 3 - import com.jagex.runescape.cache.Archive; 4 - import com.jagex.runescape.net.Buffer; 5 - 6 - public class Varp { 7 - 8 - public static int count; 9 - public static Varp cache[]; 10 - public static int currentIndex; 11 - public static int anIntArray706[]; 12 - public String aString707; 13 - public int anInt708; 14 - public int anInt709; 15 - public boolean aBoolean710 = false; 16 - public boolean aBoolean711 = true; 17 - public int anInt712; 18 - public boolean aBoolean713 = false; 19 - public int anInt714; 20 - public int anInt715; 21 - public boolean aBoolean716 = false; 22 - public int anInt717 = -1; 23 - public boolean aBoolean718 = true; 24 - 25 - public static void load(Archive archive) { 26 - Buffer buffer = new Buffer(archive.getFile("varp.dat")); 27 - currentIndex = 0; 28 - count = buffer.getUnsignedShortBE(); 29 - 30 - if (cache == null) 31 - cache = new Varp[count]; 32 - 33 - if (anIntArray706 == null) 34 - anIntArray706 = new int[count]; 35 - 36 - for (int index = 0; index < count; index++) { 37 - if (cache[index] == null) 38 - cache[index] = new Varp(); 39 - cache[index].loadDefinition(index, buffer); 40 - } 41 - 42 - if (buffer.currentPosition != buffer.buffer.length) 43 - System.out.println("varptype load mismatch"); 44 - } 45 - 46 - public void loadDefinition(int index, Buffer buffer) { 47 - while (true) { 48 - int attribute = buffer.getUnsignedByte(); 49 - if (attribute == 0) 50 - return; 51 - if (attribute == 1) 52 - anInt708 = buffer.getUnsignedByte(); 53 - else if (attribute == 2) 54 - anInt709 = buffer.getUnsignedByte(); 55 - else if (attribute == 3) { 56 - aBoolean710 = true; 57 - anIntArray706[currentIndex++] = index; 58 - } else if (attribute == 4) 59 - aBoolean711 = false; 60 - else if (attribute == 5) 61 - anInt712 = buffer.getUnsignedShortBE(); 62 - else if (attribute == 6) 63 - aBoolean713 = true; 64 - else if (attribute == 7) 65 - anInt714 = buffer.getIntBE(); 66 - else if (attribute == 8) { 67 - anInt715 = 1; 68 - aBoolean716 = true; 69 - } else if (attribute == 10) 70 - aString707 = buffer.getString(); 71 - else if (attribute == 11) 72 - aBoolean716 = true; 73 - else if (attribute == 12) 74 - anInt717 = buffer.getIntBE(); 75 - else if (attribute == 13) { 76 - anInt715 = 2; 77 - aBoolean716 = true; 78 - } else if (attribute == 14) 79 - aBoolean718 = false; 80 - else 81 - System.out.println("Error unrecognised config code: " + attribute); 82 - } 83 - } 84 - 85 - 86 - }
+87
src/main/java/com/jagex/runescape/cache/cfg/Varp.kt
··· 1 + package com.jagex.runescape.cache.cfg 2 + 3 + import com.jagex.runescape.cache.Archive 4 + import com.jagex.runescape.net.Buffer 5 + 6 + class Varp { 7 + @JvmField var aString707: String? = null 8 + @JvmField var anInt708: Int = 0 9 + @JvmField var anInt709: Int = 0 10 + @JvmField var aBoolean710: Boolean = false 11 + @JvmField var aBoolean711: Boolean = true 12 + @JvmField var anInt712: Int = 0 13 + @JvmField var aBoolean713: Boolean = false 14 + @JvmField var anInt714: Int = 0 15 + @JvmField var anInt715: Int = 0 16 + @JvmField var aBoolean716: Boolean = false 17 + @JvmField var anInt717: Int = -1 18 + @JvmField var aBoolean718: Boolean = true 19 + 20 + fun loadDefinition(index: Int, buffer: Buffer) { 21 + while (true) { 22 + val attribute = buffer.getUnsignedByte() 23 + if (attribute == 0) 24 + return 25 + if (attribute == 1) 26 + anInt708 = buffer.getUnsignedByte() 27 + else if (attribute == 2) 28 + anInt709 = buffer.getUnsignedByte() 29 + else if (attribute == 3) { 30 + aBoolean710 = true 31 + anIntArray706!![currentIndex++] = index 32 + } else if (attribute == 4) 33 + aBoolean711 = false 34 + else if (attribute == 5) 35 + anInt712 = buffer.getUnsignedShortBE() 36 + else if (attribute == 6) 37 + aBoolean713 = true 38 + else if (attribute == 7) 39 + anInt714 = buffer.getIntBE() 40 + else if (attribute == 8) { 41 + anInt715 = 1 42 + aBoolean716 = true 43 + } else if (attribute == 10) 44 + aString707 = buffer.getString() 45 + else if (attribute == 11) 46 + aBoolean716 = true 47 + else if (attribute == 12) 48 + anInt717 = buffer.getIntBE() 49 + else if (attribute == 13) { 50 + anInt715 = 2 51 + aBoolean716 = true 52 + } else if (attribute == 14) 53 + aBoolean718 = false 54 + else 55 + println("Error unrecognised config code: $attribute") 56 + } 57 + } 58 + 59 + companion object { 60 + @JvmField var count: Int = 0 61 + @JvmField var cache: Array<Varp?>? = null 62 + @JvmField var currentIndex: Int = 0 63 + @JvmField var anIntArray706: IntArray? = null 64 + 65 + @JvmStatic 66 + fun load(archive: Archive) { 67 + val buffer = Buffer(archive.getFile("varp.dat")!!) 68 + currentIndex = 0 69 + count = buffer.getUnsignedShortBE() 70 + 71 + if (cache == null) 72 + cache = arrayOfNulls(count) 73 + 74 + if (anIntArray706 == null) 75 + anIntArray706 = IntArray(count) 76 + 77 + for (index in 0 until count) { 78 + if (cache!![index] == null) 79 + cache!![index] = Varp() 80 + cache!![index]!!.loadDefinition(index, buffer) 81 + } 82 + 83 + if (buffer.currentPosition != buffer.buffer.size) 84 + println("varptype load mismatch") 85 + } 86 + } 87 + }
-53
src/main/java/com/jagex/runescape/collection/Cache.java
··· 1 - package com.jagex.runescape.collection; 2 - 3 - public class Cache { 4 - 5 - public int misses; 6 - public int hits; 7 - public int capacity; 8 - public int remaining; 9 - public HashTable hashTable; 10 - public Queue queue = new Queue(); 11 - 12 - public Cache(int size) { 13 - this.capacity = size; 14 - this.remaining = size; 15 - this.hashTable = new HashTable(1024); 16 - } 17 - 18 - public CacheableNode get(long id) { 19 - CacheableNode cacheablenode = (CacheableNode) hashTable.get(id); 20 - if (cacheablenode != null) { 21 - queue.push(cacheablenode); 22 - } 23 - return cacheablenode; 24 - } 25 - 26 - public void put(CacheableNode cacheableNode, long id) { 27 - if (remaining == 0) { 28 - CacheableNode oldestNode = queue.pop(); 29 - oldestNode.remove(); 30 - oldestNode.clear(); 31 - } else { 32 - remaining--; 33 - } 34 - hashTable.put(cacheableNode, id); 35 - queue.push(cacheableNode); 36 - } 37 - 38 - public void removeAll() { 39 - do { 40 - CacheableNode node = queue.pop(); 41 - if (node != null) { 42 - node.remove(); 43 - node.clear(); 44 - } else { 45 - remaining = capacity; 46 - return; 47 - } 48 - } while (true); 49 - } 50 - 51 - 52 - 53 - }
+50
src/main/java/com/jagex/runescape/collection/Cache.kt
··· 1 + package com.jagex.runescape.collection 2 + 3 + /** 4 + * Fixed-capacity LRU cache backed by a [HashTable] for O(1) key lookup and a 5 + * [Queue] for eviction ordering. When the cache is full, [put] evicts the 6 + * least-recently-used entry (front of the queue) before inserting. 7 + * 8 + * Used throughout the client for texture, model, and animation caching where 9 + * re-decoding from the cache archive is expensive but memory is bounded. 10 + */ 11 + class Cache(@JvmField val capacity: Int) { 12 + @JvmField var misses: Int = 0 13 + @JvmField var hits: Int = 0 14 + @JvmField var remaining: Int = capacity 15 + @JvmField val hashTable: HashTable = HashTable(1024) 16 + @JvmField val queue: Queue = Queue() 17 + 18 + fun get(id: Long): CacheableNode? { 19 + val node = hashTable.get(id) as? CacheableNode 20 + if (node != null) { 21 + queue.push(node) 22 + } 23 + return node 24 + } 25 + 26 + fun put(node: CacheableNode, id: Long) { 27 + if (remaining == 0) { 28 + val oldest = queue.pop()!! 29 + oldest.remove() 30 + oldest.clear() 31 + } else { 32 + remaining-- 33 + } 34 + hashTable.put(node, id) 35 + queue.push(node) 36 + } 37 + 38 + fun removeAll() { 39 + while (true) { 40 + val node = queue.pop() 41 + if (node != null) { 42 + node.remove() 43 + node.clear() 44 + } else { 45 + remaining = capacity 46 + return 47 + } 48 + } 49 + } 50 + }
-22
src/main/java/com/jagex/runescape/collection/CacheableNode.java
··· 1 - package com.jagex.runescape.collection; 2 - 3 - public class CacheableNode extends Node { 4 - 5 - public void clear() { 6 - if (prev == null) { 7 - return; 8 - } else { 9 - prev.next = next; 10 - next.prev = prev; 11 - next = null; 12 - prev = null; 13 - return; 14 - } 15 - } 16 - 17 - public CacheableNode() { 18 - } 19 - 20 - public CacheableNode next; 21 - public CacheableNode prev; 22 - }
+26
src/main/java/com/jagex/runescape/collection/CacheableNode.kt
··· 1 + package com.jagex.runescape.collection 2 + 3 + /** 4 + * A [Node] subclass that participates in a second linked list layer used by 5 + * [Queue] and [Cache]. The separate [nextQueue]/[prevQueue] pointers here 6 + * allow a node to be in both a [HashTable] bucket chain (via [Node.next]/ 7 + * [Node.previous]) and a [Queue] eviction chain simultaneously — which is 8 + * exactly how the LRU [Cache] works: hash table for O(1) lookup, queue for 9 + * eviction ordering. 10 + * 11 + * In the original Java client, these were also named `next`/`prev` and relied 12 + * on field shadowing. Kotlin doesn't allow field shadowing, so they're renamed 13 + * here to make the dual-link design explicit. 14 + */ 15 + open class CacheableNode : Node() { 16 + @JvmField var nextQueue: CacheableNode? = null 17 + @JvmField var prevQueue: CacheableNode? = null 18 + 19 + fun clear() { 20 + if (prevQueue == null) return 21 + prevQueue!!.nextQueue = nextQueue 22 + nextQueue!!.prevQueue = prevQueue 23 + nextQueue = null 24 + prevQueue = null 25 + } 26 + }
-39
src/main/java/com/jagex/runescape/collection/HashTable.java
··· 1 - package com.jagex.runescape.collection; 2 - 3 - public class HashTable { 4 - 5 - public int size; 6 - public Node cache[]; 7 - 8 - public HashTable(int _size) { 9 - size = _size; 10 - cache = new Node[_size]; 11 - for (int nodeId = 0; nodeId < _size; nodeId++) { 12 - Node node = cache[nodeId] = new Node(); 13 - node.next = node; 14 - node.previous = node; 15 - } 16 - } 17 - 18 - public Node get(long id) { 19 - Node bucket = cache[(int) (id & (size - 1))]; 20 - for (Node node = bucket.next; node != bucket; node = node.next) 21 - if (node.id == id) 22 - return node; 23 - return null; 24 - } 25 - 26 - public void put(Node node, long id) { 27 - if (node.previous != null) 28 - node.remove(); 29 - Node bucket = cache[(int) (id & (size - 1))]; 30 - node.previous = bucket.previous; 31 - node.next = bucket; 32 - node.previous.next = node; 33 - node.next.previous = node; 34 - node.id = id; 35 - return; 36 - } 37 - 38 - 39 - }
+32
src/main/java/com/jagex/runescape/collection/HashTable.kt
··· 1 + package com.jagex.runescape.collection 2 + 3 + /** 4 + * Open-addressing hash table with chained buckets via intrusive [Node] links. 5 + * 6 + * Each bucket is a circular doubly-linked list with a sentinel node. The hash 7 + * function is simply `id AND (size - 1)`, which means [size] must be a power 8 + * of two for correct distribution. This matches the original client behavior. 9 + */ 10 + class HashTable(@JvmField val size: Int) { 11 + @JvmField val cache: Array<Node> = Array(size) { Node().also { it.next = it; it.previous = it } } 12 + 13 + fun get(id: Long): Node? { 14 + val bucket = cache[(id and (size - 1).toLong()).toInt()] 15 + var node = bucket.next 16 + while (node !== bucket) { 17 + if (node!!.id == id) return node 18 + node = node.next 19 + } 20 + return null 21 + } 22 + 23 + fun put(node: Node, id: Long) { 24 + if (node.previous != null) node.remove() 25 + val bucket = cache[(id and (size - 1).toLong()).toInt()] 26 + node.previous = bucket.previous 27 + node.next = bucket 28 + node.previous!!.next = node 29 + node.next!!.previous = node 30 + node.id = id 31 + } 32 + }
-20
src/main/java/com/jagex/runescape/collection/Node.java
··· 1 - package com.jagex.runescape.collection; 2 - 3 - public class Node { 4 - 5 - public long id; 6 - public Node next; 7 - public Node previous; 8 - 9 - public void remove() { 10 - if (previous != null) { 11 - previous.next = next; 12 - next.previous = previous; 13 - next = null; 14 - previous = null; 15 - 16 - } 17 - } 18 - 19 - 20 - }
+26
src/main/java/com/jagex/runescape/collection/Node.kt
··· 1 + package com.jagex.runescape.collection 2 + 3 + /** 4 + * Base node for intrusive linked data structures (hash tables, linked lists). 5 + * 6 + * "Intrusive" means the node pointers live directly on the data object rather 7 + * than in a wrapper. This avoids an extra allocation per element — the same 8 + * pattern the original client uses for every cacheable asset and queue entry. 9 + * 10 + * [id] is the hash key used by [HashTable] for O(1) bucket lookup. 11 + * [next]/[previous] form a circular doubly-linked list within each bucket. 12 + */ 13 + open class Node { 14 + @JvmField var id: Long = 0 15 + @JvmField var next: Node? = null 16 + @JvmField var previous: Node? = null 17 + 18 + fun remove() { 19 + if (previous != null) { 20 + previous!!.next = next 21 + next!!.previous = previous 22 + next = null 23 + previous = null 24 + } 25 + } 26 + }
-59
src/main/java/com/jagex/runescape/collection/Queue.java
··· 1 - package com.jagex.runescape.collection; 2 - 3 - public class Queue { 4 - public CacheableNode head = new CacheableNode(); 5 - public CacheableNode current; 6 - public Queue() { 7 - head.next = head; 8 - head.prev = head; 9 - } 10 - 11 - public void push(CacheableNode node) { 12 - if (node.prev != null) 13 - node.clear(); 14 - node.prev = head.prev; 15 - node.next = head; 16 - node.prev.next = node; 17 - node.next.prev = node; 18 - } 19 - 20 - public CacheableNode pop() { 21 - CacheableNode node = head.next; 22 - if (node == head) { 23 - return null; 24 - } else { 25 - node.clear(); 26 - return node; 27 - } 28 - } 29 - 30 - public CacheableNode first() { 31 - CacheableNode node = head.next; 32 - if (node == head) { 33 - current = null; 34 - return null; 35 - } else { 36 - current = node.next; 37 - return node; 38 - } 39 - } 40 - 41 - public CacheableNode next() { 42 - CacheableNode node = current; 43 - if (node == head) { 44 - current = null; 45 - return null; 46 - } 47 - current = node.next; 48 - return node; 49 - } 50 - 51 - public int size() { 52 - int size = 0; 53 - for (CacheableNode node = head.next; node != head; node = node.next) 54 - size++; 55 - return size; 56 - } 57 - 58 - 59 - }
+69
src/main/java/com/jagex/runescape/collection/Queue.kt
··· 1 + package com.jagex.runescape.collection 2 + 3 + /** 4 + * Circular doubly-linked queue of [CacheableNode]s. Used as the eviction 5 + * order tracker inside [Cache] — most-recently-used nodes get pushed to the 6 + * back, and [pop] removes from the front (least-recently-used). 7 + * 8 + * The [head] sentinel simplifies insert/remove logic by eliminating null 9 + * checks at list boundaries — a classic intrusive-list trick. 10 + */ 11 + class Queue { 12 + @JvmField val head: CacheableNode = CacheableNode() 13 + @JvmField var current: CacheableNode? = null 14 + 15 + init { 16 + head.nextQueue = head 17 + head.prevQueue = head 18 + } 19 + 20 + fun push(node: CacheableNode) { 21 + if (node.prevQueue != null) node.clear() 22 + node.prevQueue = head.prevQueue 23 + node.nextQueue = head 24 + node.prevQueue!!.nextQueue = node 25 + node.nextQueue!!.prevQueue = node 26 + } 27 + 28 + fun pop(): CacheableNode? { 29 + val node = head.nextQueue 30 + return if (node === head) { 31 + null 32 + } else { 33 + node!!.clear() 34 + node 35 + } 36 + } 37 + 38 + fun first(): CacheableNode? { 39 + val node = head.nextQueue 40 + return if (node === head) { 41 + current = null 42 + null 43 + } else { 44 + current = node!!.nextQueue 45 + node 46 + } 47 + } 48 + 49 + fun next(): CacheableNode? { 50 + val node = current 51 + return if (node === head) { 52 + current = null 53 + null 54 + } else { 55 + current = node!!.nextQueue 56 + node 57 + } 58 + } 59 + 60 + fun size(): Int { 61 + var size = 0 62 + var node = head.nextQueue 63 + while (node !== head) { 64 + size++ 65 + node = node!!.nextQueue 66 + } 67 + return size 68 + } 69 + }
-71
src/main/java/com/jagex/runescape/config/Actions.java
··· 1 - package com.jagex.runescape.config; 2 - 3 - /** 4 - * Created by Promises on 16/06/17. 5 - */ 6 - public class Actions { 7 - /** 8 - * The action type for when the examine menu action has been selected. 9 - */ 10 - public static final int EXAMINE_ITEM = 1094; 11 - 12 - /** 13 - * The action type for when a Widget that toggles a setting value has been interacted with. 14 - */ 15 - public static final int TOGGLE_SETTING_WIDGET = 890; 16 - 17 - /** 18 - * The action type to accept a challenge from a player. 19 - */ 20 - public static final int ACCEPT_CHALLENGE = 695; 21 - 22 - /** 23 - * The action type to accept a trade from a player. 24 - */ 25 - public static final int ACCEPT_TRADE = 544; 26 - 27 - /** 28 - * The action type to add a player to the friends list. 29 - */ 30 - public static final int ADD_FRIEND = 762; 31 - 32 - /** 33 - * The action type to add a player to the ignore list. 34 - */ 35 - public static final int ADD_IGNORE = 574; 36 - 37 - /** 38 - * The action type that indicates a usable widget has been interacted with (i.e. clicked). 39 - */ 40 - public static final int USABLE_WIDGET = 70; 41 - 42 - /** 43 - * The action type to remove a player from the ignore list. 44 - */ 45 - public static final int REMOVE_IGNORE = 859; 46 - 47 - /** 48 - * The action that sends the continue dialogue frame. 49 - */ 50 - public static final int CLICK_TO_CONTINUE = 575; 51 - 52 - /** 53 - * The action type that closes the open widgets. 54 - */ 55 - public static final int CLOSE_WIDGETS = 639; 56 - 57 - /** 58 - * The action type to remove a player from the friends list. 59 - */ 60 - public static final int REMOVE_FRIEND = 775; 61 - 62 - /** 63 - * The action type for when a Widget that resets a setting value has been interacted with. 64 - */ 65 - public static final int RESET_SETTING_WIDGET = 518; 66 - 67 - /** 68 - * The action type for sending a private message. 69 - */ 70 - public static final int PRIVATE_MESSAGE = 984; 71 - }
+22
src/main/java/com/jagex/runescape/config/Actions.kt
··· 1 + package com.jagex.runescape.config 2 + 3 + /** 4 + * Menu action type constants. Each value is an opcode the client sends to the 5 + * server when the player selects a context menu option. The server uses these 6 + * to dispatch the correct handler (e.g. trade request, friend list add, etc.). 7 + */ 8 + object Actions { 9 + const val EXAMINE_ITEM = 1094 10 + const val TOGGLE_SETTING_WIDGET = 890 11 + const val ACCEPT_CHALLENGE = 695 12 + const val ACCEPT_TRADE = 544 13 + const val ADD_FRIEND = 762 14 + const val ADD_IGNORE = 574 15 + const val USABLE_WIDGET = 70 16 + const val REMOVE_IGNORE = 859 17 + const val CLICK_TO_CONTINUE = 575 18 + const val CLOSE_WIDGETS = 639 19 + const val REMOVE_FRIEND = 775 20 + const val RESET_SETTING_WIDGET = 518 21 + const val PRIVATE_MESSAGE = 984 22 + }
-152
src/main/java/com/jagex/runescape/config/Configuration.java
··· 1 - package com.jagex.runescape.config; 2 - import org.yaml.snakeyaml.Yaml; 3 - 4 - import java.io.File; 5 - import java.io.FileInputStream; 6 - import java.io.InputStream; 7 - import java.math.BigInteger; 8 - import java.util.Map; 9 - 10 - public class Configuration { 11 - 12 - 13 - public static void read() { 14 - try { 15 - final Yaml yaml = new Yaml(); 16 - InputStream inputStream = null; 17 - 18 - // Filesystem config overrides bundled defaults 19 - File externalConfig = new File("./config/client-config.yaml"); 20 - if (externalConfig.exists()) { 21 - inputStream = new FileInputStream(externalConfig); 22 - System.out.println("Loaded config from ./config/client-config.yaml"); 23 - } else { 24 - inputStream = Configuration.class.getResourceAsStream("/client-config.yaml"); 25 - if (inputStream != null) { 26 - System.out.println("Loaded bundled config from jar"); 27 - } 28 - } 29 - 30 - if (inputStream == null) { 31 - System.out.println("No config found, using defaults."); 32 - return; 33 - } 34 - 35 - final Map<String, Object> obj = yaml.load(inputStream); 36 - inputStream.close(); 37 - 38 - final Map<String, Object> net = (Map<String, Object>) obj.get("net"); 39 - final Map<String, Object> cache = (Map<String, Object>) obj.get("cache"); 40 - final Map<String, Object> rsa = (Map<String, Object>) obj.get("rsa"); 41 - final Map<String, Object> login = (Map<String, Object>) obj.get("login"); 42 - final Map<String, Object> game = (Map<String, Object>) obj.get("game"); 43 - 44 - SERVER_ADDRESS = (String) net.get("address"); 45 - GAME_PORT = (int) net.get("game_port"); 46 - JAGGRAB_PORT = (int) net.get("jaggrab_port"); 47 - ONDEMAND_PORT = (int) net.get("ondemand_port"); 48 - HTTP_PORT = (int) net.get("http_port"); 49 - CACHE_NAME = (String) cache.get("cacheDir"); 50 - JAGGRAB_ENABLED = (boolean) cache.get("jaggrabEnabled"); 51 - RSA_ENABLED = (boolean) rsa.get("rsaEnabled"); 52 - RSA_PUBLIC_KEY = new BigInteger(String.valueOf((int) rsa.get("rsaPub"))); 53 - RSA_MODULUS = (BigInteger) rsa.get("rsaModulus"); 54 - USE_STATIC_DETAILS = (boolean) login.get("useStaticCredentials"); 55 - USERNAME = (String) login.get("username"); 56 - PASSWORD = (String) login.get("password"); 57 - ROOFS_ENABLED = (boolean) game.get("roofsEnabled"); 58 - FREE_TELEPORTS = (boolean) game.get("freeTeleports"); 59 - DEBUG_CONTEXT = (boolean) game.get("debugContext"); 60 - 61 - if(USERNAME == null) { 62 - USERNAME = ""; 63 - } 64 - 65 - if(PASSWORD == null) { 66 - PASSWORD = ""; 67 - } 68 - } catch(Exception e) { 69 - System.out.println("Unable to load config: " + e.getMessage()); 70 - } 71 - } 72 - 73 - /** 74 - * IP Address or Hostname of the server to establish a connection. 75 - */ 76 - public static String SERVER_ADDRESS = "127.0.0.1"; 77 - 78 - /** 79 - * Name of the cache folder located in the users home directory. 80 - */ 81 - public static String CACHE_NAME = ".377cache"; 82 - 83 - /** 84 - * Port for establishing a connection to the game server. 85 - */ 86 - public static int GAME_PORT = 43594; 87 - 88 - /** 89 - * Port for establishing a connection to the on demand service. 90 - */ 91 - public static int ONDEMAND_PORT = 43596; 92 - 93 - /** 94 - * Port for establishing a connection to the update server. 95 - */ 96 - public static int JAGGRAB_PORT = 43595; 97 - 98 - /** 99 - * Port for establishing a backup connection to the update 100 - * server in case the initial JAGGRAB connection fails. 101 - */ 102 - public static int HTTP_PORT = 80; 103 - 104 - /** 105 - * Whether or not the update server should be used. 106 - */ 107 - public static boolean JAGGRAB_ENABLED = true; 108 - 109 - /** 110 - * Whether or not the network packets should be encrypted. 111 - */ 112 - public static boolean RSA_ENABLED = false; 113 - 114 - /** 115 - * Public key to be used in RSA network encryption. 116 - */ 117 - public static BigInteger RSA_PUBLIC_KEY = new BigInteger("65537"); 118 - 119 - /** 120 - * Modulus to be used in the RSA network encryption. 121 - */ 122 - public static BigInteger RSA_MODULUS = new BigInteger("170266381807335046121774073514220583891686029487165562794998484549236036467227923571770256617931840775621072487838687650522710227973331693237285456731778528244126984080232314114323601116304887478969296070648644633713088027922830600712492972687351204275625149978223159432963210789506993409208545916714905193639"); 123 - 124 - /** 125 - * Use static username/password pair. 126 - */ 127 - public static boolean USE_STATIC_DETAILS = false; 128 - 129 - /** 130 - * Static username and password 131 - */ 132 - 133 - public static String USERNAME = "Promises"; 134 - public static String PASSWORD = "Testing"; 135 - 136 - /** 137 - * Do you want to render roofs 138 - */ 139 - public static boolean ROOFS_ENABLED = true; 140 - 141 - /** 142 - * Always light up teleports 143 - */ 144 - public static boolean FREE_TELEPORTS = false; 145 - 146 - /** 147 - * When rightclicking objects show id and location 148 - */ 149 - public static boolean DEBUG_CONTEXT = true; 150 - 151 - 152 - }
+86
src/main/java/com/jagex/runescape/config/Configuration.kt
··· 1 + package com.jagex.runescape.config 2 + 3 + import org.yaml.snakeyaml.Yaml 4 + import java.io.File 5 + import java.io.FileInputStream 6 + import java.math.BigInteger 7 + 8 + /** 9 + * Client configuration loaded from YAML at startup. Filesystem config at 10 + * `./config/client-config.yaml` takes precedence over the bundled JAR default. 11 + * If neither is found, the hardcoded defaults below are used. 12 + */ 13 + object Configuration { 14 + @JvmField var SERVER_ADDRESS = "127.0.0.1" 15 + @JvmField var CACHE_NAME = ".377cache" 16 + @JvmField var GAME_PORT = 43594 17 + @JvmField var ONDEMAND_PORT = 43596 18 + @JvmField var JAGGRAB_PORT = 43595 19 + @JvmField var HTTP_PORT = 80 20 + @JvmField var JAGGRAB_ENABLED = true 21 + @JvmField var RSA_ENABLED = false 22 + @JvmField var RSA_PUBLIC_KEY: BigInteger = BigInteger("65537") 23 + @JvmField var RSA_MODULUS: BigInteger = BigInteger( 24 + "170266381807335046121774073514220583891686029487165562794998484549236036467227" + 25 + "923571770256617931840775621072487838687650522710227973331693237285456731778528" + 26 + "244126984080232314114323601116304887478969296070648644633713088027922830600712" + 27 + "492972687351204275625149978223159432963210789506993409208545916714905193639" 28 + ) 29 + @JvmField var USE_STATIC_DETAILS = false 30 + @JvmField var USERNAME = "Promises" 31 + @JvmField var PASSWORD = "Testing" 32 + @JvmField var ROOFS_ENABLED = true 33 + @JvmField var FREE_TELEPORTS = false 34 + @JvmField var DEBUG_CONTEXT = true 35 + 36 + @JvmStatic 37 + @Suppress("UNCHECKED_CAST") 38 + fun read() { 39 + try { 40 + val yaml = Yaml() 41 + val inputStream = run { 42 + val external = File("./config/client-config.yaml") 43 + if (external.exists()) { 44 + println("Loaded config from ./config/client-config.yaml") 45 + FileInputStream(external) 46 + } else { 47 + val bundled = Configuration::class.java.getResourceAsStream("/client-config.yaml") 48 + if (bundled != null) println("Loaded bundled config from jar") 49 + bundled 50 + } 51 + } 52 + 53 + if (inputStream == null) { 54 + println("No config found, using defaults.") 55 + return 56 + } 57 + 58 + val obj = inputStream.use { yaml.load<Map<String, Any>>(it) } 59 + 60 + val net = obj["net"] as Map<String, Any> 61 + val cache = obj["cache"] as Map<String, Any> 62 + val rsa = obj["rsa"] as Map<String, Any> 63 + val login = obj["login"] as Map<String, Any> 64 + val game = obj["game"] as Map<String, Any> 65 + 66 + SERVER_ADDRESS = net["address"] as String 67 + GAME_PORT = net["game_port"] as Int 68 + JAGGRAB_PORT = net["jaggrab_port"] as Int 69 + ONDEMAND_PORT = net["ondemand_port"] as Int 70 + HTTP_PORT = net["http_port"] as Int 71 + CACHE_NAME = cache["cacheDir"] as String 72 + JAGGRAB_ENABLED = cache["jaggrabEnabled"] as Boolean 73 + RSA_ENABLED = rsa["rsaEnabled"] as Boolean 74 + RSA_PUBLIC_KEY = BigInteger((rsa["rsaPub"] as Int).toString()) 75 + RSA_MODULUS = rsa["rsaModulus"] as BigInteger 76 + USE_STATIC_DETAILS = login["useStaticCredentials"] as Boolean 77 + USERNAME = (login["username"] as? String) ?: "" 78 + PASSWORD = (login["password"] as? String) ?: "" 79 + ROOFS_ENABLED = game["roofsEnabled"] as Boolean 80 + FREE_TELEPORTS = game["freeTeleports"] as Boolean 81 + DEBUG_CONTEXT = game["debugContext"] as Boolean 82 + } catch (e: Exception) { 83 + println("Unable to load config: ${e.message}") 84 + } 85 + } 86 + }
-96
src/main/java/com/jagex/runescape/config/IncomingPacket.java
··· 1 - package com.jagex.runescape.config; 2 - 3 - public enum IncomingPacket { 4 - REMOVE_LANDSCAPE_OBJECT(88), 5 - SET_LANDSCAPE_OBJECT(152), 6 - REMOVE_GROUND_ITEM(208), 7 - UPDATE_GROUND_ITEM_AMOUNT(121), 8 - SET_GROUND_ITEM(107), 9 - SET_PLAYER_GROUND_ITEM(106), 10 - UPDATE_GROUND_ITEMS_AND_LANDSCAPE_OBJECTS(183), 11 - CLEAR_GROUND_ITEMS_AND_LANDSCAPE_OBJECTS(40), 12 - 13 - SHOW_STILL_GRAPHICS(59), 14 - SHOW_PROJECTILE(181), 15 - SHOW_HINT_ICON(199), 16 - 17 - PLAY_SOUND(26), 18 - PLAY_POSITION_SOUND(41), 19 - PLAY_SONG(220), 20 - PLAY_TEMP_SONG(249), 21 - 22 - SYSTEM_UPDATE(190), 23 - CHATBOX_MESSAGE(63), 24 - 25 - CONSTRUCT_MAP_REGION(53), 26 - UPDATE_ACTIVE_MAP_REGION(222), 27 - 28 - SET_WIDGET_ANIMATION(2), 29 - SET_WIDGET_ITEM_MODEL(21), 30 - SET_WIDGET_PLAYER_HEAD(255), 31 - SET_CHAT_INPUT_TYPE_2(6), 32 - SET_WIDGET_MODEL_1(216), 33 - SET_WIDGET_MODEL_2(162), 34 - RESET_WIDGET_SETTINGS(113), 35 - FLASH_TAB_ICON(238), 36 - SET_TAB_WIDGET(10), 37 - SET_ACTIVE_TAB(252), 38 - CLEAR_WIDGET_ITEMS(219), 39 - UPDATE_ALL_WIDGET_ITEMS(206), 40 - UPDATE_WIDGET_ITEMS_BY_SLOT(134), 41 - UPDATE_WIDGET_SETTING_LARGE(115), 42 - UPDATE_WIDGET_SETTING_SMALL(182), 43 - UPDATE_CHAT_SETTINGS(201), 44 - UPDATE_WIDGET_COLOR(218), 45 - UPDATE_WIDGET_STRING(232), 46 - UPDATE_WIDGET_SCROLL_POSITION(200), 47 - UPDATE_WIDGET_MODEL_DISPLAY(186), 48 - HIDE_WIDGET(82), 49 - UPDATE_WIDGET_POSITION(166), 50 - SHOW_SIDEBAR_OVERLAY_WIDGET(246), 51 - SHOW_GAME_WIDGET(159), 52 - SHOW_SIDEBAR_AND_GAME_WIDGET(128), 53 - SHOW_WALKABLE_WIDGET(50), 54 - UPDATE_WELCOME_SCREEN(76), 55 - SHOW_DIALOG(158), 56 - SHOW_CHATBOX_WIDGET(109), 57 - SHOW_FULLSCREEN_WIDGET(253), 58 - CLOSE_ALL_WIDGETS(29), 59 - SET_MINIMAP_STATE(156), 60 - 61 - PLAYER_UPDATING(90), 62 - NPC_UPDATING(71), 63 - RESET_MOB_ANIMATIONS(13), 64 - 65 - UPDATE_FRIEND_LIST_STATUS(251), 66 - UPDATE_IGNORE_LIST(226), 67 - PRIVATE_MESSAGE_RECEIVED(135), 68 - UPDATE_FRIEND(78), 69 - UPDATE_PLAYER_CONTEXT_OPTION(157), 70 - 71 - SEND_REFERENCE_POSITION(75), 72 - 73 - UPDATE_MEMBERSHIP_AND_WORLD_INDEX(126), 74 - UPDATE_RUN_ENERGY(125), 75 - UPDATE_SKILL(49), 76 - UPDATE_CARRY_WEIGHT(174), 77 - SEND_LOGOUT(5), 78 - 79 - RESET_CUTSCENE_CAMERA(148), 80 - MOVE_CUTSCENE_CAMERA(167), 81 - CAMERA_SHAKE(67); 82 - 83 - private int packetId; 84 - 85 - IncomingPacket(int packetId){ 86 - this.packetId = packetId; 87 - } 88 - 89 - public int getId() { 90 - return packetId; 91 - } 92 - 93 - public boolean equals(int opcode) { 94 - return opcode == this.packetId; 95 - } 96 - }
+90
src/main/java/com/jagex/runescape/config/IncomingPacket.kt
··· 1 + package com.jagex.runescape.config 2 + 3 + /** 4 + * Server-to-client packet opcodes. Each entry maps a semantic name to the 5 + * integer opcode the server sends. The client's packet handler switches on 6 + * these to dispatch rendering, UI updates, entity sync, etc. 7 + */ 8 + enum class IncomingPacket(@JvmField val id: Int) { 9 + REMOVE_LANDSCAPE_OBJECT(88), 10 + SET_LANDSCAPE_OBJECT(152), 11 + REMOVE_GROUND_ITEM(208), 12 + UPDATE_GROUND_ITEM_AMOUNT(121), 13 + SET_GROUND_ITEM(107), 14 + SET_PLAYER_GROUND_ITEM(106), 15 + UPDATE_GROUND_ITEMS_AND_LANDSCAPE_OBJECTS(183), 16 + CLEAR_GROUND_ITEMS_AND_LANDSCAPE_OBJECTS(40), 17 + 18 + SHOW_STILL_GRAPHICS(59), 19 + SHOW_PROJECTILE(181), 20 + SHOW_HINT_ICON(199), 21 + 22 + PLAY_SOUND(26), 23 + PLAY_POSITION_SOUND(41), 24 + PLAY_SONG(220), 25 + PLAY_TEMP_SONG(249), 26 + 27 + SYSTEM_UPDATE(190), 28 + CHATBOX_MESSAGE(63), 29 + 30 + CONSTRUCT_MAP_REGION(53), 31 + UPDATE_ACTIVE_MAP_REGION(222), 32 + 33 + SET_WIDGET_ANIMATION(2), 34 + SET_WIDGET_ITEM_MODEL(21), 35 + SET_WIDGET_PLAYER_HEAD(255), 36 + SET_CHAT_INPUT_TYPE_2(6), 37 + SET_WIDGET_MODEL_1(216), 38 + SET_WIDGET_MODEL_2(162), 39 + RESET_WIDGET_SETTINGS(113), 40 + FLASH_TAB_ICON(238), 41 + SET_TAB_WIDGET(10), 42 + SET_ACTIVE_TAB(252), 43 + CLEAR_WIDGET_ITEMS(219), 44 + UPDATE_ALL_WIDGET_ITEMS(206), 45 + UPDATE_WIDGET_ITEMS_BY_SLOT(134), 46 + UPDATE_WIDGET_SETTING_LARGE(115), 47 + UPDATE_WIDGET_SETTING_SMALL(182), 48 + UPDATE_CHAT_SETTINGS(201), 49 + UPDATE_WIDGET_COLOR(218), 50 + UPDATE_WIDGET_STRING(232), 51 + UPDATE_WIDGET_SCROLL_POSITION(200), 52 + UPDATE_WIDGET_MODEL_DISPLAY(186), 53 + HIDE_WIDGET(82), 54 + UPDATE_WIDGET_POSITION(166), 55 + SHOW_SIDEBAR_OVERLAY_WIDGET(246), 56 + SHOW_GAME_WIDGET(159), 57 + SHOW_SIDEBAR_AND_GAME_WIDGET(128), 58 + SHOW_WALKABLE_WIDGET(50), 59 + UPDATE_WELCOME_SCREEN(76), 60 + SHOW_DIALOG(158), 61 + SHOW_CHATBOX_WIDGET(109), 62 + SHOW_FULLSCREEN_WIDGET(253), 63 + CLOSE_ALL_WIDGETS(29), 64 + SET_MINIMAP_STATE(156), 65 + 66 + PLAYER_UPDATING(90), 67 + NPC_UPDATING(71), 68 + RESET_MOB_ANIMATIONS(13), 69 + 70 + UPDATE_FRIEND_LIST_STATUS(251), 71 + UPDATE_IGNORE_LIST(226), 72 + PRIVATE_MESSAGE_RECEIVED(135), 73 + UPDATE_FRIEND(78), 74 + UPDATE_PLAYER_CONTEXT_OPTION(157), 75 + 76 + SEND_REFERENCE_POSITION(75), 77 + 78 + UPDATE_MEMBERSHIP_AND_WORLD_INDEX(126), 79 + UPDATE_RUN_ENERGY(125), 80 + UPDATE_SKILL(49), 81 + UPDATE_CARRY_WEIGHT(174), 82 + SEND_LOGOUT(5), 83 + 84 + RESET_CUTSCENE_CAMERA(148), 85 + MOVE_CUTSCENE_CAMERA(167), 86 + CAMERA_SHAKE(67); 87 + 88 + /** Check if a raw opcode matches this packet. */ 89 + fun equals(opcode: Int): Boolean = opcode == id 90 + }
-8
src/main/java/com/jagex/runescape/config/MovementType.java
··· 1 - package com.jagex.runescape.config; 2 - 3 - public enum MovementType { 4 - NONE, 5 - WALK, 6 - RUN, 7 - TELEPORT 8 - }
+8
src/main/java/com/jagex/runescape/config/MovementType.kt
··· 1 + package com.jagex.runescape.config 2 + 3 + enum class MovementType { 4 + NONE, 5 + WALK, 6 + RUN, 7 + TELEPORT 8 + }
-357
src/main/java/com/jagex/runescape/net/Buffer.java
··· 1 - package com.jagex.runescape.net; 2 - 3 - import com.jagex.runescape.collection.CacheableNode; 4 - import com.jagex.runescape.config.Configuration; 5 - 6 - import java.math.BigInteger; 7 - 8 - public class Buffer extends CacheableNode { 9 - 10 - 11 - public byte[] buffer; 12 - public int currentPosition; 13 - public int bitPosition; 14 - private static final int[] BIT_MASKS = {0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 15 - 32767, 65535, 0x1ffff, 0x3ffff, 0x7ffff, 0xfffff, 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff, 0x1ffffff, 16 - 0x3ffffff, 0x7ffffff, 0xfffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, -1}; 17 - public ISAACCipher random; 18 - 19 - 20 - public static Buffer allocate(int sizeMode) { 21 - Buffer buffer = new Buffer(); 22 - buffer.currentPosition = 0; 23 - if (sizeMode == 0) 24 - buffer.buffer = new byte[100]; 25 - else if (sizeMode == 1) 26 - buffer.buffer = new byte[5000]; 27 - else 28 - buffer.buffer = new byte[30000]; 29 - return buffer; 30 - } 31 - 32 - public Buffer() { 33 - } 34 - 35 - public Buffer(byte[] buffer) { 36 - this.buffer = buffer; 37 - this.currentPosition = 0; 38 - } 39 - 40 - public void putOpcode(int opcode) { 41 - System.out.println("[PACKET] Sending opcode: " + opcode); 42 - //buffer[currentPosition++] = (byte) (opcode + random.nextInt()); 43 - buffer[currentPosition++] = (byte) opcode; 44 - } 45 - 46 - public void putByte(int value) { 47 - buffer[currentPosition++] = (byte) value; 48 - } 49 - 50 - public void putShortBE(int value) { 51 - buffer[currentPosition++] = (byte) (value >> 8); 52 - buffer[currentPosition++] = (byte) value; 53 - } 54 - 55 - public void putShortLECopy(int value) { 56 - buffer[currentPosition++] = (byte) value; 57 - buffer[currentPosition++] = (byte) (value >> 8); 58 - } 59 - 60 - public void putMediumBE(int value) { 61 - buffer[currentPosition++] = (byte) (value >> 16); 62 - buffer[currentPosition++] = (byte) (value >> 8); 63 - buffer[currentPosition++] = (byte) value; 64 - } 65 - 66 - public void putIntBE(int value) { 67 - buffer[currentPosition++] = (byte) (value >> 24); 68 - buffer[currentPosition++] = (byte) (value >> 16); 69 - buffer[currentPosition++] = (byte) (value >> 8); 70 - buffer[currentPosition++] = (byte) value; 71 - } 72 - 73 - public void putIntLE(int value) { 74 - buffer[currentPosition++] = (byte) value; 75 - buffer[currentPosition++] = (byte) (value >> 8); 76 - buffer[currentPosition++] = (byte) (value >> 16); 77 - buffer[currentPosition++] = (byte) (value >> 24); 78 - } 79 - 80 - public void putLongBE(long value) { 81 - buffer[currentPosition++] = (byte) (int) (value >> 56); 82 - buffer[currentPosition++] = (byte) (int) (value >> 48); 83 - buffer[currentPosition++] = (byte) (int) (value >> 40); 84 - buffer[currentPosition++] = (byte) (int) (value >> 32); 85 - buffer[currentPosition++] = (byte) (int) (value >> 24); 86 - buffer[currentPosition++] = (byte) (int) (value >> 16); 87 - buffer[currentPosition++] = (byte) (int) (value >> 8); 88 - buffer[currentPosition++] = (byte) (int) value; 89 - } 90 - 91 - public void putString(String str) { 92 - if(str == null) { 93 - str = ""; 94 - } 95 - 96 - byte[] bytes = new byte[str.length()]; 97 - for (int i = 0; i < bytes.length; i++) { 98 - bytes[i] = (byte) str.charAt(i); 99 - } 100 - System.arraycopy(bytes, 0, buffer, currentPosition, bytes.length); 101 - currentPosition += str.length(); 102 - buffer[currentPosition++] = 10; 103 - } 104 - 105 - public void putBytes(byte[] bytes, int start, int length) { 106 - for (int pos = start; pos < start + length; pos++) 107 - buffer[currentPosition++] = bytes[pos]; 108 - } 109 - 110 - public void putLength(int length) { 111 - buffer[currentPosition - length - 1] = (byte) length; 112 - } 113 - 114 - public int getUnsignedByte() { 115 - return buffer[currentPosition++] & 0xff; 116 - } 117 - 118 - public byte getByte() { 119 - return buffer[currentPosition++]; 120 - } 121 - 122 - public int getUnsignedShortBE() { 123 - currentPosition += 2; 124 - return ((buffer[currentPosition - 2] & 0xff) << 8) + (buffer[currentPosition - 1] & 0xff); 125 - } 126 - 127 - public int getShortBE() { 128 - currentPosition += 2; 129 - int i = ((buffer[currentPosition - 2] & 0xff) << 8) + (buffer[currentPosition - 1] & 0xff); 130 - if (i > 32767) 131 - i -= 0x10000; 132 - return i; 133 - } 134 - 135 - public int getMediumBE() { 136 - currentPosition += 3; 137 - return ((buffer[currentPosition - 3] & 0xff) << 16) + ((buffer[currentPosition - 2] & 0xff) << 8) 138 - + (buffer[currentPosition - 1] & 0xff); 139 - } 140 - 141 - public int getIntBE() { 142 - currentPosition += 4; 143 - return ((buffer[currentPosition - 4] & 0xff) << 24) + ((buffer[currentPosition - 3] & 0xff) << 16) 144 - + ((buffer[currentPosition - 2] & 0xff) << 8) + (buffer[currentPosition - 1] & 0xff); 145 - } 146 - 147 - public long getLongBE() { 148 - long l = getIntBE() & 0xffffffffL; 149 - long l1 = getIntBE() & 0xffffffffL; 150 - return (l << 32) + l1; 151 - } 152 - 153 - public String getString() { 154 - int start = currentPosition; 155 - moveBufferToTarget((byte) 10); 156 - return new String(buffer, start, currentPosition - start - 1); 157 - } 158 - 159 - private void moveBufferToTarget(byte target) { 160 - if(buffer[currentPosition++] != target){ 161 - moveBufferToTarget(target); 162 - } 163 - } 164 - 165 - public byte[] getStringBytes() { 166 - int start = currentPosition; 167 - moveBufferToTarget((byte) 10); 168 - byte[] bytes = new byte[currentPosition - start - 1]; 169 - if (currentPosition - 1 - start >= 0) 170 - System.arraycopy(buffer, start, bytes, 0, currentPosition - 1 - start); 171 - return bytes; 172 - } 173 - 174 - public void getBytes(byte[] bytes, int start, int len) { 175 - for (int pos = start; pos < start + len; pos++) 176 - bytes[pos] = buffer[currentPosition++]; 177 - } 178 - 179 - public void initBitAccess() { 180 - bitPosition = currentPosition * 8; 181 - } 182 - 183 - public int getBits(int numBits) { 184 - int k = bitPosition >> 3; 185 - int l = 8 - (bitPosition & 7); 186 - int value = 0; 187 - bitPosition += numBits; 188 - for (; numBits > l; l = 8) { 189 - value += (buffer[k++] & BIT_MASKS[l]) << numBits - l; 190 - numBits -= l; 191 - } 192 - 193 - if (numBits == l) 194 - value += buffer[k] & BIT_MASKS[l]; 195 - else 196 - value += buffer[k] >> l - numBits & BIT_MASKS[numBits]; 197 - return value; 198 - } 199 - 200 - public void finishBitAccess() { 201 - currentPosition = (bitPosition + 7) / 8; 202 - } 203 - 204 - public int getSignedSmart() { 205 - int peek = buffer[currentPosition] & 0xff; 206 - if (peek < 128) 207 - return getUnsignedByte() - 64; 208 - else 209 - return getUnsignedShortBE() - 49152; 210 - } 211 - 212 - public int getSmart() { 213 - int peek = buffer[currentPosition] & 0xff; 214 - if (peek < 128) 215 - return getUnsignedByte(); 216 - else 217 - return getUnsignedShortBE() - 32768; 218 - } 219 - 220 - public void encrypt(BigInteger modulus, BigInteger key) { 221 - int length = currentPosition; 222 - currentPosition = 0; 223 - byte[] bytes = new byte[length]; 224 - 225 - getBytes(bytes, 0, length); 226 - 227 - BigInteger raw = new BigInteger(bytes); 228 - 229 - if (Configuration.RSA_ENABLED) 230 - bytes = raw.modPow(key, modulus).toByteArray(); 231 - else 232 - bytes = raw.toByteArray(); 233 - 234 - currentPosition = 0; 235 - 236 - putByte(bytes.length); 237 - putBytes(bytes, 0, bytes.length); 238 - } 239 - 240 - public void putOffsetByte(int value) { 241 - buffer[currentPosition++] = (byte) (value + 128); 242 - } 243 - 244 - public void putInvertedByte(int value) { 245 - buffer[currentPosition++] = (byte) (-value); 246 - } 247 - 248 - public void putNegativeOffsetByte(int value) { 249 - buffer[currentPosition++] = (byte) (128 - value); 250 - } 251 - 252 - public int getUnsignedPostNegativeOffsetByte() { 253 - return buffer[currentPosition++] - 128 & 0xff; 254 - } 255 - 256 - public int getUnsignedInvertedByte() { 257 - return -buffer[currentPosition++] & 0xff; 258 - } 259 - 260 - public int getUnsignedPreNegativeOffsetByte() { 261 - return 128 - buffer[currentPosition++] & 0xff; 262 - } 263 - 264 - public byte getPostNegativeOffsetByte() { 265 - return (byte) (buffer[currentPosition++] - 128); 266 - } 267 - 268 - public byte getInvertedByte() { 269 - return (byte) (-buffer[currentPosition++]); 270 - } 271 - 272 - public byte getPreNegativeOffsetByte() { 273 - return (byte) (128 - buffer[currentPosition++]); 274 - } 275 - 276 - public void putShortLE(int value) { 277 - buffer[currentPosition++] = (byte) value; 278 - buffer[currentPosition++] = (byte) (value >> 8); 279 - } 280 - 281 - public void putOffsetShortBE(int value) { 282 - buffer[currentPosition++] = (byte) (value >> 8); 283 - buffer[currentPosition++] = (byte) (value + 128); 284 - } 285 - 286 - public void putOffsetShortLE(int value) { 287 - buffer[currentPosition++] = (byte) (value + 128); 288 - buffer[currentPosition++] = (byte) (value >> 8); 289 - } 290 - 291 - public int getUnsignedShortLE() { 292 - currentPosition += 2; 293 - return ((buffer[currentPosition - 1] & 0xff) << 8) + (buffer[currentPosition - 2] & 0xff); 294 - } 295 - 296 - public int getUnsignedNegativeOffsetShortBE() { 297 - currentPosition += 2; 298 - return ((buffer[currentPosition - 2] & 0xff) << 8) + (buffer[currentPosition - 1] - 128 & 0xff); 299 - } 300 - 301 - public int getUnsignedNegativeOffsetShortLE() { 302 - currentPosition += 2; 303 - return ((buffer[currentPosition - 1] & 0xff) << 8) + (buffer[currentPosition - 2] - 128 & 0xff); 304 - } 305 - 306 - public int getShortLE() { 307 - currentPosition += 2; 308 - int j = ((buffer[currentPosition - 1] & 0xff) << 8) + (buffer[currentPosition - 2] & 0xff); 309 - if (j > 0x7fff) 310 - j -= 0x10000; 311 - return j; 312 - } 313 - 314 - public int getNegativeOffsetShortBE() { 315 - currentPosition += 2; 316 - int i = ((buffer[currentPosition - 2] & 0xff) << 8) + (buffer[currentPosition - 1] - 128 & 0xff); 317 - if (i > 32767) 318 - i -= 0x10000; 319 - return i; 320 - } 321 - 322 - public int getMediumME() { 323 - currentPosition += 3; 324 - return ((buffer[currentPosition - 2] & 0xff) << 16) + ((buffer[currentPosition - 3] & 0xff) << 8) 325 - + (buffer[currentPosition - 1] & 0xff); 326 - } 327 - 328 - public int getIntLE() { 329 - currentPosition += 4; 330 - return ((buffer[currentPosition - 1] & 0xff) << 24) + ((buffer[currentPosition - 2] & 0xff) << 16) 331 - + ((buffer[currentPosition - 3] & 0xff) << 8) + (buffer[currentPosition - 4] & 0xff); 332 - } 333 - 334 - public int getIntME1() { 335 - currentPosition += 4; 336 - return ((buffer[currentPosition - 2] & 0xff) << 24) + ((buffer[currentPosition - 1] & 0xff) << 16) 337 - + ((buffer[currentPosition - 4] & 0xff) << 8) + (buffer[currentPosition - 3] & 0xff); 338 - } 339 - 340 - public int getIntME2() { 341 - currentPosition += 4; 342 - return ((buffer[currentPosition - 3] & 0xff) << 24) + ((buffer[currentPosition - 4] & 0xff) << 16) 343 - + ((buffer[currentPosition - 1] & 0xff) << 8) + (buffer[currentPosition - 2] & 0xff); 344 - } 345 - 346 - public void getBytesReverse(byte[] bytes, int start, int len) { 347 - for (int pos = (start + len) - 1; pos >= start; pos--) 348 - bytes[pos] = buffer[currentPosition++]; 349 - } 350 - 351 - public void getBytesAdded(byte[] bytes, int start, int len) { 352 - for (int pos = start; pos < start + len; pos++) 353 - bytes[pos] = (byte) (buffer[currentPosition++] - 128); 354 - } 355 - 356 - 357 - }
+371
src/main/java/com/jagex/runescape/net/Buffer.kt
··· 1 + package com.jagex.runescape.net 2 + 3 + import com.jagex.runescape.collection.CacheableNode 4 + import com.jagex.runescape.config.Configuration 5 + import java.math.BigInteger 6 + 7 + /** 8 + * Binary packet buffer for the RS377 protocol. Extends [CacheableNode] so 9 + * instances can live in the LRU [com.jagex.runescape.collection.Cache] and be 10 + * reused without allocation. 11 + * 12 + * All read methods advance [currentPosition] and return the decoded value. 13 + * All write methods append at [currentPosition] and advance it. 14 + * 15 + * Bit-access mode ([initBitAccess]/[getBits]/[finishBitAccess]) is used for 16 + * the player/NPC update protocol where fields are packed at arbitrary bit widths. 17 + * 18 + * The "offset", "inverted", and "negative offset" byte variants are protocol 19 + * obfuscation transforms the original client uses — each applies a simple 20 + * arithmetic transform (+128, negate, 128-x) to make packet sniffing harder. 21 + */ 22 + open class Buffer : CacheableNode { 23 + 24 + @JvmField var buffer: ByteArray 25 + @JvmField var currentPosition: Int = 0 26 + @JvmField var bitPosition: Int = 0 27 + @JvmField var random: ISAACCipher? = null 28 + 29 + constructor() { 30 + buffer = ByteArray(0) 31 + } 32 + 33 + constructor(buffer: ByteArray) { 34 + this.buffer = buffer 35 + this.currentPosition = 0 36 + } 37 + 38 + // -- Write methods -- 39 + 40 + fun putOpcode(opcode: Int) { 41 + println("[PACKET] Sending opcode: $opcode") 42 + buffer[currentPosition++] = opcode.toByte() 43 + } 44 + 45 + fun putByte(value: Int) { 46 + buffer[currentPosition++] = value.toByte() 47 + } 48 + 49 + fun putShortBE(value: Int) { 50 + buffer[currentPosition++] = (value ushr 8).toByte() 51 + buffer[currentPosition++] = value.toByte() 52 + } 53 + 54 + fun putShortLECopy(value: Int) { 55 + buffer[currentPosition++] = value.toByte() 56 + buffer[currentPosition++] = (value ushr 8).toByte() 57 + } 58 + 59 + fun putMediumBE(value: Int) { 60 + buffer[currentPosition++] = (value ushr 16).toByte() 61 + buffer[currentPosition++] = (value ushr 8).toByte() 62 + buffer[currentPosition++] = value.toByte() 63 + } 64 + 65 + fun putIntBE(value: Int) { 66 + buffer[currentPosition++] = (value ushr 24).toByte() 67 + buffer[currentPosition++] = (value ushr 16).toByte() 68 + buffer[currentPosition++] = (value ushr 8).toByte() 69 + buffer[currentPosition++] = value.toByte() 70 + } 71 + 72 + fun putIntLE(value: Int) { 73 + buffer[currentPosition++] = value.toByte() 74 + buffer[currentPosition++] = (value ushr 8).toByte() 75 + buffer[currentPosition++] = (value ushr 16).toByte() 76 + buffer[currentPosition++] = (value ushr 24).toByte() 77 + } 78 + 79 + fun putLongBE(value: Long) { 80 + buffer[currentPosition++] = (value ushr 56).toByte() 81 + buffer[currentPosition++] = (value ushr 48).toByte() 82 + buffer[currentPosition++] = (value ushr 40).toByte() 83 + buffer[currentPosition++] = (value ushr 32).toByte() 84 + buffer[currentPosition++] = (value ushr 24).toByte() 85 + buffer[currentPosition++] = (value ushr 16).toByte() 86 + buffer[currentPosition++] = (value ushr 8).toByte() 87 + buffer[currentPosition++] = value.toByte() 88 + } 89 + 90 + fun putString(str: String?) { 91 + val s = str ?: "" 92 + for (ch in s) { 93 + buffer[currentPosition++] = ch.code.toByte() 94 + } 95 + buffer[currentPosition++] = 10 96 + } 97 + 98 + fun putBytes(bytes: ByteArray, start: Int, length: Int) { 99 + for (pos in start until start + length) { 100 + buffer[currentPosition++] = bytes[pos] 101 + } 102 + } 103 + 104 + fun putLength(length: Int) { 105 + buffer[currentPosition - length - 1] = length.toByte() 106 + } 107 + 108 + fun putOffsetByte(value: Int) { 109 + buffer[currentPosition++] = (value + 128).toByte() 110 + } 111 + 112 + fun putInvertedByte(value: Int) { 113 + buffer[currentPosition++] = (-value).toByte() 114 + } 115 + 116 + fun putNegativeOffsetByte(value: Int) { 117 + buffer[currentPosition++] = (128 - value).toByte() 118 + } 119 + 120 + fun putShortLE(value: Int) { 121 + buffer[currentPosition++] = value.toByte() 122 + buffer[currentPosition++] = (value ushr 8).toByte() 123 + } 124 + 125 + fun putOffsetShortBE(value: Int) { 126 + buffer[currentPosition++] = (value ushr 8).toByte() 127 + buffer[currentPosition++] = (value + 128).toByte() 128 + } 129 + 130 + fun putOffsetShortLE(value: Int) { 131 + buffer[currentPosition++] = (value + 128).toByte() 132 + buffer[currentPosition++] = (value ushr 8).toByte() 133 + } 134 + 135 + // -- Read methods -- 136 + 137 + fun getUnsignedByte(): Int = buffer[currentPosition++].toInt() and 0xff 138 + 139 + fun getByte(): Byte = buffer[currentPosition++] 140 + 141 + fun getUnsignedShortBE(): Int { 142 + currentPosition += 2 143 + return ((buffer[currentPosition - 2].toInt() and 0xff) shl 8) + 144 + (buffer[currentPosition - 1].toInt() and 0xff) 145 + } 146 + 147 + fun getShortBE(): Int { 148 + currentPosition += 2 149 + var i = ((buffer[currentPosition - 2].toInt() and 0xff) shl 8) + 150 + (buffer[currentPosition - 1].toInt() and 0xff) 151 + if (i > 32767) i -= 0x10000 152 + return i 153 + } 154 + 155 + fun getMediumBE(): Int { 156 + currentPosition += 3 157 + return ((buffer[currentPosition - 3].toInt() and 0xff) shl 16) + 158 + ((buffer[currentPosition - 2].toInt() and 0xff) shl 8) + 159 + (buffer[currentPosition - 1].toInt() and 0xff) 160 + } 161 + 162 + fun getIntBE(): Int { 163 + currentPosition += 4 164 + return ((buffer[currentPosition - 4].toInt() and 0xff) shl 24) + 165 + ((buffer[currentPosition - 3].toInt() and 0xff) shl 16) + 166 + ((buffer[currentPosition - 2].toInt() and 0xff) shl 8) + 167 + (buffer[currentPosition - 1].toInt() and 0xff) 168 + } 169 + 170 + fun getLongBE(): Long { 171 + val hi = getIntBE().toLong() and 0xffffffffL 172 + val lo = getIntBE().toLong() and 0xffffffffL 173 + return (hi shl 32) + lo 174 + } 175 + 176 + fun getString(): String { 177 + val start = currentPosition 178 + moveBufferToTarget(10.toByte()) 179 + return String(buffer, start, currentPosition - start - 1) 180 + } 181 + 182 + private fun moveBufferToTarget(target: Byte) { 183 + if (buffer[currentPosition++] != target) { 184 + moveBufferToTarget(target) 185 + } 186 + } 187 + 188 + fun getStringBytes(): ByteArray { 189 + val start = currentPosition 190 + moveBufferToTarget(10.toByte()) 191 + val bytes = ByteArray(currentPosition - start - 1) 192 + System.arraycopy(buffer, start, bytes, 0, currentPosition - 1 - start) 193 + return bytes 194 + } 195 + 196 + fun getBytes(bytes: ByteArray, start: Int, len: Int) { 197 + for (pos in start until start + len) { 198 + bytes[pos] = buffer[currentPosition++] 199 + } 200 + } 201 + 202 + // -- Bit access -- 203 + 204 + fun initBitAccess() { 205 + bitPosition = currentPosition * 8 206 + } 207 + 208 + fun getBits(numBits: Int): Int { 209 + var remaining = numBits 210 + var bytePos = bitPosition ushr 3 211 + var bitOffset = 8 - (bitPosition and 7) 212 + var value = 0 213 + bitPosition += remaining 214 + 215 + while (remaining > bitOffset) { 216 + value += (buffer[bytePos++].toInt() and BIT_MASKS[bitOffset]) shl (remaining - bitOffset) 217 + remaining -= bitOffset 218 + bitOffset = 8 219 + } 220 + 221 + value += if (remaining == bitOffset) { 222 + buffer[bytePos].toInt() and BIT_MASKS[bitOffset] 223 + } else { 224 + buffer[bytePos].toInt() ushr (bitOffset - remaining) and BIT_MASKS[remaining] 225 + } 226 + return value 227 + } 228 + 229 + fun finishBitAccess() { 230 + currentPosition = (bitPosition + 7) / 8 231 + } 232 + 233 + // -- Smart encoding -- 234 + 235 + fun getSignedSmart(): Int { 236 + val peek = buffer[currentPosition].toInt() and 0xff 237 + return if (peek < 128) getUnsignedByte() - 64 else getUnsignedShortBE() - 49152 238 + } 239 + 240 + fun getSmart(): Int { 241 + val peek = buffer[currentPosition].toInt() and 0xff 242 + return if (peek < 128) getUnsignedByte() else getUnsignedShortBE() - 32768 243 + } 244 + 245 + // -- RSA encryption -- 246 + 247 + fun encrypt(modulus: BigInteger, key: BigInteger) { 248 + val length = currentPosition 249 + currentPosition = 0 250 + val bytes = ByteArray(length) 251 + getBytes(bytes, 0, length) 252 + 253 + val raw = BigInteger(bytes) 254 + val result = if (Configuration.RSA_ENABLED) raw.modPow(key, modulus).toByteArray() 255 + else raw.toByteArray() 256 + 257 + currentPosition = 0 258 + putByte(result.size) 259 + putBytes(result, 0, result.size) 260 + } 261 + 262 + // -- Obfuscated byte variants -- 263 + 264 + fun getUnsignedPostNegativeOffsetByte(): Int = buffer[currentPosition++] - 128 and 0xff 265 + fun getUnsignedInvertedByte(): Int = -buffer[currentPosition++] and 0xff 266 + fun getUnsignedPreNegativeOffsetByte(): Int = 128 - buffer[currentPosition++] and 0xff 267 + 268 + fun getPostNegativeOffsetByte(): Byte = (buffer[currentPosition++] - 128).toByte() 269 + fun getInvertedByte(): Byte = (-buffer[currentPosition++]).toByte() 270 + fun getPreNegativeOffsetByte(): Byte = (128 - buffer[currentPosition++]).toByte() 271 + 272 + // -- Obfuscated short/int variants -- 273 + 274 + fun getUnsignedShortLE(): Int { 275 + currentPosition += 2 276 + return ((buffer[currentPosition - 1].toInt() and 0xff) shl 8) + 277 + (buffer[currentPosition - 2].toInt() and 0xff) 278 + } 279 + 280 + fun getUnsignedNegativeOffsetShortBE(): Int { 281 + currentPosition += 2 282 + return ((buffer[currentPosition - 2].toInt() and 0xff) shl 8) + 283 + (buffer[currentPosition - 1] - 128 and 0xff) 284 + } 285 + 286 + fun getUnsignedNegativeOffsetShortLE(): Int { 287 + currentPosition += 2 288 + return ((buffer[currentPosition - 1].toInt() and 0xff) shl 8) + 289 + (buffer[currentPosition - 2] - 128 and 0xff) 290 + } 291 + 292 + fun getShortLE(): Int { 293 + currentPosition += 2 294 + var j = ((buffer[currentPosition - 1].toInt() and 0xff) shl 8) + 295 + (buffer[currentPosition - 2].toInt() and 0xff) 296 + if (j > 0x7fff) j -= 0x10000 297 + return j 298 + } 299 + 300 + fun getNegativeOffsetShortBE(): Int { 301 + currentPosition += 2 302 + var i = ((buffer[currentPosition - 2].toInt() and 0xff) shl 8) + 303 + (buffer[currentPosition - 1] - 128 and 0xff) 304 + if (i > 32767) i -= 0x10000 305 + return i 306 + } 307 + 308 + fun getMediumME(): Int { 309 + currentPosition += 3 310 + return ((buffer[currentPosition - 2].toInt() and 0xff) shl 16) + 311 + ((buffer[currentPosition - 3].toInt() and 0xff) shl 8) + 312 + (buffer[currentPosition - 1].toInt() and 0xff) 313 + } 314 + 315 + fun getIntLE(): Int { 316 + currentPosition += 4 317 + return ((buffer[currentPosition - 1].toInt() and 0xff) shl 24) + 318 + ((buffer[currentPosition - 2].toInt() and 0xff) shl 16) + 319 + ((buffer[currentPosition - 3].toInt() and 0xff) shl 8) + 320 + (buffer[currentPosition - 4].toInt() and 0xff) 321 + } 322 + 323 + fun getIntME1(): Int { 324 + currentPosition += 4 325 + return ((buffer[currentPosition - 2].toInt() and 0xff) shl 24) + 326 + ((buffer[currentPosition - 1].toInt() and 0xff) shl 16) + 327 + ((buffer[currentPosition - 4].toInt() and 0xff) shl 8) + 328 + (buffer[currentPosition - 3].toInt() and 0xff) 329 + } 330 + 331 + fun getIntME2(): Int { 332 + currentPosition += 4 333 + return ((buffer[currentPosition - 3].toInt() and 0xff) shl 24) + 334 + ((buffer[currentPosition - 4].toInt() and 0xff) shl 16) + 335 + ((buffer[currentPosition - 1].toInt() and 0xff) shl 8) + 336 + (buffer[currentPosition - 2].toInt() and 0xff) 337 + } 338 + 339 + fun getBytesReverse(bytes: ByteArray, start: Int, len: Int) { 340 + for (pos in (start + len) - 1 downTo start) { 341 + bytes[pos] = buffer[currentPosition++] 342 + } 343 + } 344 + 345 + fun getBytesAdded(bytes: ByteArray, start: Int, len: Int) { 346 + for (pos in start until start + len) { 347 + bytes[pos] = (buffer[currentPosition++] - 128).toByte() 348 + } 349 + } 350 + 351 + companion object { 352 + private val BIT_MASKS = intArrayOf( 353 + 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 354 + 32767, 65535, 0x1ffff, 0x3ffff, 0x7ffff, 0xfffff, 0x1fffff, 0x3fffff, 355 + 0x7fffff, 0xffffff, 0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff, 356 + 0x1fffffff, 0x3fffffff, 0x7fffffff, -1 357 + ) 358 + 359 + @JvmStatic 360 + fun allocate(sizeMode: Int): Buffer { 361 + val buf = Buffer() 362 + buf.currentPosition = 0 363 + buf.buffer = when (sizeMode) { 364 + 0 -> ByteArray(100) 365 + 1 -> ByteArray(5000) 366 + else -> ByteArray(30000) 367 + } 368 + return buf 369 + } 370 + } 371 + }
-147
src/main/java/com/jagex/runescape/net/BufferedConnection.java
··· 1 - package com.jagex.runescape.net; 2 - 3 - import com.jagex.runescape.GameShell; 4 - 5 - import java.io.IOException; 6 - import java.io.InputStream; 7 - import java.io.OutputStream; 8 - import java.net.Socket; 9 - 10 - public class BufferedConnection implements Runnable { 11 - 12 - public InputStream inputStream; 13 - public OutputStream outputStream; 14 - public Socket socket; 15 - public boolean closed = false; 16 - public GameShell gameStub; 17 - public byte[] buffer; 18 - public int writerPosition; 19 - public int bufferPosition; 20 - public boolean writing = false; 21 - public boolean ioError = false; 22 - 23 - public BufferedConnection(GameShell gameStub, Socket socket) throws IOException { 24 - this.gameStub = gameStub; 25 - this.socket = socket; 26 - this.socket.setSoTimeout(30000); 27 - this.socket.setTcpNoDelay(true); 28 - this.inputStream = socket.getInputStream(); 29 - this.outputStream = socket.getOutputStream(); 30 - } 31 - 32 - public void close() { 33 - closed = true; 34 - try { 35 - if (inputStream != null) 36 - inputStream.close(); 37 - if (outputStream != null) 38 - outputStream.close(); 39 - if (socket != null) 40 - socket.close(); 41 - } catch (IOException _ex) { 42 - System.out.println("Error closing stream"); 43 - } 44 - writing = false; 45 - synchronized (this) { 46 - notify(); 47 - } 48 - buffer = null; 49 - } 50 - 51 - public int read() throws IOException { 52 - if (closed) 53 - return 0; 54 - else 55 - return inputStream.read(); 56 - } 57 - 58 - public int getAvailable() throws IOException { 59 - if (closed) 60 - return 0; 61 - else 62 - return inputStream.available(); 63 - } 64 - 65 - public void read(byte[] src, int offset, int length) throws IOException { 66 - if (!closed) { 67 - int byteRead; 68 - for (; length > 0; length -= byteRead) { 69 - byteRead = inputStream.read(src, offset, length); 70 - if (byteRead <= 0) 71 - throw new IOException("EOF"); 72 - offset += byteRead; 73 - } 74 - } 75 - } 76 - 77 - public void write(int length, int offset, byte[] src) throws IOException { 78 - if (closed) 79 - return; 80 - if (ioError) { 81 - ioError = false; 82 - throw new IOException("Error in writer thread"); 83 - } 84 - if (buffer == null) 85 - buffer = new byte[5000]; 86 - synchronized (this) { 87 - for (int position = 0; position < length; position++) { 88 - buffer[bufferPosition] = src[position + offset]; 89 - bufferPosition = (bufferPosition + 1) % 5000; 90 - if (bufferPosition == (writerPosition + 4900) % 5000) 91 - throw new IOException("buffer overflow"); 92 - } 93 - 94 - if (!writing) { 95 - writing = true; 96 - gameStub.startRunnable(this, 3); 97 - } 98 - notify(); 99 - } 100 - } 101 - 102 - public void run() { 103 - while (writing) { 104 - int writerLength; 105 - synchronized (this) { 106 - if (bufferPosition == writerPosition) 107 - try { 108 - wait(); 109 - } catch (InterruptedException _ex) { 110 - } 111 - if (!writing) 112 - return; 113 - if (bufferPosition >= writerPosition) 114 - writerLength = bufferPosition - writerPosition; 115 - else 116 - writerLength = 5000 - writerPosition; 117 - } 118 - if (writerLength > 0) { 119 - try { 120 - outputStream.write(buffer, writerPosition, writerLength); 121 - } catch (IOException _ex) { 122 - ioError = true; 123 - } 124 - writerPosition = (writerPosition + writerLength) % 5000; 125 - try { 126 - if (bufferPosition == writerPosition) 127 - outputStream.flush(); 128 - } catch (IOException _ex) { 129 - ioError = true; 130 - } 131 - } 132 - } 133 - } 134 - 135 - public void printDebug() { 136 - System.out.println("dummy:" + closed); 137 - System.out.println("tcycl:" + writerPosition); 138 - System.out.println("tnum:" + bufferPosition); 139 - System.out.println("writer:" + writing); 140 - System.out.println("ioerror:" + ioError); 141 - try { 142 - System.out.println("available:" + getAvailable()); 143 - } catch (IOException _ex) { 144 - } 145 - } 146 - 147 - }
+142
src/main/java/com/jagex/runescape/net/BufferedConnection.kt
··· 1 + package com.jagex.runescape.net 2 + 3 + import com.jagex.runescape.GameShell 4 + import java.io.IOException 5 + import java.io.InputStream 6 + import java.io.OutputStream 7 + import java.net.Socket 8 + 9 + /** 10 + * Threaded network connection with a circular write buffer. Incoming reads 11 + * happen synchronously on the game thread, but outgoing writes are queued 12 + * into a 5000-byte ring buffer and flushed by a background writer thread. 13 + * This prevents large outbound packets from stalling the game loop. 14 + */ 15 + class BufferedConnection( 16 + @JvmField val gameStub: GameShell, 17 + @JvmField val socket: Socket 18 + ) : Runnable { 19 + 20 + @JvmField val inputStream: InputStream 21 + @JvmField val outputStream: OutputStream 22 + @JvmField var closed: Boolean = false 23 + @JvmField var buffer: ByteArray? = null 24 + @JvmField var writerPosition: Int = 0 25 + @JvmField var bufferPosition: Int = 0 26 + @JvmField var writing: Boolean = false 27 + @JvmField var ioError: Boolean = false 28 + 29 + init { 30 + socket.soTimeout = 30000 31 + socket.tcpNoDelay = true 32 + inputStream = socket.getInputStream() 33 + outputStream = socket.getOutputStream() 34 + } 35 + 36 + fun close() { 37 + closed = true 38 + try { 39 + inputStream.close() 40 + outputStream.close() 41 + socket.close() 42 + } catch (_: IOException) { 43 + println("Error closing stream") 44 + } 45 + writing = false 46 + synchronized(this) { 47 + (this as Object).notify() 48 + } 49 + buffer = null 50 + } 51 + 52 + @Throws(IOException::class) 53 + fun read(): Int { 54 + return if (closed) 0 else inputStream.read() 55 + } 56 + 57 + @Throws(IOException::class) 58 + fun getAvailable(): Int { 59 + return if (closed) 0 else inputStream.available() 60 + } 61 + 62 + @Throws(IOException::class) 63 + fun read(src: ByteArray, offset: Int, length: Int) { 64 + if (closed) return 65 + var off = offset 66 + var remaining = length 67 + while (remaining > 0) { 68 + val byteRead = inputStream.read(src, off, remaining) 69 + if (byteRead <= 0) throw IOException("EOF") 70 + off += byteRead 71 + remaining -= byteRead 72 + } 73 + } 74 + 75 + @Throws(IOException::class) 76 + fun write(length: Int, offset: Int, src: ByteArray) { 77 + if (closed) return 78 + if (ioError) { 79 + ioError = false 80 + throw IOException("Error in writer thread") 81 + } 82 + if (buffer == null) buffer = ByteArray(5000) 83 + synchronized(this) { 84 + for (position in 0 until length) { 85 + buffer!![bufferPosition] = src[position + offset] 86 + bufferPosition = (bufferPosition + 1) % 5000 87 + if (bufferPosition == (writerPosition + 4900) % 5000) 88 + throw IOException("buffer overflow") 89 + } 90 + if (!writing) { 91 + writing = true 92 + gameStub.startRunnable(this, 3) 93 + } 94 + (this as Object).notify() 95 + } 96 + } 97 + 98 + override fun run() { 99 + while (writing) { 100 + val writerLength: Int 101 + synchronized(this) { 102 + if (bufferPosition == writerPosition) { 103 + try { 104 + (this as Object).wait() 105 + } catch (_: InterruptedException) { 106 + } 107 + } 108 + if (!writing) return 109 + writerLength = if (bufferPosition >= writerPosition) { 110 + bufferPosition - writerPosition 111 + } else { 112 + 5000 - writerPosition 113 + } 114 + } 115 + if (writerLength > 0) { 116 + try { 117 + outputStream.write(buffer, writerPosition, writerLength) 118 + } catch (_: IOException) { 119 + ioError = true 120 + } 121 + writerPosition = (writerPosition + writerLength) % 5000 122 + try { 123 + if (bufferPosition == writerPosition) outputStream.flush() 124 + } catch (_: IOException) { 125 + ioError = true 126 + } 127 + } 128 + } 129 + } 130 + 131 + fun printDebug() { 132 + println("dummy:$closed") 133 + println("tcycl:$writerPosition") 134 + println("tnum:$bufferPosition") 135 + println("writer:$writing") 136 + println("ioerror:$ioError") 137 + try { 138 + println("available:${getAvailable()}") 139 + } catch (_: IOException) { 140 + } 141 + } 142 + }
-288
src/main/java/com/jagex/runescape/net/ISAACCipher.java
··· 1 - package com.jagex.runescape.net; 2 - 3 - public class ISAACCipher { 4 - 5 - 6 - /** 7 - * The golden ratio. 8 - */ 9 - private static final int GOLDEN_RATIO = 0x9e3779b9; 10 - 11 - /** 12 - * The log of the size of the result and memory arrays. 13 - */ 14 - private static final int SIZEL = 8; 15 - 16 - /** 17 - * The size of the result and memory arrays. 18 - */ 19 - private static final int SIZE = 1 << ISAACCipher.SIZEL; 20 - 21 - /** 22 - * A mask for pseudorandom lookup. 23 - */ 24 - private static int MASK = (ISAACCipher.SIZE - 1) << 2; 25 - 26 - /** 27 - * The count through the results in the results array. 28 - */ 29 - private int count; 30 - 31 - /** 32 - * The results given to the user. 33 - */ 34 - private final int[] rsl; 35 - 36 - /** 37 - * The internal state. 38 - */ 39 - private final int[] mem; 40 - 41 - /** 42 - * The accumulator. 43 - */ 44 - private int a; 45 - 46 - /** 47 - * The last result. 48 - */ 49 - private int b; 50 - 51 - /** 52 - * The counter. 53 - */ 54 - private int c; 55 - 56 - /** 57 - * Creates the random number generator without an initial seed. 58 - */ 59 - public ISAACCipher() { 60 - mem = new int[ISAACCipher.SIZE]; 61 - rsl = new int[ISAACCipher.SIZE]; 62 - init(false); 63 - } 64 - 65 - /** 66 - * Creates the random number generator with the specified seed. 67 - * 68 - * @param seed 69 - * The seed. 70 - */ 71 - public ISAACCipher(int[] seed) { 72 - mem = new int[ISAACCipher.SIZE]; 73 - rsl = new int[ISAACCipher.SIZE]; 74 - for (int i = 0; i < seed.length; ++i) { 75 - rsl[i] = seed[i]; 76 - } 77 - init(true); 78 - } 79 - 80 - /** 81 - * Generates 256 results. 82 - */ 83 - private void isaac() { 84 - int i, j, x, y; 85 - 86 - b += ++c; 87 - for (i = 0, j = ISAACCipher.SIZE / 2; i < ISAACCipher.SIZE / 2;) { 88 - x = mem[i]; 89 - a ^= a << 13; 90 - a += mem[j++]; 91 - mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b; 92 - rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x; 93 - 94 - x = mem[i]; 95 - a ^= a >>> 6; 96 - a += mem[j++]; 97 - mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b; 98 - rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x; 99 - 100 - x = mem[i]; 101 - a ^= a << 2; 102 - a += mem[j++]; 103 - mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b; 104 - rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x; 105 - 106 - x = mem[i]; 107 - a ^= a >>> 16; 108 - a += mem[j++]; 109 - mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b; 110 - rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x; 111 - } 112 - 113 - for (j = 0; j < ISAACCipher.SIZE / 2;) { 114 - x = mem[i]; 115 - a ^= a << 13; 116 - a += mem[j++]; 117 - mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b; 118 - rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x; 119 - 120 - x = mem[i]; 121 - a ^= a >>> 6; 122 - a += mem[j++]; 123 - mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b; 124 - rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x; 125 - 126 - x = mem[i]; 127 - a ^= a << 2; 128 - a += mem[j++]; 129 - mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b; 130 - rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x; 131 - 132 - x = mem[i]; 133 - a ^= a >>> 16; 134 - a += mem[j++]; 135 - mem[i] = y = mem[(x & ISAACCipher.MASK) >> 2] + a + b; 136 - rsl[i++] = b = mem[((y >> ISAACCipher.SIZEL) & ISAACCipher.MASK) >> 2] + x; 137 - } 138 - } 139 - 140 - /** 141 - * Initialises this random number generator. 142 - * 143 - * @param flag 144 - * Set to {@code true} if a seed was passed to the constructor. 145 - */ 146 - private void init(boolean flag) { 147 - int i; 148 - int a, b, c, d, e, f, g, h; 149 - a = b = c = d = e = f = g = h = ISAACCipher.GOLDEN_RATIO; 150 - 151 - for (i = 0; i < 4; ++i) { 152 - a ^= b << 11; 153 - d += a; 154 - b += c; 155 - b ^= c >>> 2; 156 - e += b; 157 - c += d; 158 - c ^= d << 8; 159 - f += c; 160 - d += e; 161 - d ^= e >>> 16; 162 - g += d; 163 - e += f; 164 - e ^= f << 10; 165 - h += e; 166 - f += g; 167 - f ^= g >>> 4; 168 - a += f; 169 - g += h; 170 - g ^= h << 8; 171 - b += g; 172 - h += a; 173 - h ^= a >>> 9; 174 - c += h; 175 - a += b; 176 - } 177 - 178 - for (i = 0; i < ISAACCipher.SIZE; i += 8) { /* 179 - * fill in mem[] with messy 180 - * stuff 181 - */ 182 - if (flag) { 183 - a += rsl[i]; 184 - b += rsl[i + 1]; 185 - c += rsl[i + 2]; 186 - d += rsl[i + 3]; 187 - e += rsl[i + 4]; 188 - f += rsl[i + 5]; 189 - g += rsl[i + 6]; 190 - h += rsl[i + 7]; 191 - } 192 - a ^= b << 11; 193 - d += a; 194 - b += c; 195 - b ^= c >>> 2; 196 - e += b; 197 - c += d; 198 - c ^= d << 8; 199 - f += c; 200 - d += e; 201 - d ^= e >>> 16; 202 - g += d; 203 - e += f; 204 - e ^= f << 10; 205 - h += e; 206 - f += g; 207 - f ^= g >>> 4; 208 - a += f; 209 - g += h; 210 - g ^= h << 8; 211 - b += g; 212 - h += a; 213 - h ^= a >>> 9; 214 - c += h; 215 - a += b; 216 - mem[i] = a; 217 - mem[i + 1] = b; 218 - mem[i + 2] = c; 219 - mem[i + 3] = d; 220 - mem[i + 4] = e; 221 - mem[i + 5] = f; 222 - mem[i + 6] = g; 223 - mem[i + 7] = h; 224 - } 225 - 226 - if (flag) { /* second pass makes all of seed affect all of mem */ 227 - for (i = 0; i < ISAACCipher.SIZE; i += 8) { 228 - a += mem[i]; 229 - b += mem[i + 1]; 230 - c += mem[i + 2]; 231 - d += mem[i + 3]; 232 - e += mem[i + 4]; 233 - f += mem[i + 5]; 234 - g += mem[i + 6]; 235 - h += mem[i + 7]; 236 - a ^= b << 11; 237 - d += a; 238 - b += c; 239 - b ^= c >>> 2; 240 - e += b; 241 - c += d; 242 - c ^= d << 8; 243 - f += c; 244 - d += e; 245 - d ^= e >>> 16; 246 - g += d; 247 - e += f; 248 - e ^= f << 10; 249 - h += e; 250 - f += g; 251 - f ^= g >>> 4; 252 - a += f; 253 - g += h; 254 - g ^= h << 8; 255 - b += g; 256 - h += a; 257 - h ^= a >>> 9; 258 - c += h; 259 - a += b; 260 - mem[i] = a; 261 - mem[i + 1] = b; 262 - mem[i + 2] = c; 263 - mem[i + 3] = d; 264 - mem[i + 4] = e; 265 - mem[i + 5] = f; 266 - mem[i + 6] = g; 267 - mem[i + 7] = h; 268 - } 269 - } 270 - 271 - isaac(); 272 - count = ISAACCipher.SIZE; 273 - } 274 - 275 - /** 276 - * Gets the next random value. 277 - * 278 - * @return The next random value. 279 - */ 280 - public int nextInt() { 281 - if (0 == count--) { 282 - isaac(); 283 - count = ISAACCipher.SIZE - 1; 284 - } 285 - return rsl[count]; 286 - } 287 - 288 - }
+182
src/main/java/com/jagex/runescape/net/ISAACCipher.kt
··· 1 + package com.jagex.runescape.net 2 + 3 + /** 4 + * ISAAC (Indirection, Shift, Accumulate, Add, and Count) stream cipher. 5 + * Generates a cryptographically secure pseudorandom sequence from a seed. 6 + * Used to obfuscate packet opcodes — each opcode is XOR'd with the next 7 + * value from the stream, preventing trivial packet sniffing. 8 + * 9 + * The algorithm produces 256 results per round via the [isaac] function, 10 + * then serves them one at a time via [nextInt]. When the buffer is exhausted, 11 + * another round is generated automatically. 12 + */ 13 + class ISAACCipher { 14 + 15 + private var count: Int = 0 16 + private val rsl: IntArray 17 + private val mem: IntArray 18 + private var a: Int = 0 19 + private var b: Int = 0 20 + private var c: Int = 0 21 + 22 + constructor() { 23 + mem = IntArray(SIZE) 24 + rsl = IntArray(SIZE) 25 + init(false) 26 + } 27 + 28 + constructor(seed: IntArray) { 29 + mem = IntArray(SIZE) 30 + rsl = IntArray(SIZE) 31 + for (i in seed.indices) { 32 + rsl[i] = seed[i] 33 + } 34 + init(true) 35 + } 36 + 37 + private fun isaac() { 38 + b += ++c 39 + var i = 0 40 + var j = SIZE / 2 41 + while (i < SIZE / 2) { 42 + var x = mem[i] 43 + a = a xor (a shl 13) 44 + a += mem[j++] 45 + var y = mem[(x and MASK) ushr 2] + a + b 46 + mem[i] = y 47 + b = mem[(y ushr SIZEL and MASK) ushr 2] + x 48 + rsl[i++] = b 49 + 50 + x = mem[i] 51 + a = a xor (a ushr 6) 52 + a += mem[j++] 53 + y = mem[(x and MASK) ushr 2] + a + b 54 + mem[i] = y 55 + b = mem[(y ushr SIZEL and MASK) ushr 2] + x 56 + rsl[i++] = b 57 + 58 + x = mem[i] 59 + a = a xor (a shl 2) 60 + a += mem[j++] 61 + y = mem[(x and MASK) ushr 2] + a + b 62 + mem[i] = y 63 + b = mem[(y ushr SIZEL and MASK) ushr 2] + x 64 + rsl[i++] = b 65 + 66 + x = mem[i] 67 + a = a xor (a ushr 16) 68 + a += mem[j++] 69 + y = mem[(x and MASK) ushr 2] + a + b 70 + mem[i] = y 71 + b = mem[(y ushr SIZEL and MASK) ushr 2] + x 72 + rsl[i++] = b 73 + } 74 + 75 + j = 0 76 + while (j < SIZE / 2) { 77 + var x = mem[i] 78 + a = a xor (a shl 13) 79 + a += mem[j++] 80 + var y = mem[(x and MASK) ushr 2] + a + b 81 + mem[i] = y 82 + b = mem[(y ushr SIZEL and MASK) ushr 2] + x 83 + rsl[i++] = b 84 + 85 + x = mem[i] 86 + a = a xor (a ushr 6) 87 + a += mem[j++] 88 + y = mem[(x and MASK) ushr 2] + a + b 89 + mem[i] = y 90 + b = mem[(y ushr SIZEL and MASK) ushr 2] + x 91 + rsl[i++] = b 92 + 93 + x = mem[i] 94 + a = a xor (a shl 2) 95 + a += mem[j++] 96 + y = mem[(x and MASK) ushr 2] + a + b 97 + mem[i] = y 98 + b = mem[(y ushr SIZEL and MASK) ushr 2] + x 99 + rsl[i++] = b 100 + 101 + x = mem[i] 102 + a = a xor (a ushr 16) 103 + a += mem[j++] 104 + y = mem[(x and MASK) ushr 2] + a + b 105 + mem[i] = y 106 + b = mem[(y ushr SIZEL and MASK) ushr 2] + x 107 + rsl[i++] = b 108 + } 109 + } 110 + 111 + private fun init(flag: Boolean) { 112 + var a = GOLDEN_RATIO; var b = GOLDEN_RATIO; var c = GOLDEN_RATIO; var d = GOLDEN_RATIO 113 + var e = GOLDEN_RATIO; var f = GOLDEN_RATIO; var g = GOLDEN_RATIO; var h = GOLDEN_RATIO 114 + 115 + for (i in 0 until 4) { 116 + a = a xor (b shl 11); d += a; b += c 117 + b = b xor (c ushr 2); e += b; c += d 118 + c = c xor (d shl 8); f += c; d += e 119 + d = d xor (e ushr 16); g += d; e += f 120 + e = e xor (f shl 10); h += e; f += g 121 + f = f xor (g ushr 4); a += f; g += h 122 + g = g xor (h shl 8); b += g; h += a 123 + h = h xor (a ushr 9); c += h; a += b 124 + } 125 + 126 + var i = 0 127 + while (i < SIZE) { 128 + if (flag) { 129 + a += rsl[i]; b += rsl[i + 1]; c += rsl[i + 2]; d += rsl[i + 3] 130 + e += rsl[i + 4]; f += rsl[i + 5]; g += rsl[i + 6]; h += rsl[i + 7] 131 + } 132 + a = a xor (b shl 11); d += a; b += c 133 + b = b xor (c ushr 2); e += b; c += d 134 + c = c xor (d shl 8); f += c; d += e 135 + d = d xor (e ushr 16); g += d; e += f 136 + e = e xor (f shl 10); h += e; f += g 137 + f = f xor (g ushr 4); a += f; g += h 138 + g = g xor (h shl 8); b += g; h += a 139 + h = h xor (a ushr 9); c += h; a += b 140 + mem[i] = a; mem[i + 1] = b; mem[i + 2] = c; mem[i + 3] = d 141 + mem[i + 4] = e; mem[i + 5] = f; mem[i + 6] = g; mem[i + 7] = h 142 + i += 8 143 + } 144 + 145 + if (flag) { 146 + i = 0 147 + while (i < SIZE) { 148 + a += mem[i]; b += mem[i + 1]; c += mem[i + 2]; d += mem[i + 3] 149 + e += mem[i + 4]; f += mem[i + 5]; g += mem[i + 6]; h += mem[i + 7] 150 + a = a xor (b shl 11); d += a; b += c 151 + b = b xor (c ushr 2); e += b; c += d 152 + c = c xor (d shl 8); f += c; d += e 153 + d = d xor (e ushr 16); g += d; e += f 154 + e = e xor (f shl 10); h += e; f += g 155 + f = f xor (g ushr 4); a += f; g += h 156 + g = g xor (h shl 8); b += g; h += a 157 + h = h xor (a ushr 9); c += h; a += b 158 + mem[i] = a; mem[i + 1] = b; mem[i + 2] = c; mem[i + 3] = d 159 + mem[i + 4] = e; mem[i + 5] = f; mem[i + 6] = g; mem[i + 7] = h 160 + i += 8 161 + } 162 + } 163 + 164 + isaac() 165 + count = SIZE 166 + } 167 + 168 + fun nextInt(): Int { 169 + if (0 == count--) { 170 + isaac() 171 + count = SIZE - 1 172 + } 173 + return rsl[count] 174 + } 175 + 176 + companion object { 177 + private const val GOLDEN_RATIO = 0x9e3779b9.toInt() 178 + private const val SIZEL = 8 179 + private const val SIZE = 1 shl SIZEL 180 + private val MASK = (SIZE - 1) shl 2 181 + } 182 + }
-12
src/main/java/com/jagex/runescape/net/requester/OnDemandNode.java
··· 1 - package com.jagex.runescape.net.requester; 2 - 3 - import com.jagex.runescape.collection.CacheableNode; 4 - 5 - public class OnDemandNode extends CacheableNode { 6 - 7 - public int type; 8 - public int id; 9 - public int cyclesSinceSend; 10 - public byte[] buffer; 11 - public boolean immediate = true; 12 - }
+16
src/main/java/com/jagex/runescape/net/requester/OnDemandNode.kt
··· 1 + package com.jagex.runescape.net.requester 2 + 3 + import com.jagex.runescape.collection.CacheableNode 4 + 5 + /** 6 + * Represents a single on-demand file request. Tracks the file type/id, 7 + * request priority, and holds the received data buffer. Lives in the 8 + * [CacheableNode] hierarchy so it can be stored in [com.jagex.runescape.collection.Queue]. 9 + */ 10 + class OnDemandNode : CacheableNode() { 11 + @JvmField var type: Int = 0 12 + @JvmField var fileId: Int = 0 13 + @JvmField var cyclesSinceSend: Int = 0 14 + @JvmField var buffer: ByteArray? = null 15 + @JvmField var immediate: Boolean = true 16 + }
-632
src/main/java/com/jagex/runescape/net/requester/OnDemandRequester.java
··· 1 - package com.jagex.runescape.net.requester; 2 - 3 - import java.io.ByteArrayInputStream; 4 - import java.io.IOException; 5 - import java.io.InputStream; 6 - import java.io.OutputStream; 7 - import java.net.Socket; 8 - import java.util.zip.CRC32; 9 - import java.util.zip.GZIPInputStream; 10 - 11 - import com.jagex.runescape.cache.Archive; 12 - import com.jagex.runescape.Game; 13 - import com.jagex.runescape.collection.Queue; 14 - import com.jagex.runescape.net.Buffer; 15 - import com.jagex.runescape.util.LinkedList; 16 - import com.jagex.runescape.util.SignLink; 17 - import com.jagex.runescape.config.Configuration; 18 - 19 - public class OnDemandRequester extends Requester implements Runnable { 20 - 21 - public int anInt1334; 22 - public byte modelIndex[]; 23 - public int regShouldPreload[]; 24 - public byte filePriorities[][] = new byte[4][]; 25 - public boolean expectData = false; 26 - public boolean running = true; 27 - public LinkedList wanted = new LinkedList(); 28 - public int highestPriority; 29 - public int immediateRequestsSent; 30 - public int anInt1343; 31 - public int fileCrc[][] = new int[4][]; 32 - public int anInt1345; 33 - public int regHash[]; 34 - public String message = ""; 35 - public int cycle; 36 - public OutputStream outputStream; 37 - public int anInt1350; 38 - public LinkedList aClass6_1351 = new LinkedList(); 39 - public boolean aBoolean1352 = false; 40 - public int idleCycles; 41 - public CRC32 crc32 = new CRC32(); 42 - public Socket socket; 43 - public LinkedList completed = new LinkedList(); 44 - public LinkedList immediateRequests = new LinkedList(); 45 - public byte deflateOut[] = new byte[65000]; 46 - public int regMapIndex[]; 47 - public int offset; 48 - public int toRead; 49 - public int anInt1363; 50 - public byte inputBuffer[] = new byte[500]; 51 - public int regLandIndex[]; 52 - public int midiIndex[]; 53 - public int anInt1367 = 591; 54 - public Queue immediateRequests1 = new Queue(); 55 - public InputStream inputStream; 56 - public OnDemandNode onDemandNode; 57 - public Game client; 58 - public LinkedList toRequest = new LinkedList(); 59 - public int sinceKeepAlive; 60 - public int animIndex[]; 61 - public int fileVersions[][] = new int[4][]; 62 - public long lastSocketOpen; 63 - public int requestFails; 64 - 65 - public boolean verify(int expectedVersion, int expectedCrc, byte[] data) { 66 - if (data == null || data.length < 2) 67 - return false; 68 - int length = data.length - 2; 69 - int version = ((data[length] & 0xff) << 8) + (data[length + 1] & 0xff); 70 - crc32.reset(); 71 - crc32.update(data, 0, length); 72 - int crc = (int) crc32.getValue(); 73 - if (version != expectedVersion) 74 - return false; 75 - return crc == expectedCrc; 76 - } 77 - 78 - public void handleResp() { 79 - try { 80 - int available = inputStream.available(); 81 - if (toRead == 0 && available >= 6) { 82 - expectData = true; 83 - for (int read = 0; read < 6; read += inputStream.read(inputBuffer, read, 6 - read)); 84 - int type = inputBuffer[0] & 0xff; 85 - int id = ((inputBuffer[1] & 0xff) << 8) + (inputBuffer[2] & 0xff); 86 - int size = ((inputBuffer[3] & 0xff) << 8) + (inputBuffer[4] & 0xff); 87 - int chunk = inputBuffer[5] & 0xff; 88 - System.out.println("[ONDEMAND] Response [type=" + type + ", id=" + id + ", size=" + size + ", chunk=" + chunk + "]"); 89 - onDemandNode = null; 90 - for (OnDemandNode ondemandnode = (OnDemandNode) toRequest.first(); ondemandnode != null; ondemandnode = (OnDemandNode) toRequest.next()) { 91 - if (ondemandnode.type == type && ondemandnode.id == id) 92 - onDemandNode = ondemandnode; 93 - if (onDemandNode != null) 94 - ondemandnode.cyclesSinceSend = 0; 95 - } 96 - 97 - if (onDemandNode != null) { 98 - idleCycles = 0; 99 - if (size == 0) { 100 - SignLink.reportError("Rej: " + type + "," + id); 101 - onDemandNode.buffer = null; 102 - if (onDemandNode.immediate) 103 - synchronized (completed) { 104 - completed.pushBack(onDemandNode); 105 - } 106 - else 107 - onDemandNode.remove(); 108 - onDemandNode = null; 109 - } else { 110 - if (onDemandNode.buffer == null && chunk == 0) 111 - onDemandNode.buffer = new byte[size]; 112 - if (onDemandNode.buffer == null && chunk != 0) 113 - throw new IOException("missing initializeApplication of file"); 114 - } 115 - } 116 - offset = chunk * 500; 117 - toRead = 500; 118 - if (toRead > size - chunk * 500) 119 - toRead = size - chunk * 500; 120 - } 121 - if (toRead > 0 && available >= toRead) { 122 - expectData = true; 123 - byte buffer[] = inputBuffer; 124 - int bufferOffset = 0; 125 - if (onDemandNode != null) { 126 - buffer = onDemandNode.buffer; 127 - bufferOffset = offset; 128 - } 129 - for (int i = 0; i < toRead; i += inputStream.read(buffer, i + bufferOffset, toRead - i)); 130 - if (toRead + offset >= buffer.length && onDemandNode != null) { 131 - System.out.println("[ONDEMAND] File complete [type=" + onDemandNode.type + ", id=" + onDemandNode.id + ", size=" + buffer.length + " bytes]"); 132 - if (client.stores[0] != null) 133 - client.stores[onDemandNode.type + 1].put(buffer.length, buffer, 134 - onDemandNode.id); 135 - if (!onDemandNode.immediate && onDemandNode.type == 3) { 136 - onDemandNode.immediate = true; 137 - onDemandNode.type = 93; 138 - } 139 - if (onDemandNode.immediate) 140 - synchronized (completed) { 141 - completed.pushBack(onDemandNode); 142 - } 143 - else 144 - onDemandNode.remove(); 145 - } 146 - toRead = 0; 147 - return; 148 - } 149 - } catch (IOException ioexception) { 150 - try { 151 - socket.close(); 152 - } catch (Exception _ex) { 153 - } 154 - socket = null; 155 - inputStream = null; 156 - outputStream = null; 157 - toRead = 0; 158 - } 159 - } 160 - 161 - public int modelId(int model) { 162 - return modelIndex[model] & 0xff; 163 - } 164 - 165 - @Override 166 - public void requestModel(int id) { 167 - request(0, id); 168 - } 169 - 170 - public void passivesRequest(int i) { 171 - if (i != 0) 172 - return; 173 - while (immediateRequestsSent == 0 && anInt1343 < 10) { 174 - if (highestPriority == 0) 175 - break; 176 - OnDemandNode class50_sub1_sub3; 177 - synchronized (immediateRequests) { 178 - class50_sub1_sub3 = (OnDemandNode) immediateRequests.pop(); 179 - } 180 - while (class50_sub1_sub3 != null) { 181 - if (filePriorities[class50_sub1_sub3.type][class50_sub1_sub3.id] != 0) { 182 - filePriorities[class50_sub1_sub3.type][class50_sub1_sub3.id] = 0; 183 - toRequest.pushBack(class50_sub1_sub3); 184 - sendRequest(class50_sub1_sub3); 185 - expectData = true; 186 - if (anInt1334 < anInt1350) 187 - anInt1334++; 188 - message = "Loading extra files - " + (anInt1334 * 100) / anInt1350 + "%"; 189 - anInt1343++; 190 - if (anInt1343 == 10) 191 - return; 192 - } 193 - synchronized (immediateRequests) { 194 - class50_sub1_sub3 = (OnDemandNode) immediateRequests.pop(); 195 - } 196 - } 197 - for (int j = 0; j < 4; j++) { 198 - byte abyte0[] = filePriorities[j]; 199 - int k = abyte0.length; 200 - for (int l = 0; l < k; l++) 201 - if (abyte0[l] == highestPriority) { 202 - abyte0[l] = 0; 203 - OnDemandNode class50_sub1_sub3_1 = new OnDemandNode(); 204 - class50_sub1_sub3_1.type = j; 205 - class50_sub1_sub3_1.id = l; 206 - class50_sub1_sub3_1.immediate = false; 207 - toRequest.pushBack(class50_sub1_sub3_1); 208 - sendRequest(class50_sub1_sub3_1); 209 - expectData = true; 210 - if (anInt1334 < anInt1350) 211 - anInt1334++; 212 - message = "Loading extra files - " + (anInt1334 * 100) / anInt1350 + "%"; 213 - anInt1343++; 214 - if (anInt1343 == 10) 215 - return; 216 - } 217 - 218 - } 219 - 220 - highestPriority--; 221 - } 222 - } 223 - 224 - public void setPriority(byte byte0, int j, int k) { 225 - if (client.stores[0] == null) 226 - return; 227 - if (fileVersions[j][k] == 0) 228 - return; 229 - byte abyte0[] = client.stores[j + 1].get(k); 230 - if (verify(fileVersions[j][k], fileCrc[j][k], abyte0)) 231 - return; 232 - filePriorities[j][k] = byte0; 233 - if (byte0 > highestPriority) 234 - highestPriority = byte0; 235 - anInt1350++; 236 - } 237 - 238 - public boolean midiIdEqualsOne(int i) { 239 - return midiIndex[i] == 1; 240 - } 241 - 242 - public void request(int type, int id) { 243 - if (type < 0 || type > fileVersions.length || id < 0 || id > fileVersions[type].length) 244 - return; 245 - if (fileVersions[type][id] == 0) 246 - return; 247 - synchronized (immediateRequests1) { 248 - for (OnDemandNode onDemandNode = (OnDemandNode) immediateRequests1.first(); onDemandNode != null; onDemandNode = (OnDemandNode) immediateRequests1 249 - .next()) 250 - if (onDemandNode.type == type && onDemandNode.id == id) 251 - return; 252 - 253 - OnDemandNode onDemandNode = new OnDemandNode(); 254 - onDemandNode.type = type; 255 - onDemandNode.id = id; 256 - onDemandNode.immediate = true; 257 - synchronized (wanted) { 258 - wanted.pushBack(onDemandNode); 259 - } 260 - immediateRequests1.push(onDemandNode); 261 - } 262 - } 263 - 264 - public OnDemandNode next() { 265 - OnDemandNode onDemandNode; 266 - synchronized (completed) { 267 - onDemandNode = (OnDemandNode) completed.pop(); 268 - } 269 - if (onDemandNode == null) 270 - return null; 271 - synchronized (immediateRequests1) { 272 - onDemandNode.clear(); 273 - } 274 - if (onDemandNode.buffer == null) 275 - return onDemandNode; 276 - int offset = 0; 277 - try { 278 - GZIPInputStream gzipinputstream = new GZIPInputStream(new ByteArrayInputStream(onDemandNode.buffer)); 279 - do { 280 - if (offset == deflateOut.length) 281 - throw new RuntimeException("buffer overflow!"); 282 - int k = gzipinputstream.read(deflateOut, offset, deflateOut.length - offset); 283 - if (k == -1) 284 - break; 285 - offset += k; 286 - } while (true); 287 - } catch (IOException _ex) { 288 - throw new RuntimeException("error unzipping"); 289 - } 290 - onDemandNode.buffer = new byte[offset]; 291 - for (int position = 0; position < offset; position++) 292 - onDemandNode.buffer[position] = deflateOut[position]; 293 - 294 - return onDemandNode; 295 - } 296 - 297 - public void run() { 298 - try { 299 - while (running) { 300 - cycle++; 301 - int toWait = 20; 302 - if (highestPriority == 0 && client.stores[0] != null) 303 - toWait = 50; 304 - try { 305 - Thread.sleep(toWait); 306 - } catch (Exception _ex) { 307 - } 308 - expectData = true; 309 - for (int i = 0; i < 100; i++) { 310 - if (!expectData) 311 - break; 312 - expectData = false; 313 - localComplete(true); 314 - remainingRequest(0); 315 - if (immediateRequestsSent == 0 && i >= 5) 316 - break; 317 - passivesRequest(0); 318 - if (inputStream != null) 319 - handleResp(); 320 - } 321 - 322 - boolean idle = false; 323 - for (OnDemandNode onDemandNode = (OnDemandNode) toRequest.first(); onDemandNode != null; onDemandNode = (OnDemandNode) toRequest.next()) 324 - if (onDemandNode.immediate) { 325 - idle = true; 326 - onDemandNode.cyclesSinceSend++; 327 - if (onDemandNode.cyclesSinceSend > 50) { 328 - onDemandNode.cyclesSinceSend = 0; 329 - sendRequest(onDemandNode); 330 - } 331 - } 332 - 333 - if (!idle) { 334 - for (OnDemandNode onDemandNode = (OnDemandNode) toRequest.first(); onDemandNode != null; onDemandNode = (OnDemandNode) toRequest.next()) { 335 - idle = true; 336 - onDemandNode.cyclesSinceSend++; 337 - if (onDemandNode.cyclesSinceSend > 50) { 338 - onDemandNode.cyclesSinceSend = 0; 339 - sendRequest(onDemandNode); 340 - } 341 - } 342 - 343 - } 344 - if (idle) { 345 - idleCycles++; 346 - if (idleCycles > 750) { 347 - try { 348 - socket.close(); 349 - } catch (Exception _ex) { 350 - } 351 - socket = null; 352 - inputStream = null; 353 - outputStream = null; 354 - toRead = 0; 355 - } 356 - } else { 357 - idleCycles = 0; 358 - message = ""; 359 - } 360 - if (client.loggedIn && socket != null && outputStream != null 361 - && (highestPriority > 0 || client.stores[0] == null)) { 362 - sinceKeepAlive++; 363 - if (sinceKeepAlive > 500) { 364 - sinceKeepAlive = 0; 365 - inputBuffer[0] = 0; 366 - inputBuffer[1] = 0; 367 - inputBuffer[2] = 0; 368 - inputBuffer[3] = 10; 369 - try { 370 - outputStream.write(inputBuffer, 0, 4); 371 - } catch (IOException _ex) { 372 - idleCycles = 5000; 373 - } 374 - } 375 - } 376 - } 377 - return; 378 - } catch (Exception exception) { 379 - SignLink.reportError("od_ex " + exception.getMessage()); 380 - } 381 - } 382 - 383 - public void remainingRequest(int i) { 384 - immediateRequestsSent = 0; 385 - anInt1343 = 0; 386 - if (i != 0) 387 - return; 388 - for (OnDemandNode class50_sub1_sub3 = (OnDemandNode) toRequest.first(); class50_sub1_sub3 != null; class50_sub1_sub3 = (OnDemandNode) toRequest 389 - .next()) 390 - if (class50_sub1_sub3.immediate) 391 - immediateRequestsSent++; 392 - else 393 - anInt1343++; 394 - 395 - while (immediateRequestsSent < 10) { 396 - OnDemandNode class50_sub1_sub3_1 = (OnDemandNode) aClass6_1351.pop(); 397 - if (class50_sub1_sub3_1 == null) 398 - break; 399 - if (filePriorities[class50_sub1_sub3_1.type][class50_sub1_sub3_1.id] != 0) 400 - anInt1334++; 401 - filePriorities[class50_sub1_sub3_1.type][class50_sub1_sub3_1.id] = 0; 402 - toRequest.pushBack(class50_sub1_sub3_1); 403 - immediateRequestsSent++; 404 - sendRequest(class50_sub1_sub3_1); 405 - expectData = true; 406 - } 407 - } 408 - 409 - public void preloadRegions(boolean members) { 410 - for (int reg = 0; reg < regHash.length; reg++) 411 - if (members || regShouldPreload[reg] != 0) { 412 - setPriority((byte) 2, 3, regLandIndex[reg]); 413 - setPriority((byte) 2, 3, regMapIndex[reg]); 414 - } 415 - 416 - } 417 - 418 - public int method333() { 419 - synchronized (immediateRequests1) { 420 - int i = immediateRequests1.size(); 421 - return i; 422 - } 423 - } 424 - 425 - public boolean method334(int i, boolean flag) { 426 - for (int j = 0; j < regHash.length; j++) 427 - if (regLandIndex[j] == i) 428 - return true; 429 - 430 - if (flag) 431 - anInt1363 = -405; 432 - return false; 433 - } 434 - 435 - public void init(Archive archive, Game client) { 436 - String versionFiles[] = { "model_version", "anim_version", "midi_version", "map_version" }; 437 - for (int version = 0; version < 4; version++) { 438 - byte[] data = archive.getFile(versionFiles[version]); 439 - int versionCount = data.length / 2; 440 - Buffer buffer = new Buffer(data); 441 - fileVersions[version] = new int[versionCount]; 442 - filePriorities[version] = new byte[versionCount]; 443 - for (int file = 0; file < versionCount; file++) 444 - fileVersions[version][file] = buffer.getUnsignedShortBE(); 445 - 446 - } 447 - 448 - String crcFiles[] = { "model_crc", "anim_crc", "midi_crc", "map_crc" }; 449 - for (int crc = 0; crc < 4; crc++) { 450 - byte[] data = archive.getFile(crcFiles[crc]); 451 - int crcCount = data.length / 4; 452 - Buffer buffer = new Buffer(data); 453 - fileCrc[crc] = new int[crcCount]; 454 - for (int file = 0; file < crcCount; file++) 455 - fileCrc[crc][file] = buffer.getIntBE(); 456 - 457 - } 458 - 459 - byte[] data = archive.getFile("model_index"); 460 - int count = fileVersions[0].length; 461 - modelIndex = new byte[count]; 462 - for (int i = 0; i < count; i++) 463 - if (i < data.length) 464 - modelIndex[i] = data[i]; 465 - else 466 - modelIndex[i] = 0; 467 - 468 - data = archive.getFile("map_index"); 469 - Buffer buffer = new Buffer(data); 470 - count = data.length / 7; 471 - regHash = new int[count]; 472 - regMapIndex = new int[count]; 473 - regLandIndex = new int[count]; 474 - regShouldPreload = new int[count]; 475 - for (int reg = 0; reg < count; reg++) { 476 - regHash[reg] = buffer.getUnsignedShortBE(); 477 - regMapIndex[reg] = buffer.getUnsignedShortBE(); 478 - regLandIndex[reg] = buffer.getUnsignedShortBE(); 479 - regShouldPreload[reg] = buffer.getUnsignedByte(); 480 - } 481 - 482 - data = archive.getFile("anim_index"); 483 - buffer = new Buffer(data); 484 - count = data.length / 2; 485 - animIndex = new int[count]; 486 - for (int i = 0; i < count; i++) 487 - animIndex[i] = buffer.getUnsignedShortBE(); 488 - 489 - data = archive.getFile("midi_index"); 490 - buffer = new Buffer(data); 491 - count = data.length; 492 - midiIndex = new int[count]; 493 - for (int i = 0; i < count; i++) 494 - midiIndex[i] = buffer.getUnsignedByte(); 495 - 496 - this.client = client; 497 - this.running = true; 498 - this.client.startRunnable(this, 2); 499 - } 500 - 501 - public void immediateRequestCount() { 502 - synchronized (immediateRequests) { 503 - immediateRequests.clear(); 504 - } 505 - } 506 - 507 - public void passiveRequest(int i, int j) { 508 - if (client.stores[0] == null) 509 - return; 510 - if (fileVersions[j][i] == 0) 511 - return; 512 - if (filePriorities[j][i] == 0) 513 - return; 514 - if (highestPriority == 0) 515 - return; 516 - OnDemandNode class50_sub1_sub3 = new OnDemandNode(); 517 - class50_sub1_sub3.type = j; 518 - class50_sub1_sub3.id = i; 519 - class50_sub1_sub3.immediate = false; 520 - synchronized (immediateRequests) { 521 - immediateRequests.pushBack(class50_sub1_sub3); 522 - } 523 - } 524 - 525 - public void localComplete(boolean flag) { 526 - OnDemandNode class50_sub1_sub3; 527 - synchronized (wanted) { 528 - class50_sub1_sub3 = (OnDemandNode) wanted.pop(); 529 - } 530 - if (!flag) { 531 - for (int i = 1; i > 0; i++); 532 - } 533 - while (class50_sub1_sub3 != null) { 534 - expectData = true; 535 - byte abyte0[] = null; 536 - if (client.stores[0] != null) 537 - abyte0 = client.stores[class50_sub1_sub3.type + 1].get(class50_sub1_sub3.id); 538 - if (!verify(fileVersions[class50_sub1_sub3.type][class50_sub1_sub3.id], fileCrc[class50_sub1_sub3.type][class50_sub1_sub3.id], abyte0 539 - )) 540 - abyte0 = null; 541 - synchronized (wanted) { 542 - if (abyte0 == null) { 543 - aClass6_1351.pushBack(class50_sub1_sub3); 544 - } else { 545 - class50_sub1_sub3.buffer = abyte0; 546 - synchronized (completed) { 547 - completed.pushBack(class50_sub1_sub3); 548 - } 549 - } 550 - class50_sub1_sub3 = (OnDemandNode) wanted.pop(); 551 - } 552 - } 553 - } 554 - 555 - public void stop() { 556 - running = false; 557 - } 558 - 559 - public int fileCount(int file) { 560 - return fileVersions[file].length; 561 - } 562 - 563 - 564 - 565 - public void sendRequest(OnDemandNode onDemandNode) { 566 - try { 567 - if (socket == null) { 568 - long currentTime = System.currentTimeMillis(); 569 - if (currentTime - lastSocketOpen < 4000L) 570 - return; 571 - lastSocketOpen = currentTime; 572 - System.out.println("[ONDEMAND] Connecting to port " + (Configuration.ONDEMAND_PORT + client.portOffset)); 573 - socket = client.openSocket(Configuration.ONDEMAND_PORT + client.portOffset); 574 - inputStream = socket.getInputStream(); 575 - outputStream = socket.getOutputStream(); 576 - outputStream.write(15); 577 - byte[] version = new byte[2]; 578 - version[0] = (byte) (Game.VERSION >> 8); 579 - version[1] = (byte) Game.VERSION; 580 - outputStream.write(version, 0, 2); 581 - System.out.println("[ONDEMAND] Handshake sent (opcode 15, version " + Game.VERSION + ")"); 582 - for (int i = 0; i < 8; i++) 583 - inputStream.read(); 584 - System.out.println("[ONDEMAND] Connection established"); 585 - 586 - idleCycles = 0; 587 - } 588 - inputBuffer[0] = (byte) onDemandNode.type; 589 - inputBuffer[1] = (byte) (onDemandNode.id >> 8); 590 - inputBuffer[2] = (byte) onDemandNode.id; 591 - if (onDemandNode.immediate) 592 - inputBuffer[3] = 2; 593 - else if (!client.loggedIn) 594 - inputBuffer[3] = 1; 595 - else 596 - inputBuffer[3] = 0; 597 - System.out.println("[ONDEMAND] Requesting [type=" + onDemandNode.type + ", id=" + onDemandNode.id + ", priority=" + inputBuffer[3] + "]"); 598 - outputStream.write(inputBuffer, 0, 4); 599 - sinceKeepAlive = 0; 600 - requestFails = -10000; 601 - return; 602 - } catch (IOException ioexception) { 603 - } 604 - try { 605 - socket.close(); 606 - } catch (Exception _ex) { 607 - } 608 - socket = null; 609 - inputStream = null; 610 - outputStream = null; 611 - toRead = 0; 612 - requestFails++; 613 - } 614 - 615 - public int animCount() { 616 - return animIndex.length; 617 - } 618 - 619 - public int regId(int i, int regX, int regY, int l) { 620 - int localRegHash = (regX << 8) + regY; 621 - for (int reg = 0; reg < regHash.length; reg++) 622 - if (regHash[reg] == localRegHash) 623 - if (l == 0) 624 - return regMapIndex[reg]; 625 - else 626 - return regLandIndex[reg]; 627 - 628 - return -1; 629 - } 630 - 631 - 632 - }
+532
src/main/java/com/jagex/runescape/net/requester/OnDemandRequester.kt
··· 1 + package com.jagex.runescape.net.requester 2 + 3 + import com.jagex.runescape.Game 4 + import com.jagex.runescape.cache.Archive 5 + import com.jagex.runescape.collection.Queue 6 + import com.jagex.runescape.config.Configuration 7 + import com.jagex.runescape.net.Buffer 8 + import com.jagex.runescape.util.LinkedList 9 + import com.jagex.runescape.util.SignLink 10 + import java.io.ByteArrayInputStream 11 + import java.io.IOException 12 + import java.io.InputStream 13 + import java.io.OutputStream 14 + import java.net.Socket 15 + import java.util.zip.CRC32 16 + import java.util.zip.GZIPInputStream 17 + 18 + /** 19 + * Background file requester that streams assets (models, animations, maps, 20 + * midi) from the server on demand. Manages request prioritization, CRC 21 + * verification, GZIP decompression, and connection keep-alive. 22 + */ 23 + class OnDemandRequester : Requester(), Runnable { 24 + 25 + @JvmField var anInt1334: Int = 0 26 + @JvmField var modelIndex: ByteArray? = null 27 + @JvmField var regShouldPreload: IntArray? = null 28 + @JvmField var filePriorities: Array<ByteArray?> = arrayOfNulls(4) 29 + @JvmField var expectData: Boolean = false 30 + @JvmField var running: Boolean = true 31 + @JvmField var wanted: LinkedList = LinkedList() 32 + @JvmField var highestPriority: Int = 0 33 + @JvmField var immediateRequestsSent: Int = 0 34 + @JvmField var anInt1343: Int = 0 35 + @JvmField var fileCrc: Array<IntArray?> = arrayOfNulls(4) 36 + @JvmField var anInt1345: Int = 0 37 + @JvmField var regHash: IntArray? = null 38 + @JvmField var message: String = "" 39 + @JvmField var cycle: Int = 0 40 + @JvmField var outputStream: OutputStream? = null 41 + @JvmField var anInt1350: Int = 0 42 + @JvmField var aClass6_1351: LinkedList = LinkedList() 43 + @JvmField var aBoolean1352: Boolean = false 44 + @JvmField var idleCycles: Int = 0 45 + @JvmField var crc32: CRC32 = CRC32() 46 + @JvmField var socket: Socket? = null 47 + @JvmField var completed: LinkedList = LinkedList() 48 + @JvmField var immediateRequests: LinkedList = LinkedList() 49 + @JvmField var deflateOut: ByteArray = ByteArray(65000) 50 + @JvmField var regMapIndex: IntArray? = null 51 + @JvmField var offset: Int = 0 52 + @JvmField var toRead: Int = 0 53 + @JvmField var anInt1363: Int = 0 54 + @JvmField var inputBuffer: ByteArray = ByteArray(500) 55 + @JvmField var regLandIndex: IntArray? = null 56 + @JvmField var midiIndex: IntArray? = null 57 + @JvmField var anInt1367: Int = 591 58 + @JvmField var immediateRequests1: Queue = Queue() 59 + @JvmField var inputStream: InputStream? = null 60 + @JvmField var onDemandNode: OnDemandNode? = null 61 + @JvmField var client: Game? = null 62 + @JvmField var toRequest: LinkedList = LinkedList() 63 + @JvmField var sinceKeepAlive: Int = 0 64 + @JvmField var animIndex: IntArray? = null 65 + @JvmField var fileVersions: Array<IntArray?> = arrayOfNulls(4) 66 + @JvmField var lastSocketOpen: Long = 0 67 + @JvmField var requestFails: Int = 0 68 + 69 + fun verify(expectedVersion: Int, expectedCrc: Int, data: ByteArray?): Boolean { 70 + if (data == null || data.size < 2) return false 71 + val length = data.size - 2 72 + val version = ((data[length].toInt() and 0xff) shl 8) + (data[length + 1].toInt() and 0xff) 73 + crc32.reset() 74 + crc32.update(data, 0, length) 75 + val crc = crc32.value.toInt() 76 + return version == expectedVersion && crc == expectedCrc 77 + } 78 + 79 + fun handleResp() { 80 + try { 81 + val available = inputStream!!.available() 82 + if (toRead == 0 && available >= 6) { 83 + expectData = true 84 + var read = 0 85 + while (read < 6) { read += inputStream!!.read(inputBuffer, read, 6 - read) } 86 + val type = inputBuffer[0].toInt() and 0xff 87 + val id = ((inputBuffer[1].toInt() and 0xff) shl 8) + (inputBuffer[2].toInt() and 0xff) 88 + val size = ((inputBuffer[3].toInt() and 0xff) shl 8) + (inputBuffer[4].toInt() and 0xff) 89 + val chunk = inputBuffer[5].toInt() and 0xff 90 + println("[ONDEMAND] Response [type=$type, id=$id, size=$size, chunk=$chunk]") 91 + onDemandNode = null 92 + var node = toRequest.first() as OnDemandNode? 93 + while (node != null) { 94 + if (node.type == type && node.fileId == id) onDemandNode = node 95 + if (onDemandNode != null) node.cyclesSinceSend = 0 96 + node = toRequest.next() as OnDemandNode? 97 + } 98 + 99 + if (onDemandNode != null) { 100 + idleCycles = 0 101 + if (size == 0) { 102 + SignLink.reportError("Rej: $type,$id") 103 + onDemandNode!!.buffer = null 104 + if (onDemandNode!!.immediate) { 105 + synchronized(completed) { completed.pushBack(onDemandNode!!) } 106 + } else { 107 + onDemandNode!!.remove() 108 + } 109 + onDemandNode = null 110 + } else { 111 + if (onDemandNode!!.buffer == null && chunk == 0) onDemandNode!!.buffer = ByteArray(size) 112 + if (onDemandNode!!.buffer == null && chunk != 0) throw IOException("missing initializeApplication of file") 113 + } 114 + } 115 + offset = chunk * 500 116 + toRead = 500 117 + if (toRead > size - chunk * 500) toRead = size - chunk * 500 118 + } 119 + if (toRead > 0 && available >= toRead) { 120 + expectData = true 121 + var buffer = inputBuffer 122 + var bufferOffset = 0 123 + if (onDemandNode != null) { 124 + buffer = onDemandNode!!.buffer!! 125 + bufferOffset = offset 126 + } 127 + var i = 0 128 + while (i < toRead) { i += inputStream!!.read(buffer, i + bufferOffset, toRead - i) } 129 + if (toRead + offset >= buffer.size && onDemandNode != null) { 130 + println("[ONDEMAND] File complete [type=${onDemandNode!!.type}, id=${onDemandNode!!.fileId}, size=${buffer.size} bytes]") 131 + if (client!!.stores[0] != null) 132 + client!!.stores[onDemandNode!!.type + 1].put(buffer.size, buffer, onDemandNode!!.fileId) 133 + if (!onDemandNode!!.immediate && onDemandNode!!.type == 3) { 134 + onDemandNode!!.immediate = true 135 + onDemandNode!!.type = 93 136 + } 137 + if (onDemandNode!!.immediate) { 138 + synchronized(completed) { completed.pushBack(onDemandNode!!) } 139 + } else { 140 + onDemandNode!!.remove() 141 + } 142 + } 143 + toRead = 0 144 + return 145 + } 146 + } catch (_: IOException) { 147 + try { socket!!.close() } catch (_: Exception) {} 148 + socket = null 149 + inputStream = null 150 + outputStream = null 151 + toRead = 0 152 + } 153 + } 154 + 155 + fun modelId(model: Int): Int = modelIndex!![model].toInt() and 0xff 156 + 157 + override fun requestModel(id: Int) { request(0, id) } 158 + 159 + fun passivesRequest(i: Int) { 160 + if (i != 0) return 161 + while (immediateRequestsSent == 0 && anInt1343 < 10) { 162 + if (highestPriority == 0) break 163 + var node: OnDemandNode? 164 + synchronized(immediateRequests) { node = immediateRequests.pop() as OnDemandNode? } 165 + while (node != null) { 166 + if (filePriorities[node!!.type]!![node!!.fileId].toInt() != 0) { 167 + filePriorities[node!!.type]!![node!!.fileId] = 0 168 + toRequest.pushBack(node!!) 169 + sendRequest(node!!) 170 + expectData = true 171 + if (anInt1334 < anInt1350) anInt1334++ 172 + message = "Loading extra files - " + (anInt1334 * 100) / anInt1350 + "%" 173 + anInt1343++ 174 + if (anInt1343 == 10) return 175 + } 176 + synchronized(immediateRequests) { node = immediateRequests.pop() as OnDemandNode? } 177 + } 178 + for (j in 0 until 4) { 179 + val abyte0 = filePriorities[j]!! 180 + val k = abyte0.size 181 + for (l in 0 until k) { 182 + if (abyte0[l].toInt() == highestPriority) { 183 + abyte0[l] = 0 184 + val newNode = OnDemandNode() 185 + newNode.type = j 186 + newNode.fileId = l 187 + newNode.immediate = false 188 + toRequest.pushBack(newNode) 189 + sendRequest(newNode) 190 + expectData = true 191 + if (anInt1334 < anInt1350) anInt1334++ 192 + message = "Loading extra files - " + (anInt1334 * 100) / anInt1350 + "%" 193 + anInt1343++ 194 + if (anInt1343 == 10) return 195 + } 196 + } 197 + } 198 + highestPriority-- 199 + } 200 + } 201 + 202 + fun setPriority(byte0: Byte, j: Int, k: Int) { 203 + if (client!!.stores[0] == null) return 204 + if (fileVersions[j]!![k] == 0) return 205 + val abyte0 = client!!.stores[j + 1].get(k) 206 + if (verify(fileVersions[j]!![k], fileCrc[j]!![k], abyte0)) return 207 + filePriorities[j]!![k] = byte0 208 + if (byte0 > highestPriority.toByte()) highestPriority = byte0.toInt() 209 + anInt1350++ 210 + } 211 + 212 + fun midiIdEqualsOne(i: Int): Boolean = midiIndex!![i] == 1 213 + 214 + fun request(type: Int, id: Int) { 215 + if (type < 0 || type > fileVersions.size || id < 0 || id > fileVersions[type]!!.size) return 216 + if (fileVersions[type]!![id] == 0) return 217 + synchronized(immediateRequests1) { 218 + var node = immediateRequests1.first() as OnDemandNode? 219 + while (node != null) { 220 + if (node.type == type && node.fileId == id) return 221 + node = immediateRequests1.next() as OnDemandNode? 222 + } 223 + val newNode = OnDemandNode() 224 + newNode.type = type 225 + newNode.fileId = id 226 + newNode.immediate = true 227 + synchronized(wanted) { wanted.pushBack(newNode) } 228 + immediateRequests1.push(newNode) 229 + } 230 + } 231 + 232 + fun next(): OnDemandNode? { 233 + val node: OnDemandNode? 234 + synchronized(completed) { node = completed.pop() as OnDemandNode? } 235 + if (node == null) return null 236 + synchronized(immediateRequests1) { node.clear() } 237 + if (node.buffer == null) return node 238 + var offset = 0 239 + try { 240 + val gzip = GZIPInputStream(ByteArrayInputStream(node.buffer)) 241 + while (true) { 242 + if (offset == deflateOut.size) throw RuntimeException("buffer overflow!") 243 + val k = gzip.read(deflateOut, offset, deflateOut.size - offset) 244 + if (k == -1) break 245 + offset += k 246 + } 247 + } catch (_: IOException) { 248 + throw RuntimeException("error unzipping") 249 + } 250 + node.buffer = ByteArray(offset) 251 + System.arraycopy(deflateOut, 0, node.buffer!!, 0, offset) 252 + return node 253 + } 254 + 255 + override fun run() { 256 + try { 257 + while (running) { 258 + cycle++ 259 + val toWait = if (highestPriority == 0 && client!!.stores[0] != null) 50 else 20 260 + try { Thread.sleep(toWait.toLong()) } catch (_: Exception) {} 261 + expectData = true 262 + for (i in 0 until 100) { 263 + if (!expectData) break 264 + expectData = false 265 + localComplete(true) 266 + remainingRequest(0) 267 + if (immediateRequestsSent == 0 && i >= 5) break 268 + passivesRequest(0) 269 + if (inputStream != null) handleResp() 270 + } 271 + 272 + var idle = false 273 + var node = toRequest.first() as OnDemandNode? 274 + while (node != null) { 275 + if (node.immediate) { 276 + idle = true 277 + node.cyclesSinceSend++ 278 + if (node.cyclesSinceSend > 50) { 279 + node.cyclesSinceSend = 0 280 + sendRequest(node) 281 + } 282 + } 283 + node = toRequest.next() as OnDemandNode? 284 + } 285 + 286 + if (!idle) { 287 + node = toRequest.first() as OnDemandNode? 288 + while (node != null) { 289 + idle = true 290 + node.cyclesSinceSend++ 291 + if (node.cyclesSinceSend > 50) { 292 + node.cyclesSinceSend = 0 293 + sendRequest(node) 294 + } 295 + node = toRequest.next() as OnDemandNode? 296 + } 297 + } 298 + 299 + if (idle) { 300 + idleCycles++ 301 + if (idleCycles > 750) { 302 + try { socket!!.close() } catch (_: Exception) {} 303 + socket = null 304 + inputStream = null 305 + outputStream = null 306 + toRead = 0 307 + } 308 + } else { 309 + idleCycles = 0 310 + message = "" 311 + } 312 + 313 + if (client!!.loggedIn && socket != null && outputStream != null && 314 + (highestPriority > 0 || client!!.stores[0] == null)) { 315 + sinceKeepAlive++ 316 + if (sinceKeepAlive > 500) { 317 + sinceKeepAlive = 0 318 + inputBuffer[0] = 0; inputBuffer[1] = 0; inputBuffer[2] = 0; inputBuffer[3] = 10 319 + try { 320 + outputStream!!.write(inputBuffer, 0, 4) 321 + } catch (_: IOException) { 322 + idleCycles = 5000 323 + } 324 + } 325 + } 326 + } 327 + } catch (e: Exception) { 328 + SignLink.reportError("od_ex ${e.message}") 329 + } 330 + } 331 + 332 + fun remainingRequest(i: Int) { 333 + immediateRequestsSent = 0 334 + anInt1343 = 0 335 + if (i != 0) return 336 + var node = toRequest.first() as OnDemandNode? 337 + while (node != null) { 338 + if (node.immediate) immediateRequestsSent++ else anInt1343++ 339 + node = toRequest.next() as OnDemandNode? 340 + } 341 + 342 + while (immediateRequestsSent < 10) { 343 + val n = aClass6_1351.pop() as OnDemandNode? ?: break 344 + if (filePriorities[n.type]!![n.fileId].toInt() != 0) anInt1334++ 345 + filePriorities[n.type]!![n.fileId] = 0 346 + toRequest.pushBack(n) 347 + immediateRequestsSent++ 348 + sendRequest(n) 349 + expectData = true 350 + } 351 + } 352 + 353 + fun preloadRegions(members: Boolean) { 354 + for (reg in regHash!!.indices) { 355 + if (members || regShouldPreload!![reg] != 0) { 356 + setPriority(2.toByte(), 3, regLandIndex!![reg]) 357 + setPriority(2.toByte(), 3, regMapIndex!![reg]) 358 + } 359 + } 360 + } 361 + 362 + fun method333(): Int { 363 + synchronized(immediateRequests1) { return immediateRequests1.size() } 364 + } 365 + 366 + fun method334(i: Int, flag: Boolean): Boolean { 367 + for (j in regHash!!.indices) { 368 + if (regLandIndex!![j] == i) return true 369 + } 370 + if (flag) anInt1363 = -405 371 + return false 372 + } 373 + 374 + fun init(archive: Archive, client: Game) { 375 + val versionFiles = arrayOf("model_version", "anim_version", "midi_version", "map_version") 376 + for (version in 0 until 4) { 377 + val data = archive.getFile(versionFiles[version])!! 378 + val versionCount = data.size / 2 379 + val buffer = Buffer(data) 380 + fileVersions[version] = IntArray(versionCount) 381 + filePriorities[version] = ByteArray(versionCount) 382 + for (file in 0 until versionCount) fileVersions[version]!![file] = buffer.getUnsignedShortBE() 383 + } 384 + 385 + val crcFiles = arrayOf("model_crc", "anim_crc", "midi_crc", "map_crc") 386 + for (crc in 0 until 4) { 387 + val data = archive.getFile(crcFiles[crc])!! 388 + val crcCount = data.size / 4 389 + val buffer = Buffer(data) 390 + fileCrc[crc] = IntArray(crcCount) 391 + for (file in 0 until crcCount) fileCrc[crc]!![file] = buffer.getIntBE() 392 + } 393 + 394 + var data = archive.getFile("model_index")!! 395 + val count = fileVersions[0]!!.size 396 + modelIndex = ByteArray(count) 397 + for (i in 0 until count) { 398 + modelIndex!![i] = if (i < data.size) data[i] else 0 399 + } 400 + 401 + data = archive.getFile("map_index")!! 402 + var buffer = Buffer(data) 403 + val mapCount = data.size / 7 404 + regHash = IntArray(mapCount) 405 + regMapIndex = IntArray(mapCount) 406 + regLandIndex = IntArray(mapCount) 407 + regShouldPreload = IntArray(mapCount) 408 + for (reg in 0 until mapCount) { 409 + regHash!![reg] = buffer.getUnsignedShortBE() 410 + regMapIndex!![reg] = buffer.getUnsignedShortBE() 411 + regLandIndex!![reg] = buffer.getUnsignedShortBE() 412 + regShouldPreload!![reg] = buffer.getUnsignedByte() 413 + } 414 + 415 + data = archive.getFile("anim_index")!! 416 + buffer = Buffer(data) 417 + val animCount = data.size / 2 418 + animIndex = IntArray(animCount) 419 + for (i in 0 until animCount) animIndex!![i] = buffer.getUnsignedShortBE() 420 + 421 + data = archive.getFile("midi_index")!! 422 + buffer = Buffer(data) 423 + val midiCount = data.size 424 + midiIndex = IntArray(midiCount) 425 + for (i in 0 until midiCount) midiIndex!![i] = buffer.getUnsignedByte() 426 + 427 + this.client = client 428 + this.running = true 429 + this.client!!.startRunnable(this, 2) 430 + } 431 + 432 + fun immediateRequestCount() { 433 + synchronized(immediateRequests) { immediateRequests.clear() } 434 + } 435 + 436 + fun passiveRequest(i: Int, j: Int) { 437 + if (client!!.stores[0] == null) return 438 + if (fileVersions[j]!![i] == 0) return 439 + if (filePriorities[j]!![i].toInt() == 0) return 440 + if (highestPriority == 0) return 441 + val node = OnDemandNode() 442 + node.type = j 443 + node.fileId = i 444 + node.immediate = false 445 + synchronized(immediateRequests) { immediateRequests.pushBack(node) } 446 + } 447 + 448 + fun localComplete(flag: Boolean) { 449 + var node: OnDemandNode? 450 + synchronized(wanted) { node = wanted.pop() as OnDemandNode? } 451 + if (!flag) { 452 + @Suppress("ControlFlowWithEmptyBody") 453 + var i = 1; while (i > 0) { i++ } 454 + } 455 + while (node != null) { 456 + expectData = true 457 + var data: ByteArray? = null 458 + if (client!!.stores[0] != null) 459 + data = client!!.stores[node!!.type + 1].get(node!!.fileId) 460 + if (!verify(fileVersions[node!!.type]!![node!!.fileId], fileCrc[node!!.type]!![node!!.fileId], data)) 461 + data = null 462 + synchronized(wanted) { 463 + if (data == null) { 464 + aClass6_1351.pushBack(node!!) 465 + } else { 466 + node!!.buffer = data 467 + synchronized(completed) { completed.pushBack(node!!) } 468 + } 469 + node = wanted.pop() as OnDemandNode? 470 + } 471 + } 472 + } 473 + 474 + fun stop() { running = false } 475 + 476 + fun fileCount(file: Int): Int = fileVersions[file]!!.size 477 + 478 + fun sendRequest(onDemandNode: OnDemandNode) { 479 + try { 480 + if (socket == null) { 481 + val currentTime = System.currentTimeMillis() 482 + if (currentTime - lastSocketOpen < 4000L) return 483 + lastSocketOpen = currentTime 484 + println("[ONDEMAND] Connecting to port ${Configuration.ONDEMAND_PORT + client!!.portOffset}") 485 + socket = client!!.openSocket(Configuration.ONDEMAND_PORT + client!!.portOffset) 486 + inputStream = socket!!.getInputStream() 487 + outputStream = socket!!.getOutputStream() 488 + outputStream!!.write(15) 489 + val version = ByteArray(2) 490 + version[0] = (Game.VERSION shr 8).toByte() 491 + version[1] = Game.VERSION.toByte() 492 + outputStream!!.write(version, 0, 2) 493 + println("[ONDEMAND] Handshake sent (opcode 15, version ${Game.VERSION})") 494 + for (i in 0 until 8) inputStream!!.read() 495 + println("[ONDEMAND] Connection established") 496 + idleCycles = 0 497 + } 498 + inputBuffer[0] = onDemandNode.type.toByte() 499 + inputBuffer[1] = (onDemandNode.fileId shr 8).toByte() 500 + inputBuffer[2] = onDemandNode.fileId.toByte() 501 + inputBuffer[3] = when { 502 + onDemandNode.immediate -> 2 503 + !client!!.loggedIn -> 1 504 + else -> 0 505 + }.toByte() 506 + println("[ONDEMAND] Requesting [type=${onDemandNode.type}, id=${onDemandNode.fileId}, priority=${inputBuffer[3]}]") 507 + outputStream!!.write(inputBuffer, 0, 4) 508 + sinceKeepAlive = 0 509 + requestFails = -10000 510 + return 511 + } catch (_: IOException) { 512 + } 513 + try { socket!!.close() } catch (_: Exception) {} 514 + socket = null 515 + inputStream = null 516 + outputStream = null 517 + toRead = 0 518 + requestFails++ 519 + } 520 + 521 + fun animCount(): Int = animIndex!!.size 522 + 523 + fun regId(i: Int, regX: Int, regY: Int, l: Int): Int { 524 + val localRegHash = (regX shl 8) + regY 525 + for (reg in regHash!!.indices) { 526 + if (regHash!![reg] == localRegHash) { 527 + return if (l == 0) regMapIndex!![reg] else regLandIndex!![reg] 528 + } 529 + } 530 + return -1 531 + } 532 + }
-8
src/main/java/com/jagex/runescape/net/requester/Requester.java
··· 1 - package com.jagex.runescape.net.requester; 2 - 3 - public class Requester { 4 - 5 - public void requestModel(int model) { 6 - } 7 - 8 - }
+5
src/main/java/com/jagex/runescape/net/requester/Requester.kt
··· 1 + package com.jagex.runescape.net.requester 2 + 3 + open class Requester { 4 + open fun requestModel(model: Int) {} 5 + }
-112
src/main/java/com/jagex/runescape/sound/SoundFilter.java
··· 1 - package com.jagex.runescape.sound; 2 - 3 - import com.jagex.runescape.net.Buffer; 4 - 5 - public class SoundFilter { 6 - 7 - public int[] numPairs = new int[2]; 8 - public int[][][] pairPhase = new int[2][2][4]; 9 - public int[][][] magnitude = new int[2][2][4]; 10 - public int[] unity = new int[2]; 11 - public static float[][] _coefficient = new float[2][8]; 12 - public static int[][] coefficient = new int[2][8]; 13 - public static float _invUnity; 14 - public static int invUnity; 15 - 16 - public float adaptMagnitude(int i, float f, int dir) { 17 - float alpha = magnitude[i][0][dir] + f 18 - * (magnitude[i][1][dir] - magnitude[i][0][dir]); 19 - alpha *= 0.001525879F; 20 - return 1.0F - (float) Math.pow(10D, -alpha / 20F); 21 - } 22 - 23 - public float normalize(float f) { 24 - float f1 = 32.7032F * (float) Math.pow(2D, f); 25 - return (f1 * 3.141593F) / 11025F; 26 - } 27 - 28 - public float adaptPhase(int i, int dir, float f) { 29 - float f1 = pairPhase[dir][0][i] + f 30 - * (pairPhase[dir][1][i] - pairPhase[dir][0][i]); 31 - f1 *= 0.0001220703F; 32 - return normalize(f1); 33 - } 34 - 35 - public int compute(int dir, float f) { 36 - if (dir == 0) { 37 - float f1 = unity[0] + (unity[1] - unity[0]) * f; 38 - f1 *= 0.003051758F; 39 - _invUnity = (float) Math.pow(0.10000000000000001D, f1 / 20F); 40 - invUnity = (int) (_invUnity * 65536F); 41 - } 42 - if (numPairs[dir] == 0) 43 - return 0; 44 - float f2 = adaptMagnitude(dir, f, 0); 45 - _coefficient[dir][0] = -2F * f2 * (float) Math.cos(adaptPhase(0, dir, f)); 46 - _coefficient[dir][1] = f2 * f2; 47 - for (int term = 1; term < numPairs[dir]; term++) { 48 - float f3 = adaptMagnitude(dir, f, term); 49 - float f4 = -2F * f3 * (float) Math.cos(adaptPhase(term, dir, f)); 50 - float f5 = f3 * f3; 51 - _coefficient[dir][term * 2 + 1] = _coefficient[dir][term * 2 - 1] * f5; 52 - _coefficient[dir][term * 2] = _coefficient[dir][term * 2 - 1] * f4 + _coefficient[dir][term * 2 - 2] 53 - * f5; 54 - for (int i = term * 2 - 1; i >= 2; i--) 55 - _coefficient[dir][i] += _coefficient[dir][i - 1] * f4 + _coefficient[dir][i - 2] * f5; 56 - 57 - _coefficient[dir][1] += _coefficient[dir][0] * f4 + f5; 58 - _coefficient[dir][0] += f4; 59 - } 60 - 61 - if (dir == 0) { 62 - for (int l = 0; l < numPairs[0] * 2; l++) 63 - _coefficient[0][l] *= _invUnity; 64 - 65 - } 66 - for (int term = 0; term < numPairs[dir] * 2; term++) 67 - coefficient[dir][term] = (int) (_coefficient[dir][term] * 65536F); 68 - 69 - return numPairs[dir] * 2; 70 - } 71 - 72 - public void decode(SoundTrackEnvelope soundTrackEnvelope, Buffer buffer) { 73 - int numPair = buffer.getUnsignedByte(); 74 - numPairs[0] = numPair >> 4; 75 - numPairs[1] = numPair & 0xf; 76 - if (numPair != 0) { 77 - unity[0] = buffer.getUnsignedShortBE(); 78 - unity[1] = buffer.getUnsignedShortBE(); 79 - int migrated = buffer.getUnsignedByte(); 80 - for (int dir = 0; dir < 2; dir++) { 81 - for (int term = 0; term < numPairs[dir]; term++) { 82 - pairPhase[dir][0][term] = buffer.getUnsignedShortBE(); 83 - magnitude[dir][0][term] = buffer.getUnsignedShortBE(); 84 - } 85 - 86 - } 87 - 88 - for (int dir = 0; dir < 2; dir++) { 89 - for (int term = 0; term < numPairs[dir]; term++) 90 - if ((migrated & 1 << dir * 4 << term) != 0) { 91 - pairPhase[dir][1][term] = buffer.getUnsignedShortBE(); 92 - magnitude[dir][1][term] = buffer.getUnsignedShortBE(); 93 - } else { 94 - pairPhase[dir][1][term] = pairPhase[dir][0][term]; 95 - magnitude[dir][1][term] = magnitude[dir][0][term]; 96 - } 97 - 98 - } 99 - 100 - if (migrated != 0 || unity[1] != unity[0]) 101 - soundTrackEnvelope.decodeShape(buffer); 102 - return; 103 - } else { 104 - unity[0] = unity[1] = 0; 105 - return; 106 - } 107 - } 108 - 109 - 110 - 111 - 112 - }
+108
src/main/java/com/jagex/runescape/sound/SoundFilter.kt
··· 1 + package com.jagex.runescape.sound 2 + 3 + import com.jagex.runescape.net.Buffer 4 + import kotlin.math.cos 5 + import kotlin.math.pow 6 + 7 + /** 8 + * Biquad IIR filter for the sound synthesis engine. Computes filter 9 + * coefficients from parametric phase/magnitude envelopes decoded from 10 + * the cache, then applies them during [SoundTrackInstrument.synthesize]. 11 + */ 12 + class SoundFilter { 13 + @JvmField var numPairs = IntArray(2) 14 + @JvmField var pairPhase = Array(2) { Array(2) { IntArray(4) } } 15 + @JvmField var magnitude = Array(2) { Array(2) { IntArray(4) } } 16 + @JvmField var unity = IntArray(2) 17 + 18 + fun adaptMagnitude(i: Int, f: Float, dir: Int): Float { 19 + var alpha = magnitude[i][0][dir] + f * (magnitude[i][1][dir] - magnitude[i][0][dir]) 20 + alpha *= 0.001525879f 21 + return 1.0f - 10.0.pow((-alpha / 20f).toDouble()).toFloat() 22 + } 23 + 24 + fun normalize(f: Float): Float { 25 + val f1 = 32.7032f * 2.0.pow(f.toDouble()).toFloat() 26 + return (f1 * 3.141593f) / 11025f 27 + } 28 + 29 + fun adaptPhase(i: Int, dir: Int, f: Float): Float { 30 + var f1 = pairPhase[dir][0][i] + f * (pairPhase[dir][1][i] - pairPhase[dir][0][i]) 31 + f1 *= 0.0001220703f 32 + return normalize(f1) 33 + } 34 + 35 + fun compute(dir: Int, f: Float): Int { 36 + if (dir == 0) { 37 + var f1 = unity[0] + (unity[1] - unity[0]) * f 38 + f1 *= 0.003051758f 39 + _invUnity = 0.1.pow((f1 / 20f).toDouble()).toFloat() 40 + invUnity = (_invUnity * 65536f).toInt() 41 + } 42 + if (numPairs[dir] == 0) return 0 43 + val f2 = adaptMagnitude(dir, f, 0) 44 + _coefficient[dir][0] = -2f * f2 * cos(adaptPhase(0, dir, f).toDouble()).toFloat() 45 + _coefficient[dir][1] = f2 * f2 46 + for (term in 1 until numPairs[dir]) { 47 + val f3 = adaptMagnitude(dir, f, term) 48 + val f4 = -2f * f3 * cos(adaptPhase(term, dir, f).toDouble()).toFloat() 49 + val f5 = f3 * f3 50 + _coefficient[dir][term * 2 + 1] = _coefficient[dir][term * 2 - 1] * f5 51 + _coefficient[dir][term * 2] = _coefficient[dir][term * 2 - 1] * f4 + 52 + _coefficient[dir][term * 2 - 2] * f5 53 + for (i in term * 2 - 1 downTo 2) 54 + _coefficient[dir][i] += _coefficient[dir][i - 1] * f4 + _coefficient[dir][i - 2] * f5 55 + _coefficient[dir][1] += _coefficient[dir][0] * f4 + f5 56 + _coefficient[dir][0] += f4 57 + } 58 + 59 + if (dir == 0) { 60 + for (l in 0 until numPairs[0] * 2) 61 + _coefficient[0][l] *= _invUnity 62 + } 63 + for (term in 0 until numPairs[dir] * 2) 64 + coefficient[dir][term] = (_coefficient[dir][term] * 65536f).toInt() 65 + 66 + return numPairs[dir] * 2 67 + } 68 + 69 + fun decode(soundTrackEnvelope: SoundTrackEnvelope, buffer: Buffer) { 70 + val numPair = buffer.getUnsignedByte() 71 + numPairs[0] = numPair shr 4 72 + numPairs[1] = numPair and 0xf 73 + if (numPair != 0) { 74 + unity[0] = buffer.getUnsignedShortBE() 75 + unity[1] = buffer.getUnsignedShortBE() 76 + val migrated = buffer.getUnsignedByte() 77 + for (dir in 0 until 2) { 78 + for (term in 0 until numPairs[dir]) { 79 + pairPhase[dir][0][term] = buffer.getUnsignedShortBE() 80 + magnitude[dir][0][term] = buffer.getUnsignedShortBE() 81 + } 82 + } 83 + for (dir in 0 until 2) { 84 + for (term in 0 until numPairs[dir]) { 85 + if ((migrated and (1 shl dir * 4 shl term)) != 0) { 86 + pairPhase[dir][1][term] = buffer.getUnsignedShortBE() 87 + magnitude[dir][1][term] = buffer.getUnsignedShortBE() 88 + } else { 89 + pairPhase[dir][1][term] = pairPhase[dir][0][term] 90 + magnitude[dir][1][term] = magnitude[dir][0][term] 91 + } 92 + } 93 + } 94 + if (migrated != 0 || unity[1] != unity[0]) 95 + soundTrackEnvelope.decodeShape(buffer) 96 + } else { 97 + unity[1] = 0 98 + unity[0] = 0 99 + } 100 + } 101 + 102 + companion object { 103 + @JvmField var _coefficient = Array(2) { FloatArray(8) } 104 + @JvmField var coefficient = Array(2) { IntArray(8) } 105 + @JvmField var _invUnity: Float = 0f 106 + @JvmField var invUnity: Int = 0 107 + } 108 + }
-205
src/main/java/com/jagex/runescape/sound/SoundPlayer.java
··· 1 - package com.jagex.runescape.sound; 2 - 3 - import java.io.InputStream; 4 - import java.util.Arrays; 5 - import java.util.concurrent.ConcurrentLinkedQueue; 6 - import javax.sound.sampled.AudioFormat; 7 - import javax.sound.sampled.AudioSystem; 8 - import javax.sound.sampled.SourceDataLine; 9 - import com.jagex.runescape.util.SignLink; 10 - 11 - /** 12 - * Persistent audio mixer that keeps a single SourceDataLine open to avoid 13 - * macOS CoreAudio pops caused by repeated device activation/deactivation. 14 - * 15 - * Sounds are queued and mixed in a background thread. Uses 16-bit signed 16 - * output for clean mixing headroom even when multiple sounds overlap. 17 - */ 18 - public class SoundPlayer implements Runnable { 19 - 20 - private static final int SAMPLE_RATE = 22050; 21 - private static final int WAV_HEADER_SIZE = 44; 22 - private static final int SAMPLES_PER_BUFFER = 1024; 23 - private static final int BUFFER_SIZE = SAMPLES_PER_BUFFER * 2; // 16-bit = 2 bytes/sample 24 - 25 - private static final AudioFormat FORMAT = 26 - new AudioFormat(SAMPLE_RATE, 16, 1, true, false); // 16-bit signed LE mono 27 - 28 - private static SourceDataLine line; 29 - private static Thread mixerThread; 30 - private static final ConcurrentLinkedQueue<QueuedSound> soundQueue = 31 - new ConcurrentLinkedQueue<>(); 32 - private static volatile boolean running; 33 - 34 - public static int volume; 35 - 36 - private static class QueuedSound { 37 - final byte[] pcm; 38 - int position; 39 - int delayRemaining; 40 - 41 - QueuedSound(byte[] pcm, int delay) { 42 - this.pcm = pcm; 43 - this.position = 0; 44 - this.delayRemaining = delay; 45 - } 46 - 47 - boolean hasRemaining() { 48 - return position < pcm.length; 49 - } 50 - } 51 - 52 - // Active sounds currently being mixed 53 - private static final java.util.List<QueuedSound> activeSounds = 54 - new java.util.ArrayList<>(); 55 - 56 - public SoundPlayer(InputStream stream, int level, int delay) { 57 - if (volume == 4) { 58 - return; 59 - } 60 - try { 61 - byte[] wavData = readFully(stream); 62 - if (wavData.length <= WAV_HEADER_SIZE) { 63 - return; 64 - } 65 - byte[] pcm = new byte[wavData.length - WAV_HEADER_SIZE]; 66 - System.arraycopy(wavData, WAV_HEADER_SIZE, pcm, 0, pcm.length); 67 - 68 - int delaySamples = (delay * SAMPLE_RATE) / 1000; 69 - 70 - soundQueue.add(new QueuedSound(pcm, delaySamples)); 71 - ensureMixerRunning(); 72 - } catch (Exception e) { 73 - e.printStackTrace(); 74 - } 75 - } 76 - 77 - private static synchronized void ensureMixerRunning() { 78 - if (mixerThread != null && mixerThread.isAlive()) { 79 - return; 80 - } 81 - running = true; 82 - mixerThread = new Thread(new SoundPlayer(), "SoundMixer"); 83 - mixerThread.setDaemon(true); 84 - mixerThread.start(); 85 - } 86 - 87 - // Dummy constructor for the mixer thread Runnable 88 - private SoundPlayer() { 89 - } 90 - 91 - @Override 92 - public void run() { 93 - try { 94 - line = AudioSystem.getSourceDataLine(FORMAT); 95 - line.open(FORMAT, SAMPLE_RATE * 2); // 1-second buffer at 16-bit 96 - line.start(); 97 - 98 - byte[] buffer = new byte[BUFFER_SIZE]; 99 - int idleSamples = 0; 100 - 101 - while (running) { 102 - // Drain queue into active list 103 - QueuedSound queued; 104 - while ((queued = soundQueue.poll()) != null) { 105 - activeSounds.add(queued); 106 - idleSamples = 0; 107 - } 108 - 109 - // Tick delay countdowns (in samples, not bytes) 110 - for (QueuedSound s : activeSounds) { 111 - if (s.delayRemaining > 0) { 112 - s.delayRemaining -= SAMPLES_PER_BUFFER; 113 - } 114 - } 115 - 116 - if (activeSounds.isEmpty()) { 117 - idleSamples += SAMPLES_PER_BUFFER; 118 - if (idleSamples < SAMPLE_RATE * 2) { 119 - // Write silence for 2 seconds to keep device active 120 - Arrays.fill(buffer, (byte) 0); 121 - line.write(buffer, 0, BUFFER_SIZE); 122 - } else { 123 - // Sleep to avoid spinning — line stays open 124 - Thread.sleep(10); 125 - } 126 - continue; 127 - } 128 - 129 - idleSamples = 0; 130 - 131 - // Volume: waveVolume is centibels (dB × 100), convert to linear 132 - float dB = SignLink.waveVolume / 100.0f; 133 - float gain = (float) Math.pow(10.0, dB / 20.0); 134 - if (gain > 1.0f) gain = 1.0f; 135 - 136 - // Mix active sounds into 16-bit signed output 137 - for (int i = 0; i < SAMPLES_PER_BUFFER; i++) { 138 - int mixed = 0; 139 - 140 - for (QueuedSound s : activeSounds) { 141 - if (s.delayRemaining > 0 || !s.hasRemaining()) { 142 - continue; 143 - } 144 - // Upscale 8-bit unsigned PCM to 16-bit signed 145 - mixed += ((s.pcm[s.position] & 0xFF) - 128) << 8; 146 - s.position++; 147 - } 148 - 149 - // Apply volume gain 150 - mixed = (int) (mixed * gain); 151 - 152 - // Clamp to 16-bit signed range 153 - if (mixed > 32767) mixed = 32767; 154 - if (mixed < -32768) mixed = -32768; 155 - 156 - // Write as little-endian 16-bit 157 - buffer[i * 2] = (byte) (mixed & 0xFF); 158 - buffer[i * 2 + 1] = (byte) ((mixed >> 8) & 0xFF); 159 - } 160 - 161 - // Remove finished sounds 162 - activeSounds.removeIf(s -> !s.hasRemaining() && s.delayRemaining <= 0); 163 - 164 - line.write(buffer, 0, BUFFER_SIZE); 165 - } 166 - } catch (Exception e) { 167 - e.printStackTrace(); 168 - } finally { 169 - if (line != null) { 170 - line.drain(); 171 - line.close(); 172 - } 173 - } 174 - } 175 - 176 - private static byte[] readFully(InputStream in) throws Exception { 177 - java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); 178 - byte[] buf = new byte[4096]; 179 - int n; 180 - while ((n = in.read(buf)) != -1) { 181 - out.write(buf, 0, n); 182 - } 183 - in.close(); 184 - return out.toByteArray(); 185 - } 186 - 187 - /** 188 - * Shut down the mixer thread and close the audio line. 189 - * Call on application exit. 190 - */ 191 - public static void shutdown() { 192 - running = false; 193 - if (mixerThread != null) { 194 - mixerThread.interrupt(); 195 - } 196 - } 197 - 198 - public static void setVolume(int level) { 199 - volume = level; 200 - } 201 - 202 - public static int getVolume() { 203 - return volume; 204 - } 205 - }
+159
src/main/java/com/jagex/runescape/sound/SoundPlayer.kt
··· 1 + package com.jagex.runescape.sound 2 + 3 + import com.jagex.runescape.util.SignLink 4 + import java.io.ByteArrayOutputStream 5 + import java.io.InputStream 6 + import java.util.Arrays 7 + import java.util.concurrent.ConcurrentLinkedQueue 8 + import javax.sound.sampled.AudioFormat 9 + import javax.sound.sampled.AudioSystem 10 + import javax.sound.sampled.SourceDataLine 11 + 12 + /** 13 + * Persistent audio mixer that keeps a single SourceDataLine open to avoid 14 + * macOS CoreAudio pops caused by repeated device activation/deactivation. 15 + * 16 + * Sounds are queued and mixed in a background thread. Uses 16-bit signed 17 + * output for clean mixing headroom even when multiple sounds overlap. 18 + */ 19 + class SoundPlayer : Runnable { 20 + 21 + /** Public constructor — queues a sound for playback from a WAV stream. */ 22 + constructor(stream: InputStream, level: Int, delay: Int) { 23 + if (volume == 4) return 24 + try { 25 + val wavData = readFully(stream) 26 + if (wavData.size <= WAV_HEADER_SIZE) return 27 + val pcm = ByteArray(wavData.size - WAV_HEADER_SIZE) 28 + System.arraycopy(wavData, WAV_HEADER_SIZE, pcm, 0, pcm.size) 29 + val delaySamples = (delay * SAMPLE_RATE) / 1000 30 + soundQueue.add(QueuedSound(pcm, delaySamples)) 31 + ensureMixerRunning() 32 + } catch (e: Exception) { 33 + e.printStackTrace() 34 + } 35 + } 36 + 37 + /** Private constructor for the mixer thread Runnable. */ 38 + private constructor() 39 + 40 + override fun run() { 41 + try { 42 + line = AudioSystem.getSourceDataLine(FORMAT) 43 + line!!.open(FORMAT, SAMPLE_RATE * 2) 44 + line!!.start() 45 + 46 + val buffer = ByteArray(BUFFER_SIZE) 47 + var idleSamples = 0 48 + 49 + while (running) { 50 + var queued = soundQueue.poll() 51 + while (queued != null) { 52 + activeSounds.add(queued) 53 + idleSamples = 0 54 + queued = soundQueue.poll() 55 + } 56 + 57 + for (s in activeSounds) { 58 + if (s.delayRemaining > 0) s.delayRemaining -= SAMPLES_PER_BUFFER 59 + } 60 + 61 + if (activeSounds.isEmpty()) { 62 + idleSamples += SAMPLES_PER_BUFFER 63 + if (idleSamples < SAMPLE_RATE * 2) { 64 + Arrays.fill(buffer, 0.toByte()) 65 + line!!.write(buffer, 0, BUFFER_SIZE) 66 + } else { 67 + Thread.sleep(10) 68 + } 69 + continue 70 + } 71 + 72 + idleSamples = 0 73 + 74 + var dB = SignLink.waveVolume / 100.0f 75 + var gain = Math.pow(10.0, (dB / 20.0).toDouble()).toFloat() 76 + if (gain > 1.0f) gain = 1.0f 77 + 78 + for (i in 0 until SAMPLES_PER_BUFFER) { 79 + var mixed = 0 80 + for (s in activeSounds) { 81 + if (s.delayRemaining > 0 || !s.hasRemaining()) continue 82 + mixed += ((s.pcm[s.position].toInt() and 0xFF) - 128) shl 8 83 + s.position++ 84 + } 85 + mixed = (mixed * gain).toInt() 86 + if (mixed > 32767) mixed = 32767 87 + if (mixed < -32768) mixed = -32768 88 + buffer[i * 2] = (mixed and 0xFF).toByte() 89 + buffer[i * 2 + 1] = ((mixed shr 8) and 0xFF).toByte() 90 + } 91 + 92 + activeSounds.removeAll { !it.hasRemaining() && it.delayRemaining <= 0 } 93 + line!!.write(buffer, 0, BUFFER_SIZE) 94 + } 95 + } catch (e: Exception) { 96 + e.printStackTrace() 97 + } finally { 98 + if (line != null) { 99 + line!!.drain() 100 + line!!.close() 101 + } 102 + } 103 + } 104 + 105 + private class QueuedSound(val pcm: ByteArray, var delayRemaining: Int) { 106 + var position: Int = 0 107 + fun hasRemaining(): Boolean = position < pcm.size 108 + } 109 + 110 + companion object { 111 + private const val SAMPLE_RATE = 22050 112 + private const val WAV_HEADER_SIZE = 44 113 + private const val SAMPLES_PER_BUFFER = 1024 114 + private const val BUFFER_SIZE = SAMPLES_PER_BUFFER * 2 115 + 116 + private val FORMAT = AudioFormat(SAMPLE_RATE.toFloat(), 16, 1, true, false) 117 + 118 + private var line: SourceDataLine? = null 119 + private var mixerThread: Thread? = null 120 + private val soundQueue = ConcurrentLinkedQueue<QueuedSound>() 121 + @Volatile private var running: Boolean = false 122 + 123 + @JvmField var volume: Int = 0 124 + 125 + private val activeSounds = mutableListOf<QueuedSound>() 126 + 127 + @Synchronized 128 + private fun ensureMixerRunning() { 129 + if (mixerThread != null && mixerThread!!.isAlive) return 130 + running = true 131 + mixerThread = Thread(SoundPlayer(), "SoundMixer") 132 + mixerThread!!.isDaemon = true 133 + mixerThread!!.start() 134 + } 135 + 136 + private fun readFully(input: InputStream): ByteArray { 137 + val out = ByteArrayOutputStream() 138 + val buf = ByteArray(4096) 139 + var n: Int 140 + while (input.read(buf).also { n = it } != -1) { 141 + out.write(buf, 0, n) 142 + } 143 + input.close() 144 + return out.toByteArray() 145 + } 146 + 147 + @JvmStatic 148 + fun shutdown() { 149 + running = false 150 + mixerThread?.interrupt() 151 + } 152 + 153 + @JvmStatic 154 + fun setVolume(level: Int) { volume = level } 155 + 156 + @JvmStatic 157 + fun getVolume(): Int = volume 158 + } 159 + }
-154
src/main/java/com/jagex/runescape/sound/SoundTrack.java
··· 1 - package com.jagex.runescape.sound; 2 - 3 - import com.jagex.runescape.net.Buffer; 4 - 5 - public class SoundTrack { 6 - 7 - public static byte aByte664 = 6; 8 - public static SoundTrack tracks[] = new SoundTrack[5000]; 9 - public static int trackDelays[] = new int[5000]; 10 - public static byte _buffer[]; 11 - public static Buffer buffer; 12 - public SoundTrackInstrument instruments[] = new SoundTrackInstrument[10]; 13 - public int loopBegin; 14 - public int loopEnd; 15 - 16 - public SoundTrack(int i) { 17 - while (i >= 0) { 18 - throw new NullPointerException(); 19 - } 20 - 21 - } 22 - 23 - public static void load(Buffer buffer) { 24 - SoundTrack._buffer = new byte[0x6baa8]; 25 - SoundTrack.buffer = new Buffer(SoundTrack._buffer); 26 - SoundTrackInstrument.decode(); 27 - while (true) { 28 - int trackId = buffer.getUnsignedShortBE(); 29 - if (trackId == 65535) 30 - return; 31 - SoundTrack.tracks[trackId] = new SoundTrack(-524); 32 - SoundTrack.tracks[trackId].decode(buffer); 33 - SoundTrack.trackDelays[trackId] = SoundTrack.tracks[trackId].delay(); 34 - } 35 - } 36 - 37 - public static Buffer data(int trackId, int loops) { 38 - if (SoundTrack.tracks[trackId] != null) { 39 - SoundTrack soundTrack = SoundTrack.tracks[trackId]; 40 - return soundTrack.encode(loops); 41 - } else { 42 - return null; 43 - } 44 - } 45 - 46 - public void decode(Buffer buffer) { 47 - for (int instrument = 0; instrument < 10; instrument++) { 48 - int active = buffer.getUnsignedByte(); 49 - if (active != 0) { 50 - buffer.currentPosition--; 51 - instruments[instrument] = new SoundTrackInstrument(); 52 - instruments[instrument].decode(buffer); 53 - } 54 - } 55 - 56 - loopBegin = buffer.getUnsignedShortBE(); 57 - loopEnd = buffer.getUnsignedShortBE(); 58 - } 59 - 60 - public int delay() { 61 - int delay = 0x98967f; 62 - for (int instrument = 0; instrument < 10; instrument++) 63 - if (instruments[instrument] != null && instruments[instrument].pauseMillis / 20 < delay) 64 - delay = instruments[instrument].pauseMillis / 20; 65 - 66 - if (loopBegin < loopEnd && loopBegin / 20 < delay) 67 - delay = loopBegin / 20; 68 - if (delay == 0x98967f || delay == 0) 69 - return 0; 70 - for (int instrument = 0; instrument < 10; instrument++) 71 - if (instruments[instrument] != null) 72 - instruments[instrument].pauseMillis -= delay * 20; 73 - 74 - if (loopBegin < loopEnd) { 75 - loopBegin -= delay * 20; 76 - loopEnd -= delay * 20; 77 - } 78 - return delay; 79 - } 80 - 81 - public Buffer encode(int j) { 82 - int size = mix(j); 83 - SoundTrack.buffer.currentPosition = 0; 84 - SoundTrack.buffer.putIntBE(0x52494646); // "RIFF" 85 - SoundTrack.buffer.putIntLE(36 + size); // chunk length 86 - SoundTrack.buffer.putIntBE(0x57415645); // "WAVE" (format) 87 - SoundTrack.buffer.putIntBE(0x666d7420); // "FMT " (subchunk id) 88 - SoundTrack.buffer.putIntLE(16); // subchunk size 89 - SoundTrack.buffer.putShortLECopy(1); // PCM 90 - SoundTrack.buffer.putShortLECopy(1); // channels (mono) 91 - SoundTrack.buffer.putIntLE(22050); // sample rate 92 - SoundTrack.buffer.putIntLE(22050); // byte rate 93 - SoundTrack.buffer.putShortLECopy(1); // block alignment 94 - SoundTrack.buffer.putShortLECopy(8); // bits per sample 95 - SoundTrack.buffer.putIntBE(0x64617461); // "DATA" (subchunk id) 96 - SoundTrack.buffer.putIntLE(size); // length 97 - SoundTrack.buffer.currentPosition += size; 98 - return SoundTrack.buffer; 99 - } 100 - 101 - public int mix(int loops) { 102 - int millis = 0; 103 - for (int instrument = 0; instrument < 10; instrument++) 104 - if (instruments[instrument] != null && instruments[instrument].soundMillis + instruments[instrument].pauseMillis > millis) 105 - millis = instruments[instrument].soundMillis + instruments[instrument].pauseMillis; 106 - 107 - if (millis == 0) 108 - return 0; 109 - int nS = (22050 * millis) / 1000; 110 - int loopBegin = (22050 * this.loopBegin) / 1000; 111 - int loopEnd = (22050 * this.loopEnd) / 1000; 112 - if (loopBegin < 0 || loopBegin > nS || loopEnd < 0 || loopEnd > nS || loopBegin >= loopEnd) 113 - loops = 0; 114 - int length = nS + (loopEnd - loopBegin) * (loops - 1); 115 - for (int position = 44; position < length + 44; position++) 116 - SoundTrack._buffer[position] = -128; 117 - 118 - for (int instrument = 0; instrument < 10; instrument++) 119 - if (instruments[instrument] != null) { 120 - int soundSamples = (instruments[instrument].soundMillis * 22050) / 1000; 121 - int pauseSamples = (instruments[instrument].pauseMillis * 22050) / 1000; 122 - int samples[] = instruments[instrument].synthesize(soundSamples, instruments[instrument].soundMillis); 123 - for (int soundSample = 0; soundSample < soundSamples; soundSample++) { 124 - int sample = (SoundTrack._buffer[soundSample + pauseSamples + 44] & 0xff) + (samples[soundSample] >> 8); 125 - if ((sample & 0xffffff00) != 0) 126 - sample = ~(sample >> 31); 127 - SoundTrack._buffer[soundSample + pauseSamples + 44] = (byte) sample; 128 - } 129 - 130 - } 131 - 132 - if (loops > 1) { 133 - loopBegin += 44; 134 - loopEnd += 44; 135 - nS += 44; 136 - int offset = (length += 44) - nS; 137 - for (int position = nS - 1; position >= loopEnd; position--) 138 - SoundTrack._buffer[position + offset] = SoundTrack._buffer[position]; 139 - 140 - for (int loopCounter = 1; loopCounter < loops; loopCounter++) { 141 - offset = (loopEnd - loopBegin) * loopCounter; 142 - for (int position = loopBegin; position < loopEnd; position++) 143 - SoundTrack._buffer[position + offset] = SoundTrack._buffer[position]; 144 - 145 - } 146 - 147 - length -= 44; 148 - } 149 - return length; 150 - } 151 - 152 - 153 - 154 - }
+146
src/main/java/com/jagex/runescape/sound/SoundTrack.kt
··· 1 + package com.jagex.runescape.sound 2 + 3 + import com.jagex.runescape.net.Buffer 4 + 5 + /** 6 + * A sound effect composed of up to 10 [SoundTrackInstrument]s mixed together. 7 + * Decodes instrument definitions from the cache, synthesizes PCM audio, and 8 + * encodes the result as a WAV file in a [Buffer] for playback via [SignLink]. 9 + */ 10 + class SoundTrack(i: Int) { 11 + 12 + @JvmField var instruments = arrayOfNulls<SoundTrackInstrument>(10) 13 + @JvmField var loopBegin: Int = 0 14 + @JvmField var loopEnd: Int = 0 15 + 16 + init { 17 + @Suppress("ControlFlowWithEmptyBody") 18 + while (i >= 0) { throw NullPointerException() } 19 + } 20 + 21 + fun decode(buffer: Buffer) { 22 + for (instrument in 0 until 10) { 23 + val active = buffer.getUnsignedByte() 24 + if (active != 0) { 25 + buffer.currentPosition-- 26 + instruments[instrument] = SoundTrackInstrument() 27 + instruments[instrument]!!.decode(buffer) 28 + } 29 + } 30 + loopBegin = buffer.getUnsignedShortBE() 31 + loopEnd = buffer.getUnsignedShortBE() 32 + } 33 + 34 + fun delay(): Int { 35 + var delay = 0x98967f 36 + for (instrument in 0 until 10) 37 + if (instruments[instrument] != null && instruments[instrument]!!.pauseMillis / 20 < delay) 38 + delay = instruments[instrument]!!.pauseMillis / 20 39 + 40 + if (loopBegin < loopEnd && loopBegin / 20 < delay) delay = loopBegin / 20 41 + if (delay == 0x98967f || delay == 0) return 0 42 + for (instrument in 0 until 10) 43 + if (instruments[instrument] != null) instruments[instrument]!!.pauseMillis -= delay * 20 44 + 45 + if (loopBegin < loopEnd) { 46 + loopBegin -= delay * 20 47 + loopEnd -= delay * 20 48 + } 49 + return delay 50 + } 51 + 52 + fun encode(j: Int): Buffer { 53 + val size = mix(j) 54 + buffer.currentPosition = 0 55 + buffer.putIntBE(0x52494646) // "RIFF" 56 + buffer.putIntLE(36 + size) // chunk length 57 + buffer.putIntBE(0x57415645) // "WAVE" 58 + buffer.putIntBE(0x666d7420) // "fmt " 59 + buffer.putIntLE(16) // subchunk size 60 + buffer.putShortLECopy(1) // PCM 61 + buffer.putShortLECopy(1) // mono 62 + buffer.putIntLE(22050) // sample rate 63 + buffer.putIntLE(22050) // byte rate 64 + buffer.putShortLECopy(1) // block alignment 65 + buffer.putShortLECopy(8) // bits per sample 66 + buffer.putIntBE(0x64617461) // "data" 67 + buffer.putIntLE(size) // data length 68 + buffer.currentPosition += size 69 + return buffer 70 + } 71 + 72 + fun mix(loops: Int): Int { 73 + var millis = 0 74 + for (instrument in 0 until 10) 75 + if (instruments[instrument] != null && 76 + instruments[instrument]!!.soundMillis + instruments[instrument]!!.pauseMillis > millis) 77 + millis = instruments[instrument]!!.soundMillis + instruments[instrument]!!.pauseMillis 78 + 79 + if (millis == 0) return 0 80 + var nS = (22050 * millis) / 1000 81 + val loopBegin = (22050 * this.loopBegin) / 1000 82 + val loopEnd = (22050 * this.loopEnd) / 1000 83 + var actualLoops = loops 84 + if (loopBegin < 0 || loopBegin > nS || loopEnd < 0 || loopEnd > nS || loopBegin >= loopEnd) 85 + actualLoops = 0 86 + var length = nS + (loopEnd - loopBegin) * (actualLoops - 1) 87 + for (position in 44 until length + 44) 88 + _buffer!![position] = (-128).toByte() 89 + 90 + for (instrument in 0 until 10) { 91 + if (instruments[instrument] != null) { 92 + val soundSamples = (instruments[instrument]!!.soundMillis * 22050) / 1000 93 + val pauseSamples = (instruments[instrument]!!.pauseMillis * 22050) / 1000 94 + val samples = instruments[instrument]!!.synthesize(soundSamples, instruments[instrument]!!.soundMillis) 95 + for (s in 0 until soundSamples) { 96 + var sample = (_buffer!![s + pauseSamples + 44].toInt() and 0xff) + (samples[s] shr 8) 97 + if (sample and 0xffffff00.toInt() != 0) sample = (sample shr 31).inv() 98 + _buffer!![s + pauseSamples + 44] = sample.toByte() 99 + } 100 + } 101 + } 102 + 103 + if (actualLoops > 1) { 104 + val lb = loopBegin + 44 105 + val le = loopEnd + 44 106 + nS += 44 107 + val offset = (length + 44) - nS 108 + for (position in nS - 1 downTo le) 109 + _buffer!![position + offset] = _buffer!![position] 110 + for (loopCounter in 1 until actualLoops) { 111 + val off = (le - lb) * loopCounter 112 + for (position in lb until le) 113 + _buffer!![position + off] = _buffer!![position] 114 + } 115 + length = (length + 44) - 44 116 + } 117 + return length 118 + } 119 + 120 + companion object { 121 + @JvmField var aByte664: Byte = 6 122 + @JvmField var tracks = arrayOfNulls<SoundTrack>(5000) 123 + @JvmField var trackDelays = IntArray(5000) 124 + @JvmField var _buffer: ByteArray? = null 125 + @JvmField var buffer: Buffer = Buffer(ByteArray(0)) 126 + 127 + @JvmStatic 128 + fun load(buffer: Buffer) { 129 + _buffer = ByteArray(0x6baa8) 130 + Companion.buffer = Buffer(_buffer!!) 131 + SoundTrackInstrument.decode() 132 + while (true) { 133 + val trackId = buffer.getUnsignedShortBE() 134 + if (trackId == 65535) return 135 + tracks[trackId] = SoundTrack(-524) 136 + tracks[trackId]!!.decode(buffer) 137 + trackDelays[trackId] = tracks[trackId]!!.delay() 138 + } 139 + } 140 + 141 + @JvmStatic 142 + fun data(trackId: Int, loops: Int): Buffer? { 143 + return tracks[trackId]?.encode(loops) 144 + } 145 + } 146 + }
-59
src/main/java/com/jagex/runescape/sound/SoundTrackEnvelope.java
··· 1 - package com.jagex.runescape.sound; 2 - 3 - import com.jagex.runescape.net.Buffer; 4 - 5 - public class SoundTrackEnvelope { 6 - 7 - public int numPhases; 8 - public int phaseDuration[]; 9 - public int phasePeak[]; 10 - public int smart; 11 - public int end; 12 - public int form; 13 - public int critical; 14 - public int phaseIndex; 15 - public int step; 16 - public int amplitude; 17 - public int ticks; 18 - 19 - public void decode(Buffer buffer) { 20 - form = buffer.getUnsignedByte(); 21 - smart = buffer.getIntBE(); 22 - end = buffer.getIntBE(); 23 - decodeShape(buffer); 24 - } 25 - 26 - public void decodeShape(Buffer buffer) { 27 - numPhases = buffer.getUnsignedByte(); 28 - phaseDuration = new int[numPhases]; 29 - phasePeak = new int[numPhases]; 30 - for (int phase = 0; phase < numPhases; phase++) { 31 - phaseDuration[phase] = buffer.getUnsignedShortBE(); 32 - phasePeak[phase] = buffer.getUnsignedShortBE(); 33 - } 34 - 35 - } 36 - 37 - public void reset() { 38 - critical = 0; 39 - phaseIndex = 0; 40 - step = 0; 41 - amplitude = 0; 42 - ticks = 0; 43 - 44 - } 45 - 46 - public int step(int period) { 47 - if (ticks >= critical) { 48 - amplitude = phasePeak[phaseIndex++] << 15; 49 - if (phaseIndex >= numPhases) 50 - phaseIndex = numPhases - 1; 51 - critical = (int) ((phaseDuration[phaseIndex] / 65536D) * period); 52 - if (critical > ticks) 53 - step = ((phasePeak[phaseIndex] << 15) - amplitude) / (critical - ticks); 54 - } 55 - amplitude += step; 56 - ticks++; 57 - return amplitude - step >> 15; 58 - } 59 - }
+60
src/main/java/com/jagex/runescape/sound/SoundTrackEnvelope.kt
··· 1 + package com.jagex.runescape.sound 2 + 3 + import com.jagex.runescape.net.Buffer 4 + 5 + /** 6 + * Envelope generator for sound synthesis. Defines how a sound parameter 7 + * (pitch, volume, filter cutoff) evolves over time through a series of 8 + * phases with linear interpolation between peak values. 9 + */ 10 + class SoundTrackEnvelope { 11 + @JvmField var numPhases: Int = 0 12 + @JvmField var phaseDuration: IntArray? = null 13 + @JvmField var phasePeak: IntArray? = null 14 + @JvmField var smart: Int = 0 15 + @JvmField var end: Int = 0 16 + @JvmField var form: Int = 0 17 + @JvmField var critical: Int = 0 18 + @JvmField var phaseIndex: Int = 0 19 + @JvmField var step: Int = 0 20 + @JvmField var amplitude: Int = 0 21 + @JvmField var ticks: Int = 0 22 + 23 + fun decode(buffer: Buffer) { 24 + form = buffer.getUnsignedByte() 25 + smart = buffer.getIntBE() 26 + end = buffer.getIntBE() 27 + decodeShape(buffer) 28 + } 29 + 30 + fun decodeShape(buffer: Buffer) { 31 + numPhases = buffer.getUnsignedByte() 32 + phaseDuration = IntArray(numPhases) 33 + phasePeak = IntArray(numPhases) 34 + for (phase in 0 until numPhases) { 35 + phaseDuration!![phase] = buffer.getUnsignedShortBE() 36 + phasePeak!![phase] = buffer.getUnsignedShortBE() 37 + } 38 + } 39 + 40 + fun reset() { 41 + critical = 0 42 + phaseIndex = 0 43 + step = 0 44 + amplitude = 0 45 + ticks = 0 46 + } 47 + 48 + fun step(period: Int): Int { 49 + if (ticks >= critical) { 50 + amplitude = phasePeak!![phaseIndex++] shl 15 51 + if (phaseIndex >= numPhases) phaseIndex = numPhases - 1 52 + critical = ((phaseDuration!![phaseIndex] / 65536.0) * period).toInt() 53 + if (critical > ticks) 54 + step = ((phasePeak!![phaseIndex] shl 15) - amplitude) / (critical - ticks) 55 + } 56 + amplitude += step 57 + ticks++ 58 + return amplitude - step shr 15 59 + } 60 + }
-275
src/main/java/com/jagex/runescape/sound/SoundTrackInstrument.java
··· 1 - package com.jagex.runescape.sound; 2 - 3 - import com.jagex.runescape.net.Buffer; 4 - 5 - public class SoundTrackInstrument { 6 - public SoundTrackEnvelope pitchEnvelope; 7 - public SoundTrackEnvelope volumeEnvelope; 8 - public SoundTrackEnvelope pitchModEnvelope; 9 - public SoundTrackEnvelope pitchModAmpEnvelope; 10 - public SoundTrackEnvelope volumeModEnvelope; 11 - public SoundTrackEnvelope volumeModAmpEnvelope; 12 - public SoundTrackEnvelope gatingReleaseEnvelope; 13 - public SoundTrackEnvelope gatingAttackEnvelope; 14 - public int oscillVolume[] = new int[5]; 15 - public int oscillPitchDelta[] = new int[5]; 16 - public int oscillDelay[] = new int[5]; 17 - public int delayTime; 18 - public int delayFeedback = 100; 19 - public SoundFilter filter; 20 - public SoundTrackEnvelope filterEnvelope; 21 - public int soundMillis = 500; 22 - public int pauseMillis; 23 - public static int buffer[]; 24 - public static int noise[]; 25 - public static int sine[]; 26 - public static int phases[] = new int[5]; 27 - public static int delays[] = new int[5]; 28 - public static int volumeStep[] = new int[5]; 29 - public static int pitchStep[] = new int[5]; 30 - public static int pitchBaseStep[] = new int[5]; 31 - 32 - public static void decode() { 33 - SoundTrackInstrument.noise = new int[32768]; 34 - for (int noiseId = 0; noiseId < 32768; noiseId++) 35 - if (Math.random() > 0.5D) 36 - SoundTrackInstrument.noise[noiseId] = 1; 37 - else 38 - SoundTrackInstrument.noise[noiseId] = -1; 39 - 40 - SoundTrackInstrument.sine = new int[32768]; 41 - for (int sineId = 0; sineId < 32768; sineId++) 42 - SoundTrackInstrument.sine[sineId] = (int) (Math.sin(sineId / 5215.1903000000002D) * 16384D); 43 - 44 - SoundTrackInstrument.buffer = new int[0x35d54]; 45 - } 46 - 47 - public int[] synthesize(int nS, int dt) { 48 - for (int position = 0; position < nS; position++) 49 - buffer[position] = 0; 50 - 51 - if (dt < 10) 52 - return buffer; 53 - double fS = nS / (dt + 0.0D); 54 - pitchEnvelope.reset(); 55 - volumeEnvelope.reset(); 56 - int pitchModStep = 0; 57 - int pitchModBaseStep = 0; 58 - int pitchModPhase = 0; 59 - if (pitchModEnvelope != null) { 60 - pitchModEnvelope.reset(); 61 - pitchModAmpEnvelope.reset(); 62 - pitchModStep = (int) (((pitchModEnvelope.end - pitchModEnvelope.smart) * 32.768000000000001D) / fS); 63 - pitchModBaseStep = (int) ((pitchModEnvelope.smart * 32.768000000000001D) / fS); 64 - } 65 - int volumeModStep = 0; 66 - int volumeModBaseStep = 0; 67 - int volumeModPhase = 0; 68 - if (volumeModEnvelope != null) { 69 - volumeModEnvelope.reset(); 70 - volumeModAmpEnvelope.reset(); 71 - volumeModStep = (int) (((volumeModEnvelope.end - volumeModEnvelope.smart) * 32.768000000000001D) / fS); 72 - volumeModBaseStep = (int) ((volumeModEnvelope.smart * 32.768000000000001D) / fS); 73 - } 74 - for (int oscillVolumeId = 0; oscillVolumeId < 5; oscillVolumeId++) 75 - if (oscillVolume[oscillVolumeId] != 0) { 76 - phases[oscillVolumeId] = 0; 77 - delays[oscillVolumeId] = (int) (oscillDelay[oscillVolumeId] * fS); 78 - volumeStep[oscillVolumeId] = (oscillVolume[oscillVolumeId] << 14) / 100; 79 - pitchStep[oscillVolumeId] = (int) (((pitchEnvelope.end - pitchEnvelope.smart) * 32.768000000000001D * Math 80 - .pow(1.0057929410678534D, oscillPitchDelta[oscillVolumeId])) / fS); 81 - pitchBaseStep[oscillVolumeId] = (int) ((pitchEnvelope.smart * 32.768000000000001D) / fS); 82 - } 83 - 84 - for (int offset = 0; offset < nS; offset++) { 85 - int pitchChange = pitchEnvelope.step(nS); 86 - int volumeChange = volumeEnvelope.step(nS); 87 - if (pitchModEnvelope != null) { 88 - int mod = pitchModEnvelope.step(nS); 89 - int modAmp = pitchModAmpEnvelope.step(nS); 90 - pitchChange += evaluateWave(modAmp, pitchModPhase, pitchModEnvelope.form) >> 1; 91 - pitchModPhase += (mod * pitchModStep >> 16) + pitchModBaseStep; 92 - } 93 - if (volumeModEnvelope != null) { 94 - int mod = volumeModEnvelope.step(nS); 95 - int modAmp = volumeModAmpEnvelope.step(nS); 96 - volumeChange = volumeChange * ((evaluateWave(modAmp, volumeModPhase, volumeModEnvelope.form) >> 1) + 32768) >> 15; 97 - volumeModPhase += (mod * volumeModStep >> 16) + volumeModBaseStep; 98 - } 99 - for (int oscillVolumeId = 0; oscillVolumeId < 5; oscillVolumeId++) 100 - if (oscillVolume[oscillVolumeId] != 0) { 101 - int position = offset + delays[oscillVolumeId]; 102 - if (position < nS) { 103 - buffer[position] += evaluateWave(volumeChange 104 - * volumeStep[oscillVolumeId] >> 15, phases[oscillVolumeId], pitchEnvelope.form); 105 - phases[oscillVolumeId] += (pitchChange * pitchStep[oscillVolumeId] >> 16) + pitchBaseStep[oscillVolumeId]; 106 - } 107 - } 108 - 109 - } 110 - 111 - if (gatingReleaseEnvelope != null) { 112 - gatingReleaseEnvelope.reset(); 113 - gatingAttackEnvelope.reset(); 114 - int counter = 0; 115 - boolean muted = true; 116 - for (int position = 0; position < nS; position++) { 117 - int onStep = gatingReleaseEnvelope.step(nS); 118 - int offStep = gatingAttackEnvelope.step(nS); 119 - int threshold; 120 - if (muted) 121 - threshold = gatingReleaseEnvelope.smart + ((gatingReleaseEnvelope.end - gatingReleaseEnvelope.smart) * onStep >> 8); 122 - else 123 - threshold = gatingReleaseEnvelope.smart + ((gatingReleaseEnvelope.end - gatingReleaseEnvelope.smart) * offStep >> 8); 124 - if ((counter += 256) >= threshold) { 125 - counter = 0; 126 - muted = !muted; 127 - } 128 - if (muted) 129 - buffer[position] = 0; 130 - } 131 - 132 - } 133 - if (delayTime > 0 && delayFeedback > 0) { 134 - int delay = (int) (delayTime * fS); 135 - for (int position = delay; position < nS; position++) 136 - buffer[position] += (buffer[position - delay] * delayFeedback) / 100; 137 - 138 - } 139 - if (filter.numPairs[0] > 0 || filter.numPairs[1] > 0) { 140 - filterEnvelope.reset(); 141 - int t = filterEnvelope.step(nS + 1); 142 - int M = filter.compute(0, t / 65536F); 143 - int N = filter.compute(1, t / 65536F); 144 - if (nS >= M + N) { 145 - int n = 0; 146 - int delay = N; 147 - if (delay > nS - M) 148 - delay = nS - M; 149 - for (; n < delay; n++) { 150 - int y = (int) ((long) buffer[n + M] * (long) SoundFilter.invUnity >> 16); 151 - for (int position = 0; position < M; position++) 152 - y += (int) ((long) buffer[(n + M) - 1 - position] 153 - * (long) SoundFilter.coefficient[0][position] >> 16); 154 - 155 - for (int position = 0; position < n; position++) 156 - y -= (int) ((long) buffer[n - 1 - position] * (long) SoundFilter.coefficient[1][position] >> 16); 157 - 158 - buffer[n] = y; 159 - t = filterEnvelope.step(nS + 1); 160 - } 161 - 162 - char offset = '\200'; 163 - delay = offset; 164 - do { 165 - if (delay > nS - M) 166 - delay = nS - M; 167 - for (; n < delay; n++) { 168 - int y = (int) ((long) buffer[n + M] * (long) SoundFilter.invUnity >> 16); 169 - for (int position = 0; position < M; position++) 170 - y += (int) ((long) buffer[(n + M) - 1 - position] 171 - * (long) SoundFilter.coefficient[0][position] >> 16); 172 - 173 - for (int position = 0; position < N; position++) 174 - y -= (int) ((long) buffer[n - 1 - position] 175 - * (long) SoundFilter.coefficient[1][position] >> 16); 176 - 177 - buffer[n] = y; 178 - t = filterEnvelope.step(nS + 1); 179 - } 180 - 181 - if (n >= nS - M) 182 - break; 183 - M = filter.compute(0, t / 65536F); 184 - N = filter.compute(1, t / 65536F); 185 - delay += offset; 186 - } while (true); 187 - for (; n < nS; n++) { 188 - int y = 0; 189 - for (int position = (n + M) - nS; position < M; position++) 190 - y += (int) ((long) buffer[(n + M) - 1 - position] 191 - * (long) SoundFilter.coefficient[0][position] >> 16); 192 - 193 - for (int position = 0; position < N; position++) 194 - y -= (int) ((long) buffer[n - 1 - position] 195 - * (long) SoundFilter.coefficient[1][position] >> 16); 196 - 197 - buffer[n] = y; 198 - filterEnvelope.step(nS + 1); 199 - } 200 - 201 - } 202 - } 203 - for (int position = 0; position < nS; position++) { 204 - if (SoundTrackInstrument.buffer[position] < -32768) 205 - SoundTrackInstrument.buffer[position] = -32768; 206 - if (SoundTrackInstrument.buffer[position] > 32767) 207 - SoundTrackInstrument.buffer[position] = 32767; 208 - } 209 - 210 - return SoundTrackInstrument.buffer; 211 - } 212 - 213 - public int evaluateWave(int amplitude, int phase, int table) { 214 - if (table == 1) // square wave 215 - if ((phase & 0x7fff) < 16384) 216 - return amplitude; 217 - else 218 - return -amplitude; 219 - if (table == 2) // sine wave 220 - return sine[phase & 0x7fff] * amplitude >> 14; 221 - if (table == 3) // sawtooth wave 222 - return ((phase & 0x7fff) * amplitude >> 14) - amplitude; 223 - if (table == 4) // random noise 224 - return noise[phase / 2607 & 0x7fff] * amplitude; 225 - else 226 - return 0; 227 - } 228 - 229 - public void decode(Buffer buffer) { 230 - pitchEnvelope = new SoundTrackEnvelope(); 231 - pitchEnvelope.decode(buffer); 232 - volumeEnvelope = new SoundTrackEnvelope(); 233 - volumeEnvelope.decode(buffer); 234 - int option = buffer.getUnsignedByte(); 235 - if (option != 0) { 236 - buffer.currentPosition--; 237 - pitchModEnvelope = new SoundTrackEnvelope(); 238 - pitchModEnvelope.decode(buffer); 239 - pitchModAmpEnvelope = new SoundTrackEnvelope(); 240 - pitchModAmpEnvelope.decode(buffer); 241 - } 242 - option = buffer.getUnsignedByte(); 243 - if (option != 0) { 244 - buffer.currentPosition--; 245 - volumeModEnvelope = new SoundTrackEnvelope(); 246 - volumeModEnvelope.decode(buffer); 247 - volumeModAmpEnvelope = new SoundTrackEnvelope(); 248 - volumeModAmpEnvelope.decode(buffer); 249 - } 250 - option = buffer.getUnsignedByte(); 251 - if (option != 0) { 252 - buffer.currentPosition--; 253 - gatingReleaseEnvelope = new SoundTrackEnvelope(); 254 - gatingReleaseEnvelope.decode(buffer); 255 - gatingAttackEnvelope = new SoundTrackEnvelope(); 256 - gatingAttackEnvelope.decode(buffer); 257 - } 258 - for (int oscillId = 0; oscillId < 10; oscillId++) { 259 - int volume = buffer.getSmart(); 260 - if (volume == 0) 261 - break; 262 - oscillVolume[oscillId] = volume; 263 - oscillPitchDelta[oscillId] = buffer.getSignedSmart(); 264 - oscillDelay[oscillId] = buffer.getSmart(); 265 - } 266 - 267 - delayTime = buffer.getSmart(); 268 - delayFeedback = buffer.getSmart(); 269 - soundMillis = buffer.getUnsignedShortBE(); 270 - pauseMillis = buffer.getUnsignedShortBE(); 271 - filter = new SoundFilter(); 272 - filterEnvelope = new SoundTrackEnvelope(); 273 - filter.decode(filterEnvelope, buffer); 274 - } 275 - }
+266
src/main/java/com/jagex/runescape/sound/SoundTrackInstrument.kt
··· 1 + package com.jagex.runescape.sound 2 + 3 + import com.jagex.runescape.net.Buffer 4 + import kotlin.math.pow 5 + import kotlin.math.sin 6 + 7 + /** 8 + * A single instrument within a [SoundTrack]. Synthesizes audio by combining 9 + * waveform generators (sine, square, sawtooth, noise) with pitch/volume 10 + * modulation envelopes, oscillators, gating, delay feedback, and IIR filtering. 11 + * 12 + * The [synthesize] method generates samples into a shared static [buffer] to 13 + * avoid allocation during audio playback. 14 + */ 15 + class SoundTrackInstrument { 16 + @JvmField var pitchEnvelope: SoundTrackEnvelope? = null 17 + @JvmField var volumeEnvelope: SoundTrackEnvelope? = null 18 + @JvmField var pitchModEnvelope: SoundTrackEnvelope? = null 19 + @JvmField var pitchModAmpEnvelope: SoundTrackEnvelope? = null 20 + @JvmField var volumeModEnvelope: SoundTrackEnvelope? = null 21 + @JvmField var volumeModAmpEnvelope: SoundTrackEnvelope? = null 22 + @JvmField var gatingReleaseEnvelope: SoundTrackEnvelope? = null 23 + @JvmField var gatingAttackEnvelope: SoundTrackEnvelope? = null 24 + @JvmField var oscillVolume = IntArray(5) 25 + @JvmField var oscillPitchDelta = IntArray(5) 26 + @JvmField var oscillDelay = IntArray(5) 27 + @JvmField var delayTime: Int = 0 28 + @JvmField var delayFeedback: Int = 100 29 + @JvmField var filter: SoundFilter? = null 30 + @JvmField var filterEnvelope: SoundTrackEnvelope? = null 31 + @JvmField var soundMillis: Int = 500 32 + @JvmField var pauseMillis: Int = 0 33 + 34 + fun synthesize(nS: Int, dt: Int): IntArray { 35 + for (position in 0 until nS) buffer!![position] = 0 36 + if (dt < 10) return buffer!! 37 + val fS = nS / (dt + 0.0) 38 + pitchEnvelope!!.reset() 39 + volumeEnvelope!!.reset() 40 + var pitchModStep = 0 41 + var pitchModBaseStep = 0 42 + var pitchModPhase = 0 43 + if (pitchModEnvelope != null) { 44 + pitchModEnvelope!!.reset() 45 + pitchModAmpEnvelope!!.reset() 46 + pitchModStep = (((pitchModEnvelope!!.end - pitchModEnvelope!!.smart) * 32.768) / fS).toInt() 47 + pitchModBaseStep = ((pitchModEnvelope!!.smart * 32.768) / fS).toInt() 48 + } 49 + var volumeModStep = 0 50 + var volumeModBaseStep = 0 51 + var volumeModPhase = 0 52 + if (volumeModEnvelope != null) { 53 + volumeModEnvelope!!.reset() 54 + volumeModAmpEnvelope!!.reset() 55 + volumeModStep = (((volumeModEnvelope!!.end - volumeModEnvelope!!.smart) * 32.768) / fS).toInt() 56 + volumeModBaseStep = ((volumeModEnvelope!!.smart * 32.768) / fS).toInt() 57 + } 58 + for (id in 0 until 5) { 59 + if (oscillVolume[id] != 0) { 60 + phases[id] = 0 61 + delays[id] = (oscillDelay[id] * fS).toInt() 62 + volumeStep[id] = (oscillVolume[id] shl 14) / 100 63 + pitchStep[id] = (((pitchEnvelope!!.end - pitchEnvelope!!.smart) * 32.768 * 64 + 1.0057929410678534.pow(oscillPitchDelta[id].toDouble())) / fS).toInt() 65 + pitchBaseStep[id] = ((pitchEnvelope!!.smart * 32.768) / fS).toInt() 66 + } 67 + } 68 + 69 + for (offset in 0 until nS) { 70 + var pitchChange = pitchEnvelope!!.step(nS) 71 + var volumeChange = volumeEnvelope!!.step(nS) 72 + if (pitchModEnvelope != null) { 73 + val mod = pitchModEnvelope!!.step(nS) 74 + val modAmp = pitchModAmpEnvelope!!.step(nS) 75 + pitchChange += evaluateWave(modAmp, pitchModPhase, pitchModEnvelope!!.form) shr 1 76 + pitchModPhase += (mod * pitchModStep shr 16) + pitchModBaseStep 77 + } 78 + if (volumeModEnvelope != null) { 79 + val mod = volumeModEnvelope!!.step(nS) 80 + val modAmp = volumeModAmpEnvelope!!.step(nS) 81 + volumeChange = volumeChange * ((evaluateWave(modAmp, volumeModPhase, volumeModEnvelope!!.form) shr 1) + 32768) shr 15 82 + volumeModPhase += (mod * volumeModStep shr 16) + volumeModBaseStep 83 + } 84 + for (id in 0 until 5) { 85 + if (oscillVolume[id] != 0) { 86 + val position = offset + delays[id] 87 + if (position < nS) { 88 + buffer!![position] += evaluateWave( 89 + volumeChange * volumeStep[id] shr 15, phases[id], pitchEnvelope!!.form 90 + ) 91 + phases[id] += (pitchChange * pitchStep[id] shr 16) + pitchBaseStep[id] 92 + } 93 + } 94 + } 95 + } 96 + 97 + if (gatingReleaseEnvelope != null) { 98 + gatingReleaseEnvelope!!.reset() 99 + gatingAttackEnvelope!!.reset() 100 + var counter = 0 101 + var muted = true 102 + for (position in 0 until nS) { 103 + val onStep = gatingReleaseEnvelope!!.step(nS) 104 + val offStep = gatingAttackEnvelope!!.step(nS) 105 + val threshold = if (muted) { 106 + gatingReleaseEnvelope!!.smart + 107 + ((gatingReleaseEnvelope!!.end - gatingReleaseEnvelope!!.smart) * onStep shr 8) 108 + } else { 109 + gatingReleaseEnvelope!!.smart + 110 + ((gatingReleaseEnvelope!!.end - gatingReleaseEnvelope!!.smart) * offStep shr 8) 111 + } 112 + counter += 256 113 + if (counter >= threshold) { 114 + counter = 0 115 + muted = !muted 116 + } 117 + if (muted) buffer!![position] = 0 118 + } 119 + } 120 + 121 + if (delayTime > 0 && delayFeedback > 0) { 122 + val delay = (delayTime * fS).toInt() 123 + for (position in delay until nS) 124 + buffer!![position] += (buffer!![position - delay] * delayFeedback) / 100 125 + } 126 + 127 + if (filter!!.numPairs[0] > 0 || filter!!.numPairs[1] > 0) { 128 + filterEnvelope!!.reset() 129 + var t = filterEnvelope!!.step(nS + 1) 130 + var M = filter!!.compute(0, t / 65536f) 131 + var N = filter!!.compute(1, t / 65536f) 132 + if (nS >= M + N) { 133 + var n = 0 134 + var delayBound = N 135 + if (delayBound > nS - M) delayBound = nS - M 136 + while (n < delayBound) { 137 + var y = ((buffer!![n + M].toLong() * SoundFilter.invUnity.toLong()) shr 16).toInt() 138 + for (p in 0 until M) 139 + y += ((buffer!![(n + M) - 1 - p].toLong() * SoundFilter.coefficient[0][p].toLong()) shr 16).toInt() 140 + for (p in 0 until n) 141 + y -= ((buffer!![n - 1 - p].toLong() * SoundFilter.coefficient[1][p].toLong()) shr 16).toInt() 142 + buffer!![n] = y 143 + t = filterEnvelope!!.step(nS + 1) 144 + n++ 145 + } 146 + 147 + val chunkSize = 128 148 + delayBound = chunkSize 149 + do { 150 + if (delayBound > nS - M) delayBound = nS - M 151 + while (n < delayBound) { 152 + var y = ((buffer!![n + M].toLong() * SoundFilter.invUnity.toLong()) shr 16).toInt() 153 + for (p in 0 until M) 154 + y += ((buffer!![(n + M) - 1 - p].toLong() * SoundFilter.coefficient[0][p].toLong()) shr 16).toInt() 155 + for (p in 0 until N) 156 + y -= ((buffer!![n - 1 - p].toLong() * SoundFilter.coefficient[1][p].toLong()) shr 16).toInt() 157 + buffer!![n] = y 158 + t = filterEnvelope!!.step(nS + 1) 159 + n++ 160 + } 161 + if (n >= nS - M) break 162 + M = filter!!.compute(0, t / 65536f) 163 + N = filter!!.compute(1, t / 65536f) 164 + delayBound += chunkSize 165 + } while (true) 166 + 167 + while (n < nS) { 168 + var y = 0 169 + for (p in (n + M) - nS until M) 170 + y += ((buffer!![(n + M) - 1 - p].toLong() * SoundFilter.coefficient[0][p].toLong()) shr 16).toInt() 171 + for (p in 0 until N) 172 + y -= ((buffer!![n - 1 - p].toLong() * SoundFilter.coefficient[1][p].toLong()) shr 16).toInt() 173 + buffer!![n] = y 174 + filterEnvelope!!.step(nS + 1) 175 + n++ 176 + } 177 + } 178 + } 179 + 180 + for (position in 0 until nS) { 181 + if (buffer!![position] < -32768) buffer!![position] = -32768 182 + if (buffer!![position] > 32767) buffer!![position] = 32767 183 + } 184 + 185 + return buffer!! 186 + } 187 + 188 + fun evaluateWave(amplitude: Int, phase: Int, table: Int): Int { 189 + return when (table) { 190 + 1 -> if ((phase and 0x7fff) < 16384) amplitude else -amplitude // square 191 + 2 -> sine!![phase and 0x7fff] * amplitude shr 14 // sine 192 + 3 -> ((phase and 0x7fff) * amplitude shr 14) - amplitude // sawtooth 193 + 4 -> noise!![phase / 2607 and 0x7fff] * amplitude // noise 194 + else -> 0 195 + } 196 + } 197 + 198 + fun decode(buffer: Buffer) { 199 + pitchEnvelope = SoundTrackEnvelope() 200 + pitchEnvelope!!.decode(buffer) 201 + volumeEnvelope = SoundTrackEnvelope() 202 + volumeEnvelope!!.decode(buffer) 203 + var option = buffer.getUnsignedByte() 204 + if (option != 0) { 205 + buffer.currentPosition-- 206 + pitchModEnvelope = SoundTrackEnvelope() 207 + pitchModEnvelope!!.decode(buffer) 208 + pitchModAmpEnvelope = SoundTrackEnvelope() 209 + pitchModAmpEnvelope!!.decode(buffer) 210 + } 211 + option = buffer.getUnsignedByte() 212 + if (option != 0) { 213 + buffer.currentPosition-- 214 + volumeModEnvelope = SoundTrackEnvelope() 215 + volumeModEnvelope!!.decode(buffer) 216 + volumeModAmpEnvelope = SoundTrackEnvelope() 217 + volumeModAmpEnvelope!!.decode(buffer) 218 + } 219 + option = buffer.getUnsignedByte() 220 + if (option != 0) { 221 + buffer.currentPosition-- 222 + gatingReleaseEnvelope = SoundTrackEnvelope() 223 + gatingReleaseEnvelope!!.decode(buffer) 224 + gatingAttackEnvelope = SoundTrackEnvelope() 225 + gatingAttackEnvelope!!.decode(buffer) 226 + } 227 + for (oscillId in 0 until 10) { 228 + val vol = buffer.getSmart() 229 + if (vol == 0) break 230 + oscillVolume[oscillId] = vol 231 + oscillPitchDelta[oscillId] = buffer.getSignedSmart() 232 + oscillDelay[oscillId] = buffer.getSmart() 233 + } 234 + delayTime = buffer.getSmart() 235 + delayFeedback = buffer.getSmart() 236 + soundMillis = buffer.getUnsignedShortBE() 237 + pauseMillis = buffer.getUnsignedShortBE() 238 + filter = SoundFilter() 239 + filterEnvelope = SoundTrackEnvelope() 240 + filter!!.decode(filterEnvelope!!, buffer) 241 + } 242 + 243 + companion object { 244 + @JvmField var buffer: IntArray? = null 245 + @JvmField var noise: IntArray? = null 246 + @JvmField var sine: IntArray? = null 247 + @JvmField var phases = IntArray(5) 248 + @JvmField var delays = IntArray(5) 249 + @JvmField var volumeStep = IntArray(5) 250 + @JvmField var pitchStep = IntArray(5) 251 + @JvmField var pitchBaseStep = IntArray(5) 252 + 253 + @JvmStatic 254 + fun decode() { 255 + noise = IntArray(32768) 256 + for (i in 0 until 32768) 257 + noise!![i] = if (Math.random() > 0.5) 1 else -1 258 + 259 + sine = IntArray(32768) 260 + for (i in 0 until 32768) 261 + sine!![i] = (sin(i / 5215.1903) * 16384.0).toInt() 262 + 263 + buffer = IntArray(0x35d54) 264 + } 265 + } 266 + }
-116
src/main/java/com/jagex/runescape/util/ChatEncoder.java
··· 1 - package com.jagex.runescape.util; 2 - 3 - import com.jagex.runescape.net.Buffer; 4 - 5 - public class ChatEncoder { 6 - 7 - private static char[] message = new char[100]; 8 - private static Buffer messageBuffer = new Buffer(new byte[100]); 9 - private static char VALID_CHARACTERS[] = {' ', 'e', 't', 'a', 'o', 'i', 'h', 'n', 's', 'r', 'd', 'l', 'u', 'm', 'w', 10 - 'c', 'y', 'f', 'g', 'p', 'b', 'v', 'k', 'x', 'j', 'q', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', 11 - '9', ' ', '!', '?', '.', ',', ':', ';', '(', ')', '-', '&', '*', '\\', '\'', '@', '#', '+', '=', '\243', 12 - '$', '%', '"', '[', ']'}; 13 - 14 - public static String get(int length, Buffer buffer) { 15 - int count = 0; 16 - int validCharacterIndex = -1; 17 - 18 - for (int lengthCounter = 0; lengthCounter < length; lengthCounter++) { 19 - int character = buffer.getUnsignedByte(); 20 - int characterBit = character >> 4 & 0xf; 21 - 22 - if (validCharacterIndex == -1) { 23 - if (characterBit < 13) 24 - message[count++] = VALID_CHARACTERS[characterBit]; 25 - else 26 - validCharacterIndex = characterBit; 27 - } else { 28 - message[count++] = VALID_CHARACTERS[((validCharacterIndex << 4) + characterBit) - 195]; 29 - validCharacterIndex = -1; 30 - } 31 - 32 - characterBit = character & 0xf; 33 - 34 - if (validCharacterIndex == -1) { 35 - if (characterBit < 13) 36 - message[count++] = VALID_CHARACTERS[characterBit]; 37 - else 38 - validCharacterIndex = characterBit; 39 - } else { 40 - message[count++] = VALID_CHARACTERS[((validCharacterIndex << 4) + characterBit) - 195]; 41 - validCharacterIndex = -1; 42 - } 43 - } 44 - 45 - boolean isSymbol = true; 46 - 47 - for (int messageIndex = 0; messageIndex < count; messageIndex++) { 48 - char c = message[messageIndex]; 49 - 50 - if (isSymbol && c >= 'a' && c <= 'z') { 51 - message[messageIndex] += '\uFFE0'; 52 - isSymbol = false; 53 - } 54 - 55 - if (c == '.' || c == '!' || c == '?') 56 - isSymbol = true; 57 - } 58 - 59 - return new String(message, 0, count); 60 - } 61 - 62 - public static void put(String chatMessage, Buffer buffer) { 63 - if (chatMessage.length() > 80) 64 - chatMessage = chatMessage.substring(0, 80); 65 - 66 - chatMessage = chatMessage.toLowerCase(); 67 - 68 - int chatMessageCharacter = -1; 69 - 70 - for (int index = 0; index < chatMessage.length(); index++) { 71 - char character = chatMessage.charAt(index); 72 - int validCharacterIndex = 0; 73 - 74 - for (int validIndex = 0; validIndex < VALID_CHARACTERS.length; validIndex++) { 75 - if (character != VALID_CHARACTERS[validIndex]) 76 - continue; 77 - 78 - validCharacterIndex = validIndex; 79 - break; 80 - } 81 - 82 - if (validCharacterIndex > 12) 83 - validCharacterIndex += 195; 84 - 85 - if (chatMessageCharacter == -1) { 86 - if (validCharacterIndex < 13) 87 - chatMessageCharacter = validCharacterIndex; 88 - else 89 - buffer.putByte(validCharacterIndex); 90 - } else if (validCharacterIndex < 13) { 91 - buffer.putByte((chatMessageCharacter << 4) + validCharacterIndex); 92 - chatMessageCharacter = -1; 93 - } else { 94 - buffer.putByte((chatMessageCharacter << 4) + (validCharacterIndex >> 4)); 95 - chatMessageCharacter = validCharacterIndex & 0xf; 96 - } 97 - } 98 - 99 - if (chatMessageCharacter != -1) 100 - buffer.putByte(chatMessageCharacter << 4); 101 - } 102 - 103 - public static String formatChatMessage(String chatMessage) { 104 - messageBuffer.currentPosition = 0; 105 - 106 - put(chatMessage, messageBuffer); 107 - 108 - int offset = messageBuffer.currentPosition; 109 - messageBuffer.currentPosition = 0; 110 - 111 - return get(offset, messageBuffer); 112 - } 113 - 114 - 115 - 116 - }
+114
src/main/java/com/jagex/runescape/util/ChatEncoder.kt
··· 1 + package com.jagex.runescape.util 2 + 3 + import com.jagex.runescape.net.Buffer 4 + 5 + /** 6 + * RS377 nibble-based chat text codec. NOT Huffman — each character maps to an 7 + * index in [VALID_CHARACTERS]. Indices 0-12 pack as a single 4-bit nibble; 8 + * indices 13+ encode as `index + 195` split across two nibbles. This is used 9 + * for private messages; public chat uses a separate Huffman codec. 10 + */ 11 + object ChatEncoder { 12 + private val message = CharArray(100) 13 + private val messageBuffer = Buffer(ByteArray(100)) 14 + 15 + @JvmField 16 + val VALID_CHARACTERS = charArrayOf( 17 + ' ', 'e', 't', 'a', 'o', 'i', 'h', 'n', 's', 'r', 'd', 'l', 'u', 'm', 'w', 18 + 'c', 'y', 'f', 'g', 'p', 'b', 'v', 'k', 'x', 'j', 'q', 'z', '0', '1', '2', 19 + '3', '4', '5', '6', '7', '8', '9', ' ', '!', '?', '.', ',', ':', ';', '(', 20 + ')', '-', '&', '*', '\\', '\'', '@', '#', '+', '=', '\u00A3', '$', '%', '"', 21 + '[', ']' 22 + ) 23 + 24 + @JvmStatic 25 + fun get(length: Int, buffer: Buffer): String { 26 + var count = 0 27 + var validCharacterIndex = -1 28 + 29 + for (i in 0 until length) { 30 + val character = buffer.getUnsignedByte() 31 + val highNibble = character ushr 4 and 0xf 32 + 33 + if (validCharacterIndex == -1) { 34 + if (highNibble < 13) 35 + message[count++] = VALID_CHARACTERS[highNibble] 36 + else 37 + validCharacterIndex = highNibble 38 + } else { 39 + message[count++] = VALID_CHARACTERS[((validCharacterIndex shl 4) + highNibble) - 195] 40 + validCharacterIndex = -1 41 + } 42 + 43 + val lowNibble = character and 0xf 44 + 45 + if (validCharacterIndex == -1) { 46 + if (lowNibble < 13) 47 + message[count++] = VALID_CHARACTERS[lowNibble] 48 + else 49 + validCharacterIndex = lowNibble 50 + } else { 51 + message[count++] = VALID_CHARACTERS[((validCharacterIndex shl 4) + lowNibble) - 195] 52 + validCharacterIndex = -1 53 + } 54 + } 55 + 56 + // Auto-capitalize after sentence terminators 57 + var isSymbol = true 58 + for (i in 0 until count) { 59 + val c = message[i] 60 + if (isSymbol && c in 'a'..'z') { 61 + message[i] = (c.code + '\uFFE0'.code).toChar() 62 + isSymbol = false 63 + } 64 + if (c == '.' || c == '!' || c == '?') isSymbol = true 65 + } 66 + 67 + return String(message, 0, count) 68 + } 69 + 70 + @JvmStatic 71 + fun put(chatMessage: String, buffer: Buffer) { 72 + var msg = chatMessage 73 + if (msg.length > 80) msg = msg.substring(0, 80) 74 + msg = msg.lowercase() 75 + 76 + var pending = -1 77 + 78 + for (ch in msg) { 79 + var idx = 0 80 + for (v in VALID_CHARACTERS.indices) { 81 + if (ch == VALID_CHARACTERS[v]) { 82 + idx = v 83 + break 84 + } 85 + } 86 + 87 + if (idx > 12) idx += 195 88 + 89 + if (pending == -1) { 90 + if (idx < 13) 91 + pending = idx 92 + else 93 + buffer.putByte(idx) 94 + } else if (idx < 13) { 95 + buffer.putByte((pending shl 4) + idx) 96 + pending = -1 97 + } else { 98 + buffer.putByte((pending shl 4) + (idx ushr 4)) 99 + pending = idx and 0xf 100 + } 101 + } 102 + 103 + if (pending != -1) buffer.putByte(pending shl 4) 104 + } 105 + 106 + @JvmStatic 107 + fun formatChatMessage(chatMessage: String): String { 108 + messageBuffer.currentPosition = 0 109 + put(chatMessage, messageBuffer) 110 + val offset = messageBuffer.currentPosition 111 + messageBuffer.currentPosition = 0 112 + return get(offset, messageBuffer) 113 + } 114 + }
-97
src/main/java/com/jagex/runescape/util/LinkedList.java
··· 1 - package com.jagex.runescape.util; 2 - 3 - import com.jagex.runescape.collection.Node; 4 - 5 - public class LinkedList { 6 - 7 - public Node head = new Node(); 8 - public Node current; 9 - 10 - public LinkedList() { 11 - head.next = head; 12 - head.previous = head; 13 - } 14 - 15 - public void pushBack(Node node) { 16 - if (node.previous != null) 17 - node.remove(); 18 - node.previous = head.previous; 19 - node.next = head; 20 - node.previous.next = node; 21 - node.next.previous = node; 22 - } 23 - 24 - public void push(Node node) { 25 - if (node.previous != null) 26 - node.remove(); 27 - node.previous = head; 28 - node.next = head.next; 29 - node.previous.next = node; 30 - node.next.previous = node; 31 - } 32 - 33 - public Node pop() { 34 - Node node = head.next; 35 - if (node == head) { 36 - return null; 37 - } else { 38 - node.remove(); 39 - return node; 40 - } 41 - } 42 - 43 - public Node first() { 44 - Node node = head.next; 45 - if (node == head) { 46 - current = null; 47 - return null; 48 - } else { 49 - current = node.next; 50 - return node; 51 - } 52 - } 53 - 54 - public Node last() { 55 - Node node = head.previous; 56 - if (node == head) { 57 - current = null; 58 - return null; 59 - } else { 60 - current = node.previous; 61 - return node; 62 - } 63 - } 64 - 65 - public Node next() { 66 - Node node = current; 67 - if (node == head) { 68 - current = null; 69 - return null; 70 - } 71 - current = node.next; 72 - return node; 73 - } 74 - 75 - public Node previous() { 76 - Node node = current; 77 - if (node == head) { 78 - current = null; 79 - return null; 80 - } else { 81 - current = node.previous; 82 - return node; 83 - } 84 - } 85 - 86 - public void clear() { 87 - if (head.next == head) 88 - return; 89 - do { 90 - Node node = head.next; 91 - if (node == head) 92 - return; 93 - node.remove(); 94 - } while (true); 95 - } 96 - 97 - }
+98
src/main/java/com/jagex/runescape/util/LinkedList.kt
··· 1 + package com.jagex.runescape.util 2 + 3 + import com.jagex.runescape.collection.Node 4 + 5 + /** 6 + * Intrusive circular doubly-linked list of [Node]s. Unlike [com.jagex.runescape.collection.Queue] 7 + * (which uses [CacheableNode]'s separate link layer), this operates on the base [Node] 8 + * pointers directly. Used for general-purpose ordered collections — on-demand 9 + * request queues, completed file lists, etc. 10 + */ 11 + class LinkedList { 12 + @JvmField val head: Node = Node() 13 + @JvmField var current: Node? = null 14 + 15 + init { 16 + head.next = head 17 + head.previous = head 18 + } 19 + 20 + fun pushBack(node: Node) { 21 + if (node.previous != null) node.remove() 22 + node.previous = head.previous 23 + node.next = head 24 + node.previous!!.next = node 25 + node.next!!.previous = node 26 + } 27 + 28 + fun push(node: Node) { 29 + if (node.previous != null) node.remove() 30 + node.previous = head 31 + node.next = head.next 32 + node.previous!!.next = node 33 + node.next!!.previous = node 34 + } 35 + 36 + fun pop(): Node? { 37 + val node = head.next 38 + return if (node === head) { 39 + null 40 + } else { 41 + node!!.remove() 42 + node 43 + } 44 + } 45 + 46 + fun first(): Node? { 47 + val node = head.next 48 + return if (node === head) { 49 + current = null 50 + null 51 + } else { 52 + current = node!!.next 53 + node 54 + } 55 + } 56 + 57 + fun last(): Node? { 58 + val node = head.previous 59 + return if (node === head) { 60 + current = null 61 + null 62 + } else { 63 + current = node!!.previous 64 + node 65 + } 66 + } 67 + 68 + fun next(): Node? { 69 + val node = current 70 + return if (node === head) { 71 + current = null 72 + null 73 + } else { 74 + current = node!!.next 75 + node 76 + } 77 + } 78 + 79 + fun previous(): Node? { 80 + val node = current 81 + return if (node === head) { 82 + current = null 83 + null 84 + } else { 85 + current = node!!.previous 86 + node 87 + } 88 + } 89 + 90 + fun clear() { 91 + if (head.next === head) return 92 + while (true) { 93 + val node = head.next 94 + if (node === head) return 95 + node!!.remove() 96 + } 97 + } 98 + }
-34
src/main/java/com/jagex/runescape/util/MouseCapturer.java
··· 1 - package com.jagex.runescape.util; 2 - 3 - import com.jagex.runescape.Game; 4 - 5 - public class MouseCapturer implements Runnable { 6 - public Game _client; 7 - public boolean capturing = true; 8 - public int coordsY[] = new int[500]; 9 - public Object objectLock = new Object(); 10 - public Game client; 11 - public int coord; 12 - public int coordsX[] = new int[500]; 13 - 14 - 15 - public void run() { 16 - while (capturing) { 17 - synchronized (objectLock) { 18 - if (coord < 500) { 19 - coordsX[coord] = client.mouseX; 20 - coordsY[coord] = client.mouseY; 21 - coord++; 22 - } 23 - } 24 - try { 25 - Thread.sleep(50L); 26 - } catch (Exception _ex) { 27 - } 28 - } 29 - } 30 - public MouseCapturer(Game _client) { 31 - client = _client; 32 - } 33 - 34 - }
+32
src/main/java/com/jagex/runescape/util/MouseCapturer.kt
··· 1 + package com.jagex.runescape.util 2 + 3 + import com.jagex.runescape.Game 4 + 5 + /** 6 + * Background thread that samples mouse coordinates every 50ms for input 7 + * smoothing. The client reads accumulated samples from [coordsX]/[coordsY] 8 + * each frame to interpolate mouse movement for camera rotation and tooltips. 9 + */ 10 + class MouseCapturer(@JvmField val client: Game) : Runnable { 11 + @JvmField var capturing = true 12 + @JvmField val coordsY = IntArray(500) 13 + @JvmField val objectLock = Any() 14 + @JvmField var coord = 0 15 + @JvmField val coordsX = IntArray(500) 16 + 17 + override fun run() { 18 + while (capturing) { 19 + synchronized(objectLock) { 20 + if (coord < 500) { 21 + coordsX[coord] = client.mouseX 22 + coordsY[coord] = client.mouseY 23 + coord++ 24 + } 25 + } 26 + try { 27 + Thread.sleep(50L) 28 + } catch (_: Exception) { 29 + } 30 + } 31 + } 32 + }
-15
src/main/java/com/jagex/runescape/util/PacketConstants.java
··· 1 - package com.jagex.runescape.util; 2 - 3 - public class PacketConstants { 4 - 5 - 6 - public static final int[] PACKET_SIZES = { 0, 0, 4, 6, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 6, 0, 0, 7 - 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 0, 6, 2, 0, 0, -2, 0, 0, 0, 0, 0, 8 - 6, 0, 0, 0, -1, 0, 0, 0, 4, 0, 0, 0, -2, 0, 0, 0, 2, 23, 0, 9, 0, 0, 0, 3, 0, 0, 0, 0, 0, 2, 0, -2, 0, 0, 9 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 5, 0, 2, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 3, 0, 4, 10 - 0, 0, 0, 0, 0, -2, -1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 1, -1, 2, 2, 0, 0, 4, 0, 11 - 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 15, 3, -2, 0, 0, 8, 6, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 12 - 6, 4, 3, 0, 14, 0, 0, -2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 4, 2, 2, 0, 4, 0, 0, 0, -2, 0, 0, 0, 0, 0, -2, 13 - 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 5, 0, 1, 1, 4, 0, 2, 0 }; 14 - 15 - }
+18
src/main/java/com/jagex/runescape/util/PacketConstants.kt
··· 1 + package com.jagex.runescape.util 2 + 3 + /** 4 + * Incoming packet size table. Index = opcode, value = expected payload size. 5 + * -1 = size is in the first byte (variable byte), -2 = size is in the first 6 + * two bytes (variable short), 0 = no payload. 7 + */ 8 + object PacketConstants { 9 + @JvmField 10 + val PACKET_SIZES = intArrayOf(0, 0, 4, 6, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 6, 0, 0, 11 + 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 0, 6, 2, 0, 0, -2, 0, 0, 0, 0, 0, 12 + 6, 0, 0, 0, -1, 0, 0, 0, 4, 0, 0, 0, -2, 0, 0, 0, 2, 23, 0, 9, 0, 0, 0, 3, 0, 0, 0, 0, 0, 2, 0, -2, 0, 0, 13 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 5, 0, 2, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 7, 0, 0, 0, 1, 3, 0, 4, 14 + 0, 0, 0, 0, 0, -2, -1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 1, -1, 2, 2, 0, 0, 4, 0, 15 + 0, 0, 6, 6, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 15, 3, -2, 0, 0, 8, 6, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 16 + 6, 4, 3, 0, 14, 0, 0, -2, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 4, 2, 2, 0, 4, 0, 0, 0, -2, 0, 0, 0, 0, 0, -2, 17 + 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 5, 0, 1, 1, 4, 0, 2, 0) 18 + }
-466
src/main/java/com/jagex/runescape/util/SignLink.java
··· 1 - package com.jagex.runescape.util; 2 - 3 - import com.jagex.runescape.config.Configuration; 4 - 5 - import javax.sound.midi.*; 6 - import javax.sound.sampled.*; 7 - import java.io.*; 8 - import java.net.InetAddress; 9 - import java.net.Socket; 10 - import java.net.URL; 11 - 12 - import static com.jagex.runescape.config.Configuration.CACHE_NAME; 13 - 14 - public final class SignLink implements Runnable { 15 - 16 - public static final int CLIENT_REVISION = 377; 17 - 18 - public static int uid; 19 - public static int storeId = 32; 20 - public static RandomAccessFile cacheData = null; 21 - public static final RandomAccessFile[] cacheIndex = new RandomAccessFile[5]; 22 - private static boolean active; 23 - private static int threadLiveId; 24 - private static InetAddress inetAddress; 25 - private static int socketRequest; 26 - private static Socket socket = null; 27 - private static int threadRequestPriority = 1; 28 - private static Runnable threadRequest = null; 29 - private static String dnsRequest = null; 30 - public static String dns = null; 31 - private static String urlRequest = null; 32 - private static DataInputStream urlStream = null; 33 - private static int saveLength; 34 - private static String saveRequest = null; 35 - private static byte[] saveBuffer = null; 36 - private static boolean play; 37 - private static int midiPosition; 38 - public static String midi = null; 39 - public static int midiVolume; 40 - public static int fadeMidi; 41 - private static boolean midiPlay; 42 - private static int wavePosition; 43 - public static int waveVolume; 44 - public static boolean reportError = true; 45 - public static String errorName = ""; 46 - public static Sequencer music = null; 47 - private static Synthesizer synthesizer = null; 48 - private static Receiver cachedReceiver = null; 49 - private Position curPosition; 50 - 51 - enum Position { 52 - LEFT, RIGHT 53 - } 54 - 55 - public static void initialize(InetAddress address) { 56 - threadLiveId = (int) (Math.random() * 99999999D); 57 - 58 - if (active) { 59 - try { 60 - Thread.sleep(500L); 61 - } catch (Exception ignored) {} 62 - 63 - active = false; 64 - } 65 - 66 - socketRequest = 0; 67 - threadRequest = null; 68 - dnsRequest = null; 69 - saveRequest = null; 70 - urlRequest = null; 71 - inetAddress = address; 72 - Thread thread = new Thread(new SignLink()); 73 - 74 - thread.setDaemon(true); 75 - thread.start(); 76 - 77 - while (!active) { 78 - try { 79 - Thread.sleep(50L); 80 - } catch (Exception ignored) {} 81 - } 82 - } 83 - 84 - public void run() { 85 - active = true; 86 - String directory = findcachedir(); 87 - uid = getUID(directory); 88 - 89 - try { 90 - cacheData = new RandomAccessFile(directory + "main_file_cache.dat", "rw"); 91 - 92 - for (int idx = 0; idx < 5; idx++) 93 - cacheIndex[idx] = new RandomAccessFile(directory + "main_file_cache.idx" + idx, "rw"); 94 - 95 - } catch (Exception exception) { 96 - exception.printStackTrace(); 97 - } 98 - 99 - for (int i = threadLiveId; threadLiveId == i;) { 100 - if (socketRequest != 0) { 101 - try { 102 - socket = new Socket(inetAddress, socketRequest); 103 - } catch (Exception _ex) { 104 - socket = null; 105 - } 106 - 107 - socketRequest = 0; 108 - } else if (threadRequest != null) { 109 - Thread thread = new Thread(threadRequest); 110 - 111 - thread.setDaemon(true); 112 - thread.start(); 113 - thread.setPriority(threadRequestPriority); 114 - 115 - threadRequest = null; 116 - } else if (dnsRequest != null) { 117 - try { 118 - dns = InetAddress.getByName(dnsRequest).getHostName(); 119 - } catch (Exception _ex) { 120 - dns = "unknown"; 121 - } 122 - 123 - dnsRequest = null; 124 - } else if (saveRequest != null) { 125 - if (saveBuffer != null) { 126 - try { 127 - FileOutputStream fileoutputstream = new FileOutputStream(directory + saveRequest); 128 - 129 - fileoutputstream.write(saveBuffer, 0, saveLength); 130 - fileoutputstream.close(); 131 - } catch (Exception ignored) {} 132 - } 133 - 134 - if (midiPlay) { 135 - String wave = directory + saveRequest; 136 - midiPlay = false; 137 - AudioInputStream audioInputStream; 138 - 139 - try { 140 - audioInputStream = AudioSystem.getAudioInputStream(new File(wave)); 141 - } catch (UnsupportedAudioFileException | IOException e) { 142 - e.printStackTrace(); 143 - return; 144 - } 145 - 146 - AudioFormat format = audioInputStream.getFormat(); 147 - DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); 148 - SourceDataLine audioLine; 149 - 150 - try { 151 - audioLine = (SourceDataLine) AudioSystem.getLine(info); 152 - audioLine.open(format); 153 - } catch (Exception e) { 154 - e.printStackTrace(); 155 - return; 156 - } 157 - 158 - if (audioLine.isControlSupported(FloatControl.Type.PAN)) { 159 - FloatControl pan = (FloatControl) audioLine.getControl(FloatControl.Type.PAN); 160 - 161 - if (curPosition == Position.RIGHT) 162 - pan.setValue(1.0f); 163 - else if (curPosition == Position.LEFT) 164 - pan.setValue(-1.0f); 165 - } 166 - 167 - audioLine.start(); 168 - 169 - int nBytesRead = 0; 170 - byte[] abData = new byte[524288]; 171 - 172 - try { 173 - while (nBytesRead != -1) { 174 - nBytesRead = audioInputStream.read(abData, 0, abData.length); 175 - 176 - if (nBytesRead >= 0) 177 - audioLine.write(abData, 0, nBytesRead); 178 - } 179 - } catch (IOException e) { 180 - e.printStackTrace(); 181 - return; 182 - } finally { 183 - audioLine.drain(); 184 - audioLine.close(); 185 - try { audioInputStream.close(); } catch (Exception ignored) {} 186 - } 187 - } 188 - 189 - if (play) { 190 - midi = directory + saveRequest; 191 - 192 - try { 193 - if (music != null) { 194 - if (fadeMidi == 1 && synthesizer != null) { 195 - fadeOutMidi(); 196 - } 197 - music.stop(); 198 - music.close(); 199 - } 200 - 201 - playMidi(midi); 202 - 203 - if (fadeMidi == 1 && synthesizer != null) { 204 - fadeInMidi(); 205 - } 206 - } catch (Exception ex) { 207 - ex.printStackTrace(); 208 - } 209 - 210 - play = false; 211 - } 212 - 213 - saveRequest = null; 214 - } else if (urlRequest != null) { 215 - try { 216 - System.out.println("urlStream"); 217 - 218 - urlStream = new DataInputStream((new URL(new URL(Configuration.SERVER_ADDRESS), urlRequest)).openStream()); 219 - } catch (Exception _ex) { 220 - urlStream = null; 221 - } 222 - 223 - urlRequest = null; 224 - } 225 - 226 - try { 227 - Thread.sleep(50L); 228 - } catch (Exception ignored) {} 229 - } 230 - } 231 - 232 - /** 233 - * Plays the specified midi sequence. Closes any previous synthesizer 234 - * and receiver to prevent native audio resource leaks on macOS. 235 - */ 236 - private void playMidi(String location) { 237 - // Close previous synthesizer/receiver — the caller already stopped 238 - // the sequencer, but the synthesizer was left open and would leak 239 - // a CoreAudio instance on every song change. 240 - if (synthesizer != null && synthesizer != music) { 241 - try { synthesizer.close(); } catch (Exception ignored) {} 242 - } 243 - if (cachedReceiver != null) { 244 - try { cachedReceiver.close(); } catch (Exception ignored) {} 245 - cachedReceiver = null; 246 - } 247 - 248 - Sequence sequence; 249 - music = null; 250 - synthesizer = null; 251 - File midiFile = new File(location); 252 - 253 - try { 254 - sequence = MidiSystem.getSequence(midiFile); 255 - music = MidiSystem.getSequencer(); 256 - music.open(); 257 - music.setSequence(sequence); 258 - } catch (Exception e) { 259 - System.err.println("Problem loading MIDI file."); 260 - e.printStackTrace(); 261 - return; 262 - } 263 - 264 - if (music instanceof Synthesizer) { 265 - synthesizer = (Synthesizer) music; 266 - } else { 267 - try { 268 - synthesizer = MidiSystem.getSynthesizer(); 269 - synthesizer.open(); 270 - 271 - if (synthesizer.getDefaultSoundbank() == null) { 272 - cachedReceiver = MidiSystem.getReceiver(); 273 - music.getTransmitter().setReceiver(cachedReceiver); 274 - } else { 275 - music.getTransmitter().setReceiver(synthesizer.getReceiver()); 276 - } 277 - } catch (Exception e) { 278 - e.printStackTrace(); 279 - return; 280 - } 281 - } 282 - 283 - music.setLoopCount(Sequencer.LOOP_CONTINUOUSLY); 284 - music.start(); 285 - } 286 - 287 - private void fadeOutMidi() { 288 - try { 289 - int steps = 40; 290 - int savedVolume = midiVolume; 291 - for (int i = steps; i >= 0; i--) { 292 - int vol = savedVolume * i / steps; 293 - setVolume(vol); 294 - Thread.sleep(60); 295 - } 296 - midiVolume = savedVolume; 297 - } catch (Exception ignored) { 298 - } 299 - } 300 - 301 - private void fadeInMidi() { 302 - try { 303 - int steps = 30; 304 - int targetVolume = midiVolume; 305 - setVolume(0); 306 - for (int i = 1; i <= steps; i++) { 307 - int vol = targetVolume * i / steps; 308 - setVolume(vol); 309 - Thread.sleep(50); 310 - } 311 - } catch (Exception ignored) { 312 - } 313 - } 314 - 315 - /** 316 - * Sets the volume for the midi synthesizer via CC#7 (Main Volume). 317 - * 318 - * Previous bugs fixed: 319 - * - ShortMessage was reused: second setMessage() overwrote the first, 320 - * so CC#7 was never actually sent (only CC#39/LSB). Music played 321 - * near max volume regardless of the setting. 322 - * - MidiSystem.getReceiver() was called per-channel per-fade-step, 323 - * leaking hundreds of native receiver handles during a single fade. 324 - * - CC#39 (volume LSB) removed — 128 steps from CC#7 is sufficient. 325 - */ 326 - public static void setVolume(int value) { 327 - midiVolume = value; 328 - 329 - if (synthesizer == null) return; 330 - 331 - if (synthesizer.getDefaultSoundbank() == null) { 332 - if (cachedReceiver == null) return; 333 - try { 334 - for (int i = 0; i < 16; i++) { 335 - ShortMessage msg = new ShortMessage(); 336 - msg.setMessage(ShortMessage.CONTROL_CHANGE, i, 7, midiVolume); 337 - cachedReceiver.send(msg, -1); 338 - } 339 - } catch (Exception e) { 340 - e.printStackTrace(); 341 - } 342 - } else { 343 - MidiChannel[] channels = synthesizer.getChannels(); 344 - for (int c = 0; channels != null && c < channels.length; c++) { 345 - channels[c].controlChange(7, midiVolume); 346 - } 347 - } 348 - } 349 - 350 - public static String findcachedir() { 351 - File file = new File(System.getProperty("user.home") + System.getProperty("file.separator") + CACHE_NAME + System.getProperty("file.separator")); 352 - if (!file.exists()) { 353 - if (!file.mkdir()) { 354 - return secondaryLocation(); 355 - } 356 - } 357 - return System.getProperty("user.home") + System.getProperty("file.separator") + CACHE_NAME + System.getProperty("file.separator"); 358 - } 359 - 360 - public static String secondaryLocation() { 361 - File file = new File("c:/.377cache/"); 362 - if (!file.exists()) 363 - file.mkdir(); 364 - return file.toString(); 365 - } 366 - 367 - private static int getUID(String location) { 368 - try { 369 - File uid = new File(location + "uid.dat"); 370 - 371 - if (!uid.exists() || uid.length() < 4L) { 372 - DataOutputStream output = new DataOutputStream(new FileOutputStream(location + "uid.dat")); 373 - 374 - output.writeInt((int) (Math.random() * 99999999D)); 375 - output.close(); 376 - } 377 - } catch (Exception ignored) {} 378 - 379 - try { 380 - DataInputStream input = new DataInputStream(new FileInputStream(location + "uid.dat")); 381 - int value = input.readInt(); 382 - 383 - input.close(); 384 - 385 - return value + 1; 386 - } catch (Exception ex) { 387 - return 0; 388 - } 389 - } 390 - 391 - public static synchronized Socket openSocket(int port) throws IOException { 392 - for (socketRequest = port; socketRequest != 0;) { 393 - try { 394 - Thread.sleep(50L); 395 - } catch (Exception ignored) {} 396 - } 397 - 398 - if (socket == null) 399 - throw new IOException("could not open socket"); 400 - else 401 - return socket; 402 - } 403 - 404 - public static synchronized DataInputStream openURL(String url) throws IOException { 405 - for (urlRequest = url; urlRequest != null;) { 406 - try { 407 - Thread.sleep(50L); 408 - } catch (Exception ignored) {} 409 - } 410 - 411 - if (urlStream == null) 412 - throw new IOException("could not open: " + url); 413 - else 414 - return urlStream; 415 - } 416 - 417 - public static synchronized void dnsLookup(String host) { 418 - dns = host; 419 - dnsRequest = host; 420 - } 421 - 422 - public static synchronized void startThread(Runnable runnable, int priority) { 423 - threadRequestPriority = priority; 424 - threadRequest = runnable; 425 - } 426 - 427 - public static synchronized boolean saveWave(byte[] data, int length) { 428 - if (length > 2000000 || saveRequest != null) 429 - return false; 430 - 431 - wavePosition = (wavePosition + 1) % 5; 432 - saveLength = length; 433 - saveBuffer = data; 434 - midiPlay = true; 435 - saveRequest = "sound" + wavePosition + ".wav"; 436 - 437 - return true; 438 - } 439 - 440 - public static synchronized boolean replayWave() { 441 - if (saveRequest != null) 442 - return false; 443 - 444 - saveBuffer = null; 445 - midiPlay = true; 446 - saveRequest = "sound" + wavePosition + ".wav"; 447 - 448 - return true; 449 - } 450 - 451 - public static synchronized void saveMidi(byte[] data, int length) { 452 - if (length > 2000000 || saveRequest != null) 453 - return; 454 - 455 - midiPosition = (midiPosition + 1) % 5; 456 - saveLength = length; 457 - saveBuffer = data; 458 - play = true; 459 - saveRequest = "jingle" + midiPosition + ".mid"; 460 - } 461 - 462 - public static void reportError(String error) { 463 - System.out.println("Error: " + error); 464 - } 465 - 466 - }
+426
src/main/java/com/jagex/runescape/util/SignLink.kt
··· 1 + package com.jagex.runescape.util 2 + 3 + import com.jagex.runescape.config.Configuration 4 + import com.jagex.runescape.config.Configuration.CACHE_NAME 5 + import java.io.* 6 + import java.net.InetAddress 7 + import java.net.Socket 8 + import java.net.URL 9 + import javax.sound.midi.* 10 + import javax.sound.sampled.* 11 + 12 + /** 13 + * Background service thread that handles privileged I/O operations: socket 14 + * creation, file saves, DNS lookups, and audio playback. Originally needed 15 + * because applets couldn't do I/O on the main thread — kept in standalone 16 + * mode because the MIDI fade logic is blocking and would stall the game loop. 17 + */ 18 + class SignLink : Runnable { 19 + 20 + private var curPosition: Position? = null 21 + 22 + private enum class Position { LEFT, RIGHT } 23 + 24 + override fun run() { 25 + active = true 26 + val directory = findcachedir() 27 + uid = getUID(directory) 28 + 29 + try { 30 + cacheData = RandomAccessFile(directory + "main_file_cache.dat", "rw") 31 + for (idx in 0 until 5) { 32 + cacheIndex[idx] = RandomAccessFile(directory + "main_file_cache.idx$idx", "rw") 33 + } 34 + } catch (e: Exception) { 35 + e.printStackTrace() 36 + } 37 + 38 + var i = threadLiveId 39 + while (threadLiveId == i) { 40 + if (socketRequest != 0) { 41 + try { 42 + socket = Socket(inetAddress, socketRequest) 43 + } catch (_: Exception) { 44 + socket = null 45 + } 46 + socketRequest = 0 47 + } else if (threadRequest != null) { 48 + val thread = Thread(threadRequest) 49 + thread.isDaemon = true 50 + thread.start() 51 + thread.priority = threadRequestPriority 52 + threadRequest = null 53 + } else if (dnsRequest != null) { 54 + try { 55 + dns = InetAddress.getByName(dnsRequest).hostName 56 + } catch (_: Exception) { 57 + dns = "unknown" 58 + } 59 + dnsRequest = null 60 + } else if (saveRequest != null) { 61 + if (saveBuffer != null) { 62 + try { 63 + FileOutputStream(directory + saveRequest).use { fos -> 64 + fos.write(saveBuffer!!, 0, saveLength) 65 + } 66 + } catch (_: Exception) { 67 + } 68 + } 69 + 70 + if (midiPlay) { 71 + val wave = directory + saveRequest 72 + midiPlay = false 73 + 74 + val audioInputStream: AudioInputStream 75 + try { 76 + audioInputStream = AudioSystem.getAudioInputStream(File(wave)) 77 + } catch (e: Exception) { 78 + e.printStackTrace() 79 + return 80 + } 81 + 82 + val format = audioInputStream.format 83 + val info = DataLine.Info(SourceDataLine::class.java, format) 84 + val audioLine: SourceDataLine 85 + try { 86 + audioLine = AudioSystem.getLine(info) as SourceDataLine 87 + audioLine.open(format) 88 + } catch (e: Exception) { 89 + e.printStackTrace() 90 + return 91 + } 92 + 93 + if (audioLine.isControlSupported(FloatControl.Type.PAN)) { 94 + val pan = audioLine.getControl(FloatControl.Type.PAN) as FloatControl 95 + when (curPosition) { 96 + Position.RIGHT -> pan.value = 1.0f 97 + Position.LEFT -> pan.value = -1.0f 98 + else -> {} 99 + } 100 + } 101 + 102 + audioLine.start() 103 + val abData = ByteArray(524288) 104 + try { 105 + var nBytesRead = 0 106 + while (nBytesRead != -1) { 107 + nBytesRead = audioInputStream.read(abData, 0, abData.size) 108 + if (nBytesRead >= 0) audioLine.write(abData, 0, nBytesRead) 109 + } 110 + } catch (e: IOException) { 111 + e.printStackTrace() 112 + return 113 + } finally { 114 + audioLine.drain() 115 + audioLine.close() 116 + try { audioInputStream.close() } catch (_: Exception) {} 117 + } 118 + } 119 + 120 + if (play) { 121 + midi = directory + saveRequest 122 + try { 123 + if (music != null) { 124 + if (fadeMidi == 1 && synthesizer != null) fadeOutMidi() 125 + music!!.stop() 126 + music!!.close() 127 + } 128 + playMidi(midi!!) 129 + if (fadeMidi == 1 && synthesizer != null) fadeInMidi() 130 + } catch (e: Exception) { 131 + e.printStackTrace() 132 + } 133 + play = false 134 + } 135 + 136 + saveRequest = null 137 + } else if (urlRequest != null) { 138 + try { 139 + println("urlStream") 140 + urlStream = DataInputStream(URL(URL(Configuration.SERVER_ADDRESS), urlRequest).openStream()) 141 + } catch (_: Exception) { 142 + urlStream = null 143 + } 144 + urlRequest = null 145 + } 146 + 147 + try { 148 + Thread.sleep(50L) 149 + } catch (_: Exception) { 150 + } 151 + } 152 + } 153 + 154 + /** 155 + * Plays the specified MIDI sequence. Closes any previous synthesizer and 156 + * receiver to prevent native audio resource leaks on macOS. 157 + */ 158 + private fun playMidi(location: String) { 159 + if (synthesizer != null && synthesizer !== music) { 160 + try { synthesizer!!.close() } catch (_: Exception) {} 161 + } 162 + if (cachedReceiver != null) { 163 + try { cachedReceiver!!.close() } catch (_: Exception) {} 164 + cachedReceiver = null 165 + } 166 + 167 + music = null 168 + synthesizer = null 169 + val midiFile = File(location) 170 + 171 + val sequence: Sequence 172 + try { 173 + sequence = MidiSystem.getSequence(midiFile) 174 + music = MidiSystem.getSequencer() 175 + music!!.open() 176 + music!!.sequence = sequence 177 + } catch (e: Exception) { 178 + System.err.println("Problem loading MIDI file.") 179 + e.printStackTrace() 180 + return 181 + } 182 + 183 + if (music is Synthesizer) { 184 + synthesizer = music as Synthesizer 185 + } else { 186 + try { 187 + synthesizer = MidiSystem.getSynthesizer() 188 + synthesizer!!.open() 189 + if (synthesizer!!.defaultSoundbank == null) { 190 + cachedReceiver = MidiSystem.getReceiver() 191 + music!!.transmitter.receiver = cachedReceiver 192 + } else { 193 + music!!.transmitter.receiver = synthesizer!!.receiver 194 + } 195 + } catch (e: Exception) { 196 + e.printStackTrace() 197 + return 198 + } 199 + } 200 + 201 + music!!.loopCount = Sequencer.LOOP_CONTINUOUSLY 202 + music!!.start() 203 + } 204 + 205 + private fun fadeOutMidi() { 206 + try { 207 + val steps = 40 208 + val savedVolume = midiVolume 209 + for (i in steps downTo 0) { 210 + setVolume(savedVolume * i / steps) 211 + Thread.sleep(60) 212 + } 213 + midiVolume = savedVolume 214 + } catch (_: Exception) { 215 + } 216 + } 217 + 218 + private fun fadeInMidi() { 219 + try { 220 + val steps = 30 221 + val targetVolume = midiVolume 222 + setVolume(0) 223 + for (i in 1..steps) { 224 + setVolume(targetVolume * i / steps) 225 + Thread.sleep(50) 226 + } 227 + } catch (_: Exception) { 228 + } 229 + } 230 + 231 + companion object { 232 + const val CLIENT_REVISION = 377 233 + 234 + @JvmField var uid = 0 235 + @JvmField var storeId = 32 236 + @JvmField var cacheData: RandomAccessFile? = null 237 + @JvmField val cacheIndex = arrayOfNulls<RandomAccessFile>(5) 238 + 239 + private var active = false 240 + private var threadLiveId = 0 241 + private var inetAddress: InetAddress? = null 242 + private var socketRequest = 0 243 + private var socket: Socket? = null 244 + private var threadRequestPriority = 1 245 + private var threadRequest: Runnable? = null 246 + private var dnsRequest: String? = null 247 + @JvmField var dns: String? = null 248 + private var urlRequest: String? = null 249 + private var urlStream: DataInputStream? = null 250 + private var saveLength = 0 251 + private var saveRequest: String? = null 252 + private var saveBuffer: ByteArray? = null 253 + private var play = false 254 + private var midiPosition = 0 255 + @JvmField var midi: String? = null 256 + @JvmField var midiVolume = 0 257 + @JvmField var fadeMidi = 0 258 + private var midiPlay = false 259 + private var wavePosition = 0 260 + @JvmField var waveVolume = 0 261 + @JvmField var reportError = true 262 + @JvmField var errorName = "" 263 + @JvmField var music: Sequencer? = null 264 + private var synthesizer: Synthesizer? = null 265 + private var cachedReceiver: Receiver? = null 266 + 267 + @JvmStatic 268 + fun initialize(address: InetAddress) { 269 + threadLiveId = (Math.random() * 99999999.0).toInt() 270 + if (active) { 271 + try { Thread.sleep(500L) } catch (_: Exception) {} 272 + active = false 273 + } 274 + socketRequest = 0 275 + threadRequest = null 276 + dnsRequest = null 277 + saveRequest = null 278 + urlRequest = null 279 + inetAddress = address 280 + val thread = Thread(SignLink()) 281 + thread.isDaemon = true 282 + thread.start() 283 + while (!active) { 284 + try { Thread.sleep(50L) } catch (_: Exception) {} 285 + } 286 + } 287 + 288 + /** 289 + * Sets MIDI volume via CC#7 (Main Volume) on all 16 channels. 290 + */ 291 + @JvmStatic 292 + fun setVolume(value: Int) { 293 + midiVolume = value 294 + if (synthesizer == null) return 295 + 296 + if (synthesizer!!.defaultSoundbank == null) { 297 + if (cachedReceiver == null) return 298 + try { 299 + for (i in 0 until 16) { 300 + val msg = ShortMessage() 301 + msg.setMessage(ShortMessage.CONTROL_CHANGE, i, 7, midiVolume) 302 + cachedReceiver!!.send(msg, -1) 303 + } 304 + } catch (e: Exception) { 305 + e.printStackTrace() 306 + } 307 + } else { 308 + val channels = synthesizer!!.channels 309 + for (c in channels.indices) { 310 + channels[c].controlChange(7, midiVolume) 311 + } 312 + } 313 + } 314 + 315 + @JvmStatic 316 + fun findcachedir(): String { 317 + val sep = File.separator 318 + val dir = System.getProperty("user.home") + sep + CACHE_NAME + sep 319 + val file = File(dir) 320 + if (!file.exists()) { 321 + if (!file.mkdir()) return secondaryLocation() 322 + } 323 + return dir 324 + } 325 + 326 + @JvmStatic 327 + fun secondaryLocation(): String { 328 + val file = File("c:/.377cache/") 329 + if (!file.exists()) file.mkdir() 330 + return file.toString() 331 + } 332 + 333 + private fun getUID(location: String): Int { 334 + try { 335 + val uidFile = File(location + "uid.dat") 336 + if (!uidFile.exists() || uidFile.length() < 4L) { 337 + DataOutputStream(FileOutputStream(location + "uid.dat")).use { out -> 338 + out.writeInt((Math.random() * 99999999.0).toInt()) 339 + } 340 + } 341 + } catch (_: Exception) {} 342 + 343 + return try { 344 + DataInputStream(FileInputStream(location + "uid.dat")).use { input -> 345 + input.readInt() + 1 346 + } 347 + } catch (_: Exception) { 348 + 0 349 + } 350 + } 351 + 352 + @JvmStatic 353 + @Synchronized 354 + @Throws(IOException::class) 355 + fun openSocket(port: Int): Socket { 356 + socketRequest = port 357 + while (socketRequest != 0) { 358 + try { Thread.sleep(50L) } catch (_: Exception) {} 359 + } 360 + return socket ?: throw IOException("could not open socket") 361 + } 362 + 363 + @JvmStatic 364 + @Synchronized 365 + @Throws(IOException::class) 366 + fun openURL(url: String): DataInputStream { 367 + urlRequest = url 368 + while (urlRequest != null) { 369 + try { Thread.sleep(50L) } catch (_: Exception) {} 370 + } 371 + return urlStream ?: throw IOException("could not open: $url") 372 + } 373 + 374 + @JvmStatic 375 + @Synchronized 376 + fun dnsLookup(host: String) { 377 + dns = host 378 + dnsRequest = host 379 + } 380 + 381 + @JvmStatic 382 + @Synchronized 383 + fun startThread(runnable: Runnable, priority: Int) { 384 + threadRequestPriority = priority 385 + threadRequest = runnable 386 + } 387 + 388 + @JvmStatic 389 + @Synchronized 390 + fun saveWave(data: ByteArray, length: Int): Boolean { 391 + if (length > 2_000_000 || saveRequest != null) return false 392 + wavePosition = (wavePosition + 1) % 5 393 + saveLength = length 394 + saveBuffer = data 395 + midiPlay = true 396 + saveRequest = "sound$wavePosition.wav" 397 + return true 398 + } 399 + 400 + @JvmStatic 401 + @Synchronized 402 + fun replayWave(): Boolean { 403 + if (saveRequest != null) return false 404 + saveBuffer = null 405 + midiPlay = true 406 + saveRequest = "sound$wavePosition.wav" 407 + return true 408 + } 409 + 410 + @JvmStatic 411 + @Synchronized 412 + fun saveMidi(data: ByteArray, length: Int) { 413 + if (length > 2_000_000 || saveRequest != null) return 414 + midiPosition = (midiPosition + 1) % 5 415 + saveLength = length 416 + saveBuffer = data 417 + play = true 418 + saveRequest = "jingle$midiPosition.mid" 419 + } 420 + 421 + @JvmStatic 422 + fun reportError(error: String) { 423 + println("Error: $error") 424 + } 425 + } 426 + }
-13
src/main/java/com/jagex/runescape/util/SkillConstants.java
··· 1 - package com.jagex.runescape.util; 2 - 3 - public class SkillConstants { 4 - 5 - public static int SKILL_COUNT = 25; 6 - public static String[] SKILL_NAMES = { "attack", "defence", "strength", "hitpoints", "ranged", "prayer", 7 - "magic", "cooking", "woodcutting", "fletching", "fishing", "firemaking", "crafting", "smithing", "mining", 8 - "herblore", "agility", "thieving", "slayer", "farming", "runecraft", "-unused-", "-unused-", "-unused-", 9 - "-unused-" }; 10 - public static boolean[] SKILL_TOGGLES = { true, true, true, true, true, true, true, true, true, true, true, 11 - true, true, true, true, true, true, true, true, true, true, false, false, false, false }; 12 - 13 - }
+21
src/main/java/com/jagex/runescape/util/SkillConstants.kt
··· 1 + package com.jagex.runescape.util 2 + 3 + object SkillConstants { 4 + @JvmField var SKILL_COUNT = 25 5 + 6 + @JvmField 7 + val SKILL_NAMES = arrayOf( 8 + "attack", "defence", "strength", "hitpoints", "ranged", "prayer", 9 + "magic", "cooking", "woodcutting", "fletching", "fishing", "firemaking", 10 + "crafting", "smithing", "mining", "herblore", "agility", "thieving", 11 + "slayer", "farming", "runecraft", "-unused-", "-unused-", "-unused-", 12 + "-unused-" 13 + ) 14 + 15 + @JvmField 16 + val SKILL_TOGGLES = booleanArrayOf( 17 + true, true, true, true, true, true, true, true, true, true, true, 18 + true, true, true, true, true, true, true, true, true, true, 19 + false, false, false, false 20 + ) 21 + }
-87
src/main/java/com/jagex/runescape/util/TextUtils.java
··· 1 - package com.jagex.runescape.util; 2 - 3 - public class TextUtils { 4 - 5 - 6 - public static final char VALID_CHARACTERS[] = { '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 7 - 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', 8 - '7', '8', '9' }; 9 - 10 - public static long nameToLong(String name) { 11 - long longName = 0L; 12 - for (int i = 0; i < name.length() && i < 12; i++) { 13 - char ch = name.charAt(i); 14 - longName *= 37L; 15 - if (ch >= 'A' && ch <= 'Z') 16 - longName += (1 + ch) - 65; 17 - else if (ch >= 'a' && ch <= 'z') 18 - longName += (1 + ch) - 97; 19 - else if (ch >= '0' && ch <= '9') 20 - longName += (27 + ch) - 48; 21 - } 22 - 23 - for (; longName % 37L == 0L && longName != 0L; longName /= 37L); 24 - return longName; 25 - } 26 - 27 - public static String longToName(long longName) { 28 - if (longName <= 0L || longName >= 0x5b5b57f8a98a5dd1L) 29 - return "invalid_name"; 30 - if (longName % 37L == 0L) 31 - return "invalid_name"; 32 - int length = 0; 33 - char name[] = new char[12]; 34 - while (longName != 0L) { 35 - long tmp = longName; 36 - longName /= 37L; 37 - name[11 - length++] = VALID_CHARACTERS[(int) (tmp - longName * 37L)]; 38 - } 39 - return new String(name, 12 - length, length); 40 - } 41 - 42 - public static long spriteToHash(String sprite) { 43 - sprite = sprite.toUpperCase(); 44 - long spriteHash = 0L; 45 - for (int index = 0; index < sprite.length(); index++) { 46 - spriteHash = (spriteHash * 61L + sprite.charAt(index)) - 32L; 47 - spriteHash = spriteHash + (spriteHash >> 56) & 0xffffffffffffffL; 48 - } 49 - return spriteHash; 50 - } 51 - 52 - public static String decodeAddress(int address) { 53 - return (address >> 24 & 0xff) + "." + (address >> 16 & 0xff) + "." + (address >> 8 & 0xff) + "." + (address & 0xff); 54 - } 55 - 56 - public static String formatName(String name) { 57 - if (name.length() > 0) { 58 - char formatedName[] = name.toCharArray(); 59 - for (int pos = 0; pos < formatedName.length; pos++) 60 - if (formatedName[pos] == '_') { 61 - formatedName[pos] = ' '; 62 - if (pos + 1 < formatedName.length && formatedName[pos + 1] >= 'a' && formatedName[pos + 1] <= 'z') 63 - formatedName[pos + 1] = (char) ((formatedName[pos + 1] + 65) - 97); 64 - } 65 - 66 - if (formatedName[0] >= 'a' && formatedName[0] <= 'z') 67 - formatedName[0] = (char) ((formatedName[0] + 65) - 97); 68 - return new String(formatedName); 69 - } else { 70 - return name; 71 - } 72 - } 73 - 74 - public static String censorPassword(String password) { 75 - if(password == null || password.length() < 1) { 76 - return ""; 77 - } 78 - 79 - StringBuffer censoredPassword = new StringBuffer(); 80 - for (int index = 0; index < password.length(); index++) 81 - censoredPassword.append("*"); 82 - return censoredPassword.toString(); 83 - } 84 - 85 - 86 - 87 - }
+87
src/main/java/com/jagex/runescape/util/TextUtils.kt
··· 1 + package com.jagex.runescape.util 2 + 3 + /** 4 + * Text encoding/decoding utilities for the RS protocol. Player names are 5 + * stored as base-37 longs for compact transmission — 12 characters pack into 6 + * a single 64-bit value. Sprite names use a base-61 hash. 7 + */ 8 + object TextUtils { 9 + @JvmField 10 + val VALID_CHARACTERS = charArrayOf( 11 + '_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 12 + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 13 + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 14 + ) 15 + 16 + @JvmStatic 17 + fun nameToLong(name: String): Long { 18 + var longName = 0L 19 + for (i in 0 until minOf(name.length, 12)) { 20 + val ch = name[i] 21 + longName *= 37L 22 + when { 23 + ch in 'A'..'Z' -> longName += (1 + ch.code) - 65 24 + ch in 'a'..'z' -> longName += (1 + ch.code) - 97 25 + ch in '0'..'9' -> longName += (27 + ch.code) - 48 26 + } 27 + } 28 + while (longName % 37L == 0L && longName != 0L) { 29 + longName /= 37L 30 + } 31 + return longName 32 + } 33 + 34 + @JvmStatic 35 + fun longToName(longName: Long): String { 36 + var remaining = longName 37 + if (remaining <= 0L || remaining >= 0x5b5b57f8a98a5dd1L) return "invalid_name" 38 + if (remaining % 37L == 0L) return "invalid_name" 39 + var length = 0 40 + val name = CharArray(12) 41 + while (remaining != 0L) { 42 + val tmp = remaining 43 + remaining /= 37L 44 + name[11 - length++] = VALID_CHARACTERS[((tmp - remaining * 37L).toInt())] 45 + } 46 + return String(name, 12 - length, length) 47 + } 48 + 49 + @JvmStatic 50 + fun spriteToHash(sprite: String): Long { 51 + val upper = sprite.uppercase() 52 + var hash = 0L 53 + for (ch in upper) { 54 + hash = (hash * 61L + ch.code) - 32L 55 + hash = hash + (hash ushr 56) and 0xffffffffffffffL 56 + } 57 + return hash 58 + } 59 + 60 + @JvmStatic 61 + fun decodeAddress(address: Int): String = 62 + "${address ushr 24 and 0xff}.${address ushr 16 and 0xff}.${address ushr 8 and 0xff}.${address and 0xff}" 63 + 64 + @JvmStatic 65 + fun formatName(name: String): String { 66 + if (name.isEmpty()) return name 67 + val chars = name.toCharArray() 68 + for (i in chars.indices) { 69 + if (chars[i] == '_') { 70 + chars[i] = ' ' 71 + if (i + 1 < chars.size && chars[i + 1] in 'a'..'z') { 72 + chars[i + 1] = (chars[i + 1].code + 65 - 97).toChar() 73 + } 74 + } 75 + } 76 + if (chars[0] in 'a'..'z') { 77 + chars[0] = (chars[0].code + 65 - 97).toChar() 78 + } 79 + return String(chars) 80 + } 81 + 82 + @JvmStatic 83 + fun censorPassword(password: String?): String { 84 + if (password.isNullOrEmpty()) return "" 85 + return "*".repeat(password.length) 86 + } 87 + }
-65
src/main/java/com/jagex/runescape/world/GroundArray.java
··· 1 - package com.jagex.runescape.world; 2 - 3 - 4 - public class GroundArray<T> { 5 - private static final int MAX_HEIGHT = 4; 6 - private static final int MAX_TILES = 104; 7 - private Object[][][] groundArray; 8 - 9 - public GroundArray(T[][][] groundArray) { 10 - this.groundArray = groundArray; 11 - } 12 - 13 - public GroundArray() { 14 - this.groundArray = new Object[MAX_HEIGHT][MAX_TILES][MAX_TILES]; 15 - } 16 - 17 - private boolean checkIfIndexDoesNotExist(int plane, int x, int y) { 18 - if (plane < 0 || plane > this.groundArray.length - 1) { 19 - String error = String.format("Plane must be between 0 and %d, requested plane was: %d.\n", 20 - this.groundArray.length - 1, plane); 21 - new ArrayIndexOutOfBoundsException(error).printStackTrace(); 22 - return true; 23 - } 24 - if (x < 0 || x > MAX_TILES - 1) { 25 - String error = String.format("X must be between 0 and %d, requested x was: %d.\n", MAX_TILES - 1, x); 26 - new ArrayIndexOutOfBoundsException(error).printStackTrace(); 27 - return true; 28 - } 29 - if (y < 0 || y > MAX_TILES - 1) { 30 - String error = String.format("Y must be between 0 and %d, requested y was: %d.\n", MAX_TILES - 1, y); 31 - new ArrayIndexOutOfBoundsException(error).printStackTrace(); 32 - return true; 33 - } 34 - return false; 35 - } 36 - 37 - public boolean isTileEmpty(int plane, int x, int y) { 38 - if (checkIfIndexDoesNotExist(plane, x, y)) { 39 - return false; 40 - } 41 - return groundArray[plane][x][y] == null; 42 - } 43 - 44 - public void clearTile(int plane, int x, int y) { 45 - if (checkIfIndexDoesNotExist(plane, x, y)) { 46 - return; 47 - } 48 - groundArray[plane][x][y] = null; 49 - } 50 - 51 - public T getTile(int plane, int x, int y) { 52 - if (checkIfIndexDoesNotExist(plane, x, y)) { 53 - return null; 54 - } 55 - return (T) groundArray[plane][x][y]; 56 - } 57 - 58 - public T setTile(int plane, int x, int y, T tile) { 59 - if (checkIfIndexDoesNotExist(plane, x, y)) { 60 - return null; 61 - } 62 - groundArray[plane][x][y] = tile; 63 - return tile; 64 - } 65 - }
+70
src/main/java/com/jagex/runescape/world/GroundArray.kt
··· 1 + package com.jagex.runescape.world 2 + 3 + /** 4 + * 3D array indexed by (plane, x, y) for storing tile-associated data. 5 + * Used for ground items, scene tiles, and other per-tile world state. 6 + * Bounds-checks on access to avoid silent corruption from out-of-range 7 + * coordinates — prints the violation rather than crashing. 8 + */ 9 + class GroundArray<T> { 10 + 11 + private val groundArray: Array<Array<Array<Any?>>> 12 + 13 + constructor(groundArray: Array<Array<Array<T?>>>) { 14 + @Suppress("UNCHECKED_CAST") 15 + this.groundArray = groundArray as Array<Array<Array<Any?>>> 16 + } 17 + 18 + constructor() { 19 + groundArray = Array(MAX_HEIGHT) { Array(MAX_TILES) { arrayOfNulls<Any>(MAX_TILES) } } 20 + } 21 + 22 + private fun checkIfIndexDoesNotExist(plane: Int, x: Int, y: Int): Boolean { 23 + if (plane < 0 || plane > groundArray.size - 1) { 24 + ArrayIndexOutOfBoundsException( 25 + "Plane must be between 0 and ${groundArray.size - 1}, requested plane was: $plane.\n" 26 + ).printStackTrace() 27 + return true 28 + } 29 + if (x < 0 || x > MAX_TILES - 1) { 30 + ArrayIndexOutOfBoundsException( 31 + "X must be between 0 and ${MAX_TILES - 1}, requested x was: $x.\n" 32 + ).printStackTrace() 33 + return true 34 + } 35 + if (y < 0 || y > MAX_TILES - 1) { 36 + ArrayIndexOutOfBoundsException( 37 + "Y must be between 0 and ${MAX_TILES - 1}, requested y was: $y.\n" 38 + ).printStackTrace() 39 + return true 40 + } 41 + return false 42 + } 43 + 44 + fun isTileEmpty(plane: Int, x: Int, y: Int): Boolean { 45 + if (checkIfIndexDoesNotExist(plane, x, y)) return false 46 + return groundArray[plane][x][y] == null 47 + } 48 + 49 + fun clearTile(plane: Int, x: Int, y: Int) { 50 + if (checkIfIndexDoesNotExist(plane, x, y)) return 51 + groundArray[plane][x][y] = null 52 + } 53 + 54 + @Suppress("UNCHECKED_CAST") 55 + fun getTile(plane: Int, x: Int, y: Int): T? { 56 + if (checkIfIndexDoesNotExist(plane, x, y)) return null 57 + return groundArray[plane][x][y] as T? 58 + } 59 + 60 + fun setTile(plane: Int, x: Int, y: Int, tile: T?): T? { 61 + if (checkIfIndexDoesNotExist(plane, x, y)) return null 62 + groundArray[plane][x][y] = tile 63 + return tile 64 + } 65 + 66 + companion object { 67 + private const val MAX_HEIGHT = 4 68 + private const val MAX_TILES = 104 69 + } 70 + }