├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src └── main └── kotlin └── com └── displee ├── cache ├── CacheLibrary.kt ├── ProgressListener.kt └── index │ ├── Index.kt │ ├── Index255.kt │ ├── Index317.kt │ ├── ReferenceTable.kt │ └── archive │ ├── Archive.kt │ ├── Archive317.kt │ ├── ArchiveSector.kt │ └── file │ └── File.kt ├── compress ├── CompressionExt.kt ├── CompressionType.kt └── type │ ├── BZIP2Compressor.kt │ ├── Compressor.kt │ ├── Compressors.kt │ ├── EmptyCompressor.kt │ ├── GZIPCompressor.kt │ └── LZMACompressor.kt └── util ├── OtherExt.kt └── Whirlpool.kt /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.war 8 | *.ear 9 | *.classpath 10 | *.project 11 | *.iml 12 | .settings 13 | bin 14 | 15 | # IntellIJ 16 | build 17 | hs_err_pid* 18 | /.idea/ 19 | target 20 | classes 21 | 22 | # Gradle 23 | .gradle 24 | 25 | # Test files 26 | src/test/java/Test.java 27 | src/test/kotlin/TestOsrs.kt 28 | src/test/kotlin/TestCache.kt 29 | src/test/kotlin/TestLibrary.kt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Displee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Displee's cache library 3 | An library written in Kotlin which is used to read and write to all cache types of RuneScape. 4 | A RuneScape cache is built off from a 2-level container structure and in each level 1 container is the actual data of the game stored. 5 | I have named the level 0 container "Index" and the level 1 container "Archive". It looks like this. 6 | - Index (level 0 container) 7 | - Archive (level 1 container) 8 | - File (data) 9 | 10 | A cache contains multiple indices and an index can contain thousands of archives which gives RuneScape the ability to group and store a great amount of data. 11 | A file contains useful data like the properties of a certain game item, object, image or even 3D model. 12 | This library is able to read this data, and write manipulated data back to the cache. 13 | 14 | ### Features: 15 | - Easy to use 16 | - Very fast (including OSRS) 17 | - CRUD operations for indices, archives and files 18 | - Cross cache copying 19 | - XTEA cryption 20 | - BZIP2, GZIP and LZMA (de)compression 21 | - Whirlpool and CRC generation 22 | - Ukeys generation 23 | - Cache rebuilding (also known as cache defragmentation) 24 | 25 | ## Gradle 26 | ``` 27 | implementation 'com.displee:rs-cache-library:7.3.0' 28 | ``` 29 | ## Initialize your cache 30 | ```kotlin 31 | val library = CacheLibrary("path_to_your_cache") 32 | ``` 33 | ```kotlin 34 | val library = CacheLibrary.create("path_to_your_cache") 35 | ``` 36 | ## Simple usage 37 | #### Get file data 38 | ```kotlin 39 | val ags = 11694 //armadyl godsword 40 | val agsData = library.data(19, ags shr 8, ags and 0xFF) 41 | ``` 42 | ```kotlin 43 | val regionId = 12850 44 | val x = (regionId shr 8) and 0xFF 45 | val y = regionId and 0xFF 46 | val xtea = intArrayOf(0, 0, 0, 0) //optional 47 | val locsData = library.data(5, "l${x}_${y}", xtea) 48 | ``` 49 | For our 317 users :) 50 | ```kotlin 51 | val objData = library.data(0, 2, "obj.dat") 52 | val objMeta = library.data(0, 2, "obj.idx") 53 | ``` 54 | #### Put file data 55 | ```kotlin 56 | val xtea = intArrayOf(...) //optional 57 | library.put(18, 10, 2, byteArrayOf(...), xtea) 58 | ``` 59 | ```kotlin 60 | val xtea = intArrayOf(...) //optional 61 | library.put(5, "l60_62", byteArrayOf(...), xtea) 62 | ``` 63 | #### Remove archive/file 64 | ```kotlin 65 | library.remove(5, "l60_62") 66 | ``` 67 | ```kotlin 68 | library.remove(18, 10, 2) 69 | ``` 70 | #### Write your changes (important) 71 | Update a specific index 72 | ```kotlin 73 | library.index(7).update() //returns true if changes have been written with success, else false 74 | ``` 75 | ## Advanced usage 76 | #### Add an archive to an index 77 | ```kotlin 78 | val newInterface = library.index(3).add() 79 | ``` 80 | ```kotlin 81 | val modelData = byteArrayOf(...) 82 | val newModel = library.index(7).add(modelData) 83 | val newModelId = newModel.id 84 | ``` 85 | ```kotlin 86 | val newArchive = library.index(7).add("custom_name") 87 | ``` 88 | ```kotlin 89 | //returns existing archive if it already existed 90 | val newArchive = library.index(7).add(38372) 91 | ``` 92 | ```kotlin 93 | //add archive with a new id 94 | val archiveToCopy = library.index(3).archive(1200) 95 | if (archiveToCopy == null) { 96 | //Do something... 97 | return 98 | } 99 | val newId = 1500 //optional 100 | val replace = true //optional, replace whole archive 101 | val newArchive = library.index(7).add(archiveToCopy, newId, replace) 102 | ``` 103 | ```kotlin 104 | //add multiple archives if they don't exist 105 | val replace = true //optional, replace all archives 106 | library.index(3).add(*otherLibrary.index(3).archives(), replace) 107 | ``` 108 | #### Add a file to an archive 109 | ```kotlin 110 | val newFile = library.index(19).archive(2).add(byteArrayOf(...)) 111 | ``` 112 | ```kotlin 113 | val xtea = intArrayOf(...) 114 | val regionId = 12341 //barbarian village 115 | val x = (regionId shr 8) and 0xFF 116 | val y = regionId and 0xFF 117 | val replace = true //optional 118 | val file = library.index(5).archive("l${x}_${y}", xtea)?.add(0, byteArrayOf(...), replace) 119 | ``` 120 | ```kotlin 121 | val replace = true //optional 122 | val file = library.index(0).archive(2)?.add("obj.dat", byteArrayOf(...), replace) 123 | ``` 124 | #### Remove an archive or file 125 | ```kotlin 126 | val archive = library.index(7).remove(10) 127 | ``` 128 | ```kotlin 129 | val archive = library.index(5).remove("l50_50") 130 | ``` 131 | ```kotlin 132 | val file = library.index(7).archive(10)?.remove(10) 133 | ``` 134 | #### Cache an index 135 | Sometimes it's handy to read all data of an entire index and prepare this data for certain operations. 136 | ```kotlin 137 | library.index(7).cache() 138 | ``` 139 | If you want to cache an index containing xteas, set the xteas first for the archives: 140 | ```kotlin 141 | for(regionId in 0 until 255 * 255) { 142 | val x = (regionId shr 8) and 0xFF 143 | val y = regionId and 0xFF 144 | val xtea = RegionManager.getXTEA(regionId) 145 | library.index(5).archive("l${x}_${y}", true)?.xtea(xtea) 146 | } 147 | library.index(5).cache() 148 | ``` 149 | #### Cross cache copying 150 | This is actually done in the above examples. You can copy archives and files from one cache to another. 151 | 152 | #### Generate ukeys 153 | ```kotlin 154 | val exponent = BigInteger(...) 155 | val modulus = BigInteger(...) 156 | val newUkeys = library.generateNewUkeys(exponent, modulus) 157 | ``` 158 | Generate old ukeys for < 600 caches 159 | ```kotlin 160 | val oldUkeys = library.generateOldUkeys() 161 | ``` 162 | #### Cache rebuilding 163 | When you remove an archive from an index, only the reference is being deleted. 164 | The actual data is still accessible (hence why the function is called 'remove' and not 'delete'). 165 | So why is the data not being removed? This has basically to do with how binary files are built. 166 | 167 | If you want to delete something in the middle of a binary file, all data after it has to be shifted to the left. 168 | With other words, whole cache has to be rebuilt in order to delete archive data. 169 | 170 | To do this, you can use the following function: 171 | ```kotlin 172 | //I only recommend this if you deleted a lot of archives and really want to shrink your cache 173 | library.rebuild(File("location/of/new/cache")) 174 | ``` 175 | --- 176 | #### Example (replace musics in a cache with the ones from another cache) 177 | ```kotlin 178 | val cacheFrom = CacheLibrary.create("...") 179 | val cacheTo = CacheLibrary.create("...") 180 | val index = cacheTo.index(6) 181 | cacheFrom.index(6).cache() 182 | index.clear() 183 | index.add(*cacheFrom.index(6).archives()) 184 | index.update() 185 | ``` 186 | --- 187 | Easy, isn't it? 188 | There are plenty more functions you can use, check it out! 189 | 190 | ###### Note: if there are any issue's, please report them [here](https://github.com/Displee/RS2-Cache-Library/issues). 191 | 192 | 193 | ### License 194 | Displee's cache library is open-sourced software licensed under the MIT license. 195 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") 3 | id("com.github.johnrengelman.shadow") 4 | 5 | `maven-publish` 6 | signing 7 | } 8 | 9 | group = "com.displee" 10 | version = "7.3.0" 11 | 12 | description = "A library written in Kotlin used to read and write to all cache formats of RuneScape." 13 | 14 | kotlin { 15 | jvmToolchain(8) 16 | } 17 | 18 | dependencies { 19 | implementation("com.github.jponge:lzma-java:1.3") 20 | implementation("org.apache.ant:ant:1.10.14") 21 | implementation("com.displee:disio:2.2") 22 | } 23 | 24 | java { 25 | withJavadocJar() 26 | withSourcesJar() 27 | } 28 | 29 | val ossrhUsername: String? by project 30 | val ossrhPassword: String? by project 31 | 32 | publishing { 33 | repositories { 34 | maven { 35 | val releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 36 | val snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/" 37 | url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) 38 | credentials { 39 | username = ossrhUsername 40 | password = ossrhPassword 41 | } 42 | } 43 | } 44 | publications { 45 | create("mavenJava") { 46 | from(components["java"]) 47 | 48 | pom { 49 | name = rootProject.name 50 | description = rootProject.description 51 | url = "https://github.com/Displee/rs-cache-library" 52 | packaging = "jar" 53 | licenses { 54 | license { 55 | name = "MIT License" 56 | url = "https://github.com/Displee/rs-cache-library/blob/master/LICENSE" 57 | } 58 | } 59 | developers { 60 | developer { 61 | id = "Displee" 62 | name = "Yassin Amhagi" 63 | email = "displee@hotmail.com" 64 | } 65 | developer { 66 | id = "Greg" 67 | name = "Greg" 68 | email = "greg@gregs.world" 69 | } 70 | } 71 | scm { 72 | connection = "scm:git:git://github.com/Displee/rs-cache-library.git" 73 | developerConnection = "scm:git:ssh://git@github.com/Displee/rs-cache-library.git" 74 | url = "https://github.com/Displee/rs-cache-library" 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | signing { 82 | sign(publishing.publications["mavenJava"]) 83 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Displee/rs-cache-library/b2b2bc3646ab8da4eea5a21d3938f041f45c59c7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "rs-cache-library" 2 | 3 | dependencyResolutionManagement { 4 | @Suppress("UnstableApiUsage") 5 | repositories { 6 | mavenCentral() 7 | gradlePluginPortal() 8 | } 9 | } 10 | 11 | pluginManagement { 12 | plugins { 13 | kotlin("jvm") version "1.9.22" 14 | id("com.github.johnrengelman.shadow") version "8.1.1" 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/cache/CacheLibrary.kt: -------------------------------------------------------------------------------- 1 | package com.displee.cache 2 | 3 | import com.displee.cache.index.Index 4 | import com.displee.cache.index.Index.Companion.INDEX_SIZE 5 | import com.displee.cache.index.Index.Companion.WHIRLPOOL_SIZE 6 | import com.displee.cache.index.Index255 7 | import com.displee.cache.index.Index317 8 | import com.displee.cache.index.ReferenceTable.Companion.FLAG_LENGTHS 9 | import com.displee.cache.index.ReferenceTable.Companion.FLAG_CHECKSUMS 10 | import com.displee.cache.index.ReferenceTable.Companion.FLAG_NAME 11 | import com.displee.cache.index.ReferenceTable.Companion.FLAG_WHIRLPOOL 12 | import com.displee.cache.index.archive.Archive 13 | import com.displee.compress.CompressionType 14 | import com.displee.compress.type.Compressors 15 | import com.displee.io.Buffer 16 | import com.displee.io.impl.OutputBuffer 17 | import com.displee.util.Whirlpool 18 | import com.displee.util.generateWhirlpool 19 | import java.io.File 20 | import java.io.FileNotFoundException 21 | import java.io.IOException 22 | import java.io.RandomAccessFile 23 | import java.math.BigInteger 24 | import java.util.* 25 | 26 | open class CacheLibrary(val path: String, val clearDataAfterUpdate: Boolean = false, private val listener: ProgressListener? = null) { 27 | 28 | lateinit var mainFile: RandomAccessFile 29 | 30 | internal val indices = arrayOfNulls(100) 31 | val compressors = Compressors() 32 | val whirlpool = Whirlpool() 33 | var index255: Index255? = null 34 | private var rs3 = false 35 | 36 | var closed = false 37 | 38 | private val indexCount: Int 39 | get() = indices.indexOf(null) - 1 40 | 41 | init { 42 | init() 43 | } 44 | 45 | private fun init() { 46 | val mainFile317 = File(path, "$CACHE_FILE_NAME.dat") 47 | val index255 = File(path, "$CACHE_FILE_NAME.idx255") 48 | if (mainFile317.exists() && !index255.exists()) { 49 | load317() 50 | } else { 51 | load() 52 | } 53 | } 54 | 55 | /** 56 | * Re-create indices and re-read reference tables. 57 | */ 58 | fun reload() { 59 | indices.fill(null) 60 | init() 61 | } 62 | 63 | @Throws(IOException::class) 64 | private fun load() { 65 | val main = File(path, "$CACHE_FILE_NAME.dat2") 66 | mainFile = if (main.exists()) { 67 | RandomAccessFile(main, "rw") 68 | } else { 69 | listener?.notify(-1.0, "Error, main file could not be found") 70 | throw FileNotFoundException("File[path=${main.absolutePath}] could not be found.") 71 | } 72 | val index255File = File(path, "$CACHE_FILE_NAME.idx255") 73 | if (!index255File.exists()) { 74 | listener?.notify(-1.0, "Error, checksum file could not be found.") 75 | throw FileNotFoundException("File[path=${index255File.absolutePath}] could not be found.") 76 | } 77 | val index255 = Index255(this, RandomAccessFile(index255File, "rw")) 78 | this.index255 = index255 79 | listener?.notify(0.0, "Reading indices...") 80 | val indicesLength = index255.raf.length().toInt() / INDEX_SIZE 81 | rs3 = indicesLength > 39 82 | for (i in 0 until indicesLength) { 83 | val file = File(path, "$CACHE_FILE_NAME.idx$i") 84 | val progress = i / (indicesLength - 1.0) 85 | if (!file.exists()) { 86 | indices[i] = null 87 | listener?.notify(progress, "Could not load index $i, missing idx file.") 88 | continue 89 | } 90 | try { 91 | indices[i] = Index(this, i, RandomAccessFile(file, "rw")) 92 | listener?.notify(progress, "Loaded index $i.") 93 | } catch (e: Exception) { 94 | indices[i] = null 95 | e.printStackTrace() 96 | listener?.notify(progress, "Failed to load index $i.") 97 | } 98 | } 99 | } 100 | 101 | @Throws(IOException::class) 102 | private fun load317() { 103 | val main = File(path, "$CACHE_FILE_NAME.dat") 104 | mainFile = if (main.exists()) { 105 | RandomAccessFile(main, "rw") 106 | } else { 107 | listener?.notify(-1.0, "Error, main file could not be found") 108 | throw FileNotFoundException("File[path=${main.absolutePath}] could not be found.") 109 | } 110 | val indexFiles = File(path).listFiles { _: File, name: String -> 111 | return@listFiles name.startsWith("$CACHE_FILE_NAME.idx") 112 | } 113 | check(indexFiles != null) { "Files are null. Check your cache path." } 114 | listener?.notify(0.0, "Reading indices...") 115 | for (i in indexFiles.indices) { 116 | val file = File(path, "$CACHE_FILE_NAME.idx$i") 117 | val progress = i / (indexFiles.size - 1.0) 118 | if (!file.exists()) { 119 | indices[i] = null 120 | continue 121 | } 122 | try { 123 | indices[i] = Index317(this, i, RandomAccessFile(file, "rw")) 124 | listener?.notify(progress, "Loaded index $i .") 125 | } catch (e: Exception) { 126 | indices[i] = null 127 | e.printStackTrace() 128 | listener?.notify(progress, "Failed to load index $i.") 129 | } 130 | } 131 | } 132 | 133 | @JvmOverloads 134 | fun createIndex(compressionType: CompressionType = CompressionType.GZIP, version: Int = 6, revision: Int = 0, 135 | named: Boolean = false, whirlpool: Boolean = false, lengths: Boolean = false, checksums: Boolean = false, 136 | writeReferenceTable: Boolean = true, id: Int = indexCount + 1): Index { 137 | val raf = RandomAccessFile(File(path, "$CACHE_FILE_NAME.idx$id"), "rw") 138 | val index = (if (is317()) Index317(this, id, raf) else Index(this, id, raf)).also { indices[id] = it } 139 | if (!writeReferenceTable) { 140 | return index 141 | } 142 | index.version = version 143 | index.revision = revision 144 | index.compressionType = compressionType 145 | index.compressor = compressors.get(compressionType) 146 | if (named) { 147 | index.flagMask(FLAG_NAME) 148 | } 149 | if (whirlpool) { 150 | index.flagMask(FLAG_WHIRLPOOL) 151 | } 152 | if (lengths) { 153 | index.flagMask(FLAG_LENGTHS) 154 | } 155 | if (checksums) { 156 | index.flagMask(FLAG_CHECKSUMS) 157 | } 158 | index.flag() 159 | check(index.update()) 160 | return index 161 | } 162 | 163 | fun createIndex(index: Index, writeReferenceTable: Boolean = true): Index { 164 | return createIndex(index.compressionType, index.version, index.revision, 165 | index.isNamed(), index.hasWhirlpool(), index.hasLengths(), index.hasChecksums(), writeReferenceTable, index.id) 166 | } 167 | 168 | fun exists(id: Int): Boolean { 169 | return indices.getOrNull(id) != null 170 | } 171 | 172 | fun index(id: Int): Index { 173 | val index = indices.getOrNull(id) 174 | return checkNotNull(index) { "Index $id doesn't exist. Please use the {@link exists(int) exists} function to verify whether an index exists." } 175 | } 176 | 177 | @JvmOverloads 178 | fun put(index: Int, archive: Int, file: Int, data: ByteArray, xtea: IntArray? = null): com.displee.cache.index.archive.file.File { 179 | return index(index).add(archive, xtea = xtea).add(file, data) 180 | } 181 | 182 | @JvmOverloads 183 | fun put(index: Int, archive: Int, data: ByteArray, xtea: IntArray? = null): Archive { 184 | val currentArchive = index(index).add(archive, -1, xtea) 185 | currentArchive.add(0, data) 186 | return currentArchive 187 | } 188 | 189 | @JvmOverloads 190 | fun put(index: Int, archive: Int, file: String, data: ByteArray, xtea: IntArray? = null): com.displee.cache.index.archive.file.File { 191 | return index(index).add(archive, xtea = xtea).add(file, data) 192 | } 193 | 194 | @JvmOverloads 195 | fun put(index: Int, archive: String, data: ByteArray, xtea: IntArray? = null): Archive { 196 | return put(index, archive, 0, data, xtea) 197 | } 198 | 199 | @JvmOverloads 200 | fun put(index: Int, archive: String, file: Int, data: ByteArray, xtea: IntArray? = null): Archive { 201 | val currentArchive = index(index).add(archive, xtea = xtea) 202 | currentArchive.add(file, data) 203 | return currentArchive 204 | } 205 | 206 | @JvmOverloads 207 | fun put(index: Int, archive: String, file: String, data: ByteArray, xtea: IntArray? = null): com.displee.cache.index.archive.file.File { 208 | return index(index).add(archive, xtea = xtea).add(file, data) 209 | } 210 | 211 | @JvmOverloads 212 | fun data(index: Int, archive: Int, file: Int = 0, xtea: IntArray? = null): ByteArray? { 213 | return index(index).archive(archive, xtea)?.file(file)?.data 214 | } 215 | 216 | @JvmOverloads 217 | fun data(index: Int, archive: Int, file: String, xtea: IntArray? = null): ByteArray? { 218 | return index(index).archive(archive, xtea)?.file(file)?.data 219 | } 220 | 221 | @JvmOverloads 222 | fun data(index: Int, archive: String, file: Int, xtea: IntArray? = null): ByteArray? { 223 | return index(index).archive(archive, xtea)?.file(file)?.data 224 | } 225 | 226 | @JvmOverloads 227 | fun data(index: Int, archive: String, file: String, xtea: IntArray? = null): ByteArray? { 228 | return index(index).archive(archive, xtea)?.file(file)?.data 229 | } 230 | 231 | @JvmOverloads 232 | fun data(index: Int, archive: String, xtea: IntArray? = null): ByteArray? { 233 | return data(index, archive, 0, xtea) 234 | } 235 | 236 | fun remove(index: Int, archive: Int, file: Int): com.displee.cache.index.archive.file.File? { 237 | return index(index).archive(archive)?.remove(file) 238 | } 239 | 240 | fun remove(index: Int, archive: Int, file: String): com.displee.cache.index.archive.file.File? { 241 | return index(index).archive(archive)?.remove(file) 242 | } 243 | 244 | fun remove(index: Int, archive: String, file: String): com.displee.cache.index.archive.file.File? { 245 | return index(index).archive(archive)?.remove(file) 246 | } 247 | 248 | fun remove(index: Int, archive: String, file: Int): com.displee.cache.index.archive.file.File? { 249 | return index(index).archive(archive)?.remove(file) 250 | } 251 | 252 | fun remove(index: Int, archive: Int): Archive? { 253 | return index(index).remove(archive) 254 | } 255 | 256 | fun remove(index: Int, archive: String): Archive? { 257 | return index(index).remove(archive) 258 | } 259 | 260 | fun update() { 261 | for (index in indices) { 262 | if (index == null) { 263 | continue 264 | } 265 | if (index.flaggedArchives().isEmpty() && !index.flagged()) { 266 | continue 267 | } 268 | index.update() 269 | } 270 | } 271 | 272 | @Throws(RuntimeException::class) 273 | fun deleteLastIndex() { 274 | if (is317()) { 275 | throw UnsupportedOperationException("317 not supported to remove indices yet.") 276 | } 277 | val id = indexCount 278 | val index = indices.getOrNull(id) ?: return 279 | index.close() 280 | val file = File(path, "$CACHE_FILE_NAME.idx$id") 281 | if (!file.exists() || !file.delete()) { 282 | throw RuntimeException("Failed to remove the random access file of the argued index[id=$id, file exists=${file.exists()}]") 283 | } 284 | index255?.raf?.setLength(id * INDEX_SIZE.toLong()) 285 | indices[id] = null 286 | } 287 | 288 | @JvmOverloads 289 | fun generateUkeys(writeWhirlpool: Boolean = true, exponent: BigInteger? = null, modulus: BigInteger? = null): ByteArray { 290 | val buffer = OutputBuffer(6 + indexCount * 72) 291 | if (writeWhirlpool) { 292 | buffer.writeByte(indexCount) 293 | } 294 | val emptyWhirlpool = ByteArray(WHIRLPOOL_SIZE) 295 | for (index in indices()) { 296 | buffer.writeInt(index.crc).writeInt(index.revision) 297 | if (writeWhirlpool) { 298 | buffer.writeBytes(index.whirlpool ?: emptyWhirlpool) 299 | } 300 | } 301 | if (writeWhirlpool) { 302 | val indexData = buffer.array() 303 | val whirlpoolBuffer = OutputBuffer(WHIRLPOOL_SIZE + 1) 304 | .writeByte(0) 305 | .writeBytes(indexData.generateWhirlpool(whirlpool, 5, indexData.size - 5)) 306 | if (exponent != null && modulus != null) { 307 | buffer.writeBytes(Buffer.cryptRSA(whirlpoolBuffer.array(), exponent, modulus)) 308 | } 309 | } 310 | return buffer.array() 311 | } 312 | 313 | fun rebuild(directory: File) { 314 | File(directory.path).mkdirs() 315 | if (is317()) { 316 | File(directory.path, "$CACHE_FILE_NAME.dat").createNewFile() 317 | } else { 318 | File(directory.path, "$CACHE_FILE_NAME.idx255").createNewFile() 319 | File(directory.path, "$CACHE_FILE_NAME.dat2").createNewFile() 320 | } 321 | val indicesSize = indices.count { it != null } 322 | val newLibrary = CacheLibrary(directory.path) 323 | for (index in indices) { 324 | if (index == null) { 325 | continue 326 | } 327 | val id = index.id 328 | print("\rBuilding index $id/$indicesSize...") 329 | val archiveSector = index255?.readArchiveSector(id) 330 | var writeReferenceTable = true 331 | if (!is317() && archiveSector == null) { //some empty indices don't even have a reference table 332 | writeReferenceTable = false //in that case, don't write it 333 | } 334 | val newIndex = newLibrary.createIndex(index, writeReferenceTable) 335 | for (i in index.archiveIds()) { //only write referenced archives 336 | val data = index.readArchiveSector(i)?.data ?: continue 337 | newIndex.writeArchiveSector(i, data) 338 | } 339 | if (archiveSector != null) { 340 | newLibrary.index255?.writeArchiveSector(id, archiveSector.data) 341 | } 342 | } 343 | newLibrary.close() 344 | println("\rFinished building $indicesSize indices.") 345 | } 346 | 347 | fun fixCrcs(update: Boolean) { 348 | for(index in indices) { 349 | if (index == null || index.archiveIds().isEmpty()) { 350 | continue 351 | } 352 | index.fixCRCs(update) 353 | } 354 | } 355 | 356 | fun close() { 357 | if (closed) { 358 | return 359 | } 360 | mainFile.close() 361 | index255?.close() 362 | for (index in indices) { 363 | index?.close() 364 | } 365 | closed = true 366 | } 367 | 368 | fun first(): Index? { 369 | return indices[0] 370 | } 371 | 372 | fun last(): Index? { 373 | if (indices.isEmpty()) { 374 | return null 375 | } 376 | return indices.getOrNull(indices.lastIndex) 377 | } 378 | 379 | fun is317(): Boolean { 380 | return index255 == null 381 | } 382 | 383 | fun isOSRS(): Boolean { 384 | val index = index(2) 385 | return index.revision >= 300 && indexCount <= 23 386 | } 387 | 388 | fun isRS3(): Boolean { 389 | return rs3 390 | } 391 | 392 | fun indices(): Array { 393 | return indices.filterNotNull().toTypedArray() 394 | } 395 | 396 | companion object { 397 | const val CACHE_FILE_NAME = "main_file_cache" 398 | 399 | @JvmStatic 400 | @JvmOverloads 401 | fun create(path: String, clearDataAfterUpdate: Boolean = false, listener: ProgressListener? = null): CacheLibrary { 402 | return CacheLibrary(path, clearDataAfterUpdate, listener) 403 | } 404 | } 405 | 406 | } 407 | -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/cache/ProgressListener.kt: -------------------------------------------------------------------------------- 1 | package com.displee.cache 2 | 3 | interface ProgressListener { 4 | fun notify(progress: Double, message: String?) 5 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/cache/index/Index.kt: -------------------------------------------------------------------------------- 1 | package com.displee.cache.index 2 | 3 | import com.displee.cache.CacheLibrary 4 | import com.displee.cache.ProgressListener 5 | import com.displee.cache.index.archive.Archive 6 | import com.displee.cache.index.archive.ArchiveSector 7 | import com.displee.compress.CompressionType 8 | import com.displee.compress.compress 9 | import com.displee.compress.decompress 10 | import com.displee.compress.type.Compressor 11 | import com.displee.compress.type.EmptyCompressor 12 | import com.displee.io.impl.InputBuffer 13 | import com.displee.io.impl.OutputBuffer 14 | import com.displee.util.generateCrc 15 | import com.displee.util.generateWhirlpool 16 | import java.io.RandomAccessFile 17 | 18 | open class Index(origin: CacheLibrary, id: Int, val raf: RandomAccessFile) : ReferenceTable(origin, id) { 19 | 20 | var crc = 0 21 | var whirlpool: ByteArray? = null 22 | var compressionType: CompressionType = CompressionType.NONE 23 | var compressor: Compressor = EmptyCompressor 24 | private var cached = false 25 | protected var closed = false 26 | protected var autoUpdateRevision = true 27 | 28 | val info get() = "Index[id=" + id + ", archives=" + archives.size + ", compression=" + compressionType + "]" 29 | 30 | init { 31 | init() 32 | } 33 | 34 | protected open fun init() { 35 | if (id < 0 || id >= 255) { 36 | return 37 | } 38 | val archiveSector = origin.index255?.readArchiveSector(id) ?: return 39 | val archiveSectorData = archiveSector.data 40 | crc = archiveSectorData.generateCrc() 41 | whirlpool = archiveSectorData.generateWhirlpool(origin.whirlpool) 42 | read(InputBuffer(archiveSector.decompress(origin.compressors))) 43 | compressionType = archiveSector.compressionType 44 | compressor = archiveSector.compressor 45 | } 46 | 47 | fun cache() { 48 | check(!closed) { "Index is closed." } 49 | if (cached) { 50 | return 51 | } 52 | archives.values.forEach { 53 | try { 54 | archive(it.id, it.xtea, false) 55 | } catch (t: Throwable) { 56 | t.printStackTrace() 57 | } 58 | } 59 | cached = true 60 | } 61 | 62 | fun unCache() { 63 | for (archive in archives()) { 64 | archive.restore() 65 | } 66 | cached = false 67 | } 68 | 69 | @JvmOverloads 70 | open fun update(listener: ProgressListener? = null): Boolean { 71 | check(!closed) { "Index is closed." } 72 | val flaggedArchives = flaggedArchives() 73 | var i = 0.0 74 | flaggedArchives.forEach { 75 | i++ 76 | if (it.autoUpdateRevision) { 77 | it.revision++ 78 | } 79 | it.unFlag() 80 | listener?.notify((i / flaggedArchives.size) * 0.80, "Repacking archive ${it.id}...") 81 | val compressed = it.write().compress(it.compressionType, it.compressor, it.xtea, it.revision) 82 | it.crc = compressed.generateCrc(length = compressed.size - 2) 83 | it.whirlpool = compressed.generateWhirlpool(origin.whirlpool, length = compressed.size - 2) 84 | val written = writeArchiveSector(it.id, compressed) 85 | check(written) { "Unable to write data to archive sector. Your cache may be corrupt." } 86 | if (origin.clearDataAfterUpdate) { 87 | it.restore() 88 | } 89 | } 90 | listener?.notify(0.85, "Updating checksum table for index $id...") 91 | if (flaggedArchives.isNotEmpty() && !flagged()) { 92 | flag() 93 | } 94 | if (flagged()) { 95 | unFlag() 96 | if (autoUpdateRevision) { 97 | revision++ 98 | } 99 | val indexData = write().compress(compressionType, compressor) 100 | crc = indexData.generateCrc() 101 | whirlpool = indexData.generateWhirlpool(origin.whirlpool) 102 | val written = origin.index255?.writeArchiveSector(this.id, indexData) ?: false 103 | check(written) { "Unable to write data to checksum table. Your cache may be corrupt." } 104 | } 105 | listener?.notify(1.0, "Successfully updated index $id.") 106 | return true 107 | } 108 | 109 | fun readArchiveSector(id: Int): ArchiveSector? { 110 | check(!closed) { "Index is closed." } 111 | synchronized(origin.mainFile) { 112 | try { 113 | if (origin.mainFile.length() < INDEX_SIZE * id + INDEX_SIZE) { 114 | return null 115 | } 116 | val sectorData = ByteArray(SECTOR_SIZE) 117 | raf.seek(id.toLong() * INDEX_SIZE) 118 | raf.read(sectorData, 0, INDEX_SIZE) 119 | val bigSector = id > 65535 120 | val buffer = InputBuffer(sectorData) 121 | val archiveSector = ArchiveSector(bigSector, buffer.read24BitInt(), buffer.read24BitInt()) 122 | if (archiveSector.size < 0 || archiveSector.position <= 0 || archiveSector.position > origin.mainFile.length() / SECTOR_SIZE) { 123 | return null 124 | } 125 | var read = 0 126 | var chunk = 0 127 | val sectorHeaderSize = if (bigSector) SECTOR_HEADER_SIZE_BIG else SECTOR_HEADER_SIZE_SMALL 128 | val sectorDataSize = if (bigSector) SECTOR_DATA_SIZE_BIG else SECTOR_DATA_SIZE_SMALL 129 | while (read < archiveSector.size) { 130 | if (archiveSector.position == 0) { 131 | return null 132 | } 133 | var requiredToRead = archiveSector.size - read 134 | if (requiredToRead > sectorDataSize) { 135 | requiredToRead = sectorDataSize 136 | } 137 | origin.mainFile.seek(archiveSector.position.toLong() * SECTOR_SIZE) 138 | origin.mainFile.read(buffer.raw(), 0, requiredToRead + sectorHeaderSize) 139 | buffer.offset = 0 140 | archiveSector.read(buffer) 141 | if (!isIndexValid(archiveSector.index) || id != archiveSector.id || chunk != archiveSector.chunk) { 142 | return null 143 | } else if (archiveSector.nextPosition < 0 || archiveSector.nextPosition > origin.mainFile.length() / SECTOR_SIZE) { 144 | return null 145 | } 146 | val bufferData = buffer.raw() 147 | for (i in 0 until requiredToRead) { 148 | archiveSector.data[read++] = bufferData[i + sectorHeaderSize] 149 | } 150 | archiveSector.position = archiveSector.nextPosition 151 | chunk++ 152 | } 153 | return archiveSector 154 | } catch (exception: Exception) { 155 | return null 156 | } 157 | } 158 | } 159 | 160 | fun writeArchiveSector(id: Int, data: ByteArray): Boolean { 161 | check(!closed) { "Index is closed." } 162 | synchronized(origin.mainFile) { 163 | return try { 164 | var position: Int 165 | var archive: Archive? = null 166 | var archiveSector: ArchiveSector? = readArchiveSector(id) 167 | if (this.id != 255) { 168 | archive = archive(id, null, true) 169 | } 170 | var overWrite = this.id == 255 && archiveSector != null || archive?.new == false 171 | val sectorData = ByteArray(SECTOR_SIZE) 172 | val bigSector = id > 65535 173 | if (overWrite) { 174 | if (INDEX_SIZE * id + INDEX_SIZE > raf.length()) { 175 | return false 176 | } 177 | raf.seek(id.toLong() * INDEX_SIZE) 178 | raf.read(sectorData, 0, INDEX_SIZE) 179 | val buffer = InputBuffer(sectorData) 180 | buffer.offset += 3 181 | position = buffer.read24BitInt() 182 | if (position <= 0 || position > origin.mainFile.length() / SECTOR_SIZE) { 183 | return false 184 | } 185 | } else { 186 | position = ((origin.mainFile.length() + (SECTOR_SIZE - 1)) / SECTOR_SIZE).toInt() 187 | if (position == 0) { 188 | position = 1 189 | } 190 | archiveSector = ArchiveSector(bigSector, data.size, position, id, indexToWrite(this.id)) 191 | } 192 | archiveSector ?: return false 193 | val buffer = OutputBuffer(6) 194 | buffer.write24BitInt(data.size) 195 | buffer.write24BitInt(position) 196 | raf.seek(id.toLong() * INDEX_SIZE) 197 | raf.write(buffer.array(), 0, INDEX_SIZE) 198 | var written = 0 199 | var chunk = 0 200 | val archiveHeaderSize = if (bigSector) SECTOR_HEADER_SIZE_BIG else SECTOR_HEADER_SIZE_SMALL 201 | val archiveDataSize = if (bigSector) SECTOR_DATA_SIZE_BIG else SECTOR_DATA_SIZE_SMALL 202 | while (written < data.size) { 203 | var currentPosition = 0 204 | if (overWrite) { 205 | origin.mainFile.seek(position.toLong() * SECTOR_SIZE) 206 | origin.mainFile.read(sectorData, 0, archiveHeaderSize) 207 | archiveSector.read(InputBuffer(sectorData)) 208 | currentPosition = archiveSector.nextPosition 209 | if (archiveSector.id != id || archiveSector.chunk != chunk || !isIndexValid(archiveSector.index)) { 210 | return false 211 | } 212 | if (currentPosition < 0 || origin.mainFile.length() / SECTOR_SIZE < currentPosition) { 213 | return false 214 | } 215 | } 216 | if (currentPosition == 0) { 217 | overWrite = false 218 | currentPosition = ((origin.mainFile.length() + (SECTOR_SIZE - 1)) / SECTOR_SIZE).toInt() 219 | if (currentPosition == 0) { 220 | currentPosition++ 221 | } 222 | if (currentPosition == position) { 223 | currentPosition++ 224 | } 225 | } 226 | if (data.size - written <= archiveDataSize) { 227 | currentPosition = 0 228 | } 229 | archiveSector.chunk = chunk 230 | archiveSector.position = currentPosition 231 | origin.mainFile.seek(position.toLong() * SECTOR_SIZE) 232 | origin.mainFile.write(archiveSector.write(), 0, archiveHeaderSize) 233 | var length = data.size - written 234 | if (length > archiveDataSize) { 235 | length = archiveDataSize 236 | } 237 | origin.mainFile.write(data, written, length) 238 | written += length 239 | position = currentPosition 240 | chunk++ 241 | } 242 | true 243 | } catch (t: Throwable) { 244 | t.printStackTrace() 245 | false 246 | } 247 | } 248 | } 249 | 250 | fun fixCRCs(update: Boolean) { 251 | check(!closed) { "Index is closed." } 252 | if (is317()) { 253 | return 254 | } 255 | val archiveIds = archiveIds() 256 | var flag = false 257 | for (i in archiveIds) { 258 | val sector = readArchiveSector(i) ?: continue 259 | val correctCRC = sector.data.generateCrc(length = sector.data.size - 2) 260 | val archive = archive(i) ?: continue 261 | val currentCRC = archive.crc 262 | if (currentCRC == correctCRC) { 263 | continue 264 | } 265 | println("Incorrect CRC in index $id -> archive $i, current_crc=$currentCRC, correct_crc=$correctCRC") 266 | archive.flag() 267 | flag = true 268 | } 269 | val sectorData = origin.index255?.readArchiveSector(id)?.data ?: return 270 | val indexCRC = sectorData.generateCrc() 271 | if (crc != indexCRC) { 272 | flag = true 273 | } 274 | if (flag && update) { 275 | update() 276 | } else if (!flag) { 277 | println("No invalid CRCs found.") 278 | return 279 | } 280 | unCache() 281 | } 282 | 283 | /** 284 | * Clear the archives. 285 | */ 286 | fun clear() { 287 | archives.clear() 288 | crc = 0 289 | whirlpool = ByteArray(WHIRLPOOL_SIZE) 290 | } 291 | 292 | fun close() { 293 | if (closed) { 294 | return 295 | } 296 | raf.close() 297 | closed = true 298 | } 299 | 300 | fun flaggedArchives(): Array { 301 | return archives.values.filter { it.flagged() }.toTypedArray() 302 | } 303 | 304 | protected open fun isIndexValid(index: Int): Boolean { 305 | return this.id == index 306 | } 307 | 308 | protected open fun indexToWrite(index: Int): Int { 309 | return index 310 | } 311 | 312 | override fun toString(): String { 313 | return "Index $id" 314 | } 315 | 316 | companion object { 317 | const val INDEX_SIZE = 6 318 | const val SECTOR_HEADER_SIZE_SMALL = 8 319 | const val SECTOR_DATA_SIZE_SMALL = 512 320 | const val SECTOR_HEADER_SIZE_BIG = 10 321 | const val SECTOR_DATA_SIZE_BIG = 510 322 | const val SECTOR_SIZE = 520 323 | const val WHIRLPOOL_SIZE = 64 324 | } 325 | 326 | } 327 | -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/cache/index/Index255.kt: -------------------------------------------------------------------------------- 1 | package com.displee.cache.index 2 | 3 | import com.displee.cache.CacheLibrary 4 | import com.displee.cache.ProgressListener 5 | import java.io.RandomAccessFile 6 | 7 | class Index255(origin: CacheLibrary, raf: RandomAccessFile) : Index(origin, 255, raf) { 8 | 9 | override fun update(listener: ProgressListener?): Boolean { 10 | throw IllegalAccessException("Not allowed.") 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/cache/index/Index317.kt: -------------------------------------------------------------------------------- 1 | package com.displee.cache.index 2 | 3 | import com.displee.cache.CacheLibrary 4 | import com.displee.cache.ProgressListener 5 | import com.displee.cache.index.archive.Archive 6 | import com.displee.cache.index.archive.Archive317 7 | import com.displee.io.impl.InputBuffer 8 | import com.displee.io.impl.OutputBuffer 9 | import com.displee.util.generateCrc 10 | import com.displee.util.generateWhirlpool 11 | import com.displee.util.hashCode317 12 | import java.io.IOException 13 | import java.io.RandomAccessFile 14 | import java.util.* 15 | 16 | class Index317(origin: CacheLibrary, id: Int, raf: RandomAccessFile) : Index(origin, id, raf) { 17 | 18 | override fun init() { 19 | val archiveLength = try { 20 | (raf.length() / INDEX_SIZE.toLong()).toInt() 21 | } catch (e: IOException) { 22 | e.printStackTrace() 23 | return 24 | } 25 | var versions: IntArray? = null 26 | var crcs: IntArray? = null 27 | if (id != CONFIG_INDEX && id < VERSION_FILES.size) { 28 | versions = readArchiveProperties(VERSION_FILES[id - 1], BufferType.SHORT) 29 | crcs = readArchiveProperties(CRC_FILES[id - 1], BufferType.INT) 30 | } 31 | for (i in 0 until archiveLength) { 32 | readArchiveSector(i) ?: continue 33 | val archive = Archive317(origin.compressors.bzip2, i) 34 | archives[i] = archive 35 | if (versions == null || crcs == null || i >= versions.size || i >= crcs.size) { 36 | continue 37 | } 38 | archive.revision = versions[i] 39 | archive.crc = crcs[i] 40 | } 41 | } 42 | 43 | override fun read(buffer: InputBuffer) { 44 | 45 | } 46 | 47 | override fun write(): ByteArray { 48 | return byteArrayOf() 49 | } 50 | 51 | override fun update(listener: ProgressListener?): Boolean { 52 | check(!closed) { "Index is closed." } 53 | val flaggedArchives = flaggedArchives() 54 | val archives = archives() 55 | var i = 0.0 56 | flaggedArchives.forEach { 57 | i++ 58 | if (it.autoUpdateRevision) { 59 | it.revision++ 60 | } 61 | it.unFlag() 62 | listener?.notify((i / flaggedArchives.size) * 0.80, "Repacking archive ${it.id}...") 63 | val compressed = it.write() 64 | it.crc = compressed.generateCrc() 65 | it.whirlpool = compressed.generateWhirlpool(origin.whirlpool) 66 | val written = writeArchiveSector(it.id, compressed) 67 | check(written) { "Unable to write data to archive sector. Your cache may be corrupt." } 68 | if (origin.clearDataAfterUpdate) { 69 | it.restore() 70 | } 71 | } 72 | listener?.notify(0.85, "Updating version archives for index $id...") 73 | if (flaggedArchives.isNotEmpty() && !flagged()) { 74 | flag() 75 | } 76 | if (id != CONFIG_INDEX && flagged()) { 77 | writeArchiveProperties(Arrays.stream(archives).mapToInt(Archive::revision).toArray(), VERSION_FILES[id - 1], BufferType.SHORT) 78 | writeArchiveProperties(Arrays.stream(archives).mapToInt(Archive::crc).toArray(), CRC_FILES[id - 1], BufferType.INT) 79 | } 80 | listener?.notify(1.0, "Successfully updated index $id.") 81 | return true 82 | } 83 | 84 | private fun readArchiveProperties(fileId: String, type: BufferType): IntArray? { 85 | if (id == CONFIG_INDEX || id > VERSION_FILES.size) { 86 | return null 87 | } 88 | val data = origin.index(CONFIG_INDEX).archive(VERSION_ARCHIVE)?.file(fileId)?.data ?: return null 89 | val buffer = InputBuffer(data) 90 | val properties = IntArray(data.size / (1 shl type.ordinal)) 91 | val bufferFun: () -> Int = when (type) { 92 | BufferType.BYTE -> { 93 | { buffer.readUnsignedByte() } 94 | } 95 | BufferType.SHORT -> { 96 | { buffer.readUnsignedShort() } 97 | } 98 | BufferType.INT -> { 99 | { buffer.readInt() } 100 | } 101 | } 102 | for (i in properties.indices) { 103 | properties[i] = bufferFun() 104 | } 105 | return properties 106 | } 107 | 108 | private fun writeArchiveProperties(properties: IntArray, fileId: String, type: BufferType): Boolean { 109 | if (id == CONFIG_INDEX || id > VERSION_FILES.size) { 110 | return false 111 | } 112 | val buffer = OutputBuffer(properties.size) 113 | val bufferFun: (Int) -> Unit = when (type) { 114 | BufferType.BYTE -> { 115 | { buffer.writeByte(it) } 116 | } 117 | BufferType.SHORT -> { 118 | { buffer.writeShort(it) } 119 | } 120 | BufferType.INT -> { 121 | { buffer.writeInt(it) } 122 | } 123 | } 124 | properties.forEach(bufferFun) 125 | val index = origin.index(CONFIG_INDEX) 126 | index.archive(VERSION_ARCHIVE)?.add(fileId, buffer.array()) 127 | return index.update() 128 | } 129 | 130 | override fun toHash(name: String): Int { 131 | return name.hashCode317() 132 | } 133 | 134 | override fun isIndexValid(index: Int): Boolean { 135 | return id == index - 1 136 | } 137 | 138 | override fun indexToWrite(index: Int): Int { 139 | return index + 1 140 | } 141 | 142 | companion object { 143 | private const val CONFIG_INDEX = 0 144 | 145 | private const val VERSION_ARCHIVE = 5 146 | 147 | private val VERSION_FILES = mutableListOf("model_version", "anim_version", "midi_version", "map_version") 148 | private val CRC_FILES = mutableListOf("model_crc", "anim_crc", "midi_crc", "map_crc") 149 | 150 | @JvmStatic 151 | fun addMetaFiles(versionFile: String, crcFile: String) { 152 | VERSION_FILES.add(versionFile) 153 | CRC_FILES.add(crcFile) 154 | } 155 | } 156 | 157 | private enum class BufferType { 158 | BYTE, 159 | SHORT, 160 | INT 161 | } 162 | 163 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/cache/index/ReferenceTable.kt: -------------------------------------------------------------------------------- 1 | package com.displee.cache.index 2 | 3 | import com.displee.cache.CacheLibrary 4 | import com.displee.cache.index.Index.Companion.WHIRLPOOL_SIZE 5 | import com.displee.cache.index.archive.Archive 6 | import com.displee.cache.index.archive.Archive317 7 | import com.displee.cache.index.archive.file.File 8 | import com.displee.compress.CompressionType 9 | import com.displee.compress.decompress 10 | import com.displee.io.impl.InputBuffer 11 | import com.displee.io.impl.OutputBuffer 12 | import java.util.* 13 | import kotlin.collections.ArrayList 14 | 15 | open class ReferenceTable(protected val origin: CacheLibrary, val id: Int) : Comparable { 16 | 17 | var revision = 0 18 | private var mask = 0x0 19 | private var needUpdate = false 20 | protected var archives: SortedMap = TreeMap() 21 | private var archiveNames = mutableListOf() 22 | 23 | var version = 0 24 | 25 | override fun compareTo(other: ReferenceTable): Int { 26 | return id.compareTo(other.id) 27 | } 28 | 29 | open fun read(buffer: InputBuffer) { 30 | version = buffer.readUnsignedByte() 31 | if (version < 5 || version > 7) { 32 | throw RuntimeException("Unknown version: $version") 33 | } 34 | revision = if (version >= 6) buffer.readInt() else 0 35 | mask = buffer.readByte().toInt() 36 | val named = mask and FLAG_NAME != 0 37 | val whirlpool = mask and FLAG_WHIRLPOOL != 0 38 | val lengths = mask and FLAG_LENGTHS != 0 39 | val checksums = mask and FLAG_CHECKSUMS != 0 40 | 41 | val readFun: () -> (Int) = if (version >= 7) { 42 | { 43 | buffer.readBigSmart() 44 | } 45 | } else { 46 | { 47 | buffer.readUnsignedShort() 48 | } 49 | } 50 | 51 | val archiveIds = IntArray(readFun()) 52 | for (i in archiveIds.indices) { 53 | val archiveId = readFun() + if (i == 0) 0 else archiveIds[i - 1] 54 | archiveIds[i] = archiveId.also { archives[i] = Archive(it) } 55 | } 56 | val archives = archives() 57 | archiveNames = ArrayList(archives.size) 58 | if (named) { 59 | archives.forEach { 60 | it.hashName = buffer.readInt() 61 | if (it.hashName != 0) { 62 | archiveNames.add(it.hashName) 63 | } 64 | } 65 | } 66 | archives.forEach { it.crc = buffer.readInt() } 67 | if (checksums) { 68 | archives.forEach { it.checksum = buffer.readInt() } 69 | } 70 | if (whirlpool) { 71 | archives.forEach { 72 | var archiveWhirlpool = it.whirlpool 73 | if (archiveWhirlpool == null) { 74 | archiveWhirlpool = ByteArray(WHIRLPOOL_SIZE) 75 | it.whirlpool = archiveWhirlpool 76 | } 77 | buffer.readBytes(archiveWhirlpool) 78 | } 79 | } 80 | if (lengths) { 81 | archives.forEach { 82 | it.length = buffer.readInt() 83 | it.uncompressedLength = buffer.readInt() 84 | } 85 | } 86 | archives.forEach { it.revision = buffer.readInt() } 87 | val archiveFileSizes = IntArray(archives.size) 88 | for (i in archives.indices) { 89 | archiveFileSizes[i] = readFun() 90 | } 91 | for (i in archives.indices) { 92 | val archive = archives[i] 93 | var fileId = 0 94 | for (fileIndex in 0 until archiveFileSizes[i]) { 95 | fileId += readFun() 96 | archive.files[fileId] = File(fileId) 97 | } 98 | } 99 | if (named) { 100 | for (i in archives.indices) { 101 | val archive = archives[i] 102 | val fileIds = archive.fileIds() 103 | for (fileIndex in 0 until archiveFileSizes[i]) { 104 | archive.file(fileIds[fileIndex])?.hashName = buffer.readInt() 105 | } 106 | } 107 | } 108 | 109 | this.archives.clear() 110 | archives.forEach { this.archives.putIfAbsent(it.id, it) } 111 | } 112 | 113 | open fun write(): ByteArray { 114 | val buffer = OutputBuffer(10_000_000) //10mb 115 | buffer.writeByte(version) 116 | if (version >= 6) { 117 | buffer.writeInt(revision) 118 | } 119 | buffer.writeByte(mask) 120 | 121 | val writeFun: (Int) -> Unit = if (version >= 7) { 122 | { 123 | buffer.writeBigSmart(it) 124 | } 125 | } else { 126 | { 127 | buffer.writeShort(it) 128 | } 129 | } 130 | 131 | writeFun(archives.size) 132 | val archiveIds = archiveIds() 133 | val archives = archives() 134 | for (i in archives.indices) { 135 | writeFun(archiveIds[i] - if (i == 0) 0 else archiveIds[i - 1]) 136 | } 137 | if (isNamed()) { 138 | archives.forEach { buffer.writeInt(it.hashName) } 139 | } 140 | archives.forEach { buffer.writeInt(it.crc) } 141 | if (hasChecksums()) { 142 | archives.forEach { buffer.writeInt(it.checksum) } 143 | } 144 | if (hasWhirlpool()) { 145 | val empty = ByteArray(WHIRLPOOL_SIZE) 146 | archives.forEach { buffer.writeBytes(it.whirlpool ?: empty) } 147 | } 148 | if (hasLengths()) { 149 | archives.forEach { buffer.writeInt(it.length).writeInt(it.uncompressedLength) } 150 | } 151 | archives.forEach { buffer.writeInt(it.revision) } 152 | archives.forEach { writeFun(it.files.size) } 153 | archives.forEach { 154 | val fileIds = it.fileIds() 155 | for (fileIndex in fileIds.indices) { 156 | writeFun(fileIds[fileIndex] - if (fileIndex == 0) 0 else fileIds[fileIndex - 1]) 157 | } 158 | } 159 | if (isNamed()) { 160 | archives.forEach { archive -> 161 | archive.files().forEach { file -> 162 | buffer.writeInt(file.hashName) 163 | } 164 | } 165 | } 166 | return buffer.array() 167 | } 168 | 169 | @JvmOverloads 170 | fun add(data: ByteArray, xtea: IntArray? = null): Archive { 171 | val archive = add(xtea = xtea) 172 | archive.add(data) 173 | return archive 174 | } 175 | 176 | fun add(vararg archives: Archive?, overwrite: Boolean = true): Array { 177 | val newArchives = ArrayList(archives.size) 178 | archives.forEach { 179 | it ?: return@forEach 180 | newArchives.add(add(it, overwrite = overwrite)) 181 | } 182 | return newArchives.toTypedArray() 183 | } 184 | 185 | @JvmOverloads 186 | fun add(archive: Archive, newId: Int = archive.id, xtea: IntArray? = null, overwrite: Boolean = true): Archive { 187 | val new = add(newId, archive.hashName, xtea, overwrite) 188 | if (overwrite) { 189 | new.clear() 190 | new.add(*archive.copyFiles()) 191 | new.flag() 192 | } 193 | return new 194 | } 195 | 196 | @JvmOverloads 197 | fun add(name: String? = null, xtea: IntArray? = null): Archive { 198 | var id = if (name == null) nextId() else archiveId(name) 199 | if (id == -1) { 200 | id = nextId() 201 | } 202 | return add(id, toHash(name ?: ""), xtea) 203 | } 204 | 205 | @JvmOverloads 206 | fun add(id: Int, hashName: Int = -1, xtea: IntArray? = null, overwrite: Boolean = true): Archive { 207 | var existing = archive(id, direct = true) 208 | if (existing != null && !existing.read && !existing.new && !existing.flagged()) { 209 | existing = archive(id, xtea) 210 | } 211 | if (existing == null) { 212 | if (this is Index317) { 213 | existing = Archive317(origin.compressors.bzip2, id, if (hashName == -1) 0 else hashName) 214 | if (this.id != 0) { 215 | existing.compressionType = CompressionType.GZIP 216 | existing.compressor = origin.compressors.gzip 217 | } 218 | } else { 219 | existing = Archive(id, if (hashName == -1) 0 else hashName, xtea) 220 | existing.compressionType = CompressionType.GZIP 221 | existing.compressor = origin.compressors.gzip 222 | } 223 | if (hashName != -1) { 224 | archiveNames.add(existing.hashName) 225 | } 226 | archives[id] = existing 227 | existing.new = true 228 | existing.flag() 229 | flag() 230 | } else if (overwrite) { 231 | var flag = false 232 | val existingXtea = existing.xtea 233 | if (xtea == null && existingXtea != null || xtea != null && existingXtea == null || 234 | xtea != null && existingXtea != null && !xtea.contentEquals(existingXtea)) { 235 | existing.xtea = xtea 236 | flag = true 237 | } 238 | if (hashName != -1 && existing.hashName != hashName) { 239 | archiveNames.remove(existing.hashName) 240 | existing.hashName = hashName 241 | archiveNames.add(existing.hashName) 242 | flag = true 243 | } 244 | if (flag) { 245 | existing.flag() 246 | flag() 247 | } 248 | } 249 | return existing 250 | } 251 | 252 | fun archive(name: String, direct: Boolean = false): Archive? { 253 | return archive(archiveId(name), direct) 254 | } 255 | 256 | @JvmOverloads 257 | fun archive(name: String, xtea: IntArray? = null, direct: Boolean = false): Archive? { 258 | return archive(archiveId(name), xtea, direct) 259 | } 260 | 261 | fun archive(id: Int, direct: Boolean = false): Archive? { 262 | return archive(id, null, direct) 263 | } 264 | 265 | @JvmOverloads 266 | open fun archive(id: Int, xtea: IntArray? = null, direct: Boolean = false): Archive? { 267 | check(!origin.closed) { "Cache is closed." } 268 | val archive = archives[id] ?: return null 269 | if (direct || archive.read || archive.new) { 270 | return archive 271 | } 272 | val sector = origin.index(this.id).readArchiveSector(id) 273 | if (sector == null) { 274 | archive.read = true 275 | archive.new = true 276 | archive.clear() 277 | } else { 278 | val is317 = is317() 279 | if (is317) { 280 | archive.compressionType = if (this.id == 0) CompressionType.BZIP2 else CompressionType.GZIP 281 | archive.compressor = origin.compressors.get(archive.compressionType) 282 | archive.read(InputBuffer(sector.data)) 283 | } else { 284 | val decompressed = sector.decompress(origin.compressors, xtea) 285 | archive.compressionType = sector.compressionType 286 | archive.compressor = sector.compressor 287 | if (decompressed.isNotEmpty()) { 288 | archive.read(InputBuffer(decompressed)) 289 | archive.xtea = xtea 290 | } 291 | } 292 | val mapsIndex = if (is317) 4 else 5 293 | if (this.id == mapsIndex && !archive.containsData()) { 294 | archive.read = false 295 | } 296 | if (!is317) { 297 | val sectorBuffer = InputBuffer(sector.data) 298 | sectorBuffer.offset = 1 299 | val remaining: Int = sector.data.size - (sectorBuffer.readInt() + sectorBuffer.offset) 300 | if (remaining >= 2) { 301 | sectorBuffer.offset = sector.data.size - 2 302 | archive.revision = sectorBuffer.readUnsignedShort() 303 | } 304 | } 305 | } 306 | return archive 307 | } 308 | 309 | fun contains(id: Int): Boolean { 310 | return archives.containsKey(id) 311 | } 312 | 313 | fun contains(name: String): Boolean { 314 | return archiveNames.contains(toHash(name)) 315 | } 316 | 317 | fun remove(id: Int): Archive? { 318 | val archive = archives.remove(id) ?: return null 319 | archiveNames.remove(archive.hashName) 320 | flag() 321 | return archive 322 | } 323 | 324 | fun remove(name: String): Archive? { 325 | return remove(archiveId(name)) 326 | } 327 | 328 | fun first(): Archive? { 329 | if (archives.isEmpty()) { 330 | return null 331 | } 332 | return archive(archives.firstKey()) 333 | } 334 | 335 | fun last(): Archive? { 336 | if (archives.isEmpty()) { 337 | return null 338 | } 339 | return archive(archives.lastKey()) 340 | } 341 | 342 | fun archiveId(name: String): Int { 343 | val hashName = toHash(name) 344 | archives.values.forEach { 345 | if (it.hashName == hashName) { 346 | return it.id 347 | } 348 | } 349 | return -1 350 | } 351 | 352 | fun nextId(): Int { 353 | val last = last() 354 | return if (last == null) 0 else last.id + 1 355 | } 356 | 357 | fun copyArchives(): Array { 358 | val archives = archives() 359 | val copy = ArrayList(archives.size) 360 | for (i in archives.indices) { 361 | copy.add(i, Archive(archives[i])) 362 | } 363 | return copy.toTypedArray() 364 | } 365 | 366 | fun flag() { 367 | needUpdate = true 368 | } 369 | 370 | fun flagged(): Boolean { 371 | return needUpdate 372 | } 373 | 374 | fun unFlag() { 375 | needUpdate = false 376 | } 377 | 378 | fun mask(referenceTable: ReferenceTable) { 379 | this.mask = referenceTable.mask 380 | } 381 | 382 | fun flagMask(flag: Int) { 383 | mask = mask or flag 384 | } 385 | 386 | fun unFlagMask(flag: Int) { 387 | mask = mask and flag.inv() 388 | } 389 | 390 | fun isNamed(): Boolean { 391 | return mask and FLAG_NAME != 0 392 | } 393 | 394 | fun hasWhirlpool(): Boolean { 395 | return mask and FLAG_WHIRLPOOL != 0 396 | } 397 | 398 | fun hasLengths(): Boolean { 399 | return mask and FLAG_LENGTHS != 0 400 | } 401 | 402 | fun hasChecksums(): Boolean { 403 | return mask and FLAG_CHECKSUMS != 0 404 | } 405 | 406 | fun archiveIds(): IntArray { 407 | return archives.keys.toIntArray() 408 | } 409 | 410 | fun archives(): Array { 411 | return archives.values.toTypedArray() 412 | } 413 | 414 | open fun toHash(name: String): Int { 415 | return name.hashCode() 416 | } 417 | 418 | fun is317(): Boolean { 419 | return this is Index317 420 | } 421 | 422 | companion object { 423 | const val FLAG_NAME = 0x1 424 | const val FLAG_WHIRLPOOL = 0x2 425 | const val FLAG_LENGTHS = 0x4 426 | const val FLAG_CHECKSUMS = 0x8 427 | } 428 | 429 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/cache/index/archive/Archive.kt: -------------------------------------------------------------------------------- 1 | package com.displee.cache.index.archive 2 | 3 | import com.displee.cache.index.archive.file.File 4 | import com.displee.compress.CompressionType 5 | import com.displee.compress.type.Compressor 6 | import com.displee.compress.type.EmptyCompressor 7 | import com.displee.compress.type.GZIPCompressor 8 | import com.displee.io.impl.InputBuffer 9 | import com.displee.io.impl.OutputBuffer 10 | import java.util.* 11 | 12 | open class Archive(val id: Int, var hashName: Int = 0, xtea: IntArray? = null) : Comparable { 13 | 14 | var compressionType: CompressionType = CompressionType.GZIP 15 | var compressor: Compressor = EmptyCompressor 16 | 17 | var revision = 0 18 | private var needUpdate = false 19 | val files: SortedMap = TreeMap() 20 | 21 | var crc: Int = 0 22 | var whirlpool: ByteArray? = null 23 | var checksum = 0 24 | var length = 0 25 | var uncompressedLength = 0 26 | 27 | private var _xtea: IntArray? = xtea 28 | var xtea: IntArray? 29 | get() = _xtea 30 | set(value) { 31 | // only flag the archive for update when it was read and the xtea got changed 32 | if (read && !(_xtea contentEquals value)) { 33 | //bzip2 compression fails when xteas are set for some reason, cheap fix 34 | compressionType = CompressionType.GZIP 35 | compressor = GZIPCompressor() 36 | flag() 37 | } 38 | _xtea = value 39 | } 40 | 41 | var read = false 42 | var new = false 43 | var autoUpdateRevision = true 44 | 45 | constructor(id: Int) : this(id, 0) 46 | 47 | constructor(archive: Archive) : this(archive.id, archive.hashName) { 48 | for (file in archive.files()) { 49 | files[file.id] = File(file) 50 | } 51 | revision = archive.revision 52 | crc = archive.crc 53 | whirlpool = archive.whirlpool?.clone() 54 | xtea = archive.xtea?.clone() 55 | } 56 | 57 | override fun compareTo(other: Archive): Int { 58 | return id.compareTo(other.id) 59 | } 60 | 61 | open fun read(buffer: InputBuffer) { 62 | read = true 63 | val rawArray = buffer.raw() 64 | if (files.size == 1) { 65 | first()?.data = rawArray 66 | return 67 | } 68 | val fileIds = fileIds() 69 | var fileDataSizesOffset = rawArray.size 70 | val chunkSize: Int = rawArray[--fileDataSizesOffset].toInt() and 0xFF 71 | fileDataSizesOffset -= chunkSize * (fileIds.size * 4) 72 | val fileDataSizes = IntArray(fileIds.size) 73 | buffer.offset = fileDataSizesOffset 74 | for (i in 0 until chunkSize) { 75 | var offset = 0 76 | for (fileIndex in fileIds.indices) { 77 | offset += buffer.readInt() 78 | fileDataSizes[fileIndex] += offset 79 | } 80 | } 81 | val filesData = arrayOfNulls(fileIds.size) 82 | for (i in fileIds.indices) { 83 | filesData[i] = ByteArray(fileDataSizes[i]) 84 | fileDataSizes[i] = 0 85 | } 86 | buffer.offset = fileDataSizesOffset 87 | var offset = 0 88 | for (i in 0 until chunkSize) { 89 | var read = 0 90 | for (j in fileIds.indices) { 91 | read += buffer.readInt() 92 | System.arraycopy(rawArray, offset, filesData[j], fileDataSizes[j], read) 93 | offset += read 94 | fileDataSizes[j] += read 95 | } 96 | } 97 | for (i in fileIds.indices) { 98 | file(fileIds[i])?.data = filesData[i] 99 | } 100 | } 101 | 102 | open fun write(): ByteArray { 103 | val files = files() 104 | var size = 0 105 | files.forEach { size += it.data?.size ?: 0 } 106 | val buffer = OutputBuffer(size + files.size * 4) 107 | val emptyByteArray = byteArrayOf() 108 | if (files.size == 1) { 109 | return first()?.data ?: emptyByteArray 110 | } else { 111 | files.forEach { buffer.writeBytes(it.data ?: emptyByteArray) } 112 | val chunks = 1 //TODO Implement chunk writing 113 | for (i in files.indices) { 114 | val file = files[i] 115 | val fileDataSize = file.data?.size ?: 0 116 | val previousFileDataSize = if (i == 0) 0 else files[i - 1].data?.size ?: 0 117 | buffer.writeInt(fileDataSize - previousFileDataSize) 118 | } 119 | buffer.writeByte(chunks) 120 | } 121 | return buffer.array() 122 | } 123 | 124 | fun containsData(): Boolean { 125 | for (entry in files.values) { 126 | if (entry.data != null) { 127 | return true 128 | } 129 | } 130 | return false 131 | } 132 | 133 | @JvmOverloads 134 | fun add(vararg files: File, overwrite: Boolean = true): Array { 135 | val newFiles = ArrayList(files.size) 136 | files.forEach { newFiles.add(add(it, overwrite)) } 137 | return newFiles.toTypedArray() 138 | } 139 | 140 | @JvmOverloads 141 | fun add(file: File, overwrite: Boolean = true): File { 142 | return add(file.id, checkNotNull(file.data) { "File data is null." }, file.hashName, overwrite) 143 | } 144 | 145 | fun add(data: ByteArray): File { 146 | return add(nextId(), data) 147 | } 148 | 149 | @JvmOverloads 150 | fun add(name: String, data: ByteArray, overwrite: Boolean = true): File { 151 | var id = fileId(name) 152 | if (id == -1) { 153 | id = nextId() 154 | } 155 | return add(id, data, toHash(name), overwrite) 156 | } 157 | 158 | @JvmOverloads 159 | fun add(id: Int, data: ByteArray, hashName: Int = -1, overwrite: Boolean = true): File { 160 | var file = files[id] 161 | if (file == null) { 162 | file = File(id, data, if (hashName == -1) 0 else hashName) 163 | files[id] = file 164 | flag() 165 | } else if (overwrite) { 166 | var flag = false 167 | if (!Arrays.equals(file.data, data)) { 168 | file.data = data 169 | flag = true 170 | } 171 | if (hashName != -1 && file.hashName != hashName) { 172 | file.hashName = hashName 173 | flag = true 174 | } 175 | if (flag) { 176 | flag() 177 | } 178 | } 179 | return file 180 | } 181 | 182 | fun file(id: Int): File? { 183 | return files[id] 184 | } 185 | 186 | fun file(data: ByteArray): File? { 187 | return files.filterValues { Arrays.equals(it.data, data) }.values.firstOrNull() 188 | } 189 | 190 | fun file(name: String): File? { 191 | return files.filterValues { it.hashName == toHash(name) }.values.firstOrNull() 192 | } 193 | 194 | fun contains(id: Int): Boolean { 195 | return files.containsKey(id) 196 | } 197 | 198 | fun contains(name: String): Boolean { 199 | return fileId(name) != -1 200 | } 201 | 202 | fun remove(id: Int): File? { 203 | val file = files.remove(id) ?: return null 204 | flag() 205 | return file 206 | } 207 | 208 | fun remove(name: String): File? { 209 | return remove(fileId(name)) 210 | } 211 | 212 | fun first(): File? { 213 | if (files.isEmpty()) { 214 | return null 215 | } 216 | return file(files.firstKey()) 217 | } 218 | 219 | fun last(): File? { 220 | if (files.isEmpty()) { 221 | return null 222 | } 223 | return files[files.lastKey()] 224 | } 225 | 226 | fun fileId(name: String): Int { 227 | val hashName = toHash(name) 228 | files.values.forEach { 229 | if (it.hashName == hashName) { 230 | return it.id 231 | } 232 | } 233 | return -1 234 | } 235 | 236 | fun nextId(): Int { 237 | val last = last() 238 | return if (last == null) 0 else last.id + 1 239 | } 240 | 241 | fun copyFiles(): Array { 242 | val files = files() 243 | val copy = ArrayList(files.size) 244 | for (i in files.indices) { 245 | copy.add(i, File(files[i])) 246 | } 247 | return copy.toTypedArray() 248 | } 249 | 250 | fun flag() { 251 | needUpdate = true 252 | } 253 | 254 | fun flagged(): Boolean { 255 | return needUpdate 256 | } 257 | 258 | fun unFlag() { 259 | if (!flagged()) { 260 | return 261 | } 262 | needUpdate = false 263 | } 264 | 265 | fun restore() { 266 | for (file in files.values) { 267 | file.data = null 268 | } 269 | read = false 270 | new = false 271 | } 272 | 273 | /** 274 | * Clear the files. 275 | */ 276 | fun clear() { 277 | files.clear() 278 | } 279 | 280 | fun fileIds(): IntArray { 281 | return files.keys.toIntArray() 282 | } 283 | 284 | fun files(): Array { 285 | return files.values.toTypedArray() 286 | } 287 | 288 | @Deprecated("Use property syntax", ReplaceWith("xtea")) 289 | fun xtea(xtea: IntArray?) { 290 | this.xtea = xtea 291 | } 292 | 293 | @Deprecated("Use property syntax", ReplaceWith("xtea")) 294 | fun xtea(): IntArray? { 295 | return xtea 296 | } 297 | 298 | open fun toHash(name: String): Int { 299 | return name.hashCode() 300 | } 301 | 302 | override fun toString(): String { 303 | return "Archive[id=$id, hash_name=$hashName, revision=$revision, crc=$crc, has_whirlpool=${whirlpool != null}, read=$read, files_size=${files.size}]" 304 | } 305 | 306 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/cache/index/archive/Archive317.kt: -------------------------------------------------------------------------------- 1 | package com.displee.cache.index.archive 2 | 3 | import com.displee.cache.index.archive.file.File 4 | import com.displee.compress.CompressionType 5 | import com.displee.compress.type.BZIP2Compressor 6 | import com.displee.compress.type.EmptyCompressor 7 | import com.displee.compress.type.GZIPCompressor 8 | import com.displee.io.impl.InputBuffer 9 | import com.displee.io.impl.OutputBuffer 10 | import com.displee.util.hashCode317 11 | 12 | class Archive317(private val bzip2: BZIP2Compressor, id: Int, name: Int) : Archive(id, name) { 13 | 14 | private var extracted = false 15 | 16 | constructor(bzip2: BZIP2Compressor, id: Int) : this(bzip2, id, 0) 17 | 18 | override fun read(buffer: InputBuffer) { 19 | read = true 20 | if (compressionType == CompressionType.GZIP) { 21 | files[0] = File(0, (compressor as GZIPCompressor).deflate317(buffer.raw())) 22 | return 23 | } 24 | var decompressedLength = buffer.read24BitInt() 25 | var compressedLength = buffer.read24BitInt() 26 | if (decompressedLength != compressedLength) { 27 | extracted = true 28 | } 29 | var compressor = if (extracted) bzip2 else EmptyCompressor 30 | val decompressed = compressor.decompress(buffer, compressedLength, decompressedLength) 31 | val metaBuffer = InputBuffer(decompressed) 32 | val filesLength = metaBuffer.readUnsignedShort() 33 | val filesBuffer = InputBuffer(decompressed) 34 | filesBuffer.offset = metaBuffer.offset + filesLength * 10 35 | compressor = if (extracted) EmptyCompressor else bzip2 36 | for (i in 0 until filesLength) { 37 | val fileName = metaBuffer.readInt() 38 | decompressedLength = metaBuffer.read24BitInt() 39 | compressedLength = metaBuffer.read24BitInt() 40 | val data: ByteArray = compressor.decompress(filesBuffer, compressedLength, decompressedLength) 41 | files[i] = File(i, data, fileName) 42 | } 43 | } 44 | 45 | override fun write(): ByteArray { 46 | if (compressionType == CompressionType.GZIP) { 47 | return compressor.compress(first()?.data ?: byteArrayOf()) 48 | } 49 | val metaBuffer = OutputBuffer(2 + files.size * 10) 50 | metaBuffer.writeShort(files.size) 51 | val filesBuffer = OutputBuffer(2048) 52 | var compressor = if (extracted) EmptyCompressor else bzip2 53 | for (file in files.values) { 54 | val fileData = file.data ?: continue 55 | metaBuffer.writeInt(file.hashName).write24BitInt(fileData.size) 56 | val toWrite = compressor.compress(fileData) 57 | metaBuffer.write24BitInt(toWrite.size) 58 | filesBuffer.writeBytes(toWrite) 59 | } 60 | metaBuffer.writeBytes(filesBuffer.array()) 61 | val decompressed = metaBuffer.array() 62 | compressor = if (extracted) bzip2 else EmptyCompressor 63 | val compressed = compressor.compress(decompressed) 64 | val buffer = OutputBuffer(compressed.size + 6) 65 | buffer.write24BitInt(decompressed.size).write24BitInt(compressed.size).writeBytes(compressed) 66 | return buffer.array() 67 | } 68 | 69 | override fun toHash(name: String): Int { 70 | return name.hashCode317() 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/cache/index/archive/ArchiveSector.kt: -------------------------------------------------------------------------------- 1 | package com.displee.cache.index.archive 2 | 3 | import com.displee.cache.index.Index.Companion.SECTOR_HEADER_SIZE_BIG 4 | import com.displee.cache.index.Index.Companion.SECTOR_HEADER_SIZE_SMALL 5 | import com.displee.compress.CompressionType 6 | import com.displee.compress.type.Compressor 7 | import com.displee.io.impl.InputBuffer 8 | import com.displee.io.impl.OutputBuffer 9 | 10 | class ArchiveSector(private val bigSector: Boolean, val size: Int, var position: Int, var id: Int = 0, var index: Int = 0) { 11 | 12 | var chunk = 0 13 | var nextPosition = 0 14 | var data = ByteArray(size) 15 | 16 | lateinit var compressionType: CompressionType 17 | lateinit var compressor: Compressor 18 | 19 | fun read(buffer: InputBuffer) { 20 | id = if (bigSector) { 21 | buffer.readInt() 22 | } else { 23 | buffer.readUnsignedShort() 24 | } 25 | chunk = buffer.readUnsignedShort() 26 | nextPosition = buffer.read24BitInt() 27 | index = buffer.readUnsignedByte() 28 | } 29 | 30 | fun write(): ByteArray { 31 | val buffer = OutputBuffer(if (bigSector) SECTOR_HEADER_SIZE_BIG else SECTOR_HEADER_SIZE_SMALL) 32 | if (bigSector) { 33 | buffer.writeInt(id) 34 | } else { 35 | buffer.writeShort(id) 36 | } 37 | return buffer.writeShort(chunk).write24BitInt(position).writeByte(index).array() 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/cache/index/archive/file/File.kt: -------------------------------------------------------------------------------- 1 | package com.displee.cache.index.archive.file 2 | 3 | class File : Comparable { 4 | 5 | val id: Int 6 | var data: ByteArray? 7 | var hashName: Int 8 | 9 | constructor(id: Int, data: ByteArray? = null, hashName: Int = 0, @Suppress("UNUSED_PARAMETER") checkData: Boolean = false) { 10 | this.id = id 11 | this.data = data 12 | this.hashName = hashName 13 | } 14 | 15 | @JvmOverloads 16 | constructor(id: Int, data: ByteArray, hashName: Int = 0) : this(id, data, hashName, true) 17 | 18 | constructor(file: File) : this(file.id, file.data?.clone(), file.hashName) 19 | 20 | override fun compareTo(other: File): Int { 21 | return id.compareTo(other.id) 22 | } 23 | 24 | override fun toString(): String { 25 | return "File $id[data_length=${data?.size}, hash_name=$hashName]" 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/compress/CompressionExt.kt: -------------------------------------------------------------------------------- 1 | package com.displee.compress 2 | 3 | import com.displee.cache.index.archive.ArchiveSector 4 | import com.displee.compress.type.* 5 | import com.displee.io.impl.InputBuffer 6 | import com.displee.io.impl.OutputBuffer 7 | 8 | fun ByteArray.compress(compressionType: CompressionType, compressor: Compressor, xteas: IntArray? = null, revision: Int = -1): ByteArray { 9 | val compressed: ByteArray = compressor.compress(this) 10 | val compressedSize = compressed.size 11 | val buffer = OutputBuffer(9 + compressedSize + if (revision == -1) 0 else 2) 12 | buffer.writeByte(compressionType.ordinal).writeInt(compressedSize) 13 | if (compressionType != CompressionType.NONE) { 14 | buffer.writeInt(size) 15 | } 16 | buffer.writeBytes(compressed) 17 | if (xteas != null && (xteas[0] != 0 || xteas[1] != 0 || xteas[2] != 0 || 0 != xteas[3])) { 18 | check(compressionType != CompressionType.BZIP2) { "BZIP2 compression doesn't work with xtea encryption." } 19 | buffer.encryptXTEA(xteas, 5, buffer.offset) 20 | } 21 | if (revision != -1) { 22 | buffer.writeShort(revision) 23 | } 24 | return buffer.array() 25 | } 26 | 27 | fun ArchiveSector.decompress(compressors: Compressors, keys: IntArray? = null): ByteArray { 28 | val compressedData = data 29 | val buffer = InputBuffer(compressedData) 30 | if (keys != null && (keys[0] != 0 || keys[1] != 0 || keys[2] != 0 || 0 != keys[3])) { 31 | buffer.decryptXTEA(keys, 5, compressedData.size) 32 | } 33 | val type = buffer.readUnsignedByte() 34 | compressionType = CompressionType.compressionTypes[type] 35 | compressor = compressors.get(compressionType) 36 | val compressedSize = buffer.readInt() 37 | var decompressedSize = compressedSize 38 | if (compressionType != CompressionType.NONE) { 39 | decompressedSize = buffer.readInt() 40 | } 41 | return compressor.decompress(buffer, compressedSize, decompressedSize) 42 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/compress/CompressionType.kt: -------------------------------------------------------------------------------- 1 | package com.displee.compress 2 | 3 | enum class CompressionType { 4 | NONE, 5 | BZIP2, 6 | GZIP, 7 | LZMA; 8 | 9 | companion object { 10 | val compressionTypes = values() 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/compress/type/BZIP2Compressor.kt: -------------------------------------------------------------------------------- 1 | package com.displee.compress.type 2 | 3 | import com.displee.io.impl.InputBuffer 4 | import org.apache.tools.bzip2.CBZip2OutputStream 5 | import java.io.ByteArrayInputStream 6 | import java.io.ByteArrayOutputStream 7 | 8 | /** 9 | * A class representing the BZIP2 (de)compressor. 10 | * @author Jagex 11 | * @author Displee 12 | */ 13 | class BZIP2Compressor : Compressor { 14 | /** 15 | * The current bzip2 block entry. 16 | */ 17 | var bzip2BlockEntry: BZIP2BlockEntry = BZIP2BlockEntry() 18 | 19 | /** 20 | * An unkown integer array. 21 | */ 22 | private var anIntArray5786: IntArray = IntArray(100000) 23 | 24 | /** 25 | * Compress a decompressed BZIP2 file. 26 | * @param bytes The uncompressed BZIP2 file. 27 | * @return The compressed BZIP2 file. 28 | */ 29 | override fun compress(bytes: ByteArray): ByteArray { 30 | var bytes = bytes 31 | try { 32 | ByteArrayInputStream(bytes).use { stream -> 33 | val bout = ByteArrayOutputStream() 34 | CBZip2OutputStream(bout, 1).use { os -> 35 | val buf = ByteArray(4096) 36 | var len: Int 37 | while (stream.read(buf, 0, buf.size).also { len = it } != -1) { 38 | os.write(buf, 0, len) 39 | } 40 | } 41 | bytes = bout.toByteArray() 42 | val bzip2 = ByteArray(bytes.size - 2) 43 | System.arraycopy(bytes, 2, bzip2, 0, bzip2.size) 44 | return bzip2 45 | } 46 | } catch (e: Exception) { 47 | e.printStackTrace() 48 | return byteArrayOf() 49 | } 50 | } 51 | 52 | override fun decompress(buffer: InputBuffer, compressedSize: Int, decompressedSize: Int): ByteArray { 53 | val decompressed = ByteArray(decompressedSize) 54 | val compressed = buffer.readBytes(compressedSize) 55 | decompress(decompressed, decompressed.size, compressed, compressedSize, 0) 56 | return decompressed 57 | } 58 | 59 | /** 60 | * Decompress a compressed BZIP2 file. 61 | * @param decompressed An empty byte array where we put the decompressed data in. 62 | * @param decompressedLength The length to decompress. 63 | * @param archiveData The compressed BZIP2 file. 64 | * @param compressedSize The size of the compressed BZIP2 file. 65 | * @param startOffset The start offset. 66 | * @return The decompressed length. 67 | */ 68 | fun decompress(decompressed: ByteArray, decompressedLength: Int, archiveData: ByteArray, compressedSize: Int, startOffset: Int): Int { 69 | synchronized(bzip2BlockEntry) { 70 | bzip2BlockEntry.compressed = archiveData 71 | bzip2BlockEntry.startOffset = startOffset 72 | bzip2BlockEntry.decompressed = decompressed 73 | bzip2BlockEntry.anInt3100 = 0 74 | bzip2BlockEntry.decompressedLength = decompressedLength 75 | bzip2BlockEntry.anInt3088 = 0 76 | bzip2BlockEntry.anInt3078 = 0 77 | bzip2BlockEntry.anInt3085 = 0 78 | bzip2BlockEntry.anInt3097 = 0 79 | decompress(bzip2BlockEntry) 80 | bzip2BlockEntry.compressed = ByteArray(0) 81 | bzip2BlockEntry.decompressed = ByteArray(0) 82 | return decompressedLength - bzip2BlockEntry.decompressedLength 83 | } 84 | } 85 | 86 | /** 87 | * Decompress a BZIP2 block entry. 88 | * @param bzip2BlockEntry [BZIP2BlockEntry] 89 | */ 90 | fun decompress(bzip2BlockEntry: BZIP2BlockEntry) { 91 | var i: Int 92 | var is_25_: IntArray 93 | var is_26_: IntArray 94 | var is_27_: IntArray 95 | var bool_28_ = true 96 | while (bool_28_) { 97 | var i_29_ = method147(bzip2BlockEntry) 98 | if (i_29_.toInt() == 23) { 99 | break 100 | } 101 | method147(bzip2BlockEntry) 102 | method147(bzip2BlockEntry) 103 | method147(bzip2BlockEntry) 104 | method147(bzip2BlockEntry) 105 | method147(bzip2BlockEntry) 106 | method147(bzip2BlockEntry) 107 | method147(bzip2BlockEntry) 108 | method147(bzip2BlockEntry) 109 | method147(bzip2BlockEntry) 110 | method153(bzip2BlockEntry) 111 | bzip2BlockEntry.anInt3083 = 0 112 | var i_30_ = method147(bzip2BlockEntry).toInt() 113 | bzip2BlockEntry.anInt3083 = bzip2BlockEntry.anInt3083 shl 8 or (i_30_ and 0xff) 114 | i_30_ = method147(bzip2BlockEntry).toInt() 115 | bzip2BlockEntry.anInt3083 = bzip2BlockEntry.anInt3083 shl 8 or (i_30_ and 0xff) 116 | i_30_ = method147(bzip2BlockEntry).toInt() 117 | bzip2BlockEntry.anInt3083 = bzip2BlockEntry.anInt3083 shl 8 or (i_30_ and 0xff) 118 | for (i_31_ in 0..15) { 119 | i_29_ = method153(bzip2BlockEntry) 120 | if (i_29_.toInt() == 1) { 121 | bzip2BlockEntry.aBooleanArray3072[i_31_] = true 122 | } else { 123 | bzip2BlockEntry.aBooleanArray3072[i_31_] = false 124 | } 125 | } 126 | for (i_32_ in 0..255) { 127 | bzip2BlockEntry.aBooleanArray3103[i_32_] = false 128 | } 129 | for (i_33_ in 0..15) { 130 | if (bzip2BlockEntry.aBooleanArray3072[i_33_]) { 131 | for (i_34_ in 0..15) { 132 | i_29_ = method153(bzip2BlockEntry) 133 | if (i_29_.toInt() == 1) { 134 | bzip2BlockEntry.aBooleanArray3103[i_33_ * 16 + i_34_] = true 135 | } 136 | } 137 | } 138 | } 139 | method149(bzip2BlockEntry) 140 | val i_35_ = bzip2BlockEntry.anInt3073 + 2 141 | val i_36_ = method152(3, bzip2BlockEntry) 142 | val i_37_ = method152(15, bzip2BlockEntry) 143 | for (i_38_ in 0 until i_37_) { 144 | var i_39_ = 0 145 | while (true) { 146 | i_29_ = method153(bzip2BlockEntry) 147 | if (i_29_.toInt() == 0) { 148 | break 149 | } 150 | i_39_++ 151 | } 152 | bzip2BlockEntry.aByteArray3094[i_38_] = i_39_.toByte() 153 | } 154 | val is_40_ = ByteArray(6) 155 | for (i_41_ in 0 until i_36_) { 156 | is_40_[i_41_] = i_41_.toByte() 157 | } 158 | for (i_42_ in 0 until i_37_) { 159 | var i_43_ = bzip2BlockEntry.aByteArray3094[i_42_] 160 | val i_44_ = is_40_[i_43_.toInt()] 161 | while ( /**/i_43_ > 0) { 162 | is_40_[i_43_.toInt()] = is_40_[i_43_ - 1] 163 | i_43_-- 164 | } 165 | is_40_[0] = i_44_ 166 | bzip2BlockEntry.aByteArray3076[i_42_] = i_44_ 167 | } 168 | for (i_45_ in 0 until i_36_) { 169 | var i_46_ = method152(5, bzip2BlockEntry) 170 | for (i_47_ in 0 until i_35_) { 171 | while (true) { 172 | i_29_ = method153(bzip2BlockEntry) 173 | if (i_29_.toInt() == 0) { 174 | break 175 | } 176 | i_29_ = method153(bzip2BlockEntry) 177 | if (i_29_.toInt() == 0) { 178 | i_46_++ 179 | } else { 180 | i_46_-- 181 | } 182 | } 183 | bzip2BlockEntry.aByteArrayArray3098[i_45_][i_47_] = i_46_.toByte() 184 | } 185 | } 186 | for (i_48_ in 0 until i_36_) { 187 | var i_49_ = 32 188 | var i_50_: Byte = 0 189 | for (i_51_ in 0 until i_35_) { 190 | if (bzip2BlockEntry.aByteArrayArray3098[i_48_][i_51_] > i_50_) { 191 | i_50_ = bzip2BlockEntry.aByteArrayArray3098[i_48_][i_51_] 192 | } 193 | if (bzip2BlockEntry.aByteArrayArray3098[i_48_][i_51_] < i_49_) { 194 | i_49_ = bzip2BlockEntry.aByteArrayArray3098[i_48_][i_51_].toInt() 195 | } 196 | } 197 | method145(bzip2BlockEntry.anIntArrayArray3095[i_48_], 198 | bzip2BlockEntry.anIntArrayArray3082[i_48_], 199 | bzip2BlockEntry.anIntArrayArray3099[i_48_], 200 | bzip2BlockEntry.aByteArrayArray3098[i_48_], 201 | i_49_, 202 | i_50_.toInt(), 203 | i_35_) 204 | bzip2BlockEntry.anIntArray3090[i_48_] = i_49_ 205 | } 206 | val i_52_ = bzip2BlockEntry.anInt3073 + 1 207 | var i_53_ = -1 208 | for (i_55_ in 0..255) { 209 | bzip2BlockEntry.anIntArray3075[i_55_] = 0 210 | } 211 | var i_56_ = 4095 212 | for (i_57_ in 15 downTo 0) { 213 | for (i_58_ in 15 downTo 0) { 214 | bzip2BlockEntry.aByteArray3101[i_56_] = (i_57_ * 16 + i_58_).toByte() 215 | i_56_-- 216 | } 217 | bzip2BlockEntry.anIntArray3092[i_57_] = i_56_ + 1 218 | } 219 | var i_59_ = 0 220 | i_53_++ 221 | var i_54_ = 50 222 | val i_60_ = bzip2BlockEntry.aByteArray3076[i_53_] 223 | i = bzip2BlockEntry.anIntArray3090[i_60_.toInt()] 224 | is_25_ = bzip2BlockEntry.anIntArrayArray3095[i_60_.toInt()] 225 | is_27_ = bzip2BlockEntry.anIntArrayArray3099[i_60_.toInt()] 226 | is_26_ = bzip2BlockEntry.anIntArrayArray3082[i_60_.toInt()] 227 | i_54_-- 228 | var i_61_ = i 229 | var i_62_: Int 230 | var i_63_: Int 231 | i_63_ = method152(i_61_, bzip2BlockEntry) 232 | while (i_63_ > is_25_[i_61_]) { 233 | i_61_++ 234 | i_62_ = method153(bzip2BlockEntry).toInt() 235 | i_63_ = i_63_ shl 1 or i_62_ 236 | } 237 | var i_64_ = is_27_[i_63_ - is_26_[i_61_]] 238 | while (i_64_ != i_52_) { 239 | if (i_64_ == 0 || i_64_ == 1) { 240 | var i_65_ = -1 241 | var i_66_ = 1 242 | do { 243 | if (i_64_ == 0) { 244 | i_65_ += i_66_ 245 | } else if (i_64_ == 1) { 246 | i_65_ += 2 * i_66_ 247 | } 248 | i_66_ *= 2 249 | if (i_54_ == 0) { 250 | i_53_++ 251 | i_54_ = 50 252 | val i_67_ = bzip2BlockEntry.aByteArray3076[i_53_] 253 | i = bzip2BlockEntry.anIntArray3090[i_67_.toInt()] 254 | is_25_ = bzip2BlockEntry.anIntArrayArray3095[i_67_.toInt()] 255 | is_27_ = bzip2BlockEntry.anIntArrayArray3099[i_67_.toInt()] 256 | is_26_ = bzip2BlockEntry.anIntArrayArray3082[i_67_.toInt()] 257 | } 258 | i_54_-- 259 | i_61_ = i 260 | i_63_ = method152(i_61_, bzip2BlockEntry) 261 | while (i_63_ > is_25_[i_61_]) { 262 | i_61_++ 263 | i_62_ = method153(bzip2BlockEntry).toInt() 264 | i_63_ = i_63_ shl 1 or i_62_ 265 | } 266 | i_64_ = is_27_[i_63_ - is_26_[i_61_]] 267 | } while (i_64_ == 0 || i_64_ == 1) 268 | i_65_++ 269 | i_30_ = bzip2BlockEntry.aByteArray3107[bzip2BlockEntry.aByteArray3101[bzip2BlockEntry.anIntArray3092[0]].toInt() and 0xff].toInt() 270 | bzip2BlockEntry.anIntArray3075[i_30_ and 0xff] += i_65_ 271 | while ( /**/i_65_ > 0) { 272 | anIntArray5786[i_59_] = i_30_ and 0xff 273 | i_59_++ 274 | i_65_-- 275 | } 276 | } else { 277 | var i_68_ = i_64_ - 1 278 | if (i_68_ < 16) { 279 | val i_69_ = bzip2BlockEntry.anIntArray3092[0] 280 | i_29_ = bzip2BlockEntry.aByteArray3101[i_69_ + i_68_] 281 | while ( /**/i_68_ > 3) { 282 | val i_70_ = i_69_ + i_68_ 283 | bzip2BlockEntry.aByteArray3101[i_70_] = bzip2BlockEntry.aByteArray3101[i_70_ - 1] 284 | bzip2BlockEntry.aByteArray3101[i_70_ - 1] = bzip2BlockEntry.aByteArray3101[i_70_ - 2] 285 | bzip2BlockEntry.aByteArray3101[i_70_ - 2] = bzip2BlockEntry.aByteArray3101[i_70_ - 3] 286 | bzip2BlockEntry.aByteArray3101[i_70_ - 3] = bzip2BlockEntry.aByteArray3101[i_70_ - 4] 287 | i_68_ -= 4 288 | } 289 | while ( /**/i_68_ > 0) { 290 | bzip2BlockEntry.aByteArray3101[i_69_ + i_68_] = bzip2BlockEntry.aByteArray3101[i_69_ + i_68_ - 1] 291 | i_68_-- 292 | } 293 | bzip2BlockEntry.aByteArray3101[i_69_] = i_29_ 294 | } else { 295 | var i_71_ = i_68_ / 16 296 | val i_72_ = i_68_ % 16 297 | var i_73_ = bzip2BlockEntry.anIntArray3092[i_71_] + i_72_ 298 | i_29_ = bzip2BlockEntry.aByteArray3101[i_73_] 299 | while ( /**/i_73_ > bzip2BlockEntry.anIntArray3092[i_71_]) { 300 | bzip2BlockEntry.aByteArray3101[i_73_] = bzip2BlockEntry.aByteArray3101[i_73_ - 1] 301 | i_73_-- 302 | } 303 | bzip2BlockEntry.anIntArray3092[i_71_]++ 304 | while ( /**/i_71_ > 0) { 305 | bzip2BlockEntry.anIntArray3092[i_71_]-- 306 | bzip2BlockEntry.aByteArray3101[bzip2BlockEntry.anIntArray3092[i_71_]] = bzip2BlockEntry.aByteArray3101[bzip2BlockEntry.anIntArray3092[i_71_ - 1] + 16 - 1] 307 | i_71_-- 308 | } 309 | bzip2BlockEntry.anIntArray3092[0]-- 310 | bzip2BlockEntry.aByteArray3101[bzip2BlockEntry.anIntArray3092[0]] = i_29_ 311 | if (bzip2BlockEntry.anIntArray3092[0] == 0) { 312 | var i_74_ = 4095 313 | for (i_75_ in 15 downTo 0) { 314 | for (i_76_ in 15 downTo 0) { 315 | bzip2BlockEntry.aByteArray3101[i_74_] = bzip2BlockEntry.aByteArray3101[bzip2BlockEntry.anIntArray3092[i_75_] + i_76_] 316 | i_74_-- 317 | } 318 | bzip2BlockEntry.anIntArray3092[i_75_] = i_74_ + 1 319 | } 320 | } 321 | } 322 | bzip2BlockEntry.anIntArray3075[bzip2BlockEntry.aByteArray3107[i_29_.toInt() and 0xff].toInt() and 0xff]++ 323 | anIntArray5786[i_59_] = bzip2BlockEntry.aByteArray3107[i_29_.toInt() and 0xff].toInt() and 0xff 324 | i_59_++ 325 | if (i_54_ == 0) { 326 | i_53_++ 327 | i_54_ = 50 328 | val i_77_ = bzip2BlockEntry.aByteArray3076[i_53_] 329 | i = bzip2BlockEntry.anIntArray3090[i_77_.toInt()] 330 | is_25_ = bzip2BlockEntry.anIntArrayArray3095[i_77_.toInt()] 331 | is_27_ = bzip2BlockEntry.anIntArrayArray3099[i_77_.toInt()] 332 | is_26_ = bzip2BlockEntry.anIntArrayArray3082[i_77_.toInt()] 333 | } 334 | i_54_-- 335 | i_61_ = i 336 | i_63_ = method152(i_61_, bzip2BlockEntry) 337 | while (i_63_ > is_25_[i_61_]) { 338 | i_61_++ 339 | i_62_ = method153(bzip2BlockEntry).toInt() 340 | i_63_ = i_63_ shl 1 or i_62_ 341 | } 342 | i_64_ = is_27_[i_63_ - is_26_[i_61_]] 343 | } 344 | } 345 | bzip2BlockEntry.anInt3080 = 0 346 | bzip2BlockEntry.aByte3108 = 0.toByte() 347 | bzip2BlockEntry.anIntArray3091[0] = 0 348 | for (i_78_ in 1..256) { 349 | bzip2BlockEntry.anIntArray3091[i_78_] = bzip2BlockEntry.anIntArray3075[i_78_ - 1] 350 | } 351 | for (i_79_ in 1..256) { 352 | bzip2BlockEntry.anIntArray3091[i_79_] += bzip2BlockEntry.anIntArray3091[i_79_ - 1] 353 | } 354 | for (i_80_ in 0 until i_59_) { 355 | i_30_ = (anIntArray5786[i_80_] and 0xff).toByte().toInt() 356 | anIntArray5786[bzip2BlockEntry.anIntArray3091[i_30_ and 0xff]] = anIntArray5786[bzip2BlockEntry.anIntArray3091[i_30_ and 0xff]] or (i_80_ shl 8) 357 | bzip2BlockEntry.anIntArray3091[i_30_ and 0xff]++ 358 | } 359 | bzip2BlockEntry.anInt3106 = anIntArray5786[bzip2BlockEntry.anInt3083] shr 8 360 | bzip2BlockEntry.anInt3071 = 0 361 | bzip2BlockEntry.anInt3106 = anIntArray5786[bzip2BlockEntry.anInt3106] 362 | bzip2BlockEntry.anInt3070 = (bzip2BlockEntry.anInt3106 and 0xff).toByte().toInt() 363 | bzip2BlockEntry.anInt3106 = bzip2BlockEntry.anInt3106 shr 8 364 | bzip2BlockEntry.anInt3071++ 365 | bzip2BlockEntry.anInt3077 = i_59_ 366 | method151(bzip2BlockEntry) 367 | bool_28_ = bzip2BlockEntry.anInt3071 == bzip2BlockEntry.anInt3077 + 1 && bzip2BlockEntry.anInt3080 == 0 368 | } 369 | } 370 | 371 | fun method152(arg0: Int, arg1: BZIP2BlockEntry): Int { 372 | val i: Int 373 | while (true) { 374 | if (arg1.anInt3088 >= arg0) { 375 | val i_93_ = arg1.anInt3078 shr arg1.anInt3088 - arg0 and (1 shl arg0) - 1 376 | arg1.anInt3088 -= arg0 377 | i = i_93_ 378 | break 379 | } 380 | arg1.anInt3078 = arg1.anInt3078 shl 8 or (arg1.compressed[arg1.startOffset].toInt() and 0xff) 381 | arg1.anInt3088 += 8 382 | arg1.startOffset++ 383 | arg1.anInt3085++ 384 | } 385 | return i 386 | } 387 | 388 | fun method151(arg0: BZIP2BlockEntry) { 389 | var i = arg0.aByte3108 390 | var i_81_ = arg0.anInt3080 391 | var i_82_ = arg0.anInt3071 392 | var i_83_ = arg0.anInt3070 393 | val i_90_ = anIntArray5786 394 | var i_84_ = arg0.anInt3106 395 | val is_85_ = arg0.decompressed 396 | var i_86_ = arg0.anInt3100 397 | var i_87_ = arg0.decompressedLength 398 | val i_88_ = i_87_ 399 | val i_89_ = arg0.anInt3077 + 1 400 | while_68_@ while (true) { 401 | if (i_81_ > 0) { 402 | while (true) { 403 | if (i_87_ == 0) { 404 | break@while_68_ 405 | } 406 | if (i_81_ == 1) { 407 | break 408 | } 409 | is_85_[i_86_] = i 410 | i_81_-- 411 | i_86_++ 412 | i_87_-- 413 | } 414 | if (i_87_ == 0) { 415 | i_81_ = 1 416 | break 417 | } 418 | is_85_[i_86_] = i 419 | i_86_++ 420 | i_87_-- 421 | } 422 | var bool = true 423 | while (bool) { 424 | bool = false 425 | if (i_82_ == i_89_) { 426 | i_81_ = 0 427 | break@while_68_ 428 | } 429 | i = i_83_.toByte() 430 | i_84_ = i_90_[i_84_] 431 | val i_90_ = (i_84_ and 0xff).toByte().toInt() 432 | i_84_ = i_84_ shr 8 433 | i_82_++ 434 | if (i_90_ != i_83_) { 435 | i_83_ = i_90_ 436 | if (i_87_ == 0) { 437 | i_81_ = 1 438 | break@while_68_ 439 | } 440 | is_85_[i_86_] = i 441 | i_86_++ 442 | i_87_-- 443 | bool = true 444 | } else if (i_82_ == i_89_) { 445 | if (i_87_ == 0) { 446 | i_81_ = 1 447 | break@while_68_ 448 | } 449 | is_85_[i_86_] = i 450 | i_86_++ 451 | i_87_-- 452 | bool = true 453 | } 454 | } 455 | i_81_ = 2 456 | i_84_ = i_90_[i_84_] 457 | var i_91_ = (i_84_ and 0xff).toByte().toInt() 458 | i_84_ = i_84_ shr 8 459 | if (++i_82_ != i_89_) { 460 | if (i_91_ != i_83_) { 461 | i_83_ = i_91_ 462 | } else { 463 | i_81_ = 3 464 | i_84_ = i_90_[i_84_] 465 | i_91_ = (i_84_ and 0xff).toByte().toInt() 466 | i_84_ = i_84_ shr 8 467 | if (++i_82_ != i_89_) { 468 | if (i_91_ != i_83_) { 469 | i_83_ = i_91_ 470 | } else { 471 | i_84_ = i_90_[i_84_] 472 | i_91_ = (i_84_ and 0xff).toByte().toInt() 473 | i_84_ = i_84_ shr 8 474 | i_82_++ 475 | i_81_ = (i_91_ and 0xff) + 4 476 | i_84_ = i_90_[i_84_] 477 | i_83_ = (i_84_ and 0xff).toByte().toInt() 478 | i_84_ = i_84_ shr 8 479 | i_82_++ 480 | } 481 | } 482 | } 483 | } 484 | } 485 | arg0.anInt3097 486 | arg0.anInt3097 += i_88_ - i_87_ 487 | arg0.aByte3108 = i 488 | arg0.anInt3080 = i_81_ 489 | arg0.anInt3071 = i_82_ 490 | arg0.anInt3070 = i_83_ 491 | anIntArray5786 = i_90_ 492 | arg0.anInt3106 = i_84_ 493 | arg0.decompressed = is_85_ 494 | arg0.anInt3100 = i_86_ 495 | arg0.decompressedLength = i_87_ 496 | } 497 | 498 | fun method145(arg0: IntArray, arg1: IntArray, arg2: IntArray, arg3: ByteArray, arg4: Int, arg5: Int, arg6: Int) { 499 | var i = 0 500 | for (i_0_ in arg4..arg5) { 501 | for (i_1_ in 0 until arg6) { 502 | if (arg3[i_1_].toInt() == i_0_) { 503 | arg2[i] = i_1_ 504 | i++ 505 | } 506 | } 507 | } 508 | for (i_2_ in 0..22) { 509 | arg1[i_2_] = 0 510 | } 511 | for (i_3_ in 0 until arg6) { 512 | arg1[arg3[i_3_] + 1]++ 513 | } 514 | for (i_4_ in 1..22) { 515 | arg1[i_4_] += arg1[i_4_ - 1] 516 | } 517 | for (i_5_ in 0..22) { 518 | arg0[i_5_] = 0 519 | } 520 | var i_6_ = 0 521 | for (i_7_ in arg4..arg5) { 522 | i_6_ += arg1[i_7_ + 1] - arg1[i_7_] 523 | arg0[i_7_] = i_6_ - 1 524 | i_6_ = i_6_ shl 1 525 | } 526 | for (i_8_ in arg4 + 1..arg5) { 527 | arg1[i_8_] = (arg0[i_8_ - 1] + 1 shl 1) - arg1[i_8_] 528 | } 529 | } 530 | 531 | fun method147(arg0: BZIP2BlockEntry): Byte { 532 | return method152(8, arg0).toByte() 533 | } 534 | 535 | fun method149(arg0: BZIP2BlockEntry) { 536 | arg0.anInt3073 = 0 537 | for (i in 0..255) { 538 | if (arg0.aBooleanArray3103[i]) { 539 | arg0.aByteArray3107[arg0.anInt3073] = i.toByte() 540 | arg0.anInt3073++ 541 | } 542 | } 543 | } 544 | 545 | fun method153(arg0: BZIP2BlockEntry): Byte { 546 | return method152(1, arg0).toByte() 547 | } 548 | 549 | class BZIP2BlockEntry { 550 | var anInt3070 = 0 551 | var anInt3071 = 0 552 | var aBooleanArray3072: BooleanArray = BooleanArray(16) 553 | var anInt3073 = 0 554 | var startOffset = 0 555 | var anIntArray3075: IntArray = IntArray(256) 556 | var aByteArray3076: ByteArray = ByteArray(18002) 557 | var anInt3077 = 0 558 | var anInt3078 = 0 559 | var decompressed: ByteArray = ByteArray(0) 560 | var anInt3080 = 0 561 | var anIntArrayArray3082 = Array(6) { IntArray(258) } 562 | var anInt3083 = 0 563 | var anInt3085 = 0 564 | var anInt3088 = 0 565 | var anIntArray3090: IntArray = IntArray(6) 566 | var anIntArray3091: IntArray = IntArray(257) 567 | var anIntArray3092: IntArray = IntArray(16) 568 | var decompressedLength = 0 569 | var aByteArray3094 = ByteArray(18002) 570 | var anIntArrayArray3095: Array = Array(6) { IntArray(258) } 571 | var anInt3097 = 0 572 | var aByteArrayArray3098: Array = Array(6) { ByteArray(258) } 573 | var anIntArrayArray3099: Array = Array(6) { IntArray(258) } 574 | var anInt3100 = 0 575 | var aByteArray3101: ByteArray = ByteArray(4096) 576 | var aBooleanArray3103: BooleanArray = BooleanArray(256) 577 | var anInt3106 = 0 578 | var aByteArray3107: ByteArray = ByteArray(256) 579 | var aByte3108: Byte = 0 580 | var compressed: ByteArray = ByteArray(0) 581 | } 582 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/compress/type/Compressor.kt: -------------------------------------------------------------------------------- 1 | package com.displee.compress.type 2 | 3 | import com.displee.io.impl.InputBuffer 4 | 5 | interface Compressor { 6 | 7 | fun compress(bytes: ByteArray): ByteArray 8 | 9 | fun decompress(buffer: InputBuffer, compressedSize: Int, decompressedSize: Int): ByteArray 10 | 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/compress/type/Compressors.kt: -------------------------------------------------------------------------------- 1 | package com.displee.compress.type 2 | 3 | import com.displee.compress.CompressionType 4 | 5 | class Compressors { 6 | val bzip2: BZIP2Compressor by lazy { BZIP2Compressor() } 7 | val gzip: GZIPCompressor by lazy { GZIPCompressor() } 8 | private val lzma: LZMACompressor by lazy { LZMACompressor() } 9 | 10 | fun get(type: CompressionType): Compressor { 11 | return when (type) { 12 | CompressionType.NONE -> EmptyCompressor 13 | CompressionType.BZIP2 -> bzip2 14 | CompressionType.GZIP -> gzip 15 | CompressionType.LZMA -> lzma 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/compress/type/EmptyCompressor.kt: -------------------------------------------------------------------------------- 1 | package com.displee.compress.type 2 | 3 | import com.displee.io.impl.InputBuffer 4 | 5 | object EmptyCompressor : Compressor { 6 | override fun compress(bytes: ByteArray): ByteArray { 7 | return bytes 8 | } 9 | 10 | override fun decompress(buffer: InputBuffer, compressedSize: Int, decompressedSize: Int): ByteArray { 11 | return buffer.readBytes(decompressedSize) 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/compress/type/GZIPCompressor.kt: -------------------------------------------------------------------------------- 1 | package com.displee.compress.type 2 | 3 | import com.displee.io.impl.InputBuffer 4 | import java.io.ByteArrayInputStream 5 | import java.io.ByteArrayOutputStream 6 | import java.io.IOException 7 | import java.util.zip.GZIPInputStream 8 | import java.util.zip.GZIPOutputStream 9 | import java.util.zip.Inflater 10 | 11 | class GZIPCompressor : Compressor { 12 | 13 | private var inflater: Inflater? = null 14 | private var gzipBuffer: ByteArray? = null 15 | 16 | override fun decompress(buffer: InputBuffer, compressedSize: Int, decompressedSize: Int): ByteArray { 17 | return inflate(buffer, decompressedSize) 18 | } 19 | 20 | private fun inflate(buffer: InputBuffer, decompressedSize: Int): ByteArray { 21 | val bytes = buffer.raw() 22 | val offset = buffer.offset 23 | if (bytes[offset].toInt() != 31 || bytes[offset + 1].toInt() != -117) { 24 | return byteArrayOf() 25 | } 26 | val inflater = this.inflater ?: Inflater(true).also { inflater = it } 27 | return try { 28 | inflater.setInput(bytes, offset + 10, bytes.size - (10 + offset + 8)) 29 | val decompressed = ByteArray(decompressedSize) 30 | inflater.inflate(decompressed) 31 | decompressed 32 | } catch (exception: Exception) { 33 | byteArrayOf() 34 | } finally { 35 | inflater.reset() 36 | } 37 | } 38 | 39 | override fun compress(bytes: ByteArray): ByteArray { 40 | val compressed = ByteArrayOutputStream() 41 | try { 42 | val gzipOutputStream = GZIPOutputStream(compressed) 43 | gzipOutputStream.write(bytes) 44 | gzipOutputStream.finish() 45 | gzipOutputStream.close() 46 | } catch (e: Exception) { 47 | e.printStackTrace() 48 | } 49 | return compressed.toByteArray() 50 | } 51 | 52 | fun deflate317(data: ByteArray?): ByteArray { 53 | var read = 0 54 | try { 55 | val gis = GZIPInputStream(ByteArrayInputStream(data)) 56 | var gzipBuffer = gzipBuffer 57 | if (gzipBuffer == null) { 58 | gzipBuffer = ByteArray(1_000_000) //because in 317 Jagex stores a lot of data in one file 59 | this.gzipBuffer = gzipBuffer 60 | } 61 | while (true) { 62 | if (read == gzipBuffer.size) { 63 | throw RuntimeException("buffer overflow!") 64 | } 65 | val length = gis.read(gzipBuffer, read, gzipBuffer.size - read) 66 | if (length == -1) { 67 | break 68 | } 69 | read += length 70 | } 71 | } catch (ex: IOException) { 72 | throw RuntimeException("error unzipping") 73 | } 74 | val deflated = ByteArray(read) 75 | System.arraycopy(gzipBuffer, 0, deflated, 0, deflated.size) 76 | return deflated 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/compress/type/LZMACompressor.kt: -------------------------------------------------------------------------------- 1 | package com.displee.compress.type 2 | 3 | import com.displee.io.impl.InputBuffer 4 | import com.displee.io.impl.OutputBuffer 5 | import com.displee.util.writeTo 6 | import lzma.sdk.lzma.Decoder 7 | import lzma.sdk.lzma.Encoder 8 | import lzma.streams.LzmaEncoderWrapper 9 | import lzma.streams.LzmaOutputStream 10 | import java.io.* 11 | 12 | /** 13 | * A class handling LZMA compression. 14 | * @author Displee (credits to Techdaan) 15 | */ 16 | class LZMACompressor : Compressor { 17 | 18 | private val DECODER = Decoder() 19 | private val ENCODER = Encoder() 20 | private val ENCODER_WRAPPER: LzmaEncoderWrapper = object : LzmaEncoderWrapper(ENCODER) { 21 | @Throws(IOException::class) 22 | override fun code(inputStream: InputStream, outputStream: OutputStream) { 23 | ENCODER.writeCoderProperties(outputStream) 24 | ENCODER.code(inputStream, outputStream, -1, -1, null) 25 | } 26 | } 27 | 28 | init { 29 | ENCODER.setDictionarySize(8 shl 20) 30 | ENCODER.setNumFastBytes(110) 31 | ENCODER.setMatchFinder(0) 32 | ENCODER.setLcLpPb(3, 0, 2) 33 | ENCODER.setEndMarkerMode(false) 34 | } 35 | 36 | override fun compress(bytes: ByteArray): ByteArray { 37 | val baos = ByteArrayOutputStream() 38 | try { 39 | val bais = ByteArrayInputStream(bytes) 40 | val lzma = LzmaOutputStream(baos, ENCODER_WRAPPER) 41 | bais.writeTo(lzma) 42 | baos.close() 43 | lzma.close() 44 | bais.close() 45 | } catch (e: IOException) { 46 | e.printStackTrace() 47 | } 48 | return baos.toByteArray() 49 | } 50 | 51 | override fun decompress(buffer: InputBuffer, compressedSize: Int, decompressedSize: Int): ByteArray { 52 | val output = OutputBuffer(buffer.remaining()) 53 | output.writeBytes(buffer.raw(), buffer.offset, buffer.remaining()) 54 | return try { 55 | decompress(ByteArrayInputStream(output.raw()), decompressedSize) 56 | } catch (e: IOException) { 57 | e.printStackTrace() 58 | byteArrayOf() 59 | } 60 | } 61 | 62 | @Throws(IOException::class) 63 | fun decompress(input: ByteArrayInputStream, decompressedLength: Int): ByteArray { 64 | val properties = ByteArray(5) 65 | if (input.read(properties) != 5) { 66 | throw IOException("LZMA: Bad input.") 67 | } 68 | val output = ByteArrayOutputStream(decompressedLength) 69 | synchronized(DECODER) { 70 | if (!DECODER.setDecoderProperties(properties)) { 71 | throw IOException("LZMA: Bad properties.") 72 | } 73 | DECODER.code(input, output, decompressedLength.toLong()) 74 | } 75 | output.flush() 76 | return output.toByteArray() 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/util/OtherExt.kt: -------------------------------------------------------------------------------- 1 | package com.displee.util 2 | 3 | import com.displee.cache.index.Index.Companion.WHIRLPOOL_SIZE 4 | import java.io.InputStream 5 | import java.io.OutputStream 6 | 7 | private val CRC_TABLE = IntArray(256) { 8 | var crc = it 9 | for (i_84_ in 0..7) { 10 | crc = if (crc and 0x1 == 1) { 11 | crc ushr 1 xor 0x12477cdf.inv() 12 | } else { 13 | crc ushr 1 14 | } 15 | } 16 | crc 17 | } 18 | 19 | fun ByteArray.generateCrc(offset: Int = 0, length: Int = size): Int { 20 | var crc = -1 21 | for (i in offset until length) { 22 | crc = crc ushr 8 xor CRC_TABLE[crc xor this[i].toInt() and 0xff] 23 | } 24 | crc = crc xor -0x1 25 | return crc 26 | } 27 | 28 | fun ByteArray.generateWhirlpool(whirlpool: Whirlpool, offset: Int = 0, length: Int = size): ByteArray { 29 | val source: ByteArray 30 | if (offset > 0) { 31 | source = ByteArray(length) 32 | System.arraycopy(this, offset, source, 0, length) 33 | } else { 34 | source = this 35 | } 36 | whirlpool.NESSIEinit() 37 | whirlpool.NESSIEadd(source, (length * 8).toLong()) 38 | val digest = ByteArray(WHIRLPOOL_SIZE) 39 | whirlpool.NESSIEfinalize(digest, 0) 40 | return digest 41 | } 42 | 43 | fun InputStream.writeTo(to: OutputStream): Long { 44 | val buf = ByteArray(0x1000) 45 | var total: Long = 0 46 | while (true) { 47 | val r = read(buf) 48 | if (r == -1) { 49 | break 50 | } 51 | to.write(buf, 0, r) 52 | total += r.toLong() 53 | } 54 | return total 55 | } 56 | 57 | fun String.hashCode317(): Int { 58 | val upperCaseString = toUpperCase() 59 | var hash = 0 60 | for (element in upperCaseString) { 61 | hash = hash * 61 + element.toInt() - 32 62 | } 63 | return hash 64 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/displee/util/Whirlpool.kt: -------------------------------------------------------------------------------- 1 | package com.displee.util 2 | 3 | class Whirlpool { 4 | private val block = LongArray(8) 5 | private val hash = LongArray(8) 6 | private val aLongArray6637 = LongArray(8) 7 | private val aLongArray6638 = LongArray(8) 8 | private val state = LongArray(8) 9 | private val bitLength = ByteArray(32) 10 | private val buffer = ByteArray(64) 11 | private var bufferBits = 0 12 | private var bufferPosition = 0 13 | fun NESSIEinit() { 14 | for (i in 0..31) { 15 | bitLength[i] = 0.toByte() 16 | } 17 | bufferPosition = 0 18 | bufferBits = 0 19 | buffer[0] = 0.toByte() 20 | for (i_3_ in 0..7) { 21 | hash[i_3_] = 0L 22 | } 23 | } 24 | 25 | private fun processBuffer() { 26 | var i_4_ = 0 27 | var i_5_ = 0 28 | while (i_4_ < 8) { 29 | block[i_4_] = 30 | buffer[i_5_].toLong() shl 56 xor (buffer[i_5_ + 1].toLong() and 0xffL shl 48) xor (buffer[2 + i_5_].toLong() and 0xffL shl 40) xor (buffer[i_5_ + 3].toLong() and 0xffL shl 32) xor (buffer[i_5_ + 4].toLong() and 0xffL shl 24) xor (buffer[5 + i_5_].toLong() and 0xffL shl 16) xor (buffer[i_5_ + 6].toLong() and 0xffL shl 8) xor (buffer[7 + i_5_].toLong() and 0xffL) 31 | i_4_++ 32 | i_5_ += 8 33 | } 34 | i_4_ = 0 35 | while (i_4_ < 8) { 36 | state[i_4_] = block[i_4_] xor hash[i_4_].also { aLongArray6637[i_4_] = it } 37 | i_4_++ 38 | } 39 | i_4_ = 1 40 | while (i_4_ <= 10) { 41 | i_5_ = 0 42 | while (i_5_ < 8) { 43 | aLongArray6638[i_5_] = 0L 44 | var i_6_ = 0 45 | var i_7_ = 56 46 | while (i_6_ < 8) { 47 | aLongArray6638[i_5_] = aLongArray6638[i_5_] xor aLongArrayArray6630[i_6_][(aLongArray6637[i_5_ - i_6_ and 0x7] ushr i_7_).toInt() and 0xff] 48 | i_6_++ 49 | i_7_ -= 8 50 | } 51 | i_5_++ 52 | } 53 | i_5_ = 0 54 | while (i_5_ < 8) { 55 | aLongArray6637[i_5_] = aLongArray6638[i_5_] 56 | i_5_++ 57 | } 58 | aLongArray6637[0] = aLongArray6637[0] xor aLongArray6631[i_4_] 59 | i_5_ = 0 60 | while (i_5_ < 8) { 61 | aLongArray6638[i_5_] = aLongArray6637[i_5_] 62 | var i_8_ = 0 63 | var i_9_ = 56 64 | while (i_8_ < 8) { 65 | aLongArray6638[i_5_] = aLongArray6638[i_5_] xor aLongArrayArray6630[i_8_][(state[i_5_ - i_8_ and 0x7] ushr i_9_).toInt() and 0xff] 66 | i_8_++ 67 | i_9_ -= 8 68 | } 69 | i_5_++ 70 | } 71 | i_5_ = 0 72 | while (i_5_ < 8) { 73 | state[i_5_] = aLongArray6638[i_5_] 74 | i_5_++ 75 | } 76 | i_4_++ 77 | } 78 | i_4_ = 0 79 | while (i_4_ < 8) { 80 | hash[i_4_] = hash[i_4_] xor (state[i_4_] xor block[i_4_]) 81 | i_4_++ 82 | } 83 | } 84 | 85 | fun NESSIEfinalize(digest: ByteArray, i: Int) { 86 | buffer[bufferPosition] = (buffer[bufferPosition].toInt() or (128 ushr (bufferBits and 0x7))).toByte() 87 | ++bufferPosition 88 | if (bufferPosition > 32) { 89 | while (bufferPosition < 64) { 90 | buffer[bufferPosition++] = 0.toByte() 91 | } 92 | processBuffer() 93 | bufferPosition = 0 94 | } 95 | while (bufferPosition < 32) { 96 | buffer[bufferPosition++] = 0.toByte() 97 | } 98 | System.arraycopy(bitLength, 0, buffer, 32, 32) 99 | processBuffer() 100 | var i_16_ = 0 101 | var i_17_ = i 102 | while (i_16_ < 8) { 103 | val l = hash[i_16_] 104 | digest[i_17_] = (l ushr 56).toInt().toByte() 105 | digest[i_17_ + 1] = (l ushr 48).toInt().toByte() 106 | digest[i_17_ + 2] = (l ushr 40).toInt().toByte() 107 | digest[i_17_ + 3] = (l ushr 32).toInt().toByte() 108 | digest[i_17_ + 4] = (l ushr 24).toInt().toByte() 109 | digest[i_17_ + 5] = (l ushr 16).toInt().toByte() 110 | digest[i_17_ + 6] = (l ushr 8).toInt().toByte() 111 | digest[i_17_ + 7] = l.toInt().toByte() 112 | i_16_++ 113 | i_17_ += 8 114 | } 115 | } 116 | 117 | fun NESSIEadd(source: ByteArray, sourceBits: Long) { 118 | var sourceBits = sourceBits 119 | var i = 0 120 | val i_23_ = 8 - (sourceBits.toInt() and 0x7) and 0x7 121 | val i_24_ = bufferBits and 0x7 122 | var l_25_ = sourceBits 123 | var i_26_ = 31 124 | var i_27_ = 0 125 | while ( /**/i_26_ >= 0) { 126 | i_27_ += (bitLength[i_26_].toInt() and 0xff) + (l_25_.toInt() and 0xff) 127 | bitLength[i_26_] = i_27_.toByte() 128 | i_27_ = i_27_ ushr 8 129 | l_25_ = l_25_ ushr 8 130 | i_26_-- 131 | } 132 | while (sourceBits > 8L) { 133 | val i_28_ = source[i].toInt() shl i_23_ and 0xff or (source[i + 1].toInt() and 0xff ushr 8 - i_23_) 134 | if (i_28_ < 0 || i_28_ >= 256) { 135 | throw RuntimeException() 136 | } 137 | buffer[bufferPosition] = (buffer[bufferPosition].toInt() or (i_28_ ushr i_24_)).toByte() 138 | ++bufferPosition 139 | bufferBits += 8 - i_24_ 140 | if (bufferBits == 512) { 141 | processBuffer() 142 | bufferPosition = 0 143 | bufferBits = 0 144 | } 145 | buffer[bufferPosition] = (i_28_ shl 8 - i_24_ and 0xff).toByte() 146 | bufferBits += i_24_ 147 | sourceBits -= 8L 148 | i++ 149 | } 150 | val i_29_: Int 151 | if (sourceBits > 0L) { 152 | i_29_ = source[i].toInt() shl i_23_ and 0xff 153 | buffer[bufferPosition] = (buffer[bufferPosition].toInt() or (i_29_ ushr i_24_)).toByte() 154 | } else { 155 | i_29_ = 0 156 | } 157 | if (i_24_.toLong() + sourceBits < 8L) { 158 | bufferBits += sourceBits.toInt() 159 | } else { 160 | ++bufferPosition 161 | bufferBits += 8 - i_24_ 162 | sourceBits -= (8 - i_24_).toLong() 163 | if (bufferBits == 512) { 164 | processBuffer() 165 | bufferPosition = 0 166 | bufferBits = 0 167 | } 168 | buffer[bufferPosition] = (i_29_ shl 8 - i_24_ and 0xff).toByte() 169 | bufferBits += sourceBits.toInt() 170 | } 171 | } 172 | 173 | companion object { 174 | private val aLongArrayArray6630 = Array(8) { LongArray(256) } 175 | private val aLongArray6631 = LongArray(11) 176 | 177 | init { 178 | for (i in 0..255) { 179 | val i_37_ = 180 | "\u1823\uc6e8\u87b8\u014f\u36a6\ud2f5\u796f\u9152\u60bc\u9b8e\ua30c\u7b35\u1de0\ud7c2\u2e4b\ufe57\u1577\u37e5\u9ff0\u4ada\u58c9\u290a\ub1a0\u6b85\ubd5d\u10f4\ucb3e\u0567\ue427\u418b\ua77d\u95d8\ufbee\u7c66\udd17\u479e\uca2d\ubf07\uad5a\u8333\u6302\uaa71\uc819\u49d9\uf2e3\u5b88\u9a26\u32b0\ue90f\ud580\ubecd\u3448\uff7a\u905f\u2068\u1aae\ub454\u9322\u64f1\u7312\u4008\uc3ec\udba1\u8d3d\u9700\ucf2b\u7682\ud61b\ub5af\u6a50\u45f3\u30ef\u3f55\ua2ea\u65ba\u2fc0\ude1c\ufd4d\u9275\u068a\ub2e6\u0e1f\u62d4\ua896\uf9c5\u2559\u8472\u394c\u5e78\u388c\ud1a5\ue261\ub321\u9c1e\u43c7\ufc04\u5199\u6d0d\ufadf\u7e24\u3bab\uce11\u8f4e\ub7eb\u3c81\u94f7\ub913\u2cd3\ue76e\uc403\u5644\u7fa9\u2abb\uc153\udc0b\u9d6c\u3174\uf646\uac89\u14e1\u163a\u6909\u70b6\ud0ed\ucc42\u98a4\u285c\uf886"[i / 2].toInt() 181 | val l = if (i and 0x1 == 0) (i_37_ ushr 8).toLong() else (i_37_ and 0xff).toLong() 182 | var l_38_ = l shl 1 183 | if (l_38_ >= 256L) { 184 | l_38_ = l_38_ xor 0x11dL 185 | } 186 | var l_39_ = l_38_ shl 1 187 | if (l_39_ >= 256L) { 188 | l_39_ = l_39_ xor 0x11dL 189 | } 190 | val l_40_ = l_39_ xor l 191 | var l_41_ = l_39_ shl 1 192 | if (l_41_ >= 256L) { 193 | l_41_ = l_41_ xor 0x11dL 194 | } 195 | val l_42_ = l_41_ xor l 196 | aLongArrayArray6630[0][i] = l shl 56 or (l shl 48) or (l_39_ shl 40) or (l shl 32) or (l_41_ shl 24) or (l_40_ shl 16) or (l_38_ shl 8) or l_42_ 197 | for (i_43_ in 1..7) { 198 | aLongArrayArray6630[i_43_][i] = aLongArrayArray6630[i_43_ - 1][i] ushr 8 or (aLongArrayArray6630[i_43_ - 1][i] shl 56) 199 | } 200 | } 201 | aLongArray6631[0] = 0L 202 | for (i in 1..10) { 203 | val i_44_ = 8 * (i - 1) 204 | aLongArray6631[i] = 205 | aLongArrayArray6630[0][i_44_] and 0xffffffffffffffL.inv() xor (aLongArrayArray6630[1][i_44_ + 1] and 0xff000000000000L) xor (aLongArrayArray6630[2][i_44_ + 2] and 0xff0000000000L) xor (aLongArrayArray6630[3][i_44_ + 3] and 0xff00000000L) xor (aLongArrayArray6630[4][4 + i_44_] and 0xff000000L) xor (aLongArrayArray6630[5][5 + i_44_] and 0xff0000L) xor (aLongArrayArray6630[6][6 + i_44_] and 0xff00L) xor (aLongArrayArray6630[7][7 + i_44_] and 0xffL) 206 | } 207 | } 208 | } 209 | } --------------------------------------------------------------------------------